303 lines
15 KiB
C#
303 lines
15 KiB
C#
using BaseOUDAL;
|
||
using System;
|
||
using System.Collections.Generic;
|
||
using System.Linq;
|
||
using System.Text;
|
||
using System.Threading.Tasks;
|
||
using YLErp.Model.Enum;
|
||
using YLErp.Model;
|
||
using YLErp.DBModels.Consts;
|
||
using System.Linq.Expressions;
|
||
using YLErp.QdpModule;
|
||
using static YLErp.ConsGlobal;
|
||
using YLErp.Helpers;
|
||
using YLErp.Models;
|
||
|
||
namespace YLErp.Modules.SwapModule
|
||
{
|
||
public class SwapEndConfirmService : YLBaseService
|
||
{
|
||
public SwapEndConfirmService(OptUserInfo optUser) : base(optUser)
|
||
{
|
||
|
||
}
|
||
/// <summary>
|
||
/// 查询 到期结算报告和提前终止报告
|
||
/// </summary>
|
||
public SearchListResult<SwapTradeContractGroup> SearchEitherTradeWithCashList(SwapEndConfirmReq req)
|
||
{
|
||
var db = DbContext;
|
||
var actionList = new List<int>() { (int)SwapEventTypeEnum.平仓, (int)SwapEventTypeEnum.合成持仓 };
|
||
var types = new List<string>() { ContractTypeEnum.Clearing, ContractTypeEnum.UnWind };
|
||
var flowQuery = PredicateBuilder.Create<swap_flow_event>(n => actionList.Contains(n.EventType) && n.PayDirection > 0);
|
||
var eventQuery = PredicateBuilder.Create<swap_event>(n => actionList.Contains(n.EventType) && !n.Invalid);
|
||
var tradeQuery = buildTradeQuery(req);
|
||
if (!string.IsNullOrEmpty(req.UnderlyingCodes))
|
||
{
|
||
flowQuery = flowQuery.And(d => req.UnderlyingCodeList.Contains(d.UnderlyingCode));
|
||
}
|
||
if (req.UnWindDateEnd == null)
|
||
{
|
||
req.UnWindDateEnd = DateTime.MaxValue;
|
||
}
|
||
if (req.UnWindDateStart == null)
|
||
{
|
||
req.UnWindDateStart = DateTime.MinValue;
|
||
}
|
||
eventQuery = eventQuery.And(d => d.ValueDate >= req.UnWindDateStart && d.ValueDate <= req.UnWindDateEnd);
|
||
var query = from trade in db.trade.Where(tradeQuery)
|
||
join swapEvent in db.swap_event.Where(eventQuery) on trade.id equals swapEvent.SwapTradeId
|
||
join flowEvent in db.swap_flow_event.Where(flowQuery) on swapEvent.id equals flowEvent.EventId
|
||
join position in db.swap_position on flowEvent.PositionId equals position.PositionId
|
||
join tcrConfirm in db.trade_contract_r.Where(O => O.Type == ContractTypeEnum.Trade && O.IsValid) on trade.id equals tcrConfirm.TradeId into tcrConfirms
|
||
from tcrConfirm in tcrConfirms.DefaultIfEmpty()
|
||
select new SwapTradeContractGroup
|
||
{
|
||
id = flowEvent.id,
|
||
trade = trade,
|
||
swap_flow_event = flowEvent,
|
||
swap_event = swapEvent,
|
||
swap_position = position,
|
||
ConfirmContractR = tcrConfirm,
|
||
};
|
||
query = query.OrderByDescending(s => s.swap_flow_event.UnwindDate);
|
||
var retListResult = query.ToSearchList(req, isWithOrder: false);
|
||
|
||
var tradeIds = retListResult.rows.Select(s => s.swap_flow_event.SwapTradeId).Distinct().ToList();
|
||
var underlyingCodes = retListResult.rows.Select(r => r.swap_flow_event.UnderlyingCode).ToList();
|
||
var underlyings = DbContext.underlying_manager.Where(x => underlyingCodes.Contains(x.UnderlyingCode)).AsNoTracking().ToList();
|
||
List<trade_extend> extendList = DbContext.trade_extend.AsNoTracking().Where(p => tradeIds.Contains(p.TradeId)).ToList();
|
||
if (extendList == null)
|
||
{
|
||
extendList = new List<trade_extend>();
|
||
}
|
||
foreach (var x in retListResult.rows)
|
||
{
|
||
x.trade_contract_r = db.trade_contract_r.Where(O => types.Contains(O.Type) && O.IsValid && O.TradeId == x.trade.id && O.SwapFlowEventId == x.swap_flow_event.id).FirstOrDefault();
|
||
var swapEventFlow = x.swap_flow_event;
|
||
var um = underlyings.FirstOrDefault(x => x.UnderlyingCode == swapEventFlow.UnderlyingCode);
|
||
if (um != null && um.IsBond())
|
||
{
|
||
x.swap_flow_event.TradingAmountAvg = BondPriceConverter.ToDisplay(x.swap_flow_event.TradingAmountAvg);
|
||
x.swap_flow_event.TradingAmountFeeAvg = BondPriceConverter.ToDisplay(x.swap_flow_event.TradingAmountFeeAvg);
|
||
x.swap_flow_event.TradingAmountNetAvg = x.swap_flow_event.TradingAmountNetAvg.HasValue ? BondPriceConverter.ToDisplay(x.swap_flow_event.TradingAmountNetAvg.Value) : x.swap_flow_event.TradingAmountNetAvg;
|
||
x.swap_flow_event.TradingAmountNetFeeAvg = x.swap_flow_event.TradingAmountNetFeeAvg.HasValue ? BondPriceConverter.ToDisplay(x.swap_flow_event.TradingAmountNetFeeAvg.Value) : x.swap_flow_event.TradingAmountNetFeeAvg;
|
||
}
|
||
if (x.trade_contract_r != null)
|
||
{
|
||
GetTradeDocumentResults(req, x);
|
||
}
|
||
if (x.swap_event != null)
|
||
{
|
||
x.swap_event.unwindData = JsonHelper.Deserialize<UnwindData>(x.swap_event.EventData);
|
||
if (x.trade.StructureType == "多空组合")
|
||
{
|
||
UnwindData unwindData = new UnwindData();
|
||
unwindData.SwapCloseAmount = x.swap_flow_event.FloatPnlSum;
|
||
x.swap_event.unwindData = unwindData;
|
||
}
|
||
}
|
||
var extend = extendList.FirstOrDefault(p => p.TradeId == x.swap_flow_event.SwapTradeId);
|
||
if (x.swap_flow_event.PayDate == null || x.swap_flow_event.EventType != (int)SwapEventTypeEnum.平仓)
|
||
{
|
||
if (extend != null)
|
||
{
|
||
x.swap_flow_event.PayDate = x.swap_flow_event.EventDate.AddDays(extend.ExtendObj.SettlementRules);
|
||
}
|
||
}
|
||
}
|
||
return retListResult;
|
||
}
|
||
|
||
private Expression<Func<trade, bool>> buildTradeQuery(SwapEndConfirmReq req)
|
||
{
|
||
var tradeQuery = PredicateBuilder.Create<trade>(n => n.TradeType == "收益互换" && n.ValidState != ConsGlobal.InValid);
|
||
if (req.UserAssets != null && req.UserClients != null)
|
||
{
|
||
tradeQuery = tradeQuery.And(x => req.UserAssets.Contains(x.AssetId) || req.UserClients.Contains(x.ClientId));
|
||
}
|
||
if (!string.IsNullOrEmpty(req.TradeNumber))
|
||
{
|
||
tradeQuery = tradeQuery.And(d => d.TradeNumber.Contains(req.TradeNumber));
|
||
}
|
||
if (req.StartDateStart != DateTime.MinValue)
|
||
{
|
||
tradeQuery = tradeQuery.And(d => d.StartDate >= req.StartDateStart);
|
||
}
|
||
if (req.StartDateEnd != DateTime.MinValue)
|
||
{
|
||
var StartDateTemp = req.StartDateEnd.AddDays(1);
|
||
tradeQuery = tradeQuery.And(d => d.StartDate < StartDateTemp);
|
||
}
|
||
if (!string.IsNullOrEmpty(req.ClientIds))
|
||
{
|
||
tradeQuery = tradeQuery.And(d => req.ClientIdsInt.Contains(d.ClientId));
|
||
}
|
||
if (req.AssetIdList.Any())
|
||
{
|
||
tradeQuery = tradeQuery.And(d => req.AssetIdList.Contains(d.AssetId));
|
||
}
|
||
if (!string.IsNullOrEmpty(req.TraderNames))
|
||
{
|
||
tradeQuery = tradeQuery.And(d => req.TraderNamesList.Contains(d.TraderId));
|
||
}
|
||
if (req.ExerciseDateStart != null || req.ExerciseDateEnd != null)
|
||
{
|
||
if (req.ExerciseDateEnd == null)
|
||
{
|
||
req.ExerciseDateEnd = DateTime.MaxValue;
|
||
}
|
||
if (req.ExerciseDateStart == null)
|
||
{
|
||
req.ExerciseDateStart = DateTime.MinValue;
|
||
}
|
||
tradeQuery = tradeQuery.And(d => d.ExerciseDate >= req.ExerciseDateStart && d.ExerciseDate <= req.ExerciseDateEnd);
|
||
}
|
||
return tradeQuery;
|
||
}
|
||
private void GetTradeDocumentResults(SwapEndConfirmReq req, SwapTradeContractGroup x)
|
||
{
|
||
var documentQuery = (from trade in DbContext.trade_contract_document where trade.Code == x.trade_contract_r.ContractCode && trade.Type == x.trade_contract_r.Type select trade);
|
||
|
||
x.trade_contract_document = documentQuery.FirstOrDefault();
|
||
x.ContractDocUrl = x.trade_contract_document?.RelativePath;
|
||
x.ContractCode = x.trade_contract_r.ContractCode;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 发送交易确认书邮件
|
||
/// </summary>
|
||
/// <param name="tradeId"></param>
|
||
/// <returns></returns>
|
||
///
|
||
public string SendConfirmEamil(int tradeId)
|
||
{
|
||
var tradeContract = DbContext.trade_contract_r.Where(x => x.IsValid && x.TradeId == tradeId && x.Type == ContractTypeEnum.Trade).FirstOrDefault();
|
||
if (tradeContract == null)
|
||
{
|
||
// 定位要点:此处历史上静默 return "",前端当"已发送"但实际未发任何邮件。
|
||
// 打出 warn 并把失败原因透传给前端,便于区分"没生成过交易确认书"与"被重生成作废(IsValid=false)"。
|
||
var rows = DbContext.trade_contract_r.Where(x => x.TradeId == tradeId).Select(x => new { x.Type, x.IsValid, x.ContractCode }).ToList();
|
||
var detail = rows.Any()
|
||
? string.Join(";", rows.Select(r => $"Type={r.Type},IsValid={r.IsValid},Code={r.ContractCode}"))
|
||
: "trade_contract_r 无任何行";
|
||
LogFactory.GetLogger("SwapEndConfirm").Error(
|
||
$"发送交易确认书邮件中止: tradeId={tradeId} 无有效交易确认书(需 IsValid=true 且 Type={ContractTypeEnum.Trade}); 库内实际行: {detail}");
|
||
return "发送失败:未找到有效交易确认书,请先生成后再发送";
|
||
}
|
||
tradeContract.send_email_result = "发送中";
|
||
DbContext.SaveChanges();
|
||
var sendResult = SendEmail(tradeContract.ContractCode);
|
||
if (string.IsNullOrEmpty(sendResult))
|
||
{
|
||
tradeContract.send_email_result = "已发送";
|
||
}
|
||
else
|
||
{
|
||
tradeContract.send_email_result = "发送失败:" + sendResult;
|
||
}
|
||
DbContext.SaveChanges();
|
||
return string.IsNullOrEmpty(sendResult) ? "已发送" : "发送失败:" + sendResult;
|
||
}
|
||
public string SendConfirmEamil(string ContractCode)
|
||
{
|
||
//var tradeContract = DbContext.trade_contract_r.Where(x => x.IsValid && x.TradeId == tradeId && x.Type == ContractTypeEnum.Trade).FirstOrDefault();
|
||
//if (tradeContract == null)
|
||
//{
|
||
// return "";
|
||
//}
|
||
var contracts = DbContext.trade_contract_r.Where(o => o.ContractCode == ContractCode && o.Type == ContractTypeEnum.Trade && o.IsValid).ToArray();
|
||
foreach (var tradeContract in contracts)
|
||
{
|
||
tradeContract.send_email_result = "发送中";
|
||
DbContext.SaveChanges();
|
||
}
|
||
var contractCode = contracts.FirstOrDefault()?.ContractCode;
|
||
|
||
var sendResult = SendEmail(contractCode);
|
||
foreach (var tradeContract in contracts)
|
||
{
|
||
if (string.IsNullOrEmpty(sendResult))
|
||
{
|
||
tradeContract.send_email_result = "已发送";
|
||
}
|
||
else
|
||
{
|
||
tradeContract.send_email_result = "发送失败:" + sendResult;
|
||
}
|
||
}
|
||
|
||
DbContext.SaveChanges();
|
||
return string.IsNullOrEmpty(sendResult) ? "已发送" : "发送失败:" + sendResult;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 发送邮件
|
||
/// </summary>
|
||
/// <returns></returns>
|
||
private string SendEmail(string contractCode)
|
||
{
|
||
var baseUrl = Environment.GetEnvironmentVariable("BondOmsInterface_BaseUrl");
|
||
//换成contractCode
|
||
var sendEmailPUrl = "/swap/email/confirm/send?contractCode=" + contractCode;
|
||
if (!string.IsNullOrEmpty(baseUrl))
|
||
{
|
||
var httpHelper = new HttpHelper(baseUrl, null);
|
||
var result = httpHelper.GetRequestNoAuth<SendEmailResult>(sendEmailPUrl).Result;
|
||
if (result != null && result.success && !string.IsNullOrEmpty(result.data))
|
||
{
|
||
return "";
|
||
}
|
||
else
|
||
{
|
||
LogFactory.GetLogger().Error("发送邮件失败:contractCode=" + contractCode, new Exception(result?.message));
|
||
return result?.message ?? "发送邮件失败";
|
||
}
|
||
}
|
||
return "未配置邮件接口地址";
|
||
}
|
||
|
||
/// <summary>
|
||
/// EQD-5320 批量发送结算确认书邮件:服务端代理 bond-oms /swap/email/settle/batchSend。
|
||
/// 前端原直连 /trs_hub_api 反向代理(依赖 nginx 配置,未配的环境 404)——统一改走本代理,
|
||
/// 与债券计算器/SendEmail 同一条 BondOmsInterface_BaseUrl 出口,不再依赖前端网关。
|
||
/// 返回 空串=成功;非空=失败原因(透传 bond-oms message)。
|
||
/// </summary>
|
||
public string BatchSendSettleEmail(List<long> swapFlowEventIds)
|
||
{
|
||
var baseUrl = Environment.GetEnvironmentVariable("BondOmsInterface_BaseUrl");
|
||
if (string.IsNullOrEmpty(baseUrl))
|
||
{
|
||
return "未配置邮件接口地址(BondOmsInterface_BaseUrl)";
|
||
}
|
||
const string url = "/swap/email/settle/batchSend";
|
||
var logger = LogFactory.GetLogger("SwapEndConfirm");
|
||
var idsDesc = string.Join(",", swapFlowEventIds);
|
||
try
|
||
{
|
||
var result = new HttpHelper(baseUrl, null)
|
||
.PostRequestNoAuth<OmsSettleBatchSendReq, SendEmailResult>(url,
|
||
new OmsSettleBatchSendReq { swapFlowEventIds = swapFlowEventIds })
|
||
.Result;
|
||
logger.Info($"批量发送结算确认书: url={baseUrl}{url} ids=[{idsDesc}] → success={result?.success} message={result?.message}");
|
||
if (result == null)
|
||
{
|
||
return "邮件服务无响应";
|
||
}
|
||
return result.success ? "" : (result.message ?? "发送失败");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
logger.Error($"批量发送结算确认书异常: url={baseUrl}{url} ids=[{idsDesc}]", ex);
|
||
return "请求邮件服务异常:" + ex.GetBaseException().Message;
|
||
}
|
||
}
|
||
|
||
/// <summary>bond-oms SettleEmailSendParam 契约(字段名须与 Java 端一致,Jackson 按 swapFlowEventIds 绑定)</summary>
|
||
private class OmsSettleBatchSendReq
|
||
{
|
||
public List<long> swapFlowEventIds { get; set; }
|
||
}
|
||
}
|
||
}
|