contract_controller.js 70 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251
  1. 'use strict';
  2. const stdDataAddType = {
  3. withParent: 1,
  4. child: 2,
  5. next: 3,
  6. };
  7. const auditConst = require('../const/audit');
  8. const shenpiConst = require('../const/sp_shenpi');
  9. const contractConst = require('../const/contract');
  10. const moment = require('moment');
  11. const sendToWormhole = require('stream-wormhole');
  12. const fs = require('fs');
  13. const path = require('path');
  14. const AiDify = require('../lib/ai_dify');
  15. module.exports = app => {
  16. class ContractController extends app.BaseController {
  17. /**
  18. * 构造函数
  19. *
  20. * @param {Object} ctx - egg全局变量
  21. * @return {void}
  22. */
  23. constructor(ctx) {
  24. super(ctx);
  25. ctx.showProject = true;
  26. // ctx.showTitle = true;
  27. }
  28. /**
  29. * 项目合同列表页
  30. *
  31. * @param {Object} ctx - egg全局页面
  32. * @return {void}
  33. */
  34. async index(ctx) {
  35. try {
  36. if (!ctx.subProject.page_show.openContract) {
  37. throw '该功能已关闭或无法查看';
  38. }
  39. const renderData = {
  40. jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.contract.index),
  41. auditConst,
  42. isTender: false,
  43. };
  44. // renderData.budgetStd = await ctx.service.budgetStd.getDataByProjectId(ctx.session.sessionProject.id);
  45. renderData.projectList = await ctx.service.subProject.getSubProjectByContract(ctx.session.sessionProject.id, ctx.session.sessionUser.accountId, ctx.session.sessionUser.is_admin);
  46. for (const t of renderData.projectList) {
  47. if (!t.is_folder) {
  48. const expensesList = await ctx.service.contract.getListByUsers({ spid: t.id, contract_type: contractConst.type.expenses }, ctx.session.sessionUser);
  49. t.expenses_count = expensesList.length;
  50. t.expenses_total_price = expensesList.reduce((total, item) => ctx.helper.add(total, item.total_price), 0);
  51. t.expenses_yf_price = expensesList.reduce((total, item) => ctx.helper.add(total, item.yf_price), 0);
  52. const incomeList = await ctx.service.contract.getListByUsers({ spid: t.id, contract_type: contractConst.type.income }, ctx.session.sessionUser);
  53. t.income_count = incomeList.length;
  54. t.income_total_price = incomeList.reduce((total, item) => ctx.helper.add(total, item.total_price), 0);
  55. t.income_yf_price = incomeList.reduce((total, item) => ctx.helper.add(total, item.yf_price), 0);
  56. }
  57. }
  58. const accountList = await ctx.service.projectAccount.getAllSubProjectAccount(ctx.subProject);
  59. renderData.accountList = accountList;
  60. const unitList = await ctx.service.constructionUnit.getAllDataByCondition({ where: { pid: ctx.session.sessionProject.id } });
  61. renderData.accountGroup = unitList.map(item => {
  62. const groupList = accountList.filter(item1 => item1.company === item.name);
  63. return { groupName: item.name, groupList };
  64. }).filter(x => { return x.groupList.length > 0; });
  65. // renderData.permissionConst = ctx.service.subProjPermission.PermissionConst;
  66. renderData.categoryData = await this.ctx.service.category.getAllCategory(ctx.subProject);
  67. renderData.companys = await this.ctx.service.constructionUnit.getAllDataByCondition({ where: { pid: ctx.session.sessionProject.id } });
  68. // renderData.templates = await this.ctx.service.filingTemplateList.getAllTemplate(ctx.session.sessionProject.id);
  69. await this.layout('contract/index.ejs', renderData, 'contract/modal.ejs');
  70. } catch (err) {
  71. ctx.log(err);
  72. ctx.session.postError = err.toString();
  73. ctx.redirect(this.menu.menu.dashboard.url);
  74. }
  75. }
  76. /**
  77. * 标段合同列表页
  78. *
  79. * @param {Object} ctx - egg全局页面
  80. * @return {void}
  81. */
  82. async tender(ctx) {
  83. try {
  84. if (!ctx.subProject.page_show.openTenderContract) {
  85. throw '该功能已关闭或无法查看';
  86. }
  87. // 获取用户新建标段权利
  88. const accountInfo = await this.ctx.service.projectAccount.getDataById(ctx.session.sessionUser.accountId);
  89. const userPermission = accountInfo !== undefined && accountInfo.permission !== ''
  90. ? JSON.parse(accountInfo.permission) : null;
  91. let tenderList = await ctx.service.tender.getContractList('', userPermission, ctx.session.sessionUser.is_admin);
  92. const projectList = await ctx.service.subProject.getSubProjectByTender(ctx.session.sessionProject.id, tenderList);
  93. const removeTidList = [];
  94. for (const t of tenderList) {
  95. const spInfo = ctx.helper._.find(projectList, { id: t.spid });
  96. if (!spInfo) {
  97. removeTidList.push(t.id);
  98. continue;
  99. }
  100. spInfo.child_order = spInfo.child_order ? spInfo.child_order : 0;
  101. spInfo.is_folder = 1;
  102. t.tree_pid = spInfo.id;
  103. t.tree_level = spInfo.tree_level + 1;
  104. t.tree_order = spInfo.child_order + 1;
  105. spInfo.child_order++;
  106. const expensesList = await ctx.service.contract.getListByUsers({ tid: t.id, contract_type: contractConst.type.expenses }, ctx.session.sessionUser);
  107. t.expenses_count = expensesList.length;
  108. t.expenses_total_price = expensesList.reduce((total, item) => ctx.helper.add(total, item.total_price), 0);
  109. t.expenses_yf_price = expensesList.reduce((total, item) => ctx.helper.add(total, item.yf_price), 0);
  110. const incomeList = await ctx.service.contract.getListByUsers({ tid: t.id, contract_type: contractConst.type.income }, ctx.session.sessionUser);
  111. t.income_count = incomeList.length;
  112. t.income_total_price = incomeList.reduce((total, item) => ctx.helper.add(total, item.total_price), 0);
  113. t.income_yf_price = incomeList.reduce((total, item) => ctx.helper.add(total, item.yf_price), 0);
  114. }
  115. if (removeTidList.length > 0) {
  116. tenderList = tenderList.filter(item => !removeTidList.includes(item.id));
  117. }
  118. const categoryData = await ctx.service.category.getAllCategory(ctx.subProject);
  119. const renderData = {
  120. jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.contract.tender),
  121. // jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.contract.index),
  122. auditConst,
  123. tenderList,
  124. projectList: projectList.concat(tenderList),
  125. categoryData,
  126. isTender: true,
  127. selfCategoryLevel: accountInfo ? accountInfo.self_category_level : '',
  128. // selfCategoryLevel: '',
  129. pid: ctx.session.sessionProject.id,
  130. uid: ctx.session.sessionUser.accountId,
  131. };
  132. if (ctx.session.sessionUser.is_admin) {
  133. const projectId = ctx.session.sessionProject.id;
  134. // 获取所有项目参与者
  135. const accountList = await ctx.service.projectAccount.getAllSubProjectAccount(ctx.subProject);
  136. const unitList = await ctx.service.constructionUnit.getAllDataByCondition({ where: { pid: projectId } });
  137. const accountGroupList = unitList.map(item => {
  138. const groupList = accountList.filter(item1 => item1.company === item.name);
  139. return { groupName: item.name, groupList };
  140. }).filter(x => { return x.groupList.length > 0; });
  141. renderData.accountList = accountList;
  142. renderData.accountGroup = accountGroupList;
  143. }
  144. await this.layout('contract/tender.ejs', renderData, 'contract/modal.ejs');
  145. // await this.layout('contract/index.ejs', renderData, 'contract/modal.ejs');
  146. } catch (err) {
  147. ctx.log(err);
  148. ctx.session.postError = err.toString();
  149. ctx.redirect(`/sp/${ctx.subProject.id}/dashboard`);
  150. }
  151. }
  152. async auditSave(ctx) {
  153. try {
  154. if (ctx.session.sessionUser.is_admin === 0) throw '没有设置权限';
  155. const responseData = {
  156. err: 0, msg: '', data: null,
  157. };
  158. const data = JSON.parse(ctx.request.body.data);
  159. if (!data.type) {
  160. throw '提交数据错误';
  161. }
  162. const info = ctx.contract;
  163. const options = ctx.contractOptions;
  164. let uids;
  165. let result = false;
  166. let auditList = [];
  167. switch (data.type) {
  168. case 'check':
  169. await ctx.service.contractTree.insertTree(options, info);
  170. // 新建合同信息
  171. break;
  172. case 'add-audit':
  173. // 判断用户是单个还是数组
  174. uids = data.id instanceof Array ? data.id : [data.id];
  175. // 判断该用户的组是否已加入到表中,已加入则提示无需添加
  176. auditList = await ctx.service.contractAudit.getList(options);
  177. const addAidList = ctx.helper._.difference(uids, ctx.helper._.map(auditList, 'uid'));
  178. if (addAidList.length === 0) {
  179. throw '用户已存在成员管理中,无需重复添加';
  180. }
  181. const accountList = await ctx.service.projectAccount.getAllDataByCondition({ where: { id: addAidList } });
  182. await ctx.service.contractAudit.saveAudits(options, accountList);
  183. responseData.data = await ctx.service.contractAudit.getList(options);
  184. break;
  185. case 'del-audit':
  186. uids = data.id instanceof Array ? data.id : [data.id];
  187. const cloneOptions = ctx.helper._.cloneDeep(options);
  188. cloneOptions.uid = uids;
  189. auditList = options.spid ? await ctx.service.subProjPermission.getContractAuditList(options.spid, uids) : await ctx.service.contractAudit.getAllDataByCondition({ where: cloneOptions });
  190. if (auditList.length !== uids.length) {
  191. throw '该用户已不存在成员管理中,移除失败';
  192. }
  193. await ctx.service.contractAudit.delAudit(options, ctx.helper._.map(auditList, 'id'));
  194. responseData.data = await ctx.service.contractAudit.getList(options);
  195. break;
  196. case 'save-permission':
  197. result = await ctx.service.contractAudit.updatePermission(options, data.updateData);
  198. if (!result) {
  199. throw '修改权限失败';
  200. }
  201. break;
  202. case 'list':
  203. responseData.data = await ctx.service.contractAudit.getList(options);
  204. break;
  205. case 'get-contract-type':
  206. const commonJson = ctx.subProject.common_json ? JSON.parse(ctx.subProject.common_json) : null;
  207. responseData.data = commonJson && commonJson[(data.is_tender ? 'tender_' : '') + 'contract_type'] ? commonJson[(data.is_tender ? 'tender_' : '') + 'contract_type'] : [];
  208. break;
  209. case 'set-contract-type':
  210. result = await ctx.service.subProject.saveCommonJsons(ctx.subProject.id, ['contract_type', 'tender_contract_type'], data.contract_type);
  211. break;
  212. case 'get-contract-used':
  213. const commonJson2 = ctx.subProject.common_json ? JSON.parse(ctx.subProject.common_json) : null;
  214. responseData.data = commonJson2 && commonJson2[(data.is_tender ? 'tender_' : '') + 'contract_used'] ? commonJson2[(data.is_tender ? 'tender_' : '') + 'contract_used'] : [];
  215. break;
  216. case 'set-contract-used':
  217. result = await ctx.service.subProject.saveCommonJsons(ctx.subProject.id, ['contract_used', 'tender_contract_used'], data.contract_used);
  218. break;
  219. default: throw '参数有误';
  220. }
  221. ctx.body = responseData;
  222. } catch (err) {
  223. this.log(err);
  224. ctx.body = { err: 1, msg: err.toString(), data: null };
  225. }
  226. }
  227. async panel(ctx) {
  228. try {
  229. const whiteList = ctx.app.config.multipart.whitelist;
  230. const contractOptions = ctx.helper._.cloneDeep(ctx.contractOptions);
  231. contractOptions.contract_type = ctx.contract_type;
  232. const contractTreeAudits = await ctx.service.contractTreeAudit.getAllDataByCondition({ where: contractOptions });
  233. const expensesOptions = ctx.helper._.cloneDeep(ctx.contractOptions);
  234. expensesOptions.contract_type = contractConst.type.expenses;
  235. const expensesList = await ctx.service.contract.getListByUsers(expensesOptions, ctx.session.sessionUser);
  236. const expensesPayList = expensesList.length > 0 ? await ctx.service.contractPay.getAllDataByCondition({ where: { cid: ctx.helper._.map(expensesList, 'id') } }) : [];
  237. const expenses_total_price = expensesList.reduce((total, item) => ctx.helper.add(total, item.total_price), 0);
  238. const expenses_yf_price = expensesList.reduce((total, item) => ctx.helper.add(total, item.yf_price), 0);
  239. const incomeOptions = ctx.helper._.cloneDeep(ctx.contractOptions);
  240. incomeOptions.contract_type = contractConst.type.income;
  241. const incomeList = await ctx.service.contract.getListByUsers(incomeOptions, ctx.session.sessionUser);
  242. const incomePayList = incomeList.length > 0 ? await ctx.service.contractPay.getAllDataByCondition({ where: { cid: ctx.helper._.map(incomeList, 'id') } }) : [];
  243. const income_total_price = incomeList.reduce((total, item) => ctx.helper.add(total, item.total_price), 0);
  244. const income_yf_price = incomeList.reduce((total, item) => ctx.helper.add(total, item.yf_price), 0);
  245. const prices = {
  246. expenses_total_price,
  247. expenses_yf_price,
  248. expenses_rate: ctx.helper.div(expenses_yf_price, expenses_total_price) * 100,
  249. income_total_price,
  250. income_yf_price,
  251. income_rate: ctx.helper.div(income_yf_price, income_total_price) * 100,
  252. };
  253. const renderData = {
  254. contractTreeAudits,
  255. audit_permission: ctx.contract_audit_permission,
  256. preUrl: `/sp/${ctx.subProject.id}` + (ctx.contractOptions.tid ? '/contract/tender' : '/contract'),
  257. jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.contract.panel),
  258. contractConst,
  259. prices,
  260. expensesList,
  261. incomeList,
  262. expensesPayList,
  263. incomePayList,
  264. thisUrl: `/sp/${ctx.subProject.id}` + (ctx.contract_tender ? `/contract/tender/${ctx.contract.id}/detail` : '/contract/detail'),
  265. is_setting: false,
  266. };
  267. await this.layout('contract/panel.ejs', renderData, 'contract/setting_modal.ejs');
  268. } catch (err) {
  269. ctx.log(err);
  270. ctx.session.postError = err.toString();
  271. ctx.redirect(`/sp/${ctx.subProject.id}` + (ctx.contractOptions.spid ? '/contract/detail' : '/contract/tender'));
  272. }
  273. }
  274. async detail(ctx) {
  275. try {
  276. const whiteList = ctx.app.config.multipart.whitelist;
  277. const contractOptions = ctx.helper._.cloneDeep(ctx.contractOptions);
  278. contractOptions.contract_type = ctx.contract_type;
  279. const contractTreeAudits = await ctx.service.contractTreeAudit.getAllDataByCondition({ where: contractOptions });
  280. const [stdBills, stdChapters] = ctx.contract_tender ? await this.ctx.service.valuation.getValuationStdList(
  281. ctx.contract.valuation, ctx.contract.measure_type) : await ctx.service.budgetStd.getStdList(ctx.subProject.std_id, 'yu');
  282. const commonJson = ctx.subProject.common_json ? JSON.parse(ctx.subProject.common_json) : null;
  283. const types = commonJson && commonJson[(ctx.contract_tender ? 'tender_' : '') + 'contract_type'] ? commonJson[(ctx.contract_tender ? 'tender_' : '') + 'contract_type'] : [];
  284. const used = commonJson && commonJson[(ctx.contract_tender ? 'tender_' : '') + 'contract_used'] ? commonJson[(ctx.contract_tender ? 'tender_' : '') + 'contract_used'] : [];
  285. const shenpi_status = ctx.contract_tender ? ctx.subProject.page_show.openContractTenderShenpi : ctx.subProject.page_show.openContractSubProjectShenpi;
  286. const pay_shenpi_status = ctx.contract_tender ? ctx.subProject.page_show.openContractPayTenderShenpi : ctx.subProject.page_show.openContractPaySubProjectShenpi;
  287. const renderData = {
  288. contractTreeAudits,
  289. audit_permission: ctx.contract_audit_permission,
  290. preUrl: `/sp/${ctx.subProject.id}` + (ctx.contractOptions.tid ? '/contract/tender' : '/contract'),
  291. jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.contract.detail),
  292. contract_type: ctx.contract_type,
  293. contractConst,
  294. whiteList,
  295. types,
  296. used,
  297. thisUrl: `/sp/${ctx.subProject.id}` + (ctx.contract_tender ? `/contract/tender/${ctx.contract.id}/detail` : '/contract/detail'),
  298. stdChapters,
  299. is_setting: false,
  300. shenpi_status,
  301. pay_shenpi_status,
  302. auditConst: auditConst.contract,
  303. shenpiConst,
  304. auditType: auditConst.auditType,
  305. };
  306. if (ctx.contract_tender) {
  307. const tenderInfo = await ctx.service.tenderInfo.getTenderInfo(ctx.contract.id);
  308. if (tenderInfo.shenpi.contract === shenpiConst.sp_status.gdspl) {
  309. renderData.spGroupList = await ctx.service.shenpiGroup.getGroupListByChangeType(ctx.contract.id, shenpiConst.sp_type.contract, 'contract');
  310. renderData.paySpGroupList = await ctx.service.shenpiGroup.getGroupListByChangeType(ctx.contract.id, shenpiConst.sp_type.contract, contractConst.typeMap[ctx.contract_type]);
  311. }
  312. renderData.shenpi = tenderInfo.shenpi;
  313. } else {
  314. if (ctx.subProject.shenpi.contract === shenpiConst.sp_status.gdspl) {
  315. // 获取固定审批流列表
  316. renderData.spGroupList = await ctx.service.shenpiGroup.getGroupListByChangeType(ctx.subProject.id, shenpiConst.sp_type.contract, 'contract');
  317. renderData.paySpGroupList = await ctx.service.shenpiGroup.getGroupListByChangeType(ctx.subProject.id, shenpiConst.sp_type.contract, contractConst.typeMap[ctx.contract_type]);
  318. }
  319. renderData.shenpi = ctx.subProject.shenpi;
  320. }
  321. const accountList = await ctx.service.projectAccount.getAllSubProjectAccount(ctx.subProject);
  322. renderData.accountList = accountList;
  323. const unitList = await ctx.service.constructionUnit.getAllDataByCondition({ where: { pid: ctx.session.sessionProject.id } });
  324. renderData.accountGroup = unitList.map(item => {
  325. const groupList = accountList.filter(item1 => item1.company === item.name);
  326. return { groupName: item.name, groupList };
  327. }).filter(x => { return x.groupList.length > 0; });
  328. const contractAttributeSet = commonJson && commonJson.contract_attribute ? commonJson.contract_attribute : contractConst.attributeSet;
  329. const attributeSet = ctx.service.contractColSet.analysisColSetWithDefine(contractConst.attributeSet, contractAttributeSet, contractConst.defaultAttributeSet);
  330. renderData.attributeSet = ctx.helper._.filter(attributeSet, { show: 1 });
  331. const detailColumnSet = contractConst.detailColumnSet.filter(item => {
  332. return !item.contractTypes || item.contractTypes.indexOf(ctx.contract_type) >= 0;
  333. }).map(item => {
  334. const result = Object.assign({ source: 'detail' }, item);
  335. if (item.field === 'pay_price') {
  336. result.name = ctx.contract_type === contractConst.type.income ? '累计回款(P)' : '累计付款(P)';
  337. } else if (item.field === 'df_price') {
  338. result.name = ctx.contract_type === contractConst.type.income ? '待回款' : '待支付';
  339. }
  340. return result;
  341. });
  342. const attributeColumnSet = renderData.attributeSet.map(item => {
  343. return Object.assign({}, item, {
  344. name: item.alias || item.name,
  345. source: 'attribute',
  346. fixed: ['alias'],
  347. removable: true,
  348. });
  349. });
  350. const contractColumnDefine = contractConst.colSet[ctx.contract_type]
  351. .concat(detailColumnSet, attributeColumnSet);
  352. const contractColSet = await ctx.service.contractColSet.getContractColSet(ctx.subProject.id, ctx.contractOptions.tid, ctx.contract_type);
  353. renderData.colSet = ctx.service.contractColSet.analysisColSetWithDefine(
  354. contractColumnDefine,
  355. contractColSet.info,
  356. contractConst.defaultColSet[ctx.contract_type]
  357. );
  358. renderData.contractColumnOptions = {
  359. detail: contractConst.colSet[ctx.contract_type]
  360. .filter(item => { return item.removable; })
  361. .map(item => { return Object.assign({ source: 'detail' }, item); })
  362. .concat(detailColumnSet),
  363. attribute: attributeColumnSet,
  364. };
  365. if (ctx.contractOptions.tid) {
  366. const tenders = await ctx.service.tender.getAllDataByCondition({
  367. where: { spid: ctx.subProject.id },
  368. columns: ['id', 'name'],
  369. });
  370. renderData.contractColCopyScope = 'tender';
  371. renderData.contractColCopyTargets = [{
  372. id: 'sub-project-root', name: ctx.subProject.name, tree_level: 1, is_folder: true,
  373. }].concat(tenders.filter(item => {
  374. return Number(item.id) !== Number(ctx.contractOptions.tid);
  375. }).map(item => {
  376. return { id: item.id, name: item.name, tree_level: 2, is_folder: false };
  377. }));
  378. } else {
  379. const subProjects = await ctx.service.subProject.getSubProjectTreeNodes(
  380. ctx.session.sessionProject.id,
  381. ctx.session.sessionUser.accountId,
  382. true
  383. );
  384. renderData.contractColCopyScope = 'project';
  385. renderData.contractColCopyTargets = subProjects.map(item => {
  386. return Object.assign({}, item, {
  387. current: String(item.id) === String(ctx.subProject.id),
  388. });
  389. });
  390. }
  391. await this.layout('contract/detail.ejs', renderData, 'contract/detail_modal.ejs');
  392. } catch (err) {
  393. ctx.log(err);
  394. ctx.session.postError = err.toString();
  395. ctx.redirect(`/sp/${ctx.subProject.id}` + (ctx.contractOptions.spid ? '/contract/detail' : '/contract/tender'));
  396. }
  397. }
  398. /**
  399. * 保存列设置
  400. * @param ctx
  401. * @return {Promise<void>}
  402. */
  403. async colSet(ctx) {
  404. try {
  405. if (!ctx.session.sessionUser.is_admin) throw '无权进行列设置';
  406. const colType = ctx.request.body.col_type;
  407. const colSet = JSON.parse(ctx.request.body.col_set);
  408. const contractType = ctx.request.body.type;
  409. if ([contractConst.type.expenses, contractConst.type.income].indexOf(contractType) < 0) throw '合同类型错误';
  410. await ctx.service.contractColSet.setContractColSet(ctx.subProject.id, ctx.contractOptions.tid, contractType, colType, colSet);
  411. ctx.redirect(ctx.request.header.referer);
  412. } catch (err) {
  413. ctx.log(err);
  414. ctx.redirect(ctx.request.header.referer);
  415. }
  416. }
  417. async copyColSet(ctx) {
  418. try {
  419. if (!ctx.session.sessionUser.is_admin) throw '无权进行列设置';
  420. const data = JSON.parse(ctx.request.body.data);
  421. const contractType = Number(data.type);
  422. if ([contractConst.type.expenses, contractConst.type.income].indexOf(contractType) < 0) throw '合同类型错误';
  423. if (!Array.isArray(data.col_set) || !Array.isArray(data.target_ids)) throw '列设置参数错误';
  424. const targetIds = Array.from(new Set(data.target_ids.map(String)));
  425. if (!targetIds.length) throw '请选择需要设置的目标';
  426. let targets = [];
  427. if (ctx.contractOptions.tid) {
  428. const tenders = await ctx.service.tender.getAllDataByCondition({
  429. where: { spid: ctx.subProject.id },
  430. columns: ['id'],
  431. });
  432. const validIds = new Set(tenders.map(item => String(item.id)));
  433. targets = targetIds.filter(id => validIds.has(id) && id !== String(ctx.contractOptions.tid))
  434. .map(id => { return { spid: ctx.subProject.id, tid: Number(id) }; });
  435. } else {
  436. const subProjects = await ctx.service.subProject.getSubProject(
  437. ctx.session.sessionProject.id,
  438. ctx.session.sessionUser.accountId,
  439. true
  440. );
  441. const validIds = new Set(subProjects.filter(item => !item.is_folder).map(item => String(item.id)));
  442. targets = targetIds.filter(id => validIds.has(id) && id !== String(ctx.subProject.id))
  443. .map(id => { return { spid: id, tid: null }; });
  444. }
  445. if (targets.length !== targetIds.length) throw '选择的目标不存在或不属于当前范围';
  446. await ctx.service.contractColSet.copyContractColSet(targets, contractType, 'info', data.col_set);
  447. ctx.body = { err: 0, msg: '列设置复制成功', data: { count: targets.length } };
  448. } catch (err) {
  449. ctx.log(err);
  450. ctx.body = this.ajaxErrorBody(err, '复制列设置失败');
  451. }
  452. }
  453. async loadDetail(ctx) {
  454. try {
  455. const responseData = {
  456. err: 0, msg: '', data: {},
  457. };
  458. const contractOptions = ctx.helper._.cloneDeep(ctx.contractOptions);
  459. contractOptions.contract_type = ctx.contract_type;
  460. // const data = JSON.parse(ctx.request.body.data);
  461. // ctx.contractOptions.contract_type = ctx.contract_type;
  462. responseData.data.contractTree = await ctx.service.contractTree.getAllDataByCondition({ where: contractOptions });
  463. // 获取权限并展示对应合同
  464. responseData.data.contractList = await ctx.service.contract.getListByUsers(contractOptions, ctx.session.sessionUser);
  465. // responseData.data.contractList = await ctx.service.contract.getAllDataByCondition({ where: contractOptions });
  466. // const userOptions = ctx.helper._.cloneDeep(ctx.contractOptions);
  467. // // userOptions.uid = ctx.session.sessionUser.id;
  468. // const permissionEdit = ctx.session.sessionUser.is_admin ? true : await ctx.service.contractAudit.getUserPermissionEdit(userOptions);
  469. // responseData.data.auditContractTreePermission = permissionEdit ? [] : await ctx.service.contractTreeAudit.getAllDataByCondition({ where: contractOptions });
  470. // responseData.data.contractAttList = await ctx.service.contractAtt.getAllDataByCondition({ where: ctx.contractOptions });
  471. if (ctx.session.sessionUser.is_admin) {
  472. const contractAudits = await ctx.service.contractAudit.getList(ctx.contractOptions);
  473. const unitList = await ctx.service.constructionUnit.getAllDataByCondition({ where: { pid: ctx.session.sessionProject.id } });
  474. const accountGroup = unitList.map(item => {
  475. const groupList = contractAudits.filter(item1 => item1.company === item.name);
  476. return { groupName: item.name, groupList };
  477. }).filter(x => { return x.groupList.length > 0; });
  478. responseData.data.contractAudits = contractAudits;
  479. responseData.data.accountGroup = ctx.helper._.filter(accountGroup, function(item) {
  480. return item.groupList.length > 0;
  481. });
  482. }
  483. ctx.body = responseData;
  484. } catch (err) {
  485. this.log(err);
  486. ctx.body = { err: 1, msg: err.toString(), data: null };
  487. }
  488. }
  489. /**
  490. * 审批及上报和审批人员增删改,切换审批组等
  491. * @param ctx
  492. * @return {Promise<void>}
  493. */
  494. async checkAudit(ctx) {
  495. try {
  496. const data = JSON.parse(ctx.request.body.data);
  497. if (!data.postType || !data.id || !data.type) throw '数据错误';
  498. const responseData = {
  499. err: 0, msg: '', data: {},
  500. };
  501. const contract = await this._getContractByType(ctx, data);
  502. if (!contract) {
  503. throw '未找到合同相关数据';
  504. }
  505. let tip = '';
  506. // 判断功能是否已关闭
  507. const shenpi_status = ctx.contract_tender ? ctx.subProject.page_show.openContractTenderShenpi : ctx.subProject.page_show.openContractSubProjectShenpi;
  508. const pay_shenpi_status = ctx.contract_tender ? ctx.subProject.page_show.openContractPayTenderShenpi : ctx.subProject.page_show.openContractPaySubProjectShenpi;
  509. switch (data.type) {
  510. case 'contract':
  511. if (!shenpi_status) {
  512. throw '合同审批功能已关闭, 无法进行审批操作, 请刷新页面';
  513. }
  514. break;
  515. case 'pay':
  516. if (!pay_shenpi_status) {
  517. throw '合同' + tip + '审批功能已关闭, 无法进行审批操作, 请刷新页面';
  518. }
  519. tip = contractConst.typeName[contract.contract_type];
  520. break;
  521. case 'supplement':
  522. if (!shenpi_status) {
  523. throw '合同审批功能已关闭, 无法进行审批操作, 请刷新页面';
  524. }
  525. break;
  526. default: throw '数据错误';
  527. }
  528. let shenpi = '';
  529. if (ctx.contract_tender) {
  530. const tenderInfo = await ctx.service.tenderInfo.getTenderInfo(ctx.contract.id);
  531. shenpi = tenderInfo ? tenderInfo.shenpi : '';
  532. } else {
  533. shenpi = ctx.subProject.shenpi;
  534. }
  535. await ctx.service.contractSpAudit.loadUser(contract, data.type);
  536. await ctx.service.contractSpAudit.loadAuditViewData(contract, data.type);
  537. switch (data.postType) {
  538. case 'start':
  539. if (contract.status !== auditConst.contract.status.uncheck && contract.status !== auditConst.contract.status.checkNo) {
  540. throw '该合同' + tip + '当前无法上报';
  541. }
  542. await ctx.service.contractSpAudit.start(ctx.contractOptions, shenpi, contract, data.type, contract.times);
  543. break;
  544. case 'check':
  545. if (contract.status !== auditConst.contract.status.checking && contract.status !== auditConst.contract.status.checkNoPre) {
  546. throw '当前合同' + tip + '数据有误';
  547. }
  548. if (contract.curAuditorIds.length === 0 || contract.curAuditorIds.indexOf(ctx.session.sessionUser.accountId) === -1) {
  549. throw '您无权进行该操作';
  550. }
  551. const checkData = {
  552. checkType: parseInt(data.checkType),
  553. opinion: data.opinion,
  554. };
  555. if (!checkData.checkType || isNaN(checkData.checkType)) {
  556. throw '提交数据错误';
  557. }
  558. await ctx.service.contractSpAudit.check(contract, data.type, checkData);
  559. break;
  560. case 'add-audit':
  561. if (shenpi.contract === shenpiConst.sp_status.gdspl) {
  562. throw '固定审批流程不允许添加人员,请刷新页面';
  563. }
  564. const add_id = this.app._.toInteger(data.auditorId);
  565. if (isNaN(add_id) || add_id <= 0) {
  566. throw '参数错误';
  567. }
  568. // 检查权限等
  569. if (!(contract.uid === ctx.session.sessionUser.accountId || ctx.contract_audit_permission.permission_edit_contract)) {
  570. throw '您无权添加审核人';
  571. }
  572. if (contract.status !== auditConst.contract.status.uncheck && contract.status !== auditConst.contract.status.checkNo) {
  573. throw '当前不允许添加审核人';
  574. }
  575. const auditorList = await ctx.service.contractSpAudit.getAuditors(contract, data.type, contract.times, 'asc', true);
  576. // 检查审核人是否已存在
  577. const exist = this.app._.find(auditorList, { aid: add_id });
  578. if (exist) {
  579. throw '该审核人已存在,请勿重复添加';
  580. }
  581. const shenpiCondition = ctx.helper._.assign({ sp_type: shenpiConst.sp_type.contract, sp_status: shenpiConst.sp_status.gdzs }, ctx.contractOptions);
  582. const shenpiInfo = await ctx.service.shenpiAudit.getDataByCondition(shenpiCondition);
  583. const is_gdzs = shenpiInfo && shenpi.contract === shenpiConst.sp_status.gdzs ? 1 : 0;
  584. const add_result = await ctx.service.contractSpAudit.addAuditor(ctx.contractOptions, contract, data.type, add_id, contract.times, is_gdzs);
  585. if (!add_result) {
  586. throw '添加审核人失败';
  587. }
  588. break;
  589. case 'del-audit':
  590. if (shenpi.contract === shenpiConst.sp_status.gdspl) {
  591. throw '固定审批流程不允许删除人员,请刷新页面';
  592. }
  593. const del_id = data.auditorId instanceof Number ? data.auditorId : this.app._.toNumber(data.auditorId);
  594. if (isNaN(del_id) || del_id <= 0) {
  595. throw '参数错误';
  596. }
  597. // 检查权限等
  598. if (!(contract.uid === ctx.session.sessionUser.accountId || ctx.contract_audit_permission.permission_edit_contract)) {
  599. throw '您无权移除审核人';
  600. }
  601. if (contract.status !== auditConst.contract.status.uncheck && contract.status !== auditConst.contract.status.checkNo) {
  602. throw '当前不允许移除审核人';
  603. }
  604. const del_result = await ctx.service.contractSpAudit.deleteAuditor(contract, data.type, del_id, contract.times);
  605. if (!del_result) {
  606. throw '移除审核人失败';
  607. }
  608. break;
  609. case 'change-sp':
  610. if (shenpi.contract !== shenpiConst.sp_status.gdspl) {
  611. throw '非固定审批流程不允许切换审批组,请刷新页面';
  612. }
  613. if (contract.status !== auditConst.contract.status.uncheck && contract.status !== auditConst.contract.status.checkNo) {
  614. throw '当前状态不允许切换审批组';
  615. }
  616. const chagne_result = await ctx.service.contractSpAudit.changeSpGroup(contract, data.type, data.sp_group);
  617. if (!chagne_result) {
  618. throw '切换审批组失败';
  619. }
  620. break;
  621. default:
  622. throw '参数错误';
  623. }
  624. const newContract = await this._getContractByType(ctx, data);
  625. await ctx.service.contractSpAudit.loadUser(newContract, data.type);
  626. await ctx.service.contractSpAudit.loadAuditViewData(newContract, data.type);
  627. responseData.data = newContract;
  628. ctx.body = responseData;
  629. } catch (err) {
  630. console.log(err);
  631. this.log(err);
  632. ctx.body = { err: 1, msg: err.toString(), data: null };
  633. }
  634. }
  635. async _getContractByType(ctx, data) {
  636. let contract = null;
  637. switch (data.type) {
  638. case 'contract':
  639. contract = await ctx.service.contract.getDataById(data.id);
  640. break;
  641. case 'pay':
  642. contract = await ctx.service.contractPay.getDataById(data.id);
  643. break;
  644. case 'supplement':
  645. contract = await ctx.service.contractSupplement.getDataById(data.id);
  646. break;
  647. default: throw '数据错误';
  648. }
  649. return contract;
  650. }
  651. async updateBills(ctx) {
  652. try {
  653. const data = JSON.parse(ctx.request.body.data);
  654. if (!data.postType || !data.postData) throw '数据错误';
  655. const responseData = { err: 0, msg: '', data: {} };
  656. const options = ctx.helper._.cloneDeep(ctx.contractOptions);
  657. options.contract_type = ctx.contract_type;
  658. switch (data.postType) {
  659. case 'add':
  660. case 'add-child':
  661. case 'delete':
  662. case 'up-move':
  663. case 'down-move':
  664. case 'up-level':
  665. case 'down-level':
  666. responseData.data = await this._billsBase(ctx, data.postType, data.postData, options);
  667. break;
  668. case 'update':
  669. responseData.data = await ctx.service.contractTree.updateCalc(options, data.postData, data.updateAll || false);
  670. break;
  671. case 'update-contract':
  672. responseData.data = await ctx.service.contract.updateCalc(options, data.postData);
  673. break;
  674. case 'paste-block':
  675. responseData.data = await this._pasteBlock(ctx, data.postData, options);
  676. break;
  677. case 'add-std':
  678. responseData.data = await this._addStd(ctx, data.postData, options);
  679. break;
  680. case 'add-contract':
  681. responseData.data = await ctx.service.contract.add(options, data.postData.select, data.postData.contract);
  682. break;
  683. case 'add-tree-audit':
  684. responseData.data = await ctx.service.contractTreeAudit.add(options, data.postData.select, data.postData.auditId);
  685. break;
  686. case 'del-tree-audit':
  687. responseData.data = await ctx.service.contractTreeAudit.dels(options, data.postData.select, data.postData.auditIds);
  688. break;
  689. case 'get-contract':
  690. const contract = await ctx.service.contract.getDataById(data.postData);
  691. const shenpi_status = ctx.contract_tender ? ctx.subProject.page_show.openContractTenderShenpi : ctx.subProject.page_show.openContractSubProjectShenpi;
  692. if (shenpi_status) {
  693. await ctx.service.contractSpAudit.loadUser(contract);
  694. await ctx.service.contractSpAudit.loadAuditViewData(contract);
  695. await ctx.service.contractSpAudit.checkShenpi(contract);
  696. }
  697. responseData.data.contract = contract;
  698. responseData.data.pays = await ctx.service.contractPay.getPays(options, data.postData);
  699. responseData.data.files = await ctx.service.contractAtt.getAtt(data.postData);
  700. responseData.data.supplements = await ctx.service.contractSupplement.getSupplements(options, data.postData);
  701. break;
  702. case 'add-contract-pay':
  703. responseData.data = await ctx.service.contractPay.add(options, data.postData.select, data.postData.pay);
  704. break;
  705. case 'save-contract-pay':
  706. responseData.data = await ctx.service.contractPay.save(options, data.postData.select, data.postData.pay);
  707. break;
  708. case 'del-contract-pay':
  709. responseData.data = await ctx.service.contractPay.del(options, data.postData.select, data.postData.pay);
  710. break;
  711. case 'add-contract-supplement':
  712. responseData.data = await ctx.service.contractSupplement.add(options, data.postData.select, data.postData.supplement);
  713. break;
  714. case 'save-contract-supplement':
  715. responseData.data = await ctx.service.contractSupplement.save(options, data.postData.select, data.postData.supplement);
  716. break;
  717. case 'del-contract-supplement':
  718. responseData.data = await ctx.service.contractSupplement.del(options, data.postData.select, data.postData.supplement);
  719. break;
  720. case 'get-tenders':
  721. // 获取用户新建标段权利
  722. const accountInfo = await ctx.service.projectAccount.getDataById(ctx.session.sessionUser.accountId);
  723. const userPermission = accountInfo !== undefined && accountInfo.permission !== ''
  724. ? JSON.parse(accountInfo.permission) : null;
  725. const tenderList = await ctx.service.tender.getBuildList('', userPermission, ctx.session.sessionUser.is_admin);
  726. responseData.data.categoryData = await this.ctx.service.category.getAllCategory(ctx.subProject);
  727. responseData.data.tenders = tenderList;
  728. break;
  729. case 'get-tender-pays':
  730. responseData.data.tender = await ctx.service.tender.getDataById(data.postData.tid);
  731. if (!responseData.data.tender) {
  732. throw '关联标段不存在';
  733. }
  734. // 判断项目开没开启独立支付合同
  735. if (ctx.subProject.page_show.phasePay) {
  736. // 直接读phasePay表数据并展示
  737. responseData.data.stages = await ctx.service.phasePay.getDatasForContract(data.postData.tid);
  738. } else {
  739. // 读取期支付数据
  740. responseData.data.stages = await ctx.service.stagePay.getDatasForContract(data.postData.tid);
  741. }
  742. break;
  743. case 'add-tender-pays':
  744. responseData.data = await ctx.service.contractPay.addTenderPays(
  745. options,
  746. data.postData.select,
  747. data.postData.pays
  748. );
  749. break;
  750. default:
  751. throw '未知操作';
  752. }
  753. ctx.body = responseData;
  754. } catch (err) {
  755. console.log(err);
  756. this.log(err);
  757. ctx.body = this.ajaxErrorBody(err, '数据错误');
  758. }
  759. }
  760. async _billsBase(ctx, type, data, options) {
  761. if (isNaN(data.id) || data.id <= 0) throw '数据错误';
  762. if (type !== 'add') {
  763. if (isNaN(data.count) || data.count <= 0) data.count = 1;
  764. }
  765. switch (type) {
  766. case 'add':
  767. return await ctx.service.contractTree.addNodeBatch(options, data.id, data.count);
  768. case 'add-child':
  769. return await ctx.service.contractTree.addChildNode(options, data.id, data.count);
  770. case 'delete':
  771. return await ctx.service.contractTree.delete(options, data.id, data.count);
  772. case 'up-move':
  773. return await ctx.service.contractTree.upMoveNode(options, data.id, data.count);
  774. case 'down-move':
  775. return await ctx.service.contractTree.downMoveNode(options, data.id, data.count);
  776. case 'up-level':
  777. return await ctx.service.contractTree.upLevelNode(options, data.id, data.count);
  778. case 'down-level':
  779. return await ctx.service.contractTree.downLevelNode(options, data.id, data.count);
  780. default:
  781. throw '未知操作';
  782. }
  783. }
  784. /**
  785. * 复制粘贴整块
  786. *
  787. * @param ctx
  788. * @return {Promise<void>}
  789. */
  790. async _pasteBlock(ctx, data, options) {
  791. if ((isNaN(data.id) || data.id <= 0) ||
  792. (!data.block || data.block.length <= 0)) throw '参数错误';
  793. return await ctx.service.contractTree.pasteBlockData(options, data.id, data.block);
  794. }
  795. /**
  796. * 从标准项目表添加数据
  797. * @param ctx
  798. * @return {Promise<void>}
  799. */
  800. async _addStd(ctx, data, options) {
  801. if ((isNaN(data.id) || data.id <= 0) || !data.stdType || !data.stdNode) throw '参数错误';
  802. // todo 校验项目是否使用该库的权限
  803. if (!(ctx.session.sessionUser.is_admin || ctx.contract_audit_permission.permission_edit)) {
  804. throw '没有操作权限';
  805. }
  806. let stdLib,
  807. addType;
  808. switch (data.stdType) {
  809. case 'xmj':
  810. stdLib = ctx.service.stdXmj;
  811. addType = stdDataAddType.withParent;
  812. break;
  813. default:
  814. throw '未知标准库';
  815. }
  816. const stdData = await stdLib.getDataByDataId(data.stdLibId, data.stdNode);
  817. switch (addType) {
  818. // case stdDataAddType.child:
  819. // return await ctx.service.contractTree.addStdNodeAsChild(options, data.id, stdData);
  820. // case stdDataAddType.next:
  821. // return await ctx.service.contractTree.addStdNode(options, data.id, stdData);
  822. case stdDataAddType.withParent:
  823. return await ctx.service.contractTree.addStdNodeWithParent(options, data.id, stdData, stdLib);
  824. default:
  825. throw '未知添加方式';
  826. }
  827. }
  828. /**
  829. * 上传附件
  830. * @param {*} ctx 上下文
  831. */
  832. async uploadFile(ctx) {
  833. let stream;
  834. try {
  835. const responseData = { err: 0, msg: '', data: {} };
  836. const cid = ctx.params.cid;
  837. if (!cid) throw '参数有误';
  838. const cpid = parseInt(ctx.params.cpid);
  839. const csid = parseInt(ctx.params.csid);
  840. const parts = this.ctx.multipart({
  841. autoFields: true,
  842. });
  843. const files = [];
  844. const create_time = Date.parse(new Date()) / 1000;
  845. let idx = 0;
  846. while ((stream = await parts()) !== undefined) {
  847. if (!stream.filename) {
  848. // 如果没有传入直接返回
  849. return;
  850. }
  851. const fileInfo = path.parse(stream.filename);
  852. const headpath = ctx.contractOptions.spid ? `sp/contract/${ctx.contractOptions.spid}` : `app/public/upload/${ctx.contractOptions.tid}`;
  853. const filepath = `${headpath}/contract/${cid}${cpid ? '/pay/' + cpid : (csid ? '/supplement/' + csid : '')}/fujian_${create_time + idx.toString() + fileInfo.ext}`;
  854. // await ctx.helper.saveStreamFile(stream, path.resolve(this.app.baseDir, 'app', filepath));
  855. await ctx.app.fujianOss.put(ctx.app.config.fujianOssFolder + filepath, stream);
  856. files.push({ filepath, name: stream.filename, ext: fileInfo.ext });
  857. ++idx;
  858. stream && (await sendToWormhole(stream));
  859. }
  860. const in_time = new Date();
  861. const payload = files.map(file => {
  862. let idx;
  863. if (Array.isArray(parts.field.name)) {
  864. idx = parts.field.name.findIndex(name => name === file.name);
  865. } else {
  866. idx = 'isString';
  867. }
  868. const newFile = {
  869. spid: ctx.contractOptions.spid || null,
  870. tid: ctx.contractOptions.tid || null,
  871. contract_type: ctx.contract_type,
  872. cid,
  873. uid: ctx.session.sessionUser.accountId,
  874. filename: file.name,
  875. fileext: file.ext,
  876. filesize: ctx.helper.bytesToSize(idx === 'isString' ? parts.field.size : parts.field.size[idx]),
  877. filepath: file.filepath,
  878. upload_time: in_time,
  879. };
  880. if (cpid) {
  881. newFile.cpid = cpid;
  882. }
  883. if (csid) {
  884. newFile.csid = csid;
  885. }
  886. return newFile;
  887. });
  888. if (cpid) {
  889. // 执行文件信息写入数据库
  890. await ctx.service.contractPayAtt.saveFileMsgToDb(payload);
  891. // 将最新的当前标段的所有文件信息返回
  892. responseData.data = await ctx.service.contractPayAtt.getAtt(cpid);
  893. } else if (csid) {
  894. // 执行文件信息写入数据库
  895. await ctx.service.contractSupplementAtt.saveFileMsgToDb(payload);
  896. // 将最新的当前标段的所有文件信息返回
  897. responseData.data = await ctx.service.contractSupplementAtt.getAtt(csid);
  898. } else {
  899. // 执行文件信息写入数据库
  900. await ctx.service.contractAtt.saveFileMsgToDb(payload);
  901. // 将最新的当前标段的所有文件信息返回
  902. responseData.data = await ctx.service.contractAtt.getAtt(cid);
  903. }
  904. ctx.body = responseData;
  905. } catch (err) {
  906. stream && (await sendToWormhole(stream));
  907. this.log(err);
  908. ctx.body = { err: 1, msg: err.toString(), data: null };
  909. }
  910. }
  911. /**
  912. * 删除附件
  913. * @param {Ojbect} ctx 上下文
  914. */
  915. async deleteFile(ctx) {
  916. try {
  917. const cpid = ctx.params.cpid;
  918. const csid = ctx.params.csid;
  919. const cid = ctx.params.cid;
  920. const responseData = { err: 0, msg: '', data: {} };
  921. const data = JSON.parse(ctx.request.body.data);
  922. let fileInfo = null;
  923. if (cpid) {
  924. fileInfo = await ctx.service.contractPayAtt.getDataById(data.id);
  925. } else if (csid) {
  926. fileInfo = await ctx.service.contractSupplementAtt.getDataById(data.id);
  927. } else if (cid) {
  928. fileInfo = await ctx.service.contractAtt.getDataById(data.id);
  929. } else {
  930. throw '参数错误';
  931. }
  932. if (fileInfo || Object.keys(fileInfo).length) {
  933. // 先删除文件
  934. // await fs.unlinkSync(path.resolve(this.app.baseDir, './app', fileInfo.filepath));
  935. await ctx.app.fujianOss.delete(ctx.app.config.fujianOssFolder + fileInfo.filepath);
  936. // 再删除数据库
  937. if (cpid) {
  938. await ctx.service.contractPayAtt.delete(data.id);
  939. } else if (csid) {
  940. await ctx.service.contractSupplementAtt.delete(data.id);
  941. } else {
  942. await ctx.service.contractAtt.delete(data.id);
  943. }
  944. } else {
  945. throw '不存在该文件';
  946. }
  947. if (cpid) {
  948. responseData.data = await ctx.service.contractPayAtt.getAtt(cpid);
  949. } else if (csid) {
  950. responseData.data = await ctx.service.contractSupplementAtt.getAtt(csid);
  951. } else {
  952. responseData.data = await ctx.service.contractAtt.getAtt(cid);
  953. }
  954. ctx.body = responseData;
  955. } catch (err) {
  956. this.log(err);
  957. ctx.body = { err: 1, msg: err.toString(), data: null };
  958. }
  959. }
  960. /**
  961. * 下载附件
  962. * @param {Object} ctx - egg全局变量
  963. * @return {void}
  964. */
  965. async downloadFile(ctx) {
  966. const id = ctx.params.fid;
  967. if (id) {
  968. try {
  969. const cpid = ctx.params.cpid;
  970. const csid = ctx.params.csid;
  971. const cid = ctx.params.cid;
  972. let fileInfo = null;
  973. if (cpid) {
  974. fileInfo = await ctx.service.contractPayAtt.getDataById(id);
  975. } else if (csid) {
  976. fileInfo = await ctx.service.contractSupplementAtt.getDataById(id);
  977. } else if (cid) {
  978. fileInfo = await ctx.service.contractAtt.getDataById(id);
  979. } else {
  980. throw '参数错误';
  981. }
  982. if (fileInfo || Object.keys(fileInfo).length) {
  983. // const fileName = path.join(__dirname, '../', fileInfo.filepath);
  984. // 解决中文无法下载问题
  985. const userAgent = (ctx.request.header['user-agent'] || '').toLowerCase();
  986. let disposition = '';
  987. if (userAgent.indexOf('msie') >= 0 || userAgent.indexOf('chrome') >= 0) {
  988. disposition = 'attachment; filename=' + encodeURIComponent(fileInfo.filename);
  989. } else if (userAgent.indexOf('firefox') >= 0) {
  990. disposition = 'attachment; filename*="utf8\'\'' + encodeURIComponent(fileInfo.filename) + '"';
  991. } else {
  992. /* safari等其他非主流浏览器只能自求多福了 */
  993. disposition = 'attachment; filename=' + new Buffer(fileInfo.filename).toString('binary');
  994. }
  995. ctx.response.set({
  996. 'Content-Type': 'application/octet-stream',
  997. 'Content-Disposition': disposition,
  998. 'Content-Length': fileInfo.filesize,
  999. });
  1000. // ctx.body = await fs.createReadStream(fileName);
  1001. ctx.body = await ctx.helper.ossFileGet(fileInfo.filepath);
  1002. } else {
  1003. throw '不存在该文件';
  1004. }
  1005. } catch (err) {
  1006. this.log(err);
  1007. this.setMessage(err.toString(), this.messageType.ERROR);
  1008. }
  1009. }
  1010. }
  1011. async setting(ctx) {
  1012. try {
  1013. if (!ctx.subProject.page_show.openTenderContract && !ctx.subProject.page_show.openContract) {
  1014. throw '该功能已关闭或无法查看';
  1015. }
  1016. if (!ctx.session.sessionUser.is_admin) {
  1017. throw '您没有权限查看该页面';
  1018. }
  1019. // url是否包含tender
  1020. const types_from = ctx.url.indexOf('tender') > -1 ? 'tender' : 'subProject';
  1021. const commonJson = ctx.subProject.common_json ? JSON.parse(ctx.subProject.common_json) : null;
  1022. const types = commonJson && commonJson.contract_type ? commonJson.contract_type : [];
  1023. const tender_types = commonJson && commonJson.tender_contract_type ? commonJson.tender_contract_type : [];
  1024. const used = commonJson && commonJson.contract_used ? commonJson.contract_used : [];
  1025. const tender_used = commonJson && commonJson.tender_contract_used ? commonJson.tender_contract_used : [];
  1026. const renderData = {
  1027. types_from,
  1028. types,
  1029. tender_types,
  1030. used,
  1031. tender_used,
  1032. jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.contract.setting),
  1033. thisUrl: `/sp/${ctx.subProject.id}/contract${types_from === 'tender' ? '/tender' : ''}/setting`,
  1034. is_setting: true,
  1035. };
  1036. const attributeSet = commonJson && commonJson.contract_attribute ? commonJson.contract_attribute : contractConst.attributeSet;
  1037. renderData.attributeSet = ctx.service.contractColSet.analysisColSetWithDefine(contractConst.attributeSet, attributeSet, contractConst.defaultAttributeSet);
  1038. await this.layout('contract/setting.ejs', renderData);
  1039. } catch (err) {
  1040. ctx.log(err);
  1041. ctx.session.postError = err.toString();
  1042. ctx.redirect(`/sp/${ctx.subProject.id}/dashboard`);
  1043. }
  1044. }
  1045. /**
  1046. * 保存功能设置相关
  1047. * @param ctx
  1048. * @return {Promise<void>}
  1049. */
  1050. async updateSetting(ctx) {
  1051. try {
  1052. const responseData = { err: 0, msg: '', data: null };
  1053. if (!ctx.subProject) throw '没有对应的项目数据';
  1054. if (ctx.session.sessionUser.is_admin === 0) throw '没有访问权限';
  1055. const data = JSON.parse(ctx.request.body.data);
  1056. if (data) ctx.request.body = data;
  1057. switch (data.type) {
  1058. case 'page_show':
  1059. this.ctx.subProject.page_show.openContractSubProjectShenpi = data.openContractSubProjectShenpi ? 1 : 0;
  1060. this.ctx.subProject.page_show.openContractPaySubProjectShenpi = data.openContractPaySubProjectShenpi ? 1 : 0;
  1061. this.ctx.subProject.page_show.openContractTenderShenpi = data.openContractTenderShenpi ? 1 : 0;
  1062. this.ctx.subProject.page_show.openContractPayTenderShenpi = data.openContractPayTenderShenpi ? 1 : 0;
  1063. const result = await ctx.service.contract.savePageShow(ctx.subProject.id, ctx.subProject.page_show);
  1064. if (!result) throw '保存数据失败';
  1065. break;
  1066. case 'attribute_set':
  1067. const result2 = await ctx.service.subProject.saveCommonJson(ctx.subProject.id, 'contract_attribute', data.attribute_set);
  1068. if (!result2) throw '保存数据失败';
  1069. responseData.data = ctx.service.contractColSet.analysisColSetWithDefine(contractConst.attributeSet, data.attribute_set, contractConst.defaultAttributeSet);
  1070. break;
  1071. default:
  1072. throw '参数有误';
  1073. }
  1074. ctx.body = responseData;
  1075. } catch (error) {
  1076. ctx.log(error);
  1077. this.ajaxErrorBody(error, '保存数据失败');
  1078. }
  1079. }
  1080. async shenpiSet(ctx) {
  1081. try {
  1082. if (!ctx.subProject) throw '没有对应的项目数据';
  1083. if (ctx.session.sessionUser.is_admin === 0) throw '没有访问权限';
  1084. // 获取所有项目参与者
  1085. const accountList = await ctx.service.projectAccount.getAllSubProjectAccount(ctx.subProject);
  1086. const unitList = await ctx.service.constructionUnit.getAllDataByCondition({ where: { pid: ctx.session.sessionProject.id } });
  1087. const accountGroupList = unitList.map(item => {
  1088. const groupList = accountList.filter(item1 => item1.company === item.name);
  1089. return { groupName: item.name, groupList };
  1090. }).filter(x => { return x.groupList.length > 0; });
  1091. // 获取固定审批流 or 固定终审
  1092. const spConst = ctx.helper._.cloneDeep(shenpiConst);
  1093. for (const sp of spConst.sp_lc) {
  1094. sp.status = ctx.subProject.shenpi ? ctx.subProject.shenpi[sp.code] : spConst.sp_status.sqspr;
  1095. if (sp.status === shenpiConst.sp_status.gdspl) {
  1096. sp.groupList = await ctx.service.shenpiGroup.getGroupList(ctx.subProject.id, sp.type) || [];
  1097. if (sp.groupList && sp.groupList.length > 0) {
  1098. for (const group of sp.groupList) {
  1099. if (group.change_type) group.change_type = JSON.parse(group.change_type);
  1100. group.auditGroupList = await ctx.service.shenpiAudit.getAuditGroupList(ctx.subProject.id, sp.type, sp.status, group.id);
  1101. if (group.is_select) sp.auditGroupList = group.auditGroupList;
  1102. }
  1103. } else {
  1104. sp.auditGroupList = await ctx.service.shenpiAudit.getAuditGroupList(ctx.subProject.id, sp.type, sp.status);
  1105. }
  1106. } else if (sp.status === shenpiConst.sp_status.gdzs) {
  1107. sp.audit = await ctx.service.shenpiAudit.getAudit(ctx.subProject.id, sp.type, sp.status);
  1108. }
  1109. }
  1110. const renderData = {
  1111. shenpi: spConst,
  1112. accountList,
  1113. accountGroup: accountGroupList,
  1114. auditConst,
  1115. jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.contract.shenpi),
  1116. auditType: auditConst.auditType,
  1117. types_from: 'subProject',
  1118. thisUrl: `/sp/${ctx.subProject.id}/contract/setting`,
  1119. contract_type_list: shenpiConst.contract_type_list,
  1120. is_setting: true,
  1121. };
  1122. await this.layout('contract/shenpi.ejs', renderData, 'contract/shenpi_modal.ejs');
  1123. } catch (err) {
  1124. ctx.log(err);
  1125. ctx.session.postError = err.toString();
  1126. ctx.redirect(`/sp/${ctx.subProject.id}/dashboard`);
  1127. }
  1128. }
  1129. async editAndAi(ctx) {
  1130. try {
  1131. const data = JSON.parse(ctx.request.body.data);
  1132. if (!data.id || !data.fid) throw '数据错误';
  1133. const contract = await ctx.service.contract.getDataById(data.id);
  1134. if (!contract) {
  1135. throw '未找到合同相关数据';
  1136. }
  1137. const att = await ctx.service.contractAtt.getDataById(data.fid);
  1138. if (!att) {
  1139. throw '未找到合同文件';
  1140. }
  1141. att.viewpath = ctx.app.config.fujianOssPath + att.filepath;
  1142. const aiDify = new AiDify({ apiKey: ctx.difyInfo.apiKey.contract });
  1143. const transResult = await aiDify.contractTrans(att);
  1144. transResult.res = JSON.parse(transResult.res);
  1145. const responseData = {
  1146. ...transResult,
  1147. cid: contract.id,
  1148. attached: false,
  1149. };
  1150. ctx.body = { err: 0, msg: '', data: responseData };
  1151. } catch (err) {
  1152. console.log(err);
  1153. this.log(err);
  1154. ctx.body = { err: 1, msg: err.toString(), data: null };
  1155. }
  1156. }
  1157. async uploadAndAi(ctx) {
  1158. const stream = await ctx.getFileStream();
  1159. try {
  1160. const hasExistingCid = !!stream.fields.cid;
  1161. const cid = stream.fields.cid || this.ctx.app.uuid.v4();
  1162. if (!cid) throw '参数有误';
  1163. const attachToContract = stream.fields.attachToContract === 'true' || stream.fields.attachToContract === '1';
  1164. const create_time = Date.parse(new Date()) / 1000;
  1165. const parsedPath = path.parse(stream.filename);
  1166. const headpath = ctx.contractOptions.spid
  1167. ? `sp/contract/${ctx.contractOptions.spid}`
  1168. : `app/public/upload/${ctx.contractOptions.tid}`;
  1169. const filepath = `${headpath}/contract/${cid}/fujian_${create_time}${parsedPath.ext}`;
  1170. await ctx.app.fujianOss.put(ctx.app.config.fujianOssFolder + filepath, stream);
  1171. const fileInfo = { filepath, name: stream.filename, ext: parsedPath.ext };
  1172. if (stream) await sendToWormhole(stream);
  1173. if (!fileInfo) {
  1174. throw '请上传文件';
  1175. }
  1176. const newFile = {
  1177. spid: ctx.contractOptions.spid || null,
  1178. tid: ctx.contractOptions.tid || null,
  1179. contract_type: ctx.contract_type,
  1180. cid,
  1181. uid: ctx.session.sessionUser.accountId,
  1182. filename: fileInfo.name,
  1183. fileext: fileInfo.ext,
  1184. filesize: stream.fields.size,
  1185. filepath: fileInfo.filepath,
  1186. upload_time: new Date(),
  1187. };
  1188. // 写入数据库(只有这一条记录)
  1189. if (!hasExistingCid || attachToContract) {
  1190. const fileResult = await ctx.service.contractAtt.saveFileMsgToDb([newFile]);
  1191. newFile.id = fileResult.insertId;
  1192. newFile.username = ctx.session.sessionUser.name;
  1193. }
  1194. // 重新查出该cid下的附件,按filepath精确匹配出刚上传的这一份(避免拿到该cid下其他历史文件)
  1195. newFile.viewpath = ctx.app.config.fujianOssPath + newFile.filepath;
  1196. const aiDify = new AiDify({ apiKey: ctx.difyInfo.apiKey.contract });
  1197. const transResult = await aiDify.contractTrans(newFile);
  1198. transResult.res = JSON.parse(transResult.res);
  1199. // 如果不勾选"附加到合同"并且没有cid,则要删除oss文件
  1200. if (!attachToContract && hasExistingCid) {
  1201. await ctx.app.fujianOss.delete(ctx.app.config.fujianOssFolder + newFile.filepath);
  1202. }
  1203. const responseData = {
  1204. ...transResult,
  1205. cid,
  1206. attached: attachToContract,
  1207. };
  1208. if ((!hasExistingCid || attachToContract) && newFile.id) responseData.file = newFile;
  1209. ctx.body = { err: 0, msg: '', data: responseData };
  1210. } catch (err) {
  1211. stream && (await sendToWormhole(stream));
  1212. this.log(err);
  1213. ctx.body = { err: 1, msg: err.toString(), data: null };
  1214. }
  1215. }
  1216. }
  1217. return ContractController;
  1218. };