273 lines
13 KiB
C#
273 lines
13 KiB
C#
using BaseOUDAL;
|
||
using NPOI.SS.Formula.Functions;
|
||
using Qdp.Foundation.Utilities;
|
||
using YLErp.DBModels;
|
||
using YLErp.DBModels.Consts;
|
||
using YLErp.DBModels.Enums;
|
||
using YLErp.Model;
|
||
using YLErp.Modules.ApiModule;
|
||
using YLErp.Modules.TradeDalModule;
|
||
using YLErp.Modules.UnderlyingModule;
|
||
using static YLErp.ConsGlobal;
|
||
|
||
namespace YLErp.Modules.TradeModule
|
||
{
|
||
/// <summary>
|
||
/// 交易OA服务
|
||
/// </summary>
|
||
public class TradeOAService : YLBaseService
|
||
{
|
||
private static readonly IYcLogger logger = LogFactory.GetLogger<TradeOAService>();
|
||
|
||
public TradeOAService(OptUserInfo userInfo) : base(userInfo)
|
||
{
|
||
}
|
||
|
||
public TradeOAService(YLBaseService baseService) : base(baseService)
|
||
{
|
||
}
|
||
|
||
/// <summary>
|
||
/// 处理单个交易的OA提交
|
||
/// </summary>
|
||
/// <param name="tradeId">交易ID</param>
|
||
/// <returns>处理结果</returns>
|
||
public async Task<TradeOAResult> ProcessSingleOAAsync(int tradeId)
|
||
{
|
||
try
|
||
{
|
||
// 获取交易信息
|
||
var trade = DbContext.trade.FirstOrDefault(x=>x.id==tradeId);
|
||
if (trade == null)
|
||
{
|
||
return new TradeOAResult {TradeId=tradeId, Success = false, Message = "未找到交易信息" };
|
||
}
|
||
|
||
// 获取交易确认书信息
|
||
var tcr = DbContext.trade_contract_r.Where(t => t.Type == ContractTypeEnum.Trade && t.IsValid&&t.TradeId==tradeId)
|
||
.FirstOrDefault();
|
||
trade_contract_document tdoc = null;
|
||
if (tcr!=null)
|
||
{
|
||
tdoc = DbContext.trade_contract_document.Where(t => t.Code == tcr.ContractCode&&t.Type==tcr.Type).FirstOrDefault();
|
||
}
|
||
|
||
// 将之前的OA记录设置为无效
|
||
var existingOAResults = DbContext.tradeContractOaResult.Where(x => x.trade_id == tradeId && x.is_valid == true).ToList();
|
||
foreach (var existingResult in existingOAResults)
|
||
{
|
||
existingResult.is_valid = false;
|
||
existingResult.SetOpt(UserInfo);
|
||
}
|
||
|
||
|
||
// 创建OA结果记录
|
||
var oaResult = new trade_contract_oa_result
|
||
{
|
||
trade_id = tradeId,
|
||
contract_code = tcr?.ContractCode,
|
||
status = "提交中",
|
||
is_valid = true,
|
||
};
|
||
oaResult.SetOpt(UserInfo);
|
||
DbContext.tradeContractOaResult.Add(oaResult);
|
||
await DbContext.SaveChangesAsync();
|
||
|
||
// 调用OA接口
|
||
var oaResp = await CallOAInterfaceAsync(trade, tcr?.ContractCode, tdoc, oaResult.id);
|
||
if (oaResp != null&& oaResp.success)
|
||
{
|
||
oaResult.status = "提交成功";
|
||
oaResult.oa_fileid = oaResp.data?.fileid;
|
||
oaResult.oa_msg = oaResp.message;
|
||
oaResult.oa_url = oaResp.data?.url;
|
||
}
|
||
else
|
||
{
|
||
oaResult.status = "提交失败";
|
||
oaResult.oa_msg = oaResp?.message ?? "OA接口调用失败";
|
||
}
|
||
await DbContext.SaveChangesAsync();
|
||
|
||
return new TradeOAResult { TradeId = tradeId, Success = oaResult.status== "提交成功", Message = oaResult.oa_msg };
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
logger.Error($"处理交易ID {tradeId} 的OA时发生错误: {ex.Message}", ex);
|
||
return new TradeOAResult { TradeId = tradeId, Success = false, Message = $"提交OA失败: {ex.Message}" };
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 调用OA接口
|
||
/// </summary>
|
||
/// <param name="trade">交易信息</param>
|
||
/// <param name="contractCode">合同编号</param>
|
||
/// <param name="tdoc">交易确认书文档</param>
|
||
/// <param name="oaResultId">OA结果记录ID</param>
|
||
/// <returns>是否成功</returns>
|
||
private async Task<OAResponse> CallOAInterfaceAsync(trade trade, string contractCode, trade_contract_document tdoc, int oaResultId)
|
||
{
|
||
try
|
||
{
|
||
// 获取OA配置
|
||
var oaConfigSection = AppManager.GetConfiguration().GetSection("oa_confg");
|
||
var baseUrl = oaConfigSection["BaseUrl"];
|
||
var loginName = string.Empty;
|
||
using (var adminDb = new ErpBaseContext()) {
|
||
var userInfo=adminDb.SystemUsers.FirstOrDefault(x=>x.Id==UserInfo.UserId);
|
||
loginName= userInfo?.OaAccount;
|
||
}
|
||
var systemKey = oaConfigSection["SystemKey"];
|
||
var objectClass = oaConfigSection["ObjectClass"];
|
||
var urgent = int.Parse(oaConfigSection["Urgent"] ?? "1");
|
||
var issend = int.Parse(oaConfigSection["Issend"] ?? "0");
|
||
|
||
if (string.IsNullOrEmpty(loginName))
|
||
{
|
||
logger.Error("OA账号未配置");
|
||
return new OAResponse() { data = null, success = false, message = "OA账号未配置" };
|
||
|
||
}
|
||
if (string.IsNullOrEmpty(baseUrl))
|
||
{
|
||
logger.Error("OA接口配置中BaseUrl为空");
|
||
return new OAResponse() { data = null, success = false, message = "OA接口配置中地址为空" };
|
||
}
|
||
|
||
// 处理附件
|
||
var attachments = new List<object>();
|
||
if (tdoc != null && !string.IsNullOrEmpty(tdoc.AbsolutePath))
|
||
{
|
||
try
|
||
{
|
||
if (File.Exists(tdoc.AbsolutePath))
|
||
{
|
||
var fileBytes = await File.ReadAllBytesAsync(tdoc.AbsolutePath);
|
||
var base64Content = Convert.ToBase64String(fileBytes);
|
||
var fileName = Path.GetFileName(tdoc.AbsolutePath);
|
||
|
||
attachments.Add(new
|
||
{
|
||
nrtitle = fileName,
|
||
nrtype= "1",
|
||
content = base64Content
|
||
});
|
||
|
||
logger.Info($"成功读取附件文件:{tdoc.AbsolutePath},文件大小:{fileBytes.Length} 字节");
|
||
}
|
||
else
|
||
{
|
||
logger.Error($"附件文件不存在:{tdoc.AbsolutePath}");
|
||
return new OAResponse() { data = null, success = false, message = $"附件文件不存在" };
|
||
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
logger.Error($"读取附件文件失败:{tdoc.AbsolutePath},错误:{ex.Message}", ex);
|
||
return new OAResponse() { data = null, success = false, message = $"读取附件文件失败" };
|
||
}
|
||
}
|
||
|
||
// 计算同一客户、同一交易日期、同一多空方向名义本金汇总
|
||
// 1. 先获取当前交易的多空方向
|
||
var currentPosition = DbContext.swap_position.AsNoTracking()
|
||
.Where(sp => sp.SwapTradeId == trade.id && sp.IsInitial && sp.PositionType > 0 && !sp.Invalid)
|
||
.FirstOrDefault();
|
||
var currentPositionType = currentPosition.PositionType;
|
||
// 2. 查询同一客户、同一交易日期的所有有效交易
|
||
var sameClientTrades = DbContext.trade.AsNoTracking()
|
||
.Where(t => t.ClientId == trade.ClientId
|
||
&& t.TradeDate == trade.TradeDate
|
||
&& t.ValidState != "InValid");
|
||
|
||
var exerciseDate = sameClientTrades.Select(x => x.ExerciseDate).OrderBy(o => o).FirstOrDefault();
|
||
// 3. 关联持仓表,筛选相同多空方向的持仓
|
||
var matchingPositions = from t in sameClientTrades
|
||
join sp in DbContext.swap_position.AsNoTracking()
|
||
on t.id equals sp.SwapTradeId
|
||
where sp.PositionType == currentPositionType
|
||
&& sp.IsInitial && sp.PositionType > 0 && !sp.Invalid
|
||
select t;
|
||
|
||
// 4. 计算名义本金汇总和加权平均保证金率
|
||
var totalNotionalPrincipal = matchingPositions.Sum(x => x.OriginalStockEqvNotional);
|
||
|
||
// 计算加权平均保证金率
|
||
decimal marginRate = 0;
|
||
if (totalNotionalPrincipal > 0)
|
||
{
|
||
decimal weightedMarginSum = 0;
|
||
foreach (var t in matchingPositions) {
|
||
var marginDetail = UnderlyingHelper.GetApplicableMarginRate(t.ClientId, t.UnderlyingCode, t.TradeDate.Value);
|
||
var rate = marginDetail?.init_rate ?? 0;
|
||
weightedMarginSum += rate*Convert.ToDecimal(t.OriginalStockEqvNotional);
|
||
}
|
||
marginRate = weightedMarginSum / Convert.ToDecimal(totalNotionalPrincipal);
|
||
// marginRate=Math.Round(marginRate, 4); // 保留四位小数
|
||
}
|
||
|
||
var posiTypeStr = currentPositionType == (int)PositionTypeFlag.Long ? "买入" : "卖出";
|
||
var underlyingType = sameClientTrades.Count() > 0 ? "利率债" : trade.UnderlyingInstrumentTypeCn;
|
||
string bgContent = @$"<p>各位领导:</p>
|
||
<p style=""margin-left: 40px"">经友好协商,我司拟与交易对手开展以下场外利率收益互换交易:</p>
|
||
<p style=""margin-left: 40px;"">浮动收益支付方: 浙商证券</p>
|
||
<p style=""margin-left: 40px;"">浮动收益接收方: {trade.ClientName}</p>
|
||
<p style=""margin-left: 111px;"">标的: {underlyingType}</p>
|
||
<p style=""margin-left: 111px;"">方向: {posiTypeStr}</p>
|
||
<p style=""margin-left: 95px;"">起始日: {trade.StartDate?.ToString("yyyy/M/d")}</p>
|
||
<p style=""margin-left: 95px;"">到期日: {exerciseDate?.ToString("yyyy/M/d")}</p>
|
||
<p style=""margin-left: 48px;"">合约名义本金: {totalNotionalPrincipal:N0}元</p>
|
||
<p style=""margin-left: 48px;"">保证金支付方: {trade.ClientName}</p>
|
||
<p>交易标的满足浙商证券标的池管理要求,交易对手方为非交易商,提供【{(marginRate*100).ToString("0.##")}%】名义本金的履约担保品作为初始保证金。保证金预警线与盯市追保符合内外规要求。</p>";
|
||
// 构建OA请求参数
|
||
var oaRequest = new
|
||
{
|
||
loginname = loginName,
|
||
systemkey = systemKey,
|
||
objectclass = objectClass,
|
||
title = $"关于场外利率债收益互换交易的审批盖章申请({trade.TradeDate.Value.ToString("yyyyMMdd")}-{trade.ClientName})",
|
||
urgent = urgent,
|
||
issend = issend,
|
||
copyattfileid = "",
|
||
je = (double)totalNotionalPrincipal,
|
||
filerela = new object[0],
|
||
groupno = "",
|
||
extinfo = new
|
||
{
|
||
busdata = new
|
||
{
|
||
BM1 = "",
|
||
BM2 = "",
|
||
BGRQ = trade.TradeDate.Value.ToString("yyyy-MM-dd"),
|
||
NGR1 = "",
|
||
NGR2 = "",
|
||
CKDX1 = "",
|
||
CKDX2 = "",
|
||
LB = "",
|
||
QSBGNR = bgContent,
|
||
MX = new object[0],
|
||
SQMX = new object[0]
|
||
},
|
||
attachments = attachments.ToArray()
|
||
}
|
||
};
|
||
logger.Info($"OA接口参数:{JsonHelper.Serialize(oaRequest)}");
|
||
// 调用OA接口
|
||
var httpClient = new HttpClientWrap(baseUrl);
|
||
var response = httpClient.PostJson("/ZSZQ/fileDraft", oaRequest, null);// fileDraft
|
||
logger.Info($"OA接口返回:{response}");
|
||
// 解析响应
|
||
var responseObj = JsonHelper.Deserialize<OAResponse>(response);
|
||
return responseObj;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
logger.Error($"调用OA接口时发生异常,交易ID:{trade.id},异常信息:{ex.Message}", ex);
|
||
return new OAResponse() { data = null, success = false, message = $"调用OA接口时发生异常" };
|
||
}
|
||
}
|
||
}
|
||
|
||
} |