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
{
///
/// 发票服务基类
///
///
public class InvoiceService : FeeServiceBase, IInvoiceService
where TEntity : Entity.Invoice, new()
{
readonly Lazy CommonService;
///
/// 初始化
///
///
public InvoiceService(IServiceProvider provider) : base(provider)
{
CommonService = new Lazy(provider.GetRequiredService());
}
///
/// 获取分页列表
///
///
///
public async Task>> GetListAsync(PageRequest request)
{
var query = TenantDb.Queryable()
.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().InnerJoin((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().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().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;
}
///
/// 获取发票详情
///
/// 发票ID
///
public async Task> GetAsync(long id)
{
var invoice = await TenantDb.Queryable().Select().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().Where(
x => x.ApplicationId == id && x.Category == DetailCategory.InvoiceIssuance).ToListAsync();
}
return DataResult.Success(invoice);
}
///
/// 提交发票开票
///
/// 请求参数
///
public async Task> SaveAsync(InvoiceRequest 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().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.FailedWithDesc(nameof(MultiLanguageConst.InvoiceIsLocked));
}
var result = EnsureSettlement(request.Invoice, dbValue);
if (!result.Succeeded)
return DataResult.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.FailedWithDesc(nameof(MultiLanguageConst.AmountCannotBeZero));
if (invoice.Details.Any(x => x.OriginalCurrency.IsNullOrEmpty()))
return DataResult.FailedWithDesc(nameof(MultiLanguageConst.OriginalCurrencyCanNotNull));
if (invoice.Details.Any(x => x.Currency != invoice.Currency && x.ExchangeRate == null))
return DataResult.FailedWithDesc(nameof(MultiLanguageConst.NeedExchangeRate));
result = await PreSaveAsync(invoice);
if (!result.Succeeded)
return DataResult.Failed(result.Message, result.MultiCode);
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();
if (!sequence.Succeeded)
{
return DataResult.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().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.Success(await PostSaveAsync(invoice));
}
catch (Exception ex)
{
await TenantDb.Ado.RollbackTranAsync();
await ex.LogAsync(Db);
return DataResult.FailedWithDesc(nameof(MultiLanguageConst.Operation_Failed));
}
}
///
/// 用于发票的状态检查
///
/// 提交的发票
/// 数据库值,新增时为null
///
protected virtual DataResult EnsureSettlement(TEntity invoice, TEntity? dbValue)
{
return DataResult.Success;
}
///
/// 在保存前调用
///
/// 发票
///
protected virtual Task PreSaveAsync(TEntity invoice)
{
return Task.FromResult(DataResult.Success);
}
///
/// 在保存时调用
///
/// 要保存的发票
///
protected virtual Task OnSaveAsync(TEntity invoice)
{
return Task.CompletedTask;
}
///
/// 在保存完成后调用
///
/// 发票
protected virtual Task PostSaveAsync(TEntity invoice)
{
return Task.FromResult(invoice);
}
///
/// 删除发票
///
/// 发票ID
///
public async Task DeleteAsync(params long[] ids)
{
var apps = await TenantDb.Queryable().Where(x => ids.Contains(x.Id)).Select(x => new TEntity
{
Id = x.Id,
IsLocked = x.IsLocked
}).ToListAsync();
var details = await TenantDb.Queryable().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(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));
}
}
///
/// 删除发票费用明细
///
/// 明细ID
///
public async Task DeleteDetailAsync(params long[] ids)
{
var details = await TenantDb.Queryable().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().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));
}
}
///
/// 在删除发票或其明细之前调用,用于检查状态
///
/// 发票
///
protected virtual DataResult PreDelete(List invoices)
{
if (invoices.Any(x => x.IsLocked))
return DataResult.FailedWithDesc(nameof(MultiLanguageConst.InvoiceIsLocked));
return DataResult.Success;
}
///
/// 在执行删除发票或其明细时调用
///
/// 发票及其明细
/// 发票删除选项
///
protected virtual async Task OnDeleteDetailAsync(List 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().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().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();
}
///
/// 生成发票明细
///
///
///
protected async Task CreateInvoiceDetailsAsync(IEnumerable 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().InnerJoin((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(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);
}
}
}
}
///
/// 设置发票的锁定状态
///
/// 是否锁定
/// 发票ID
///
public async Task SetLockAsync(bool isLocked, params long[] ids)
{
var dt = DateTime.Now;
var userId = long.Parse(User.UserId);
var list = ids.Select(x => new TEntity
{
Id = x,
IsLocked = isLocked,
LockTime = isLocked ? dt : null,
LockUserId = isLocked ? userId : null
}).ToList();
int rows = await TenantDb.Updateable(list)
.UpdateColumns(x => new { x.IsLocked, x.LockTime, x.LockUserId }).ExecuteCommandAsync();
return rows > 0 ? DataResult.Success : DataResult.FailedWithDesc(nameof(MultiLanguageConst.Operation_Failed));
}
///
/// 设置发票的作废状态
///
/// 是否锁定
/// 发票ID
///
public async Task SetCancelAsync(bool isCancelled, params long[] ids)
{
var dt = DateTime.Now;
var userId = long.Parse(User.UserId);
var list = ids.Select(x => new TEntity
{
Id = x,
IsCancelled = isCancelled,
CancelTime = isCancelled ? dt : null,
CancelUserId = isCancelled ? userId : null
}).ToList();
int rows = await TenantDb.Updateable(list)
.UpdateColumns(x => new { x.IsCancelled, x.CancelTime, x.CancelUserId }).ExecuteCommandAsync();
return rows > 0 ? DataResult.Success : DataResult.FailedWithDesc(nameof(MultiLanguageConst.Operation_Failed));
}
}
}