Compare commits
2 Commits
ce75fca721
...
314c5ac02f
| Author | SHA1 | Date | |
|---|---|---|---|
| 314c5ac02f | |||
| d38963055b |
3
db.sql
3
db.sql
@ -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 '品牌',
|
||||
|
||||
34
sql/migrate_fee_item_marketing_discount_20261019.sql
Normal file
34
sql/migrate_fee_item_marketing_discount_20261019.sql
Normal 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'
|
||||
);
|
||||
55
sql/migrate_order_declaration_product_ids_20261019.sql
Normal file
55
sql/migrate_order_declaration_product_ids_20261019.sql
Normal 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;
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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
|
||||
{
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -1075,6 +1075,8 @@ internal static class ParcelBillingCalculator
|
||||
"handling" => "操作费",
|
||||
"proxyPacking" => "代打包费",
|
||||
"insurance" => "保险费",
|
||||
OrderFeeDiscountCostItems.CouponDiscount => "优惠券抵扣",
|
||||
OrderFeeDiscountCostItems.MemberDiscount => "会员折扣",
|
||||
_ => costItem
|
||||
};
|
||||
}
|
||||
|
||||
@ -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: "选填");
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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))
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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";
|
||||
|
||||
@ -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; }
|
||||
|
||||
Loading…
Reference in New Issue
Block a user