Files
zszq-trs/YLErpDAL/Modules/TradeModule/TradeServiceBase.cs
T
tengyufan 3f0984800a feat: 收益互换开平仓及收益结算接入OA移动审批
#EQD-6425 国联民生-衍生品系统移动审批(提交OA流程)
- 审批节点支持配置“关联OA移动审批”
- 创建OA流程并记录请求、requestId、状态及异常备注
- 支持OA轮询同步审批通过、退回和归档状态
- 本地通过、拒绝、撤回时同步强制归档未结束OA流程
- 增加审批列表OA备注展示及本地Mock联调接口
- 新增OA审批记录表和数据库升级脚本
- OA移动审批使用独立查询地址及isnextflow配置,不影响交易确认书OA
2026-08-21 15:54:54 +08:00

744 lines
35 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using BaseOUDAL;
using YLErp.BLL;
using YLErp.DBModels.Consts;
using YLErp.DBModels.Enums;
using YLErp.Helpers;
namespace YLErp.Modules.TradeModule
{
/// <summary>
/// 交易服务基类
/// </summary>
public class TradeServiceBase : YLBaseService
{
int? _tradeProcessCount;
public TradeServiceBase(YLBaseService baseService) : base(baseService)
{
}
public TradeServiceBase(OptUserInfo userInfo) : base(userInfo)
{
}
public TradeServiceBase(OptUserInfo userInfo, YLContext dbContext) : base(userInfo, dbContext)
{
}
/// <summary>
/// 是否有交易审批(复核)任务
/// </summary>
protected bool HasTradeProcess()
{
return TradeProcessCount() > 0;
}
/// <summary>
/// 交易审批(复核)任务数量
/// </summary>
public int TradeProcessCount()
{
if (_tradeProcessCount == null)
{
_tradeProcessCount = DbContext.approvalprocess.Count(t => t.processType == "TradeProcess");
}
return _tradeProcessCount.Value;
}
/// <summary>
/// 获取所有交易审批节点(开仓流程 TradeProcess)。
/// </summary>
public List<approvalprocess> TradeProcess()
{
var tradeOrders = DbContext.approvalprocess.Where(t => t.processType == "TradeProcess").OrderBy(o => o.order).ToList();
return tradeOrders;
}
/// <summary>
/// 按交易推断流程类别(开仓/了结)获取审批节点(需求②)。
/// <para>了结类(平仓待复核/行权待复核/互换待复核)取 CloseProcess;开仓类取 TradeProcess。</para>
/// <para>未配置对应流程时返回空列表(由调用方决定是否直接通过)。</para>
/// </summary>
public List<approvalprocess> TradeProcessByCategory(trade td)
{
var category = ResolveProcessCategory(td);
return DbContext.approvalprocess.Where(t => t.processType == category).OrderBy(o => o.order).ToList();
}
/// <summary>
/// 按类别统计审批节点数(需求②)。
/// </summary>
public int TradeProcessCountByCategory(trade td)
{
return TradeProcessByCategory(td).Count;
}
/// <summary>
/// 初始化交易 审批点
/// </summary>
/// <param name="td"></param>
public void InitTradeProcessOrder(trade td,int userId)
{
var tradeProcess = TradeProcessByCategory(td);
// 该交易类别未配置审批流程 → 直接审批通过,无需审核
if (tradeProcess == null || tradeProcess.Count == 0)
{
td.ProcessOrderId = ProcessTradeLog.审批通过;
td.ProcessStatus = ProcessTradeStatus.通过审批.ToString();
td.ProcessOptDate = DateTime.Now;
return;
}
td.ProcessOrderId = ProcessTradeLog.审批中;
td.ProcessStatus = ProcessTradeStatus.审批中.ToString();
var groupId = UserBLL.GetApprovalProcessGroup(userId);
bool approvalBranch = tradeProcess.Any(x => x.approvalGroupId != 0);//审批流程有分支情况
if (td.ProcessOrderId == 1)
{
if (approvalBranch)//审批流程有分支情况
{
//该审批组属于流程的审批组或该审批组不属于流程审批组
var tradeProessBranchQuery = tradeProcess.FirstOrDefault(x => (x.approvalGroupId == groupId && x.approvalCondition == 1) || (x.approvalGroupId != groupId && x.approvalCondition == 2));
if (tradeProessBranchQuery == null)//都没有情况
{
throw new ServiceException("当前存在分支交易审批流程,检测到申请人不满足任意一个分支进入条件,无法提交审批,请更换申请人或调整审批组");
}
td.ProcessOrderBranch = tradeProessBranchQuery.node ?? 1;//审批组分子
}
else
{
td.ProcessOrderBranch = 0;
}
}
var branch = tradeProcess.Where(x=>x.node==td.ProcessOrderBranch).OrderBy(o=>o.order);
var branchFirst= branch.FirstOrDefault();
if (branchFirst!=null&& branchFirst.order==1&& td.ProcessOrderBranch!=0)
{
td.ProcessOrderId = 2;
}
// 需求①:进入审批流程时即应用触发条件——从起始节点开始,跳过所有不满足触发条件的节点。
// 若所有节点均不满足 → 直接审批通过(无需任何人审核)。
ApplyTriggerFromStart(tradeProcess, td, userId);
new TradeApprovalOAService(this).EnsureForCurrentNode(td);
}
/// <summary>
/// 从当前 ProcessOrderId 起点开始,按触发条件跳过无需审批的节点(需求①)。
/// <para>场景:交易提交进入审批流程时,若节点1、2配置的触发条件均不满足,则直接跳到节点3;
/// 若全部节点都不满足,则直接审批通过。</para>
/// </summary>
private void ApplyTriggerFromStart(List<approvalprocess> tradeProcess, trade td, int userId)
{
if (tradeProcess == null || tradeProcess.Count == 0) return;
// 取当前起点节点(主干 node=0)
var current = tradeProcess.FirstOrDefault(x => x.order == td.ProcessOrderId && x.node == 0);
// 若起点不在主干(如分支调整后 ProcessOrderId=2),取该 order 的主干节点
if (current == null)
{
current = tradeProcess.FirstOrDefault(x => x.order >= td.ProcessOrderId && x.node == 0);
}
if (current == null) return;
var ctx = BuildTriggerContext(td, userId);
var target = ConditionEvaluator.FindFirstTriggeredNode(tradeProcess, current, ctx);
if (target == null)
{
// 所有节点都不满足触发条件 → 直接审批通过,无需审核
td.ProcessOrderId = ProcessTradeLog.审批通过;
td.ProcessStatus = ProcessTradeStatus.通过审批.ToString();
td.ProcessOptDate = DateTime.Now;
return;
}
// target 即为第一个满足触发条件、需实际审批的节点
if (target.order != td.ProcessOrderId)
{
td.ProcessOrderId = target.order;
}
}
/// <summary>
/// 节点触发条件(需求①):在已确定下一审批节点 nextOrder 后,若该节点配置了 triggerCondition
/// 则只有「满足触发条件」才进入该节点审批;不满足则跳过该节点,继续向后寻找,直到找到可进入的节点或抵达流程末尾。
/// <para>语义:triggerCondition 为空 → 无条件进入审批;非空 → 满足才进入,不满足则跳过。</para>
/// <para>非侵入式:原有分支推进逻辑不变,仅在其结果之上叠加触发判断循环。</para>
/// </summary>
/// <param name="tradeProcess">当前流程的全部节点(已按 order 排序)</param>
/// <param name="nextOrder">原逻辑计算出的下一节点(可能为 null)</param>
/// <param name="ctx">条件求值业务上下文(已含 trade、本次交易名义本金等)</param>
/// <returns>最终应推进到的节点;若应结束流程则返回 null</returns>
protected static approvalprocess AdvanceThroughTriggerNodes(
List<approvalprocess> tradeProcess,
approvalprocess nextOrder,
ConditionContext ctx)
{
// 不满足触发条件的节点需跳过:循环向后找第一个可进入的节点。
while (nextOrder != null && !string.IsNullOrWhiteSpace(nextOrder.triggerCondition))
{
if (ConditionEvaluator.Evaluate(nextOrder.triggerCondition, ctx))
{
break; // 满足触发条件 → 进入该节点审批,停止跳过。
}
// 不满足触发条件 → 跳过该节点,向后取下一个主干节点(node=0),继续判断。
nextOrder = tradeProcess.FirstOrDefault(x => x.order > nextOrder.order && x.node == 0);
}
return nextOrder;
}
/// <summary>
/// 构建触发条件求值上下文:从 trade 及其关联的 trade_cash 取本次交易名义本金(了结场景)。
/// <para>本次交易名义本金 = 本次了结操作的 trade_cash.UnwindStockEqvNotional 绝对值。</para>
/// </summary>
protected ConditionContext BuildTriggerContext(trade td, int userId)
{
var ctx = new ConditionContext
{
UserId = userId,
Trade = td,
ProcessCategory = ResolveProcessCategory(td),
InitGroupId = UserBLL.GetApprovalProcessGroup(userId)
};
// 了结场景:取本次平仓名义本金
if (td != null && ctx.ProcessCategory == ProcessCategoryConst.Close)
{
if (td.TradeType == "收益互换")
{
// 互换:从 swap_event.EventData(JSON) 反序列化取 CloseNotionalValue
var swapEvent = DbContext.swap_event
.Where(x => x.SwapTradeId == td.id && !x.Invalid
&& (x.EventType == (int)SwapEventTypeEnum.平仓 || x.EventType == (int)SwapEventTypeEnum.互换))
.OrderByDescending(x => x.id)
.FirstOrDefault();
ctx.CurrentNotional = CalcSwapCloseNotionalFromEventData(swapEvent?.EventData);
}
else
{
// 期权:当次平仓名义本金 = 期初名义本金 × 平仓比例(UnwindPercentRate),取绝对值
var tc = DbContext.trade_cash
.Where(t => t.TradeId == td.id && t.ValidState == ConsGlobal.InValid && !t.IsDeleted)
.OrderByDescending(t => t.id)
.FirstOrDefault();
ctx.CurrentNotional = CalcOptionCloseNotional(td.OriginalStockEqvNotional, tc?.UnwindPercentRate);
}
}
return ctx;
}
/// <summary>
/// 互换:从 swap_event.EventData(JSON) 反序列化取 CloseNotionalValue 绝对值。
/// 容错:EventData 为 null/空/非法 JSON / unwindData=null 时返回 0(不影响审批阈值判断)。
/// 抽为静态纯函数以支持无库单测。
/// </summary>
public static double CalcSwapCloseNotionalFromEventData(string eventData)
{
if (string.IsNullOrEmpty(eventData)) return 0;
try
{
var unwindData = Newtonsoft.Json.JsonConvert.DeserializeObject<UnwindData>(eventData);
return unwindData != null ? Math.Abs((double)unwindData.CloseNotionalValue) : 0;
}
catch
{
return 0;
}
}
/// <summary>
/// 期权:当次平仓名义本金 = 期初名义本金 × 平仓比例(UnwindPercentRate),取绝对值。
/// 容错:任一参数为 null 时返回 0。抽为静态纯函数以支持无库单测。
/// </summary>
public static double CalcOptionCloseNotional(double? originalStockEqvNotional, double? unwindPercentRate)
{
if (!originalStockEqvNotional.HasValue || !unwindPercentRate.HasValue) return 0;
return Math.Abs(originalStockEqvNotional.Value * unwindPercentRate.Value);
}
/// <summary>
/// 需求①:判断按触发条件是否需要审批。
/// <para>取该交易类别的审批流程,若所有节点配置的触发条件都不满足当前业务,则无需审批(返回 false)。</para>
/// <para>若无审批流程或节点无触发条件,返回 true(需要审批)。</para>
/// </summary>
protected bool NeedApprovalByTrigger(trade td)
{
var tradeProcess = TradeProcessByCategory(td);
if (tradeProcess == null || tradeProcess.Count == 0) return true;
var start = tradeProcess.FirstOrDefault(x => x.node == 0);
if (start == null) start = tradeProcess.OrderBy(x => x.order).First();
var ctx = BuildTriggerContext(td, UserId);
var target = ConditionEvaluator.FindFirstTriggeredNode(tradeProcess, start, ctx);
return target != null;
}
/// <summary>
/// 按交易状态推断流程类别:开仓(TradeProcess) / 了结(CloseProcess)。需求②。
/// <para>委托给 ProcessCategoryConst.Resolve,供全局复用。</para>
/// </summary>
protected static string ResolveProcessCategory(trade td)
{
return ProcessCategoryConst.Resolve(td);
}
/// <summary>
/// 添加交易操作日志
/// </summary>
protected void AddTradeOperationHistoryAndSetParentTradeInfo(bool saveChanges, OtcTradeBase trade, string optType, string comments = null)
{
DbContext.TradeAuditLog.Add(new TradeAuditLog
{
TradeId = trade.id,
AuditFlag = TradeAuditFlag.operation,
OptType = optType,
Changes = comments ?? string.Empty,
DataType = "00",
OptId = UserId,
OptName = UserName,
OptDate = OptDate
});
if (trade.IsGroup == 0)
{
SetParentTrade(trade);
}
if (saveChanges)
{
DbContext.SaveChanges();
}
}
/// <summary>
/// 保存交易操作日志(调用SaveChanges)
/// </summary>
protected void SaveTradeOperationHistory(OtcTradeBase trade, string optType, string Comments = null)
{
AddTradeOperationHistoryAndSetParentTradeInfo(true, trade, optType, Comments);
}
/// <summary>
/// 添加交易元数据
/// </summary>
public void AddTradeMeta(bool saveChanges, int tradeId, string metaKey, string metaValue)
{
DbContext.TradeMeta.Add(new TradeMeta
{
TradeId = tradeId,
MetaKey = metaKey,
MetaValue = metaValue,
CreateTime = DateTime.Now
});
if (saveChanges)
{
DbContext.SaveChanges();
}
}
/// <summary>
/// 添加或更新交易元数据
/// </summary>
protected void AddOrUpdateTradeMeta(bool saveChanges, int tradeId, string metaKey, string metaValue)
{
new TradeMetaService(this).AddTradeMeta(tradeId, metaKey, metaValue, saveChanges);
}
/// <summary>
/// 设置交易元数据
/// </summary>
protected void SaveTradeMeta(int tradeId, string metaKey, string metaValue)
{
new TradeMetaService(this).AddTradeMeta(tradeId, metaKey, metaValue, true);
}
/// <summary>
/// 移除指定交易的相关数据
/// </summary>
/// <param name="saveChanges"></param>
/// <param name="tradeId">交易Id</param>
/// <param name="removeStartDate">删除数据开始日期</param>
/// <param name="tradeCashIds">指定要删除的TradeCash记录</param>
public void RemoveEodTradeAndFutureInfo(bool saveChanges, int tradeId, DateTime removeStartDate, IEnumerable<int> tradeCashIds = null, bool deleteOtherCashs = false, IEnumerable<int> tradeCashPreIds = null, bool isDelTradeCashIncludeStartDay = false, bool backToBegin = false)
{
var tradeIds = new List<int> { tradeId };
var trade = DbContext.trade.Find(tradeId);
if (removeStartDate.Year < 2000)
{
removeStartDate = new DateTime(2000, 1, 1);
}
if (trade.ParentTradeId > 0 && trade.IsGroup == 0)
{
var pTrideId = trade.ParentTradeId;
//DbContext.SetDebugLog();
var subIds =
DbContext.eod_trade
.Where(O => O.ValueDate >= removeStartDate && O.ParentTradeId == pTrideId)
.Select(O => O.TradeId)
.ToArray()
.GroupBy(O => O)
.Select(O => O.Key)
.ToArray();
//查询eodTrade表中相同父交易id仅有一条记录,且包含当前Id,表示不存在兄弟交易,应同步删除主交易
if (subIds.Length <= 1 && subIds.Contains(tradeId))
{
tradeIds.Add(trade.ParentTradeId);
}
}
var delEodArr = DbContext.eod_trade.Where(O => tradeIds.Contains(O.TradeId) && O.ValueDate >= removeStartDate);
DbContext.eod_trade.RemoveRange(delEodArr);
var delBodArr = DbContext.bod_Trade.Where(O => tradeIds.Contains(O.TradeId) && O.ValueDate > removeStartDate);
DbContext.bod_Trade.RemoveRange(delBodArr);
if (deleteOtherCashs)
{
//其他收入其他收入/支出在删除资金记录时跟着删除
var delTcOthers = DbContext.trade_cash.Where(x => x.TradeId == tradeId && !x.IsDeleted && x.Action == ClientCashInCashOut.人工操作_其他);
delTcOthers.ToList().ForEach(O => { O.ValidState = "InValid"; O.OptDate = DateTime.Now; O.IsDeleted = true; });
var delCashInCashOutOthers = DbContext.ClientCashInCashOut.Where(x => x.TradeId == tradeId && x.Action == ClientCashInCashOut.人工操作_其他);
DbContext.ClientCashInCashOut.RemoveRange(delCashInCashOutOthers);
}
var delTCArr = new List<trade_cash>();
var delTCPreArr = new List<trade_cash_pre>();
if (tradeCashIds != null && tradeCashIds.Any() || tradeCashPreIds != null && tradeCashPreIds.Any())
{
if (tradeCashIds != null && tradeCashIds.Any())
{
delTCArr = DbContext.trade_cash.Where(O => tradeCashIds.Contains(O.id) && !O.IsDeleted).ToList();
}
if (tradeCashPreIds != null && tradeCashPreIds.Any())
{
delTCPreArr = DbContext.trade_cash_pre.Where(O => tradeCashPreIds.Contains(O.id)).ToList();
}
}
else
{
if(isDelTradeCashIncludeStartDay)
{
delTCArr = DbContext.trade_cash.Where(O => O.TradeId == tradeId && (O.ValueDate >= removeStartDate && (O.HappenedDate == null || O.HappenedDate >= removeStartDate)) && O.ValidState != ConsGlobal.InValid && !O.IsDeleted).ToList();
if (removeStartDate.Year > 2000)
{
delTCArr = delTCArr.Where(O => O.Action != "系统操作-期权费" || O.Action == "系统操作-期权费" && O.ValueDate != trade.TradeDate && O.ValueDate != trade.PremiumPayDate).ToList();
}
delTCPreArr = DbContext.trade_cash_pre.Where(x => x.TradeId == tradeId && x.HappenedDate >= removeStartDate).ToList();
}
else
{
delTCArr = DbContext.trade_cash.Where(O => O.TradeId == tradeId && (O.ValueDate > removeStartDate && (O.HappenedDate == null || O.HappenedDate > removeStartDate)) && O.ValidState != ConsGlobal.InValid && !O.IsDeleted).ToList();
if (removeStartDate.Year > 2000)
{
delTCArr = delTCArr.Where(O => O.Action != "系统操作-期权费" || O.Action == "系统操作-期权费" && O.ValueDate != trade.TradeDate && O.ValueDate != trade.PremiumPayDate).ToList();
}
delTCPreArr = DbContext.trade_cash_pre.Where(x => x.TradeId == tradeId && x.HappenedDate > removeStartDate).ToList();
}
}
if (trade.IsGroup == 1)
{
var delTCArrIds = delTCArr.Select(x => x.id).ToArray();
//如果回退到开仓日,则删除所有的group action
var groupActions = DbContext.trade_cash_group_action.Where(x => x.ParentTradeId == tradeId && (delTCArrIds.Contains(x.ParentTradeCashId) || backToBegin));
if (groupActions.Any())
{
DbContext.trade_cash_group_action.RemoveRange(groupActions);
}
}
var mindate = removeStartDate;
if (delTCArr.Any())
{
delTCArr.ToList().ForEach(O => { O.ValidState = "InValid"; O.Status = TradeCashStatusEnum.已执行; O.OptDate = DateTime.Now; O.IsDeleted = true; });
mindate = delTCArr.Min(O => (DateTime?)O.ValueDate).GetValueOrDefault();
//如果权利金支付日在回退日期以后,则需要将权利金记录还原;
if (trade.PremiumPayDate != null && trade.TradeDate != trade.PremiumPayDate && trade.PremiumPayDate >= mindate)
{
DbContext.trade_cash
.Where(O =>
O.TradeId == tradeId &&
O.Action == ClientCashInCashOut.系统操作_期权费 &&
O.ValidState != "InValid" &&
!O.IsDeleted)
.ToList().ForEach(O =>
{
if (O.Status != TradeCashStatusEnum.冻结)
{
O.Comments = "";
O.OptId = UserId;
O.OptDate = DateTime.Now;
O.OptName = UserName;
O.Status = TradeCashStatusEnum.冻结;
}
});
}
}
var codeList = DbContext.trade_contract_r.Where(O => O.TradeId == tradeId && O.Type == "交易确认书").Select(O => O.ContractCode).ToList();
foreach (var code in codeList)
{
var infoTag = "_" + code.Replace("_", "-") + "_" + "了结_";
var notes = from note in DbContext.sac_report_notes
where
note.IsValid &&
note.InfoTag.Contains(infoTag) &&
note.ReportDate >= mindate
select note;
foreach (var item in notes)
{
item.changeStatus = true;
}
}
//DbContext.trade_cash.RemoveRange(delTCArr);
var tcIds = delTCArr.Select(O => O.id).ToArray();
var predicate_detail = PredicateBuilder.Create<trade_cash_detail>(x => tcIds.Contains(x.TradeCashId));
//如果权利金支付日在回退日期以后,则需要将权利金记录一起删除;
if (trade.PremiumPayDate != null && trade.TradeDate != trade.PremiumPayDate && trade.PremiumPayDate >= mindate)
{
predicate_detail = predicate_detail.Or(O => O.TradeId == tradeId && O.Action == ClientCashInCashOut.系统操作_期权费);
}
var tradeCashDetials = DbContext.trade_cash_detail.Where(predicate_detail);
if (tradeCashDetials.Any())
{
DbContext.trade_cash_detail.RemoveRange(tradeCashDetials);
}
if (PS.Config.SalesCommissionCalculation == "公式1")
{
//删除销售提成数据
var commissionInfos = DbContext.SalesCommissionDetail.Where(l => l.TradeID == tradeId && tcIds.Contains(l.TradeCashID)).ToList();
var useZero = DbContext.SalesCommissionDetail.Where(l => l.TradeID == tradeId && l.TradeCashID == 0).Any();//有tradeCashId为0的记录,则直接将有TradeCashId不为0的记录置为无效;
commissionInfos.ForEach(l =>
{
l.IsValid = false;
l.TradeCashID = (!useZero && l.TradeCashID == tcIds.First()) ? 0 : -1;
});
}
var trade_cash_swaps = DbContext.trade_cash_swap.Where(x => tcIds.Contains(x.TradeCashId));
if (trade_cash_swaps.Any())
{
if (PS.Config.IsGuoJun)
{
var flowid = trade_cash_swaps.Select(x => x.FlowId).ToList();
var swapflowmore = DbContext.trade_swap_flow_more.Where(x => flowid.Contains(x.id)).ToList();
swapflowmore.ForEach(x =>
{
x.IsDelete = true;
});
}
DbContext.trade_cash_swap.RemoveRange(trade_cash_swaps);
}
if (delTCPreArr.Any())
{
var tcPreIds = delTCPreArr.Select(O => O.id).ToArray();
var tradeCashDetialsPre = DbContext.trade_cash_detail.Where(x => tcPreIds.Contains(x.TradeCashPreId));
if (tradeCashDetials.Any())
{
DbContext.trade_cash_detail.RemoveRange(tradeCashDetials);
}
var trade_cash_swapsPre = DbContext.trade_cash_swap.Where(x => tcPreIds.Contains(x.TradeCashPreId));
if (trade_cash_swaps.Any())
{
DbContext.trade_cash_swap.RemoveRange(trade_cash_swaps);
}
DbContext.trade_cash_pre.RemoveRange(delTCPreArr);
}
var predicate_cashIncashOut = PredicateBuilder.Create<ClientCashInCashOut>(x => tcIds.Contains(x.TradeCashId));
//如果权利金支付日在回退日期以后,则需要将权利金记录一起删除;
if (trade.PremiumPayDate != null && trade.TradeDate != trade.PremiumPayDate && trade.PremiumPayDate >= mindate)
{
predicate_cashIncashOut = predicate_cashIncashOut.Or(O => O.TradeId == tradeId && O.Action == ClientCashInCashOut.系统操作_期权费);
}
var delCashInCashOutArr = DbContext.ClientCashInCashOut.Where(predicate_cashIncashOut);
DbContext.ClientCashInCashOut.RemoveRange(delCashInCashOutArr);
var delContractArr = DbContext.trade_contract_r.Where(O => tcIds.Contains(O.TradeCashId ?? 0) && O.IsValid).ToList();
//DbContext.trade_contract_r.RemoveRange(delContractArr);
delContractArr.ForEach(O => O.IsValid = false);
if (trade.TradeType == "远期")
{
var forwardMargin = DbContext.eod_forward_margin
.Where(x => x.TradeId == trade.id && x.ValueDate > removeStartDate);
DbContext.eod_forward_margin.RemoveRange(forwardMargin);
}
var tradeChanges = DbContext.trade_changes.Where(O => O.RecordId == trade.id && (O.OptDate > removeStartDate || (O.ChangeType == ConsInfoChangeType.Dividend && O.OptDate >= removeStartDate)));
DbContext.trade_changes.RemoveRange(tradeChanges);
if (saveChanges)
{
DbContext.SaveChanges();
}
}
/// <summary>
/// 移除指定交易的相关数据
/// </summary>
/// <param name="tradeId">交易Id</param>
/// <param name="removeStartDate">删除数据开始日期</param>
/// <param name="tradeCashIds">指定要删除的TradeCash记录</param>
protected void RemoveGroupEodTradeAndFutureInfo(IEnumerable<int> tradeIds, DateTime removeStartDate, IEnumerable<int> tradeCashIds = null)
{
if (removeStartDate.Year < 2000)
{
removeStartDate = new DateTime(2000, 1, 1);
}
var delEodArr = DbContext.eod_trade.Where(O => tradeIds.Contains(O.TradeId) && O.ValueDate >= removeStartDate);
DbContext.eod_trade.RemoveRange(delEodArr);
var delBodArr = DbContext.bod_Trade.Where(O => tradeIds.Contains(O.TradeId) && O.ValueDate > removeStartDate);
DbContext.bod_Trade.RemoveRange(delBodArr);
var tradeCashDetials = DbContext.trade_cash_detail.Where(x => tradeCashIds.Contains(x.TradeCashId));
if (tradeCashDetials.Any())
{
DbContext.trade_cash_detail.RemoveRange(tradeCashDetials);
}
var trade_cash_swaps = DbContext.trade_cash_swap.Where(x => tradeCashIds.Contains(x.TradeCashId));
if (trade_cash_swaps.Any())
{
DbContext.trade_cash_swap.RemoveRange(trade_cash_swaps);
}
var delCashInCashOutArr = DbContext.ClientCashInCashOut.Where(O => tradeCashIds.Contains(O.TradeCashId));
DbContext.ClientCashInCashOut.RemoveRange(delCashInCashOutArr);
var delContractArr = DbContext.trade_contract_r.Where(O => tradeCashIds.Contains(O.TradeCashId ?? 0) && O.IsValid).ToList();
delContractArr.ForEach(O => O.IsValid = false);
var tradeChanges = DbContext.trade_changes.Where(O => tradeIds.Contains(O.RecordId) && (O.OptDate > removeStartDate || (O.ChangeType == ConsInfoChangeType.Dividend && O.OptDate >= removeStartDate)));
DbContext.trade_changes.RemoveRange(tradeChanges);
DbContext.SaveChanges();
}
/// <summary>
/// 设置子交易数据到父交易,
/// 这个方法在AddTradeOperationHistory和SaveTradeOperationHistory中调用,
/// 除非必要才公开此方法以便调用
/// </summary>
protected void SetParentTrade(OtcTradeBase subTrade, trade parent = null)
{
if (subTrade is null)
{
throw new ArgumentNullException(nameof(subTrade));
}
if (parent is null)
{
if (subTrade.ParentTradeId == 0)
{
return;
}
parent = DbContext.trade.Find(subTrade.ParentTradeId);
if (parent == null)
{
throw new ServiceException("结构化交易父交易不存在");
}
}
//分组交易单独逻辑处理
if (subTrade.IsGroup == 2 && subTrade.TradeStatus == "审批中")
{
parent.OptId = subTrade.OptId;
parent.OptName = subTrade.OptName;
parent.OptDate = subTrade.OptDate;
parent.ProcessStatus = subTrade.ProcessStatus;
parent.ProcessOptDate = subTrade.OptDate;
parent.ProcessOrderId = subTrade.ProcessOrderId;
}
else
{
parent.OptId = subTrade.OptId;
parent.OptName = subTrade.OptName;
parent.OptDate = subTrade.OptDate;
parent.UnderlyingCode = subTrade.UnderlyingCode;
parent.UnderlyingId = subTrade.UnderlyingId;
parent.TradePrice = subTrade.TradePrice;
parent.TradeSinglePrice = subTrade.TradeSinglePrice;
parent.UnWindNotional = subTrade.UnWindNotional;
parent.Notional = subTrade.Notional;
parent.TradeAmount = subTrade.TradeAmount;
parent.StockEqvNotional = subTrade.StockEqvNotional;
parent.StockEqvNotionalReal = subTrade.StockEqvNotionalReal;
parent.OriginalNotional = subTrade.OriginalNotional;
parent.OriginalStockEqvNotional = subTrade.OriginalStockEqvNotional;
parent.HasPartialUnWind = subTrade.HasPartialUnWind;
parent.UnWindDate = subTrade.UnWindDate;
parent.ExerciseDate = subTrade.ExerciseDate;
parent.ValidState = subTrade.ValidState;
parent.DividendDate = subTrade.DividendDate;
parent.ProcessStatus = subTrade.ProcessStatus;
parent.ProcessOptDate = subTrade.OptDate;
parent.ProcessOrderId = subTrade.ProcessOrderId;
parent.ClientId = subTrade.ClientId;
parent.ClientName = subTrade.ClientName;
}
if (parent.id > 0 && ConsTrade.TradeCompleteStatus.Contains(subTrade.TradeStatus))
{
parent.TradeStatus = DbContext.trade
.Where(n => n.ParentTradeId == parent.id && n.ValidState != "InValid" && !ConsTrade.TradeCompleteStatus.Contains(n.TradeStatus))
.Select(n => n.TradeStatus).FirstOrDefault() ?? subTrade.TradeStatus;
}
else
{
parent.TradeStatus = subTrade.TradeStatus;
}
if (parent.ExerciseDate < subTrade.ExerciseDate)
{
parent.ExerciseDate = subTrade.ExerciseDate;
}
}
/// <summary>
/// 计息方式
/// </summary>
/// <returns></returns>
public string GetRateCalcMode(string rateCalcMode)
{
Dictionary<string,string> ret = new Dictionary<string,string>();
//11:算头算尾;10:算头不算尾;01:不算头算尾;00:不算头不算尾
ret.Add("算头算尾","11");
ret.Add("算头不算尾", "10");
ret.Add("不算头算尾", "01");
ret.Add("不算头不算尾", "00");
string rateCalcModeValue = "11";
ret.TryGetValue(rateCalcMode,out rateCalcModeValue);
return rateCalcModeValue;
}
}
/// <summary>
/// 交易流程类别常量(需求②):对应 approvalprocess.processType 的取值。
/// </summary>
public static class ProcessCategoryConst
{
/// <summary>开仓审批流程</summary>
public const string Open = "TradeProcess";
/// <summary>了结/平仓/行权审批流程</summary>
public const string Close = "CloseProcess";
/// <summary>
/// 按交易状态推断流程类别:处于平仓/行权/互换待复核的交易视为「了结」类操作。
/// </summary>
public static string Resolve(trade td)
{
if (td == null) return Open;
if (td.TradeStatus == ConsTrade.平仓待复核
|| td.TradeStatus == ConsTrade.行权待复核
|| td.TradeStatus == ConsTrade.互换待复核)
{
return Close;
}
return Open;
}
}
}