- 后端 internal/service 按职责拆分: fulfillment.go(1397→527)拆出 wallet/timeout/data/order/query/dashboard/shipnotify delivery.go(1124→801)拆出 upstream/link/state/helpers merchant.go(855→251)拆出 member/product/api_client/catalog/helpers - 前端 MerchantCenter.tsx(1327→606)拆出 merchantCenterTabs/merchantCenterUtils - docker-compose backend 透传 CORS_ALLOWED_ORIGINS - CORS 白名单实现(config/router/README/.env.example 配套) - 修复 gofmt 与文件尾部多余空行
156 lines
4.7 KiB
Go
156 lines
4.7 KiB
Go
package service
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"affiliate_dash/internal/model"
|
|
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
type CreateMerchantProductInput struct {
|
|
ProductCode string
|
|
ProductName string
|
|
Category string
|
|
Description string
|
|
Attributes string
|
|
SKU string
|
|
DisplayName string
|
|
PriceAmount int64
|
|
CostAmount int64
|
|
Currency string
|
|
Stock int64
|
|
Status string
|
|
FulfillmentConfig string
|
|
}
|
|
|
|
func (s *MerchantService) CreateMerchantProduct(merchantID uint, in CreateMerchantProductInput, actorUserID uint) (*model.MerchantProduct, error) {
|
|
in.SKU = strings.TrimSpace(in.SKU)
|
|
in.ProductCode = strings.TrimSpace(in.ProductCode)
|
|
in.ProductName = strings.TrimSpace(in.ProductName)
|
|
if in.SKU == "" {
|
|
return nil, errors.New("商户商品 SKU 不能为空")
|
|
}
|
|
if in.PriceAmount < 0 || in.CostAmount < 0 {
|
|
return nil, errors.New("商品金额不能小于零")
|
|
}
|
|
if in.Stock < -1 {
|
|
return nil, errors.New("库存只能为 -1 或非负整数")
|
|
}
|
|
if in.Currency == "" {
|
|
in.Currency = "POINT"
|
|
}
|
|
in.Currency = strings.ToUpper(in.Currency)
|
|
if in.Status == "" {
|
|
in.Status = model.ProductStatusActive
|
|
}
|
|
if in.Status != model.ProductStatusActive && in.Status != model.ProductStatusInactive {
|
|
return nil, errors.New("无效的商品状态")
|
|
}
|
|
|
|
merchantProduct := &model.MerchantProduct{}
|
|
err := s.db.Transaction(func(tx *gorm.DB) error {
|
|
var merchant model.Merchant
|
|
if err := tx.Where("id = ? AND status = ?", merchantID, model.MerchantStatusActive).First(&merchant).Error; err != nil {
|
|
return errors.New("商户不存在或已禁用")
|
|
}
|
|
product, err := ensureProduct(tx, in)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
merchantProduct = &model.MerchantProduct{
|
|
MerchantID: merchantID,
|
|
ProductID: product.ID,
|
|
SKU: in.SKU,
|
|
DisplayName: fallbackName(in.DisplayName, product.Name),
|
|
PriceAmount: in.PriceAmount,
|
|
CostAmount: in.CostAmount,
|
|
Currency: in.Currency,
|
|
Stock: in.Stock,
|
|
Status: in.Status,
|
|
FulfillmentConfig: in.FulfillmentConfig,
|
|
}
|
|
if err := tx.Create(merchantProduct).Error; err != nil {
|
|
return err
|
|
}
|
|
return writeAudit(tx, &merchantID, &actorUserID, nil, "merchant_product.create", "merchant_product", fmt.Sprint(merchantProduct.ID), map[string]string{"sku": in.SKU})
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return merchantProduct, nil
|
|
}
|
|
|
|
func (s *MerchantService) ListMerchantProducts(merchantID uint, page, size int, activeOnly bool) ([]model.MerchantProduct, int64, error) {
|
|
page, size = normalizePage(page, size)
|
|
tx := s.db.Model(&model.MerchantProduct{}).Where("merchant_id = ?", merchantID)
|
|
if activeOnly {
|
|
tx = tx.Where("status = ?", model.ProductStatusActive)
|
|
}
|
|
var total int64
|
|
if err := tx.Count(&total).Error; err != nil {
|
|
return nil, 0, err
|
|
}
|
|
var products []model.MerchantProduct
|
|
err := tx.Preload("Product").Order("id DESC").Offset((page - 1) * size).Limit(size).Find(&products).Error
|
|
return products, total, err
|
|
}
|
|
|
|
type UpdateMerchantProductInput struct {
|
|
DisplayName *string
|
|
PriceAmount *int64
|
|
CostAmount *int64
|
|
Stock *int64
|
|
Status *string
|
|
FulfillmentConfig *string
|
|
}
|
|
|
|
func (s *MerchantService) UpdateMerchantProduct(merchantID, id uint, in UpdateMerchantProductInput, actorUserID uint) error {
|
|
updates := make(map[string]interface{})
|
|
if in.DisplayName != nil {
|
|
updates["display_name"] = *in.DisplayName
|
|
}
|
|
if in.PriceAmount != nil {
|
|
if *in.PriceAmount < 0 {
|
|
return errors.New("商品售价不能小于零")
|
|
}
|
|
updates["price_amount"] = *in.PriceAmount
|
|
}
|
|
if in.CostAmount != nil {
|
|
if *in.CostAmount < 0 {
|
|
return errors.New("商品成本不能小于零")
|
|
}
|
|
updates["cost_amount"] = *in.CostAmount
|
|
}
|
|
if in.Stock != nil {
|
|
if *in.Stock < -1 {
|
|
return errors.New("库存只能为 -1 或非负整数")
|
|
}
|
|
updates["stock"] = *in.Stock
|
|
}
|
|
if in.Status != nil {
|
|
if *in.Status != model.ProductStatusActive && *in.Status != model.ProductStatusInactive {
|
|
return errors.New("无效的商品状态")
|
|
}
|
|
updates["status"] = *in.Status
|
|
}
|
|
if in.FulfillmentConfig != nil {
|
|
updates["fulfillment_config"] = *in.FulfillmentConfig
|
|
}
|
|
if len(updates) == 0 {
|
|
return errors.New("没有可更新字段")
|
|
}
|
|
return s.db.Transaction(func(tx *gorm.DB) error {
|
|
result := tx.Model(&model.MerchantProduct{}).Where("id = ? AND merchant_id = ?", id, merchantID).Updates(updates)
|
|
if result.Error != nil {
|
|
return result.Error
|
|
}
|
|
if result.RowsAffected == 0 {
|
|
return errors.New("商户商品不存在")
|
|
}
|
|
return writeAudit(tx, &merchantID, &actorUserID, nil, "merchant_product.update", "merchant_product", fmt.Sprint(id), nil)
|
|
})
|
|
}
|