'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.ex_memo1 = []; item.ex_memo2 = []; item.ex_memo3 = []; 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, measureType) { 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' }); this.checkResult = { error: [], source: { bills: [], pos: [], }, }; this.measureType = measureType; } _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) { 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.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, 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) { this.checkResult.error.push({ ledger_id: bills.ledger_id, b_code: bills.b_code, name: bills.name, errorType: 's2b_over_' + o, }); } for (const l of lost) { this.checkResult.error.push({ ledger_id: bills.ledger_id, b_code: bills.b_code, name: bills.name, errorType: 's2b_lost_' + l, }); } if (!this.checkResult.source.bills.find(x => {return x.ledger_id === bills.ledger_id})) { this.checkResult.source.bills.push(bills); if (posRange && posRange.length > 0) this.checkResult.source.pos.push(...posRange); } } } _recursiveCheckBills3fLimit(checkType, bills, 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, checkInfo); } } else { this._checkLeafBills3fLimit(checkType, bills, checkInfo); } } loadData(bills, pos) { this.checkBills.loadDatas(bills); this.checkPos.loadDatas(pos); } checkSibling() { 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) this.checkResult.error.push({ ledger_id: node.ledger_id, b_code: node.b_code, name: node.name, errorType: 'sibling', }); } } checkSameCode() { //let xmj = this.checkBills.nodes.filter(x => { return /^((GD*)|G)?[0-9]+/.test(x.code); }); let xmj = []; const addXmjCheck = function (node) { if (/^((GD*)|G)?[0-9]+/.test(node.code)) xmj.push(node); for (const child of node.children) { addXmjCheck(child); } }; for (const topLevel of this.checkBills.children) { if ([1, 2, 3, 4].indexOf(topLevel.node_type) < 0) continue; addXmjCheck(topLevel); } const xmjPart = {}, xmjIndex = []; for (const x of xmj) { if (!xmjPart[x.code]) { xmjPart[x.code] = []; xmjIndex.push(x.code); } xmjPart[x.code].push(x); } for (const x of xmjIndex) { if (xmjPart[x].length <= 1) continue; for (const xp of xmjPart[x]) { this.checkResult.error.push({ ledger_id: xp.ledger_id, b_code: xp.b_code, name: xp.name, errorType: 'same_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) { this.checkResult.error.push({ ledger_id: c.ledger_id, b_code: c.b_code, name: c.name, errorType: 'same_code', }) } } } } check3fLimit(tender) { const check = []; if (tender.s2b_gxby_limit) check.push('gxby'); if (tender.s2b_dagl_limit) check.push('dagl'); if (check.length === 0) return; for (const b of this.checkBills.children) { this._recursiveCheckBills3fLimit(check, b, {}); } } checkBillsQty(fields) { for (const b of this.checkBills.nodes) { if (b.children && b.children.length > 0) continue; const pr = this.checkPos.getLedgerPos(b.id); if (!pr || pr.length === 0) continue; const checkData = {}, calcData = {}; for (const field of fields) { checkData[field] = b[field] ? b[field] : 0; } for (const p of pr) { for (const field of fields) { calcData[field] = this.ctx.helper.add(calcData[field], p[field]); } } if (!this.ctx.helper._.isMatch(checkData, calcData)) { this.checkResult.error.push({ ledger_id: b.ledger_id, b_code: b.b_code, name: b.name, errorType: 'qty', error: { checkData, calcData }, }); if (!this.checkResult.source.bills.find(x => {return x.ledger_id === b.ledger_id})) { this.checkResult.source.bills.push(b); for (const p of pr) { this.checkResult.source.pos.push(p); } } } } } checkBillsTp(field, decimal, filter) { for (const b of this.checkBills.nodes) { if ((b.children && b.children.length > 0) || !b.check_calc) continue; if (filter && filter(b)) continue; const checkData = {}, calcData = {}; for (const f of field) { checkData[f.tp] = b[f.tp] || 0; calcData[f.tp] = this.ctx.helper.mul(b.unit_price, b[f.qty], decimal.tp) || 0; } if (!this.ctx.helper._.isMatch(checkData, calcData)) { this.checkResult.error.push({ ledger_id: b.ledger_id, b_code: b.b_code, name: b.name, errorType: 'tp', error: { checkData, calcData }, }); if (!this.checkResult.source.bills.find(x => {return x.ledger_id === b.ledger_id})) { this.checkResult.source.bills.push(b); } } } } _checkBillsOverRange(bills, posRange, isTz) { // if (isTz && posRange.length > 0) { // for (const p of posRange) { // const end_contract_qty = this.add(p.pre_contract_qty, p.contract_qty); // if (end_contract_qty > p.quantity) return true; // } // return false; // } else { // const end_qc_qty = this.add(bills.qc_qty, bills.pre_qc_qty); // const end_qc_tp = this.add(bills.qc_tp, bills.pre_qc_tp); // const end_gather_qty = this.sum([bills.contract_qty, bills.pre_contract_qty, end_qc_qty]); // const end_gather_tp = this.sum([bills.contract_tp, bills.pre_contract_tp, end_qc_tp]); // if (isTz) { // if (end_gather_qty) { // return !bills.quantity || Math.abs(end_gather_qty) > Math.abs(this.add(bills.quantity, end_qc_qty)); // } else if (end_gather_tp) { // return !bills.total_price || Math.abs(end_gather_tp) > Math.abs(this.add(bills.total_price, end_qc_tp)); // } // } else { // if (end_gather_qty) { // return !bills.deal_qty || Math.abs(end_gather_qty) > Math.abs(this.add(bills.deal_qty, end_qc_qty)); // } else if (end_gather_tp) { // return !bills.deal_tp || Math.abs(end_gather_tp) > Math.abs(this.add(bills.deal_tp, end_qc_tp)); // } // } // } if (isTz && posRange.length > 0) { if (posRange.length > 0) { for (const p of posRange) { const end_contract_qty = this.ctx.helper.add(p.pre_contract_qty, p.contract_qty); if (!p.quantity && !!end_contract_qty) return true; if (p.quantity > 0) { if (end_contract_qty > p.quantity) return true; } else { if (end_contract_qty < p.quantity || end_contract_qty > 0) return true; } } return false; } } else { const end_contract_qty = this.ctx.helper.add(bills.contract_qty, bills.pre_contract_qty); const end_contract_tp = this.ctx.helper.add(bills.contract_tp, bills.pre_contract_tp); if (bills.is_tp) { const compare_tp = isTz ? bills.total_price : bills.deal_tp; if (!compare_tp) return !!end_contract_tp; return compare_tp >= 0 ? end_contract_tp > compare_tp : end_contract_tp < compare_tp || end_contract_tp > 0; } else { const compare_qty = isTz ? bills.quantity : bills.deal_qty; if (!compare_qty) return !!end_contract_qty; return compare_qty >= 0 ? end_contract_qty > compare_qty : end_contract_qty < compare_qty || end_contract_qty > 0; } } } checkOverRange() { const isTz = this.ctx.tender.data.measure_type === this.measureType.tz.value; for (const b of this.checkBills.nodes) { if (b.children && b.children.length > 0) continue; const pr = this.checkPos.getLedgerPos(b.id) || []; if (this._checkBillsOverRange(b, pr, isTz)) { this.checkResult.error.push({ ledger_id: b.ledger_id, b_code: b.b_code, name: b.name, errorType: 'over', }); if (!this.checkResult.source.bills.find(x => {return x.ledger_id === b.ledger_id})) { this.checkResult.source.bills.push(b); if (pr.length > 0) this.checkResult.source.pos.push(...pr); } } } } } module.exports = { billsTree, pos, filterTree, filterGatherTree, gatherTree, checkData, };