增加 统计新接口
增加每日02时自动更新统计
This commit is contained in:
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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时按用户名下的商铺查询)
|
||||
/// 有效订单:没有发生售后的订单;退款订单:发生过售后的订单;买手未退回:退款订单中买手款项还没退回的
|
||||
/// 未退回:买手订单存在退款信息(退款金额 > 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>
|
||||
/// 买手未退回金额:不是"退款成功" 状态的买手订单退款金额之和
|
||||
/// 只算有退款信息的(退款金额 > 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,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;
|
||||
}
|
||||
}
|
||||
@@ -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" />
|
||||
|
||||
@@ -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
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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]
|
||||
|
||||
Reference in new issue
Block a user