material_manage_system/internal/service/admin_master.go
2026-09-21 16:53:44 +08:00

322 lines
10 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package service
import (
"errors"
"strings"
"github.com/wmms/wmms/internal/model"
"github.com/wmms/wmms/internal/repository"
)
var errInvalidMasterData = errors.New("code and name are required")
var errUpdateRequiresID = errors.New("id is required for update")
var ErrProtectedRole = repository.ErrProtectedRole
type OrganizationService struct {
repo *repository.OrganizationRepository
}
func NewOrganizationService() *OrganizationService {
return &OrganizationService{repo: repository.NewOrganizationRepository()}
}
func (s *OrganizationService) List() ([]model.Organization, error) { return s.repo.List() }
func (s *OrganizationService) ListRoles() ([]model.Role, error) { return s.repo.ListRoles() }
func (s *OrganizationService) ListPermissions() ([]model.Permission, error) {
return s.repo.ListPermissions()
}
func (s *OrganizationService) UpdateRole(roleID int64, req *model.UpdateRoleRequest) error {
req.Name = strings.TrimSpace(req.Name)
if req.Name == "" {
return errors.New("role name is required")
}
return s.repo.UpdateRole(roleID, req.Name, req.Enabled, req.PermissionCodes)
}
func (s *OrganizationService) Create(item *model.Organization) error {
item.Code = strings.TrimSpace(item.Code)
item.Name = strings.TrimSpace(item.Name)
if item.Code == "" || item.Name == "" {
return errInvalidMasterData
}
applyDefaults(&item.Status, &item.DefaultTimezone)
return s.repo.Create(item)
}
func (s *OrganizationService) Update(item *model.Organization) error {
item.Name = strings.TrimSpace(item.Name)
if item.Name == "" {
return errInvalidMasterData
}
applyDefaults(&item.Status, &item.DefaultTimezone)
return s.repo.Update(item)
}
func applyDefaults(status, timezone *string) {
if *status == "" {
*status = "active"
}
if *timezone == "" {
*timezone = "Asia/Shanghai"
}
}
type MasterDataService struct {
repo *repository.MasterDataRepository
}
func NewMasterDataService() *MasterDataService {
return &MasterDataService{repo: repository.NewMasterDataRepository()}
}
func (s *MasterDataService) ListCategories(orgID int64) ([]model.MaterialCategory, error) {
return s.repo.ListCategories(orgID)
}
// CreateCategory 新建分类。请求体可能带着前端表单残留的 id,这里不读取 item.ID,
// repository 的 INSERT 也不写 id 列,所以脏 id 不会把新建变成覆盖。
func (s *MasterDataService) CreateCategory(item *model.MaterialCategory) error {
if err := prepareCategory(item); err != nil {
return err
}
return s.repo.CreateCategory(item)
}
// UpdateCategory 更新分类。id 来自路由参数,必须显式存在。
func (s *MasterDataService) UpdateCategory(item *model.MaterialCategory) error {
if item.ID == 0 {
return errUpdateRequiresID
}
if err := prepareCategory(item); err != nil {
return err
}
return s.repo.UpdateCategory(item)
}
// prepareCategory 是新建与更新共用的字段清理、校验和默认值补全。
func prepareCategory(item *model.MaterialCategory) error {
item.Code = strings.TrimSpace(item.Code)
item.Name = strings.TrimSpace(item.Name)
item.Description = strings.TrimSpace(item.Description)
if item.Code == "" || item.Name == "" {
return errInvalidMasterData
}
if item.Status == "" {
item.Status = "active"
}
return nil
}
func (s *MasterDataService) ListUnits(orgID int64) ([]model.Unit, error) {
return s.repo.ListUnits(orgID)
}
// CreateUnit 新建单位。同样不读取 item.ID,避免表单残留的脏 id 把新建变成覆盖。
func (s *MasterDataService) CreateUnit(item *model.Unit) error {
if err := prepareUnit(item); err != nil {
return err
}
return s.repo.CreateUnit(item)
}
// UpdateUnit 更新单位。id 来自路由参数,必须显式存在。
func (s *MasterDataService) UpdateUnit(item *model.Unit) error {
if item.ID == 0 {
return errUpdateRequiresID
}
if err := prepareUnit(item); err != nil {
return err
}
return s.repo.UpdateUnit(item)
}
// prepareUnit 是新建与更新共用的字段清理、校验和默认值补全。
func prepareUnit(item *model.Unit) error {
item.Code = strings.TrimSpace(item.Code)
item.Name = strings.TrimSpace(item.Name)
if item.Code == "" || item.Name == "" {
return errInvalidMasterData
}
if item.Status == "" {
item.Status = "active"
}
if item.PrecisionScale < 0 || item.PrecisionScale > 6 {
return errors.New("precision_scale must be between 0 and 6")
}
return nil
}
func (s *MasterDataService) ListCodeRules(orgID int64) ([]model.CodeRule, error) {
return s.repo.ListCodeRules(orgID)
}
// NextMaterialCode 预览新增物料时按小类规则自动生成的编码。这里只读不落库,
// 返回的编码只是候选值;真正占号发生在创建物料的事务里。
func (s *MasterDataService) NextMaterialCode(orgID, categoryID int64) (*model.NextMaterialCode, error) {
rule, err := s.repo.FindActiveCodeRule(orgID, categoryID)
if err != nil {
return nil, err
}
if rule == nil {
return &model.NextMaterialCode{Available: false}, nil
}
return &model.NextMaterialCode{Available: true, Code: rule.FormatCode(rule.NextSequence)}, nil
}
// CreateCodeRule 新建编码规则。同样不读取 item.ID,避免表单残留的脏 id 把新建变成覆盖。
func (s *MasterDataService) CreateCodeRule(item *model.CodeRule) error {
if err := prepareCodeRule(item); err != nil {
return err
}
return s.repo.CreateCodeRule(item)
}
// UpdateCodeRule 更新编码规则。id 来自路由参数,必须显式存在。
func (s *MasterDataService) UpdateCodeRule(item *model.CodeRule) error {
if item.ID == 0 {
return errUpdateRequiresID
}
if err := prepareCodeRule(item); err != nil {
return err
}
return s.repo.UpdateCodeRule(item)
}
// prepareCodeRule 是新建与更新共用的字段清理、校验和默认值补全。
func prepareCodeRule(item *model.CodeRule) error {
item.Prefix = strings.TrimSpace(item.Prefix)
if item.CategoryID <= 0 || item.Prefix == "" {
return errors.New("category_id and prefix are required")
}
if item.SequenceLength < 1 || item.SequenceLength > 12 {
return errors.New("sequence_length must be between 1 and 12")
}
if item.NextSequence <= 0 {
item.NextSequence = 1
}
if item.Status == "" {
item.Status = "active"
}
return nil
}
type PartnerService struct{ repo *repository.PartnerRepository }
func NewPartnerService() *PartnerService {
return &PartnerService{repo: repository.NewPartnerRepository()}
}
func (s *PartnerService) List(orgID int64, role string, limit, offset int) ([]model.BusinessPartner, int64, error) {
if role != "" && role != "supplier" && role != "customer" {
return nil, 0, errors.New("role must be supplier or customer")
}
return s.repo.List(orgID, role, limit, offset)
}
// Create 新建伙伴。请求体可能带着前端表单残留的 id,这里不读取 item.ID,
// repository 的 INSERT 也不写 id 列,所以脏 id 不会把新建变成覆盖。
func (s *PartnerService) Create(item *model.BusinessPartner) error {
if err := preparePartner(item); err != nil {
return err
}
return s.repo.Create(item)
}
// Update 更新伙伴。id 来自路由参数,必须显式存在。
func (s *PartnerService) Update(item *model.BusinessPartner) error {
if item.ID == 0 {
return errUpdateRequiresID
}
if err := preparePartner(item); err != nil {
return err
}
return s.repo.Update(item)
}
// preparePartner 是新建与更新共用的字段清理、校验和默认值补全。
func preparePartner(item *model.BusinessPartner) error {
item.PartnerCode = strings.TrimSpace(item.PartnerCode)
item.LegalName = strings.TrimSpace(item.LegalName)
if item.PartnerCode == "" || item.LegalName == "" {
return errors.New("partner_code and legal_name are required")
}
if !item.IsSupplier && !item.IsCustomer {
return errors.New("at least one partner role is required")
}
if item.IsSupplier && item.SupplierStatus == "" {
item.SupplierStatus = "active"
}
if item.IsCustomer && item.CustomerStatus == "" {
item.CustomerStatus = "active"
}
return nil
}
type WarehouseService struct {
repo *repository.WarehouseRepository
}
func NewWarehouseService() *WarehouseService {
return &WarehouseService{repo: repository.NewWarehouseRepository()}
}
func (s *WarehouseService) List(orgID int64) ([]model.Warehouse, error) {
return s.repo.List(orgID)
}
// Create 新建仓库。不读取 item.ID,脏 id 不会把新建变成覆盖。
func (s *WarehouseService) Create(item *model.Warehouse) error {
if err := prepareWarehouse(item); err != nil {
return err
}
return s.repo.Create(item)
}
// Update 更新仓库。id 来自路由参数,必须显式存在。
func (s *WarehouseService) Update(item *model.Warehouse) error {
if item.ID == 0 {
return errUpdateRequiresID
}
if err := prepareWarehouse(item); err != nil {
return err
}
return s.repo.Update(item)
}
// prepareWarehouse 是新建与更新共用的字段清理、校验和默认值补全。
func prepareWarehouse(item *model.Warehouse) error {
item.Code = strings.TrimSpace(item.Code)
item.Name = strings.TrimSpace(item.Name)
if item.Code == "" || item.Name == "" {
return errInvalidMasterData
}
if item.Status == "" {
item.Status = "active"
}
return nil
}
// CreateLocation 新建库位。orgID 用于校验仓库归属,item.ID 不参与。
func (s *WarehouseService) CreateLocation(item *model.WarehouseLocation, orgID int64) error {
if err := prepareLocation(item); err != nil {
return err
}
return s.repo.CreateLocation(item, orgID)
}
// UpdateLocation 更新库位。id 来自路由参数,必须显式存在。
func (s *WarehouseService) UpdateLocation(item *model.WarehouseLocation, orgID int64) error {
if item.ID == 0 {
return errUpdateRequiresID
}
if err := prepareLocation(item); err != nil {
return err
}
return s.repo.UpdateLocation(item, orgID)
}
// prepareLocation 是新建与更新共用的字段清理、校验和默认值补全。
func prepareLocation(item *model.WarehouseLocation) error {
item.Code = strings.TrimSpace(item.Code)
item.Name = strings.TrimSpace(item.Name)
if item.Code == "" || item.Name == "" || item.WarehouseID <= 0 {
return errInvalidMasterData
}
if item.Status == "" {
item.Status = "active"
}
return nil
}