AsyncMaterialSummaryService.java 81 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683
  1. package com.hotent.project.service;
  2. import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
  3. import com.hotent.base.util.StringUtil;
  4. import com.hotent.baseInfo.manager.AuditedUnitManager;
  5. import com.hotent.config.EipConfig;
  6. import com.hotent.project.manager.*;
  7. import com.hotent.project.model.*;
  8. import com.hotent.surveyinfo.manager.CostSurveyTemplateUploadManager;
  9. import com.hotent.surveyinfo.model.CostSurveyTemplateUpload;
  10. import com.hotent.uc.manager.OrgManager;
  11. import com.hotent.uc.manager.UserManager;
  12. import com.hotent.uc.model.Org;
  13. import com.hotent.uc.model.User;
  14. import com.hotent.uc.util.ContextUtil;
  15. import com.hotent.util.FileUploadUtil;
  16. import com.hotent.util.wordexcelutils.CompleteTemplateProcessor;
  17. import com.hotent.util.wordexcelutils.SmartTemplateWriter;
  18. import org.apache.pdfbox.pdmodel.PDDocument;
  19. import org.apache.poi.xwpf.usermodel.*;
  20. import org.slf4j.Logger;
  21. import org.slf4j.LoggerFactory;
  22. import org.springframework.beans.factory.annotation.Autowired;
  23. import org.springframework.scheduling.annotation.Async;
  24. import org.springframework.stereotype.Service;
  25. import java.io.FileOutputStream;
  26. import java.time.LocalDateTime;
  27. import java.util.Comparator;
  28. import java.util.List;
  29. import java.util.Map;
  30. import java.util.stream.Collectors;
  31. /**
  32. * 异步资料归纳服务
  33. *
  34. * @company 山西清众科技股份有限公司
  35. * @author 超级管理员
  36. * @since 2025-12-02
  37. */
  38. @Service
  39. public class AsyncMaterialSummaryService {
  40. private static final Logger logger = LoggerFactory.getLogger(AsyncMaterialSummaryService.class);
  41. @Autowired
  42. private CostProjectTaskMaterialSummaryManager costProjectTaskMaterialSummaryManager;
  43. @Autowired
  44. private CostProjectTaskMaterialSummaryDetailManager costProjectTaskMaterialSummaryDetailManager;
  45. @Autowired
  46. private CostProjectDocumentManager costProjectDocumentManager;
  47. @Autowired
  48. private CostProjectTaskManager costProjectTaskManager;
  49. @Autowired
  50. private CostSurveyTemplateUploadManager costSurveyTemplateUploadManager;
  51. @Autowired
  52. private CostProjectTaskMaterialManager costProjectTaskMaterialManager;
  53. @Autowired
  54. private CostProjectDeliberateManager costProjectDeliberateManager;
  55. @Autowired
  56. private CostProjectTaskAdjustmentRecordManager adjustmentRecordManager;
  57. @Autowired
  58. private CostProjectTaskEvidenceManager costProjectTaskEvidenceManager;
  59. @Autowired
  60. private CostProjectTaskPreliminaryOpinionManager preliminaryOpinionManager;
  61. @Autowired
  62. private CostProjectApprovalManager costProjectApprovalManager;
  63. @Autowired
  64. private AuditedUnitManager auditedUnitManager;
  65. @Autowired
  66. private OrgManager orgManager;
  67. @Autowired
  68. private UserManager userManager;
  69. // 从配置文件读取模板路径
  70. @org.springframework.beans.factory.annotation.Value("${archive.template.coverPath:}")
  71. private String coverTemplatePath;
  72. @org.springframework.beans.factory.annotation.Value("${archive.template.catalogPath:}")
  73. private String catalogTemplatePath;
  74. @org.springframework.beans.factory.annotation.Value("${archive.template.backCoverPath:}")
  75. private String backCoverTemplatePath;
  76. /**
  77. * 生成资料归纳主表(14个基础资料类别)- 核心逻辑
  78. *
  79. * @param mainTask 主任务
  80. * @param childTasks 子任务列表
  81. */
  82. public void generateMaterialSummary(CostProjectTask mainTask, List<CostProjectTask> childTasks) {
  83. try {
  84. // 定义14个基础资料类别
  85. String[] materialNames = {
  86. "成本监审报告(含成本监审报告签发稿、送达回证)",
  87. "被监审单位申请定(调)价报告(复印件)",
  88. "成本监审通知书(含送达回证)",
  89. "经营者需提供成本资料清单",
  90. "《政府定价成本监审调查表》",
  91. "成本监审补充资料通知书(含送达回证)",
  92. "成本审核初步意见告知书(含送达回证)",
  93. "经营者书面反馈的材料",
  94. "成本审核初步意见表(集体审议用)",
  95. "成本监审集体审议记录",
  96. "成本监审工作底稿",
  97. "成本监审提取资料登记表",
  98. "提取的成本资料和会计凭证等复印件",
  99. "中止定价成本监审料通知书(含送达回证)"
  100. };
  101. // 为主任务生成14个资料归纳主表记录
  102. for (int i = 0; i < materialNames.length; i++) {
  103. CostProjectTaskMaterialSummary summary = new CostProjectTaskMaterialSummary();
  104. summary.setTaskId(mainTask.getId());
  105. summary.setMaterialName(materialNames[i]);
  106. summary.setMaterialOrderNum(i + 1);
  107. summary.setIsDeleted("0");
  108. costProjectTaskMaterialSummaryManager.createOrUpdate(summary);
  109. // 根据不同的资料类别生成对应的明细记录
  110. generateDetailsByMaterialType(summary, mainTask, childTasks, i + 1);
  111. }
  112. } catch (Exception e) {
  113. // 记录异常日志,但不影响主流程
  114. logger.error("生成资料归纳失败", e);
  115. }
  116. }
  117. /**
  118. * 根据资料类别生成对应的明细记录
  119. *
  120. * @param summary 资料归纳主表
  121. * @param mainTask 主任务
  122. * @param childTasks 子任务列表
  123. * @param materialType 资料类别序号(1-13)
  124. */
  125. private void generateDetailsByMaterialType(CostProjectTaskMaterialSummary summary,
  126. CostProjectTask mainTask,
  127. List<CostProjectTask> childTasks,
  128. int materialType) {
  129. try {
  130. // 查询项目下的所有文书
  131. List<CostProjectDocument> documents = costProjectDocumentManager.getListByProjectId(mainTask.getProjectId());
  132. if (documents == null) {
  133. documents = new java.util.ArrayList<>();
  134. }
  135. switch (materialType) {
  136. case 1:
  137. // 成本监审报告(含成本监审报告签发稿、送达回证)
  138. generateType1Details(summary, mainTask, documents);
  139. break;
  140. case 2:
  141. // 被监审单位申请定(调)价报告(复印件)
  142. generateType2Details(summary, mainTask, childTasks, documents);
  143. break;
  144. case 3:
  145. // 成本监审通知书(含送达回证)
  146. generateType3Details(summary, mainTask, childTasks, documents);
  147. break;
  148. case 4:
  149. // 经营者需提供成本资料清单
  150. generateType4Details(summary, mainTask, childTasks, documents);
  151. break;
  152. case 5:
  153. // 《政府定价成本监审调查表》
  154. generateType5Details(summary, mainTask, childTasks, documents);
  155. break;
  156. case 6:
  157. // 成本监审补充资料通知书(含送达回证)
  158. generateType6Details(summary, mainTask, childTasks, documents);
  159. break;
  160. case 7:
  161. // 成本审核初步意见告知书(含送达回证)
  162. generateType7Details(summary, mainTask, childTasks, documents);
  163. break;
  164. case 8:
  165. // 经营者书面反馈的材料
  166. generateType8Details(summary, mainTask, childTasks, documents);
  167. break;
  168. case 9:
  169. // 成本审核初步意见表(集体审议用)
  170. generateType9Details(summary, mainTask, childTasks, documents);
  171. break;
  172. case 10:
  173. // 成本监审集体审议记录
  174. generateType10Details(summary, mainTask, documents);
  175. break;
  176. case 11:
  177. // 成本监审工作底稿
  178. generateType11Details(summary, mainTask, childTasks, documents);
  179. break;
  180. case 12:
  181. // 成本监审提取资料登记表
  182. generateType12Details(summary, mainTask, childTasks, documents);
  183. break;
  184. case 13:
  185. // 提取的成本资料和会计凭证等复印件
  186. generateType13Details(summary, mainTask, childTasks, documents);
  187. break;
  188. case 14:
  189. // 中止定价成本监审料通知书(含送达回证)
  190. generateType14Details(summary, mainTask, childTasks, documents);
  191. break;
  192. default:
  193. throw new RuntimeException("未知的资料类型");
  194. }
  195. } catch (Exception e) {
  196. logger.error("生成资料类型 {} 的明细时出错:{}", materialType, e.getMessage(), e);
  197. // 继续执行,不中断整个流程
  198. }
  199. }
  200. //1 卷宗封面
  201. //2 卷内目录
  202. //3 政府定价成本监审结论报告
  203. //4 成本监审通知书
  204. //5 成本审核初步意见告知书
  205. //6 成本审核初步意见表
  206. //7 成本监审集体审议记录
  207. //8 成本监审工作底稿
  208. //9 成本监审提取资料登记表
  209. //10 成本监审补充资料通知书
  210. //11 中止定价成本监审通知书
  211. //12 送达回证
  212. //13 成本监审卷宗备考表
  213. // ==================== 类型1:成本监审报告(含成本监审报告签发稿、送达回证) ====================
  214. // A.成本监审报告(含成本监审报告签发稿)
  215. // 包括成本监审报告、报告签发稿等文书。
  216. private void generateType1Details(CostProjectTaskMaterialSummary summary, CostProjectTask mainTask, List<CostProjectDocument> documents) {
  217. int orderNum = 1;
  218. // 获取"成本监审报告"、"成本监审报告签发稿"、"送达回证"
  219. List<CostProjectDocument> matchedDocuments = documents.stream()
  220. .filter(doc -> "3".equals(doc.getDocumentType())
  221. || "3-12".equals(doc.getDocumentType() + "-" + doc.getDocumentTypeName())
  222. )
  223. .sorted(Comparator.comparing(doc -> doc.getOrderNum() != null ? doc.getOrderNum() : 0))
  224. .collect(Collectors.toList());
  225. for (CostProjectDocument document : matchedDocuments) {
  226. CostProjectTaskMaterialSummaryDetail detail = new CostProjectTaskMaterialSummaryDetail();
  227. detail.setMasterId(summary.getId());
  228. detail.setTaskId(mainTask.getId());
  229. detail.setDocumentName(document.getDocumentName());
  230. detail.setDocumentNumber(document.getDocumentNumber() != null ? document.getDocumentNumber() : "");
  231. detail.setAuditedUnitId(document.getEnterpriseId());
  232. if (document.getEnterpriseId() != null) {
  233. detail.setAuditedUnitName(auditedUnitManager.getById(document.getEnterpriseId()).getUnitName());
  234. }
  235. detail.setFileSource("系统生成电子文书");
  236. detail.setPageCount(0);
  237. detail.setAttachmentUrl(document.getActUrl() != null ? document.getActUrl() : "");
  238. detail.setOrderNum(orderNum++);
  239. detail.setIsDeleted("0");
  240. costProjectTaskMaterialSummaryDetailManager.save(detail);
  241. }
  242. }
  243. // ==================== 类型2:被监审单位申请定(调)价报告(复印件) ====================
  244. // 被监审单位申请定(调)价报告等。
  245. //来源:从“监审立项”附件获取。
  246. private void generateType2Details(CostProjectTaskMaterialSummary summary, CostProjectTask mainTask, List<CostProjectTask> childTasks, List<CostProjectDocument> documents) {
  247. int orderNum = 1;
  248. // 获取"申请定(调)价报告"
  249. List<CostProjectDocument> matchedDocuments = documents.stream()
  250. .filter(doc -> "申请定(调)价报告".equals(doc.getDocumentName()))
  251. .sorted(Comparator.comparing(doc -> doc.getOrderNum() != null ? doc.getOrderNum() : 0))
  252. .collect(Collectors.toList());
  253. for (CostProjectDocument document : matchedDocuments) {
  254. CostProjectTaskMaterialSummaryDetail detail = new CostProjectTaskMaterialSummaryDetail();
  255. detail.setMasterId(summary.getId());
  256. detail.setTaskId(mainTask.getId());
  257. detail.setDocumentName(document.getDocumentName());
  258. detail.setDocumentNumber(document.getDocumentNumber() != null ? document.getDocumentNumber() : "");
  259. detail.setAuditedUnitId(document.getEnterpriseId());
  260. if (document.getEnterpriseId() != null) {
  261. detail.setAuditedUnitName(auditedUnitManager.getById(document.getEnterpriseId()).getUnitName());
  262. }
  263. detail.setFileSource("系统生成电子文书");
  264. detail.setPageCount(0);
  265. detail.setAttachmentUrl(document.getScanDocumentUrl() != null ? document.getScanDocumentUrl() : "");
  266. detail.setOrderNum(orderNum++);
  267. detail.setIsDeleted("0");
  268. costProjectTaskMaterialSummaryDetailManager.save(detail);
  269. }
  270. CostProjectApproval approval = costProjectApprovalManager.getById(mainTask.getProjectId());
  271. if (approval != null && StringUtil.isNotEmpty(approval.getAccordingFileUrl())) {
  272. CostProjectTaskMaterialSummaryDetail detail = new CostProjectTaskMaterialSummaryDetail();
  273. detail.setMasterId(summary.getId());
  274. detail.setTaskId(mainTask.getId());
  275. detail.setDocumentName("成本项目定(调)价依据");
  276. detail.setDocumentNumber("");
  277. detail.setAuditedUnitId(mainTask.getAuditedUnitId());
  278. detail.setAuditedUnitName(mainTask.getAuditedUnitName() != null ? mainTask.getAuditedUnitName() : "");
  279. detail.setFileSource("系统生成电子文书");
  280. detail.setPageCount(0);
  281. detail.setAttachmentUrl(approval.getAccordingFileUrl());
  282. detail.setOrderNum(orderNum++);
  283. detail.setIsDeleted("0");
  284. costProjectTaskMaterialSummaryDetailManager.save(detail);
  285. }
  286. }
  287. // ==================== 类型3:成本监审通知书(含送达回证) ====================
  288. // 包括成本监审通知书、送达回证等文书。
  289. //来源:从“监审文书”中获取。
  290. private void generateType3Details(CostProjectTaskMaterialSummary summary, CostProjectTask mainTask, List<CostProjectTask> childTasks, List<CostProjectDocument> documents) {
  291. int orderNum = 1;
  292. // "成本监审通知书"和"送达回证"
  293. List<CostProjectDocument> matchedDocuments = documents.stream()
  294. .filter(doc -> "4".equals(doc.getDocumentType())
  295. || "4-12".equals(doc.getDocumentType() + "-" + doc.getDocumentTypeName()))
  296. .sorted(Comparator.comparing(doc -> doc.getOrderNum() != null ? doc.getOrderNum() : 0))
  297. .collect(Collectors.toList());
  298. for (CostProjectDocument document : matchedDocuments) {
  299. CostProjectTaskMaterialSummaryDetail detail = new CostProjectTaskMaterialSummaryDetail();
  300. detail.setMasterId(summary.getId());
  301. detail.setTaskId(mainTask.getId());
  302. detail.setDocumentName(document.getDocumentName());
  303. detail.setDocumentNumber(document.getDocumentNumber() != null ? document.getDocumentNumber() : "");
  304. detail.setAuditedUnitId(document.getEnterpriseId());
  305. if (document.getEnterpriseId() != null) {
  306. detail.setAuditedUnitName(auditedUnitManager.getById(document.getEnterpriseId()).getUnitName());
  307. }
  308. detail.setFileSource("系统生成电子文书");
  309. detail.setPageCount(0);
  310. detail.setAttachmentUrl(document.getActUrl() != null ? document.getActUrl() : "");
  311. detail.setOrderNum(orderNum++);
  312. detail.setIsDeleted("0");
  313. costProjectTaskMaterialSummaryDetailManager.save(detail);
  314. }
  315. }
  316. // ==================== 类型4:经营者需提供成本资料清单(提取资料) ====================
  317. // 成本监审任务中的资料报送要求。
  318. //来源:从“监审任务部署”中获取。
  319. private void generateType4Details(CostProjectTaskMaterialSummary summary, CostProjectTask mainTask, List<CostProjectTask> childTasks, List<CostProjectDocument> documents) {
  320. int orderNum = 1;
  321. // 获取"成本资料清单"
  322. List<CostProjectDocument> matchedDocuments = documents.stream()
  323. .filter(doc -> "9".equals(doc.getDocumentType()))
  324. .sorted(Comparator.comparing(doc -> doc.getOrderNum() != null ? doc.getOrderNum() : 0))
  325. .collect(Collectors.toList());
  326. for (CostProjectDocument document : matchedDocuments) {
  327. CostProjectTaskMaterialSummaryDetail detail = new CostProjectTaskMaterialSummaryDetail();
  328. detail.setMasterId(summary.getId());
  329. detail.setTaskId(mainTask.getId());
  330. detail.setDocumentName(document.getDocumentName());
  331. detail.setDocumentNumber(document.getDocumentNumber() != null ? document.getDocumentNumber() : "");
  332. detail.setAuditedUnitId(document.getEnterpriseId());
  333. if (document.getEnterpriseId() != null) {
  334. detail.setAuditedUnitName(auditedUnitManager.getById(document.getEnterpriseId()).getUnitName());
  335. }
  336. detail.setFileSource("系统生成电子文书");
  337. detail.setPageCount(0);
  338. detail.setAttachmentUrl(document.getActUrl() != null ? document.getActUrl() : "");
  339. detail.setOrderNum(orderNum++);
  340. detail.setIsDeleted("0");
  341. costProjectTaskMaterialSummaryDetailManager.save(detail);
  342. }
  343. for (CostProjectTask childTask : childTasks) {
  344. List<CostProjectTaskEvidence> list = costProjectTaskEvidenceManager.list(
  345. new LambdaQueryWrapper<CostProjectTaskEvidence>().eq(CostProjectTaskEvidence::getTaskId, childTask.getId())
  346. );
  347. for (CostProjectTaskEvidence evidence : list) {
  348. // 只处理有附件的资料登记
  349. if (StringUtil.isNotEmpty(evidence.getAttachmentUrl())) {
  350. CostProjectTaskMaterialSummaryDetail detail = new CostProjectTaskMaterialSummaryDetail();
  351. detail.setMasterId(summary.getId());
  352. detail.setTaskId(mainTask.getId());
  353. detail.setDocumentName(evidence.getMaterialName() != null ? evidence.getMaterialName() : "提取资料登记");
  354. detail.setDocumentNumber("");
  355. detail.setAuditedUnitId(childTask.getAuditedUnitId());
  356. detail.setAuditedUnitName(childTask.getAuditedUnitName() != null ? childTask.getAuditedUnitName() : "");
  357. detail.setFileSource("监审单位反馈文件");
  358. detail.setPageCount(evidence.getPageCount() != null ? evidence.getPageCount() : 0);
  359. detail.setAttachmentUrl(evidence.getAttachmentUrl());
  360. detail.setOrderNum(orderNum++);
  361. detail.setIsDeleted("0");
  362. costProjectTaskMaterialSummaryDetailManager.save(detail);
  363. }
  364. }
  365. }
  366. }
  367. // ==================== 类型5:《政府定价成本监审调查表》 ====================
  368. // 各被监审单位的“成本监审调查表”文档。
  369. //来源:从“监审任务”中获取各被监审单位填报的成本监审调查表。
  370. private void generateType5Details(CostProjectTaskMaterialSummary summary, CostProjectTask mainTask, List<CostProjectTask> childTasks, List<CostProjectDocument> documents) {
  371. int orderNum = 1;
  372. for (CostProjectTask childTask : childTasks) {
  373. List<CostSurveyTemplateUpload> costSurveyTemplateUploads = costSurveyTemplateUploadManager.listByTaskId(childTask.getId());
  374. for (CostSurveyTemplateUpload upload : costSurveyTemplateUploads) {
  375. if ("1".equals(upload.getIsUpload()) && StringUtil.isNotEmpty(upload.getFileUrl())) {
  376. CostProjectTaskMaterialSummaryDetail detail = new CostProjectTaskMaterialSummaryDetail();
  377. detail.setMasterId(summary.getId());
  378. detail.setTaskId(mainTask.getId());
  379. detail.setDocumentName(upload.getSurveyTemplateName() != null ? upload.getSurveyTemplateName() : "政府定价成本监审调查表");
  380. detail.setDocumentNumber("");
  381. detail.setAuditedUnitId(childTask.getAuditedUnitId());
  382. detail.setAuditedUnitName(childTask.getAuditedUnitName() != null ? childTask.getAuditedUnitName() : "");
  383. detail.setFileSource("监审单位反馈文件");
  384. detail.setPageCount(0);
  385. detail.setAttachmentUrl(upload.getFileUrl());
  386. detail.setOrderNum(orderNum++);
  387. detail.setIsDeleted("0");
  388. costProjectTaskMaterialSummaryDetailManager.save(detail);
  389. }
  390. }
  391. }
  392. // 成本调查表(kkkk)
  393. }
  394. // ==================== 类型6:成本监审补充资料通知书(含送达回证) ====================
  395. // 监审主体发送给各被监审单位的补充资料通知书、送达回证等文书。
  396. //来源:从“监审文书”中获取。
  397. private void generateType6Details(CostProjectTaskMaterialSummary summary, CostProjectTask mainTask, List<CostProjectTask> childTasks, List<CostProjectDocument> documents) {
  398. int orderNum = 1;
  399. List<CostProjectDocument> matchedDocuments = documents.stream()
  400. .filter(doc -> "10".equals(doc.getDocumentType())
  401. || "10-12".equals(doc.getDocumentType() + "-" + doc.getDocumentTypeName()))
  402. .sorted(Comparator.comparing(doc -> doc.getOrderNum() != null ? doc.getOrderNum() : 0))
  403. .collect(Collectors.toList());
  404. for (CostProjectDocument document : matchedDocuments) {
  405. CostProjectTaskMaterialSummaryDetail detail = new CostProjectTaskMaterialSummaryDetail();
  406. detail.setMasterId(summary.getId());
  407. detail.setTaskId(mainTask.getId());
  408. detail.setDocumentName(document.getDocumentName());
  409. detail.setDocumentNumber(document.getDocumentNumber() != null ? document.getDocumentNumber() : "");
  410. detail.setAuditedUnitId(document.getEnterpriseId());
  411. if (document.getEnterpriseId() != null) {
  412. detail.setAuditedUnitName(auditedUnitManager.getById(document.getEnterpriseId()).getUnitName());
  413. }
  414. detail.setFileSource("系统生成电子文书");
  415. detail.setPageCount(0);
  416. detail.setAttachmentUrl(document.getActUrl() != null ? document.getActUrl() : "");
  417. detail.setOrderNum(orderNum++);
  418. detail.setIsDeleted("0");
  419. costProjectTaskMaterialSummaryDetailManager.save(detail);
  420. }
  421. }
  422. // ==================== 类型7:成本审核初步意见告知书(含送达回证) ====================
  423. // 监审主体发送给各被监审单位的初步意见告知书、送达回证等文书。
  424. // 来源:从“监审文书”中获取。
  425. private void generateType7Details(CostProjectTaskMaterialSummary summary, CostProjectTask mainTask, List<CostProjectTask> childTasks, List<CostProjectDocument> documents) {
  426. int orderNum = 1;
  427. // 获取"成本审核初步意见告知书"和"送达回证"
  428. List<CostProjectDocument> matchedDocuments = documents.stream()
  429. .filter(doc -> "5".equals(doc.getDocumentType())
  430. || "5-12".equals(doc.getDocumentType() + "-" + doc.getDocumentTypeName()))
  431. .sorted(Comparator.comparing(doc -> doc.getOrderNum() != null ? doc.getOrderNum() : 0))
  432. .collect(Collectors.toList());
  433. for (CostProjectDocument document : matchedDocuments) {
  434. CostProjectTaskMaterialSummaryDetail detail = new CostProjectTaskMaterialSummaryDetail();
  435. detail.setMasterId(summary.getId());
  436. detail.setTaskId(mainTask.getId());
  437. detail.setDocumentName(document.getDocumentName());
  438. detail.setDocumentNumber(document.getDocumentNumber() != null ? document.getDocumentNumber() : "");
  439. detail.setAuditedUnitId(document.getEnterpriseId());
  440. if (document.getEnterpriseId() != null) {
  441. detail.setAuditedUnitName(auditedUnitManager.getById(document.getEnterpriseId()).getUnitName());
  442. }
  443. detail.setFileSource("系统生成电子文书");
  444. detail.setPageCount(0);
  445. detail.setAttachmentUrl(document.getActUrl() != null ? document.getActUrl() : "");
  446. detail.setOrderNum(orderNum++);
  447. detail.setIsDeleted("0");
  448. costProjectTaskMaterialSummaryDetailManager.save(detail);
  449. }
  450. }
  451. // ==================== 类型8:经营者书面反馈的材料 ====================
  452. // 各被监审单位对初步意见告知书的意见反馈资料。
  453. //来源:从“监审任务”中获取反馈意见。
  454. private void generateType8Details(CostProjectTaskMaterialSummary summary, CostProjectTask mainTask, List<CostProjectTask> childTasks, List<CostProjectDocument> documents) {
  455. int orderNum = 1;
  456. // 获取"书面反馈材料"(来源:监审单位反馈文件)
  457. List<CostProjectDocument> matchedDocuments = documents.stream()
  458. .filter(doc -> "书面反馈材料".equals(doc.getDocumentType()))
  459. .sorted(Comparator.comparing(doc -> doc.getOrderNum() != null ? doc.getOrderNum() : 0))
  460. .collect(Collectors.toList());
  461. for (CostProjectDocument document : matchedDocuments) {
  462. CostProjectTaskMaterialSummaryDetail detail = new CostProjectTaskMaterialSummaryDetail();
  463. detail.setMasterId(summary.getId());
  464. detail.setTaskId(mainTask.getId());
  465. detail.setDocumentName(document.getDocumentName());
  466. detail.setDocumentNumber(document.getDocumentNumber() != null ? document.getDocumentNumber() : "");
  467. detail.setAuditedUnitId(document.getEnterpriseId());
  468. if (document.getEnterpriseId() != null) {
  469. detail.setAuditedUnitName(auditedUnitManager.getById(document.getEnterpriseId()).getUnitName());
  470. }
  471. detail.setFileSource("系统生成电子文书");
  472. detail.setPageCount(0);
  473. detail.setAttachmentUrl(document.getActUrl() != null ? document.getActUrl() : "");
  474. detail.setOrderNum(orderNum++);
  475. detail.setIsDeleted("0");
  476. costProjectTaskMaterialSummaryDetailManager.save(detail);
  477. }
  478. // 获取被监审单位的反馈意见和反馈资料
  479. for (CostProjectTask childTask : childTasks) {
  480. List<CostProjectTaskPreliminaryOpinion> list = preliminaryOpinionManager.list(
  481. new LambdaQueryWrapper<>(CostProjectTaskPreliminaryOpinion.class)
  482. .eq(CostProjectTaskPreliminaryOpinion::getTaskId, childTask.getId())
  483. );
  484. for (CostProjectTaskPreliminaryOpinion opinion : list) {
  485. // 只处理有反馈资料的记录
  486. if (StringUtil.isNotEmpty(opinion.getFeedbackMaterials())) {
  487. CostProjectTaskMaterialSummaryDetail detail = new CostProjectTaskMaterialSummaryDetail();
  488. detail.setMasterId(summary.getId());
  489. detail.setTaskId(mainTask.getId());
  490. detail.setDocumentName("书面反馈材料");
  491. detail.setDocumentNumber("");
  492. detail.setAuditedUnitId(childTask.getAuditedUnitId());
  493. detail.setAuditedUnitName(childTask.getAuditedUnitName() != null ? childTask.getAuditedUnitName() : "");
  494. detail.setFileSource("监审单位反馈文件");
  495. detail.setPageCount(0);
  496. detail.setAttachmentUrl(opinion.getFeedbackMaterials());
  497. detail.setOrderNum(orderNum++);
  498. detail.setIsDeleted("0");
  499. costProjectTaskMaterialSummaryDetailManager.save(detail);
  500. }
  501. }
  502. }
  503. }
  504. // ==================== 类型9:成本审核初步意见表(集体审议用) ====================
  505. // 来源:从“监审文书”中获取
  506. private void generateType9Details(CostProjectTaskMaterialSummary summary, CostProjectTask mainTask, List<CostProjectTask> childTasks, List<CostProjectDocument> documents) {
  507. int orderNum = 1;
  508. // 获取"成本审核初步意见表"
  509. List<CostProjectDocument> matchedDocuments = documents.stream()
  510. .filter(doc -> "6".equals(doc.getDocumentType()))
  511. .sorted(Comparator.comparing(doc -> doc.getOrderNum() != null ? doc.getOrderNum() : 0))
  512. .collect(Collectors.toList());
  513. for (CostProjectDocument document : matchedDocuments) {
  514. CostProjectTaskMaterialSummaryDetail detail = new CostProjectTaskMaterialSummaryDetail();
  515. detail.setMasterId(summary.getId());
  516. detail.setTaskId(mainTask.getId());
  517. detail.setDocumentName(document.getDocumentName());
  518. detail.setDocumentNumber(document.getDocumentNumber() != null ? document.getDocumentNumber() : "");
  519. detail.setAuditedUnitId(document.getEnterpriseId());
  520. if (document.getEnterpriseId() != null) {
  521. detail.setAuditedUnitName(auditedUnitManager.getById(document.getEnterpriseId()).getUnitName());
  522. }
  523. detail.setFileSource("系统生成电子文书");
  524. detail.setPageCount(0);
  525. detail.setAttachmentUrl(document.getActUrl() != null ? document.getActUrl() : "");
  526. detail.setOrderNum(orderNum++);
  527. detail.setIsDeleted("0");
  528. costProjectTaskMaterialSummaryDetailManager.save(detail);
  529. }
  530. }
  531. // ==================== 类型10:成本监审集体审议记录 ====================
  532. // 来源:从“监审文书”中获取。
  533. private void generateType10Details(CostProjectTaskMaterialSummary summary, CostProjectTask mainTask, List<CostProjectDocument> documents) {
  534. int orderNum = 1;
  535. // 获取"成本监审集体审议记录"
  536. List<CostProjectDocument> matchedDocuments = documents.stream()
  537. .filter(doc -> "7".equals(doc.getDocumentType()))
  538. .sorted(Comparator.comparing(doc -> doc.getOrderNum() != null ? doc.getOrderNum() : 0))
  539. .collect(Collectors.toList());
  540. for (CostProjectDocument document : matchedDocuments) {
  541. CostProjectTaskMaterialSummaryDetail detail = new CostProjectTaskMaterialSummaryDetail();
  542. detail.setMasterId(summary.getId());
  543. detail.setTaskId(mainTask.getId());
  544. detail.setDocumentName(document.getDocumentName());
  545. detail.setDocumentNumber(document.getDocumentNumber() != null ? document.getDocumentNumber() : "");
  546. detail.setAuditedUnitId(document.getEnterpriseId());
  547. if (document.getEnterpriseId() != null) {
  548. detail.setAuditedUnitName(auditedUnitManager.getById(document.getEnterpriseId()).getUnitName());
  549. }
  550. detail.setFileSource("系统生成电子文书");
  551. detail.setPageCount(0);
  552. detail.setAttachmentUrl(document.getActUrl() != null ? document.getActUrl() : "");
  553. detail.setOrderNum(orderNum++);
  554. detail.setIsDeleted("0");
  555. costProjectTaskMaterialSummaryDetailManager.save(detail);
  556. }
  557. // 获取集体审议记录
  558. List<CostProjectDeliberate> list = costProjectDeliberateManager.list(
  559. new LambdaQueryWrapper<CostProjectDeliberate>().eq(CostProjectDeliberate::getTaskId, mainTask.getId())
  560. );
  561. for (CostProjectDeliberate deliberate : list) {
  562. // 只处理有附件的审议记录
  563. if (StringUtil.isNotEmpty(deliberate.getAttachmentUrl())) {
  564. CostProjectTaskMaterialSummaryDetail detail = new CostProjectTaskMaterialSummaryDetail();
  565. detail.setMasterId(summary.getId());
  566. detail.setTaskId(mainTask.getId());
  567. detail.setDocumentName("成本监审集体审议记录");
  568. detail.setDocumentNumber("");
  569. detail.setAuditedUnitId(mainTask.getAuditedUnitId());
  570. detail.setAuditedUnitName(mainTask.getAuditedUnitName() != null ? mainTask.getAuditedUnitName() : "");
  571. detail.setFileSource("监审主体上传文件");
  572. detail.setPageCount(0);
  573. detail.setAttachmentUrl(deliberate.getAttachmentUrl());
  574. detail.setOrderNum(orderNum++);
  575. detail.setIsDeleted("0");
  576. costProjectTaskMaterialSummaryDetailManager.save(detail);
  577. }
  578. }
  579. }
  580. // ==================== 类型11:成本监审工作底稿 ====================
  581. // 来源:从“监审文书”中获取。
  582. private void generateType11Details(CostProjectTaskMaterialSummary summary, CostProjectTask mainTask, List<CostProjectTask> childTasks, List<CostProjectDocument> documents) {
  583. int orderNum = 1;
  584. // 获取"成本监审工作底稿"
  585. List<CostProjectDocument> matchedDocuments = documents.stream()
  586. .filter(doc -> "8".equals(doc.getDocumentType()))
  587. .sorted(Comparator.comparing(doc -> doc.getOrderNum() != null ? doc.getOrderNum() : 0))
  588. .collect(Collectors.toList());
  589. for (CostProjectDocument document : matchedDocuments) {
  590. CostProjectTaskMaterialSummaryDetail detail = new CostProjectTaskMaterialSummaryDetail();
  591. detail.setMasterId(summary.getId());
  592. detail.setTaskId(mainTask.getId());
  593. detail.setDocumentName(document.getDocumentName());
  594. detail.setDocumentNumber(document.getDocumentNumber() != null ? document.getDocumentNumber() : "");
  595. detail.setAuditedUnitId(document.getEnterpriseId());
  596. if (document.getEnterpriseId() != null) {
  597. detail.setAuditedUnitName(auditedUnitManager.getById(document.getEnterpriseId()).getUnitName());
  598. }
  599. detail.setFileSource("系统生成电子文书");
  600. detail.setPageCount(0);
  601. detail.setAttachmentUrl(document.getActUrl() != null ? document.getActUrl() : "");
  602. detail.setOrderNum(orderNum++);
  603. detail.setIsDeleted("0");
  604. costProjectTaskMaterialSummaryDetailManager.save(detail);
  605. }
  606. // 获取核增核减记录(工作底稿的一部分)
  607. for (CostProjectTask childTask : childTasks) {
  608. List<CostProjectTaskAdjustmentRecord> costProjectTaskAdjustmentRecords = adjustmentRecordManager.listByTaskId(childTask.getId());
  609. for (CostProjectTaskAdjustmentRecord record : costProjectTaskAdjustmentRecords) {
  610. // 只处理有附件的记录
  611. if (StringUtil.isNotEmpty(record.getAttachmentUrl())) {
  612. CostProjectTaskMaterialSummaryDetail detail = new CostProjectTaskMaterialSummaryDetail();
  613. detail.setMasterId(summary.getId());
  614. detail.setTaskId(mainTask.getId());
  615. detail.setDocumentName(record.getSubject() != null ? record.getSubject() + "-工作底稿" : "成本监审工作底稿");
  616. detail.setDocumentNumber("");
  617. detail.setAuditedUnitId(childTask.getAuditedUnitId());
  618. detail.setAuditedUnitName(childTask.getAuditedUnitName() != null ? childTask.getAuditedUnitName() : "");
  619. detail.setFileSource("监审单位反馈文件");
  620. detail.setPageCount(0);
  621. detail.setAttachmentUrl(record.getAttachmentUrl());
  622. detail.setOrderNum(orderNum++);
  623. detail.setIsDeleted("0");
  624. costProjectTaskMaterialSummaryDetailManager.save(detail);
  625. }
  626. }
  627. }
  628. }
  629. // ==================== 类型12:成本监审提取资料登记表 ====================
  630. // 来源:从“监审文书”中获取。
  631. private void generateType12Details(CostProjectTaskMaterialSummary summary, CostProjectTask mainTask, List<CostProjectTask> childTasks, List<CostProjectDocument> documents) {
  632. int orderNum = 1;
  633. // 获取"成本监审提取资料登记表"
  634. List<CostProjectDocument> matchedDocuments = documents.stream()
  635. .filter(doc -> "9".equals(doc.getDocumentType()))
  636. .sorted(Comparator.comparing(doc -> doc.getOrderNum() != null ? doc.getOrderNum() : 0))
  637. .collect(Collectors.toList());
  638. for (CostProjectDocument document : matchedDocuments) {
  639. CostProjectTaskMaterialSummaryDetail detail = new CostProjectTaskMaterialSummaryDetail();
  640. detail.setMasterId(summary.getId());
  641. detail.setTaskId(mainTask.getId());
  642. detail.setDocumentName(document.getDocumentName());
  643. detail.setDocumentNumber(document.getDocumentNumber() != null ? document.getDocumentNumber() : "");
  644. detail.setAuditedUnitId(document.getEnterpriseId());
  645. if (document.getEnterpriseId() != null) {
  646. detail.setAuditedUnitName(auditedUnitManager.getById(document.getEnterpriseId()).getUnitName());
  647. }
  648. detail.setFileSource("系统生成电子文书");
  649. detail.setPageCount(0);
  650. detail.setAttachmentUrl(document.getActUrl() != null ? document.getActUrl() : "");
  651. detail.setOrderNum(orderNum++);
  652. detail.setIsDeleted("0");
  653. costProjectTaskMaterialSummaryDetailManager.save(detail);
  654. }
  655. // 获取子任务的资料登记表
  656. for (CostProjectTask childTask : childTasks) {
  657. List<CostProjectTaskEvidence> list = costProjectTaskEvidenceManager.list(
  658. new LambdaQueryWrapper<CostProjectTaskEvidence>().eq(CostProjectTaskEvidence::getTaskId, childTask.getId())
  659. );
  660. for (CostProjectTaskEvidence evidence : list) {
  661. // 只处理有附件的资料登记
  662. if (StringUtil.isNotEmpty(evidence.getAttachmentUrl())) {
  663. CostProjectTaskMaterialSummaryDetail detail = new CostProjectTaskMaterialSummaryDetail();
  664. detail.setMasterId(summary.getId());
  665. detail.setTaskId(mainTask.getId());
  666. detail.setDocumentName(evidence.getMaterialName() != null ? evidence.getMaterialName() : "提取资料登记");
  667. detail.setDocumentNumber("");
  668. detail.setAuditedUnitId(childTask.getAuditedUnitId());
  669. detail.setAuditedUnitName(childTask.getAuditedUnitName() != null ? childTask.getAuditedUnitName() : "");
  670. detail.setFileSource("监审单位反馈文件");
  671. detail.setPageCount(evidence.getPageCount() != null ? evidence.getPageCount() : 0);
  672. detail.setAttachmentUrl(evidence.getAttachmentUrl());
  673. detail.setOrderNum(orderNum++);
  674. detail.setIsDeleted("0");
  675. costProjectTaskMaterialSummaryDetailManager.save(detail);
  676. }
  677. }
  678. }
  679. }
  680. // ==================== 类型13:提取的成本资料和会计凭证等复印件 ====================
  681. // 来源:各被监审单位首次及补充材料时提交的资料,包括综合性材料、财会资料等。
  682. private void generateType13Details(CostProjectTaskMaterialSummary summary, CostProjectTask mainTask, List<CostProjectTask> childTasks, List<CostProjectDocument> documents) {
  683. int orderNum = 1;
  684. // 获取"成本资料和会计凭证"(来源:被监审单位提交的资料)
  685. List<CostProjectDocument> matchedDocuments = documents.stream()
  686. .filter(doc -> "成本资料和会计凭证".equals(doc.getDocumentType()))
  687. .sorted(Comparator.comparing(doc -> doc.getOrderNum() != null ? doc.getOrderNum() : 0))
  688. .collect(Collectors.toList());
  689. for (CostProjectDocument document : matchedDocuments) {
  690. CostProjectTaskMaterialSummaryDetail detail = new CostProjectTaskMaterialSummaryDetail();
  691. detail.setMasterId(summary.getId());
  692. detail.setTaskId(mainTask.getId());
  693. detail.setDocumentName(document.getDocumentName());
  694. detail.setDocumentNumber(document.getDocumentNumber() != null ? document.getDocumentNumber() : "");
  695. detail.setAuditedUnitId(document.getEnterpriseId());
  696. if (document.getEnterpriseId() != null) {
  697. detail.setAuditedUnitName(auditedUnitManager.getById(document.getEnterpriseId()).getUnitName());
  698. }
  699. detail.setFileSource("监审单位反馈文件");
  700. detail.setPageCount(0);
  701. detail.setAttachmentUrl(document.getActUrl() != null ? document.getActUrl() : "");
  702. detail.setOrderNum(orderNum++);
  703. detail.setIsDeleted("0");
  704. costProjectTaskMaterialSummaryDetailManager.save(detail);
  705. }
  706. // 获取子任务的资料登记表
  707. for (CostProjectTask childTask : childTasks) {
  708. // 获取任务的报送资料要求(被监审单位提交的补充资料)
  709. List<CostProjectTaskMaterial> list = costProjectTaskMaterialManager.list(
  710. new LambdaQueryWrapper<>(CostProjectTaskMaterial.class)
  711. .eq(CostProjectTaskMaterial::getTaskId, childTask.getId())
  712. );
  713. for (CostProjectTaskMaterial material : list) {
  714. // 只处理已上传的资料
  715. if ("1".equals(material.getIsUpload()) && StringUtil.isNotEmpty(material.getFileUrl())) {
  716. CostProjectTaskMaterialSummaryDetail detail = new CostProjectTaskMaterialSummaryDetail();
  717. detail.setMasterId(summary.getId());
  718. detail.setTaskId(mainTask.getId());
  719. detail.setDocumentName(material.getInformationName() != null ? material.getInformationName() : "补充资料");
  720. detail.setDocumentNumber("");
  721. detail.setAuditedUnitId(childTask.getAuditedUnitId());
  722. detail.setAuditedUnitName(childTask.getAuditedUnitName() != null ? childTask.getAuditedUnitName() : "");
  723. detail.setFileSource("监审单位反馈文件");
  724. detail.setPageCount(0);
  725. detail.setAttachmentUrl(material.getFileUrl());
  726. detail.setOrderNum(orderNum++);
  727. detail.setIsDeleted("0");
  728. costProjectTaskMaterialSummaryDetailManager.save(detail);
  729. }
  730. }
  731. }
  732. }
  733. // ==================== 类型14:中止定价成本监审料通知书(含送达回证) ====================
  734. // 来源:从“监审文书”中获取。
  735. private void generateType14Details(CostProjectTaskMaterialSummary summary, CostProjectTask mainTask, List<CostProjectTask> childTasks, List<CostProjectDocument> documents) {
  736. int orderNum = 1;
  737. // 获取"中止定价成本监审通知书"(来源:被监审单位提交的资料)
  738. List<CostProjectDocument> matchedDocuments = documents.stream()
  739. .filter(doc -> "11".equals(doc.getDocumentType())
  740. || "11-12".equals(doc.getDocumentType() + "-" + doc.getDocumentTypeName()))
  741. .sorted(Comparator.comparing(doc -> doc.getOrderNum() != null ? doc.getOrderNum() : 0))
  742. .collect(Collectors.toList());
  743. for (CostProjectDocument document : matchedDocuments) {
  744. CostProjectTaskMaterialSummaryDetail detail = new CostProjectTaskMaterialSummaryDetail();
  745. detail.setMasterId(summary.getId());
  746. detail.setTaskId(mainTask.getId());
  747. detail.setDocumentName(document.getDocumentName());
  748. detail.setDocumentNumber(document.getDocumentNumber() != null ? document.getDocumentNumber() : "");
  749. detail.setAuditedUnitId(document.getEnterpriseId());
  750. if (document.getEnterpriseId() != null) {
  751. detail.setAuditedUnitName(auditedUnitManager.getById(document.getEnterpriseId()).getUnitName());
  752. }
  753. detail.setFileSource("系统生成电子文书");
  754. detail.setPageCount(0);
  755. detail.setAttachmentUrl(document.getActUrl() != null ? document.getActUrl() : "");
  756. detail.setOrderNum(orderNum++);
  757. detail.setIsDeleted("0");
  758. costProjectTaskMaterialSummaryDetailManager.save(detail);
  759. }
  760. }
  761. /**
  762. * 档案校对通过后,异步生成Word卷宗
  763. * 顺序:封面(-1) -> 目录(-2) -> 14个资料(1-13) -> 封底(-3)
  764. *
  765. * @param taskId 任务ID
  766. */
  767. @Async
  768. public void generateWordArchiveAsync(String taskId) {
  769. CostProjectTask task = costProjectTaskManager.getById(taskId);
  770. try {
  771. logger.info("开始生成Word卷宗,任务ID:{}", taskId);
  772. // 查询任务的所有资料归纳主表(包含封面、目录、封底)
  773. List<CostProjectTaskMaterialSummary> allSummaryList =
  774. costProjectTaskMaterialSummaryManager.listAllByTaskId(taskId);
  775. // 创建Word文档
  776. XWPFDocument document = new XWPFDocument();
  777. boolean isFirstDocument = true;
  778. // 按顺序合并:封面(-1) -> 目录(-2) -> 14个资料(1-13) -> 封底(-3)
  779. // 排序:-1, -2, 1, 2, ..., 13, -3
  780. allSummaryList.sort((a, b) -> {
  781. int orderA = a.getMaterialOrderNum() != null ? a.getMaterialOrderNum() : 0;
  782. int orderB = b.getMaterialOrderNum() != null ? b.getMaterialOrderNum() : 0;
  783. // -1(封面) < -2(目录) < 1-13(资料) < -3(封底)
  784. int sortA = orderA == -1 ? -100 : (orderA == -2 ? -99 : (orderA == -3 ? 100 : orderA));
  785. int sortB = orderB == -1 ? -100 : (orderB == -2 ? -99 : (orderB == -3 ? 100 : orderB));
  786. return Integer.compare(sortA, sortB);
  787. });
  788. // 遍历所有主表,从明细表获取附件URL进行合并
  789. for (CostProjectTaskMaterialSummary summary : allSummaryList) {
  790. logger.info("合并资料:{}", summary.getMaterialName());
  791. List<CostProjectTaskMaterialSummaryDetail> detailList = summary.getDetailList();
  792. if (detailList != null && !detailList.isEmpty()) {
  793. detailList.sort(Comparator.comparing(d -> d.getOrderNum() != null ? d.getOrderNum() : 0));
  794. for (CostProjectTaskMaterialSummaryDetail detail : detailList) {
  795. if (StringUtil.isNotEmpty(detail.getAttachmentUrl())) {
  796. isFirstDocument = mergeDocumentFile(document, detail.getAttachmentUrl(), isFirstDocument);
  797. }
  798. }
  799. }
  800. }
  801. // 生成输出文件路径
  802. String fileName = FileUploadUtil.generateFileName("卷宗_" + taskId + ".docx");
  803. String outputPath = FileUploadUtil.generateSavePath(
  804. EipConfig.getUploadPath(), fileName, "docx");
  805. // 确保目录存在
  806. java.io.File outputFile = new java.io.File(outputPath);
  807. if (!outputFile.getParentFile().exists()) {
  808. outputFile.getParentFile().mkdirs();
  809. }
  810. // 保存Word文档
  811. FileOutputStream out = new FileOutputStream(outputPath);
  812. document.write(out);
  813. out.close();
  814. document.close();
  815. // 生成访问URL
  816. String archiveUrl = FileUploadUtil.getPathFileName(outputPath, fileName);
  817. logger.info("Word卷宗生成完成,任务ID:{},URL:{}", taskId, archiveUrl);
  818. // 更新任务的卷宗URL
  819. if (task != null) {
  820. task.setArchiveUrl(archiveUrl);
  821. task.setArchiveStatus("2");
  822. task.setArchiveTime(LocalDateTime.now());
  823. costProjectTaskManager.updateById(task);
  824. logger.info("卷宗URL已更新,任务ID:{}", taskId);
  825. }
  826. } catch (Exception e) {
  827. if (task != null) {
  828. task.setArchiveStatus("3");
  829. costProjectTaskManager.updateById(task);
  830. }
  831. logger.error("生成Word卷宗失败,任务ID:,错误:{}", taskId, e.getMessage(), e);
  832. }
  833. }
  834. /**
  835. * 合并单个文档文件到主文档(支持Word、PDF、Excel等多种格式)
  836. *
  837. * @param document 主文档
  838. * @param fileUrl 文件URL(如:/profile/upload/20251116/xxx.docx)
  839. * @param isFirstDocument 是否是第一个文档
  840. * @return 是否是第一个文档(用于下次调用)
  841. */
  842. private boolean mergeDocumentFile(XWPFDocument document, String fileUrl, boolean isFirstDocument) {
  843. try {
  844. String filePath = fileUrl;
  845. // 处理路径:/profile 开头的路径需要替换为实际路径
  846. if (filePath.startsWith("/profile")) {
  847. filePath = EipConfig.getProfile() + filePath.substring(8);
  848. }
  849. java.io.File file = new java.io.File(filePath);
  850. if (!file.exists()) {
  851. logger.warn("文件不存在:{}(原始路径:{})", filePath, fileUrl);
  852. return isFirstDocument;
  853. }
  854. String lowerCasePath = filePath.toLowerCase();
  855. // 添加分页符(非第一个文档)
  856. if (!isFirstDocument) {
  857. XWPFParagraph pageBreakPara = document.createParagraph();
  858. XWPFRun pageBreakRun = pageBreakPara.createRun();
  859. pageBreakRun.addBreak(BreakType.PAGE);
  860. }
  861. // 根据文件类型处理
  862. if (lowerCasePath.endsWith(".docx") || lowerCasePath.endsWith(".doc")) {
  863. mergeWordDocument(document, filePath);
  864. } else if (lowerCasePath.endsWith(".pdf")) {
  865. mergePdfDocument(document, filePath);
  866. } else if (lowerCasePath.endsWith(".xlsx") || lowerCasePath.endsWith(".xls")) {
  867. mergeExcelDocument(document, filePath);
  868. } else {
  869. logger.warn("不支持的文件格式:{}", fileUrl);
  870. return isFirstDocument;
  871. }
  872. return false;
  873. } catch (Exception e) {
  874. logger.error("合并文件失败:{},错误:{}", fileUrl, e.getMessage(), e);
  875. return isFirstDocument;
  876. }
  877. }
  878. /**
  879. * 合并Word文档
  880. */
  881. private void mergeWordDocument(XWPFDocument document, String filePath) throws Exception {
  882. java.io.FileInputStream fis = new java.io.FileInputStream(filePath);
  883. XWPFDocument sourceDoc = new XWPFDocument(fis);
  884. org.openxmlformats.schemas.wordprocessingml.x2006.main.CTBody srcBody = sourceDoc.getDocument().getBody();
  885. org.openxmlformats.schemas.wordprocessingml.x2006.main.CTBody destBody = document.getDocument().getBody();
  886. for (org.apache.xmlbeans.XmlObject obj : srcBody.selectPath("./*")) {
  887. if (obj instanceof org.openxmlformats.schemas.wordprocessingml.x2006.main.CTP) {
  888. org.openxmlformats.schemas.wordprocessingml.x2006.main.CTP newP = destBody.addNewP();
  889. newP.set(obj.copy());
  890. } else if (obj instanceof org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTbl) {
  891. org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTbl newTbl = destBody.addNewTbl();
  892. newTbl.set(obj.copy());
  893. }
  894. }
  895. sourceDoc.close();
  896. fis.close();
  897. }
  898. /**
  899. * 合并PDF文档(转换为图片后插入)
  900. */
  901. private void mergePdfDocument(XWPFDocument document, String filePath) throws Exception {
  902. try {
  903. PDDocument pdfDoc = PDDocument.load(new java.io.File(filePath));
  904. org.apache.pdfbox.rendering.PDFRenderer renderer = new org.apache.pdfbox.rendering.PDFRenderer(pdfDoc);
  905. for (int i = 0; i < pdfDoc.getNumberOfPages(); i++) {
  906. java.awt.image.BufferedImage image = renderer.renderImageWithDPI(i, 200);
  907. java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
  908. javax.imageio.ImageIO.write(image, "png", baos);
  909. byte[] imageBytes = baos.toByteArray();
  910. baos.close();
  911. XWPFParagraph para = document.createParagraph();
  912. XWPFRun run = para.createRun();
  913. java.io.ByteArrayInputStream bais = new java.io.ByteArrayInputStream(imageBytes);
  914. run.addPicture(bais, org.apache.poi.xwpf.usermodel.Document.PICTURE_TYPE_PNG,
  915. "pdf_page_" + i + ".png", org.apache.poi.util.Units.toEMU(410), org.apache.poi.util.Units.toEMU(600));
  916. bais.close();
  917. para.createRun().addBreak();
  918. if (i < pdfDoc.getNumberOfPages() - 1) {
  919. XWPFParagraph pageBreak = document.createParagraph();
  920. XWPFRun pageBreakRun = pageBreak.createRun();
  921. pageBreakRun.addBreak(BreakType.PAGE);
  922. }
  923. }
  924. pdfDoc.close();
  925. logger.info("PDF文件已合并:{}", filePath);
  926. } catch (Exception e) {
  927. logger.error("合并PDF失败:{},错误:{}", filePath, e.getMessage());
  928. XWPFParagraph para = document.createParagraph();
  929. XWPFRun run = para.createRun();
  930. run.setText("[PDF文件:" + new java.io.File(filePath).getName() + " - 无法嵌入]");
  931. }
  932. }
  933. /**
  934. * 合并Excel文档(转换为表格后插入)
  935. */
  936. private void mergeExcelDocument(XWPFDocument document, String filePath) throws Exception {
  937. try {
  938. org.apache.poi.ss.usermodel.Workbook workbook = org.apache.poi.ss.usermodel.WorkbookFactory.create(new java.io.File(filePath));
  939. for (int sheetIndex = 0; sheetIndex < workbook.getNumberOfSheets(); sheetIndex++) {
  940. org.apache.poi.ss.usermodel.Sheet sheet = workbook.getSheetAt(sheetIndex);
  941. if (sheet.getPhysicalNumberOfRows() == 0) continue;
  942. int rows = sheet.getPhysicalNumberOfRows();
  943. int cols = 0;
  944. for (int i = 0; i < rows; i++) {
  945. org.apache.poi.ss.usermodel.Row row = sheet.getRow(i);
  946. if (row != null && row.getPhysicalNumberOfCells() > cols) {
  947. cols = row.getPhysicalNumberOfCells();
  948. }
  949. }
  950. if (cols == 0) cols = 1;
  951. XWPFTable table = document.createTable(rows, cols);
  952. table.setStyleID("TableGrid");
  953. for (int i = 0; i < rows; i++) {
  954. org.apache.poi.ss.usermodel.Row excelRow = sheet.getRow(i);
  955. XWPFTableRow wordRow = table.getRow(i);
  956. if (excelRow != null) {
  957. for (int j = 0; j < cols; j++) {
  958. org.apache.poi.ss.usermodel.Cell cell = excelRow.getCell(j);
  959. String cellValue = cell != null ? cell.toString() : "";
  960. XWPFTableCell wordCell = wordRow.getCell(j);
  961. wordCell.setText(cellValue);
  962. }
  963. }
  964. }
  965. if (sheetIndex < workbook.getNumberOfSheets() - 1) {
  966. XWPFParagraph pageBreak = document.createParagraph();
  967. XWPFRun pageBreakRun = pageBreak.createRun();
  968. pageBreakRun.addBreak(BreakType.PAGE);
  969. }
  970. }
  971. workbook.close();
  972. logger.info("Excel文件已合并:{}", filePath);
  973. } catch (Exception e) {
  974. logger.error("合并Excel失败:{},错误:{}", filePath, e.getMessage());
  975. XWPFParagraph para = document.createParagraph();
  976. XWPFRun run = para.createRun();
  977. run.setText("[Excel文件:" + new java.io.File(filePath).getName() + " - 无法嵌入,请查看原文件]");
  978. }
  979. }
  980. /**
  981. * 更新单个资料归纳主表的总页数
  982. *
  983. * @param masterId 主表ID
  984. */
  985. public void updateMasterTotalPageCount(String masterId) {
  986. try {
  987. CostProjectTaskMaterialSummary summary = costProjectTaskMaterialSummaryManager.getById(masterId);
  988. if (summary == null) {
  989. logger.warn("资料归纳主表不存在,ID:{}", masterId);
  990. return;
  991. }
  992. int totalPageCount = 0;
  993. // 查询该主表的所有明细
  994. List<CostProjectTaskMaterialSummaryDetail> detailList = costProjectTaskMaterialSummaryDetailManager.list(
  995. new LambdaQueryWrapper<CostProjectTaskMaterialSummaryDetail>()
  996. .eq(CostProjectTaskMaterialSummaryDetail::getMasterId, masterId)
  997. .eq(CostProjectTaskMaterialSummaryDetail::getIsDeleted, "0")
  998. );
  999. // 累加所有明细的页数
  1000. for (CostProjectTaskMaterialSummaryDetail detail : detailList) {
  1001. if (detail.getPageCount() != null) {
  1002. totalPageCount += detail.getPageCount();
  1003. }
  1004. }
  1005. // 更新主表的总页数
  1006. summary.setTotalPageCount(String.valueOf(totalPageCount));
  1007. costProjectTaskMaterialSummaryManager.updateById(summary);
  1008. logger.info("资料归纳【{}】总页数已更新:{}", summary.getMaterialName(), totalPageCount);
  1009. } catch (Exception e) {
  1010. logger.error("更新主表总页数失败,主表ID:{},错误:{}", masterId, e.getMessage(), e);
  1011. }
  1012. }
  1013. /**
  1014. * 统计每个资料归纳主表的总页数(所有明细的页数之和)
  1015. *
  1016. * @param taskId 任务ID
  1017. */
  1018. public void calculatePageCountAsync(String taskId) {
  1019. try {
  1020. logger.info("开始异步统计页数,任务ID:{}", taskId);
  1021. // 查询任务的所有资料归纳主表
  1022. List<CostProjectTaskMaterialSummary> summaryList = costProjectTaskMaterialSummaryManager.listByTaskId(taskId);
  1023. for (CostProjectTaskMaterialSummary summary : summaryList) {
  1024. int totalPageCount = 0;
  1025. // 获取该主表的所有明细
  1026. if (summary.getDetailList() != null && !summary.getDetailList().isEmpty()) {
  1027. for (CostProjectTaskMaterialSummaryDetail detail : summary.getDetailList()) {
  1028. if (StringUtil.isNotEmpty(detail.getAttachmentUrl())) {
  1029. // 统计该明细的页数
  1030. int pageCount = calculateFilePageCount(detail.getAttachmentUrl());
  1031. detail.setPageCount(pageCount);
  1032. costProjectTaskMaterialSummaryDetailManager.updateById(detail);
  1033. // 累加到总页数
  1034. totalPageCount += pageCount;
  1035. }
  1036. }
  1037. }
  1038. // 更新主表的总页数
  1039. summary.setTotalPageCount(String.valueOf(totalPageCount));
  1040. costProjectTaskMaterialSummaryManager.updateById(summary);
  1041. logger.info("资料归纳【{}】统计完成,总页数:{}", summary.getMaterialName(), totalPageCount);
  1042. }
  1043. logger.info("页数统计完成,任务ID:{}", taskId);
  1044. } catch (Exception e) {
  1045. logger.error("统计页数失败,任务ID:{},错误:{}", taskId, e.getMessage(), e);
  1046. }
  1047. }
  1048. /**
  1049. * 计算文件页数(公共方法,可被外部调用)
  1050. *
  1051. * @param fileUrl 文件URL或路径
  1052. * @return 页数
  1053. */
  1054. public int calculateFilePageCount(String fileUrl) {
  1055. try {
  1056. if (StringUtil.isEmpty(fileUrl)) {
  1057. return 0;
  1058. }
  1059. String lowerCaseUrl = fileUrl.toLowerCase();
  1060. if (lowerCaseUrl.endsWith(".doc") || lowerCaseUrl.endsWith(".docx")) {
  1061. return getWordPageCount(fileUrl);
  1062. } else {
  1063. return 1;
  1064. }
  1065. } catch (Exception e) {
  1066. logger.error("计算文件页数失败,文件:{},错误:{}", fileUrl, e.getMessage(), e);
  1067. return 1;
  1068. }
  1069. }
  1070. /**
  1071. * 获取Word文档的页数
  1072. *
  1073. * @param fileUrl 文件URL或路径(如:/profile/upload/20251116/xxx.docx)
  1074. * @return 页数
  1075. */
  1076. private int getWordPageCount(String fileUrl) {
  1077. java.io.FileInputStream fis = null;
  1078. XWPFDocument document = null;
  1079. try {
  1080. String filePath = fileUrl;
  1081. if (filePath.startsWith("/profile")) {
  1082. filePath = EipConfig.getProfile() + filePath.substring(8);
  1083. }
  1084. fis = new java.io.FileInputStream(filePath);
  1085. document = new XWPFDocument(fis);
  1086. // 获取页数(可能为null或0)
  1087. try {
  1088. int pageCount = document.getProperties().getExtendedProperties().getUnderlyingProperties().getPages();
  1089. if (pageCount > 0) {
  1090. return pageCount;
  1091. }
  1092. } catch (Exception e) {
  1093. logger.debug("无法读取Word文档页数属性:{}", e.getMessage());
  1094. }
  1095. // 如果页数属性不可用,通过段落数估算(每页约50行)
  1096. int paragraphCount = document.getParagraphs().size();
  1097. int tableCount = document.getTables().size();
  1098. int estimatedPages = Math.max(1, (paragraphCount + tableCount * 10) / 50);
  1099. return estimatedPages;
  1100. } catch (Exception e) {
  1101. logger.error("读取Word页数失败,文件:{},错误:{}", fileUrl, e.getMessage(), e);
  1102. return 1;
  1103. } finally {
  1104. try {
  1105. if (document != null) {
  1106. document.close();
  1107. }
  1108. if (fis != null) {
  1109. fis.close();
  1110. }
  1111. } catch (Exception e) {
  1112. logger.warn("关闭文件流失败:{}", e.getMessage());
  1113. }
  1114. }
  1115. }
  1116. // ==================== 卷宗校对文书生成功能 ====================
  1117. /**
  1118. * 生成卷宗文书(封面/目录/封底)
  1119. * id为空是新增,id不为空是编辑
  1120. *
  1121. * @param req 生成请求
  1122. * @return 生成的文书URL
  1123. */
  1124. public String generateArchiveDocument(com.hotent.project.req.ArchiveProofreadReq req) throws Exception {
  1125. Integer documentType = req.getDocumentType();
  1126. String existingId = req.getRelatedId();
  1127. // 如果id不为空,从数据库获取已有记录的documentType(id是主表ID)
  1128. if (StringUtil.isNotEmpty(existingId)) {
  1129. CostProjectTaskMaterialSummary existing = costProjectTaskMaterialSummaryManager.getById(existingId);
  1130. if (existing == null) {
  1131. throw new RuntimeException("主表记录不存在:" + existingId);
  1132. }
  1133. Integer orderNum = existing.getMaterialOrderNum();
  1134. if (orderNum != null && orderNum == -1) {
  1135. documentType = 1;
  1136. } else if (orderNum != null && orderNum == -2) {
  1137. documentType = 2;
  1138. } else if (orderNum != null && orderNum == -3) {
  1139. documentType = 3;
  1140. } else {
  1141. throw new RuntimeException("无效的文书记录");
  1142. }
  1143. }
  1144. String templatePath;
  1145. String documentName;
  1146. switch (documentType) {
  1147. case 1:
  1148. templatePath = coverTemplatePath;
  1149. documentName = "案卷封面";
  1150. if (StringUtil.isEmpty(templatePath)) {
  1151. throw new RuntimeException("案卷封面模板路径未配置");
  1152. }
  1153. break;
  1154. case 2:
  1155. templatePath = catalogTemplatePath;
  1156. documentName = "卷内目录";
  1157. if (StringUtil.isEmpty(templatePath)) {
  1158. throw new RuntimeException("卷内目录模板路径未配置");
  1159. }
  1160. break;
  1161. case 3:
  1162. templatePath = backCoverTemplatePath;
  1163. documentName = "案卷封底";
  1164. if (StringUtil.isEmpty(templatePath)) {
  1165. throw new RuntimeException("案卷封底模板路径未配置");
  1166. }
  1167. break;
  1168. default:
  1169. throw new RuntimeException("不支持的文书类型:" + documentType);
  1170. }
  1171. // 检查模板文件是否存在
  1172. java.io.File templateFile = new java.io.File(templatePath);
  1173. if (!templateFile.exists()) {
  1174. throw new RuntimeException("模板文件不存在:" + templatePath);
  1175. }
  1176. // 生成文书
  1177. String outputUrl = generateDocumentFromTemplate(templatePath, documentName, req);
  1178. // 保存到数据库(主表+明细表)
  1179. int orderNum = -documentType; // -1封面、-2目录、-3封底
  1180. String masterId;
  1181. if (StringUtil.isNotEmpty(existingId)) {
  1182. // 编辑:existingId是主表ID,查找对应的明细记录
  1183. masterId = existingId;
  1184. CostProjectTaskMaterialSummary existingSummary = costProjectTaskMaterialSummaryManager.getById(masterId);
  1185. if (existingSummary != null) {
  1186. // 更新task的卷宗号和归档人
  1187. CostProjectTask task = costProjectTaskManager.getById(existingSummary.getTaskId());
  1188. if (task != null) {
  1189. task.setArchiveNo(req.getArchiveNo());
  1190. task.setArchiveUser(ContextUtil.getCurrentUser().getFullname());
  1191. costProjectTaskManager.updateById(task);
  1192. }
  1193. }
  1194. CostProjectTaskMaterialSummaryDetail existingDetail = costProjectTaskMaterialSummaryDetailManager.getOne(
  1195. new LambdaQueryWrapper<CostProjectTaskMaterialSummaryDetail>()
  1196. .eq(CostProjectTaskMaterialSummaryDetail::getMasterId, masterId)
  1197. .eq(CostProjectTaskMaterialSummaryDetail::getIsDeleted, "0")
  1198. );
  1199. if (existingDetail != null) {
  1200. // 更新已有明细
  1201. existingDetail.setAttachmentUrl(outputUrl);
  1202. existingDetail.setGenerateTime(java.time.LocalDateTime.now());
  1203. costProjectTaskMaterialSummaryDetailManager.updateById(existingDetail);
  1204. } else {
  1205. // 创建新明细
  1206. CostProjectTaskMaterialSummaryDetail detail = new CostProjectTaskMaterialSummaryDetail();
  1207. detail.setMasterId(masterId);
  1208. detail.setTaskId(req.getTaskId());
  1209. detail.setDocumentName(documentName);
  1210. detail.setFileSource("系统生成电子文书");
  1211. detail.setAttachmentUrl(outputUrl);
  1212. detail.setOrderNum(1);
  1213. detail.setGenerateTime(java.time.LocalDateTime.now());
  1214. detail.setIsDeleted("0");
  1215. costProjectTaskMaterialSummaryDetailManager.save(detail);
  1216. }
  1217. } else {
  1218. // 新增:创建主表记录
  1219. CostProjectTaskMaterialSummary summary = new CostProjectTaskMaterialSummary();
  1220. summary.setTaskId(req.getTaskId());
  1221. summary.setMaterialOrderNum(orderNum);
  1222. summary.setMaterialName(documentName);
  1223. summary.setIsDeleted("0");
  1224. costProjectTaskMaterialSummaryManager.save(summary);
  1225. masterId = summary.getId();
  1226. // 更新task的卷宗号和归档人
  1227. CostProjectTask task = costProjectTaskManager.getById(req.getTaskId());
  1228. if (task != null) {
  1229. task.setArchiveNo(req.getArchiveNo());
  1230. task.setArchiveUser(ContextUtil.getCurrentUser().getAccount());
  1231. costProjectTaskManager.updateById(task);
  1232. }
  1233. // 创建明细记录
  1234. CostProjectTaskMaterialSummaryDetail detail = new CostProjectTaskMaterialSummaryDetail();
  1235. detail.setMasterId(masterId);
  1236. detail.setTaskId(req.getTaskId());
  1237. detail.setDocumentName(documentName);
  1238. detail.setFileSource("系统生成电子文书");
  1239. detail.setAttachmentUrl(outputUrl);
  1240. detail.setOrderNum(1);
  1241. detail.setGenerateTime(java.time.LocalDateTime.now());
  1242. detail.setIsDeleted("0");
  1243. costProjectTaskMaterialSummaryDetailManager.save(detail);
  1244. }
  1245. return outputUrl;
  1246. }
  1247. /**
  1248. * 根据模板生成文书
  1249. *
  1250. * @param templatePath 模板路径
  1251. * @param documentName 文书名称
  1252. * @param req 请求参数
  1253. * @return 生成的文书URL
  1254. */
  1255. private String generateDocumentFromTemplate(String templatePath, String documentName,
  1256. com.hotent.project.req.ArchiveProofreadReq req) throws Exception {
  1257. java.io.FileInputStream fis = null;
  1258. XWPFDocument document = null;
  1259. java.io.FileOutputStream fos = null;
  1260. try {
  1261. // 读取模板
  1262. fis = new java.io.FileInputStream(templatePath);
  1263. document = new XWPFDocument(fis);
  1264. // 构建替换映射
  1265. Map<String, String> replacements = buildReplacementMap(req);
  1266. // 根据文书类型进行不同处理
  1267. switch (req.getDocumentType()) {
  1268. case 1:
  1269. // 案卷封面
  1270. generateCoverDocument(document, replacements);
  1271. break;
  1272. case 2:
  1273. // 卷内目录
  1274. generateCatalogDocument(document, replacements, req.getTaskId());
  1275. break;
  1276. case 3:
  1277. // 案卷封底
  1278. generateBackCoverDocument(document, replacements);
  1279. break;
  1280. }
  1281. // 生成输出文件路径
  1282. String fileName = FileUploadUtil.generateFileName(documentName + ".docx");
  1283. String outputPath = FileUploadUtil.generateSavePath(
  1284. EipConfig.getUploadPath(), fileName, "docx");
  1285. // 确保目录存在
  1286. java.io.File outputFile = new java.io.File(outputPath);
  1287. if (!outputFile.getParentFile().exists()) {
  1288. outputFile.getParentFile().mkdirs();
  1289. }
  1290. // 写入文件
  1291. fos = new java.io.FileOutputStream(outputPath);
  1292. document.write(fos);
  1293. // 返回访问URL
  1294. return FileUploadUtil.getPathFileName(outputPath, fileName);
  1295. } finally {
  1296. // 关闭资源
  1297. try {
  1298. if (fos != null) fos.close();
  1299. if (document != null) document.close();
  1300. if (fis != null) fis.close();
  1301. } catch (Exception e) {
  1302. logger.warn("关闭文件流失败:{}", e.getMessage());
  1303. }
  1304. }
  1305. }
  1306. /**
  1307. * 构建替换映射
  1308. * 自动从数据库获取:年份、监审项目名称、被监审单位、监审期间(开始-结束年度)、监审人员、办结时间
  1309. * 需要用户传递:价格主管部门、卷宗号、保管期间周期、立卷人、立卷日期、检查人、检查日期、备注
  1310. */
  1311. private Map<String, String> buildReplacementMap(com.hotent.project.req.ArchiveProofreadReq req) {
  1312. Map<String, String> map = new java.util.HashMap<>();
  1313. // 从数据库获取任务信息
  1314. CostProjectTask task = costProjectTaskManager.getById(req.getTaskId());
  1315. // 从数据库获取立项审批信息(包含监审组人员)
  1316. CostProjectApproval approval = costProjectApprovalManager.getOne(
  1317. new LambdaQueryWrapper<CostProjectApproval>()
  1318. .eq(CostProjectApproval::getProjectId, task.getProjectId())
  1319. );
  1320. // ========== 自动获取的字段 ==========
  1321. Org org = orgManager.getById(approval.getOrgId());
  1322. map.put("{价格主管部门或成本监审机构}", org.getName());
  1323. map.put("{年份}", task.getYear());
  1324. map.put("{监审项目名称}", task.getProjectName());
  1325. map.put("{被监审单位}", task.getAuditedUnitName());
  1326. // 监审期间(解析开始-结束年度,格式如:2024,2025,2023 逗号分隔)
  1327. String startYear = "";
  1328. String endYear = "";
  1329. String auditPeriod = task.getAuditPeriod();
  1330. if (StringUtil.isNotEmpty(auditPeriod)) {
  1331. String[] years = auditPeriod.split(",");
  1332. int minYear = Integer.MAX_VALUE;
  1333. int maxYear = Integer.MIN_VALUE;
  1334. for (String year : years) {
  1335. try {
  1336. int y = Integer.parseInt(year.trim());
  1337. if (y < minYear) minYear = y;
  1338. if (y > maxYear) maxYear = y;
  1339. } catch (NumberFormatException e) {
  1340. }
  1341. }
  1342. if (minYear != Integer.MAX_VALUE) {
  1343. startYear = String.valueOf(minYear);
  1344. endYear = String.valueOf(maxYear);
  1345. }
  1346. }
  1347. map.put("{监审开始年度}", startYear);
  1348. map.put("{监审结束年度}", endYear);
  1349. // 监审组负责人及人员(将ID转换为姓名)
  1350. String auditGroupNames = "";
  1351. if (approval != null && StringUtil.isNotEmpty(approval.getAuditGroup())) {
  1352. String[] userIds = approval.getAuditGroup().split(",");
  1353. StringBuilder names = new StringBuilder();
  1354. for (String userId : userIds) {
  1355. try {
  1356. User user = userManager.get(userId.trim());
  1357. if (user != null) {
  1358. if (names.length() > 0) {
  1359. names.append(",");
  1360. }
  1361. names.append(user.getFullname());
  1362. }
  1363. } catch (Exception e) {
  1364. }
  1365. }
  1366. auditGroupNames = names.toString();
  1367. }
  1368. map.put("{监审组负责人及人员}", auditGroupNames);
  1369. // 监审办结时间(任务状态为办结时,取更新时间,格式:xxxx年xx月)
  1370. String auditEndTime = "";
  1371. if ("400".equals(task.getStatus()) && task.getUpdateTime() != null) {
  1372. auditEndTime = task.getUpdateTime().getYear() + "年" + task.getUpdateTime().getMonthValue() + "月";
  1373. }
  1374. map.put("{监审办结时间}", auditEndTime);
  1375. // ========== 需要用户传递的字段 ==========
  1376. map.put("{卷宗号}", req.getArchiveNo() != null ? req.getArchiveNo() : "");
  1377. map.put("{保管期间周期}", req.getRetentionPeriod() != null ? req.getRetentionPeriod() : "");
  1378. // ========== 卷宗统计信息 ==========
  1379. // 获取14个资料归纳的总页数
  1380. List<CostProjectTaskMaterialSummary> summaryList = costProjectTaskMaterialSummaryManager.listByTaskId(req.getTaskId());
  1381. summaryList = summaryList.stream()
  1382. .filter(s -> s.getMaterialOrderNum() != null && s.getMaterialOrderNum() > 0)
  1383. .collect(Collectors.toList());
  1384. int totalPageCount = 0;
  1385. for (CostProjectTaskMaterialSummary summary : summaryList) {
  1386. if (summary.getTotalPageCount() != null) {
  1387. try {
  1388. totalPageCount += Integer.parseInt(summary.getTotalPageCount());
  1389. } catch (NumberFormatException e) {
  1390. }
  1391. }
  1392. }
  1393. map.put("{卷宗件数}", "1"); // 默认1件
  1394. map.put("{卷宗页数}", String.valueOf(totalPageCount));
  1395. map.put("{第几件}", "1"); // 默认第1件
  1396. map.put("{备注}", req.getRemark());
  1397. return map;
  1398. }
  1399. /**
  1400. * 生成案卷封面
  1401. */
  1402. private void generateCoverDocument(XWPFDocument document, Map<String, String> replacements) {
  1403. SmartTemplateWriter.writeToTemplate(document, replacements);
  1404. }
  1405. /**
  1406. * 生成卷内目录(只填充表格,不替换占位符)
  1407. */
  1408. private void generateCatalogDocument(XWPFDocument document, Map<String, String> replacements, String taskId) {
  1409. // 获取资料归纳列表,生成目录表格
  1410. List<CostProjectTaskMaterialSummary> summaryList = costProjectTaskMaterialSummaryManager.listByTaskId(taskId);
  1411. // 过滤掉封面、目录、封底(负数序号)
  1412. summaryList = summaryList.stream()
  1413. .filter(s -> s.getMaterialOrderNum() != null && s.getMaterialOrderNum() > 0)
  1414. .sorted(Comparator.comparing(CostProjectTaskMaterialSummary::getMaterialOrderNum))
  1415. .collect(Collectors.toList());
  1416. // 构建目录数据
  1417. List<CompleteTemplateProcessor.TableRowData> tableDataList = new java.util.ArrayList<>();
  1418. int currentPage = 1;
  1419. for (CostProjectTaskMaterialSummary summary : summaryList) {
  1420. CompleteTemplateProcessor.TableRowData rowData =
  1421. new CompleteTemplateProcessor.TableRowData();
  1422. rowData.setDocumentName(summary.getMaterialName());
  1423. int pageCount = 0;
  1424. if (summary.getTotalPageCount() != null) {
  1425. try {
  1426. pageCount = Integer.parseInt(summary.getTotalPageCount());
  1427. } catch (NumberFormatException e) {
  1428. pageCount = 0;
  1429. }
  1430. }
  1431. rowData.setPageCount(pageCount);
  1432. // 计算页码范围作为备注
  1433. if (pageCount > 0) {
  1434. rowData.setRemark(currentPage + "-" + (currentPage + pageCount - 1));
  1435. currentPage += pageCount;
  1436. } else {
  1437. rowData.setRemark("—");
  1438. }
  1439. tableDataList.add(rowData);
  1440. }
  1441. // 只填充表格数据,
  1442. if (!tableDataList.isEmpty()) {
  1443. processTableData(document, tableDataList);
  1444. }
  1445. }
  1446. /**
  1447. * 生成案卷封底
  1448. */
  1449. private void generateBackCoverDocument(XWPFDocument document, Map<String, String> replacements) {
  1450. SmartTemplateWriter.writeToTemplate(document, replacements);
  1451. }
  1452. /**
  1453. * 处理目录表格数据
  1454. */
  1455. private void processTableData(XWPFDocument document, List<CompleteTemplateProcessor.TableRowData> dataList) {
  1456. List<XWPFTable> tables = document.getTables();
  1457. if (tables.isEmpty()) {
  1458. logger.warn("未找到表格");
  1459. return;
  1460. }
  1461. XWPFTable table = tables.get(0);
  1462. // 找到模板行(第二行,第一行是表头)
  1463. int templateRowIndex = 0;
  1464. if (table.getRows().size() <= templateRowIndex) {
  1465. logger.warn("表格行数不足");
  1466. return;
  1467. }
  1468. // 获取模板行的单元格数量
  1469. int cellCount = table.getRow(templateRowIndex).getTableCells().size();
  1470. // 删除模板行
  1471. table.removeRow(templateRowIndex);
  1472. // 从模板行位置开始插入数据行
  1473. for (int i = 0; i < dataList.size(); i++) {
  1474. CompleteTemplateProcessor.TableRowData data = dataList.get(i);
  1475. XWPFTableRow newRow = table.insertNewTableRow(templateRowIndex + i);
  1476. // 创建单元格
  1477. for (int j = 0; j < cellCount; j++) {
  1478. newRow.addNewTableCell();
  1479. }
  1480. fillTableRow(newRow, i + 1, data);
  1481. }
  1482. }
  1483. /**
  1484. * 填充表格行数据
  1485. */
  1486. private void fillTableRow(XWPFTableRow row, int sequence,
  1487. CompleteTemplateProcessor.TableRowData data) {
  1488. List<XWPFTableCell> cells = row.getTableCells();
  1489. if (cells.size() >= 4) {
  1490. setCellText(cells.get(0), String.valueOf(sequence));
  1491. setCellText(cells.get(1), data.getDocumentName());
  1492. setCellText(cells.get(2), data.getPageCount() > 0 ? String.valueOf(data.getPageCount()) : "");
  1493. setCellText(cells.get(3), data.getRemark() != null ? data.getRemark() : "");
  1494. }
  1495. }
  1496. /**
  1497. * 设置单元格文本
  1498. */
  1499. private void setCellText(XWPFTableCell cell, String text) {
  1500. // 清除现有内容
  1501. for (int i = cell.getParagraphs().size() - 1; i >= 0; i--) {
  1502. cell.removeParagraph(i);
  1503. }
  1504. // 添加新内容
  1505. XWPFParagraph paragraph = cell.addParagraph();
  1506. XWPFRun run = paragraph.createRun();
  1507. run.setText(text);
  1508. paragraph.setAlignment(ParagraphAlignment.CENTER);
  1509. }
  1510. }