report_archive_controller.js 51 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000
  1. 'use strict';
  2. /**
  3. * Created by Tony on 2021/3/31.
  4. */
  5. const path = require('path');
  6. const uuidV1 = require('uuid').v1;
  7. const fs = require('fs');
  8. const MAX_ARCHIVE = 3;
  9. const tenderMenu = require('../../config/menu').tenderMenu;
  10. const measureType = require('../const/tender').measureType;
  11. const fsUtil = require('../public/js/fsUtil');
  12. const auditConst = require('../const/audit');
  13. const signConst = require('../const/sign');
  14. const shenpiConst = require('../const/shenpi');
  15. const accountGroup = require('../const/account_group').group;
  16. const sendToWormhole = require('stream-wormhole');
  17. const pushOperate = require('../const/spec_3f').pushOperate;
  18. const sourceTypeConst = require('../const/source_type');
  19. const rptArchiveConst = require('../const/rpt_archive');
  20. module.exports = app => {
  21. class ReportArchiveController extends app.BaseController {
  22. /**
  23. * 获取审批界面所需的 原报、审批人数据等
  24. * @param ctx
  25. * @return {Promise<void>}
  26. * @private
  27. */
  28. async _getStageAuditViewData(ctx) {
  29. if (!ctx.stage) return;
  30. await ctx.service.stage.loadStageAuditViewData(ctx.stage);
  31. }
  32. async index(ctx) {
  33. await this._getStageAuditViewData(ctx);
  34. const tender = ctx.tender;
  35. const stage = ctx.stage;
  36. let stage_id = -1;
  37. let stage_order = -1;
  38. let stage_times = -1;
  39. let stage_status = -1;
  40. const treeNodes = await ctx.service.rptTreeNode.getNodesByProjectId([-1, tender.data.project_id]);
  41. const custTreeNodes = await ctx.service.rptTreeNodeCust.getCustFoldersByUserId(this.ctx.session.sessionUser.accountId);
  42. const stageList = await ctx.service.stage.getValidStagesShort(tender.id);
  43. const isAdmin = ctx.session.sessionUser.is_admin;
  44. //
  45. // 。。。
  46. let archiveList = [];
  47. let archiveEncryptionList = [];
  48. // console.log('tender.data.project_id: ' + tender.data.project_id);
  49. if (stage) {
  50. // console.log('ctx.stage.id: ' + ctx.stage.id);
  51. const archives = await ctx.service.rptArchive.getPrjStgArchive(tender.data.project_id, ctx.stage.id);
  52. const archiveEncryptions = await ctx.service.rptArchiveEncryption.getPrjStgArchiveEncryption(tender.data.project_id, ctx.stage.id);
  53. stage_id = stage.id;
  54. stage_order = stage.order;
  55. stage_times = stage.times;
  56. stage_status = stage.status;
  57. if (archives.length > 0) {
  58. archiveList = JSON.parse(archives[0].content);
  59. }
  60. if (archiveEncryptions.length > 0) {
  61. archiveEncryptionList = JSON.parse(archiveEncryptions[0].content);
  62. }
  63. } else if (stageList.length > 0 && stageList[0].status === auditConst.stage.status.checked) {
  64. // console.log('stageList[0].id: ' + stageList[0].id);
  65. let archives = [];
  66. for (let sidx = stageList.length - 1; sidx >= 0; sidx--) {
  67. if (stageList[sidx].status === 3) {
  68. archives = await ctx.service.rptArchive.getPrjStgArchive(tender.data.project_id, stageList[sidx].id);
  69. ctx.stage = stageList[sidx]; // 为了sub menu用
  70. break;
  71. }
  72. }
  73. ctx.stage = ctx.stage ? ctx.stage : stageList[stageList.length - 1];
  74. // const archives = await ctx.service.rptArchive.getPrjStgArchive(tender.data.project_id, stageList[stageList.length - 1].id);
  75. const archiveEncryptions = await ctx.service.rptArchiveEncryption.getPrjStgArchiveEncryption(tender.data.project_id, ctx.stage.id);
  76. // stage_id = stageList[0].id;
  77. // stage_order = stageList[0].order;
  78. // stage_times = stageList[0].times;
  79. // stage_status = stageList[0].status;
  80. if (archives && archives.length > 0) {
  81. archiveList = JSON.parse(archives[0].content);
  82. }
  83. if (archiveEncryptions && archiveEncryptions.length > 0) {
  84. archiveEncryptionList = JSON.parse(archiveEncryptions[0].content);
  85. }
  86. }
  87. let rpt_tpl_items = '{ customize: [], common: [] }';
  88. if (custTreeNodes.length > 0) {
  89. rpt_tpl_items = custTreeNodes[0].rpt_tpl_items;
  90. }
  91. // 获取用户权限
  92. const accountInfo = await this.ctx.service.projectAccount.getDataById(this.ctx.session.sessionUser.accountId);
  93. // 获取所有项目参与者
  94. const accountList = await ctx.service.projectAccount.getAllDataByCondition({
  95. where: { project_id: ctx.session.sessionProject.id, enable: 1 },
  96. columns: ['id', 'name', 'company', 'role', 'enable', 'is_admin', 'account_group', 'mobile'],
  97. });
  98. const newAccountGroup = accountGroup.map((item, idx) => {
  99. const groupList = accountList.filter(item => item.account_group === idx);
  100. return { groupName: item, groupList };
  101. });
  102. const needFileMsg = await this.ctx.service.specMsg.reportNeedMsg(this.ctx.session.sessionProject.id, this.ctx.tender.id);
  103. const renderData = {
  104. tender: tender.data,
  105. rpt_tpl_data: JSON.stringify(treeNodes),
  106. cust_tpl_data: rpt_tpl_items,
  107. project_id: tender.data.project_id,
  108. tender_id: tender.id,
  109. stg_id: stage_id,
  110. stg_order: stage_order,
  111. stg_times: stage_times,
  112. stg_status: stage_status,
  113. stage_list: stageList.length > 0 && stageList[0].status === auditConst.stage.status.checked ? JSON.stringify(stageList) : JSON.stringify([]),
  114. tenderMenu,
  115. measureType,
  116. jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.report.main),
  117. stages: stageList,
  118. auditConst: auditConst.stage,
  119. archiveList,
  120. archiveEncryptionList,
  121. can_netcasign: false,
  122. ossPath: signConst.path.oss,
  123. shenpiConst,
  124. preUrl: '/tender/' + ctx.tender.id + '/measure/stage/' + ctx.params.order,
  125. authMobile: accountInfo.auth_mobile,
  126. accountGroup: newAccountGroup,
  127. accountList,
  128. isAdmin,
  129. needFileMsg,
  130. auditType: auditConst.auditType,
  131. };
  132. if (stage_id === -1) {
  133. await this.layout('report/index_archive.ejs', renderData, 'report/archive_popup.ejs');
  134. } else {
  135. await this.layout('report/index_archive.ejs', renderData, 'report/stage_archive_modal.ejs');
  136. }
  137. }
  138. async getReportArchive(ctx) {
  139. const params = JSON.parse(ctx.request.body.params);
  140. // ctx.body = await this._getReport(ctx, params);
  141. let archives = [];
  142. if ([-400, -500].includes(params.stgId)) {
  143. archives = await ctx.service.rptArchive.getArchiveByBzId(params.prjId, params.stgId, params.bizId);
  144. } else {
  145. archives = await ctx.service.rptArchive.getPrjStgArchive(params.prjId, params.stgId);
  146. }
  147. const archiveEncryptions = await ctx.service.rptArchiveEncryption.getPrjStgArchiveEncryption(params.prjId, params.stgId);
  148. let archiveList = [];
  149. let archiveEncryptionList = [];
  150. if (archives.length > 0) {
  151. archiveList = JSON.parse(archives[0].content);
  152. }
  153. if (archiveEncryptions.length > 0) {
  154. archiveEncryptionList = JSON.parse(archiveEncryptions[0].content);
  155. }
  156. const lastAuditor = await ctx.service.stageAudit.getLastestAuditor(params.stgId, params.stgTimes, auditConst.stage.status.checked);
  157. ctx.body = {
  158. data: archiveList,
  159. encryptionData: archiveEncryptionList,
  160. lastAuditor,
  161. };
  162. }
  163. async _createNodes(ctx, source_type, pid) {
  164. const treeNodes = await ctx.service.rptTreeNode.getNodesBySourceType([pid], source_type); // 这个查定制的
  165. const commonTreeNodes = await ctx.service.rptTreeNode.getNodesByProjectId([-1]); // 这个查通用的
  166. const nodeItems = JSON.parse(commonTreeNodes[0].items);
  167. const commonArrs = [];
  168. nodeItems.forEach(nodeItem => {
  169. const srcType = nodeItem.source_type ? nodeItem.source_type : 1;
  170. if (srcType === source_type) {
  171. commonArrs.push(nodeItem);
  172. }
  173. });
  174. // 加一个dummy的通用报表
  175. const dummyCommonRptNode = { id: 1, name: '通用报表', pid: -1, rpt_type: 0, items: JSON.stringify(commonArrs) };
  176. treeNodes.push(dummyCommonRptNode);
  177. const custCfg = await ctx.service.rptCustomizeCfg.getCustomizeCfgByUserId('Administrator');
  178. return { treeNodes, commonArrs, custCfg };
  179. }
  180. async getReportArchive4bz(ctx) {
  181. const params = JSON.parse(ctx.request.body.params);
  182. const type = params.business_type;
  183. const tenderId = params.tenderId;
  184. const stgId = rptArchiveConst.getStageId(type);
  185. const is_type_change = !params.bzId; // post值里不存在bzId代表切换总变更种类,存在代表切换变更列表值
  186. const postBody = {};
  187. if (is_type_change) {
  188. let changes = [];
  189. const allArchives = await ctx.service.rptArchive.getPrjStgArchiveByBz(ctx.session.sessionProject.id, stgId, tenderId);
  190. switch (type) {
  191. case 'change':
  192. changes = await ctx.service.change.getListByArchives(tenderId, ctx.helper._.map(allArchives, 'business_id'));
  193. break;
  194. case 'change_project':
  195. changes = await ctx.service.changeProject.getListByArchives(tenderId, ctx.helper._.map(allArchives, 'business_id'));
  196. break;
  197. case 'change_apply':
  198. changes = await ctx.service.changeApply.getListByArchives(tenderId, ctx.helper._.map(allArchives, 'business_id'));
  199. break;
  200. case 'change_plan':
  201. changes = await ctx.service.changePlan.getListByArchives(tenderId, ctx.helper._.map(allArchives, 'business_id'));
  202. break;
  203. default: break;
  204. }
  205. postBody.changes = changes;
  206. }
  207. const bzId = !is_type_change ? params.bzId : (postBody.changes.length > 0 ? postBody.changes[0].cid || postBody.changes[0].id : null);
  208. let archiveList = [];
  209. const archives = await ctx.service.rptArchive.getArchiveByBzId(ctx.session.sessionProject.id, stgId, bzId);
  210. const archiveEncryptions = await ctx.service.rptArchiveEncryption.getArchiveEncryptionByBzId(ctx.session.sessionProject.id, stgId, bzId);
  211. if (archives.length > 0) {
  212. archiveList = JSON.parse(archives[0].content);
  213. }
  214. // ctx.body = await this._getReport(ctx, params);
  215. let archiveEncryptionList = [];
  216. if (archives.length > 0) {
  217. archiveList = JSON.parse(archives[0].content);
  218. }
  219. if (archiveEncryptions.length > 0) {
  220. archiveEncryptionList = JSON.parse(archiveEncryptions[0].content);
  221. }
  222. const { treeNodes, custCfg } = await this._createNodes(ctx, sourceTypeConst.sourceType[type], ctx.session.sessionProject.id);
  223. const cust_select_keys = JSON.stringify(['common', 'customize']); // 因其他地方也有可能保存用户报表的显示选择项,因当初设计问题,不好改数据库结构了,但可以调节内部json来满足需求
  224. const rpt_tpl_items = { customize: [], common: [] };
  225. if (treeNodes && treeNodes.length > 0) {
  226. for (let tIdx = treeNodes.length - 1; tIdx >= 0; tIdx--) {
  227. treeNodes[tIdx].items = treeNodes[tIdx].items ? JSON.parse(treeNodes[tIdx].items) : [];
  228. if (treeNodes[tIdx].name === '通用报表') {
  229. const items = treeNodes[tIdx].items;
  230. for (let itemIdx = items.length - 1; itemIdx >= 0; itemIdx--) {
  231. rpt_tpl_items.common.push(items[itemIdx].name);
  232. }
  233. } else {
  234. rpt_tpl_items.customize.push(treeNodes[tIdx].name);
  235. }
  236. }
  237. }
  238. postBody.data = archiveList;
  239. postBody.encryptionData = archiveEncryptionList;
  240. postBody.rpt_tpl_data = treeNodes;
  241. postBody.stgId = stgId;
  242. // postBody.cust_tpl_data = rpt_tpl_items;
  243. // postBody.cust_select_keys = cust_select_keys;
  244. // postBody.cust_cfg = JSON.stringify(custCfg);
  245. ctx.body = postBody;
  246. }
  247. async _updateReportArchiveAdhocInfo(ctx, params) {
  248. const userId = ctx.session.sessionUser.accountId;
  249. const prjId = params.prjId;
  250. const stgId = params.stgId;
  251. const rptId = params.rptId;
  252. const ttlPgs = params.ttlPgs;
  253. const uuid = params.uuid;
  254. const reportName = params.reportName;
  255. const reportAreas = params.signatureAreas;
  256. // 这里要更新zh_rpt_archive表的相关数据
  257. const orgArchiveList = await ctx.service.rptArchive.getPrjStgArchive(prjId, stgId);
  258. if (orgArchiveList.length > 0) {
  259. const contentArr = JSON.parse(orgArchiveList[0].content);
  260. for (const item of contentArr) {
  261. if (parseInt(item.rpt_id) === parseInt(rptId)) {
  262. for (const rptItem of item.items) {
  263. if (rptItem.uuid === uuid) {
  264. rptItem.uid = userId;
  265. rptItem.reportName = reportName;
  266. rptItem.ttl_pages = ttlPgs;
  267. rptItem.signature_area = reportAreas;
  268. break;
  269. }
  270. }
  271. break;
  272. }
  273. }
  274. const updatedRst = await ctx.service.rptArchive.updateArchive(orgArchiveList[0].id, prjId, stgId, contentArr);
  275. } else {
  276. // 正常情况下不可能的分支
  277. }
  278. }
  279. async addReportArchiveEncryption(ctx) {
  280. const params = JSON.parse(ctx.request.body.params);
  281. const prjId = params.prjId;
  282. const stgId = params.stgId;
  283. const tdId = params.tenderId;
  284. const bzId = params.businessId;
  285. const rptId = params.rptId;
  286. const ttlPgs = params.ttlPgs;
  287. const uuid = params.uuid;
  288. const removeUuid = params.removeUuid;
  289. const childUuids = params.childUuids;
  290. const splitArcPages = params.splitArcPages;
  291. const reportName = params.reportName;
  292. const userId = ctx.session.sessionUser.accountId;
  293. const content = params.content;
  294. const orgArchiveList = await ctx.service.rptArchiveEncryption.getArchiveEncryptionByBzId(prjId, stgId, bzId);
  295. if (orgArchiveList.length > 0) {
  296. const contentArr = JSON.parse(orgArchiveList[0].content);
  297. let hasArchive = false;
  298. for (const item of contentArr) {
  299. if (item.rpt_id === rptId) {
  300. // 考虑到报表模板的稳定性,只保留一项来记录位置就足够了,都不考虑用uuid了
  301. if (item.uuid === uuid) {
  302. // 最后打脸了,还真的要考虑不同的uuid,不早说,TNND
  303. item.encryption = content;
  304. item.total_page = ttlPgs;
  305. item.user_id = userId;
  306. item.report_name = reportName;
  307. item.childUuids = childUuids;
  308. item.splitArcPages = splitArcPages;
  309. hasArchive = true;
  310. break;
  311. }
  312. }
  313. }
  314. if (!hasArchive) {
  315. // 表示有新的要加, 有加有减
  316. for (let idx = contentArr.length - 1; idx >= 0; idx--) {
  317. if (contentArr[idx].uuid === removeUuid) {
  318. contentArr.splice(idx, 1);
  319. break;
  320. }
  321. }
  322. contentArr.push({ rpt_id: rptId, uuid, total_page: ttlPgs, encryption: content, user_id: userId, report_name: reportName, childUuids, splitArcPages });
  323. } else {
  324. //
  325. }
  326. const updatedRst = await ctx.service.rptArchiveEncryption.updateArchiveEncryption(orgArchiveList[0].id, prjId, stgId, tdId, bzId, contentArr);
  327. // console.log(updatedRst);
  328. ctx.body = { err: 0, msg: '', data: { addedRst: contentArr } };
  329. } else {
  330. // 需要增加
  331. const archiveArr = [];
  332. archiveArr.push({ rpt_id: rptId, uuid, total_page: ttlPgs, encryption: content, user_id: userId, report_name: reportName, childUuids, splitArcPages });
  333. const addedRst = await ctx.service.rptArchiveEncryption.createArchiveEncryption(prjId, stgId, tdId, bzId, archiveArr);
  334. // console.log(addedRst);
  335. ctx.body = { err: 0, msg: '', data: { addedRst: archiveArr } };
  336. }
  337. }
  338. async _updateArchiveCommon(ctx, prjId, stgId, bzId, tdId, rptId, parentUuidName, childUuids) {
  339. const updateDate = new Date();
  340. const montStr = (updateDate.getMonth() + 1) < 10 ? ('0' + (updateDate.getMonth() + 1)) : (updateDate.getMonth() + 1);
  341. const dateStr = (updateDate.getDate()) < 10 ? ('0' + updateDate.getDate()) : (updateDate.getDate());
  342. let hrStr = '' + updateDate.getHours();
  343. if (hrStr.length === 1) hrStr = '0' + hrStr;
  344. let minStr = '' + updateDate.getMinutes();
  345. if (minStr.length === 1) minStr = '0' + minStr;
  346. let secStr = '' + updateDate.getSeconds();
  347. if (secStr.length === 1) secStr = '0' + secStr;
  348. const dtStr = `${updateDate.getFullYear()}-${montStr}-${dateStr} ${hrStr}:${minStr}:${secStr}`;
  349. let rst = null;
  350. const fileName = parentUuidName + '.PDF';
  351. let removeUuid = ''; // 因这里把增删功能做在一起,有可能要删除一个旧的uuid,需要返回,给加密处理用
  352. const orgArchiveList = await ctx.service.rptArchive.getArchiveByBzId(prjId, stgId, bzId);
  353. if (orgArchiveList.length > 0) {
  354. const contentArr = JSON.parse(orgArchiveList[0].content);
  355. let hasArchive = false;
  356. for (const item of contentArr) {
  357. if (item.rpt_id === rptId) {
  358. hasArchive = true;
  359. let updateRmIdx = -1;
  360. for (let idx = 0; idx < item.items.length; idx++) {
  361. if (parentUuidName === item.items[idx].uuid) {
  362. // 这里的判断是否是update archive逻辑(是否存在相同uuid的item)
  363. updateRmIdx = idx;
  364. break;
  365. }
  366. }
  367. if (updateRmIdx < 0 && item.items.length >= MAX_ARCHIVE) {
  368. updateRmIdx = 0;
  369. for (let idx = 1; idx < item.items.length; idx++) {
  370. if (item.items[updateRmIdx].updateDate_time > item.items[idx].updateDate_time) {
  371. updateRmIdx = idx;
  372. }
  373. }
  374. }
  375. if (updateRmIdx >= 0) {
  376. // 删除oss文件
  377. removeUuid = item.items[updateRmIdx].uuid;
  378. await ctx.app.signPdfOss.delete(`archive/${item.items[updateRmIdx].uuid}.PDF`);
  379. // 以及删除子oss文件
  380. if (item.items[updateRmIdx].childUuids && item.items[updateRmIdx].childUuids.length > 0) {
  381. for (const childUuid of item.items[updateRmIdx].childUuids) {
  382. let uuid = '';
  383. if (typeof childUuid === 'string') {
  384. uuid = childUuid;
  385. } else {
  386. uuid = childUuid.uuid;
  387. }
  388. await ctx.app.signPdfOss.delete(`archive/${uuid}.PDF`);
  389. }
  390. }
  391. item.items.splice(updateRmIdx, 1);
  392. }
  393. const newItem = { uuid: parentUuidName, updateDate_time: dtStr, childUuids };
  394. item.items.push(newItem);
  395. break;
  396. }
  397. }
  398. if (!hasArchive) {
  399. // 表示有新的模板需要添加
  400. contentArr.push({ rpt_id: rptId, items: [{ uuid: parentUuidName, updateDate_time: dtStr, childUuids }] });
  401. }
  402. const updatedRst = await ctx.service.rptArchive.updateArchive(orgArchiveList[0].id, prjId, stgId, tdId, bzId, contentArr);
  403. // console.log(updatedRst);
  404. rst = { err: 0, msg: parentUuidName, data: { uuid: parentUuidName, childUuids, removeUuid, fileName, updateDate, updatedRst: contentArr } };
  405. } else {
  406. // 需要增加
  407. const archiveArr = [];
  408. archiveArr.push({ rpt_id: rptId, items: [{ uuid: parentUuidName, updateDate_time: dtStr, childUuids }] });
  409. const addedRst = await ctx.service.rptArchive.createArchive(prjId, stgId, tdId, bzId, archiveArr);
  410. rst = { err: 0, msg: parentUuidName, data: { uuid: parentUuidName, childUuids, removeUuid, fileName, updateDate, updatedRst: archiveArr } };
  411. }
  412. return rst;
  413. }
  414. async addParentChildrenArchiveReports(ctx) {
  415. // 接收多个子母PDF
  416. let stream;
  417. try {
  418. const prjId = ctx.params.prjId;
  419. const stgId = ctx.params.stgId;
  420. const bzId = ctx.params.bzId;
  421. const tdId = ctx.params.tdId;
  422. const rptId = ctx.params.rptId;
  423. // const childAmt = parseInt(ctx.params.childAmt); // 子分页数量
  424. const childInfo = ctx.params.splitInfo.split(';'); // 这个参数带比较多的信息,包含分割指标的:1. 名称 2. ID 3. 内容
  425. const splitArcPages = JSON.parse(childInfo[0]);
  426. // console.log(splitArcPages);
  427. const childAmt = splitArcPages.length;
  428. const splitFieldObj = JSON.parse(childInfo[1]);
  429. const ttlPages = parseInt(childInfo[2]); // 总页数
  430. // console.log(splitFieldObj);
  431. const parentUuid = uuidV1();
  432. const childUuids = [];
  433. // const newUuidName = uuidV1();
  434. const parts = ctx.multipart({ autoFields: true });
  435. stream = await parts();
  436. let index = 0;
  437. while (stream !== undefined) {
  438. /*
  439. // 测试保存文件
  440. await ctx.helper.saveStreamFile(stream, path.join(this.app.baseDir, 'app/public/upload/', stream.filename));
  441. await sendToWormhole(stream);
  442. //*/
  443. let fileName = parentUuid + '.PDF';
  444. if (index > 0) {
  445. const cUuid = uuidV1();
  446. const startPage = splitArcPages[index - 1];
  447. // const endPage = splitArcPages[index - 1];
  448. let endPage = ttlPages;
  449. if (index < splitArcPages.length) {
  450. endPage = splitArcPages[index] - 1;
  451. }
  452. childUuids.push({ uuid: cUuid, totalPages: (endPage - startPage + 1), startPage, endPage, splitKeyValue: splitFieldObj.splitPageValues[index - 1] });
  453. fileName = cUuid + '.PDF';
  454. }
  455. const oss_result = await ctx.app.signPdfOss.put('archive/' + fileName, stream);
  456. if (!(oss_result && oss_result.url && oss_result.res.status === 200)) {
  457. throw '上传文件失败';
  458. }
  459. await sendToWormhole(stream);
  460. ++index;
  461. if (index <= childAmt) {
  462. stream = await parts();
  463. } else {
  464. stream = undefined;
  465. }
  466. // stream = await parts();
  467. }
  468. const body = await this._updateArchiveCommon(ctx, prjId, stgId, bzId, tdId, rptId, parentUuid, childUuids);
  469. ctx.body = body;
  470. } catch (error) {
  471. ctx.helper.log(error);
  472. // 失败需要消耗掉stream 以防卡死
  473. if (stream) {
  474. await sendToWormhole(stream);
  475. }
  476. ctx.body = this.ajaxErrorBody(error, '上传附件失败,请重试');
  477. }
  478. }
  479. async addReportArchive(ctx) {
  480. try {
  481. const stream = await ctx.getFileStream();
  482. const prjId = ctx.params.prjId;
  483. const stgId = ctx.params.stgId;
  484. const bzId = ctx.params.bzId;
  485. const tdId = ctx.params.tdId;
  486. const rptId = ctx.params.rptId;
  487. const newUuidName = uuidV1();
  488. const fileName = newUuidName + '.PDF';
  489. const oss_result = await ctx.app.signPdfOss.put('archive/' + fileName, stream);
  490. if (!(oss_result && oss_result.url && oss_result.res.status === 200)) {
  491. throw '上传文件失败';
  492. }
  493. const body = await this._updateArchiveCommon(ctx, prjId, stgId, bzId, tdId, rptId, newUuidName, []);
  494. // console.log(body);
  495. ctx.body = body;
  496. } catch (err) {
  497. this.log(err);
  498. ctx.body = { err: 1, msg: err.toString(), data: null };
  499. }
  500. }
  501. async updateReportArchiveEncryption(ctx) {
  502. // 在add方法中已经处理
  503. await this.addReportArchiveEncryption(ctx);
  504. }
  505. async updateReportArchive(ctx) {
  506. try {
  507. const stream = await ctx.getFileStream();
  508. const prjId = ctx.params.prjId;
  509. const stgId = ctx.params.stgId;
  510. const bzId = ctx.params.bzId;
  511. const tdId = ctx.params.tdId;
  512. const rptId = ctx.params.rptId;
  513. const orgUuidName = ctx.params.orgName;
  514. const orgFileName = orgUuidName + '.PDF';
  515. const newUuidName = uuidV1();
  516. const fileName = newUuidName + '.PDF'; // 要用新的uuid!!!
  517. console.log('updating fileName: ' + fileName);
  518. // await ctx.helper.saveStreamFile(stream, path.join(this.app.baseDir, 'app', 'public/archive', fileName));
  519. const oss_result = await ctx.app.signPdfOss.put('archive/' + fileName, stream);
  520. if (!(oss_result && oss_result.url && oss_result.res.status === 200)) {
  521. throw '上传文件失败';
  522. }
  523. // 判断是否存在已签名文档,存在则删除文档并删除签名记录
  524. const pdfMsg = await ctx.curl(signConst.path.oss + '/sign/' + orgUuidName + '.PDF');
  525. if (pdfMsg && pdfMsg.status === 200) {
  526. const oss_reuslt = await ctx.app.signPdfOss.delete('archive/sign/' + orgFileName);
  527. if (oss_reuslt && oss_reuslt.res && oss_reuslt.res.status === 204) {
  528. const delSign_result = await ctx.service.netcasignLog.removeSign(orgUuidName);
  529. } else {
  530. throw '删除已签文档失败';
  531. }
  532. }
  533. const body = await this._updateArchiveCommon(ctx, prjId, stgId, bzId, tdId, rptId, newUuidName, []); // 只管用新的uuid,此方法中会自动删除最旧的那个记录及相关PDF文档
  534. ctx.body = body;
  535. } catch (err) {
  536. this.log(err);
  537. ctx.body = { err: 1, msg: err.toString(), data: null };
  538. }
  539. }
  540. async _removeReportArchiveEncryption(ctx) {
  541. let rst = null;
  542. try {
  543. const prjId = ctx.params.prjId;
  544. const stgId = ctx.params.stgId;
  545. const bzId = ctx.params.bzId;
  546. const rptId = parseInt(ctx.params.rptId);
  547. const uuid = ctx.params.orgName;
  548. const orgArchiveList = await ctx.service.rptArchiveEncryption.getArchiveEncryptionByBzId(prjId, stgId, bzId);
  549. if (orgArchiveList.length > 0) {
  550. const contentArr = JSON.parse(orgArchiveList[0].content);
  551. for (let idx = contentArr.length - 1; idx >= 0; idx--) {
  552. if (contentArr[idx].rpt_id === rptId && contentArr[idx].uuid === uuid) {
  553. contentArr.splice(idx, 1);
  554. break;
  555. }
  556. }
  557. rst = await ctx.service.rptArchiveEncryption.updateArchiveEncryption(orgArchiveList[0].id, prjId, stgId, orgArchiveList[0].tender_id, bzId, contentArr);
  558. }
  559. } catch (err) {
  560. this.log(err);
  561. }
  562. return rst;
  563. }
  564. async removeReportArchiveEncryption(ctx) {
  565. try {
  566. const rst = await this._removeReportArchiveEncryption(ctx);
  567. if (rst) {
  568. ctx.body = { err: 0, msg: '', data: { updatedRst: rst } };
  569. } else {
  570. ctx.body = { err: 0, msg: '', data: { updatedRst: null } };
  571. }
  572. } catch (err) {
  573. this.log(err);
  574. ctx.body = { err: 1, msg: err.toString(), data: null };
  575. }
  576. }
  577. async _removeReportArchive(ctx) {
  578. let rst = null;
  579. try {
  580. const prjId = ctx.params.prjId;
  581. const stgId = ctx.params.stgId;
  582. const rptId = ctx.params.rptId;
  583. const bzId = ctx.params.bzId;
  584. const orgUuidName = ctx.params.orgName;
  585. // const fileName = orgUuidName + '.PDF';
  586. const orgArchiveList = await ctx.service.rptArchive.getArchiveByBzId(prjId, stgId, bzId);
  587. if (orgArchiveList.length > 0) {
  588. const contentArr = JSON.parse(orgArchiveList[0].content);
  589. for (let idx = contentArr.length - 1; idx >= 0; idx--) {
  590. const item = contentArr[idx];
  591. if (item.rpt_id === rptId) {
  592. if (item.items && item.items.length > 0) {
  593. for (const subIdx in item.items) {
  594. if (item.items[subIdx].uuid === orgUuidName) {
  595. if (item.items[subIdx].childUuids && item.items[subIdx].childUuids.length > 0) {
  596. // 如果有子分页,也得删除!
  597. for (const childUuid of item.items[subIdx].childUuids) {
  598. let childUuidName = '';
  599. if (typeof childUuid === 'string') {
  600. childUuidName = childUuid;
  601. } else {
  602. childUuidName = childUuid.uuid;
  603. }
  604. if (!childUuidName.includes('.PDF')) childUuidName = childUuidName + '.PDF';
  605. await ctx.app.signPdfOss.delete(`archive/${childUuidName}`);
  606. }
  607. }
  608. item.items.splice(subIdx, 1);
  609. break;
  610. }
  611. }
  612. if (item.items.length === 0) {
  613. contentArr.splice(idx, 1);
  614. }
  615. }
  616. break;
  617. }
  618. }
  619. // contentArr 为空时,应该移除当前条
  620. rst = await ctx.service.rptArchive.updateArchive(orgArchiveList[0].id, prjId, stgId, orgArchiveList[0].tender_id, bzId, contentArr);
  621. }
  622. } catch (err) {
  623. this.log(err);
  624. }
  625. return rst;
  626. }
  627. async removeReportArchive(ctx) {
  628. try {
  629. const orgUuidName = ctx.params.orgName;
  630. const fileName = orgUuidName + '.PDF';
  631. // console.log(ctx.params);
  632. console.log('removing fileName: ' + fileName);
  633. // const fullName = path.join(this.app.baseDir, 'app', 'public/archive', fileName);
  634. const oss_sign_result = await ctx.app.signPdfOss.delete(`archive/sign/${fileName}`);
  635. if (oss_sign_result && oss_sign_result.res && oss_sign_result.res.status === 204) {
  636. // console.log('删除归档的签名信息成功!');
  637. const oss_result = await ctx.app.signPdfOss.delete(`archive/${fileName}`);
  638. if (!(oss_result && oss_result.res.status === 204)) {
  639. throw '删除归档文件失败';
  640. }
  641. } else {
  642. throw '删除归档签名文件失败';
  643. }
  644. // 还有加密签名信息
  645. const archiveSignRemovedRst = await this._removeReportArchiveEncryption(ctx);
  646. // console.log(archiveSignRemovedRst);
  647. const archiveRemovedRst = await this._removeReportArchive(ctx);
  648. if (archiveRemovedRst) {
  649. ctx.body = { err: 0, msg: orgUuidName, data: { fileName, updatedRst: archiveRemovedRst } };
  650. } else {
  651. ctx.body = { err: 0, msg: orgUuidName, data: { fileName, updatedRst: null } };
  652. }
  653. } catch (err) {
  654. this.log(err);
  655. ctx.body = { err: 1, msg: err.toString(), data: null };
  656. }
  657. }
  658. async addMultiReportArchive(ctx, params) {
  659. // 暂时不支持
  660. }
  661. async getArchivedFileByUUID(ctx) {
  662. // console.log('downloading : ' + ctx.params.uuid);
  663. const uuid = ctx.params.uuid;
  664. const rptName = ctx.params.rptName;
  665. const suffix = '.PDF';
  666. try {
  667. const rptNameURI = encodeURI(rptName);
  668. const filePath = this.app.baseDir + '/app/public/archive/';
  669. // console.log('filePath: ' + filePath);
  670. // await this.ctx.helper.recursiveMkdirSync(this.app.baseDir + '/app/public/download');
  671. ctx.set({
  672. 'Content-Type': 'application/vnd.openxmlformats',
  673. 'Content-Disposition': 'attachment; filename="' + rptNameURI + suffix + "\"; filename*=utf-8''" + rptNameURI + suffix,
  674. });
  675. ctx.body = await fs.readFileSync(filePath + uuid + suffix);
  676. } catch (e) {
  677. console.log(e);
  678. }
  679. }
  680. async pdfShow(ctx) {
  681. // const renderData = {
  682. // can_netcasign: ctx.session.sessionProject.page_show.openNetCaSign === 1,
  683. // };
  684. await ctx.render('report/archive_pdf.ejs');
  685. }
  686. /*
  687. * 网证通电子签名页,(和归档报表页高度相似)
  688. */
  689. async signReport(ctx) {
  690. const tender = ctx.tender;
  691. const stage = ctx.stage;
  692. let stage_id = -1;
  693. let stage_order = -1;
  694. let stage_times = -1;
  695. let stage_status = -1;
  696. const treeNodes = await ctx.service.rptTreeNode.getNodesByProjectId([-1, tender.data.project_id]);
  697. const custTreeNodes = await ctx.service.rptTreeNodeCust.getCustFoldersByUserId(this.ctx.session.sessionUser.accountId);
  698. const stageList = await ctx.service.stage.getValidStagesShort(tender.id);
  699. //
  700. // 。。。
  701. let archiveList = [];
  702. let archiveEncryptionList = [];
  703. // console.log('tender.data.project_id: ' + tender.data.project_id);
  704. if (stage) {
  705. // console.log('ctx.stage.id: ' + ctx.stage.id);
  706. const archives = await ctx.service.rptArchive.getPrjStgArchive(tender.data.project_id, ctx.stage.id);
  707. const archiveEncryptions = await ctx.service.rptArchiveEncryption.getPrjStgArchiveEncryption(tender.data.project_id, ctx.stage.id);
  708. stage_id = stage.id;
  709. stage_order = stage.order;
  710. stage_times = stage.times;
  711. stage_status = stage.status;
  712. if (archives.length > 0) {
  713. archiveList = JSON.parse(archives[0].content);
  714. }
  715. console.log('2:', archiveEncryptions, ctx.stage.id);
  716. if (archiveEncryptions.length > 0) {
  717. archiveEncryptionList = JSON.parse(archiveEncryptions[0].content);
  718. }
  719. } else if (stageList.length > 0 && stageList[0].status === auditConst.stage.status.checked) {
  720. // console.log('stageList[0].id: ' + stageList[0].id);
  721. // const archives = await ctx.service.rptArchive.getPrjStgArchive(tender.data.project_id, stageList[stageList.length - 1].id);
  722. let archives = [];
  723. for (let sidx = stageList.length - 1; sidx >= 0; sidx--) {
  724. if (stageList[sidx].status === 3) {
  725. archives = await ctx.service.rptArchive.getPrjStgArchive(tender.data.project_id, stageList[sidx].id);
  726. ctx.stage = stageList[sidx]; // 为了sub menu用
  727. break;
  728. }
  729. }
  730. ctx.stage = ctx.stage ? ctx.stage : stageList[stageList.length - 1];
  731. const archiveEncryptions = await ctx.service.rptArchiveEncryption.getPrjStgArchiveEncryption(tender.data.project_id, ctx.stage.id);
  732. // stage_id = stageList[0].id;
  733. // stage_order = stageList[0].order;
  734. // stage_times = stageList[0].times;
  735. // stage_status = stageList[0].status;
  736. if (archives && archives.length > 0) {
  737. archiveList = JSON.parse(archives[0].content);
  738. }
  739. if (archiveEncryptions && archiveEncryptions.length > 0) {
  740. archiveEncryptionList = JSON.parse(archiveEncryptions[0].content);
  741. }
  742. }
  743. let rpt_tpl_items = '{ customize: [], common: [] }';
  744. if (custTreeNodes.length > 0) {
  745. rpt_tpl_items = custTreeNodes[0].rpt_tpl_items;
  746. }
  747. // 获取ukey绑定数据
  748. const netcaSignData = await ctx.service.netcasign.getDataByCondition({ uid: ctx.session.sessionUser.accountId });
  749. // 获取已签名数据
  750. const signLogList = await ctx.service.netcasignLog.getLogList(ctx.tender.id);
  751. const renderData = {
  752. tender: tender.data,
  753. rpt_tpl_data: JSON.stringify(treeNodes),
  754. cust_tpl_data: rpt_tpl_items,
  755. project_id: tender.data.project_id,
  756. tender_id: tender.id,
  757. stg_id: stage_id,
  758. stg_order: stage_order,
  759. stg_times: stage_times,
  760. stg_status: stage_status,
  761. stage_list: JSON.stringify(stageList),
  762. tenderMenu,
  763. measureType,
  764. jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.report.main),
  765. stages: stageList,
  766. auditConst: auditConst.stage,
  767. archiveList,
  768. archiveEncryptionList,
  769. netcaSignData,
  770. can_netcasign: ctx.session.sessionProject.page_show.openNetCaSign === 1,
  771. ossPath: signConst.path.oss,
  772. signLogList,
  773. };
  774. await this.layout('report/index_sign.ejs', renderData, 'report/index_sign_modal.ejs');
  775. }
  776. /**
  777. * 网证通电子签名接口
  778. *
  779. * @param {object} ctx - egg全局变量
  780. * @return {void}
  781. */
  782. async signPost(ctx) {
  783. const response = {
  784. err: 0,
  785. msg: '',
  786. };
  787. try {
  788. const data = JSON.parse(ctx.request.body.data);
  789. let signData;
  790. const netcaSignApi = signConst.path.api;
  791. switch (data.type) {
  792. case 'getPdfBase64':
  793. const pdfResult = await ctx.app.signPdfOss.get(data.path);
  794. if (pdfResult.res.status !== 200) {
  795. throw '该文件不存在';
  796. }
  797. response.data = Buffer.from(pdfResult.content, 'binary').toString('base64');
  798. break;
  799. case 'pdfIsExist':
  800. const pdfMsg = await ctx.app.signPdfOss.head('archive/sign/' + data.uuid + '.PDF');
  801. response.data = pdfMsg.res.status === 200;
  802. break;
  803. // 获取摘要值
  804. case 'assemblyDigest':
  805. const postData = {
  806. requestJson: JSON.stringify(data.requestJson),
  807. };
  808. const result = await ctx.helper.sendMoreRequest(netcaSignApi + '/assemblyDigest', postData, 'POST');
  809. response.data = result;
  810. break;
  811. // 生成签名pdf
  812. case 'assemblyPdf':
  813. const postData2 = {
  814. requestJson: JSON.stringify(data.requestJson),
  815. };
  816. const result3 = await this.roundNetcaSign(ctx, postData2);
  817. // const result2 = await ctx.helper.sendMoreRequest(netcaSignApi + '/assemblyPdf', postData2, 'POST');
  818. // 上传到oss
  819. // console.log(result3);
  820. if (result3.code === 0) {
  821. // const result3 = await ctx.helper.sendMoreRequest(netcaSignApi + result2.data);
  822. const oss_result = await ctx.app.signPdfOss.put('archive/sign/' + data.requestJson.fileName + '.PDF', result3.data);
  823. if (oss_result && oss_result.res && oss_result.res.status === 200) {
  824. if (data.end) {
  825. const versionId = oss_result.res.headers['x-oss-version-id'];
  826. // 记录签名和保存
  827. await ctx.service.netcasignLog.add(data.requestJson.fileName, data.role, ctx.session.sessionUser.accountId, versionId);
  828. const signLogList = await ctx.service.netcasignLog.getLogList(ctx.tender.id);
  829. response.data = signLogList;
  830. } else {
  831. // next page
  832. }
  833. } else {
  834. throw '上传文件失败';
  835. }
  836. } else {
  837. throw result3.msg;
  838. }
  839. break;
  840. // 移除签名和已签移除pdf
  841. case 'removeSign':
  842. const oss_reuslt = await ctx.app.signPdfOss.delete('archive/sign/' + data.uuid + '.PDF');
  843. if (oss_reuslt && oss_reuslt.res && oss_reuslt.res.status === 204) {
  844. const delSign_result = await ctx.service.netcasignLog.removeSign(data.uuid);
  845. const signLogList = await ctx.service.netcasignLog.getLogList(ctx.tender.id);
  846. response.data = signLogList;
  847. } else {
  848. throw '删除文件失败';
  849. }
  850. break;
  851. default:throw '参数有误';
  852. }
  853. } catch (error) {
  854. response.err = 1;
  855. response.msg = error.toString();
  856. const data = JSON.parse(ctx.request.body.data);
  857. if (data && data.type === 'pdfIsExist') {
  858. response.err = 0;
  859. response.data = false;
  860. }
  861. }
  862. ctx.body = response;
  863. }
  864. /**
  865. * 网证通电子签名报表上传
  866. *
  867. * @param {object} ctx - egg全局变量
  868. * @return {void}
  869. */
  870. async signFile(ctx) {
  871. const stream = await ctx.getFileStream();
  872. try {
  873. const uuid = stream.fields.uuid;
  874. const role = stream.fields.role;
  875. const oss_result = await ctx.app.signPdfOss.put('archive/sign/' + uuid + '.PDF', stream);
  876. if (!(oss_result && oss_result.url && oss_result.res.status === 200)) {
  877. throw '上传文件失败';
  878. }
  879. const versionId = oss_result.res.headers['x-oss-version-id'];
  880. // 记录签名和保存
  881. await ctx.service.netcasignLog.add(uuid, role, ctx.session.sessionUser.accountId, versionId);
  882. const signLogList = await ctx.service.netcasignLog.getLogList(ctx.tender.id);
  883. ctx.body = { err: 0, msg: '', data: signLogList };
  884. } catch (err) {
  885. // 必须将上传的文件流消费掉,要不然浏览器响应会卡死
  886. await sendToWormhole(stream);
  887. this.log(err);
  888. ctx.body = { err: 1, msg: err.toString(), data: null };
  889. }
  890. }
  891. async roundNetcaSign(ctx, postData2, round = 3) {
  892. let response = {
  893. code: 0,
  894. data: '',
  895. };
  896. // 无法获取到result3,因为result2生成的pdf已损坏,请重复获取result2,直到获取成功或尝试3次失败后报错为止
  897. try {
  898. const netcaSignApi = signConst.path.api;
  899. const result2 = await ctx.helper.sendMoreRequest(netcaSignApi + '/assemblyPdf', postData2, 'POST');
  900. if (result2.code === 0) {
  901. const result3 = await ctx.curl(netcaSignApi + result2.data, {
  902. timeout: 300000, // 超时 5分钟中
  903. });
  904. if (result3) {
  905. response.data = result3.data;
  906. } else {
  907. if (round > 0) {
  908. round = round - 1;
  909. response = await this.roundNetcaSign(ctx, postData2, round);
  910. } else {
  911. throw 'pdf获取失败,网证通接口无法生成pdf';
  912. }
  913. }
  914. }
  915. } catch (error) {
  916. response.code = 1;
  917. response.msg = error;
  918. }
  919. return response;
  920. }
  921. async sendFileMsg(ctx) {
  922. try {
  923. if (this.ctx.session.sessionUser.accountId !== ctx.tender.data.user_id) throw '您无权操作';
  924. const needFileMsg = await this.ctx.service.specMsg.tenderNeedMsg(this.ctx.session.sessionProject.id, this.ctx.tender.id, pushOperate.report.file);
  925. if (!needFileMsg) throw '该标段暂不可进行该操作';
  926. const waitingMsg = await this.ctx.service.specMsg.getDataByCondition({ tid: this.ctx.tender.id, timing: pushOperate.report.file, status: [0, 1] });
  927. if (waitingMsg) throw '上一次归档完成,未执行完毕,请稍后再试';
  928. const data = JSON.parse(ctx.request.body.data);
  929. const stage = await this.ctx.service.stage.getDataById(data.sid);
  930. await this.ctx.service.specMsg.addReportMsg(null, this.ctx.session.sessionProject.id, this.ctx.tender.data, stage, pushOperate.report.file);
  931. ctx.body = { err: 0, msg: '提交成功,稍后将同步至档案系统', data: null };
  932. } catch (err) {
  933. this.ctx.log(err);
  934. this.ctx.ajaxErrorBody(err, '操作失败');
  935. }
  936. }
  937. async sendOtherFileMsg(ctx) {
  938. try {
  939. if (this.ctx.session.sessionUser.accountId !== ctx.tender.data.user_id) throw '您无权操作';
  940. const data = JSON.parse(ctx.request.body.data);
  941. const needFileMsg = await this.ctx.service.specMsg.tenderNeedMsg(this.ctx.session.sessionProject.id, this.ctx.tender.id, data.msgType);
  942. if (!needFileMsg) throw '该标段暂不可进行该操作';
  943. const waitingMsg = await this.ctx.service.specMsg.getDataByCondition({ tid: this.ctx.tender.id, timing: data.msgType, status: [0, 1] });
  944. if (waitingMsg) throw '上一次归档完成,未执行完毕,请稍后再试';
  945. await this.ctx.service.specMsg.addOtherReportMsg(null, this.ctx.session.sessionProject.id, this.ctx.tender.data, data.id, data.msgType);
  946. ctx.body = { err: 0, msg: '提交成功,稍后将同步至档案系统', data: null };
  947. } catch (err) {
  948. this.ctx.log(err);
  949. this.ctx.ajaxErrorBody(err, '操作失败');
  950. }
  951. }
  952. }
  953. return ReportArchiveController;
  954. };