商户管理优化, 商户只能上架下架, 管理员可以编辑
This commit is contained in:
@@ -52,73 +52,21 @@ func (h *MerchantHandler) ListProducts(c *gin.Context) {
|
||||
response.Page(c, list, total, page, size)
|
||||
}
|
||||
|
||||
type merchantProductReq struct {
|
||||
ProductCode string `json:"product_code"`
|
||||
ProductName string `json:"product_name"`
|
||||
Category string `json:"category"`
|
||||
Description string `json:"description"`
|
||||
Attributes string `json:"attributes"`
|
||||
SKU string `json:"sku" binding:"required"`
|
||||
DisplayName string `json:"display_name"`
|
||||
PriceAmount int64 `json:"price_amount"`
|
||||
CostAmount int64 `json:"cost_amount"`
|
||||
Currency string `json:"currency"`
|
||||
Stock int64 `json:"stock"`
|
||||
Status string `json:"status"`
|
||||
FulfillmentConfig string `json:"fulfillment_config"`
|
||||
type merchantProductStatusReq struct {
|
||||
Status string `json:"status" binding:"required"`
|
||||
}
|
||||
|
||||
func (h *MerchantHandler) CreateProduct(c *gin.Context) {
|
||||
var req merchantProductReq
|
||||
// UpdateProductStatus PATCH /api/merchant/products/:id/status
|
||||
// 商户仅能上架/下架商品,价格、成本等配置由平台管理员维护。
|
||||
func (h *MerchantHandler) UpdateProductStatus(c *gin.Context) {
|
||||
var req merchantProductStatusReq
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.BadRequest(c, "参数错误:sku 必填")
|
||||
return
|
||||
}
|
||||
product, err := h.merchantSvc.CreateMerchantProduct(middleware.GetMerchantID(c), service.CreateMerchantProductInput{
|
||||
ProductCode: req.ProductCode,
|
||||
ProductName: req.ProductName,
|
||||
Category: req.Category,
|
||||
Description: req.Description,
|
||||
Attributes: req.Attributes,
|
||||
SKU: req.SKU,
|
||||
DisplayName: req.DisplayName,
|
||||
PriceAmount: req.PriceAmount,
|
||||
CostAmount: req.CostAmount,
|
||||
Currency: req.Currency,
|
||||
Stock: req.Stock,
|
||||
Status: req.Status,
|
||||
FulfillmentConfig: req.FulfillmentConfig,
|
||||
}, middleware.GetUserID(c))
|
||||
if err != nil {
|
||||
response.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
response.OK(c, product)
|
||||
}
|
||||
|
||||
type merchantProductUpdateReq struct {
|
||||
DisplayName *string `json:"display_name"`
|
||||
PriceAmount *int64 `json:"price_amount"`
|
||||
CostAmount *int64 `json:"cost_amount"`
|
||||
Stock *int64 `json:"stock"`
|
||||
Status *string `json:"status"`
|
||||
FulfillmentConfig *string `json:"fulfillment_config"`
|
||||
}
|
||||
|
||||
func (h *MerchantHandler) UpdateProduct(c *gin.Context) {
|
||||
var req merchantProductUpdateReq
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.BadRequest(c, "参数错误")
|
||||
response.BadRequest(c, "参数错误:status 必填")
|
||||
return
|
||||
}
|
||||
id, _ := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
if err := h.merchantSvc.UpdateMerchantProduct(middleware.GetMerchantID(c), uint(id), service.UpdateMerchantProductInput{
|
||||
DisplayName: req.DisplayName,
|
||||
PriceAmount: req.PriceAmount,
|
||||
CostAmount: req.CostAmount,
|
||||
Stock: req.Stock,
|
||||
Status: req.Status,
|
||||
FulfillmentConfig: req.FulfillmentConfig,
|
||||
Status: &req.Status,
|
||||
}, middleware.GetUserID(c)); err != nil {
|
||||
response.BadRequest(c, err.Error())
|
||||
return
|
||||
@@ -453,6 +401,42 @@ func (h *MerchantHandler) ListMerchantProductsByAdmin(c *gin.Context) {
|
||||
response.OK(c, list)
|
||||
}
|
||||
|
||||
type merchantProductUpdateReq struct {
|
||||
DisplayName *string `json:"display_name"`
|
||||
PriceAmount *int64 `json:"price_amount"`
|
||||
CostAmount *int64 `json:"cost_amount"`
|
||||
Stock *int64 `json:"stock"`
|
||||
Status *string `json:"status"`
|
||||
FulfillmentConfig *string `json:"fulfillment_config"`
|
||||
}
|
||||
|
||||
// AdminUpdateMerchantProduct 供平台管理员编辑指定商户的商品(价格、成本、库存、状态等)。
|
||||
func (h *MerchantHandler) AdminUpdateMerchantProduct(c *gin.Context) {
|
||||
merchantID, _ := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
productID, _ := strconv.ParseUint(c.Param("pid"), 10, 64)
|
||||
if merchantID == 0 || productID == 0 {
|
||||
response.BadRequest(c, "参数错误")
|
||||
return
|
||||
}
|
||||
var req merchantProductUpdateReq
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.BadRequest(c, "参数错误")
|
||||
return
|
||||
}
|
||||
if err := h.merchantSvc.UpdateMerchantProduct(uint(merchantID), uint(productID), service.UpdateMerchantProductInput{
|
||||
DisplayName: req.DisplayName,
|
||||
PriceAmount: req.PriceAmount,
|
||||
CostAmount: req.CostAmount,
|
||||
Stock: req.Stock,
|
||||
Status: req.Status,
|
||||
FulfillmentConfig: req.FulfillmentConfig,
|
||||
}, middleware.GetUserID(c)); err != nil {
|
||||
response.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
response.OK(c, nil)
|
||||
}
|
||||
|
||||
type assignProductsReq struct {
|
||||
CatalogIDs []uint `json:"catalog_ids"`
|
||||
}
|
||||
|
||||
@@ -124,8 +124,7 @@ func Setup(h *Handlers) *gin.Engine {
|
||||
{
|
||||
merchant.GET("", h.Merchant.Current)
|
||||
merchant.GET("/products", middleware.RequireMerchantFeature(h.OpenDB, model.MerchantFeatureProducts), h.Merchant.ListProducts)
|
||||
merchant.POST("/products", middleware.RequireMerchantFeature(h.OpenDB, model.MerchantFeatureProducts), middleware.RequireMerchantRole(model.MemberRoleOwner, model.MemberRoleOperator), h.Merchant.CreateProduct)
|
||||
merchant.PATCH("/products/:id", middleware.RequireMerchantFeature(h.OpenDB, model.MerchantFeatureProducts), middleware.RequireMerchantRole(model.MemberRoleOwner, model.MemberRoleOperator), h.Merchant.UpdateProduct)
|
||||
merchant.PATCH("/products/:id/status", middleware.RequireMerchantFeature(h.OpenDB, model.MerchantFeatureProducts), middleware.RequireMerchantRole(model.MemberRoleOwner, model.MemberRoleOperator), h.Merchant.UpdateProductStatus)
|
||||
merchant.GET("/orders", middleware.RequireMerchantFeature(h.OpenDB, model.MerchantFeatureOrders), h.Merchant.ListOrders)
|
||||
merchant.POST("/orders/test", middleware.RequireMerchantFeature(h.OpenDB, model.MerchantFeatureOrders), middleware.RequireMerchantRole(model.MemberRoleOwner, model.MemberRoleOperator), h.Merchant.CreateTestOrder)
|
||||
merchant.GET("/orders/:order_no/delivery-link", middleware.RequireMerchantFeature(h.OpenDB, model.MerchantFeatureOrders), middleware.RequireMerchantRole(model.MemberRoleOwner, model.MemberRoleOperator), h.Merchant.GetDeliveryLink)
|
||||
@@ -165,6 +164,7 @@ func Setup(h *Handlers) *gin.Engine {
|
||||
admin.POST("/platform/merchants/:id/members", h.Merchant.AddPlatformMerchantMember)
|
||||
admin.GET("/platform/product-catalog", h.Merchant.ListProductCatalog)
|
||||
admin.GET("/platform/merchants/:id/products", h.Merchant.ListMerchantProductsByAdmin)
|
||||
admin.PATCH("/platform/merchants/:id/products/:pid", h.Merchant.AdminUpdateMerchantProduct)
|
||||
admin.POST("/platform/merchants/:id/products/assign", h.Merchant.AssignProducts)
|
||||
admin.GET("/platform/merchants/:id/wallet", h.Merchant.AdminGetWallet)
|
||||
admin.GET("/platform/merchants/:id/wallet/ledger", h.Merchant.AdminListWalletLedger)
|
||||
|
||||
@@ -723,6 +723,32 @@ func TestCreateOrderRejectsInsufficientBalance(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateOrderRejectsInactiveProduct(t *testing.T) {
|
||||
db := newServiceTestDB(t)
|
||||
merchantID, product := seedFulfillmentMerchant(t, db, "merchant-inactive-product", 5000, 1, 100)
|
||||
svc := NewFulfillmentService(db, nil)
|
||||
|
||||
// 商户下架商品后:开放接口下单与商户测试订单都应被拒绝
|
||||
if err := db.Model(&model.MerchantProduct{}).Where("id = ?", product.ID).Update("status", model.ProductStatusInactive).Error; err != nil {
|
||||
t.Fatalf("deactivate product: %v", err)
|
||||
}
|
||||
if _, err := svc.CreateOrder(CreateFulfillmentOrderInput{
|
||||
MerchantID: merchantID,
|
||||
APIClientID: 16,
|
||||
ClientOrderNo: "client-inactive-product",
|
||||
SKU: product.SKU,
|
||||
}); err == nil || !strings.Contains(err.Error(), "已下架") {
|
||||
t.Fatalf("inactive product should reject order, got %v", err)
|
||||
}
|
||||
if _, err := svc.CreateTestOrder(CreateTestOrderInput{
|
||||
MerchantID: merchantID,
|
||||
ActorUserID: 99,
|
||||
SKU: product.SKU,
|
||||
}); err == nil || !strings.Contains(err.Error(), "已下架") {
|
||||
t.Fatalf("inactive product should reject test order, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateTestOrderCreatesFulfillableOrderWithoutBilling(t *testing.T) {
|
||||
db := newServiceTestDB(t)
|
||||
merchantID, product := seedFulfillmentMerchant(t, db, "merchant-test-order", 0, 0, 100)
|
||||
|
||||
Reference in New Issue
Block a user