fix: 采购价格上限按数量计算总价 (#181)

This commit is contained in:
chengma
2026-08-12 14:43:18 +08:00
parent 8c7be7bd07
commit 253be7cb61
24 changed files with 203 additions and 106 deletions
+36 -16
View File
@@ -3,6 +3,7 @@ package service
import (
"database/sql"
"errors"
"math"
"strings"
"testing"
"time"
@@ -143,7 +144,8 @@ func TestSybMapping_动态维度保存并复用(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if list.Total != 1 || !list.Rows[0].CanPurchase || list.Rows[0].DefaultMaxPrice != "39.90" {
if list.Total != 1 || !list.Rows[0].CanPurchase || list.Rows[0].DefaultUnitPrice != "39.90" ||
list.Rows[0].DefaultTotalPrice != "39.90" {
t.Fatalf("可采购阶段不对: %+v", list)
}
row := list.Rows[0]
@@ -300,6 +302,9 @@ func stringsReplaceGoodsID(raw string) string {
func TestCreatePurchaseTasks_安全字段和重复保护(t *testing.T) {
db := newTestDB(t)
key := seedPurchasableWorkflow(t, db, "SYB-1")
if _, err := db.Exec(`UPDATE syb_orders SET quantity=2 WHERE syb_id='SYB-1'`); err != nil {
t.Fatal(err)
}
if err := SaveSybMapping(db, "SYB-1", key, "USR-1"); err != nil {
t.Fatal(err)
}
@@ -309,7 +314,7 @@ func TestCreatePurchaseTasks_安全字段和重复保护(t *testing.T) {
}
result, err := CreatePurchaseTasks(db, admin,
[]PurchaseTaskRequest{{SybID: "SYB-1", MaxPriceCent: 4200}}, "CLIENT-1")
[]PurchaseTaskRequest{{SybID: "SYB-1", UnitPriceLimitCent: 4200}}, "CLIENT-1")
if err != nil || result.Created != 1 || len(result.Failures) != 0 {
t.Fatalf("创建结果=%+v err=%v", result, err)
}
@@ -324,18 +329,18 @@ func TestCreatePurchaseTasks_安全字段和重复保护(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if taskID != "cg1" || status != "assigned" || task.MaxPriceCent != 4200 || task.MaxPriceCent == 999900 ||
task.Quantity != 1 || task.PddOptions != key || task.AssignedClient != "CLIENT-1" {
if taskID != "cg1" || status != "assigned" || task.MaxPriceCent != 8400 || task.MaxPriceCent == 999900 ||
task.Quantity != 2 || task.PddOptions != key || task.AssignedClient != "CLIENT-1" {
t.Fatalf("采购任务字段不安全或不完整: status=%s task=%+v", status, task)
}
claimed, err := repository.ClaimNextTask(db, "CLIENT-1", []string{"purchase"})
if err != nil || claimed == nil || claimed.PddOptions != key ||
claimed.Quantity != 1 || claimed.MaxPriceCent != 4200 {
claimed.Quantity != 2 || claimed.MaxPriceCent != 8400 {
t.Fatalf("现有 Client 领取契约读不到完整采购参数: task=%+v err=%v", claimed, err)
}
second, err := CreatePurchaseTasks(db, admin,
[]PurchaseTaskRequest{{SybID: "SYB-1", MaxPriceCent: 4300}}, "CLIENT-1")
[]PurchaseTaskRequest{{SybID: "SYB-1", UnitPriceLimitCent: 4300}}, "CLIENT-1")
if err != nil || second.Created != 0 || len(second.Failures) != 1 {
t.Fatalf("重复提交应逐条拒绝: result=%+v err=%v", second, err)
}
@@ -363,7 +368,7 @@ func TestCreatePurchaseTasks_成功或待核对任务禁止重复采购(t *testi
t.Fatal(err)
}
created, err := CreatePurchaseTasks(db, admin,
[]PurchaseTaskRequest{{SybID: "SYB-LOCKED", MaxPriceCent: 4200}}, "CLIENT-1")
[]PurchaseTaskRequest{{SybID: "SYB-LOCKED", UnitPriceLimitCent: 4200}}, "CLIENT-1")
if err != nil || created.Created != 1 {
t.Fatalf("准备采购任务失败: result=%+v err=%v", created, err)
}
@@ -373,7 +378,7 @@ func TestCreatePurchaseTasks_成功或待核对任务禁止重复采购(t *testi
before := countPurchaseTasksForSyb(t, db, "SYB-LOCKED")
result, err := CreatePurchaseTasks(db, admin,
[]PurchaseTaskRequest{{SybID: "SYB-LOCKED", MaxPriceCent: 4300}}, "CLIENT-1")
[]PurchaseTaskRequest{{SybID: "SYB-LOCKED", UnitPriceLimitCent: 4300}}, "CLIENT-1")
if err != nil || result.Created != 0 || len(result.Failures) != 1 ||
!strings.Contains(result.Failures[0].Reason, tc.wantReason) {
t.Fatalf("%s 任务必须阻止重复采购: result=%+v err=%v", tc.status, result, err)
@@ -408,11 +413,11 @@ func TestCreatePurchaseTasks_客户端权限和逐条失败(t *testing.T) {
AssignClient(db, admin, "CLIENT-A", buyerA.UserID, time.Now())
if _, err := CreatePurchaseTasks(db, buyerB,
[]PurchaseTaskRequest{{SybID: "SYB-1", MaxPriceCent: 4200}}, "CLIENT-A"); err == nil {
[]PurchaseTaskRequest{{SybID: "SYB-1", UnitPriceLimitCent: 4200}}, "CLIENT-A"); err == nil {
t.Fatal("采购员不得使用其他采购员的客户端")
}
result, err := CreatePurchaseTasks(db, buyerA,
[]PurchaseTaskRequest{{SybID: "SYB-1", MaxPriceCent: 0}, {SybID: "NOT-FOUND", MaxPriceCent: 100}}, "CLIENT-A")
[]PurchaseTaskRequest{{SybID: "SYB-1", UnitPriceLimitCent: 0}, {SybID: "NOT-FOUND", UnitPriceLimitCent: 100}}, "CLIENT-A")
if err != nil || result.Created != 0 || len(result.Failures) != 2 {
t.Fatalf("业务失败应逐条返回且不创建: result=%+v err=%v", result, err)
}
@@ -425,8 +430,8 @@ func TestCreatePurchaseTasks_部分业务失败不影响有效明细(t *testing.
admin, _, _ := prepareClientAssignmentUsers(t, db)
RegisterClient(db, model.Client{ClientID: "CLIENT-1"}, true)
result, err := CreatePurchaseTasks(db, admin, []PurchaseTaskRequest{
{SybID: "NOT-FOUND", MaxPriceCent: 100},
{SybID: "SYB-1", MaxPriceCent: 4200},
{SybID: "NOT-FOUND", UnitPriceLimitCent: 100},
{SybID: "SYB-1", UnitPriceLimitCent: 4200},
}, "CLIENT-1")
if err != nil || result.Created != 1 || len(result.Failures) != 1 || result.Failures[0].SybID != "NOT-FOUND" {
t.Fatalf("部分失败边界不对: result=%+v err=%v", result, err)
@@ -446,7 +451,7 @@ func TestCreatePurchaseTasksWithOptions_正常采购员可创建真实任务(t *
}
result, err := CreatePurchaseTasksWithOptions(db, buyer,
[]PurchaseTaskRequest{{SybID: "SYB-LIVE", MaxPriceCent: 4200}},
[]PurchaseTaskRequest{{SybID: "SYB-LIVE", UnitPriceLimitCent: 4200}},
PurchaseTaskOptions{ClientID: "CLIENT-LIVE", ExecutionMode: model.TaskExecutionLive})
if err != nil || result.Created != 1 {
t.Fatalf("正常采购员创建真实任务失败: result=%+v err=%v", result, err)
@@ -470,7 +475,7 @@ func TestCreatePurchaseTasksWithOptions_真实模式不要求Client预先声明L
SaveSybMapping(db, "SYB-GATE", key, "USR-1")
admin, _, _ := prepareClientAssignmentUsers(t, db)
RegisterClient(db, model.Client{ClientID: "CLIENT-DRY", Capabilities: `{"purchase_mode":"dry_run"}`}, true)
request := []PurchaseTaskRequest{{SybID: "SYB-GATE", MaxPriceCent: 4200}}
request := []PurchaseTaskRequest{{SybID: "SYB-GATE", UnitPriceLimitCent: 4200}}
result, err := CreatePurchaseTasksWithOptions(db, admin, request, PurchaseTaskOptions{
ClientID: "CLIENT-DRY", ExecutionMode: model.TaskExecutionLive,
@@ -496,7 +501,7 @@ func TestCreatePurchaseTasksWithOptions_离线Live客户端可提前指派(t *te
}
result, err := CreatePurchaseTasksWithOptions(db, admin,
[]PurchaseTaskRequest{{SybID: "SYB-OFFLINE", MaxPriceCent: 4200}},
[]PurchaseTaskRequest{{SybID: "SYB-OFFLINE", UnitPriceLimitCent: 4200}},
PurchaseTaskOptions{ClientID: "CLIENT-OFFLINE", ExecutionMode: model.TaskExecutionLive})
if err != nil || result.Created != 1 {
t.Fatalf("离线 live 客户端应可提前指派: result=%+v err=%v", result, err)
@@ -518,7 +523,7 @@ func TestCreatePurchaseTasksWithOptions_拒绝弹窗打开后变化的规格映
RegisterClient(db, model.Client{ClientID: "CLIENT-LIVE", Capabilities: `{"purchase_mode":"live"}`}, true)
result, err := CreatePurchaseTasksWithOptions(db, admin,
[]PurchaseTaskRequest{{SybID: "SYB-STALE", MaxPriceCent: 3990,
[]PurchaseTaskRequest{{SybID: "SYB-STALE", UnitPriceLimitCent: 3990,
MappingOptionKey: blackKey, ContextVersion: oldVersion}},
PurchaseTaskOptions{ClientID: "CLIENT-LIVE", ExecutionMode: model.TaskExecutionLive})
if err != nil || result.Created != 0 || len(result.Failures) != 1 ||
@@ -540,3 +545,18 @@ func TestParsePriceYuanToCent_精确转换(t *testing.T) {
}
}
}
func TestCalculateOrderPriceLimitCent_按数量计算且拒绝溢出(t *testing.T) {
got, err := CalculateOrderPriceLimitCent(1180, 2)
if err != nil || got != 2360 {
t.Fatalf("1180 × 2 = %d, %v; want 2360", got, err)
}
for _, tc := range []struct {
unit int64
qty int
}{{0, 2}, {1180, 0}, {math.MaxInt64, 2}} {
if _, err := CalculateOrderPriceLimitCent(tc.unit, tc.qty); err == nil {
t.Fatalf("应拒绝 unit=%d qty=%d", tc.unit, tc.qty)
}
}
}