using System.Linq.Expressions; using DS.Module.Core; using DS.Module.Core.Extensions; using DS.WMS.Core.Application.Entity; using DS.WMS.Core.Fee.Entity; using DS.WMS.Core.Fee.Method; using DS.WMS.Core.Op.Entity; 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 Microsoft.Extensions.DependencyInjection; using SqlSugar; namespace DS.WMS.Core.Settlement.Method { /// /// 结算基础实现 /// /// 实体的类型声明 public class SettlementService : FeeServiceBase, ISettlementService where TEntity : SettlementBase, new() { protected readonly Lazy SeqService; /// /// 初始化 /// /// DI容器 public SettlementService(IServiceProvider serviceProvider) : base(serviceProvider) { SeqService = new Lazy(serviceProvider.GetRequiredService()); } #region 保存 internal static DataResult AssignAmount(List details, decimal stlAmount, string currency) { if (details.Count == 0) return DataResult.Success; if (stlAmount == 0) return DataResult.Failed("结算金额不能为零"); var totalAmount = details.Sum(x => x.ApplyAmount); if (Math.Abs(stlAmount) > totalAmount) return DataResult.Failed("申请结算金额不能大于剩余结算金额"); decimal rest = stlAmount; decimal currentAmount = default; foreach (var detail in details) { if (rest <= 0) break; if (totalAmount != stlAmount) //非全额结算 { if (rest >= detail.ApplyAmount) { rest -= detail.ApplyAmount; } else { detail.ApplyAmount = rest; rest = 0; } } currentAmount = detail.ApplyAmount; if (detail.Currency == currency) { if (detail.Currency == detail.OriginalCurrency) { detail.OriginalAmount = detail.ApplyAmount; } else { detail.OriginalAmount = detail.ApplyAmount / detail.ExchangeRate.Value; } detail.ExchangeRate = 1; } else //结算币别与费用币别不一致 { detail.ExchangeRate ??= 1; detail.ApplyAmount *= detail.ExchangeRate.Value; if (detail.OriginalCurrency == detail.Currency) { detail.OriginalAmount = currentAmount; } else { detail.OriginalAmount = currentAmount * detail.ExchangeRate.Value; } detail.Currency = currency; } detail.Assigned = true; } return DataResult.Success; } /// /// 提交结算单 /// /// /// public virtual async Task> SaveAsync(SettlementRequest request) { var settlement = request.Settlement; if (settlement.SettlementDate == default) settlement.SettlementDate = DateTime.Now; //金额禁止为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)); var 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 = SeqService.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(); } //计算结算总金额 settlement.Amount = await TenantDb.Queryable().Where(x => x.ApplicationId == settlement.Id).SumAsync(x => x.ApplyAmount); await TenantDb.Updateable(settlement).UpdateColumns(x => x.Amount).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)); } } /// /// 用于结算单的状态检查 /// /// 提交的结算单 /// 数据库值,新增时为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, Currency = x.Currency, OriginalCurrency = x.OriginalCurrency }).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, Currency = x.Currency, OriginalCurrency = x.OriginalCurrency }).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 /// 此方法内部将始终异步执行,请确保在调用前已提交数据库事务等必要的操作。 protected internal void UpdateFeeStatus(IEnumerable ids) { var task1 = Task.Factory.StartNew(UpdateFeeStatusCore, ids, CancellationToken.None); task1.ContinueWith(t => UpdateBizStatusCore(t.Result), TaskContinuationOptions.OnlyOnRanToCompletion); } private List UpdateFeeStatusCore(object? state) { if (state == null) return []; var ids = (IEnumerable)state; var fees = TenantDb.Queryable().Where(x => ids.Contains(x.Id)) .Select(x => new FeeRecord { Id = x.Id, BusinessId = x.BusinessId, BusinessType = x.BusinessType, FeeStatus = x.FeeStatus, Amount = x.Amount, SettlementAmount = x.SettlementAmount }).ToList(); if (fees.Count == 0) return []; List list = new(fees.Count); foreach (var item in fees) { if (item.SettlementAmount == item.Amount) { item.FeeStatus = FeeStatus.SettlementCompleted; list.Add(item); } else if (item.SettlementAmount == 0) { item.FeeStatus = FeeStatus.AuditPassed; list.Add(item); } else if (item.Amount != item.SettlementAmount) { item.FeeStatus = FeeStatus.PartialSettlement; list.Add(item); } } TenantDb.Updateable(list).UpdateColumns(x => new { x.FeeStatus }).ExecuteCommand(); return list; } private void UpdateBizStatusCore(List list) { var bizIds = list.Select(x => x.BusinessId); var types = list.Select(x => x.BusinessType).Distinct(); var fees2 = TenantDb.Queryable().Where(x => bizIds.Contains(x.BusinessId) && types.Contains(x.BusinessType)) .Select(x => new { x.BusinessId, x.BusinessType, x.FeeType, x.FeeStatus }).ToList(); if (fees2.Count == 0) return; var gpList = fees2.GroupBy(x => new { x.BusinessId, x.BusinessType }); foreach (var gp in gpList) { BusinessFeeStatus biz = new() { BusinessId = gp.Key.BusinessId, BusinessType = gp.Key.BusinessType }; var upt = TenantDb.Updateable(biz).WhereColumns(x => new { x.BusinessId, x.BusinessType }); //应收 var arList = gp.Where(x => x.FeeType == FeeType.Receivable).ToList(); if (arList.Count > 0) { if (arList.All(x => x.FeeStatus == FeeStatus.SettlementCompleted)) { biz.ARFeeStatus = BillFeeStatus.SettlementCompleted; upt = upt.UpdateColumns(x => x.ARFeeStatus); } else if (arList.Any(x => x.FeeStatus == FeeStatus.PartialSettlement)) { biz.ARFeeStatus = BillFeeStatus.PartialSettlement; upt = upt.UpdateColumns(x => x.ARFeeStatus); } } //应付 var apList = gp.Where(x => x.FeeType == FeeType.Payable).ToList(); if (apList.Count > 0) { if (apList.All(x => x.FeeStatus == FeeStatus.SettlementCompleted)) { biz.APFeeStatus = BillFeeStatus.SettlementCompleted; upt = upt.UpdateColumns(x => x.APFeeStatus); } else if (apList.Any(x => x.FeeStatus == FeeStatus.PartialSettlement)) { biz.APFeeStatus = BillFeeStatus.PartialSettlement; upt = upt.UpdateColumns(x => x.APFeeStatus); } } upt.ExecuteCommand(); } } /// /// 设置结算单的锁定状态 /// /// 是否锁定 /// 结算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)); } /// /// 返回针对结算费用明细及其关联业务的查询对象 /// /// 关联条件1 /// 查询对象 protected virtual ISugarQueryable CreateApplicationDetailQuery( Expression>? expr1) { //海运出口 var query1 = TenantDb.Queryable() .InnerJoin((d, f) => d.RecordId == f.Id) .LeftJoin((d, f, s) => f.BusinessId == s.Id && f.BusinessType == BusinessType.OceanShippingExport) .Where(expr1) .Select((d, f, s) => new SettlementDetailDto { //---------------明细表-------------- Id = d.Id, ApplicationId = d.ApplicationId, DetailId = d.DetailId, RefId = d.RefId, RecordId = d.RecordId, FeeType = d.FeeType, FeeId = d.FeeId, FeeName = d.FeeName, Currency = d.Currency, ApplyAmount = d.ApplyAmount, ExchangeRate = d.ExchangeRate, OriginalAmount = d.OriginalAmount, OriginalCurrency = d.OriginalCurrency, ProcessedAmount = d.ProcessedAmount, OriginalProcessedAmount = d.OriginalProcessedAmount, RestAmount = d.ProcessedAmount - d.OriginalProcessedAmount, //---------------费用表-------------- OriginalRate = f.ExchangeRate, Amount = f.Amount, AccTaxRate = f.AccTaxRate, CustomerId = f.CustomerId,//费用对象ID CustomerName = f.CustomerName, OrderAmount = f.OrderAmount, InvoiceAmount = f.InvoiceAmount, SettlementAmount = f.SettlementAmount, OrderSettlementAmount = f.OrderSettlementAmount, OrderInvSettlementAmount = f.OrderInvSettlementAmount, BusinessId = f.BusinessId, BusinessType = f.BusinessType, Note = f.Note, //---------------业务表-------------- AccountDate = s.AccountDate, CntrTotal = s.CntrTotal, CustomerNo = s.CustomerNo, ClientName = s.CustomerName, //委托单位 ETD = s.ETD, ETA = s.ETA, Forwarder = s.Forwarder, Operator = s.OperatorName, MBLNO = s.MBLNO, HBLNO = s.HBLNO, LoadPort = s.LoadPort, SaleDeptId = s.SaleDeptId, SaleName = s.Sale,//揽货人 Carrier = s.Carrier, Vessel = s.Vessel,//船名 Voyage = s.Voyno,//航次 BookingNo = s.BookingNo, SourceName = s.SourceName, Enterprise = s.Enterprise, CustomNo = s.CustomNo }); //海运进口 return TenantDb.UnionAll(new List> { query1 }); } protected virtual Task> GetSettlementDetails(long id) { return Task.FromResult(new List()); } } }