using DS.Module.Core; using DS.Module.Core.Data; 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.Op.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()); TenantDb.QueryFilter.Clear(); } /// /// 获取分页列表 /// /// /// public async Task> GetListAsync(PageRequest request) { var query = TenantDb.Queryable() .LeftJoin((i, s) => i.SaleDeptId == s.Id, "shippingweb8_dev.sys_org") .LeftJoin((i, s, u1) => i.LockUserId == s.Id, "shippingweb8_dev.sys_user") .LeftJoin((i, s, u1, u2) => i.OperatorId == s.Id, "shippingweb8_dev.sys_user") .WhereIF(!string.IsNullOrEmpty(request.OtherQueryCondition?.Number), i => i.BillNO.Contains(request.OtherQueryCondition.Number) || i.InvoiceNO.Contains(request.OtherQueryCondition.Number) || SqlFunc.Subqueryable().InnerJoin((d, f) => d.RecordId == f.Id && f.BusinessType == BusinessType.OceanShippingExport) .InnerJoin((d, f, s) => f.BusinessId == s.Id).Where((d, f, s) => s.CustomerNo.Contains(request.OtherQueryCondition.Number) || s.BookingNo.Contains(request.OtherQueryCondition.Number) || s.MBLNO.Contains(request.OtherQueryCondition.Number) || s.CustomerNum.Contains(request.OtherQueryCondition.Number)).Any()) .Select((i, s, u1, u2) => new InvoiceDto { CreateByName = i.CreateUserName, SaleDeptName = s.OrgName, LockUserName = u1.UserName, OperatorName = u2.UserName, InvoiceApplicationList = SqlFunc.Subqueryable().LeftJoin((d, a) => d.ApplicationId == i.Id && d.RefId == a.Id) //.WhereIF(request.OtherQueryCondition != null && !string.IsNullOrEmpty(request.OtherQueryCondition.Number), // (d, a) => a.ApplicationNO.Contains(request.OtherQueryCondition.Number)) .GroupBy((d, a) => a.ApplicationNO).ToList((d, a) => a.ApplicationNO), ApplyAmountRMB = SqlFunc.Subqueryable().LeftJoin((d1, d2) => d1.Id == d2.DetailId).Where((d1, d2) => d2.ApplicationId == i.Id && d1.Currency == FeeCurrency.RMB_CODE) .Sum((d1, d2) => d1.ApplyAmount), ApplyAmountUSD = SqlFunc.Subqueryable().LeftJoin((d1, d2) => d1.Id == d2.DetailId).Where((d1, d2) => d2.ApplicationId == i.Id && d1.Currency == FeeCurrency.USD_CODE) .Sum((d1, d2) => d1.ApplyAmount) }, true).MergeTable(); var whereList = request.GetConditionalModels(Db); var result = await query.Where(whereList).ToQueryPageAsync(request.PageCondition); if (result.Data?.Count > 0) { } 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() .LeftJoin((i, ri) => i.RedId == ri.Id) .LeftJoin((i, ri, s) => i.SaleDeptId == s.Id, "shippingweb8_dev.sys_org") .LeftJoin((i, ri, s, u1) => i.LockUserId == s.Id, "shippingweb8_dev.sys_user") .LeftJoin((i, ri, s, u1, u2) => i.OperatorId == s.Id, "shippingweb8_dev.sys_user") .Select((i, ri, s, u1, u2) => new InvoiceDto { RedNO = ri.InvoiceNO, CreateByName = i.CreateUserName, SaleDeptName = s.OrgName, LockUserName = u1.UserName, OperatorName = u2.UserName, ApplyAmountRMB = SqlFunc.Subqueryable().LeftJoin((d1, d2) => d1.Id == d2.DetailId).Where((d1, d2) => d2.ApplicationId == i.Id && d1.Currency == FeeCurrency.RMB_CODE) .Sum((d1, d2) => d1.ApplyAmount), ApplyAmountUSD = SqlFunc.Subqueryable().LeftJoin((d1, d2) => d1.Id == d2.DetailId).Where((d1, d2) => d2.ApplicationId == i.Id && d1.Currency == FeeCurrency.USD_CODE) .Sum((d1, d2) => d1.ApplyAmount) }, true).FirstAsync(i => i.Id == id); if (invoice != null) { if (!string.IsNullOrEmpty(invoice.PushMode)) { invoice.PushModeValues = invoice.PushMode.Split(',', StringSplitOptions.RemoveEmptyEntries) .Select(x => (PushMode)int.Parse(x)).ToArray(); } 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)) .Select(x => new InvoiceApplicationDto { Id = x.Id, ApplicationNO = x.ApplicationNO, Status = x.Status, Currency = x.Currency, InvoiceRemark = x.InvoiceRemark, CreateBy = x.CreateBy, CreateByName = x.CreateUserName, ApplyAmountRMB = SqlFunc.Subqueryable().Where(y => x.Id == y.ApplicationId && y.Currency == FeeCurrency.RMB_CODE).Sum(y => y.ApplyAmount), ApplyAmountUSD = SqlFunc.Subqueryable().Where(y => x.Id == y.ApplicationId && y.Currency == FeeCurrency.USD_CODE).Sum(y => y.ApplyAmount) }).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(DetailInquiry inquiry) { return Task.FromResult(DataResult.Success(new InvoiceApplicaitonBiz([]))); } #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, InvoiceNO = x.InvoiceNO, IsLocked = x.IsLocked, IsSettled = x.IsSettled, 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)); string? invRemark = null; //按申请开票 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.ExchangeRate, 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, y.InvoiceRemark, z.BankAccountNo, z.BankName }).ToListAsync(); invRemark = string.Join(" | ", details.Where(x => !string.IsNullOrEmpty(x.InvoiceRemark)).Select(x => x.InvoiceRemark)); //税率不一致 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 = item.Currency, OriginalCurrency = item.OriginalCurrency, ApplyAmount = item.ApplyAmount - item.ProcessedAmount, OriginalAmount = item.OriginalAmount - item.OriginalProcessedAmount, ExchangeRate = item.ExchangeRate }; var app = request.Applications.Find(x => x.ApplicationId == item.ApplicationId); if (app != null) //设置汇率 { var er = app.ExchangeRates?.FirstOrDefault(x => x.Currency == detail.OriginalCurrency); if (er != null) detail.ExchangeRate = er.ExchangeRate; } 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.Currency == 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.Currency == 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.Currency != FeeCurrency.RMB_CODE && x.Currency != FeeCurrency.USD_CODE), app.AmountOther.Value); if (!result2.Succeeded) return DataResult.Failed(result2.Message, result2.MultiCode); } } invoice.Details.RemoveAll(x => !x.Assigned); } //自由开票 else if (request.Invoice.Mode == InvoiceMode.Free) { if (request.FreeInvoice?.Items.Count > 0) { var result2 = await GetFeesAsync(request.FreeInvoice); 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.FreeInvoice.Items.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) { if (request.Invoice.Mode == InvoiceMode.Free) { //补充购方信息 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 orgId = invoice.SaleDeptId.GetValueOrDefault() == 0 ? User.OrgId : invoice.SaleDeptId; var org = await Db.Queryable().LeftJoin((t, a) => t.Id == a.OrgId).Where((t, a) => t.Id == orgId).Select((t, a) => new { t.OrgFullName, t.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; } if (!string.IsNullOrEmpty(invRemark)) invoice.InvoiceRemark += Environment.NewLine + invRemark; } //筛选出新增的费用明细 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; invoice.Type = InvoiceType.Blue; //关联导航属性插入 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.InvoiceRemark, x.Note }).ExecuteCommandAsync(); if (invoice.Details?.Count > 0) await TenantDb.Insertable(invoice.Details).ExecuteCommandAsync(); if (invoice.InvoiceDetails?.Count > 0) await TenantDb.Storageable(invoice.InvoiceDetails).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)); } } internal static DataResult AssignAmount(List details, decimal invoiceAmount, string currency = FeeCurrency.RMB_CODE) { if (details.Count == 0) return DataResult.Success; if (invoiceAmount == 0) return DataResult.Failed("开票金额不能为零"); var totalAmount = details.Sum(x => x.ApplyAmount); if (Math.Abs(invoiceAmount) > totalAmount) return DataResult.Failed("申请开票金额不能大于剩余发票金额"); decimal rest = invoiceAmount; decimal currentAmount = default; foreach (var detail in details) { if (rest <= 0) break; if (totalAmount != invoiceAmount) //非全额开 { 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; } /// /// 生成发票明细 /// /// 发票 /// 生成类型 /// 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.OriginalCurrency).Distinct(); var codeList = await TenantDb.Queryable() .Where(ci => currencies.Contains(ci.DefaultCurrency) && 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.OriginalCurrency); 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, Currency = FeeCurrency.RMB_CODE, 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; invDetail.UnitPrice = invDetail.TaxUnitPrice = invDetail.Amount; } } foreach (var item in invoice.InvoiceDetails) { item.TaxAmount = item.Amount * (invoice.TaxRate / 100); item.UnitPrice = item.TaxUnitPrice - item.TaxAmount; item.Amount = item.TaxUnitPrice * item.Quantity; } } /// /// 重新计算发票的各项金额数据 /// /// 发票 /// 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.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.OriginalAmount, x.OtherInvoiceAmount, x.InvoiceAmount }).ExecuteCommandAsync(); } /// /// 用于发票的状态检查 /// /// 提交的发票 /// 数据库值,新增时为null /// protected virtual DataResult EnsureSettlement(TEntity invoice, TEntity? dbValue) { if (dbValue != null) { if (dbValue.IsLocked) return DataResult.FailedWithDesc(nameof(MultiLanguageConst.InvoiceIsLocked)); if (!string.IsNullOrEmpty(dbValue.InvoiceNO)) return DataResult.FailedWithDesc(nameof(MultiLanguageConst.InvoiceIsIssued)); if (dbValue.IsSettled) return DataResult.FailedWithDesc(nameof(MultiLanguageConst.InvoiceIsSettled)); } 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.InvoiceNO))) 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)); if (invoices.Any(x => x.IsSettled)) 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.InvoiceAmount = item.InvoiceDetails.Sum(x => x.Amount); } } 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)); } } }