增加 统计新接口

增加每日02时自动更新统计
This commit is contained in:
chfei committed 2026-10-08 12:02:41 +08:00
1 parent 3dac74c6c2
commit 22c7ecd4d8
24 files changed
+1895 -255

No files matched your search

@@ -1,14 +1,19 @@
using KJD_Web.BLL.BXX;
using KJD_Web.BLL.WXXD;
using KJD_Web.Requests.Shops;
using KJD_Web.Responses.Shops;
using KJD_Web.Services.Shops;
using KJD_Web.Utils;
using Newtonsoft.Json.Converters;
using Store.Framework.Models;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Web;
using System.Web.Services;
using System.Web.Http;
using System.Web.Services;
namespace KJD_Web.Controllers.WX
{
@@ -16,6 +21,12 @@ namespace KJD_Web.Controllers.WX
[Route("{action}")]
public class WXShopRevenueStatisticsController : AsmxApiController
{
private readonly ShopRevenueStatisticsService _statsService;
public WXShopRevenueStatisticsController(ShopRevenueStatisticsService statsService)
{
_statsService = statsService;
}
public IHttpActionResult get_shop_order_amount()
{
string jsontext = GetRequestJsonString();
@@ -25,5 +36,75 @@ namespace KJD_Web.Controllers.WX
return JsonString(result);
}
/// <summary>
/// 按天统计(兼容旧接口,等价于 GetShopStats 传 天)
/// </summary>
public IHttpActionResult GetShopStatsDetails(ShopStatsRequest request)
{
return GetShopStats(request, ShopStatsGroupType.Day);
}
public IHttpActionResult GetShopStats(ShopStatsRequest request)
{
return GetShopStats(request, request.GroupType);
}
/// <summary>
/// 商铺销售趋势:按天返回所有商铺合计的销售额和订单数(无数据的天补 0)
/// </summary>
public IHttpActionResult GetShopStatsTrend(ShopStatsRequest request)
{
List<ShopStatsTrendResponse> ret;
try
{
if (request.ShopIds == null || request.ShopIds.Count == 0)
ret = _statsService.GetShopStatsTrend(request.StartDate, request.EndDate, request.UserName);
else
ret = _statsService.GetShopStatsTrend(request.StartDate, request.EndDate, "", request.ShopIds);
return Ok(ApiResult.Success(ret));
}
catch (Exception ex)
{
return Ok(ApiResult.Fail("查询失败", ResultCode.ServerError));
}
}
/// <summary>
/// 订单明细统一入口:有效订单 / 退款订单 / 买手未退回金额的订单,三种列表返回同一结构
/// 请求参数 OrderType:All(默认全部) / Valid(有效订单) / Refund(退款订单) / BuyerUnrefunded(买手未退回金额)
/// 时间范围为订单创建时间,商铺ID为空时按用户名的商铺查询
/// </summary>
public IHttpActionResult GetShopOrderDetails(ShopStatsRequest request)
{
List<ShopOrderDetailResponse> ret;
try
{
if (request.ShopIds == null || request.ShopIds.Count == 0)
ret = _statsService.GetShopOrderDetails(request.StartDate, request.EndDate, request.OrderType, request.UserName);
else
ret = _statsService.GetShopOrderDetails(request.StartDate, request.EndDate, request.OrderType, "", request.ShopIds);
return Ok(ApiResult.Success(ret));
}
catch (Exception ex)
{
return Ok(ApiResult.Fail("查询失败", ResultCode.ServerError));
}
}
private IHttpActionResult GetShopStats(ShopStatsRequest request, ShopStatsGroupType groupType)
{
Dictionary<string, List<ShopStatsResponse>> ret;
try
{
if (request.ShopIds == null || request.ShopIds.Count == 0)
ret = _statsService.GetShopStats(request.StartDate, request.EndDate, request.UserName, null, groupType);
else
ret = _statsService.GetShopStats(request.StartDate, request.EndDate, "", request.ShopIds, groupType);
return Ok(ApiResult.Success(ret));
}
catch (Exception ex)
{
return Ok(ApiResult.Fail("查询失败", ResultCode.ServerError));
}
}
}
}
+2 -122
View File
@@ -8,6 +8,7 @@ using MAX.Core.DependencyInjection;
using MAX.Core.Logger;
using MAX.Framework.Logger;
using MySql.Data.MySqlClient;
using Newtonsoft.Json.Converters;
using NLog;
using Store.Framework.DependencyInjection;
using System;
@@ -26,6 +27,7 @@ namespace KJD_Web
{
protected void Application_Start(object sender, EventArgs e)
{
NLogger.Instance.Debug("应用程序启动");
NLogger.Instance.Info("应用程序启动");
var container = ContainerConfig.BuildContainer();
@@ -54,128 +56,6 @@ namespace KJD_Web
Context.Response.Write("Service is running.");
Context.Response.End();
}
string hPath = Request.Url.LocalPath.ToString();
//if (!hPath.Contains(".asmx") && hPath.Contains("/user/"))
//{
//hPath = hPath.Replace("/user/", "/UserService.asmx/");
//Context.RewritePath(hPath);
//}
//else if (!hPath.Contains(".asmx") && hPath.Contains("/shop/"))
//{
//hPath = hPath.Replace("/shop/", "/ShopService.asmx/");
//Context.RewritePath(hPath);
//}
//else if (!hPath.Contains(".asmx") && hPath.Contains("/update/"))
//{
//hPath = hPath.Replace("/update/", "/UpdateService.asmx/");
//Context.RewritePath(hPath);
//}
//else if (!hPath.Contains(".asmx") && hPath.Contains("/order/"))
//{
//hPath = hPath.Replace("/order/", "/OrderService.asmx/");
//Context.RewritePath(hPath);
//}
//else if (!hPath.Contains(".asmx") && hPath.Contains("/product/"))
//{
//hPath = hPath.Replace("/product/", "/ProductService.asmx/");
//Context.RewritePath(hPath);
//}
//else if (!hPath.Contains(".asmx") && hPath.Contains("/product_third/"))
//{
//hPath = hPath.Replace("/product_third/", "/ProductThirdService.asmx/");
//Context.RewritePath(hPath);
//}
//else if (!hPath.Contains(".asmx") && hPath.Contains("/price/"))
//{
//hPath = hPath.Replace("/price/", "/PriceService.asmx/");
//Context.RewritePath(hPath);
//}
//if (!hPath.Contains(".asmx") && hPath.Contains("/bxx_user/"))
//{
//hPath = hPath.Replace("/bxx_user/", "/BXXUserService.asmx/");
//Context.RewritePath(hPath);
//}
//else if (!hPath.Contains(".asmx") && hPath.Contains("/bxx_shop/"))
//{
//hPath = hPath.Replace("/bxx_shop/", "/BXXShopService.asmx/");
//Context.RewritePath(hPath);
//}
//else if (!hPath.Contains(".asmx") && hPath.Contains("/update/"))
//{
// hPath = hPath.Replace("/update/", "/UpdateService.asmx/");
// Context.RewritePath(hPath);
//}
//else if (!hPath.Contains(".asmx") && hPath.Contains("/bxx_product/"))
//{
//hPath = hPath.Replace("/bxx_product/", "/BXXProductService.asmx/");
//Context.RewritePath(hPath);
//}
//else if (!hPath.Contains(".asmx") && hPath.Contains("/bxx_product_third/"))
//{
//hPath = hPath.Replace("/bxx_product_third/", "/BXXProductThirdService.asmx/");
//Context.RewritePath(hPath);
//}
//else if (!hPath.Contains(".asmx") && hPath.Contains("/bxx_price/"))
//{
//hPath = hPath.Replace("/bxx_price/", "/BXXPriceService.asmx/");
//Context.RewritePath(hPath);
//}
//if (!hPath.Contains(".asmx") && hPath.Contains("/dyxd_user/"))
//{
//hPath = hPath.Replace("/dyxd_user/", "/DYUserService.asmx/");
//Context.RewritePath(hPath);
//}
//else if (!hPath.Contains(".asmx") && hPath.Contains("/dyxd_shop/"))
//{
//hPath = hPath.Replace("/dyxd_shop/", "/DYShopService.asmx/");
//Context.RewritePath(hPath);
//}
//else if (!hPath.Contains(".asmx") && hPath.Contains("/update/"))
//{
// hPath = hPath.Replace("/update/", "/UpdateService.asmx/");
// Context.RewritePath(hPath);
//}
//if (!hPath.Contains(".asmx") && hPath.Contains("/wxxd_user/"))
//{
// //hPath = hPath.Replace("/wxxd_user/", "/WXUserService.asmx/");
// //Context.RewritePath(hPath, false);
//}
//else if (!hPath.Contains(".asmx") && hPath.Contains("/wxxd_shop/"))
//{
// hPath = hPath.Replace("/wxxd_shop/", "/WXShopService.asmx/");
// Context.RewritePath(hPath);
//}
//else if (!hPath.Contains(".asmx") && hPath.Contains("/update/"))
//{
// hPath = hPath.Replace("/update/", "/UpdateService.asmx/");
// Context.RewritePath(hPath);
//}
//if (!hPath.Contains(".asmx") && hPath.Contains("/wxxd_product/"))
//{
//hPath = hPath.Replace("/wxxd_product/", "/WXProductService.asmx/");
//Context.RewritePath(hPath);
//}
//else if (!hPath.Contains(".asmx") && hPath.Contains("/wxxd_order/"))
//{
//hPath = hPath.Replace("/wxxd_order/", "/WXOrderService.asmx/");
//Context.RewritePath(hPath);
//}
//else if (!hPath.Contains(".asmx") && hPath.Contains("/wxxd_shop_revenue_statistics/"))
//{
//hPath = hPath.Replace("/wxxd_shop_revenue_statistics/", "/WXShopRevenueStatistics.asmx/");
//Context.RewritePath(hPath);
//}
// 请求录制:捕获请求链接与请求内容,用于迁移后的回放校验
//try { ApiRequestLogger.BeginRequest(Context); } catch { }
}
protected void Application_AuthenticateRequest(object sender, EventArgs e)
+8 -1
View File
@@ -1,4 +1,4 @@
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\packages\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.2.0.1\build\net46\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.props" Condition="Exists('..\packages\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.2.0.1\build\net46\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.props')" />
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
@@ -217,11 +217,18 @@
<Compile Include="Mappers\WeOrderMapper.cs" />
<Compile Include="Model\WxModel\oms_wei_ewaybill_path.cs" />
<Compile Include="Model\WxModel\oms_wei_transfer_extra_package.cs" />
<Compile Include="Model\WxModel\ShopRevenueStatistics.cs" />
<Compile Include="Model\WxModel\yh_sync_logistics.cs" />
<Compile Include="Requests\RequestBase.cs" />
<Compile Include="Requests\ShopRequest.cs" />
<Compile Include="Requests\Shops\ShopOrderDetailType.cs" />
<Compile Include="Requests\Shops\ShopStatsGroupType.cs" />
<Compile Include="Requests\Shops\ShopStatsRequest.cs" />
<Compile Include="Requests\Users\CUUserInfoRequest.cs" />
<Compile Include="Requests\Users\RebateRecordRequest.cs" />
<Compile Include="Responses\Shops\ShopOrderDetailResponse.cs" />
<Compile Include="Responses\Shops\ShopStatsResponse.cs" />
<Compile Include="Responses\Shops\ShopStatsTrendResponse.cs" />
<Compile Include="Responses\Users\RebateRecordResponse.cs" />
<Compile Include="Responses\Users\ShopRenewResponse.cs" />
<Compile Include="Entitys\ShopEntity.cs" />
@@ -0,0 +1,194 @@
using KJD_Web.Utils;
using MySqlX.XDevAPI.Relational;
using Store.Contracts.Order.Entitys;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace KJD_Web.Model.WxModel;
public class ShopRevenueStatistics
{
public class StatisticsBase
{
public long ShopId { get; set; }
public string OrderId { get; set; }
}
/// <summary>
/// 订单信息
/// </summary>
public class OrderInfo : StatisticsBase
{
public string ShopName { get; set; }
/// <summary>
/// 外采金额(单位:分,整数存储)
/// </summary>
public int OutAmount => BuyerOrders.Sum(a => a.OutAmount);
/// <summary>
/// 订单金额,单位为分,
/// </summary>
public int OrderPrice { get; set; }
/// <summary>
/// 平台服务费(微信小店:预计技术服务费),单位为分
/// </summary>
public int PredictCommissionFee { get; set; }
/// <summary>
/// 达人佣金(微信小店:分账金额),单位为分
/// </summary>
public int CommissionAmount { get; set; }
/// <summary>
/// 利润
/// </summary>
public int Profit { get; set; }
/// <summary>
/// 采购类型
/// </summary>
public PurchaseType PurchaseType { get; set; }
public List<OrderItemInfo> Items { get; set; } = [];
/// <summary>
/// 买手订单信息
/// </summary>
public List<BuyerOrderInfo> BuyerOrders { get; set; } = [];
public bool HasRefund => Items.Any(x => x.IsAfterSale);
public DateTime CreateTime { get; set; }
public DateTime UpdateTime { get; set; }
}
public class OrderItemInfo
{
public string OrderId { get; set; }
public string SkuId { get; set; }
/// <summary>
/// SKU价格
/// </summary>
public int SkuPrice { get; set; }
/// <summary>
/// 是否退款完成
/// </summary>
public bool IsRefundSuccessed
{
get
{
return AfterSaleStatus == AfterSaleStatus.MERCHANT_REFUND_SUCCESS || AfterSaleStatus == AfterSaleStatus.MERCHANT_RETURN_SUCCESS;
}
}
/// <summary>
/// 是否是售后订单
/// </summary>
public bool IsAfterSale
{
get
{
return AfterSaleStatus != AfterSaleStatus.NONE;
}
}
/// <summary>
/// 退款状态
/// 售后状态 1:无售后或售后关闭,2:售后处理中,3:退款中,4: 退款成功 11 退款完成订单已取消
/// </summary>
public int RefundStatus { get; set; }
public AfterSaleStatus AfterSaleStatus { get; set; } = AfterSaleStatus.NONE;
/// <summary>
/// 退款金额
/// </summary>
public int RefundAmount { get; set; }
}
public class OrderFlatInfo
{
public long ShopId { get; set; }
public string OrderId { get; set; }
public int CreateTime { get; set; }
public int UpdateTime { get; set; }
public string ShopName { get; set; }
public int OutAmount { get; set; }
public int OrderPrice { get; set; }
public int PredictCommissionFee { get; set; }
public int CommissionAmount { get; set; }
// 子表字段
public string SkuId { get; set; }
public int SkuPrice { get; set; }
public int CashCouponDiscountedPrice { get; set; }
public int RefundStatus { get; set; }
}
/// <summary>
/// 订单退款
/// </summary>
public class OrderRefundInfo : StatisticsBase
{
public string SkuId { get; set; }
/// <summary>
/// 退款状态
/// </summary>
public string Status { get; set; }
/// <summary>
/// 退款金额
/// </summary>
public int RefundAmount { get; set; }
public int CreateTime { get; set; }
}
/// <summary>
/// 买手订单
/// </summary>
public class BuyerOrderInfo : StatisticsBase
{
/// <summary>
/// 买手订单
/// </summary>
public string SkuId { get; set; }
public string OutOrderSn { get; set; }
/// <summary>
/// 购买金额(分)
/// </summary>
public int OutAmount { get; set; }
///// <summary>
///// 订单作废状态:0=正常,1=已作废,2=作废失败
///// </summary>
//public int Status { get; set; }
/// <summary>
/// 退款金额(分)
/// </summary>
public int RefundAmount { get; set; }
/// <summary>
/// 退款状态
/// </summary>
public string RefundStatusText { get; set; }
}
/// <summary>
/// 更新要素信息
/// </summary>
public class UpdateStatsInfo
{
/// <summary>
/// 商铺Id
/// </summary>
public string ShopId { get; set; }
/// <summary>
/// 订单创建时间
/// </summary>
public int OrderCreateAt { get; set; }
public DateTime OrderCreateDate => TimeUtils.StampToDateTime(OrderCreateAt).Date;
}
}
+3
View File
@@ -64,6 +64,9 @@
<!-- 映射规则 -->
<rules>
<!-- 忽略 Hangfire 的 Debug 日志 -->
<logger name="Hangfire.*" maxlevel="Debug" writeTo="BlackHole" final="true" />
<!-- 规则1: 调试级别(Trace, Debug)写入debug文件 -->
<logger name="*" minlevel="Trace" maxlevel="Debug" writeTo="debug,console" />
@@ -0,0 +1,27 @@
namespace KJD_Web.Requests.Shops;
/// <summary>
/// 订单明细类型(统一明细入口 GetShopOrderDetails 使用)
/// </summary>
public enum ShopOrderDetailType
{
/// <summary>
/// 全部订单
/// </summary>
All = 0,
/// <summary>
/// 有效订单:没有发生售后的订单(对应统计里的订单数 / 销售额 / 利润口径)
/// </summary>
Valid = 1,
/// <summary>
/// 退款订单:发生过售后的订单(对应统计里的退款订单数口径)
/// </summary>
Refund = 2,
/// <summary>
/// 买手未退回金额的订单:退款订单中买手款项还没退回的(对应统计里的未退回金额口径)
/// </summary>
BuyerUnrefunded = 3,
}
@@ -0,0 +1,27 @@
namespace KJD_Web.Requests.Shops;
/// <summary>
/// 商铺统计聚合维度
/// </summary>
public enum ShopStatsGroupType
{
/// <summary>
/// 汇总:每个商铺只返回一条(整个查询范围合计)
/// </summary>
Summary = 0,
/// <summary>
/// 天:按自然日聚合,每个商铺每天一条
/// </summary>
Day = 1,
/// <summary>
/// 周:按自然周聚合(周一为一周开始),每个商铺每周一条
/// </summary>
Week = 2,
/// <summary>
/// 月:按自然月聚合,每个商铺每月一条
/// </summary>
Month = 3,
}
@@ -0,0 +1,26 @@
using Store.Framework.Dtos;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace KJD_Web.Requests.Shops;
public class ShopStatsRequest: SignRequestBase
{
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
public List<string> ShopIds { get; set; }
/// <summary>
/// 聚合维度:汇总(默认) / 天 / 周 / 月
/// </summary>
public ShopStatsGroupType GroupType { get; set; } = ShopStatsGroupType.Summary;
/// <summary>
/// 订单明细类型(统一明细入口 GetShopOrderDetails 使用):
/// 全部(默认) / 有效订单 / 退款订单 / 买手未退回金额的订单
/// </summary>
public ShopOrderDetailType OrderType { get; set; } = ShopOrderDetailType.All;
}
@@ -0,0 +1,61 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using static KJD_Web.Model.WxModel.ShopRevenueStatistics;
namespace KJD_Web.Responses.Shops;
/// <summary>
/// 订单明细(有效订单 / 退款订单 / 买手未退回金额的订单 共用的返回结构)
/// 一个商铺订单可能对应多个买手订单(见 BuyerOrders)
/// 金额单位:分
/// </summary>
public class ShopOrderDetailResponse
{
/// <summary>
/// 店铺ID
/// </summary>
public long ShopId { get; set; }
/// <summary>
/// 店铺名称
/// </summary>
public string ShopName { get; set; }
/// <summary>
/// 商铺订单号
/// </summary>
public string OrderId { get; set; }
/// <summary>
/// 订单创建时间
/// </summary>
public DateTime CreateTime { get; set; }
/// <summary>
/// 订单金额(分)
/// </summary>
public int OrderPrice { get; set; }
/// <summary>
/// 订单退款金额(分),即商铺订单商品明细的退款金额之和;有效订单为 0
/// </summary>
public int OrderRefundAmount { get; set; }
/// <summary>
/// 外采金额(分),即该订单下所有买手订单的购买金额之和
/// </summary>
public int OutAmount { get; set; }
/// <summary>
/// 买手未退回金额(分),即买手订单中退款未成功的退款金额之和;无售后时为 0
/// </summary>
public int UnrefundedAmount { get; set; }
/// <summary>
/// 买手订单信息(一个订单号对应多个买手订单)
/// 字段含义:OutOrderSn=外部订单(买手订单)号,OutAmount=金额,RefundAmount=退款金额,RefundStatusText=退款状态
/// </summary>
public List<BuyerOrderInfo> BuyerOrders { get; set; } = [];
}
@@ -0,0 +1,89 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace KJD_Web.Responses.Shops;
/// <summary>
/// 商铺统计记录
/// </summary>
public class ShopStatsResponse
{
/// <summary>
/// 店铺ID
/// </summary>
public string ShopId { get; set; }
/// <summary>
/// 销售订单创建日期
/// </summary>
public DateTime StatDate { get; set; }
/// <summary>
/// 订单数
/// </summary>
public int OrderCount { get; set; }
/// <summary>
/// 销售总额
/// </summary>
public long SalesAmount { get; set; }
/// <summary>
/// 利润
/// </summary>
public long ProfitAmount { get; set; }
/// <summary>
/// 平台佣金
/// </summary>
public long PlatformFee { get; set; }
/// <summary>
/// 退款订单数
/// </summary>
public int RefundOrderCount { get; set; }
/// <summary>
/// 已成功退款金额
/// </summary>
public long RefundDone { get; set; }
/// <summary>
/// 退款处理中金额
/// </summary>
public long RefundPending { get; set; }
/// <summary>
/// 采购单数
/// </summary>
public int PurchaseCount { get; set; }
/// <summary>
/// 总花费金额
/// </summary>
public long TotalPurchase { get; set; }
/// <summary>
/// 售后中买手总金额
/// </summary>
public long TotalRefund => DoneRefund + PendingRefund + RejectedRefund;
/// <summary>
/// 已成功退款金额
/// </summary>
public long DoneRefund { get; set; }
/// <summary>
/// 未退回金额
/// </summary>
public long PendingRefund { get; set; }
/// <summary>
/// 被拒绝金额
/// </summary>
public long RejectedRefund { get; set; }
}
@@ -0,0 +1,24 @@
using System;
namespace KJD_Web.Responses.Shops;
/// <summary>
/// 商铺销售趋势(按天,所有商铺合计)
/// </summary>
public class ShopStatsTrendResponse
{
/// <summary>
/// 统计日期(天)
/// </summary>
public DateTime StatDate { get; set; }
/// <summary>
/// 销售额(分)
/// </summary>
public long SalesAmount { get; set; }
/// <summary>
/// 订单数
/// </summary>
public int OrderCount { get; set; }
}
@@ -1,12 +1,23 @@
using KJD_Web.Model.WxModel;
using KJD_Web.Model.WxModel;
using KJD_Web.Requests.Shops;
using KJD_Web.Responses.Shops;
using KJD_Web.Services.Users;
using KJD_Web.Utils;
using MAX.Core.DependencyInjection;
using MAX.Core.Logger;
using MySqlX.XDevAPI.Relational;
using Store.Contracts.Order.Entitys;
using Store.Contracts.Shops;
using Store.Contracts.User.Interfaces;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.Remoting.Metadata.W3cXsd2001;
using System.Text.RegularExpressions;
using System.Web;
using static FreeSql.Internal.GlobalFilter;
using static KJD_Web.Model.WxModel.ShopRevenueStatistics;
namespace KJD_Web.Services.Shops;
@@ -15,137 +26,779 @@ namespace KJD_Web.Services.Shops;
/// </summary>
public class ShopRevenueStatisticsService : ITransientDependency
{
private const int BatchSize = 500;
private readonly IFreeSql _freeSql;
private readonly ILoggerFacade _logger;
private readonly IUserService _userService;
private readonly ShopService _shopService;
public ShopRevenueStatisticsService(IFreeSql freeSql, ILoggerFacade logger)
public ShopRevenueStatisticsService(IFreeSql freeSql, ILoggerFacade logger, IUserService userService, ShopService shopService)
{
_freeSql = freeSql;
_logger = logger;
_userService = userService;
_shopService = shopService;
}
/// <summary>
/// 获取商铺统计
/// 汇总:每个商铺只返回一条(整个查询范围合计,StatDate 为查询结束日期)
/// 天/周/月:按自然日 / 自然周(周一为开始)/ 自然月聚合,每个周期一条(StatDate 为该周期的起始日期)
/// </summary>
/// <param name="startTime">统计开始时间(含)</param>
/// <param name="endTime">统计结束时间(含)</param>
/// <param name="userName">用户名(未指定商铺ID时,按该用户名下的商铺统计)</param>
/// <param name="shopIds">指定商铺ID集合(为空时按用户名统计)</param>
/// <param name="groupType">聚合维度,默认汇总</param>
/// <returns>商铺ID -> 该商铺按维度聚合后的统计列表</returns>
public Dictionary<string, List<ShopStatsResponse>> GetShopStats(DateTime startTime, DateTime endTime, string userName, List<string> shopIds = null, ShopStatsGroupType groupType = ShopStatsGroupType.Summary)
{
var result = new Dictionary<string, List<ShopStatsResponse>>();
(Dictionary<string, List<ShopDailyStatsEntity>> shopGroups, Dictionary<string, List<BuyerDailyStatsEntity>> buyerGroups) ret;
if (shopIds != null && shopIds.Count > 0)
ret = getShopStats(startTime, endTime, shopIds);
else
ret = getShopStatsByUserName(startTime, endTime, userName);
if (ret.shopGroups == null || ret.buyerGroups == null) return result;
// 汇总维度下所有日期归到同一组,用查询结束日期作为该组的标识
DateTime summaryKey = endTime.Date;
foreach (var id in ret.shopGroups.Keys.Union(ret.buyerGroups.Keys))
{
ret.shopGroups.TryGetValue(id, out var sList);
ret.buyerGroups.TryGetValue(id, out var bList);
sList = sList ?? new List<ShopDailyStatsEntity>();
bList = bList ?? new List<BuyerDailyStatsEntity>();
// 同一商铺内先按聚合维度分组(同一周期内的多条明细在下面求和)
var sDict = sList
.GroupBy(x => GetGroupKey(x.StatDate, groupType, summaryKey))
.ToDictionary(g => g.Key, g => g.ToList());
var bDict = bList
.GroupBy(x => GetGroupKey(x.StatDate, groupType, summaryKey))
.ToDictionary(g => g.Key, g => g.ToList());
var list = new List<ShopStatsResponse>();
foreach (var key in sDict.Keys.Union(bDict.Keys).OrderBy(d => d))
{
var resp = new ShopStatsResponse { ShopId = id, StatDate = key };
if (sDict.TryGetValue(key, out var ss))
{
resp.OrderCount = ss.Sum(x => x.OrderCount);
resp.SalesAmount = ss.Sum(x => x.SalesAmount);
resp.ProfitAmount = ss.Sum(x => x.ProfitAmount);
resp.PlatformFee = ss.Sum(x => x.PlatformFee);
resp.RefundOrderCount = ss.Sum(x => x.RefundOrderCount);
resp.RefundDone = ss.Sum(x => x.RefundDone);
resp.RefundPending = ss.Sum(x => x.RefundPending);
}
if (bDict.TryGetValue(key, out var bb))
{
resp.PurchaseCount = bb.Sum(x => x.PurchaseCount);
resp.TotalPurchase = bb.Sum(x => x.TotalPurchase);
resp.DoneRefund = bb.Sum(x => x.DoneRefund);
resp.PendingRefund = bb.Sum(x => x.PendingRefund);
resp.RejectedRefund = bb.Sum(x => x.RejectedRefund);
}
list.Add(resp);
}
result[id] = list;
}
return result;
}
public void GetShopOrderAmountRange(List<long> shopIds, DateTime startTime, DateTime endTime)
/// <summary>
/// 获取商铺销售趋势(按天,所有商铺合计)
/// 返回查询范围内每一天的销售额和订单数,没有数据的天补 0,保证日期连续
/// </summary>
/// <param name="startTime">统计开始时间(含)</param>
/// <param name="endTime">统计结束时间(含)</param>
/// <param name="userName">用户名(未指定商铺ID时,按该用户名下的商铺统计)</param>
/// <param name="shopIds">指定商铺ID集合(为空时按用户名统计)</param>
public List<ShopStatsTrendResponse> GetShopStatsTrend(DateTime startTime, DateTime endTime, string userName, List<string> shopIds = null)
{
int startTimestamp = TimeUtils.DateTimeToTimestampSeconds(startTime);
int endTimestamp = TimeUtils.DateTimeToTimestampSeconds(endTime);
var result = new List<ShopStatsTrendResponse>();
var ids = shopIds != null && shopIds.Count > 0 ? shopIds : getShopIdsByUserName(userName);
if (ids == null || ids.Count == 0) return result;
if (endTime.Date < startTime.Date) return result;
// 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
// 所有商铺先按天合计
var byDate = loadShopStats(startTime, endTime, ids)
.GroupBy(x => x.StatDate.Date)
.ToDictionary(g => g.Key, g => new ShopStatsTrendResponse
{
StatDate = g.Key,
SalesAmount = g.Sum(x => x.SalesAmount),
OrderCount = g.Sum(x => x.OrderCount),
});
// 再按天逐个输出,缺失的日期补 0
for (var date = startTime.Date; date <= endTime.Date; date = date.AddDays(1))
{
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
});
if (!byDate.TryGetValue(date, out var item))
item = new ShopStatsTrendResponse { StatDate = date };
// 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
}));
result.Add(item);
}
// 3. 内存里 group,每个订单取 id 最大的那条
var latestRefund = refunds
.GroupBy(r => r.OrderId)
.ToDictionary(g => g.Key, g => g.OrderByDescending(r => r.Id).First());
return result;
}
// 4. 回填(等价 LEFT JOIN + IFNULL)
foreach (var o in orders)
/// <summary>
/// 订单明细统一入口:有效订单 / 退款订单 / 买手未退回金额的订单,三种列表返回同一结构
/// 按订单创建时间范围 + 商铺ID查询(未指定商铺ID时按用户名下的商铺查询)
/// 有效订单:没有发生售后的订单;退款订单:发生过售后的订单;买手未退回:退款订单中买手款项还没退回的
/// 未退回:买手订单存在退款信息(退款金额 &gt; 0 或退款状态非空)且退款未成功(不是"退款成功"),
/// 未退回金额 = 这些买手订单的退款金额之和(与统计里的"未退回金额/PendingRefund"口径一致)
/// 一个订单号可能对应多个买手订单(BuyerOrders),金额单位:分
/// 与统计口径保持一致:只统计单商品订单(多商品订单视为刷单,统计时已排除)
/// </summary>
/// <param name="startTime">订单创建开始时间(含)</param>
/// <param name="endTime">订单创建结束时间(含)</param>
/// <param name="orderType">明细类型:全部 / 有效订单 / 退款订单 / 买手未退回金额的订单</param>
/// <param name="userName">用户名(未指定商铺ID时,按该用户名下的商铺查询)</param>
/// <param name="shopIds">指定商铺ID集合(为空时按用户名查询)</param>
/// <returns>按订单创建时间倒序的订单明细</returns>
public List<ShopOrderDetailResponse> GetShopOrderDetails(DateTime startTime, DateTime endTime, ShopOrderDetailType orderType = ShopOrderDetailType.All, string userName = null, List<string> shopIds = null)
{
var result = new List<ShopOrderDetailResponse>();
if (endTime < startTime) return result;
var ids = shopIds != null && shopIds.Count > 0 ? shopIds : getShopIdsByUserName(userName);
if (ids == null || ids.Count == 0) return result;
// 订单表的 shop_id 是数字,这里做一次转换
var orderShopIds = new List<long>();
foreach (var id in ids)
{
if (latestRefund.TryGetValue(o.order_id, out var r))
if (long.TryParse(id, out long shopId) && !orderShopIds.Contains(shopId))
orderShopIds.Add(shopId);
}
if (orderShopIds.Count == 0) return result;
int startStamp = TimeUtils.DateTimeToTimestampSeconds(startTime);
int endStamp = TimeUtils.DateTimeToTimestampSeconds(endTime) + 1; // 结束时间含当天/当秒
var orders = LoadOrders(startStamp, endStamp, orderShopIds);
if (orders.Count == 0) return result;
// 加载买手订单(直拍和中转仓都要查),并回填商铺订单的退款金额
LoadBuyerOrder(orders, true);
FillRefunds(orders);
foreach (var order in orders)
{
var buyerOrders = order.BuyerOrders ?? new List<BuyerOrderInfo>();
bool hasRefund = order.HasRefund;
if (orderType == ShopOrderDetailType.BuyerUnrefunded)
{
o.refund_status_string = r.Status;
o.refund_amount = r.RefundAmount.Value;
buyerOrders = buyerOrders.Where(a => !IsRefundDone(a.RefundStatusText)).ToList();
}
else
int unrefundedAmount = GetBuyerUnrefundedAmount(buyerOrders);
switch (orderType)
{
o.refund_amount = 0;
case ShopOrderDetailType.Valid: // 有效订单:没有售后
if (hasRefund) continue;
break;
case ShopOrderDetailType.Refund: // 退款订单:发生过售后
if (!hasRefund) continue;
break;
case ShopOrderDetailType.BuyerUnrefunded: // 买手未退回金额的订单
if (!hasRefund || unrefundedAmount <= 0) continue;
break;
}
result.Add(new ShopOrderDetailResponse
{
ShopId = order.ShopId,
ShopName = order.ShopName,
OrderId = order.OrderId,
CreateTime = order.CreateTime,
OrderPrice = order.OrderPrice,
OrderRefundAmount = order.Items.Sum(a => a.RefundAmount),
OutAmount = order.OutAmount,
UnrefundedAmount = unrefundedAmount,
BuyerOrders = buyerOrders,
});
}
return result.OrderByDescending(a => a.CreateTime).ToList();
}
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()
/// <summary>
/// 买手未退回金额:不是"退款成功" 状态的买手订单退款金额之和
/// 只算有退款信息的(退款金额 &gt; 0 或退款状态非空),没发生退款的正常订单不计入
/// </summary>
private static int GetBuyerUnrefundedAmount(List<BuyerOrderInfo> buyerOrders)
{
if (buyerOrders == null || buyerOrders.Count == 0) return 0;
return buyerOrders
.Where(a => !IsRefundDone(a.RefundStatusText))
.Where(a => a.RefundAmount > 0 || !string.IsNullOrEmpty(a.RefundStatusText))
.Sum(a => a.RefundAmount);
}
/// <summary>
/// 取某个日期在指定聚合维度下所属周期的起始日期
/// </summary>
/// <param name="date">明细日期</param>
/// <param name="groupType">聚合维度</param>
/// <param name="summaryKey">汇总维度使用的分组标识</param>
private static DateTime GetGroupKey(DateTime date, ShopStatsGroupType groupType, DateTime summaryKey)
{
switch (groupType)
{
case ShopStatsGroupType.Day:
return date.Date;
case ShopStatsGroupType.Week:
// 周一为一周的开始
return date.Date.AddDays(-(((int)date.DayOfWeek + 6) % 7));
case ShopStatsGroupType.Month:
return new DateTime(date.Year, date.Month, 1);
default:
// 汇总:所有日期归到同一组
return summaryKey;
}
}
private (Dictionary<string, List<ShopDailyStatsEntity>> shopGroups, Dictionary<string, List<BuyerDailyStatsEntity>> buyerGroups) getShopStatsByUserName(DateTime startTime, DateTime endTime, string userName)
{
var shopIds = getShopIdsByUserName(userName);
if (shopIds == null) return (null, null);
return getShopStats(startTime, endTime, shopIds);
}
/// <summary>按用户名取商铺ID集合,取不到返回 null</summary>
private List<string> getShopIdsByUserName(string userName)
{
if (string.IsNullOrEmpty(userName)) return null;
var shopIds = _shopService.GetShopIds(userName).Select(a => a.ToString()).ToList();
return shopIds.Count == 0 ? null : shopIds;
}
/// <summary>加载指定商铺在某时间范围内的商铺日统计</summary>
private List<ShopDailyStatsEntity> loadShopStats(DateTime startTime, DateTime endTime, List<string> shopIds)
{
return _freeSql.Select<ShopDailyStatsEntity>()
.Where(a => shopIds.Contains(a.ShopId))
.Where(a => a.StatDate >= startTime)
.Where(a => a.StatDate <= endTime)
.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)
private (Dictionary<string, List<ShopDailyStatsEntity>> shopGroups, Dictionary<string, List<BuyerDailyStatsEntity>> buyerGroups) getShopStats(DateTime startTime, DateTime endTime, List<string> shopIds)
{
if (shopIds == null || shopIds.Count() == 0) return (null, null);
var shopStats = loadShopStats(startTime, endTime, shopIds);
var buyerStats = _freeSql.Select<BuyerDailyStatsEntity>()
.Where(a => shopIds.Contains(a.ShopId))
.Where(a => a.StatDate >= startTime)
.Where(a => a.StatDate <= endTime)
.ToList();
// 先按商铺分组
Dictionary<string, List<ShopDailyStatsEntity>> shopGroups = shopStats.GroupBy(x => x.ShopId).ToDictionary(g => g.Key, g => g.ToList());
Dictionary<string, List<BuyerDailyStatsEntity>> buyerGroups = buyerStats.GroupBy(x => x.ShopId).ToDictionary(g => g.Key, g => g.ToList());
return (shopGroups, buyerGroups);
}
/// <summary>商铺统计指定天(同时会统计买手数据)</summary>
public void ShopDailyStats(DateTime date, List<long> shopId = null)
{
var orders = LoadOrders(date, shopId);
LoadBuyerOrder(orders);
FillRefunds(orders);
CalcProfit(orders);
SaveShopStats(date.Date, orders);
SaveBuyerStats(date.Date, orders);
}
/// <summary>买手统计指定天</summary>
public void BuyerDailyStats(DateTime date, List<long> shopId = null)
{
var orders = LoadOrders(date, shopId);
LoadBuyerOrder(orders);
SaveBuyerStats(date.Date, orders);
}
/// <summary>
/// 更新
/// </summary>
public void UpdateStats(DateTime date)
{
/*
商铺新增订单创建时间
商铺退款状态更新时间
买手购买记录增加创建时间
买手退款表更新时间
*/
DateTime startDate = date.Date; // 当天0点
DateTime endDate = date.Date.AddDays(1);// 次日0点
int startUnix = TimeUtils.DateTimeToTimestampSeconds(startDate);
int endUnix = TimeUtils.DateTimeToTimestampSeconds(endDate);
List<string> allUpdateOrderIds = new List<string>();
//获取新增订单
allUpdateOrderIds.AddRange(_freeSql.Select<WeiOrderEntity>()
.Where(a => a.CreateTime >= startUnix && a.CreateTime < endUnix)
.ToList(a => a.OrderId));
//获取退款信息
allUpdateOrderIds.AddRange(_freeSql.Select<WeiRefundEntity>()
.Where(a => a.UpdateTime >= startUnix && a.UpdateTime < endUnix)
.ToList(a => a.OrderId));
//获取买手购买
//云仓
allUpdateOrderIds.AddRange(_freeSql.Select<WeiExternalProcurementLogEntity>()
.Where(a => a.CreatedTime >= startDate && a.CreatedTime < endDate)
.ToList(a => a.OrderNo));
//直发
allUpdateOrderIds.AddRange(_freeSql.Select<OrderPddBuyerOrderEntity>()
.Where(a => a.AddTime >= startDate && a.AddTime < endDate)
.ToList(a => a.WxOrderId));
//买手退款
allUpdateOrderIds.AddRange(_freeSql.Select<OrderPddBuyerAfterSaleEntity>()
.Where(a => a.MdyTime >= startDate && a.MdyTime < endDate)
.ToList(a => a.WxOrderId));
allUpdateOrderIds = allUpdateOrderIds.Distinct().ToList();//合并
List<UpdateStatsInfo> allUpdateStats = new List<UpdateStatsInfo>();
if (allUpdateOrderIds.Count > 0)
{
//查询订单归属商铺和时间
foreach (string[] batch in Batch(allUpdateOrderIds, BatchSize))
{
allUpdateStats.AddRange(_freeSql.Select<WeiOrderEntity>()
.Where(a => batch.Contains(a.OrderId))
.ToList(a => new UpdateStatsInfo
{
ShopId = a.ShopId.ToString(),
OrderCreateAt = a.CreateTime ?? 0,
}));
if (allUpdateStats.Any(a => a.OrderCreateDate < DateTime.Now.AddMonths(-1)))
{
if (string.IsNullOrEmpty(t.OrderNo)) continue;
if (!transitOutAmountMap.ContainsKey(t.OrderNo))
transitOutAmountMap[t.OrderNo] = (int)(t.OutAmount ?? 0);
var list = allUpdateStats.Where(a => a.OrderCreateDate < DateTime.Now.AddMonths(-1)).ToList();
}
}
}
foreach (var o in orders)
//重新计算指定商铺指定时间
if (allUpdateStats.Count > 0)
{
// 1. 外采金额补全
if (o.out_amount == 0 && transitOutAmountMap.TryGetValue(o.order_id, out var transitAmount))
// 1. 按日期分组,找出"ShopId 数量 > 80%"的日期
Dictionary<DateTime, List<long>> dict = allUpdateStats
.GroupBy(x => x.OrderCreateDate)
.ToDictionary(a => a.Key, a => a.Select(a => long.Parse(a.ShopId)).ToList());
// 用 HashSet 加速 Contains
// 2. 这些日期走"全量执行":对所有商铺跑
foreach (var item in dict)
{
o.external_procurement = 1;
o.out_amount = transitAmount;
if (item.Value.Count > 1000)
{
ShopDailyStats(item.Key);
}
else
{
ShopDailyStats(item.Key, item.Value);
}
string log = $"更新统计:{item.Key.ToString("yyyy/MM/dd")} {item.Value.Count}条商铺";
_logger.Info(log);
Debug.WriteLine(log);
}
// 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)//退款利润计算
#region 数据加载
/// <summary>加载某天的订单(只保留单商品订单)</summary>
private List<OrderInfo> LoadOrders(DateTime date, List<long> shopId)
{
int start = TimeUtils.DateTimeToTimestampSeconds(date.Date); // 当天0点
int end = start + 86400; // 次日0点
return LoadOrders(start, end, shopId);
}
/// <summary>
/// 按时间范围加载订单(秒级时间戳,左闭右开 [startStamp, endStamp))
/// 只保留单商品订单;shopIds 为空时不限制商铺
/// </summary>
private List<OrderInfo> LoadOrders(int startStamp, int endStamp, List<long> shopIds)
{
var query = _freeSql.Select<WeiOrderEntity, WeiOrderItemEntity>()
.InnerJoin((a, b) => a.OrderId == b.OrderId)
.Where((a, b) => a.CreateTime >= startStamp && a.CreateTime < endStamp)
.Where((a, b) => a.Status > 10);
if (shopIds != null && shopIds.Count > 0)
query = query.Where((a, b) => shopIds.Contains(a.ShopId));
var flatList = query.ToList((a, b) => new OrderFlatInfo
{
OrderId = a.OrderId,
ShopId = a.ShopId,
ShopName = a.ShopName,
OutAmount = a.OutAmount,
OrderPrice = a.OrderPrice ?? 0,
PredictCommissionFee = a.PredictCommissionFee ?? 0,
CommissionAmount = a.CommissionAmount,
CreateTime = a.CreateTime ?? 0,
UpdateTime = a.UpdateTime ?? 0,
SkuId = b.SkuId,
SkuPrice = b.EstimatePrice ?? 0,
CashCouponDiscountedPrice = b.CashCouponDiscountedPrice ?? 0,
RefundStatus = b.RefundStatus,
});
var orders = new List<OrderInfo>();
foreach (var g in flatList.GroupBy(x => x.OrderId))
{
var rows = g.ToList();
if (rows.Count > 1) continue; // 暂时不记录多商品订单,可能是刷单
var first = rows[0];
orders.Add(new OrderInfo
{
o.profit = o.order_price - o.refund_amount;
ShopId = first.ShopId,
OrderId = first.OrderId,
ShopName = first.ShopName,
PurchaseType = first.OutAmount > 0 ? PurchaseType.DirectPurchase : PurchaseType.FromWarehouse,
OrderPrice = first.OrderPrice,
PredictCommissionFee = first.PredictCommissionFee,
CommissionAmount = first.CommissionAmount,
CreateTime = TimeUtils.StampToDateTime(first.CreateTime),
UpdateTime = TimeUtils.StampToDateTime(first.UpdateTime),
Items = rows.Select(x => new OrderItemInfo
{
OrderId = x.OrderId,
SkuId = x.SkuId,
SkuPrice = x.SkuPrice + x.CashCouponDiscountedPrice,
RefundStatus = x.RefundStatus
}).ToList()
});
}
return orders;
}
/// <summary>
/// 加载买手订单信息
/// </summary>
/// <param name="orders">商铺订单</param>
/// <param name="byPurchaseType">
/// true:按订单的采购类型区分直拍 / 中转仓,两张买手表都能查到(查询明细用)
/// false:沿用历史口径,按 OutAmount 是否为 0 判断(统计任务保持原行为不变)
/// </param>
private void LoadBuyerOrder(List<OrderInfo> orders, bool byPurchaseType = false)
{
var result = new List<BuyerOrderInfo>();
//查询中转仓购买表
var outWarehouseOrderIds = orders
.Where(a => byPurchaseType ? a.PurchaseType == PurchaseType.FromWarehouse : a.OutAmount == 0)
.Select(a => a.OrderId).Distinct().ToList();
if (outWarehouseOrderIds.Count > 0)
{
//查询中转仓购买表
foreach (string[] batch in Batch(outWarehouseOrderIds, BatchSize))
{
result.AddRange(_freeSql.Select<WeiExternalProcurementLogEntity>()
.Where(a => batch.Contains(a.OrderNo))
.OrderBy(a => a.Id) //
.ToList(a => new BuyerOrderInfo
{
OrderId = a.OrderNo,
SkuId = a.OrderSkuId,
OutOrderSn = a.OutOrderNo,
OutAmount = a.OutAmount ?? 0,
}));
}
}
//查询直拍表
var outDirectOrderIds = orders
.Where(a => byPurchaseType ? a.PurchaseType == PurchaseType.DirectPurchase : a.OutAmount > 0)
.Select(a => a.OrderId).Distinct().ToList();
foreach (string[] batch in Batch(outDirectOrderIds, BatchSize))
{
result.AddRange(_freeSql.Select<OrderPddBuyerOrderEntity>()
.Where(a => a.WxSendType == 1)
.Where(a => batch.Contains(a.WxOrderId))
.ToList(a => new BuyerOrderInfo
{
OrderId = a.WxOrderId,
SkuId = a.WxSkuId,
OutOrderSn = a.OrderSn,
OutAmount = a.OrderAmount ?? 0,
}));
}
var orderDict = orders.ToDictionary(o => o.OrderId);
foreach (var b in result)
{
if (orderDict.TryGetValue(b.OrderId, out var o))
{
b.ShopId = o.ShopId;
o.BuyerOrders.Add(b);
}
}
//买手退款信息
var ids = orders
.Where(a => a.OutAmount > 0 && a.Items.Any(b => b.RefundStatus > 1))
.Select(a => a.OrderId).Distinct().ToList();
var refundlist = new List<BuyerOrderInfo>();
foreach (var batch in Batch(ids, BatchSize))
{
refundlist.AddRange(_freeSql.Select<OrderPddBuyerAfterSaleEntity>()
.Where(a => batch.Contains(a.WxOrderId))
.OrderBy(a => a.Id)
.ToList(a => new BuyerOrderInfo
{
OrderId = a.WxOrderId,
SkuId = a.WxSkuId,
OutOrderSn = a.OrderSn,
OutAmount = a.OrderAmount ?? 0,
RefundAmount = a.RefundAmount ?? 0,
RefundStatusText = a.StatusText
}));
}
//填充买手订单退款信息
foreach (var r in refundlist)
{
if (orderDict.TryGetValue(r.OrderId, out var o))
{
r.ShopId = o.ShopId;
var buyerOrder = o.BuyerOrders.FirstOrDefault(a => a.OutOrderSn == r.OutOrderSn);
if (buyerOrder != null)//补充退款信息
{
buyerOrder.RefundAmount = r.RefundAmount;
buyerOrder.RefundStatusText = r.RefundStatusText;
}
else
{
o.BuyerOrders.Add(r);
}
}
}
}
/// <summary>回填商铺订单的退款信息</summary>
private void FillRefunds(List<OrderInfo> orders)
{
var refundOrderIds = orders
.Where(a => a.Items.Any(b => b.RefundStatus > 1))
.Select(a => a.OrderId).Distinct().ToList();
if (refundOrderIds.Count == 0) return;
var allRefunds = new List<OrderRefundInfo>();
foreach (var batch in Batch(refundOrderIds, BatchSize))
{
allRefunds.AddRange(_freeSql.Select<WeiRefundEntity>()
.Where(a => batch.Contains(a.OrderId))
.ToList(a => new OrderRefundInfo
{
OrderId = a.OrderId,
SkuId = a.SkuId,
RefundAmount = a.RefundAmount ?? 0,
Status = a.Status,
CreateTime = a.CreateTime ?? 0
}));
}
// 按 (OrderId, SkuId) 分组,取 CreateTime 最新一条
var refundDict = allRefunds
.GroupBy(x => (x.OrderId, x.SkuId))
.ToDictionary(g => g.Key, g => g.OrderByDescending(x => x.CreateTime).First());
foreach (var item in orders.SelectMany(o => o.Items))
{
if (!refundDict.TryGetValue((item.OrderId, item.SkuId), out var refund)) continue;
item.RefundAmount = refund.RefundAmount;
if (Enum.TryParse(refund.Status, out AfterSaleStatus v))
item.AfterSaleStatus = v;
}
}
#endregion
#region 计算与保存
/// <summary>
/// 计算利润
/// </summary>
/// <param name="orders"></param>
private static void CalcProfit(List<OrderInfo> orders)
{
foreach (var order in orders)
{
order.Profit = order.HasRefund
? order.OrderPrice - order.Items.Sum(a => a.RefundAmount)
: order.OrderPrice - order.OutAmount - order.PredictCommissionFee - order.CommissionAmount;
}
}
private void SaveShopStats(DateTime statDate, List<OrderInfo> orders)
{
var statsList = new List<ShopDailyStatsEntity>();
foreach (var shopGroup in orders.GroupBy(o => o.ShopId))
{
var valid = shopGroup.Where(a => !a.HasRefund).ToList();
var afterSale = shopGroup.Where(a => a.HasRefund).ToList();
var afterSaleItems = afterSale.SelectMany(o => o.Items).ToList();
statsList.Add(new ShopDailyStatsEntity
{
ShopId = shopGroup.Key.ToString(),
StatDate = statDate,
OrderCount = valid.Count,
SalesAmount = valid.Sum(o => (long)o.OrderPrice),
ProfitAmount = valid.Sum(o => (long)o.Profit),
PlatformFee = valid.Sum(o => (long)o.PredictCommissionFee),
RefundOrderCount = afterSale.Count,
RefundDone = afterSaleItems.Where(i => i.IsRefundSuccessed)
.Sum(i => (long)i.RefundAmount),
RefundPending = afterSaleItems.Where(i => !i.IsRefundSuccessed && i.IsAfterSale)
.Sum(i => (long)i.RefundAmount),
});
}
if (statsList.Count == 0) return; // 空集合不要提交
_freeSql.InsertOrUpdate<ShopDailyStatsEntity>()
.SetSource(statsList, a => new { a.ShopId, a.StatDate })
.ExecuteAffrows();
}
private void SaveBuyerStats(DateTime statDate, List<OrderInfo> orders)
{
if (orders == null || orders.Count == 0) return;
var statsList = new List<BuyerDailyStatsEntity>();
// 按店铺分组,每个店铺下展开所有买手订单
foreach (var shopGroup in orders.GroupBy(o => o.ShopId))
{
var buyerOrders = shopGroup
.Where(o => o.BuyerOrders != null)
.SelectMany(o => o.BuyerOrders)
.ToList();
if (buyerOrders.Count == 0) continue;
// 该店铺当天没有采购单,不写入
// 同一个外部订单可能有多条记录,按 OutOrderSn 再分组
// OutOrderSn 为空时用 SkuId 兜底,避免多个空单号被合并成一个
var outOrderGroups = buyerOrders
.GroupBy(b => b.OutOrderSn)
.ToList();
long done = 0, pending = 0, rejected = 0;
long totalPurchase = 0;
foreach (var g in outOrderGroups)
{
//购买金额:同一外部订单的多条记录可能是退款记录重复展开,购买金额只算一次
totalPurchase += g.Max(x => x.OutAmount);
// 只要有一条退款成功,整个外部订单视为退款成功
var successRow = g.FirstOrDefault(x => IsRefundDone(x.RefundStatusText));
if (successRow != null)
{
done += successRow.RefundAmount;
continue;
}
// 没有任何退款信息,说明没发生退款
var refundRows = g.Where(x => x.RefundAmount > 0 || !string.IsNullOrEmpty(x.RefundStatusText)).ToList();
if (refundRows.Count == 0) continue;
// 没有成功记录:视为未成功,金额取该订单下最大的退款金额,避免重复累加
long refundAmount = refundRows.Max(x => x.RefundAmount);
if (refundRows.Any(x => IsRefundRejected(x.RefundStatusText)))
rejected += refundAmount;
else
pending += refundAmount;
}
statsList.Add(new BuyerDailyStatsEntity
{
ShopId = shopGroup.Key.ToString(),
BuyerId = "",
StatDate = statDate.Date,
PurchaseCount = outOrderGroups.Count, // 按外部订单数统计
TotalPurchase = totalPurchase,
DoneRefund = done,
PendingRefund = pending,
RejectedRefund = rejected,
});
}
if (statsList.Count == 0) return;
_freeSql.InsertOrUpdate<BuyerDailyStatsEntity>()
.SetSource(statsList, a => new { a.ShopId, a.StatDate })
.ExecuteAffrows();
}
#endregion
#region 辅助
/// <summary>.NET Framework 没有 Enumerable.Chunk,自己分批</summary>
private static IEnumerable<string[]> Batch(IList<string> source, int size)
{
for (int i = 0; i < source.Count; i += size)
yield return source.Skip(i).Take(size).ToArray();
}
private static bool IsRefundDone(string statusText)
{
return !string.IsNullOrEmpty(statusText) && statusText == "退款成功";
}
private static bool IsRefundRejected(string statusText)
{
// TODO: 目前业务上暂不区分"被拒绝",需要时放开下面逻辑
return false;
//return !string.IsNullOrEmpty(statusText)
// && (statusText.Contains("拒绝") || statusText.Contains("驳回") || statusText.Contains("失败"));
}
#endregion
}
+21 -1
View File
@@ -21,11 +21,15 @@ public class ShopService : ITransientDependency
_logger = logger;
_userService = userService;
}
public long GetShopCount()
{
return _freeSql.Select<ShopEntity>().Count();
}
/// <summary>
/// 修改商铺永不过期
/// </summary>
public bool UpdateShopNeverExpires(string userName, string shopId,string ownerUser, bool isNever)
public bool UpdateShopNeverExpires(string userName, string shopId, string ownerUser, bool isNever)
{
if (string.IsNullOrEmpty(userName)) return false;
if (string.IsNullOrEmpty(shopId)) return false;
@@ -39,4 +43,20 @@ public class ShopService : ITransientDependency
.ExecuteAffrows();
return affrows == 1;
}
public List<ShopEntity> GetShops(string userName)
{
return _freeSql.Select<ShopEntity>()
.Where(a => a.UserName == userName)
.ToList();
}
public List<long> GetShopIds(string userName)
{
var shops = GetShops(userName);
var shopAppkeys = shops.Select(a => a.ShopId).Distinct().ToList();
return _freeSql.Select<WXShopEntity>()
.Where(a => shopAppkeys.Contains(a.AppKey))
.ToList(a => a.Id);
}
}
@@ -0,0 +1,28 @@
using MAX.Core.DependencyInjection;
using Store.Framework.Jobs;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Web;
namespace KJD_Web.Services.Shops;
/// <summary>
/// 更新商铺统计
/// </summary>
[Job("ShopStats", "0 2 * * *")]
public class ShopStatsJob : IScheduledJobs, ITransientDependency
{
private readonly ShopRevenueStatisticsService _shopRevenueStatisticsService;
public ShopStatsJob(ShopRevenueStatisticsService shopRevenueStatisticsService)
{
_shopRevenueStatisticsService = shopRevenueStatisticsService;
}
public Task RunAsync()
{
_shopRevenueStatisticsService.UpdateStats(DateTime.Now.AddDays(-1).Date);
return Task.CompletedTask;
}
}
+2 -2
View File
@@ -5,8 +5,8 @@
-->
<configuration>
<connectionStrings>
<!--<add name="ConnKJD" connectionString="Max Pool Size=512;Database='kjd';Data Source='nj-cynosdbmysql-grp-hl51pkfr.sql.tencentcdb.com';Port=27660;User Id='kjd';Password='Temukjd0625';Charset=utf8mb4;" />-->
<add name="ConnKJD" connectionString="Server=192.168.0.200;Port=3306;Database=mysql;Uid=root;Pwd=123456;"/>
<add name="ConnKJD" connectionString="Max Pool Size=512;Database='kjd';Data Source='nj-cynosdbmysql-grp-hl51pkfr.sql.tencentcdb.com';Port=27660;User Id='kjd';Password='Temukjd0625';Charset=utf8mb4;" />
<!--<add name="ConnKJD" connectionString="Server=192.168.0.200;Port=3306;Database=mysql;Uid=root;Pwd=123456;"/>-->
<add name="ConnKJDBAK" connectionString="Max Pool Size=512;Database='kjd';Data Source='nj-cynosdbmysql-grp-9s7x16mt.sql.tencentcdb.com';Port=24049;User Id='kjdbak';Password='Temukjd0126';Charset=utf8mb4;" />
<add name="UpdateVersion" connectionString="99999" />
<add name="UpdateFileDir" connectionString="C:\inetpub\wwwroot\kjd_update" />
+5 -2
View File
@@ -1,4 +1,5 @@
using Newtonsoft.Json.Serialization;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -28,7 +29,9 @@ namespace KJD_Web
var jsonFormatter = config.Formatters.JsonFormatter;
jsonFormatter.SerializerSettings.ContractResolver =
new CamelCasePropertyNamesContractResolver();
jsonFormatter.SerializerSettings.DateFormatHandling = DateFormatHandling.IsoDateFormat;
//jsonFormatter.SerializerSettings.NullValueHandling = NullValueHandling.Ignore;
jsonFormatter.SerializerSettings.Formatting = Formatting.None;
//config.Filters.Add(new SignValidateFilterAttribute());
config.Formatters.Remove(config.Formatters.XmlFormatter);
@@ -0,0 +1,99 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Store.Contracts.Order.Entitys;
/// <summary>
/// 售后状态枚举
/// </summary>
public enum AfterSaleStatus
{
NONE,
/// <summary>
/// 用户取消申请
/// </summary>
USER_CANCELD,
/// <summary>
/// 商家受理中
/// </summary>
MERCHANT_PROCESSING,
/// <summary>
/// 商家拒绝退款
/// </summary>
MERCHANT_REJECT_REFUND,
/// <summary>
/// 商家拒绝退货退款
/// </summary>
MERCHANT_REJECT_RETURN,
/// <summary>
/// 待买家退货
/// </summary>
USER_WAIT_RETURN,
/// <summary>
/// 退货退款关闭
/// </summary>
RETURN_CLOSED,
/// <summary>
/// 待商家收货
/// </summary>
MERCHANT_WAIT_RECEIPT,
/// <summary>
/// 商家逾期未退款
/// </summary>
MERCHANT_OVERDUE_REFUND,
/// <summary>
/// 退款完成
/// </summary>
MERCHANT_REFUND_SUCCESS,
/// <summary>
/// 退货退款完成
/// </summary>
MERCHANT_RETURN_SUCCESS,
/// <summary>
/// 平台退款中
/// </summary>
PLATFORM_REFUNDING,
/// <summary>
/// 平台退款失败
/// </summary>
PLATFORM_REFUND_FAIL,
/// <summary>
/// 待用户确认
/// </summary>
USER_WAIT_CONFIRM,
/// <summary>
/// 商家打款失败,客服关闭售后
/// </summary>
MERCHANT_REFUND_RETRY_FAIL,
/// <summary>
/// 售后关闭
/// </summary>
MERCHANT_FAIL,
/// <summary>
/// 待用户处理商家协商
/// </summary>
USER_WAIT_CONFIRM_UPDATE,
/// <summary>
/// 待用户处理商家代发起的售后申请
/// </summary>
USER_WAIT_HANDLE_MERCHANT_AFTER_SALE
}
@@ -0,0 +1,123 @@
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.order_pdd_buyer_after_sale")]
public class OrderPddBuyerAfterSaleEntity
{
/// <summary>
/// id
/// </summary>
[Column(Name = "id", IsPrimary = true, IsIdentity = true)]
public long Id { get; set; }
/// <summary>
/// 微信订单
/// </summary>
[Column(Name = "wx_order_id")]
public string WxOrderId { get; set; }
/// <summary>
/// 微信订单sku
/// </summary>
[Column(Name = "wx_sku_id")]
public string WxSkuId { get; set; }
/// <summary>
/// 微信订单发货类型 0 中转仓,1店铺后台直发
/// </summary>
[Column(Name = "wx_send_type")]
public byte WxSendType { get; set; }
/// <summary>
/// 中转单创建时间
/// </summary>
[Column(Name = "TransitCreateTime")]
public DateTime? TransitCreateTime { get; set; }
/// <summary>
/// 中转仓取号状态
/// </summary>
[Column(Name = "pre_allocated_order_no")]
public int? PreAllocatedOrderNo { get; set; }
/// <summary>
/// 买手编号
/// </summary>
[Column(Name = "buyerId")]
public string BuyerId { get; set; }
/// <summary>
/// 买手名称
/// </summary>
[Column(Name = "buyerName")]
public string BuyerName { get; set; }
/// <summary>
/// 售后单号
/// </summary>
[Column(Name = "afterSalesId")]
public string AfterSalesId { get; set; }
/// <summary>
/// 买手单号
/// </summary>
[Column(Name = "orderSn")]
public string OrderSn { get; set; }
/// <summary>
/// 拍单订单金额 单位分
/// </summary>
[Column(Name = "orderAmount")]
public int? OrderAmount { get; set; }
/// <summary>
/// 售后金额 单位分
/// </summary>
[Column(Name = "refundAmount")]
public int? RefundAmount { get; set; }
/// <summary>
/// 拍单日期 yyMMdd
/// </summary>
[Column(Name = "orderDate")]
public long? OrderDate { get; set; }
/// <summary>
/// 售后状态
/// </summary>
[Column(Name = "statusText")]
public string StatusText { get; set; }
/// <summary>
/// 退货地址
/// </summary>
[Column(Name = "afterSaleAddress")]
public string AfterSaleAddress { get; set; }
/// <summary>
/// 买手订单详情
/// </summary>
[Column(Name = "order_detail")]
public string OrderDetail { get; set; }
/// <summary>
/// add_time
/// </summary>
[Column(Name = "add_time")]
public DateTime AddTime { get; set; }
/// <summary>
/// mdy_time
/// </summary>
[Column(Name = "mdy_time")]
public DateTime MdyTime { get; set; }
}
@@ -0,0 +1,23 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Store.Contracts.Order.Entitys;
/// <summary>
/// 采购类型
/// </summary>
public enum PurchaseType
{
/// <summary>
/// 从仓库采购
/// </summary>
FromWarehouse = 0,
/// <summary>
/// 直接购买
/// </summary>
DirectPurchase = 1
}
@@ -10,7 +10,7 @@ namespace Store.Contracts.Order.Entitys;
/// <summary>
/// 微信外采取号日志表
/// </summary>
[Table(Name = "wxxd.oms_wei_external_procurement_log")]
[Table(Name = "erp.oms_wei_external_procurement_log")]
public class WeiExternalProcurementLogEntity
{
/// <summary>
@@ -161,7 +161,7 @@ public class WeiExternalProcurementLogEntity
/// 订单作废状态:0=正常,1=已作废,2=作废失败
/// </summary>
[Column(Name = "order_void_status")]
public byte OrderVoidStatus { get; set; }
public int OrderVoidStatus { get; set; }
/// <summary>
/// 虚拟单号
@@ -0,0 +1,84 @@
using FreeSql.DataAnnotations;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Store.Contracts.Shops;
/// <summary>
/// 买手每日统计表
/// </summary>
[Table(Name = "wxxd.buyer_daily_stats")]
public class BuyerDailyStatsEntity
{
/// <summary>
/// 自增主键
/// </summary>
[Column(Name = "id", IsPrimary = true, IsIdentity = true)]
public long Id { get; set; }
/// <summary>
/// 店铺ID
/// </summary>
[Column(Name = "shop_id")]
public string ShopId { get; set; }
/// <summary>
/// 买手ID
/// </summary>
[Column(Name = "buyer_id")]
public string BuyerId { get; set; }
/// <summary>
/// 采购订单创建日期
/// </summary>
[Column(Name = "stat_date")]
public DateTime StatDate { get; set; }
/// <summary>
/// 采购单数
/// </summary>
[Column(Name = "purchase_count")]
public int PurchaseCount { get; set; }
/// <summary>
/// 总花费金额
/// </summary>
[Column(Name = "total_purchase")]
public long TotalPurchase { get; set; }
/// <summary>
/// 售后中买手总金额
/// </summary>
public long TotalRefund => DoneRefund + PendingRefund + RejectedRefund;
/// <summary>
/// 已成功退款金额
/// </summary>
[Column(Name = "done_refund")]
public long DoneRefund { get; set; }
/// <summary>
/// 未退回金额
/// </summary>
[Column(Name = "pending_refund")]
public long PendingRefund { get; set; }
/// <summary>
/// 被拒绝金额
/// </summary>
[Column(Name = "rejected_refund")]
public long RejectedRefund { get; set; }
/// <summary>
/// 创建时间
/// </summary>
[Column(Name = "created_at", ServerTime = DateTimeKind.Local, CanUpdate = false)]
public DateTime CreatedAt { get; set; }
/// <summary>
/// 最后刷新时间
/// </summary>
[Column(Name = "updated_at", ServerTime = DateTimeKind.Local)]
public DateTime UpdatedAt { get; set; }
}
@@ -0,0 +1,87 @@
using FreeSql.DataAnnotations;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Store.Contracts.Shops;
/// <summary>
/// 店铺每日统计表
/// </summary>
[Table(Name = "wxxd.shop_daily_stats")]
public class ShopDailyStatsEntity
{
/// <summary>
/// 自增主键
/// </summary>
[Column(Name = "id", IsPrimary = true, IsIdentity = true)]
public long Id { get; set; }
/// <summary>
/// 店铺ID
/// </summary>
[Column(Name = "shop_id")]
public string ShopId { get; set; }
/// <summary>
/// 销售订单创建日期
/// </summary>
[Column(Name = "stat_date")]
public DateTime StatDate { get; set; }
/// <summary>
/// 订单数
/// </summary>
[Column(Name = "order_count")]
public int OrderCount { get; set; }
/// <summary>
/// 销售总额
/// </summary>
[Column(Name = "sales_amount")]
public long SalesAmount { get; set; }
/// <summary>
/// 利润
/// </summary>
[Column(Name = "profit_amount")]
public long ProfitAmount { get; set; }
/// <summary>
/// 平台佣金
/// </summary>
[Column(Name = "platform_fee")]
public long PlatformFee { get; set; }
/// <summary>
/// 退款订单数
/// </summary>
[Column(Name = "refund_order_count")]
public int RefundOrderCount { get; set; }
/// <summary>
/// 已成功退款金额
/// </summary>
[Column(Name = "refund_done")]
public long RefundDone { get; set; }
/// <summary>
/// 退款处理中金额
/// </summary>
[Column(Name = "refund_pending")]
public long RefundPending { get; set; }
/// <summary>
/// 创建时间
/// </summary>
[Column(Name = "created_at", ServerTime = DateTimeKind.Local, CanUpdate = false)]
public DateTime CreatedAt { get; set; }
/// <summary>
/// 最后刷新时间
/// </summary>
[Column(Name = "updated_at", ServerTime = DateTimeKind.Local)]
public DateTime UpdatedAt { get; set; }
}
+129 -28
View File
@@ -1,8 +1,9 @@
using Autofac;
using Autofac;
using Autofac.Integration.WebApi;
using Autofac.Integration.WebApi;
using KJD_Web;
using KJD_Web;
using KJD_Web.Requests.Shops;
using KJD_Web.Services.Shops;
using KJD_Web.Services.Users;
using Newtonsoft.Json;
@@ -14,6 +15,7 @@ using System.Collections.Generic;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices.ComTypes;
using System.Text;
using System.Threading.Tasks;
using System.Timers;
@@ -44,36 +46,135 @@ internal class ShopTest
}
[Test]
public void TestAm()
//[TestCase(1960967267443631281)]
//[TestCase(1960967267443631212)]
//[TestCase(1960967267443631213)]
//[TestCase(1960967267443631215)]
//[TestCase(1960967267443631216)]
//[TestCase(1960967267443631218)]
//[TestCase(1960967267443631219)]
//[TestCase(1960967267443631221)]
//[TestCase(1960967267443631222)]
//[TestCase(1960967267443631223)]
//[TestCase(1960967267443631224)]
//[TestCase(1960967267443631226)]
//[TestCase(1960967267443631227)]
//[TestCase(1960967267443631228)]
//[TestCase(1960967267443630057)]
//[TestCase(1960967267443629984)]
//[TestCase(1960967267443629858)]
[TestCase(1960967267443629855)]
//[TestCase(1960967267443629809)]
//[TestCase(1960967267443629737)]
//[TestCase(1960967267443627649)]
//[TestCase(1960967267443627648)]
public void TestAm(long id)
{
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
};
// 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);
//shopService.GetShopOrderAmountRange(id, DateTime.Now.AddDays(-10));
}
[Test]
public void GetShopSalesOrderTest()
{
var shopService = _container.Resolve<ShopRevenueStatisticsService>();
DateTime startDate = DateTime.Parse("2026/08/01");
DateTime endDate = DateTime.Parse("2026/10/07");
DateTime date = startDate;
while (date <= endDate)
{
Console.WriteLine($"开始:{date.ToLongDateString()}");
try
{
shopService.ShopDailyStats(date);
}
catch
{
Console.WriteLine($"错误:{date.ToLongDateString()}");
}
date = date.AddDays(1);
}
}
[Test]
public void GetBuyerDailyStatsTest()
{
var shopService = _container.Resolve<ShopRevenueStatisticsService>();
shopService.BuyerDailyStats(DateTime.Parse("2026/10/02"), [1960967267443620449]);
}
[Test]
public void UpdateStatsTest()
{
var shopService = _container.Resolve<ShopRevenueStatisticsService>();
shopService.UpdateStats(DateTime.Now.AddDays(-1).Date);
}
[Test]
public void GetShopStatsTest()
{
var shopService = _container.Resolve<ShopRevenueStatisticsService>();
var data = shopService.GetShopStats(DateTime.Parse("2026/10/01"), DateTime.Parse("2026/10/07"), "18990991234", null, ShopStatsGroupType.Day);
var sb = new System.Text.StringBuilder();
if (data == null || data.Count == 0)
{
sb.AppendLine("没有统计数据");
}
else
{
Func<long, string> yuan = fen => (fen / 100m).ToString("N2");
foreach (var shopId in data.Keys)
{
sb.AppendLine("==================== 商铺 " + shopId + " ====================");
foreach (var r in data[shopId])
{
sb.AppendLine("[日期] " + r.StatDate.ToString("yyyy-MM-dd"));
sb.AppendLine("[销售] 订单数: " + r.OrderCount
+ " | 销售额: " + yuan(r.SalesAmount)
+ " | 利润: " + yuan(r.ProfitAmount)
+ " | 佣金: " + yuan(r.PlatformFee));
sb.AppendLine("[售后] 退款单: " + r.RefundOrderCount
+ " | 已退款: " + yuan(r.RefundDone)
+ " | 退款中: " + yuan(r.RefundPending));
sb.AppendLine("[买手] 采购单: " + r.PurchaseCount
+ " | 采购额: " + yuan(r.TotalPurchase)
+ " | 已退: " + yuan(r.DoneRefund)
+ " | 未退: " + yuan(r.PendingRefund)
+ " | 拒绝: " + yuan(r.RejectedRefund)
+ " | 买手退款合计: " + yuan(r.TotalRefund));
sb.AppendLine();
}
}
}
Console.WriteLine(sb.ToString());
}
}
+1 -1
View File
@@ -94,7 +94,7 @@ internal class UserTest
public void GetUserAmountSummate()
{
var userService = _container.Resolve<UserService>();
var data = userService.GetUserAmountSummary("15395107196");
var data = userService.GetUserAmountSummary("17734871710");
Console.WriteLine(JsonConvert.SerializeObject(data));
}
//[Test]