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.

634 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 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.Application.Interface;
using DS.WMS.Core.Fee.Entity;
using DS.WMS.Core.Fee.Method;
using DS.WMS.Core.Flow.Dtos;
using DS.WMS.Core.Flow.Entity;
using DS.WMS.Core.Flow.Interface;
using DS.WMS.Core.Invoice.Dtos;
using DS.WMS.Core.Op.Dtos.TaskInteraction;
using DS.WMS.Core.Op.Interface.TaskInteraction;
using DS.WMS.Core.Sys.Interface;
using Masuit.Tools.Systems;
using Microsoft.Extensions.DependencyInjection;
using SqlSugar;
namespace DS.WMS.Core.Application.Method
{
/// <summary>
/// 申请单基础实现
/// </summary>
/// <typeparam name="TEntity">实体的类型声明</typeparam>
public abstract class ApplicationService<TEntity> : FeeServiceBase, IApplicationService<TEntity>
where TEntity : ApplicationForm, new()
{
internal static readonly DetailCategory[] detailCategories = [
DetailCategory.InvoiceSettlement, DetailCategory.InvoiceIssuance,
DetailCategory.ChargeApplicationSettlement,DetailCategory.PaidApplicationSettlement];
/// <summary>
/// 适用于当前申请单的审核类型
/// </summary>
public abstract TaskBaseTypeEnum AuditType { get; }
readonly IClientFlowInstanceService flowService;
readonly Lazy<ICommonService> commonService;
readonly ITaskService taskService;
/// <summary>
/// 初始化
/// </summary>
/// <param name="serviceProvider">DI容器</param>
public ApplicationService(IServiceProvider serviceProvider) : base(serviceProvider)
{
flowService = serviceProvider.GetRequiredService<IClientFlowInstanceService>();
commonService = new Lazy<ICommonService>(serviceProvider.GetRequiredService<ICommonService>());
taskService = serviceProvider.GetRequiredService<ITaskService>();
}
/// <summary>
/// 根据业务编号及类型获取该票业务的币别
/// </summary>
/// <param name="items">业务ID与业务类型</param>
/// <returns></returns>
public async Task<DataResult<List<FeeClient>>> GetCurrenciesAsync(params FeeClient[] items)
{
var bizIds = items.Select(x => x.Id).Distinct();
var types = items.Select(x => x.BusinessType).Distinct();
var cIds = items.Select(x => x.CustomerId).Distinct();
var list = await TenantDb.Queryable<FeeRecord>()
.Where(f => bizIds.Contains(f.BusinessId) && types.Contains(f.BusinessType) && cIds.Contains(f.CustomerId)
&& f.FeeStatus == FeeStatus.AuditPassed)
.Select(f => new
{
f.BusinessId,
f.BusinessType,
f.CustomerId,
f.Currency
}).ToListAsync();
var currencies = list.GroupBy(x => new { x.BusinessId, x.BusinessType, x.CustomerId }).Select(x => new FeeClient
{
Id = x.Key.BusinessId,
BusinessType = x.Key.BusinessType,
CustomerId = x.Key.CustomerId,
ExchangeRates = x.GroupBy(y => y.Currency).Select(y => new CurrencyExchangeRate
{
Currency = y.Key
}).ToList()
}).ToList();
return DataResult<List<FeeClient>>.Success(currencies);
}
#region 保存
/// <summary>
/// 提交保存费用申请单
/// </summary>
/// <param name="application">申请单</param>
/// <returns></returns>
public async Task<DataResult<TEntity>> SaveAsync(TEntity application)
{
TEntity? dbValue = null;
if (application.Id > 0)
{
application.BuildOption = BuildOption.Update;
//修改需检查申请单状态
dbValue = await TenantDb.Queryable<TEntity>().Where(x => x.Id == application.Id).Select(
x => new TEntity { Status = x.Status, Currency = x.Currency }).FirstAsync();
if (dbValue == null)
return DataResult<TEntity>.FailedWithDesc(nameof(MultiLanguageConst.EmptyData));
}
var result = EnsureApplication(application, dbValue);
if (!result.Succeeded)
return DataResult<TEntity>.Failed(result.Message, result.MultiCode);
List<FeeRecord> fees = [];
if (application.Details.Count > 0)
{
application.Details = application.Details.FindAll(x => x.Id == 0);
if (application.Details.GroupBy(x => x.CustomerName).Select(x => x.Key).Count() > 1)
return DataResult<TEntity>.FailedWithDesc(nameof(MultiLanguageConst.DetailCustomerOnlyOne));
if (application.Details.GroupBy(x => x.RecordId).Where(g => g.Count() > 1).Select(x => x.Key).Count() > 0)
return DataResult<TEntity>.FailedWithDesc(nameof(MultiLanguageConst.ApplicationRecordOnlyOne));
//申请金额禁止为0
if (application.Details.Any(x => x.ApplyAmount == 0))
return DataResult<TEntity>.FailedWithDesc(nameof(MultiLanguageConst.AmountCannotBeZero));
var ids = application.Details.Select(x => x.RecordId).Distinct().ToList();
fees = await TenantDb.Queryable<FeeRecord>().Where(x => ids.Contains(x.Id)).Select(x => new FeeRecord
{
Id = x.Id,
BusinessId = x.BusinessId,
BusinessType = x.BusinessType,
FeeId = x.FeeId,
FeeName = x.FeeName,
FeeType = x.FeeType,
CustomerId = x.CustomerId,
CustomerName = x.CustomerName,
Amount = x.Amount,
Currency = x.Currency,
ExchangeRate = x.ExchangeRate,
OrderAmount = x.OrderAmount,
OrderSettlementAmount = x.OrderSettlementAmount,
SettlementAmount = x.SettlementAmount
}).ToListAsync();
if (application.Id == 0)
{
//填充申请单信息
var fee = fees[0];
application.CustomerId = fee.CustomerId;
application.CustomerName = fee.CustomerName;
}
foreach (var detail in application.Details)
{
detail.ApplicationId = application.Id;
var fee = fees.Find(x => x.Id == detail.RecordId);
detail.ExchangeRate = detail.ExchangeRate ?? fee.ExchangeRate;
detail.FeeId = fee.FeeId;
detail.FeeName = fee.FeeName;
detail.FeeType = fee.FeeType;
detail.CustomerName = detail.CustomerName ?? application.CustomerName;
//原币申请
if (application.Currency.IsNullOrEmpty())
{
detail.OriginalAmount = detail.ApplyAmount;
if (detail.OriginalCurrency.IsNullOrEmpty())
detail.OriginalCurrency = fee.Currency;
}
}
result = CalculateAmount(application, fees);
if (!result.Succeeded)
return DataResult<TEntity>.Failed(result.Message, result.MultiCode);
}
await TenantDb.Ado.BeginTranAsync();
try
{
await PreSaveAsync(application);
//关联导航属性插入
if (application.Id == 0)
{
//创建时需要生成申请单编号
var sequence = commonService.Value.GetSequenceNext<TEntity>();
if (!sequence.Succeeded)
{
return DataResult<TEntity>.Failed(sequence.Message, MultiLanguageConst.SequenceSetNotExist);
}
application.ApplicationNO = sequence.Data;
await TenantDb.InsertNav(application).Include(x => x.Details).ExecuteCommandAsync();
}
else
{
if (application.Details.Count > 0)
await TenantDb.Insertable(application.Details).ExecuteCommandAsync();
}
await OnSaveAsync(application, fees);
await TenantDb.Ado.CommitTranAsync();
var postResult = await PostSaveAsync(application);
return postResult;
}
catch (Exception ex)
{
await TenantDb.Ado.RollbackTranAsync();
await ex.LogAsync(Db);
return DataResult<TEntity>.FailedWithDesc(nameof(MultiLanguageConst.Operation_Failed));
}
}
/// <summary>
/// 提交保存费用申请单
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
public async Task<DataResult<TEntity>> SaveAsync(ApplicationRequest<TEntity> request)
{
request.Application ??= new();
request.Application.Details = await GetDetailsAsync(request);
if (!string.IsNullOrEmpty(request.Application.Currency)) //非原币申请
{
var details = request.Application.Details.FindAll(x => x.Currency != request.Application.Currency);
foreach (var detail in details)
{
var fc = request.Items.Find(x => x.Id == detail.BusinessId && x.BusinessType == x.BusinessType);
var exchange = fc?.ExchangeRates?.Find(x => x.Currency == detail.Currency);
if (exchange == null)
return DataResult<TEntity>.Failed($"非原币申请必须传入费用原币与申请币别 {detail.Currency} 之间的汇率信息");
detail.ExchangeRate = exchange.ExchangeRate;
detail.ApplyAmount = Math.Round(detail.OriginalAmount * (exchange.ExchangeRate == null ? 1 : exchange.ExchangeRate.Value),
2, MidpointRounding.AwayFromZero);
detail.Currency = request.Application.Currency;
}
}
return await SaveAsync(request.Application);
}
/// <summary>
/// 用于申请单的状态检查
/// </summary>
/// <param name="application">提交的申请单</param>
/// <param name="dbValue">数据库值新增时为null</param>
/// <returns></returns>
protected virtual DataResult EnsureApplication(TEntity application, TEntity? dbValue)
{
return DataResult.Success;
}
/// <summary>
/// 根据申请单明细,检查费用记录的剩余额度
/// </summary>
/// <param name="application">申请单</param>
/// <param name="fees">费用记录</param>
/// <returns></returns>
protected virtual DataResult CalculateAmount(TEntity application, List<FeeRecord> fees)
{
return DataResult.Success;
}
/// <summary>
/// 在保存前调用
/// </summary>
/// <param name="application">申请单</param>
/// <returns></returns>
protected virtual Task PreSaveAsync(TEntity application)
{
return Task.CompletedTask;
}
/// <summary>
/// 在提交事务前,保存时调用
/// </summary>
/// <param name="application">已保存的申请单</param>
/// <param name="fees">需要更新信息的费用记录</param>
/// <returns></returns>
protected virtual async Task OnSaveAsync(TEntity application, List<FeeRecord>? fees)
{
await TenantDb.Updateable(application).IgnoreColumns(x => new
{
x.ApplicationNO,
x.Status,
x.CreateBy,
x.CreateTime,
x.Deleted,
x.DeleteBy,
x.DeleteTime
}).ExecuteCommandAsync();
}
/// <summary>
/// 在保存完成后调用
/// </summary>
/// <param name="application">申请单</param>
protected virtual Task<DataResult<TEntity>> PostSaveAsync(TEntity application)
{
return Task.FromResult(DataResult<TEntity>.Success(application));
}
/// <summary>
/// 获取业务所关联的申请明细
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
protected abstract Task<List<ApplicationDetail>> GetDetailsAsync(ApplicationRequest<TEntity> request);
#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,
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,
Status = x.Status
}).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="applications">申请单</param>
/// <returns></returns>
protected virtual DataResult PreDelete(List<TEntity> applications)
{
var ids = applications.Select(x => x.Id);
bool exists = TenantDb.Queryable<ApplicationDetail>().Where(x => ids.Contains(x.ApplicationId))
.InnerJoin<ApplicationDetail>((d1, d2) => d1.Id == d2.DetailId)
.Where((d1, d2) => d1.DetailId.HasValue && detailCategories.Contains(d2.Category.Value))
.Any();
if (exists)
return DataResult.FailedWithDesc(nameof(MultiLanguageConst.ApplicationIsUsed));
return DataResult.Success;
}
/// <summary>
/// 在执行删除申请单或其明细时调用
/// </summary>
/// <param name="applications">申请单及其明细</param>
/// <param name="deleteOption">删除选项</param>
/// <returns></returns>
protected virtual Task OnDeleteDetailAsync(List<TEntity> applications, DeleteOption deleteOption)
{
return Task.CompletedTask;
}
/// <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,
Status = x.Status
}).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));
}
}
#endregion
#region 审批
/// <summary>
/// 提交审批
/// </summary>
/// <param name="auditType">审批类型</param>
/// <param name="remark">审批备注</param>
/// <param name="idArray">申请单ID</param>
/// <returns></returns>
public async Task<DataResult> SubmitApprovalAsync(TaskBaseTypeEnum auditType, string remark, params long[] idArray)
{
var list = await TenantDb.Queryable<TEntity>().LeftJoin<ApplicationDetail>((x, y) => x.Id == y.ApplicationId)
.GroupBy((x, y) => x.Id)
.Where((x, y) => idArray.Contains(x.Id))
.Select((x, y) => new TEntity
{
Id = x.Id,
ApplicationNO = x.ApplicationNO,
Status = x.Status,
DetailCount = SqlFunc.AggregateCount(y.Id)
}).ToListAsync();
if (list.Count == 0)
return DataResult.FailedWithDesc(nameof(MultiLanguageConst.EmptyData));
if (list.Exists(x => x.DetailCount == 0))
return DataResult.FailedWithDesc(nameof(MultiLanguageConst.ApplicationMustHaveDetail));
var result = PreSubmitApproval(list);
if (!result.Succeeded)
return result;
List<TEntity> entities = new(idArray.Length);
await TenantDb.Ado.BeginTranAsync();
try
{
if (await taskService.HasAuthorizedAsync())
{
for (int i = 0; i < list.Count; i++)
{
var item = list[i];
var req = new TaskCreationRequest
{
BusinessId = item.Id,
TaskTypeName = auditType.ToString(),
TaskTitle = $"【{auditType.GetDescription()}】{item.ApplicationNO}"
};
result = await taskService.CreateTaskAsync(req);
if (!result.Succeeded)
return result;
var entity = new TEntity { Id = req.BusinessId };
OnSubmitApproval(entity);
entities.Add(entity);
}
}
else
{
var template = await FindTemplateAsync(auditType);
if (template == null)
return DataResult.FailedWithDesc(nameof(MultiLanguageConst.TemplateNotFound));
var list2 = list.Select(x => new TEntity { Id = x.Id, Status = x.Status }).ToList();
foreach (var item in list2)
{
result = flowService.CreateFlowInstance(new CreateFlowInstanceReq
{
BusinessId = item.Id,
TemplateId = template.Id
});
if (result.Succeeded)
{
var instance = result.Data as FlowInstance;
flowService.StartFlowInstance(instance.Id.ToString());
OnSubmitApproval(item);
entities.Add(item);
}
}
}
await TenantDb.Updateable(entities).UpdateColumns(x => new { x.Status }).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="applications">申请单</param>
/// <returns></returns>
protected virtual DataResult PreSubmitApproval(List<TEntity> applications)
{
return DataResult.Success;
}
/// <summary>
/// 在提交审批时调用,用于更新申请单信息
/// </summary>
/// <param name="application">申请单</param>
/// <returns></returns>
protected virtual void OnSubmitApproval(TEntity application)
{
}
/// <summary>
/// 撤销审批
/// </summary>
/// <param name="ids">申请单ID</param>
/// <returns></returns>
public async Task<DataResult> WithdrawAsync(params long[] ids)
{
var list = await TenantDb.Queryable<TEntity>().Where(x => ids.Contains(x.Id)).Select(
x => new TEntity
{
Id = x.Id,
ApplicationNO = x.ApplicationNO,
Status = x.Status
}).ToListAsync();
if (list.Count == 0)
return DataResult.FailedWithDesc(nameof(MultiLanguageConst.EmptyData));
//未在审批状态中
if (!await flowService.Exists(ids: ids))
return DataResult.FailedWithDesc(nameof(MultiLanguageConst.NotInAudit));
DataResult result;
bool hasAuthorized = await taskService.HasAuthorizedAsync();
try
{
if (hasAuthorized)
{
foreach (var item in list)
{
result = await taskService.WithdrawAsync(new TaskRequest
{
BusinessId = item.Id,
TaskTypeName = AuditType.ToString()
}, false);
OnWithdraw(item);
}
}
else
{
result = await flowService.WithdrawAsync(ids);
if (!result.Succeeded)
return result;
foreach (var item in list)
{
OnWithdraw(item);
}
}
await TenantDb.Updateable(list).UpdateColumns(x => new { x.Status }).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="application">申请单</param>
/// <returns></returns>
protected virtual void OnWithdraw(TEntity application)
{
}
#endregion
}
}