material_audit.js 72 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339
  1. 'use strict';
  2. /**
  3. *
  4. *
  5. * @author Mai
  6. * @date 2019/2/27
  7. * @version
  8. */
  9. const auditConst = require('../const/audit').material;
  10. const auditType = require('../const/audit').auditType;
  11. const pushType = require('../const/audit').pushType;
  12. const pushOperate = require('../const/spec_3f').pushOperate;
  13. const smsTypeConst = require('../const/sms_type');
  14. const wxConst = require('../const/wechat_template');
  15. const shenpiConst = require('../const/shenpi');
  16. const materialConst = require('../const/material');
  17. module.exports = app => {
  18. class MaterialAudit extends app.BaseService {
  19. /**
  20. * 构造函数
  21. *
  22. * @param {Object} ctx - egg全局变量
  23. * @return {void}
  24. */
  25. constructor(ctx) {
  26. super(ctx);
  27. this.tableName = 'material_audit';
  28. }
  29. /**
  30. * 获取 审核人信息
  31. *
  32. * @param {Number} materialId - 材料调差期id
  33. * @param {Number} auditorId - 审核人id
  34. * @param {Number} times - 第几次审批
  35. * @return {Promise<*>}
  36. */
  37. async getAuditor(materialId, auditorId, times = 1) {
  38. const sql = 'SELECT la.`aid`, pa.`name`, pa.`company`, pa.`role`, pa.`mobile`, pa.`telephone`, la.`times`, la.`order`, la.`status`, la.`opinion`, la.`begin_time`, la.`end_time` ' +
  39. ' FROM ?? AS la Left Join ?? AS pa On la.`aid` = pa.`id`' +
  40. ' WHERE la.`mid` = ? and la.`aid` = ? and la.`times` = ?';
  41. const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, materialId, auditorId, times];
  42. return await this.db.queryOne(sql, sqlParam);
  43. }
  44. /**
  45. * 获取 审核列表信息
  46. *
  47. * @param {Number} materialId - 材料调差期id
  48. * @param {Number} times - 第几次审批
  49. * @return {Promise<*>}
  50. */
  51. async getAuditors(materialId, times = 1, order_sort = 'asc') {
  52. const sql = 'SELECT la.id, la.aid, la.times, la.order, la.status, la.opinion, la.begin_time, la.end_time, la.audit_type, la.audit_order,' +
  53. ' pa.name, pa.company, pa.role, pa.mobile, pa.telephone, pa.sign_path' +
  54. ` FROM ${this.tableName} la LEFT JOIN ${this.ctx.service.projectAccount.tableName} pa ON la.aid = pa.id` +
  55. ' WHERE la.mid = ? AND la.times = ?' +
  56. ' ORDER BY la.order ' + order_sort;
  57. const sqlParam = [materialId, times];
  58. const result = await this.db.query(sql, sqlParam);
  59. const max_sort = this._.max(result.map(x => { return x.audit_order; }));
  60. for (const i in result) {
  61. result[i].max_sort = max_sort;
  62. }
  63. return result;
  64. }
  65. async getFinalAuditGroup(materialId, times = 1) {
  66. const sql =
  67. 'SELECT la.`aid`, pa.`name`, pa.`company`, pa.`role`, pa.`mobile`, pa.`telephone`, pa.`sign_path`, la.`times`, la.`mid`, la.`audit_order`, la.`audit_type`, Max(la.`order`) as max_order, GROUP_CONCAT(la.tp_data) as tp_data ' +
  68. ' FROM ?? AS la Left Join ?? AS pa On la.`aid` = pa.`id` ' +
  69. ' WHERE la.`mid` = ? and la.`times` = ? GROUP BY la.`aid` ORDER BY la.`order`';
  70. const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, materialId, times];
  71. const result = await this.db.query(sql, sqlParam);
  72. for (const r of result) {
  73. const auditor = await this.getDataByCondition({mid: materialId, times: r.times, order: r.max_order});
  74. r.status = auditor.status;
  75. r.opinion = auditor.opinion;
  76. r.begin_time = auditor.begin_time;
  77. r.end_time = auditor.end_time;
  78. }
  79. return result;
  80. }
  81. /**
  82. * 获取 当前审核人
  83. *
  84. * @param {Number} materialId - 材料调差期id
  85. * @param {Number} times - 第几次审批
  86. * @return {Promise<*>}
  87. */
  88. async getCurAuditor(materialId, times = 1) {
  89. const sql = 'SELECT la.`id`, la.`aid`, pa.`name`, pa.`company`, pa.`role`, pa.`mobile`, pa.`telephone`, la.`times`, la.`order`, la.`status`, la.`opinion`, la.`begin_time`, la.`end_time`, la.audit_order ' +
  90. ' FROM ?? AS la Left Join ?? AS pa On la.`aid` = pa.`id` ' +
  91. ' WHERE la.`mid` = ? and la.`status` = ? and la.`times` = ?';
  92. const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, materialId, auditConst.status.checking, times];
  93. return await this.db.queryOne(sql, sqlParam);
  94. }
  95. async getCurAuditors(materialId, times = 1) {
  96. const sql =
  97. 'SELECT la.`aid`, pa.`name`, pa.`company`, pa.`role`, pa.`mobile`, pa.`telephone`, la.`times`, la.`order`, la.`status`, la.`opinion`, la.`begin_time`, la.`end_time`, la.audit_type, la.audit_order ' +
  98. ' FROM ?? AS la Left Join ?? AS pa On la.`aid` = pa.`id`' +
  99. ' WHERE la.`mid` = ? and la.`status` = ? and la.`times` = ?';
  100. const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, materialId, auditConst.status.checking, times];
  101. return await this.db.query(sql, sqlParam);
  102. }
  103. /**
  104. * 获取 最新审核顺序
  105. *
  106. * @param {Number} materialId - 材料调差期id
  107. * @param {Number} times - 第几次审批
  108. * @return {Promise<number>}
  109. */
  110. async getNewOrder(materialId, times = 1) {
  111. const sql = 'SELECT Max(??) As max_order, Max(audit_order) As max_audit_order FROM ?? Where `mid` = ? and `times` = ?';
  112. const sqlParam = ['order', this.tableName, materialId, times];
  113. const result = await this.db.queryOne(sql, sqlParam);
  114. return result && result.max_order ? [result.max_order + 1, result.max_audit_order + 1] : [1, 1];
  115. }
  116. /**
  117. * 新增审核人
  118. *
  119. * @param {Number} materialId - 材料调差期id
  120. * @param {Number} auditorId - 审核人id
  121. * @param {Number} times - 第几次审批
  122. * @return {Promise<number>}
  123. */
  124. async addAuditor(materialId, auditorId, times = 1, is_gdzs = 0) {
  125. const transaction = await this.db.beginTransaction();
  126. try {
  127. let [newOrder, newAuditOrder] = await this.getNewOrder(materialId, times);
  128. // 判断是否存在固定终审,存在则newOrder - 1并使终审order+1
  129. newOrder = is_gdzs === 1 ? newOrder - 1 : newOrder;
  130. newAuditOrder = is_gdzs === 1 ? newAuditOrder - 1 : newAuditOrder;
  131. if (is_gdzs) await this._syncOrderByDelete(transaction, materialId, newOrder, times, '+');
  132. const data = {
  133. tid: this.ctx.tender.id,
  134. mid: materialId,
  135. aid: auditorId,
  136. times,
  137. order: newOrder,
  138. status: auditConst.status.uncheck,
  139. audit_order: newAuditOrder,
  140. };
  141. const result = await transaction.insert(this.tableName, data);
  142. await transaction.commit();
  143. return result.effectRows = 1;
  144. } catch (err) {
  145. await transaction.rollback();
  146. throw err;
  147. }
  148. return false;
  149. }
  150. /**
  151. * 移除审核人时,同步其后审核人order
  152. * @param transaction - 事务
  153. * @param {Number} materialId - 材料调差期id
  154. * @param {Number} auditorId - 审核人id
  155. * @param {Number} times - 第几次审批
  156. * @return {Promise<*>}
  157. * @private
  158. */
  159. async _syncOrderByDelete(transaction, materialId, order, times, selfOperate = '-') {
  160. this.initSqlBuilder();
  161. this.sqlBuilder.setAndWhere('mid', {
  162. value: materialId,
  163. operate: '=',
  164. });
  165. this.sqlBuilder.setAndWhere('order', {
  166. value: order,
  167. operate: '>=',
  168. });
  169. this.sqlBuilder.setAndWhere('times', {
  170. value: times,
  171. operate: '=',
  172. });
  173. this.sqlBuilder.setUpdateData('order', {
  174. value: 1,
  175. selfOperate: selfOperate,
  176. });
  177. this.sqlBuilder.setUpdateData('audit_order', {
  178. value: 1,
  179. selfOperate: selfOperate,
  180. });
  181. const [sql, sqlParam] = this.sqlBuilder.build(this.tableName, 'update');
  182. const data = await transaction.query(sql, sqlParam);
  183. return data;
  184. }
  185. /**
  186. * 移除审核人
  187. *
  188. * @param {Number} materialId - 材料调差期id
  189. * @param {Number} auditorId - 审核人id
  190. * @param {Number} times - 第几次审批
  191. * @return {Promise<boolean>}
  192. */
  193. async deleteAuditor(materialId, auditorId, times = 1) {
  194. const transaction = await this.db.beginTransaction();
  195. try {
  196. const condition = { mid: materialId, aid: auditorId, times };
  197. const auditor = await this.getDataByCondition(condition);
  198. if (!auditor) {
  199. throw '该审核人不存在';
  200. }
  201. await transaction.delete(this.tableName, { mid: materialId, order: auditor.order, times});
  202. await this._syncOrderByDelete(transaction, materialId, auditor.order, times);
  203. await transaction.commit();
  204. } catch (err) {
  205. await transaction.rollback();
  206. throw err;
  207. }
  208. return true;
  209. }
  210. async getTpData(transaction, materialId, decimal = (this.ctx.material.decimal ? this.ctx.material.decimal : materialConst.decimal)) {
  211. const materialInfo = await transaction.get(this.ctx.service.material.tableName, { id: materialId });
  212. const tp_data = {
  213. m_tp: materialInfo.m_tp !== null ? this.ctx.helper.round(materialInfo.m_tp, decimal.tp) : null,
  214. tax_tp: materialInfo.material_tax ? (materialInfo.m_tax_tp ? materialInfo.m_tax_tp : materialInfo.m_tp) :
  215. (materialInfo.m_tp !== null ? this.ctx.helper.round(this.ctx.helper.mul(materialInfo.m_tp, 1+materialInfo.rate/100), decimal.tp) : null),
  216. ex_tp: materialInfo.ex_tp !== null ? this.ctx.helper.round(materialInfo.ex_tp, decimal.tp) : null,
  217. ex_tax_tp: materialInfo.ex_tp !== null ? this.ctx.helper.round(this.ctx.helper.mul(materialInfo.ex_tp, 1+materialInfo.exponent_rate/100), decimal.tp) : null,
  218. }
  219. tp_data.total_tp = this.ctx.helper.add(tp_data.m_tp, tp_data.ex_tp);
  220. tp_data.total_tax_tp = !materialInfo.material_tax ? this.ctx.helper.add(tp_data.tax_tp, tp_data.ex_tax_tp) : tp_data.ex_tax_tp;
  221. if (materialInfo.is_stage_self) {
  222. const materialStageList = await transaction.select(this.ctx.service.materialStage.tableName, { where: { mid: materialId } });
  223. const stage_tp = [];
  224. for (const ms of materialStageList) {
  225. stage_tp.push({
  226. id: ms.id,
  227. sid: ms.sid,
  228. order: ms.order,
  229. m_tp: this.ctx.helper.round(ms.m_tp, decimal.tp),
  230. m_tax_tp: this.ctx.helper.round(ms.m_tax_tp, decimal.tp),
  231. });
  232. }
  233. tp_data.stage_tp = stage_tp;
  234. }
  235. return tp_data;
  236. }
  237. /**
  238. * 开始审批
  239. * @param {Number} materialId - 材料调差期id
  240. * @param {Number} times - 第几次审批
  241. * @return {Promise<boolean>}
  242. */
  243. async start(materialId, times = 1) {
  244. const audits = await this.getAllDataByCondition({ where: { mid: materialId, times, order: 1 } });
  245. if (audits.length === 0) {
  246. if(this.ctx.tender.info.shenpi.material === shenpiConst.sp_status.gdspl) {
  247. throw '请联系管理员添加审批人';
  248. } else {
  249. throw '请先选择审批人,再上报数据';
  250. }
  251. }
  252. const transaction = await this.db.beginTransaction();
  253. try {
  254. const tp_data = await this.getTpData(transaction, materialId);
  255. const begin_time = new Date();
  256. const updateData = audits.map(x => { return { id: x.id, status: auditConst.status.checking, begin_time, tp_data: JSON.stringify(tp_data) } });
  257. await transaction.updateRows(this.tableName, updateData);
  258. await transaction.update(this.ctx.service.material.tableName, {
  259. id: materialId, status: auditConst.status.checking, tp_data: JSON.stringify(tp_data),
  260. });
  261. // 本期一些必要数据(如应耗数量和上期调差金额)插入到material_bills_history表里
  262. const materialBillsData = await this.ctx.service.materialBills.getAllDataByCondition({ where: { tid: this.ctx.tender.id } });
  263. if (materialBillsData.length === 0 && this.ctx.material.ex_expr === null) {
  264. throw '请至少使用一种调差方式';
  265. }
  266. // 微信模板通知
  267. const materialInfo = await this.ctx.service.material.getDataById(materialId);
  268. const material_decimal = materialInfo && materialInfo.decimal ? JSON.parse(materialInfo.decimal) : materialConst.decimal;
  269. const users = this._.map(audits, 'aid');
  270. const wechatData = {
  271. qi: materialInfo.order,
  272. status: wxConst.status.check,
  273. tips: wxConst.tips.check,
  274. begin_time: Date.parse(new Date()),
  275. m_tp: this.ctx.helper.add(this.ctx.helper.round(materialInfo.m_tp, material_decimal.tp), this.ctx.helper.round(materialInfo.ex_tp, material_decimal.tp)),
  276. hs_m_tp: this.ctx.helper.add(this.ctx.helper.round(this.ctx.helper.mul(materialInfo.m_tp, 1+materialInfo.rate/100), material_decimal.tp), this.ctx.helper.round(this.ctx.helper.mul(materialInfo.ex_tp, 1+materialInfo.exponent_rate/100), material_decimal.tp)),
  277. };
  278. await this.ctx.helper.sendWechat(users, smsTypeConst.const.TC, smsTypeConst.judge.approval.toString(), wxConst.template.material, wechatData);
  279. for (const audit of audits) {
  280. await this.ctx.service.noticeAgain.addNoticeAgain(transaction, smsTypeConst.const.TC, {
  281. pid: this.ctx.session.sessionProject.id,
  282. tid: this.ctx.tender.id,
  283. uid: audit.aid,
  284. sp_type: 'material',
  285. sp_id: audit.id,
  286. table_name: this.tableName,
  287. template: wxConst.template.material,
  288. wx_data: wechatData,
  289. });
  290. }
  291. // todo 更新标段tender状态 ?
  292. // 检查三方特殊推送
  293. await this.ctx.service.specMsg.addMaterialMsg(transaction, this.ctx.session.sessionProject.id, materialInfo, pushOperate.material.flow);
  294. await transaction.commit();
  295. } catch (err) {
  296. await transaction.rollback();
  297. throw err;
  298. }
  299. return true;
  300. }
  301. async _checked(pid, materialId, checkData, times) {
  302. const accountId = this.ctx.session.sessionUser.accountId;
  303. const time = new Date();
  304. // 整理当前流程审核人状态更新
  305. const audits = await this.getAllDataByCondition({ where: { mid: materialId, times, status: auditConst.status.checking } });
  306. if (audits.length === 0) throw '审核数据错误';
  307. const selfAudit = audits.find(x => { return x.aid === accountId; });
  308. if (!selfAudit) throw '当前标段您无权审批';
  309. const flowAudits = await this.getAllDataByCondition({ where: { mid: materialId, times, order: selfAudit.order } });
  310. const nextAudits = await this.getAllDataByCondition({ where: { mid: materialId, times, order: selfAudit.order + 1 } });
  311. const transaction = await this.db.beginTransaction();
  312. try {
  313. // 获取当前总金额及独立单价期的金额,添加到tp_data中,报表使用
  314. const tp_data = await this.getTpData(transaction, materialId);
  315. await transaction.update(this.tableName, {
  316. id: selfAudit.id,
  317. status: checkData.checkType,
  318. opinion: checkData.opinion,
  319. end_time: time,
  320. tp_data: JSON.stringify(tp_data),
  321. });
  322. await this.ctx.service.noticeAgain.stopNoticeAgain(transaction, this.tableName, selfAudit.id);
  323. // 获取推送必要信息
  324. const noticeContent = await this.getNoticeContent(pid, selfAudit.tid, materialId, accountId, checkData.opinion);
  325. const auditors = await this.getAuditorsWithOwner(materialId, times);
  326. // 添加推送
  327. const records = [];
  328. // const records = [{ pid, type: pushType.material, uid: this.ctx.material.user_id, status: auditConst.status.checked, content: noticeContent }];
  329. auditors.forEach(audit => {
  330. records.push({ pid, type: pushType.material, uid: audit.aid, status: auditConst.status.checked, content: noticeContent });
  331. });
  332. await transaction.insert('zh_notice', records);
  333. const materialInfo = await this.ctx.service.material.getDataById(materialId);
  334. const material_decimal = materialInfo && materialInfo.decimal ? JSON.parse(materialInfo.decimal) : materialConst.decimal;
  335. if (audits.length === 1 || selfAudit.audit_type === auditType.key.or) {
  336. // 或签更新他人审批状态
  337. if (selfAudit.audit_type === auditType.key.or) {
  338. const updateOther = [];
  339. for (const audit of audits) {
  340. if (audit.aid === selfAudit.aid) continue;
  341. updateOther.push({
  342. id: audit.id,
  343. status: auditConst.status.checkSkip,
  344. opinion: '',
  345. end_time: time,
  346. });
  347. await this.ctx.service.noticeAgain.stopNoticeAgain(transaction, this.tableName, audit.id);
  348. }
  349. if (updateOther.length > 0) transaction.updateRows(this.tableName, updateOther);
  350. }
  351. // 无下一审核人表示,审核结束
  352. if (nextAudits.length > 0) {
  353. // 复制一份下一审核人数据
  354. // await this.ctx.service.stagePay.copyAuditStagePays(this.ctx.stage, this.ctx.stage.times, nextAudit.order, transaction);
  355. // 流程至下一审批人
  356. const updateData = nextAudits.map(x => {
  357. return {
  358. id: x.id,
  359. status: auditConst.status.checking,
  360. begin_time: time,
  361. tp_data: JSON.stringify(tp_data)
  362. };
  363. });
  364. await transaction.updateRows(this.tableName, updateData);
  365. // 同步 期信息
  366. await transaction.update(this.ctx.service.material.tableName, {
  367. id: materialId, status: auditConst.status.checking,
  368. });
  369. const begin_audit = await this.getDataByCondition({
  370. mid: materialId,
  371. order: 1,
  372. });
  373. // 微信模板通知
  374. const wechatData = {
  375. qi: materialInfo.order,
  376. status: wxConst.status.check,
  377. tips: wxConst.tips.check,
  378. begin_time: Date.parse(begin_audit.begin_time),
  379. m_tp: this.ctx.helper.add(this.ctx.helper.round(materialInfo.m_tp, material_decimal.tp), this.ctx.helper.round(materialInfo.ex_tp, material_decimal.tp)),
  380. hs_m_tp: this.ctx.helper.add(this.ctx.helper.round(this.ctx.helper.mul(materialInfo.m_tp, 1 + materialInfo.rate / 100), material_decimal.tp), this.ctx.helper.round(this.ctx.helper.mul(materialInfo.ex_tp, 1 + materialInfo.exponent_rate / 100), material_decimal.tp)),
  381. };
  382. await this.ctx.helper.sendWechat(this._.map(nextAudits, 'aid'), smsTypeConst.const.TC, smsTypeConst.judge.approval.toString(), wxConst.template.material, wechatData);
  383. // 重新发送配置
  384. for (const a of nextAudits) {
  385. await this.ctx.service.noticeAgain.addNoticeAgain(transaction, smsTypeConst.const.TC, {
  386. pid: this.ctx.session.sessionProject.id,
  387. tid: this.ctx.tender.id,
  388. uid: a.aid,
  389. sp_type: 'material',
  390. sp_id: a.id,
  391. table_name: this.tableName,
  392. template: wxConst.template.material,
  393. wx_data: wechatData,
  394. });
  395. }
  396. // 检查三方特殊推送
  397. await this.ctx.service.specMsg.addMaterialMsg(transaction, pid, materialInfo, pushOperate.material.flow);
  398. } else {
  399. // 本期结束
  400. // 同步 期信息
  401. await transaction.update(this.ctx.service.material.tableName, {
  402. id: materialId, status: checkData.checkType,
  403. });
  404. // 处理旧数据,防止重复插入到历史表
  405. await transaction.delete(this.ctx.service.materialBillsHistory.tableName, {
  406. tid: this.ctx.tender.id,
  407. order: this.ctx.material.order
  408. });
  409. const mbhList = [];
  410. const materialBillsData = await this.ctx.service.materialBills.getAllDataByCondition({where: {tid: this.ctx.tender.id}});
  411. for (const mb of materialBillsData) {
  412. if (mb.code === '') {
  413. throw '调差工料编号不能为空';
  414. }
  415. const newMbh = {
  416. tid: this.ctx.tender.id,
  417. mid: this.ctx.material.id,
  418. order: this.ctx.material.order,
  419. mb_id: mb.id,
  420. quantity: mb.quantity,
  421. expr: mb.expr,
  422. msg_tp: mb.msg_tp,
  423. msg_times: mb.msg_times,
  424. msg_spread: mb.msg_spread,
  425. m_up_risk: mb.m_up_risk,
  426. m_down_risk: mb.m_down_risk,
  427. m_spread: mb.m_spread,
  428. m_tp: mb.m_tp,
  429. pre_tp: mb.pre_tp,
  430. m_tax_tp: mb.m_tax_tp,
  431. tax_pre_tp: mb.tax_pre_tp,
  432. origin: mb.origin,
  433. is_summary: mb.is_summary,
  434. m_tax: mb.m_tax,
  435. };
  436. mbhList.push(newMbh);
  437. }
  438. if (mbhList.length !== 0) await transaction.insert(this.ctx.service.materialBillsHistory.tableName, mbhList);
  439. // 处理旧数据,防止重复插入到历史表
  440. await transaction.delete(this.ctx.service.materialExponentHistory.tableName, {
  441. tid: this.ctx.tender.id,
  442. order: this.ctx.material.order
  443. });
  444. const materialExponentData = await this.ctx.service.materialExponent.getAllDataByCondition({where: {tid: this.ctx.tender.id}});
  445. const mehList = [];
  446. for (const me of materialExponentData) {
  447. const newMeh = {
  448. tid: this.ctx.tender.id,
  449. mid: this.ctx.material.id,
  450. order: this.ctx.material.order,
  451. me_id: me.id,
  452. type: me.type,
  453. weight_num: me.weight_num,
  454. basic_price: me.basic_price,
  455. basic_times: me.basic_times,
  456. m_price: me.m_price,
  457. calc_num: me.calc_num,
  458. is_summary: me.is_summary,
  459. };
  460. mehList.push(newMeh);
  461. }
  462. if (mehList.length !== 0) await transaction.insert(this.ctx.service.materialExponentHistory.tableName, mehList);
  463. // 微信模板通知
  464. const begin_audit = await this.getDataByCondition({
  465. mid: materialId,
  466. order: 1,
  467. });
  468. const users = this._.uniq(this._.concat(this._.map(auditors, 'aid'), materialInfo.user_id));
  469. const wechatData = {
  470. qi: materialInfo.order,
  471. status: wxConst.status.success,
  472. tips: wxConst.tips.success,
  473. begin_time: Date.parse(begin_audit.begin_time),
  474. m_tp: this.ctx.helper.add(this.ctx.helper.round(materialInfo.m_tp, material_decimal.tp), this.ctx.helper.round(materialInfo.ex_tp, material_decimal.tp)),
  475. hs_m_tp: this.ctx.helper.add(this.ctx.helper.round(this.ctx.helper.mul(materialInfo.m_tp, 1 + materialInfo.rate / 100), material_decimal.tp), this.ctx.helper.round(this.ctx.helper.mul(materialInfo.ex_tp, 1 + materialInfo.exponent_rate / 100), material_decimal.tp)),
  476. };
  477. await this.ctx.helper.sendWechat(users, smsTypeConst.const.TC, smsTypeConst.judge.result.toString(), wxConst.template.material, wechatData);
  478. // 检查三方特殊推送
  479. await this.ctx.service.specMsg.addMaterialMsg(transaction, pid, materialInfo, pushOperate.material.flow);
  480. await this.ctx.service.specMsg.addMaterialMsg(transaction, pid, materialInfo, pushOperate.material.checked);
  481. }
  482. } else {
  483. // 同步 期信息
  484. await transaction.update(this.ctx.service.material.tableName, {
  485. id: materialId,
  486. status: auditConst.status.checking,
  487. });
  488. }
  489. await transaction.commit();
  490. } catch (err) {
  491. await transaction.rollback();
  492. throw err;
  493. }
  494. }
  495. async _checkNo(pid, materialId, checkData, times) {
  496. const accountId = this.ctx.session.sessionUser.accountId;
  497. const time = new Date();
  498. // 整理当前流程审核人状态更新
  499. const audits = await this.getAllDataByCondition({ where: { mid: materialId, times, status: auditConst.status.checking } });
  500. if (!audits) throw '审核数据错误';
  501. const selfAudit = audits.find(x => { return x.aid === accountId; });
  502. if (!selfAudit) throw '当前标段您无权审批';
  503. const flowAudits = await this.getAllDataByCondition({ where: { mid: materialId, times: selfAudit.times, order: selfAudit.order }});
  504. const auditors = await this.getUniqAuditor(materialId, times); // 全部参与的审批人
  505. const newAuditors = auditors.map(x => {
  506. return {
  507. aid: x.aid, tid: selfAudit.tid, mid: selfAudit.mid,
  508. times: times + 1, order: x.audit_order, status: auditConst.status.uncheck,
  509. audit_type: x.audit_type, audit_order: x.audit_order,
  510. }
  511. });
  512. const transaction = await this.db.beginTransaction();
  513. try {
  514. const tp_data = await this.getTpData(transaction, materialId);
  515. const updateData = [];
  516. audits.forEach(x => {
  517. updateData.push({
  518. id: x.id,
  519. status: x.aid === selfAudit.aid ? checkData.checkType : auditConst.status.checkSkip,
  520. opinion: x.aid === selfAudit.aid ? checkData.opinion : '',
  521. end_time: x.aid === selfAudit.aid ? time : null,
  522. tp_data: JSON.stringify(tp_data),
  523. });
  524. });
  525. await transaction.updateRows(this.tableName, updateData);
  526. await this.ctx.service.noticeAgain.stopNoticeAgain(transaction, this.tableName, this._.map(updateData, 'id'));
  527. // 添加到消息推送表
  528. const noticeContent = await this.getNoticeContent(pid, selfAudit.tid, materialId, selfAudit.aid, checkData.opinion);
  529. const records = [{ pid, type: pushType.material, uid: this.ctx.material.user_id, status: auditConst.status.checkNo, content: noticeContent }];
  530. auditors.forEach(audit => {
  531. records.push({ pid, type: pushType.material, uid: audit.aid, status: auditConst.status.checkNo, content: noticeContent });
  532. });
  533. await transaction.insert(this.ctx.service.noticePush.tableName, records);
  534. // 同步期信息
  535. await transaction.update(this.ctx.service.material.tableName, {
  536. id: materialId, status: checkData.checkType,
  537. times: times + 1,
  538. tp_data: JSON.stringify(tp_data),
  539. });
  540. // 拷贝新一次审核流程列表
  541. await transaction.insert(this.tableName, newAuditors);
  542. // 微信模板通知
  543. const begin_audit = await this.getDataByCondition({
  544. mid: materialId,
  545. order: 1,
  546. });
  547. const materialInfo = await this.ctx.service.material.getDataById(materialId);
  548. const material_decimal = materialInfo && materialInfo.decimal ? JSON.parse(materialInfo.decimal) : materialConst.decimal;
  549. const users = this._.uniq(this._.concat(this._.map(auditors, 'aid'), materialInfo.user_id));
  550. const wechatData = {
  551. qi: materialInfo.order,
  552. status: wxConst.status.back,
  553. tips: wxConst.tips.back,
  554. begin_time: Date.parse(begin_audit.begin_time),
  555. m_tp: this.ctx.helper.add(this.ctx.helper.round(materialInfo.m_tp, material_decimal.tp), this.ctx.helper.round(materialInfo.ex_tp, material_decimal.tp)),
  556. hs_m_tp: this.ctx.helper.add(this.ctx.helper.round(this.ctx.helper.mul(materialInfo.m_tp, 1+materialInfo.rate/100), material_decimal.tp), this.ctx.helper.round(this.ctx.helper.mul(materialInfo.ex_tp, 1+materialInfo.exponent_rate/100), material_decimal.tp)),
  557. };
  558. await this.ctx.helper.sendWechat(users, smsTypeConst.const.TC, smsTypeConst.judge.result.toString(), wxConst.template.material, wechatData);
  559. // 检查三方特殊推送
  560. await this.ctx.service.specMsg.addMaterialMsg(transaction, pid, materialInfo, pushOperate.material.flow);
  561. await transaction.commit();
  562. } catch (err) {
  563. await transaction.rollback();
  564. throw err;
  565. }
  566. }
  567. async _checkNoPre(pid, materialId, checkData, times) {
  568. const accountId = this.ctx.session.sessionUser.accountId;
  569. const time = new Date();
  570. // 整理当前流程审核人状态更新
  571. const audits = await this.getAllDataByCondition({ where: { mid: materialId, times, status: auditConst.status.checking } });
  572. if (audits.length === 0 || audits[0].order <= 1) throw '审核数据错误';
  573. const selfAudit = audits.find(x => { return x.aid === accountId; });
  574. if (!selfAudit) throw '当前标段您无权审批';
  575. const flowAudits = await this.getAllDataByCondition({ where: { mid: materialId, times: selfAudit.times, order: selfAudit.order }});
  576. // 添加重新审批后,不能用order-1,取groupby值里的上一个才对
  577. const auditors2 = await this.getAuditGroupByList(materialId, times);
  578. const preAuditors = auditors2.filter(x => { return x.audit_order === selfAudit.audit_order - 1});
  579. const transaction = await this.db.beginTransaction();
  580. try {
  581. const tp_data = await this.getTpData(transaction, materialId);
  582. // 添加到消息推送表
  583. const noticeContent = await this.getNoticeContent(pid, selfAudit.tid, materialId, selfAudit.aid, checkData.opinion);
  584. const records = [{ pid, type: pushType.material, uid: this.ctx.material.user_id, status: auditConst.status.checkNoPre, content: noticeContent }];
  585. auditors2.forEach(audit => {
  586. records.push({ pid, type: pushType.material, uid: audit.aid, status: auditConst.status.checkNoPre, content: noticeContent });
  587. });
  588. await transaction.insert('zh_notice', records);
  589. const updateData = [];
  590. for (const audit of audits) {
  591. if (audit.aid === selfAudit.aid) {
  592. updateData.push({
  593. id: audit.id, status: checkData.checkType, opinion: checkData.opinion, end_time: time, tp_data: JSON.stringify(tp_data)
  594. });
  595. } else {
  596. updateData.push({
  597. id: audit.id, status: auditConst.status.checkSkip, opinion: '', end_time: null, tp_data: JSON.stringify(tp_data)
  598. });
  599. }
  600. }
  601. await transaction.updateRows(this.tableName, updateData);
  602. await this.ctx.service.noticeAgain.stopNoticeAgain(transaction, this.tableName, this._.map(updateData, 'id'));
  603. // 顺移其后审核人流程顺序
  604. const sql = 'UPDATE ' + this.tableName + ' SET `order` = `order` + 2 WHERE mid = ? AND times = ? AND `order` > ?';
  605. await transaction.query(sql, [materialId, selfAudit.times, selfAudit.order]);
  606. const newAuditors = [];
  607. preAuditors.forEach(x => {
  608. newAuditors.push({
  609. tid: selfAudit.tid, mid: materialId, aid: x.aid,
  610. times: x.times, order: selfAudit.order + 1,
  611. status: auditConst.status.checking, begin_time: time,
  612. audit_type: x.audit_type, audit_order: x.audit_order,
  613. tp_data: JSON.stringify(tp_data),
  614. });
  615. });
  616. const newAuditors_result = await transaction.insert(this.tableName, newAuditors);
  617. // 获取刚批量添加的所有list
  618. for (let j = 0; j < preAuditors.length; j++) {
  619. newAuditors[j].id = newAuditors_result.insertId + j;
  620. }
  621. const newFlowAuditors = [];
  622. flowAudits.forEach(x => {
  623. newFlowAuditors.push({
  624. tid: selfAudit.tid, mid: materialId, aid: x.aid,
  625. times: x.times, order: selfAudit.order + 2,
  626. status: auditConst.status.uncheck,
  627. audit_type: x.audit_type, audit_order: x.audit_order,
  628. });
  629. });
  630. await transaction.insert(this.tableName, newFlowAuditors);
  631. // 微信模板通知
  632. const begin_audit = await this.getDataByCondition({
  633. mid: materialId,
  634. order: 1,
  635. });
  636. const materialInfo = await this.ctx.service.material.getDataById(materialId);
  637. const material_decimal = materialInfo && materialInfo.decimal ? JSON.parse(materialInfo.decimal) : materialConst.decimal;
  638. const wechatData = {
  639. qi: materialInfo.order,
  640. status: wxConst.status.check,
  641. tips: wxConst.tips.check,
  642. begin_time: Date.parse(begin_audit.begin_time),
  643. m_tp: this.ctx.helper.add(this.ctx.helper.round(materialInfo.m_tp, material_decimal.tp), this.ctx.helper.round(materialInfo.ex_tp, material_decimal.tp)),
  644. hs_m_tp: this.ctx.helper.add(this.ctx.helper.round(this.ctx.helper.mul(materialInfo.m_tp, 1+materialInfo.rate/100), material_decimal.tp), this.ctx.helper.round(this.ctx.helper.mul(materialInfo.ex_tp, 1+materialInfo.exponent_rate/100), material_decimal.tp)),
  645. };
  646. await this.ctx.helper.sendWechat(this._.map(preAuditors, 'aid'), smsTypeConst.const.TC, smsTypeConst.judge.approval.toString(), wxConst.template.material, wechatData);
  647. for (const a of newAuditors) {
  648. await this.ctx.service.noticeAgain.addNoticeAgain(transaction, smsTypeConst.const.TC, {
  649. pid: this.ctx.session.sessionProject.id,
  650. tid: this.ctx.tender.id,
  651. uid: a.aid,
  652. sp_type: 'material',
  653. sp_id: a.id,
  654. table_name: this.tableName,
  655. template: wxConst.template.material,
  656. wx_data: wechatData,
  657. });
  658. }
  659. // 检查三方特殊推送
  660. await this.ctx.service.specMsg.addMaterialMsg(transaction, pid, materialInfo, pushOperate.material.flow);
  661. await transaction.commit();
  662. } catch (error) {
  663. await transaction.rollback();
  664. throw error;
  665. }
  666. }
  667. /**
  668. * 审批
  669. * @param {Number} materialId - 材料调差期id
  670. * @param {auditConst.status.checked|auditConst.status.checkNo} checkType - 审批结果
  671. * @param {Number} times - 第几次审批
  672. * @return {Promise<void>}
  673. */
  674. async check(materialId, checkData, times = 1) {
  675. if (checkData.checkType !== auditConst.status.checked && checkData.checkType !== auditConst.status.checkNo && checkData.checkType !== auditConst.status.checkNoPre) {
  676. throw '提交数据错误';
  677. }
  678. const pid = this.ctx.session.sessionProject.id;
  679. switch (checkData.checkType) {
  680. case auditConst.status.checked:
  681. await this._checked(pid, materialId, checkData, times);
  682. break;
  683. case auditConst.status.checkNo:
  684. await this._checkNo(pid, materialId, checkData, times);
  685. break;
  686. case auditConst.status.checkNoPre:
  687. await this._checkNoPre(pid, materialId, checkData, times);
  688. break;
  689. default:
  690. throw '无效审批操作';
  691. }
  692. }
  693. /**
  694. * 用于添加推送所需的content内容
  695. * @param {Number} pid 项目id
  696. * @param {Number} tid 台账id
  697. * @param {Number} mid 期id
  698. * @param {Number} uid 审批人id
  699. */
  700. async getNoticeContent(pid, tid, mid, uid, opinion = '') {
  701. const noticeSql = 'SELECT * FROM (SELECT ' +
  702. ' t.`id` As `tid`, ma.`mid`, t.`name`, m.`order`, pa.`name` As `su_name`, pa.role As `su_role`' +
  703. ' FROM (SELECT * FROM ?? WHERE `id` = ? ) As t' +
  704. ' LEFT JOIN ?? As m On t.`id` = m.`tid` AND m.`id` = ?' +
  705. ' LEFT JOIN ?? As ma ON m.`id` = ma.`mid`' +
  706. ' LEFT JOIN ?? As pa ON pa.`id` = ?' +
  707. ' WHERE t.`project_id` = ? ) as new_t GROUP BY new_t.`tid`';
  708. const noticeSqlParam = [this.ctx.service.tender.tableName, tid, this.ctx.service.material.tableName, mid, this.tableName, this.ctx.service.projectAccount.tableName, uid, pid];
  709. const content = await this.db.query(noticeSql, noticeSqlParam);
  710. if (content.length) {
  711. content[0].opinion = opinion;
  712. }
  713. return content.length ? JSON.stringify(content[0]) : '';
  714. }
  715. /**
  716. * 审批
  717. * @param {Number} materialId - 材料调差期id
  718. * @param {Number} times - 第几次审批
  719. * @return {Promise<void>}
  720. */
  721. async checkAgain(materialId, times = 1) {
  722. const accountId = this.ctx.session.sessionUser.accountId;
  723. const time = new Date();
  724. // 整理当前流程审核人状态更新
  725. const auditors = await this.getAllDataByCondition({
  726. where: { mid: materialId, times },
  727. orders: [['order', 'asc']],
  728. });
  729. const maxOrder = auditors[auditors.length - 1].order;
  730. const audits = auditors.filter(x => { return x.order === maxOrder});
  731. if (!audits || audits.length === 0 || maxOrder < 1) throw '审核数据错误';
  732. const selfAudit = audits.find(x => { return x.aid === accountId; });
  733. if (!selfAudit) throw '当前标段您无权审批';
  734. const finalAudit = selfAudit || audits[0];
  735. const transaction = await this.db.beginTransaction();
  736. try {
  737. const materialInfo = await this.ctx.service.material.getDataById(materialId);
  738. const material_decimal = materialInfo && materialInfo.decimal ? JSON.parse(materialInfo.decimal) : materialConst.decimal;
  739. const tp_data = await this.getTpData(transaction, materialId, material_decimal);
  740. const checkAgainAuditors = [], checkingAuditors = [];
  741. audits.forEach(x => {
  742. checkAgainAuditors.push({
  743. tid: x.tid, mid: x.mid, aid: x.aid,
  744. times: x.times, order: x.order + 1,
  745. status: !selfAudit || x.aid === selfAudit.aid ? auditConst.status.checkAgain : auditConst.status.checkSkip,
  746. begin_time: time, end_time: time, opinion: '',
  747. audit_type: x.audit_type, audit_order: x.audit_order,
  748. });
  749. });
  750. audits.forEach(x => {
  751. checkingAuditors.push({
  752. tid: x.tid, mid: x.mid, aid: x.aid,
  753. times: x.times, order: x.order + 2,
  754. status: auditConst.status.checking,
  755. begin_time: time, end_time: time, opinion: '',
  756. audit_type: x.audit_type, audit_order: x.audit_order,
  757. tp_data: JSON.stringify(tp_data),
  758. });
  759. });
  760. await transaction.insert(this.tableName, checkAgainAuditors);
  761. const checkingAuditors_result = await transaction.insert(this.tableName, checkingAuditors);
  762. // 获取刚批量添加的所有list
  763. for (let j = 0; j < checkingAuditors.length; j++) {
  764. checkingAuditors[j].id = checkingAuditors_result.insertId + j;
  765. }
  766. // 本期结束
  767. // 同步 期信息
  768. await transaction.update(this.ctx.service.material.tableName, {
  769. id: materialId, status: auditConst.status.checking,
  770. final_auditor_str: '',
  771. });
  772. // 微信模板通知
  773. const wechatData = {
  774. qi: materialInfo.order,
  775. status: wxConst.status.check,
  776. tips: wxConst.tips.check,
  777. begin_time: Date.parse(new Date()),
  778. m_tp: this.ctx.helper.add(this.ctx.helper.round(materialInfo.m_tp, material_decimal.tp), this.ctx.helper.round(materialInfo.ex_tp, material_decimal.tp)),
  779. hs_m_tp: this.ctx.helper.add(this.ctx.helper.round(this.ctx.helper.mul(materialInfo.m_tp, 1+materialInfo.rate/100), material_decimal.tp), this.ctx.helper.round(this.ctx.helper.mul(materialInfo.ex_tp, 1+materialInfo.exponent_rate/100), material_decimal.tp)),
  780. };
  781. await this.ctx.helper.sendWechat(this._.map(checkingAuditors, 'aid'), smsTypeConst.const.TC, smsTypeConst.judge.approval.toString(), wxConst.template.material, wechatData);
  782. for (const a of checkingAuditors) {
  783. await this.ctx.service.noticeAgain.addNoticeAgain(transaction, smsTypeConst.const.TC, {
  784. pid: this.ctx.session.sessionProject.id,
  785. tid: this.ctx.tender.id,
  786. uid: checkingAuditors.aid,
  787. sp_type: 'material',
  788. sp_id: checkingAuditors.id,
  789. table_name: this.tableName,
  790. template: wxConst.template.material,
  791. wx_data: wechatData,
  792. });
  793. }
  794. // 检查三方特殊推送
  795. await this.ctx.service.specMsg.addMaterialMsg(transaction, this.ctx.session.sessionProject.id, materialInfo, pushOperate.material.flow);
  796. await transaction.commit();
  797. } catch (err) {
  798. await transaction.rollback();
  799. throw err;
  800. }
  801. }
  802. /**
  803. * 获取审核人需要审核的期列表
  804. *
  805. * @param auditorId
  806. * @return {Promise<*>}
  807. */
  808. async getAuditMaterial(auditorId) {
  809. const sql = 'SELECT ma.`aid`, ma.`times`, ma.`order`, ma.`begin_time`, ma.`end_time`, ma.`tid`, ma.`mid`,' +
  810. ' m.`order` As `morder`, m.`status` As `mstatus`,' +
  811. ' t.`name`, t.`project_id`, t.`type`, t.`user_id` ' +
  812. ' FROM ?? AS ma, ?? AS m, ?? As t ' +
  813. ' WHERE ((ma.`aid` = ? and ma.`status` = ?) OR (m.`user_id` = ? and ma.`status` = ? and m.`status` = ? and ma.`times` = (m.`times`-1)))' +
  814. ' and ma.`mid` = m.`id` and ma.`tid` = t.`id` ORDER BY ma.`begin_time` DESC';
  815. const sqlParam = [this.tableName, this.ctx.service.material.tableName, this.ctx.service.tender.tableName, auditorId, auditConst.status.checking, auditorId, auditConst.status.checkNo, auditConst.status.checkNo];
  816. const result = await this.db.query(sql, sqlParam);
  817. // 过滤result中存在重复sid的值, 保留最新的一条
  818. const filterResult = [];
  819. const midArr = [];
  820. for (const r of result) {
  821. if (midArr.indexOf(r.mid) === -1) {
  822. filterResult.push(r);
  823. midArr.push(r.mid);
  824. }
  825. }
  826. return filterResult;
  827. }
  828. /**
  829. * 获取审核人审核的次数
  830. *
  831. * @param auditorId
  832. * @return {Promise<*>}
  833. */
  834. async getCountByChecked(auditorId) {
  835. return await this.db.count(this.tableName, { aid: auditorId, status: [auditConst.status.checked, auditConst.status.checkNo, auditConst.status.checkNoPre] });
  836. }
  837. /**
  838. * 获取最近一次审批结束时间
  839. *
  840. * @param auditorId
  841. * @return {Promise<*>}
  842. */
  843. async getLastEndTimeByChecked(auditorId) {
  844. const sql = 'SELECT `end_time` FROM ?? WHERE `aid` = ? ' +
  845. 'AND `status` in (' + this.ctx.helper.getInArrStrSqlFilter([auditConst.status.checked, auditConst.status.checkNo]) + ') ORDER BY `end_time` DESC';
  846. const sqlParam = [this.tableName, auditorId];
  847. const result = await this.db.queryOne(sql, sqlParam);
  848. return result ? result.end_time : null;
  849. }
  850. /**
  851. * 获取 某时间后 审批进度 更新的期
  852. * @param {Number} pid - 查询标段
  853. * @param {Number} uid - 查询人
  854. * @param {Date} time - 查询时间
  855. * @return {Promise<*>}
  856. */
  857. async getNoticeMaterial(pid, uid, time) {
  858. // const sql = 'SELECT * FROM (SELECT t.`name`, t.`project_id`, t.`type`, t.`user_id`, ' +
  859. // ' m.`order` As `m_order`, m.`status` As `m_status`, ' +
  860. // ' ma.`aid`, ma.`times`, ma.`order`, ma.`end_time`, ma.`tid`, ma.`mid`, ma.`status`, ' +
  861. // ' pa.`name` As `su_name`, pa.role As `su_role`, pa.company As `su_company`' +
  862. // ' FROM (SELECT * FROM ?? WHERE `user_id` = ? OR `id` in (SELECT `tid` FROM ?? WHERE `aid` = ? GROUP BY `tid`)) As t' +
  863. // ' LEFT JOIN ?? As m On t.`id` = m.`tid`' +
  864. // ' LEFT JOIN ?? As ma ON m.`id` = ma.`mid`' +
  865. // ' LEFT JOIN ?? As pa ON ma.`aid` = pa.`id`' +
  866. // ' WHERE ma.`end_time` > ? and t.`project_id` = ?' +
  867. // ' ORDER By ma.`end_time` DESC LIMIT 1000) as new_t GROUP BY new_t.`tid`' +
  868. // ' ORDER BY new_t.`end_time`';
  869. // const sqlParam = [this.ctx.service.tender.tableName, uid, this.tableName, uid, this.ctx.service.material.tableName, this.tableName,
  870. // this.ctx.service.projectAccount.tableName, time, pid];
  871. // return await this.db.query(sql, sqlParam);
  872. let notice = await this.db.select('zh_notice', {
  873. where: { pid, type: pushType.material, uid },
  874. orders: [['create_time', 'desc']],
  875. limit: 10, offset: 0,
  876. });
  877. notice = notice.map(v => {
  878. const extra = JSON.parse(v.content);
  879. delete v.content;
  880. return { ...v, ...extra };
  881. });
  882. return notice;
  883. }
  884. /**
  885. * 获取审核人流程列表
  886. *
  887. * @param auditorId
  888. * @return {Promise<*>}
  889. */
  890. async getAuditGroupByList(materialId, times) {
  891. const sql = 'SELECT la.`aid`, pa.`name`, pa.`company`, pa.`role`, la.`times`, la.`tid`, la.`mid`, la.`aid`, la.`order`, la.`status`, la.audit_type, la.audit_order ' +
  892. ' FROM ?? AS la Left Join ?? AS pa On la.`aid` = pa.`id`' +
  893. ' WHERE la.`mid` = ? and la.`times` = ? GROUP BY la.`aid` ORDER BY la.`order`';
  894. const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, materialId, times];
  895. return await this.db.query(sql, sqlParam);
  896. }
  897. /**
  898. * 获取审核人流程列表(包括原报)
  899. * @param {Number} materialId 调差id
  900. * @param {Number} times 审核次数
  901. * @return {Promise<Array>} 查询结果集(包括原报)
  902. */
  903. async getAuditorsWithOwner(materialId, times = 1) {
  904. const result = await this.getAuditGroupByList(materialId, times);
  905. const sql =
  906. 'SELECT pa.`id` As aid, pa.`name`, pa.`company`, pa.`role`, ? As times, ? As mid, 0 As `order`' +
  907. ' FROM ' +
  908. this.ctx.service.material.tableName +
  909. ' As s' +
  910. ' LEFT JOIN ' +
  911. this.ctx.service.projectAccount.tableName +
  912. ' As pa' +
  913. ' ON s.user_id = pa.id' +
  914. ' WHERE s.id = ?';
  915. const sqlParam = [times, materialId, materialId];
  916. const user = await this.db.queryOne(sql, sqlParam);
  917. result.unshift(user);
  918. return result;
  919. }
  920. /**
  921. * 获取tender下所有的审核列表(报表用)
  922. * @param {Number} tenderId
  923. */
  924. async getAuditorsByTender(tenderId) {
  925. const sql = 'SELECT la.`tid`, la.`mid`, la.`aid`, la.`order`, la.`times`, la.`status`, la.`opinion`, la.`begin_time`, la.`end_time`' +
  926. ' FROM ?? AS la ' +
  927. ' WHERE la.`tid` = ?';
  928. const sqlParam = [this.tableName, tenderId];
  929. return await this.db.query(sql, sqlParam);
  930. }
  931. /**
  932. * 复制上一期的审批人列表给最新一期
  933. *
  934. * @param transaction - 新增一期的事务
  935. * @param {Object} preMaterial - 上一期
  936. * @param {Object} newaMaterial - 最新一期
  937. * @return {Promise<*>}
  938. */
  939. async copyPreMaterialAuditors(transaction, preMaterial, newMaterial) {
  940. const auditors = await this.getAuditGroupByList(preMaterial.id, preMaterial.times);
  941. const newAuditors = [];
  942. for (const a of auditors) {
  943. const na = {
  944. tid: preMaterial.tid,
  945. mid: newMaterial.id,
  946. aid: a.aid,
  947. times: newMaterial.times,
  948. order: a.audit_order,
  949. status: auditConst.status.uncheck,
  950. audit_type: a.audit_type,
  951. audit_order: a.audit_order,
  952. };
  953. newAuditors.push(na);
  954. }
  955. if (newAuditors.length > 0) {
  956. const result = await transaction.insert(this.tableName, newAuditors);
  957. return (result.effectRows = auditors.length);
  958. } else {
  959. return true;
  960. }
  961. }
  962. /**
  963. * 移除审核人
  964. *
  965. * @param {Number} materialId - 材料调差期id
  966. * @param {Number} status - 期状态
  967. * @param {Number} status - 期次数
  968. * @return {Promise<boolean>}
  969. */
  970. async getAuditorByStatus(materialId, status, times = 1) {
  971. let auditor = null;
  972. let sql = '';
  973. let sqlParam = '';
  974. switch (status) {
  975. case auditConst.status.checking :
  976. case auditConst.status.checked :
  977. case auditConst.status.checkNoPre :
  978. sql = 'SELECT la.`aid`, pa.`name`, pa.`company`, pa.`role`, la.`times`, la.`mid`, la.`aid`, la.`order` ' +
  979. ' FROM ?? AS la Left Join ?? AS pa On la.`aid` = pa.`id` ' +
  980. ' WHERE la.`mid` = ? and la.`status` = ? ' +
  981. ' ORDER BY la.`times` desc, la.`order` desc';
  982. sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, materialId, status];
  983. auditor = await this.db.queryOne(sql, sqlParam);
  984. break;
  985. case auditConst.status.checkNo :
  986. sql = 'SELECT la.`aid`, pa.`name`, pa.`company`, pa.`role`, la.`times`, la.`mid`, la.`aid`, la.`order` ' +
  987. ' FROM ?? AS la Left Join ?? AS pa On la.`aid` = pa.`id`' +
  988. ' WHERE la.`mid` = ? and la.`status` = ? and la.`times` = ?' +
  989. ' ORDER BY la.`times` desc, la.`order` desc';
  990. sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, materialId, auditConst.status.checkNo, parseInt(times) - 1];
  991. auditor = await this.db.queryOne(sql, sqlParam);
  992. break;
  993. case auditConst.status.uncheck :
  994. default:break;
  995. }
  996. return auditor;
  997. }
  998. async getAuditorsByStatus(materialId, status, times = 1) {
  999. let auditor = [];
  1000. let sql = '';
  1001. let sqlParam = '';
  1002. let cur;
  1003. switch (status) {
  1004. case auditConst.status.checking:
  1005. case auditConst.status.checked:
  1006. case auditConst.status.checkNoPre:
  1007. cur = await this.db.queryOne(`SELECT * From ${this.tableName} where mid = ? AND times = ? AND status = ? ORDER By times DESC, ` + '`order` DESC', [materialId, times, status]);
  1008. if (!cur) return [];
  1009. sql = 'SELECT la.`aid`, pa.`name`, pa.`company`, pa.`role`, la.`times`, la.`mid`, la.`order`, la.audit_order, la.audit_type ' +
  1010. ' FROM ?? AS la Left Join ?? AS pa On la.`aid` = pa.`id` ' +
  1011. ' WHERE la.`mid` = ? and la.`order` = ? and times = ?';
  1012. sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, materialId, cur.order, times];
  1013. auditor = await this.db.query(sql, sqlParam);
  1014. break;
  1015. case auditConst.status.checkNo:
  1016. cur = await this.db.queryOne(`SELECT * From ${this.tableName} where mid = ? AND times = ? AND status = ? ORDER By times DESC, ` + '`order` DESC', [materialId, parseInt(times) - 1, status]);
  1017. if (!cur) return [];
  1018. sql = 'SELECT la.`aid`, pa.`name`, pa.`company`, pa.`role`, la.`times`, la.`mid`, la.`order`, la.audit_order, la.audit_type ' +
  1019. ' FROM ?? AS la Left Join ?? AS pa On la.`aid` = pa.`id` ' +
  1020. ' WHERE la.`mid` = ? and la.`order` = ? and la.`times` = ?';
  1021. sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, materialId, cur.order, parseInt(times) - 1];
  1022. auditor = await this.db.query(sql, sqlParam);
  1023. break;
  1024. case auditConst.status.uncheck:
  1025. default:
  1026. break;
  1027. }
  1028. return auditor;
  1029. }
  1030. async getAllAuditors(tenderId) {
  1031. const sql = 'SELECT ma.aid, ma.tid FROM ' + this.tableName + ' ma' +
  1032. ' LEFT JOIN ' + this.ctx.service.tender.tableName + ' t On ma.tid = t.id' +
  1033. ' WHERE t.id = ?' +
  1034. ' GROUP BY ma.aid';
  1035. const sqlParam = [tenderId];
  1036. return this.db.query(sql, sqlParam);
  1037. }
  1038. async updateNewAuditList(material, newList) {
  1039. const transaction = await this.db.beginTransaction();
  1040. try {
  1041. // 先删除旧的审批流,再添加新的
  1042. await transaction.delete(this.tableName, { mid: material.id, times: material.times });
  1043. const newAuditors = [];
  1044. for (const auditor of newList) {
  1045. newAuditors.push({
  1046. tid: material.tid, mid: material.id, aid: auditor.audit_id,
  1047. times: material.times, order: auditor.audit_order, status: auditConst.status.uncheck,
  1048. audit_type: auditor.audit_type, audit_order: auditor.audit_order,
  1049. });
  1050. }
  1051. if(newAuditors.length > 0) await transaction.insert(this.tableName, newAuditors);
  1052. await transaction.commit();
  1053. } catch (err) {
  1054. await transaction.rollback();
  1055. throw err;
  1056. }
  1057. }
  1058. async updateLastAudit(material, auditList, lastId) {
  1059. const transaction = await this.db.beginTransaction();
  1060. try {
  1061. // 先判断auditList里的aid是否与lastId相同,相同则删除并重新更新order
  1062. const existAudit = auditList.find(x => { return x.aid === lastId });
  1063. let order = auditList.length > 0 ? auditList.reduce((rst, a) => { return Math.max(rst, a.order)}, 0) + 1 : 1; // 最大值 + 1
  1064. // const idList = this._.map(auditList, 'aid');
  1065. // let order = idList.length + 1;
  1066. if (existAudit) {
  1067. await transaction.delete(this.tableName, {mid: material.id, times: material.times, aid: lastId});
  1068. const sameOrder = auditList.filter(x => { return x.order === existAudit.order });
  1069. if (sameOrder.length === 1) {
  1070. const updateData = [];
  1071. auditList.forEach(x => {
  1072. if (x.order <= existAudit.order) return;
  1073. updateData.push({id: x.id, order: x.order - 1, audit_order: x.audit_order - 1});
  1074. });
  1075. if (updateData.length > 0) {
  1076. await transaction.updateRows(updateData);
  1077. }
  1078. order = order - 1;
  1079. }
  1080. }
  1081. // 添加终审
  1082. const newAuditor = {
  1083. tid: material.tid, mid: material.id, aid: lastId,
  1084. times: material.times, order, status: auditConst.status.uncheck,
  1085. audit_type: auditType.key.common, audit_order: order,
  1086. };
  1087. await transaction.insert(this.tableName, newAuditor);
  1088. await transaction.commit();
  1089. } catch (err) {
  1090. await transaction.rollback();
  1091. throw err;
  1092. }
  1093. }
  1094. async getNumByMonth(tid, startMonth, endMonth) {
  1095. const sql = 'SELECT COUNT(*) as num FROM ?? t1 JOIN (SELECT MAX(id) as max_id FROM ?? WHERE tid = ? GROUP BY mid) t2 ON t1.id = t2.max_id WHERE t1.status = ? AND t1.end_time between ? and ?';
  1096. // const sql = 'SELECT COUNT(*) as num FROM ?? WHERE id in (SELECT MAX(id) FROM ?? WHERE tid = ? GROUP BY mid) AND status = ? AND end_time between ? and ?';
  1097. const sqlParam = [this.tableName, this.tableName, tid, auditConst.status.checked, startMonth, endMonth];
  1098. const result = await this.db.queryOne(sql, sqlParam);
  1099. return result ? result.num : 0;
  1100. }
  1101. /**
  1102. * 删除本次审批流程
  1103. * @param {Number} materialId - 调差id
  1104. * @param {Number} times - 第几次审批
  1105. * @param {Object} data - 更改参数
  1106. * @return {Promise<void>}
  1107. */
  1108. async saveAudit(materialId, times, sp_group, data) {
  1109. const transaction = await this.db.beginTransaction();
  1110. try {
  1111. const auditors = await this.getAuditGroupByList(materialId, times);
  1112. const now_audit = this._.find(auditors, { aid: data.old_aid });
  1113. if (data.operate !== 'del') {
  1114. const exist = await this.getDataByCondition({ mid: materialId, times, aid: data.new_aid });
  1115. if (exist) throw '该审核人已存在,请勿重复添加';
  1116. }
  1117. if (data.operate === 'add') {
  1118. if (now_audit.status !== auditConst.status.uncheck && now_audit.status !== auditConst.status.checking) {
  1119. throw '当前人下无法操作新增';
  1120. }
  1121. const newAudit = {
  1122. tid: this.ctx.tender.id,
  1123. mid: materialId,
  1124. aid: data.new_aid,
  1125. order: now_audit.order + 1,
  1126. audit_order: now_audit.audit_order + 1,
  1127. audit_type: auditType.key.common,
  1128. times: times,
  1129. status: 1
  1130. };
  1131. // order+1
  1132. await this._syncOrderByDelete(transaction, materialId, now_audit.order+1, times, '+');
  1133. await transaction.insert(this.tableName, newAudit);
  1134. // 更新审批流程页数据,如果存在
  1135. } else if (data.operate === 'add-sibling') {
  1136. if (now_audit.status !== auditConst.status.uncheck && now_audit.status !== auditConst.status.checking) {
  1137. throw '当前人下无法操作新增';
  1138. }
  1139. const newAudit = {
  1140. tid: this.ctx.tender.id,
  1141. mid: materialId,
  1142. aid: data.new_aid,
  1143. order: now_audit.order,
  1144. audit_order: now_audit.audit_order,
  1145. audit_type: now_audit.audit_type,
  1146. times: times,
  1147. status: 1
  1148. };
  1149. await transaction.insert(this.tableName, newAudit);
  1150. } else if (data.operate === 'del') {
  1151. if (now_audit.status !== auditConst.status.uncheck) {
  1152. throw '当前人无法操作删除';
  1153. }
  1154. const flowAuditors = auditors.filter(x => { return x.order === now_audit.order; });
  1155. await transaction.delete(this.tableName, { mid: materialId, times, aid: now_audit.aid, order: now_audit.order });
  1156. if (flowAuditors.length === 1) await this._syncOrderByDelete(transaction, materialId, now_audit.order, times);
  1157. } else if (data.operate === 'change') {
  1158. const nowAudit = await this.getDataByCondition({ mid: materialId, times, aid: now_audit.aid, order: now_audit.order });
  1159. if (now_audit.status !== auditConst.status.uncheck || !nowAudit) {
  1160. throw '当前人无法操作替换';
  1161. }
  1162. nowAudit.aid = data.new_aid;
  1163. await transaction.update(this.tableName, nowAudit);
  1164. }
  1165. if (this.ctx.tender.info.shenpi.material === shenpiConst.sp_status.gdspl) {
  1166. const newAuditors = await transaction.select(this.tableName, { where: { mid: materialId, times } });
  1167. const newAuditorGroup = this.ctx.helper.groupAuditors(newAuditors);
  1168. const uniqNewAuditorGroup = this.ctx.helper.groupAuditorsUniq(newAuditorGroup);
  1169. await this.ctx.service.shenpiAudit.updateAuditListWithAuditType(transaction, this.ctx.tender.id, this.ctx.tender.info.shenpi.material, shenpiConst.sp_type.material, uniqNewAuditorGroup, sp_group);
  1170. } else if (this.ctx.tender.info.shenpi.material === shenpiConst.sp_status.gdzs) {
  1171. const newAuditors = await this.getAuditGroupByList(materialId, times, transaction);
  1172. await this.ctx.service.shenpiAudit.updateAuditList(transaction, this.ctx.tender.id, this.ctx.tender.info.shenpi.material, shenpiConst.sp_type.material, this._.map(newAuditors, 'aid'));
  1173. }
  1174. // 更新到审批流程方法
  1175. await transaction.commit();
  1176. } catch (err) {
  1177. await transaction.rollback();
  1178. throw err;
  1179. }
  1180. }
  1181. async getAuditorGroup(materialId, times) {
  1182. const auditors = await this.getAuditors(materialId, times); // 全部参与的审批人
  1183. return this.ctx.helper.groupAuditors(auditors);
  1184. }
  1185. async getUserGroup(materialId, times) {
  1186. const group = await this.getAuditorGroup(materialId, times);
  1187. const sql =
  1188. 'SELECT pa.`id` As aid, pa.`name`, pa.`company`, pa.`role`, ? As times, ? As mid, 0 As `order`, 1 As audit_type, 0 As audit_order' +
  1189. ' FROM ' + this.ctx.service.material.tableName + ' As m' +
  1190. ' LEFT JOIN ' + this.ctx.service.projectAccount.tableName + ' As pa' +
  1191. ' ON m.user_id = pa.id' +
  1192. ' WHERE m.id = ?';
  1193. const sqlParam = [times, materialId, materialId];
  1194. const user = await this.db.queryOne(sql, sqlParam);
  1195. group.unshift([ user ]);
  1196. return group;
  1197. }
  1198. async getUniqUserGroup(materialId, times) {
  1199. const group = await this.getAuditorGroup(materialId, times);
  1200. const sql =
  1201. 'SELECT pa.`id` As aid, pa.`name`, pa.`company`, pa.`role`, ? As times, ? As mid, 0 As `order`, 1 As audit_type, 0 As audit_order' +
  1202. ' FROM ' + this.ctx.service.material.tableName + ' As m' +
  1203. ' LEFT JOIN ' + this.ctx.service.projectAccount.tableName + ' As pa' +
  1204. ' ON m.user_id = pa.id' +
  1205. ' WHERE m.id = ?';
  1206. const sqlParam = [times, materialId, materialId];
  1207. const user = await this.db.queryOne(sql, sqlParam);
  1208. user.audit_order = 0;
  1209. group.unshift([ user ]);
  1210. return this.ctx.helper.groupAuditorsUniq(group);
  1211. }
  1212. async getAuditorHistory(materialId, times, reverse = false) {
  1213. const history = [];
  1214. if (times >= 1) {
  1215. for (let i = 1; i <= times; i++) {
  1216. const auditors = await this.getAuditors(materialId, i);
  1217. const group = this.ctx.helper.groupAuditors(auditors);
  1218. const historyGroup = [];
  1219. const max_order = group.length > 0 && group[group.length - 1].length > 0 ? group[group.length - 1][0].audit_order : -1;
  1220. for (const g of group) {
  1221. const his = {
  1222. beginYear: '', beginDate: '', beginTime: '', endYear: '', endDate: '', endTime: '', begin_time: null, end_time: null,
  1223. audit_type: g[0].audit_type, audit_order: g[0].audit_order,
  1224. auditors: g
  1225. };
  1226. if (his.audit_type === auditType.key.common) {
  1227. his.name = g[0].name;
  1228. } else {
  1229. his.name = this.ctx.helper.transFormToChinese(his.audit_order) + '审';
  1230. }
  1231. his.is_final = his.audit_order === max_order;
  1232. if (g[0].begin_time) {
  1233. his.begin_time = g[0].begin_time;
  1234. const beginTime = this.ctx.moment(g[0].begin_time);
  1235. his.beginYear = beginTime.format('YYYY');
  1236. his.beginDate = beginTime.format('MM-DD');
  1237. his.beginTime = beginTime.format('HH:mm:ss');
  1238. }
  1239. let end_time;
  1240. g.forEach(x => {
  1241. if (x.status === auditConst.status.checkSkip) return;
  1242. if (!his.status || x.status === auditConst.status.checking) his.status = x.status;
  1243. if (x.end_time && (!end_time || x.end_time > end_time)) {
  1244. end_time = x.end_time;
  1245. if (his.status !== auditConst.status.checking) his.status = x.status;
  1246. }
  1247. });
  1248. if (end_time) {
  1249. his.end_time = end_time;
  1250. const endTime = this.ctx.moment(end_time);
  1251. his.endYear = endTime.format('YYYY');
  1252. his.endDate = endTime.format('MM-DD');
  1253. his.endTime = endTime.format('HH:mm:ss');
  1254. }
  1255. historyGroup.push(his);
  1256. }
  1257. if (reverse) {
  1258. history.push(historyGroup.reverse());
  1259. } else {
  1260. history.push(historyGroup);
  1261. }
  1262. }
  1263. }
  1264. return history;
  1265. }
  1266. async getUniqAuditor(materialId, times) {
  1267. const auditors = await this.getAuditors(materialId, times); // 全部参与的审批人
  1268. const result = [];
  1269. auditors.forEach(x => {
  1270. if (result.findIndex(r => { return x.aid === r.aid && x.audit_order === r.audit_order; }) < 0) {
  1271. result.push(x);
  1272. }
  1273. });
  1274. return result;
  1275. }
  1276. }
  1277. return MaterialAudit;
  1278. };