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.

595 lines
25 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.Fee.Entity;
using DS.WMS.Core.Fee.Method;
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> : FeeServiceBase, 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.Type,
Category = i.Category,
InvoiceHeader = i.InvoiceHeader,
TaxID = i.TaxID,
CustomerAddTel = i.CustomerAddTel,
CustomerBank = i.CustomerBank,
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,
InvoiceApplicationList = SqlFunc.Subqueryable<ApplicationDetail>().InnerJoin<InvoiceApplication>((d, a) =>
d.ApplicationId == i.Id && d.Category == DetailCategory.InvoiceIssuance && d.RefId == a.Id)
.GroupBy((d, a) => a.ApplicationNO).ToList((d, a) => a.ApplicationNO)
});
if (!request.OtherQueryCondition.IsNullOrEmpty())
{
query = query.Where(i => i.InvoiceApplicationList.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);
}
/// <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 = 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,
Type = x.Type,
}).FirstAsync(x => x.Id == request.Invoice.Id);
if (dbValue != null && dbValue.IsLocked)
return DataResult<TEntity>.FailedWithDesc(nameof(MultiLanguageConst.InvoiceIsLocked));
}
var result = EnsureSettlement(request.Invoice, dbValue);
if (!result.Succeeded)
return DataResult<TEntity>.Failed(result.Message, result.MultiCode);
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
{
ApplicationId = x.ApplicationId,
DetailId = x.Id == 0 ? null : x.Id,
RecordId = x.RecordId,
CustomerName = x.CustomerName ?? invoice.CustomerName,
FeeId = x.FeeId,
FeeName = x.FeeName,
FeeType = x.FeeType,
ApplyAmount = x.Amount,
Currency = x.Currency,
ExchangeRate = x.ExchangeRate,
OriginalAmount = x.OriginalAmount,
OriginalCurrency = x.OriginalCurrency ?? (invoice.Currency.IsNullOrEmpty() ? x.Currency : invoice.Currency),
}).ToList();
//金额禁止为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);
invoice.ApplyAmount = invoice.Details.Sum(x => x.ApplyAmount);
invoice.AmountUppercase = new Money(invoice.InvoiceAmount).ToString();
invoice.OriginalAmount = invoice.Details.Sum(x => x.OriginalAmount);
invoice.OtherInvoiceAmount = invoice.Details.Where(x => x.Currency != RMB_CODE).Sum(x => x.OriginalAmount);
}
await TenantDb.Ado.BeginTranAsync();
try
{
//关联导航属性插入
if (invoice.Id == 0)
{
//创建时需要生成申请单编号
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
{
if (invoice.Details?.Count > 0)
{
await TenantDb.Insertable(invoice.Details).ExecuteCommandAsync();
//因需要重新生成明细,所以要先清空现有发票明细
await TenantDb.Deleteable<InvoiceDetail>().Where(x => x.ApplicationId == invoice.Id).ExecuteCommandAsync();
}
await TenantDb.Updateable(invoice).IgnoreColumns(x => new
{
x.BillNO,
x.IsLocked,
x.CreateBy,
x.CreateTime,
x.Deleted,
x.DeleteBy,
x.DeleteTime
}).ExecuteCommandAsync();
}
if (invoice.Details?.Count > 0)
{
//更新费用记录的已开票金额
var fees = invoice.Details.Select(x => new FeeRecord
{
Id = x.RecordId,
InvoiceAmount = x.OriginalAmount
}).ToList();
await TenantDb.Updateable(fees)
.PublicSetColumns(x => x.InvoiceAmount, "+")
.UpdateColumns(x => new { x.InvoiceAmount })
.ExecuteCommandAsync();
await CreateInvoiceDetailsAsync([invoice]);
if (invoice.InvoiceDetails?.Count > 0)
await TenantDb.Insertable(invoice.InvoiceDetails).ExecuteCommandAsync();
}
await OnSaveAsync(invoice);
await TenantDb.Ado.CommitTranAsync();
return DataResult<TEntity>.Success(await PostSaveAsync(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="dbValue">数据库值新增时为null</param>
/// <returns></returns>
protected virtual DataResult EnsureSettlement(TEntity invoice, TEntity? dbValue)
{
return DataResult.Success;
}
/// <summary>
/// 在保存前调用
/// </summary>
/// <param name="invoice">发票</param>
/// <returns></returns>
protected virtual Task<DataResult> PreSaveAsync(TEntity invoice)
{
return Task.FromResult(DataResult.Success);
}
/// <summary>
/// 在保存时调用
/// </summary>
/// <param name="invoice">要保存的发票</param>
/// <returns></returns>
protected virtual Task OnSaveAsync(TEntity invoice)
{
return Task.CompletedTask;
}
/// <summary>
/// 在保存完成后调用
/// </summary>
/// <param name="invoice">发票</param>
protected virtual Task<TEntity> PostSaveAsync(TEntity invoice)
{
return Task.FromResult(invoice);
}
/// <summary>
/// 删除发票
/// </summary>
/// <param name="ids">发票ID</param>
/// <returns></returns>
public async Task<DataResult> DeleteAsync(params long[] ids)
{
var apps = await TenantDb.Queryable<TEntity>().Where(x => ids.Contains(x.Id)).Select(x => new TEntity
{
Id = x.Id,
IsLocked = x.IsLocked
}).ToListAsync();
var details = await TenantDb.Queryable<ApplicationDetail>().Where(x => ids.Contains(x.ApplicationId)).Select(
x => new ApplicationDetail
{
Id = x.Id,
ApplicationId = x.ApplicationId,
RecordId = x.RecordId,
OriginalAmount = x.OriginalAmount
}).ToListAsync();
foreach (var app in apps)
app.Details = details.FindAll(x => x.ApplicationId == app.Id); await TenantDb.Ado.BeginTranAsync();
var result = PreDelete(apps);
if (!result.Succeeded)
return result;
try
{
await OnDeleteDetailAsync(apps, DeleteOption.Entire);
await TenantDb.DeleteNav<TEntity>(x => ids.Contains(x.Id)).Include(x => x.Details).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="ids">明细ID</param>
/// <returns></returns>
public async Task<DataResult> DeleteDetailAsync(params long[] ids)
{
var details = await TenantDb.Queryable<ApplicationDetail>().Where(x => ids.Contains(x.Id)).Select(
x => new ApplicationDetail
{
Id = x.Id,
ApplicationId = x.ApplicationId,
RecordId = x.RecordId,
OriginalAmount = x.OriginalAmount
}).ToListAsync();
var appIds = details.Select(x => x.ApplicationId).Distinct().ToList();
var apps = await TenantDb.Queryable<TEntity>().Where(x => appIds.Contains(x.Id)).Select(x => new TEntity
{
Id = x.Id,
IsLocked = x.IsLocked
}).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 TenantDb.Ado.BeginTranAsync();
try
{
await OnDeleteDetailAsync(apps, DeleteOption.DetailOnly);
await TenantDb.Deleteable(details).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)
{
if (deleteOption == DeleteOption.DetailOnly)
{
//删除明细需要同时变更发票明细
var appIds = invoices.Select(x => x.Id).ToList();
var excludeIds = invoices.SelectMany(x => x.Details).Select(x => x.Id).ToList();
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);
}
await TenantDb.Deleteable<InvoiceDetail>().Where(x => appIds.Contains(x.ApplicationId)).ExecuteCommandAsync();
await CreateInvoiceDetailsAsync(invoices);
var invDetails = invoices.SelectMany(x => x.InvoiceDetails).ToList();
if (invDetails.Count > 0)
await TenantDb.Insertable(invDetails).ExecuteCommandAsync();
await TenantDb.Updateable(invoices).UpdateColumns(x => new
{
x.ApplyAmount,
x.AmountUppercase
}).ExecuteCommandAsync();
}
//还原费用表的已开票金额
var fees = invoices.SelectMany(x => x.Details).Select(x => new FeeRecord { Id = x.RecordId, InvoiceAmount = x.OriginalAmount }).ToList();
await TenantDb.Updateable(fees)
.PublicSetColumns(it => it.InvoiceAmount, "-").UpdateColumns(x => new { x.InvoiceAmount })
.ExecuteCommandAsync();
}
/// <summary>
/// 生成发票明细
/// </summary>
/// <param name="invoices"></param>
/// <returns></returns>
protected async Task CreateInvoiceDetailsAsync(IEnumerable<TEntity> invoices)
{
foreach (var invoice in invoices)
{
if (invoice.Details?.Count > 0 && (invoice.InvoiceDetails == null || invoice.InvoiceDetails.Count == 0))
{
var ids = invoice.Details.Select(x => x.RecordId).ToList();
var feesCodes = await TenantDb.Queryable<FeeRecord>().InnerJoin<FeeCode>((f, c) => f.FeeId == c.Id && ids.Contains(f.Id))
.Select((f, c) => new
{
FeeId = c.Id,
c.GoodName
}).ToListAsync();
var list = feesCodes.GroupBy(x => x.GoodName).Select(x => new
{
Name = x.Key,
FeeIds = x.Select(y => y.FeeId)
}).ToList();
invoice.InvoiceDetails = new List<InvoiceDetail>(list.Count);
foreach (var item in list)
{
var invDetail = new InvoiceDetail
{
ApplicationId = invoice.Id,
Name = item.Name,
TaxRate = invoice.TaxRate,
TaxUnitPrice = invoice.Details.FindAll(x => item.FeeIds.Contains(x.FeeId)).Sum(x => x.ApplyAmount),
Category = DetailCategory.InvoiceIssuance
};
invDetail.TaxAmount = invDetail.TaxUnitPrice * invoice.TaxRate;
invDetail.UnitPrice = invDetail.TaxUnitPrice - invDetail.TaxAmount;
invoice.InvoiceDetails.Add(invDetail);
invoice.InvoiceAmount = invoice.InvoiceDetails.Sum(x => x.TaxUnitPrice);
}
}
}
}
/// <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));
}
}
}