修复配送合同候选范围与失效联动

This commit is contained in:
czl231
2026-08-13 23:38:29 +08:00
parent 029724a46c
commit 4d2acbaf46
10 changed files with 243 additions and 10 deletions

View File

@@ -36,8 +36,32 @@ var (
errBindingProductDuplicate = errors.New("所选气瓶已绑定到该合同,请勿重复绑定")
)
func ListGasorderContract(ctx *gin.Context) { common.ListResource(ctx, &models.GasorderContract{}) }
func GetGasorderContract(ctx *gin.Context) { getGasorderContract(ctx) }
// ListGasorderContract 按明确用途收窄合同候选;合同管理列表不带用途参数时保持全量查询。
func ListGasorderContract(ctx *gin.Context) {
candidate := strings.TrimSpace(ctx.Query("candidate"))
if candidate != "order" && candidate != "binding" {
common.ListResource(ctx, &models.GasorderContract{})
return
}
now := time.Now()
common.ListPageFiltered[models.GasorderContract](ctx, func(query *gorm.DB) *gorm.DB {
return filterGasorderContractCandidates(query, candidate, now)
})
}
// filterGasorderContractCandidates 应用订单履约或草稿绑定场景的合同候选条件。
func filterGasorderContractCandidates(query *gorm.DB, candidate string, now time.Time) *gorm.DB {
if candidate == "binding" {
return query.Where("contract_status = ?", common.StatusDraft)
}
return query.Where(
"contract_status = ? AND effective_at <= ? AND expired_at IS NOT NULL AND expired_at > ?",
common.StatusActive,
now,
now,
)
}
func GetGasorderContract(ctx *gin.Context) { getGasorderContract(ctx) }
func ListGasorderContractProduct(ctx *gin.Context) {
common.ListResource(ctx, &models.GasorderContractProduct{})
}
@@ -538,7 +562,7 @@ func CreateGasorderBasic(ctx *gin.Context) {
return err
}
now := time.Now()
if contract.ContractStatus != common.StatusActive || contract.EffectiveAt.After(now) || (contract.ExpiredAt != nil && !contract.ExpiredAt.After(now)) {
if !contractEligibleForOrder(contract, now) {
return errors.New("contract is not active")
}
if request.CreatorType == "user" && creatorID != contract.UserAccountID {
@@ -618,6 +642,14 @@ func CreateGasorderBasic(ctx *gin.Context) {
common.RespondCreatedResource(ctx, order)
}
// contractEligibleForOrder 校验合同当前确实处于可履约时间窗口。
func contractEligibleForOrder(contract models.GasorderContract, now time.Time) bool {
return contract.ContractStatus == common.StatusActive &&
!contract.EffectiveAt.After(now) &&
contract.ExpiredAt != nil &&
contract.ExpiredAt.After(now)
}
func AssignGasorderBasic(ctx *gin.Context) {
var request struct {
DeliveryIdentity string `json:"delivery_basic_identity" binding:"required"`

View File

@@ -8,8 +8,69 @@ import (
"git.apinb.com/heqiapp/platforms/backend/api/internal/logic/common"
"git.apinb.com/heqiapp/platforms/backend/api/internal/models"
"github.com/DATA-DOG/go-sqlmock"
"gorm.io/driver/postgres"
"gorm.io/gorm"
)
// TestContractEligibleForOrder 覆盖草稿、未生效、有效、到期、终止和缺少到期时间的合同。
func TestContractEligibleForOrder(t *testing.T) {
now := time.Date(2026, 8, 13, 12, 0, 0, 0, time.UTC)
past := now.Add(-time.Hour)
future := now.Add(time.Hour)
tests := []struct {
name string
contract models.GasorderContract
want bool
}{
{"草稿", models.GasorderContract{ContractStatus: common.StatusDraft, EffectiveAt: past, ExpiredAt: &future}, false},
{"未到生效时间", models.GasorderContract{ContractStatus: common.StatusActive, EffectiveAt: future, ExpiredAt: &future}, false},
{"当前有效", models.GasorderContract{ContractStatus: common.StatusActive, EffectiveAt: past, ExpiredAt: &future}, true},
{"已经到期", models.GasorderContract{ContractStatus: common.StatusActive, EffectiveAt: past, ExpiredAt: &now}, false},
{"已经终止", models.GasorderContract{ContractStatus: common.StatusTerminated, EffectiveAt: past, ExpiredAt: &future}, false},
{"缺少到期时间", models.GasorderContract{ContractStatus: common.StatusActive, EffectiveAt: past}, false},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
if got := contractEligibleForOrder(test.contract, now); got != test.want {
t.Fatalf("contractEligibleForOrder() = %v, want %v", got, test.want)
}
})
}
}
// TestFilterGasorderContractCandidates 验证订单候选和绑定候选使用不同的服务端过滤条件。
func TestFilterGasorderContractCandidates(t *testing.T) {
sqlDatabase, _, err := sqlmock.New()
if err != nil {
t.Fatalf("创建模拟数据库失败:%v", err)
}
defer sqlDatabase.Close()
database, err := gorm.Open(postgres.New(postgres.Config{Conn: sqlDatabase}), &gorm.Config{})
if err != nil {
t.Fatalf("创建 GORM 数据库失败:%v", err)
}
now := time.Date(2026, 8, 13, 12, 0, 0, 0, time.UTC)
statement := func(candidate string) string {
return database.ToSQL(func(tx *gorm.DB) *gorm.DB {
var contracts []models.GasorderContract
return filterGasorderContractCandidates(
tx.Model(&models.GasorderContract{}), candidate, now,
).Find(&contracts)
})
}
orderSQL := statement("order")
for _, fragment := range []string{"contract_status = 11", "effective_at <=", "expired_at IS NOT NULL", "expired_at >"} {
if !strings.Contains(orderSQL, fragment) {
t.Fatalf("订单合同候选缺少 %q%s", fragment, orderSQL)
}
}
bindingSQL := statement("binding")
if !strings.Contains(bindingSQL, "contract_status = 0") {
t.Fatalf("合同气瓶绑定候选未限定草稿:%s", bindingSQL)
}
}
func TestGasorderCreatorTypesCoverEveryConfirmedOrigin(t *testing.T) {
for _, creatorType := range []string{"user", "staff", "delivery", "gas"} {
if gasorderCreatorModels[creatorType] == nil {