692 lines
31 KiB
C#
692 lines
31 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>();
|
||
private static readonly System.Net.Http.HttpClient _httpClient = new System.Net.Http.HttpClient
|
||
{
|
||
Timeout = TimeSpan.FromMinutes(2)
|
||
};
|
||
|
||
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.code == 0)
|
||
{
|
||
oaResult.status = "提交成功";
|
||
oaResult.oa_fileid = oaResp.data?.requestid;
|
||
oaResult.oa_msg = oaResp.data?.msg ?? "提交成功";
|
||
}
|
||
else
|
||
{
|
||
oaResult.status = "提交失败";
|
||
oaResult.oa_msg = oaResp?.data?.msg ?? "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="filePath">文件路径</param>
|
||
/// <returns>附件数据列表(包含 annexId 和 annexName)</returns>
|
||
private async Task<List<UploadAttachmentData>> UploadAttachmentAsync(string filePath)
|
||
{
|
||
try
|
||
{
|
||
// 从配置文件读取上传配置
|
||
var oaConfigSection = AppManager.GetConfiguration().GetSection("oa_confg");
|
||
var uploadUrl = oaConfigSection["UploadUrl"];
|
||
var systemToken = oaConfigSection["SystemToken"];
|
||
|
||
if (string.IsNullOrEmpty(uploadUrl))
|
||
{
|
||
logger.Info("OA接口配置中UploadUrl为空");
|
||
throw new Exception("OA接口配置中上传地址为空");
|
||
}
|
||
if (string.IsNullOrEmpty(systemToken))
|
||
{
|
||
logger.Info("OA接口配置中SystemToken为空");
|
||
throw new Exception("OA接口配置中上传令牌为空");
|
||
}
|
||
|
||
if (!File.Exists(filePath))
|
||
{
|
||
logger.Info($"附件文件不存在:{filePath}");
|
||
throw new Exception("附件文件不存在");
|
||
}
|
||
|
||
// 创建multipart/form-data请求
|
||
using (var httpClient = new HttpClient())
|
||
{
|
||
// 设置超时时间为2分钟
|
||
httpClient.Timeout = TimeSpan.FromMinutes(2);
|
||
|
||
using (var form = new MultipartFormDataContent())
|
||
{
|
||
// 添加文件
|
||
using var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read);
|
||
var fileName = Path.GetFileName(filePath);
|
||
form.Add(new StreamContent(fileStream), "file", fileName);
|
||
|
||
// 添加systemToken
|
||
form.Add(new StringContent(systemToken), "systemToken");
|
||
|
||
// 发送请求
|
||
try
|
||
{
|
||
var response = await httpClient.PostAsync(uploadUrl, form);
|
||
response.EnsureSuccessStatusCode(); // 确保响应成功
|
||
var responseContent = await response.Content.ReadAsStringAsync();
|
||
|
||
logger.Info($"附件上传接口返回: {responseContent}");
|
||
|
||
// 解析响应
|
||
try
|
||
{
|
||
var uploadResponse = JsonHelper.Deserialize<UploadAttachmentResponse>(responseContent);
|
||
if (uploadResponse != null)
|
||
{
|
||
if (uploadResponse.code == "0" && uploadResponse.data != null)
|
||
{
|
||
// 返回完整的附件数据列表(包含 annexId 和 annexName)
|
||
logger.Info($"成功上传附件,获取到{uploadResponse.data.Count}个附件");
|
||
return uploadResponse.data;
|
||
}
|
||
else
|
||
{
|
||
logger.Error($"附件上传失败:{uploadResponse.status},code:{uploadResponse.code}");
|
||
throw new Exception($"附件上传失败:{uploadResponse.status}");
|
||
}
|
||
}
|
||
else
|
||
{
|
||
logger.Error("附件上传响应解析为空");
|
||
throw new Exception("附件上传响应解析失败");
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
logger.Error($"解析附件上传响应失败:{ex.Message},响应内容:{responseContent}", ex);
|
||
throw new Exception($"解析附件上传响应失败:{ex.Message}");
|
||
}
|
||
}
|
||
catch (TaskCanceledException ex)
|
||
{
|
||
logger.Error($"上传附件时任务被取消:{ex.Message}", ex);
|
||
throw new Exception("上传附件超时,请检查网络连接");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
logger.Error($"上传附件时发生异常:{ex.Message}", ex);
|
||
throw;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
logger.Error($"上传附件时发生异常:{ex.Message}", ex);
|
||
throw;
|
||
}
|
||
}
|
||
|
||
/// <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<GuoLianOAResponse> 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 subUrl = oaConfigSection["SubUrl"];
|
||
var systemToken = oaConfigSection["SystemToken"];
|
||
var systemName = oaConfigSection["SystemName"];
|
||
var isNextFlow = oaConfigSection["IsNextFlow"] ?? "0";
|
||
var flowId = oaConfigSection["FlowId"];
|
||
|
||
if (string.IsNullOrEmpty(baseUrl))
|
||
{
|
||
logger.Info("OA接口配置中BaseUrl为空");
|
||
return new GuoLianOAResponse() { code = -1, data = new GuoLianOAData { msg = "OA接口配置中地址为空" } };
|
||
}
|
||
if (string.IsNullOrEmpty(systemToken))
|
||
{
|
||
logger.Info("OA接口配置中SystemToken为空");
|
||
return new GuoLianOAResponse() { code = -1, data = new GuoLianOAData { msg = "OA接口配置中SystemToken为空" } };
|
||
}
|
||
|
||
// 处理附件
|
||
List<UploadAttachmentData> attachmentDataList = null;
|
||
if (tdoc != null && !string.IsNullOrEmpty(tdoc.AbsolutePath))
|
||
{
|
||
try
|
||
{
|
||
attachmentDataList = await UploadAttachmentAsync(tdoc.AbsolutePath);
|
||
logger.Info($"成功上传附件并获取到annexId:{string.Join(", ", attachmentDataList.Select(a => a.annexId))}");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
logger.Error($"上传附件失败:{tdoc.AbsolutePath},错误:{ex.Message}", ex);
|
||
return new GuoLianOAResponse() { code = -1, data = new GuoLianOAData { msg = "上传附件失败" } };
|
||
}
|
||
}
|
||
if (tdoc == null)
|
||
{
|
||
logger.Info("未找到确认书文档");
|
||
return new GuoLianOAResponse() { code = -1, data = new GuoLianOAData { msg = "未找到确认书文档" } };
|
||
}
|
||
|
||
//通过UserName找到LoginName
|
||
var applicantLoginName = DbContextFactory.GetErpBaseContext().SystemUsers.Where(u => u.Name == UserInfo.UserName).Select(u => u.LoginName).FirstOrDefault();
|
||
// 构建detailList,yycl(应用场景/附件)放在明细列表中
|
||
var detailList = new List<object>();
|
||
if (attachmentDataList != null && attachmentDataList.Any())
|
||
{
|
||
foreach (var att in attachmentDataList)
|
||
{
|
||
detailList.Add(new
|
||
{
|
||
detailIndex = 1,
|
||
yylxzx = 3, //用印类型 0公司章 1法人代表签章 2部门章 3营业部章 4无需用章
|
||
sl = 1,//用印数量
|
||
yzbgyx = "yangx",
|
||
qysdzz = "2809700652352016752",
|
||
yyslmx = 1,
|
||
yycl = att
|
||
});
|
||
}
|
||
}
|
||
|
||
// 构建dataMap
|
||
var dataMap = new
|
||
{
|
||
title = $"固定收益部申请【{contractCode}】TRS交易确认书",
|
||
urgentLevel = "正常",//未找到对应
|
||
applyDate = DateTime.Now.ToString("yyyy-MM-dd"),
|
||
applicant = applicantLoginName,
|
||
applicantComp = "国联民生证券总部",
|
||
htlx = 0,//合同类型 0业务 1非业务
|
||
ywlx = 1,
|
||
//业务类型 0资产管理业务金融产品筹设及调整事项合同(含ABS业务合同) 1金融交易类合同(除股票质押式回购业务部分购回、购回协议) 4采购类 3工会类 7党建活动类 2其他 6股票质押式回购业务部分购回、购回协议 8代销协议
|
||
htmj = 0,
|
||
htmb = 0,
|
||
//合同密级 0一般 1秘密 2绝密
|
||
qysdzz = "2809700652352016752",
|
||
yyr = "yangx," + applicantLoginName,
|
||
hehj = 0,
|
||
//合同缓急 0一般 1紧急 2特急
|
||
//去除后缀
|
||
htmc = Path.GetFileNameWithoutExtension(tdoc.FileName),//合同名称
|
||
sfgljy = 1,//是否关联交易 0是 1否
|
||
jflx = 0,//甲方类型 0企业 1个人
|
||
jf = trade.ClientName,//甲方
|
||
yyfs = 0,//用印方式 0电子章 1物理章
|
||
yflx = 0,//乙方类型 0企业 1个人
|
||
yf = "国联民生证券股份有限公司",//乙方
|
||
sfdxtzdfqsjf = 1,//是否短信通知对方签署 0是 1否
|
||
sfdxtzyf = 1,//是否短信通知乙方 0是 1否
|
||
htje = trade.OriginalStockEqvNotional,//合同金额
|
||
htyxksrq = trade.StartDate?.ToString("yyyy-MM-dd"),//合同有效期开始日期
|
||
htyxjsrq = trade.ExerciseDate?.ToString("yyyy-MM-dd"),//合同有效期结束日期
|
||
bz = "人民币",//币种
|
||
htqdrq = DateTime.Now.ToString("yyyy-MM-dd"),//合同签订日期
|
||
sfqdljxy = 2,
|
||
//是否签订廉洁协议 0已签 1未签 2无需签订
|
||
htbjjs = $"我司与{trade.ClientName}已完成NAFMII协议签署,并开展TRS交易:{contractCode},参与标的{trade.UnderlyingCode} {trade.UnderlyingName},参与标的数量面额{trade.OriginalStockEqvNotional}元人民币",
|
||
//合同背景介绍
|
||
detailList = detailList.Any() ? detailList : null,
|
||
creater = applicantLoginName
|
||
};
|
||
|
||
// 构建查询参数(不包含dataMap,dataMap放在请求体中)
|
||
// 注意:Java后端使用驼峰命名,参数名需与后端@RequestParam注解的名称一致
|
||
var query =
|
||
$"flowId={Uri.EscapeDataString(flowId)}" +
|
||
$"&isnextflow={Uri.EscapeDataString(isNextFlow ?? string.Empty)}" +
|
||
$"&systemToken={Uri.EscapeDataString(systemToken)}" +
|
||
$"&systemName={Uri.EscapeDataString(systemName ?? string.Empty)}";
|
||
|
||
var requestUrl = subUrl.Contains("?") ? $"{subUrl}&{query}" : $"{subUrl}?{query}";
|
||
logger.Info($"国联OA接口URL: {requestUrl}");
|
||
|
||
// 调用OA接口 (POST请求,dataMap放在请求体中)
|
||
string response = null;
|
||
try
|
||
{
|
||
// 设置较长的超时时间,默认15秒可能不够
|
||
var httpClient = new HttpClientWrap(baseUrl, 60); // 60秒超时
|
||
response = httpClient.PostJson(requestUrl, dataMap, null);
|
||
|
||
logger.Info($"国联OA接口返回: {response}");
|
||
|
||
// 解析响应
|
||
var responseObj = JsonHelper.Deserialize<GuoLianOAResponse>(response);
|
||
return responseObj;
|
||
}
|
||
catch (TaskCanceledException ex)
|
||
{
|
||
logger.Error($"调用OA接口时任务被取消:{ex.Message}", ex);
|
||
return new GuoLianOAResponse() { code = -2, data = new GuoLianOAData { msg = "调用OA接口超时,请检查网络连接" } };
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
logger.Error($"调用OA接口时发生异常:{ex.Message},响应内容:{response}", ex);
|
||
return new GuoLianOAResponse() { code = -2, data = new GuoLianOAData { msg = $"调用OA接口时发生异常: {ex.Message}" } };
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
logger.Error($"调用OA接口时发生异常,交易ID:{trade.id},异常信息:{ex.Message}", ex);
|
||
return new GuoLianOAResponse() { code = -2, data = new GuoLianOAData { msg = $"调用OA接口时发生异常: {ex.Message}" } };
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 批量查询并同步 OA 用印状态
|
||
/// </summary>
|
||
/// <returns>处理结果列表</returns>
|
||
public List<TradeOAResult> SyncOAStampStatus()
|
||
{
|
||
var results = new List<TradeOAResult>();
|
||
|
||
// 1. 查询提交成功且有效的 OA 记录
|
||
var pendingOAs = DbContext.tradeContractOaResult
|
||
.Where(x => x.is_valid && x.status == "提交成功" && !string.IsNullOrEmpty(x.oa_fileid))
|
||
.ToList();
|
||
|
||
if (!pendingOAs.Any())
|
||
{
|
||
logger.Info("没有待同步的OA用印记录");
|
||
return results;
|
||
}
|
||
|
||
logger.Info($"找到 {pendingOAs.Count} 条待同步OA用印记录");
|
||
|
||
foreach (var oa in pendingOAs)
|
||
{
|
||
try
|
||
{
|
||
// 2. 调用 OA 查询接口
|
||
var queryResult = QueryOAStatus(oa.oa_fileid);
|
||
if (queryResult == null)
|
||
{
|
||
logger.Info($"OA查询返回空,requestid:{oa.oa_fileid}");
|
||
continue;
|
||
}
|
||
|
||
if (queryResult.code != 0)
|
||
{
|
||
logger.Info($"OA查询失败,requestid:{oa.oa_fileid}, msg:{queryResult.msg}");
|
||
continue;
|
||
}
|
||
|
||
// 从 data 数组中找到匹配的 requestId
|
||
var flowData = queryResult.data?.FirstOrDefault(d => d.requestId == oa.oa_fileid);
|
||
if (flowData == null)
|
||
{
|
||
logger.Info($"OA查询结果中未找到匹配记录,requestid:{oa.oa_fileid}");
|
||
continue;
|
||
}
|
||
|
||
// 3. 判断用印是否完成:检查 detailMap 中是否有用印文件
|
||
if (!HasStampFiles(flowData.flowInfos))
|
||
{
|
||
logger.Info($"OA用印未完成,detailMap 中无文件信息,requestid:{oa.oa_fileid}");
|
||
continue;
|
||
}
|
||
|
||
// 4. 获取用印文件并同步
|
||
var syncResult = SyncStampFileToDoc(oa, flowData);
|
||
results.Add(syncResult);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
logger.Error($"同步OA状态失败,OA ID:{oa.id}, requestid:{oa.oa_fileid}, 错误:{ex.Message}", ex);
|
||
results.Add(new TradeOAResult { TradeId = oa.trade_id, Success = false, Message = $"同步失败: {ex.Message}" });
|
||
}
|
||
}
|
||
|
||
logger.Info($"OA用印同步完成,共处理 {results.Count} 条,成功 {results.Count(r => r.Success)} 条");
|
||
return results;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 查询 OA 审批状态
|
||
/// </summary>
|
||
/// <param name="requestId">OA请求ID</param>
|
||
/// <returns>查询结果</returns>
|
||
private OAStatusQueryResponse QueryOAStatus(string requestId)
|
||
{
|
||
try
|
||
{
|
||
var oaConfigSection = AppManager.GetConfiguration().GetSection("oa_confg");
|
||
var baseUrl = oaConfigSection["BaseUrl"];
|
||
var queryUrl = oaConfigSection["QueryUrl"];
|
||
var systemToken = oaConfigSection["SystemToken"];
|
||
var systemName = oaConfigSection["SystemName"];
|
||
|
||
if (string.IsNullOrEmpty(baseUrl) || string.IsNullOrEmpty(queryUrl))
|
||
{
|
||
logger.Info("OA查询接口配置不完整");
|
||
return null;
|
||
}
|
||
|
||
var requestParam = $"{queryUrl}?requestId={Uri.EscapeDataString(requestId)}&systemToken={Uri.EscapeDataString(systemToken)}&systemName={Uri.EscapeDataString(systemName ?? string.Empty)}";
|
||
|
||
var httpClient = new HttpClientWrap(baseUrl, 30);
|
||
var response = httpClient.Get(requestParam, null);
|
||
|
||
logger.Info($"查询OA用印状态返回: {response}");
|
||
|
||
if (string.IsNullOrEmpty(response))
|
||
{
|
||
return null;
|
||
}
|
||
|
||
return JsonHelper.Deserialize<OAStatusQueryResponse>(response);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
logger.Error($"查询OA状态异常,requestid:{requestId}, 错误:{ex.Message}", ex);
|
||
return null;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 通过 OA 接口下载用印文件
|
||
/// </summary>
|
||
/// <param name="fileId">文件 ID</param>
|
||
/// <returns>文件字节数组</returns>
|
||
private byte[] DownloadOAFile(string fileId)
|
||
{
|
||
try
|
||
{
|
||
if (string.IsNullOrEmpty(fileId))
|
||
{
|
||
return null;
|
||
}
|
||
|
||
var oaConfigSection = AppManager.GetConfiguration().GetSection("oa_confg");
|
||
var baseUrl = oaConfigSection["BaseUrl"];
|
||
var downloadFileUrl = oaConfigSection["DownloadFileUrl"];
|
||
var systemToken = oaConfigSection["SystemToken"];
|
||
var systemName = oaConfigSection["SystemName"];
|
||
|
||
if (string.IsNullOrEmpty(baseUrl) || string.IsNullOrEmpty(downloadFileUrl))
|
||
{
|
||
logger.Info("OA下载接口配置不完整");
|
||
return null;
|
||
}
|
||
|
||
var requestUrl = $"{baseUrl.TrimEnd('/')}/{downloadFileUrl.TrimStart('/')}?fileid={Uri.EscapeDataString(fileId)}&systemToken={Uri.EscapeDataString(systemToken)}&systemName={Uri.EscapeDataString(systemName ?? string.Empty)}";
|
||
logger.Info($"通过OA接口下载用印文件: fileid={fileId}");
|
||
|
||
using (var httpClient = new System.Net.Http.HttpClient { Timeout = TimeSpan.FromMinutes(2) })
|
||
{
|
||
var response = httpClient.GetAsync(requestUrl).Result;
|
||
var fileBytes = response.Content.ReadAsByteArrayAsync().Result;
|
||
|
||
// 检查返回的是否为错误 JSON
|
||
if (response.Content.Headers.ContentType?.MediaType == "application/json" ||
|
||
(fileBytes.Length > 0 && fileBytes[0] == (byte)'{'))
|
||
{
|
||
var responseStr = System.Text.Encoding.UTF8.GetString(fileBytes);
|
||
logger.Error($"下载用印文件接口返回错误: {responseStr}");
|
||
return null;
|
||
}
|
||
|
||
logger.Info($"通过OA接口下载用印文件成功,大小: {fileBytes.Length} 字节");
|
||
return fileBytes;
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
logger.Error($"通过OA接口下载用印文件失败: fileid={fileId}, 错误:{ex.Message}", ex);
|
||
return null;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 将 OA 用印文件同步到确认书记录
|
||
/// </summary>
|
||
/// <param name="oa">OA结果记录</param>
|
||
/// <param name="queryData">OA查询返回的流程数据</param>
|
||
/// <returns>处理结果</returns>
|
||
private TradeOAResult SyncStampFileToDoc(trade_contract_oa_result oa, OAFlowData queryData)
|
||
{
|
||
try
|
||
{
|
||
// 根据 contract_code 查找确认书文档(直接使用基类 DbContext)
|
||
var contractDoc = DbContext.trade_contract_document.FirstOrDefault(d => d.Code == oa.contract_code);
|
||
if (contractDoc == null)
|
||
{
|
||
logger.Info($"未找到确认书文档,contract_code:{oa.contract_code}");
|
||
return new TradeOAResult { TradeId = oa.trade_id, Success = false, Message = "未找到确认书文档" };
|
||
}
|
||
|
||
// 从 fjFileInfo 中提取用印文件信息
|
||
if (!TryExtractFileFromFjFileInfo(queryData.flowInfos, out var fileId, out var fileName))
|
||
{
|
||
logger.Info($"无法从 fjFileInfo 中提取用印文件信息,contract_code:{oa.contract_code}");
|
||
return new TradeOAResult { TradeId = oa.trade_id, Success = false, Message = "无法提取用印文件信息" };
|
||
}
|
||
|
||
// 通过 OA 接口下载用印文件
|
||
var fileBytes = DownloadOAFile(fileId);
|
||
if (fileBytes == null || fileBytes.Length == 0)
|
||
{
|
||
logger.Info($"未获取到用印文件,contract_code:{oa.contract_code}");
|
||
return new TradeOAResult { TradeId = oa.trade_id, Success = false, Message = "未获取到用印文件" };
|
||
}
|
||
|
||
// 文件名安全过滤:防止路径遍历
|
||
fileName = SanitizeFileName(fileName);
|
||
if (string.IsNullOrEmpty(fileName))
|
||
{
|
||
fileName = $"{oa.contract_code}_用印.pdf";
|
||
}
|
||
|
||
// 保存用印文件到本地
|
||
var tradeNumber = "未知";
|
||
var contractR = DbContext.trade_contract_r.FirstOrDefault(r => r.ContractCode == oa.contract_code && r.IsValid);
|
||
if (contractR != null)
|
||
{
|
||
tradeNumber = contractR.TradeNumber;
|
||
}
|
||
|
||
var filePath = Path.Combine(OtcAppContext.AppDocsPath, "contractdoc", PS.Config.Company.ToString(), tradeNumber);
|
||
if (!Directory.Exists(filePath))
|
||
{
|
||
Directory.CreateDirectory(filePath);
|
||
}
|
||
|
||
var stampFileFullName = Path.Combine(filePath, fileName);
|
||
if (File.Exists(stampFileFullName))
|
||
{
|
||
File.Delete(stampFileFullName);
|
||
}
|
||
|
||
File.WriteAllBytes(stampFileFullName, fileBytes);
|
||
logger.Info($"用印文件保存成功: {stampFileFullName}");
|
||
|
||
// 更新确认书文档状态
|
||
contractDoc.StampDocumentFileName = stampFileFullName.Replace(OtcAppContext.AppDocsPath, "/App_Docs").Replace("\\", "/");
|
||
|
||
// 根据 SourceType 判断用印状态:null或1=我方用印,2=双方用印
|
||
var sourceType = contractDoc.SourceType ?? 1;
|
||
if (sourceType == 1)
|
||
{
|
||
contractDoc.Status = ContractStatusEnum.OurPartyStamped; // 我方用印
|
||
}
|
||
else if (sourceType == 2)
|
||
{
|
||
contractDoc.Status = ContractStatusEnum.BothPartyStamped; // 双方用印
|
||
}
|
||
|
||
contractDoc.OptDate = DateTime.Now;
|
||
|
||
// 更新 OA 结果状态(oa 本身由基类 DbContext 追踪,直接修改即可)
|
||
oa.status = "用印完成";
|
||
oa.OptDate = DateTime.Now;
|
||
|
||
DbContext.SaveChanges();
|
||
|
||
logger.Info($"确认书 {oa.contract_code} 用印状态已更新为: {contractDoc.Status}");
|
||
return new TradeOAResult { TradeId = oa.trade_id, Success = true, Message = $"用印同步成功,状态: {contractDoc.Status}" };
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
logger.Error($"同步用印文件失败,contract_code:{oa.contract_code}, 错误:{ex.Message}", ex);
|
||
return new TradeOAResult { TradeId = oa.trade_id, Success = false, Message = $"同步失败: {ex.Message}" };
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 判断 flowInfos 中是否存在用印文件
|
||
/// </summary>
|
||
private bool HasStampFiles(Newtonsoft.Json.Linq.JObject flowInfos)
|
||
{
|
||
var fjFileInfo = flowInfos?["fjFileInfo"];
|
||
return fjFileInfo != null && fjFileInfo.Any();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 从 fjFileInfo 中提取用印文件 ID 和文件名
|
||
/// </summary>
|
||
private bool TryExtractFileFromFjFileInfo(Newtonsoft.Json.Linq.JObject flowInfos, out string fileId, out string fileName)
|
||
{
|
||
fileId = null;
|
||
fileName = null;
|
||
|
||
var fjFileInfo = flowInfos?["fjFileInfo"];
|
||
if (fjFileInfo == null || !fjFileInfo.Any())
|
||
{
|
||
return false;
|
||
}
|
||
|
||
var firstFile = fjFileInfo.FirstOrDefault();
|
||
if (firstFile != null)
|
||
{
|
||
fileId = firstFile["id"]?.ToString();
|
||
fileName = firstFile["fileName"]?.ToString();
|
||
if (!string.IsNullOrEmpty(fileId))
|
||
{
|
||
return true;
|
||
}
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 过滤文件名中的非法字符,防止路径遍历
|
||
/// </summary>
|
||
private string SanitizeFileName(string fileName)
|
||
{
|
||
if (string.IsNullOrWhiteSpace(fileName))
|
||
{
|
||
return null;
|
||
}
|
||
|
||
// 仅保留纯文件名,丢弃路径
|
||
fileName = Path.GetFileName(fileName);
|
||
|
||
// 替换 Windows 非法字符
|
||
var invalidChars = Path.GetInvalidFileNameChars();
|
||
foreach (var c in invalidChars)
|
||
{
|
||
fileName = fileName.Replace(c, '_');
|
||
}
|
||
|
||
// 防御 .. 路径遍历
|
||
fileName = fileName.Replace("..", "_");
|
||
|
||
return fileName;
|
||
}
|
||
}
|
||
|
||
} |