Files
29WxOrder/29WxOrderWpf/Ctrls/Statistics/Services/ShopRevenueStatisticsService.cs
T

657 lines
25 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using _29WxOrderWpf.Ctrls.Statistics.Models;
using AllBase;
using AllBase.Helper;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Serialization;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Work;
using WxOrder.Framework.Commons;
namespace _29WxOrderWpf.Ctrls.Statistics.Services
{
/// <summary>
/// 店铺营收统计 - 数据服务调用接口。
/// 页面只依赖此接口,接入真实数据源时新建实现类(如对接 HTTP 接口 / SQL 查询),
/// 并通过 ShopRevenueStatisticsViewModel 构造函数注入即可,无需改动页面。
/// </summary>
public interface IShopRevenueStatisticsService
{
/// <summary>
/// 查询原始统计明细(接口返回的 A.* / B.* / c.* 原始字段)。
/// 真实实现通常通过 SQL / HTTP 一次性返回这些行,再由聚合器计算。
/// </summary>
Task<List<ShopRevenueStatisticsRow>> QueryRowsAsync(ShopRevenueStatisticsQuery query);
/// <summary>获取顶部汇总卡片数据</summary>
Task<ShopRevenueSummary> GetSummaryAsync(ShopRevenueStatisticsQuery query);
/// <summary>获取销售趋势(按天)</summary>
Task<List<RevenueTrendPoint>> GetSalesTrendAsync(ShopRevenueStatisticsQuery query);
/// <summary>获取店铺销售占比(饼图)</summary>
Task<List<ShopSalesProportionItem>> GetShopSalesProportionAsync(ShopRevenueStatisticsQuery query);
/// <summary>获取店铺统计表格</summary>
Task<List<ShopStatisticsItem>> GetShopStatisticsAsync(ShopRevenueStatisticsQuery query);
/// <summary>获取天/周/月周期统计表格(每天/每周/每月各一行,按所选店铺或全部店铺统计)</summary>
Task<List<ShopPeriodStatisticsItem>> GetPeriodStatisticsAsync(ShopRevenueStatisticsQuery query, PeriodStatisticType periodType);
/// <summary>获取店铺下拉列表(用于查询条件)</summary>
Task<List<ShopOption>> GetShopOptionsAsync();
}
/// <summary>
/// 统计聚合器:把接口返回的原始明细行聚合成页面所需的汇总/趋势/占比/表格数据。
/// 聚合口径说明(依据接口字段描述):
/// - 订单状态 status:10-待付款;20-待发货;21-部分发货;30-待收货;100-完成;
/// 200-全部商品售后之后订单取消;250-未付款用户主动取消或超时未付款订单自动取消。
/// - 有效订单:已付款(20/21/30/100)且未退款;10/250 未付款不计入销售额。
/// - 已退款:c.refund_status 为 4/11,或订单状态为 200(全部商品售后后取消)。
/// - 金额字段单位均为“分”,除以 100 转为元。
/// </summary>
public static class RevenueStatisticsAggregator
{
/// <summary>订单状态 - 待付款</summary>
public const int StatusPendingPay = 10;
/// <summary>订单状态 - 待发货</summary>
public const int StatusWaitSend = 20;
/// <summary>订单状态 - 部分发货</summary>
public const int StatusPartSend = 21;
/// <summary>订单状态 - 待收货</summary>
public const int StatusWaitReceive = 30;
/// <summary>订单状态 - 完成</summary>
public const int StatusFinished = 100;
/// <summary>订单状态 - 全部商品售后之后订单取消</summary>
public const int StatusCancelAfterAfterSale = 200;
/// <summary>订单状态 - 未付款用户主动取消/超时未付款自动取消</summary>
public const int StatusCancelNotPay = 250;
/// <summary>是否已付款的订单(20/21/30/100;10 待付款、250 未付款取消不计入)</summary>
public static bool IsPaidOrder(ShopRevenueStatisticsRow row)
{
if (row == null) return false;
return row.Status == StatusWaitSend
|| row.Status == StatusPartSend
|| row.Status == StatusWaitReceive
|| row.Status == StatusFinished;
}
/// <summary>是否已退款(c.refund_status 为 4/11,或订单状态为 200 全部商品售后后取消)</summary>
public static bool IsRefunded(ShopRevenueStatisticsRow row)
{
if (row == null) return false;
return row.RefundStatus > 1 || row.RefundStatusString == "MERCHANT_REFUND_SUCCESS" || row.RefundStatusString == "MERCHANT_RETURN_SUCCESS";
}
/// <summary>是否有效订单(已付款且未退款)</summary>
public static bool IsValidOrder(ShopRevenueStatisticsRow row)
{
return !IsRefunded(row);
}
/// <summary>买手号退款成功</summary>
public static bool IsBuyerRefundSuccessful(ShopRevenueStatisticsRow row)
{
return row.BuyerRefundStatus == "退款成功";
}
private static List<ShopRevenueStatisticsRow> GetPaymentOrderList(IEnumerable<ShopRevenueStatisticsRow> rows)
{
return (rows ?? new List<ShopRevenueStatisticsRow>()).Where(a => a.OutAmount > 0).ToList(); //买手付钱后的订单数量
}
/// <summary>汇总卡片</summary>
public static ShopRevenueSummary GetSummary(IEnumerable<ShopRevenueStatisticsRow> rows)
{
var list = GetPaymentOrderList(rows); //买手付钱后的订单数量
var valid = list.Where(IsValidOrder).ToList();
var refunded = list.Where(IsRefunded).ToList();
decimal salesAmount = valid.Sum(r => (decimal)(r.OrderPrice)) / 100m;//有效订单总金额
decimal profit = valid.Concat(refunded).Sum(r => (decimal)(r.Profit)) / 100m;
decimal refundAmount = refunded.Sum(r => (decimal)(r.OrderPrice)) / 100m;
decimal platformFeeTotal = valid.Sum(r => (decimal)(r.PredictCommissionFee)) / 100m;
decimal commission = valid.Sum(r => (decimal)(r.CommissionAmount)) / 100m;
decimal purchaseAmount = valid.Sum(r => (decimal)(r.OutAmount)) / 100m;
decimal buyerPaymentAmount = valid.Sum(r => (decimal)(r.OutAmount)) / 100m; //买手号付款金额(采购金额)
decimal buyerRefundAmount = refunded.Sum(r => (decimal)(r.OutAmount)) / 100m; //买手号退款金额(采购退款金额)
decimal buyerRefundSuccessedAmount = refunded.Where(IsBuyerRefundSuccessful).Sum(r => (decimal)(r.BuyerRefundAmount)) / 100m; //买手号退款成功金额
int validOrderCount = valid.Select(r => r.OrderId).Distinct().Count();
// 总订单数(退款率分母)= 已付款订单 + 已退款订单,不含未付款的 10/250
int totalOrderCount = list.Count();
int refundOrderCount = refunded.Select(r => r.OrderId).Distinct().Count();
Debug.WriteLine($"计算利润:{(salesAmount - buyerPaymentAmount - platformFeeTotal)}");
Debug.WriteLine($"正常订单利润:{valid.Sum(r => (decimal)(r.Profit)) / 100}");
Debug.WriteLine($"售后订单利润:{refunded.Sum(r => (decimal)(r.Profit)) / 100}");
return new ShopRevenueSummary
{
SalesAmount = salesAmount,
ValidOrderCount = validOrderCount,
Profit = profit,
RefundAmount = refundAmount,
RefundOrderCount = refundOrderCount,
PlatformFeeTotal = platformFeeTotal,
Commission = commission,
AvgOrderAmount = validOrderCount == 0 ? 0m : salesAmount / validOrderCount,
RefundRate = totalOrderCount == 0 ? 0m : (decimal)refundOrderCount * 100m / totalOrderCount,
AverageProfitRate = purchaseAmount == 0m ? 0m : profit / purchaseAmount * 100m,
BuyerPaymentAmount = buyerPaymentAmount,
BuyerRefundAmount = buyerRefundAmount,
BuyerRefundSuccessedAmount = buyerRefundSuccessedAmount,
};
}
/// <summary>
/// 获取有效订单
/// </summary>
/// <param name="rows"></param>
/// <returns></returns>
public static List<ShopRevenueStatisticsRow> GetValidOrder(IEnumerable<ShopRevenueStatisticsRow> rows)
{
var list = GetPaymentOrderList(rows); //买手付钱后的订单数量
return list.Where(IsValidOrder).ToList();
}
/// <summary>
/// 获取退款订单
/// </summary>
/// <param name="rows"></param>
/// <returns></returns>
public static List<ShopRevenueStatisticsRow> GetRefundOrder(IEnumerable<ShopRevenueStatisticsRow> rows)
{
var list = GetPaymentOrderList(rows); //买手付钱后的订单数量
return list.Where(IsRefunded).ToList();
}
/// <summary>
/// 获取退款订单
/// </summary>
/// <param name="rows"></param>
/// <returns></returns>
public static List<ShopRevenueStatisticsRow> GetBuyerRefundRemainingOrder(IEnumerable<ShopRevenueStatisticsRow> rows)
{
var list = GetPaymentOrderList(rows); //买手付钱后的订单数量
var refunded = list.Where(IsRefunded).ToList();
decimal buyerRefundAmount = refunded.Sum(r => (decimal)(r.OutAmount)) / 100m; //买手号退款金额(采购退款金额)
decimal buyerRefundSuccessedAmount = refunded.Where(a => IsBuyerRefundSuccessful(a) == false).Sum(r => (decimal)(r.BuyerRefundAmount)) / 100m; //买手号退款成功金额
return refunded.Where(a => IsBuyerRefundSuccessful(a) == false).ToList();
}
/// <summary>销售趋势(按天,缺失日期补 0,仅统计有效订单)</summary>
public static List<RevenueTrendPoint> GetSalesTrend(IEnumerable<ShopRevenueStatisticsRow> rows, DateTime startDate, DateTime endDate)
{
var list = GetPaymentOrderList(rows);
list = list.Where(IsValidOrder).ToList();
var map = list
.GroupBy(r => r.CreateTime.Date)
.ToDictionary(
g => g.Key,
g => new RevenueTrendPoint
{
Date = g.Key,
SalesAmount = g.Sum(r => (decimal)(r.OrderPrice)) / 100m,
OrderCount = g.Select(r => r.OrderId).Distinct().Count()
});
var result = new List<RevenueTrendPoint>();
for (var d = startDate.Date; d <= endDate.Date; d = d.AddDays(1))
{
if (map.TryGetValue(d, out var point))
result.Add(point);
else
result.Add(new RevenueTrendPoint { Date = d, SalesAmount = 0m, OrderCount = 0 });
}
return result;
}
/// <summary>店铺销售占比(饼图,仅统计有效订单)</summary>
public static List<ShopSalesProportionItem> GetShopSalesProportion(IEnumerable<ShopRevenueStatisticsRow> rows)
{
var list = GetPaymentOrderList(rows);
list = list.Where(IsValidOrder).ToList();
decimal total = list.Sum(r => (decimal)(r.OrderPrice)) / 100m;
return list
.GroupBy(r => r.ShopName)
.Select(g => new ShopSalesProportionItem
{
ShopName = string.IsNullOrEmpty(g.Key) ? "未知店铺" : g.Key,
SalesAmount = g.Sum(r => (decimal)(r.OrderPrice)) / 100m,
Percentage = total == 0m ? 0d : (double)(g.Sum(r => (decimal)(r.OrderPrice)) / 100m / total * 100m)
})
.OrderByDescending(i => i.SalesAmount)
.ToList();
}
/// <summary>店铺统计表格</summary>
public static List<ShopStatisticsItem> GetShopStatistics(IEnumerable<ShopRevenueStatisticsRow> rows)
{
var list = GetPaymentOrderList(rows); //买手付钱后的订单
return list
.GroupBy(r => r.ShopName)
.Select(g =>
{
var valid = g.Where(IsValidOrder).ToList();
var refunded = g.Where(IsRefunded).ToList();
decimal salesAmount = valid.Sum(r => (decimal)(r.OrderPrice)) / 100m; //与 GetSummary 一致:不含已退款订单的销售额
int orderCount = valid.Select(r => r.OrderId).Distinct().Count();
decimal profit = valid.Concat(refunded).Sum(r => (decimal)(r.Profit)) / 100m; //与 GetSummary 一致:有效+退款订单利润
decimal purchaseAmount = valid.Sum(r => (decimal)(r.OutAmount)) / 100m; //与 GetSummary 一致:买手付款金额(采购金额)
decimal buyerPaymentAmount = valid.Sum(r => (decimal)(r.OutAmount)) / 100m; //买手号付款金额(采购金额)
return new ShopStatisticsItem
{
ShopName = string.IsNullOrEmpty(g.Key) ? "未知店铺" : g.Key,
OrderCount = orderCount,
SalesAmount = salesAmount,
BuyerPaymentAmount = buyerPaymentAmount,
Profit = profit,
RefundAmount = refunded.Sum(r => (decimal)(r.OrderPrice)) / 100m,
PlatformFeeTotal = valid.Sum(r => (decimal)(r.PredictCommissionFee)) / 100m,
Commission = valid.Sum(r => (decimal)(r.CommissionAmount)) / 100m,
AvgOrderAmount = orderCount == 0 ? 0m : salesAmount / orderCount,
AverageProfitRate = purchaseAmount == 0m ? 0m : profit / purchaseAmount * 100m
};
})
.OrderByDescending(i => i.SalesAmount)
.ToList();
}
/// <summary>天/周/月周期统计表格(每天/每周/每月各一行,无数据的周期也补一行,金额单位:元)</summary>
public static List<ShopPeriodStatisticsItem> GetPeriodStatistics(IEnumerable<ShopRevenueStatisticsRow> rows, DateTime startDate, DateTime endDate, PeriodStatisticType periodType)
{
var list = (rows ?? new List<ShopRevenueStatisticsRow>()).Where(a => a.OutAmount > 0).ToList(); //买手付钱后的订单
// 有数据的周期
var map = list
.GroupBy(r => GetPeriodStart(r.CreateTime, periodType))
.ToDictionary(
g => g.Key,
g =>
{
var valid = g.Where(IsValidOrder).ToList();
var refunded = g.Where(IsRefunded).ToList();
decimal salesAmount = valid.Sum(r => (decimal)(r.OrderPrice)) / 100m; //与 GetSummary 一致:不含已退款订单的销售额
decimal profit = valid.Concat(refunded).Sum(r => (decimal)(r.Profit)) / 100m; //与 GetSummary 一致:有效+退款订单利润
decimal purchaseAmount = valid.Sum(r => (decimal)(r.OutAmount)) / 100m; //与 GetSummary 一致:买手付款金额(采购金额)
int orderCount = valid.Select(r => r.OrderId).Distinct().Count();
decimal buyerPaymentAmount = valid.Sum(r => (decimal)(r.OutAmount)) / 100m; //买手号付款金额(采购金额)
return new ShopPeriodStatisticsItem
{
PeriodStart = g.Key,
Period = GetPeriodText(g.Key, periodType),
OrderCount = orderCount,
SalesAmount = salesAmount,
BuyerPaymentAmount = buyerPaymentAmount,
Profit = profit,
RefundAmount = refunded.Sum(r => (decimal)(r.OrderPrice)) / 100m,
PlatformFeeTotal = valid.Sum(r => (decimal)(r.PredictCommissionFee)) / 100m,
Commission = valid.Sum(r => (decimal)(r.CommissionAmount)) / 100m,
AverageProfitRate = purchaseAmount == 0m ? 0m : profit / purchaseAmount * 100m,
CustomerCount = orderCount,
AvgOrderAmount = orderCount == 0 ? 0m : salesAmount / orderCount
};
});
// 按周期补齐整段时间:无数据的周期也生成一行(全 0)
var result = new List<ShopPeriodStatisticsItem>();
foreach (var periodStart in EnumeratePeriods(startDate, endDate, periodType))
{
if (map.TryGetValue(periodStart, out var item))
result.Add(item);
else
result.Add(new ShopPeriodStatisticsItem
{
PeriodStart = periodStart,
Period = GetPeriodText(periodStart, periodType),
OrderCount = 0,
SalesAmount = 0m,
Profit = 0m,
RefundAmount = 0m,
PlatformFeeTotal = 0m,
Commission = 0m,
AverageProfitRate = 0m,
CustomerCount = 0,
AvgOrderAmount = 0m
});
}
// 倒序排列:最新的周期在最上面
result.Reverse();
return result;
}
/// <summary>枚举 startDate~endDate 覆盖的所有周期开始日期(天/周/月)</summary>
private static IEnumerable<DateTime> EnumeratePeriods(DateTime startDate, DateTime endDate, PeriodStatisticType periodType)
{
var start = GetPeriodStart(startDate, periodType);
var end = GetPeriodStart(endDate, periodType);
switch (periodType)
{
case PeriodStatisticType.Week:
for (var d = start; d <= end; d = d.AddDays(7))
yield return d;
break;
case PeriodStatisticType.Month:
for (var d = start; d <= end; d = d.AddMonths(1))
yield return d;
break;
default:
for (var d = start; d <= end; d = d.AddDays(1))
yield return d;
break;
}
}
/// <summary>周期开始日期:天=当天;周=周一起始;月=当月1号</summary>
private static DateTime GetPeriodStart(DateTime time, PeriodStatisticType periodType)
{
switch (periodType)
{
case PeriodStatisticType.Week:
var d = time.Date;
int diff = d.DayOfWeek == DayOfWeek.Sunday ? 6 : (int)d.DayOfWeek - 1;
return d.AddDays(-diff);
case PeriodStatisticType.Month:
return new DateTime(time.Year, time.Month, 1);
default:
return time.Date;
}
}
/// <summary>周期显示文本:天=yyyy-MM-dd;周=yyyy-MM-dd ~ yyyy-MM-dd;月=yyyy-MM</summary>
private static string GetPeriodText(DateTime periodStart, PeriodStatisticType periodType)
{
switch (periodType)
{
case PeriodStatisticType.Week:
return periodStart.ToString("yyyy-MM-dd") + " ~ " + periodStart.AddDays(6).ToString("yyyy-MM-dd");
case PeriodStatisticType.Month:
return periodStart.ToString("yyyy-MM");
default:
return periodStart.ToString("yyyy-MM-dd");
}
}
}
/// <summary>
/// 默认实现:请求真实接口(shop_revenue_statistics/get_shop_order_amount)。
/// 接入其他数据源时,新建实现类并注入即可(见 IShopRevenueStatisticsService 注释)。
/// </summary>
public class ShopRevenueStatisticsService : IShopRevenueStatisticsService
{
/// <summary>缓存有效期:订单/退款数据会持续变化,超过该时长后缓存自动失效并重新请求。</summary>
private static readonly TimeSpan CacheDuration = TimeSpan.FromSeconds(60);
/// <summary>
/// 明细数据缓存(按天缓存,纯时间维度,不含店铺)。
/// key = 日期(yyyyMMdd),value = 该天全部店铺的明细 + 缓存时间。
/// 查询时按天命中:未命中的天合并成连续段一次请求,返回后按天拆分写入;
/// 前台选某一个或某多个店铺时,按所选店铺(ShopName)过滤,复用同一批时间数据。
/// </summary>
private static readonly Dictionary<string, CacheEntry> _rowCache =
new Dictionary<string, CacheEntry>();
private readonly object _cacheLock = new object();
private sealed class DateRange
{
public DateTime Start;
public DateTime End;
}
private sealed class CacheEntry
{
public List<ShopRevenueStatisticsRow> Rows;
public DateTime CachedAt;
}
private static string CacheKey(DateTime day)
{
return day.Date.ToString("yyyyMMdd");
}
/// <summary>枚举 startDate ~ endDate 之间的每一天(含首尾)</summary>
private static IEnumerable<DateTime> EnumerateDays(DateTime startDate, DateTime endDate)
{
for (var d = startDate.Date; d <= endDate.Date; d = d.AddDays(1))
yield return d;
}
/// <summary>该天缓存是否有效(存在且未过期);调用方需持有 _cacheLock</summary>
private bool IsValidLocked(DateTime day)
{
return _rowCache.TryGetValue(CacheKey(day), out var entry)
&& DateTime.Now - entry.CachedAt <= CacheDuration;
}
/// <summary>把缺失日期合并为连续日期段(每段一次请求)</summary>
private static List<DateRange> BuildRuns(List<DateTime> missingDays)
{
var sorted = missingDays.OrderBy(d => d).ToList();
var runs = new List<DateRange>();
int i = 0;
while (i < sorted.Count)
{
var start = sorted[i];
var end = start;
i++;
while (i < sorted.Count && sorted[i] == end.AddDays(1))
{
end = sorted[i];
i++;
}
runs.Add(new DateRange { Start = start, End = end });
}
return runs;
}
public async Task<List<ShopRevenueStatisticsRow>> QueryRowsAsync(ShopRevenueStatisticsQuery query)
{
var startDay = query.StartDate.Date;
var endDay = query.EndDate.Date;
if (startDay > endDay)
{
var tmp = startDay;
startDay = endDay;
endDay = tmp;
}
// 1. 找出未命中或已过期的天
List<DateTime> missingDays;
lock (_cacheLock)
{
missingDays = EnumerateDays(startDay, endDay)
.Where(d => !IsValidLocked(d))
.ToList();
}
// 2. 有缺失:按连续段请求整个时间范围的全部店铺明细,并按天写入缓存
if (missingDays.Count > 0)
{
var shopIds = GetShopOptions().Select(a => a.ShopId).ToList();
if (shopIds.Count == 0)
return new List<ShopRevenueStatisticsRow>();
foreach (var run in BuildRuns(missingDays))
{
var rows = await RequestRowsAsync(shopIds, run.Start, run.End);
CacheResponse(run.Start, run.End, rows);
}
}
// 3. 按天从缓存拼装完整范围,再按所选店铺过滤(ShopKeys 为空 = 全部店铺)
return FilterByShop(GetCachedRows(startDay, endDay), query.ShopKeys);
}
/// <summary>
/// 把一次请求的结果按天写入缓存。
/// 本次请求覆盖的每一天都以返回数据为准整体替换(无数据的天记为空的已缓存,
/// 避免“该天无数据”被反复判定为未命中而重复请求)。
/// </summary>
private void CacheResponse(DateTime startDate, DateTime endDate, List<ShopRevenueStatisticsRow> rows)
{
if (rows == null)
rows = new List<ShopRevenueStatisticsRow>();
lock (_cacheLock)
{
RemoveExpiredEntriesLocked();
var byDay = rows
.Where(r => r.CreateTime.Date >= startDate.Date && r.CreateTime.Date <= endDate.Date)
.GroupBy(r => r.CreateTime.Date)
.ToDictionary(g => g.Key, g => g.ToList());
foreach (var day in EnumerateDays(startDate, endDate))
{
byDay.TryGetValue(day, out var dayRows);
_rowCache[CacheKey(day)] = new CacheEntry { Rows = dayRows ?? new List<ShopRevenueStatisticsRow>(), CachedAt = DateTime.Now };
}
}
}
/// <summary>按天从缓存拼装某时间范围的明细(返回新列表,避免外部修改污染缓存)</summary>
private List<ShopRevenueStatisticsRow> GetCachedRows(DateTime startDate, DateTime endDate)
{
var result = new List<ShopRevenueStatisticsRow>();
lock (_cacheLock)
{
foreach (var day in EnumerateDays(startDate, endDate))
{
if (_rowCache.TryGetValue(CacheKey(day), out var entry))
result.AddRange(entry.Rows);
}
}
return result;
}
/// <summary>按所选店铺过滤明细:ShopKeys 为空/Null 返回全部;否则按店铺名(ShopName)精确匹配。</summary>
private static List<ShopRevenueStatisticsRow> FilterByShop(List<ShopRevenueStatisticsRow> rows, List<string> shopKeys)
{
if (rows == null)
return new List<ShopRevenueStatisticsRow>();
if (shopKeys == null || shopKeys.Count == 0)
return new List<ShopRevenueStatisticsRow>(rows);
var shops = GetShopOptions()
.Where(a => shopKeys.Contains(a.AppKey))
.Where(a => !string.IsNullOrEmpty(a.ShopName))
.ToList();
if (shops.Count == 0)
return new List<ShopRevenueStatisticsRow>();
return rows
.Where(r => shops.Any(s => string.Equals(r.ShopName, s.ShopName, StringComparison.OrdinalIgnoreCase)))
.ToList();
}
/// <summary>清理已过期的缓存条目(调用方需持有 _cacheLock)</summary>
private void RemoveExpiredEntriesLocked()
{
var expiredKeys = _rowCache
.Where(p => DateTime.Now - p.Value.CachedAt > CacheDuration)
.Select(p => p.Key)
.ToList();
foreach (var key in expiredKeys)
_rowCache.Remove(key);
}
/// <summary>请求接口一次(可传多个店铺),返回原始明细行</summary>
private async Task<List<ShopRevenueStatisticsRow>> RequestRowsAsync(List<string> shopIds, DateTime startDate, DateTime endDate)
{
var url = BaseClass.Build29WxObjString("shop_revenue_statistics/get_shop_order_amount");
var sb = new StringBuilder();
sb.Append(UserLogin.GetSignaturex());
sb.Append("&shop_ids=").Append(string.Join(",", shopIds));
sb.Append("&start_time=").Append(BaseClass.ConvertDateTimeLong(startDate.Date));
sb.Append("&end_time=").Append(BaseClass.ConvertDateTimeLong(endDate.Date.AddDays(1).AddTicks(-1)));
var html = await GetHtml.PostUtf8Async(url, sb.ToString());
try
{
var ret = JsonConvert.DeserializeObject<Result<List<ShopRevenueStatisticsRow>>>(html, new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver()
});
if (ret.Code != 200)
{
throw new Exception(ret.Msg);
}
return ret.Data ?? new List<ShopRevenueStatisticsRow>();
}
catch
{
throw new Exception("查询失败");
}
}
public async Task<ShopRevenueSummary> GetSummaryAsync(ShopRevenueStatisticsQuery query)
{
var rows = await QueryRowsAsync(query);
return RevenueStatisticsAggregator.GetSummary(rows);
}
public async Task<List<RevenueTrendPoint>> GetSalesTrendAsync(ShopRevenueStatisticsQuery query)
{
var rows = await QueryRowsAsync(query);
return RevenueStatisticsAggregator.GetSalesTrend(rows, query.StartDate, query.EndDate);
}
public async Task<List<ShopSalesProportionItem>> GetShopSalesProportionAsync(ShopRevenueStatisticsQuery query)
{
var rows = await QueryRowsAsync(query);
return RevenueStatisticsAggregator.GetShopSalesProportion(rows);
}
public async Task<List<ShopStatisticsItem>> GetShopStatisticsAsync(ShopRevenueStatisticsQuery query)
{
var rows = await QueryRowsAsync(query);
return RevenueStatisticsAggregator.GetShopStatistics(rows);
}
/// <summary>获取天/周/月周期统计表格</summary>
public async Task<List<ShopPeriodStatisticsItem>> GetPeriodStatisticsAsync(ShopRevenueStatisticsQuery query, PeriodStatisticType periodType)
{
var rows = await QueryRowsAsync(query);
return RevenueStatisticsAggregator.GetPeriodStatistics(rows, query.StartDate, query.EndDate, periodType);
}
/// <summary>店铺下拉列表(同步读取,供查询与过滤使用)</summary>
private static List<ShopOption> GetShopOptions()
{
return GlobalConfig.ShopLoginList.Select(a =>
new ShopOption()
{
AppKey = a.ShopId,
ShopId = a.ErpShopId,
ShopName = a.ShopAccount
}).ToList();
}
public Task<List<ShopOption>> GetShopOptionsAsync()
{
return Task.FromResult(GetShopOptions());
}
}
}