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.Filter; using NCC.Dependency; using NCC.DynamicApiController; using NCC.Extend.Entitys.Dto.LqContract; using NCC.Extend.Entitys.Enum; using NCC.Extend.Entitys.lq_contract; using NCC.Extend.Entitys.lq_contract_rent_detail; using NCC.Extend.Entitys.lq_mdxx; using NCC.FriendlyException; using NCC.System.Entitys.Permission; using SqlSugar; using Yitter.IdGenerator; namespace NCC.Extend { /// /// 合同管理服务 /// [ApiDescriptionSettings(Tag = "绿纤合同管理", Name = "LqContract", Order = 250)] [Route("api/Extend/LqContract")] public class LqContractService : IDynamicApiController, ITransient { private readonly IUserManager _userManager; private readonly ILogger _logger; private readonly ISqlSugarClient _db; /// /// 构造函数 /// /// 用户管理器 /// 日志记录器 /// 数据库客户端 public LqContractService(IUserManager userManager, ILogger logger, ISqlSugarClient db) { _userManager = userManager; _logger = logger; _db = db; } #region 创建合同 /// /// 创建合同 /// /// /// 创建新合同,系统会自动根据合同信息生成月租明细 /// /// 示例请求: /// ```json /// { /// "storeId": "门店ID", /// "title": "门店租赁合同", /// "category": "租赁合同", /// "tenantName": "张三", /// "contractStartDate": "2025-01-01T00:00:00", /// "contractEndDate": "2025-12-31T23:59:59", /// "reminderDays": 7, /// "deposit": 5000.00, /// "monthlyRent": 1000.00, /// "paymentAmount": 3000.00, /// "paymentCycle": 3, /// "remarks": "季度交租", /// "attachment": "" /// } /// ``` /// /// 业务流程: /// 1. 验证合同信息 /// 2. 查询门店信息(获取店名) /// 3. 创建合同记录 /// 4. 自动生成月租明细(根据合同起始日期、结束日期、交租周期、缴租金额) /// 5. 计算下次应交时间 /// /// 创建输入 /// 创建结果 /// 创建成功 /// 请求参数错误 /// 服务器错误 [HttpPost("Create")] public async Task CreateAsync([FromBody] LqContractCrInput input) { try { // 验证合同日期 if (input.ContractStartDate >= input.ContractEndDate) { throw NCCException.Oh("合同起始日期必须小于合同结束日期"); } // 验证交租周期 if (input.PaymentCycle <= 0 || input.PaymentCycle > 12) { throw NCCException.Oh("交租周期必须在1-12个月之间"); } // 查询门店信息 var store = await _db.Queryable() .Where(x => x.Id == input.StoreId) .Select(x => new { x.Dm }) .FirstAsync(); if (store == null) { throw NCCException.Oh("门店不存在"); } _db.Ado.BeginTran(); try { // 创建合同 var contractEntity = new LqContractEntity { Id = YitIdHelper.NextId().ToString(), StoreId = input.StoreId, StoreName = store.Dm ?? "", Title = input.Title, Category = input.Category, TenantName = input.TenantName, ContractStartDate = input.ContractStartDate, ContractEndDate = input.ContractEndDate, ReminderDays = input.ReminderDays, Deposit = input.Deposit, MonthlyRent = input.MonthlyRent, PaymentAmount = input.PaymentAmount, PaymentCycle = input.PaymentCycle, Remarks = input.Remarks, Attachment = input.Attachment, CreateUser = _userManager.UserId, CreateTime = DateTime.Now, IsEffective = StatusEnum.有效.GetHashCode() }; var insertCount = await _db.Insertable(contractEntity).ExecuteCommandAsync(); if (insertCount <= 0) { throw NCCException.Oh("创建合同失败"); } // 自动生成月租明细 await GenerateRentDetailsAsync(contractEntity.Id, contractEntity.ContractStartDate, contractEntity.ContractEndDate, contractEntity.PaymentCycle, contractEntity.PaymentAmount); // 计算下次应交时间 await CalculateNextPaymentDate(contractEntity.Id); _db.Ado.CommitTran(); } catch { _db.Ado.RollbackTran(); throw; } } catch (Exception ex) { _db.Ado.RollbackTran(); _logger.LogError(ex, "创建合同失败"); throw NCCException.Oh($"创建失败:{ex.Message}"); } } #endregion #region 更新合同 /// /// 更新合同 /// /// /// 更新合同信息。如果修改了合同起始日期、结束日期、交租周期或缴租金额,系统会重新生成月租明细。 /// /// 示例请求: /// ```json /// { /// "id": "合同ID", /// "storeId": "门店ID", /// "title": "门店租赁合同", /// "category": "租赁合同", /// "tenantName": "张三", /// "contractStartDate": "2025-01-01T00:00:00", /// "contractEndDate": "2025-12-31T23:59:59", /// "reminderDays": 7, /// "deposit": 5000.00, /// "monthlyRent": 1000.00, /// "paymentAmount": 3000.00, /// "paymentCycle": 3, /// "remarks": "季度交租", /// "attachment": "" /// } /// ``` /// /// 更新输入 /// 更新结果 /// 更新成功 /// 请求参数错误或合同不存在 /// 服务器错误 [HttpPut("Update")] public async Task UpdateAsync([FromBody] LqContractUpInput input) { try { // 查询合同 var contract = await _db.Queryable() .Where(x => x.Id == input.Id && x.IsEffective == StatusEnum.有效.GetHashCode()) .FirstAsync(); if (contract == null) { throw NCCException.Oh("合同不存在或已失效"); } // 验证合同日期 if (input.ContractStartDate >= input.ContractEndDate) { throw NCCException.Oh("合同起始日期必须小于合同结束日期"); } // 验证交租周期 if (input.PaymentCycle <= 0 || input.PaymentCycle > 12) { throw NCCException.Oh("交租周期必须在1-12个月之间"); } // 查询门店信息 var store = await _db.Queryable() .Where(x => x.Id == input.StoreId) .Select(x => new { x.Dm }) .FirstAsync(); if (store == null) { throw NCCException.Oh("门店不存在"); } _db.Ado.BeginTran(); try { // 判断是否需要重新生成明细 bool needRegenerateDetails = contract.ContractStartDate != input.ContractStartDate || contract.ContractEndDate != input.ContractEndDate || contract.PaymentCycle != input.PaymentCycle || contract.PaymentAmount != input.PaymentAmount; // 更新合同信息 contract.StoreId = input.StoreId; contract.StoreName = store.Dm ?? ""; contract.Title = input.Title; contract.Category = input.Category; contract.TenantName = input.TenantName; contract.ContractStartDate = input.ContractStartDate; contract.ContractEndDate = input.ContractEndDate; contract.ReminderDays = input.ReminderDays; contract.Deposit = input.Deposit; contract.MonthlyRent = input.MonthlyRent; contract.PaymentAmount = input.PaymentAmount; contract.PaymentCycle = input.PaymentCycle; contract.Remarks = input.Remarks; contract.Attachment = input.Attachment; contract.UpdateUser = _userManager.UserId; contract.UpdateTime = DateTime.Now; var updateCount = await _db.Updateable(contract).ExecuteCommandAsync(); if (updateCount <= 0) { throw NCCException.Oh("更新合同失败"); } // 如果需要重新生成明细,先删除旧明细,再生成新明细 if (needRegenerateDetails) { // 删除旧的明细(逻辑删除) await _db.Updateable() .SetColumns(x => new LqContractRentDetailEntity { IsEffective = StatusEnum.无效.GetHashCode(), UpdateUser = _userManager.UserId, UpdateTime = DateTime.Now }) .Where(x => x.ContractId == contract.Id && x.IsEffective == StatusEnum.有效.GetHashCode()) .ExecuteCommandAsync(); // 生成新的明细 await GenerateRentDetailsAsync(contract.Id, contract.ContractStartDate, contract.ContractEndDate, contract.PaymentCycle, contract.PaymentAmount); } // 重新计算下次应交时间 await CalculateNextPaymentDate(contract.Id); _db.Ado.CommitTran(); } catch { _db.Ado.RollbackTran(); throw; } } catch (Exception ex) { _db.Ado.RollbackTran(); _logger.LogError(ex, "更新合同失败"); throw NCCException.Oh($"更新失败:{ex.Message}"); } } #endregion #region 删除合同 /// /// 删除合同 /// /// /// 删除合同,同时会级联删除该合同的所有月租明细(逻辑删除) /// /// 示例请求: /// ``` /// DELETE /api/Extend/LqContract/{id} /// ``` /// /// 合同ID /// 删除结果 /// 删除成功 /// 合同不存在 /// 服务器错误 [HttpDelete("{id}")] public async Task DeleteAsync([FromRoute] string id) { try { if (string.IsNullOrWhiteSpace(id)) { throw NCCException.Oh("合同ID不能为空"); } // 查询合同 var contract = await _db.Queryable() .Where(x => x.Id == id && x.IsEffective == StatusEnum.有效.GetHashCode()) .FirstAsync(); if (contract == null) { throw NCCException.Oh("合同不存在或已失效"); } _db.Ado.BeginTran(); try { // 删除该合同的所有月租明细(逻辑删除) await _db.Updateable() .SetColumns(x => new LqContractRentDetailEntity { IsEffective = StatusEnum.无效.GetHashCode(), UpdateUser = _userManager.UserId, UpdateTime = DateTime.Now }) .Where(x => x.ContractId == id && x.IsEffective == StatusEnum.有效.GetHashCode()) .ExecuteCommandAsync(); // 删除合同(逻辑删除) contract.IsEffective = StatusEnum.无效.GetHashCode(); contract.UpdateUser = _userManager.UserId; contract.UpdateTime = DateTime.Now; var updateCount = await _db.Updateable(contract).ExecuteCommandAsync(); if (updateCount <= 0) { throw NCCException.Oh("删除合同失败"); } _db.Ado.CommitTran(); } catch { _db.Ado.RollbackTran(); throw; } } catch (Exception ex) { _db.Ado.RollbackTran(); _logger.LogError(ex, "删除合同失败"); throw NCCException.Oh($"删除失败:{ex.Message}"); } } #endregion #region 获取合同列表 /// /// 获取合同列表 /// /// /// 分页查询合同列表,支持多条件筛选 /// /// 示例请求: /// ``` /// GET /api/Extend/LqContract/GetList?currentPage=1&pageSize=10&storeId=门店ID&title=合同标题 /// ``` /// /// 查询输入 /// 合同列表 /// 查询成功 /// 服务器错误 [HttpGet("GetList")] public async Task GetListAsync([FromQuery] LqContractListQueryInput input) { try { var sidx = string.IsNullOrEmpty(input.sidx) ? "createTime" : input.sidx; var sort = string.IsNullOrEmpty(input.sort) ? "desc" : input.sort; var data = await _db.Queryable() .WhereIF(!string.IsNullOrWhiteSpace(input.StoreId), x => x.StoreId == input.StoreId) .WhereIF(!string.IsNullOrWhiteSpace(input.StoreName), x => x.StoreName != null && x.StoreName.Contains(input.StoreName)) .WhereIF(!string.IsNullOrWhiteSpace(input.Category), x => x.Category == input.Category) .WhereIF(!string.IsNullOrWhiteSpace(input.Title), x => x.Title != null && x.Title.Contains(input.Title)) .WhereIF(input.ContractStartDateBegin.HasValue, x => x.ContractStartDate >= input.ContractStartDateBegin.Value) .WhereIF(input.ContractStartDateEnd.HasValue, x => x.ContractStartDate <= input.ContractStartDateEnd.Value) .WhereIF(input.ContractEndDateBegin.HasValue, x => x.ContractEndDate >= input.ContractEndDateBegin.Value) .WhereIF(input.ContractEndDateEnd.HasValue, x => x.ContractEndDate <= input.ContractEndDateEnd.Value) .WhereIF(input.IsEffective.HasValue, x => x.IsEffective == input.IsEffective.Value) .Where(x => x.IsEffective == StatusEnum.有效.GetHashCode()) .Select(x => new LqContractListOutput { id = x.Id, storeId = x.StoreId, storeName = x.StoreName, title = x.Title, category = x.Category, tenantName = x.TenantName, contractStartDate = x.ContractStartDate, contractEndDate = x.ContractEndDate, reminderDays = x.ReminderDays, deposit = x.Deposit, nextPaymentDate = x.NextPaymentDate, monthlyRent = x.MonthlyRent, paymentAmount = x.PaymentAmount, paymentCycle = x.PaymentCycle, remarks = x.Remarks, attachment = x.Attachment, createUser = x.CreateUser, createUserName = "", createTime = x.CreateTime, updateUser = x.UpdateUser, updateUserName = "", updateTime = x.UpdateTime, isEffective = x.IsEffective }) .MergeTable() .OrderBy(sidx + " " + sort) .ToPagedListAsync(input.currentPage, input.pageSize); // 补充用户信息 var userIds = data.list.SelectMany(x => new[] { x.createUser, x.updateUser }) .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) { if (!string.IsNullOrEmpty(item.createUser) && userDict.ContainsKey(item.createUser)) item.createUserName = userDict[item.createUser]; if (!string.IsNullOrEmpty(item.updateUser) && userDict.ContainsKey(item.updateUser)) item.updateUserName = userDict[item.updateUser]; } } return PageResult.SqlSugarPageResult(data); } catch (Exception ex) { _logger.LogError(ex, "获取合同列表失败"); throw NCCException.Oh($"获取合同列表失败:{ex.Message}"); } } #endregion #region 获取合同详情 /// /// 获取合同详情 /// /// /// 根据合同ID获取合同详细信息,包括月租明细列表 /// /// 示例请求: /// ``` /// GET /api/Extend/LqContract/GetInfo?id=合同ID /// ``` /// /// 合同ID /// 合同详情 /// 查询成功 /// 合同ID不能为空 /// 合同不存在 /// 服务器错误 [HttpGet("GetInfo")] public async Task GetInfoAsync([FromQuery] string id) { try { if (string.IsNullOrWhiteSpace(id)) { throw NCCException.Oh("合同ID不能为空"); } // 查询合同 var contract = await _db.Queryable() .Where(x => x.Id == id && x.IsEffective == StatusEnum.有效.GetHashCode()) .FirstAsync(); if (contract == null) { throw NCCException.Oh("合同不存在或已失效"); } // 查询月租明细 var rentDetails = await _db.Queryable() .Where(x => x.ContractId == id && x.IsEffective == StatusEnum.有效.GetHashCode()) .OrderBy(x => x.PaymentMonth) .Select(x => new LqContractRentDetailListOutput { id = x.Id, contractId = x.ContractId, paymentMonth = x.PaymentMonth, dueDate = x.DueDate, dueAmount = x.DueAmount, isPaid = x.IsPaid, actualPaymentDate = x.ActualPaymentDate, actualPaymentAmount = x.ActualPaymentAmount, remarks = x.Remarks, createUser = x.CreateUser, createUserName = "", createTime = x.CreateTime, updateUser = x.UpdateUser, updateUserName = "", updateTime = x.UpdateTime, isEffective = x.IsEffective }) .ToListAsync(); // 补充用户信息 var userIds = new List { contract.CreateUser, contract.UpdateUser }; userIds.AddRange(rentDetails.SelectMany(x => new[] { x.createUser, x.updateUser }).Where(x => !string.IsNullOrEmpty(x))); string createUserName = ""; string updateUserName = ""; if (userIds.Any()) { var userList = await _db.Queryable() .Where(x => userIds.Distinct().Contains(x.Id)) .Select(x => new { x.Id, x.RealName }) .ToListAsync(); var userDict = userList.ToDictionary(k => k.Id, v => v.RealName); if (!string.IsNullOrEmpty(contract.CreateUser) && userDict.ContainsKey(contract.CreateUser)) createUserName = userDict[contract.CreateUser]; if (!string.IsNullOrEmpty(contract.UpdateUser) && userDict.ContainsKey(contract.UpdateUser)) updateUserName = userDict[contract.UpdateUser]; foreach (var detail in rentDetails) { if (!string.IsNullOrEmpty(detail.createUser) && userDict.ContainsKey(detail.createUser)) detail.createUserName = userDict[detail.createUser]; if (!string.IsNullOrEmpty(detail.updateUser) && userDict.ContainsKey(detail.updateUser)) detail.updateUserName = userDict[detail.updateUser]; } } return new LqContractInfoOutput { id = contract.Id, storeId = contract.StoreId, storeName = contract.StoreName, title = contract.Title, category = contract.Category, tenantName = contract.TenantName, contractStartDate = contract.ContractStartDate, contractEndDate = contract.ContractEndDate, reminderDays = contract.ReminderDays, deposit = contract.Deposit, nextPaymentDate = contract.NextPaymentDate, monthlyRent = contract.MonthlyRent, paymentAmount = contract.PaymentAmount, paymentCycle = contract.PaymentCycle, remarks = contract.Remarks, attachment = contract.Attachment, createUser = contract.CreateUser, createUserName = createUserName, createTime = contract.CreateTime, updateUser = contract.UpdateUser, updateUserName = updateUserName, updateTime = contract.UpdateTime, isEffective = contract.IsEffective, rentDetails = rentDetails }; } catch (Exception ex) { _logger.LogError(ex, "获取合同详情失败"); throw NCCException.Oh($"获取合同详情失败:{ex.Message}"); } } #endregion #region 获取月租明细列表 /// /// 获取月租明细列表 /// /// /// 根据合同ID获取该合同的所有月租明细 /// /// 示例请求: /// ``` /// GET /api/Extend/LqContract/GetRentDetails?contractId=合同ID /// ``` /// /// 合同ID /// 月租明细列表 /// 查询成功 /// 合同ID不能为空 /// 服务器错误 [HttpGet("GetRentDetails")] public async Task GetRentDetailsAsync([FromQuery] string contractId) { try { if (string.IsNullOrWhiteSpace(contractId)) { throw NCCException.Oh("合同ID不能为空"); } var rentDetails = await _db.Queryable() .Where(x => x.ContractId == contractId && x.IsEffective == StatusEnum.有效.GetHashCode()) .OrderBy(x => x.PaymentMonth) .Select(x => new LqContractRentDetailListOutput { id = x.Id, contractId = x.ContractId, paymentMonth = x.PaymentMonth, dueDate = x.DueDate, dueAmount = x.DueAmount, isPaid = x.IsPaid, actualPaymentDate = x.ActualPaymentDate, actualPaymentAmount = x.ActualPaymentAmount, remarks = x.Remarks, createUser = x.CreateUser, createUserName = "", createTime = x.CreateTime, updateUser = x.UpdateUser, updateUserName = "", updateTime = x.UpdateTime, isEffective = x.IsEffective }) .ToListAsync(); // 补充用户信息 var userIds = rentDetails.SelectMany(x => new[] { x.createUser, x.updateUser }) .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 detail in rentDetails) { if (!string.IsNullOrEmpty(detail.createUser) && userDict.ContainsKey(detail.createUser)) detail.createUserName = userDict[detail.createUser]; if (!string.IsNullOrEmpty(detail.updateUser) && userDict.ContainsKey(detail.updateUser)) detail.updateUserName = userDict[detail.updateUser]; } } return rentDetails; } catch (Exception ex) { _logger.LogError(ex, "获取月租明细列表失败"); throw NCCException.Oh($"获取月租明细列表失败:{ex.Message}"); } } #endregion #region 标记明细已缴费 /// /// 标记月租明细已缴费 /// /// /// 标记某条月租明细已缴费,记录实际缴费时间和金额 /// /// 示例请求: /// ```json /// { /// "id": "明细ID", /// "actualPaymentDate": "2025-01-15T00:00:00", /// "actualPaymentAmount": 3000.00, /// "remarks": "已缴费" /// } /// ``` /// /// 标记已缴费输入 /// 标记结果 /// 标记成功 /// 明细不存在或已缴费 /// 服务器错误 [HttpPut("MarkRentDetailPaid")] public async Task MarkRentDetailPaidAsync([FromBody] LqContractRentDetailMarkPaidInput input) { try { // 查询明细 var detail = await _db.Queryable() .Where(x => x.Id == input.Id && x.IsEffective == StatusEnum.有效.GetHashCode()) .FirstAsync(); if (detail == null) { throw NCCException.Oh("月租明细不存在或已失效"); } if (detail.IsPaid == 1) { throw NCCException.Oh("该明细已标记为已缴费"); } _db.Ado.BeginTran(); try { // 更新明细 detail.IsPaid = 1; detail.ActualPaymentDate = input.ActualPaymentDate; detail.ActualPaymentAmount = input.ActualPaymentAmount; detail.Remarks = input.Remarks; detail.UpdateUser = _userManager.UserId; detail.UpdateTime = DateTime.Now; var updateCount = await _db.Updateable(detail).ExecuteCommandAsync(); if (updateCount <= 0) { throw NCCException.Oh("标记已缴费失败"); } // 重新计算下次应交时间 await CalculateNextPaymentDate(detail.ContractId); _db.Ado.CommitTran(); } catch { _db.Ado.RollbackTran(); throw; } } catch (Exception ex) { _db.Ado.RollbackTran(); _logger.LogError(ex, "标记明细已缴费失败"); throw NCCException.Oh($"标记失败:{ex.Message}"); } } #endregion #region 私有方法 /// /// 自动生成月租明细 /// /// 合同ID /// 合同起始日期 /// 合同结束日期 /// 交租周期(月) /// 缴租金额 private async Task GenerateRentDetailsAsync(string contractId, DateTime contractStartDate, DateTime contractEndDate, int paymentCycle, decimal paymentAmount) { var details = new List(); var currentDate = contractStartDate.Date; // 从合同起始日期开始,每隔交租周期生成一条明细 while (currentDate <= contractEndDate) { // 应缴月份:当前日期的月份第一天(格式:YYYY-MM-01) var paymentMonth = new DateTime(currentDate.Year, currentDate.Month, 1); // 应缴日期:应缴月份的第一天(根据业务规则,应缴日期为应缴月份的第一天) var dueDate = paymentMonth; var detail = new LqContractRentDetailEntity { Id = YitIdHelper.NextId().ToString(), ContractId = contractId, PaymentMonth = paymentMonth, DueDate = dueDate, DueAmount = paymentAmount, IsPaid = 0, CreateUser = _userManager.UserId, CreateTime = DateTime.Now, IsEffective = StatusEnum.有效.GetHashCode() }; details.Add(detail); // 计算下一个交租日期(加上交租周期) currentDate = currentDate.AddMonths(paymentCycle); } // 批量插入 if (details.Any()) { await _db.Insertable(details).ExecuteCommandAsync(); } } /// /// 计算下次应交时间(匿名方法) /// /// 合同ID private async Task CalculateNextPaymentDate(string contractId) { // 查询合同信息 var contract = await _db.Queryable() .Where(x => x.Id == contractId && x.IsEffective == StatusEnum.有效.GetHashCode()) .FirstAsync(); if (contract == null) { return; } // 查询该合同未缴费的明细,按应缴日期升序排列 var unpaidDetail = await _db.Queryable() .Where(x => x.ContractId == contractId && x.IsEffective == StatusEnum.有效.GetHashCode() && x.IsPaid == 0) .OrderBy(x => x.DueDate) .FirstAsync(); if (unpaidDetail != null) { // 计算:下次应交时间 = 最早应缴日期 - 提前提醒天数 var nextPaymentDate = unpaidDetail.DueDate.AddDays(-contract.ReminderDays); // 更新合同的下次应交时间 contract.NextPaymentDate = nextPaymentDate; contract.UpdateUser = _userManager.UserId; contract.UpdateTime = DateTime.Now; await _db.Updateable(contract).ExecuteCommandAsync(); } else { // 如果没有未缴费的明细,清空下次应交时间 contract.NextPaymentDate = null; contract.UpdateUser = _userManager.UserId; contract.UpdateTime = DateTime.Now; await _db.Updateable(contract).ExecuteCommandAsync(); } } #endregion #region 统计门店合同费用 /// /// 统计门店合同费用 /// /// /// 根据门店ID和月份统计该门店的合同费用,支持按分类统计(租门店、员工宿舍、车辆、场所等) /// /// 示例请求: /// ```json /// { /// "storeId": "1649328471923847168", /// "year": 2025, /// "month": 1, /// "categories": ["租门店", "员工宿舍", "车辆", "场所"] /// } /// ``` /// /// 参数说明: /// - storeId: 门店ID(必填) /// - year: 统计年份(必填) /// - month: 统计月份(必填,1-12) /// - categories: 分类列表(可选,不传则统计所有分类) /// /// 返回数据说明: /// - storeId: 门店ID /// - storeName: 门店名称 /// - year: 统计年份 /// - month: 统计月份 /// - totalAmount: 总费用(所有分类的费用总和) /// - categoryDetails: 按分类统计的费用明细 /// - category: 分类名称 /// - amount: 该分类的费用总额 /// - detailCount: 该分类的明细数量 /// - details: 明细列表 /// /// 统计输入 /// 费用统计结果 /// 统计成功 /// 请求参数错误 /// 服务器错误 [HttpPost("GetExpenseStatistics")] public async Task GetExpenseStatisticsAsync([FromBody] ContractExpenseStatisticsInput input) { try { // 验证月份 if (input.Month < 1 || input.Month > 12) { throw NCCException.Oh("月份必须在1-12之间"); } // 构建统计月份的开始和结束时间 var statisticsMonth = new DateTime(input.Year, input.Month, 1); var monthStart = statisticsMonth; var monthEnd = statisticsMonth.AddMonths(1).AddDays(-1); // 查询门店信息 var store = await _db.Queryable() .Where(x => x.Id == input.StoreId) .Select(x => new { x.Dm }) .FirstAsync(); if (store == null) { throw NCCException.Oh("门店不存在"); } // 查询该门店在指定月份的月租明细 var details = await _db.Queryable( (detail, contract) => new JoinQueryInfos( JoinType.Inner, detail.ContractId == contract.Id)) .Where((detail, contract) => contract.StoreId == input.StoreId && contract.IsEffective == StatusEnum.有效.GetHashCode() && detail.IsEffective == StatusEnum.有效.GetHashCode() && detail.PaymentMonth >= monthStart && detail.PaymentMonth <= monthEnd) .WhereIF(input.Categories != null && input.Categories.Length > 0, (detail, contract) => contract.Category != null && input.Categories.Contains(contract.Category)) .Select((detail, contract) => new { DetailId = detail.Id, ContractId = contract.Id, ContractTitle = contract.Title, Category = contract.Category ?? "未分类", DueAmount = detail.DueAmount, IsPaid = detail.IsPaid, ActualPaymentAmount = detail.ActualPaymentAmount }) .ToListAsync(); // 按分类分组统计 var categoryGroups = details.GroupBy(x => x.Category).ToList(); var categoryDetails = new List(); decimal totalAmount = 0; foreach (var group in categoryGroups) { var categoryName = group.Key; var categoryItems = group.ToList(); var categoryAmount = categoryItems.Sum(x => x.DueAmount); totalAmount += categoryAmount; var expenseDetails = categoryItems.Select(x => new ExpenseDetailItem { detailId = x.DetailId, contractId = x.ContractId, contractTitle = x.ContractTitle, category = x.Category, dueAmount = x.DueAmount, isPaid = x.IsPaid, actualPaymentAmount = x.ActualPaymentAmount }).ToList(); categoryDetails.Add(new CategoryExpenseDetail { category = categoryName, amount = categoryAmount, detailCount = categoryItems.Count, details = expenseDetails }); } // 按分类名称排序 categoryDetails = categoryDetails.OrderBy(x => x.category).ToList(); return new ContractExpenseStatisticsOutput { storeId = input.StoreId, storeName = store.Dm ?? "", year = input.Year, month = input.Month, totalAmount = totalAmount, categoryDetails = categoryDetails }; } catch (Exception ex) { _logger.LogError(ex, "统计门店合同费用失败"); throw NCCException.Oh($"统计失败:{ex.Message}"); } } #endregion } }