Files
zszq-trs/YLErpDAL/Modules/SwapModule/SwapEndConfirmService.cs
T

303 lines
15 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 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; }
}
}
}