refactor: 拆分 MasterDataService 的 Create/Update,分类与单位创建不再受脏 id 影响

This commit is contained in:
文睿 张 2026-09-21 15:03:57 +08:00
parent 1b0cdde780
commit 16ca9c02ba
3 changed files with 116 additions and 21 deletions

View File

@ -133,7 +133,7 @@ func (h *Handler) CreateMaterialCategory(c *gin.Context) {
return return
} }
item.OrganizationID = orgID item.OrganizationID = orgID
if err := h.masterDataSvc.SaveCategory(&item); err != nil { if err := h.masterDataSvc.CreateCategory(&item); err != nil {
response.BadRequest(c, err.Error()) response.BadRequest(c, err.Error())
return return
} }
@ -153,7 +153,7 @@ func (h *Handler) UpdateMaterialCategory(c *gin.Context) {
return return
} }
item.ID, item.OrganizationID = id, orgID item.ID, item.OrganizationID = id, orgID
if err := h.masterDataSvc.SaveCategory(&item); err != nil { if err := h.masterDataSvc.UpdateCategory(&item); err != nil {
response.BadRequest(c, err.Error()) response.BadRequest(c, err.Error())
return return
} }
@ -182,7 +182,7 @@ func (h *Handler) CreateUnit(c *gin.Context) {
return return
} }
item.OrganizationID = orgID item.OrganizationID = orgID
if err := h.masterDataSvc.SaveUnit(&item); err != nil { if err := h.masterDataSvc.CreateUnit(&item); err != nil {
response.BadRequest(c, err.Error()) response.BadRequest(c, err.Error())
return return
} }
@ -202,7 +202,7 @@ func (h *Handler) UpdateUnit(c *gin.Context) {
return return
} }
item.ID, item.OrganizationID = id, orgID item.ID, item.OrganizationID = id, orgID
if err := h.masterDataSvc.SaveUnit(&item); err != nil { if err := h.masterDataSvc.UpdateUnit(&item); err != nil {
response.BadRequest(c, err.Error()) response.BadRequest(c, err.Error())
return return
} }
@ -251,7 +251,7 @@ func (h *Handler) CreateCodeRule(c *gin.Context) {
return return
} }
item.OrganizationID = orgID item.OrganizationID = orgID
if err := h.masterDataSvc.SaveCodeRule(&item); err != nil { if err := h.masterDataSvc.CreateCodeRule(&item); err != nil {
response.BadRequest(c, err.Error()) response.BadRequest(c, err.Error())
return return
} }
@ -271,7 +271,7 @@ func (h *Handler) UpdateCodeRule(c *gin.Context) {
return return
} }
item.ID, item.OrganizationID = id, orgID item.ID, item.OrganizationID = id, orgID
if err := h.masterDataSvc.SaveCodeRule(&item); err != nil { if err := h.masterDataSvc.UpdateCodeRule(&item); err != nil {
response.BadRequest(c, err.Error()) response.BadRequest(c, err.Error())
return return
} }

View File

@ -68,7 +68,28 @@ func NewMasterDataService() *MasterDataService {
func (s *MasterDataService) ListCategories(orgID int64) ([]model.MaterialCategory, error) { func (s *MasterDataService) ListCategories(orgID int64) ([]model.MaterialCategory, error) {
return s.repo.ListCategories(orgID) return s.repo.ListCategories(orgID)
} }
func (s *MasterDataService) SaveCategory(item *model.MaterialCategory) error { // 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.Code = strings.TrimSpace(item.Code)
item.Name = strings.TrimSpace(item.Name) item.Name = strings.TrimSpace(item.Name)
item.Description = strings.TrimSpace(item.Description) item.Description = strings.TrimSpace(item.Description)
@ -78,15 +99,32 @@ func (s *MasterDataService) SaveCategory(item *model.MaterialCategory) error {
if item.Status == "" { if item.Status == "" {
item.Status = "active" item.Status = "active"
} }
if item.ID == 0 { return nil
return s.repo.CreateCategory(item)
}
return s.repo.UpdateCategory(item)
} }
func (s *MasterDataService) ListUnits(orgID int64) ([]model.Unit, error) { func (s *MasterDataService) ListUnits(orgID int64) ([]model.Unit, error) {
return s.repo.ListUnits(orgID) return s.repo.ListUnits(orgID)
} }
func (s *MasterDataService) SaveUnit(item *model.Unit) error { // 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.Code = strings.TrimSpace(item.Code)
item.Name = strings.TrimSpace(item.Name) item.Name = strings.TrimSpace(item.Name)
if item.Code == "" || item.Name == "" { if item.Code == "" || item.Name == "" {
@ -98,10 +136,7 @@ func (s *MasterDataService) SaveUnit(item *model.Unit) error {
if item.PrecisionScale < 0 || item.PrecisionScale > 6 { if item.PrecisionScale < 0 || item.PrecisionScale > 6 {
return errors.New("precision_scale must be between 0 and 6") return errors.New("precision_scale must be between 0 and 6")
} }
if item.ID == 0 { return nil
return s.repo.CreateUnit(item)
}
return s.repo.UpdateUnit(item)
} }
func (s *MasterDataService) ListCodeRules(orgID int64) ([]model.CodeRule, error) { func (s *MasterDataService) ListCodeRules(orgID int64) ([]model.CodeRule, error) {
return s.repo.ListCodeRules(orgID) return s.repo.ListCodeRules(orgID)
@ -119,7 +154,27 @@ func (s *MasterDataService) NextMaterialCode(orgID, categoryID int64) (*model.Ne
} }
return &model.NextMaterialCode{Available: true, Code: rule.FormatCode(rule.NextSequence)}, nil return &model.NextMaterialCode{Available: true, Code: rule.FormatCode(rule.NextSequence)}, nil
} }
func (s *MasterDataService) SaveCodeRule(item *model.CodeRule) error { // 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) item.Prefix = strings.TrimSpace(item.Prefix)
if item.CategoryID <= 0 || item.Prefix == "" { if item.CategoryID <= 0 || item.Prefix == "" {
return errors.New("category_id and prefix are required") return errors.New("category_id and prefix are required")
@ -133,10 +188,7 @@ func (s *MasterDataService) SaveCodeRule(item *model.CodeRule) error {
if item.Status == "" { if item.Status == "" {
item.Status = "active" item.Status = "active"
} }
if item.ID == 0 { return nil
return s.repo.CreateCodeRule(item)
}
return s.repo.UpdateCodeRule(item)
} }
type PartnerService struct{ repo *repository.PartnerRepository } type PartnerService struct{ repo *repository.PartnerRepository }

View File

@ -55,3 +55,46 @@ func TestPartnerCreateStillRequiresARole(t *testing.T) {
t.Fatal("既非供应商也非客户时应当被拒绝") t.Fatal("既非供应商也非客户时应当被拒绝")
} }
} }
// MasterDataSettings.vue 的 openCreate 同样是 Object.assign 复用表单,
// 分类新增也会带上前一次编辑残留的 id,必须走 INSERT。
func TestCategoryCreateInsertsEvenWhenItemCarriesStaleID(t *testing.T) {
db, mock, err := sqlmock.New()
if err != nil {
t.Fatalf("sqlmock: %v", err)
}
database.DB = db
t.Cleanup(func() { database.DB = nil; db.Close() })
mock.ExpectExec("INSERT INTO material_categories").WillReturnResult(sqlmock.NewResult(9, 1))
item := &model.MaterialCategory{ID: 1, Code: "A99", Name: "新分类"}
if err := NewMasterDataService().CreateCategory(item); err != nil {
t.Fatalf("CreateCategory: %v", err)
}
if err := mock.ExpectationsWereMet(); err != nil {
t.Fatalf("应当执行 INSERT,实际 SQL 不是插入: %v", err)
}
}
func TestUnitUpdateRequiresID(t *testing.T) {
err := NewMasterDataService().UpdateUnit(&model.Unit{Code: "pcs", Name: "件"})
if err == nil {
t.Fatal("缺少 id 的更新应当被拒绝")
}
}
func TestCodeRuleUpdateRequiresID(t *testing.T) {
err := NewMasterDataService().UpdateCodeRule(&model.CodeRule{CategoryID: 1, Prefix: "A", SequenceLength: 4})
if err == nil {
t.Fatal("缺少 id 的更新应当被拒绝")
}
}
// 单位的小数位校验不能因为拆方法而丢失。
func TestUnitCreateStillValidatesPrecisionScale(t *testing.T) {
err := NewMasterDataService().CreateUnit(&model.Unit{Code: "kg", Name: "千克", PrecisionScale: 9})
if err == nil {
t.Fatal("precision_scale 超过 6 应当被拒绝")
}
}