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.

622 lines
24 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 System.Linq.Expressions;
using DS.Module.Core;
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.Op.Entity;
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()
{
protected readonly Lazy<ICommonService> SeqService;
/// <summary>
/// 初始化
/// </summary>
/// <param name="serviceProvider">DI容器</param>
public SettlementService(IServiceProvider serviceProvider) : base(serviceProvider)
{
SeqService = new Lazy<ICommonService>(serviceProvider.GetRequiredService<ICommonService>());
}
#region 保存
internal static DataResult AssignAmount(List<ApplicationDetail> details, decimal stlAmount, string currency)
{
if (details.Count == 0)
return DataResult.Success;
if (stlAmount == 0)
return DataResult.Failed("结算金额不能为零");
var totalAmount = details.Sum(x => x.ApplyAmount);
if (Math.Abs(stlAmount) > totalAmount)
return DataResult.Failed("申请结算金额不能大于剩余结算金额");
decimal rest = stlAmount;
decimal currentAmount = default;
foreach (var detail in details)
{
if (rest <= 0)
break;
if (totalAmount != stlAmount) //非全额结算
{
if (rest >= detail.ApplyAmount)
{
rest -= detail.ApplyAmount;
}
else
{
detail.ApplyAmount = rest;
rest = 0;
}
}
currentAmount = detail.ApplyAmount;
if (detail.Currency == currency)
{
if (detail.Currency == detail.OriginalCurrency)
{
detail.OriginalAmount = detail.ApplyAmount;
}
else
{
detail.OriginalAmount = detail.ApplyAmount / detail.ExchangeRate.Value;
}
detail.ExchangeRate = 1;
}
else //结算币别与费用币别不一致
{
detail.ExchangeRate ??= 1;
detail.ApplyAmount *= detail.ExchangeRate.Value;
if (detail.OriginalCurrency == detail.Currency)
{
detail.OriginalAmount = currentAmount;
}
else
{
detail.OriginalAmount = currentAmount * detail.ExchangeRate.Value;
}
detail.Currency = currency;
}
detail.Assigned = true;
}
return DataResult.Success;
}
/// <summary>
/// 提交结算单
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
public virtual async Task<DataResult<TEntity>> SaveAsync(SettlementRequest<TEntity> request)
{
var settlement = request.Settlement;
if (settlement.SettlementDate == default)
settlement.SettlementDate = DateTime.Now;
//金额禁止为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));
var 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 = SeqService.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();
}
//计算结算总金额
settlement.Amount = await TenantDb.Queryable<ApplicationDetail>().Where(x => x.ApplicationId == settlement.Id).SumAsync(x => x.ApplyAmount);
await TenantDb.Updateable(settlement).UpdateColumns(x => x.Amount).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,
Currency = x.Currency,
OriginalCurrency = x.OriginalCurrency
}).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,
Currency = x.Currency,
OriginalCurrency = x.OriginalCurrency
}).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="ids">费用记录ID</param>
/// <remarks>此方法内部将始终异步执行,请确保在调用前已提交数据库事务等必要的操作。</remarks>
protected internal void UpdateFeeStatus(IEnumerable<long> ids)
{
var task1 = Task.Factory.StartNew(UpdateFeeStatusCore, ids, CancellationToken.None);
task1.ContinueWith(t => UpdateBizStatusCore(t.Result), TaskContinuationOptions.OnlyOnRanToCompletion);
}
private List<FeeRecord> UpdateFeeStatusCore(object? state)
{
if (state == null)
return [];
var ids = (IEnumerable<long>)state;
var fees = TenantDb.Queryable<FeeRecord>().Where(x => ids.Contains(x.Id))
.Select(x => new FeeRecord
{
Id = x.Id,
BusinessId = x.BusinessId,
BusinessType = x.BusinessType,
FeeStatus = x.FeeStatus,
Amount = x.Amount,
SettlementAmount = x.SettlementAmount
}).ToList();
if (fees.Count == 0)
return [];
List<FeeRecord> list = new(fees.Count);
foreach (var item in fees)
{
if (item.SettlementAmount == item.Amount)
{
item.FeeStatus = FeeStatus.SettlementCompleted;
list.Add(item);
}
else if (item.SettlementAmount == 0)
{
item.FeeStatus = FeeStatus.AuditPassed;
list.Add(item);
}
else if (item.Amount != item.SettlementAmount)
{
item.FeeStatus = FeeStatus.PartialSettlement;
list.Add(item);
}
}
TenantDb.Updateable(list).UpdateColumns(x => new { x.FeeStatus }).ExecuteCommand();
return list;
}
private void UpdateBizStatusCore(List<FeeRecord> list)
{
var bizIds = list.Select(x => x.BusinessId);
var types = list.Select(x => x.BusinessType).Distinct();
var fees2 = TenantDb.Queryable<FeeRecord>().Where(x => bizIds.Contains(x.BusinessId) && types.Contains(x.BusinessType))
.Select(x => new { x.BusinessId, x.BusinessType, x.FeeType, x.FeeStatus }).ToList();
if (fees2.Count == 0)
return;
var gpList = fees2.GroupBy(x => new { x.BusinessId, x.BusinessType });
foreach (var gp in gpList)
{
BusinessFeeStatus biz = new() { BusinessId = gp.Key.BusinessId, BusinessType = gp.Key.BusinessType };
var upt = TenantDb.Updateable(biz).WhereColumns(x => new { x.BusinessId, x.BusinessType });
//应收
var arList = gp.Where(x => x.FeeType == FeeType.Receivable).ToList();
if (arList.Count > 0)
{
if (arList.All(x => x.FeeStatus == FeeStatus.SettlementCompleted))
{
biz.ARFeeStatus = BillFeeStatus.SettlementCompleted;
upt = upt.UpdateColumns(x => x.ARFeeStatus);
}
else if (arList.Any(x => x.FeeStatus == FeeStatus.PartialSettlement))
{
biz.ARFeeStatus = BillFeeStatus.PartialSettlement;
upt = upt.UpdateColumns(x => x.ARFeeStatus);
}
}
//应付
var apList = gp.Where(x => x.FeeType == FeeType.Payable).ToList();
if (apList.Count > 0)
{
if (apList.All(x => x.FeeStatus == FeeStatus.SettlementCompleted))
{
biz.APFeeStatus = BillFeeStatus.SettlementCompleted;
upt = upt.UpdateColumns(x => x.APFeeStatus);
}
else if (apList.Any(x => x.FeeStatus == FeeStatus.PartialSettlement))
{
biz.APFeeStatus = BillFeeStatus.PartialSettlement;
upt = upt.UpdateColumns(x => x.APFeeStatus);
}
}
upt.ExecuteCommand();
}
}
/// <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));
}
/// <summary>
/// 返回针对结算费用明细及其关联业务的查询对象
/// </summary>
/// <param name="expr1">关联条件1</param>
/// <returns>查询对象</returns>
protected virtual ISugarQueryable<SettlementDetailDto> CreateApplicationDetailQuery(
Expression<Func<ApplicationDetail, FeeRecord, SeaExport, bool>>? expr1)
{
//海运出口
var query1 = TenantDb.Queryable<ApplicationDetail>()
.InnerJoin<FeeRecord>((d, f) => d.RecordId == f.Id)
.LeftJoin<SeaExport>((d, f, s) => f.BusinessId == s.Id && f.BusinessType == BusinessType.OceanShippingExport)
.Where(expr1)
.Select((d, f, s) => new SettlementDetailDto
{
//---------------明细表--------------
Id = d.Id,
ApplicationId = d.ApplicationId,
DetailId = d.DetailId,
RefId = d.RefId,
RecordId = d.RecordId,
FeeType = d.FeeType,
FeeId = d.FeeId,
FeeName = d.FeeName,
Currency = d.Currency,
ApplyAmount = d.ApplyAmount,
ExchangeRate = d.ExchangeRate,
OriginalAmount = d.OriginalAmount,
OriginalCurrency = d.OriginalCurrency,
ProcessedAmount = d.ProcessedAmount,
OriginalProcessedAmount = d.OriginalProcessedAmount,
RestAmount = d.ProcessedAmount - d.OriginalProcessedAmount,
//---------------费用表--------------
OriginalRate = f.ExchangeRate,
Amount = f.Amount,
AccTaxRate = f.AccTaxRate,
CustomerId = f.CustomerId,//费用对象ID
CustomerName = f.CustomerName,
OrderAmount = f.OrderAmount,
InvoiceAmount = f.InvoiceAmount,
SettlementAmount = f.SettlementAmount,
OrderSettlementAmount = f.OrderSettlementAmount,
OrderInvSettlementAmount = f.OrderInvSettlementAmount,
BusinessId = f.BusinessId,
BusinessType = f.BusinessType,
Note = f.Note,
//---------------业务表--------------
AccountDate = s.AccountDate,
CntrTotal = s.CntrTotal,
CustomerNo = s.CustomerNo,
ClientName = s.CustomerName, //委托单位
ETD = s.ETD,
ETA = s.ETA,
Forwarder = s.Forwarder,
Operator = s.OperatorName,
MBLNO = s.MBLNO,
HBLNO = s.HBLNO,
LoadPort = s.LoadPort,
SaleDeptId = s.SaleDeptId,
SaleName = s.Sale,//揽货人
Carrier = s.Carrier,
Vessel = s.Vessel,//船名
Voyage = s.Voyno,//航次
BookingNo = s.BookingNo,
SourceName = s.SourceName,
Enterprise = s.Enterprise,
CustomNo = s.CustomNo
});
//海运进口
return TenantDb.UnionAll(new List<ISugarQueryable<SettlementDetailDto>> { query1 });
}
protected virtual Task<List<SettlementDetailGroup>> GetSettlementDetails(long id)
{
return Task.FromResult(new List<SettlementDetailGroup>());
}
}
}