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 } type LocationContext struct { ID int64 `json:"id"` Name string `json:"name"` Status string `json:"status"` } type WarehouseContext struct { ID int64 `json:"id"` Name string `json:"name"` Status string `json:"status"` Location *LocationContext `json:"location"` } 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 } access, err := s.List(userID) if err != nil { return nil, err } var target *model.Organization 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} warehouses, err := repository.NewWarehouseRepository().List(organizationID) if err != nil { return nil, err } if len(warehouses) > 1 { return nil, errors.New("公司仓库配置不唯一") } if len(warehouses) == 1 { w := warehouses[0] if len(w.Locations) > 1 { return nil, errors.New("公司库位配置不唯一") } result.Warehouse = &WarehouseContext{ID: w.ID, Name: w.Name, Status: w.Status} if len(w.Locations) == 1 { l := w.Locations[0] result.Warehouse.Location = &LocationContext{ID: l.ID, Name: l.Name, Status: l.Status} } } 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 }