'use strict'; const auditConst = require('../const/audit'); const shenpiConst = require('../const/shenpi'); const crypto = require('crypto'); const TenderApprovalDraft = require('../public/js/shares/tender_approval_draft'); class TenderApprovalSetting { constructor(ctx) { this.ctx = ctx; } async load(tid) { const ctx = this.ctx; if (!ctx.session.sessionUser.is_admin) throw '没有设置权限'; const tenderId = Number(tid); if (!Number.isSafeInteger(tenderId) || tenderId <= 0) throw '参数有误'; const tender = await ctx.service.tender.getDataById(tenderId); if (!tender || tender.spid !== ctx.subProject.id || tender.project_id !== ctx.session.sessionProject.id) { throw '标段不存在或不属于当前项目'; } const tenderInfo = await ctx.service.tenderInfo.getTenderInfo(tender.id); if (!tenderInfo) throw '标段信息不存在'; const lastRevise = await ctx.service.ledgerRevise.getLastestRevise(tender.id); return { tender, revising: Boolean(lastRevise && lastRevise.status !== auditConst.revise.status.checked), shenpi: await ctx.service.shenpiAudit.getShenpi(tender.id, tenderInfo), }; } async loadDraft(tid, code) { const current = await this.load(tid); const spec = shenpiConst.sp_lc.find(x => x.code === code); if (!spec) throw '审批栏目不存在'; const snapshot = await this.ctx.service.tenderApprovalSetting.getSnapshot(current.tender.id, spec.type, code === 'stage'); if (!snapshot.info) throw '标段信息不存在'; const options = await this.getOptions(current, spec); return { snapshot: this.toState(snapshot, code, options.accounts), options, version: this.version(snapshot, code) }; } async saveDraft(tid, code, version, operations) { const current = await this.load(tid); const spec = shenpiConst.sp_lc.find(x => x.code === code); if (!spec || !Array.isArray(operations) || operations.length > 1000 || !operations.length) throw '保存参数有误'; const service = this.ctx.service.tenderApprovalSetting; const transaction = await service.beginTransaction(); try { const tenders = await this.ctx.service.tender.getAllDataForUpdate({ id: current.tender.id }, transaction); if (!tenders.length || tenders[0].spid !== this.ctx.subProject.id || tenders[0].project_id !== this.ctx.session.sessionProject.id) throw '标段已变更,请刷新'; current.tender = tenders[0]; const snapshot = await service.getSnapshot(current.tender.id, spec.type, code === 'stage', transaction); if (!snapshot.info || this.version(snapshot, code) !== version) throw '审批配置已被修改,请关闭弹窗后重新编辑'; const options = await this.getOptions(current, spec); const before = this.toState(snapshot, code, options.accounts); const draft = new TenderApprovalDraft(before, options); for (const operation of operations) draft.apply(operation); draft.validate(); const shenpi = snapshot.info.shenpi ? JSON.parse(snapshot.info.shenpi) : {}; shenpi[code] = draft.state.status; await service.persist(transaction, current.tender.id, spec.type, { before: this.databaseRows(before), after: this.databaseRows(draft.state), shenpi, stage: code === 'stage', cooperation: draft.cooperation, }); await transaction.commit(); } catch (error) { await transaction.rollback(); throw error; } return { saved: true }; } async getOptions(current, spec) { const accounts = await this.ctx.service.projectAccount.getAllSubProjectAccount(this.ctx.subProject); let ledgerIds = null; if (spec.code === 'stage') { const ledgers = await this.ctx.service.ledger.getAllDataByCondition({ columns: ['ledger_id', 'b_code'], where: { tender_id: current.tender.id } }); ledgerIds = ledgers.filter(x => !x.b_code).map(x => String(x.ledger_id)); } return { code: spec.code, spType: spec.type, name: spec.name, owner: current.tender.user_id, canCooperate: !current.revising && current.tender.ledger_status === auditConst.ledger.status.checked, accounts: accounts.map(x => ({ id: x.id, name: x.name, company: x.company, role: x.role })), auditTypes: auditConst.auditType.types, ledgerIds, }; } toState(snapshot, code, accounts) { const shenpi = snapshot.info.shenpi ? JSON.parse(snapshot.info.shenpi) : {}; const rows = this.databaseRows(snapshot); for (const row of rows.groups) row.change_type = row.change_type ? JSON.parse(row.change_type) : null; for (const row of rows.audits) { const account = accounts.find(x => x.id === row.audit_id); Object.assign(row, { name: account ? account.name : '用户' + row.audit_id, company: account ? account.company : '', role: account ? account.role : '' }); } return Object.assign(rows, { status: shenpi[code] || 1 }); } version(snapshot, code) { const shenpi = snapshot.info.shenpi ? JSON.parse(snapshot.info.shenpi) : {}; const rows = this.databaseRows(snapshot); for (const key of Object.keys(rows)) rows[key].sort((a, b) => a.id - b.id); return crypto.createHash('sha256').update(JSON.stringify({ status: shenpi[code] || 1, rows })).digest('hex'); } databaseRows(state) { const fields = { groups: ['id', 'name', 'is_select', 'change_type'], audits: ['id', 'audit_id', 'sp_status', 'sp_group', 'audit_order', 'audit_type', 'audit_group', 'audit_group_order', 'audit_group_limit', 'audit_checkno_valid', 'audit_group_need', 'audit_ledger_id'], assistants: ['id', 'user_id', 'ass_user_id', 'name', 'company', 'role', 'ass_ledger_id'], }; const result = {}; for (const key of Object.keys(fields)) { result[key] = state[key].map(row => { const data = {}; for (const field of fields[key]) { if (row[field] !== undefined) data[field] = row[field]; } if (key === 'groups' && data.change_type && typeof data.change_type !== 'string') data.change_type = JSON.stringify(data.change_type); return data; }); } return result; } } module.exports = TenderApprovalSetting;