package service import ( "database/sql" "encoding/json" "errors" "github.com/wmms/wmms/internal/model" "github.com/wmms/wmms/internal/repository" "github.com/wmms/wmms/pkg/database" ) var ErrInvalidOrganizationID = errors.New("请选择有效的公司") var ErrOrganizationInaccessible = errors.New("当前公司不可访问") type OrganizationAccessService struct { repo *repository.OrganizationRepository } func NewOrganizationAccessService() *OrganizationAccessService { return &OrganizationAccessService{repo: repository.NewOrganizationRepository()} } // EffectiveRoles reloads the principal for every authenticated request. A JWT // authenticates identity; its role snapshot never grants current privileges. func (s *OrganizationAccessService) EffectiveRoles(userID int64) ([]string, error) { rows, err := database.DB.Query(`SELECT u.id, r.code FROM users u LEFT JOIN user_roles ur ON ur.user_id=u.id LEFT JOIN roles r ON r.id=ur.role_id AND r.enabled=1 WHERE u.id=? ORDER BY r.id`, userID) if err != nil { return nil, err } defer rows.Close() roles := []string{} found := false for rows.Next() { var id int64 var code sql.NullString if err := rows.Scan(&id, &code); err != nil { return nil, err } found = true if code.Valid { roles = append(roles, code.String) } } if err := rows.Err(); err != nil { return nil, err } if !found { return nil, sql.ErrNoRows } return roles, nil } func (s *OrganizationAccessService) List(userID int64) (*model.OrganizationAccess, error) { return s.repo.ListAccessible(userID) } func (s *OrganizationAccessService) Validate(userID, organizationID int64) error { if organizationID <= 0 { return ErrInvalidOrganizationID } allowed, err := s.repo.CanAccess(userID, organizationID) if err != nil { return err } if !allowed { return ErrOrganizationInaccessible } return nil } // 复用 model 中的仓库摘要结构,切换接口与组织初始化列表字段名、结构完全一致。 type LocationContext = model.LocationSummary type WarehouseContext = model.WarehouseSummary type OrganizationContext struct { OrganizationID int64 `json:"organization_id"` Organization model.Organization `json:"organization"` Warehouse *WarehouseContext `json:"warehouse"` } // Switch validates the target independently of the client-reported previous // context. It records an audit, without storing a server-wide selected company. func (s *OrganizationAccessService) Switch(userID, organizationID, previousID int64) (*OrganizationContext, error) { if organizationID <= 0 { return nil, ErrInvalidOrganizationID } // List 已为每家公司解析好仓库摘要,直接复用,避免重复查询。 access, err := s.List(userID) if err != nil { return nil, err } var target *model.OrganizationSummary for i := range access.Organizations { if access.Organizations[i].ID == organizationID { target = &access.Organizations[i] break } } if target == nil { return nil, ErrOrganizationInaccessible } result := &OrganizationContext{ OrganizationID: organizationID, Organization: target.Organization, Warehouse: target.Warehouse, } oldValue, err := json.Marshal(map[string]int64{"organization_id": previousID}) if err != nil { return nil, err } newValue, err := json.Marshal(map[string]int64{"organization_id": organizationID}) if err != nil { return nil, err } err = repository.NewOrganizationAuditRepository().Create(&model.OrganizationAuditLog{ ActorUserID: userID, OrganizationID: &organizationID, Action: "organization_context_switched", OldValue: oldValue, NewValue: newValue, }) if err != nil { return nil, err } return result, nil }