using DS.Module.Core; using DS.Module.Core.Enums; using DS.Module.Core.Extensions; using DS.Module.UserModule; using DS.WMS.ContainerManagement.Info.Entity; using DS.WMS.Core.Application.Dtos; using DS.WMS.Core.Application.Entity; using DS.WMS.Core.Fee.Entity; using DS.WMS.Core.Fee.Method; using DS.WMS.Core.Invoice.Dtos; using DS.WMS.Core.Settlement.Dtos; using DS.WMS.Core.Settlement.Entity; using DS.WMS.Core.Settlement.Interface; using DS.WMS.Core.Sys.Interface; using Mapster; using Microsoft.Extensions.DependencyInjection; using Org.BouncyCastle.Ocsp; using SqlSugar; using System; namespace DS.WMS.Core.Settlement.Method { /// /// 结算基础实现 /// /// 实体的类型声明 public class SettlementService : FeeServiceBase, ISettlementService where TEntity : SettlementBase, new() { readonly Lazy CommonService; //readonly Lazy FreeSettlementService; /// /// 初始化 /// /// DI容器 public SettlementService(IServiceProvider serviceProvider) : base(serviceProvider) { CommonService = new Lazy(serviceProvider.GetRequiredService()); //FreeSettlementService = new Lazy(serviceProvider.GetRequiredService()); } /// /// 根据业务编号及类型获取关联费用记录 /// /// 业务ID与业务类型 /// public async Task> GetFeesAsync(params FeeClient[] items) { var bizIds = items.Select(x => x.Id).Distinct(); var types = items.Select(x => x.BusinessType).Distinct(); var cIds = items.Select(x => x.CustomerId).Distinct(); var list = await TenantDb.Queryable() .Where(f => bizIds.Contains(f.BusinessId) && types.Contains(f.BusinessType) && cIds.Contains(f.CustomerId) && f.FeeStatus == FeeStatus.AuditPassed && (f.Amount - f.SettlementAmount - f.OrderAmount + f.OrderSettlementAmount) != 0) .Select(f => new FeeItem { RecordId = f.Id, BusinessId = f.BusinessId, BusinessType = f.BusinessType, CustomerId = f.CustomerId, CustomerName = f.CustomerName, FeeId = f.FeeId, FeeName = f.FeeName, FeeType = f.FeeType, TotalAmount = f.Amount, Currency = f.Currency, OriginalRate = f.ExchangeRate, RestAmount = f.Amount - f.SettlementAmount - f.OrderAmount + f.OrderSettlementAmount, InvoiceAmount = f.InvoiceAmount, AccTaxRate = f.AccTaxRate, Remark = f.Remark }).ToListAsync(); foreach (var item in list) { //本次结算金额默认等于剩余金额 item.Amount = item.RestAmount; item.OriginalAmount = item.RestAmount; } return DataResult.Success(new FeeForm(list)); } #region 保存 /// /// 提交结算单 /// /// /// public async Task> SaveAsync(SettlementRequest request) { var settlement = request.Settlement; if (settlement.SettlementDate == default) settlement.SettlementDate = DateTime.Now; if (settlement.Mode == 0) return DataResult.FailedWithDesc(nameof(MultiLanguageConst.UnknownSettlementMode)); if (settlement.BillType == 0) return DataResult.FailedWithDesc(nameof(MultiLanguageConst.UnknownSettlementType)); TEntity? dbValue = default; if (request.Settlement.Id > 0) { dbValue = await TenantDb.Queryable().Select(x => new TEntity { Id = x.Id, IsLocked = x.IsLocked, Mode = x.Mode }).FirstAsync(x => x.Id == request.Settlement.Id); if (dbValue != null && dbValue.IsLocked) return DataResult.FailedWithDesc(nameof(MultiLanguageConst.SettlementIsLocked)); } var result = EnsureSettlement(request.Settlement, dbValue); if (!result.Succeeded) return DataResult.Failed(result.Message, result.MultiCode); List? details1 = null; //自由结算 if (settlement.Mode == SettlementMode.FreeSettlement && request.Details?.Count > 0) { if (settlement.Id == 0) { var first = request.Details[0]; settlement.CustomerId = first.CustomerId; settlement.CustomerName = first.CustomerName; } //var tempDetailList = request.Details.Select(x => new ApplicationDetail //{ // RefId = x.ApplicationId, // DetailId = x.Id, // RecordId = x.RecordId, // CustomerName = x.CustomerName ?? settlement.CustomerName, // FeeId = x.FeeId, // FeeName = x.FeeName, // FeeType = x.FeeType, // ApplyAmount = x.RestAmount.GetValueOrDefault(), // Currency = x.Currency, // ExchangeRate = x.ExchangeRate, // OriginalAmount = x.OriginalAmount, // OriginalCurrency = x.OriginalCurrency ?? (settlement.Currency.IsNullOrEmpty() ? x.Currency : settlement.Currency), //}).ToList(); //if(tempDetailList.Count>0) details1 = new List(); //foreach (var tempDetail in tempDetailList) //{ // if (tempDetail.DetailId != null) // { // details1.Add(tempDetail); // } // else { // //20240929 该业务的所有符合条件的费用 // var feeClient = new FeeClient(); // var _detail = request.Details.First(x => x.BusinessId == tempDetail.BusinessId); // feeClient.Id = _detail.BusinessId; // feeClient.BusinessType = _detail.BusinessType; // feeClient.CustomerId= request.Settlement.CustomerId; // var paramarray = new FeeClient[]{ feeClient}; // var feeListResult = await GetFeesAsync(paramarray); // var feeList = feeListResult.Data.Items; // var _detailList = feeList.Where(x => x.FeeType == tempDetail.FeeType && x.RestAmount > 0) // .Select(x => new ApplicationDetail // { // RefId = x.BusinessId, // DetailId = 0, // RecordId = x.RecordId, // CustomerName = x.CustomerName ?? settlement.CustomerName, // FeeId = x.FeeId, // FeeName = x.FeeName, // FeeType = x.FeeType, // ApplyAmount = 0, // Currency = x.Currency, // ExchangeRate = tempDetail.ExchangeRate, // OriginalAmount = 0, // OriginalCurrency = x.Currency, // }) // .ToList(); // details1.AddRange(_detailList); // } //} details1 = await GetFeeDetailByBill(request); } //按付费/发票申请结算 if (request.Documents?.Count > 0) { if (settlement.Id == 0) { var first = request.Documents[0]; settlement.CustomerId = first.CustomerId; settlement.CustomerName = first.CustomerName; } var ids = request.Documents.Select(x => x.Id); //收/付费申请结算 if (settlement.Mode == SettlementMode.Payment || settlement.Mode == SettlementMode.Charge) { var detailCategory = settlement.Mode == SettlementMode.Payment ? DetailCategory.PaidApplication : DetailCategory.ChargeApplication; details1 = await TenantDb.Queryable().Where(x => ids.Contains(x.ApplicationId) && x.Category == detailCategory) .Select(x => new ApplicationDetail { ApplicationId = settlement.Id, RefId = x.ApplicationId, DetailId = x.Id, RecordId = x.RecordId, Category = settlement.Mode == SettlementMode.Payment ? DetailCategory.PaidApplicationSettlement : DetailCategory.ChargeApplicationSettlement, CustomerName = x.CustomerName ?? settlement.CustomerName, FeeId = x.FeeId, FeeName = x.FeeName, FeeType = x.FeeType, ApplyAmount = x.ApplyAmount - x.ProcessedAmount, Currency = x.Currency, OriginalAmount = x.OriginalAmount - x.OriginalProcessedAmount, OriginalCurrency = x.OriginalCurrency, ExchangeRate = x.ExchangeRate }).ToListAsync(); if (!string.IsNullOrEmpty(settlement.Currency)) //指定结算币别 { var details2 = details1.FindAll(x => x.OriginalCurrency != settlement.Currency); foreach (var detail in details2) { detail.Currency = settlement.Currency; var doc = request.Documents.Find(x => x.Id == detail.ApplicationId); if (doc == null) return DataResult.Failed("结算单据与费用明细不一致"); var exchange = doc.ExchangeRates?.Find(x => x.Currency == settlement.Currency); if (exchange == null) return DataResult.Failed($"未传入结算币别 {settlement.Currency} 与费用原币别 {detail.OriginalCurrency} 之间的汇率信息"); detail.ExchangeRate = exchange.ExchangeRate; detail.ApplyAmount = Math.Round(exchange.ExchangeRate.GetValueOrDefault() * detail.OriginalAmount, 2, MidpointRounding.AwayFromZero); } } } //发票结算 else if (settlement.Mode == SettlementMode.InvoiceSettlement || settlement.Mode == SettlementMode.PaymentInvoiceSettlement) { details1 = await TenantDb.Queryable().Where(x => ids.Contains(x.ApplicationId) && x.Category == DetailCategory.InvoiceIssuance) .Select(x => new ApplicationDetail { ApplicationId = settlement.Id, RefId = x.ApplicationId, DetailId = x.Id, RecordId = x.RecordId, Category = DetailCategory.InvoiceSettlement, CustomerName = x.CustomerName ?? settlement.CustomerName, FeeId = x.FeeId, FeeName = x.FeeName, FeeType = x.FeeType, ApplyAmount = x.ApplyAmount - x.ProcessedAmount, Currency = x.Currency, OriginalAmount = x.OriginalAmount - x.OriginalProcessedAmount, OriginalCurrency = x.OriginalCurrency, ExchangeRate = x.ExchangeRate }).ToListAsync(); if (settlement.Currency != FeeCurrency.RMB_CODE) //发票结算非人民币需做转换 { var details2 = details1.FindAll(x => x.OriginalCurrency != settlement.Currency); foreach (var detail in details2) { detail.Currency = settlement.Currency; var doc = request.Documents.Find(x => x.Id == detail.ApplicationId); if (doc == null) return DataResult.Failed("结算单据与费用明细不一致"); var exchange = doc.ExchangeRates?.Find(x => x.Currency == settlement.Currency); if (exchange == null) return DataResult.Failed($"使用发票做结算时,非 {FeeCurrency.RMB_CODE} 的费用必须指定汇率"); detail.ExchangeRate = exchange.ExchangeRate; detail.ApplyAmount = Math.Round(exchange.ExchangeRate.GetValueOrDefault() * detail.OriginalAmount, 2, MidpointRounding.AwayFromZero); } } } //执行结算费用分配 foreach (var doc in request.Documents) { var details2 = details1.Where(x => x.RefId == doc.Id).OrderBy(x => x.ApplyAmount).ToList(); var rmbDetails = details2.FindAll(x => x.Currency == FeeCurrency.RMB_CODE); var totalRMB = rmbDetails.Sum(x => x.ApplyAmount); doc.SettlementRMB ??= totalRMB; if (doc.SettlementRMB > totalRMB) return DataResult.Failed("人民币结算金额不能大于剩余人民币金额"); if (totalRMB < doc.SettlementRMB) { var rest = totalRMB - doc.SettlementRMB.GetValueOrDefault(); foreach (var detail in rmbDetails) { if (rest == 0) break; detail.ApplyAmount = detail.ApplyAmount - rest; rest = detail.ApplyAmount - rest; } } var usdDetails = details2.FindAll(x => x.Currency == FeeCurrency.USD_CODE); var totalUSD = usdDetails.Sum(x => x.ApplyAmount); doc.SettlementUSD ??= totalUSD; if (doc.SettlementUSD > totalUSD) return DataResult.Failed("美元结算金额不能大于剩余美元金额"); if (totalUSD < doc.SettlementUSD) { var rest = totalUSD - doc.SettlementUSD.GetValueOrDefault(); foreach (var detail in usdDetails) { if (rest == 0) break; detail.ApplyAmount = detail.ApplyAmount - rest; rest = detail.ApplyAmount - rest; } } var otherDetails = details2.FindAll(x => x.Currency != FeeCurrency.RMB_CODE && x.Currency != FeeCurrency.USD_CODE); var total = rmbDetails.Sum(x => x.ApplyAmount); doc.SettlementOther ??= total; if (doc.SettlementOther > total) return DataResult.Failed("其他结算金额不能大于剩余其他金额"); if (total < doc.SettlementOther) { var rest = total - doc.SettlementOther.GetValueOrDefault(); foreach (var detail in otherDetails) { if (rest == 0) break; detail.ApplyAmount = detail.ApplyAmount - rest; rest = detail.ApplyAmount - rest; } } } } if (details1?.Count > 0) settlement.Details.AddRange(details1); //金额禁止为0 if (settlement.Details.Exists(x => x.ApplyAmount == 0 || x.OriginalAmount == 0)) return DataResult.FailedWithDesc(nameof(MultiLanguageConst.AmountCannotBeZero)); if (settlement.Details.Exists(x => x.OriginalCurrency.IsNullOrEmpty())) return DataResult.FailedWithDesc(nameof(MultiLanguageConst.OriginalCurrencyCanNotNull)); settlement.Amount = settlement.Details.Sum(x => x.ApplyAmount); result = await PreSaveAsync(settlement); if (!result.Succeeded) return DataResult.Failed(result.Message, result.MultiCode); var details = settlement.Details.FindAll(x => x.Id == 0); await TenantDb.Ado.BeginTranAsync(); try { //关联导航属性插入 if (settlement.Id == 0) { //创建时需要生成申请单编号 var sequence = CommonService.Value. GetSequenceNext(); if (!sequence.Succeeded) { return DataResult.Failed(sequence.Message, MultiLanguageConst.SequenceSetNotExist); } settlement.ApplicationNO = "ST" + SnowFlakeSingle.Instance.NextId(); //申请编号 settlement.SettlementNO = sequence.Data; //结算单号 await TenantDb.InsertNav(settlement).Include(x => x.Details).ExecuteCommandAsync(); } else { if (details.Count > 0) await TenantDb.Insertable(details).ExecuteCommandAsync(); await TenantDb.Updateable(settlement).IgnoreColumns(x => new { x.ApplicationNO, x.SettlementNO, x.IsLocked, x.CreateBy, x.CreateTime, x.Deleted, x.DeleteBy, x.DeleteTime }).ExecuteCommandAsync(); } await OnSaveAsync(settlement); if (details.Count > 0) { //更新费用记录的已结算金额 var fees = details.Select(x => new FeeRecord { Id = x.RecordId, SettlementAmount = x.OriginalAmount }).ToList(); await TenantDb.Updateable(fees) .PublicSetColumns(x => x.SettlementAmount, "+") .UpdateColumns(x => new { x.SettlementAmount }) .ExecuteCommandAsync(); } await TenantDb.Ado.CommitTranAsync(); await PostSaveAsync(settlement); return DataResult.Success(settlement); } catch (Exception ex) { await TenantDb.Ado.RollbackTranAsync(); await ex.LogAsync(Db); return DataResult.FailedWithDesc(nameof(MultiLanguageConst.Operation_Failed)); } } /// /// 通过前端传递的仅包含businessid的ApplicationDetail 获取这些业务的所有指定结算对象的费用明细 /// /// 如果参数的明细当中包含Detailid 代表传递的是具体费用 /// /// /// private async Task< List> GetFeeDetailByBill(SettlementRequest request) { var result = new List(); var settlement = request.Settlement; var tempDetailList = request.Details.Select(x => new ApplicationDetail { RefId = x.ApplicationId, DetailId = x.Id, RecordId = x.RecordId, CustomerName = x.CustomerName ?? settlement.CustomerName, FeeId = x.FeeId, FeeName = x.FeeName, FeeType = x.FeeType, ApplyAmount = x.RestAmount.GetValueOrDefault(), Currency = x.Currency, ExchangeRate = x.ExchangeRate, OriginalAmount = x.OriginalAmount, OriginalCurrency = x.OriginalCurrency ?? (settlement.Currency.IsNullOrEmpty() ? x.Currency : settlement.Currency), }).ToList(); foreach (var tempDetail in tempDetailList) { if (tempDetail.RecordId != null) { result.Add(tempDetail); } else { //20240929 该业务的所有符合条件的费用 var feeClient = new FeeClient(); var _detail = request.Details.First(x => x.BusinessId == tempDetail.BusinessId); feeClient.Id = _detail.BusinessId; feeClient.BusinessType = _detail.BusinessType; feeClient.CustomerId = request.Settlement.CustomerId; var paramarray = new FeeClient[] { feeClient }; var feeListResult = await GetFeesAsync(paramarray); var feeList = feeListResult.Data.Items; var _detailList = feeList.Where(x => x.FeeType == tempDetail.FeeType && x.RestAmount > 0) .Select(x => new ApplicationDetail { RefId = x.BusinessId, DetailId = 0, RecordId = x.RecordId, CustomerName = x.CustomerName ?? settlement.CustomerName, FeeId = x.FeeId, FeeName = x.FeeName, FeeType = x.FeeType, ApplyAmount = 0, Currency = x.Currency, ExchangeRate = tempDetail.ExchangeRate, OriginalAmount = 0, OriginalCurrency = x.Currency, }) .ToList(); result.AddRange(_detailList); } } return result; } /// /// 用于结算单的状态检查 /// /// 提交的结算单 /// 数据库值,新增时为null /// protected virtual DataResult EnsureSettlement(TEntity settlement, TEntity? dbValue) { return DataResult.Success; } /// /// 在保存前调用 /// /// 结算单 /// protected virtual Task PreSaveAsync(TEntity settlement) { return Task.FromResult(DataResult.Success); } /// /// 在保存时调用 /// /// 已保存的结算单 /// protected virtual Task OnSaveAsync(TEntity settlement) { return Task.CompletedTask; } /// /// 在保存完成后调用 /// /// 申请单 protected virtual async Task PostSaveAsync(TEntity settlement) { if (settlement.Details?.Count > 0) { //更新费用记录状态 var ids = settlement.Details.Where(x => x.RecordId > 0).Select(x => x.RecordId); if (!ids.Any()) return settlement; UpdateFeeStatus(ids); } return settlement; } #endregion #region 删除 /// /// 删除结算单明细 /// /// 结算单明细ID /// public async Task DeleteDetailAsync(params long[] ids) { var details = await TenantDb.Queryable().Where(x => ids.Contains(x.Id)).Select( x => new ApplicationDetail { Id = x.Id, ApplicationId = x.ApplicationId, RecordId = x.RecordId, DetailId = x.DetailId, ApplyAmount = x.ApplyAmount, OriginalAmount = x.OriginalAmount, ProcessedAmount = x.ProcessedAmount, OriginalProcessedAmount = x.OriginalProcessedAmount }).ToListAsync(); if (details.Count == 0) return DataResult.FailedWithDesc(nameof(MultiLanguageConst.EmptyData)); var appIds = details.Select(x => x.ApplicationId).Distinct().ToList(); var apps = await TenantDb.Queryable().Where(x => appIds.Contains(x.Id)).Select(x => new TEntity { Id = x.Id, Mode = x.Mode }).ToListAsync(); foreach (var app in apps) { app.Details = details.FindAll(x => x.ApplicationId == app.Id); app.Amount = app.Details.Sum(x => x.ApplyAmount); } var result = PreDelete(apps); if (!result.Succeeded) return result; await TenantDb.Ado.BeginTranAsync(); try { await OnDeleteDetailAsync(apps); await TenantDb.Deleteable(details).ExecuteCommandAsync(); //更新结算单的结算总金额 await TenantDb.Updateable(apps) .PublicSetColumns(it => it.Amount, "-").UpdateColumns(x => new { x.Amount }) .ExecuteCommandAsync(); await TenantDb.Ado.CommitTranAsync(); //更新费用结算状态 UpdateFeeStatus(details.Select(x => x.RecordId)); return DataResult.Success; } catch (Exception ex) { await TenantDb.Ado.RollbackTranAsync(); await ex.LogAsync(Db); return DataResult.FailedWithDesc(nameof(MultiLanguageConst.Operation_Failed)); } } /// /// 删除结算单 /// /// 结算单ID /// public async Task DeleteAsync(params long[] ids) { var apps = await TenantDb.Queryable().Where(x => ids.Contains(x.Id)).Select(x => new TEntity { Id = x.Id, Mode = x.Mode }).ToListAsync(); if (apps.Count == 0) return DataResult.FailedWithDesc(nameof(MultiLanguageConst.EmptyData)); var details = await TenantDb.Queryable().Where(x => ids.Contains(x.ApplicationId)).Select( x => new ApplicationDetail { Id = x.Id, ApplicationId = x.ApplicationId, RecordId = x.RecordId, DetailId = x.DetailId, ApplyAmount = x.ApplyAmount, OriginalAmount = x.OriginalAmount }).ToListAsync(); foreach (var app in apps) app.Details = details.FindAll(x => x.ApplicationId == app.Id); await TenantDb.Ado.BeginTranAsync(); var result = PreDelete(apps); if (!result.Succeeded) return result; try { await OnDeleteDetailAsync(apps); await TenantDb.DeleteNav(x => ids.Contains(x.Id)).Include(x => x.Details).ExecuteCommandAsync(); await TenantDb.Ado.CommitTranAsync(); //更新费用结算状态 UpdateFeeStatus(details.Select(x => x.RecordId)); return DataResult.Success; } catch (Exception ex) { await TenantDb.Ado.RollbackTranAsync(); await ex.LogAsync(Db); return DataResult.FailedWithDesc(nameof(MultiLanguageConst.Operation_Failed)); } } /// /// 在删除结算单或其明细之前调用,用于检查状态 /// /// 结算单 /// protected virtual DataResult PreDelete(List settlements) { return DataResult.Success; } /// /// 在执行删除结算单或其明细时调用 /// /// 结算单及其明细 /// protected virtual async Task OnDeleteDetailAsync(List settlements) { //还原费用表的已结算金额 var fees = settlements.SelectMany(x => x.Details).Select(x => new FeeRecord { Id = x.RecordId, SettlementAmount = x.OriginalAmount }).ToList(); await TenantDb.Updateable(fees) .PublicSetColumns(it => it.SettlementAmount, "-").UpdateColumns(x => new { x.SettlementAmount }) .ExecuteCommandAsync(); } #endregion /// /// 设置结算单的锁定状态 /// /// 是否锁定 /// 结算ID /// public async Task SetLockAsync(bool isLocked, params long[] ids) { var dtNow = DateTime.Now; var userId = long.Parse(User.UserId); List list = null; if (isLocked) { list = ids.Select(x => new TEntity { Id = x, IsLocked = isLocked, LockTime = dtNow, LockUserId = userId }).ToList(); } else { list = ids.Select(x => new TEntity { Id = x, IsLocked = isLocked, UnlockTime = dtNow, UnlockUserId = userId }).ToList(); } return await TenantDb.Updateable(list).UpdateColumns(x => new { x.IsLocked, x.LockTime, x.UnlockTime, x.LockUserId, x.UnlockUserId }).ExecuteCommandAsync() > 0 ? DataResult.Success : DataResult.FailedWithDesc(nameof(MultiLanguageConst.Operation_Failed)); } protected virtual Task> GetSettlementDetails(long id) { return Task.FromResult(new List()); } /// /// 获取待结算费用明细的原始币别 /// /// /// public async Task>> GetExchangesAsync(SettlementRequest request) { var details = await GetFeeDetailByBill(request); var result = new List(); foreach (var item in request.Details) { var document = item.Adapt(); document.Id = item.BusinessId; if (details.Exists(x => x.BusinessId == document.Id)) { foreach (var detail in details.Where(x => x.BusinessId == document.Id)) { if (!document.ExchangeRates.Exists(x => x.Currency == item.OriginalCurrency)) document.ExchangeRates.Add(new CurrencyExchangeRate { Currency = item.OriginalCurrency }); } } result.Add(document); } return DataResult>.Success(result); } } }