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
}
}