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
@@ -156,11 +156,12 @@ func mappingIsValid(context repository.SybOrderContext) bool {
}
// PurchaseTaskRequest 是一条采购任务的人工确认输入。价格单位固定为人民币分。
// UnitPriceLimitCent 是采购员确认的单价上限;订单总价上限必须在事务内按最新数量计算。
type PurchaseTaskRequest struct {
SybID string
MaxPriceCent int64
MappingOptionKey string
ContextVersion string
SybID string
UnitPriceLimitCent int64
MappingOptionKey string
ContextVersion string
}
// PurchaseTaskOptions 是一次批量创建共用的执行门禁。
@@ -240,6 +241,11 @@ func CreatePurchaseTasksWithOptions(db *sql.DB, actor *model.User, requests []Pu
result.Failures = append(result.Failures, TaskCreateError{SybID: request.SybID, Reason: reason})
continue
}
totalPriceLimitCent, err := CalculateOrderPriceLimitCent(request.UnitPriceLimitCent, context.Order.Quantity)
if err != nil {
result.Failures = append(result.Failures, TaskCreateError{SybID: request.SybID, Reason: err.Error()})
continue
}
taskID, err := repository.NextTaskID(tx, model.TaskPurchase)
if err != nil {
return PurchaseTaskResult{}, err
@@ -252,7 +258,7 @@ func CreatePurchaseTasksWithOptions(db *sql.DB, actor *model.User, requests []Pu
GoodsID: context.Order.ShopeeGoodsID,
PddGoodsURL: context.PddGoodsURL, PddGoodsID: context.PddGoodsID,
PddOptions: optionsJSON, Quantity: context.Order.Quantity,
MaxPriceCent: request.MaxPriceCent,
MaxPriceCent: totalPriceLimitCent,
}); err != nil {
return PurchaseTaskResult{}, err
}
@@ -306,8 +312,8 @@ func validatePurchaseRequest(q repository.Execer, request PurchaseTaskRequest) (
if context.Order.Quantity <= 0 {
return "采购数量必须大于 0", context, "", nil
}
if request.MaxPriceCent <= 0 {
return "人民币价格上限必须大于 0", context, "", nil
if request.UnitPriceLimitCent <= 0 {
return "人民币单价上限必须大于 0", context, "", nil
}
active, err := repository.HasActivePurchaseTask(q, request.SybID)
if err != nil {
@@ -319,28 +325,42 @@ func validatePurchaseRequest(q repository.Execer, request PurchaseTaskRequest) (
return "", context, choice.OptionsJSON, nil
}
// parsePriceYuanToCent 把网页输入的人民币元精确转成分,不使用浮点数。
// CalculateOrderPriceLimitCent 用整数分计算订单总价上限,避免浮点误差和乘法溢出。
func CalculateOrderPriceLimitCent(unitPriceLimitCent int64, quantity int) (int64, error) {
if unitPriceLimitCent <= 0 {
return 0, fmt.Errorf("人民币单价上限必须大于 0")
}
if quantity <= 0 {
return 0, fmt.Errorf("采购数量必须大于 0")
}
if unitPriceLimitCent > math.MaxInt64/int64(quantity) {
return 0, fmt.Errorf("订单总价上限过大")
}
return unitPriceLimitCent * int64(quantity), nil
}
// ParsePriceYuanToCent 把网页输入的人民币单价上限精确转成分,不使用浮点数。
func ParsePriceYuanToCent(raw string) (int64, error) {
raw = strings.TrimSpace(raw)
if raw == "" {
return 0, fmt.Errorf("价格上限不能为空")
return 0, fmt.Errorf("人民币单价上限不能为空")
}
var whole, fraction string
parts := strings.Split(raw, ".")
if len(parts) > 2 {
return 0, fmt.Errorf("价格格式不正确")
return 0, fmt.Errorf("单价格式不正确")
}
whole = parts[0]
if len(parts) == 2 {
fraction = parts[1]
}
if whole == "" || len(fraction) > 2 {
return 0, fmt.Errorf("价格最多保留两位小数")
return 0, fmt.Errorf("单价最多保留两位小数")
}
for _, value := range []string{whole, fraction} {
for _, r := range value {
if r < '0' || r > '9' {
return 0, fmt.Errorf("价格只能填写数字")
return 0, fmt.Errorf("单价只能填写数字")
}
}
}
@@ -351,17 +371,17 @@ func ParsePriceYuanToCent(raw string) (int64, error) {
}
var yuan, cent int64
if _, err := fmt.Sscan(whole, &yuan); err != nil {
return 0, fmt.Errorf("价格格式不正确")
return 0, fmt.Errorf("单价格式不正确")
}
if _, err := fmt.Sscan(fraction, &cent); err != nil {
return 0, fmt.Errorf("价格格式不正确")
return 0, fmt.Errorf("单价格式不正确")
}
if yuan > (math.MaxInt64-cent)/100 {
return 0, fmt.Errorf("价格上限过大")
return 0, fmt.Errorf("人民币单价上限过大")
}
total := yuan*100 + cent
if total <= 0 {
return 0, fmt.Errorf("价格上限必须大于 0")
return 0, fmt.Errorf("人民币单价上限必须大于 0")
}
return total, nil
}