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.

931 lines
40 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.Fee.Entity;
using DS.WMS.Core.Invoice.Dtos;
using DS.WMS.Core.Invoice.Interface;
using DS.WMS.Core.Sys.Entity;
using DS.WMS.Core.Sys.Interface;
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
{
Id = i.Id,
InvoiceNO = i.InvoiceNO,
BillNO = i.BillNO,
InvoiceDate = i.InvoiceDate,
CustomerId = i.CustomerId,
CustomerName = i.CustomerName,
Type = i.Mode,
Category = i.Category,
InvoiceHeader = i.InvoiceHeader,
TaxID = i.TaxID,
CustomerAddress = i.CustomerAddress,
CustomerBankName = i.CustomerBankName,
CustomerPhone = i.CustomerPhone,
CustomerAccount = i.CustomerAccount,
AutualCustomerName = i.AutualCustomerName,
Currency = i.Currency,
ReceiptCurrency = i.ReceiptCurrency,
ApplyAmount = i.ApplyAmount,
InvoiceAmount = i.InvoiceAmount,
OperatorId = i.OperatorId,
IsLocked = i.IsLocked,
LockUserId = i.LockUserId,
LockTime = i.LockTime,
TaxRate = i.TaxRate,
OrgId = i.OrgId,
SaleDeptId = i.SaleDeptId,
IsCancelled = i.IsCancelled,
CancelUserId = i.CancelUserId,
CancelTime = i.CancelTime,
CreateTime = i.CreateTime,
CreateBy = i.CreateBy,
Details = SqlFunc.Subqueryable<ApplicationDetail>().Where(d => d.ApplicationId == i.Id)
.ToList(d => new ApplicationDetailDto
{
OriginalCurrency = d.OriginalCurrency,
OriginalAmount = d.OriginalAmount,
OriginalSettlementAmount = d.OriginalSettlementAmount,
ExchangeRate = d.ExchangeRate
}),
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)
}).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)
.Union(result.Data.Where(x => x.OrgId.HasValue).Select(x => x.OrgId.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;
item.OrgName = orgs.Find(x => x.Id == item.OrgId)?.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)
{
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,
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();
invoice.InvoiceDetails = await TenantDb.Queryable<InvoiceDetail>().Where(
x => x.ApplicationId == id && x.Category == DetailCategory.InvoiceIssuance).ToListAsync();
}
return DataResult<InvoiceDto>.Success(invoice);
}
#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.InvoiceDate == default)
invoice.InvoiceDate = DateTime.Now;
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 (request.Applications != null && 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)
.Where((x, y) => ids.Contains(x.ApplicationId) && x.Category == DetailCategory.InvoiceApplication)
.Select((x, y) => 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
}).ToListAsync();
invoice.Details ??= new List<ApplicationDetail>(details.Count);
foreach (var item in details)
{
if (request.Invoice.CustomerId == 0)
{
request.Invoice.CustomerId = item.CustomerId;
request.Invoice.CustomerName = item.CustomerName;
}
else if (request.Invoice.CustomerId != item.CustomerId) //校验开票单位是否一致
{
return DataResult<TEntity>.FailedWithDesc(nameof(MultiLanguageConst.InvoiceCustomerOnlyOne));
}
//需转换为费用明细
var detail = new ApplicationDetail
{
ApplicationId = request.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 = FeeCurrency.RMB_CODE,
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 && app.Currency != invoice.Currency)
detail.ExchangeRate = app.ExchangeRate;
else
detail.ExchangeRate = 1m;
if (detail.ExchangeRate.HasValue)
detail.ApplyAmount = detail.ApplyAmount * detail.ExchangeRate.Value;
invoice.Details.Add(detail);
}
}
//自由申请
if (request.Details?.Count > 0)
{
if (invoice.Id == 0 && invoice.CustomerId == 0)
{
var first = request.Details[0];
invoice.CustomerId = first.CustomerId.GetValueOrDefault();
invoice.CustomerName = first.CustomerName;
}
//将请求明细转换为数据库的费用明细
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.Details?.Count > 0)
{
//筛选出新增的费用明细
invoice.Details = invoice.Details.FindAll(x => x.Id == 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.InvoiceNO,
x.InvoiceDate,
x.Account,
x.BankName,
//x.Currency,
x.ReceiptCurrency,
x.CustomerId,
x.CustomerName,
x.AutualCustomerName,
x.InvoiceHeader,
x.CustomerAddress,
x.CustomerPhone,
x.CustomerBankName,
x.CustomerAccount,
x.OperatorId,
x.TaxID,
x.TaxRate,
x.OrgId,
x.SaleDeptId
}).ExecuteCommandAsync();
if (invoice.Details?.Count > 0)
await TenantDb.Insertable(invoice.Details).ExecuteCommandAsync();
if (invoice.InvoiceDetails?.Count > 0)
//发票明细只有更新
await TenantDb.Updateable(invoice.InvoiceDetails).IgnoreColumns(x => new { x.Category }).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);
//重新计算发票总金额
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));
}
}
/// <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;
var ids = invoice.Details.Select(x => x.Id);
if (option == BuildOption.Create)
{
var feesCodes = await TenantDb.Queryable<ApplicationDetail>().InnerJoin<FeeCode>((d, fc) => d.FeeId == fc.Id && ids.Contains(d.Id))
.Select((d, fc) => new
{
DetailId = d.Id,
FeeId = fc.Id,
fc.GoodName
}).ToListAsync();
var feeCodeGroups = feesCodes.GroupBy(x => x.GoodName).ToList();
invoice.InvoiceDetails = new List<InvoiceDetail>(feeCodeGroups.Count);
List<ApplicationDetailRelation> relations = [];
foreach (var g in feeCodeGroups)
{
var invDetail = new InvoiceDetail
{
Id = SnowFlakeSingle.Instance.NextId(),
ApplicationId = invoice.Id,
Name = g.Key,
TaxRate = invoice.TaxRate,
Quantity = 1,
TaxUnitPrice = invoice.Details.FindAll(x => g.Select(x => x.DetailId).Contains(x.Id)).Sum(x => x.ApplyAmount),
Category = DetailCategory.InvoiceIssuance
};
invDetail.TaxAmount = invDetail.TaxUnitPrice * invoice.TaxRate;
invDetail.UnitPrice = invDetail.TaxUnitPrice - invDetail.TaxAmount;
invDetail.Amount = invDetail.TaxUnitPrice * invDetail.Quantity;
invoice.InvoiceDetails.Add(invDetail);
foreach (var item in g)
{
relations.Add(new ApplicationDetailRelation
{
DetailId = item.DetailId,
InvoiceDetailId = invDetail.Id,
OriginalName = g.Key,
InvoiceId = invoice.Id
});
}
}
await TenantDb.Insertable(invoice.InvoiceDetails).ExecuteCommandAsync();
await TenantDb.Insertable(relations).ExecuteCommandAsync();
}
//更新时只处理增加的明细
else if (option == BuildOption.Update)
{
//获取现有发票明细对应关系
var relations = await TenantDb.Queryable<ApplicationDetailRelation>().Where(x => x.InvoiceId == invoice.Id)
.Select(x => new
{
x.DetailId,
x.InvoiceDetailId,
x.OriginalName,
}).ToListAsync();
var feeIds = invoice.Details.Select(x => x.FeeId);
var feesCodes = await TenantDb.Queryable<FeeCode>().Where(x => feeIds.Contains(x.Id))
.Select(x => new
{
FeeId = x.Id,
x.GoodName
}).ToListAsync();
invoice.InvoiceDetails = []; //新增的发票明细
List<InvoiceDetail> invoiceDetailList = []; //更新的发票明细
List<ApplicationDetailRelation> relationList = []; //新增的明细关系
foreach (var detail in invoice.Details)
{
var goodsName = feesCodes.Find(x => x.FeeId == detail.FeeId)?.GoodName; //转换为发票明细名称
var relation = relations.Find(x => string.Equals(x.OriginalName, goodsName, StringComparison.Ordinal));
if (relation == null) //需要新增发票明细
{
var invDetail = new InvoiceDetail
{
Id = SnowFlakeSingle.Instance.NextId(),
ApplicationId = invoice.Id,
Name = goodsName,
Quantity = 1,
TaxRate = invoice.TaxRate,
TaxUnitPrice = detail.ApplyAmount,
Category = DetailCategory.InvoiceIssuance
};
invDetail.TaxAmount = invDetail.TaxUnitPrice * invoice.TaxRate;
invDetail.UnitPrice = invDetail.TaxUnitPrice - invDetail.TaxAmount;
invDetail.Amount = invDetail.TaxUnitPrice * invDetail.Quantity;
invoice.InvoiceDetails.Add(invDetail);
relationList.Add(new ApplicationDetailRelation
{
DetailId = detail.Id,
InvoiceDetailId = invDetail.Id,
InvoiceId = invoice.Id,
OriginalName = goodsName
});
}
else //发票明细已存在,追加金额
{
//添加到更新列表
invoiceDetailList.Add(new InvoiceDetail
{
Id = relation.InvoiceDetailId,
TaxUnitPrice = detail.ApplyAmount
});
}
}
if (invoice.InvoiceDetails.Count > 0)
await TenantDb.Insertable(invoice.InvoiceDetails).ExecuteCommandAsync();
//执行发票明细金额更新
if (invoiceDetailList.Count > 0)
{
var ids2 = invoiceDetailList.Select(x => x.Id);
var updateList = await TenantDb.Queryable<InvoiceDetail>().Where(x => ids2.Contains(x.Id)).Select(x => new InvoiceDetail
{
Id = x.Id,
TaxUnitPrice = x.TaxUnitPrice,
TaxRate = x.TaxRate,
TaxAmount = x.TaxAmount,
UnitPrice = x.UnitPrice
}).ToListAsync();
foreach (var item in updateList)
{
var invDetail = invoiceDetailList.Find(x => x.Id == item.Id);
item.TaxUnitPrice += (invDetail?.TaxUnitPrice).GetValueOrDefault();
item.TaxAmount = item.TaxUnitPrice * invoice.TaxRate;
item.UnitPrice = item.TaxUnitPrice - item.TaxAmount;
}
await TenantDb.Updateable(updateList).UpdateColumns(x => new
{
x.TaxUnitPrice,
x.TaxAmount,
x.UnitPrice
}).ExecuteCommandAsync();
}
if (relationList.Count > 0)
await TenantDb.Insertable(relationList).ExecuteCommandAsync();
}
else if (option == BuildOption.Delete)
{
}
}
/// <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.Deleteable<ApplicationDetailRelation>().Where(x => ids.Contains(x.InvoiceDetailId)).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));
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 appIds = invoices.Select(x => x.Id);
if (deleteOption == DeleteOption.DetailOnly)
{
//删除明细需要同时变更发票明细
foreach (var item in invoices)
BuildInvoiceDetailAsync(item, BuildOption.Delete);
var excludeIds = invoices.SelectMany(x => x.Details).Select(x => x.Id);
var details = await TenantDb.Queryable<ApplicationDetail>().Where(x => appIds.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();
//删除明细之间的关系
await TenantDb.Deleteable<ApplicationDetailRelation>().Where(x => excludeIds.Contains(x.DetailId)).ExecuteCommandAsync();
}
else if (deleteOption == DeleteOption.Entire)
{
//删除发票主表则同时删除对应发票明细
await TenantDb.Deleteable<InvoiceDetail>().Where(x => appIds.Contains(x.ApplicationId)).ExecuteCommandAsync();
//删除明细之间的关系
await TenantDb.Deleteable<ApplicationDetailRelation>().Where(x => appIds.Contains(x.InvoiceId)).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));
}
}
}