cost_stage.js 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366
  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 CostStage extends app.BaseService {
  14. /**
  15. * 构造函数
  16. *
  17. * @param {Object} ctx - egg全局变量
  18. * @return {void}
  19. */
  20. constructor(ctx) {
  21. super(ctx);
  22. this.tableName = 'cost_stage';
  23. this.stageType = {
  24. duty: { key: 'duty', name: '责任成本', decimal: { tp: 6, tax: 2, excl_tax_tp: 6 }, shenpi_status: 'cost_stage_duty', push_type: 'costStageDuty', dataService: 'costStageDuty' },
  25. ledger: { key: 'ledger', name: '成本报审', decimal: { tp: 6, tax: 2, excl_tax_tp: 6 }, shenpi_status: 'cost_stage_ledger', push_type: 'costStageLedger', dataService: 'costStageLedger', detailService: 'costStageDetail' },
  26. book: { key: 'book', name: '财务账面', decimal: { tp: 6, tax: 2, excl_tax_tp: 6 }, shenpi_status: 'cost_stage_book', push_type: 'costStageBook', dataService: 'costStageBook', detailService: 'costStageBookDetail' },
  27. analysis: { key: 'analysis', name: '成本分析', decimal: { tp: 6, tax: 2, excl_tax_tp: 6 }, shenpi_status: 'cost_stage_analysis', push_type: 'costStageAnalysis', dataService: 'costStageAnalysis', detailService: 'costStageAnalysisDetail' },
  28. };
  29. }
  30. _analysisStage(stage) {
  31. if (!stage) return;
  32. const stages = stage instanceof Array ? stage : [stage];
  33. if (stage.length === 0) return;
  34. const typeInfo = this.stageType[stages[0].stage_type];
  35. stages.forEach(s => {
  36. if (s.decimal) {
  37. s.decimal = this.ctx.helper._.assignIn(typeInfo.decimal, JSON.parse(s.decimal));
  38. } else {
  39. s.decimal = typeInfo.decimal;
  40. }
  41. s.stage_tp = s.stage_tp ? JSON.parse(s.stage_tp) : {};
  42. s.stage_pre_tp = s.stage_pre_tp ? JSON.parse(s.stage_pre_tp) : {};
  43. s.stage_end_tp = {};
  44. for (const prop in s.stage_tp) {
  45. s.stage_end_tp[prop] = this.ctx.helper.add(s.stage_tp[prop], s.stage_pre_tp[prop]);
  46. }
  47. s.typeInfo = this.stageType[s.stage_type];
  48. s.rela_stage = s.rela_stage ? JSON.parse(s.rela_stage) : null;
  49. s.extra_info = s.extra_info ? JSON.parse(s.extra_info) : {};
  50. });
  51. }
  52. async calcRuntime(stage) {
  53. const typeInfo = this.stageType[stage.stage_type];
  54. if (!typeInfo) return;
  55. stage.stage_tp = await this.ctx.service[typeInfo.dataService].getSum(stage);
  56. for (const prop in stage.stage_tp) {
  57. stage.stage_end_tp[prop] = this.ctx.helper.add(stage.stage_tp[prop], stage.stage_pre_tp[prop]);
  58. }
  59. }
  60. /**
  61. * 获取全部期
  62. * @param tid
  63. * @returns {Promise<*>}
  64. */
  65. async getAllStages(tid, stage_type, sort = 'ASC') {
  66. const sql = `SELECT cls.*, pa.name AS user_name FROM ${this.tableName} cls LEFT JOIN ${this.ctx.service.projectAccount.tableName} pa ON cls.create_user_id = pa.id` +
  67. ` WHERE cls.tid = ? and cls.stage_type = ? ORDER BY cls.stage_order ${sort}`;
  68. const result = await this.db.query(sql, [tid, stage_type]);
  69. this._analysisStage(result);
  70. return result;
  71. }
  72. async getAllCheckedStages(tid, stage_type, sort = 'ASC') {
  73. const sql = `SELECT cls.*, pa.name AS user_name FROM ${this.tableName} cls LEFT JOIN ${this.ctx.service.projectAccount.tableName} pa ON cls.create_user_id = pa.id` +
  74. ` WHERE cls.tid = ? AND cls.stage_type = ? AND audit_status = ? ORDER BY cls.stage_order ${sort}`;
  75. const result = await this.db.query(sql, [tid, stage_type, audit.status.checked]);
  76. this._analysisStage(result);
  77. return result;
  78. }
  79. async getStage(id) {
  80. const result = await this.getDataById(id);
  81. this._analysisStage(result);
  82. return result;
  83. }
  84. async getStageByOrder(tid, stage_type, stage_order) {
  85. const result = await this.getDataByCondition({ tid, stage_type, stage_order });
  86. this._analysisStage(result);
  87. return result;
  88. }
  89. async getMaxOrder(tid, stage_type) {
  90. const sql = 'SELECT Max(`stage_order`) As max_order FROM ' + this.tableName + ' Where `tid` = ? and stage_type = ?';
  91. const sqlParam = [tid, stage_type];
  92. const result = await this.db.queryOne(sql, sqlParam);
  93. return result.max_order || 0;
  94. }
  95. async add(tid, stage_type, stage_date, rela_stage) {
  96. const typeInfo = this.stageType[stage_type];
  97. if (!tid) throw '数据错误';
  98. const user_id = this.ctx.session.sessionUser.accountId;
  99. const maxOrder = await this.getMaxOrder(tid, stage_type);
  100. const data = {
  101. id: this.uuid.v4(), tid: tid, create_user_id: user_id, update_user_id: user_id,
  102. stage_order: maxOrder + 1, stage_date, stage_type,
  103. audit_times: 1, audit_status: audit.status.uncheck,
  104. decimal: JSON.stringify({ tp: 6 }),
  105. };
  106. if (stage_type === 'analysis') data.calc_template = this.ctx.subProject.cost_calc_template;
  107. if (rela_stage) data.rela_stage = JSON.stringify(rela_stage);
  108. const preStage = maxOrder > 0 ? await this.getStageByOrder(tid, stage_type, maxOrder) : null;
  109. if (preStage) {
  110. data.stage_pre_tp = JSON.stringify(preStage.stage_end_tp);
  111. data.decimal = JSON.stringify(preStage.decimal);
  112. }
  113. const transaction = await this.db.beginTransaction();
  114. try {
  115. const result = await transaction.insert(this.tableName, data);
  116. if (result.affectedRows !== 1) throw '新增安全计量期失败';
  117. await this.ctx.service[typeInfo.dataService].initStageData(transaction, data, preStage);
  118. await this.ctx.service.costStageAudit.copyPreAuditors(transaction, preStage, data);
  119. await transaction.commit();
  120. } catch(err) {
  121. this.ctx.log(err);
  122. await transaction.rollback();
  123. throw err;
  124. }
  125. return data;
  126. }
  127. async delete(id) {
  128. const stage = await this.getDataById(id);
  129. const typeInfo = this.stageType[stage.stage_type];
  130. const conn = await this.db.beginTransaction();
  131. try {
  132. await conn.delete(this.tableName, { id });
  133. await conn.delete(this.ctx.service[typeInfo.dataService].tableName, { stage_id: id });
  134. await conn.delete(this.ctx.service[typeInfo.detailService].tableName, { stage_id: id });
  135. const files = await this.ctx.service.costStageFile.getFiles({ where: { stage_id: id} });
  136. for (const f of files) {
  137. this.ctx.app.fujianOss.delete(f.filepath);
  138. }
  139. await conn.delete(this.ctx.service.costStageFile.tableName, { stage_id: id });
  140. await conn.delete(this.ctx.service.costStageAudit.tableName, { stage_id: id });
  141. // 记录删除日志
  142. // await this.ctx.service.projectLog.addProjectLog(conn, projectLogConst.type.stage, projectLogConst.status.delete, `第${info.stage_order}期`);
  143. await conn.commit();
  144. } catch (err) {
  145. await conn.rollback();
  146. throw err;
  147. }
  148. }
  149. async save(stage, data) {
  150. await this.defaultUpdate({ id: stage.id, stage_date: data.stage_date, update_user_id: this.ctx.session.sessionUser.accountId });
  151. }
  152. async loadUser(stage) {
  153. stage.user = await this.ctx.service.projectAccount.getAccountInfoById(stage.create_user_id);
  154. stage.auditors = await this.ctx.service.costStageAudit.getAuditors(stage.id, stage.curTimes || stage.audit_times);
  155. stage.auditorIds = this._.map(stage.auditors, 'audit_id');
  156. stage.curAuditors = stage.auditors.filter(x => { return x.audit_status === audit.status.checking; });
  157. stage.curAuditorIds = stage.curAuditors.map(x => { return x.audit_id; });
  158. stage.flowAuditors = stage.curAuditors.length === 0 ? [] : stage.auditors.filter(x => { return x.active_order === stage.curAuditors[0].active_order; });
  159. stage.flowAuditorIds = stage.flowAuditors.map(x => { return x.audit_id; });
  160. stage.nextAuditors = stage.curAuditors.length > 0 ? stage.auditors.filter(x => { return x.active_order === stage.curAuditors[0].active_order + 1; }) : [];
  161. stage.nextAuditorIds = this._.map(stage.nextAuditors, 'audit_id');
  162. stage.auditorGroups = this.ctx.helper.groupAuditors(stage.auditors, 'active_order');
  163. stage.userGroups = this.ctx.helper.groupAuditorsUniq(stage.auditorGroups);
  164. stage.finalAuditorIds = stage.userGroups.length > 1 ? stage.userGroups[stage.userGroups.length - 1].map(x => { return x.audit_id; }) : [];
  165. stage.userIds = stage.audit_status === audit.status.uncheck // 当前流程下全部参与人id
  166. ? [stage.create_user_id]
  167. : stage.auditorIds;
  168. if (stage.audit_status === audit.status.checkNo) {
  169. stage.checkNoAuditors = await this.ctx.service.costStageAudit.getAuditorsByStatus(stage.id, audit.status.checkNo, stage.audit_times-1);
  170. }
  171. }
  172. async loadAuditViewData(stage) {
  173. if (!stage.user) stage.user = await this.ctx.service.projectAccount.getAccountInfoById(stage.user_id);
  174. const auditTimes = stage.audit_status === audit.status.checkNo ? stage.audit_times - 1 : stage.audit_times;
  175. stage.auditHistory = await this.ctx.service.costStageAudit.getAuditorHistory(stage.id, auditTimes);
  176. // 获取审批流程中左边列表
  177. if (stage.audit_status === audit.status.checkNo && stage.create_user_id !== this.ctx.session.sessionUser.accountId) {
  178. const auditors = await this.ctx.service.costStageAudit.getAuditors(stage.id, stage.audit_times - 1); // 全部参与的审批人
  179. const auditorGroups = this.ctx.helper.groupAuditors(auditors);
  180. stage.hisUserGroup = this.ctx.helper.groupAuditorsUniq(auditorGroups);
  181. } else {
  182. stage.hisUserGroup = stage.userGroups;
  183. }
  184. }
  185. /**
  186. * cancancel = 0 不可撤回
  187. * cancancel = 1 原报撤回
  188. * cancancel = 2 审批人撤回 审批通过
  189. * cancancel = 3 审批人撤回 审批退回上一人
  190. * cancancel = 4 审批人撤回 退回原报
  191. * cancancel = 5 会签未全部审批通过时,审批人撤回 审批通过
  192. *
  193. * @param stage
  194. * @returns {Promise<void>}
  195. */
  196. async doCheckCanCancel(stage) {
  197. // 默认不可撤回
  198. stage.cancancel = 0;
  199. // 获取当前审批人的上一个审批人,判断是否是当前登录人,并赋予撤回功能,(当审批人存在有审批过时,上一人不允许再撤回)
  200. const status = audit.status;
  201. if (stage.audit_status === status.checked || stage.audit_status === status.uncheck) return;
  202. const accountId = this.ctx.session.sessionUser.accountId;
  203. if (stage.audit_status !== status.checkNo) {
  204. // 找出当前操作人上一个审批人,包括审批完成的和退回上一个审批人的,同时当前操作人为第一人时,就是则为原报
  205. if (stage.flowAuditors.find(x => { return x.audit_status !== status.checking}) && stage.flowAuditorIds.indexOf(accountId) < 0) return; // 当前流程存在审批人审批通过时,不可撤回
  206. if (stage.curAuditorIds.indexOf(accountId) < 0 && stage.flowAuditorIds.indexOf(accountId) >= 0) {
  207. stage.cancancel = 5; // 会签未全部审批通过时,审批人撤回审批通过
  208. return;
  209. }
  210. const preAuditors = stage.curAuditors[0] && stage.curAuditors[0].active_order !== 1 ? stage.auditors.filter(x => { return x.active_order === stage.curAuditors[0].active_order - 1; }) : [];
  211. const preAuditorCheckAgain = preAuditors.find(pa => { return pa.audit_status === status.checkAgain; });
  212. const preAuditorCheckCancel = preAuditors.find(pa => { return pa.audit_status === status.checkCancel; });
  213. const preAuditorHasOld = preAuditors.find(pa => { return pa.is_old === 1; });
  214. const preAuditorIds = (preAuditorCheckAgain ? [] : preAuditors.map(x => { return x.audit_id })); // 重审不可撤回
  215. if ((this._.isEqual(stage.flowAuditorIds, preAuditorIds) && preAuditorCheckCancel) || preAuditorHasOld) {
  216. return; // 不可以多次撤回
  217. }
  218. const preAuditChecked = preAuditors.find(pa => { return pa.audit_status === status.checked && pa.audit_id === accountId; });
  219. const preAuditCheckNoPre = preAuditors.find(pa => { return pa.audit_status === status.checkNoPre && pa.audit_id === accountId; });
  220. if (preAuditorIds.indexOf(accountId) >= 0) {
  221. if (preAuditChecked) {
  222. stage.cancancel = 2;// 审批人撤回审批通过
  223. } else if (preAuditCheckNoPre) {
  224. stage.cancancel = 3;// 审批人撤回审批退回上一人
  225. }
  226. stage.preAuditors = preAuditors;
  227. } else if (preAuditors.length === 0 && accountId === stage.create_user_id) {
  228. stage.cancancel = 1;// 原报撤回
  229. }
  230. } else {
  231. const lastAuditors = await this.ctx.service.costStageAudit.getAuditors(stage.id, stage.audit_times - 1);
  232. const onAuditor = this._.findLast(lastAuditors, { audit_status: status.checkNo });
  233. if (onAuditor.audit_id === accountId) {
  234. stage.cancancel = 4;// 审批人撤回退回原报
  235. stage.preAuditors = lastAuditors.filter(x => { return x.active_order === onAuditor.active_order });
  236. }
  237. }
  238. }
  239. async doCheckStage(stage) {
  240. const accountId = this.ctx.session.sessionUser.accountId;
  241. // 审批退回时,原报读取本轮流程,其他人读取上一轮流程
  242. if (stage.audit_status === audit.status.checkNo) {
  243. stage.curTimes = stage.create_user_id === accountId ? stage.audit_times : stage.audit_times - 1;
  244. } else {
  245. stage.curTimes = stage.audit_times;
  246. }
  247. // 加载参与人
  248. await this.loadUser(stage);
  249. if (stage.audit_status === audit.status.uncheck) {
  250. stage.readOnly = accountId !== stage.create_user_id;
  251. stage.curSort = 0;
  252. } else if (stage.audit_status === audit.status.checkNo) {
  253. stage.readOnly = accountId !== stage.create_user_id;
  254. if (!stage.readOnly) {
  255. stage.curSort = 0;
  256. } else {
  257. const checkNoAudit = await this.service.costStageAudit.getDataByCondition({
  258. stage_id: stage.id, audit_times: stage.audit_times - 1, audit_status: audit.status.checkNo,
  259. });
  260. stage.curSort = checkNoAudit.active_order;
  261. }
  262. } else if (stage.audit_status === audit.status.checked) {
  263. stage.readOnly = true;
  264. stage.curSort = stage.audit_max_sort;
  265. } else {
  266. // 会签,会签人部分审批通过时,只读,但是curSort需按原来的取值
  267. stage.curSort = stage.flowAuditorIds.indexOf(accountId) >= 0 ? stage.curAuditors[0].active_order : stage.curAuditors[0].active_order - 1;
  268. if (stage.curAuditors[0].audit_type === auditType.key.and) {
  269. stage.readOnly = !this.ctx.helper._.isEqual(stage.flowAuditorIds, stage.curAuditorIds);
  270. stage.canCheck = true;
  271. } else {
  272. stage.readOnly = stage.curAuditorIds.indexOf(accountId) < 0;
  273. stage.canCheck = stage.readOnly && stage.curAuditorIds.indexOf(accountId) > 0;
  274. }
  275. }
  276. await this.doCheckCanCancel(stage);
  277. }
  278. async checkShenpi(stage) {
  279. const status = audit.status;
  280. const info = this.ctx.tender.info;
  281. const typeInfo = this.stageType[stage.stage_type];
  282. const shenpi_status = info.shenpi[typeInfo.shenpi_status];
  283. console.log(typeInfo.shenpi_status, info.shenpi);
  284. if ((stage.audit_status === status.uncheck || stage.audit_status === status.checkNo) && shenpi_status !== shenpiConst.sp_status.sqspr) {
  285. // 进一步比较审批流是否与审批流程设置的相同,不同则替换为固定审批流或固定的终审
  286. const auditList = await this.ctx.service.costStageAudit.getAllDataByCondition({ where: { stage_id: stage.id, audit_times: stage.audit_times }, orders: [['audit_order', 'asc']] });
  287. auditList.shift();
  288. if (shenpi_status === shenpiConst.sp_status.gdspl) {
  289. const shenpiList = await this.ctx.service.shenpiAudit.getAllDataByCondition({ where: { tid: stage.tid, sp_type: shenpiConst.cost_sp_type[typeInfo.shenpi_status], sp_status: shenpi_status } });
  290. // 判断2个id数组是否相同,不同则删除原审批流,切换成固定的审批流
  291. let sameAudit = auditList.length === shenpiList.length;
  292. if (sameAudit) {
  293. for (const audit of auditList) {
  294. const shenpi = shenpiList.find(x => { return x.audit_id === audit.audit_id; });
  295. if (!shenpi || shenpi.audit_order !== audit.audit_order || shenpi.audit_type !== audit.audit_type) {
  296. sameAudit = false;
  297. break;
  298. }
  299. }
  300. }
  301. if (!sameAudit) {
  302. await this.ctx.service.costStageAudit.updateNewAuditList(stage, shenpiList);
  303. await this.loadUser(stage);
  304. }
  305. } else if (shenpi_status === shenpiConst.sp_status.gdzs) {
  306. const shenpiInfo = await this.ctx.service.shenpiAudit.getDataByCondition({ tid: stage.tid, sp_type: shenpiConst.cost_sp_type[typeInfo.shenpi_status], sp_status: shenpi_status });
  307. // 判断最后一个id是否与固定终审id相同,不同则删除原审批流中如果存在的id和添加终审
  308. const lastAuditors = auditList.filter(x => { x.active_order === auditList.active_order; });
  309. if (shenpiInfo && (lastAuditors.length === 0 || (lastAuditors.length > 1 || shenpiInfo.audit_id !== lastAuditors[0].audit_id))) {
  310. await this.ctx.service.costStageAudit.updateLastAudit(stage, auditList, shenpiInfo.audit_id);
  311. await this.loadUser(stage);
  312. } else if (!shenpiInfo) {
  313. // 不存在终审人的状态下这里恢复为授权审批人
  314. this.ctx.tender.info.shenpi[typeInfo.shenpi_status] = shenpiConst.sp_status.sqspr;
  315. }
  316. }
  317. }
  318. }
  319. async getLastCheckedStage(tid, stage_type) {
  320. const lastCheckedStage = await this.ctx.service.costStage.getAllDataByCondition({
  321. where: { tid, stage_type, audit_status: audit.status.checked },
  322. orders: [['stage_order', 'desc']],
  323. });
  324. if (lastCheckedStage && lastCheckedStage.length > 0) {
  325. this._analysisStage(lastCheckedStage[0]);
  326. return lastCheckedStage[0];
  327. }
  328. return null;
  329. }
  330. async getNumByChecked(tenderId, stage_type) {
  331. const num = await this.db.queryOne(`SELECT COUNT(*) as num From ${this.tableName} WHERE tid = ? and stage_type = ? and audit_status = ?`, [tenderId, stage_type, audit.status.checked]);
  332. return num ? num.num : 0;
  333. }
  334. async saveExtraInfo(stage, data) {
  335. const extra_info = this.ctx.helper._.assign(stage.extra_info, data);
  336. try {
  337. await this.defaultUpdate({ id: stage.id, extra_info: JSON.stringify(extra_info) });
  338. return extra_info;
  339. } catch (err) {
  340. throw '保存附加信息错误';
  341. }
  342. }
  343. }
  344. return CostStage;
  345. };