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)
{
}
///
/// 查询 到期结算报告和提前终止报告
///
public SearchListResult SearchEitherTradeWithCashList(SwapEndConfirmReq req)
{
var db = DbContext;
var actionList = new List() { (int)SwapEventTypeEnum.平仓, (int)SwapEventTypeEnum.合成持仓 };
var types = new List() { ContractTypeEnum.Clearing, ContractTypeEnum.UnWind };
var flowQuery = PredicateBuilder.Create(n => actionList.Contains(n.EventType) && n.PayDirection > 0);
var eventQuery = PredicateBuilder.Create(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 extendList = DbContext.trade_extend.AsNoTracking().Where(p => tradeIds.Contains(p.TradeId)).ToList();
if (extendList == null)
{
extendList = new List();
}
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(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> buildTradeQuery(SwapEndConfirmReq req)
{
var tradeQuery = PredicateBuilder.Create(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;
}
///
/// 发送交易确认书邮件
///
///
///
///
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;
}
///
/// 发送邮件
///
///
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(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 "未配置邮件接口地址";
}
///
/// EQD-5320 批量发送结算确认书邮件:服务端代理 bond-oms /swap/email/settle/batchSend。
/// 前端原直连 /trs_hub_api 反向代理(依赖 nginx 配置,未配的环境 404)——统一改走本代理,
/// 与债券计算器/SendEmail 同一条 BondOmsInterface_BaseUrl 出口,不再依赖前端网关。
/// 返回 空串=成功;非空=失败原因(透传 bond-oms message)。
///
public string BatchSendSettleEmail(List 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(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;
}
}
/// bond-oms SettleEmailSendParam 契约(字段名须与 Java 端一致,Jackson 按 swapFlowEventIds 绑定)
private class OmsSettleBatchSendReq
{
public List swapFlowEventIds { get; set; }
}
}
}