|
|
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.Dto;
|
|
|
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<List<InvoiceDto>>> 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
|
|
|
}),
|
|
|
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;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return result;
|
|
|
}
|
|
|
|
|
|
/// <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));
|
|
|
}
|
|
|
}
|
|
|
|
|
|
}
|