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.

636 lines
28 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.Entity;
using DS.WMS.Core.Fee.Entity;
using DS.WMS.Core.Fee.Method;
using DS.WMS.Core.Settlement.Dtos;
using DS.WMS.Core.Settlement.Entity;
using DS.WMS.Core.Settlement.Interface;
using DS.WMS.Core.Sys.Interface;
using Microsoft.Extensions.DependencyInjection;
using SqlSugar;
namespace DS.WMS.Core.Settlement.Method
{
/// <summary>
/// 结算基础实现
/// </summary>
/// <typeparam name="TEntity">实体的类型声明</typeparam>
public class SettlementService<TEntity> : FeeServiceBase, ISettlementService<TEntity>
where TEntity : SettlementBase, new()
{
readonly Lazy<ICommonService> CommonService;
/// <summary>
/// 初始化
/// </summary>
/// <param name="serviceProvider">DI容器</param>
public SettlementService(IServiceProvider serviceProvider) : base(serviceProvider)
{
CommonService = new Lazy<ICommonService>(serviceProvider.GetRequiredService<ICommonService>());
}
#region 保存
/// <summary>
/// 提交结算单
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
public async Task<DataResult<TEntity>> SaveAsync(SettlementRequest<TEntity> request)
{
var settlement = request.Settlement;
if (settlement.SettlementDate == default)
settlement.SettlementDate = DateTime.Now;
if (settlement.Mode == 0)
return DataResult<TEntity>.FailedWithDesc(nameof(MultiLanguageConst.UnknownSettlementMode));
if (settlement.BillType == 0)
return DataResult<TEntity>.FailedWithDesc(nameof(MultiLanguageConst.UnknownSettlementType));
TEntity? dbValue = default;
if (request.Settlement.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.Settlement.Id);
if (dbValue != null && dbValue.IsLocked)
return DataResult<TEntity>.FailedWithDesc(nameof(MultiLanguageConst.SettlementIsLocked));
}
var result = EnsureSettlement(request.Settlement, dbValue);
if (!result.Succeeded)
return DataResult<TEntity>.Failed(result.Message, result.MultiCode);
List<ApplicationDetail>? details1 = null;
//自由结算
if (settlement.Mode == SettlementMode.FreeSettlement && request.Details?.Count > 0)
{
if (settlement.Id == 0)
{
var first = request.Details[0];
settlement.CustomerId = first.CustomerId;
settlement.CustomerName = first.CustomerName;
}
details1 = request.Details.Select(x => new ApplicationDetail
{
RefId = x.ApplicationId,
DetailId = x.Id,
RecordId = x.RecordId,
CustomerName = x.CustomerName ?? settlement.CustomerName,
FeeId = x.FeeId,
FeeName = x.FeeName,
FeeType = x.FeeType,
ApplyAmount = x.RestAmount.GetValueOrDefault(),
Currency = x.Currency,
ExchangeRate = x.ExchangeRate,
OriginalAmount = x.OriginalAmount,
OriginalCurrency = x.OriginalCurrency ?? (settlement.Currency.IsNullOrEmpty() ? x.Currency : settlement.Currency),
}).ToList();
}
//按付费/发票申请/自由业务结算
if (request.Documents?.Count > 0)
{
if (settlement.Id == 0)
{
var first = request.Documents[0];
settlement.CustomerId = first.CustomerId;
settlement.CustomerName = first.CustomerName;
}
var ids = request.Documents.Select(x => x.Id);
//收/付费申请结算
if (settlement.Mode == SettlementMode.Payment || settlement.Mode == SettlementMode.Charge)
{
var detailCategory = settlement.Mode == SettlementMode.Payment ? DetailCategory.PaidApplication : DetailCategory.ChargeApplication;
details1 = await TenantDb.Queryable<ApplicationDetail>().Where(x => ids.Contains(x.ApplicationId) && x.Category == detailCategory)
.Select(x => new ApplicationDetail
{
ApplicationId = settlement.Id,
RefId = x.ApplicationId,
DetailId = x.Id,
RecordId = x.RecordId,
Category = settlement.Mode == SettlementMode.Payment ? DetailCategory.PaidApplicationSettlement : DetailCategory.ChargeApplicationSettlement,
CustomerName = x.CustomerName ?? settlement.CustomerName,
FeeId = x.FeeId,
FeeName = x.FeeName,
FeeType = x.FeeType,
ApplyAmount = x.ApplyAmount - x.ProcessedAmount,
Currency = x.Currency,
OriginalAmount = x.OriginalAmount - x.OriginalProcessedAmount,
OriginalCurrency = x.OriginalCurrency,
ExchangeRate = x.ExchangeRate
}).ToListAsync();
if (!string.IsNullOrEmpty(settlement.Currency)) //指定结算币别
{
var details2 = details1.FindAll(x => x.OriginalCurrency != settlement.Currency);
foreach (var detail in details2)
{
detail.Currency = settlement.Currency;
var doc = request.Documents.Find(x => x.Id == detail.ApplicationId);
if (doc == null)
return DataResult<TEntity>.Failed("结算单据与费用明细不一致");
var exchange = doc.ExchangeRates?.Find(x => x.Currency == settlement.Currency);
if (exchange == null)
return DataResult<TEntity>.Failed($"未传入结算币别 {settlement.Currency} 与费用原币别 {detail.OriginalCurrency} 之间的汇率信息");
detail.ExchangeRate = exchange.ExchangeRate;
detail.ApplyAmount = Math.Round(exchange.ExchangeRate.GetValueOrDefault() * detail.OriginalAmount, 2, MidpointRounding.AwayFromZero);
}
}
}
//发票结算
else if (settlement.Mode == SettlementMode.InvoiceSettlement || settlement.Mode == SettlementMode.PaymentInvoiceSettlement)
{
details1 = await TenantDb.Queryable<ApplicationDetail>().Where(x => ids.Contains(x.ApplicationId) && x.Category == DetailCategory.InvoiceIssuance)
.Select(x => new ApplicationDetail
{
ApplicationId = settlement.Id,
RefId = x.ApplicationId,
DetailId = x.Id,
RecordId = x.RecordId,
Category = DetailCategory.InvoiceSettlement,
CustomerName = x.CustomerName ?? settlement.CustomerName,
FeeId = x.FeeId,
FeeName = x.FeeName,
FeeType = x.FeeType,
ApplyAmount = x.ApplyAmount - x.ProcessedAmount,
Currency = x.Currency,
OriginalAmount = x.OriginalAmount - x.OriginalProcessedAmount,
OriginalCurrency = x.OriginalCurrency,
ExchangeRate = x.ExchangeRate
}).ToListAsync();
if (settlement.Currency != FeeCurrency.RMB_CODE) //发票结算非人民币需做转换
{
var details2 = details1.FindAll(x => x.OriginalCurrency != settlement.Currency);
foreach (var detail in details2)
{
detail.Currency = settlement.Currency;
var doc = request.Documents.Find(x => x.Id == detail.ApplicationId);
if (doc == null)
return DataResult<TEntity>.Failed("结算单据与费用明细不一致");
var exchange = doc.ExchangeRates?.Find(x => x.Currency == settlement.Currency);
if (exchange == null)
return DataResult<TEntity>.Failed($"使用发票做结算时,非 {FeeCurrency.RMB_CODE} 的费用必须指定汇率");
detail.ExchangeRate = exchange.ExchangeRate;
detail.ApplyAmount = Math.Round(exchange.ExchangeRate.GetValueOrDefault() * detail.OriginalAmount, 2, MidpointRounding.AwayFromZero);
}
}
}
//按业务自由结算
else if (settlement.Mode == SettlementMode.FreeSettlement)
{
var types = request.Documents.Select(x => x.BusinessType.GetValueOrDefault());
var custIds = request.Documents.Select(x => x.CustomerId);
details1 = await TenantDb.Queryable<FeeRecord>().Where(f => ids.Contains(f.BusinessId) && types.Contains(f.BusinessType) && custIds.Contains(f.CustomerId) &&
(f.FeeStatus == FeeStatus.AuditPassed || f.FeeStatus == FeeStatus.PartialSettlement) && (f.Amount - f.SettlementAmount - f.OrderAmount + f.OrderSettlementAmount) != 0)
.Select(f => new ApplicationDetail
{
ApplicationId = settlement.Id,
BusinessId = f.BusinessId,
BusinessType = f.BusinessType,
RecordId = f.Id,
Category = f.FeeType == FeeType.Payable ? DetailCategory.PaidFreeSettlement : DetailCategory.ChargeFreeSettlement,
CustomerName = f.CustomerName ?? settlement.CustomerName,
FeeId = f.FeeId,
FeeName = f.FeeName,
FeeType = f.FeeType,
ApplyAmount = f.Amount - f.SettlementAmount - f.OrderAmount + f.OrderSettlementAmount,
Currency = f.Currency,
OriginalAmount = f.Amount - f.SettlementAmount - f.OrderAmount + f.OrderSettlementAmount,
OriginalCurrency = f.Currency,
ExchangeRate = f.ExchangeRate
}).ToListAsync();
if (!string.IsNullOrEmpty(settlement.Currency)) //指定结算币别
{
var details2 = details1.FindAll(x => x.OriginalCurrency != settlement.Currency);
foreach (var detail in details2)
{
detail.Currency = settlement.Currency;
var doc = request.Documents.Find(x => x.Id == detail.BusinessId && x.BusinessType == detail.BusinessType && x.CustomerName == x.CustomerName);
if (doc == null)
return DataResult<TEntity>.Failed("结算单据与费用明细不一致");
var exchange = doc.ExchangeRates?.Find(x => x.Currency == detail.OriginalCurrency);
if (exchange == null)
return DataResult<TEntity>.Failed($"未传入结算币别 {settlement.Currency} 与费用原币别 {detail.OriginalCurrency} 之间的汇率信息");
detail.ExchangeRate = exchange.ExchangeRate;
detail.ApplyAmount = Math.Round(exchange.ExchangeRate.GetValueOrDefault() * detail.OriginalAmount, 2, MidpointRounding.AwayFromZero);
}
}
}
//执行结算费用分配
foreach (var doc in request.Documents)
{
var details2 = settlement.Mode != SettlementMode.FreeSettlement ?
details1.Where(x => x.RefId == doc.Id).OrderBy(x => x.ApplyAmount) :
details1.Where(x => x.BusinessId == doc.Id && x.BusinessType == doc.BusinessType && x.CustomerName == doc.CustomerName).OrderBy(x => x.ApplyAmount);
var rmbDetails = details2.Where(x => x.Currency == FeeCurrency.RMB_CODE);
var totalRMB = rmbDetails.Sum(x => x.ApplyAmount);
doc.SettlementRMB ??= totalRMB;
if (doc.SettlementRMB > totalRMB)
return DataResult<TEntity>.Failed("人民币结算金额不能大于剩余人民币金额");
if (totalRMB < doc.SettlementRMB)
{
var rest = totalRMB - doc.SettlementRMB.GetValueOrDefault();
foreach (var detail in rmbDetails)
{
if (rest == 0)
break;
detail.ApplyAmount = detail.ApplyAmount - rest;
rest = detail.ApplyAmount - rest;
}
}
var usdDetails = details2.Where(x => x.Currency == FeeCurrency.USD_CODE);
var totalUSD = usdDetails.Sum(x => x.ApplyAmount);
doc.SettlementUSD ??= totalUSD;
if (doc.SettlementUSD > totalUSD)
return DataResult<TEntity>.Failed("美元结算金额不能大于剩余美元金额");
if (totalUSD < doc.SettlementUSD)
{
var rest = totalUSD - doc.SettlementUSD.GetValueOrDefault();
foreach (var detail in usdDetails)
{
if (rest == 0)
break;
detail.ApplyAmount = detail.ApplyAmount - rest;
rest = detail.ApplyAmount - rest;
}
}
var otherDetails = details2.Where(x => x.Currency != FeeCurrency.RMB_CODE && x.Currency != FeeCurrency.USD_CODE);
var total = rmbDetails.Sum(x => x.ApplyAmount);
doc.SettlementOther ??= total;
if (doc.SettlementOther > total)
return DataResult<TEntity>.Failed("其他结算金额不能大于剩余其他金额");
if (total < doc.SettlementOther)
{
var rest = total - doc.SettlementOther.GetValueOrDefault();
foreach (var detail in otherDetails)
{
if (rest == 0)
break;
detail.ApplyAmount = detail.ApplyAmount - rest;
rest = detail.ApplyAmount - rest;
}
}
}
}
if (details1?.Count > 0)
settlement.Details.AddRange(details1);
//金额禁止为0
if (settlement.Details.Exists(x => x.ApplyAmount == 0 || x.OriginalAmount == 0))
return DataResult<TEntity>.FailedWithDesc(nameof(MultiLanguageConst.AmountCannotBeZero));
if (settlement.Details.Exists(x => x.OriginalCurrency.IsNullOrEmpty()))
return DataResult<TEntity>.FailedWithDesc(nameof(MultiLanguageConst.OriginalCurrencyCanNotNull));
settlement.Amount = settlement.Details.Sum(x => x.ApplyAmount);
result = await PreSaveAsync(settlement);
if (!result.Succeeded)
return DataResult<TEntity>.Failed(result.Message, result.MultiCode);
var details = settlement.Details.FindAll(x => x.Id == 0);
await TenantDb.Ado.BeginTranAsync();
try
{
//关联导航属性插入
if (settlement.Id == 0)
{
//创建时需要生成申请单编号
var sequence = CommonService.Value.GetSequenceNext<TEntity>();
if (!sequence.Succeeded)
{
return DataResult<TEntity>.Failed(sequence.Message, MultiLanguageConst.SequenceSetNotExist);
}
settlement.ApplicationNO = "ST" + SnowFlakeSingle.Instance.NextId(); //申请编号
settlement.SettlementNO = sequence.Data; //结算单号
await TenantDb.InsertNav(settlement).Include(x => x.Details).ExecuteCommandAsync();
}
else
{
if (details.Count > 0)
await TenantDb.Insertable(details).ExecuteCommandAsync();
await TenantDb.Updateable(settlement).IgnoreColumns(x => new
{
x.ApplicationNO,
x.SettlementNO,
x.IsLocked,
x.CreateBy,
x.CreateTime,
x.Deleted,
x.DeleteBy,
x.DeleteTime
}).ExecuteCommandAsync();
}
await OnSaveAsync(settlement);
if (details.Count > 0)
{
//更新费用记录的已结算金额
var fees = details.Select(x => new FeeRecord
{
Id = x.RecordId,
SettlementAmount = x.OriginalAmount
}).ToList();
await TenantDb.Updateable(fees)
.PublicSetColumns(x => x.SettlementAmount, "+")
.UpdateColumns(x => new { x.SettlementAmount })
.ExecuteCommandAsync();
}
await TenantDb.Ado.CommitTranAsync();
await PostSaveAsync(settlement);
return DataResult<TEntity>.Success(settlement);
}
catch (Exception ex)
{
await TenantDb.Ado.RollbackTranAsync();
await ex.LogAsync(Db);
return DataResult<TEntity>.FailedWithDesc(nameof(MultiLanguageConst.Operation_Failed));
}
}
/// <summary>
/// 用于结算单的状态检查
/// </summary>
/// <param name="settlement">提交的结算单</param>
/// <param name="dbValue">数据库值新增时为null</param>
/// <returns></returns>
protected virtual DataResult EnsureSettlement(TEntity settlement, TEntity? dbValue)
{
return DataResult.Success;
}
/// <summary>
/// 在保存前调用
/// </summary>
/// <param name="settlement">结算单</param>
/// <returns></returns>
protected virtual Task<DataResult> PreSaveAsync(TEntity settlement)
{
return Task.FromResult(DataResult.Success);
}
/// <summary>
/// 在保存时调用
/// </summary>
/// <param name="settlement">已保存的结算单</param>
/// <returns></returns>
protected virtual Task OnSaveAsync(TEntity settlement)
{
return Task.CompletedTask;
}
/// <summary>
/// 在保存完成后调用
/// </summary>
/// <param name="settlement">申请单</param>
protected virtual async Task<TEntity> PostSaveAsync(TEntity settlement)
{
if (settlement.Details?.Count > 0)
{
//更新费用记录状态
var ids = settlement.Details.Where(x => x.RecordId > 0).Select(x => x.RecordId);
if (!ids.Any())
return settlement;
UpdateFeeStatus(ids);
}
return settlement;
}
#endregion
#region 删除
/// <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,
DetailId = x.DetailId,
ApplyAmount = x.ApplyAmount,
OriginalAmount = x.OriginalAmount,
ProcessedAmount = x.ProcessedAmount,
OriginalProcessedAmount = x.OriginalProcessedAmount
}).ToListAsync();
if (details.Count == 0)
return DataResult.FailedWithDesc(nameof(MultiLanguageConst.EmptyData));
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,
Mode = x.Mode
}).ToListAsync();
foreach (var app in apps)
{
app.Details = details.FindAll(x => x.ApplicationId == app.Id);
app.Amount = app.Details.Sum(x => x.ApplyAmount);
}
var result = PreDelete(apps);
if (!result.Succeeded)
return result;
await TenantDb.Ado.BeginTranAsync();
try
{
await OnDeleteDetailAsync(apps);
await TenantDb.Deleteable(details).ExecuteCommandAsync();
//更新结算单的结算总金额
await TenantDb.Updateable(apps)
.PublicSetColumns(it => it.Amount, "-").UpdateColumns(x => new { x.Amount })
.ExecuteCommandAsync();
await TenantDb.Ado.CommitTranAsync();
//更新费用结算状态
UpdateFeeStatus(details.Select(x => x.RecordId));
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> DeleteAsync(params long[] ids)
{
var apps = await TenantDb.Queryable<TEntity>().Where(x => ids.Contains(x.Id)).Select(x => new TEntity
{
Id = x.Id,
Mode = x.Mode
}).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,
RecordId = x.RecordId,
DetailId = x.DetailId,
ApplyAmount = x.ApplyAmount,
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);
await TenantDb.DeleteNav<TEntity>(x => ids.Contains(x.Id)).Include(x => x.Details).ExecuteCommandAsync();
await TenantDb.Ado.CommitTranAsync();
//更新费用结算状态
UpdateFeeStatus(details.Select(x => x.RecordId));
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="settlements">结算单</param>
/// <returns></returns>
protected virtual DataResult PreDelete(List<TEntity> settlements)
{
return DataResult.Success;
}
/// <summary>
/// 在执行删除结算单或其明细时调用
/// </summary>
/// <param name="settlements">结算单及其明细</param>
/// <returns></returns>
protected virtual async Task OnDeleteDetailAsync(List<TEntity> settlements)
{
//还原费用表的已结算金额
var fees = settlements.SelectMany(x => x.Details).Select(x => new FeeRecord { Id = x.RecordId, SettlementAmount = x.OriginalAmount }).ToList();
await TenantDb.Updateable(fees)
.PublicSetColumns(it => it.SettlementAmount, "-").UpdateColumns(x => new { x.SettlementAmount })
.ExecuteCommandAsync();
}
#endregion
/// <summary>
/// 设置结算单的锁定状态
/// </summary>
/// <param name="isLocked">是否锁定</param>
/// <param name="ids">结算ID</param>
/// <returns></returns>
public async Task<DataResult> SetLockAsync(bool isLocked, params long[] ids)
{
var dtNow = DateTime.Now;
var userId = long.Parse(User.UserId);
List<TEntity> list = null;
if (isLocked)
{
list = ids.Select(x => new TEntity
{
Id = x,
IsLocked = isLocked,
LockTime = dtNow,
LockUserId = userId
}).ToList();
}
else
{
list = ids.Select(x => new TEntity
{
Id = x,
IsLocked = isLocked,
UnlockTime = dtNow,
UnlockUserId = userId
}).ToList();
}
return await TenantDb.Updateable(list).UpdateColumns(x => new
{
x.IsLocked,
x.LockTime,
x.UnlockTime,
x.LockUserId,
x.UnlockUserId
}).ExecuteCommandAsync() > 0 ?
DataResult.Success : DataResult.FailedWithDesc(nameof(MultiLanguageConst.Operation_Failed));
}
protected virtual Task<List<SettlementDetailDto>> GetSettlementDetails(long id)
{
return Task.FromResult(new List<SettlementDetailDto>());
}
}
}