修改 商铺统计 买手号退款计算

This commit is contained in:
chfei committed 2026-09-29 13:53:31 +08:00
1 parent 766afa2fb8
commit be1fc11e07
7 files changed
+773 -2

No files matched your search

@@ -84,6 +84,7 @@ namespace KJD_Web.BLL.WXXD
sb.Append("A.profit, ");
sb.Append("A.purchase_status, ");
sb.Append("A.out_amount, ");
sb.Append("A.out_order_no,");
sb.Append("B.refund_status, ");
sb.Append("c.`status` as refund_status_string, ");
sb.Append("IFNULL(C.refund_amount, 0) AS refund_amount ");
@@ -178,15 +179,18 @@ namespace KJD_Web.BLL.WXXD
NullValueHandling = NullValueHandling.Ignore
});
}
/// <summary>
/// 将订单行转换为金额详情:out_amount 为空或0时从中转仓订单表补全,并计算利润
/// </summary>
/// <param name="row"></param>
/// <param name="transitOutAmountMap">中转仓订单金额字典表</param>
/// <returns></returns>
private WXOrderAmountInfo BuildOrderAmountInfo(DataRow row, Dictionary<string, int> transitOutAmountMap)
{
string orderId = row["order_id"].ParseToString();
int outAmount = row["out_amount"].ParseToInt();
string out_order_no = row["out_order_no"].ParseToString();
int externalProcurement = 0;
if (outAmount == 0)
{
@@ -197,6 +201,10 @@ namespace KJD_Web.BLL.WXXD
outAmount = transitAmount;
}
}
if (!string.IsNullOrEmpty(out_order_no))
{
externalProcurement = 1;
}
int orderPrice = row["order_price"].ParseToInt();
int predictCommissionFee = row["predict_commission_fee"].ParseToInt();
@@ -226,6 +234,7 @@ namespace KJD_Web.BLL.WXXD
profit = profit,
purchase_status = row["purchase_status"].ParseToInt(),
out_amount = outAmount,
out_order_no = out_order_no,
refund_status = row["refund_status"].ParseToInt(),
refund_status_string = row["refund_status_string"].ParseToString(),
refund_amount = row["refund_amount"].ParseToInt(),
@@ -234,7 +243,7 @@ namespace KJD_Web.BLL.WXXD
}
/// <summary>
/// 买手号退款金额:退款订单(refund_status>1)且非外采时,从买手售后表(wxxd.order_pdd_buyer_after_sale)查询退款金额(refundAmount)填入
/// 直拍买手号退款金额:退款订单(refund_status>1)且非外采时,从买手售后表(wxxd.order_pdd_buyer_after_sale)查询退款金额(refundAmount)填入
/// </summary>
private void FillBuyerRefundAmount(List<WXOrderAmountInfo> orderList, string conn)
{
+1
View File
@@ -266,6 +266,7 @@
<Compile Include="Repository\UserPointsRecordRepository.cs" />
<Compile Include="Requests\Users\ShopRenewRequest.cs" />
<Compile Include="Services\ImageCacheService.cs" />
<Compile Include="Services\Shops\ShopRevenueStatisticsService.cs" />
<Compile Include="Services\Shops\ShopService.cs" />
<Compile Include="Services\Users\UserService.FeeRecord.cs" />
<Compile Include="Services\Users\UserService.OEM.cs" />
@@ -67,7 +67,16 @@ namespace KJD_Web.Model.WxModel
/// </summary>
public int out_amount { get; set; }
/// <summary>
/// 外采订单号
/// </summary>
public string out_order_no { get; set; }
/// <summary>
/// 售后状态(订单明细 refund_status)
/// 售后状态 1:无售后或售后关闭,
/// 2:售后处理中,
/// 3:退款中,
/// 4:退款成功
/// 11 退款完成订单已取消
/// </summary>
public int refund_status { get; set; }
/// <summary>
@@ -0,0 +1,151 @@
using KJD_Web.Model.WxModel;
using KJD_Web.Utils;
using MAX.Core.DependencyInjection;
using MAX.Core.Logger;
using Store.Contracts.Order.Entitys;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace KJD_Web.Services.Shops;
/// <summary>
/// 商铺统计
/// </summary>
public class ShopRevenueStatisticsService : ITransientDependency
{
private readonly IFreeSql _freeSql;
public ShopRevenueStatisticsService(IFreeSql freeSql, ILoggerFacade logger)
{
_freeSql = freeSql;
}
public void GetShopOrderAmountRange(List<long> shopIds, DateTime startTime, DateTime endTime)
{
int startTimestamp = TimeUtils.DateTimeToTimestampSeconds(startTime);
int endTimestamp = TimeUtils.DateTimeToTimestampSeconds(endTime);
// 1. 先查订单(含 item 的 INNER JOIN 可以放在数据库里做)
var ordersQuery = _freeSql.Select<WeiOrderEntity, WeiOrderItemEntity>()
.InnerJoin((a, b) => a.OrderId == b.OrderId)
.Where((a, b) => a.CreateTime >= startTimestamp && a.CreateTime <= endTimestamp)
.Where((a, b) => a.Status > 10)
.Where((a, b) => shopIds.Contains(a.ShopId))
.OrderBy((a, b) => a.CreateTime);
var sql = ordersQuery.ToSql();
var orders = ordersQuery.ToList((a, b) => new WXOrderAmountInfo
{
shop_name = a.ShopName,
create_time = a.CreateTime.Value,
order_id = a.OrderId,
status = a.Status.Value,
product_price = a.ProductPrice.Value,
order_price = a.OrderPrice.Value,
purchase_price = a.PurchasePrice.Value,
freight = a.Freight.Value,
predict_commission_fee = a.PredictCommissionFee.Value,
commission_amount = a.CommissionAmount,
discounted_price = a.DiscountedPrice.Value,
profit = a.Profit.Value,
purchase_status = a.PurchaseStatus,
out_amount = a.OutAmount,
refund_status = b.RefundStatus
});
// 2. 只按订单号查退款,分批避免 IN 过长
var orderIds = orders.Select(o => o.order_id).Distinct().ToList();
var refunds = new List<WeiRefundEntity>();
for (int i = 0; i < orderIds.Count; i += 1000)
{
var batch = orderIds.Skip(i).Take(1000).ToArray();
var ids = batch.ToList();
refunds.AddRange(
_freeSql.Select<WeiRefundEntity>()
.Where(r => ids.Contains(r.OrderId))
.ToList(r => new WeiRefundEntity
{
Id = r.Id,
OrderId = r.OrderId,
Status = r.Status,
RefundAmount = r.RefundAmount
}));
}
// 3. 内存里 group,每个订单取 id 最大的那条
var latestRefund = refunds
.GroupBy(r => r.OrderId)
.ToDictionary(g => g.Key, g => g.OrderByDescending(r => r.Id).First());
// 4. 回填(等价 LEFT JOIN + IFNULL)
foreach (var o in orders)
{
if (latestRefund.TryGetValue(o.order_id, out var r))
{
o.refund_status_string = r.Status;
o.refund_amount = r.RefundAmount.Value;
}
else
{
o.refund_amount = 0;
}
}
var transitOutAmountMap = new Dictionary<string, int>();
// needLookupOrderIds:先跑一遍订单列表,out_amount 为空或0的订单号
var needLookupOrderIds = orders
.Where(o => o.out_amount == 0)
.Select(o => o.order_id)
.Distinct()
.ToList();
if (needLookupOrderIds.Count > 0)
{
for (int i = 0; i < orderIds.Count; i += 1000)
{
var batch = orderIds.Skip(i).Take(1000).ToArray();
var ids = batch.ToList();
var transitList = _freeSql.Select<WeiExternalProcurementLogEntity>()
.Where(t => ids.Contains(t.OrderNo))
.ToList(t => new { t.OrderNo, t.OutAmount });
foreach (var t in transitList)
{
if (string.IsNullOrEmpty(t.OrderNo)) continue;
if (!transitOutAmountMap.ContainsKey(t.OrderNo))
transitOutAmountMap[t.OrderNo] = (int)(t.OutAmount ?? 0);
}
}
}
foreach (var o in orders)
{
// 1. 外采金额补全
if (o.out_amount == 0 && transitOutAmountMap.TryGetValue(o.order_id, out var transitAmount))
{
o.external_procurement = 1;
o.out_amount = transitAmount;
}
// 2. 退款信息补全(latestRefund 是前面按订单号分组取最新退款得到的字典)
if (latestRefund.TryGetValue(o.order_id, out var r))
{
o.refund_status_string = r.Status.ToString(); // 按实际类型调整
o.refund_amount = (int)(r.RefundAmount ?? 0);
}
// 利润 = 订单金额 - 外采金额 - 平台服务费 - 达人佣金(为空按0处理)
o.profit = o.order_price - o.out_amount - o.predict_commission_fee - o.commission_amount;
if (o.refund_status > 1 && o.refund_amount > 0)//退款利润计算
{
o.profit = o.order_price - o.refund_amount;
}
}
}
}
@@ -0,0 +1,285 @@
using FreeSql.DataAnnotations;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Store.Contracts.Order.Entitys;
/// <summary>
/// 微信外采取号日志表
/// </summary>
[Table(Name = "wxxd.oms_wei_external_procurement_log")]
public class WeiExternalProcurementLogEntity
{
/// <summary>
/// 主键ID
/// </summary>
[Column(Name = "id", IsPrimary = true, IsIdentity = true)]
public long Id { get; set; }
/// <summary>
/// 外部订单号
/// </summary>
[Column(Name = "out_order_no")]
public string OutOrderNo { get; set; }
/// <summary>
/// 平台订单号
/// </summary>
[Column(Name = "order_no")]
public string OrderNo { get; set; }
/// <summary>
/// 订单sku id
/// </summary>
[Column(Name = "order_sku_id")]
public string OrderSkuId { get; set; }
/// <summary>
/// 买家账号
/// </summary>
[Column(Name = "buyer_account")]
public string BuyerAccount { get; set; }
/// <summary>
/// 外部金额(分)
/// </summary>
[Column(Name = "out_amount")]
public int? OutAmount { get; set; }
/// <summary>
/// 采购人备注
/// </summary>
[Column(Name = "purchaser_note")]
public string PurchaserNote { get; set; }
/// <summary>
/// 快递公司1
/// </summary>
[Column(Name = "express_1_company")]
public string Express1Company { get; set; }
/// <summary>
/// 快递单号1
/// </summary>
[Column(Name = "express_1_waybill")]
public string Express1Waybill { get; set; }
/// <summary>
/// 快递公司2
/// </summary>
[Column(Name = "express_2_company")]
public string Express2Company { get; set; }
/// <summary>
/// 快递单号2
/// </summary>
[Column(Name = "express_2_waybill")]
public string Express2Waybill { get; set; }
/// <summary>
/// 退款状态
/// </summary>
[Column(Name = "refund_status")]
public string RefundStatus { get; set; }
/// <summary>
/// 取货时间
/// </summary>
[Column(Name = "pickup_time")]
public DateTime? PickupTime { get; set; }
/// <summary>
/// 中转仓状态:0 未使用,1 已创建,2 取号失败,3 处理中,4创建异常
/// </summary>
[Column(Name = "transfer_warehouse_status")]
public byte? TransferWarehouseStatus { get; set; }
/// <summary>
/// 取号失败原因
/// </summary>
[Column(Name = "raw_message")]
public string RawMessage { get; set; }
/// <summary>
/// 创建时间
/// </summary>
[Column(Name = "created_time")]
public DateTime? CreatedTime { get; set; }
/// <summary>
/// 更新时间
/// </summary>
[Column(Name = "updated_time")]
public DateTime? UpdatedTime { get; set; }
/// <summary>
/// 中转仓库编码
/// </summary>
[Column(Name = "platform")]
public string Platform { get; set; }
/// <summary>
/// 订单平台
/// </summary>
[Column(Name = "order_platform_code")]
public string OrderPlatformCode { get; set; }
/// <summary>
/// 唯一值编码
/// </summary>
[Column(Name = "order_code")]
public string OrderCode { get; set; }
/// <summary>
/// 1正常取号 2提前取号 3取号更正
/// </summary>
[Column(Name = "pre_allocated_order_no")]
public int? PreAllocatedOrderNo { get; set; }
/// <summary>
/// 是否转寄:0否,1是
/// </summary>
[Column(Name = "is_transfer_ship")]
public byte IsTransferShip { get; set; }
/// <summary>
/// 随机溯源码
/// </summary>
[Column(Name = "random_code")]
public string RandomCode { get; set; }
/// <summary>
/// 中转仓售后状态
/// </summary>
[Column(Name = "transfer_refund_status")]
public int? TransferRefundStatus { get; set; }
/// <summary>
/// 订单作废状态:0=正常,1=已作废,2=作废失败
/// </summary>
[Column(Name = "order_void_status")]
public byte OrderVoidStatus { get; set; }
/// <summary>
/// 虚拟单号
/// </summary>
[Column(Name = "virtual_waybill_no")]
public string VirtualWaybillNo { get; set; }
/// <summary>
/// 虚拟快递公司
/// </summary>
[Column(Name = "virtual_express_company")]
public string VirtualExpressCompany { get; set; }
/// <summary>
/// 0: 未入库, 1: 已入库, 2: 已出库, 3:拦截到仓
/// </summary>
[Column(Name = "warehouse_status")]
public int WarehouseStatus { get; set; }
/// <summary>
/// 入库时间
/// </summary>
[Column(Name = "inbound_time")]
public DateTime? InboundTime { get; set; }
/// <summary>
/// 出库时间
/// </summary>
[Column(Name = "outbound_time")]
public DateTime? OutboundTime { get; set; }
/// <summary>
/// 是否有退货: 0=无,1=有
/// </summary>
[Column(Name = "has_return")]
public byte? HasReturn { get; set; }
/// <summary>
/// 订单初始金额(单位:分)
/// </summary>
[Column(Name = "postage")]
public int? Postage { get; set; }
/// <summary>
/// 重试取号是否成功:0=未尝试 1=成功 2=失败
/// </summary>
[Column(Name = "retry_acquire_success")]
public byte RetryAcquireSuccess { get; set; }
/// <summary>
/// 超重费用,单位:分
/// </summary>
[Column(Name = "over_weight_fee")]
public int? OverWeightFee { get; set; }
/// <summary>
/// 99拒收成功退回 0未申请拒收 1已申请拒收 2申请拒收失败 3申请拒收成功(不一定真成功) 5取消拒收
/// </summary>
[Column(Name = "has_reject")]
public byte HasReject { get; set; }
/// <summary>
/// 拦截到仓时间
/// </summary>
[Column(Name = "blocked_arrival_time")]
public DateTime? BlockedArrivalTime { get; set; }
/// <summary>
/// 拒收时间
/// </summary>
[Column(Name = "rejected_time")]
public DateTime? RejectedTime { get; set; }
/// <summary>
/// 拦截退回状态:0未处理,1已退回,2退回失败
/// </summary>
[Column(Name = "intercept_return_status")]
public int? InterceptReturnStatus { get; set; }
/// <summary>
/// 拦截退回时间
/// </summary>
[Column(Name = "intercept_return_time")]
public DateTime? InterceptReturnTime { get; set; }
/// <summary>
/// 拦截退回操作信息/回调信息
/// </summary>
[Column(Name = "intercept_return_info")]
public string InterceptReturnInfo { get; set; }
/// <summary>
/// 拦截改地址状态:0未处理,1修改成功,2修改失败
/// </summary>
[Column(Name = "intercept_address_status")]
public byte? InterceptAddressStatus { get; set; }
/// <summary>
/// 拦截改地址时间
/// </summary>
[Column(Name = "intercept_address_time")]
public DateTime? InterceptAddressTime { get; set; }
/// <summary>
/// 拦截改地址操作信息/回调信息
/// </summary>
[Column(Name = "intercept_address_info")]
public string InterceptAddressInfo { get; set; }
/// <summary>
/// 是否手动发货:0自动发货,1手动发货
/// </summary>
[Column(Name = "is_manual_shipment")]
public int IsManualShipment { get; set; }
/// <summary>
/// 手动发货处理状态:0未处理,1成功,2失败
/// </summary>
[Column(Name = "manual_shipment_status")]
public int? ManualShipmentStatus { get; set; }
}
@@ -0,0 +1,237 @@
using FreeSql.DataAnnotations;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Store.Contracts.Order.Entitys;
/// <summary>
/// 微信小店售后单表
/// </summary>
[Table(Name = "erp.oms_wei_refund")]
public class WeiRefundEntity
{
/// <summary>
/// id
/// </summary>
[Column(Name = "id", IsPrimary = true, IsIdentity = true)]
public long Id { get; set; }
/// <summary>
/// 店铺id
/// </summary>
[Column(Name = "shop_id")]
public long? ShopId { get; set; }
/// <summary>
/// 售后单号
/// </summary>
[Column(Name = "after_sale_order_id")]
public string AfterSaleOrderId { get; set; }
/// <summary>
/// 售后单当前状态,参考:USER_CANCELD 用户取消申请 / MERCHANT_PROCESSING 商家受理中 / MERCHANT_REJECT_REFUND 商家拒绝退款 / MERCHANT_REJECT_RETURN 商家拒绝退货退款 / USER_WAIT_RETURN 待买家退货 / RETURN_CLOSED 退货退款关闭 / MERCHANT_WAIT_RECEIPT 待商家收货 / MERCHANT_OVERDUE_REFUND 商家逾期未退款 / MERCHANT_REFUND_SUCCESS 退款完成 / MERCHANT_RETURN_SUCCESS 退货退款完成 / PLATFORM_REFUNDING 平台退款中 / PLATFORM_REFUND_FAIL 平台退款失败 / USER_WAIT_CONFIRM 待用户确认 / MERCHANT_REFUND_RETRY_FAIL 商家打款失败,客服关闭售后 / MERCHANT_FAIL 售后关闭 / USER_WAIT_CONFIRM_UPDATE 待用户处理商家协商 / USER_WAIT_HANDLE_MERCHANT_AFTER_SALE 待用户处理商家代发起的售后申请
/// </summary>
[Column(Name = "status")]
public string Status { get; set; }
/// <summary>
/// 买家身份标识
/// </summary>
[Column(Name = "openid")]
public string Openid { get; set; }
/// <summary>
/// 买家在开放平台的唯一标识符,若当前视频号小店已绑定到微信开放平台账号下会返回
/// </summary>
[Column(Name = "unionid")]
public string Unionid { get; set; }
/// <summary>
/// 商品spuid
/// </summary>
[Column(Name = "product_id")]
public string ProductId { get; set; }
/// <summary>
/// 商品skuid
/// </summary>
[Column(Name = "sku_id")]
public string SkuId { get; set; }
/// <summary>
/// 售后数量
/// </summary>
[Column(Name = "count")]
public int? Count { get; set; }
/// <summary>
/// 是否极速退款RefundInfo
/// </summary>
[Column(Name = "fast_refund")]
public string FastRefund { get; set; }
/// <summary>
/// 标明售后单退款直接原因, 枚举值参考 RefundReason
/// </summary>
[Column(Name = "refund_reason")]
public int? RefundReason { get; set; }
/// <summary>
/// 退款金额(分)
/// </summary>
[Column(Name = "refund_amount")]
public int? RefundAmount { get; set; }
/// <summary>
/// 快递单号
/// </summary>
[Column(Name = "return_waybill_id")]
public string ReturnWaybillId { get; set; }
/// <summary>
/// 物流公司id
/// </summary>
[Column(Name = "return_delivery_id")]
public string ReturnDeliveryId { get; set; }
/// <summary>
/// 物流公司名称
/// </summary>
[Column(Name = "return_delivery_name")]
public string ReturnDeliveryName { get; set; }
/// <summary>
/// 售后相关商品信息JSON
/// </summary>
[Column(Name = "merchant_upload_info")]
public string MerchantUploadInfo { get; set; }
/// <summary>
/// 售后单创建时间戳
/// </summary>
[Column(Name = "create_time")]
public int? CreateTime { get; set; }
/// <summary>
/// 售后单更新时间戳
/// </summary>
[Column(Name = "update_time")]
public int? UpdateTime { get; set; }
/// <summary>
/// 退款原因
/// </summary>
[Column(Name = "reason")]
public string Reason { get; set; }
/// <summary>
/// 退款原因解释
/// </summary>
[Column(Name = "reason_text")]
public string ReasonText { get; set; }
/// <summary>
/// 售后类型。REFUND:退款;RETURN:退货退款。
/// </summary>
[Column(Name = "type")]
public string Type { get; set; }
/// <summary>
/// 订单号,该字段可用于获取订单
/// </summary>
[Column(Name = "order_id")]
public string OrderId { get; set; }
/// <summary>
/// detail json
/// </summary>
[Column(Name = "details")]
public string Details { get; set; }
/// <summary>
/// 纠纷id,该字段可用于获取纠纷信息
/// </summary>
[Column(Name = "complaint_id")]
public string ComplaintId { get; set; }
/// <summary>
/// 微信支付退款的响应
/// </summary>
[Column(Name = "refund_resp")]
public string RefundResp { get; set; }
/// <summary>
/// 推送状态(0未推送;1推送成功;2推送失败)
/// </summary>
[Column(Name = "pull_status")]
public int? PullStatus { get; set; }
/// <summary>
/// 订单审核时间
/// </summary>
[Column(Name = "pull_time")]
public DateTime? PullTime { get; set; }
/// <summary>
/// 商品id(o_goods外键)
/// </summary>
[Column(Name = "erp_goods_id")]
public long ErpGoodsId { get; set; }
/// <summary>
/// 商品skuid(o_goods_sku外键)
/// </summary>
[Column(Name = "erp_goods_sku_id")]
public long ErpGoodsSkuId { get; set; }
/// <summary>
/// 商户id
/// </summary>
[Column(Name = "merchant_id")]
public long MerchantId { get; set; }
/// <summary>
/// 仓库受理状态
/// </summary>
[Column(Name = "warehouse_status")]
public int WarehouseStatus { get; set; }
/// <summary>
/// 退货收件人姓名
/// </summary>
[Column(Name = "return_user_name")]
public string ReturnUserName { get; set; }
/// <summary>
/// 退货省份
/// </summary>
[Column(Name = "return_province")]
public string ReturnProvince { get; set; }
/// <summary>
/// 退货城市
/// </summary>
[Column(Name = "return_city")]
public string ReturnCity { get; set; }
/// <summary>
/// 退货区县
/// </summary>
[Column(Name = "return_county")]
public string ReturnCounty { get; set; }
/// <summary>
/// 退货详细地址
/// </summary>
[Column(Name = "return_detail")]
public string ReturnDetail { get; set; }
/// <summary>
/// 退货联系电话
/// </summary>
[Column(Name = "return_tel")]
public string ReturnTel { get; set; }
}
+79
View File
@@ -0,0 +1,79 @@
using Autofac;
using Autofac.Integration.WebApi;
using Autofac.Integration.WebApi;
using KJD_Web;
using KJD_Web;
using KJD_Web.Services.Shops;
using KJD_Web.Services.Users;
using Newtonsoft.Json;
using Store.Framework.DependencyInjection;
using Store.Framework.DependencyInjection;
using System;
using System;
using System.Collections.Generic;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Timers;
using System.Web.Http;
namespace WebServiceTest;
internal class ShopTest
{
private static NUnitDebugListener? _listener;
private IContainer _container;
[SetUp]
public void Setup()
{
_listener = new NUnitDebugListener();
Debug.Listeners.Add(_listener);
var config = new HttpConfiguration();
_container = ContainerConfig.BuildContainer();
config.DependencyResolver = new AutofacWebApiDependencyResolver(_container);
WebApiConfig.Register(config);
}
[TearDown]
public void TearDown()
{
_container?.Dispose();
_listener.Dispose();
}
[Test]
public void TestAm()
{
List<long> ShopIds = new List<long>
{
1960967267443631281,
1960967267443631212,
1960967267443631213,
1960967267443631215,
1960967267443631216,
1960967267443631218,
1960967267443631219,
1960967267443631221,
1960967267443631222,
1960967267443631223,
1960967267443631224,
1960967267443631226,
1960967267443631227,
1960967267443631228,
1960967267443630057,
1960967267443629984,
1960967267443629858,
1960967267443629855,
1960967267443629809,
1960967267443629737,
1960967267443627649,
1960967267443627648
};
var shopService = _container.Resolve<ShopRevenueStatisticsService>();
shopService.GetShopOrderAmountRange(ShopIds, DateTime.Now.AddMonths(-1), DateTime.Now);
}
}