using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; using NCC.Common.Core.Manager; using NCC.Common.Enum; using NCC.Common.Filter; using NCC.Dependency; using NCC.DynamicApiController; using NCC.Extend.Entitys.Dto.LqLaundryFlow; using NCC.Extend.Entitys.Enum; using NCC.Extend.Entitys.lq_laundry_flow; using NCC.Extend.Entitys.lq_laundry_supplier; using NCC.Extend.Entitys.lq_mdxx; using NCC.Extend.Interfaces.LqLaundryFlow; using NCC.FriendlyException; using NCC.System.Entitys.Permission; using SqlSugar; using Yitter.IdGenerator; namespace NCC.Extend { /// /// 清洗流水服务 /// [ApiDescriptionSettings(Tag = "绿纤清洗流水管理", Name = "LqLaundryFlow", Order = 200)] [Route("api/Extend/LqLaundryFlow")] public class LqLaundryFlowService : IDynamicApiController, ITransient, ILqLaundryFlowService { private readonly IUserManager _userManager; private readonly ILogger _logger; private readonly ISqlSugarClient _db; /// /// 构造函数 /// public LqLaundryFlowService(IUserManager userManager, ILogger logger, ISqlSugarClient db) { _userManager = userManager; _logger = logger; _db = db; } #region 创建送出记录 /// /// 创建送出记录 /// /// /// 门店选择清洗商和产品,填写送出数量,创建送出记录 /// /// 示例请求: /// ```json /// { /// "storeId": "门店ID", /// "productType": "毛巾", /// "laundrySupplierId": "清洗商ID", /// "quantity": 100, /// "remark": "备注", /// "sendTime": "2025-01-15T10:30:00" // 可选,如果不传则使用当前时间 /// } /// ``` /// /// 参数说明: /// - storeId: 门店ID(必填) /// - productType: 产品类型(必填) /// - laundrySupplierId: 清洗商ID(必填) /// - quantity: 送出数量(必填) /// - remark: 备注(可选) /// - sendTime: 送出时间(可选,如果不传则使用当前时间) /// /// 送出输入 /// 创建结果(包含批次号) /// 创建成功 /// 参数错误或清洗商不存在 /// 服务器错误 [HttpPost("Send")] public async Task SendAsync([FromBody] LqLaundryFlowSendInput input) { try { // 验证门店是否存在 var store = await _db.Queryable() .Where(x => x.Id == input.StoreId) .FirstAsync(); if (store == null) { throw NCCException.Oh("门店不存在"); } // 验证清洗商是否存在 var supplier = await _db.Queryable() .Where(x => x.Id == input.LaundrySupplierId && x.IsEffective == StatusEnum.有效.GetHashCode()) .FirstAsync(); if (supplier == null) { throw NCCException.Oh("清洗商不存在或已失效"); } // 验证产品类型是否匹配 if (supplier.ProductType != input.ProductType) { throw NCCException.Oh($"清洗商【{supplier.SupplierName}】不支持清洗产品类型【{input.ProductType}】"); } // 生成批次号(使用ID) var batchId = YitIdHelper.NextId().ToString(); // 创建送出记录 var entity = new LqLaundryFlowEntity { Id = batchId, FlowType = 0, // 送出 BatchNumber = batchId, // 批次号等于ID StoreId = input.StoreId, ProductType = input.ProductType, LaundrySupplierId = input.LaundrySupplierId, Quantity = input.Quantity, LaundryPrice = supplier.LaundryPrice, // 记录历史价格 TotalPrice = input.Quantity * supplier.LaundryPrice, // 送出时总费用为数量 * 单价 Remark = input.Remark, IsEffective = StatusEnum.有效.GetHashCode(), CreateUser = _userManager.UserId, CreateTime = DateTime.Now, SendTime = input.SendTime ?? DateTime.Now // 如果传入了送出时间则使用,否则使用当前时间 }; var isOk = await _db.Insertable(entity).ExecuteCommandAsync(); if (!(isOk > 0)) throw NCCException.Oh(ErrorCode.COM1000); return new { batchNumber = batchId, message = "送出记录创建成功" }; } catch (Exception ex) { _logger.LogError(ex, "创建送出记录失败"); throw NCCException.Oh($"创建失败:{ex.Message}"); } } /// /// 批量创建送出记录 /// /// /// 批量创建送出记录,每条记录独立生成批次号;任一条校验失败则全部回滚 /// /// 示例请求: /// ```json /// { /// "items": [ /// { /// "storeId": "门店ID", /// "productType": "毛巾", /// "laundrySupplierId": "清洗商ID", /// "quantity": 100, /// "remark": "备注", /// "sendTime": "2025-01-15T10:30:00" /// } /// ] /// } /// ``` /// /// 参数说明: /// - items: 送出记录列表(必填,至少一条) /// - 每条记录字段与单条送出接口相同 /// /// 批量送出输入 /// 成功条数、批次号列表 /// 批量创建成功 /// 参数错误或任一条校验失败 /// 服务器错误 [HttpPost("BatchSend")] public async Task BatchSendAsync([FromBody] LqLaundryFlowBatchSendInput input) { if (input?.Items == null || !input.Items.Any()) { throw NCCException.Oh("送出记录列表不能为空"); } try { var batchNumbers = new List(); await _db.Ado.UseTranAsync(async () => { for (var i = 0; i < input.Items.Count; i++) { var item = input.Items[i]; // 验证门店是否存在 var store = await _db.Queryable() .Where(x => x.Id == item.StoreId) .FirstAsync(); if (store == null) { throw NCCException.Oh($"第{i + 1}条:门店不存在"); } // 验证清洗商是否存在 var supplier = await _db.Queryable() .Where(x => x.Id == item.LaundrySupplierId && x.IsEffective == StatusEnum.有效.GetHashCode()) .FirstAsync(); if (supplier == null) { throw NCCException.Oh($"第{i + 1}条:清洗商不存在或已失效"); } // 验证产品类型是否匹配 if (supplier.ProductType != item.ProductType) { throw NCCException.Oh($"第{i + 1}条:清洗商【{supplier.SupplierName}】不支持清洗产品类型【{item.ProductType}】"); } var batchId = YitIdHelper.NextId().ToString(); var entity = new LqLaundryFlowEntity { Id = batchId, FlowType = 0, BatchNumber = batchId, StoreId = item.StoreId, ProductType = item.ProductType, LaundrySupplierId = item.LaundrySupplierId, Quantity = item.Quantity, LaundryPrice = supplier.LaundryPrice, TotalPrice = item.Quantity * supplier.LaundryPrice, Remark = item.Remark, IsEffective = StatusEnum.有效.GetHashCode(), CreateUser = _userManager.UserId, CreateTime = DateTime.Now, SendTime = item.SendTime ?? DateTime.Now }; var isOk = await _db.Insertable(entity).ExecuteCommandAsync(); if (!(isOk > 0)) throw NCCException.Oh(ErrorCode.COM1000); batchNumbers.Add(batchId); } }); return new { successCount = batchNumbers.Count, batchNumbers, message = "批量送出成功" }; } catch (Exception ex) { _logger.LogError(ex, "批量创建送出记录失败"); throw NCCException.Oh($"批量送出失败:{ex.Message}"); } } #endregion #region 创建送回记录 /// /// 创建送回记录 /// /// /// 清洗完毕后,填写送回清洗商、送回数量,创建送回记录 /// /// 示例请求: /// ```json /// { /// "batchNumber": "批次号(对应的送出记录的ID)", /// "laundrySupplierId": "清洗商ID", /// "quantity": 95, /// "remark": "5条损坏", /// "returnTime": "2025-01-20T14:30:00" // 可选,如果不传则使用当前时间 /// } /// ``` /// /// 参数说明: /// - batchNumber: 批次号(必填) /// - laundrySupplierId: 清洗商ID(必填) /// - quantity: 送回数量(必填) /// - remark: 备注(可选) /// - returnTime: 送回时间(可选,如果不传则使用当前时间) /// /// 送回输入 /// 创建结果 /// 创建成功 /// 批次号不存在或参数错误 /// 服务器错误 [HttpPost("Return")] public async Task ReturnAsync([FromBody] LqLaundryFlowReturnInput input) { try { // 验证对应的送出记录是否存在 var sendRecord = await _db.Queryable() .Where(x => x.BatchNumber == input.BatchNumber && x.FlowType == 0 && x.IsEffective == StatusEnum.有效.GetHashCode()) .FirstAsync(); if (sendRecord == null) { throw NCCException.Oh("对应的送出记录不存在或已失效"); } // 检查是否已经存在送回记录 var existingReturn = await _db.Queryable() .Where(x => x.BatchNumber == input.BatchNumber && x.FlowType == 1 && x.IsEffective == StatusEnum.有效.GetHashCode()) .FirstAsync(); if (existingReturn != null) { throw NCCException.Oh("该批次已存在送回记录"); } // 验证清洗商是否存在 var supplier = await _db.Queryable() .Where(x => x.Id == input.LaundrySupplierId && x.IsEffective == StatusEnum.有效.GetHashCode()) .FirstAsync(); if (supplier == null) { throw NCCException.Oh("清洗商不存在或已失效"); } // 验证产品类型是否匹配 if (supplier.ProductType != sendRecord.ProductType) { throw NCCException.Oh($"清洗商【{supplier.SupplierName}】不支持清洗产品类型【{sendRecord.ProductType}】"); } // 计算总费用(送回数量 × 清洗单价) var totalPrice = input.Quantity * supplier.LaundryPrice; // 创建送回记录 var entity = new LqLaundryFlowEntity { Id = YitIdHelper.NextId().ToString(), FlowType = 1, // 送回 BatchNumber = input.BatchNumber, // 使用送出记录的批次号 StoreId = sendRecord.StoreId, ProductType = sendRecord.ProductType, LaundrySupplierId = input.LaundrySupplierId, Quantity = input.Quantity, LaundryPrice = supplier.LaundryPrice, // 记录历史价格(可能已变化) TotalPrice = totalPrice, Remark = input.Remark, IsEffective = StatusEnum.有效.GetHashCode(), CreateUser = _userManager.UserId, CreateTime = DateTime.Now, ReturnTime = input.ReturnTime ?? DateTime.Now // 如果传入了送回时间则使用,否则使用当前时间 }; var isOk = await _db.Insertable(entity).ExecuteCommandAsync(); if (!(isOk > 0)) throw NCCException.Oh(ErrorCode.COM1000); return new { message = "送回记录创建成功", totalPrice = totalPrice }; } catch (Exception ex) { _logger.LogError(ex, "创建送回记录失败"); throw NCCException.Oh($"创建失败:{ex.Message}"); } } /// /// 批量创建送回记录 /// /// /// 批量创建送回记录;任一条校验失败则全部回滚 /// /// 示例请求: /// ```json /// { /// "items": [ /// { /// "batchNumber": "批次号", /// "laundrySupplierId": "清洗商ID", /// "quantity": 95, /// "remark": "5条损坏", /// "returnTime": "2025-01-20T14:30:00" /// } /// ] /// } /// ``` /// /// 参数说明: /// - items: 送回记录列表(必填,至少一条) /// - 每条记录字段与单条送回接口相同 /// /// 批量送回输入 /// 成功条数 /// 批量创建成功 /// 参数错误或任一条校验失败 /// 服务器错误 [HttpPost("BatchReturn")] public async Task BatchReturnAsync([FromBody] LqLaundryFlowBatchReturnInput input) { if (input?.Items == null || !input.Items.Any()) { throw NCCException.Oh("送回记录列表不能为空"); } try { var successCount = 0; await _db.Ado.UseTranAsync(async () => { for (var i = 0; i < input.Items.Count; i++) { var item = input.Items[i]; // 验证对应的送出记录是否存在 var sendRecord = await _db.Queryable() .Where(x => x.BatchNumber == item.BatchNumber && x.FlowType == 0 && x.IsEffective == StatusEnum.有效.GetHashCode()) .FirstAsync(); if (sendRecord == null) { throw NCCException.Oh($"第{i + 1}条:对应的送出记录不存在或已失效"); } // 检查是否已经存在送回记录 var existingReturn = await _db.Queryable() .Where(x => x.BatchNumber == item.BatchNumber && x.FlowType == 1 && x.IsEffective == StatusEnum.有效.GetHashCode()) .FirstAsync(); if (existingReturn != null) { throw NCCException.Oh($"第{i + 1}条:该批次已存在送回记录"); } // 验证清洗商是否存在 var supplier = await _db.Queryable() .Where(x => x.Id == item.LaundrySupplierId && x.IsEffective == StatusEnum.有效.GetHashCode()) .FirstAsync(); if (supplier == null) { throw NCCException.Oh($"第{i + 1}条:清洗商不存在或已失效"); } // 验证产品类型是否匹配 if (supplier.ProductType != sendRecord.ProductType) { throw NCCException.Oh($"第{i + 1}条:清洗商【{supplier.SupplierName}】不支持清洗产品类型【{sendRecord.ProductType}】"); } var totalPrice = item.Quantity * supplier.LaundryPrice; var entity = new LqLaundryFlowEntity { Id = YitIdHelper.NextId().ToString(), FlowType = 1, BatchNumber = item.BatchNumber, StoreId = sendRecord.StoreId, ProductType = sendRecord.ProductType, LaundrySupplierId = item.LaundrySupplierId, Quantity = item.Quantity, LaundryPrice = supplier.LaundryPrice, TotalPrice = totalPrice, Remark = item.Remark, IsEffective = StatusEnum.有效.GetHashCode(), CreateUser = _userManager.UserId, CreateTime = DateTime.Now, ReturnTime = item.ReturnTime ?? DateTime.Now }; var isOk = await _db.Insertable(entity).ExecuteCommandAsync(); if (!(isOk > 0)) throw NCCException.Oh(ErrorCode.COM1000); successCount++; } }); return new { successCount, message = "批量送回成功" }; } catch (Exception ex) { _logger.LogError(ex, "批量创建送回记录失败"); throw NCCException.Oh($"批量送回失败:{ex.Message}"); } } #endregion #region 修改送出/送回记录 /// /// 修改送出/送回记录 /// /// /// 修改清洗流水记录的数量、送出时间、送回时间和备注 /// /// 示例请求: /// ```json /// { /// "id": "记录ID", /// "quantity": 95, /// "sendTime": "2025-11-01 10:00:00", /// "returnTime": "2025-11-05 15:00:00", /// "remark": "修改备注" /// } /// ``` /// /// 参数说明: /// - id: 记录ID(必填) /// - quantity: 数量(可选,修改时需重新计算总费用) /// - sendTime: 送出时间(可选,仅流水类型为0时有效) /// - returnTime: 送回时间(可选,仅流水类型为1时有效) /// - remark: 备注(可选) /// /// 修改输入 /// 修改结果 /// 修改成功 /// 记录不存在或参数错误 /// 服务器错误 [HttpPost("Update")] public async Task UpdateAsync([FromBody] LqLaundryFlowUpdateInput input) { try { // 查询记录是否存在 var entity = await _db.Queryable() .Where(x => x.Id == input.Id && x.IsEffective == StatusEnum.有效.GetHashCode()) .FirstAsync(); if (entity == null) { throw NCCException.Oh("记录不存在或已失效"); } // 更新数量(如果提供) if (input.Quantity.HasValue) { entity.Quantity = input.Quantity.Value; // 重新计算总费用 entity.TotalPrice = entity.Quantity * entity.LaundryPrice; } // 更新送出时间(仅流水类型为0时有效) if (input.SendTime.HasValue) { if (entity.FlowType != 0) { throw NCCException.Oh("只有送出记录才能修改送出时间"); } entity.SendTime = input.SendTime.Value; } // 更新送回时间(仅流水类型为1时有效) if (input.ReturnTime.HasValue) { if (entity.FlowType != 1) { throw NCCException.Oh("只有送回记录才能修改送回时间"); } entity.ReturnTime = input.ReturnTime.Value; } // 更新备注(如果提供) if (input.Remark != null) { entity.Remark = input.Remark; } // 执行更新 var isOk = await _db.Updateable(entity) .UpdateColumns(it => new { it.Quantity, it.TotalPrice, it.SendTime, it.ReturnTime, it.Remark }) .ExecuteCommandAsync(); if (!(isOk > 0)) throw NCCException.Oh(ErrorCode.COM1000); return new { message = "修改成功", totalPrice = entity.TotalPrice }; } catch (Exception ex) { _logger.LogError(ex, "修改清洗流水记录失败"); throw NCCException.Oh($"修改失败:{ex.Message}"); } } #endregion #region 获取清洗流水列表 /// /// 获取清洗流水列表 /// /// /// 分页查询清洗流水列表,支持按流水类型、批次号、门店、产品类型、清洗商、时间范围筛选 /// /// 查询输入 /// 清洗流水列表 /// 查询成功 /// 服务器错误 [HttpGet("GetList")] public async Task GetListAsync([FromQuery] LqLaundryFlowListQueryInput input) { try { var sidx = string.IsNullOrEmpty(input.sidx) ? "createTime" : input.sidx; var sort = string.IsNullOrEmpty(input.sort) ? "desc" : input.sort; var query = _db.Queryable( (flow, store, supplier) => flow.StoreId == store.Id && flow.LaundrySupplierId == supplier.Id) .WhereIF(input.FlowType.HasValue, (flow, store, supplier) => flow.FlowType == input.FlowType.Value) .WhereIF(!string.IsNullOrWhiteSpace(input.BatchNumber), (flow, store, supplier) => flow.BatchNumber == input.BatchNumber) .WhereIF(!string.IsNullOrWhiteSpace(input.StoreId), (flow, store, supplier) => flow.StoreId == input.StoreId) .WhereIF(!string.IsNullOrWhiteSpace(input.ProductType), (flow, store, supplier) => flow.ProductType == input.ProductType) // 清洗商ID筛选:优先使用多选列表,如果为空则使用单选 .WhereIF(input.LaundrySupplierIds != null && input.LaundrySupplierIds.Any(), (flow, store, supplier) => input.LaundrySupplierIds.Contains(flow.LaundrySupplierId)) .WhereIF((input.LaundrySupplierIds == null || !input.LaundrySupplierIds.Any()) && !string.IsNullOrWhiteSpace(input.LaundrySupplierId), (flow, store, supplier) => flow.LaundrySupplierId == input.LaundrySupplierId) // 创建时间过滤:优先使用SendTime,如果为空则使用CreateTime(与工资计算逻辑保持一致) .WhereIF(input.StartTime.HasValue, (flow, store, supplier) => (flow.SendTime ?? flow.CreateTime) >= input.StartTime.Value) .WhereIF(input.EndTime.HasValue, (flow, store, supplier) => (flow.SendTime ?? flow.CreateTime) <= input.EndTime.Value) // 送出时间过滤 .WhereIF(input.SendStartTime.HasValue, (flow, store, supplier) => flow.SendTime.HasValue && flow.SendTime.Value >= input.SendStartTime.Value) .WhereIF(input.SendEndTime.HasValue, (flow, store, supplier) => flow.SendTime.HasValue && flow.SendTime.Value <= input.SendEndTime.Value) // 送回时间过滤 .WhereIF(input.ReturnStartTime.HasValue, (flow, store, supplier) => flow.ReturnTime.HasValue && flow.ReturnTime.Value >= input.ReturnStartTime.Value) .WhereIF(input.ReturnEndTime.HasValue, (flow, store, supplier) => flow.ReturnTime.HasValue && flow.ReturnTime.Value <= input.ReturnEndTime.Value) .WhereIF(input.IsEffective.HasValue, (flow, store, supplier) => flow.IsEffective == input.IsEffective.Value); var data = await query .Select((flow, store, supplier) => new LqLaundryFlowListOutput { id = flow.Id, flowType = flow.FlowType, flowTypeName = flow.FlowType == 0 ? "送出" : "送回", batchNumber = flow.BatchNumber, storeId = flow.StoreId, storeName = store.Dm ?? "", productType = flow.ProductType, laundrySupplierId = flow.LaundrySupplierId, laundrySupplierName = supplier.SupplierName ?? "", quantity = flow.Quantity, laundryPrice = flow.LaundryPrice, totalPrice = flow.TotalPrice, remark = flow.Remark, isEffective = flow.IsEffective, createUser = flow.CreateUser, createUserName = "", createTime = flow.CreateTime, sendTime = flow.SendTime, returnTime = flow.ReturnTime }) .MergeTable() .OrderBy(sidx + " " + sort) .ToPagedListAsync(input.currentPage, input.pageSize); // 补充用户名称信息 var userIds = data.list.Select(x => x.createUser) .Where(x => !string.IsNullOrEmpty(x)) .Distinct() .ToList(); if (userIds.Any()) { var userList = await _db.Queryable() .Where(x => userIds.Contains(x.Id)) .Select(x => new { x.Id, x.RealName }) .ToListAsync(); var userDict = userList.ToDictionary(k => k.Id, v => v.RealName); foreach (var item in data.list) { item.createUserName = userDict.ContainsKey(item.createUser) ? userDict[item.createUser] : ""; } } return PageResult.SqlSugarPageResult(data); } catch (Exception ex) { _logger.LogError(ex, "获取清洗流水列表失败"); throw NCCException.Oh($"查询失败:{ex.Message}"); } } #endregion #region 获取清洗流水详情 /// /// 获取清洗流水详情 /// /// /// 根据ID获取清洗流水的详细信息 /// /// 流水ID /// 流水详情 /// 查询成功 /// 记录不存在 /// 服务器错误 [HttpGet("{id}")] public async Task GetInfoAsync(string id) { try { var entity = await _db.Queryable( (flow, store, supplier) => flow.StoreId == store.Id && flow.LaundrySupplierId == supplier.Id) .Where((flow, store, supplier) => flow.Id == id) .Select((flow, store, supplier) => new LqLaundryFlowInfoOutput { id = flow.Id, flowType = flow.FlowType, flowTypeName = flow.FlowType == 0 ? "送出" : "送回", batchNumber = flow.BatchNumber, storeId = flow.StoreId, storeName = store.Dm ?? "", productType = flow.ProductType, laundrySupplierId = flow.LaundrySupplierId, laundrySupplierName = supplier.SupplierName ?? "", quantity = flow.Quantity, laundryPrice = flow.LaundryPrice, totalPrice = flow.TotalPrice, remark = flow.Remark, isEffective = flow.IsEffective, createUser = flow.CreateUser, createUserName = "", createTime = flow.CreateTime, sendTime = flow.SendTime, returnTime = flow.ReturnTime }) .FirstAsync(); if (entity == null) { throw NCCException.Oh("流水记录不存在"); } // 补充用户名称 if (!string.IsNullOrEmpty(entity.createUser)) { var createUser = await _db.Queryable() .Where(x => x.Id == entity.createUser) .Select(x => x.RealName) .FirstAsync(); entity.createUserName = createUser ?? ""; } return entity; } catch (Exception ex) { _logger.LogError(ex, "获取清洗流水详情失败"); throw NCCException.Oh($"查询失败:{ex.Message}"); } } #endregion #region 查询差异记录 /// /// 查询差异记录(送出数量 > 送回数量) /// /// /// 查询所有送出数量大于送回数量的记录,用于追踪差异来源 /// /// 查询输入(支持分页) /// 差异记录列表 /// 查询成功 /// 服务器错误 [HttpPost("GetDifferenceList")] public async Task GetDifferenceListAsync([FromBody] LqLaundryFlowListQueryInput input) { try { // 查询所有送出记录 var sendRecords = await _db.Queryable( (flow, store) => flow.StoreId == store.Id) .Where((flow, store) => flow.FlowType == 0 && flow.IsEffective == StatusEnum.有效.GetHashCode()) .WhereIF(!string.IsNullOrWhiteSpace(input.StoreId), (flow, store) => flow.StoreId == input.StoreId) .WhereIF(!string.IsNullOrWhiteSpace(input.ProductType), (flow, store) => flow.ProductType == input.ProductType) .WhereIF(input.StartTime.HasValue, (flow, store) => flow.CreateTime >= input.StartTime.Value) .WhereIF(input.EndTime.HasValue, (flow, store) => flow.CreateTime <= input.EndTime.Value) .Select((flow, store) => new { flow.BatchNumber, flow.StoreId, StoreName = store.Dm ?? "", flow.ProductType, SendQuantity = flow.Quantity, SendTime = flow.SendTime }) .ToListAsync(); if (!sendRecords.Any()) { return new { list = new List(), pagination = new { page = input.currentPage, pageSize = input.pageSize, total = 0 } }; } // 查询所有送回记录 var returnRecords = await _db.Queryable() .Where(x => x.FlowType == 1 && x.IsEffective == StatusEnum.有效.GetHashCode()) .Select(x => new { x.BatchNumber, ReturnQuantity = x.Quantity, ReturnTime = x.ReturnTime, x.Remark }) .ToListAsync(); var returnDict = returnRecords .GroupBy(x => x.BatchNumber) .ToDictionary(g => g.Key, g => g.First()); // 计算差异 var differenceList = sendRecords .Select(send => new LqLaundryFlowDifferenceOutput { batchNumber = send.BatchNumber, storeId = send.StoreId, storeName = send.StoreName, productType = send.ProductType, sendQuantity = send.SendQuantity, returnQuantity = returnDict.ContainsKey(send.BatchNumber) ? returnDict[send.BatchNumber].ReturnQuantity : 0, differenceQuantity = send.SendQuantity - (returnDict.ContainsKey(send.BatchNumber) ? returnDict[send.BatchNumber].ReturnQuantity : 0), differenceRemark = returnDict.ContainsKey(send.BatchNumber) ? returnDict[send.BatchNumber].Remark : "", sendTime = send.SendTime, returnTime = returnDict.ContainsKey(send.BatchNumber) ? returnDict[send.BatchNumber].ReturnTime : (DateTime?)null }) .Where(x => x.differenceQuantity > 0) .ToList(); // 手动分页 var totalCount = differenceList.Count; var pagedList = differenceList .Skip((input.currentPage - 1) * input.pageSize) .Take(input.pageSize) .ToList(); return new { list = pagedList, pagination = new { page = input.currentPage, pageSize = input.pageSize, total = totalCount } }; } catch (Exception ex) { _logger.LogError(ex, "查询差异记录失败"); throw NCCException.Oh($"查询失败:{ex.Message}"); } } #endregion #region 门店每月清洗费用统计 /// /// 门店每月清洗费用统计 /// /// /// 统计每个门店每月的清洗费用(只统计送回记录) /// /// 示例请求: /// ```json /// { /// "startMonth": "202411", /// "endMonth": "202412", /// "storeId": "门店ID(可选)" /// } /// ``` /// /// 统计输入 /// 统计结果 /// 统计成功 /// 服务器错误 [HttpPost("GetStoreMonthlyStatistics")] public async Task> GetStoreMonthlyStatisticsAsync([FromBody] LaundryStatisticsInput input) { try { // 构建月份过滤条件 var startDate = (DateTime?)null; var endDate = (DateTime?)null; if (!string.IsNullOrWhiteSpace(input.StartMonth) && input.StartMonth.Length == 6) { var year = int.Parse(input.StartMonth.Substring(0, 4)); var month = int.Parse(input.StartMonth.Substring(4, 2)); startDate = new DateTime(year, month, 1); } if (!string.IsNullOrWhiteSpace(input.EndMonth) && input.EndMonth.Length == 6) { var year = int.Parse(input.EndMonth.Substring(0, 4)); var month = int.Parse(input.EndMonth.Substring(4, 2)); endDate = new DateTime(year, month, DateTime.DaysInMonth(year, month), 23, 59, 59); } var query = _db.Queryable( (flow, store) => flow.StoreId == store.Id) .Where((flow, store) => flow.FlowType == 1 && flow.IsEffective == StatusEnum.有效.GetHashCode()) .WhereIF(startDate.HasValue, (flow, store) => flow.CreateTime >= startDate.Value) .WhereIF(endDate.HasValue, (flow, store) => flow.CreateTime <= endDate.Value) .WhereIF(!string.IsNullOrWhiteSpace(input.StoreId), (flow, store) => flow.StoreId == input.StoreId); var allRecords = await query .Select((flow, store) => new { flow.StoreId, StoreName = store.Dm ?? "", flow.CreateTime, flow.TotalPrice, flow.Id }) .ToListAsync(); // 在内存中分组统计 var result = allRecords .GroupBy(x => new { x.StoreId, x.StoreName, Month = x.CreateTime.ToString("yyyyMM") }) .Select(g => new LqLaundryStatisticsOutput { storeId = g.Key.StoreId, storeName = g.Key.StoreName, statisticsMonth = g.Key.Month, totalPrice = g.Sum(x => x.TotalPrice), count = g.Count() }) .OrderBy(x => x.storeId) .ThenBy(x => x.statisticsMonth) .ToList(); return result; } catch (Exception ex) { _logger.LogError(ex, "门店每月清洗费用统计失败"); throw NCCException.Oh($"统计失败:{ex.Message}"); } } #endregion #region 产品每月清洗费用统计 /// /// 产品每月清洗费用统计 /// /// /// 统计每个产品每月的清洗费用(只统计送回记录) /// /// 示例请求: /// ```json /// { /// "startMonth": "202411", /// "endMonth": "202412", /// "productType": "毛巾(可选)" /// } /// ``` /// /// 统计输入 /// 统计结果 /// 统计成功 /// 服务器错误 [HttpPost("GetProductMonthlyStatistics")] public async Task> GetProductMonthlyStatisticsAsync([FromBody] LaundryStatisticsInput input) { try { // 构建月份过滤条件 var startDate = (DateTime?)null; var endDate = (DateTime?)null; if (!string.IsNullOrWhiteSpace(input.StartMonth) && input.StartMonth.Length == 6) { var year = int.Parse(input.StartMonth.Substring(0, 4)); var month = int.Parse(input.StartMonth.Substring(4, 2)); startDate = new DateTime(year, month, 1); } if (!string.IsNullOrWhiteSpace(input.EndMonth) && input.EndMonth.Length == 6) { var year = int.Parse(input.EndMonth.Substring(0, 4)); var month = int.Parse(input.EndMonth.Substring(4, 2)); endDate = new DateTime(year, month, DateTime.DaysInMonth(year, month), 23, 59, 59); } var query = _db.Queryable() .Where(x => x.FlowType == 1 && x.IsEffective == StatusEnum.有效.GetHashCode()) .WhereIF(startDate.HasValue, x => x.CreateTime >= startDate.Value) .WhereIF(endDate.HasValue, x => x.CreateTime <= endDate.Value) .WhereIF(!string.IsNullOrWhiteSpace(input.ProductType), x => x.ProductType == input.ProductType); var allRecords = await query .Select(x => new { x.ProductType, x.CreateTime, x.TotalPrice, x.Id }) .ToListAsync(); // 在内存中分组统计 var result = allRecords .GroupBy(x => new { x.ProductType, Month = x.CreateTime.ToString("yyyyMM") }) .Select(g => new LqLaundryStatisticsOutput { productType = g.Key.ProductType, statisticsMonth = g.Key.Month, totalPrice = g.Sum(x => x.TotalPrice), count = g.Count() }) .OrderBy(x => x.productType) .ThenBy(x => x.statisticsMonth) .ToList(); return result; } catch (Exception ex) { _logger.LogError(ex, "产品每月清洗费用统计失败"); throw NCCException.Oh($"统计失败:{ex.Message}"); } } #endregion #region 作废送洗记录 /// /// 作废送洗记录 /// /// /// 作废送洗记录(将F_IsEffective设置为0),同时可以修改备注说明作废原因 /// /// **重要说明**: /// - 作废后的记录不会参与费用计算、成本计算、工资计算、股份计算等相关计算 /// - 所有计算逻辑都使用了F_IsEffective=1的条件,因此作废是安全的 /// - 作废后的记录仍保留在数据库中,可以通过列表查询查看,但不会参与统计 /// /// 示例请求: /// ```json /// { /// "id": "记录ID", /// "remark": "作废原因说明" /// } /// ``` /// /// 参数说明: /// - id: 记录ID(必填) /// - remark: 备注(可选,用于说明作废原因) /// /// 作废输入 /// 作废结果 /// 作废成功 /// 记录不存在或已作废 /// 服务器错误 [HttpPost("Cancel")] public async Task CancelAsync([FromBody] LqLaundryFlowCancelInput input) { try { if (input == null || string.IsNullOrWhiteSpace(input.Id)) { throw NCCException.Oh("记录ID不能为空"); } // 查询记录是否存在 var entity = await _db.Queryable() .Where(x => x.Id == input.Id) .FirstAsync(); if (entity == null) { throw NCCException.Oh("送洗记录不存在"); } // 检查是否已经作废 if (entity.IsEffective == StatusEnum.无效.GetHashCode()) { throw NCCException.Oh("该记录已经作废"); } // 如果该记录是送出记录(F_FlowType = 0),需要检查是否有对应的送回记录 if (entity.FlowType == 0) { var returnRecord = await _db.Queryable() .Where(x => x.BatchNumber == entity.BatchNumber && x.FlowType == 1 && x.IsEffective == StatusEnum.有效.GetHashCode()) .FirstAsync(); if (returnRecord != null) { throw NCCException.Oh("该送出记录已有对应的送回记录,不能单独作废送出记录。如需作废,请先作废对应的送回记录"); } } // 作废记录:将F_IsEffective设置为0 entity.IsEffective = StatusEnum.无效.GetHashCode(); // 更新备注(如果提供了备注) if (!string.IsNullOrWhiteSpace(input.Remark)) { // 如果原备注不为空,追加新备注;否则直接设置 if (!string.IsNullOrWhiteSpace(entity.Remark)) { entity.Remark = $"{entity.Remark}\n[作废]{input.Remark}"; } else { entity.Remark = $"[作废]{input.Remark}"; } } else if (string.IsNullOrWhiteSpace(entity.Remark)) { // 如果没有提供备注且原备注为空,则添加默认作废标记 entity.Remark = "[作废]"; } else { // 如果没有提供备注但原备注不为空,则添加默认作废标记 entity.Remark = $"{entity.Remark}\n[作废]"; } // 执行更新 var isOk = await _db.Updateable(entity) .UpdateColumns(it => new { it.IsEffective, it.Remark }) .ExecuteCommandAsync(); if (!(isOk > 0)) throw NCCException.Oh(ErrorCode.COM1000); return new { message = "作废成功", id = entity.Id, remark = entity.Remark }; } catch (Exception ex) { _logger.LogError(ex, "作废送洗记录失败"); throw NCCException.Oh($"作废失败:{ex.Message}"); } } #endregion } }