safe_stage.js 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399
  1. 'use strict';
  2. /**
  3. *
  4. *
  5. * @author Mai
  6. * @date
  7. * @version
  8. */
  9. const audit = require('../const/audit').common;
  10. const auditType = require('../const/audit').auditType;
  11. const shenpiConst = require('../const/shenpi');
  12. module.exports = app => {
  13. class SafeStage extends app.BaseService {
  14. /**
  15. * 构造函数
  16. *
  17. * @param {Object} ctx - egg全局变量
  18. * @return {void}
  19. */
  20. constructor(ctx) {
  21. super(ctx);
  22. this.tableName = 'safe_stage';
  23. }
  24. calculateStage(data) {
  25. const helper = this.ctx.helper;
  26. const datas = data instanceof Array ? data : [data];
  27. const formatNum = this.ctx.tender.info.display.thousandth ? this.ctx.helper.formatNum : function(num) { return num ? num + '' : ''; };
  28. datas.forEach(x => {
  29. x.end_tp = helper.add(x.tp, x.pre_tp);
  30. for (const prop in x) {
  31. if (prop.indexOf('_tp') > 0) {
  32. x['display_' + prop] = formatNum(x[prop]);
  33. }
  34. }
  35. });
  36. }
  37. /**
  38. * 获取全部修订
  39. * @param tid
  40. * @returns {Promise<*>}
  41. */
  42. async getAllStages (tid, sort = 'ASC') {
  43. // const result = await this.getAllDataByCondition({
  44. // where: { tid: tid },
  45. // orders: [['stage_order', sort]],
  46. // });
  47. const result = await this.db.query(`SELECT ss.*, pa.name AS user_name FROM ${this.tableName} ss LEFT JOIN ${this.ctx.service.projectAccount.tableName} pa ON ss.create_user_id = pa.id WHERE ss.tid = ? ORDER BY ss.stage_order ${sort}`, [tid])
  48. return result;
  49. }
  50. async getStage(id) {
  51. const result = await this.getDataById(id);
  52. result.decimal = result.bills_decimal ? JSON.parse(result.bills_decimal) : { qty: 3, tp: 2, up: 2};
  53. return result;
  54. }
  55. async getStageByOrder(tid, stage_order) {
  56. const result = await this.getDataByCondition({ tid, stage_order });
  57. result.decimal = result.bills_decimal ? JSON.parse(result.bills_decimal) : { qty: 3, tp: 2, up: 2};
  58. return result;
  59. }
  60. async getMaxOrder(tid) {
  61. const sql = 'SELECT Max(`stage_order`) As max_order FROM ' + this.tableName + ' Where `tid` = ?';
  62. const sqlParam = [tid];
  63. const result = await this.db.queryOne(sql, sqlParam);
  64. return result.max_order || 0;
  65. }
  66. async add(tid, stage_code, stage_date) {
  67. if (!tid) throw '数据错误';
  68. const user_id = this.ctx.session.sessionUser.accountId;
  69. const maxOrder = await this.getMaxOrder(tid);
  70. const data = {
  71. id: this.uuid.v4(), tid: tid, create_user_id: user_id, update_user_id: user_id,
  72. stage_order: maxOrder + 1, stage_code, stage_date,
  73. audit_times: 1, audit_status: audit.status.uncheck,
  74. bills_decimal: JSON.stringify({ up: 2, tp: 2, qty: 3 }),
  75. };
  76. const preStage = maxOrder > 0 ? await this.getStageByOrder(tid, maxOrder) : null;
  77. if (preStage) {
  78. data.pre_bills_tp = this.ctx.helper.add(preStage.bills_tp, preStage.pre_bills_tp);
  79. }
  80. const transaction = await this.db.beginTransaction();
  81. try {
  82. const result = await transaction.insert(this.tableName, data);
  83. if (result.affectedRows !== 1) throw '新增安全计量期失败';
  84. await this.ctx.service.safeStageBills.initStageBills(transaction, data, preStage);
  85. await this.ctx.service.safeStageAudit.copyPreAuditors(transaction, preStage, data);
  86. await transaction.commit();
  87. return data;
  88. } catch(err) {
  89. await transaction.rollback();
  90. throw err;
  91. }
  92. }
  93. async delete(id) {
  94. // const info = await this.getDataById(id);
  95. const conn = await this.db.beginTransaction();
  96. try {
  97. await conn.delete(this.tableName, { id });
  98. await conn.delete(this.ctx.service.safeStageBills.tableName, { stage_id: id });
  99. const files = await this.ctx.service.safeStageFile.getFiles({ where: { stage_id: id} });
  100. for (const f of files) {
  101. this.ctx.app.fujianOss.delete(f.filepath);
  102. }
  103. await conn.delete(this.ctx.service.safeStageFile.tableName, { stage_id: id });
  104. await conn.delete(this.ctx.service.safeStageAudit.tableName, { stage_id: id });
  105. // 记录删除日志
  106. // await this.ctx.service.projectLog.addProjectLog(conn, projectLogConst.type.safeStage, projectLogConst.status.delete, `第${info.stage_order}期`);
  107. await conn.commit();
  108. } catch (err) {
  109. await conn.rollback();
  110. throw err;
  111. }
  112. }
  113. async save(stage, data) {
  114. await this.defaultUpdate({ id: stage.id, stage_date: data.stage_date, stage_code: data.stage_code });
  115. }
  116. async loadUser(safeStage) {
  117. safeStage.user = await this.ctx.service.projectAccount.getAccountInfoById(safeStage.create_user_id);
  118. safeStage.auditors = await this.ctx.service.safeStageAudit.getAuditors(safeStage.id, safeStage.curTimes || safeStage.audit_times);
  119. safeStage.auditorIds = this._.map(safeStage.auditors, 'audit_id');
  120. safeStage.curAuditors = safeStage.auditors.filter(x => { return x.audit_status === audit.status.checking; });
  121. safeStage.curAuditorIds = safeStage.curAuditors.map(x => { return x.audit_id; });
  122. safeStage.flowAuditors = safeStage.curAuditors.length === 0 ? [] : safeStage.auditors.filter(x => { return x.active_order === safeStage.curAuditors[0].active_order; });
  123. safeStage.flowAuditorIds = safeStage.flowAuditors.map(x => { return x.audit_id; });
  124. safeStage.nextAuditors = safeStage.curAuditors.length > 0 ? safeStage.auditors.filter(x => { return x.active_order === safeStage.curAuditors[0].active_order + 1; }) : [];
  125. safeStage.nextAuditorIds = this._.map(safeStage.nextAuditors, 'audit_id');
  126. safeStage.auditorGroups = this.ctx.helper.groupAuditors(safeStage.auditors, 'active_order');
  127. safeStage.userGroups = this.ctx.helper.groupAuditorsUniq(safeStage.auditorGroups);
  128. safeStage.finalAuditorIds = safeStage.userGroups.length > 1 ? safeStage.userGroups[safeStage.userGroups.length - 1].map(x => { return x.audit_id; }) : [];
  129. safeStage.userIds = safeStage.audit_status === audit.status.uncheck // 当前流程下全部参与人id
  130. ? [safeStage.user_id]
  131. : safeStage.auditorIds;
  132. if (safeStage.audit_status === audit.status.checkNo) {
  133. safeStage.checkNoAuditors = await this.ctx.service.safeStageAudit.getAuditorsByStatus(safeStage.id, audit.status.checkNo, safeStage.audit_times-1);
  134. }
  135. }
  136. async loadAuditViewData(safeStage) {
  137. if (!safeStage.user) safeStage.user = await this.ctx.service.projectAccount.getAccountInfoById(safeStage.user_id);
  138. const auditTimes = safeStage.audit_status === audit.status.checkNo ? safeStage.audit_times - 1 : safeStage.audit_times;
  139. safeStage.auditHistory = await this.ctx.service.safeStageAudit.getAuditorHistory(safeStage.id, auditTimes);
  140. // 获取审批流程中左边列表
  141. if (safeStage.audit_status === audit.status.checkNo && safeStage.create_user_id !== this.ctx.session.sessionUser.accountId) {
  142. const auditors = await this.ctx.service.safeStageAudit.getAuditors(safeStage.id, safeStage.audit_times - 1); // 全部参与的审批人
  143. const auditorGroups = this.ctx.helper.groupAuditors(auditors);
  144. safeStage.hisUserGroup = this.ctx.helper.groupAuditorsUniq(auditorGroups);
  145. } else {
  146. safeStage.hisUserGroup = safeStage.userGroups;
  147. }
  148. }
  149. /**
  150. * cancancel = 0 不可撤回
  151. * cancancel = 1 原报撤回
  152. * cancancel = 2 审批人撤回 审批通过
  153. * cancancel = 3 审批人撤回 审批退回上一人
  154. * cancancel = 4 审批人撤回 退回原报
  155. * cancancel = 5 会签未全部审批通过时,审批人撤回 审批通过
  156. *
  157. * @param safeStage
  158. * @returns {Promise<void>}
  159. */
  160. async doCheckCanCancel(safeStage) {
  161. // 默认不可撤回
  162. safeStage.cancancel = 0;
  163. // 获取当前审批人的上一个审批人,判断是否是当前登录人,并赋予撤回功能,(当审批人存在有审批过时,上一人不允许再撤回)
  164. const status = audit.status;
  165. if (safeStage.audit_status === status.checked || safeStage.audit_status === status.uncheck) return;
  166. const accountId = this.ctx.session.sessionUser.accountId;
  167. if (safeStage.audit_status !== status.checkNo) {
  168. // 找出当前操作人上一个审批人,包括审批完成的和退回上一个审批人的,同时当前操作人为第一人时,就是则为原报
  169. if (safeStage.flowAuditors.find(x => { return x.audit_status !== status.checking}) && safeStage.flowAuditorIds.indexOf(accountId) < 0) return; // 当前流程存在审批人审批通过时,不可撤回
  170. if (safeStage.curAuditorIds.indexOf(accountId) < 0 && safeStage.flowAuditorIds.indexOf(accountId) >= 0) {
  171. safeStage.cancancel = 5; // 会签未全部审批通过时,审批人撤回审批通过
  172. return;
  173. }
  174. const preAuditors = safeStage.curAuditors[0] && safeStage.curAuditors[0].active_order !== 1 ? safeStage.auditors.filter(x => { return x.active_order === safeStage.curAuditors[0].active_order - 1; }) : [];
  175. const preAuditorCheckAgain = preAuditors.find(pa => { return pa.audit_status === status.checkAgain; });
  176. const preAuditorCheckCancel = preAuditors.find(pa => { return pa.audit_status === status.checkCancel; });
  177. const preAuditorHasOld = preAuditors.find(pa => { return pa.is_old === 1; });
  178. const preAuditorIds = (preAuditorCheckAgain ? [] : preAuditors.map(x => { return x.audit_id })); // 重审不可撤回
  179. if ((this._.isEqual(safeStage.flowAuditorIds, preAuditorIds) && preAuditorCheckCancel) || preAuditorHasOld) {
  180. return; // 不可以多次撤回
  181. }
  182. const preAuditChecked = preAuditors.find(pa => { return pa.audit_status === status.checked && pa.audit_id === accountId; });
  183. const preAuditCheckNoPre = preAuditors.find(pa => { return pa.audit_status === status.checkNoPre && pa.audit_id === accountId; });
  184. if (preAuditorIds.indexOf(accountId) >= 0) {
  185. if (preAuditChecked) {
  186. safeStage.cancancel = 2;// 审批人撤回审批通过
  187. } else if (preAuditCheckNoPre) {
  188. safeStage.cancancel = 3;// 审批人撤回审批退回上一人
  189. }
  190. safeStage.preAuditors = preAuditors;
  191. } else if (preAuditors.length === 0 && accountId === safeStage.create_user_id) {
  192. safeStage.cancancel = 1;// 原报撤回
  193. }
  194. } else {
  195. const lastAuditors = await this.ctx.service.safeStageAudit.getAuditors(safeStage.id, safeStage.audit_times - 1);
  196. const onAuditor = this._.findLast(lastAuditors, { audit_status: status.checkNo });
  197. if (onAuditor.audit_id === accountId) {
  198. safeStage.cancancel = 4;// 审批人撤回退回原报
  199. safeStage.preAuditors = lastAuditors.filter(x => { return x.active_order === onAuditor.active_order });
  200. }
  201. }
  202. }
  203. async doCheckStage(safeStage) {
  204. const accountId = this.ctx.session.sessionUser.accountId;
  205. // 审批退回时,原报读取本轮流程,其他人读取上一轮流程
  206. if (safeStage.audit_status === audit.status.checkNo) {
  207. safeStage.curTimes = safeStage.create_user_id === accountId ? safeStage.audit_times : safeStage.audit_times - 1;
  208. } else {
  209. safeStage.curTimes = safeStage.audit_times;
  210. }
  211. // 加载参与人
  212. await this.loadUser(safeStage);
  213. if (safeStage.audit_status === audit.status.uncheck) {
  214. safeStage.readOnly = accountId !== safeStage.create_user_id;
  215. safeStage.curSort = 0;
  216. } else if (safeStage.audit_status === audit.status.checkNo) {
  217. safeStage.readOnly = accountId !== safeStage.create_user_id;
  218. if (!safeStage.readOnly) {
  219. safeStage.curSort = 0;
  220. } else {
  221. const checkNoAudit = await this.service.safeStageAudit.getDataByCondition({
  222. stage_id: safeStage.id, audit_times: safeStage.audit_times - 1, audit_status: audit.status.checkNo,
  223. });
  224. safeStage.curSort = checkNoAudit.active_order;
  225. }
  226. } else if (safeStage.audit_status === audit.status.checked) {
  227. safeStage.readOnly = true;
  228. safeStage.curSort = safeStage.audit_max_sort;
  229. } else {
  230. // 会签,会签人部分审批通过时,只读,但是curSort需按原来的取值
  231. safeStage.curSort = safeStage.flowAuditorIds.indexOf(accountId) >= 0 ? safeStage.curAuditors[0].active_order : safeStage.curAuditors[0].active_order - 1;
  232. safeStage.readOnly = safeStage.curAuditorIds.indexOf(accountId) < 0;
  233. safeStage.canCheck = safeStage.readOnly && safeStage.curAuditorIds.indexOf(accountId) > 0;
  234. }
  235. await this.doCheckCanCancel(safeStage);
  236. }
  237. async checkShenpi(safeStage) {
  238. const status = audit.status;
  239. const info = this.ctx.tender.info;
  240. const shenpi_status = info.shenpi.safe_payment;
  241. if ((safeStage.audit_status === status.uncheck || safeStage.audit_status === status.checkNo) && shenpi_status !== shenpiConst.sp_status.sqspr) {
  242. // 进一步比较审批流是否与审批流程设置的相同,不同则替换为固定审批流或固定的终审
  243. const auditList = await this.ctx.service.safeStageAudit.getAllDataByCondition({ where: { stage_id: safeStage.id, audit_times: safeStage.audit_times }, orders: [['audit_order', 'asc']] });
  244. auditList.shift();
  245. if (shenpi_status === shenpiConst.sp_status.gdspl) {
  246. const shenpiList = await this.ctx.service.shenpiAudit.getAllDataByCondition({ where: { tid: safeStage.tid, sp_type: shenpiConst.sp_type.safe_payment, sp_status: shenpi_status } });
  247. // 判断2个id数组是否相同,不同则删除原审批流,切换成固定的审批流
  248. let sameAudit = auditList.length === shenpiList.length;
  249. if (sameAudit) {
  250. for (const audit of auditList) {
  251. const shenpi = shenpiList.find(x => { return x.audit_id === audit.audit_id; });
  252. if (!shenpi || shenpi.audit_order !== audit.audit_order || shenpi.audit_type !== audit.audit_type) {
  253. sameAudit = false;
  254. break;
  255. }
  256. }
  257. }
  258. if (!sameAudit) {
  259. await this.ctx.service.safeStageAudit.updateNewAuditList(safeStage, shenpiList);
  260. await this.loadUser(safeStage);
  261. }
  262. } else if (shenpi_status === shenpiConst.sp_status.gdzs) {
  263. const shenpiInfo = await this.ctx.service.shenpiAudit.getDataByCondition({ tid: safeStage.tid, sp_type: shenpiConst.sp_type.safe_payment, sp_status: shenpi_status });
  264. // 判断最后一个id是否与固定终审id相同,不同则删除原审批流中如果存在的id和添加终审
  265. const lastAuditors = auditList.filter(x => { x.active_order === auditList.active_order; });
  266. if (shenpiInfo && (lastAuditors.length === 0 || (lastAuditors.length > 1 || shenpiInfo.audit_id !== lastAuditors[0].audit_id))) {
  267. await this.ctx.service.safeStageAudit.updateLastAudit(safeStage, auditList, shenpiInfo.audit_id);
  268. await this.loadUser(safeStage);
  269. } else if (!shenpiInfo) {
  270. // 不存在终审人的状态下这里恢复为授权审批人
  271. this.ctx.tender.info.shenpi.safe_payment = shenpiConst.sp_status.sqspr;
  272. }
  273. }
  274. }
  275. }
  276. async _getUserInfo(id) {
  277. if (!this.cacheUserInfo) this.cacheUserInfo = [];
  278. const cache = this.cacheUserInfo.find(x => { return x.id === id; });
  279. if (cache) return cache;
  280. const user = await this.ctx.service.projectAccount.getDataById(id);
  281. this.cacheUserInfo.push(user);
  282. return user;
  283. }
  284. async copyPaySafeData(payTenderId) {
  285. const details = await this.ctx.service.paymentDetail.getAllDataByCondition({ where: { tender_id: payTenderId, type: 1 }, orders: [['order', 'asc']] });
  286. const tid = this.ctx.tender.id;
  287. const conn = await this.db.beginTransaction();
  288. try {
  289. const insertStage = [], insertBills = [], insertAudit = [], insertFile = [];
  290. for (const detail of details) {
  291. const stage = {
  292. id: this.uuid.v4(), tid, create_user_id: detail.uid, update_user_id: this.ctx.session.sessionUser.accountId,
  293. stage_order: detail.order, stage_code: detail.code, stage_date: detail.s_time,
  294. audit_times: detail.times, audit_status: detail.status,
  295. bills_decimal: detail.bills_decimal || JSON.stringify({ up: 2, tp: 2, qty: 3 }),
  296. create_time: detail.in_time, final_auditor_str: '',
  297. };
  298. insertStage.push(stage);
  299. const safeBills = await this.ctx.service.paymentSafeBills.getAllDataByCondition({ where: { detail_id: detail.id } });
  300. const exist = safeBills.length > 0 ? await this.ctx.service.safeStageBills.getDataById(safeBills[0].id) : null;
  301. if (exist) {
  302. const existTender = await this.ctx.service.tender.getDataById(exist.tender_id);
  303. throw '该标段安全生产费数据已迁移' + (existTender ? `至标段【${existTender.name}】下,请勿重复迁移` : '');
  304. }
  305. let tp = 0, pre_tp = 0;
  306. for (const sb of safeBills) {
  307. sb.tender_id = tid;
  308. delete sb.detail_id;
  309. sb.stage_id = stage.id;
  310. const his = sb.cur_his ? JSON.parse(sb.cur_his) : [];
  311. for (const h of his) {
  312. h.audit_times = h.times;
  313. h.active_order = h.order;
  314. delete h.times;
  315. delete h.order;
  316. }
  317. sb.cur_his = JSON.stringify(his);
  318. if (sb.tree_is_leaf) {
  319. pre_tp = this.ctx.helper.add(pre_tp, sb.pre_tp);
  320. tp = this.ctx.helper.add(tp, sb.cur_tp);
  321. }
  322. insertBills.push(sb);
  323. }
  324. stage.pre_bills_tp = pre_tp;
  325. stage.bills_tp = tp;
  326. const user = await this._getUserInfo(detail.uid);
  327. const audits = await this.ctx.service.paymentDetailAudit.getAllDataByCondition({ where: { td_id: detail.id }, orders: [['times', 'asc'], ['order', 'asc']]});
  328. if (detail.status === audit.status.checked) {
  329. const fa = await this._getUserInfo(audits[audits.length - 1].aid);
  330. stage.final_auditor_str =`${fa.name}${(fa.role ? '-' + fa.role : '')}`;
  331. }
  332. if (audits.length === 0) {
  333. insertAudit.push({
  334. tid, stage_id: stage.id, audit_id: user.id,
  335. name: user.name, company: user.company, role: user.role, mobile: user.mobile,
  336. audit_times: 1, audit_order: 0, active_order: 0, audit_type: auditType.key.common,
  337. });
  338. }
  339. for (const a of audits) {
  340. const auditor = await this._getUserInfo(a.aid);
  341. if (a.order === 1) {
  342. insertAudit.push({
  343. tid, stage_id: stage.id, audit_id: user.id,
  344. name: user.name, company: user.company, role: user.role, mobile: user.mobile,
  345. audit_times: a.times, audit_order: 0, active_order: 0, audit_type: auditType.key.common,
  346. audit_time: a.begin_time, audit_status: audit.status.checked,
  347. });
  348. }
  349. const same = audits.filter(x => { return a.aid === x.aid && a.times === x.times; });
  350. const audit_order = this.ctx.helper._.min(same.map(x => { return x.order}));
  351. insertAudit.push({
  352. tid, stage_id: stage.id, audit_id: auditor.id,
  353. name: auditor.name, company: auditor.company, role: auditor.role, mobile: auditor.mobile,
  354. audit_times: a.times, audit_order, active_order: a.order, audit_type: auditType.key.common,
  355. audit_status: a.status, audit_time: a.end_time, opinion: a.opinion || '',
  356. });
  357. }
  358. const files = await this.ctx.service.paymentDetailAtt.getAllDataByCondition({ where: { td_id: detail.id } });
  359. for (const f of files) {
  360. const fu = await this._getUserInfo(f.uid);
  361. insertFile.push({
  362. id: this.uuid.v4(), tid, stage_id: stage.id, type: 'bills', rela_id: f.safe_id,
  363. filename: f.filename, fileext: f.fileext, filesize: f.filesize, filepath: f.filepath,
  364. user_id: fu.id, user_name: fu.name, user_company: fu.company, user_role: fu.role,
  365. create_time: fu.upload_time, update_time: fu.upload_time,
  366. });
  367. }
  368. }
  369. await conn.insert(this.tableName, insertStage);
  370. if (insertBills.length > 0) await conn.insert(this.ctx.service.safeStageBills.tableName, insertBills);
  371. if (insertAudit.length > 0) await conn.insert(this.ctx.service.safeStageAudit.tableName, insertAudit);
  372. if (insertFile.length > 0) await conn.insert(this.ctx.service.safeStageFile.tableName, insertFile);
  373. await conn.commit();
  374. } catch (err) {
  375. await conn.rollback();
  376. throw err;
  377. }
  378. }
  379. }
  380. return SafeStage;
  381. };