using DS.Module.Core; using DS.Module.Core.Enums; using DS.Module.Core.Extensions; using DS.WMS.Core.Application.Dtos; using DS.WMS.Core.Application.Entity; using DS.WMS.Core.Application.Interface; using DS.WMS.Core.Fee.Entity; using DS.WMS.Core.Fee.Method; using DS.WMS.Core.Flow.Dtos; using DS.WMS.Core.Flow.Entity; using DS.WMS.Core.Flow.Interface; using DS.WMS.Core.Invoice.Dtos; using DS.WMS.Core.Sys.Interface; using DS.WMS.Core.TaskInteraction.Dtos; using DS.WMS.Core.TaskInteraction.Interface; using Masuit.Tools.Systems; using Microsoft.Extensions.DependencyInjection; using SqlSugar; namespace DS.WMS.Core.Application.Method { /// /// 申请单基础实现 /// /// 实体的类型声明 public abstract class ApplicationService : FeeServiceBase, IApplicationService where TEntity : ApplicationForm, new() { internal static readonly DetailCategory[] detailCategories = [ DetailCategory.InvoiceSettlement, DetailCategory.InvoiceIssuance, DetailCategory.ChargeApplicationSettlement,DetailCategory.PaidApplicationSettlement]; /// /// 适用于当前申请单的审核类型 /// public abstract TaskBaseTypeEnum AuditType { get; } readonly IClientFlowInstanceService flowService; readonly Lazy commonService; readonly IAuditTaskService taskService; /// /// 初始化 /// /// DI容器 public ApplicationService(IServiceProvider serviceProvider) : base(serviceProvider) { flowService = serviceProvider.GetRequiredService(); commonService = new Lazy(serviceProvider.GetRequiredService()); taskService = serviceProvider.GetRequiredService(); } /// /// 根据业务编号及类型获取该票业务的币别 /// /// 业务ID与业务类型 /// public async Task>> GetCurrenciesAsync(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) .Select(f => new { f.BusinessId, f.BusinessType, f.CustomerId, f.Currency }).ToListAsync(); var currencies = list.GroupBy(x => new { x.BusinessId, x.BusinessType, x.CustomerId }).Select(x => new FeeClient { Id = x.Key.BusinessId, BusinessType = x.Key.BusinessType, CustomerId = x.Key.CustomerId, ExchangeRates = x.GroupBy(y => y.Currency).Select(y => new CurrencyExchangeRate { Currency = y.Key }).ToList() }).ToList(); return DataResult>.Success(currencies); } #region 保存 /// /// 提交保存费用申请单 /// /// 申请单 /// public async Task> SaveAsync(TEntity application) { TEntity? dbValue = null; if (application.Id > 0) { application.BuildOption = BuildOption.Update; //修改需检查申请单状态 dbValue = await TenantDb.Queryable().Where(x => x.Id == application.Id).Select( x => new TEntity { Status = x.Status, Currency = x.Currency }).FirstAsync(); if (dbValue == null) return DataResult.FailedWithDesc(nameof(MultiLanguageConst.EmptyData)); } var result = EnsureApplication(application, dbValue); if (!result.Succeeded) return DataResult.Failed(result.Message, result.MultiCode); List fees = []; if (application.Details.Count > 0) { application.Details = application.Details.FindAll(x => x.Id == 0); if (application.Details.GroupBy(x => x.CustomerName).Select(x => x.Key).Count() > 1) return DataResult.FailedWithDesc(nameof(MultiLanguageConst.DetailCustomerOnlyOne)); if (application.Details.GroupBy(x => x.RecordId).Where(g => g.Count() > 1).Select(x => x.Key).Count() > 0) return DataResult.FailedWithDesc(nameof(MultiLanguageConst.ApplicationRecordOnlyOne)); //申请金额禁止为0 if (application.Details.Any(x => x.ApplyAmount == 0)) return DataResult.FailedWithDesc(nameof(MultiLanguageConst.AmountCannotBeZero)); var ids = application.Details.Select(x => x.RecordId).Distinct().ToList(); fees = await TenantDb.Queryable().Where(x => ids.Contains(x.Id)).Select(x => new FeeRecord { Id = x.Id, BusinessId = x.BusinessId, BusinessType = x.BusinessType, FeeId = x.FeeId, FeeName = x.FeeName, FeeType = x.FeeType, CustomerId = x.CustomerId, CustomerName = x.CustomerName, Amount = x.Amount, Currency = x.Currency, ExchangeRate = x.ExchangeRate, OrderAmount = x.OrderAmount, OrderSettlementAmount = x.OrderSettlementAmount, SettlementAmount = x.SettlementAmount }).ToListAsync(); if (application.Id == 0) { //填充申请单信息 var fee = fees[0]; application.CustomerId = fee.CustomerId; application.CustomerName = fee.CustomerName; } foreach (var detail in application.Details) { detail.ApplicationId = application.Id; var fee = fees.Find(x => x.Id == detail.RecordId); detail.ExchangeRate = detail.ExchangeRate ?? fee.ExchangeRate; detail.FeeId = fee.FeeId; detail.FeeName = fee.FeeName; detail.FeeType = fee.FeeType; detail.CustomerName = detail.CustomerName ?? application.CustomerName; //原币申请 if (application.Currency.IsNullOrEmpty()) { detail.OriginalAmount = detail.ApplyAmount; if (detail.OriginalCurrency.IsNullOrEmpty()) detail.OriginalCurrency = fee.Currency; } } result = CalculateAmount(application, fees); if (!result.Succeeded) return DataResult.Failed(result.Message, result.MultiCode); } await TenantDb.Ado.BeginTranAsync(); try { await PreSaveAsync(application); //关联导航属性插入 if (application.Id == 0) { //创建时需要生成申请单编号 var sequence = commonService.Value.GetSequenceNext(); if (!sequence.Succeeded) { return DataResult.Failed(sequence.Message, MultiLanguageConst.SequenceSetNotExist); } application.ApplicationNO = sequence.Data; await TenantDb.InsertNav(application).Include(x => x.Details).ExecuteCommandAsync(); } else { if (application.Details.Count > 0) await TenantDb.Insertable(application.Details).ExecuteCommandAsync(); } await OnSaveAsync(application, fees); await TenantDb.Ado.CommitTranAsync(); var postResult = await PostSaveAsync(application); return postResult; } catch (Exception ex) { await TenantDb.Ado.RollbackTranAsync(); await ex.LogAsync(Db); return DataResult.FailedWithDesc(nameof(MultiLanguageConst.Operation_Failed)); } } /// /// 提交保存费用申请单 /// /// /// public async Task> SaveAsync(ApplicationRequest request) { request.Application ??= new(); request.Application.Details = await GetDetailsAsync(request); if (!string.IsNullOrEmpty(request.Application.Currency)) //非原币申请 { var details = request.Application.Details.FindAll(x => x.Currency != request.Application.Currency); foreach (var detail in details) { var fc = request.Items.Find(x => x.Id == detail.BusinessId && x.BusinessType == x.BusinessType); var exchange = fc?.ExchangeRates?.Find(x => x.Currency == detail.Currency); if (exchange == null) return DataResult.Failed($"非原币申请必须传入费用原币与申请币别 {detail.Currency} 之间的汇率信息"); detail.ExchangeRate = exchange.ExchangeRate; detail.ApplyAmount = Math.Round(detail.OriginalAmount * (exchange.ExchangeRate == null ? 1 : exchange.ExchangeRate.Value), 2, MidpointRounding.AwayFromZero); detail.Currency = request.Application.Currency; } } return await SaveAsync(request.Application); } /// /// 用于申请单的状态检查 /// /// 提交的申请单 /// 数据库值,新增时为null /// protected virtual DataResult EnsureApplication(TEntity application, TEntity? dbValue) { return DataResult.Success; } /// /// 根据申请单明细,检查费用记录的剩余额度 /// /// 申请单 /// 费用记录 /// protected virtual DataResult CalculateAmount(TEntity application, List fees) { return DataResult.Success; } /// /// 在保存前调用 /// /// 申请单 /// protected virtual Task PreSaveAsync(TEntity application) { return Task.CompletedTask; } /// /// 在提交事务前,保存时调用 /// /// 已保存的申请单 /// 需要更新信息的费用记录 /// protected virtual async Task OnSaveAsync(TEntity application, List? fees) { await TenantDb.Updateable(application).IgnoreColumns(x => new { x.ApplicationNO, x.Status, x.CreateBy, x.CreateTime, x.Deleted, x.DeleteBy, x.DeleteTime }).ExecuteCommandAsync(); } /// /// 在保存完成后调用 /// /// 申请单 protected virtual Task> PostSaveAsync(TEntity application) { return Task.FromResult(DataResult.Success(application)); } /// /// 获取业务所关联的申请明细 /// /// /// protected abstract Task> GetDetailsAsync(ApplicationRequest request); #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, OriginalAmount = x.OriginalAmount }).ToListAsync(); 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, Status = x.Status }).ToListAsync(); foreach (var app in apps) app.Details = details.FindAll(x => x.ApplicationId == app.Id); var result = PreDelete(apps); if (!result.Succeeded) return result; await TenantDb.Ado.BeginTranAsync(); try { await OnDeleteDetailAsync(apps, DeleteOption.DetailOnly); await TenantDb.Deleteable(details).ExecuteCommandAsync(); await TenantDb.Ado.CommitTranAsync(); 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 applications) { var ids = applications.Select(x => x.Id); bool exists = TenantDb.Queryable().Where(x => ids.Contains(x.ApplicationId)) .InnerJoin((d1, d2) => d1.Id == d2.DetailId) .Where((d1, d2) => d1.DetailId.HasValue && detailCategories.Contains(d2.Category.Value)) .Any(); if (exists) return DataResult.FailedWithDesc(nameof(MultiLanguageConst.ApplicationIsUsed)); return DataResult.Success; } /// /// 在执行删除申请单或其明细时调用 /// /// 申请单及其明细 /// 删除选项 /// protected virtual Task OnDeleteDetailAsync(List applications, DeleteOption deleteOption) { return Task.CompletedTask; } /// /// 删除申请单 /// /// 申请单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, Status = x.Status }).ToListAsync(); var details = await TenantDb.Queryable().Where(x => ids.Contains(x.ApplicationId)).Select( x => new ApplicationDetail { Id = x.Id, ApplicationId = x.ApplicationId, RecordId = x.RecordId, 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, DeleteOption.Entire); await TenantDb.DeleteNav(x => ids.Contains(x.Id)).Include(x => x.Details).ExecuteCommandAsync(); await TenantDb.Ado.CommitTranAsync(); return DataResult.Success; } catch (Exception ex) { await TenantDb.Ado.RollbackTranAsync(); await ex.LogAsync(Db); return DataResult.FailedWithDesc(nameof(MultiLanguageConst.Operation_Failed)); } } #endregion #region 审批 /// /// 提交审批 /// /// 审批类型 /// 审批备注 /// 申请单ID /// public async Task SubmitApprovalAsync(TaskBaseTypeEnum auditType, string remark, params long[] idArray) { var list = await TenantDb.Queryable().LeftJoin((x, y) => x.Id == y.ApplicationId) .GroupBy((x, y) => x.Id) .Where((x, y) => idArray.Contains(x.Id)) .Select((x, y) => new TEntity { Id = x.Id, ApplicationNO = x.ApplicationNO, Status = x.Status, DetailCount = SqlFunc.AggregateCount(y.Id) }).ToListAsync(); if (list.Count == 0) return DataResult.FailedWithDesc(nameof(MultiLanguageConst.EmptyData)); if (list.Exists(x => x.DetailCount == 0)) return DataResult.FailedWithDesc(nameof(MultiLanguageConst.ApplicationMustHaveDetail)); var result = PreSubmitApproval(list); if (!result.Succeeded) return result; List entities = new(idArray.Length); await TenantDb.Ado.BeginTranAsync(); try { if (await taskService.HasAuthorizedAsync()) { for (int i = 0; i < list.Count; i++) { var item = list[i]; var req = new TaskCreationRequest { BusinessId = item.Id, TaskTypeName = auditType.ToString(), TaskTitle = $"【{auditType.GetDescription()}】{item.ApplicationNO}" }; result = await taskService.CreateTaskAsync(req); if (!result.Succeeded) return result; var entity = new TEntity { Id = req.BusinessId }; OnSubmitApproval(entity); entities.Add(entity); } } else { var template = await FindTemplateAsync(auditType); if (template == null) return DataResult.FailedWithDesc(nameof(MultiLanguageConst.TemplateNotFound)); var list2 = list.Select(x => new TEntity { Id = x.Id, Status = x.Status }).ToList(); foreach (var item in list2) { result = flowService.CreateFlowInstance(new CreateFlowInstanceReq { BusinessId = item.Id, TemplateId = template.Id }); if (result.Succeeded) { var instance = result.Data as FlowInstance; flowService.StartFlowInstance(instance.Id.ToString()); OnSubmitApproval(item); entities.Add(item); } } } await TenantDb.Updateable(entities).UpdateColumns(x => new { x.Status }).ExecuteCommandAsync(); await TenantDb.Ado.CommitTranAsync(); return DataResult.Success; } catch (Exception ex) { await TenantDb.Ado.RollbackTranAsync(); await ex.LogAsync(Db); return DataResult.FailedWithDesc(nameof(MultiLanguageConst.Operation_Failed)); } } /// /// 在提交审批前调用,用于检查申请单状态 /// /// 申请单 /// protected virtual DataResult PreSubmitApproval(List applications) { return DataResult.Success; } /// /// 在提交审批时调用,用于更新申请单信息 /// /// 申请单 /// protected virtual void OnSubmitApproval(TEntity application) { } /// /// 撤销审批 /// /// 申请单ID /// public async Task WithdrawAsync(params long[] ids) { var list = await TenantDb.Queryable().Where(x => ids.Contains(x.Id)).Select( x => new TEntity { Id = x.Id, ApplicationNO = x.ApplicationNO, Status = x.Status }).ToListAsync(); if (list.Count == 0) return DataResult.FailedWithDesc(nameof(MultiLanguageConst.EmptyData)); //未在审批状态中 if (!await flowService.Exists(ids: ids)) return DataResult.FailedWithDesc(nameof(MultiLanguageConst.NotInAudit)); DataResult result; bool hasAuthorized = await taskService.HasAuthorizedAsync(); try { if (hasAuthorized) { foreach (var item in list) { result = await taskService.WithdrawAsync(new TaskRequest { BusinessId = item.Id, TaskTypeName = AuditType.ToString() }, false); OnWithdraw(item); } } else { result = await flowService.WithdrawAsync(ids); if (!result.Succeeded) return result; foreach (var item in list) { OnWithdraw(item); } } await TenantDb.Updateable(list).UpdateColumns(x => new { x.Status }).ExecuteCommandAsync(); await TenantDb.Ado.CommitTranAsync(); return DataResult.Success; } catch (Exception ex) { await TenantDb.Ado.RollbackTranAsync(); await ex.LogAsync(Db); return DataResult.FailedWithDesc(nameof(MultiLanguageConst.Operation_Failed)); } } /// /// 在撤销审批时调用,用于更新申请单信息 /// /// 申请单 /// protected virtual void OnWithdraw(TEntity application) { } #endregion } }