material_audit.js 73 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361
  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` = ? ORDER BY la.`order` DESC';
  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. const sql =
  896. 'SELECT la.`aid`, pa.`name`, pa.`company`, pa.`role`, la.`times`, la.`mid`, la.`order`, la.`status`, la.audit_type, la.audit_order ' +
  897. ' FROM (SELECT `aid`, max(`order`) `order` FROM ?? WHERE `mid` = ? and `times` = ? and `is_old` = ? GROUP BY aid) sa' +
  898. ' LEFT JOIN ?? la ON sa.`aid` = la.`aid` AND sa.`order` = la.`order`' +
  899. ' Left JOIN ?? AS pa On la.`aid` = pa.`id` WHERE la.`mid` = ? and la.`times` = ? and la.`is_old` = ? order BY la.`order`';
  900. const sqlParam = [this.tableName, materialId, times, 0, this.tableName, this.ctx.service.projectAccount.tableName, materialId, times, 0];
  901. return await this.db.query(sql, sqlParam);
  902. }
  903. /**
  904. * 获取审核人流程列表(包括原报)
  905. * @param {Number} materialId 调差id
  906. * @param {Number} times 审核次数
  907. * @return {Promise<Array>} 查询结果集(包括原报)
  908. */
  909. async getAuditorsWithOwner(materialId, times = 1) {
  910. const result = await this.getAuditGroupByList(materialId, times);
  911. const sql =
  912. 'SELECT pa.`id` As aid, pa.`name`, pa.`company`, pa.`role`, ? As times, ? As mid, 0 As `order`' +
  913. ' FROM ' +
  914. this.ctx.service.material.tableName +
  915. ' As s' +
  916. ' LEFT JOIN ' +
  917. this.ctx.service.projectAccount.tableName +
  918. ' As pa' +
  919. ' ON s.user_id = pa.id' +
  920. ' WHERE s.id = ?';
  921. const sqlParam = [times, materialId, materialId];
  922. const user = await this.db.queryOne(sql, sqlParam);
  923. result.unshift(user);
  924. return result;
  925. }
  926. /**
  927. * 获取tender下所有的审核列表(报表用)
  928. * @param {Number} tenderId
  929. */
  930. async getAuditorsByTender(tenderId) {
  931. const sql = 'SELECT la.`tid`, la.`mid`, la.`aid`, la.`order`, la.`times`, la.`status`, la.`opinion`, la.`begin_time`, la.`end_time`' +
  932. ' FROM ?? AS la ' +
  933. ' WHERE la.`tid` = ?';
  934. const sqlParam = [this.tableName, tenderId];
  935. return await this.db.query(sql, sqlParam);
  936. }
  937. /**
  938. * 复制上一期的审批人列表给最新一期
  939. *
  940. * @param transaction - 新增一期的事务
  941. * @param {Object} preMaterial - 上一期
  942. * @param {Object} newaMaterial - 最新一期
  943. * @return {Promise<*>}
  944. */
  945. async copyPreMaterialAuditors(transaction, preMaterial, newMaterial) {
  946. const auditors = await this.getAuditGroupByList(preMaterial.id, preMaterial.times);
  947. const newAuditors = [];
  948. for (const a of auditors) {
  949. const na = {
  950. tid: preMaterial.tid,
  951. mid: newMaterial.id,
  952. aid: a.aid,
  953. times: newMaterial.times,
  954. order: a.audit_order,
  955. status: auditConst.status.uncheck,
  956. audit_type: a.audit_type,
  957. audit_order: a.audit_order,
  958. };
  959. newAuditors.push(na);
  960. }
  961. if (newAuditors.length > 0) {
  962. const result = await transaction.insert(this.tableName, newAuditors);
  963. return (result.effectRows = auditors.length);
  964. } else {
  965. return true;
  966. }
  967. }
  968. /**
  969. * 移除审核人
  970. *
  971. * @param {Number} materialId - 材料调差期id
  972. * @param {Number} status - 期状态
  973. * @param {Number} status - 期次数
  974. * @return {Promise<boolean>}
  975. */
  976. async getAuditorByStatus(materialId, status, times = 1) {
  977. let auditor = null;
  978. let sql = '';
  979. let sqlParam = '';
  980. switch (status) {
  981. case auditConst.status.checking :
  982. case auditConst.status.checked :
  983. case auditConst.status.checkNoPre :
  984. sql = 'SELECT la.`aid`, pa.`name`, pa.`company`, pa.`role`, la.`times`, la.`mid`, la.`aid`, la.`order` ' +
  985. ' FROM ?? AS la Left Join ?? AS pa On la.`aid` = pa.`id` ' +
  986. ' WHERE la.`mid` = ? and la.`status` = ? ' +
  987. ' ORDER BY la.`times` desc, la.`order` desc';
  988. sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, materialId, status];
  989. auditor = await this.db.queryOne(sql, sqlParam);
  990. break;
  991. case auditConst.status.checkNo :
  992. sql = 'SELECT la.`aid`, pa.`name`, pa.`company`, pa.`role`, la.`times`, la.`mid`, la.`aid`, la.`order` ' +
  993. ' FROM ?? AS la Left Join ?? AS pa On la.`aid` = pa.`id`' +
  994. ' WHERE la.`mid` = ? and la.`status` = ? and la.`times` = ?' +
  995. ' ORDER BY la.`times` desc, la.`order` desc';
  996. sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, materialId, auditConst.status.checkNo, parseInt(times) - 1];
  997. auditor = await this.db.queryOne(sql, sqlParam);
  998. break;
  999. case auditConst.status.uncheck :
  1000. default:break;
  1001. }
  1002. return auditor;
  1003. }
  1004. async getAuditorsByStatus(materialId, status, times = 1) {
  1005. let auditor = [];
  1006. let sql = '';
  1007. let sqlParam = '';
  1008. let cur;
  1009. switch (status) {
  1010. case auditConst.status.checking:
  1011. case auditConst.status.checked:
  1012. case auditConst.status.checkNoPre:
  1013. cur = await this.db.queryOne(`SELECT * From ${this.tableName} where mid = ? AND times = ? AND status = ? ORDER By times DESC, ` + '`order` DESC', [materialId, times, status]);
  1014. if (!cur) return [];
  1015. sql = 'SELECT la.`aid`, pa.`name`, pa.`company`, pa.`role`, la.`times`, la.`mid`, la.`order`, la.audit_order, la.audit_type ' +
  1016. ' FROM ?? AS la Left Join ?? AS pa On la.`aid` = pa.`id` ' +
  1017. ' WHERE la.`mid` = ? and la.`order` = ? and times = ?';
  1018. sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, materialId, cur.order, times];
  1019. auditor = await this.db.query(sql, sqlParam);
  1020. break;
  1021. case auditConst.status.checkNo:
  1022. 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]);
  1023. if (!cur) return [];
  1024. sql = 'SELECT la.`aid`, pa.`name`, pa.`company`, pa.`role`, la.`times`, la.`mid`, la.`order`, la.audit_order, la.audit_type ' +
  1025. ' FROM ?? AS la Left Join ?? AS pa On la.`aid` = pa.`id` ' +
  1026. ' WHERE la.`mid` = ? and la.`order` = ? and la.`times` = ?';
  1027. sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, materialId, cur.order, parseInt(times) - 1];
  1028. auditor = await this.db.query(sql, sqlParam);
  1029. break;
  1030. case auditConst.status.uncheck:
  1031. default:
  1032. break;
  1033. }
  1034. return auditor;
  1035. }
  1036. async getAllAuditors(tenderId) {
  1037. const sql = 'SELECT ma.aid, ma.tid FROM ' + this.tableName + ' ma' +
  1038. ' LEFT JOIN ' + this.ctx.service.tender.tableName + ' t On ma.tid = t.id' +
  1039. ' WHERE t.id = ?' +
  1040. ' GROUP BY ma.aid';
  1041. const sqlParam = [tenderId];
  1042. return this.db.query(sql, sqlParam);
  1043. }
  1044. async updateNewAuditList(material, newList) {
  1045. const transaction = await this.db.beginTransaction();
  1046. try {
  1047. // 先删除旧的审批流,再添加新的
  1048. await transaction.delete(this.tableName, { mid: material.id, times: material.times });
  1049. const newAuditors = [];
  1050. for (const auditor of newList) {
  1051. newAuditors.push({
  1052. tid: material.tid, mid: material.id, aid: auditor.audit_id,
  1053. times: material.times, order: auditor.audit_order, status: auditConst.status.uncheck,
  1054. audit_type: auditor.audit_type, audit_order: auditor.audit_order,
  1055. });
  1056. }
  1057. if(newAuditors.length > 0) await transaction.insert(this.tableName, newAuditors);
  1058. await transaction.commit();
  1059. } catch (err) {
  1060. await transaction.rollback();
  1061. throw err;
  1062. }
  1063. }
  1064. async updateLastAudit(material, auditList, lastId) {
  1065. const transaction = await this.db.beginTransaction();
  1066. try {
  1067. // 先判断auditList里的aid是否与lastId相同,相同则删除并重新更新order
  1068. const existAudit = auditList.find(x => { return x.aid === lastId });
  1069. let order = auditList.length > 0 ? auditList.reduce((rst, a) => { return Math.max(rst, a.order)}, 0) + 1 : 1; // 最大值 + 1
  1070. // const idList = this._.map(auditList, 'aid');
  1071. // let order = idList.length + 1;
  1072. if (existAudit) {
  1073. await transaction.delete(this.tableName, {mid: material.id, times: material.times, aid: lastId});
  1074. const sameOrder = auditList.filter(x => { return x.order === existAudit.order });
  1075. if (sameOrder.length === 1) {
  1076. const updateData = [];
  1077. auditList.forEach(x => {
  1078. if (x.order <= existAudit.order) return;
  1079. updateData.push({id: x.id, order: x.order - 1, audit_order: x.audit_order - 1});
  1080. });
  1081. if (updateData.length > 0) {
  1082. await transaction.updateRows(updateData);
  1083. }
  1084. order = order - 1;
  1085. }
  1086. }
  1087. // 添加终审
  1088. const newAuditor = {
  1089. tid: material.tid, mid: material.id, aid: lastId,
  1090. times: material.times, order, status: auditConst.status.uncheck,
  1091. audit_type: auditType.key.common, audit_order: order,
  1092. };
  1093. await transaction.insert(this.tableName, newAuditor);
  1094. await transaction.commit();
  1095. } catch (err) {
  1096. await transaction.rollback();
  1097. throw err;
  1098. }
  1099. }
  1100. async getNumByMonth(tid, startMonth, endMonth) {
  1101. 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 ?';
  1102. // 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 ?';
  1103. const sqlParam = [this.tableName, this.tableName, tid, auditConst.status.checked, startMonth, endMonth];
  1104. const result = await this.db.queryOne(sql, sqlParam);
  1105. return result ? result.num : 0;
  1106. }
  1107. /**
  1108. * 删除本次审批流程
  1109. * @param {Number} materialId - 调差id
  1110. * @param {Number} times - 第几次审批
  1111. * @param {Object} data - 更改参数
  1112. * @return {Promise<void>}
  1113. */
  1114. async saveAudit(materialId, times, sp_group, data) {
  1115. const transaction = await this.db.beginTransaction();
  1116. try {
  1117. const auditors = await this.getAuditGroupByList(materialId, times);
  1118. const now_audit = this._.find(auditors, { aid: data.old_aid });
  1119. if (data.operate !== 'del') {
  1120. const exist = await this.getDataByCondition({ mid: materialId, times, aid: data.new_aid, is_old: 0 });
  1121. if (exist) throw '该审核人已存在,请勿重复添加';
  1122. }
  1123. if (data.operate === 'add') {
  1124. if (now_audit.status !== auditConst.status.uncheck && now_audit.status !== auditConst.status.checking) {
  1125. throw '当前人下无法操作新增';
  1126. }
  1127. const newAudit = {
  1128. tid: this.ctx.tender.id,
  1129. mid: materialId,
  1130. aid: data.new_aid,
  1131. order: now_audit.order + 1,
  1132. audit_order: now_audit.audit_order + 1,
  1133. audit_type: auditType.key.common,
  1134. times: times,
  1135. status: 1
  1136. };
  1137. // order+1
  1138. await this._syncOrderByDelete(transaction, materialId, now_audit.order+1, times, '+');
  1139. await transaction.insert(this.tableName, newAudit);
  1140. // 更新审批流程页数据,如果存在
  1141. } else if (data.operate === 'add-sibling') {
  1142. if (now_audit.status !== auditConst.status.uncheck && now_audit.status !== auditConst.status.checking) {
  1143. throw '当前人下无法操作新增';
  1144. }
  1145. const newAudit = {
  1146. tid: this.ctx.tender.id,
  1147. mid: materialId,
  1148. aid: data.new_aid,
  1149. order: now_audit.order,
  1150. audit_order: now_audit.audit_order,
  1151. audit_type: now_audit.audit_type,
  1152. times: times,
  1153. status: 1
  1154. };
  1155. await transaction.insert(this.tableName, newAudit);
  1156. } else if (data.operate === 'del') {
  1157. if (now_audit.status !== auditConst.status.uncheck) {
  1158. throw '当前人无法操作删除';
  1159. }
  1160. const flowAuditors = auditors.filter(x => { return x.order === now_audit.order; });
  1161. await transaction.delete(this.tableName, { mid: materialId, times, aid: now_audit.aid, order: now_audit.order });
  1162. if (flowAuditors.length === 1) await this._syncOrderByDelete(transaction, materialId, now_audit.order, times);
  1163. // 旧的更新为is_old为1
  1164. await transaction.update(this.tableName, { is_old: 1 }, {
  1165. where: {
  1166. mid: materialId,
  1167. times,
  1168. aid: data.old_aid,
  1169. }
  1170. });
  1171. } else if (data.operate === 'change') {
  1172. const nowAudit = await this.getDataByCondition({ mid: materialId, times, aid: now_audit.aid, order: now_audit.order });
  1173. if (now_audit.status !== auditConst.status.uncheck || !nowAudit) {
  1174. throw '当前人无法操作替换';
  1175. }
  1176. nowAudit.aid = data.new_aid;
  1177. await transaction.update(this.tableName, nowAudit);
  1178. // 旧的更新为is_old为1
  1179. await transaction.update(this.tableName, { is_old: 1 }, {
  1180. where: {
  1181. mid: materialId,
  1182. times,
  1183. aid: data.old_aid,
  1184. }
  1185. });
  1186. }
  1187. if (this.ctx.tender.info.shenpi.material === shenpiConst.sp_status.gdspl) {
  1188. const newAuditors = await transaction.select(this.tableName, { where: { mid: materialId, times } });
  1189. const newAuditorGroup = this.ctx.helper.groupAuditors(newAuditors);
  1190. const uniqNewAuditorGroup = this.ctx.helper.groupAuditorsUniq(newAuditorGroup);
  1191. await this.ctx.service.shenpiAudit.updateAuditListWithAuditType(transaction, this.ctx.tender.id, this.ctx.tender.info.shenpi.material, shenpiConst.sp_type.material, uniqNewAuditorGroup, sp_group);
  1192. } else if (this.ctx.tender.info.shenpi.material === shenpiConst.sp_status.gdzs) {
  1193. const newAuditors = await this.getAuditGroupByList(materialId, times, transaction);
  1194. 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'));
  1195. }
  1196. // 更新到审批流程方法
  1197. await transaction.commit();
  1198. } catch (err) {
  1199. await transaction.rollback();
  1200. throw err;
  1201. }
  1202. }
  1203. async getAuditorGroup(materialId, times) {
  1204. const auditors = await this.getAuditors(materialId, times); // 全部参与的审批人
  1205. return this.ctx.helper.groupAuditors(auditors);
  1206. }
  1207. async getUserGroup(materialId, times) {
  1208. const group = await this.getAuditorGroup(materialId, times);
  1209. const sql =
  1210. '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' +
  1211. ' FROM ' + this.ctx.service.material.tableName + ' As m' +
  1212. ' LEFT JOIN ' + this.ctx.service.projectAccount.tableName + ' As pa' +
  1213. ' ON m.user_id = pa.id' +
  1214. ' WHERE m.id = ?';
  1215. const sqlParam = [times, materialId, materialId];
  1216. const user = await this.db.queryOne(sql, sqlParam);
  1217. group.unshift([ user ]);
  1218. return group;
  1219. }
  1220. async getUniqUserGroup(materialId, times) {
  1221. const group = await this.getAuditorGroup(materialId, times);
  1222. const sql =
  1223. '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' +
  1224. ' FROM ' + this.ctx.service.material.tableName + ' As m' +
  1225. ' LEFT JOIN ' + this.ctx.service.projectAccount.tableName + ' As pa' +
  1226. ' ON m.user_id = pa.id' +
  1227. ' WHERE m.id = ?';
  1228. const sqlParam = [times, materialId, materialId];
  1229. const user = await this.db.queryOne(sql, sqlParam);
  1230. user.audit_order = 0;
  1231. group.unshift([ user ]);
  1232. return this.ctx.helper.groupAuditorsUniq(group);
  1233. }
  1234. async getAuditorHistory(materialId, times, reverse = false) {
  1235. const history = [];
  1236. if (times >= 1) {
  1237. for (let i = 1; i <= times; i++) {
  1238. const auditors = await this.getAuditors(materialId, i);
  1239. const group = this.ctx.helper.groupAuditors(auditors);
  1240. const historyGroup = [];
  1241. const max_order = group.length > 0 && group[group.length - 1].length > 0 ? group[group.length - 1][0].audit_order : -1;
  1242. for (const g of group) {
  1243. const his = {
  1244. beginYear: '', beginDate: '', beginTime: '', endYear: '', endDate: '', endTime: '', begin_time: null, end_time: null,
  1245. audit_type: g[0].audit_type, audit_order: g[0].audit_order,
  1246. auditors: g
  1247. };
  1248. if (his.audit_type === auditType.key.common) {
  1249. his.name = g[0].name;
  1250. } else {
  1251. his.name = this.ctx.helper.transFormToChinese(his.audit_order) + '审';
  1252. }
  1253. his.is_final = his.audit_order === max_order;
  1254. if (g[0].begin_time) {
  1255. his.begin_time = g[0].begin_time;
  1256. const beginTime = this.ctx.moment(g[0].begin_time);
  1257. his.beginYear = beginTime.format('YYYY');
  1258. his.beginDate = beginTime.format('MM-DD');
  1259. his.beginTime = beginTime.format('HH:mm:ss');
  1260. }
  1261. let end_time;
  1262. g.forEach(x => {
  1263. if (x.status === auditConst.status.checkSkip) return;
  1264. if (!his.status || x.status === auditConst.status.checking) his.status = x.status;
  1265. if (x.end_time && (!end_time || x.end_time > end_time)) {
  1266. end_time = x.end_time;
  1267. if (his.status !== auditConst.status.checking) his.status = x.status;
  1268. }
  1269. });
  1270. if (end_time) {
  1271. his.end_time = end_time;
  1272. const endTime = this.ctx.moment(end_time);
  1273. his.endYear = endTime.format('YYYY');
  1274. his.endDate = endTime.format('MM-DD');
  1275. his.endTime = endTime.format('HH:mm:ss');
  1276. }
  1277. historyGroup.push(his);
  1278. }
  1279. if (reverse) {
  1280. history.push(historyGroup.reverse());
  1281. } else {
  1282. history.push(historyGroup);
  1283. }
  1284. }
  1285. }
  1286. return history;
  1287. }
  1288. async getUniqAuditor(materialId, times) {
  1289. const auditors = await this.getAuditors(materialId, times); // 全部参与的审批人
  1290. const result = [];
  1291. auditors.forEach(x => {
  1292. if (result.findIndex(r => { return x.aid === r.aid && x.audit_order === r.audit_order; }) < 0) {
  1293. result.push(x);
  1294. }
  1295. });
  1296. return result;
  1297. }
  1298. }
  1299. return MaterialAudit;
  1300. };