fix: 国联民生收益互换支持审批前生成交易确认书
- 仅国联民生收益互换支持在新增待确认、修改待确认、审批中生成交易确认书,期权、其他公司及其他交易状态保持原限制 - 交易确认书页面同步开放上述交易的“生成确认书”按钮,后端批量生成链路同步放开状态过滤 - 审批前生成仅校验客户、标的、初始持仓等模板必需核心数据;不以银行账户、联系人、保证金等非模板必填资料阻断 - 审批前已生成确认书的交易被修改时,使该交易原确认书关联失效,重新生成仍按原覆盖逻辑处理 - 审批拒绝时清理该交易审批前生成的确认书关联、OA 结果及用印队列;无其他有效引用时删除确认书数据,避免遗留无效确认书 - 保持原交易编号逻辑:自动生成和手工填写均不复用已拒绝交易编号 - 新增审批前适用范围、确认书关联及 OA 清理谓词专项单测
This commit is contained in:
@@ -226,9 +226,10 @@ namespace YLErp.Modules.TradeModule.DealModule
|
||||
return true;
|
||||
}
|
||||
|
||||
// 过滤掉待确认和审批中的交易,这些状态不允许生成确认书
|
||||
// 仅国联收益互换允许在审批前生成确认书,其他交易保持原状态限制。
|
||||
var invalidStatusList = new List<string> { ConsTrade.新增待确认, ConsTrade.修改待确认, ConsTrade.审批中 };
|
||||
var invalidTrades = trades.Where(t => invalidStatusList.Contains(t.TradeStatus)).ToList();
|
||||
var invalidTrades = trades.Where(t => invalidStatusList.Contains(t.TradeStatus)
|
||||
&& !GuolianSwapConfirmBookScope.IsPreApprovalTrade(t)).ToList();
|
||||
if (invalidTrades.Any())
|
||||
{
|
||||
error.AddRange(invalidTrades.Select(t => $"交易 {t.TradeNumber} 状态为【{t.TradeStatus}】,不能生成确认书"));
|
||||
@@ -237,6 +238,20 @@ namespace YLErp.Modules.TradeModule.DealModule
|
||||
logger.Info($"[GJGenerateConfirmBook] 过滤掉 {invalidTrades.Count} 笔待确认/审批中交易");
|
||||
}
|
||||
|
||||
var preApprovalTrades = trades.Where(GuolianSwapConfirmBookScope.IsPreApprovalTrade).ToList();
|
||||
foreach (var trade in preApprovalTrades)
|
||||
{
|
||||
var validationErrors = new GuolianSwapConfirmBookValidationService(this).Validate(trade);
|
||||
if (!validationErrors.Any())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
error.Add($"交易 {trade.TradeNumber} 无法生成确认书:{string.Join(";", validationErrors)}");
|
||||
tradeIds = tradeIds.Where(id => id != trade.id).ToList();
|
||||
trades = trades.Where(item => item.id != trade.id).ToList();
|
||||
}
|
||||
|
||||
if (!trades.Any())
|
||||
{
|
||||
logger.Info("[GJGenerateConfirmBook] 过滤后无有效交易可生成确认书");
|
||||
|
||||
@@ -7,6 +7,7 @@ using YLErp.Model.Enum;
|
||||
using YLErp.Modules.ClientModule;
|
||||
using YLErp.Modules.SwapModule;
|
||||
using YLErp.Modules.TradeDalModule;
|
||||
using YLErp.Modules.TradeModule.DocGenerateModule;
|
||||
|
||||
namespace YLErp.Modules.TradeModule.DealModule
|
||||
{
|
||||
@@ -295,6 +296,7 @@ namespace YLErp.Modules.TradeModule.DealModule
|
||||
/// </summary>
|
||||
private TradeOpenResult TradeReject(TradeOpenReqModel req, trade td)
|
||||
{
|
||||
var isGuolianPreApprovalTrade = GuolianSwapConfirmBookScope.IsPreApprovalTrade(td);
|
||||
var oldOrderId = td.ProcessOrderId;
|
||||
req.comments = FormatCommentsWithRole(oldOrderId, req.comments);
|
||||
td.ProcessOrderId = Convert.ToInt32(PStatusEnum.reject);
|
||||
@@ -367,6 +369,11 @@ namespace YLErp.Modules.TradeModule.DealModule
|
||||
var tcDetails = DbContext.trade_cash_detail.Where(O => O.TradeCashId == tcId);
|
||||
DbContext.trade_cash_detail.RemoveRange(tcDetails);
|
||||
|
||||
if (isGuolianPreApprovalTrade)
|
||||
{
|
||||
new GuolianSwapConfirmBookCleanupService(this).CleanRejectedPreApprovalTrade(td);
|
||||
}
|
||||
|
||||
DbContext.SaveChanges();
|
||||
|
||||
return new TradeOpenResult(td) { RetCode = TradeOpenRetCode.Success };
|
||||
|
||||
@@ -75,7 +75,7 @@ namespace YLErp.Modules.TradeModule.DocGenerateModule
|
||||
throw new ServiceException("没有已确认的记录信息!");
|
||||
}
|
||||
var tradeDic = allTrades.Where(O =>
|
||||
statusList.Contains(O.TradeStatus)
|
||||
(statusList.Contains(O.TradeStatus) || GuolianSwapConfirmBookScope.IsPreApprovalTrade(O))
|
||||
&& O.ClientId != 0
|
||||
&& O.ParentTradeId == 0)
|
||||
.ToDictionary(n => n.id, n => new trade_contract_group { trade = n });
|
||||
|
||||
+192
@@ -0,0 +1,192 @@
|
||||
using System.Linq.Expressions;
|
||||
using YLErp.Configuration;
|
||||
using YLErp.DBModels;
|
||||
using YLErp.DBModels.Consts;
|
||||
using YLErp.DBModels.Enums;
|
||||
using YLErp.Model;
|
||||
|
||||
namespace YLErp.Modules.TradeModule.DocGenerateModule
|
||||
{
|
||||
/// <summary>
|
||||
/// 国联收益互换审批前生成确认书的范围与资料校验。
|
||||
/// </summary>
|
||||
public static class GuolianSwapConfirmBookScope
|
||||
{
|
||||
private static readonly HashSet<string> PreApprovalStatuses = new()
|
||||
{
|
||||
ConsTrade.新增待确认,
|
||||
ConsTrade.修改待确认,
|
||||
ConsTrade.审批中
|
||||
};
|
||||
|
||||
public static bool IsPreApprovalTrade(trade trade)
|
||||
{
|
||||
return IsPreApprovalTrade(trade, PS.Config.Company);
|
||||
}
|
||||
|
||||
public static bool IsPreApprovalTrade(trade trade, CompanyEnum company)
|
||||
{
|
||||
return IsGuolianSwap(trade, company)
|
||||
&& trade.ValidState != ConsGlobal.InValid
|
||||
&& IsPreApprovalStatus(trade.TradeStatus);
|
||||
}
|
||||
|
||||
public static bool IsGuolianSwap(trade trade)
|
||||
{
|
||||
return IsGuolianSwap(trade, PS.Config.Company);
|
||||
}
|
||||
|
||||
public static bool IsGuolianSwap(trade trade, CompanyEnum company)
|
||||
{
|
||||
return company == CompanyEnum.国联 && trade.TradeType == "收益互换";
|
||||
}
|
||||
|
||||
public static bool IsPreApprovalStatus(string tradeStatus)
|
||||
{
|
||||
return PreApprovalStatuses.Contains(tradeStatus);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清理国联收益互换审批前生成的确认书,不复用按合同编号批量清理的通用逻辑。
|
||||
/// </summary>
|
||||
public class GuolianSwapConfirmBookCleanupService : YLBaseService
|
||||
{
|
||||
public GuolianSwapConfirmBookCleanupService(YLBaseService baseService) : base(baseService)
|
||||
{
|
||||
}
|
||||
|
||||
public void InvalidateAfterEdit(trade trade)
|
||||
{
|
||||
if (!GuolianSwapConfirmBookScope.IsGuolianSwap(trade)
|
||||
|| !GuolianSwapConfirmBookScope.IsPreApprovalStatus(trade.TradeStatus))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
InvalidateRelations(trade.id);
|
||||
}
|
||||
|
||||
public void CleanRejectedPreApprovalTrade(trade trade)
|
||||
{
|
||||
if (!GuolianSwapConfirmBookScope.IsGuolianSwap(trade))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GlobalConfig.SealHandler.RemoveQueue(trade.id);
|
||||
InvalidateOaResults(trade.id);
|
||||
var contractCodes = InvalidateRelations(trade.id);
|
||||
foreach (var contractCode in contractCodes)
|
||||
{
|
||||
var hasActiveReference = DbContext.trade_contract_r.Any(
|
||||
BuildOtherActiveReferencePredicate(contractCode, trade.id));
|
||||
if (!hasActiveReference)
|
||||
{
|
||||
var documents = DbContext.trade_contract_document.Where(x => x.Code == contractCode
|
||||
&& x.Type == ContractTypeEnum.Trade);
|
||||
DbContext.trade_contract_document.RemoveRange(documents);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static Expression<Func<trade_contract_r, bool>> BuildOtherActiveReferencePredicate(
|
||||
string contractCode,
|
||||
int rejectedTradeId)
|
||||
{
|
||||
return relation => relation.ContractCode == contractCode
|
||||
&& relation.Type == ContractTypeEnum.Trade
|
||||
&& relation.IsValid
|
||||
&& relation.TradeId != rejectedTradeId;
|
||||
}
|
||||
|
||||
public static Expression<Func<trade_contract_r, bool>> BuildTradeConfirmationRelationPredicate(
|
||||
int tradeId)
|
||||
{
|
||||
return relation => relation.TradeId == tradeId
|
||||
&& relation.Type == ContractTypeEnum.Trade;
|
||||
}
|
||||
|
||||
public static Expression<Func<trade_contract_oa_result, bool>> BuildActiveOaResultPredicate(
|
||||
int tradeId)
|
||||
{
|
||||
return result => result.trade_id == tradeId && result.is_valid;
|
||||
}
|
||||
|
||||
private void InvalidateOaResults(int tradeId)
|
||||
{
|
||||
var oaResults = DbContext.tradeContractOaResult
|
||||
.Where(BuildActiveOaResultPredicate(tradeId))
|
||||
.ToList();
|
||||
oaResults.ForEach(x => x.is_valid = false);
|
||||
}
|
||||
|
||||
private List<string> InvalidateRelations(int tradeId)
|
||||
{
|
||||
var relations = DbContext.trade_contract_r
|
||||
.Where(BuildTradeConfirmationRelationPredicate(tradeId))
|
||||
.ToList();
|
||||
relations.Where(x => x.IsValid).ToList().ForEach(x => x.IsValid = false);
|
||||
return relations.Select(x => x.ContractCode).Distinct().ToList();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 仅校验模板无法正常生成或选择时必需的交易数据。
|
||||
/// </summary>
|
||||
public class GuolianSwapConfirmBookValidationService : YLBaseService
|
||||
{
|
||||
public GuolianSwapConfirmBookValidationService(YLBaseService baseService) : base(baseService)
|
||||
{
|
||||
}
|
||||
|
||||
public List<string> Validate(trade trade)
|
||||
{
|
||||
var errors = new List<string>();
|
||||
var client = DataCacheProvider.GetClientDataSource().GetData(trade.ClientId);
|
||||
var underlying = DataCacheProvider.GetUnderlyingDataSource().GetData(trade.UnderlyingCode);
|
||||
|
||||
if (client == null)
|
||||
{
|
||||
errors.Add("客户信息不存在");
|
||||
return errors;
|
||||
}
|
||||
|
||||
if (underlying == null)
|
||||
{
|
||||
errors.Add("标的信息不存在");
|
||||
}
|
||||
|
||||
ValidateInitialPosition(trade, errors);
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
||||
private void ValidateInitialPosition(trade trade, List<string> errors)
|
||||
{
|
||||
var position = DbContext.swap_position.FirstOrDefault(x => x.SwapTradeId == trade.id
|
||||
&& x.IsInitial
|
||||
&& !x.Invalid
|
||||
&& (x.PositionType == (int)PositionTypeFlag.Long || x.PositionType == (int)PositionTypeFlag.Short));
|
||||
|
||||
if (position == null)
|
||||
{
|
||||
errors.Add("缺少初始标的持仓");
|
||||
return;
|
||||
}
|
||||
|
||||
if (position.PosiDirection != (int)SwapDirectionEnum.支付
|
||||
&& position.PosiDirection != (int)SwapDirectionEnum.收取)
|
||||
{
|
||||
errors.Add("初始标的持仓收支方向无效");
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(position.UnderlyingCode))
|
||||
{
|
||||
errors.Add("初始标的持仓缺少标的代码");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user