修复配送订单创建方合同约束
This commit is contained in:
@@ -34,6 +34,11 @@ var (
|
||||
errBindingProductScrapped = errors.New("所选气瓶已报废,不能绑定到合同")
|
||||
errBindingProductOwner = errors.New("所选气瓶不属于该合同用户,请选择合同用户所属的气瓶")
|
||||
errBindingProductDuplicate = errors.New("所选气瓶已绑定到该合同,请勿重复绑定")
|
||||
errOrderCreatorUser = errors.New("创建方必须是配送合同的签约用户")
|
||||
errOrderCreatorGas = errors.New("创建方必须是配送合同的签约气站")
|
||||
errOrderCreatorNoDelivery = errors.New("所选合同未指定配送点,不能以配送点作为创建方")
|
||||
errOrderCreatorDelivery = errors.New("创建方必须是配送合同指定的配送点")
|
||||
errOrderCreatorStaff = errors.New("所选工作人员不属于配送合同的履约组织或账号未启用")
|
||||
)
|
||||
|
||||
// ListGasorderContract 按明确用途收窄合同候选;合同管理列表不带用途参数时保持全量查询。
|
||||
@@ -588,11 +593,16 @@ func CreateGasorderBasic(ctx *gin.Context) {
|
||||
if !contractEligibleForOrder(contract, now) {
|
||||
return errors.New("contract is not active")
|
||||
}
|
||||
if request.CreatorType == "user" && creatorID != contract.UserAccountID {
|
||||
return errors.New("user cannot create an order for another user")
|
||||
var creatorStaff *models.StaffAccount
|
||||
if request.CreatorType == "staff" {
|
||||
var staff models.StaffAccount
|
||||
if err := tx.Where("id = ? AND status = ?", creatorID, common.StatusEnable).First(&staff).Error; err != nil {
|
||||
return errOrderCreatorStaff
|
||||
}
|
||||
creatorStaff = &staff
|
||||
}
|
||||
if request.CreatorType == "gas" && creatorID != contract.GasBasicID {
|
||||
return errors.New("gas station does not own the contract")
|
||||
if err := gasorderCreatorValidationError(contract, request.CreatorType, creatorID, creatorStaff); err != nil {
|
||||
return err
|
||||
}
|
||||
var address models.UserAddress
|
||||
if err := tx.Where("identity = ? AND user_account_id = ?", request.UserAddressIdentity, contract.UserAccountID).First(&address).Error; err != nil {
|
||||
@@ -659,12 +669,60 @@ func CreateGasorderBasic(ctx *gin.Context) {
|
||||
common.RespondCreatedResource(ctx, order)
|
||||
return
|
||||
}
|
||||
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
|
||||
if isGasorderCreatorBusinessError(err) {
|
||||
infra.Response.Error(ctx, err)
|
||||
} else {
|
||||
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
|
||||
}
|
||||
return
|
||||
}
|
||||
common.RespondCreatedResource(ctx, order)
|
||||
}
|
||||
|
||||
// gasorderCreatorValidationError 校验平台代建订单中的业务创建方必须属于当前配送合同。
|
||||
// 参数:contract 为当前生效合同,creatorType/creatorID 为提交的创建方,staff 仅在工作人员类型时传入。
|
||||
// 返回值:组合合法返回 nil,否则返回可安全展示的中文业务错误。
|
||||
func gasorderCreatorValidationError(contract models.GasorderContract, creatorType string, creatorID uint64, staff *models.StaffAccount) error {
|
||||
switch creatorType {
|
||||
case "user":
|
||||
if creatorID != contract.UserAccountID {
|
||||
return errOrderCreatorUser
|
||||
}
|
||||
case "gas":
|
||||
if creatorID != contract.GasBasicID {
|
||||
return errOrderCreatorGas
|
||||
}
|
||||
case "delivery":
|
||||
if contract.DeliveryBasicID == 0 {
|
||||
return errOrderCreatorNoDelivery
|
||||
}
|
||||
if creatorID != contract.DeliveryBasicID {
|
||||
return errOrderCreatorDelivery
|
||||
}
|
||||
case "staff":
|
||||
if staff == nil || staff.ID != creatorID || staff.Status != common.StatusEnable || staff.GasBasicID != contract.GasBasicID {
|
||||
return errOrderCreatorStaff
|
||||
}
|
||||
if contract.DeliveryBasicID > 0 {
|
||||
if staff.DeliveryBasicID != contract.DeliveryBasicID {
|
||||
return errOrderCreatorStaff
|
||||
}
|
||||
} else if staff.DeliveryBasicID != 0 {
|
||||
return errOrderCreatorStaff
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// isGasorderCreatorBusinessError 限制仅透传预定义创建方错误,避免数据库细节泄露给前端。
|
||||
func isGasorderCreatorBusinessError(err error) bool {
|
||||
return errors.Is(err, errOrderCreatorUser) ||
|
||||
errors.Is(err, errOrderCreatorGas) ||
|
||||
errors.Is(err, errOrderCreatorNoDelivery) ||
|
||||
errors.Is(err, errOrderCreatorDelivery) ||
|
||||
errors.Is(err, errOrderCreatorStaff)
|
||||
}
|
||||
|
||||
// contractEligibleForOrder 校验合同当前确实处于可履约时间窗口。
|
||||
func contractEligibleForOrder(contract models.GasorderContract, now time.Time) bool {
|
||||
return contract.ContractStatus == common.StatusActive &&
|
||||
|
||||
@@ -110,6 +110,58 @@ func TestGasorderCreatorTypesCoverEveryConfirmedOrigin(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestGasorderCreatorValidationError 验证四类创建方均被严格限制在当前配送合同履约范围内。
|
||||
func TestGasorderCreatorValidationError(t *testing.T) {
|
||||
contract := models.GasorderContract{UserAccountID: 10, GasBasicID: 20, DeliveryBasicID: 30}
|
||||
validStaff := &models.StaffAccount{
|
||||
Entity: models.Entity{ID: 40, Status: common.StatusEnable},
|
||||
GasBasicID: 20, DeliveryBasicID: 30,
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
contract models.GasorderContract
|
||||
creatorType string
|
||||
creatorID uint64
|
||||
staff *models.StaffAccount
|
||||
want error
|
||||
}{
|
||||
{"合同用户", contract, "user", 10, nil, nil},
|
||||
{"跨合同用户", contract, "user", 11, nil, errOrderCreatorUser},
|
||||
{"合同气站", contract, "gas", 20, nil, nil},
|
||||
{"跨合同气站", contract, "gas", 21, nil, errOrderCreatorGas},
|
||||
{"合同配送点", contract, "delivery", 30, nil, nil},
|
||||
{"跨合同配送点", contract, "delivery", 31, nil, errOrderCreatorDelivery},
|
||||
{"合同未指定配送点", models.GasorderContract{UserAccountID: 10, GasBasicID: 20}, "delivery", 30, nil, errOrderCreatorNoDelivery},
|
||||
{"合同配送点工作人员", contract, "staff", 40, validStaff, nil},
|
||||
{"跨配送点工作人员", contract, "staff", 40, &models.StaffAccount{Entity: models.Entity{ID: 40, Status: common.StatusEnable}, GasBasicID: 20, DeliveryBasicID: 31}, errOrderCreatorStaff},
|
||||
{"停用工作人员", contract, "staff", 40, &models.StaffAccount{Entity: models.Entity{ID: 40, Status: common.StatusDisable}, GasBasicID: 20, DeliveryBasicID: 30}, errOrderCreatorStaff},
|
||||
{"气站直属工作人员", models.GasorderContract{UserAccountID: 10, GasBasicID: 20}, "staff", 40, &models.StaffAccount{Entity: models.Entity{ID: 40, Status: common.StatusEnable}, GasBasicID: 20}, nil},
|
||||
{"无配送点合同拒绝下属配送点人员", models.GasorderContract{UserAccountID: 10, GasBasicID: 20}, "staff", 40, validStaff, errOrderCreatorStaff},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
if got := gasorderCreatorValidationError(test.contract, test.creatorType, test.creatorID, test.staff); got != test.want {
|
||||
t.Fatalf("创建方校验结果 = %v,期望 %v", got, test.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestGasorderCreatorBusinessErrorsAreSafe 验证仅预定义中文创建方错误允许返回客户端。
|
||||
func TestGasorderCreatorBusinessErrorsAreSafe(t *testing.T) {
|
||||
for _, businessError := range []error{
|
||||
errOrderCreatorUser, errOrderCreatorGas, errOrderCreatorNoDelivery,
|
||||
errOrderCreatorDelivery, errOrderCreatorStaff,
|
||||
} {
|
||||
if !isGasorderCreatorBusinessError(businessError) {
|
||||
t.Fatalf("创建方业务错误未被识别:%v", businessError)
|
||||
}
|
||||
}
|
||||
if isGasorderCreatorBusinessError(gorm.ErrInvalidDB) {
|
||||
t.Fatal("数据库错误不应透传给客户端")
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnbindWritesIntegerGenericStatusAndReason(t *testing.T) {
|
||||
updates := gasorderUnbindUpdates(" contract ended ", time.Now())
|
||||
status, ok := updates["status"].(int)
|
||||
|
||||
Reference in New Issue
Block a user