纯文件级移动,无行为变更,沿用既有的 fulfillment_<aspect>.go 命名约定: - fulfillment.go 结构体、构造函数、上游钩子、CanFulfill、orderCallbackData - fulfillment_create.go 下单类型定义、CreateManualOrder、CreateOrder - fulfillment_testorder.go 联调测试订单 - fulfillment_update.go 履约结果更新 - fulfillment_cancel.go 取消订单(含上游联动取消) 校验:拆分前后 19 个 func/type 声明完全一致(无缺失、无多出、无重复)。
92 lines
2.6 KiB
Go
92 lines
2.6 KiB
Go
package service
|
|
|
|
import (
|
|
"errors"
|
|
"time"
|
|
|
|
"affiliate_dash/internal/model"
|
|
"gorm.io/gorm"
|
|
"gorm.io/gorm/clause"
|
|
)
|
|
|
|
type FulfillmentUpdateInput struct {
|
|
MerchantID uint
|
|
APIClientID uint
|
|
OrderNo string
|
|
Status string
|
|
ProviderOrderNo string
|
|
FailureReason string
|
|
ResultData interface{}
|
|
}
|
|
|
|
func (s *FulfillmentService) UpdateFulfillment(in FulfillmentUpdateInput) (*model.FulfillmentOrder, error) {
|
|
switch in.Status {
|
|
case model.OrderStatusDelivering, model.OrderStatusDelivered, model.OrderStatusShipFailed:
|
|
default:
|
|
return nil, errors.New("无效的订单状态")
|
|
}
|
|
nextOrderStatus := in.Status
|
|
var resultData string
|
|
hasResultData := in.ResultData != nil
|
|
if in.ResultData != nil {
|
|
encoded, err := encodeFulfillmentResultData(in.ResultData)
|
|
if err != nil {
|
|
return nil, errors.New("发货结果无法序列化")
|
|
}
|
|
resultData = encoded
|
|
}
|
|
var out model.FulfillmentOrder
|
|
err := s.db.Transaction(func(tx *gorm.DB) error {
|
|
var order model.FulfillmentOrder
|
|
if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).
|
|
Where("merchant_id = ? AND order_no = ?", in.MerchantID, in.OrderNo).
|
|
First(&order).Error; err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return errors.New("订单不存在")
|
|
}
|
|
return err
|
|
}
|
|
if normalizeOrderStatus(&order) == model.OrderStatusDelivered && nextOrderStatus == model.OrderStatusDelivered {
|
|
out = order
|
|
return nil
|
|
}
|
|
if err := validateOrderStatusTransition(&order, nextOrderStatus, fulfillmentTransitionUpdate); err != nil {
|
|
return err
|
|
}
|
|
now := time.Now()
|
|
updates := map[string]interface{}{"order_status": nextOrderStatus}
|
|
if hasResultData {
|
|
updates["result_data"] = resultData
|
|
}
|
|
if in.ProviderOrderNo != "" {
|
|
updates["provider_order_no"] = in.ProviderOrderNo
|
|
}
|
|
switch nextOrderStatus {
|
|
case model.OrderStatusDelivered:
|
|
updates["delivered_at"] = now
|
|
updates["failure_reason"] = ""
|
|
case model.OrderStatusShipFailed:
|
|
updates["failure_reason"] = in.FailureReason
|
|
}
|
|
if err := tx.Model(&order).Updates(updates).Error; err != nil {
|
|
return err
|
|
}
|
|
if err := tx.First(&out, order.ID).Error; err != nil {
|
|
return err
|
|
}
|
|
if err := writeAudit(tx, &in.MerchantID, nil, &in.APIClientID, "fulfillment.update", "fulfillment_order", order.OrderNo, map[string]string{"status": nextOrderStatus}); err != nil {
|
|
return err
|
|
}
|
|
if s.callbacks != nil {
|
|
if err := s.callbacks.Enqueue(tx, in.MerchantID, "order.shipping.updated", orderCallbackData(&out)); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &out, nil
|
|
}
|