Compare commits

...

2 Commits

Author SHA1 Message Date
zzh
314c5ac02f feat: 申报明细新增产品标识编码/非标产品标识编码/标准产品标识编码
All checks were successful
Build and Deploy / deploy (push) Successful in 1m3s
- t_order_declaration 新增 ProductId、NonStdProductId、StdProductId(迁移 migrate_order_declaration_product_ids_20261019.sql)
- 下单/改单/开放接口/Excel 模板入参、订单详情出参、下游 createOrderOne 报文同步字段
- 自动触发公式目录增加三个字段
2026-10-19 15:48:16 +08:00
zzh
d38963055b feat: 支付用券/会员折扣写入负数应收行,订单应收与实付一致
- 支付成功后写入 coupon_discount / member_discount 负数应收(币种、阶段跟随运费行,记录券码/会员等级)
- 营销重算的折前应收排除抵扣行,避免重复扣减;规则公式忽略抵扣行
- 预报/入库计费任务跳过已付款订单,防止重复投递冲掉应收
- 手工新增费用禁止抵扣费用项;费用输出给出抵扣行显示名
- 订单详情返回折前小计、会员折扣、优惠券抵扣、客户实付
- 新增 fee_item 字典迁移 migrate_fee_item_marketing_discount_20261019.sql
2026-10-19 15:48:15 +08:00
22 changed files with 325 additions and 6 deletions

3
db.sql
View File

@ -432,6 +432,9 @@ CREATE TABLE `t_order_declaration` (
`ImHsCode` VARCHAR(50) NULL COMMENT '进口HsCode',
`ExHsCode` VARCHAR(50) NULL COMMENT '出口HsCode',
`DeclareElement` VARCHAR(255) NULL COMMENT '出口申报要素',
`ProductId` VARCHAR(100) NULL COMMENT '产品标识编码',
`NonStdProductId` VARCHAR(100) NULL COMMENT '非标产品标识编码',
`StdProductId` VARCHAR(100) NULL COMMENT '标准产品标识编码',
`SkuImage` VARCHAR(512) NULL COMMENT '图片(存 storage key 或短 URL)',
`Category` VARCHAR(20) NULL COMMENT '类别',
`Brand` VARCHAR(50) NULL COMMENT '品牌',

View File

@ -0,0 +1,34 @@
-- 费用项字典:营销抵扣(支付时写入的负数应收行)。可重复执行。
-- coupon_discount 优惠券抵扣、member_discount 会员折扣;由 /order/pay 自动生成,禁止手工添加。
-- 确保字典类型存在
INSERT INTO `t_dict_type` (`DictType`, `Remark`, `TypeSort`, `IsDeleted`, `CreateUserId`)
SELECT 'fee_item', '费用项', 0, 0, 0
FROM DUAL
WHERE NOT EXISTS (
SELECT 1 FROM `t_dict_type`
WHERE `DictType` = 'fee_item' AND `IsDeleted` = 0
);
-- 曾被软删的同 code 项直接恢复
UPDATE `t_dict`
SET `IsDeleted` = 0, `UpdateTime` = NOW()
WHERE `DictType` = 'fee_item'
AND `DictCode` IN ('coupon_discount', 'member_discount')
AND `IsDeleted` = 1;
INSERT INTO `t_dict` (`DictType`, `DictSort`, `DictCode`, `DictName`, `IsDeleted`, `CreateUserId`)
SELECT 'fee_item', 900, 'coupon_discount', '优惠券抵扣', 0, 0
FROM DUAL
WHERE NOT EXISTS (
SELECT 1 FROM `t_dict`
WHERE `DictType` = 'fee_item' AND `DictCode` = 'coupon_discount'
);
INSERT INTO `t_dict` (`DictType`, `DictSort`, `DictCode`, `DictName`, `IsDeleted`, `CreateUserId`)
SELECT 'fee_item', 901, 'member_discount', '会员折扣', 0, 0
FROM DUAL
WHERE NOT EXISTS (
SELECT 1 FROM `t_dict`
WHERE `DictType` = 'fee_item' AND `DictCode` = 'member_discount'
);

View File

@ -0,0 +1,55 @@
-- 申报明细:新增产品标识编码 / 非标产品标识编码 / 标准产品标识编码。
-- 可重复执行。
SET @db := DATABASE();
-- t_order_declaration.ProductId
SET @exists := (
SELECT COUNT(1)
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = @db
AND TABLE_NAME = 't_order_declaration'
AND COLUMN_NAME = 'ProductId'
);
SET @sql := IF(
@exists = 0,
'ALTER TABLE `t_order_declaration` ADD COLUMN `ProductId` VARCHAR(100) NULL COMMENT ''产品标识编码'' AFTER `DeclareElement`',
'SELECT ''t_order_declaration.ProductId already exists'' AS info'
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
-- t_order_declaration.NonStdProductId
SET @exists := (
SELECT COUNT(1)
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = @db
AND TABLE_NAME = 't_order_declaration'
AND COLUMN_NAME = 'NonStdProductId'
);
SET @sql := IF(
@exists = 0,
'ALTER TABLE `t_order_declaration` ADD COLUMN `NonStdProductId` VARCHAR(100) NULL COMMENT ''非标产品标识编码'' AFTER `ProductId`',
'SELECT ''t_order_declaration.NonStdProductId already exists'' AS info'
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
-- t_order_declaration.StdProductId
SET @exists := (
SELECT COUNT(1)
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = @db
AND TABLE_NAME = 't_order_declaration'
AND COLUMN_NAME = 'StdProductId'
);
SET @sql := IF(
@exists = 0,
'ALTER TABLE `t_order_declaration` ADD COLUMN `StdProductId` VARCHAR(100) NULL COMMENT ''标准产品标识编码'' AFTER `NonStdProductId`',
'SELECT ''t_order_declaration.StdProductId already exists'' AS info'
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;

View File

@ -20,6 +20,11 @@ public class OrderFeeOutput
/// </summary>
public string CostItem { get; set; } = string.Empty;
/// <summary>
/// 费用项显示名:仅营销抵扣行(优惠券抵扣/会员折扣)由后端给出,其余为空由前端按 fee_item 字典解析。
/// </summary>
public string? CostItemName { get; set; }
/// <summary>
/// 金额。
/// </summary>

View File

@ -70,6 +70,21 @@ public class OrderItemOutput
/// </summary>
public string? DeclareElement { get; set; }
/// <summary>
/// 产品标识编码。
/// </summary>
public string? ProductId { get; set; }
/// <summary>
/// 非标产品标识编码。
/// </summary>
public string? NonStdProductId { get; set; }
/// <summary>
/// 标准产品标识编码。
/// </summary>
public string? StdProductId { get; set; }
/// <summary>
/// SKU 图片。
/// </summary>

View File

@ -368,6 +368,31 @@ public class OrderOutput
/// </summary>
public decimal? ForecastPayableAmount { get; set; }
/// <summary>
/// 营销快照:折前应收小计。
/// </summary>
public decimal? ReceivableSubtotal { get; set; }
/// <summary>
/// 营销快照:会员折扣金额(与券互斥)。
/// </summary>
public decimal LevelDiscountAmount { get; set; }
/// <summary>
/// 营销快照:优惠券抵扣金额。
/// </summary>
public decimal CouponDiscountAmount { get; set; }
/// <summary>
/// 营销快照:客户实付金额(支付扣款口径;区别于应付成本)。
/// </summary>
public decimal? ActualPayAmount { get; set; }
/// <summary>
/// 营销快照:使用的优惠券实例 Id。
/// </summary>
public int? CouponInstanceId { get; set; }
/// <summary>
/// 订单绑定的销售产品规则发布版本。
/// </summary>

View File

@ -1,3 +1,5 @@
using System.ComponentModel.DataAnnotations;
namespace Oms.Application.Dtos.Orders;
/// <summary>
@ -65,6 +67,24 @@ public class OrderParcelItemInput
/// </summary>
public string? DeclareElement { get; set; }
/// <summary>
/// 产品标识编码(选填)。
/// </summary>
[StringLength(100)]
public string? ProductId { get; set; }
/// <summary>
/// 非标产品标识编码(选填)。
/// </summary>
[StringLength(100)]
public string? NonStdProductId { get; set; }
/// <summary>
/// 标准产品标识编码(选填)。
/// </summary>
[StringLength(100)]
public string? StdProductId { get; set; }
/// <summary>
/// SKU 图片。
/// </summary>

View File

@ -199,6 +199,9 @@ public static class AutoTriggerFormulaMeta
yield return ("进口HS编码", "t_order_declaration", "ImHsCode", FormulaValueType.String, false);
yield return ("出口HS编码", "t_order_declaration", "ExHsCode", FormulaValueType.String, false);
yield return ("出口申报要素", "t_order_declaration", "DeclareElement", FormulaValueType.String, false);
yield return ("产品标识编码", "t_order_declaration", "ProductId", FormulaValueType.String, false);
yield return ("非标产品标识编码", "t_order_declaration", "NonStdProductId", FormulaValueType.String, false);
yield return ("标准产品标识编码", "t_order_declaration", "StdProductId", FormulaValueType.String, false);
yield return ("申报类别", "t_order_declaration", "Category", FormulaValueType.String, false);
yield return ("品牌", "t_order_declaration", "Brand", FormulaValueType.String, false);
yield return ("品牌类型", "t_order_declaration", "BrandType", FormulaValueType.String, false);

View File

@ -29,7 +29,7 @@ public static class OrderFormulaFieldResolver
if (fieldRef.Table == "t_order_fee")
{
return context.Fees.Any(f => ContainsValue(GetPropertyValue(f, fieldRef.Field), needle));
return RuleFees(context).Any(f => ContainsValue(GetPropertyValue(f, fieldRef.Field), needle));
}
var raw = GetRawValue(fieldRef, context);
@ -255,10 +255,16 @@ public static class OrderFormulaFieldResolver
private static object? ResolveFeeField(string field, OrderRuleEvaluationContext context)
{
var fee = context.Fees.FirstOrDefault();
var fee = RuleFees(context).FirstOrDefault();
return fee == null ? null : GetPropertyValue(fee, field);
}
/// <summary>
/// 规则只看计费产生的费用行,忽略支付写入的营销抵扣负数行。
/// </summary>
private static IEnumerable<OrderFee> RuleFees(OrderRuleEvaluationContext context) =>
context.Fees.Where(f => !OrderFeeDiscountCostItems.IsDiscount(f.CostItem));
private static object? GetDeclarationValue(OrderDeclaration declaration, string field) =>
field switch
{

View File

@ -263,6 +263,11 @@ public partial class OrderService
HasInsurance = hasInsurance,
ForecastReceivableAmount = order.ForecastReceivableAmount,
ForecastPayableAmount = order.ForecastPayableAmount,
ReceivableSubtotal = order.ReceivableSubtotal,
LevelDiscountAmount = order.LevelDiscountAmount,
CouponDiscountAmount = order.CouponDiscountAmount,
ActualPayAmount = order.PayableAmount,
CouponInstanceId = order.CouponInstanceId,
SalesProductRulesVersion = string.IsNullOrWhiteSpace(order.SalesProductRulesVersion)
? null
: order.SalesProductRulesVersion.Trim(),
@ -812,6 +817,9 @@ public partial class OrderService
ImHsCode = x.ImHsCode ?? x.HsCode,
ExHsCode = x.ExHsCode ?? x.HsCode,
DeclareElement = x.DeclareElement,
ProductId = x.ProductId,
NonStdProductId = x.NonStdProductId,
StdProductId = x.StdProductId,
SkuImage = FileStorage.GetPublicUrl(x.SkuImage),
Category = x.Category,
Brand = x.Brand,
@ -851,6 +859,9 @@ public partial class OrderService
Id = x.Id,
Type = x.Type,
CostItem = x.CostItem,
CostItemName = OrderFeeDiscountCostItems.IsDiscount(x.CostItem)
? ParcelBillingCalculator.ResolveFeeDisplayName(x.CostItem, null)
: null,
Amount = x.Amount,
Currency = x.Currency,
DownstreamProvider = x.DownstreamProvider,

View File

@ -900,6 +900,11 @@ public partial class OrderService
throw new BusinessException("费用项不能为空");
}
if (OrderFeeDiscountCostItems.IsDiscount(costItem))
{
throw new BusinessException("优惠券抵扣、会员折扣由支付自动生成,不能手工添加");
}
if (input.Amount <= 0)
{
throw new BusinessException("金额必须大于0");
@ -1355,6 +1360,9 @@ public partial class OrderService
ImHsCode = x.ImHsCode,
ExHsCode = x.ExHsCode ?? x.ImHsCode,
DeclareElement = x.DeclareElement,
ProductId = x.ProductId?.Trim(),
NonStdProductId = x.NonStdProductId?.Trim(),
StdProductId = x.StdProductId?.Trim(),
SkuImage = OmsObjectStorageExtensions.ToStorageKey(x.SkuImage),
Category = x.Category,
Brand = x.Brand,

View File

@ -2213,6 +2213,9 @@ public partial class OrderService
ImHsCode = x.ImHsCode,
ExHsCode = x.ExHsCode,
DeclareElement = x.DeclareElement,
ProductId = x.ProductId?.Trim(),
NonStdProductId = x.NonStdProductId?.Trim(),
StdProductId = x.StdProductId?.Trim(),
SkuImage = OmsObjectStorageExtensions.ToStorageKey(x.SkuImage),
Category = x.Category,
Brand = x.Brand,
@ -2449,6 +2452,12 @@ public partial class OrderService
{
_ = cancellationToken;
var order = await GetOrderOrThrowAsync(orderId);
// 已付款订单应收已固化(含营销抵扣行),重复投递的入库计费消息不得重算
if (order.IsPaid != 0)
{
return;
}
if (string.IsNullOrWhiteSpace(order.ProductCode))
{
order.OrderStatus = OrderStatusCodes.PendingAllocation;
@ -2630,7 +2639,8 @@ public partial class OrderService
throw new InvalidOperationException($"预报计费时订单尚未可见,稍后重试 OrderId={orderId}");
}
if (string.Equals(order.OrderStatus, OrderStatusCodes.Deleted, StringComparison.OrdinalIgnoreCase))
if (string.Equals(order.OrderStatus, OrderStatusCodes.Deleted, StringComparison.OrdinalIgnoreCase)
|| order.IsPaid != 0)
{
return;
}

View File

@ -3,6 +3,7 @@ using Microsoft.AspNetCore.Mvc;
using Oms.Application.Dtos.Marketing;
using Oms.Application.Dtos.Orders;
using Oms.Application.Shared;
using Oms.Domain.OrdersAggregate.Consts;
using Oms.Domain.OrdersAggregate.Entities;
namespace Oms.Application.Services.Orders;
@ -129,6 +130,75 @@ public partial class OrderService
}
}
/// <summary>
/// 支付成功后把会员折扣 / 优惠券抵扣写成负数应收行,使订单应收与实付一致;币种与计费阶段跟随基础运费行。
/// </summary>
private async Task UpsertMarketingDiscountFeesAsync(
Order order,
IEnumerable<OrderFee> fees,
MarketingPriceBreakdown breakdown,
string? couponCode)
{
var discountRows = new List<OrderFee>();
if (breakdown.CouponDiscountAmount <= 0 && breakdown.LevelDiscountAmount <= 0)
{
return;
}
var receivable = fees
.Where(x => x.Type == OrderFeeTypeCodes.Receivable
&& !OrderFeeDiscountCostItems.IsDiscount(x.CostItem))
.ToList();
var anchor = receivable
.Where(x => MarketingFeeHelper.IsBaseFreightCostItem(x.CostItem))
.OrderByDescending(x => ChargingStageRank(x.ChargingStage))
.FirstOrDefault()
?? receivable.OrderByDescending(x => ChargingStageRank(x.ChargingStage)).FirstOrDefault();
var currency = string.IsNullOrWhiteSpace(anchor?.Currency) ? "CNY" : anchor!.Currency;
var stage = anchor?.ChargingStage;
if (breakdown.CouponDiscountAmount > 0)
{
discountRows.Add(new OrderFee
{
Type = OrderFeeTypeCodes.Receivable,
CostItem = OrderFeeDiscountCostItems.CouponDiscount,
Amount = -breakdown.CouponDiscountAmount,
Currency = currency,
ChargingStage = stage,
PriceNo = couponCode,
PriceEffectiveTime = DateTime.Now
});
}
if (breakdown.LevelDiscountAmount > 0)
{
discountRows.Add(new OrderFee
{
Type = OrderFeeTypeCodes.Receivable,
CostItem = OrderFeeDiscountCostItems.MemberDiscount,
Amount = -breakdown.LevelDiscountAmount,
Currency = currency,
ChargingStage = stage,
PriceNo = breakdown.MemberLevelName,
PriceEffectiveTime = DateTime.Now
});
}
await OrderRepository.UpsertFeesAsync(order.Id, discountRows);
}
/// <summary>
/// 抵扣行取入库优先、其次预报的阶段;出库阶段客户侧不展示,排最后。
/// </summary>
private static int ChargingStageRank(string? stage) => stage?.Trim().ToLowerInvariant() switch
{
OrderFeeChargingStageCodes.Outbound => -1,
OrderFeeChargingStageCodes.Inbound => 2,
OrderFeeChargingStageCodes.Forecast => 1,
_ => 0
};
private static int? ResolveRecommendedPayCouponId(Order order, List<CouponAvailableOutput> coupons)
{
if (order.CouponInstanceId is > 0

View File

@ -184,12 +184,16 @@ public partial class OrderService : ApplicationService
LazyServiceProvider.GetRequiredService<MarketingOrderHelper>()
.ApplySnapshotToOrder(order, breakdown, null);
string? couponCode = null;
if (breakdown.CouponInstanceId.HasValue)
{
var mainCustomerId = await couponManager.ResolveMainCustomerIdAsync(customer.Id);
await couponManager.MarkUsedAsync(breakdown.CouponInstanceId.Value, mainCustomerId, order.Id);
var coupon = await couponManager.MarkUsedAsync(breakdown.CouponInstanceId.Value, mainCustomerId, order.Id);
couponCode = coupon.CouponCode;
}
await UpsertMarketingDiscountFeesAsync(order, fees, breakdown, couponCode);
var mainCustomer = await MarketingCustomerHelper.ResolveMarketingMainCustomerAsync(CustomerRepository, customer);
var pointsManager = LazyServiceProvider.GetRequiredService<MarketingPointsManager>();
var paidBaseFreight = MarketingFeeHelper.ResolvePaidBaseFreight(

View File

@ -1075,6 +1075,8 @@ internal static class ParcelBillingCalculator
"handling" => "操作费",
"proxyPacking" => "代打包费",
"insurance" => "保险费",
OrderFeeDiscountCostItems.CouponDiscount => "优惠券抵扣",
OrderFeeDiscountCostItems.MemberDiscount => "会员折扣",
_ => costItem
};
}

View File

@ -215,6 +215,9 @@ public static class OrderTemplateFieldRegistry
Add("items[0].weight", "总重量(kg)", false, "number", group: "declaration", example: "自动计算", hint: "不填=单重×数量");
Add("items[0].imHsCode", "进口HsCode", false, group: "declaration", hint: "选填");
Add("items[0].exHsCode", "出口HsCode", false, group: "declaration", hint: "选填");
Add("items[0].productId", "产品标识编码", false, group: "declaration", hint: "选填");
Add("items[0].nonStdProductId", "非标产品标识编码", false, group: "declaration", hint: "选填");
Add("items[0].stdProductId", "标准产品标识编码", false, group: "declaration", hint: "选填");
Add("items[0].skuImage", "商品图片URL", false, group: "declaration", hint: "选填");
Add("items[0].category", "类别", false, group: "declaration", example: "Accessories", hint: "选填");
Add("items[0].brand", "品牌", false, group: "declaration", example: "DemoBrand", hint: "选填");

View File

@ -20,6 +20,7 @@ public static class OrderTemplateRowMapper
"sender.countryCode", "sender.province", "sender.city", "sender.zipCode", "sender.streetAddress1",
"sender.streetAddress2", "sender.streetAddress3", "sender.houseNumber", "items[0].cnName", "items[0].enName",
"items[0].sku", "items[0].unit", "items[0].imHsCode", "items[0].exHsCode", "items[0].skuImage",
"items[0].productId", "items[0].nonStdProductId", "items[0].stdProductId",
"items[0].category", "items[0].brand", "items[0].brandType", "items[0].distribution", "items[0].material",
"items[0].enMaterial", "items[0].purpose", "items[0].attributes", "items[0].originCountry", "items[0].salesLink",
"items[0].model", "items[0].specifications", "items[0].comment"

View File

@ -266,7 +266,10 @@ public class MarketingCouponManager : IScopedDependency
await _instanceRepository.UpdateAsync(instance, cancellationToken);
}
public async Task MarkUsedAsync(int couponInstanceId, int mainCustomerId, int orderId, CancellationToken cancellationToken = default)
/// <summary>
/// 核销券并返回券实例(调用方可取券码写入费用行)。
/// </summary>
public async Task<CouponInstance> MarkUsedAsync(int couponInstanceId, int mainCustomerId, int orderId, CancellationToken cancellationToken = default)
{
var instance = await GetOwnedInstanceAsync(couponInstanceId, mainCustomerId, cancellationToken);
instance.Status = CouponInstanceStatus.Used;
@ -274,6 +277,7 @@ public class MarketingCouponManager : IScopedDependency
instance.UseTime = DateTime.Now;
instance.LockExpireTime = null;
await _instanceRepository.UpdateAsync(instance, cancellationToken);
return instance;
}
public async Task RestoreOnOrderRefundAsync(Order order, bool isFullRefund, CancellationToken cancellationToken = default)

View File

@ -12,8 +12,13 @@ public static class MarketingFeeHelper
public static bool IsBaseFreightCostItem(string? costItem) =>
!string.IsNullOrWhiteSpace(costItem) && ParcelBillingCalculator.IsFreightCostItem(costItem);
/// <summary>
/// 折前应收小计:排除支付时写入的营销抵扣行,避免重算营销金额时重复扣减。
/// </summary>
public static decimal SumReceivable(IEnumerable<OrderFee> fees) =>
fees.Where(x => x.Type == OrderFeeTypeCodes.Receivable).Sum(x => x.Amount);
fees.Where(x => x.Type == OrderFeeTypeCodes.Receivable
&& !OrderFeeDiscountCostItems.IsDiscount(x.CostItem))
.Sum(x => x.Amount);
public static decimal SumReceivableBaseFreight(IEnumerable<OrderFee> fees) =>
fees.Where(x => x.Type == OrderFeeTypeCodes.Receivable && IsBaseFreightCostItem(x.CostItem))

View File

@ -201,6 +201,9 @@ public static class OrderDownstreamCreateOneBodyBuilder
ImHsCode = x.ImHsCode,
ExHsCode = x.ExHsCode ?? x.HsCode,
DeclareElement = x.DeclareElement,
ProductId = x.ProductId,
NonStdProductId = x.NonStdProductId,
StdProductId = x.StdProductId,
SkuImage = fileStorage?.GetPublicUrl(x.SkuImage) ?? x.SkuImage,
Category = x.Category,
Brand = x.Brand,

View File

@ -6,6 +6,23 @@ public static class OrderFeeTypeCodes
public const string Payable = "payable";
}
/// <summary>
/// 营销抵扣费用项(支付时写入的负数应收行,fee_item 字典同 code)。
/// </summary>
public static class OrderFeeDiscountCostItems
{
/// <summary>优惠券抵扣。</summary>
public const string CouponDiscount = "coupon_discount";
/// <summary>会员折扣。</summary>
public const string MemberDiscount = "member_discount";
public static bool IsDiscount(string? costItem) =>
!string.IsNullOrWhiteSpace(costItem)
&& (string.Equals(costItem.Trim(), CouponDiscount, StringComparison.OrdinalIgnoreCase)
|| string.Equals(costItem.Trim(), MemberDiscount, StringComparison.OrdinalIgnoreCase));
}
public static class OrderFeeChargingStageCodes
{
public const string Forecast = "forecast";

View File

@ -37,6 +37,21 @@ public class OrderDeclaration : OmsAuditedEntity
/// </summary>
public string? DeclareElement { get; set; }
/// <summary>
/// 产品标识编码。
/// </summary>
public string? ProductId { get; set; }
/// <summary>
/// 非标产品标识编码。
/// </summary>
public string? NonStdProductId { get; set; }
/// <summary>
/// 标准产品标识编码。
/// </summary>
public string? StdProductId { get; set; }
public string? SkuImage { get; set; }
public string? Category { get; set; }