|
|
@@ -12,6 +12,8 @@
|
|
|
let doAfterLoadGuidance = null;
|
|
|
|
|
|
const billsGuidance = (function () {
|
|
|
+ //更新类型
|
|
|
+ const updateType = {update: 'update', create: 'create'};
|
|
|
let currentLib = null;
|
|
|
//库类型
|
|
|
const libType = {'guidance': 1, 'elf': 2}; //清单指引、清单精灵
|
|
|
@@ -20,6 +22,8 @@ const billsGuidance = (function () {
|
|
|
let stdBillsJobData = [];
|
|
|
//项目特征
|
|
|
let stdBillsFeatureData = [];
|
|
|
+ //正在插入
|
|
|
+ let isInserting = false;
|
|
|
const bills = {
|
|
|
dom: $('#billsGuidance_bills'),
|
|
|
workBook: null,
|
|
|
@@ -105,7 +109,8 @@ const billsGuidance = (function () {
|
|
|
if(!node){
|
|
|
return;
|
|
|
}
|
|
|
- if(node.children.length > 0){
|
|
|
+ //展开收起(非最底层节点且双击的是第一列)
|
|
|
+ if (args.col === 0 && node.children.length > 0) {
|
|
|
node.setExpanded(!node.expanded);
|
|
|
//设置展开收起状态
|
|
|
sessionStorage.setItem('stdBillsGuidanceExpState', bills.tree.getExpState(bills.tree.items));
|
|
|
@@ -118,12 +123,275 @@ const billsGuidance = (function () {
|
|
|
args.sheet.invalidateLayout();
|
|
|
});
|
|
|
args.sheet.repaint();
|
|
|
- } else if(!projectReadOnly) {
|
|
|
- billsLibObj.insertBills(stdBillsJobData, stdBillsFeatureData, node);
|
|
|
+ } else if (!projectReadOnly && !isInserting && (args.col !== 0 || node.children.length === 0)) {
|
|
|
+ //选中部分的最底层(只是选中部分的最底)
|
|
|
+ let lowestNodes = [bills.tree.items[args.row]];
|
|
|
+ insertBills(lowestNodes);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
};
|
|
|
+ //插入清单
|
|
|
+ function insertBills(lowestNodes) {
|
|
|
+ let selTree = getSelTree(lowestNodes);
|
|
|
+ let compareData = compareTree(projectObj.project.Bills.tree, selTree);
|
|
|
+ let sheet = projectObj.mainSpread.getActiveSheet(),
|
|
|
+ row = sheet.getActiveColumnIndex(),
|
|
|
+ col = sheet.getActiveColumnIndex();
|
|
|
+ if (compareData.postData.length > 0) {
|
|
|
+ isInserting = true;
|
|
|
+ CommonAjax.post('/bills/insertBills', {postData: compareData.postData}, function () {
|
|
|
+ //插入
|
|
|
+ let insertData = _.filter(compareData.postData, {updateType: updateType.create});
|
|
|
+ let treeData = [];
|
|
|
+ for (let data of insertData) {
|
|
|
+ treeData.push(data.updateData);
|
|
|
+ }
|
|
|
+ //插入清单节点
|
|
|
+ projectObj.project.Bills.tree.insertByDatas(treeData);
|
|
|
+ projectObj.project.Bills.datas = projectObj.project.Bills.datas.concat(treeData);
|
|
|
+ //插入主树节点
|
|
|
+ let newNodes = projectObj.project.mainTree.insertByDatas(treeData);
|
|
|
+ for (let node of newNodes) {
|
|
|
+ node.source = projectObj.project.Bills.tree.nodes[projectObj.project.Bills.tree.prefix + node.getID()];
|
|
|
+ node.data = node.source.data;
|
|
|
+ node.sourceType = projectObj.project.Bills.getSourceType();
|
|
|
+ }
|
|
|
+ ProjectController.syncDisplayNewNodes(projectObj.mainController, newNodes, true);
|
|
|
+ row = newNodes[newNodes.length - 1].serialNo();
|
|
|
+ //有新的节点插入,也有可能定位至旧节点(批量选用的情况下)
|
|
|
+ if (compareData.locateNode) {
|
|
|
+ row = compareData.locateNode.serialNo();
|
|
|
+ }
|
|
|
+ sheet.setSelection(row, col, 1, 1);
|
|
|
+ projectObj.mainController.setTreeSelected(projectObj.mainController.tree.items[row]);//触发树节点选中事件
|
|
|
+ sheet.showRow(row, GC.Spread.Sheets.VerticalPosition.center);
|
|
|
+ isInserting = false;
|
|
|
+ }, function () {
|
|
|
+ isInserting = false;
|
|
|
+ });
|
|
|
+ } else if (compareData.locateNode) {
|
|
|
+ row = compareData.locateNode.serialNo();
|
|
|
+ sheet.setSelection(row, col, 1, 1);
|
|
|
+ projectObj.mainController.setTreeSelected(projectObj.mainController.tree.items[row]);//触发树节点选中事件
|
|
|
+ sheet.showRow(row, GC.Spread.Sheets.VerticalPosition.center);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ /*
|
|
|
+ *
|
|
|
+ * 1.选中的树结构(清单规则选中的节点及其所有父项)与主树对比(主树中节点“编码-名称-单位”与选中树~组合相同视为同一节点),
|
|
|
+ * 将主树中不含有的选中节点全部插入
|
|
|
+ * 2.插入位置由1对比得出,主树有与选中树结构相同层次结构的片段,这个相同片段为插入位置
|
|
|
+ * 3.选中树结构除去相同片段,为需要插入的节点,插入时,遇到同层节点,根据编码的字符编码值确定顺序
|
|
|
+ * a.code <= b.code,则a节点在b节点前
|
|
|
+ * */
|
|
|
+ //获取选中的树(将选中的节点及其所有父项,组合成一棵树,没有重复节点)
|
|
|
+ //@param {Array}lowestNodes(选中的最底层节点) @return {Array}
|
|
|
+ function getSelTree(lowestNodes) {
|
|
|
+ let allNodes = [];
|
|
|
+ //获取树上所有的节点
|
|
|
+ for (let node of lowestNodes) {
|
|
|
+ while (node && !allNodes.includes(node)) {
|
|
|
+ allNodes.push(node);
|
|
|
+ node = node.parent;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ //根据原节点serialNo排序,排序后,后一项节点只可能前节点的后兄弟项,或子项(根据原节点ID-ParentID判定)
|
|
|
+ allNodes.sort(function (a, b) {
|
|
|
+ let aV = a.serialNo(),
|
|
|
+ bV = b.serialNo();
|
|
|
+ if (aV > bV) {
|
|
|
+ return 1;
|
|
|
+ } else if (aV < bV) {
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+ return 0;
|
|
|
+ });
|
|
|
+ //生成树数据
|
|
|
+ let treeData = [];
|
|
|
+ //旧ID与新ID映射
|
|
|
+ let IDMapping = {};
|
|
|
+ for (let i = 0; i < allNodes.length; i++) {
|
|
|
+ //原节点
|
|
|
+ let preN = allNodes[i - 1],
|
|
|
+ thisN = allNodes[i];
|
|
|
+ let newNodeData = {ID: uuid.v1(), NextSiblingID: -1, ParentID: -1, orgID: thisN.data.ID};
|
|
|
+ IDMapping[newNodeData.orgID] = newNodeData.ID;
|
|
|
+ treeData.push(newNodeData);
|
|
|
+ //新树数据
|
|
|
+ let preData = treeData[i - 1],
|
|
|
+ thisData = treeData[i];
|
|
|
+ //节点与上节点为同层节点,则此节点设为上节点的后兄弟
|
|
|
+ if (preN && preN.data.ParentID === thisN.data.ParentID) {
|
|
|
+ preData.NextSiblingID = thisData.ID;
|
|
|
+ //节点与上节点不为同层节点,则此节点为某上节点的子几点
|
|
|
+ } else if (preN && preN.data.ParentID !== thisN.data.ParentID) {
|
|
|
+ let parentID = IDMapping[thisN.data.ParentID];
|
|
|
+ thisData.ParentID = parentID;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ let selTree = idTree.createNew({id: 'ID', pid: 'ParentID', nid: 'NextSiblingID', rootId: -1, autoUpdate: true});
|
|
|
+ selTree.loadDatas(treeData);
|
|
|
+ return selTree;
|
|
|
+ }
|
|
|
+ /*
|
|
|
+ * 选中树与清单主树进行对比,(自上而下,从roots开始)获取插入、更新数据
|
|
|
+ * 找到的清单树中清单子项含有非清单项或输入了计算基数,则中止该清单的对比,不可插入数据
|
|
|
+ * */
|
|
|
+
|
|
|
+ //获取节点的匹配数据:编码-名称-单位
|
|
|
+ //@param {Object}node @return {String}
|
|
|
+ function getMatchContent(node) {
|
|
|
+ return `${node.data.code ? node.data.code : '*'}-${node.data.name ? node.data.name : '*'}-${node.data.unit ? node.data.unit: '*'}`;
|
|
|
+ }
|
|
|
+ //**对比清单主树与选中树,获取需要更新和插入的数据**
|
|
|
+ //@param {Object}mainTree {Object}selTree
|
|
|
+ function compareTree(billsTree, selTree) {
|
|
|
+ //需要插入、更新的数据
|
|
|
+ let postData = [],
|
|
|
+ //跟树结构自动定位至的清单节点(最近匹配到的节点)
|
|
|
+ locateNode = null;
|
|
|
+ if (!billsTree || !selTree) {
|
|
|
+ return postData;
|
|
|
+ }
|
|
|
+ comparePeer(null, billsTree.roots, selTree.roots);
|
|
|
+ return {postData, locateNode};
|
|
|
+ /*
|
|
|
+ * 该清单节点是否可以继续往下递归匹配,即该节点是否还可插入子项(在该层匹配到的时候判断)
|
|
|
+ * 1.该底层节点不能含有非清单子项
|
|
|
+ * 2.该节点不能含有计算基数
|
|
|
+ * todo 3.数量单价等相关概念做完了再补上
|
|
|
+ * @param {Object}billsNode(清单树中的某节点) @return {Boolean}
|
|
|
+ * */
|
|
|
+ function canRecursive(billsNode) {
|
|
|
+ //不含有子清单节点但却含有子节点,说明该节点含有非清单子项
|
|
|
+ if (billsNode.children > 0 && billsNode.source.children === 0) {
|
|
|
+ return false;
|
|
|
+ } else if (billsNode.data.calcBase) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ //获取某选中节点要往清单主树同层插入的位置
|
|
|
+ function insertPos(peerNodes, node) {
|
|
|
+ //node选中树中没有记录原清单的数据,只记录了原清单ID,需要node的数据,则要找回原清单
|
|
|
+ let orgNode = bills.tree.nodes[`${bills.tree.prefix}${node.data.orgID}`];
|
|
|
+ if (!orgNode) {
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+ let insertCode = orgNode.data.code ? orgNode.data.code : '';
|
|
|
+ //插入最顶层节点或者无编码,则直接插入到清单主树最后面
|
|
|
+ if (orgNode.depth() === 0 || insertCode === '') {
|
|
|
+ return peerNodes.length;
|
|
|
+ }
|
|
|
+ for (let i = 0; i < peerNodes.length; i++) {
|
|
|
+ let thisNode = peerNodes[i];
|
|
|
+ let thisCode = thisNode.data.code ? thisNode.data.code : '';
|
|
|
+ //确定同层节点的顺序,编码小于等于在前,大于在后
|
|
|
+ if (insertCode <= thisCode) {
|
|
|
+ return i;
|
|
|
+ } else if (insertCode > thisCode) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return peerNodes.length;
|
|
|
+ }
|
|
|
+ //获取插入清单数据
|
|
|
+ function getInsertData(insertObj) {
|
|
|
+ let stdNode = bills.tree.nodes[`${bills.tree.prefix}${insertObj.orgID}`];
|
|
|
+ if (!stdNode) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ let stdData = {};
|
|
|
+ stdData.projectID = projectInfoObj.projectInfo.ID;
|
|
|
+ stdData.isAdd = 1;
|
|
|
+ stdData.ID = insertObj.ID;
|
|
|
+ stdData.ParentID = insertObj.ParentID;
|
|
|
+ stdData.NextSiblingID = insertObj.NextSiblingID;
|
|
|
+ //顶层节点是大项费用
|
|
|
+ stdData.type = stdNode.parent ? billType.BILL : billType.DXFY;
|
|
|
+ stdData.code = stdNode.data.code;
|
|
|
+ stdData.name = stdNode.data.name;
|
|
|
+ stdData.unit = stdNode.data.unit;
|
|
|
+ stdData.ruleText = stdNode.data.ruleText;
|
|
|
+ stdData.comments = stdNode.data.comments;
|
|
|
+ stdData.programID = stdNode.data.engineering;
|
|
|
+ stdData.billsLibId = stdNode.data.billsLibId;
|
|
|
+ return stdData;
|
|
|
+ }
|
|
|
+ //从同层节点中获取更新数据
|
|
|
+ //@param {Object}parentNode(同层节点挂载的父节点,清单主树中) {Array}peerNodes(同层节点,含有清单树和选中树节点)
|
|
|
+ // {Array}billsNodes(该层清单树节点) {Array}selNodes(该层选中树节点) @return {Array}
|
|
|
+ function getUpdateDataFromPeer(parentNode, peerNodes, billsNodes, selNodes) {
|
|
|
+ let rst = [];
|
|
|
+ //向下获取插入数据直到最底层
|
|
|
+ function getDataTillDeepest(node) {
|
|
|
+ if (node) {
|
|
|
+ let insertData = getInsertData(node.data);
|
|
|
+ if (insertData) {
|
|
|
+ rst.push({updateType: updateType.create, updateData: insertData});
|
|
|
+ }
|
|
|
+ for (let child of node.children) {
|
|
|
+ getDataTillDeepest(child);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ for (let i = 0; i < peerNodes.length; i++) {
|
|
|
+ let thisNode = peerNodes[i],
|
|
|
+ nextNode = peerNodes[i + 1];
|
|
|
+ //更新原清单节点NextSiblingID
|
|
|
+ if (billsNodes.includes(thisNode) && selNodes.includes(nextNode)) {
|
|
|
+ rst.push({updateType: updateType.update, updateData: {ID: thisNode.data.ID, NextSiblingID: nextNode.data.ID}});
|
|
|
+ } else if (selNodes.includes(thisNode) && billsNodes.includes(nextNode)) { //变更选中节点的NextSiblingID
|
|
|
+ thisNode.data.NextSiblingID = nextNode.data.ID;
|
|
|
+ }
|
|
|
+ //所有选中的同层节点设为清单树父节点的子项,获取插入数据(插入该同层节点及其所有子节点)
|
|
|
+ if (selNodes.includes(thisNode)) {
|
|
|
+ thisNode.data.ParentID = parentNode ? parentNode.data.ID : -1;
|
|
|
+ getDataTillDeepest(thisNode);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return rst;
|
|
|
+ }
|
|
|
+ //同层节点之间比较,匹配到的则继续往下匹配,匹配不到的节点则按照规定的顺序进行排序,插入更新(更新清单树节点中NextSiblingID改变的节点)
|
|
|
+ function comparePeer(parentNode, billsNodes, selNodes) {
|
|
|
+ let peerNodes = [].concat(billsNodes); //同层节点
|
|
|
+ let matchNode = null; //匹配到的清单主树节点
|
|
|
+ for (let selNode of selNodes) {
|
|
|
+ let stdNode = bills.tree.nodes[`${bills.tree.prefix}${selNode.data.orgID}`],
|
|
|
+ selMatch = getMatchContent(stdNode),
|
|
|
+ isMatched = false;
|
|
|
+ for( let billsNode of billsNodes) {
|
|
|
+ let billsMatch = getMatchContent(billsNode);
|
|
|
+ if (selMatch === billsMatch) {//只进行一次成功匹配
|
|
|
+ matchNode = billsNode;
|
|
|
+ isMatched = true;
|
|
|
+ if (selNode.children.length === 0) {//成功匹配且为选中的最底节点,则为自动定位节点
|
|
|
+ locateNode = matchNode;
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (isMatched && canRecursive(matchNode)) {//匹配成功,且该匹配到的节点可插入子项,递归匹配子项
|
|
|
+ comparePeer(matchNode, matchNode.children, selNode.children);
|
|
|
+ } else if (!isMatched) { //匹配不成功,其节点与该层清单节点同层,根据编码插入同层数组中
|
|
|
+ let pos = insertPos(peerNodes, selNode);
|
|
|
+ if (pos >= 0) {
|
|
|
+ peerNodes.splice(pos, 0, selNode);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ //同层节点比清单树节点多了,说明在该层有选中节点插入清单树中
|
|
|
+ if (peerNodes.length > billsNodes.length) {
|
|
|
+ let updateData = getUpdateDataFromPeer(parentNode, peerNodes, billsNodes, selNodes);
|
|
|
+ postData = postData.concat(updateData);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
//项目指引类型
|
|
|
const itemType = {
|
|
|
job: 0,
|