material_manage_system/internal/service/organization_access.go
2026-10-09 09:56:21 +08:00

147 lines
4.1 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
}
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
}