组织初始化接口 GET /api/auth/organizations 现在为每项授权公司附带
warehouse 摘要({id, name, status, location: {id, name, status} | null} | null),
结构与切换接口一致,供普通用户在公司切换器直接展示,无需放开管理员仓库配置接口。
- model:新增 WarehouseSummary/LocationSummary/OrganizationSummary,
OrganizationAccess 每项改为内嵌公司资料再加 warehouse 摘要。
- repository:新增 WarehouseRepository.Summary 复用切换接口的解析规则;
ListAccessible 逐公司补齐摘要,仓库或库位配置不唯一时报错而非随意取第一条,
无仓库、无库位或已停用如实返回,不回退旧公司数据。
- service:Switch 复用 List 已解析的摘要,响应格式保持不变。
- 测试:新增服务层 sqlmock 用例与仓储层 MySQL 集成用例,
覆盖启用唯一仓库加启用库位、无仓库、仓库停用三类。
127 lines
3.6 KiB
Go
127 lines
3.6 KiB
Go
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
|
|
}
|