Files
affiliate_dash/backend/internal/service/fulfillment_update.go
T
yml2213 5ed36c724f refactor(fulfillment): 按职责拆分 fulfillment.go(640 → 最大 235 行)
纯文件级移动,无行为变更,沿用既有的 fulfillment_<aspect>.go 命名约定:
- fulfillment.go            结构体、构造函数、上游钩子、CanFulfill、orderCallbackData
- fulfillment_create.go     下单类型定义、CreateManualOrder、CreateOrder
- fulfillment_testorder.go  联调测试订单
- fulfillment_update.go     履约结果更新
- fulfillment_cancel.go     取消订单(含上游联动取消)

校验:拆分前后 19 个 func/type 声明完全一致(无缺失、无多出、无重复)。
2026-10-06 19:50:13 +08:00

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
}