TRS-ZS-574 交易确认书对接OA自动提交审批

This commit is contained in:
shangzhongyuan
2025-07-25 13:40:56 +08:00
parent 75793f9f4f
commit d9fff3e9b8
32 changed files with 730 additions and 126 deletions
@@ -1,4 +1,5 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace YLErp.DBModels
{
@@ -54,6 +55,13 @@ namespace YLErp.DBModels
[DisplayName("品种")]
public string VarietyIds { get; set; }
/// <summary>
/// oa账号
/// </summary>
[DisplayName("OA账号")]
[Column("oa_account")]
public string OaAccount { get; set; }
public List<int> VarietyIdList
{
get
@@ -0,0 +1,46 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace YLErp.DBModels
{
/// <summary>
/// 确认书OA审批结果
/// </summary>
[Table("trade_contract_oa_result")]
public class trade_contract_oa_result : DBModelBaseV6
{
/// <summary>
/// 交易id
/// </summary>
public int trade_id { get; set; }
/// <summary>
/// 确认书编号
/// </summary>
public string contract_code { get; set; }
/// <summary>
/// oa审批返回id
/// </summary>
public string oa_fileid { get; set; }
/// <summary>
/// 状态
/// </summary>
public string status { get; set; }
/// <summary>
/// oa审批返回消息
/// </summary>
public string oa_msg { get; set; }
/// <summary>
/// oa返回url
/// </summary>
public string oa_url { get; set; }
/// <summary>
/// 是否有效
/// </summary>
public bool is_valid { get; set; }
}
}
@@ -23,6 +23,10 @@ namespace YLErp
/// 用户名称
/// </summary>
public string UserName { get; set; }
/// <summary>
/// oa账户
/// </summary>
public string OaAccount { get; set; }
/// <summary>
/// 用户组
@@ -27,10 +27,10 @@ namespace YLErp.UnitTestProject.Modules.UnderlyingModule
// (5,10]年,应返回10年
var code3 = "UNLIMITED_10Y";
var maturity3 = new DateTime(2040, 1, 1);
var maturity3 = new DateTime(2030, 8, 2);
var underlying3 = CreateTestUnderlying(code3, null, maturity3);
var valueDate3 = maturity3.AddYears(-7); // 剩余13年
var valueDate3_2 = maturity3.AddYears(-10); // 剩余10年
var valueDate3_2 = DateTime.Now.Date; // 剩余10年
var result3 = UnderlyingHelper.GetApplicableMarginTerm(code3, valueDate3_2);
Assert.AreEqual(ConsMarginTerm.TenYear, result3, "(5,10]年 应为10年");
+1 -1
View File
@@ -43,7 +43,7 @@ namespace YLErp
});
ServiceProvider = services.BuildServiceProvider();
YLServiceLocator.SetServiceProvider(ServiceProvider);
OtcAppContext.Initialize(s =>
{
var s2 = s.Trim('/').Replace("/", "\\");
+9 -1
View File
@@ -1,4 +1,4 @@
using Autofac;
using Autofac;
using Microsoft.Extensions.Configuration;
using System.Collections.Concurrent;
using System.Diagnostics;
@@ -176,6 +176,14 @@ namespace YLErp
};
}
/// <summary>
/// 获取配置对象
/// </summary>
public static IConfiguration GetConfiguration()
{
return _configuration;
}
/// <summary>
/// 解析IOC容器中的实现(当前主要用于插件)
/// </summary>
+1
View File
@@ -289,6 +289,7 @@ namespace BaseOUDAL
State = source.State,
UserGroup = source.UserGroup,
AccountPost = source.AccountPost + "",
OaAccount=source.OaAccount
};
var result = query.OrderBy(u => u.id).ToSearchList(req);
+2
View File
@@ -37,5 +37,7 @@ namespace BaseOUDAL
public string UserGroup { get; set; }
public string AccountPost { get; set; }
public string OaAccount { get; set; }
}
}
+1
View File
@@ -405,5 +405,6 @@ namespace YLErp.BLL
public DbSet<client_margin_config> clientMarginConfig { get; set; }
public DbSet<client_margin_detail> clientMarginDetail { get; set; }
public DbSet<trade_contract_oa_result> tradeContractOaResult { get; set; }
}
}
+1
View File
@@ -65,6 +65,7 @@ namespace BaseOUDAL
AccountPost = newValue.AccountPost;
ParentId = newValue.ParentId;
VarietyIds = newValue.VarietyIds;
OaAccount=newValue.OaAccount;
db.SaveChanges();
//刷新缓存
UserBLL.UpdateUsers();
@@ -153,7 +153,7 @@ namespace YLErp.Modules.SwapModule
var curEodPosis = DealFloatPositions(posiList, realPosiList, eodPositions, todyEodPositions, settleDate, td, preSettleDate, flowEvents);
var posiLongNotional = curEodPosis.Where(s => s.PositionType == (int)PositionTypeFlag.Long).Sum(s => s.PosiNotionalValue);
var posiShortNotional = curEodPosis.Where(s => s.PositionType == (int)PositionTypeFlag.Short).Sum(s => s.PosiNotionalValue);
var closePosiNotional = curEodPosis.Where(s => s.TdCloseQty > 0).Sum(s => s.TdCloseQty * s.ContractSize * s.PosiNetPrice);
var closePosiNotional = curEodPosis.Where(s => s.TdCloseQty > 0).Sum(s => s.TdCloseQty * s.ContractSize * s.PosiGrossPrice);
var grossPrice = curEodPosis.Where(x => x.PosiDirection > 0).FirstOrDefault()?.PosiGrossPrice ?? 0;
//处理利息腿
DealInterests(interestList, eodPositions, todyEodPositions, settleDate, td, flowEvents, autoInterests, lastEodSwap, posiLongNotional, posiShortNotional, closePosiNotional, grossPrice, orginPv);
@@ -535,6 +535,9 @@ namespace YLErp.Modules.TradeModule.DealModule
from tcd in tcdsGroup.DefaultIfEmpty()
join sp in DbContext.swap_position.AsNoTracking().Where(s => s.IsInitial && s.PositionType > 0 && !s.Invalid)
on t.id equals sp.SwapTradeId
join oa in DbContext.tradeContractOaResult on t.id equals oa.trade_id into oaTemp
from oa in oaTemp.DefaultIfEmpty()
where oa == null || oa.is_valid == true
select new
{
t.id,
@@ -555,6 +558,9 @@ namespace YLErp.Modules.TradeModule.DealModule
tcr.OptDate,
tcr.OptName,
ContractDocId = tcd != null ? tcd.id : 0,
OAStatus = oa != null ? oa.status : "",
OAURL = oa != null ? oa.oa_url : "",
OAMessage = oa != null ? oa.oa_msg : ""
};
if (req.StartDate != null && req.StartDate != DateTime.MinValue)
@@ -611,7 +617,10 @@ namespace YLErp.Modules.TradeModule.DealModule
StampDocumentFileName = group.FirstOrDefault().StampDocumentFileName,
OptDate = group.FirstOrDefault().OptDate,
OptName = group.FirstOrDefault().OptName,
ContractDocId = group.FirstOrDefault().ContractDocId
ContractDocId = group.FirstOrDefault().ContractDocId,
OAStatus = group.FirstOrDefault().OAStatus,
OAURL = group.FirstOrDefault().OAURL,
OAMessage = group.FirstOrDefault().OAMessage
});
}
result = contractList.OrderByDescending(r => r.TradeDate).AsQueryable().ToSearchList(req);
@@ -694,5 +703,9 @@ namespace YLErp.Modules.TradeModule.DealModule
public int ContractDocId { get; set; }
public string OAStatus { get; set; }
public string OAURL { get; set; }
public string OAMessage { get; set; }
}
}
+25
View File
@@ -0,0 +1,25 @@
using System;
namespace YLErp.Modules.TradeModule
{
/// <summary>
/// OA接口响应模型
/// </summary>
public class OAResponse
{
public bool success { get; set; }
public long time { get; set; }
public int messageCode { get; set; }
public string message { get; set; }
public OAResponseData data { get; set; }
}
/// <summary>
/// OA接口响应数据模型
/// </summary>
public class OAResponseData
{
public string url { get; set; }
public string fileid { get; set; }
}
}
@@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace YLErp.Modules.TradeModule
{
/// <summary>
/// 交易OA处理结果
/// </summary>
public class TradeOAResult
{
public int TradeId { get; set; }
public bool Success { get; set; }
public string Message { get; set; }
}
/// <summary>
/// 交易OA批量处理结果
/// </summary>
public class TradeOABatchResult : TradeOAResult
{
// 继承自TradeOAResult,避免重复定义相同属性
// 如果需要批量处理特有的属性,可以在这里添加
}
}
@@ -0,0 +1,240 @@
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.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 = 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");
// 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 sp;
// 4. 计算名义本金汇总
var totalNotionalPrincipal = matchingPositions.Sum(sp => sp.PosiNotionalValue);
var posiTypeStr= currentPositionType == (int)PositionTypeFlag.Long ? "买入" : "卖出";
var marginDetail=UnderlyingHelper.GetApplicableMarginRate(trade.ClientId, trade.UnderlyingCode, trade.TradeDate.Value);
var marginRate = marginDetail?.init_rate ?? 0;
// 构建OA请求参数
var oaRequest = new
{
loginname = loginName,
systemkey = systemKey,
objectclass = objectClass,
title = $"交易确认书OA申请-{trade.ClientName}-{contractCode}",
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 = $"各位领导:\n\t\t经友好协商,我司拟与交易对手开展以下场外利率收益互换交易:\n浮动收益交付方\t存款收益接收方\t标的\t方向\t起始日\t到期日\t合约名义本金\t保证金支付方\n浙商证券\t{trade.ClientName}\t{trade.UnderlyingInstrumentTypeCn}\t{posiTypeStr}\t{trade.StartDate?.ToString("yyyy/M/d")}\t{trade.ExerciseDate?.ToString("yyyy/M/d")}\t{totalNotionalPrincipal:N0}元\t{trade.ClientName}\n交易标的满足浙商证券标的池管理要求,交易对手方为非交易商,提供【{marginRate*100}%】名义本金的履约担保品作为初始保证金。保证金预警线与盯市追保符合内外规要求。",
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("/fileDraft", oaRequest, null);
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接口时发生异常" };
}
}
}
}
@@ -262,12 +262,14 @@ namespace YLErp.Modules.UnderlyingModule
int totalMonths = (sourceDate.Value.Year - calcDate.Value.Year) * 12
+ (sourceDate.Value.Month - calcDate.Value.Month);
if (sourceDate.Value.Day < calcDate.Value.Day)
double fractionalMonth = 0;
if (sourceDate.Value.Day != calcDate.Value.Day)
{
totalMonths--;
int daysInMonth = DateTime.DaysInMonth(sourceDate.Value.Year, sourceDate.Value.Month);
fractionalMonth = (sourceDate.Value.Day - calcDate.Value.Day) / (double)daysInMonth;
}
double yearTerm = totalMonths / 12.0;
double yearTerm = (totalMonths + fractionalMonth) / 12.0;
return yearTerm;
}
/// <summary>
+1
View File
@@ -265,6 +265,7 @@ namespace YLErp.Web
user.UserToken = systemuser.UserToken;
user.UserId = systemuser.Id;
user.UserName = systemuser.Name;
user.OaAccount = systemuser.OaAccount;
using (var db = new ErpBaseContext())
{
user.Roles = (from o in db.RoleUsers
+5 -3
View File
@@ -684,13 +684,13 @@ namespace YLErp.Web.Controllers
public ActionResult userEditJson(IFormCollection collection, SystemUserDto req)
{
var result = UserEdit(req.Id, req.LoginName, req.Name, req.Password,
req.State, req.QQ, req.Mobile, req.Tel, req.RoleType, req.Email, req.UserGroup, collection, req.AccountPost, req.ParentId);
req.State, req.QQ, req.Mobile, req.Tel, req.RoleType, req.Email, req.UserGroup, collection, req.AccountPost, req.ParentId,req.OaAccount);
return result;
}
[HttpPost]
public ActionResult UserEdit(int id, string loginname, string name, string password, int state, string qq,
string Mobile, string tel, string roletype, string email, string userGroup, IFormCollection collection, int accountPost, int? parentId)
string Mobile, string tel, string roletype, string email, string userGroup, IFormCollection collection, int accountPost, int? parentId,string oaAccount)
{
var roles = roletype?.Split(',');
@@ -778,6 +778,7 @@ namespace YLErp.Web.Controllers
r.UserGroup = userGroup;
r.AccountPost = accountPost;
r.ParentId = parentId;
r.OaAccount = oaAccount;
return View(r);
}
else
@@ -797,7 +798,8 @@ namespace YLErp.Web.Controllers
UserGroup = userGroup,
AccountPost = accountPost,
ParentId = parentId,
VarietyIds = ""
VarietyIds = "",
OaAccount=oaAccount
};
r.Save(basedb, vo);
@@ -1,4 +1,4 @@
using Org.BouncyCastle.Ocsp;
using Org.BouncyCastle.Ocsp;
using System.Diagnostics.Contracts;
using YLErp.Commons;
using YLErp.Configuration;
@@ -108,6 +108,28 @@ namespace YLErp.Web.Controllers
}
}
/// <summary>
/// 发送OA
/// </summary>
/// <param name="id">交易ID</param>
/// <returns></returns>
[HttpPost]
public async Task<JsonResult> SendOA(int id)
{
try
{
var oaService = new TradeOAService(CurUser);
var result = await oaService.ProcessSingleOAAsync(id);
return result.Success ? JsonSuccess(result.Message) : JsonError(result.Message);
}
catch (Exception ex)
{
logger.Error($"提交OA失败: {ex.Message}", ex);
return JsonError($"提交OA失败: {ex.Message}");
}
}
private async Task<EmailTradeConfirmResultType> DoSendMailSywg(string tradeId, List<string> reciver,bool needDecryption = true)
{
try
+2 -61
View File
@@ -2628,7 +2628,7 @@ namespace YLErp.Web.Controllers
From = reqModel.TradeMarketReportDateType == "当日" ? reqModel.ValueDate : (reqModel.TradeMarketReportDateType == "当月区间" ? QdpCalendarHelper.GetNonHoliday(reqModel.ValueDate.Date.AddDays(1 - reqModel.ValueDate.Day)) : DateTime.MinValue),
To = reqModel.ValueDate,
CurUserName = exParam.CurUser.UserName,
SendContent = string.IsNullOrEmpty(sheets) ? new List<string>() { "账户状况", "持仓明细", "历史交易", "资金明细", "质押记录" } : sheets.Split(',').ToList(),
SendContent = string.IsNullOrEmpty(sheets) ? new List<string>() { "账户状况", "互换估值", "互换交易流水", "资金明细"} : sheets.Split(',').ToList(),
PayableFund = -1,//表示从后台获取
PayableMargin = -1,//表示从后台获取
ReportType = reqModel.ClientBalanceDataType,
@@ -2662,32 +2662,6 @@ namespace YLErp.Web.Controllers
message += "[客户" + clientName + "发送结算报告失败," + e.Message + "]; ";
}
}
if (reqModel.HasTradeDetails)
{
try
{
var req = new TradeDetailsReq()
{
ClientId = client.Key,
StartDate = reqModel.TradeDetailsDateType == "当日" ? reqModel.ValueDate : (reqModel.TradeDetailsDateType == "当月区间" ? QdpCalendarHelper.GetNonHoliday(reqModel.ValueDate.Date.AddDays(1 - reqModel.ValueDate.Day)) : DateTime.MinValue),
EndDate = reqModel.ValueDate,
DetailStatuses = "成交,提前终止,到期"
};
var tradeDetailsResult = new tradeController().SendReportMails(req, exParam.CurUser, exParam.detailTemplateName, exParam.tradeListHtmlViewPath, exParam.detailReceiver).Result;
var data = tradeDetailsResult.Value as Result;
if (!data.success)
{
message += "[客户" + clientName + "发送交易明细失败," + data.msg + "]; ";
}
}
catch (Exception e)
{
LogFactory.GetLogger<ClientController>().Error(e, "发送结算报告失败2");
var msg = e.InnerException != null ? e.InnerException.Message : e.Message;
message += "[客户" + clientName + "发送交易明细失败," + msg + "]; ";
}
}
return message;
}
@@ -2827,11 +2801,9 @@ namespace YLErp.Web.Controllers
var message = "";
var tempFolder = Server.MapPath("~/App_Docs/Temp");
var marketFolder = Path.Combine(tempFolder, "结算报告" + DateTime.Now.ToString("yyyyMMddHHmmss"));
var detailsFolder = Path.Combine(tempFolder, "交易明细" + DateTime.Now.ToString("yyyyMMddHHmmss"));
//如果没有相关数据,会创建一个空文件夹
Directory.CreateDirectory(marketFolder);
Directory.CreateDirectory(detailsFolder);
foreach (var clientId in clientIds)
{
@@ -2879,29 +2851,6 @@ namespace YLErp.Web.Controllers
message += "[客户" + clientName + "生成结算报告失败," + e.Message + "]; ";
}
}
if (input.HasTradeDetails)
{
try
{
var req = new TradeDetailsReq()
{
ClientId = int.Parse(clientId),
StartDate = input.TradeDetailsDateType == "当日" ? input.ValueDate : (input.TradeDetailsDateType == "当月区间" ? QdpCalendarHelper.GetNonHoliday(input.ValueDate.Date.AddDays(1 - input.ValueDate.Day)) : DateTime.MinValue),
EndDate = input.ValueDate,
DetailStatuses = "成交,提前终止,到期",
OutputFolder = detailsFolder
};
var biaoTou = DBCacheManager.Single.GetStr(CacheTable.TradeDetailsBiaoTou, detailTemplate);
var biaoWei = DBCacheManager.Single.GetStr(CacheTable.TradeDetailsBiaoWei, detailTemplate);
var tradeDetailsService = new TradeDetailsQueryService(CurUser);
tradeDetailsService.ExportReport(req, biaoTou, biaoWei, true);
}
catch (Exception e)
{
message += "[客户" + clientName + "生成交易明细失败," + e.Message + "]; ";
}
}
}
}
@@ -2909,22 +2858,15 @@ namespace YLErp.Web.Controllers
+ (input.TradeMarketReportDateType == "当日" ? (input.ValueDate.ToString("yyyyMMdd") + "-") : (input.TradeMarketReportDateType == "当月区间" ? (QdpCalendarHelper.GetNonHoliday(input.ValueDate.Date.AddDays(1 - input.ValueDate.Day)).ToString("yyyyMMdd") + "-") : ""))
+ input.ValueDate.ToString("yyyyMMdd")
+ ".zip";
var detailsZipFileName = "交易明细"
+ (input.TradeDetailsDateType == "当日" ? (input.ValueDate.ToString("yyyyMMdd") + "-") : (input.TradeDetailsDateType == "当月区间" ? (QdpCalendarHelper.GetNonHoliday(input.ValueDate.Date.AddDays(1 - input.ValueDate.Day)).ToString("yyyyMMdd") + "-") : ""))
+ input.ValueDate.ToString("yyyyMMdd")
+ ".zip";
var zipWebPath = $"/App_Docs/Temp/";
var zipLocalFolder = Server.MapPath(zipWebPath);
Directory.CreateDirectory(zipLocalFolder);
var marketZipFile = Path.Combine(zipLocalFolder, marketZipFileName);
var detailsZipFile = Path.Combine(zipLocalFolder, detailsZipFileName);
ZipHelper.ZipDirectory(marketFolder, marketZipFile);
ZipHelper.ZipDirectory(detailsFolder, detailsZipFile);
Directory.Delete(marketFolder, true);
Directory.Delete(detailsFolder, true);
if (!string.IsNullOrEmpty(message))
{
@@ -2933,8 +2875,7 @@ namespace YLErp.Web.Controllers
return JsonSuccess("批量下载成功", new
{
marketZipFile = zipWebPath + marketZipFileName,
detailsZipFile = zipWebPath + detailsZipFileName
marketZipFile = zipWebPath + marketZipFileName
});
}
@@ -563,8 +563,8 @@ namespace YLErp.Web.Controllers
}
else
{
var report = new SettlementReportService(user).GetReportData(emailData, userAssetUnits, template);
new SettlementReportService(user).GenerateFileEntry(report, "excel");
var report = new SettlementReportFotShanXiService(user).GetReportData(emailData, userAssetUnits, template);
new SettlementReportFotShanXiService(user).GenerateFileEntry(report, "excel");
}
}
+99
View File
@@ -0,0 +1,99 @@
using Microsoft.AspNetCore.SignalR;
using YLErp.DBModels;
using YLErp.Enums;
using YLErp.MailKit;
using YLErp.Model;
using YLErp.Modules.RiskModule;
using YLErp.Modules.SwapModule;
using YLErp.Modules.TradeModule;
using static YLErp.ConsGlobal;
namespace YLErp.Web.Hubs
{
public class TradeConfirmSendOAHub : Hub
{
private static bool isProcessing = false;
protected static IYcLogger Log = LogFactory.GetLogger(typeof(TradeConfirmSendOAHub).FullName);
static readonly Dictionary<string, DateTime> _dic = new Dictionary<string, DateTime>(StringComparer.OrdinalIgnoreCase);
private static readonly Dictionary<string, string> _clientProgressDic = new Dictionary<string, string>();
public async Task StartProcessing(string jsonString)
{
var req = JsonHelper.Deserialize<dynamic>(jsonString);
var client = Clients.Caller;
if (Context.User == null)
{
await client.SendAsync("ExceptionMessage", "登录已失效,请重新登录");
return;
}
var user = Server.CacheProvider.Get("loginUser^" + Context.User.GetUserId()) as UserInfo;
if (user == null)
{
await client.SendAsync("ExceptionMessage", "登录已失效,请重新登录");
return;
}
if (isProcessing)
{
await client.SendAsync("ExceptionMessage", "正在发送OA,请稍后再试");
return;
}
isProcessing = true;
try
{
await ProcessOAByIds(req.ids.ToString(), user);
isProcessing = false;
await Clients.Caller.SendAsync("ProcessCompleted", "");
}
catch (Exception ex)
{
Log.Error(ex.Message, ex);
isProcessing = false;
await Clients.Caller.SendAsync("ExceptionMessage", ex.Message);
}
}
private async Task ProcessOAByIds(string idsJson, UserInfo user)
{
var ids = JsonHelper.Deserialize<List<int>>(idsJson);
foreach (var id in ids)
{
var key = id.ToString();
_clientProgressDic[key] = "提交中";
await Clients.All.SendAsync("UpdateProgress", GetUpdateProcess(key, "提交中"));
var result = await ProcessSingleOA(id, user);
_clientProgressDic[key] = result;
await Clients.All.SendAsync("UpdateProgress", GetUpdateProcess(key, result));
}
}
private async Task<string> ProcessSingleOA(int id, UserInfo user)
{
try
{
var oaService = new TradeOAService(user);
var result = await oaService.ProcessSingleOAAsync(id);
return result.Success ? "提交成功" : result.Message;
}
catch (Exception ex)
{
Log.Error($"处理交易ID {id} 的OA时发生错误: {ex.Message}", ex);
return $"提交失败: {ex.Message}";
}
}
private string GetUpdateProcess(string key, string msg)
{
var resp = new
{
key = key,
send_oa_result = msg
};
return JsonHelper.Serialize(resp);
}
}
}
+3 -2
View File
@@ -1,4 +1,4 @@
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.ResponseCompression;
using Microsoft.AspNetCore.StaticFiles;
using Microsoft.Extensions.FileProviders;
@@ -150,7 +150,8 @@ try
{
endpoints.MapHub<SwapFlowCombookingHub>("/swapflow/combookinghub"); // 映射Hub路径
endpoints.MapHub<SwapFlowResetHub>("/swapflow/resethub"); // 映射Hub路径
endpoints.MapHub<SwapConfirmSendEmailHub>("/tradeconfirm/sendemailhub"); // 映射Hub路径
endpoints.MapHub<SwapConfirmSendEmailHub>("/tradeconfirm/sendemailhub"); // 映射Hub路径
endpoints.MapHub<TradeConfirmSendOAHub>("/tradeconfirm/sendoahub"); // 映射OA Hub路径
});
var provider = new FileExtensionContentTypeProvider();
provider.Mappings[".pdf"] = "application/pdf";
+1
View File
@@ -133,6 +133,7 @@
@Html.MyTextFor(model => model.Mobile)
@Html.MyTextFor(model => model.Tel)
@Html.MyTextFor(model => model.QQ)
@Html.MyTextFor(model => model.OaAccount)
@if (ConsUserGroup.HasGroup)
{
<div class="form-group col-md-6">
+1
View File
@@ -64,6 +64,7 @@
{ name: 'Tel', label: "座机", index: 'Tel', width: 100 },
{ name: 'Mobile', label: "手机", index: 'Mobile', width: 100 },
{ name: 'QQ', label: "QT", index: 'QQ', width: 100 },
{ name: 'OaAccount', label: "OA账号", index: 'OaAccount', width: 100 },
{ name: 'UserGroup', label: "用户组", sortable: false, width: 150, hide: page.HasGroup, optionHide: page.HasGroup },
{ name: 'Roles', label: "角色", sortable: false, align: 'left', width: 200 },
{ name: 'AccountPost', label: "账号岗位", sortable: false, align: 'left', width: 200 },
+1
View File
@@ -37,6 +37,7 @@
<tr>@Html.MyDisplayFor(m => m.Mobile)</tr>
<tr>@Html.MyDisplayFor(m => m.Tel)</tr>
<tr>@Html.MyDisplayFor(m => m.QQ)</tr>
<tr>@Html.MyDisplayFor(m => m.OaAccount)</tr>
@if (ConsUserGroup.HasGroup)
{
<tr>
@@ -69,6 +69,7 @@
}
@MyControls.Btn("批量下载交易确认书", "BatchDownLoadDoc()")
<button class="btn btn-primary" id="batchSendEmail" style="margin-left:16px;">批量发送邮件</button>
<button class="btn btn-primary" id="batchSendOA" style="margin-left:16px;">批量发送OA</button>
@if (PS.GetErpConfig().IsAutoSealAndUploadFiles && !PS.GetErpConfig().IsAutoSealAfterGeneratedBook)
{
@MyControls.Btn("批量确认书用印", "BatchSealContracts()")
@@ -77,12 +77,7 @@
var balanceTemplate = $("#setTemplate").val();
if (thisObj.Model.HasTradeDetails || thisObj.Model.HasTradeMarketReport) {
main.post("/client/BatchDownloadClientRiskMonitor", { input: thisObj.Model, balanceTemplate: balanceTemplate, detailTemplate: detailTemplate}).done(function (res) {
if (thisObj.Model.HasTradeDetails) {
window.open(res.obj.detailsZipFile);
}
if (thisObj.Model.HasTradeMarketReport) {
window.open(res.obj.marketZipFile);
}
window.open(res.obj.marketZipFile);
});;
}
else {
+44 -37
View File
@@ -1,32 +1,32 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"ylcms": "server=139.196.109.225;uid=root;pooling=true;port=3306;pwd=Midnight001!@#$;database=yltrs_ylcms;charset=utf8;Allow User Variables=True;SslMode=none;Connection Timeout=30;IgnoreCommandTransaction=true;",
"yladmin": "server=139.196.109.225;uid=root;pooling=true;port=3306;pwd=Midnight001!@#$;database=yltrs_admin;charset=utf8;Allow User Variables=True;SslMode=none;Connection Timeout=30;IgnoreCommandTransaction=true;",
"ylclient": "server=139.196.109.225;uid=root;pooling=true;port=3306;pwd=Midnight001!@#$;database=yltrs_client;charset=utf8;Allow User Variables=True;SslMode=none;Connection Timeout=30;IgnoreCommandTransaction=true;",
"bondoms": "server=139.196.109.225;uid=root;pooling=true;port=3306;pwd=Midnight001!@#$;database=bond_oms;charset=utf8;Allow User Variables=True;SslMode=none;Connection Timeout=30;IgnoreCommandTransaction=true;"
// "apex_oracle": "Data Source=(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)(HOST=122.112.205.57)(PORT=11521)))(CONNECT_DATA=(SERVICE_NAME=xe)));User ID=system;Password=oracle;"
},
"AppSettings": {
"VirtualPathRoot": "",
"UseRightAligned": "",
"PluginFolder": "D:\\gitCode\\zszq-trs\\Plugins\\build\\ZheShang\\Debug\\net6.0"
},
"LibreOffice": {
"ExePath": "",
"UserInstallation": ""
},
"EPPlus": {
"ExcelPackage": {
"LicenseContext": "NonCommercial" //The license context used
}
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"ylcms": "server=139.196.109.225;uid=root;pooling=true;port=3306;pwd=Midnight001!@#$;database=yltrs_ylcms;charset=utf8;Allow User Variables=True;SslMode=none;Connection Timeout=30;IgnoreCommandTransaction=true;",
"yladmin": "server=139.196.109.225;uid=root;pooling=true;port=3306;pwd=Midnight001!@#$;database=yltrs_admin;charset=utf8;Allow User Variables=True;SslMode=none;Connection Timeout=30;IgnoreCommandTransaction=true;",
"ylclient": "server=139.196.109.225;uid=root;pooling=true;port=3306;pwd=Midnight001!@#$;database=yltrs_client;charset=utf8;Allow User Variables=True;SslMode=none;Connection Timeout=30;IgnoreCommandTransaction=true;",
"bondoms": "server=139.196.109.225;uid=root;pooling=true;port=3306;pwd=Midnight001!@#$;database=bond_oms;charset=utf8;Allow User Variables=True;SslMode=none;Connection Timeout=30;IgnoreCommandTransaction=true;"
// "apex_oracle": "Data Source=(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)(HOST=122.112.205.57)(PORT=11521)))(CONNECT_DATA=(SERVICE_NAME=xe)));User ID=system;Password=oracle;"
},
"AppSettings": {
"VirtualPathRoot": "",
"UseRightAligned": "",
"PluginFolder": "D:\\project\\zsza-trs\\Plugins\\build\\ZheShang\\Debug\\net6.0"
},
"LibreOffice": {
"ExePath": "",
"UserInstallation": ""
},
"EPPlus": {
"ExcelPackage": {
"LicenseContext": "NonCommercial" //The license context used
}
},
"KafkaConfig": {
"BootstrapServers": "139.196.109.225:9092", // Kafka 集群的地址
"Acks": -1, // 消息确认方式,可以是 All(-1)、Leader(1)、None(0) 中的一种
@@ -65,14 +65,21 @@
"YiLian_HolidayResetGroupId": "YiLian_HedgingOrderGroup1", //交易日历缓存重新加载消费 Group
"YiLian_SwapFlowTopic": "YiLian_SwapFlow", //交易端同步流水
"YiLian_SwapFlowGroup": "YiLian_SwapFlowGroup1", //交易端同步流水消费组
"YiLian_SysUserInfoChangeTopic": "YiLian_SysUserInfoChangeTopic"//管理端系统用户变更
"YiLian_SysUserInfoChangeTopic": "YiLian_SysUserInfoChangeTopic" //管理端系统用户变更
},
"BondOmsInterface": {
"BaseUrl": "http://trs.yiliantech.com:8080/trs_hub_api"
},
"OrcaleDatabaseConfig": {
"Schema": "APEX_040000"
},
"SendKafakaCron": "10 0 * * *",
"MaxThreadPercent": 0.6 //占系统资源最多60%的线程数量
"BondOmsInterface": {
"BaseUrl": "http://trs.yiliantech.com:8080/trs_hub_api"
},
"OrcaleDatabaseConfig": {
"Schema": "APEX_040000"
},
"SendKafakaCron": "10 0 * * *",
"MaxThreadPercent": 0.6, //占系统资源最多60%的线程数量
"oa_confg": {
"BaseUrl": "http://ip:port",
"SystemKey": "M001", //OA系统key
"ObjectClass": "ZSZQ_ZDNX", //OA文件类别
"Urgent": 1, //紧急程度,1一般 2紧急 3加急
"Issend": 1 //流程是否自动发送,0不发送,1自动发送下一步
}
}
@@ -37,6 +37,16 @@ const colModelGrid = (new function () {
return html;
}
},
{ label: 'OA状态', name: 'OAStatus', width: 100, align: 'center' },
{ label: 'OA-URL', name: 'OAURL', width: 150, align: 'center',
formatter: function (cellValue, options, rowObject) {
if (cellValue && cellValue.length > 0) {
return '<a href="' + cellValue + '" target="_blank">查看</a>';
}
return '';
}
},
{ label: 'OA消息', name: 'OAMessage', width: 150, align: 'left' },
{ label: '交易数量', name: 'tradeCount', width: 80, align: 'center' },
{ label: 'tradeIds', name: 'tradeIds', hidden: true, optionHide: true }];
this.upload = function (encryptId, sendMail) {
@@ -156,6 +166,7 @@ $(function () {
// $("#batchSendMailBtn").click(BatchSendMail);
initJqGrid();
SendEmailHub()
SendOAHub(); // 初始化OA发送功能
});
// 初始化jqGrid函数
@@ -244,8 +255,12 @@ function showToolName(cellValue, options, rowObject) {
html += "<input type=\"button\" class=\"wentiEdit\" title='交易确认书未生成或未找到制定确认书文件' disabled readonly=\"true\" value=\"{1}\" />"
.template(rowObject.EncryptId, "发送Email");
}
var canUpload = page.canGenerate == true && rowObject.ContractDocUrl ? "" : "disabled";
var canUpload = page.canGenerate == true && rowObject.ContractDocUrl ? "" : "disabled";
html += "<input type=\"button\" class=\"wentiEdit\" value=\"上传\" onclick=\"colModelGrid.uploadNew('{0}')\" {1} />".template(rowObject.ContractCode, canUpload);
// 添加发送OA按钮
html += "<input type=\"button\" class=\"wentiEdit\" title='发送交易确认书到OA系统' onclick=\"sendOA('{0}')\" value=\"发送OA\" />"
.template(rowObject.id);
return html;
}
@@ -597,7 +612,147 @@ function SendEmailHub() {
}, 3000); // 3秒
});
}
function SendOAHub() {
// 创建SignalR连接并连接到服务器上的Hub
var connection = new signalR.HubConnectionBuilder()
.withUrl('/tradeconfirm/sendoahub')
.withAutomaticReconnect()
.build();
connection.start()
.then(function () {
$("#batchSendOA").click(function () {
var jgrid = jQuery('#listGrid');
var rowIds = jgrid.jqGrid('getGridParam', 'selarrrow');
if (rowIds.length == 0) {
main.alert("请至少选择一笔交易"); return;
}
// 检查选中的行中是否有无确认书的情况
var hasNoConfirmBook = false;
var hasOAStatus = false;
var ids = [];
var contractCodes = [];
rowIds.forEach((v, i, arr) => {
var rowId = v;
var data = jgrid.jqGrid('getRowData', rowId);
if (data.id) {
ids.push(data.id);
contractCodes.push(data.ContractCode);
// 检查是否有无确认书的情况
if (!data.ContractCode) {
hasNoConfirmBook = true;
}
// 检查是否有已发送OA的情况
if (data.OAStatus && data.OAStatus !== "") {
hasOAStatus = true;
}
}
});
// 根据不同情况显示不同提示
if (hasNoConfirmBook) {
main.confirm("选中的交易中有无确认书附件的情况,确定提交OA吗?", function() {
batchSubmitOA(connection, ids);
});
} else if (hasOAStatus) {
main.confirm("选中的交易中有已经提交过OA的情况,确定再次提交新的OA吗?", function() {
batchSubmitOA(connection, ids);
});
} else {
// 有确认书且OA状态为未发送过:无提示,直接在OA中起草内容
batchSubmitOA(connection, ids);
}
});
// 批量提交OA的函数
function batchSubmitOA(connection, ids) {
var req = {
ids: ids,
}
connection.invoke('StartProcessing', JSON.stringify(req));
main.waitMe(true);
}
}).catch(function (error) {
console.error(error);
});
// 监听服务器发送的消息。
connection.on('UpdateProgress', function (msg) {
if (msg != null && msg != '') {
let jsonObject = JSON.parse(msg);
SearchClick(true); // 刷新列表
}
});
// 监听服务器发送的异常消息。
connection.on('ExceptionMessage', function (msg) {
if (msg.indexOf("正在发送OA") < 0) {
main.waitMe(false);
SearchClick(true);
}
main.alert(msg);
});
// 监听服务器发送的完成消息。
connection.on('ProcessCompleted', function () {
main.alert("OA发送处理完毕");
main.waitMe(false);
SearchClick(true);
});
// 监听连接关闭,启动重连
connection.onclose(async (error) => {
console.log("连接断开");
// 等待3秒后尝试重新连接
setTimeout(async () => {
try {
await connection.start();
console.log("已重新连接");
} catch (err) {
console.error("重新连接失败: ", err);
}
}, 3000); // 3秒
});
}
// 单个发送OA函数
function sendOA(id) {
// 获取当前行数据
var jgrid = jQuery('#listGrid');
var rowData = jgrid.jqGrid('getRowData', id);
if (!rowData) return;
var tradeIdArray = rowData.tradeIds ? rowData.tradeIds.split(",") : [];
if (tradeIdArray.length === 0) return;
// 根据不同情况显示不同提示
if (!rowData.ContractCode) {
// 无确认书:提示用户
main.confirm("目前无确认书附件,确定提交OA吗?", function() {
submitOA(id);
});
} else if (!rowData.OAStatus || rowData.OAStatus === "") {
// 有确认书,OA状态为未发送过:无提示,直接在OA中起草内容
submitOA(id);
} else {
// OA状态已经有值:提示用户是否再次提交
main.confirm("已经提交过OA,确定再次提交一笔新的OA吗?", function() {
submitOA(id);
});
}
}
// 提交OA请求
function submitOA(id) {
var req = {
id: id
};
main.post("/TradeConfirmBook/SendOA", req).done(function (res) {
SearchClick(true);
}).fail(function (xhr, status, error) {
SearchClick(true);
});
}
function openWindowWithPost(url, data) {
var form = document.createElement("form");
form.target = "_blank";
@@ -55,8 +55,6 @@ namespace RealTimeCalcPositionService
{
try
{
using var clientDb = new ClientDBContext();
var clients= clientDb.client.ToList();
//系统交易日
var valuedate = valuedateBLL.ValueDate;
var clientSettles = new RealTimeClientBanlanceService(new OptUserInfo(0, "实时客户资金服务", OptUserFrom.Service)).GetBalances();
@@ -6,6 +6,7 @@ using YLErp;
using YLErp.Abstract;
using YLErp.Helpers;
using YLErp.Model;
using static alglib;
internal class Program
{
@@ -28,6 +29,7 @@ internal class Program
services.AddHostedService<ClientPosiTask>();
services.AddHostedService<Worker>();
services.AddHostedService<ClientNoDMABalanceTask>();
}).ConfigureLogging(logging =>
{
logging.ClearProviders();
@@ -35,8 +37,7 @@ internal class Program
})
.UseNLog()
.Build();
//YLServiceLocator.SetServiceProvider(host.Services);
YLServiceLocator.SetServiceProvider(host.Services);
await host.RunAsync();
}