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.

547 lines
26 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.Text;
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.Code.Entity;
using DS.WMS.Core.Fee.Entity;
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.Entity;
using Masuit.Tools.Systems;
using SqlSugar;
namespace DS.WMS.Core.Settlement.Method
{
/// <summary>
/// 收/付费申请结算服务
/// </summary>
public class ApplicationSettlementService : SettlementService<ApplicationSettlement>, IApplicationSettlementService
{
/// <summary>
/// 初始化
/// </summary>
/// <param name="provider"></param>
public ApplicationSettlementService(IServiceProvider provider) : base(provider)
{
}
/// <summary>
/// 获取结算单分页列表
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
public async Task<DataResult<List<PaymentSettlementDto>>> GetListAsync(PageRequest request)
{
var query = TenantDb.Queryable<ApplicationSettlement>().Select(x => new PaymentSettlementDto
{
SettlementTypeName = x.SettlementType.StlName, //结算方式
RMBAmount = SqlFunc.Subqueryable<ApplicationDetail>().Where(y => y.ApplicationId == x.Id && y.Currency == FeeCurrency.RMB_CODE)
.Select(y => SqlFunc.AggregateSum(y.ApplyAmount)),
USDAmount = SqlFunc.Subqueryable<ApplicationDetail>().Where(y => y.ApplicationId == x.Id && y.Currency == FeeCurrency.USD_CODE)
.Select(y => SqlFunc.AggregateSum(y.ApplyAmount)),
OtherAmount = SqlFunc.Subqueryable<ApplicationDetail>().Where(y => y.ApplicationId == x.Id && y.Currency != FeeCurrency.RMB_CODE && y.Currency != FeeCurrency.USD_CODE)
.Select(y => SqlFunc.AggregateSum(y.ApplyAmount)),
CustomerAccount = x.CustomerBank.AccountName + "/" + x.CustomerBank.Currency,
CustomerBankName = x.CustomerBank.BankName,
//未开票
UnInvoiceList = SqlFunc.Subqueryable<ApplicationDetail>().InnerJoin<FeeRecord>((d, f) => d.RecordId == f.Id && (f.Amount - f.InvoiceAmount - f.OrderInvoiceAmount) != 0)
.GroupBy((d, f) => f.Currency).ToList((d, f) => new CurrencyAmount { Currency = f.Currency, Amount = f.Amount - f.InvoiceAmount - f.OrderInvoiceAmount }),
}, true);
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.UnlockUserId.HasValue).Select(x => x.UnlockUserId.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.Select(x => x.SaleDeptId).Distinct().ToList();
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.LockUser = users.Find(x => x.Id == item.LockUserId)?.UserName;
item.UnlockUser = users.Find(x => x.Id == item.UnlockUserId)?.UserName;
item.SaleDeptName = orgs.Find(x => x.Id == item.SaleDeptId)?.OrgName;
}
}
return result;
}
/// <summary>
/// 获取结算单详情
/// </summary>
/// <param name="id">结算单ID</param>
/// <returns></returns>
public async Task<DataResult<PaymentSettlementDto>> GetAsync(long id)
{
var model = await TenantDb.Queryable<ApplicationSettlement>().Select(x => new PaymentSettlementDto
{
}, true).FirstAsync(x => x.Id == id);
if (model != null)
{
model.Details = await GetSettlementDetails(id);
if (model.Details.Count > 0)
{
//关联用户名称
var userIds = model.Details.Select(x => x.CreateBy).Distinct();
var users = await Db.Queryable<SysUser>().Where(x => userIds.Contains(x.Id)).Select(x => new { x.Id, x.UserName }).ToListAsync();
foreach (var item in model.Details)
{
item.CreateByName = users.Find(x => x.Id == item.CreateBy)?.UserName;
}
}
}
return DataResult<PaymentSettlementDto>.Success(model);
}
protected virtual async Task<List<SettlementDetailDto>> GetSettlementDetails(long id)
{
var list = await TenantDb.Queryable<ApplicationDetail>()
.InnerJoin<ApplicationDetail>((d, pd) => d.DetailId == pd.Id)
.InnerJoin<PaymentApplication>((d, pd, pa) => pd.ApplicationId == pa.Id)
.Where(d => d.ApplicationId == id)
.Select((d, pd, pa) => new
{
d.Id,
d.ApplicationId,
d.ApplyAmount,
d.Currency,
d.OriginalCurrency,
d.OriginalAmount,
pa.ApplicationNO,
pa.Status,
pa.CreateTime,
pa.CreateBy,
pa.PaymentDate,
pa.Note
}).ToListAsync();
var details = new List<SettlementDetailDto>();
if (list.Count == 0)
return details;
var gp = list.GroupBy(d => d.ApplicationId);
var uids = list.Select(x => x.CreateBy).Distinct();
var users = await Db.Queryable<SysUser>().Where(x => uids.Contains(x.Id)).Select(x => new
{
x.Id,
x.UserName
}).ToListAsync();
foreach (var g in gp)
{
var firstItem = g.FirstOrDefault();
var dto = new SettlementDetailDto
{
ApplicationId = g.Key,
//Ids = g.Select(x => x.Id),
RMBApplyAmount = g.Where(x => x.OriginalCurrency == FeeCurrency.RMB_CODE).Sum(x => x.ApplyAmount),
USDApplyAmount = g.Where(x => x.OriginalCurrency == FeeCurrency.USD_CODE).Sum(x => x.ApplyAmount),
ApplicationNO = firstItem.ApplicationNO,
Status = (int)firstItem.Status,
StatusText = firstItem.Status.GetDescription(),
CreateTime = firstItem?.CreateTime,
CreateBy = firstItem?.CreateBy,
CreateByName = users.Find(x => x.Id == firstItem?.CreateBy)?.UserName,
PaymentDate = firstItem?.PaymentDate,
Note = firstItem?.Note
};
//包含多个币别
if (g.GroupBy(x => x.OriginalCurrency).Select(x => x.Key).Count() > 1)
{
//原始币别=不等于结算单币别的首个币别参考DS7
dto.OriginalCurrency = g.Select(x => x.OriginalCurrency).FirstOrDefault();
//原始金额=当前结算单的币别合计
dto.OriginalAmount = g.Sum(x => x.ApplyAmount);
}
else
{
dto.OriginalCurrency = firstItem?.OriginalCurrency;
dto.OriginalAmount = g.Where(x => x.OriginalCurrency == dto.OriginalCurrency).Sum(x => x.ApplyAmount);
}
//结算金额=原申请的原始币别合计
dto.SettlementAmount = g.Where(x => x.OriginalCurrency == dto.OriginalCurrency).Sum(x => x.ApplyAmount);
details.Add(dto);
}
return details;
}
/// <summary>
/// 获取结算单明细
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
public async Task<DataResult<List<SettlementDetailDto>>> GetDetailsAsync(PageRequest<long> request)
{
var details = await GetSettlementDetails(request.OtherQueryCondition);
return DataResult<List<SettlementDetailDto>>.Success(details);
}
/// <summary>
/// 获取结算单费用明细
/// </summary>
/// <param name="ids">申请单明细ID</param>
/// <returns></returns>
public async Task<DataResult<List<PaymentApplicationDetailDto>>> GetSettlementDetailsAsync(long[] ids)
{
var details = await TenantDb.Queryable<ApplicationDetail>().Where(d => ids.Contains(d.Id) && d.Category == DetailCategory.PaidApplicationSettlement)
.LeftJoin<FeeRecord>((d, f) => d.RecordId == f.Id)
.LeftJoin<BusinessFeeStatus>((d, f, b) => f.BusinessId == b.BusinessId && f.BusinessType == b.BusinessType)
.LeftJoin<PaymentApplication>((d, f, b, p) => d.ApplicationId == p.Id)
.Select((d, f, b, p) => new PaymentApplicationDetailDto
{
Id = d.Id,
ApplicationId = d.ApplicationId,
BusinessId = f.BusinessId,
BusinessType = f.BusinessType,
RecordId = d.RecordId,
FeeName = d.FeeName,
FeeType = d.FeeType, //收付
ApplyAmount = d.ApplyAmount, //结算金额
CustomerId = f.CustomerId,
CustomerName = d.CustomerName,
OriginalCurrency = d.OriginalCurrency, //原始币别
OriginalRate = f.ExchangeRate, //原始汇率
ExchangeRate = d.ExchangeRate, //折算汇率
OriginalAmount = d.OriginalAmount, //原始金额
InvoiceNO = p.InvoiceNO //发票号
}).ToListAsync();
await FulfillDetailsAsync(details);
return DataResult<List<PaymentApplicationDetailDto>>.Success(details);
}
/// <summary>
/// 获取待结算的申请分页列表
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
public async Task<DataResult<List<PaymentApplicationDtoV2>>> GetApplicationListAsync(PageRequest<ApplicationListQuery> request)
{
var query = TenantDb.Queryable<PaymentApplication>()
.InnerJoin<ApplicationDetail>((a, d) => a.Id == d.ApplicationId && d.ApplyAmount - d.ProcessedAmount != 0)
.InnerJoin<FeeRecord>((a, d, f) => d.RecordId == f.Id)
.InnerJoin<SeaExport>((a, d, f, s) => f.BusinessId == s.Id && f.BusinessType == BusinessType.OceanShippingExport)
.GroupBy((a, d) => a.Id);
if (request.OtherQueryCondition != null)
{
if (request.OtherQueryCondition.PortId.HasValue)
query = query.Where((a, d, f, s) => s.LoadPortId == request.OtherQueryCondition.PortId || s.DischargePortId == request.OtherQueryCondition.PortId);
if (request.OtherQueryCondition.UnsettledOnly)
query = query.Where(a => a.Status == PaymentApplicationStatus.AuditPassed || a.Status == PaymentApplicationStatus.PartialSettlement);
}
else
{
query = query.Where(a => a.Status == PaymentApplicationStatus.AuditPassed || a.Status == PaymentApplicationStatus.PartialSettlement || a.Status == PaymentApplicationStatus.SettlementCompleted);
}
var whereList = request.GetConditionalModels(Db);
var result = await query.Select(a => new PaymentApplicationDtoV2
{
AmountRMB = a.AmountRMB == null ? 0 : a.AmountRMB.Value, //RMB申请金额
AmountUSD = a.AmountUSD == null ? 0 : a.AmountUSD.Value, //USD申请金额
AmountOther = a.AmountOther == null ? 0 : a.AmountOther.Value,
//RMB未结金额
UnSettledRMB = SqlFunc.Subqueryable<ApplicationDetail>().Where(d => d.ApplicationId == a.Id && d.Currency == FeeCurrency.RMB_CODE)
.Select(d => SqlFunc.AggregateSum(d.ApplyAmount - d.ProcessedAmount)),
//USD未结金额
UnSettledUSD = SqlFunc.Subqueryable<ApplicationDetail>().Where(d => d.ApplicationId == a.Id && d.Currency == FeeCurrency.USD_CODE)
.Select(d => SqlFunc.AggregateSum(d.ApplyAmount - d.ProcessedAmount)),
//USD未结其他
UnSettledOther = SqlFunc.Subqueryable<ApplicationDetail>().Where(d => d.ApplicationId == a.Id && d.Currency != FeeCurrency.RMB_CODE && d.Currency != FeeCurrency.USD_CODE)
.Select(d => SqlFunc.AggregateSum(d.ApplyAmount - d.ProcessedAmount)),
SettlementTypeName = a.SettlementType.StlName, //结算方式
}, true).MergeTable().Where(whereList).ToQueryPageAsync(request.PageCondition);
if (result.Data?.Count > 0)
{
var userIds = result.Data.Select(x => x.CreateBy).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.Select(x => x.SaleDeptId).Where(x => x.HasValue).Distinct().ToList();
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.SaleDeptName = orgs.Find(x => x.Id == item.SaleDeptId)?.OrgName;
item.SettlementRMB = item.UnSettledRMB;
item.SettlementUSD = item.UnSettledUSD;
item.SettlementOther = item.UnSettledOther;
}
}
return result;
}
/// <summary>
/// 获取申请单费用明细
/// </summary>
/// <param name="id">申请单ID</param>
/// <returns></returns>
public async Task<DataResult<List<PaymentApplicationDetailDto>>> GetApplicationDetailsAsync(long id)
{
var details = await TenantDb.Queryable<ApplicationDetail>().Where(d => d.ApplicationId == id && d.Category == DetailCategory.PaidApplication && (d.ApplyAmount - d.ProcessedAmount) != 0)
.LeftJoin<FeeRecord>((d, f) => d.RecordId == f.Id)
.Select((d, f) => new PaymentApplicationDetailDto
{
Id = d.Id,
ApplicationId = d.ApplicationId,
BusinessId = f.BusinessId,
BusinessType = f.BusinessType,
RecordId = d.RecordId,
FeeName = d.FeeName,
FeeType = d.FeeType, //收付
Amount = d.ApplyAmount, //申请金额
ApplyAmount = f.SettlementAmount, //已结算金额
RestAmount = d.ApplyAmount - d.ProcessedAmount, //剩余结算金额
CustomerId = f.CustomerId,
CustomerName = d.CustomerName,
OriginalCurrency = d.OriginalCurrency, //原始币别
OriginalRate = f.ExchangeRate, //原始汇率
ExchangeRate = d.ExchangeRate, //折算汇率
OriginalAmount = d.OriginalAmount //原始金额
}).ToListAsync();
await FulfillDetailsAsync(details);
return DataResult<List<PaymentApplicationDetailDto>>.Success(details);
}
internal async Task FulfillDetailsAsync(List<PaymentApplicationDetailDto> details)
{
if (details.Count == 0)
return;
var gList = details.GroupBy(x => x.BusinessType);
foreach (var g in gList)
{
var ids = g.Select(x => x.BusinessId);
switch (g.Key)
{
case BusinessType.OceanShippingExport:
var list1 = await TenantDb.Queryable<SeaExport>().Where(s => ids.Contains(s.Id))
.LeftJoin<CodeSource>((s, cs) => s.SourceId == cs.Id)
.Select((s, cs) => new
{
s.Id,
s.AccountDate,//会计期间
s.Vessel, //船名
s.Voyno, //航次
s.CustomerName,
s.MBLNO, //主提单号
s.CustomerNo, //委托编号
s.ETD, //开船日期
s.Sale, //揽货人
cs.SourceName, //业务来源
s.Note
}).ToListAsync();
foreach (var item in g)
{
var biz = list1.Find(x => x.Id == item.BusinessId);
if (biz != null)
{
item.MBLNO = biz.MBLNO;
item.CustomerNo = biz.CustomerNo;
item.ClientName = biz.CustomerName;
item.ETD = biz.ETD;
item.SourceName = biz.SourceName;
item.SaleName = biz.Sale;
item.AccountDate = biz.AccountDate;
item.Vessel = biz.Vessel;
item.Voyage = biz.Voyno;
item.Note = biz.Note;
}
}
break;
case BusinessType.OceanShippingImport:
break;
}
}
}
protected override async Task<DataResult> PreSaveAsync(ApplicationSettlement settlement)
{
var appIds = settlement.Details.Select(x => x.RefId).Distinct();
var appList = await TenantDb.Queryable<PaymentApplication>().Where(x => appIds.Contains(x.Id))
.Select(x => new
{
x.Id,
x.Status,
x.CustomerId,
x.CustomerName,
}).ToListAsync();
if (appList.Count == 0)
return DataResult.FailedWithDesc(nameof(MultiLanguageConst.EmptyData));
if (appList.Exists(x => x.Status != PaymentApplicationStatus.AuditPassed && x.Status != PaymentApplicationStatus.PartialSettlement))
return DataResult.FailedWithDesc(nameof(MultiLanguageConst.ApplicationSelectStatusError));
if (appList.GroupBy(x => x.CustomerId).Select(x => x.Key).Count() > 1)
return DataResult.FailedWithDesc(nameof(MultiLanguageConst.DetailCustomerOnlyOne));
//获取剩余待结算金额
var ids2 = settlement.Details.Select(x => x.DetailId);
var appDetails = await TenantDb.Queryable<ApplicationDetail>().Where(x => ids2.Contains(x.Id) && x.Category == DetailCategory.PaidApplication)
.Select(x => new
{
x.Id,
//RestAmount = x.ApplyAmount - x.ProcessedAmount,
OriginalRestAmount = x.OriginalAmount - x.OriginalProcessedAmount
}).ToListAsync();
StringBuilder sb = new();
foreach (var detail in settlement.Details)
{
var item = appDetails.Find(x => x.Id == detail.DetailId);
if (item == null)
{
sb.Append(MultiLanguageConst.GetDescription(nameof(MultiLanguageConst.EmptyData)));
break;
}
if (detail.OriginalAmount > 0 && detail.OriginalAmount > item.OriginalRestAmount)
{
sb.AppendFormat(MultiLanguageConst.GetDescription(nameof(MultiLanguageConst.DetailExceedingLimit)), detail.FeeName);
sb.Append("");
continue;
}
else if (detail.OriginalAmount < 0 && detail.OriginalAmount < item.OriginalRestAmount)
{
sb.AppendFormat(MultiLanguageConst.GetDescription(nameof(MultiLanguageConst.DetailExceedingLimit)), detail.FeeName);
sb.Append("");
continue;
}
detail.Category = settlement.BillType == SettlementBillType.Payment ? DetailCategory.PaidApplicationSettlement : DetailCategory.ChargeApplicationSettlement;
}
return sb.Length > 0 ? DataResult.Failed(sb.ToString()) : DataResult.Success;
}
protected override async Task OnSaveAsync(Entity.ApplicationSettlement settlement)
{
//更新付费申请明细的已处理金额
var list = settlement.Details.Select(x => new ApplicationDetail
{
Id = x.DetailId.Value,
Category = DetailCategory.PaidApplication,
ProcessedAmount = x.ApplyAmount,
OriginalProcessedAmount = x.OriginalAmount
}).ToList();
await TenantDb.Updateable(list)
.PublicSetColumns(x => x.ProcessedAmount, "+")
.PublicSetColumns(x => x.OriginalProcessedAmount, "+")
.UpdateColumns(x => new { x.ProcessedAmount, x.OriginalProcessedAmount })
.ExecuteCommandAsync();
}
protected override async Task<Entity.ApplicationSettlement> PostSaveAsync(Entity.ApplicationSettlement settlement)
{
//回写付费申请的状态
var ids = settlement.Details.Select(x => x.DetailId);
var appIds = await TenantDb.Queryable<ApplicationDetail>().Where(x => ids.Contains(x.Id) && x.Category == DetailCategory.PaidApplication)
.Select(x => x.ApplicationId).ToListAsync();
var details = await TenantDb.Queryable<ApplicationDetail>().Where(x => appIds.Contains(x.ApplicationId) && x.Category == DetailCategory.PaidApplication).GroupBy(x => x.ApplicationId).Select(x => new
{
x.ApplicationId,
Count = SqlFunc.AggregateCount(x.Id),
ProcessedCount = SqlFunc.Subqueryable<ApplicationDetail>().Where(y => y.ApplicationId == x.ApplicationId &&
y.Category == DetailCategory.PaidApplication && y.OriginalAmount - y.OriginalProcessedAmount == 0).Count()
}).ToListAsync();
List<PaymentApplication> applications = [];
foreach (var item in details)
{
if (item.Count == 0 || item.ProcessedCount == 0)
continue;
var entity = new PaymentApplication { Id = item.ApplicationId };
if (item.Count == item.ProcessedCount)
{
entity.Status = PaymentApplicationStatus.SettlementCompleted;
}
else if (item.ProcessedCount > 0)
{
entity.Status = PaymentApplicationStatus.PartialSettlement;
}
applications.Add(entity);
}
await TenantDb.Updateable(applications).UpdateColumns(x => new { x.Status }).ExecuteCommandAsync();
return await base.PostSaveAsync(settlement);
}
protected override DataResult PreDelete(List<Entity.ApplicationSettlement> settlements)
{
if (settlements.Any(x => x.IsLocked))
return DataResult.FailedWithDesc(nameof(MultiLanguageConst.SettlementIsLocked));
return DataResult.Success;
}
protected override async Task OnDeleteDetailAsync(List<Entity.ApplicationSettlement> settlements)
{
await base.OnDeleteDetailAsync(settlements);
//还原付费申请明细
var list = settlements.FindAll(x => x.Mode == SettlementMode.Payment);
if (list.Count > 0)
{
var items = list.SelectMany(x => x.Details);
var ids = items.Select(x => x.DetailId);
var details = TenantDb.Queryable<ApplicationDetail>().Where(x => ids.Contains(x.Id) && x.Category == DetailCategory.PaidApplication)
.Select(x => new ApplicationDetail
{
Id = x.Id,
ProcessedAmount = x.ProcessedAmount,
OriginalProcessedAmount = x.OriginalProcessedAmount
}).ToList();
foreach (var detail in details)
{
var item = items.FirstOrDefault(x => x.DetailId == detail.Id);
detail.ProcessedAmount -= item.ApplyAmount;
detail.OriginalProcessedAmount -= item.OriginalAmount;
}
await TenantDb.Updateable(details)
.UpdateColumns(x => new { x.ProcessedAmount, x.OriginalProcessedAmount })
.ExecuteCommandAsync();
}
}
}
}