refactor: 拆分 PartnerService 的 Create/Update,创建接口不再被脏 id 带偏
This commit is contained in:
parent
cbcd54b70c
commit
1b0cdde780
|
|
@ -300,7 +300,7 @@ func (h *Handler) CreatePartner(c *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
item.OrganizationID = orgID
|
item.OrganizationID = orgID
|
||||||
if err := h.partnerSvc.Save(&item); err != nil {
|
if err := h.partnerSvc.Create(&item); err != nil {
|
||||||
response.BadRequest(c, err.Error())
|
response.BadRequest(c, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -320,7 +320,7 @@ func (h *Handler) UpdatePartner(c *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
item.ID, item.OrganizationID = id, orgID
|
item.ID, item.OrganizationID = id, orgID
|
||||||
if err := h.partnerSvc.Save(&item); err != nil {
|
if err := h.partnerSvc.Update(&item); err != nil {
|
||||||
response.BadRequest(c, err.Error())
|
response.BadRequest(c, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
var errInvalidMasterData = errors.New("code and name are required")
|
var errInvalidMasterData = errors.New("code and name are required")
|
||||||
|
var errUpdateRequiresID = errors.New("id is required for update")
|
||||||
var ErrProtectedRole = repository.ErrProtectedRole
|
var ErrProtectedRole = repository.ErrProtectedRole
|
||||||
|
|
||||||
type OrganizationService struct {
|
type OrganizationService struct {
|
||||||
|
|
@ -149,7 +150,28 @@ func (s *PartnerService) List(orgID int64, role string) ([]model.BusinessPartner
|
||||||
}
|
}
|
||||||
return s.repo.List(orgID, role)
|
return s.repo.List(orgID, role)
|
||||||
}
|
}
|
||||||
func (s *PartnerService) Save(item *model.BusinessPartner) error {
|
// 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.PartnerCode = strings.TrimSpace(item.PartnerCode)
|
||||||
item.LegalName = strings.TrimSpace(item.LegalName)
|
item.LegalName = strings.TrimSpace(item.LegalName)
|
||||||
if item.PartnerCode == "" || item.LegalName == "" {
|
if item.PartnerCode == "" || item.LegalName == "" {
|
||||||
|
|
@ -164,10 +186,7 @@ func (s *PartnerService) Save(item *model.BusinessPartner) error {
|
||||||
if item.IsCustomer && item.CustomerStatus == "" {
|
if item.IsCustomer && item.CustomerStatus == "" {
|
||||||
item.CustomerStatus = "active"
|
item.CustomerStatus = "active"
|
||||||
}
|
}
|
||||||
if item.ID == 0 {
|
return nil
|
||||||
return s.repo.Create(item)
|
|
||||||
}
|
|
||||||
return s.repo.Update(item)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type WarehouseService struct {
|
type WarehouseService struct {
|
||||||
|
|
|
||||||
57
internal/service/admin_master_test.go
Normal file
57
internal/service/admin_master_test.go
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/DATA-DOG/go-sqlmock"
|
||||||
|
"github.com/wmms/wmms/internal/model"
|
||||||
|
"github.com/wmms/wmms/pkg/database"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 前端表单在「新增」时可能残留上次编辑过的 id(PartnerList.vue 的
|
||||||
|
// openCreate 只做 Object.assign、不删多余键)。创建必须无视这个 id,
|
||||||
|
// 否则会走成 UPDATE 把旧记录覆盖掉——这正是本次要修的缺陷。
|
||||||
|
func TestPartnerCreateInsertsEvenWhenItemCarriesStaleID(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.ExpectBegin()
|
||||||
|
mock.ExpectExec("INSERT INTO business_partners").WillReturnResult(sqlmock.NewResult(2, 1))
|
||||||
|
mock.ExpectExec("INSERT INTO organization_partners").WillReturnResult(sqlmock.NewResult(2, 1))
|
||||||
|
mock.ExpectCommit()
|
||||||
|
|
||||||
|
item := &model.BusinessPartner{ID: 1, PartnerCode: "SUP-002", LegalName: "新伙伴", IsSupplier: true}
|
||||||
|
if err := NewPartnerService().Create(item); err != nil {
|
||||||
|
t.Fatalf("Create: %v", err)
|
||||||
|
}
|
||||||
|
if err := mock.ExpectationsWereMet(); err != nil {
|
||||||
|
t.Fatalf("应当执行 INSERT,实际 SQL 不是插入: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新接口漏传 id 时必须报错,否则会静默写 0 号记录(什么都不改)而不自知。
|
||||||
|
func TestPartnerUpdateRequiresID(t *testing.T) {
|
||||||
|
err := NewPartnerService().Update(&model.BusinessPartner{PartnerCode: "SUP-001", LegalName: "伙伴", IsSupplier: true})
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("缺少 id 的更新应当被拒绝")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 拆方法不能把校验丢了。
|
||||||
|
func TestPartnerCreateStillValidatesName(t *testing.T) {
|
||||||
|
err := NewPartnerService().Create(&model.BusinessPartner{PartnerCode: "SUP-001", IsSupplier: true})
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("legal_name 为空应当被拒绝")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPartnerCreateStillRequiresARole(t *testing.T) {
|
||||||
|
err := NewPartnerService().Create(&model.BusinessPartner{PartnerCode: "SUP-001", LegalName: "伙伴"})
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("既非供应商也非客户时应当被拒绝")
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user