499 lines
28 KiB
C#
499 lines
28 KiB
C#
using BaseOUDAL;
|
||
using Newtonsoft.Json.Linq;
|
||
using YLErp.DBModels;
|
||
using YLErp.DBModels.Consts;
|
||
using YLErp.DBModels.Enums;
|
||
using YLErp.Modules.ApiModule;
|
||
using YLErp.Modules.TradeModule.DealModule;
|
||
|
||
namespace YLErp.Modules.TradeModule
|
||
{
|
||
/// <summary>
|
||
/// 收益互换审批节点的国联民生 OA 对接。
|
||
/// 本期只负责 HTTP 创建、查询和归档;审批结果由轮询任务回写本地流程。
|
||
/// </summary>
|
||
public class TradeApprovalOAService : YLBaseService
|
||
{
|
||
private const string Pending = "提交成功";
|
||
private const string Syncing = "同步中";
|
||
private const string Ending = "归档中";
|
||
private readonly IYcLogger _logger = LogFactory.GetLogger<TradeApprovalOAService>();
|
||
|
||
public TradeApprovalOAService(OptUserInfo userInfo) : base(userInfo) { }
|
||
public TradeApprovalOAService(YLBaseService baseService) : base(baseService) { }
|
||
|
||
public bool IsSupported(trade trade)
|
||
{
|
||
return trade != null && trade.TradeType == "收益互换";
|
||
}
|
||
|
||
public trade_approval_oa_result CreateForNode(trade trade, approvalprocess node)
|
||
{
|
||
if (!IsSupported(trade) || node == null || !node.isOaApproval)
|
||
return null;
|
||
|
||
var scenario = node.processType == "TradeProcess"
|
||
? "开仓"
|
||
: (trade.TradeStatus == ConsTrade.平仓待复核 ? "平仓" : "收益结算");
|
||
_logger.Info($"开始创建 OA 移动审批,交易:{trade.id},节点:{node.id},场景:{scenario}");
|
||
var record = new trade_approval_oa_result
|
||
{
|
||
trade_id = trade.id,
|
||
approval_process_id = node.id,
|
||
process_type = node.processType,
|
||
applicant_id = UserId,
|
||
applicant_login_name = Config("Applicant"),
|
||
status = "提交中",
|
||
is_valid = true
|
||
};
|
||
record.SetOpt(UserInfo);
|
||
DbContext.tradeApprovalOaResult.Add(record);
|
||
DbContext.SaveChanges();
|
||
|
||
try
|
||
{
|
||
var creator = Config("Creator");
|
||
var applicant = Config("Applicant");
|
||
if (string.IsNullOrWhiteSpace(creator) || string.IsNullOrWhiteSpace(applicant))
|
||
throw new ServiceException("OA 配置缺少 Creator 或 Applicant");
|
||
|
||
var spr = GetSprLoginName(node);
|
||
var payload = BuildPayload(trade, creator, applicant, spr, scenario);
|
||
record.request_payload = JsonHelper.Serialize(payload);
|
||
var responseText = Post("SubUrl", payload);
|
||
record.last_response = responseText;
|
||
var response = JObject.Parse(responseText);
|
||
var responseData = response["data"] as JObject;
|
||
var responseCode = (int?)response["code"];
|
||
var responseStatus = (string)response["status"];
|
||
_logger.Info($"OA 创建流程原始响应,交易:{trade.id},节点:{node.id},响应:{responseText}");
|
||
if ((int?)response["code"] != 0 || !string.Equals((string)response["status"], "SUCCESS", StringComparison.OrdinalIgnoreCase))
|
||
{
|
||
record.status = "提交失败";
|
||
record.oa_msg = (string)responseData?["msg"] ?? (string)response["msg"] ?? "OA 创建流程失败";
|
||
_logger.Error($"创建 OA 移动审批返回失败,交易:{trade.id},节点:{node.id},code:{responseCode},status:{responseStatus}");
|
||
}
|
||
else
|
||
{
|
||
record.status = Pending;
|
||
record.oa_fileid = (string)responseData?["requestid"] ?? (string)responseData?["requestId"];
|
||
record.oa_msg = (string)responseData?["msg"] ?? (string)response["msg"];
|
||
if (string.IsNullOrWhiteSpace(record.oa_fileid))
|
||
{
|
||
record.status = "提交失败";
|
||
_logger.Error($"创建 OA 移动审批未返回 requestId,交易:{trade.id},节点:{node.id}");
|
||
}
|
||
else
|
||
{
|
||
_logger.Info($"创建 OA 移动审批成功,交易:{trade.id},节点:{node.id},requestId:{record.oa_fileid}");
|
||
}
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
record.status = "提交失败";
|
||
record.oa_msg = ex.Message;
|
||
_logger.Error($"创建 OA 移动审批失败,交易:{trade.id},节点:{node.id}", ex);
|
||
}
|
||
DbContext.SaveChanges();
|
||
return record;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 交易首次进入审批时,确保当前 OA 节点已创建对应流程。
|
||
/// </summary>
|
||
public trade_approval_oa_result EnsureForCurrentNode(trade trade)
|
||
{
|
||
if (!IsSupported(trade) || trade.ProcessOrderId <= 0)
|
||
return null;
|
||
|
||
var node = GetCurrentNode(trade);
|
||
if (node?.isOaApproval != true)
|
||
return null;
|
||
|
||
var hasActiveRecord = DbContext.tradeApprovalOaResult.Any(x => x.trade_id == trade.id
|
||
&& x.approval_process_id == node.id
|
||
&& x.is_valid
|
||
&& (x.status == "提交中" || x.status == Pending || x.status == Syncing || x.status == Ending));
|
||
return hasActiveRecord ? null : CreateForNode(trade, node);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 本地撤回前归档交易仍在审批中的 OA 流程。
|
||
/// </summary>
|
||
public bool ArchiveActiveForTrade(trade trade, out string errorMessage)
|
||
{
|
||
errorMessage = null;
|
||
if (!IsSupported(trade))
|
||
return true;
|
||
|
||
var records = DbContext.tradeApprovalOaResult
|
||
.Where(x => x.trade_id == trade.id
|
||
&& x.is_valid
|
||
&& !string.IsNullOrWhiteSpace(x.oa_fileid)
|
||
&& (x.status == Pending || x.status == Syncing || x.status == Ending))
|
||
.OrderBy(x => x.id)
|
||
.ToList();
|
||
if (records.Count == 0)
|
||
{
|
||
_logger.Info($"本地撤回前未找到待归档 OA 流程,交易:{trade.id}");
|
||
return true;
|
||
}
|
||
|
||
foreach (var record in records)
|
||
{
|
||
if (!ArchiveForLocalAction(record, out errorMessage))
|
||
return false;
|
||
}
|
||
return true;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 本地审批或撤回前归档 OA。通过条件更新抢占记录,避免与轮询回写同时推进同一节点。
|
||
/// </summary>
|
||
public bool ArchiveForLocalAction(trade_approval_oa_result record, out string errorMessage)
|
||
{
|
||
errorMessage = null;
|
||
if (record == null)
|
||
return true;
|
||
|
||
_logger.Info($"本地操作前开始归档 OA 流程,交易:{record.trade_id},节点:{record.approval_process_id},requestId:{record.oa_fileid}");
|
||
|
||
if (!TryChangeStatus(record.id, Pending, Ending, null))
|
||
{
|
||
_logger.Info($"本地操作前归档 OA 流程未抢占记录,交易:{record.trade_id},节点:{record.approval_process_id},requestId:{record.oa_fileid}");
|
||
errorMessage = "OA 审批结果正在同步,请稍后再试";
|
||
return false;
|
||
}
|
||
|
||
if (!ForceEnd(record.oa_fileid))
|
||
{
|
||
TryChangeStatus(record.id, Ending, Pending, "OA 流程归档失败,等待重试");
|
||
_logger.Error($"本地操作前归档 OA 流程失败,交易:{record.trade_id},节点:{record.approval_process_id},requestId:{record.oa_fileid}");
|
||
errorMessage = "OA 流程归档失败,暂不能执行本地审批操作";
|
||
return false;
|
||
}
|
||
|
||
TryChangeStatus(record.id, Ending, "已归档", "本地操作前已归档 OA 流程");
|
||
_logger.Info($"本地操作前归档 OA 流程成功,交易:{record.trade_id},节点:{record.approval_process_id},requestId:{record.oa_fileid}");
|
||
return true;
|
||
}
|
||
|
||
public JObject Query(string requestId)
|
||
{
|
||
var query = "requestId=" + Uri.EscapeDataString(requestId ?? string.Empty)
|
||
+ "&systemToken=" + Uri.EscapeDataString(Config("SystemToken"))
|
||
+ "&systemName=" + Uri.EscapeDataString(Config("SystemName"));
|
||
var queryUrl = Config("TradeApprovalQueryUrl");
|
||
if (string.IsNullOrWhiteSpace(queryUrl))
|
||
throw new ServiceException("OA 配置缺少 TradeApprovalQueryUrl");
|
||
var url = queryUrl + (queryUrl.Contains("?") ? "&" : "?") + query;
|
||
var client = new HttpClientWrap(Config("BaseUrl"), 60);
|
||
return JObject.Parse(client.Get(url, null));
|
||
}
|
||
|
||
public bool ForceEnd(string requestId)
|
||
{
|
||
try
|
||
{
|
||
// 客户 OA 的 requestId 由 @RequestParam 接收,必须放在 URL QueryString。
|
||
var response = JObject.Parse(Post("ForceEndUrl", new { }, requestId));
|
||
var code = (int?)response["code"];
|
||
var status = (string)response["status"];
|
||
var responseData = response["data"] as JObject;
|
||
var resultCode = (string)responseData?["resultcode"];
|
||
var success = code == 0
|
||
&& string.Equals((string)response["status"], "SUCCESS", StringComparison.OrdinalIgnoreCase)
|
||
&& resultCode == "0";
|
||
_logger.Info($"OA 强制归档返回,requestId:{requestId},code:{code},status:{status},resultcode:{resultCode},success:{success}");
|
||
return success;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.Error($"归档 OA 流程失败,requestId:{requestId}", ex);
|
||
return false;
|
||
}
|
||
}
|
||
|
||
public List<string> SyncPendingStatuses(string requestId = null)
|
||
{
|
||
var messages = new List<string>();
|
||
var staleAt = DateTime.Now.AddMinutes(-5);
|
||
var pending = DbContext.tradeApprovalOaResult
|
||
.Where(x => x.is_valid
|
||
&& !string.IsNullOrWhiteSpace(x.oa_fileid)
|
||
&& (string.IsNullOrWhiteSpace(requestId) || x.oa_fileid == requestId)
|
||
&& (x.status == Pending || (x.status == Syncing && (x.last_query_time == null || x.last_query_time < staleAt))))
|
||
.ToList();
|
||
_logger.Info($"开始轮询 OA 移动审批状态,requestId:{requestId ?? "全部"},待查询数量:{pending.Count},同步中超时阈值:{staleAt:yyyy-MM-dd HH:mm:ss.fff}");
|
||
foreach (var record in pending)
|
||
{
|
||
var claimed = false;
|
||
try
|
||
{
|
||
_logger.Info($"查询 OA 移动审批状态,OA记录:{record.id},交易:{record.trade_id},节点:{record.approval_process_id},requestId:{record.oa_fileid},本地状态:{record.status},上次查询:{record.last_query_time:yyyy-MM-dd HH:mm:ss.fff}");
|
||
var response = Query(record.oa_fileid);
|
||
record.last_response = response.ToString();
|
||
var flow = response["data"] is JArray data
|
||
? data.FirstOrDefault(x => (string)x["requestId"] == record.oa_fileid) as JObject
|
||
: null;
|
||
var flowStatusType = (string)flow?["flowStatusType"];
|
||
var flowNode = (string)flow?["flowNode"];
|
||
// OA 状态口径(以查询结果中的 flowStatusType + flowNode 为准):
|
||
// 0 + 退回:审批人退回,本地按拒绝处理;
|
||
// 3 + 结束:审批人批准,本地按通过处理;
|
||
// 3 + 强制归档:OA 流程被强制关闭。正常情况下,本地发起
|
||
// forceEndOaFlow 后记录会先变为“归档中”、成功后变为“已归档”,
|
||
// 不会进入本轮待查询集合;若仍被查询到,则视为同意。
|
||
// 0(非退回):草稿,继续等待。
|
||
// 1 + 审批人:OA 待办,继续等待。
|
||
// 其他组合也必须继续等待,不会仅凭 flowStatusType=1 或 3 推进本地流程。
|
||
var isReturned = flowStatusType == "0" && flowNode == "退回";
|
||
var isApproved = flowStatusType == "3"
|
||
&& (flowNode == "结束" || flowNode == "强制归档");
|
||
_logger.Info($"OA 移动审批查询结果,OA记录:{record.id},交易:{record.trade_id},节点:{record.approval_process_id},requestId:{record.oa_fileid},是否命中流程:{flow != null},flowStatusType:{flowStatusType},flowNode:{flowNode},是否退回:{isReturned},是否通过:{isApproved},判定:{(isReturned ? "退回" : isApproved ? "通过" : "等待")}");
|
||
if (!isApproved && !isReturned)
|
||
{
|
||
_logger.Info($"OA 移动审批继续等待,更新最近查询时间但不修改本地审批流程,OA记录:{record.id},requestId:{record.oa_fileid},本地状态保持:{record.status}");
|
||
record.last_query_time = DateTime.Now;
|
||
DbContext.SaveChanges();
|
||
continue;
|
||
}
|
||
|
||
DbContext.SaveChanges();
|
||
_logger.Info($"OA 审批结果满足回写条件,准备将记录设为同步中,OA记录:{record.id},交易:{record.trade_id},节点:{record.approval_process_id},requestId:{record.oa_fileid},当前本地状态:{record.status}");
|
||
var claimAffectedRows = ClaimForSync(record.id);
|
||
if (claimAffectedRows != 1)
|
||
{
|
||
_logger.Info($"OA 审批结果未能将记录设为同步中,OA记录:{record.id},requestId:{record.oa_fileid},更新行数:{claimAffectedRows}");
|
||
continue;
|
||
}
|
||
claimed = true;
|
||
_logger.Info($"OA 审批结果已将记录设为同步中,OA记录:{record.id},requestId:{record.oa_fileid},更新行数:{claimAffectedRows}");
|
||
|
||
var trade = DbContext.trade.Find(record.trade_id);
|
||
if (trade == null)
|
||
{
|
||
var statusAffectedRows = SetClaimedStatus(record.id, "同步失败", "交易不存在");
|
||
_logger.Error($"OA 审批结果不再回写,交易不存在,OA记录:{record.id},requestId:{record.oa_fileid},目标状态:同步失败,更新行数:{statusAffectedRows}");
|
||
continue;
|
||
}
|
||
if (GetCurrentNode(trade)?.id != record.approval_process_id)
|
||
{
|
||
var statusAffectedRows = SetClaimedStatus(record.id, "同步忽略", "当前审批节点已变化,忽略 OA 结果");
|
||
_logger.Info($"OA 审批结果不再回写,当前审批节点已变化,OA记录:{record.id},requestId:{record.oa_fileid},目标状态:同步忽略,更新行数:{statusAffectedRows}");
|
||
continue;
|
||
}
|
||
var result = new TradeOpenService(this).UpdateTradeProcessLog(new TradeOpenReqModel
|
||
{
|
||
tradeId = trade.id,
|
||
status = isReturned ? "reject" : "pass",
|
||
comments = isReturned ? "OA 移动审批退回" : "OA 移动审批通过",
|
||
isFromOa = true,
|
||
notNeedOperationHistory = false
|
||
});
|
||
if (!string.IsNullOrWhiteSpace(result.ErrorMsg))
|
||
{
|
||
var statusAffectedRows = SetClaimedStatus(record.id, Pending, result.ErrorMsg);
|
||
_logger.Error($"OA 审批结果回写本地失败,OA记录:{record.id},交易:{record.trade_id},节点:{record.approval_process_id},requestId:{record.oa_fileid},目标状态:{Pending},更新行数:{statusAffectedRows},原因:{result.ErrorMsg}");
|
||
}
|
||
else
|
||
{
|
||
var targetStatus = isReturned ? "退回" : "通过";
|
||
var statusAffectedRows = SetClaimedStatus(record.id, targetStatus, null);
|
||
_logger.Info($"OA 审批结果回写本地成功,OA记录:{record.id},交易:{record.trade_id},节点:{record.approval_process_id},requestId:{record.oa_fileid},本地审批结果:{(isReturned ? "拒绝" : "通过")},OA记录目标状态:{targetStatus},更新行数:{statusAffectedRows}");
|
||
}
|
||
messages.Add(record.trade_id + ":" + (string.IsNullOrWhiteSpace(result.ErrorMsg) ? (isReturned ? "退回" : "通过") : Pending));
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
if (claimed)
|
||
{
|
||
var statusAffectedRows = SetClaimedStatus(record.id, Pending, ex.Message);
|
||
_logger.Error($"OA 轮询异常后恢复记录状态,OA记录:{record.id},requestId:{record.oa_fileid},目标状态:{Pending},更新行数:{statusAffectedRows}", ex);
|
||
}
|
||
else
|
||
{
|
||
record.last_query_time = DateTime.Now;
|
||
record.oa_msg = ex.Message;
|
||
DbContext.SaveChanges();
|
||
}
|
||
_logger.Error($"轮询 OA 审批状态失败,OA记录:{record.id},交易:{record.trade_id},节点:{record.approval_process_id},requestId:{record.oa_fileid},是否已设为同步中:{claimed}", ex);
|
||
}
|
||
}
|
||
_logger.Info($"OA 移动审批状态轮询结束,已处理数量:{messages.Count}");
|
||
return messages;
|
||
}
|
||
|
||
private string Post(string urlKey, object payload, string requestId = null)
|
||
{
|
||
var path = Config(urlKey);
|
||
if (string.IsNullOrWhiteSpace(path))
|
||
path = urlKey == "ForceEndUrl" ? "/gateway/oaflow/forceEndOaFlow" : "/gateway/oaflow/createOaFlow";
|
||
var query = "systemToken=" + Uri.EscapeDataString(Config("SystemToken"))
|
||
+ "&systemName=" + Uri.EscapeDataString(Config("SystemName"));
|
||
if (urlKey == "ForceEndUrl")
|
||
query += "&requestId=" + Uri.EscapeDataString(requestId ?? string.Empty);
|
||
else
|
||
query += "&flowId=" + Uri.EscapeDataString(Config("MobileFlowId", "245103"))
|
||
+ "&isnextflow=" + Uri.EscapeDataString(Config("TradeApprovalIsNextFlow", "0"));
|
||
var url = path + (path.Contains("?") ? "&" : "?") + query;
|
||
return new HttpClientWrap(Config("BaseUrl"), 60).PostJson(url, payload, null);
|
||
}
|
||
|
||
private string Config(string key, string fallback = "")
|
||
{
|
||
if (key == "BaseUrl" || key == "SubUrl" || key == "TradeApprovalQueryUrl" || key == "ForceEndUrl")
|
||
{
|
||
var mockEnabled = bool.TryParse(AppManager.GetConfiguration()["TradeApprovalOaMock:Enabled"], out var enabled) && enabled;
|
||
var mockValue = AppManager.GetConfiguration()[$"TradeApprovalOaMock:{key}"];
|
||
if (mockEnabled && !string.IsNullOrWhiteSpace(mockValue))
|
||
return mockValue;
|
||
}
|
||
var value = AppManager.GetConfiguration()[$"oa_confg:{key}"];
|
||
return string.IsNullOrWhiteSpace(value) ? fallback : value;
|
||
}
|
||
|
||
private approvalprocess GetCurrentNode(trade trade)
|
||
{
|
||
if (trade == null) return null;
|
||
var processType = IsCloseScenario(trade) ? "CloseProcess" : "TradeProcess";
|
||
return DbContext.approvalprocess
|
||
.Where(x => x.processType == processType
|
||
&& x.order == trade.ProcessOrderId
|
||
&& (x.node == trade.ProcessOrderBranch || x.node == 0))
|
||
.OrderByDescending(x => x.node == trade.ProcessOrderBranch)
|
||
.FirstOrDefault();
|
||
}
|
||
|
||
private static bool IsCloseScenario(trade trade)
|
||
{
|
||
return trade.TradeStatus == ConsTrade.平仓待复核
|
||
|| trade.TradeStatus == ConsTrade.行权待复核
|
||
|| trade.TradeStatus == ConsTrade.互换待复核;
|
||
}
|
||
|
||
private int ClaimForSync(int recordId)
|
||
{
|
||
var now = DateTime.Now;
|
||
return DbContext.Database.ExecuteSqlRaw(
|
||
"UPDATE trade_approval_oa_result SET status = {0}, last_query_time = {1} " +
|
||
"WHERE id = {2} AND is_valid = 1 AND (status = {3} OR (status = {0} AND (last_query_time IS NULL OR last_query_time < {4})))",
|
||
Syncing, now, recordId, Pending, now.AddMinutes(-5));
|
||
}
|
||
|
||
private bool TryChangeStatus(int recordId, string expectedStatus, string targetStatus, string message)
|
||
{
|
||
return DbContext.Database.ExecuteSqlRaw(
|
||
"UPDATE trade_approval_oa_result SET status = {0}, oa_msg = {1} WHERE id = {2} AND is_valid = 1 AND status = {3}",
|
||
targetStatus, message, recordId, expectedStatus) == 1;
|
||
}
|
||
|
||
private int SetClaimedStatus(int recordId, string status, string message)
|
||
{
|
||
return DbContext.Database.ExecuteSqlRaw(
|
||
"UPDATE trade_approval_oa_result SET status = {0}, oa_msg = {1} WHERE id = {2} AND is_valid = 1 AND status = {3}",
|
||
status, message, recordId, Syncing);
|
||
}
|
||
|
||
private string GetSprLoginName(approvalprocess node)
|
||
{
|
||
using var baseDb = DbContextFactory.GetErpBaseContext();
|
||
var users = (from roleUser in baseDb.RoleUsers
|
||
join user in baseDb.SystemUsers on roleUser.UserId equals user.Id
|
||
where roleUser.RoleId == node.roleId && user.State == (int)UserState.Enabled
|
||
select user.LoginName).ToList();
|
||
if (users.Count == 0)
|
||
throw new ServiceException($"关联 OA 的审批角色未配置启用用户,角色ID:{node.roleId}");
|
||
if (users.Count > 1)
|
||
throw new ServiceException($"关联 OA 的审批角色配置了多个启用用户,角色ID:{node.roleId}");
|
||
if (string.IsNullOrWhiteSpace(users[0]))
|
||
throw new ServiceException($"关联 OA 的审批用户未配置登录名,角色ID:{node.roleId}");
|
||
return users[0];
|
||
}
|
||
|
||
private Dictionary<string, object> BuildPayload(trade trade, string creator, string applicant, string spr, string scenario)
|
||
{
|
||
var positions = DbContext.swap_position
|
||
.Where(x => x.SwapTradeId == trade.id && x.IsInitial && !x.Invalid)
|
||
.ToList();
|
||
var floatPosition = positions.FirstOrDefault(x => x.IsInitial && !string.IsNullOrWhiteSpace(x.UnderlyingCode));
|
||
var underlyingName = floatPosition == null
|
||
? string.Empty
|
||
: DbContext.underlying_manager.Where(x => x.UnderlyingCode == floatPosition.UnderlyingCode)
|
||
.Select(x => x.UnderlyingName)
|
||
.FirstOrDefault() ?? string.Empty;
|
||
var interestPositions = positions.Where(x => string.IsNullOrWhiteSpace(x.UnderlyingCode)
|
||
&& x.InterestMode != (int)InterestModeEnum.初始预付金
|
||
&& x.InterestMode != (int)InterestModeEnum.追加预付金
|
||
&& x.InterestMode != (int)InterestModeEnum.Unknown).ToList();
|
||
var marginPositions = positions.Where(x => x.InterestMode == (int)InterestModeEnum.初始预付金
|
||
|| x.InterestMode == (int)InterestModeEnum.追加预付金).ToList();
|
||
var tradeDate = trade.TradeDate?.ToString("yyyy-MM-dd") ?? string.Empty;
|
||
var startDate = floatPosition?.PosiStartDate.ToString("yyyy-MM-dd") ?? string.Empty;
|
||
var maturityDate = floatPosition?.PosiMatuirityDate?.ToString("yyyy-MM-dd") ?? string.Empty;
|
||
var contractDays = floatPosition?.PosiMatuirityDate == null
|
||
? string.Empty
|
||
: ((floatPosition.PosiMatuirityDate.Value.Date - floatPosition.PosiStartDate.Date).Days).ToString();
|
||
|
||
var elementLines = new List<string>
|
||
{
|
||
"交易日期=" + tradeDate,
|
||
"审批角色=衍生品负责人",
|
||
"交易编号=" + (trade.TradeNumber ?? string.Empty),
|
||
"对手方=" + (trade.ClientName ?? string.Empty),
|
||
"交易类型=" + scenario,
|
||
"浮动端多空头=" + (floatPosition == null ? string.Empty : floatPosition.PositionType == 1 ? "多头" : "空头"),
|
||
"利息端方向=" + JoinValues(interestPositions.Select(x => x.InterestDirection == 1 ? "收取" : "支付")),
|
||
"簿记账户=" + (trade.AssetBookName ?? string.Empty),
|
||
"结构类型=" + (trade.StructureType ?? string.Empty),
|
||
"标的代码=" + (floatPosition?.UnderlyingCode ?? string.Empty),
|
||
"标的名称=" + underlyingName,
|
||
"期初收益率=" + (floatPosition?.InitYtm?.ToString("0.####%") ?? string.Empty),
|
||
"期初价格=" + (floatPosition?.PosiNetPrice.ToString("0.####") ?? string.Empty),
|
||
"行权方式=" + (trade.GetExerciseModeCn() ?? string.Empty),
|
||
"合约期限=" + contractDays,
|
||
"数量=" + (floatPosition?.PosiQuantity.ToString("0.####") ?? string.Empty),
|
||
"起始日=" + startDate,
|
||
"到期日=" + maturityDate,
|
||
"交易员=" + (trade.TraderName ?? string.Empty),
|
||
"支付日期=" + JoinValues(marginPositions.Select(x => x.HappenDate?.ToString("yyyy-MM-dd") ?? string.Empty)),
|
||
"期初预付金=" + JoinValues(marginPositions.Select(x => x.InterestPrincipalFix.ToString("0.00"))),
|
||
"期初预付金方向=" + JoinValues(marginPositions.Select(x => x.InterestDirection == 1 ? "收取" : "支付"))
|
||
};
|
||
|
||
if (scenario == "开仓")
|
||
{
|
||
elementLines.Add("平仓金额=");
|
||
elementLines.Add("平仓日期=");
|
||
}
|
||
else
|
||
{
|
||
var eventType = scenario == "平仓" ? (int)SwapEventTypeEnum.平仓 : (int)SwapEventTypeEnum.互换;
|
||
var swapEvent = DbContext.swap_event.Where(x => x.SwapTradeId == trade.id && !x.Invalid && x.EventType == eventType)
|
||
.OrderByDescending(x => x.id).FirstOrDefault();
|
||
var unwind = string.IsNullOrWhiteSpace(swapEvent?.EventData) ? null : JsonHelper.Deserialize<UnwindData>(swapEvent.EventData);
|
||
elementLines.Add("平仓金额=" + (unwind?.SwapCloseAmount.ToString("0.##") ?? ""));
|
||
elementLines.Add("平仓日期=" + (unwind?.UnwindDate?.ToString("yyyy-MM-dd") ?? swapEvent?.ValueDate.ToString("yyyy-MM-dd") ?? ""));
|
||
}
|
||
|
||
var elements = string.Join("<br/>", elementLines);
|
||
var payload = new Dictionary<string, object>
|
||
{
|
||
["title"] = "衍生品系统TRS移动审批-" + trade.TradeNumber,
|
||
["creater"] = creator,
|
||
["applicant"] = applicant,
|
||
["spr"] = spr,
|
||
["applyDate"] = DateTime.Now.ToString("yyyy-MM-dd"),
|
||
["elements"] = elements
|
||
};
|
||
return payload;
|
||
}
|
||
|
||
private static string JoinValues(IEnumerable<string> values)
|
||
{
|
||
return string.Join(";", values.Where(x => !string.IsNullOrWhiteSpace(x)));
|
||
}
|
||
}
|
||
}
|