纯文件级移动,无行为变更,沿用既有的 fulfillment_<aspect>.go 命名约定: - fulfillment.go 结构体、构造函数、上游钩子、CanFulfill、orderCallbackData - fulfillment_create.go 下单类型定义、CreateManualOrder、CreateOrder - fulfillment_testorder.go 联调测试订单 - fulfillment_update.go 履约结果更新 - fulfillment_cancel.go 取消订单(含上游联动取消) 校验:拆分前后 19 个 func/type 声明完全一致(无缺失、无多出、无重复)。
132 lines
3.9 KiB
Go
132 lines
3.9 KiB
Go
package service
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"strings"
|
|
"time"
|
|
|
|
"affiliate_dash/internal/model"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
type CreateTestOrderInput struct {
|
|
MerchantID uint
|
|
ActorUserID uint
|
|
SKU string
|
|
BuyerReference string
|
|
Note string
|
|
OrderStatus string
|
|
}
|
|
|
|
// CreateTestOrder 创建后台联调订单:不扣钱包、不占库存,只用于上游按订单号查询与发货回传。
|
|
func (s *FulfillmentService) CreateTestOrder(in CreateTestOrderInput) (*model.FulfillmentOrder, error) {
|
|
in.SKU = strings.TrimSpace(in.SKU)
|
|
in.BuyerReference = strings.TrimSpace(in.BuyerReference)
|
|
in.Note = strings.TrimSpace(in.Note)
|
|
if in.MerchantID == 0 {
|
|
return nil, errors.New("无效的商户")
|
|
}
|
|
if in.SKU == "" {
|
|
return nil, errors.New("sku 不能为空")
|
|
}
|
|
if in.BuyerReference == "" {
|
|
in.BuyerReference = "测试买家"
|
|
}
|
|
if len(in.BuyerReference) > 128 {
|
|
return nil, errors.New("买家标识最长 128 位")
|
|
}
|
|
if len(in.Note) > 512 {
|
|
return nil, errors.New("备注最长 512 位")
|
|
}
|
|
if in.OrderStatus == "" {
|
|
in.OrderStatus = model.OrderStatusPaid
|
|
}
|
|
switch in.OrderStatus {
|
|
case model.OrderStatusPaid, model.OrderStatusShipFailed, model.OrderStatusCancelled:
|
|
default:
|
|
return nil, errors.New("测试订单仅支持已支付待发货、发货失败或已取消状态")
|
|
}
|
|
|
|
requestData := map[string]interface{}{
|
|
"source": "merchant_test_order",
|
|
}
|
|
if in.Note != "" {
|
|
requestData["note"] = in.Note
|
|
}
|
|
rawRequestData, err := json.Marshal(requestData)
|
|
if err != nil {
|
|
return nil, errors.New("订单请求数据无法序列化")
|
|
}
|
|
var out model.FulfillmentOrder
|
|
err = s.db.Transaction(func(tx *gorm.DB) error {
|
|
var merchant model.Merchant
|
|
if err := tx.Where("id = ? AND status = ?", in.MerchantID, model.MerchantStatusActive).First(&merchant).Error; err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return errors.New("商户不存在或已禁用")
|
|
}
|
|
return err
|
|
}
|
|
|
|
var product model.MerchantProduct
|
|
if err := tx.Preload("Product").
|
|
Where("merchant_id = ? AND sku = ? AND status = ?", in.MerchantID, in.SKU, model.ProductStatusActive).
|
|
First(&product).Error; err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return errors.New("商品不存在或已下架")
|
|
}
|
|
return err
|
|
}
|
|
if product.Product == nil || product.Product.Status != model.ProductStatusActive {
|
|
return errors.New("商品目录已下架")
|
|
}
|
|
|
|
orderNo := newTestFulfillmentOrderNo()
|
|
order := &model.FulfillmentOrder{
|
|
MerchantID: in.MerchantID,
|
|
OrderNo: orderNo,
|
|
ClientOrderNo: "TEST-" + orderNo,
|
|
MerchantProductID: product.ID,
|
|
ProductSKU: product.SKU,
|
|
ProductName: fallbackName(product.DisplayName, product.Product.Name),
|
|
Quantity: 1,
|
|
BaseAmount: 0,
|
|
FeeType: merchant.FeeType,
|
|
FeeRateBP: merchant.FeeRateBP,
|
|
FeeFixedAmount: merchant.FeeFixedAmount,
|
|
ServiceFeeAmount: 0,
|
|
Amount: 0,
|
|
Currency: product.Currency,
|
|
OrderStatus: in.OrderStatus,
|
|
OrderSource: model.OrderSourceTest,
|
|
BuyerReference: in.BuyerReference,
|
|
RequestData: string(rawRequestData),
|
|
}
|
|
if in.OrderStatus == model.OrderStatusShipFailed {
|
|
order.FailureReason = fallbackName(in.Note, "联调测试订单初始化为发货失败,可重新发货")
|
|
}
|
|
if in.OrderStatus == model.OrderStatusCancelled {
|
|
now := time.Now()
|
|
order.CancelledAt = &now
|
|
}
|
|
if err := tx.Create(order).Error; err != nil {
|
|
return err
|
|
}
|
|
canShip, _ := CanFulfill(order)
|
|
if err := writeAudit(tx, &in.MerchantID, &in.ActorUserID, nil, "merchant_test_order.create", "fulfillment_order", order.OrderNo, map[string]interface{}{
|
|
"sku": in.SKU,
|
|
"order_status": in.OrderStatus,
|
|
"can_ship": canShip,
|
|
}); err != nil {
|
|
return err
|
|
}
|
|
out = *order
|
|
out.MerchantProduct = &product
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &out, nil
|
|
}
|