Files
LabelChange-server/Bak/DAL/repositories/LabelReplaceRepository.cs
2026-06-01 16:30:29 +08:00

1043 lines
42 KiB
C#
Raw Permalink 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 System;
using System.Collections.Generic;
using System.Threading.Tasks;
using MDL.Models;
using MDL.DTOs;
using DAL.Interfaces;
using DB.Database;
using SqlSugar;
namespace DAL.Repositories
{
/// <summary>
/// 标签替换请求的数据访问实现类
/// </summary>
public class LabelReplaceRepository : ILabelReplaceRepository
{
private readonly ISqlSugarProvider _provider;
/// <summary>
/// 构造函数
/// </summary>
/// <param name="provider">SqlSugar数据库提供程序</param>
public LabelReplaceRepository(ISqlSugarProvider provider)
{
_provider = provider;
}
/// <summary>
/// 创建标签替换请求记录
/// </summary>
/// <param name="entity">标签替换请求实体</param>
/// <returns>创建的记录ID</returns>
public async Task<int> CreateAsync(LabelReplaceEntity entity)
{
var db = _provider.GetClient();
// 确保表存在
// 使用更安全的表初始化方式
if (!db.DbMaintenance.IsAnyTable("label_replace_requests"))
{
db.CodeFirst.InitTables(typeof(LabelReplaceEntity));
}
// 设置时间戳
entity.CreatedAt = DateTime.UtcNow;
entity.UpdatedAt = DateTime.UtcNow;
// 插入数据并返回自增ID
return await db.Insertable(entity).ExecuteReturnIdentityAsync();
}
/// <summary>
/// 根据ID获取标签替换请求记录
/// </summary>
/// <param name="id">记录ID</param>
/// <returns>标签替换请求实体</returns>
public async Task<LabelReplaceEntity?> GetByIdAsync(int id)
{
var db = _provider.GetClient();
return await db.Queryable<LabelReplaceEntity>()
.Where(x => x.Id == id)
.FirstAsync();
}
/// <summary>
/// 根据中性面单单号获取标签替换请求记录
/// </summary>
/// <param name="waybillNumber">中性面单单号</param>
/// <returns>标签替换请求实体</returns>
public async Task<LabelReplaceEntity?> GetByWaybillNumberAsync(string waybillNumber)
{
var db = _provider.GetClient();
return await db.Queryable<LabelReplaceEntity>()
.Where(x => x.NeutralWaybillNumber == waybillNumber)
.FirstAsync();
}
/// <summary>
/// 根据跟踪单号获取标签替换请求记录
/// </summary>
/// <param name="trackingNumber">跟踪单号</param>
/// <returns>标签替换请求实体列表</returns>
public async Task<List<LabelReplaceEntity>> GetByTrackingNumberAsync(string trackingNumber)
{
var db = _provider.GetClient();
return await db.Queryable<LabelReplaceEntity>()
.Where(x => x.FinalMileTrackingNumber == trackingNumber)
.OrderByDescending(x => x.CreatedAt)
.ToListAsync();
}
/// <summary>
/// 更新标签替换请求记录
/// </summary>
/// <param name="entity">标签替换请求实体</param>
/// <returns>更新是否成功</returns>
public async Task<bool> UpdateAsync(LabelReplaceEntity entity)
{
var db = _provider.GetClient();
// 更新时间戳
entity.UpdatedAt = DateTime.UtcNow;
// 更新数据并返回影响行数
var rows = await db.Updateable(entity).ExecuteCommandAsync();
return rows > 0;
}
/// <summary>
/// 删除标签替换请求记录
/// </summary>
/// <param name="id">记录ID</param>
/// <returns>删除是否成功</returns>
public async Task<bool> DeleteAsync(int id)
{
var db = _provider.GetClient();
var rows = await db.Deleteable<LabelReplaceEntity>()
.Where(x => x.Id == id)
.ExecuteCommandAsync();
return rows > 0;
}
/// <summary>
/// 获取所有标签替换请求记录
/// </summary>
/// <returns>标签替换请求实体列表</returns>
public async Task<List<LabelReplaceEntity>> GetAllAsync()
{
var db = _provider.GetClient();
return await db.Queryable<LabelReplaceEntity>()
.OrderByDescending(x => x.CreatedAt)
.ToListAsync();
}
/// <summary>
/// 分页获取标签替换请求记录(包含客户信息)
/// </summary>
/// <param name="page">页码</param>
/// <param name="pageSize">每页大小</param>
/// <param name="sortBy">排序字段</param>
/// <param name="sortOrder">排序顺序</param>
/// <param name="billOfLadingNumber">提单号</param>
/// <param name="masterPackageNumber">大包号</param>
/// <param name="referenceNumber">参考号</param>
/// <param name="neutralWaybillNumber">中性面单单号</param>
/// <param name="finalMileTrackingNumber">尾程跟踪单号</param>
/// <param name="replaceStatus">换单状态</param>
/// <param name="customerId">客户ID</param>
/// <param name="startCreatedAt">创建时间开始</param>
/// <param name="endCreatedAt">创建时间结束</param>
/// <param name="startReplacedAt">换单时间开始</param>
/// <param name="endReplacedAt">换单时间结束</param>
/// <returns>包含客户信息的标签替换请求DTO列表和总记录数</returns>
public async Task<(List<MDL.DTOs.LabelReplaceWithCustomerDto>, int)> GetByPageAsync(int page, int pageSize, string sortBy, string sortOrder, string billOfLadingNumber, string masterPackageNumber, string referenceNumber, string neutralWaybillNumber, string finalMileTrackingNumber, string replaceStatus, int? customerId, string startCreatedAt, string endCreatedAt, string startReplacedAt, string endReplacedAt)
{
var db = _provider.GetClient();
// 解析时间筛选参数
DateTime? parsedStartCreatedAt = null;
DateTime? parsedEndCreatedAt = null;
DateTime? parsedStartReplacedAt = null;
DateTime? parsedEndReplacedAt = null;
if (!string.IsNullOrEmpty(startCreatedAt) && DateTime.TryParse(startCreatedAt, out DateTime startCreatedAtDate))
{
parsedStartCreatedAt = startCreatedAtDate;
}
if (!string.IsNullOrEmpty(endCreatedAt) && DateTime.TryParse(endCreatedAt, out DateTime endCreatedAtDate))
{
parsedEndCreatedAt = endCreatedAtDate;
}
if (!string.IsNullOrEmpty(startReplacedAt) && DateTime.TryParse(startReplacedAt, out DateTime startReplacedAtDate))
{
parsedStartReplacedAt = startReplacedAtDate;
}
if (!string.IsNullOrEmpty(endReplacedAt) && DateTime.TryParse(endReplacedAt, out DateTime endReplacedAtDate))
{
parsedEndReplacedAt = endReplacedAtDate;
}
// 先创建基础查询
var baseQuery = db.Queryable<LabelReplaceEntity>();
// 应用基础筛选条件
if (!string.IsNullOrEmpty(billOfLadingNumber))
{
baseQuery = baseQuery.Where(l => l.BillOfLadingNumber.Contains(billOfLadingNumber));
}
if (!string.IsNullOrEmpty(masterPackageNumber))
{
baseQuery = baseQuery.Where(l => l.MasterPackageNumber.Contains(masterPackageNumber));
}
if (!string.IsNullOrEmpty(referenceNumber))
{
baseQuery = baseQuery.Where(l => l.ReferenceNumber.Contains(referenceNumber));
}
if (!string.IsNullOrEmpty(neutralWaybillNumber))
{
// 处理多个单号的情况,支持逗号分隔
var waybillNumbers = neutralWaybillNumber.Split(new[] { ',', '\n', '\r', ' ' }, StringSplitOptions.RemoveEmptyEntries);
if (waybillNumbers.Length > 1)
{
baseQuery = baseQuery.Where(l => waybillNumbers.Contains(l.NeutralWaybillNumber));
}
else if (waybillNumbers.Length == 1)
{
baseQuery = baseQuery.Where(l => l.NeutralWaybillNumber.Contains(waybillNumbers[0]));
}
}
if (!string.IsNullOrEmpty(finalMileTrackingNumber))
{
baseQuery = baseQuery.Where(l => l.FinalMileTrackingNumber.Contains(finalMileTrackingNumber));
}
if (!string.IsNullOrEmpty(replaceStatus))
{
baseQuery = baseQuery.Where(l => l.ReplaceStatus == replaceStatus);
}
if (customerId.HasValue)
{
baseQuery = baseQuery.Where(l => l.CustomerId == customerId);
}
// 只应用 CreatedAt 筛选条件ReplacedAt 稍后在内存中筛选
if (parsedStartCreatedAt.HasValue)
{
baseQuery = baseQuery.Where(l => l.CreatedAt >= parsedStartCreatedAt.Value);
}
if (parsedEndCreatedAt.HasValue)
{
baseQuery = baseQuery.Where(l => l.CreatedAt <= parsedEndCreatedAt.Value);
}
// 先获取所有符合基础条件的记录(不分页)
var allLabelReplaceEntities = await baseQuery.ToListAsync();
// 如果没有数据,直接返回
if (allLabelReplaceEntities.Count == 0)
{
return (new List<MDL.DTOs.LabelReplaceWithCustomerDto>(), 0);
}
// 提取所有中性面单单号,用于批量查询最新扫描记录
var scanWaybillNumbers = allLabelReplaceEntities.Select(l => l.NeutralWaybillNumber).Distinct().ToList();
// 批量查询每个中性面单单号的最新扫描成功记录
var latestScans = new List<MDL.Models.LabelScanEntity>();
if (scanWaybillNumbers.Count > 0)
{
// 先获取所有符合条件的扫描记录
var allScans = await db.Queryable<MDL.Models.LabelScanEntity>()
.Where(s => scanWaybillNumbers.Contains(s.NeutralWaybillNumber) && s.Result == 0)
.OrderByDescending(s => s.CreatedAt)
.ToListAsync();
// 按中性面单单号分组,取每组的第一条记录(最新的)
var groupedScans = allScans.GroupBy(s => s.NeutralWaybillNumber);
foreach (var group in groupedScans)
{
latestScans.Add(group.First());
}
}
// 创建中性面单单号到最新扫描时间的映射,确保类型正确
var latestScanMap = new Dictionary<string, DateTime>();
foreach (var scan in latestScans)
{
latestScanMap[scan.NeutralWaybillNumber] = scan.CreatedAt;
}
// 根据 ReplacedAt 筛选记录
var filteredEntities = new List<LabelReplaceEntity>();
foreach (var entity in allLabelReplaceEntities)
{
bool shouldInclude = true;
// 获取此订单的ReplacedAt时间
DateTime? replacedAt = null;
if (latestScanMap.TryGetValue(entity.NeutralWaybillNumber, out var latestCreatedAt))
{
replacedAt = latestCreatedAt;
}
// 应用 ReplacedAt 筛选
if (parsedStartReplacedAt.HasValue)
{
if (!replacedAt.HasValue || replacedAt.Value < parsedStartReplacedAt.Value)
{
shouldInclude = false;
}
}
if (parsedEndReplacedAt.HasValue)
{
if (!replacedAt.HasValue || replacedAt.Value > parsedEndReplacedAt.Value)
{
shouldInclude = false;
}
}
if (shouldInclude)
{
filteredEntities.Add(entity);
}
}
// 计算筛选后的总记录数
int totalCount = filteredEntities.Count;
// 对筛选后的实体进行排序
if (!string.IsNullOrEmpty(sortBy))
{
if (sortOrder.ToLower() == "asc")
{
// 根据常用排序字段排序
filteredEntities = sortBy switch
{
"CreatedAt" => filteredEntities.OrderBy(e => e.CreatedAt).ToList(),
"UpdatedAt" => filteredEntities.OrderBy(e => e.UpdatedAt).ToList(),
"Id" => filteredEntities.OrderBy(e => e.Id).ToList(),
_ => filteredEntities.OrderBy(e => e.CreatedAt).ToList()
};
}
else
{
filteredEntities = sortBy switch
{
"CreatedAt" => filteredEntities.OrderByDescending(e => e.CreatedAt).ToList(),
"UpdatedAt" => filteredEntities.OrderByDescending(e => e.UpdatedAt).ToList(),
"Id" => filteredEntities.OrderByDescending(e => e.Id).ToList(),
_ => filteredEntities.OrderByDescending(e => e.CreatedAt).ToList()
};
}
}
else
{
filteredEntities = filteredEntities.OrderByDescending(e => e.CreatedAt).ToList();
}
// 应用分页
var pagedEntities = filteredEntities
.Skip((page - 1) * pageSize)
.Take(pageSize)
.ToList();
// 如果分页后没有数据,直接返回
if (pagedEntities.Count == 0)
{
return (new List<MDL.DTOs.LabelReplaceWithCustomerDto>(), totalCount);
}
// 提取所有非null的客户ID用于批量查询
var customerIds = pagedEntities.Where(l => l.CustomerId.HasValue).Select(l => l.CustomerId.Value).Distinct().ToList();
// 批量查询客户信息
var customerList = await db.Queryable<MDL.Models.CustomerEntity>()
.Where(c => customerIds.Contains(c.Id))
.ToListAsync();
// 手动构建字典,确保类型正确
Dictionary<int, string> customers = new Dictionary<int, string>();
foreach (var customer in customerList)
{
customers[customer.Id] = customer.CustomerCode;
}
// 构建DTO列表
var dtos = new List<MDL.DTOs.LabelReplaceWithCustomerDto>();
foreach (var l in pagedEntities)
{
var dto = new MDL.DTOs.LabelReplaceWithCustomerDto
{
Id = l.Id,
BillOfLadingNumber = l.BillOfLadingNumber,
MasterPackageNumber = l.MasterPackageNumber,
ReferenceNumber = l.ReferenceNumber,
NeutralWaybillNumber = l.NeutralWaybillNumber,
FinalMileTrackingNumber = l.FinalMileTrackingNumber,
Label = l.Label,
HasLabel = !string.IsNullOrEmpty(l.Label),
ReplaceStatus = l.ReplaceStatus,
LabelRetrievedAt = l.LabelRetrievedAt,
CreatedAt = l.CreatedAt,
UpdatedAt = l.UpdatedAt
};
// 处理客户代码
if (l.CustomerId.HasValue)
{
if (customers.TryGetValue(l.CustomerId.Value, out var customerCode))
{
dto.CustomerCode = customerCode;
}
}
// 处理扫描时间
if (latestScanMap.TryGetValue(l.NeutralWaybillNumber, out var latestCreatedAt))
{
dto.ReplacedAt = latestCreatedAt;
}
dtos.Add(dto);
}
return (dtos, totalCount);
}
/// <summary>
/// 批量查询换单状态
/// </summary>
/// <param name="customerId">客户ID</param>
/// <param name="waybillNumbers">中性面单单号列表</param>
/// <param name="trackingNumbers">尾程跟踪单号列表</param>
/// <returns>标签替换请求实体列表</returns>
public async Task<(List<LabelReplaceEntity>, Dictionary<string, DateTime>)> GetLabelReplaceStatusAsync(int customerId, List<string> waybillNumbers, List<string> trackingNumbers)
{
var db = _provider.GetClient();
// 构建查询条件
var query = db.Queryable<LabelReplaceEntity>()
.Where(x => x.CustomerId == customerId);
// 添加单号查询条件
if (waybillNumbers != null && waybillNumbers.Count > 0)
{
query = query.Where(x => waybillNumbers.Contains(x.NeutralWaybillNumber));
}
if (trackingNumbers != null && trackingNumbers.Count > 0)
{
query = query.Where(x => x.FinalMileTrackingNumber != null && trackingNumbers.Contains(x.FinalMileTrackingNumber));
}
// 执行查询
var labelReplaceEntities = await query.ToListAsync();
// 提取所有中性面单单号,用于批量查询最新扫描记录
var scanWaybillNumbers = labelReplaceEntities.Select(l => l.NeutralWaybillNumber).Distinct().ToList();
// 批量查询每个中性面单单号的最新扫描成功记录
var latestScanMap = new Dictionary<string, DateTime>();
if (scanWaybillNumbers.Count > 0)
{
// 先获取所有符合条件的扫描记录
var allScans = await db.Queryable<MDL.Models.LabelScanEntity>()
.Where(s => scanWaybillNumbers.Contains(s.NeutralWaybillNumber) && s.Result == 0)
.OrderByDescending(s => s.CreatedAt)
.ToListAsync();
// 按中性面单单号分组,取每组的第一条记录(最新的)
var groupedScans = allScans.GroupBy(s => s.NeutralWaybillNumber);
foreach (var group in groupedScans)
{
var latestScan = group.First();
latestScanMap[latestScan.NeutralWaybillNumber] = latestScan.CreatedAt;
}
}
return (labelReplaceEntities, latestScanMap);
}
/// <summary>
/// 根据交接单号列表批量获取标签替换请求记录
/// </summary>
/// <param name="handoverNumbers">交接单号列表</param>
/// <param name="customerId">客户ID</param>
/// <returns>标签替换请求实体列表</returns>
public async Task<List<LabelReplaceEntity>> GetByHandoverNumbersAsync(List<string> handoverNumbers, int? customerId)
{
var db = _provider.GetClient();
if (handoverNumbers == null || handoverNumbers.Count == 0)
return new List<LabelReplaceEntity>();
var query = db.Queryable<LabelReplaceEntity>()
.Where(x => handoverNumbers.Contains(x.BillOfLadingNumber) || handoverNumbers.Contains(x.MasterPackageNumber));
if (customerId.HasValue)
{
query = query.Where(x => x.CustomerId == customerId.Value);
}
return await query.ToListAsync();
}
/// <summary>
/// 将base64编码转换为PDF文件并保存
/// </summary>
/// <param name="base64Content">base64编码的PDF内容</param>
/// <param name="fileName">文件名</param>
/// <returns>保存的文件路径</returns>
public async Task<string> ConvertBase64ToPdfAsync(string base64Content, string fileName)
{
// 定义保存目录
string saveDirectory = @"C:\TESYSTEM\OMS\api-lable\pdf\20260302";
// 确保目录存在
if (!System.IO.Directory.Exists(saveDirectory))
{
System.IO.Directory.CreateDirectory(saveDirectory);
}
// 构建完整的文件路径
string filePath = System.IO.Path.Combine(saveDirectory, fileName);
// 处理base64内容移除可能的前缀
if (base64Content.StartsWith("data:application/pdf;base64,"))
{
base64Content = base64Content.Substring("data:application/pdf;base64,".Length);
}
// 转换base64为字节数组
byte[] pdfBytes = Convert.FromBase64String(base64Content);
// 保存到文件
await System.IO.File.WriteAllBytesAsync(filePath, pdfBytes);
return filePath;
}
/// <summary>
/// 更新Label字段但保持LabelRetrievedAt不变
/// </summary>
/// <param name="id">记录ID</param>
/// <param name="newLabel">新的Label值</param>
/// <returns>更新是否成功</returns>
public async Task<bool> UpdateLabelWithoutChangingRetrievedAtAsync(int id, string newLabel)
{
var db = _provider.GetClient();
// 首先获取现有记录以便获取LabelRetrievedAt的当前值
var existing = await db.Queryable<LabelReplaceEntity>()
.Where(x => x.Id == id)
.FirstAsync();
if (existing == null)
{
return false;
}
// 显式更新Label字段同时将LabelRetrievedAt设置为原值
// 这样触发器就不会改变它
var rows = await db.Updateable<LabelReplaceEntity>()
.SetColumns(x => x.Label == newLabel)
.SetColumns(x => x.LabelRetrievedAt == existing.LabelRetrievedAt)
.SetColumns(x => x.UpdatedAt == DateTime.UtcNow)
.Where(x => x.Id == id)
.ExecuteCommandAsync();
return rows > 0;
}
/// <summary>
/// 获取每日标签统计数据
/// </summary>
/// <param name="startDate">开始日期</param>
/// <param name="endDate">结束日期</param>
/// <param name="customerId">客户ID</param>
/// <returns>每日标签统计数据列表</returns>
public async Task<List<DailyLabelStatsDto>> GetDailyLabelStatsAsync(string startDate, string endDate, int? customerId)
{
var db = _provider.GetClient();
// 构建日期范围条件
DateTime? start = null;
DateTime? end = null;
if (!string.IsNullOrEmpty(startDate))
{
start = DateTime.Parse(startDate);
}
if (!string.IsNullOrEmpty(endDate))
{
end = DateTime.Parse(endDate).AddDays(1).AddSeconds(-1);
}
// 获取所有符合条件的 label_replace_requestslabel不为空
var labelRequestsQuery = db.Queryable<LabelReplaceEntity>()
.Where(x => x.Label != null && x.Label != "");
if (customerId.HasValue)
{
labelRequestsQuery = labelRequestsQuery.Where(x => x.CustomerId == customerId);
}
var labelRequests = await labelRequestsQuery.ToListAsync();
var waybillNumbers = labelRequests.Select(x => x.NeutralWaybillNumber).Distinct().ToList();
if (waybillNumbers.Count == 0)
{
return new List<DailyLabelStatsDto>();
}
// 获取所有相关的扫描记录
var scansQuery = db.Queryable<LabelScanEntity>()
.Where(x => waybillNumbers.Contains(x.NeutralWaybillNumber));
if (customerId.HasValue)
{
scansQuery = scansQuery.Where(x => x.CustomerId == customerId);
}
var allScans = await scansQuery.OrderBy(x => x.CreatedAt).ToListAsync();
// 构建统计字典
var statsDict = new Dictionary<string, DailyLabelStatsDto>();
// 获取数据拉取时间当前UTC时间减5小时
var dataFetchTime = DateTime.UtcNow.AddHours(-5);
// 1. 统计换单失败未完结数(所有有扫描记录但未能成功换单的数量)
// 对于每个中性面单单号检查是否有Result = 0的记录如果没有就算失败未完结
var waybillScanGroups = allScans.GroupBy(x => x.NeutralWaybillNumber);
var unfinishedFailureSet = new HashSet<string>();
foreach (var group in waybillScanGroups)
{
var hasSuccess = group.Any(x => x.Result == 0);
if (!hasSuccess)
{
unfinishedFailureSet.Add(group.Key);
}
}
// 2. 按日期统计各项指标
foreach (var scan in allScans)
{
// 转换为UTC-5时区的日期
var dateKey = scan.CreatedAt.AddHours(-5).ToString("yyyy-MM-dd");
// 应用日期范围过滤
if (start.HasValue && scan.CreatedAt.AddHours(-5) < start.Value)
continue;
if (end.HasValue && scan.CreatedAt.AddHours(-5) > end.Value)
continue;
if (!statsDict.ContainsKey(dateKey))
{
statsDict[dateKey] = new DailyLabelStatsDto
{
Date = dateKey,
DataFetchTime = dataFetchTime
};
}
var stats = statsDict[dateKey];
// 当日扫描数
stats.DailyScanCount++;
// 当日换单成功数Result = 0
if (scan.Result == 0)
{
stats.DailySuccessCount++;
// 当日STOP数Result = 0且描述包含"成功返回STOP标签"
if (!string.IsNullOrEmpty(scan.Description) && scan.Description.Contains("成功返回STOP标签"))
{
stats.DailyStopCount++;
}
}
else
{
// 当日换单失败数Result != 0
stats.DailyFailureCount++;
}
}
// 3. 统计当日标签推送数按LabelRetrievedAt统计
foreach (var request in labelRequests)
{
if (request.LabelRetrievedAt.HasValue)
{
var dateKey = request.LabelRetrievedAt.Value.AddHours(-5).ToString("yyyy-MM-dd");
// 应用日期范围过滤
if (start.HasValue && request.LabelRetrievedAt.Value.AddHours(-5) < start.Value)
continue;
if (end.HasValue && request.LabelRetrievedAt.Value.AddHours(-5) > end.Value)
continue;
if (!statsDict.ContainsKey(dateKey))
{
statsDict[dateKey] = new DailyLabelStatsDto
{
Date = dateKey,
DataFetchTime = dataFetchTime
};
}
statsDict[dateKey].DailyLabelPushCount++;
}
}
// 4. 为每个日期设置换单失败未完结数(这是一个全局统计,适用于所有日期)
foreach (var stats in statsDict.Values)
{
stats.UnfinishedFailureCount = unfinishedFailureSet.Count;
}
// 转换为列表并按日期排序
var result = statsDict.Values.OrderBy(x => x.Date).ToList();
return result;
}
/// <summary>
/// 获取每日标签统计数据中文版本使用自定义SQL
/// </summary>
/// <returns>每日标签统计数据列表</returns>
public async Task<List<DailyLabelStatsChineseDto>> GetDailyLabelStatsChineseAsync()
{
// 使用固定的数据库连接
string customConnectionString = "server=172.233.222.200;port=6033;user id=oms_user;password=oms_user@pwd;database=lr01mainusa;CharSet=utf8;allow zero datetime=true;Convert Zero Datetime=true;Max Pool Size=100;Min Pool Size=10;Connection Timeout=30;Allow User Variables=True;";
// 直接使用 MySqlConnection 来执行 SQL绕过 SqlSugar 初始化问题
var result = new List<DailyLabelStatsChineseDto>();
using (var connection = new MySql.Data.MySqlClient.MySqlConnection(customConnectionString))
{
await connection.OpenAsync();
// 读取 SQL 文件内容
string sql = @"
WITH
-- 步骤1获取所有到货交接单日期已是UTC-5
ArrivalFormsWithDate AS (
SELECT
a.Id,
a.HandoverNumber,
DATE(a.ReceiptTime) AS 到货日期,
a.ReceiptTime AS 到货时间
FROM arrival_handover_forms a
),
-- 步骤2关联到货交接单与换单请求只取Label有值的并记录订单级别的信息计算考核时间
ArrivalRequests AS (
SELECT
a.到货日期,
a.到货时间,
l.Id AS RequestId,
l.NeutralWaybillNumber,
l.BillOfLadingNumber,
l.MasterPackageNumber,
l.Label,
l.LabelRetrievedAt,
l.CustomerId,
-- 考核时间:标签推送时间与到仓时间比较,哪个最新用哪个
CASE
WHEN l.LabelRetrievedAt IS NULL THEN a.到货时间
WHEN l.LabelRetrievedAt > a.到货时间 THEN l.LabelRetrievedAt
ELSE a.到货时间
END AS 考核时间
FROM ArrivalFormsWithDate a
INNER JOIN label_replace_requests l
ON l.BillOfLadingNumber = a.HandoverNumber
OR l.MasterPackageNumber = a.HandoverNumber
WHERE l.Label IS NOT NULL AND l.Label != ''
),
-- 步骤3获取每个订单的扫描记录情况按天
DailyScanStatus AS (
SELECT
s.NeutralWaybillNumber,
DATE(CONVERT_TZ(s.CreatedAt, '+00:00', '-05:00')) AS 日期,
MAX(CASE WHEN s.Result = 0 THEN 1 ELSE 0 END) AS 当日是否成功,
MAX(CASE WHEN s.Result != 0 THEN 1 ELSE 0 END) AS 当日是否失败
FROM label_scan_history s
INNER JOIN label_replace_requests l ON s.NeutralWaybillNumber = l.NeutralWaybillNumber
WHERE l.Label IS NOT NULL AND l.Label != ''
GROUP BY s.NeutralWaybillNumber, DATE(CONVERT_TZ(s.CreatedAt, '+00:00', '-05:00'))
),
-- 步骤4获取每个订单是否曾经成功以及首次成功日期和时间
OverallScanStatus AS (
SELECT
s.NeutralWaybillNumber,
MAX(CASE WHEN s.Result = 0 THEN 1 ELSE 0 END) AS 曾成功,
MIN(CASE WHEN s.Result = 0 THEN DATE(CONVERT_TZ(s.CreatedAt, '+00:00', '-05:00')) ELSE NULL END) AS 首次成功日期,
MIN(CASE WHEN s.Result = 0 THEN s.CreatedAt ELSE NULL END) AS 首次成功时间
FROM label_scan_history s
GROUP BY s.NeutralWaybillNumber
),
-- 步骤5从数据中收集所有日期
AllDates AS (
SELECT 到货日期 AS 日期 FROM ArrivalRequests
UNION
SELECT 日期 FROM DailyScanStatus
UNION
SELECT DATE(CONVERT_TZ(l.LabelRetrievedAt, '+00:00', '-05:00')) AS 日期
FROM label_replace_requests l
WHERE l.LabelRetrievedAt IS NOT NULL
),
-- 步骤6去重并排序日期
DistinctDates AS (
SELECT DISTINCT 日期
FROM AllDates
ORDER BY 日期
),
-- 步骤7获取最新日期
LatestDate AS (
SELECT MAX(日期) AS 日期
FROM DistinctDates
),
-- 步骤8每日基础统计 - 当日新增换单数
DailyBase AS (
SELECT
dd.日期,
-- 当日新增换单数:当天到货并且推送了标签数据的订单
COUNT(DISTINCT CASE
WHEN ar.到货日期 = dd.日期 AND ar.LabelRetrievedAt IS NOT NULL
THEN ar.RequestId
END) AS 当日新增换单数,
-- 当天标签推送数
COUNT(DISTINCT CASE
WHEN DATE(CONVERT_TZ(ar.LabelRetrievedAt, '+00:00', '-05:00')) = dd.日期
THEN ar.RequestId
END) AS 当日标签推送数
FROM DistinctDates dd
CROSS JOIN ArrivalRequests ar
GROUP BY dd.日期
),
-- 步骤9每日扫描统计扫描次数换单成功数去重
DailyScanMetrics AS (
SELECT
DATE(CONVERT_TZ(s.CreatedAt, '+00:00', '-05:00')) AS 日期,
COUNT(*) AS 当日扫描数,
-- 当日STOP数同样去重
COUNT(DISTINCT CASE WHEN s.Result = 0 AND s.Description LIKE '%成功返回STOP标签%' THEN s.NeutralWaybillNumber END) AS 当日STOP数
FROM label_scan_history s
INNER JOIN label_replace_requests l ON s.NeutralWaybillNumber = l.NeutralWaybillNumber
WHERE l.Label IS NOT NULL AND l.Label != ''
GROUP BY DATE(CONVERT_TZ(s.CreatedAt, '+00:00', '-05:00'))
),
-- 步骤9b每日换单成功数去重
DailySuccessCount AS (
SELECT
scan_date AS 日期,
COUNT(DISTINCT NeutralWaybillNumber) AS 当日换单成功数
FROM (
-- 找出每个包裹每天最新的成功记录
SELECT
s.NeutralWaybillNumber,
DATE(CONVERT_TZ(s.CreatedAt, '+00:00', '-05:00')) AS scan_date,
ROW_NUMBER() OVER (PARTITION BY s.NeutralWaybillNumber, DATE(CONVERT_TZ(s.CreatedAt, '+00:00', '-05:00')) ORDER BY s.CreatedAt DESC) AS rn
FROM label_scan_history s
INNER JOIN label_replace_requests l ON s.NeutralWaybillNumber = l.NeutralWaybillNumber
WHERE l.Label IS NOT NULL AND l.Label != '' AND s.Result = 0
) AS s
WHERE rn = 1
GROUP BY scan_date
),
-- 步骤10历史日期的换单失败未完结统计非最新日期
HistoryUnfinished AS (
SELECT
dd.日期,
COUNT(DISTINCT CASE
-- 当天失败并且当天没有成功的订单
WHEN dss.日期 = dd.日期 AND dss.当日是否失败 = 1 AND dss.当日是否成功 = 0
THEN dss.NeutralWaybillNumber
END) AS 换单失败未完结订单
FROM DistinctDates dd
LEFT JOIN DailyScanStatus dss ON dd.日期 = dss.日期
CROSS JOIN LatestDate ld
WHERE dd.日期 != ld.日期
GROUP BY dd.日期
),
-- 步骤11最新日期的换单失败未完结统计所有历史从未成功的
LatestUnfinished AS (
SELECT
ld.日期,
COUNT(DISTINCT CASE
-- 必须同时满足:
-- 1. 有过扫描记录OverallScanStatus中有该订单
-- 2. 从未成功(曾成功 = 0
-- 3. 并且至少有一次失败记录
WHEN oss.曾成功 = 0
THEN ar.NeutralWaybillNumber
END) AS 换单失败未完结订单
FROM LatestDate ld
CROSS JOIN ArrivalRequests ar
INNER JOIN OverallScanStatus oss ON ar.NeutralWaybillNumber = oss.NeutralWaybillNumber
GROUP BY ld.日期
),
-- 步骤12每日换单失败订单统计当天失败并且当天没成功的订单数
DailyFailedOrders AS (
SELECT
日期,
COUNT(DISTINCT CASE
WHEN 当日是否失败 = 1 AND 当日是否成功 = 0
THEN NeutralWaybillNumber
END) AS 当日换单失败
FROM DailyScanStatus
GROUP BY 日期
),
-- 步骤13每日完成订单统计 - 订单必须在我们关联的ArrivalRequests中
DailyCompletedOrders AS (
SELECT
oss.首次成功日期 AS 日期,
COUNT(DISTINCT oss.NeutralWaybillNumber) AS 当日完成数
FROM OverallScanStatus oss
INNER JOIN ArrivalRequests ar ON oss.NeutralWaybillNumber = ar.NeutralWaybillNumber
WHERE oss.曾成功 = 1
GROUP BY oss.首次成功日期
),
-- 步骤1424小时换单完成订单统计 - 根据考核时间判断
Daily24HCompletedOrders AS (
SELECT
oss.首次成功日期 AS 日期,
COUNT(DISTINCT ar.NeutralWaybillNumber) AS 24H内完成数
FROM ArrivalRequests ar
INNER JOIN OverallScanStatus oss ON ar.NeutralWaybillNumber = oss.NeutralWaybillNumber
WHERE
oss.曾成功 = 1
AND oss.首次成功时间 IS NOT NULL
AND ar.考核时间 IS NOT NULL
-- 考核时间减去换单完成时间小于等于24小时
AND TIMESTAMPDIFF(HOUR, ar.考核时间, oss.首次成功时间) <= 24
GROUP BY oss.首次成功日期
),
-- 步骤15完整的每日统计基础 - 准备每日的新增和完成,并获取前一日数据
DailyStatsWithPrev AS (
SELECT
db.日期,
db.当日新增换单数,
db.当日标签推送数,
COALESCE(dco.当日完成数, 0) AS 当日完成数,
COALESCE(dc24h.24H内完成数, 0) AS 24H内完成数,
-- 获取前一日的新增
LAG(db.当日新增换单数, 1, 0) OVER (ORDER BY db.日期) AS 前一日新增,
-- 获取前一日的完成数
LAG(COALESCE(dco.当日完成数, 0), 1, 0) OVER (ORDER BY db.日期) AS 前一日完成数,
-- 行号
ROW_NUMBER() OVER (ORDER BY db.日期) AS rn
FROM DailyBase db
LEFT JOIN DailyCompletedOrders dco ON db.日期 = dco.日期
LEFT JOIN Daily24HCompletedOrders dc24h ON db.日期 = dc24h.日期
ORDER BY db.日期
)
-- 步骤16计算累计数据并最终输出
SELECT
日期,
当日新增换单数,
累计要换的总单数,
换单失败未完结订单,
当日换单失败,
当日换单成功数,
当日STOP数,
-- 24小时换单率
CASE
WHEN 当日完成数 = 0 THEN '0.00%'
ELSE CONCAT(ROUND(24H内完成数 / 当日完成数 * 100, 2), '%')
END AS 24H换单率,
-- 当天换单完成率
CASE
WHEN (当日新增换单数 + 累计要换的总单数) = 0 THEN '0.00%'
ELSE CONCAT(ROUND(当日完成数 / (当日新增换单数 + 累计要换的总单数) * 100, 2), '%')
END AS 当天换单完成率,
当日标签推送数,
当日扫描数,
数据拉取时间UTC_5
FROM (
SELECT
t.日期,
t.当日新增换单数,
-- 使用变量保持状态,每次计算前一天的累计
-- 公式:累计 = MAX(0, 前一日累计 + 前一日新增 - 前一日完成)
-- 第1天直接用当日新增
@running_total := GREATEST(0,
CASE
WHEN t.rn = 1 THEN t.当日新增换单数
ELSE @running_total + t.前一日新增 - t.前一日完成数
END) AS 累计要换的总单数,
-- 根据是否是最新日期选择不同的未完结统计
COALESCE(
CASE
WHEN t.日期 = (SELECT 日期 FROM LatestDate) THEN lu.换单失败未完结订单
ELSE hu.换单失败未完结订单
END,
0
) AS 换单失败未完结订单,
COALESCE(dfo.当日换单失败, 0) AS 当日换单失败,
COALESCE(dsc.当日换单成功数, 0) AS 当日换单成功数,
COALESCE(dsm.当日STOP数, 0) AS 当日STOP数,
t.24H内完成数,
t.当日完成数,
t.当日标签推送数,
COALESCE(dsm.当日扫描数, 0) AS 当日扫描数,
(UTC_TIMESTAMP() - INTERVAL 5 HOUR) AS 数据拉取时间UTC_5
FROM DailyStatsWithPrev t
LEFT JOIN DailyScanMetrics dsm ON t.日期 = dsm.日期
LEFT JOIN DailySuccessCount dsc ON t.日期 = dsc.日期
LEFT JOIN HistoryUnfinished hu ON t.日期 = hu.日期
LEFT JOIN LatestUnfinished lu ON t.日期 = lu.日期
LEFT JOIN DailyFailedOrders dfo ON t.日期 = dfo.日期
-- 初始化变量
CROSS JOIN (SELECT @running_total := 0) AS init
ORDER BY t.日期
) AS subquery
ORDER BY 日期 DESC
";
using (var command = new MySql.Data.MySqlClient.MySqlCommand(sql, connection))
using (var reader = await command.ExecuteReaderAsync())
{
while (await reader.ReadAsync())
{
var dto = new DailyLabelStatsChineseDto
{
Date = reader["日期"] != DBNull.Value ? Convert.ToDateTime(reader["日期"]).ToString("yyyy-MM-dd") : string.Empty,
DailyNewReplaceCount = reader["当日新增换单数"] != DBNull.Value ? Convert.ToInt32(reader["当日新增换单数"]) : 0,
CumulativeTotalReplaceCount = reader["累计要换的总单数"] != DBNull.Value ? Convert.ToInt32(reader["累计要换的总单数"]) : 0,
UnfinishedFailureCount = reader["换单失败未完结订单"] != DBNull.Value ? Convert.ToInt32(reader["换单失败未完结订单"]) : 0,
DailyFailureCount = reader["当日换单失败"] != DBNull.Value ? Convert.ToInt32(reader["当日换单失败"]) : 0,
DailySuccessCount = reader["当日换单成功数"] != DBNull.Value ? Convert.ToInt32(reader["当日换单成功数"]) : 0,
DailyStopCount = reader["当日STOP数"] != DBNull.Value ? Convert.ToInt32(reader["当日STOP数"]) : 0,
Rate24Hour = reader["24H换单率"] as string,
DailyCompletionRate = reader["当天换单完成率"] as string,
DailyLabelPushCount = reader["当日标签推送数"] != DBNull.Value ? Convert.ToInt32(reader["当日标签推送数"]) : 0,
DailyScanCount = reader["当日扫描数"] != DBNull.Value ? Convert.ToInt32(reader["当日扫描数"]) : 0,
DataFetchTime = reader["数据拉取时间UTC_5"] != DBNull.Value ? Convert.ToDateTime(reader["数据拉取时间UTC_5"]) : DateTime.Now
};
result.Add(dto);
}
}
}
return result;
}
}
}