diff --git a/KJD_WebService/Controllers/WX/WXShopRevenueStatisticsController.cs b/KJD_WebService/Controllers/WX/WXShopRevenueStatisticsController.cs
index e7f4d39..94af99b 100644
--- a/KJD_WebService/Controllers/WX/WXShopRevenueStatisticsController.cs
+++ b/KJD_WebService/Controllers/WX/WXShopRevenueStatisticsController.cs
@@ -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);
}
+ ///
+ /// 按天统计(兼容旧接口,等价于 GetShopStats 传 天)
+ ///
+ public IHttpActionResult GetShopStatsDetails(ShopStatsRequest request)
+ {
+ return GetShopStats(request, ShopStatsGroupType.Day);
+ }
+ public IHttpActionResult GetShopStats(ShopStatsRequest request)
+ {
+ return GetShopStats(request, request.GroupType);
+ }
+
+ ///
+ /// 商铺销售趋势:按天返回所有商铺合计的销售额和订单数(无数据的天补 0)
+ ///
+ public IHttpActionResult GetShopStatsTrend(ShopStatsRequest request)
+ {
+ List 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));
+ }
+ }
+
+ ///
+ /// 订单明细统一入口:有效订单 / 退款订单 / 买手未退回金额的订单,三种列表返回同一结构
+ /// 请求参数 OrderType:All(默认全部) / Valid(有效订单) / Refund(退款订单) / BuyerUnrefunded(买手未退回金额)
+ /// 时间范围为订单创建时间,商铺ID为空时按用户名的商铺查询
+ ///
+ public IHttpActionResult GetShopOrderDetails(ShopStatsRequest request)
+ {
+ List 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> 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));
+ }
+ }
}
}
diff --git a/KJD_WebService/Global.asax.cs b/KJD_WebService/Global.asax.cs
index 429b543..1accdf3 100644
--- a/KJD_WebService/Global.asax.cs
+++ b/KJD_WebService/Global.asax.cs
@@ -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)
diff --git a/KJD_WebService/KJD_WebService.csproj b/KJD_WebService/KJD_WebService.csproj
index 1854533..c5ebd1e 100644
--- a/KJD_WebService/KJD_WebService.csproj
+++ b/KJD_WebService/KJD_WebService.csproj
@@ -1,4 +1,4 @@
-
+
@@ -217,11 +217,18 @@
+
+
+
+
+
+
+
diff --git a/KJD_WebService/Model/WxModel/ShopRevenueStatistics.cs b/KJD_WebService/Model/WxModel/ShopRevenueStatistics.cs
new file mode 100644
index 0000000..8deef6c
--- /dev/null
+++ b/KJD_WebService/Model/WxModel/ShopRevenueStatistics.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; }
+
+ }
+ ///
+ /// 订单信息
+ ///
+ public class OrderInfo : StatisticsBase
+ {
+ public string ShopName { get; set; }
+
+ ///
+ /// 外采金额(单位:分,整数存储)
+ ///
+ public int OutAmount => BuyerOrders.Sum(a => a.OutAmount);
+ ///
+ /// 订单金额,单位为分,
+ ///
+ public int OrderPrice { get; set; }
+
+ ///
+ /// 平台服务费(微信小店:预计技术服务费),单位为分
+ ///
+ public int PredictCommissionFee { get; set; }
+
+ ///
+ /// 达人佣金(微信小店:分账金额),单位为分
+ ///
+ public int CommissionAmount { get; set; }
+
+ ///
+ /// 利润
+ ///
+ public int Profit { get; set; }
+ ///
+ /// 采购类型
+ ///
+ public PurchaseType PurchaseType { get; set; }
+
+ public List Items { get; set; } = [];
+ ///
+ /// 买手订单信息
+ ///
+ public List 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; }
+ ///
+ /// SKU价格
+ ///
+ public int SkuPrice { get; set; }
+
+ ///
+ /// 是否退款完成
+ ///
+ public bool IsRefundSuccessed
+ {
+ get
+ {
+ return AfterSaleStatus == AfterSaleStatus.MERCHANT_REFUND_SUCCESS || AfterSaleStatus == AfterSaleStatus.MERCHANT_RETURN_SUCCESS;
+ }
+ }
+ ///
+ /// 是否是售后订单
+ ///
+ public bool IsAfterSale
+ {
+ get
+ {
+ return AfterSaleStatus != AfterSaleStatus.NONE;
+ }
+ }
+ ///
+ /// 退款状态
+ /// 售后状态 1:无售后或售后关闭,2:售后处理中,3:退款中,4: 退款成功 11 退款完成订单已取消
+ ///
+ public int RefundStatus { get; set; }
+ public AfterSaleStatus AfterSaleStatus { get; set; } = AfterSaleStatus.NONE;
+ ///
+ /// 退款金额
+ ///
+ 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; }
+ }
+
+
+ ///
+ /// 订单退款
+ ///
+ public class OrderRefundInfo : StatisticsBase
+ {
+ public string SkuId { get; set; }
+ ///
+ /// 退款状态
+ ///
+ public string Status { get; set; }
+ ///
+ /// 退款金额
+ ///
+ public int RefundAmount { get; set; }
+ public int CreateTime { get; set; }
+ }
+
+
+ ///
+ /// 买手订单
+ ///
+ public class BuyerOrderInfo : StatisticsBase
+ {
+ ///
+ /// 买手订单
+ ///
+ public string SkuId { get; set; }
+ public string OutOrderSn { get; set; }
+ ///
+ /// 购买金额(分)
+ ///
+ public int OutAmount { get; set; }
+ /////
+ ///// 订单作废状态:0=正常,1=已作废,2=作废失败
+ /////
+ //public int Status { get; set; }
+
+
+
+
+ ///
+ /// 退款金额(分)
+ ///
+ public int RefundAmount { get; set; }
+ ///
+ /// 退款状态
+ ///
+ public string RefundStatusText { get; set; }
+
+
+ }
+ ///
+ /// 更新要素信息
+ ///
+ public class UpdateStatsInfo
+ {
+ ///
+ /// 商铺Id
+ ///
+ public string ShopId { get; set; }
+ ///
+ /// 订单创建时间
+ ///
+ public int OrderCreateAt { get; set; }
+
+ public DateTime OrderCreateDate => TimeUtils.StampToDateTime(OrderCreateAt).Date;
+ }
+}
diff --git a/KJD_WebService/Nlog.config b/KJD_WebService/Nlog.config
index d938001..88fb1ac 100644
--- a/KJD_WebService/Nlog.config
+++ b/KJD_WebService/Nlog.config
@@ -64,6 +64,9 @@
+
+
+
diff --git a/KJD_WebService/Requests/Shops/ShopOrderDetailType.cs b/KJD_WebService/Requests/Shops/ShopOrderDetailType.cs
new file mode 100644
index 0000000..a176aaf
--- /dev/null
+++ b/KJD_WebService/Requests/Shops/ShopOrderDetailType.cs
@@ -0,0 +1,27 @@
+namespace KJD_Web.Requests.Shops;
+
+///
+/// 订单明细类型(统一明细入口 GetShopOrderDetails 使用)
+///
+public enum ShopOrderDetailType
+{
+ ///
+ /// 全部订单
+ ///
+ All = 0,
+
+ ///
+ /// 有效订单:没有发生售后的订单(对应统计里的订单数 / 销售额 / 利润口径)
+ ///
+ Valid = 1,
+
+ ///
+ /// 退款订单:发生过售后的订单(对应统计里的退款订单数口径)
+ ///
+ Refund = 2,
+
+ ///
+ /// 买手未退回金额的订单:退款订单中买手款项还没退回的(对应统计里的未退回金额口径)
+ ///
+ BuyerUnrefunded = 3,
+}
diff --git a/KJD_WebService/Requests/Shops/ShopStatsGroupType.cs b/KJD_WebService/Requests/Shops/ShopStatsGroupType.cs
new file mode 100644
index 0000000..93944c5
--- /dev/null
+++ b/KJD_WebService/Requests/Shops/ShopStatsGroupType.cs
@@ -0,0 +1,27 @@
+namespace KJD_Web.Requests.Shops;
+
+///
+/// 商铺统计聚合维度
+///
+public enum ShopStatsGroupType
+{
+ ///
+ /// 汇总:每个商铺只返回一条(整个查询范围合计)
+ ///
+ Summary = 0,
+
+ ///
+ /// 天:按自然日聚合,每个商铺每天一条
+ ///
+ Day = 1,
+
+ ///
+ /// 周:按自然周聚合(周一为一周开始),每个商铺每周一条
+ ///
+ Week = 2,
+
+ ///
+ /// 月:按自然月聚合,每个商铺每月一条
+ ///
+ Month = 3,
+}
diff --git a/KJD_WebService/Requests/Shops/ShopStatsRequest.cs b/KJD_WebService/Requests/Shops/ShopStatsRequest.cs
new file mode 100644
index 0000000..3ef9ead
--- /dev/null
+++ b/KJD_WebService/Requests/Shops/ShopStatsRequest.cs
@@ -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 ShopIds { get; set; }
+
+ ///
+ /// 聚合维度:汇总(默认) / 天 / 周 / 月
+ ///
+ public ShopStatsGroupType GroupType { get; set; } = ShopStatsGroupType.Summary;
+
+ ///
+ /// 订单明细类型(统一明细入口 GetShopOrderDetails 使用):
+ /// 全部(默认) / 有效订单 / 退款订单 / 买手未退回金额的订单
+ ///
+ public ShopOrderDetailType OrderType { get; set; } = ShopOrderDetailType.All;
+}
\ No newline at end of file
diff --git a/KJD_WebService/Responses/Shops/ShopOrderDetailResponse.cs b/KJD_WebService/Responses/Shops/ShopOrderDetailResponse.cs
new file mode 100644
index 0000000..3aa2edd
--- /dev/null
+++ b/KJD_WebService/Responses/Shops/ShopOrderDetailResponse.cs
@@ -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;
+
+///
+/// 订单明细(有效订单 / 退款订单 / 买手未退回金额的订单 共用的返回结构)
+/// 一个商铺订单可能对应多个买手订单(见 BuyerOrders)
+/// 金额单位:分
+///
+public class ShopOrderDetailResponse
+{
+ ///
+ /// 店铺ID
+ ///
+ public long ShopId { get; set; }
+
+ ///
+ /// 店铺名称
+ ///
+ public string ShopName { get; set; }
+
+ ///
+ /// 商铺订单号
+ ///
+ public string OrderId { get; set; }
+
+ ///
+ /// 订单创建时间
+ ///
+ public DateTime CreateTime { get; set; }
+
+ ///
+ /// 订单金额(分)
+ ///
+ public int OrderPrice { get; set; }
+
+ ///
+ /// 订单退款金额(分),即商铺订单商品明细的退款金额之和;有效订单为 0
+ ///
+ public int OrderRefundAmount { get; set; }
+
+ ///
+ /// 外采金额(分),即该订单下所有买手订单的购买金额之和
+ ///
+ public int OutAmount { get; set; }
+
+ ///
+ /// 买手未退回金额(分),即买手订单中退款未成功的退款金额之和;无售后时为 0
+ ///
+ public int UnrefundedAmount { get; set; }
+
+ ///
+ /// 买手订单信息(一个订单号对应多个买手订单)
+ /// 字段含义:OutOrderSn=外部订单(买手订单)号,OutAmount=金额,RefundAmount=退款金额,RefundStatusText=退款状态
+ ///
+ public List BuyerOrders { get; set; } = [];
+}
diff --git a/KJD_WebService/Responses/Shops/ShopStatsResponse.cs b/KJD_WebService/Responses/Shops/ShopStatsResponse.cs
new file mode 100644
index 0000000..6c968c2
--- /dev/null
+++ b/KJD_WebService/Responses/Shops/ShopStatsResponse.cs
@@ -0,0 +1,89 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Web;
+
+namespace KJD_Web.Responses.Shops;
+
+///
+/// 商铺统计记录
+///
+public class ShopStatsResponse
+{
+ ///
+ /// 店铺ID
+ ///
+ public string ShopId { get; set; }
+
+ ///
+ /// 销售订单创建日期
+ ///
+ public DateTime StatDate { get; set; }
+
+ ///
+ /// 订单数
+ ///
+ public int OrderCount { get; set; }
+
+ ///
+ /// 销售总额
+ ///
+ public long SalesAmount { get; set; }
+
+ ///
+ /// 利润
+ ///
+ public long ProfitAmount { get; set; }
+
+ ///
+ /// 平台佣金
+ ///
+ public long PlatformFee { get; set; }
+
+ ///
+ /// 退款订单数
+ ///
+ public int RefundOrderCount { get; set; }
+
+ ///
+ /// 已成功退款金额
+ ///
+ public long RefundDone { get; set; }
+
+ ///
+ /// 退款处理中金额
+ ///
+ public long RefundPending { get; set; }
+
+
+
+
+
+ ///
+ /// 采购单数
+ ///
+ public int PurchaseCount { get; set; }
+
+ ///
+ /// 总花费金额
+ ///
+ public long TotalPurchase { get; set; }
+ ///
+ /// 售后中买手总金额
+ ///
+ public long TotalRefund => DoneRefund + PendingRefund + RejectedRefund;
+ ///
+ /// 已成功退款金额
+ ///
+ public long DoneRefund { get; set; }
+
+ ///
+ /// 未退回金额
+ ///
+ public long PendingRefund { get; set; }
+
+ ///
+ /// 被拒绝金额
+ ///
+ public long RejectedRefund { get; set; }
+}
\ No newline at end of file
diff --git a/KJD_WebService/Responses/Shops/ShopStatsTrendResponse.cs b/KJD_WebService/Responses/Shops/ShopStatsTrendResponse.cs
new file mode 100644
index 0000000..bcd68c6
--- /dev/null
+++ b/KJD_WebService/Responses/Shops/ShopStatsTrendResponse.cs
@@ -0,0 +1,24 @@
+using System;
+
+namespace KJD_Web.Responses.Shops;
+
+///
+/// 商铺销售趋势(按天,所有商铺合计)
+///
+public class ShopStatsTrendResponse
+{
+ ///
+ /// 统计日期(天)
+ ///
+ public DateTime StatDate { get; set; }
+
+ ///
+ /// 销售额(分)
+ ///
+ public long SalesAmount { get; set; }
+
+ ///
+ /// 订单数
+ ///
+ public int OrderCount { get; set; }
+}
diff --git a/KJD_WebService/Services/Shops/ShopRevenueStatisticsService.cs b/KJD_WebService/Services/Shops/ShopRevenueStatisticsService.cs
index 0061434..91636b7 100644
--- a/KJD_WebService/Services/Shops/ShopRevenueStatisticsService.cs
+++ b/KJD_WebService/Services/Shops/ShopRevenueStatisticsService.cs
@@ -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;
///
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;
+ }
+ ///
+ /// 获取商铺统计
+ /// 汇总:每个商铺只返回一条(整个查询范围合计,StatDate 为查询结束日期)
+ /// 天/周/月:按自然日 / 自然周(周一为开始)/ 自然月聚合,每个周期一条(StatDate 为该周期的起始日期)
+ ///
+ /// 统计开始时间(含)
+ /// 统计结束时间(含)
+ /// 用户名(未指定商铺ID时,按该用户名下的商铺统计)
+ /// 指定商铺ID集合(为空时按用户名统计)
+ /// 聚合维度,默认汇总
+ /// 商铺ID -> 该商铺按维度聚合后的统计列表
+ public Dictionary> GetShopStats(DateTime startTime, DateTime endTime, string userName, List shopIds = null, ShopStatsGroupType groupType = ShopStatsGroupType.Summary)
+ {
+ var result = new Dictionary>();
+
+ (Dictionary> shopGroups, Dictionary> 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();
+ bList = bList ?? new List();
+
+ // 同一商铺内先按聚合维度分组(同一周期内的多条明细在下面求和)
+ 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();
+ 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 shopIds, DateTime startTime, DateTime endTime)
+ ///
+ /// 获取商铺销售趋势(按天,所有商铺合计)
+ /// 返回查询范围内每一天的销售额和订单数,没有数据的天补 0,保证日期连续
+ ///
+ /// 统计开始时间(含)
+ /// 统计结束时间(含)
+ /// 用户名(未指定商铺ID时,按该用户名下的商铺统计)
+ /// 指定商铺ID集合(为空时按用户名统计)
+ public List GetShopStatsTrend(DateTime startTime, DateTime endTime, string userName, List shopIds = null)
{
- int startTimestamp = TimeUtils.DateTimeToTimestampSeconds(startTime);
- int endTimestamp = TimeUtils.DateTimeToTimestampSeconds(endTime);
+ var result = new List();
+ 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()
- .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();
-
- 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()
- .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)
+ ///
+ /// 订单明细统一入口:有效订单 / 退款订单 / 买手未退回金额的订单,三种列表返回同一结构
+ /// 按订单创建时间范围 + 商铺ID查询(未指定商铺ID时按用户名下的商铺查询)
+ /// 有效订单:没有发生售后的订单;退款订单:发生过售后的订单;买手未退回:退款订单中买手款项还没退回的
+ /// 未退回:买手订单存在退款信息(退款金额 > 0 或退款状态非空)且退款未成功(不是"退款成功"),
+ /// 未退回金额 = 这些买手订单的退款金额之和(与统计里的"未退回金额/PendingRefund"口径一致)
+ /// 一个订单号可能对应多个买手订单(BuyerOrders),金额单位:分
+ /// 与统计口径保持一致:只统计单商品订单(多商品订单视为刷单,统计时已排除)
+ ///
+ /// 订单创建开始时间(含)
+ /// 订单创建结束时间(含)
+ /// 明细类型:全部 / 有效订单 / 退款订单 / 买手未退回金额的订单
+ /// 用户名(未指定商铺ID时,按该用户名下的商铺查询)
+ /// 指定商铺ID集合(为空时按用户名查询)
+ /// 按订单创建时间倒序的订单明细
+ public List GetShopOrderDetails(DateTime startTime, DateTime endTime, ShopOrderDetailType orderType = ShopOrderDetailType.All, string userName = null, List shopIds = null)
+ {
+ var result = new List();
+ 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();
+ 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();
+ 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();
- // needLookupOrderIds:先跑一遍订单列表,out_amount 为空或0的订单号
- var needLookupOrderIds = orders
- .Where(o => o.out_amount == 0)
- .Select(o => o.order_id)
- .Distinct()
+ ///
+ /// 买手未退回金额:不是"退款成功" 状态的买手订单退款金额之和
+ /// 只算有退款信息的(退款金额 > 0 或退款状态非空),没发生退款的正常订单不计入
+ ///
+ private static int GetBuyerUnrefundedAmount(List 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);
+ }
+
+ ///
+ /// 取某个日期在指定聚合维度下所属周期的起始日期
+ ///
+ /// 明细日期
+ /// 聚合维度
+ /// 汇总维度使用的分组标识
+ 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> shopGroups, Dictionary> buyerGroups) getShopStatsByUserName(DateTime startTime, DateTime endTime, string userName)
+ {
+ var shopIds = getShopIdsByUserName(userName);
+ if (shopIds == null) return (null, null);
+
+ return getShopStats(startTime, endTime, shopIds);
+
+ }
+
+ /// 按用户名取商铺ID集合,取不到返回 null
+ private List 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;
+ }
+
+ /// 加载指定商铺在某时间范围内的商铺日统计
+ private List loadShopStats(DateTime startTime, DateTime endTime, List shopIds)
+ {
+ return _freeSql.Select()
+ .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()
- .Where(t => ids.Contains(t.OrderNo))
- .ToList(t => new { t.OrderNo, t.OutAmount });
+ }
- foreach (var t in transitList)
+ private (Dictionary> shopGroups, Dictionary> buyerGroups) getShopStats(DateTime startTime, DateTime endTime, List shopIds)
+ {
+ if (shopIds == null || shopIds.Count() == 0) return (null, null);
+
+ var shopStats = loadShopStats(startTime, endTime, shopIds);
+
+ var buyerStats = _freeSql.Select()
+ .Where(a => shopIds.Contains(a.ShopId))
+ .Where(a => a.StatDate >= startTime)
+ .Where(a => a.StatDate <= endTime)
+ .ToList();
+
+ // 先按商铺分组
+ Dictionary> shopGroups = shopStats.GroupBy(x => x.ShopId).ToDictionary(g => g.Key, g => g.ToList());
+ Dictionary> buyerGroups = buyerStats.GroupBy(x => x.ShopId).ToDictionary(g => g.Key, g => g.ToList());
+
+ return (shopGroups, buyerGroups);
+ }
+
+ /// 商铺统计指定天(同时会统计买手数据)
+ public void ShopDailyStats(DateTime date, List shopId = null)
+ {
+
+ var orders = LoadOrders(date, shopId);
+ LoadBuyerOrder(orders);
+ FillRefunds(orders);
+ CalcProfit(orders);
+
+ SaveShopStats(date.Date, orders);
+ SaveBuyerStats(date.Date, orders);
+
+ }
+
+ /// 买手统计指定天
+ public void BuyerDailyStats(DateTime date, List shopId = null)
+ {
+ var orders = LoadOrders(date, shopId);
+ LoadBuyerOrder(orders);
+ SaveBuyerStats(date.Date, orders);
+ }
+
+ ///
+ /// 更新
+ ///
+
+ 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 allUpdateOrderIds = new List();
+
+ //获取新增订单
+ allUpdateOrderIds.AddRange(_freeSql.Select()
+ .Where(a => a.CreateTime >= startUnix && a.CreateTime < endUnix)
+ .ToList(a => a.OrderId));
+
+ //获取退款信息
+ allUpdateOrderIds.AddRange(_freeSql.Select()
+ .Where(a => a.UpdateTime >= startUnix && a.UpdateTime < endUnix)
+ .ToList(a => a.OrderId));
+
+ //获取买手购买
+ //云仓
+ allUpdateOrderIds.AddRange(_freeSql.Select()
+ .Where(a => a.CreatedTime >= startDate && a.CreatedTime < endDate)
+ .ToList(a => a.OrderNo));
+ //直发
+ allUpdateOrderIds.AddRange(_freeSql.Select()
+ .Where(a => a.AddTime >= startDate && a.AddTime < endDate)
+ .ToList(a => a.WxOrderId));
+
+ //买手退款
+ allUpdateOrderIds.AddRange(_freeSql.Select()
+ .Where(a => a.MdyTime >= startDate && a.MdyTime < endDate)
+ .ToList(a => a.WxOrderId));
+
+ allUpdateOrderIds = allUpdateOrderIds.Distinct().ToList();//合并
+
+
+ List allUpdateStats = new List();
+ if (allUpdateOrderIds.Count > 0)
+ {
+ //查询订单归属商铺和时间
+ foreach (string[] batch in Batch(allUpdateOrderIds, BatchSize))
+ {
+ allUpdateStats.AddRange(_freeSql.Select()
+ .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> 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 数据加载
+
+ /// 加载某天的订单(只保留单商品订单)
+ private List LoadOrders(DateTime date, List shopId)
+ {
+ int start = TimeUtils.DateTimeToTimestampSeconds(date.Date); // 当天0点
+ int end = start + 86400; // 次日0点
+
+ return LoadOrders(start, end, shopId);
+ }
+
+ ///
+ /// 按时间范围加载订单(秒级时间戳,左闭右开 [startStamp, endStamp))
+ /// 只保留单商品订单;shopIds 为空时不限制商铺
+ ///
+ private List LoadOrders(int startStamp, int endStamp, List shopIds)
+ {
+ var query = _freeSql.Select()
+ .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();
+ 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;
+ }
+
+ ///
+ /// 加载买手订单信息
+ ///
+ /// 商铺订单
+ ///
+ /// true:按订单的采购类型区分直拍 / 中转仓,两张买手表都能查到(查询明细用)
+ /// false:沿用历史口径,按 OutAmount 是否为 0 判断(统计任务保持原行为不变)
+ ///
+ private void LoadBuyerOrder(List orders, bool byPurchaseType = false)
+ {
+ var result = new List();
+
+
+ //查询中转仓购买表
+ 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()
+ .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()
+ .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();
+ foreach (var batch in Batch(ids, BatchSize))
+ {
+ refundlist.AddRange(_freeSql.Select()
+ .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);
+ }
+
+ }
+ }
}
+
+ /// 回填商铺订单的退款信息
+ private void FillRefunds(List 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();
+ foreach (var batch in Batch(refundOrderIds, BatchSize))
+ {
+ allRefunds.AddRange(_freeSql.Select()
+ .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 计算与保存
+ ///
+ /// 计算利润
+ ///
+ ///
+ private static void CalcProfit(List 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 orders)
+ {
+ var statsList = new List();
+
+ 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()
+ .SetSource(statsList, a => new { a.ShopId, a.StatDate })
+ .ExecuteAffrows();
+ }
+
+ private void SaveBuyerStats(DateTime statDate, List orders)
+ {
+ if (orders == null || orders.Count == 0) return;
+
+
+ var statsList = new List();
+ // 按店铺分组,每个店铺下展开所有买手订单
+ 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()
+ .SetSource(statsList, a => new { a.ShopId, a.StatDate })
+ .ExecuteAffrows();
+ }
+
+
+
+ #endregion
+
+ #region 辅助
+
+ /// .NET Framework 没有 Enumerable.Chunk,自己分批
+ private static IEnumerable Batch(IList 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
+
}
\ No newline at end of file
diff --git a/KJD_WebService/Services/Shops/ShopService.cs b/KJD_WebService/Services/Shops/ShopService.cs
index 43a15e9..4349b63 100644
--- a/KJD_WebService/Services/Shops/ShopService.cs
+++ b/KJD_WebService/Services/Shops/ShopService.cs
@@ -21,11 +21,15 @@ public class ShopService : ITransientDependency
_logger = logger;
_userService = userService;
}
+ public long GetShopCount()
+ {
+ return _freeSql.Select().Count();
+ }
///
/// 修改商铺永不过期
///
- 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 GetShops(string userName)
+ {
+ return _freeSql.Select()
+ .Where(a => a.UserName == userName)
+ .ToList();
+ }
+
+ public List GetShopIds(string userName)
+ {
+ var shops = GetShops(userName);
+ var shopAppkeys = shops.Select(a => a.ShopId).Distinct().ToList();
+ return _freeSql.Select()
+ .Where(a => shopAppkeys.Contains(a.AppKey))
+ .ToList(a => a.Id);
+ }
}
\ No newline at end of file
diff --git a/KJD_WebService/Services/Shops/ShopStatsJob.cs b/KJD_WebService/Services/Shops/ShopStatsJob.cs
new file mode 100644
index 0000000..7db69e4
--- /dev/null
+++ b/KJD_WebService/Services/Shops/ShopStatsJob.cs
@@ -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;
+
+///
+/// 更新商铺统计
+///
+[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;
+ }
+}
\ No newline at end of file
diff --git a/KJD_WebService/Web.config b/KJD_WebService/Web.config
index d8c0792..5ffe245 100644
--- a/KJD_WebService/Web.config
+++ b/KJD_WebService/Web.config
@@ -5,8 +5,8 @@
-->
-
-
+
+
diff --git a/KJD_WebService/WebApiConfig.cs b/KJD_WebService/WebApiConfig.cs
index e5d6a7a..5aedcd9 100644
--- a/KJD_WebService/WebApiConfig.cs
+++ b/KJD_WebService/WebApiConfig.cs
@@ -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);
diff --git a/Store.Contracts/Order/Entitys/AfterSaleStatus.cs b/Store.Contracts/Order/Entitys/AfterSaleStatus.cs
new file mode 100644
index 0000000..42571d3
--- /dev/null
+++ b/Store.Contracts/Order/Entitys/AfterSaleStatus.cs
@@ -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;
+
+///
+/// 售后状态枚举
+///
+public enum AfterSaleStatus
+{
+ NONE,
+ ///
+ /// 用户取消申请
+ ///
+ 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
+}
diff --git a/Store.Contracts/Order/Entitys/OrderPddBuyerAfterSaleEntity.cs b/Store.Contracts/Order/Entitys/OrderPddBuyerAfterSaleEntity.cs
new file mode 100644
index 0000000..ff4603e
--- /dev/null
+++ b/Store.Contracts/Order/Entitys/OrderPddBuyerAfterSaleEntity.cs
@@ -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;
+
+///
+/// 拼多多买手售后单表
+///
+[Table(Name = "wxxd.order_pdd_buyer_after_sale")]
+public class OrderPddBuyerAfterSaleEntity
+{
+ ///
+ /// id
+ ///
+ [Column(Name = "id", IsPrimary = true, IsIdentity = true)]
+ public long Id { get; set; }
+
+ ///
+ /// 微信订单
+ ///
+ [Column(Name = "wx_order_id")]
+ public string WxOrderId { get; set; }
+
+ ///
+ /// 微信订单sku
+ ///
+ [Column(Name = "wx_sku_id")]
+ public string WxSkuId { get; set; }
+
+ ///
+ /// 微信订单发货类型 0 中转仓,1店铺后台直发
+ ///
+ [Column(Name = "wx_send_type")]
+ public byte WxSendType { get; set; }
+
+ ///
+ /// 中转单创建时间
+ ///
+ [Column(Name = "TransitCreateTime")]
+ public DateTime? TransitCreateTime { get; set; }
+
+ ///
+ /// 中转仓取号状态
+ ///
+ [Column(Name = "pre_allocated_order_no")]
+ public int? PreAllocatedOrderNo { get; set; }
+
+ ///
+ /// 买手编号
+ ///
+ [Column(Name = "buyerId")]
+ public string BuyerId { get; set; }
+
+ ///
+ /// 买手名称
+ ///
+ [Column(Name = "buyerName")]
+ public string BuyerName { get; set; }
+
+ ///
+ /// 售后单号
+ ///
+ [Column(Name = "afterSalesId")]
+ public string AfterSalesId { get; set; }
+
+ ///
+ /// 买手单号
+ ///
+ [Column(Name = "orderSn")]
+ public string OrderSn { get; set; }
+
+ ///
+ /// 拍单订单金额 单位分
+ ///
+ [Column(Name = "orderAmount")]
+ public int? OrderAmount { get; set; }
+
+ ///
+ /// 售后金额 单位分
+ ///
+ [Column(Name = "refundAmount")]
+ public int? RefundAmount { get; set; }
+
+ ///
+ /// 拍单日期 yyMMdd
+ ///
+ [Column(Name = "orderDate")]
+ public long? OrderDate { get; set; }
+
+ ///
+ /// 售后状态
+ ///
+ [Column(Name = "statusText")]
+ public string StatusText { get; set; }
+
+ ///
+ /// 退货地址
+ ///
+ [Column(Name = "afterSaleAddress")]
+ public string AfterSaleAddress { get; set; }
+
+ ///
+ /// 买手订单详情
+ ///
+ [Column(Name = "order_detail")]
+ public string OrderDetail { get; set; }
+
+ ///
+ /// add_time
+ ///
+ [Column(Name = "add_time")]
+ public DateTime AddTime { get; set; }
+
+ ///
+ /// mdy_time
+ ///
+ [Column(Name = "mdy_time")]
+ public DateTime MdyTime { get; set; }
+}
diff --git a/Store.Contracts/Order/Entitys/PurchaseType.cs b/Store.Contracts/Order/Entitys/PurchaseType.cs
new file mode 100644
index 0000000..985ec89
--- /dev/null
+++ b/Store.Contracts/Order/Entitys/PurchaseType.cs
@@ -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;
+
+///
+/// 采购类型
+///
+public enum PurchaseType
+{
+ ///
+ /// 从仓库采购
+ ///
+ FromWarehouse = 0,
+
+ ///
+ /// 直接购买
+ ///
+ DirectPurchase = 1
+}
diff --git a/Store.Contracts/Order/Entitys/WeiExternalProcurementLogEntity.cs b/Store.Contracts/Order/Entitys/WeiExternalProcurementLogEntity.cs
index 5aeb931..0d63e8e 100644
--- a/Store.Contracts/Order/Entitys/WeiExternalProcurementLogEntity.cs
+++ b/Store.Contracts/Order/Entitys/WeiExternalProcurementLogEntity.cs
@@ -10,7 +10,7 @@ namespace Store.Contracts.Order.Entitys;
///
/// 微信外采取号日志表
///
-[Table(Name = "wxxd.oms_wei_external_procurement_log")]
+[Table(Name = "erp.oms_wei_external_procurement_log")]
public class WeiExternalProcurementLogEntity
{
///
@@ -161,7 +161,7 @@ public class WeiExternalProcurementLogEntity
/// 订单作废状态:0=正常,1=已作废,2=作废失败
///
[Column(Name = "order_void_status")]
- public byte OrderVoidStatus { get; set; }
+ public int OrderVoidStatus { get; set; }
///
/// 虚拟单号
diff --git a/Store.Contracts/Shops/BuyerDailyStatsEntity.cs b/Store.Contracts/Shops/BuyerDailyStatsEntity.cs
new file mode 100644
index 0000000..be128f7
--- /dev/null
+++ b/Store.Contracts/Shops/BuyerDailyStatsEntity.cs
@@ -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;
+
+///
+/// 买手每日统计表
+///
+[Table(Name = "wxxd.buyer_daily_stats")]
+public class BuyerDailyStatsEntity
+{
+ ///
+ /// 自增主键
+ ///
+ [Column(Name = "id", IsPrimary = true, IsIdentity = true)]
+ public long Id { get; set; }
+
+ ///
+ /// 店铺ID
+ ///
+ [Column(Name = "shop_id")]
+ public string ShopId { get; set; }
+
+ ///
+ /// 买手ID
+ ///
+ [Column(Name = "buyer_id")]
+ public string BuyerId { get; set; }
+
+ ///
+ /// 采购订单创建日期
+ ///
+ [Column(Name = "stat_date")]
+ public DateTime StatDate { get; set; }
+
+ ///
+ /// 采购单数
+ ///
+ [Column(Name = "purchase_count")]
+ public int PurchaseCount { get; set; }
+
+ ///
+ /// 总花费金额
+ ///
+ [Column(Name = "total_purchase")]
+ public long TotalPurchase { get; set; }
+ ///
+ /// 售后中买手总金额
+ ///
+ public long TotalRefund => DoneRefund + PendingRefund + RejectedRefund;
+ ///
+ /// 已成功退款金额
+ ///
+ [Column(Name = "done_refund")]
+ public long DoneRefund { get; set; }
+
+ ///
+ /// 未退回金额
+ ///
+ [Column(Name = "pending_refund")]
+ public long PendingRefund { get; set; }
+
+ ///
+ /// 被拒绝金额
+ ///
+ [Column(Name = "rejected_refund")]
+ public long RejectedRefund { get; set; }
+
+ ///
+ /// 创建时间
+ ///
+ [Column(Name = "created_at", ServerTime = DateTimeKind.Local, CanUpdate = false)]
+ public DateTime CreatedAt { get; set; }
+
+ ///
+ /// 最后刷新时间
+ ///
+ [Column(Name = "updated_at", ServerTime = DateTimeKind.Local)]
+ public DateTime UpdatedAt { get; set; }
+}
\ No newline at end of file
diff --git a/Store.Contracts/Shops/ShopDailyStatsEntity.cs b/Store.Contracts/Shops/ShopDailyStatsEntity.cs
new file mode 100644
index 0000000..1cb79a1
--- /dev/null
+++ b/Store.Contracts/Shops/ShopDailyStatsEntity.cs
@@ -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;
+
+///
+/// 店铺每日统计表
+///
+[Table(Name = "wxxd.shop_daily_stats")]
+public class ShopDailyStatsEntity
+{
+ ///
+ /// 自增主键
+ ///
+ [Column(Name = "id", IsPrimary = true, IsIdentity = true)]
+ public long Id { get; set; }
+
+ ///
+ /// 店铺ID
+ ///
+ [Column(Name = "shop_id")]
+ public string ShopId { get; set; }
+
+ ///
+ /// 销售订单创建日期
+ ///
+ [Column(Name = "stat_date")]
+ public DateTime StatDate { get; set; }
+
+ ///
+ /// 订单数
+ ///
+ [Column(Name = "order_count")]
+ public int OrderCount { get; set; }
+
+ ///
+ /// 销售总额
+ ///
+ [Column(Name = "sales_amount")]
+ public long SalesAmount { get; set; }
+
+ ///
+ /// 利润
+ ///
+ [Column(Name = "profit_amount")]
+ public long ProfitAmount { get; set; }
+
+ ///
+ /// 平台佣金
+ ///
+ [Column(Name = "platform_fee")]
+ public long PlatformFee { get; set; }
+
+ ///
+ /// 退款订单数
+ ///
+ [Column(Name = "refund_order_count")]
+ public int RefundOrderCount { get; set; }
+
+ ///
+ /// 已成功退款金额
+ ///
+ [Column(Name = "refund_done")]
+ public long RefundDone { get; set; }
+
+ ///
+ /// 退款处理中金额
+ ///
+ [Column(Name = "refund_pending")]
+ public long RefundPending { get; set; }
+
+ ///
+ /// 创建时间
+ ///
+ [Column(Name = "created_at", ServerTime = DateTimeKind.Local, CanUpdate = false)]
+ public DateTime CreatedAt { get; set; }
+
+ ///
+ /// 最后刷新时间
+ ///
+ [Column(Name = "updated_at", ServerTime = DateTimeKind.Local)]
+ public DateTime UpdatedAt { get; set; }
+}
\ No newline at end of file
diff --git a/test/WebServiceTest/ShopTest.cs b/test/WebServiceTest/ShopTest.cs
index a504214..6ddbe11 100644
--- a/test/WebServiceTest/ShopTest.cs
+++ b/test/WebServiceTest/ShopTest.cs
@@ -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 ShopIds = new List
- {
- 1960967267443631281,
- 1960967267443631212,
- 1960967267443631213,
- 1960967267443631215,
- 1960967267443631216,
- 1960967267443631218,
- 1960967267443631219,
- 1960967267443631221,
- 1960967267443631222,
- 1960967267443631223,
- 1960967267443631224,
- 1960967267443631226,
- 1960967267443631227,
- 1960967267443631228,
- 1960967267443630057,
- 1960967267443629984,
- 1960967267443629858,
- 1960967267443629855,
- 1960967267443629809,
- 1960967267443629737,
- 1960967267443627649,
- 1960967267443627648
- };
+ // List ShopIds = new List
+ // {
+ //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();
- shopService.GetShopOrderAmountRange(ShopIds, DateTime.Now.AddMonths(-1), DateTime.Now);
+ //shopService.GetShopOrderAmountRange(id, DateTime.Now.AddDays(-10));
+ }
+
+
+ [Test]
+ public void GetShopSalesOrderTest()
+ {
+ var shopService = _container.Resolve();
+ 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();
+ shopService.BuyerDailyStats(DateTime.Parse("2026/10/02"), [1960967267443620449]);
+ }
+ [Test]
+ public void UpdateStatsTest()
+ {
+ var shopService = _container.Resolve();
+ shopService.UpdateStats(DateTime.Now.AddDays(-1).Date);
+ }
+
+ [Test]
+ public void GetShopStatsTest()
+ {
+ var shopService = _container.Resolve();
+ 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 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());
}
}
diff --git a/test/WebServiceTest/UserTest.cs b/test/WebServiceTest/UserTest.cs
index 4f773e6..c8f6f57 100644
--- a/test/WebServiceTest/UserTest.cs
+++ b/test/WebServiceTest/UserTest.cs
@@ -94,7 +94,7 @@ internal class UserTest
public void GetUserAmountSummate()
{
var userService = _container.Resolve();
- var data = userService.GetUserAmountSummary("15395107196");
+ var data = userService.GetUserAmountSummary("17734871710");
Console.WriteLine(JsonConvert.SerializeObject(data));
}
//[Test]