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.Method; using DS.WMS.Core.Code.Entity; using DS.WMS.Core.Fee.Entity; using DS.WMS.Core.Info.Entity; using DS.WMS.Core.Invoice.Dtos; using DS.WMS.Core.Invoice.Interface; using DS.WMS.Core.Settlement.Entity; using DS.WMS.Core.Sys.Entity; using DS.WMS.Core.Sys.Interface; using Mapster; using Microsoft.Extensions.DependencyInjection; using SqlSugar; namespace DS.WMS.Core.Invoice.Method { /// /// 发票服务基类 /// /// public class InvoiceService : ApplicationServiceBase, IInvoiceService where TEntity : Entity.Invoice, new() { readonly Lazy CommonService; /// /// 初始化 /// /// public InvoiceService(IServiceProvider provider) : base(provider) { CommonService = new Lazy(provider.GetRequiredService()); } /// /// 获取分页列表 /// /// /// public async Task> GetListAsync(PageRequest request) { var query = TenantDb.Queryable() .Select((i) => new InvoiceDto { Details = SqlFunc.Subqueryable().Where(d => d.ApplicationId == i.Id) .ToList(d => new ApplicationDetailDto { Currency = d.Currency, ProcessedAmount = d.ProcessedAmount, ExchangeRate = d.ExchangeRate, OriginalCurrency = d.OriginalCurrency, OriginalAmount = d.OriginalAmount, OriginalProcessedAmount = d.OriginalProcessedAmount }), InvoiceApplicationList = SqlFunc.Subqueryable().LeftJoin((d, a) => d.ApplicationId == i.Id && d.Category == DetailCategory.InvoiceIssuance && d.RefId == a.Id) .WhereIF(!string.IsNullOrEmpty(request.OtherQueryCondition), (d, a) => a.ApplicationNO.Contains(request.OtherQueryCondition)) .GroupBy((d, a) => a.ApplicationNO).ToList((d, a) => a.ApplicationNO) }, true).MergeTable(); if (!string.IsNullOrEmpty(request.OtherQueryCondition)) query = query.Where(i => i.InvoiceNO.Contains(request.OtherQueryCondition) || i.BillNO.Contains(request.OtherQueryCondition)); var whereList = request.GetConditionalModels(Db); var result = await query.Where(whereList).ToQueryPageAsync(request.PageCondition); if (result.Data?.Count > 0) { var userIds = result.Data.Select(x => x.CreateBy) .Union(result.Data.Where(x => x.LockUserId.HasValue).Select(x => x.LockUserId.Value)) .Union(result.Data.Where(x => x.OperatorId.HasValue).Select(x => x.OperatorId.Value)) .Distinct(); var users = await Db.Queryable().Where(x => userIds.Contains(x.Id)).Select(x => new { x.Id, x.UserName }).ToListAsync(); var orgIds = result.Data.Where(x => x.SaleDeptId.HasValue).Select(x => x.SaleDeptId.Value) .Distinct(); var orgs = await Db.Queryable().Where(x => orgIds.Contains(x.Id)).Select(x => new { x.Id, x.OrgName }).ToListAsync(); foreach (var item in result.Data) { item.CreateByName = users.Find(x => x.Id == item.CreateBy)?.UserName; item.LockUserName = users.Find(x => x.Id == item.LockUserId)?.UserName; item.OperatorName = users.Find(x => x.Id == item.OperatorId)?.UserName; item.SaleDeptName = orgs.Find(x => x.Id == item.SaleDeptId)?.OrgName; } } InvoiceList list = new() { List = result.Data }; var invResult = DataResult.Success(list, result.MultiCode); invResult.Count = result.Count; return invResult; } /// /// 获取发票详情 /// /// 发票ID /// public async Task> GetAsync(long id) { var invoice = await TenantDb.Queryable().Select().FirstAsync(x => x.Id == id); if (invoice != null) { if (!string.IsNullOrEmpty(invoice.PushMode)) { invoice.PushModeValues = invoice.PushMode.Split(',', StringSplitOptions.RemoveEmptyEntries) .Select(x => (PushMode)int.Parse(x)).ToArray(); } if (invoice.OperatorId.HasValue) { invoice.OperatorName = await Db.Queryable().Where(x => x.Id == invoice.OperatorId.Value) .Select(x => x.UserName).FirstAsync(); } invoice.Details = await CreateApplicationDetailQuery((d, f, s) => d.ApplicationId == id && d.Category == DetailCategory.InvoiceIssuance) .Select(x => new ApplicationDetailDto { Id = x.Id, ApplicationId = x.ApplicationId, RecordId = x.RecordId, DetailId = x.DetailId, RefId = x.RefId, FeeName = x.FeeName, FeeType = x.FeeType, ApplyAmount = x.ApplyAmount, ExchangeRate = x.ExchangeRate, Currency = x.Currency, OriginalAmount = x.OriginalAmount, OriginalCurrency = x.OriginalCurrency, OriginalRate = x.OriginalRate, CustomerNo = x.CustomerNo, MBLNO = x.MBLNO, ClientName = x.ClientName, ETD = x.ETD, SaleName = x.SaleName, SourceName = x.SourceName, LoadPort = x.LoadPort, Vessel = x.Vessel, Voyage = x.Voyage, }).ToListAsync(); invoice.Summary = invoice.Details.GroupBy(x => new { x.FeeType, x.Currency }).Select(x => new SummaryItem { FeeType = x.Key.FeeType, Currency = x.Key.Currency, Amount = x.Sum(y => y.ApplyAmount) }).ToList(); if (invoice.Mode == InvoiceMode.Applcation) { var ids = invoice.Details.Where(x => x.RefId.HasValue).Select(x => x.RefId).Distinct(); invoice.Applications = await TenantDb.Queryable().Where(x => ids.Contains(x.Id)) .LeftJoin((x, y) => x.CreateBy == y.Id, "shippingweb8_dev.sys_user") .Select((x, y) => new InvoiceApplicationDto { ApplicationNO = x.ApplicationNO, Status = x.Status, Currency = x.Currency, ApplyAmount = x.ApplyAmount, InvoiceRemark = x.InvoiceRemark, CreateBy = x.CreateBy, CreateByName = y.UserName }).ToListAsync(); } invoice.InvoiceDetails = await TenantDb.Queryable().Where( x => x.ApplicationId == id && x.Category == DetailCategory.InvoiceIssuance).ToListAsync(); } return DataResult.Success(invoice); } /// /// 根据业务编号及类型获取费用记录 /// /// 业务ID与业务类型 /// public virtual Task> GetFeesAsync(params FeeClient[] items) { return Task.FromResult(DataResult.Success( new InvoiceApplicaitonBiz(new List()))); } #pragma warning disable CS4014 /// /// 提交 /// /// 请求参数 /// public async Task> SaveAsync(InvoiceRequest request) { var invoice = request.Invoice; if (invoice.Currency.IsNullOrEmpty()) invoice.Currency = FeeCurrency.RMB_CODE; TEntity? dbValue = default; if (request.Invoice.Id > 0) { dbValue = await TenantDb.Queryable().Select(x => new TEntity { Id = x.Id, IsLocked = x.IsLocked, Mode = x.Mode, }).FirstAsync(x => x.Id == request.Invoice.Id); } var result = EnsureSettlement(request.Invoice, dbValue); if (!result.Succeeded) return DataResult.Failed(result.Message, result.MultiCode); if (invoice.InvoiceDate == default) invoice.InvoiceDate = DateTime.Now; invoice.OperatorId ??= long.Parse(User.UserId); if (invoice.PushModeValues.Length > 0) invoice.PushMode = string.Join(",", invoice.PushModeValues.Select(x => (int)x)); //按申请开票 if (request.Invoice.Mode == InvoiceMode.Applcation && request.Applications?.Count > 0) { var ids = request.Applications.Select(x => x.ApplicationId); var details = await TenantDb.Queryable() .InnerJoin((x, y) => x.ApplicationId == y.Id) .LeftJoin((x, y, z) => y.CustomerId == z.ClientId && z.Currency == invoice.Currency && z.IsInvoiceDefault == true) .Where((x, y, z) => ids.Contains(x.ApplicationId) && x.Category == DetailCategory.InvoiceApplication) .Select((x, y, z) => new { x.Id, x.ApplicationId, x.RecordId, x.CustomerName, x.FeeId, x.FeeType, x.FeeName, x.ApplyAmount, x.OriginalAmount, x.Currency, x.OriginalCurrency, x.ProcessedAmount, x.OriginalProcessedAmount, y.CustomerId, y.TaxRate, y.TaxID, y.InvoiceHeader, y.CustomerAddTel, y.SaleDeptId, y.PushMode, y.CellPhoneNO, y.Email, z.BankAccountNo, z.BankName }).ToListAsync(); //税率不一致 if (details.GroupBy(x => x.TaxRate).Select(x => x.Key).Count() > 1) return DataResult.FailedWithDesc(nameof(MultiLanguageConst.InconsistentTaxRates)); invoice.Details ??= new List(details.Count); foreach (var item in details) { if (invoice.Id == 0) { invoice.CustomerId = item.CustomerId; invoice.CustomerName = item.CustomerName; invoice.TaxRate = item.TaxRate; invoice.InvoiceHeader = item.InvoiceHeader; invoice.CustomerTaxID = item.TaxID; invoice.CustomerAddressTel = item.CustomerAddTel; invoice.CustomerAccount = item.BankAccountNo; invoice.CustomerBankName = item.BankName; invoice.SaleDeptId = item.SaleDeptId; invoice.PushMode = item.PushMode; invoice.CellPhoneNO = item.CellPhoneNO; invoice.Email = item.Email; } else if (invoice.CustomerId != item.CustomerId) //校验开票单位是否一致 { return DataResult.FailedWithDesc(nameof(MultiLanguageConst.InvoiceCustomerOnlyOne)); } else if (invoice.TaxRate != item.TaxRate) //校验税率是否一致 { return DataResult.FailedWithDesc(nameof(MultiLanguageConst.InconsistentTaxRates)); } //需转换为费用明细 var detail = new ApplicationDetail { ApplicationId = invoice.Id, RefId = item.ApplicationId, DetailId = item.Id, RecordId = item.RecordId, Category = DetailCategory.InvoiceIssuance, CustomerName = item.CustomerName, FeeId = item.FeeId, FeeName = item.FeeName, FeeType = item.FeeType, Currency = invoice.Currency, OriginalCurrency = item.Currency, ApplyAmount = item.ApplyAmount - item.ProcessedAmount, OriginalAmount = item.OriginalAmount - item.OriginalProcessedAmount }; var app = request.Applications.Find(x => x.ApplicationId == item.ApplicationId); if (app != null) { if (app.Currency == invoice.Currency) { detail.ExchangeRate = 1m; } else if (string.IsNullOrEmpty(app.Currency)) //原币申请 { detail.ExchangeRate = app.ExchangeRates.FirstOrDefault(x => x.Currency == item.Currency)?.ExchangeRate; } else { detail.ExchangeRate = app.ExchangeRates.FirstOrDefault(x => x.Currency == invoice.Currency)?.ExchangeRate; } } if (!detail.ExchangeRate.HasValue) detail.ExchangeRate = 1m; if (detail.ExchangeRate.HasValue) detail.ApplyAmount *= detail.ExchangeRate.Value; invoice.Details.Add(detail); } DataResult result2; //执行开票金额分配 foreach (var app in request.Applications) { var details2 = invoice.Details.Where(x => x.RefId == app.ApplicationId).OrderBy(x => x.ApplyAmount).ToList(); if (app.AmountRMB.HasValue) { result2 = AssignAmount(details2.FindAll(x => x.OriginalCurrency == FeeCurrency.RMB_CODE), app.AmountRMB.Value); if (!result2.Succeeded) return DataResult.Failed(result2.Message, result2.MultiCode); } if (app.AmountUSD.HasValue) { result2 = AssignAmount(details2.FindAll(x => x.OriginalCurrency == FeeCurrency.USD_CODE), app.AmountUSD.Value); if (!result2.Succeeded) return DataResult.Failed(result2.Message, result2.MultiCode); } if (app.AmountOther.HasValue) { result2 = AssignAmount(details2.FindAll(x => x.OriginalCurrency != FeeCurrency.RMB_CODE && x.OriginalCurrency != FeeCurrency.USD_CODE), app.AmountOther.Value); if (!result2.Succeeded) return DataResult.Failed(result2.Message, result2.MultiCode); } } } //自由开票 else if (request.Invoice.Mode == InvoiceMode.Free) { if (request.BizList?.Count > 0) { var result2 = await GetFeesAsync([.. request.BizList]); if (result2.Data != null) { request.Details ??= []; foreach (var item in result2.Data.Items) { var dto = item.Adapt(); if (dto.Currency != invoice.Currency) { var biz = request.BizList.Find(x => x.Id == dto.BusinessId && x.BusinessType == dto.BusinessType && x.CustomerId == dto.CustomerId); var er = biz?.ExchangeRates?.FirstOrDefault(x => x.Currency == dto.Currency); dto.ExchangeRate = er == null ? 1 : er.ExchangeRate; } request.Details.Add(dto); } } } if (request.Details?.Count > 0) { if (invoice.Id == 0 && invoice.CustomerId == 0) { var first = request.Details[0]; invoice.CustomerId = first.CustomerId; invoice.CustomerName = first.CustomerName; } var ids = request.Details.Select(x => x.RecordId).Distinct(); var fees = await TenantDb.Queryable().Where(x => ids.Contains(x.Id)).Select(x => new { x.Id, x.TaxRate }).ToListAsync(); //税率不一致 if (fees.GroupBy(x => x.TaxRate).Select(x => x.Key).Count() > 1 || (invoice.Id > 0 && invoice.TaxRate != fees[0].TaxRate)) return DataResult.FailedWithDesc(nameof(MultiLanguageConst.InconsistentTaxRates)); //将请求明细转换为数据库的费用明细 invoice.Details = request.Details.Select(x => new ApplicationDetail { Id = x.Id, ApplicationId = x.ApplicationId == 0 ? x.ApplicationId : invoice.Id, RefId = x.RefId, DetailId = x.Id == 0 ? null : x.Id, RecordId = x.RecordId, Category = DetailCategory.InvoiceIssuance, CustomerName = x.CustomerName ?? invoice.CustomerName, FeeId = x.FeeId, FeeName = x.FeeName, FeeType = x.FeeType, Currency = x.Currency, ApplyAmount = x.ApplyAmount, ExchangeRate = x.ExchangeRate, OriginalAmount = x.OriginalAmount, OriginalCurrency = x.OriginalCurrency ?? (invoice.Currency.IsNullOrEmpty() ? x.Currency : invoice.Currency), }).ToList(); } } if (invoice.Id == 0) { //补充购方信息 invoice.CustomerTaxID = await TenantDb.Queryable().Where(x => x.Id == invoice.CustomerId).Select(x => x.TaxNo).FirstAsync(); var header = await TenantDb.Queryable().Where(x => x.RelativeId == invoice.CustomerId) .OrderByDescending(x => x.Id).FirstAsync(); if (header != null) { invoice.InvoiceHeader = header.Header; invoice.CustomerAddressTel = header.AddressTel; } var clientBank = await TenantDb.Queryable().Where(x => x.ClientId == invoice.CustomerId && x.Currency == invoice.Currency) .OrderByDescending(x => x.IsInvoiceDefault).Select(x => new { x.Account, x.BankName }).FirstAsync(); if (clientBank != null) { invoice.CustomerAccount = clientBank.Account; invoice.CustomerBankName = clientBank.BankName; } //补充销方信息 var org = await Db.Queryable().Where(x => x.Id == User.OrgId).Select(x => new { x.OrgFullName, x.LicenseCode }).FirstAsync(); if (org != null) { invoice.OrgName = org.OrgFullName; invoice.TaxID = org.LicenseCode; } var orgBank = await Db.Queryable().Where(x => x.LinkId == User.OrgId && x.Currency == invoice.Currency).OrderByDescending(x => x.IsDefault).Select(x => new { x.BankAccountNo, x.BankName }).FirstAsync(); if (orgBank != null) { invoice.BankName = orgBank.BankName; invoice.Account = orgBank.BankAccountNo; } } //筛选出新增的费用明细 invoice.Details = invoice.Details.FindAll(x => x.Id == 0); if (invoice.Details.Count > 0) { //金额禁止为0 if (invoice.Details.Any(x => x.ApplyAmount == 0 || x.OriginalAmount == 0)) return DataResult.FailedWithDesc(nameof(MultiLanguageConst.AmountCannotBeZero)); if (invoice.Details.Any(x => x.OriginalCurrency.IsNullOrEmpty())) return DataResult.FailedWithDesc(nameof(MultiLanguageConst.OriginalCurrencyCanNotNull)); if (invoice.Details.Any(x => x.Currency != invoice.Currency && x.ExchangeRate == null)) return DataResult.FailedWithDesc(nameof(MultiLanguageConst.NeedExchangeRate)); result = await PreSaveAsync(invoice); if (!result.Succeeded) return DataResult.Failed(result.Message, result.MultiCode); } await TenantDb.Ado.BeginTranAsync(); try { BuildOption buildOption; if (invoice.Id == 0)//新增 { buildOption = BuildOption.Create; //创建时需要生成业务编号 var sequence = CommonService.Value.GetSequenceNext(); if (!sequence.Succeeded) return DataResult.Failed(sequence.Message, MultiLanguageConst.SequenceSetNotExist); invoice.BillNO = sequence.Data; //关联导航属性插入 await TenantDb.InsertNav(invoice).Include(x => x.Details).ExecuteCommandAsync(); } else//编辑 { buildOption = BuildOption.Update; await TenantDb.Updateable(invoice).UpdateColumns(x => new { x.InvoiceDate, x.ReceiptCurrency, x.AutualCustomerName, x.OperatorId, x.SaleDeptId, x.Category, x.CategoryCode, x.IsOverseasInvoice, x.Payee, x.Checker, x.PushMode, x.CellPhoneNO, x.Email, x.Note }).ExecuteCommandAsync(); if (invoice.Details?.Count > 0) await TenantDb.Insertable(invoice.Details).ExecuteCommandAsync(); if (invoice.InvoiceDetails?.Count > 0) await TenantDb.Storageable(invoice.Details).DefaultAddElseUpdate().ExecuteCommandAsync(); } if (invoice.Details?.Count > 0) { //更新费用记录的已开票金额 var fees = invoice.Details.Select(x => new FeeRecord { Id = x.RecordId, InvoiceAmount = x.OriginalAmount, OrderInvSettlementAmount = x.OriginalAmount, }).ToList(); var updateable = TenantDb.Updateable(fees).PublicSetColumns(x => x.InvoiceAmount, "+"); if (invoice.Mode == InvoiceMode.Applcation) updateable = updateable.PublicSetColumns(x => x.OrderInvSettlementAmount, "+") .UpdateColumns(x => new { x.OrderInvSettlementAmount }); await updateable.UpdateColumns(x => new { x.InvoiceAmount }).ExecuteCommandAsync(); //生成发票明细 await BuildInvoiceDetailAsync(invoice, buildOption); if (invoice.InvoiceDetails?.Count > 0) await TenantDb.Storageable(invoice.InvoiceDetails).DefaultAddElseUpdate().ExecuteCommandAsync(); //重新计算发票总金额 await RefreshInvoiceAsync([invoice]); } await OnSaveAsync(invoice); await TenantDb.Ado.CommitTranAsync(); PostSaveAsync(invoice); return DataResult.Success(invoice); } catch (Exception ex) { await TenantDb.Ado.RollbackTranAsync(); await ex.LogAsync(Db); return DataResult.FailedWithDesc(nameof(MultiLanguageConst.Operation_Failed)); } } static DataResult AssignAmount(List details, decimal invoiceAmount) { if (details.Count == 0) return DataResult.Success; if (invoiceAmount == 0) return DataResult.Failed("开票金额不能为零"); var totalRMB = details.Sum(x => x.ApplyAmount); if (Math.Abs(invoiceAmount) > totalRMB) return DataResult.Failed("申请开票金额不能大于剩余开票金额"); if (totalRMB != invoiceAmount) //非全额开票 { var rest = totalRMB - invoiceAmount; foreach (var detail in details) { if (rest == 0) break; detail.ApplyAmount = detail.ApplyAmount - rest; rest = detail.ApplyAmount - rest; } } return DataResult.Success; } /// /// 生成发票明细 /// /// 发票 /// 生成类型 /// protected async Task BuildInvoiceDetailAsync(TEntity invoice, BuildOption option) { if (invoice.Details == null || invoice.Details.Count == 0) return; invoice.InvoiceDetails ??= []; var currencies = invoice.Details.Select(x => x.Currency).Distinct(); var codeList = await TenantDb.Queryable() .Where(ci => currencies.Contains(ci.DefaultCurrency)) .OrderBy(ci => ci.IsDefault) .Select(ci => new { ci.Id, ci.Name, ci.DefaultCurrency, ci.TaxRate, ci.Specification, ci.Unit }).ToListAsync(); foreach (var detail in invoice.Details) { var code = codeList.Find(x => x.DefaultCurrency == detail.Currency); if (code == null || string.IsNullOrEmpty(code.Name)) continue; if (option == BuildOption.Create && codeList.IndexOf(code) == 0)//取第一条发票代码税率 invoice.TaxRate = code.TaxRate; var invDetail = invoice.InvoiceDetails.Find(x => x.CodeId == code.Id); if (invDetail == null) { invDetail = new InvoiceDetail { ApplicationId = invoice.Id, CodeId = code.Id, Name = code.Name, Quantity = 1, TaxUnitPrice = detail.ApplyAmount, TaxRate = code.TaxRate, Specification = code.Specification, Unit = code.Unit, Category = DetailCategory.InvoiceIssuance }; invDetail.Amount = invDetail.TaxUnitPrice; invoice.InvoiceDetails.Add(invDetail); } else { invDetail.Amount += detail.ApplyAmount; } } foreach (var item in invoice.InvoiceDetails) { item.TaxAmount = item.Amount * (invoice.TaxRate / 100); item.UnitPrice += item.Amount - item.TaxAmount; } } /// /// 重新计算发票的各项金额数据 /// /// 发票 /// protected async Task RefreshInvoiceAsync(List invoices) { var ids = invoices.Select(x => x.Id); var details = await TenantDb.Queryable().Where(x => ids.Contains(x.ApplicationId)) .Select(x => new { x.ApplicationId, x.Currency, x.ApplyAmount, x.OriginalAmount }).ToListAsync(); var invDetails = await TenantDb.Queryable().Where(x => ids.Contains(x.ApplicationId)) .Select(x => new { x.ApplicationId, x.Amount }).ToListAsync(); foreach (var invoice in invoices) { var currDetails = details.FindAll(x => x.ApplicationId == invoice.Id); invoice.ApplyAmount = currDetails.Sum(x => x.ApplyAmount); invoice.AmountUppercase = new Money(invoice.ApplyAmount).ToString(); invoice.OriginalAmount = currDetails.Sum(x => x.OriginalAmount); invoice.OtherInvoiceAmount = currDetails.FindAll(x => x.Currency != FeeCurrency.RMB_CODE).Sum(x => x.OriginalAmount); invoice.InvoiceAmount = invDetails.FindAll(x => x.ApplicationId == invoice.Id).Sum(x => x.Amount); } return await TenantDb.Updateable(invoices).UpdateColumns(x => new { x.ApplyAmount, x.AmountUppercase, x.OriginalAmount, x.OtherInvoiceAmount, x.InvoiceAmount }).ExecuteCommandAsync(); } /// /// 用于发票的状态检查 /// /// 提交的发票 /// 数据库值,新增时为null /// protected virtual DataResult EnsureSettlement(TEntity invoice, TEntity? dbValue) { if (dbValue != null && dbValue.IsLocked) return DataResult.FailedWithDesc(nameof(MultiLanguageConst.InvoiceIsLocked)); return DataResult.Success; } /// /// 在保存前调用 /// /// 发票 /// protected virtual Task PreSaveAsync(TEntity invoice) => Task.FromResult(DataResult.Success); /// /// 在保存时调用 /// /// 要保存的发票 /// protected virtual Task OnSaveAsync(TEntity invoice) => Task.CompletedTask; /// /// 在保存完成后调用 /// /// 发票 protected virtual Task PostSaveAsync(TEntity invoice) => Task.CompletedTask; /// /// 删除发票 /// /// 发票ID /// public async Task DeleteAsync(params long[] ids) { await TenantDb.Ado.BeginTranAsync(); try { var apps = await TenantDb.Queryable().Where(x => ids.Contains(x.Id)).Select(x => new TEntity { Id = x.Id, Mode = x.Mode, IsLocked = x.IsLocked }).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, DetailId = x.DetailId, RefId = x.RefId, RecordId = x.RecordId, ApplyAmount = x.ApplyAmount, OriginalAmount = x.OriginalAmount }).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 OnDeleteDetailAsync(apps, DeleteOption.Entire); await TenantDb.DeleteNav(x => ids.Contains(x.Id)).Include(x => x.Details).ExecuteCommandAsync(); await TenantDb.Ado.CommitTranAsync(); PostDeleteAsync(apps, DeleteOption.Entire); 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 DeleteDetailAsync(params long[] ids) { await TenantDb.Ado.BeginTranAsync(); try { var details = await TenantDb.Queryable().Where(x => ids.Contains(x.Id)).Select( x => new ApplicationDetail { Id = x.Id, ApplicationId = x.ApplicationId, DetailId = x.DetailId, RefId = x.RefId, RecordId = x.RecordId, ApplyAmount = x.ApplyAmount, OriginalAmount = x.OriginalAmount }).ToListAsync(); if (details.Count == 0) return DataResult.FailedWithDesc(nameof(MultiLanguageConst.EmptyData)); var invIds = details.Select(x => x.ApplicationId).Distinct().ToList(); var invoices = await TenantDb.Queryable().Where(x => invIds.Contains(x.Id)).Select(x => new TEntity { Id = x.Id, Mode = x.Mode, IsLocked = x.IsLocked }).ToListAsync(); foreach (var app in invoices) app.Details = details.FindAll(x => x.ApplicationId == app.Id); var result = PreDelete(invoices); if (!result.Succeeded) return result; await OnDeleteDetailAsync(invoices, DeleteOption.DetailOnly); await TenantDb.Deleteable(details).ExecuteCommandAsync(); //重新计算发票总金额 await RefreshInvoiceAsync(invoices); await TenantDb.Ado.CommitTranAsync(); PostDeleteAsync(invoices, DeleteOption.Entire); 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 DeleteInvoiceDetailAsync(params long[] ids) { await TenantDb.Ado.BeginTranAsync(); try { var invDetails = await TenantDb.Queryable().Where(x => ids.Contains(x.Id)) .Select(x => new { x.ApplicationId, x.Amount }).ToListAsync(); var groups = invDetails.GroupBy(x => x.ApplicationId); foreach (var g in groups) { var list = g.Select(x => new TEntity { Id = g.Key, InvoiceAmount = g.Sum(x => x.Amount) }).ToList(); //更新发票主表的开票金额 await TenantDb.Updateable(list).PublicSetColumns(x => x.InvoiceAmount, "-") .UpdateColumns(x => new { x.InvoiceAmount }).ExecuteCommandAsync(); } await TenantDb.Deleteable().Where(x => ids.Contains(x.Id)).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 invoices) { if (invoices.Any(x => x.IsLocked)) return DataResult.FailedWithDesc(nameof(MultiLanguageConst.InvoiceIsLocked)); if (invoices.Any(x => !string.IsNullOrEmpty(x.ApiCode))) return DataResult.FailedWithDesc(nameof(MultiLanguageConst.InvoiceIsIssued)); var ids = invoices.Select(x => x.Id); if (TenantDb.Queryable().Any(x => ids.Contains(x.RefId.Value) && x.Category == DetailCategory.InvoiceSettlement)) return DataResult.FailedWithDesc(nameof(MultiLanguageConst.InvoiceIsSettled)); return DataResult.Success; } /// /// 在执行删除发票或其明细时调用 /// /// 发票及其明细 /// 发票删除选项 /// protected virtual async Task OnDeleteDetailAsync(List invoices, DeleteOption deleteOption) { var ids = invoices.Select(x => x.Id); if (deleteOption == DeleteOption.DetailOnly) { var excludeIds = invoices.SelectMany(x => x.Details).Select(x => x.Id); var details = await TenantDb.Queryable().Where(x => ids.Contains(x.ApplicationId) && !excludeIds.Contains(x.Id)) .Select(x => new ApplicationDetail { Id = x.Id, ApplicationId = x.ApplicationId, RecordId = x.RecordId, FeeId = x.FeeId, ApplyAmount = x.ApplyAmount }).ToListAsync(); foreach (var item in invoices) { //重新设置申请明细与总金额 item.Details = details.FindAll(x => x.ApplicationId == item.Id); item.ApplyAmount = item.Details.Sum(x => x.ApplyAmount); item.AmountUppercase = new Money(item.ApplyAmount).ToString(); } await TenantDb.Updateable(invoices).UpdateColumns(x => new { x.ApplyAmount, x.AmountUppercase }).ExecuteCommandAsync(); } else if (deleteOption == DeleteOption.Entire) { //删除发票主表则同时删除对应发票明细 await TenantDb.Deleteable().Where(x => ids.Contains(x.ApplicationId)).ExecuteCommandAsync(); } foreach (var item in invoices) { //还原费用表的已开票金额 var fees = item.Details?.Select(x => new FeeRecord { Id = x.RecordId, InvoiceAmount = x.OriginalAmount }).ToList(); var updateable = TenantDb.Updateable(fees).PublicSetColumns(x => x.InvoiceAmount, "-"); if (item.Mode == InvoiceMode.Applcation) updateable = updateable.PublicSetColumns(x => x.OrderInvSettlementAmount, "-") .UpdateColumns(x => new { x.OrderInvSettlementAmount }); await updateable.UpdateColumns(x => new { x.InvoiceAmount }).ExecuteCommandAsync(); } } /// /// 在删除完成后调用 /// /// 发票及其明细 /// 发票删除选项 protected virtual Task PostDeleteAsync(List invoices, DeleteOption deleteOption) => Task.CompletedTask; /// /// 设置发票的锁定状态 /// /// 是否锁定 /// 发票ID /// public async Task SetLockAsync(bool isLocked, params long[] ids) { var dt = DateTime.Now; var userId = long.Parse(User.UserId); var list = ids.Select(x => new TEntity { Id = x, IsLocked = isLocked, LockTime = isLocked ? dt : null, LockUserId = isLocked ? userId : null }).ToList(); int rows = await TenantDb.Updateable(list) .UpdateColumns(x => new { x.IsLocked, x.LockTime, x.LockUserId }).ExecuteCommandAsync(); return rows > 0 ? DataResult.Success : DataResult.FailedWithDesc(nameof(MultiLanguageConst.Operation_Failed)); } /// /// 设置发票的作废状态 /// /// 是否锁定 /// 发票ID /// public async Task SetCancelAsync(bool isCancelled, params long[] ids) { var dt = DateTime.Now; var userId = long.Parse(User.UserId); var list = ids.Select(x => new TEntity { Id = x, IsCancelled = isCancelled, CancelTime = isCancelled ? dt : null, CancelUserId = isCancelled ? userId : null }).ToList(); int rows = await TenantDb.Updateable(list) .UpdateColumns(x => new { x.IsCancelled, x.CancelTime, x.CancelUserId }).ExecuteCommandAsync(); return rows > 0 ? DataResult.Success : DataResult.FailedWithDesc(nameof(MultiLanguageConst.Operation_Failed)); } } }