'use strict'; /** * * * @author Mai * @date * @version */ const itemsPre = 'id_'; class baseTree { /** * 构造函数 */ constructor (ctx, setting) { this.ctx = ctx; // 无索引 this.datas = []; // 以key为索引 this.items = {}; // 以排序为索引 this.nodes = []; // 根节点 this.children = []; // 树设置 this.setting = setting; } clear() { // 无索引 this.datas = []; // 以key为索引 this.items = {}; // 以排序为索引 this.nodes = []; // 根节点 this.children = []; } /** * 根据id获取树结构节点数据 * @param {Number} id * @returns {Object} */ getItems (id) { return this.items[itemsPre + id]; }; /** * 查找node的parent * @param {Object} node * @returns {Object} */ getParent (node) { return this.getItems(node[this.setting.pid]); }; /** * 查询node的已下载子节点 * @param {Object} node * @returns {Array} */ getChildren (node) { const setting = this.setting; const pid = node ? node[setting.id] : setting.rootId; const children = this.datas.filter(function (x) { return x[setting.pid] === pid; }); children.sort(function (a, b) { return a.order - b.order; }); return children; }; /** * 获取节点的 index * @param node * @returns {number} */ getNodeSerialNo(node) { return this.nodes.indexOf(node); } /** * 树结构根据显示排序 */ sortTreeNode (isResort) { const self = this; const addSortNodes = function (nodes) { if (!nodes) { return } for (let i = 0; i < nodes.length; i++) { self.nodes.push(nodes[i]); nodes[i].index = self.nodes.length - 1; if (!isResort) { nodes[i].children = self.getChildren(nodes[i]); } else { nodes[i].children.sort(function (a, b) { return a.order - b.order; }) } addSortNodes(nodes[i].children); } }; this.nodes = []; if (!isResort) { this.children = this.getChildren(); } else { this.children.sort(function (a, b) { return a.order - b.order; }) } addSortNodes(this.children); } /** * 加载数据(初始化), 并给数据添加部分树结构必须数据 * @param datas */ loadDatas (datas) { // 清空旧数据 this.items = {}; this.nodes = []; this.datas = []; this.children = []; // 加载全部数据 datas.sort(function (a, b) { return a.level - b.level; }); for (const data of datas) { const keyName = itemsPre + data[this.setting.id]; if (!this.items[keyName]) { const item = JSON.parse(JSON.stringify(data)); item.children = []; item.expanded = true; item.visible = true; this.items[keyName] = item; this.datas.push(item); if (item[this.setting.pid] === this.setting.rootId) { this.children.push(item); } else { const parent = this.getParent(item); if (parent) { parent.children.push(item); } } } } this.children.sort(function (a, b) { return a.order - b.order; }); this.sortTreeNode(true); } /** * 递归方式 查询node的已下载的全部后代 (兼容full_path不存在的情况) * @param node * @returns {*} * @private */ _recursiveGetPosterity (node) { let posterity = node.children; for (const c of node.children) { posterity = posterity.concat(this._recursiveGetPosterity(c)); } return posterity; }; /** * 查询node的已下载的全部后代 * @param {Object} node * @returns {Array} */ getPosterity (node) { const self = this; let posterity; if (node.full_path !== '') { const reg = new RegExp('^' + node.full_path + '-'); posterity = this.datas.filter(function (x) { return reg.test(x.full_path); }); } else { posterity = this._recursiveGetPosterity(node); } posterity.sort(function (x, y) { return self.getNodeSerialNo(x) - self.getNodeSerialNo(y); }); return posterity; }; /** * 根据 字段名称 获取数据 * @param fields * @returns {Array} */ getDatas (fields) { const datas = []; for (const node of this.nodes) { if (node.b_code && node.b_code !== '') node.chapter = this.ctx.helper.getChapterCode(node.b_code); node.is_leaf = !node.children || node.children.length === 0; const data = {}; for (const field of fields) { data[field] = node[field]; } datas.push(data); } return datas; } /** * 排除 某些字段 获取数据 * @param fields * @returns {Array} */ getDatasWithout (fields, filter) { const datas = []; for (const node of this.nodes) { if (filter && filter(node)) { continue; } if (node.b_code && node.b_code !== '') node.chapter = this.ctx.helper.getChapterCode(node.b_code); node.is_leaf = !node.children || node.children.length === 0; const data = {}; for (const field in node) { if (fields.indexOf(field) === -1) { data[field] = node[field]; } } datas.push(data); } return datas; } /** * 获取默认数据 剔除一些树结构需要的缓存数据 * @returns {Array} */ getDefaultDatas(filter) { return this.getDatasWithout(['expanded', 'visible', 'children', 'index'], filter); } _mapTreeNode () { let map = {}, maxLevel = 0; for (const node of this.nodes) { let levelArr = map[node.level]; if (!levelArr) { levelArr = []; map[node.level] = levelArr; } if (node.level > maxLevel) { maxLevel = node.level; } levelArr.push(node); } return [maxLevel, map]; } _calculateNode (node, fun) { const self = this; if (node.children && node.children.length > 0) { const gather = node.children.reduce(function (rst, x) { const result = {}; for (const cf of self.setting.calcFields) { result[cf] = self.ctx.helper.add(rst[cf], x[cf]); } return result; }); // 汇总子项 for (const cf of this.setting.calcFields) { if (gather[cf]) { node[cf] = gather[cf]; } else { node[cf] = null; } } } // 自身运算 if (fun) { fun(node); } else if (this.setting.calc) { this.setting.calc(node, this.ctx.helper); } } calculateAll(fun) { const [maxLevel, levelMap] = this._mapTreeNode(); for (let i = maxLevel; i >= 0; i--) { const levelNodes = levelMap[i]; if (levelNodes && levelNodes.length > 0) { for (const node of levelNodes) { this._calculateNode(node, fun); } } } } } class billsTree extends baseTree { /** * 检查节点是否是最底层项目节 * @param node * @returns {boolean} */ isLeafXmj(node) { if (node.b_code && node.b_code !== '') { return false; } for (const child of node.children) { if (!child.b_code || child.b_code === '') { return false; } } return true; } /** * 查询最底层项目节(本身或父项) * @param {Object} node - 查询节点 * @returns {Object} */ getLeafXmjParent(node) { let parent = node; while (parent) { if (this.isLeafXmj(parent)) { return parent; } else { parent = this.getParent(parent); } } return null; } } class filterTree extends baseTree { addData(data, fields) { const item = {}; for (const prop in data) { if (fields.indexOf(prop) >= 0) { item[prop] = data[prop]; } } const keyName = itemsPre + item[this.setting.id]; if (!this.items[keyName]) { item.children = []; item.is_leaf = true; item.expanded = true; item.visible = true; this.items[keyName] = item; this.datas.push(item); if (item[this.setting.pid] === this.setting.rootId) { this.children.push(item); } else { const parent = this.getParent(item); if (parent) { parent.is_leaf = false; parent.children.push(item); } } } else { return this.items[keyName]; } return item; } } class filterGatherTree extends baseTree { clearDatas() { this.items = {}; this.nodes = []; this.datas = []; this.children = []; } get newId() { if (!this._maxId) { this._maxId = 0; } this._maxId++; return this._maxId; } addNode(data, parent) { data[this.setting.pid] = parent ? parent[this.setting.id] : this.setting.rootId; let item = this.ctx.helper._.find(this.items, data); if (item) return item; item = data; item.drawing_code = []; item.memo = []; item.postil = []; item[this.setting.id] = this.newId; const keyName = itemsPre + item[this.setting.id]; item.children = []; item.is_leaf = true; item.expanded = true; item.visible = true; this.items[keyName] = item; this.datas.push(item); if (parent) { item[this.setting.fullPath] = parent[this.setting.fullPath] + '-' + item[this.setting.id]; item[this.setting.level] = parent[this.setting.level] + 1; item[this.setting.order] = parent.children.length + 1; parent.is_leaf = false; parent.children.push(item); } else { item[this.setting.fullPath] = '' + item[this.setting.id]; item[this.setting.level] = 1; item[this.setting.order] = this.children.length + 1; this.children.push(item); } return item; } generateSortNodes() { const self = this; const addSortNode = function (node) { self.nodes.push(node); for (const c of node.children) { addSortNode(c); } }; this.nodes = []; for (const n of this.children) { addSortNode(n); } } sortTreeNodeCustom(fun) { const sortNodes = function (nodes) { nodes.sort(fun); for (const [i, node] of nodes.entries()) { node.order = i + 1; } for (const node of nodes) { if (node.children && node.children.length > 1) { sortNodes(node.children); } } }; this.nodes = []; this.children = this.getChildren(null); sortNodes(this.children); this.generateSortNodes(); } } class gatherTree extends baseTree { constructor(ctx, setting) { super(ctx, setting); this._newId = 1; } get newId() { return this._newId++; } loadGatherNode(node, parent, loadFun) { const siblings = parent ? parent.children : this.children; let cur = siblings.find(function (x) { return node.b_code ? x.b_code === node.b_code && x.name === node.name && x.unit === node.unit && x.unit_price === node.unit_price : x.code === node.code && x.name === node.name; }); if (!cur) { const id = this.newId; cur = { id: id, pid: parent ? parent.id : this.setting.rootId, full_path: parent ? parent.full_path + '-' + id : '' + id, level: parent ? parent.level + 1 : 1, order: siblings.length + 1, children: [], code: node.code, b_code: node.b_code, name: node.name, unit: node.unit, unit_price: node.unit_price, }; siblings.push(cur); this.datas.push(cur); } loadFun(cur, node); for (const c of node.children) { this.loadGatherNode(c, cur, loadFun); } } generateSortNodes() { const self = this; const addSortNode = function (node) { self.nodes.push(node); for (const c of node.children) { addSortNode(c); } }; this.nodes = []; for (const n of this.children) { addSortNode(n); } } loadGatherTree(sourceTree, loadFun) { for (const c of sourceTree.children) { this.loadGatherNode(c, null, loadFun); } // todo load Pos Data; } calculateSum() { if (this.setting.calcSum) { for (const d of this.datas) { this.setting.calcSum(d, this.count); } } } } class pos { /** * 构造函数 * @param {id|Number, masterId|Number} setting */ constructor (setting) { // 无索引 this.datas = []; // 以key为索引 this.items = {}; // 以分类id为索引的有序 this.ledgerPos = {}; // pos设置 this.setting = setting; } /** * 加载部位明细数据 * @param datas */ loadDatas(datas) { this.datas = datas; this.items = {}; this.ledgerPos = {}; for (const data of this.datas) { const key = itemsPre + data[this.setting.id]; this.items[key] = data; const masterKey = itemsPre + data[this.setting.ledgerId]; if (!this.ledgerPos[masterKey]) { this.ledgerPos[masterKey] = []; } this.ledgerPos[masterKey].push(data); } for (const prop in this.ledgerPos) { this.resortLedgerPos(this.ledgerPos[prop]); } } getLedgerPos(mid) { return this.ledgerPos[itemsPre + mid]; } resortLedgerPos(ledgerPos) { if (ledgerPos instanceof Array) { ledgerPos.sort(function (a, b) { return a.porder - b.porder; }) } } /** * 计算全部 */ calculateAll(fun) { const calcFun = fun ? fun : this.setting.calc; if (!calcFun) return; for (const pos of this.datas) { calcFun(pos); } } getDatas () { return this.datas; } } class checkData { constructor(ctx) { this.ctx = ctx; this.checkBills = new billsTree(ctx, { id: 'ledger_id', pid: 'ledger_pid', order: 'order', level: 'level', rootId: -1 }); this.checkPos = new pos({ id: 'id', ledgerId: 'lid' }); } _check3f(data, limit, ratio) { if (limit === 0) { if (data.contract_tp || data.pre_contract_tp) return 1; // 违规 } if (limit === 1) { if (ratio === 0) { if (!data.contract_tp && !data.pre_contract_tp) return 2; // 漏计 } else { const tp = this.ctx.helper.mul(data.total_price, this.ctx.helper.div(ratio, 100, 4), this.ctx.tender.info.decimal.tp); const checkTp = this.ctx.helper.add(data.contract_tp, data.pre_contract_tp); if (tp > checkTp) return 1; // 违规 if (tp < checkTp) return 2; // 漏计 } } return 0; // 合法 } _check3fQty(data, limit, ratio, unit) { if (limit === 0) { if (data.contract_qty || data.qc_qty || data.pre_contract_qty || data.pre_qc_qty) return 1; // 违规 } if (limit === 1) { if (!ratio || ratio === 0) { if (!data.contract_qty && !data.qc_qty && !data.pre_contract_qty && !data.pre_qc_qty) return 2; // 漏计 } else { const precision = this.ctx.helper.findPrecision(this.ctx.tender.info.precision, unit); const checkQty = this.ctx.helper.mul(data.quantity, this.ctx.helper.div(ratio, 100, 4), precision.value); const qty = this.ctx.helper.add(data.contract_qty, data.pre_contract_qty); if (qty > checkQty) return 1; // 违规 if (qty < checkQty) return 2; // 漏计 } } return 0; // 合法 } _getRatio(type, status) { if (type === 'gxby') return null; const gs = this.ctx.session.sessionProject.dagl_status.find(x => { return x.value === status }); return gs ? gs.ratio : null; } _getValid = function (type, status, limit) { if (limit) { const statusConst = type === 'gxby' ? this.ctx.session.sessionProject.gxby_status : this.ctx.session.sessionProject.dagl_status; const sc = statusConst.find(x => { return x.value === status; }); return sc ? (sc.limit ? 1 : 0) : 0; } else { return -1; } }; _checkLeafBills3fLimit(checkType, bills, result, checkInfo) { const over = [], lost = []; const posRange = this.checkPos.getLedgerPos(bills.id); if (posRange && posRange.length > 0) { for (const p of posRange) { const posCheckInfo = this.ctx.helper._.assign({}, checkInfo); for (const ct of checkType) { if (p[ct + '_limit'] > 0) { posCheckInfo[ct + '_limit'] = p[ct + '_limit']; } } for (const ct of checkType) { const checkResult = this._check3fQty(p, this._getValid(ct, p[ct + '_status'], posCheckInfo[ct + '_limit']), this._getRatio(ct, p[ct+'_status']), bills.unit); if (checkResult === 1) { if (over.indexOf(ct) === -1) over.push(ct); } if (checkResult === 2) { if (lost.indexOf(ct) === -1) lost.push(ct); } } } } else { for (const ct of checkType) { const checkResult = bills.is_tp ? this._check3f(bills, this._getValid(ct, bills[ct + '_status'], checkInfo[ct + '_limit']), this._getRatio(ct, bills[ct+'_status'])) : this._check3fQty(bills, this._getValid(ct, bills[ct + '_status'], checkInfo[ct + '_limit']), this._getRatio(ct, bills[ct+'_status']), bills.unit); if (checkResult === 1) { if (over.indexOf(ct) === -1) over.push(ct); } if (checkResult === 2) { if (lost.indexOf(ct) === -1) lost.push(ct); } } } if (over.length + lost.length > 0) { for (const o of over) { result.error.push({ ledger_id: bills.ledger_id, b_code: bills.b_code, name: bills.name, errorType: 's2b_over_' + o, }); } for (const l of lost) { result.error.push({ ledger_id: bills.ledger_id, b_code: bills.b_code, name: bills.name, errorType: 's2b_lost_' + l, }); } result.source.bills.push(bills); if (posRange && posRange.length > 0) result.source.pos.push(...posRange); } } _recursiveCheckBills3fLimit(checkType, bills, result, parentCheckInfo) { const checkInfo = this.ctx.helper._.assign({}, parentCheckInfo); for (const ct of checkType) { if (bills[ct + '_limit'] > 0) { checkInfo[ct + '_limit'] = bills[ct + '_limit']; } } if (bills.children && bills.children.length > 0) { for (const c of bills.children) { this._recursiveCheckBills3fLimit(checkType, c, result, checkInfo); } } else { this._checkLeafBills3fLimit(checkType, bills, result, checkInfo); } } loadData(bills, pos) { this.checkBills.loadDatas(bills); this.checkPos.loadDatas(pos); } checkSibling() { const error = []; for (const node of this.checkBills.nodes) { if (!node.children || node.children.length === 0) continue; let hasXmj, hasGcl; for (const child of node.children) { if (child.b_code) hasXmj = true; if (!child.b_code) hasGcl = true; } if (hasXmj && hasGcl) error.push({ ledger_id: node.ledger_id, b_code: node.b_code, name: node.name, errorType: 'sibling', }); } return error; } checkSameCode() { const error = []; let xmj = this.checkBills.nodes.filter(x => { return /^((GD*)|G)?[0-9]+/.test(x.code); }); let check = null; while (xmj.length > 0) { [check, xmj] = this.ctx.helper._.partition(xmj, x => { return x.code === xmj[0].code; }); if (check.length > 1) { for (const c of check) { error.push({ ledger_id: c.ledger_id, b_code: c.b_code, name: c.name, errorType: 'same_code', }) } } } return error; } check3fLimit(tender) { const result = { error: [], source: {bills: [], pos: []}, }; const check = []; if (tender.s2b_gxby_limit) check.push('gxby'); if (tender.s2b_dagl_limit) check.push('dagl'); if (check.length === 0) return result; for (const b of this.checkBills.children) { this._recursiveCheckBills3fLimit(check, b, result, {}); } return result; } } module.exports = { billsTree, pos, filterTree, filterGatherTree, gatherTree, checkData, };