diff --git a/Framework/YLErp.Core/DBModels/Abstract/ITradeContractDocument.cs b/Framework/YLErp.Core/DBModels/Abstract/ITradeContractDocument.cs
index c1368bf1..35168333 100644
--- a/Framework/YLErp.Core/DBModels/Abstract/ITradeContractDocument.cs
+++ b/Framework/YLErp.Core/DBModels/Abstract/ITradeContractDocument.cs
@@ -26,6 +26,7 @@ namespace YLErp.DBModels.Abstract
int? SellerId { get; set; }
string SellerName { get; set; }
string StampDocumentFileName { get; set; }
+ int? SourceType { get; set; }
string Status { get; set; }
string Type { get; set; }
DateTime? ValueDate { get; set; }
diff --git a/YLErpDAL/BLL/GenerateDocument/BaseGenerate.cs b/YLErpDAL/BLL/GenerateDocument/BaseGenerate.cs
index 7f8cfe7d..09611ac8 100644
--- a/YLErpDAL/BLL/GenerateDocument/BaseGenerate.cs
+++ b/YLErpDAL/BLL/GenerateDocument/BaseGenerate.cs
@@ -343,6 +343,7 @@ namespace YLErp.BLL.GenerateDocument
cd.SellerName = isGJBuyer ? trade.ClientName : (PS.Config.CompanyName ?? "");
cd.FileType = FileTypeEnum.Word;
cd.Status = ContractStatusEnum.Draft;
+ cd.SourceType = 1; // 系统生成
cd.Paths = $"App_Docs\\contractdoc\\output\\{savedOutputFileName}";
cd.FileName = savedOutputFileName;
cd.Type = ContractType;
diff --git a/YLErpDAL/Model/TradeContractDocument.cs b/YLErpDAL/Model/TradeContractDocument.cs
index b4efedf3..d0656e68 100644
--- a/YLErpDAL/Model/TradeContractDocument.cs
+++ b/YLErpDAL/Model/TradeContractDocument.cs
@@ -106,6 +106,12 @@ namespace YLErp.DBModels
get { return SellerName; }
}
+ ///
+ /// 确认书来源类型:1=系统生成,2=人工上传,null=默认为系统生成
+ ///
+ [DisplayName("确认书来源类型")]
+ public int? SourceType { get; set; }
+
///
/// 用印文件
///
diff --git a/YLErpDAL/Model/TradeOtherDocument.cs b/YLErpDAL/Model/TradeOtherDocument.cs
index 20eef766..62f4b857 100644
--- a/YLErpDAL/Model/TradeOtherDocument.cs
+++ b/YLErpDAL/Model/TradeOtherDocument.cs
@@ -29,6 +29,8 @@ namespace YLErp.Model
public string RelativePath { get; set; }
+ public int? SourceType { get; set; }
+
public int? SellerId { get; set; }
public string SellerName { get; set; }
public string StampDocumentFileName { get; set; }
diff --git a/YLErpDAL/Modules/TradeModule/DocGenerateModule/BatchConfirmationGenerateService.cs b/YLErpDAL/Modules/TradeModule/DocGenerateModule/BatchConfirmationGenerateService.cs
index a0caffbd..db8136e4 100644
--- a/YLErpDAL/Modules/TradeModule/DocGenerateModule/BatchConfirmationGenerateService.cs
+++ b/YLErpDAL/Modules/TradeModule/DocGenerateModule/BatchConfirmationGenerateService.cs
@@ -497,6 +497,7 @@ namespace YLErp.Modules.TradeModule.DocGenerateModule
contractRecord.SellerName = isGJBuyer ? trade.ClientName : PS.Config.CompanyFullName;
contractRecord.FileType = FileTypeEnum.Word;
contractRecord.Status = ContractStatusEnum.Draft;
+ contractRecord.SourceType = 1; // 系统生成
contractRecord.Paths = $"App_Docs\\GTJA\\{outPutFileName}";
contractRecord.FileName = outPutFileName;
contractRecord.Type = ContractTypeEnum.Trade;
diff --git a/YLErpDAL/Modules/TradeModule/DocGenerateModule/BatchTradeDocGeneratorContext.cs b/YLErpDAL/Modules/TradeModule/DocGenerateModule/BatchTradeDocGeneratorContext.cs
index 5a704aaa..70646789 100644
--- a/YLErpDAL/Modules/TradeModule/DocGenerateModule/BatchTradeDocGeneratorContext.cs
+++ b/YLErpDAL/Modules/TradeModule/DocGenerateModule/BatchTradeDocGeneratorContext.cs
@@ -58,6 +58,7 @@ namespace YLErp.Modules.TradeModule.DocGenerateModule
contractRecord.ContractVersion = contractVersion;
contractRecord.FileType = FileTypeEnum.Pdf;
contractRecord.Status = ContractStatusEnum.Draft;
+ contractRecord.SourceType = 1; // 系统生成
contractRecord.Paths = outPath;
contractRecord.FileName = outPath.Replace(".xlsx", ".pdf");
contractRecord.Type = ContractTypeEnum.Trade;
diff --git a/YLErpDAL/Modules/TradeModule/DocGenerateModule/ConfirmationGenerateContext.cs b/YLErpDAL/Modules/TradeModule/DocGenerateModule/ConfirmationGenerateContext.cs
index 9ae58919..fbc8eb50 100644
--- a/YLErpDAL/Modules/TradeModule/DocGenerateModule/ConfirmationGenerateContext.cs
+++ b/YLErpDAL/Modules/TradeModule/DocGenerateModule/ConfirmationGenerateContext.cs
@@ -545,6 +545,7 @@ namespace YLErp.Modules.TradeModule.DocGenerateModule
contractRecord.SellerName = isGJBuyer ? firstTrade.ClientName : (PS.Config.CompanyName ?? "");
contractRecord.FileType = fileType;
contractRecord.Status = ContractStatusEnum.Draft;
+ contractRecord.SourceType = 1; // 系统生成
contractRecord.Paths = "/App_Docs/contractdoc/output/" + fileName;
contractRecord.FileName = fileName;
contractRecord.StampDocumentFileName = null;
@@ -697,6 +698,7 @@ namespace YLErp.Modules.TradeModule.DocGenerateModule
contractRecord.SellerName = isGJBuyer ? firstTrade.ClientName : (PS.Config.CompanyName ?? "");
contractRecord.FileType = fileType;
contractRecord.Status = ContractStatusEnum.Draft;
+ contractRecord.SourceType = 1; // 系统生成
contractRecord.Paths = "/App_Docs/contractdoc/output/" + fileName;
contractRecord.FileName = fileName;
contractRecord.StampDocumentFileName = null;
diff --git a/YLErpDAL/Modules/TradeModule/DocGenerateModule/ConfirmationGenerateService.cs b/YLErpDAL/Modules/TradeModule/DocGenerateModule/ConfirmationGenerateService.cs
index c056d953..6b97713a 100644
--- a/YLErpDAL/Modules/TradeModule/DocGenerateModule/ConfirmationGenerateService.cs
+++ b/YLErpDAL/Modules/TradeModule/DocGenerateModule/ConfirmationGenerateService.cs
@@ -611,6 +611,7 @@ namespace YLErp.Modules.TradeModule.DocGenerateModule
contractRecord.SellerName = isGJBuyer ? trade.ClientName : PS.Config.CompanyFullName;
contractRecord.FileType = FileTypeEnum.Word;
contractRecord.Status = ContractStatusEnum.Draft;
+ contractRecord.SourceType = 1; // 系统生成
contractRecord.Paths = $"App_Docs\\GTJA\\{outPutFileName}";
contractRecord.FileName = outPutFileName;
contractRecord.Type = ContractTypeEnum.Trade;
@@ -800,6 +801,7 @@ namespace YLErp.Modules.TradeModule.DocGenerateModule
#region 更新数据
contractDoc.Comments = fileDescription;
+ contractDoc.SourceType = 2; // 人工上传
if (OurpartySeal && CounterpartySeal)
{ contractDoc.Status = ContractStatusEnum.BothPartyStamped; }
else if (OurpartySeal) { contractDoc.Status = ContractStatusEnum.OurPartyStamped; }
@@ -922,6 +924,7 @@ namespace YLErp.Modules.TradeModule.DocGenerateModule
}
#region 更新数据
+ contractDoc.SourceType = 2; // 人工上传
var confrimService = new TradeConfirmService(OptUser, db);
foreach (var contractDocR in contractDocRArr)
{
diff --git a/YLErpDAL/Modules/TradeModule/OAModels.cs b/YLErpDAL/Modules/TradeModule/OAModels.cs
index ae51ccc7..6b31e699 100644
--- a/YLErpDAL/Modules/TradeModule/OAModels.cs
+++ b/YLErpDAL/Modules/TradeModule/OAModels.cs
@@ -1,3 +1,4 @@
+using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
@@ -61,4 +62,24 @@ namespace YLErp.Modules.TradeModule
public string annexId { get; set; }
public string annexName { get; set; }
}
+
+ ///
+ /// OA 审批状态查询响应模型(适配新查询接口 /gateway/oaflow/getRequestData)
+ ///
+ public class OAStatusQueryResponse
+ {
+ public int code { get; set; }
+ public string status { get; set; }
+ public string msg { get; set; }
+ public List data { get; set; }
+ }
+
+ ///
+ /// OA 流程查询数据模型
+ ///
+ public class OAFlowData
+ {
+ public string requestId { get; set; }
+ public JObject flowInfos { get; set; }
+ }
}
\ No newline at end of file
diff --git a/YLErpDAL/Modules/TradeModule/TradeOAService.cs b/YLErpDAL/Modules/TradeModule/TradeOAService.cs
index 67585f03..fec432f5 100644
--- a/YLErpDAL/Modules/TradeModule/TradeOAService.cs
+++ b/YLErpDAL/Modules/TradeModule/TradeOAService.cs
@@ -18,6 +18,10 @@ namespace YLErp.Modules.TradeModule
public class TradeOAService : YLBaseService
{
private static readonly IYcLogger logger = LogFactory.GetLogger();
+ private static readonly System.Net.Http.HttpClient _httpClient = new System.Net.Http.HttpClient
+ {
+ Timeout = TimeSpan.FromMinutes(2)
+ };
public TradeOAService(OptUserInfo userInfo) : base(userInfo)
{
@@ -332,6 +336,314 @@ namespace YLErp.Modules.TradeModule
return new GuoLianOAResponse() { code = -2, data = new GuoLianOAData { msg = $"调用OA接口时发生异常: {ex.Message}" } };
}
}
+
+ ///
+ /// 批量查询并同步 OA 用印状态
+ ///
+ /// 处理结果列表
+ public List SyncOAStampStatus()
+ {
+ var results = new List();
+
+ // 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. 判断用印是否完成:fj 字段为空表示未盖完章,有值表示已用印
+ var fj = flowData.flowInfos?["fj"];
+ if (fj == null || fj.Type == Newtonsoft.Json.Linq.JTokenType.Null)
+ {
+ logger.Info($"OA用印未完成,fj为空,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;
+ }
+
+ ///
+ /// 查询 OA 审批状态
+ ///
+ /// OA请求ID
+ /// 查询结果
+ 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(response);
+ }
+ catch (Exception ex)
+ {
+ logger.Error($"查询OA状态异常,requestid:{requestId}, 错误:{ex.Message}", ex);
+ return null;
+ }
+ }
+
+ ///
+ /// 下载 OA 用印文件
+ ///
+ /// 文件下载地址
+ /// 文件字节数组
+ private byte[] DownloadOAFile(string fileUrl)
+ {
+ try
+ {
+ if (string.IsNullOrEmpty(fileUrl))
+ {
+ return null;
+ }
+
+ logger.Info($"下载OA用印文件: {fileUrl}");
+
+ var response = _httpClient.GetAsync(fileUrl).Result;
+ response.EnsureSuccessStatusCode();
+ var fileBytes = response.Content.ReadAsByteArrayAsync().Result;
+
+ logger.Info($"下载OA用印文件成功,大小: {fileBytes.Length} 字节");
+ return fileBytes;
+ }
+ catch (Exception ex)
+ {
+ logger.Error($"下载OA用印文件失败: {fileUrl}, 错误:{ex.Message}", ex);
+ return null;
+ }
+ }
+
+ ///
+ /// 将 OA 用印文件同步到确认书记录
+ ///
+ /// OA结果记录
+ /// OA查询返回的流程数据
+ /// 处理结果
+ 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 = "未找到确认书文档" };
+ }
+
+ // 从 fj 字段提取用印文件信息
+ var fj = queryData.flowInfos?["fj"];
+ if (!TryExtractFileFromFj(fj, out var fileUrl, out var fileName))
+ {
+ logger.Info($"无法从 fj 中提取用印文件信息,contract_code:{oa.contract_code}");
+ return new TradeOAResult { TradeId = oa.trade_id, Success = false, Message = "无法提取用印文件信息" };
+ }
+
+ // 下载用印文件
+ var fileBytes = DownloadOAFile(fileUrl);
+ 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}" };
+ }
+ }
+
+ ///
+ /// 从 fj 字段尝试提取文件下载地址和文件名
+ /// fj 格式:{"annexId":"url1,url2","annexName":"name1,name2"}
+ ///
+ private bool TryExtractFileFromFj(Newtonsoft.Json.Linq.JToken fj, out string fileUrl, out string fileName)
+ {
+ fileUrl = null;
+ fileName = null;
+
+ if (fj == null || fj.Type == Newtonsoft.Json.Linq.JTokenType.Null)
+ {
+ return false;
+ }
+
+ // 如果是字符串,直接当作 URL
+ if (fj.Type == Newtonsoft.Json.Linq.JTokenType.String)
+ {
+ fileUrl = fj.ToString();
+ fileName = Path.GetFileName(fileUrl);
+ return !string.IsNullOrEmpty(fileUrl);
+ }
+
+ // 解析 annexId(逗号分隔的 URL 数组)和 annexName(逗号分隔的文件名数组)
+ var annexIdStr = fj["annexId"]?.ToString();
+ var annexNameStr = fj["annexName"]?.ToString();
+
+ if (!string.IsNullOrEmpty(annexIdStr))
+ {
+ fileUrl = annexIdStr.Split(',').Select(s => s.Trim()).FirstOrDefault(s => !string.IsNullOrEmpty(s));
+ }
+
+ if (!string.IsNullOrEmpty(annexNameStr))
+ {
+ fileName = annexNameStr.Split(',').Select(s => s.Trim()).FirstOrDefault(s => !string.IsNullOrEmpty(s));
+ }
+
+ if (string.IsNullOrEmpty(fileName) && !string.IsNullOrEmpty(fileUrl))
+ {
+ fileName = Path.GetFileName(fileUrl);
+ }
+
+ return !string.IsNullOrEmpty(fileUrl);
+ }
+
+ ///
+ /// 过滤文件名中的非法字符,防止路径遍历
+ ///
+ 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;
+ }
}
}
\ No newline at end of file
diff --git a/YLErpWeb/Controllers/TradeConfirmBookController.cs b/YLErpWeb/Controllers/TradeConfirmBookController.cs
index 3ffaf734..f8167ae3 100644
--- a/YLErpWeb/Controllers/TradeConfirmBookController.cs
+++ b/YLErpWeb/Controllers/TradeConfirmBookController.cs
@@ -1106,6 +1106,7 @@ namespace YLErp.Web.Controllers
contractRecord.ContractVersion = contractVersion;
contractRecord.FileType = FileTypeEnum.Pdf;
contractRecord.Status = ContractStatusEnum.Draft;
+ contractRecord.SourceType = 1; // 系统生成
contractRecord.Paths = outPath.ToString();
contractRecord.FileName = outputFileName.Replace(".xlsx", ".pdf");
contractRecord.Type = ContractTypeEnum.Trade;
@@ -1499,6 +1500,7 @@ namespace YLErp.Web.Controllers
contractRecord.ContractVersion = contractVersion;
contractRecord.FileType = FileTypeEnum.Pdf;
contractRecord.Status = ContractStatusEnum.Draft;
+ contractRecord.SourceType = 1; // 系统生成
contractRecord.Paths = Path.Combine(DocumentPathConvert.ConvertToUrlPath(outPath), outputFileName.Replace(".xlsx", ".pdf"));
contractRecord.FileName = outputFileName.Replace(".xlsx", ".pdf");
contractRecord.Type = ContractTypeEnum.Trade;
diff --git a/YLErpWeb/WebAPI/Controllers/TradeOAJobController.cs b/YLErpWeb/WebAPI/Controllers/TradeOAJobController.cs
new file mode 100644
index 00000000..93f24305
--- /dev/null
+++ b/YLErpWeb/WebAPI/Controllers/TradeOAJobController.cs
@@ -0,0 +1,92 @@
+using System.Text.Json;
+using YLErp.Helpers;
+using YLErp.Modules.TradeModule;
+
+namespace YLErp.Web.WebAPI.Controllers
+{
+ ///
+ /// OA用印状态同步定时任务接口(无鉴权,供PowerJob等外部调度系统调用)
+ ///
+ [ApiController]
+ public class TradeOAJobController : ControllerBase
+ {
+ private static readonly IYcLogger logger = LogFactory.GetLogger();
+
+ ///
+ /// 同步OA用印状态
+ ///
+ ///
+ /// 无需鉴权,建议通过内网限制或Nginx限制来源IP。
+ /// 可选:在请求Header中携带 X-Job-Api-Key 进行简单校验。
+ ///
+ [HttpPost]
+ [Route("api/job/tradeContract/syncOAStampStatus")]
+ public JsonResult SyncOAStampStatus()
+ {
+ try
+ {
+ // 可选:简单 API Key 校验
+ var configKey = AppManager.GetConfiguration()["JobConfig:ApiKey"];
+ if (!string.IsNullOrEmpty(configKey))
+ {
+ var requestKey = Request.Headers["X-Job-Api-Key"].FirstOrDefault();
+ if (configKey != requestKey)
+ {
+ return JsonError(401, "Invalid API Key");
+ }
+ }
+
+ logger.Info("开始执行OA用印状态同步任务");
+
+ // 使用系统用户身份执行
+ var systemUser = new OptUserInfo(0, "系统用印同步", OptUserFrom.System);
+ var service = new TradeOAService(systemUser);
+
+ var results = service.SyncOAStampStatus();
+
+ var successCount = results.Count(r => r.Success);
+ var failCount = results.Count(r => !r.Success);
+
+ logger.Info($"OA用印状态同步任务完成,共处理 {results.Count} 条,成功 {successCount} 条,失败 {failCount} 条");
+
+ return JsonSuccess(new
+ {
+ total = results.Count,
+ successCount,
+ failCount,
+ executeTime = DateTime.Now,
+ details = results.Select(r => new { r.TradeId, r.Success, r.Message }).ToList()
+ });
+ }
+ catch (Exception ex)
+ {
+ logger.Error($"OA用印状态同步任务异常: {ex.Message}", ex);
+ return JsonError($"同步OA用印状态失败: {ex.Message}");
+ }
+ }
+
+ #region JSON消息处理
+
+ private static readonly JsonSerializerOptions _jsonOptions = new JsonSerializerOptions
+ {
+ PropertyNamingPolicy = JsonNamingPolicy.CamelCase
+ };
+
+ private JsonResult JsonSuccess(object data, string msg = null)
+ {
+ return new JsonResult(new ApiResult { code = 0, msg = msg, data = data }, _jsonOptions);
+ }
+
+ private JsonResult JsonError(string msg)
+ {
+ return new JsonResult(new ApiResult { code = 1, msg = msg, data = null }, _jsonOptions);
+ }
+
+ private JsonResult JsonError(int code, string msg)
+ {
+ return new JsonResult(new ApiResult { code = code, msg = msg, data = null }, _jsonOptions);
+ }
+
+ #endregion
+ }
+}
diff --git a/YLErpWeb/appsettings.dev.json b/YLErpWeb/appsettings.dev.json
index 4593237d..5efcd87b 100644
--- a/YLErpWeb/appsettings.dev.json
+++ b/YLErpWeb/appsettings.dev.json
@@ -15,7 +15,7 @@
"AppSettings": {
"VirtualPathRoot": "",
"UseRightAligned": "",
- "PluginFolder": "D:\\shenzhengpoc\\zszq-trs\\Plugins\\build\\ZheShang\\Debug\\net6.0\\"
+ "PluginFolder": "D:\\workspace\\glms\\zszq-trs\\Plugins\\YLErp.Plugins.GuoLian\\obj\\Debug\\net6.0"
},
"LibreOffice": {
"ExePath": "",
@@ -76,10 +76,12 @@
"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自动发送下一步
+ "SubUrl": "",
+ "UploadUrl": "/api/oa/upload", // OA附件上传接口路径
+ "SystemToken": "your-system-token", // OA系统鉴权Token
+ "SystemName": "OTC_TRADE", // 调用方系统标识
+ "IsNextFlow": 0
+
},
"GeneralSSO": {
"enable": true, //是否启用通用SSO登录
diff --git a/YLerpbase.sln b/YLerpbase.sln
index a9b1bc83..ab0cdf50 100644
--- a/YLerpbase.sln
+++ b/YLerpbase.sln
@@ -35,6 +35,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "YLErp.Cache", "Framework\YL
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "YLErp.Plugins.ZheShang", "Plugins\YLErp.Plugins.ZheShang\YLErp.Plugins.ZheShang.csproj", "{C94D2467-B0E8-48CA-B60D-CD660B73AE90}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "YLErp.Plugins.GuoLian", "Plugins\YLErp.Plugins.GuoLian\YLErp.Plugins.GuoLian.csproj", "{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -86,6 +88,10 @@ Global
{C94D2467-B0E8-48CA-B60D-CD660B73AE90}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C94D2467-B0E8-48CA-B60D-CD660B73AE90}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C94D2467-B0E8-48CA-B60D-CD660B73AE90}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -100,6 +106,7 @@ Global
{DB3402A2-7121-4F5A-9F0F-A258EC582370} = {F315B5D3-F4FE-43E5-AF22-AF92978A71BE}
{5665673C-1157-444F-AABC-2A873D8E4703} = {F315B5D3-F4FE-43E5-AF22-AF92978A71BE}
{C94D2467-B0E8-48CA-B60D-CD660B73AE90} = {5981434D-792E-4528-AFC8-7EB8AFD9F80C}
+ {A1B2C3D4-E5F6-7890-ABCD-EF1234567890} = {5981434D-792E-4528-AFC8-7EB8AFD9F80C}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {28638B67-A6D3-4355-8ADB-EACEE98510B0}