weapp_tender_controller.js 51 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895
  1. 'use strict';
  2. const auditConst = require('../const/audit');
  3. const changeMap = require('../const/weapp').changeMap;
  4. const measureType = require('../const/tender').measureType;
  5. const advanceConst = require('../const/advance');
  6. const shenpiConst = require('../const/shenpi');
  7. const stdConst = require('../const/standard');
  8. const _ = require('lodash');
  9. const PinYinOrder = require('../public/js/PinYinOrder.bundle');
  10. module.exports = app => {
  11. class WeappTenderController extends app.BaseController {
  12. async listManage(ctx) {
  13. try {
  14. const projectId = ctx.query.projectId;
  15. if (!projectId) {
  16. ctx.body = { err: 1, msg: '缺少projectId参数', data: null };
  17. return;
  18. }
  19. const accountInfo = ctx.session.sessionUser;
  20. const userPermission = accountInfo !== undefined && accountInfo.permission !== '' ? JSON.parse(accountInfo.permission) : null;
  21. if (userPermission !== null && userPermission.tender !== undefined && userPermission.tender.indexOf('1') !== -1) {
  22. const buildStatus = ctx.query.buildStatus;
  23. if (buildStatus !== undefined && buildStatus !== '1' && buildStatus !== '2') {
  24. throw '标段建设状态参数错误';
  25. }
  26. const subProject = await this.ctx.service.subProject.getDataById(projectId);
  27. if (!subProject) throw '项目不存在';
  28. if (subProject.project_id !== ctx.session.sessionProject.id) throw '您无权查看该项目';
  29. // 与网页端 subProjectCheck 使用相同的个人分类和页面配置。
  30. const subProjectPermission = accountInfo.is_admin
  31. ? ctx.service.subProjPermission.adminPermission
  32. : await ctx.service.subProjPermission.getSubProjectUserPermission(projectId, accountInfo.accountId);
  33. if (!subProjectPermission) throw '您无权查看该项目';
  34. subProject.page_show = ctx.service.subProject.getPageShow(subProject.page_show);
  35. const buildStatusFilter = buildStatus === undefined ? '' : app.mysql.format(' AND t.build_status = ?', [Number(buildStatus)]);
  36. const tenderList = await this.ctx.service.tender.getList('', userPermission, accountInfo.is_admin, buildStatusFilter, subProject);
  37. // 复用网页的名称比较器,保持默认拼音排序一致。
  38. const nameOrder = new PinYinOrder();
  39. tenderList.sort((a, b) => nameOrder.compareWord(a.name, b.name));
  40. for (const tender of tenderList) {
  41. // 查询当前审批期以及状态
  42. const stage = await this.ctx.service.stage.getLastestStage(tender.id, true);
  43. tender.stage_order = stage && stage.order ? stage.order : null;
  44. tender.stage_status = stage && stage.status ? stage.status : null;
  45. }
  46. const categoryData = await this.ctx.service.category.getAllCategory(subProject);
  47. const renderData = {
  48. categoryData,
  49. tenderList,
  50. selfCategoryLevel: subProjectPermission.self_category_level || '',
  51. };
  52. ctx.body = { err: 0, msg: '', data: renderData };
  53. } else {
  54. ctx.body = { err: 1, msg: '您没有管理权限', data: null };
  55. }
  56. } catch (err) {
  57. this.log(err);
  58. ctx.body = { err: 1, msg: err.toString(), data: null };
  59. }
  60. }
  61. async detail(ctx) {
  62. try {
  63. const tender = ctx.tender.data;
  64. let bCalcTp = ctx.tender.data.user_id === ctx.session.sessionUser.accountId && (
  65. ctx.tender.ledger_status === auditConst.ledger.status.checkNo || ctx.tender.ledger_status === auditConst.ledger.status.uncheck);
  66. const stages = await ctx.service.stage.getValidStages(ctx.tender.id);
  67. const lastStage = stages.length > 0 ? stages[0] : null; // await ctx.service.stage.getLastestStage(ctx.tender.id);
  68. const [change_tp, change_p_tp, change_n_tp, change_valuation_tp, change_unvaluation_tp] = await ctx.service.change.getChangeTp(ctx.tender.id);
  69. tender.change_tp = change_tp;
  70. tender.change_p_tp = change_p_tp;
  71. tender.change_n_tp = change_n_tp;
  72. tender.change_valuation_tp = change_valuation_tp;
  73. tender.change_unvaluation_tp = change_unvaluation_tp;
  74. if (lastStage) {
  75. await this.ctx.service.stage.checkStageGatherData(lastStage, ctx.session.sessionUser.is_admin);
  76. if ((!bCalcTp) && tender.measure_type === measureType.gcl.value) {
  77. bCalcTp = lastStage.status !== auditConst.stage.status.checked && !lastStage.readOnly;
  78. }
  79. if (bCalcTp) {
  80. const sum = await this.ctx.service.ledger.addUp({ tender_id: ctx.tender.id/* , is_leaf: true*/ });
  81. tender.total_price = sum.total_price;
  82. tender.deal_tp = sum.deal_tp;
  83. }
  84. tender.sum = ctx.tender.info.calc_type === 'tp' ? ctx.helper.add(tender.total_price, tender.change_valuation_tp) : ctx.helper.add(tender.total_price, tender.change_tp);
  85. tender.gather_tp = ctx.helper.sum([lastStage.contract_tp, lastStage.qc_tp, lastStage.pc_tp]);
  86. tender.end_contract_tp = ctx.helper.sum([lastStage.contract_tp, lastStage.pre_contract_tp, lastStage.contract_pc_tp]);
  87. tender.end_qc_tp = ctx.helper.sum([lastStage.qc_tp, lastStage.pre_qc_tp, lastStage.qc_pc_tp]);
  88. tender.end_positive_qc_tp = ctx.helper.sum([lastStage.positive_qc_tp, lastStage.pre_positive_qc_tp, lastStage.positive_qc_pc_tp]);
  89. tender.end_negative_qc_tp = ctx.helper.sum([lastStage.negative_qc_tp, lastStage.pre_negative_qc_tp, lastStage.negative_qc_pc_tp]);
  90. tender.end_gather_tp = ctx.helper.add(tender.end_contract_tp, tender.end_qc_tp);
  91. tender.pre_gather_tp = ctx.helper.add(lastStage.pre_contract_tp, lastStage.pre_qc_tp);
  92. tender.yf_tp = lastStage.yf_tp;
  93. tender.sf_tp = lastStage.sf_tp;
  94. tender.qc_ratio = ctx.helper.mul(ctx.helper.div(tender.end_qc_tp, ctx.tender.info.deal_param.contractPrice, 2), 100);
  95. tender.pre_ratio = ctx.helper.mul(ctx.helper.div(tender.pre_gather_tp, tender.sum, 2), 100);
  96. tender.cur_ratio = ctx.helper.mul(ctx.helper.div(tender.gather_tp, tender.sum, 2), 100);
  97. tender.other_tp = ctx.helper.sub(ctx.helper.sub(tender.sum, tender.pre_gather_tp), tender.gather_tp);
  98. tender.other_ratio = Math.max(0, 100 - tender.pre_ratio - tender.cur_ratio);
  99. tender.end_yf_tp = ctx.helper.add(lastStage.yf_tp, lastStage.pre_yf_tp);
  100. tender.end_sf_tp = ctx.helper.add(lastStage.sf_tp, lastStage.pre_sf_tp);
  101. tender.undone_tp = ctx.helper.sub(ctx.helper.sub(ctx.helper.add(tender.total_price, change_tp), tender.end_contract_tp), tender.end_qc_tp);
  102. } else {
  103. if (bCalcTp) {
  104. const sum = await this.ctx.service.ledger.addUp({ tender_id: ctx.tender.id/* , is_leaf: true*/ });
  105. tender.total_price = sum.total_price;
  106. tender.deal_tp = sum.deal_tp;
  107. }
  108. tender.sum = ctx.helper.add(tender.total_price, tender.change_tp);
  109. }
  110. // const monthProgress = [];
  111. // for (const s of stages) {
  112. // if (s.s_time) {
  113. // let progress = monthProgress.find(function(x) {
  114. // return x.month === s.s_time;
  115. // });
  116. // if (!progress) {
  117. // progress = { month: s.s_time };
  118. // monthProgress.push(progress);
  119. // }
  120. // progress.tp = ctx.helper.add(ctx.helper.add(progress.tp, s.contract_tp), s.qc_tp);
  121. // }
  122. // }
  123. // monthProgress.sort(function(x, y) {
  124. // return Date.parse(x.month) - Date.parse(y.month);
  125. // });
  126. // let sum = 0;
  127. // for (const p of monthProgress) {
  128. // p.ratio = ctx.helper.mul(ctx.helper.div(p.tp, tender.sum, 4), 100);
  129. // sum = ctx.helper.add(sum, p.tp);
  130. // p.end_tp = sum;
  131. // p.end_ratio = ctx.helper.mul(ctx.helper.div(p.end_tp, tender.sum, 4), 100);
  132. // }
  133. ctx.body = {
  134. code: 0, msg: '', data: {
  135. tender,
  136. // monthProgress,
  137. // stagesEcharts: JSON.parse(JSON.stringify(stages)).reverse(),
  138. },
  139. };
  140. } catch (error) {
  141. this.log(error);
  142. ctx.body = { code: -1, msg: error.toString(), data: null };
  143. }
  144. }
  145. async advanceList(ctx) {
  146. try {
  147. const { advanceType } = ctx.query;
  148. const typeCol = advanceConst.typeCol.find(x => x.type === Number(advanceType));
  149. if (!typeCol) {
  150. ctx.body = { code: -1, msg: '预付款类型错误', data: null };
  151. return;
  152. }
  153. const { decimal } = ctx.tender.info;
  154. const advancePayTotal = ctx.tender.info.deal_param[typeCol.key + 'Advance'];
  155. const advances = await ctx.service.advance.getAdvanceList(ctx.tender.id, typeCol.type, decimal.pay ? decimal.payTp : decimal.tp, advancePayTotal);
  156. ctx.body = { code: 0, msg: '', data: advances };
  157. } catch (error) {
  158. this.log(error);
  159. ctx.body = { code: -1, msg: error.toString(), data: null };
  160. }
  161. }
  162. async advanceCheck(advance) {
  163. if (!advance) {
  164. throw '预付款数据错误';
  165. }
  166. const times = advance.status === auditConst.advance.status.checkNo ? advance.times - 1 : advance.times;
  167. advance.user = await this.service.projectAccount.getAccountInfoById(advance.uid);
  168. // 读取审核人列表数据
  169. advance.auditors = await this.service.advanceAudit.getAuditorsWithOwner(advance.id, advance.status === auditConst.advance.status.checkNo && this.ctx.session.sessionUser.accountId !== advance.uid ? times : advance.times);
  170. advance.auditHistory = await this.service.advanceAudit.getAuditors(advance.id, advance.times);
  171. advance.curAuditor = await this.service.advanceAudit.getCurAuditor(advance.id, advance.times);
  172. // 根据状态判断是否需要更新审批人列表
  173. if ((advance.status === auditConst.advance.status.uncheck || advance.status === auditConst.advance.status.checkNo) && this.ctx.tender.info.shenpi.advance !== shenpiConst.sp_status.sqspr) {
  174. const shenpi_status = this.ctx.tender.info.shenpi.advance;
  175. // 进一步比较审批流是否与审批流程设置的相同,不同则替换为固定审批流或固定的终审
  176. const auditList = await this.service.advanceAudit.getAllDataByCondition({ where: { vid: advance.id, times: advance.times }, orders: [['order', 'asc']] });
  177. const auditIdList = _.map(auditList, 'audit_id');
  178. if (shenpi_status === shenpiConst.sp_status.gdspl) {
  179. const shenpiList = await this.service.shenpiAudit.getAllDataByCondition({ where: { tid: advance.tid, sp_type: shenpiConst.sp_type.advance, sp_status: shenpi_status } });
  180. const shenpiIdList = _.map(shenpiList, 'audit_id');
  181. // 判断2个id数组是否相同,不同则删除原审批流,切换成固定的审批流
  182. if (!_.isEqual(auditIdList, shenpiIdList)) {
  183. await this.service.advanceAudit.updateNewAuditList(advance, shenpiIdList);
  184. }
  185. } else if (shenpi_status === shenpiConst.sp_status.gdzs) {
  186. const shenpiInfo = await this.service.shenpiAudit.getDataByCondition({ tid: advance.tid, sp_type: shenpiConst.sp_type.advance, sp_status: shenpi_status });
  187. // 判断最后一个id是否与固定终审id相同,不同则删除原审批流中如果存在的id和添加终审
  188. if (shenpiInfo && shenpiInfo.audit_id !== _.last(auditIdList)) {
  189. await this.service.advanceAudit.updateLastAudit(advance, auditList, shenpiInfo.audit_id);
  190. } else if (!shenpiInfo) {
  191. // 不存在终审人的状态下这里恢复为授权审批人
  192. this.ctx.tender.info.shenpi.advance = shenpiConst.sp_status.sqspr;
  193. }
  194. }
  195. }
  196. advance.advancePayTotal = this.ctx.tender.info.deal_param[advanceConst.typeCol[advance.type].key + 'Advance'];
  197. if (advance.status === auditConst.advance.status.checkNo) {
  198. advance.curAuditor = await this.service.advanceAudit.getAuditorByStatus(advance.id, advance.status, times);
  199. advance.auditHistory = await this.service.advanceAudit.getAuditors(advance.id, times);
  200. }
  201. }
  202. async advanceDetail(ctx) {
  203. try {
  204. const { id } = ctx.query;
  205. const advance = await this.service.advance.getDataById(id);
  206. if (!advance) {
  207. throw '预付款数据错误';
  208. }
  209. await this.advanceCheck(advance);
  210. advance.auditHistory = advance.auditHistory.reduce((prev, curr, idx) => {
  211. if (idx === 0) {
  212. const reportor = {
  213. audit_id: advance.uid,
  214. audit_order: 0,
  215. audit_type: 1,
  216. status: advance.status === auditConst.advance.status.uncheck ? auditConst.advance.status.uncheck : auditConst.advance.status.checked,
  217. times: curr.times,
  218. begin_time: advance.pay_time,
  219. end_time: advance.auditHistory.length ? advance.auditHistory[0].create_time : null,
  220. name: advance.user.name,
  221. company: advance.user.company,
  222. role: advance.user.role,
  223. mobile: '',
  224. opinion: '',
  225. };
  226. prev.push({
  227. name: '原报',
  228. status: reportor.status,
  229. audit_type: reportor.audit_type,
  230. auditors: [reportor],
  231. });
  232. }
  233. const currentOrder = advance.auditors.find(auditor => auditor.audit_id === curr.audit_id).order;
  234. prev.push(
  235. {
  236. name: currentOrder === curr.max_sort ? '终审' : `${currentOrder}审`,
  237. status: curr.status,
  238. audit_type: 1,
  239. auditors: [{
  240. audit_id: curr.audit_id,
  241. audit_order: curr.order,
  242. audit_type: curr.type,
  243. status: curr.status,
  244. max_sort: curr.max_sort,
  245. begin_time: curr.create_time,
  246. end_time: curr.end_time,
  247. name: curr.name,
  248. company: curr.company,
  249. mobile: curr.mobile,
  250. opinion: curr.opinion,
  251. role: curr.role,
  252. times: curr.times,
  253. }],
  254. }
  255. );
  256. return prev;
  257. }, []);
  258. if (advance.curAuditor) {
  259. advance.curAuditor.audit_order = advance.auditors.find(auditor => auditor.audit_id === advance.curAuditor.audit_id).order;
  260. }
  261. ctx.body = { code: 0, msg: '', data: { advance } };
  262. } catch (error) {
  263. this.log(error);
  264. ctx.body = { code: -1, msg: error.toString(), data: null };
  265. }
  266. }
  267. async advanceAudit(ctx) {
  268. try {
  269. const advance = await this.service.advance.getDataById(ctx.request.body.id);
  270. await this.advanceCheck(advance);
  271. ctx.advance = advance;
  272. const data = {
  273. checkType: parseInt(ctx.request.body.checkType),
  274. opinion: ctx.request.body.opinion,
  275. };
  276. if (!advance || advance.status !== auditConst.advance.status.checking) {
  277. throw '当前预付款数据有误';
  278. }
  279. if (!advance.curAuditor || advance.curAuditor.audit_id !== ctx.session.sessionUser.accountId) {
  280. throw '您无权进行该操作';
  281. }
  282. if (!data.checkType || isNaN(data.checkType)) {
  283. throw '提交数据错误';
  284. }
  285. if (data.checkType === auditConst.advance.status.checkNo) {
  286. if (!data.checkType || isNaN(data.checkType)) {
  287. throw '提交数据错误';
  288. }
  289. }
  290. await ctx.service.advanceAudit.check(advance.id, data, advance.times, advance.type);
  291. ctx.body = { code: 0, msg: '操作成功', data: null };
  292. } catch (error) {
  293. this.log(error);
  294. ctx.body = { code: -1, msg: error.toString(), data: null };
  295. }
  296. }
  297. async ledgerAuditCheck(ctx) {
  298. const tender = ctx.tender.data;
  299. const info = ctx.tender.info;
  300. if (info.shenpi.ledger === shenpiConst.sp_status.sqspr) return;
  301. if (tender.ledger_status !== auditConst.ledger.status.uncheck && tender.ledger_status !== auditConst.ledger.status.checkNo) return;
  302. const shenpi_status = info.shenpi.ledger;
  303. // 进一步比较审批流是否与审批流程设置的相同,不同则替换为固定审批流或固定的终审
  304. const auditList = await ctx.service.ledgerAudit.getAuditors(tender.id, tender.ledger_times);
  305. if (shenpi_status === shenpiConst.sp_status.gdspl) {
  306. const shenpiList = await ctx.service.shenpiAudit.getAllDataByCondition({ where: { tid: tender.id, sp_type: shenpiConst.sp_type.ledger, sp_status: shenpi_status } });
  307. // 判断2个id数组是否相同,不同则删除原审批流,切换成固定的审批流
  308. let sameAudit = auditList.length === shenpiList.length;
  309. if (sameAudit) {
  310. for (const audit of auditList) {
  311. const shenpi = shenpiList.find(x => { return x.audit_id === audit.audit_id; });
  312. if (!shenpi || shenpi.audit_order !== audit.audit_order || shenpi.audit_type !== audit.audit_type || shenpi.audit_ledger_id !== audit.audit_ledger_id) {
  313. sameAudit = false;
  314. break;
  315. }
  316. }
  317. }
  318. if (!sameAudit) await ctx.service.ledgerAudit.updateNewAuditList(tender, shenpiList);
  319. } else if (shenpi_status === shenpiConst.sp_status.gdzs) {
  320. const shenpiInfo = await ctx.service.shenpiAudit.getDataByCondition({ tid: tender.id, sp_type: shenpiConst.sp_type.ledger, sp_status: shenpi_status });
  321. // 判断最后一个id是否与固定终审id相同,不同则删除原审批流中如果存在的id和添加终审
  322. const lastAuditors = auditList.filter(x => { x.audit_order === auditList[auditList.length - 1].audit_order; });
  323. if (shenpiInfo && (lastAuditors.length === 0 || (lastAuditors.length > 1 || shenpiInfo.audit_id !== lastAuditors[0].audit_id))) {
  324. await ctx.service.ledgerAudit.updateLastAudit(tender, auditList, shenpiInfo.audit_id);
  325. } else if (!shenpiInfo) {
  326. // 不存在终审人的状态下这里恢复为授权审批人
  327. info.shenpi.ledger = shenpiConst.sp_status.sqspr;
  328. }
  329. }
  330. }
  331. async ledgerDetail(ctx) {
  332. try {
  333. await this.ledgerAuditCheck(ctx);
  334. const tender = ctx.tender.data;
  335. await ctx.service.ledgerAudit.loadLedgerUser(tender);
  336. await ctx.service.ledgerAudit.loadLedgerAuditViewData(tender);
  337. const auditHistory = tender.auditHistory && tender.auditHistory.length && tender.auditHistory[tender.auditHistory.length - 1].reduce((prev, curr, idx) => {
  338. if (idx === 0) {
  339. const reportor = {
  340. audit_id: tender.user.id,
  341. audit_order: 0,
  342. audit_type: 1,
  343. status: tender.ledger_status === auditConst.ledger.status.uncheck ? auditConst.ledger.status.uncheck : auditConst.ledger.status.checked,
  344. times: tender.ledger_status === auditConst.ledger.status.checkNo ? tender.ledger_times - 1 : tender.ledger_times,
  345. begin_time: curr.begin_time,
  346. end_time: curr.begin_time,
  347. name: tender.user.name,
  348. company: tender.user.company,
  349. role: tender.user.role,
  350. mobile: '',
  351. opinion: '',
  352. };
  353. prev.push({
  354. name: '原报',
  355. status: reportor.status,
  356. audit_type: reportor.audit_type,
  357. auditors: [reportor],
  358. });
  359. }
  360. prev.push({
  361. name: curr.is_final ? '终审' : `${curr.order}审`,
  362. audit_type: curr.audit_type,
  363. status: curr.status,
  364. auditors: curr.auditors,
  365. });
  366. return prev;
  367. }, []);
  368. const curAuditors = tender.curAuditors;
  369. const ledger = {
  370. contractPrice: ctx.tender.info.deal_param.contractPrice,
  371. totalPrice: tender.total_price || 0,
  372. auditHistory,
  373. curAuditors,
  374. };
  375. ctx.body = { code: 0, msg: '', data: { ledger } };
  376. } catch (error) {
  377. this.log(error);
  378. ctx.body = { code: -1, msg: error.toString(), data: null };
  379. }
  380. }
  381. async ledgerAudit(ctx) {
  382. try {
  383. await this.ledgerAuditCheck(ctx);
  384. const tender = ctx.tender;
  385. if (!tender.data || tender.data.ledger_status !== auditConst.ledger.status.checking) {
  386. throw '当前标段数据有误';
  387. }
  388. const curAudits = await ctx.service.ledgerAudit.getCurAuditors(tender.id, tender.data.ledger_times);
  389. const curAuditIds = curAudits.map(x => { return x.audit_id; });
  390. if (curAuditIds.indexOf(ctx.session.sessionUser.accountId) < 0) {
  391. throw '审批失败';
  392. }
  393. const checkType = parseInt(ctx.request.body.checkType);
  394. if (!checkType || isNaN(checkType)) {
  395. throw '提交数据错误';
  396. }
  397. await ctx.service.ledgerAudit.check(tender.id, checkType, ctx.request.body.opinion, tender.data.ledger_times);
  398. ctx.body = { code: 0, msg: '操作成功', data: null };
  399. } catch (error) {
  400. this.log(error);
  401. ctx.body = { code: -1, msg: error.toString(), data: null };
  402. }
  403. }
  404. async changeList(ctx) {
  405. try {
  406. const { type } = ctx.request.query;
  407. const curChangeMap = changeMap[type];
  408. if (!curChangeMap) {
  409. throw '变更类型错误';
  410. }
  411. const tender = ctx.tender.data;
  412. const list = await ctx.service[curChangeMap.service].getListByStatus(tender.id, 0, 1, 0, 0, 0);
  413. ctx.body = { code: 0, msg: '', data: list };
  414. } catch (error) {
  415. this.log(error);
  416. ctx.body = { code: -1, msg: error.toString(), data: null };
  417. }
  418. }
  419. async changeCheck(ctx, curChangeMap) {
  420. try {
  421. const subProject = await ctx.service.subProject.getDataById(ctx.tender.data.spid);
  422. if (subProject.page_show && typeof subProject.page_show === 'string') {
  423. subProject.page_show = JSON.parse(subProject.page_show);
  424. };
  425. if (curChangeMap && curChangeMap.pageShowKey && !subProject.page_show[curChangeMap.pageShowKey]) {
  426. throw '该功能已关闭';
  427. }
  428. const id = ctx.request.query.id || ctx.request.body.id;
  429. if (!id) {
  430. throw '您访问的变更数据不存在';
  431. }
  432. const condition = { [curChangeMap.idKey]: id };
  433. const change = await ctx.service[curChangeMap.service].getDataByCondition(condition);
  434. if (!change) throw '变更方案数据有误';
  435. // 读取原报、审核人数据
  436. await ctx.service[curChangeMap.service].loadChangeUser(change);
  437. await ctx.service[curChangeMap.service].loadChangeAuditViewData(change);
  438. // decimal小数位设置
  439. change.decimal = change.decimal ? JSON.parse(change.decimal) : { tp: ctx.tender.info.decimal.tp, up: ctx.tender.info.decimal.up, precision: ctx.tender.info.precision };
  440. // 权限相关
  441. // todo 校验权限 (标段参与人、分享)
  442. const accountId = ctx.session.sessionUser.accountId,
  443. shareIds = [];
  444. const permission = ctx.session.sessionUser.permission;
  445. const type = ctx.request.query.type || ctx.request.body.type;
  446. const status = auditConst[type].status;
  447. if (accountId === change.uid) { // 原报
  448. change.curTimes = change.times;
  449. change.filePermission = true;
  450. } else if (change.auditorIds.indexOf(accountId) !== -1) { // 审批人
  451. if (change.status === status.uncheck) {
  452. throw '您无权查看该数据';
  453. }
  454. // change.readOnly = change.status !== status.checking || accountId !== change.curAuditor.aid;
  455. change.curTimes = change.status === status.checkNo || change.status === status.revise ? change.times - 1 : change.times;
  456. change.filePermission = true;
  457. } else if ((change.status === status.checkNo || change.status === status.revise) && change.uid !== accountId) {
  458. const preAuditors = await ctx.service[curChangeMap.auditService].getAuditors(change.id, change.times - 1);
  459. const preAuditorIds = _.map(preAuditors, 'aid');
  460. if (preAuditorIds.indexOf(accountId) === -1) {
  461. throw '您无权查看该数据';
  462. }
  463. change.filePermission = true;
  464. } else if (ctx.tender.isTourist || ctx.session.sessionUser.is_admin) {
  465. change.curTimes = change.times;
  466. change.filePermission = ctx.tender.touristPermission.file || change.auditorIds.indexOf(accountId) !== -1;
  467. } else if (shareIds.indexOf(accountId) !== -1 || (permission !== null && permission.tender !== undefined && permission.tender.indexOf('2') !== -1)) { // 分享人
  468. if (change.status === auditConsts[type].status.uncheck) {
  469. throw '您无权查看该数据';
  470. }
  471. // change.readOnly = true;
  472. change.curTimes = change.status === auditConsts[type].status.checkNo || change.status === auditConsts[type].status.revise ? change.times - 1 : change.times;
  473. change.filePermission = false;
  474. } else { // 其他不可见
  475. throw '您无权查看该数据';
  476. }
  477. // 调差的readOnly 指表格和页面只能看不能改,和审批无关
  478. change.readOnly = !((change.status === status.uncheck || change.status === status.checkNo || change.status === status.revise) && accountId === change.uid);
  479. change.shenpiPower = change.status === status.checking && change.curAuditorIds.indexOf(accountId) !== -1;
  480. ctx.change = change;
  481. await ctx.service[curChangeMap.service].doCheckChangeCanCancel(ctx.change);
  482. } catch (error) {
  483. this.log(error);
  484. }
  485. }
  486. async changeDetail(ctx) {
  487. try {
  488. const type = ctx.request.query.type;
  489. const curChangeMap = changeMap[type];
  490. await this.changeCheck(ctx, curChangeMap);
  491. const tpUnit = ctx.tender.info.decimal.tp;
  492. const subProject = await ctx.service.subProject.getDataById(ctx.tender.data.spid);
  493. const fun_set = subProject.fun_set;
  494. const originAuditHistory = ctx.change.auditHistory;
  495. const currentAuditHistory = Array.isArray(originAuditHistory) && originAuditHistory.length
  496. ? originAuditHistory[originAuditHistory.length - 1]
  497. : [];
  498. const auditHistory = currentAuditHistory.map(curr => {
  499. // 普通变更的原报记录保存在审批表中,usort/usite 为 0;其余变更类型使用 audit_order 判断。
  500. const isReporter = curr.audit_order === 0 || curr.auditors.some(a => {
  501. return type === 'change' && (Number(a.usort) === 0 || Number(a.usite) === 0);
  502. });
  503. return {
  504. name: isReporter ? '原报' : curr.is_final ? '终审' : `${curr.audit_order}审`,
  505. audit_type: curr.audit_type,
  506. status: curr.status,
  507. auditors: curr.auditors.map(a => ({...a, audit_id: a.uid || a.aid, opinion: a.opinion || a.sdesc})),
  508. };
  509. });
  510. // 立项、申请、方案的审批历史不包含原报记录,需要根据变更创建人补齐。
  511. const hasReporter = auditHistory.some(group => {
  512. return group.name === '原报' || group.auditors.some(a => Number(a.audit_id) === Number(ctx.change.uid) && Number(a.audit_order) === 0);
  513. });
  514. if (!hasReporter) {
  515. const status = auditConst[type].status;
  516. const firstAudit = currentAuditHistory.length && currentAuditHistory[0].auditors.length
  517. ? currentAuditHistory[0].auditors[0]
  518. : null;
  519. const reporterStatus = ctx.change.status === status.uncheck ? status.uncheck : status.checked;
  520. const reporter = {
  521. audit_id: ctx.change.uid,
  522. audit_order: 0,
  523. audit_type: auditConst.auditType.key.common,
  524. status: reporterStatus,
  525. times: firstAudit ? firstAudit.times : ctx.change.times,
  526. begin_time: ctx.change.in_time || ctx.change.create_time || null,
  527. end_time: firstAudit ? firstAudit.begin_time : null,
  528. name: ctx.change.user.name,
  529. company: ctx.change.user.company,
  530. role: ctx.change.user.role,
  531. mobile: ctx.change.user.mobile || '',
  532. opinion: '',
  533. };
  534. auditHistory.unshift({
  535. name: '原报',
  536. audit_type: reporter.audit_type,
  537. status: reporter.status,
  538. auditors: [reporter],
  539. });
  540. }
  541. const curAuditors = ctx.change.curAuditors;
  542. const change = {
  543. id: ctx.change.id || ctx.change.cid,
  544. tid: ctx.tender.id,
  545. name: ctx.change.name,
  546. status: ctx.change.status,
  547. code: ctx.change.code,
  548. p_code: ctx.change.p_code,
  549. quality: ctx.change.quality,
  550. auditHistory,
  551. originAuditHistory,
  552. curAuditors: curAuditors.map(a => ({...a, audit_id: a.uid || a.aid})),
  553. total_price: ctx.helper.add(ctx.change.total_price, tpUnit),
  554. valuation_tp: ctx.helper.add(ctx.change.valuation_tp, tpUnit),
  555. unvaluation_tp: ctx.helper.add(ctx.change.unvaluation_tp, tpUnit),
  556. state_name: subProject.page_show.openChangeState ? ctx.helper._.find(fun_set.change_state, { order: ctx.change.state }).name : '',
  557. plan_code: ctx.change.plan_code,
  558. peg: ctx.change.peg,
  559. org_name: ctx.change.org_name,
  560. };
  561. ctx.body = { code: 0, msg: '', data: {
  562. change,
  563. } };
  564. } catch (error) {
  565. this.log(error);
  566. ctx.body = { code: -1, msg: error.toString(), data: null };
  567. }
  568. }
  569. async changeAuditCheck(ctx) {
  570. const id = ctx.request.body.id;
  571. if (!id) {
  572. throw '您访问的变更数据不存在';
  573. }
  574. const type = ctx.request.body.type;
  575. const curChangeMap = changeMap[type];
  576. if (!ctx.change) {
  577. const change = await this.service[curChangeMap.service].getDataByCondition({
  578. [curChangeMap.idKey]: id,
  579. });
  580. if (!change) throw '变更数据有误';
  581. await this.service[curChangeMap.service].loadChangeUser(change);
  582. ctx.change = change;
  583. }
  584. const change = ctx.change;
  585. const status = auditConst[type].status;
  586. if ((change.status === status.uncheck || change.status === status.checkNo || change.status === status.revise) && ctx.tender.info.shenpi.change !== shenpiConst.sp_status.sqspr) {
  587. const shenpi_status = ctx.tender.info.shenpi.change;
  588. // 进一步比较审批流是否与审批流程设置的相同,不同则替换为固定审批流或固定的终审
  589. const auditList = await ctx.service[curChangeMap.auditService].getAllDataByCondition({ where: { caid: change.id, times: change.times }, orders: [['order', 'asc']] });
  590. if (shenpi_status === shenpiConst.sp_status.gdspl) {
  591. // 判断并获取审批组
  592. const group = await ctx.service.shenpiGroup.getSelectGroupByChangeType(ctx.tender.id, shenpiConst.sp_type.change, type, change.sp_group);
  593. if ((group && change.sp_group !== group.id) || (!group && change.sp_group !== 0)) {
  594. change.sp_group = group ? group.id : 0;
  595. await ctx.service[curChangeMap.service].defaultUpdate({ id: change.id, sp_group: change.sp_group });
  596. }
  597. const condition = { tid: ctx.tender.id, sp_type: shenpiConst.sp_type.change, sp_status: shenpi_status, sp_group: change.sp_group };
  598. const shenpiList = await ctx.service.shenpiAudit.getAllDataByCondition({ where: condition, orders: [['audit_order', 'asc']] });
  599. await ctx.service.shenpiAudit.noYbShenpiList(change.uid, shenpiList);
  600. if (change.sp_group === 0 && shenpiList.length === 0) {
  601. ctx.tender.info.shenpi.change = shenpiConst.sp_status.sqspr;
  602. } else {
  603. // 判断2个id数组是否相同,不同则删除原审批流,切换成固定的审批流
  604. let sameAudit = auditList.length === shenpiList.length;
  605. if (sameAudit) {
  606. for (const audit of auditList) {
  607. const shenpi = shenpiList.find(x => { return x.audit_id === audit.aid; });
  608. if (!shenpi || shenpi.audit_order !== audit.audit_order || shenpi.audit_type !== audit.audit_type) {
  609. sameAudit = false;
  610. break;
  611. }
  612. }
  613. }
  614. if (!sameAudit) {
  615. await ctx.service[curChangeMap.auditService].updateNewAuditList(change, shenpiList);
  616. await ctx.service[curChangeMap.service].loadChangeUser(change);
  617. await ctx.service[curChangeMap.service].doCheckChangeCanCancel(change);
  618. }
  619. }
  620. } else if (shenpi_status === shenpiConst.sp_status.gdzs) {
  621. const shenpiInfo = await ctx.service.shenpiAudit.getDataByCondition({ tid: ctx.tender.id, sp_type: shenpiConst.sp_type.change, sp_status: shenpi_status });
  622. if (!shenpiInfo || (shenpiInfo && shenpiInfo.audit_id === change.uid)) {
  623. // 不存在终审人 或 存在终审但与原报相同时, 这里恢复为授权审批人
  624. ctx.tender.info.shenpi.change = shenpiConst.sp_status.sqspr;
  625. } else {
  626. // 判断最后一个id是否与固定终审id相同,不同则删除原审批流中如果存在的id和添加终审
  627. const lastAuditors = auditList.filter(x => {
  628. return x.order === auditList.length - 1;
  629. });
  630. if (shenpiInfo && (lastAuditors.length === 0 || (lastAuditors.length > 1 || shenpiInfo.audit_id !== lastAuditors[0].aid))) {
  631. await ctx.service[curChangeMap.auditService].updateLastAudit(change, auditList, shenpiInfo.audit_id);
  632. await ctx.service[curChangeMap.service].loadChangeUser(change);
  633. await ctx.service[curChangeMap.service].doCheckChangeCanCancel(change);
  634. }
  635. }
  636. }
  637. }
  638. }
  639. async changeAudit(ctx) {
  640. try {
  641. const type = ctx.request.body.type;
  642. const curChangeMap = changeMap[type];
  643. await this.changeCheck(ctx, curChangeMap);
  644. await this.changeAuditCheck(ctx);
  645. if (!ctx.change || ctx.change.status !== auditConst[type].status.checking) {
  646. throw '当前变更申请数据有误';
  647. }
  648. if (ctx.change.curAuditorIds.length === 0 || ctx.change.curAuditorIds.indexOf(ctx.session.sessionUser.accountId) === -1) {
  649. throw '您无权进行该操作';
  650. }
  651. const data = {
  652. checkType: parseInt(ctx.request.body.checkType),
  653. opinion: ctx.request.body.opinion,
  654. };
  655. if (!data.checkType || isNaN(data.checkType)) {
  656. throw '提交数据错误';
  657. }
  658. if (curChangeMap.auditService === 'change') {
  659. // 单独处理变更令审批
  660. const status = parseInt(data.checkType);
  661. // 判断是否到你审批,如果不是则无法审批
  662. const curAuditor = await ctx.service.changeAudit.getCurAuditors(ctx.change.cid, ctx.change.times);
  663. if (!curAuditor || (curAuditor && ctx.helper._.findIndex(curAuditor, { uid: ctx.session.sessionUser.accountId }) === -1)) {
  664. throw '该变更令当前您无权操作';
  665. }
  666. const readySettle = await ctx.service.settle.getReadySettle(ctx.change.tid);
  667. if (readySettle && readySettle.settle_order !== ctx.tender.data.settle_order) {
  668. throw '结算数据发生变化,请刷新页面再提交';
  669. }
  670. let result = false;
  671. const pid = this.ctx.session.sessionProject.id;
  672. const checkData = {
  673. checkType: status,
  674. sdesc: data.opinion,
  675. p_code: typeof ctx.request.body.p_code === 'string' ? ctx.request.body.p_code.trim() : '',
  676. }
  677. switch (status) {
  678. case 3:// 审批通过
  679. if (!checkData.p_code) {
  680. throw '请输入变更令号(批复编号)';
  681. }
  682. const revising = await ctx.service.change.checkRevising(ctx.change.tid, ctx.change.cid);
  683. if (revising) {
  684. throw '台账修订中并本变更令存在新增清单,无法审批通过';
  685. }
  686. result = await ctx.service.change.approvalSuccess(pid, checkData, ctx.change);
  687. break;
  688. // case 4:// 审批终止
  689. // result = await ctx.service.change.approvalStop(ctx.request.body);
  690. // break;
  691. case 5:// 审批退回到原报人
  692. result = await ctx.service.change.approvalCheckNo(pid, checkData, ctx.change);
  693. break;
  694. case 6:// 审批退回到上一个审批人
  695. result = await ctx.service.change.approvalCheckNoPre(pid, checkData, ctx.change);
  696. break;
  697. default:break;
  698. }
  699. if (!result) {
  700. throw '审批失败';
  701. }
  702. } else {
  703. await ctx.service[curChangeMap.auditService].check(ctx.change.id, data, ctx.change.times);
  704. }
  705. ctx.body = { code: 0, msg: '操作成功', data: null };
  706. } catch (error) {
  707. this.log(error);
  708. ctx.body = { code: -1, msg: error.toString(), data: null };
  709. }
  710. }
  711. async reviseList(ctx) {
  712. try {
  713. const tenderId = ctx.tender.id;
  714. const show_revise_invalid = 0;
  715. const ledgerRevise = await ctx.service.ledgerRevise.getReviseList(tenderId, show_revise_invalid);
  716. ctx.body = { code: 0, msg: '', data: ledgerRevise };
  717. } catch (err) {
  718. this.log(err);
  719. ctx.body = { code: -1, msg: err.toString(), data: null };
  720. }
  721. }
  722. async reviseDetail(ctx) {
  723. try {
  724. const { rid } = ctx.query;
  725. const revise = rid
  726. ? await ctx.service.ledgerRevise.getRevise(ctx.tender.id, rid)
  727. : await ctx.service.ledgerRevise.getLastestRevise(ctx.tender.id);
  728. if (!revise) throw '修订数据不存在或已失效';
  729. revise.preHis = revise.pre_his_id ? await ctx.service.ledgerHistory.getDataById(revise.pre_his_id) : null;
  730. revise.curHis = revise.his_id ? await ctx.service.ledgerHistory.getDataById(revise.his_id) : null;
  731. await ctx.service.reviseAudit.loadReviseUser(revise);
  732. await ctx.service.reviseAudit.loadReviseAuditViewData(revise);
  733. revise.auditHistory = revise.auditors.reduce((prev, curr, idx) => {
  734. if (idx === 0) {
  735. const reportor = {
  736. audit_id: revise.uid,
  737. audit_order: 0,
  738. audit_type: 1,
  739. status: revise.status === auditConst.revise.status.uncheck ? auditConst.revise.status.uncheck : auditConst.revise.status.checked,
  740. times: curr.times,
  741. begin_time: revise.in_time,
  742. end_time: revise.auditors.length ? revise.auditors[0].begin_time : null,
  743. name: revise.user.name,
  744. company: revise.user.company,
  745. role: revise.user.role,
  746. mobile: '',
  747. opinion: '',
  748. };
  749. prev.push({
  750. name: '原报',
  751. status: reportor.status,
  752. audit_type: reportor.audit_type,
  753. auditors: [reportor],
  754. });
  755. }
  756. const currentOrder = curr.audit_order;
  757. prev.push(
  758. {
  759. name: currentOrder === curr.max_sort ? '终审' : `${currentOrder}审`,
  760. status: curr.status,
  761. audit_type: 1,
  762. auditors: [{
  763. audit_id: curr.audit_id,
  764. audit_order: curr.audit_order,
  765. audit_type: curr.audit_type,
  766. status: curr.status,
  767. max_sort: curr.max_sort,
  768. begin_time: curr.begin_time,
  769. end_time: curr.end_time,
  770. name: curr.name,
  771. company: curr.company,
  772. mobile: curr.mobile,
  773. opinion: curr.opinion,
  774. role: curr.role,
  775. times: curr.times,
  776. }],
  777. }
  778. );
  779. return prev;
  780. }, []);
  781. const GclGather = require('../lib/gcl_gather');
  782. const gclGatherModal = new GclGather.gclCompareGather(this.ctx);
  783. const spec = { zlj: stdConst.zlj, jrg: stdConst.jrg };
  784. spec.zlj.deal_bills_tp = ctx.tender.info.deal_param.zanLiePrice;
  785. gclGatherModal.init(ctx.tender.info.chapter, spec);
  786. const reviseBillsData = revise.readOnly && revise.curHis
  787. ? await ctx.helper.loadLedgerDataFromOss(revise.curHis.bills_file)
  788. : await ctx.service.reviseBills.getAllDataByCondition({ where: { tender_id: ctx.tender.id } });
  789. const revisePosData = revise.readOnly && revise.curHis
  790. ? await this.ctx.helper.loadLedgerDataFromOss(revise.curHis.pos_file)
  791. : await ctx.service.revisePos.getAllDataByCondition({ where: { tid: ctx.tender.id } });
  792. const price = await this.ctx.service.revisePrice.getAllDataByCondition({ where: { rid: revise.id } });
  793. gclGatherModal.gatherReviseLedgerData(reviseBillsData, revisePosData, { prefix: 'new_' }, price, ctx.tender.info.decimal);
  794. const billsData = revise.preHis ? await this.ctx.helper.loadLedgerDataFromOss(revise.preHis.bills_file) : [];
  795. const posData = revise.preHis ? await this.ctx.helper.loadLedgerDataFromOss(revise.preHis.pos_file) : [];
  796. gclGatherModal.gatherLedgerData(billsData, posData, { prefix: 'org_' });
  797. revise.chapterList = gclGatherModal.chapterData();
  798. revise.new_tp = gclGatherModal.otherChapter.hj.new_total_price || 0;
  799. revise.org_tp = gclGatherModal.otherChapter.hj.org_total_price || 0;
  800. revise.tenderName = ctx.tender.data.name;
  801. ctx.body = { code: 0, msg: '', data: { revise } };
  802. } catch (error) {
  803. ctx.log(error);
  804. ctx.body = { code: -1, msg: error.toString(), data: null };
  805. }
  806. }
  807. async reviseCheck(ctx) {
  808. try {
  809. const reviseId = ctx.params.rid;
  810. const revise = await ctx.service.ledgerRevise.getDataById(reviseId);
  811. if (!revise) {
  812. ctx.body = { code: -1, msg: '修订不存在', data: null };
  813. return;
  814. }
  815. if (revise.status !== audit.revise.status.checking) {
  816. ctx.body = {
  817. code: -1,
  818. msg: `修订状态不正确,当前状态为"${audit.revise.statusString[revise.status] || '未知'}",无法审批`,
  819. data: { currentStatus: revise.status }
  820. };
  821. return;
  822. }
  823. revise.curHis = revise.his_id ? await ctx.service.ledgerHistory.getDataById(revise.his_id) : null;
  824. const curAudits = await ctx.service.reviseAudit.getCurAuditors(revise.id, revise.times);
  825. const curAuditorIds = curAudits.map(x => { return x.audit_id; });
  826. if (curAuditorIds.indexOf(ctx.session.sessionUser.accountId) < 0) {
  827. ctx.body = { code: -1, msg: '您不是当前审批人,无权进行该操作', data: null };
  828. return;
  829. }
  830. const data = ctx.request.body;
  831. const checkType = parseInt(data.checkType);
  832. if (!checkType || isNaN(checkType)) {
  833. ctx.body = { code: -1, msg: '提交数据错误:缺少审批类型参数', data: null };
  834. return;
  835. }
  836. const opinion = data.opinion || '';
  837. await ctx.service.reviseAudit.check(revise, checkType, opinion, revise.times);
  838. ctx.body = { code: 0, msg: '审批成功', data: null };
  839. } catch (err) {
  840. ctx.body = { code: -1, msg: err.toString(), data: null };
  841. }
  842. }
  843. }
  844. return WeappTenderController;
  845. };