You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1017 lines
44 KiB
C#

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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
{
/// <summary>
/// 发票服务基类
/// </summary>
/// <typeparam name="TEntity"></typeparam>
public class InvoiceService<TEntity> : ApplicationServiceBase, IInvoiceService<TEntity>
where TEntity : Entity.Invoice, new()
{
readonly Lazy<ICommonService> CommonService;
/// <summary>
/// 初始化
/// </summary>
/// <param name="provider"></param>
public InvoiceService(IServiceProvider provider) : base(provider)
{
CommonService = new Lazy<ICommonService>(provider.GetRequiredService<ICommonService>());
}
/// <summary>
/// 获取分页列表
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
public async Task<DataResult<InvoiceList>> GetListAsync(PageRequest<string> request)
{
var query = TenantDb.Queryable<Entity.Invoice>()
.Select((i) => new InvoiceDto
{
Details = SqlFunc.Subqueryable<ApplicationDetail>().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<ApplicationDetail>().LeftJoin<InvoiceApplication>((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<SysUser>().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<SysOrg>().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<InvoiceList>.Success(list, result.MultiCode);
invResult.Count = result.Count;
return invResult;
}
/// <summary>
/// 获取发票详情
/// </summary>
/// <param name="id">发票ID</param>
/// <returns></returns>
public async Task<DataResult<InvoiceDto>> GetAsync(long id)
{
var invoice = await TenantDb.Queryable<Entity.Invoice>().Select<InvoiceDto>().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<SysUser>().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<InvoiceApplication>().Where(x => ids.Contains(x.Id))
.LeftJoin<SysUser>((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<InvoiceDetail>().Where(
x => x.ApplicationId == id && x.Category == DetailCategory.InvoiceIssuance).ToListAsync();
}
return DataResult<InvoiceDto>.Success(invoice);
}
/// <summary>
/// 根据业务编号及类型获取费用记录
/// </summary>
/// <param name="items">业务ID与业务类型</param>
/// <returns></returns>
public virtual Task<DataResult<InvoiceApplicaitonBiz>> GetFeesAsync(params FeeClient[] items)
{
return Task.FromResult(DataResult<InvoiceApplicaitonBiz>.Success(
new InvoiceApplicaitonBiz(new List<FeeInvoiceDto>())));
}
#pragma warning disable CS4014
/// <summary>
/// 提交
/// </summary>
/// <param name="request">请求参数</param>
/// <returns></returns>
public async Task<DataResult<TEntity>> SaveAsync(InvoiceRequest<TEntity> 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<TEntity>().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<TEntity>.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<ApplicationDetail>()
.InnerJoin<InvoiceApplication>((x, y) => x.ApplicationId == y.Id)
.LeftJoin<InfoClientBank>((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<TEntity>.FailedWithDesc(nameof(MultiLanguageConst.InconsistentTaxRates));
invoice.Details ??= new List<ApplicationDetail>(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<TEntity>.FailedWithDesc(nameof(MultiLanguageConst.InvoiceCustomerOnlyOne));
}
else if (invoice.TaxRate != item.TaxRate) //校验税率是否一致
{
return DataResult<TEntity>.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<TEntity>.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<TEntity>.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<TEntity>.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<PaymentApplicationDetailDto>();
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<FeeRecord>().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<TEntity>.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<InfoClient>().Where(x => x.Id == invoice.CustomerId).Select(x => x.TaxNo).FirstAsync();
var header = await TenantDb.Queryable<InvoiceHeader>().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<InfoClientBank>().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<SysOrg>().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<SysBank>().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<TEntity>.FailedWithDesc(nameof(MultiLanguageConst.AmountCannotBeZero));
if (invoice.Details.Any(x => x.OriginalCurrency.IsNullOrEmpty()))
return DataResult<TEntity>.FailedWithDesc(nameof(MultiLanguageConst.OriginalCurrencyCanNotNull));
if (invoice.Details.Any(x => x.Currency != invoice.Currency && x.ExchangeRate == null))
return DataResult<TEntity>.FailedWithDesc(nameof(MultiLanguageConst.NeedExchangeRate));
result = await PreSaveAsync(invoice);
if (!result.Succeeded)
return DataResult<TEntity>.Failed(result.Message, result.MultiCode);
}
await TenantDb.Ado.BeginTranAsync();
try
{
BuildOption buildOption;
if (invoice.Id == 0)//新增
{
buildOption = BuildOption.Create;
//创建时需要生成业务编号
var sequence = CommonService.Value.GetSequenceNext<TEntity>();
if (!sequence.Succeeded)
return DataResult<TEntity>.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<TEntity>.Success(invoice);
}
catch (Exception ex)
{
await TenantDb.Ado.RollbackTranAsync();
await ex.LogAsync(Db);
return DataResult<TEntity>.FailedWithDesc(nameof(MultiLanguageConst.Operation_Failed));
}
}
static DataResult AssignAmount(List<ApplicationDetail> 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;
}
/// <summary>
/// 生成发票明细
/// </summary>
/// <param name="invoice">发票</param>
/// <param name="option">生成类型</param>
/// <returns></returns>
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<CodeInvoice>()
.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;
}
}
/// <summary>
/// 重新计算发票的各项金额数据
/// </summary>
/// <param name="invoices">发票</param>
/// <returns></returns>
protected async Task<int> RefreshInvoiceAsync(List<TEntity> invoices)
{
var ids = invoices.Select(x => x.Id);
var details = await TenantDb.Queryable<ApplicationDetail>().Where(x => ids.Contains(x.ApplicationId))
.Select(x => new { x.ApplicationId, x.Currency, x.ApplyAmount, x.OriginalAmount }).ToListAsync();
var invDetails = await TenantDb.Queryable<InvoiceDetail>().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();
}
/// <summary>
/// 用于发票的状态检查
/// </summary>
/// <param name="invoice">提交的发票</param>
/// <param name="dbValue">数据库值新增时为null</param>
/// <returns></returns>
protected virtual DataResult EnsureSettlement(TEntity invoice, TEntity? dbValue)
{
if (dbValue != null && dbValue.IsLocked)
return DataResult.FailedWithDesc(nameof(MultiLanguageConst.InvoiceIsLocked));
return DataResult.Success;
}
/// <summary>
/// 在保存前调用
/// </summary>
/// <param name="invoice">发票</param>
/// <returns></returns>
protected virtual Task<DataResult> PreSaveAsync(TEntity invoice) => Task.FromResult(DataResult.Success);
/// <summary>
/// 在保存时调用
/// </summary>
/// <param name="invoice">要保存的发票</param>
/// <returns></returns>
protected virtual Task OnSaveAsync(TEntity invoice) => Task.CompletedTask;
/// <summary>
/// 在保存完成后调用
/// </summary>
/// <param name="invoice">发票</param>
protected virtual Task PostSaveAsync(TEntity invoice) => Task.CompletedTask;
/// <summary>
/// 删除发票
/// </summary>
/// <param name="ids">发票ID</param>
/// <returns></returns>
public async Task<DataResult> DeleteAsync(params long[] ids)
{
await TenantDb.Ado.BeginTranAsync();
try
{
var apps = await TenantDb.Queryable<TEntity>().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<ApplicationDetail>().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<TEntity>(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));
}
}
/// <summary>
/// 删除发票费用明细
/// </summary>
/// <param name="ids">明细ID</param>
/// <returns></returns>
public async Task<DataResult> DeleteDetailAsync(params long[] ids)
{
await TenantDb.Ado.BeginTranAsync();
try
{
var details = await TenantDb.Queryable<ApplicationDetail>().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<TEntity>().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));
}
}
/// <summary>
/// 删除发票明细
/// </summary>
/// <param name="ids">发票明细ID</param>
/// <returns></returns>
public async Task<DataResult> DeleteInvoiceDetailAsync(params long[] ids)
{
await TenantDb.Ado.BeginTranAsync();
try
{
var invDetails = await TenantDb.Queryable<InvoiceDetail>().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<InvoiceDetail>().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));
}
}
/// <summary>
/// 在删除发票或其明细之前调用,用于检查状态
/// </summary>
/// <param name="invoices">发票</param>
/// <returns></returns>
protected virtual DataResult PreDelete(List<TEntity> 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<ApplicationDetail>().Any(x => ids.Contains(x.RefId.Value) && x.Category == DetailCategory.InvoiceSettlement))
return DataResult.FailedWithDesc(nameof(MultiLanguageConst.InvoiceIsSettled));
return DataResult.Success;
}
/// <summary>
/// 在执行删除发票或其明细时调用
/// </summary>
/// <param name="invoices">发票及其明细</param>
/// <param name="deleteOption">发票删除选项</param>
/// <returns></returns>
protected virtual async Task OnDeleteDetailAsync(List<TEntity> 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<ApplicationDetail>().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<InvoiceDetail>().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();
}
}
/// <summary>
/// 在删除完成后调用
/// </summary>
/// <param name="invoices">发票及其明细</param>
/// <param name="deleteOption">发票删除选项</param>
protected virtual Task PostDeleteAsync(List<TEntity> invoices, DeleteOption deleteOption) => Task.CompletedTask;
/// <summary>
/// 设置发票的锁定状态
/// </summary>
/// <param name="isLocked">是否锁定</param>
/// <param name="ids">发票ID</param>
/// <returns></returns>
public async Task<DataResult> 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));
}
/// <summary>
/// 设置发票的作废状态
/// </summary>
/// <param name="isCancelled">是否锁定</param>
/// <param name="ids">发票ID</param>
/// <returns></returns>
public async Task<DataResult> 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));
}
}
}