12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181 |
- /*
- * @Descripttion: 超高降效相关
- * @Author: Zhong
- * @Date: 2019-12-19 17:45:40
- */
- /*
- * 整体逻辑为:
- * 1.一个请求中
- * {删除}所有超高子目
- * {更新}清单、定额下拉列文本
- * {新增}清单、定额、定额人材机、项目人材机
- * 2.一个请求{重算被删除的定额清单节点、新增的定额节点
- */
- const OVER_HEIGHT = (() => {
- // 选项类型,生成的超高子目所在位置
- const Option = {
- // 对应清单或分部下(默认)
- SEPARATION: 1,
- // 指定措施清单011704001
- MEASURE: 2,
- // 指定具体位置,显示分部分项以及措施项目的树结构显示叶子清单(分项)供勾选
- SPECIFIC: 3,
- };
- // 系数类别,类别不同,生成的定额人材机和算法不同
- const RateType = {
- LABOUR: 1,
- MACHINE: 2,
- LABOUR_MACHINE: 3,
- };
- // 系数类别对应的汇总定额金额字段(feeIndex里的字段)
- const FeeField = {
- [RateType.LABOUR]: 'labour',
- [RateType.MACHINE]: 'machine',
- [RateType.LABOUR_MACHINE]: 'material',
- };
- // 系数类别的定额人材机基础数据,不同系数类别,对应的人材机编码、名称等不同
- const BaseRatoinGLJ = {
- [RateType.LABOUR]: {
- code: '00010052',
- original_code: '00010052',
- name: '人工降效',
- type: gljType.LABOUR,
- shorName: '人',
- },
- [RateType.MACHINE]: {
- code: '99918004',
- original_code: '99918004',
- name: '机械降效',
- type: gljType.GENERAL_MACHINE,
- shorName: '机',
- },
- [RateType.LABOUR_MACHINE]: {
- code: '00010053',
- original_code: '00010053',
- name: '人工、机械降效增加费',
- type: gljType.GENERAL_MATERIAL,
- shorName: '材',
- },
- }
- // 选项二时的前九位清单编号
- const fixedCode = '011704001';
- const fixedCodeReg = new RegExp(`^${fixedCode}`);
- // 取费专业名称
- const programName = '超高降效';
- //const programName = '公共建筑工程'; // for Test
- // 指定清单表格
- let specificSpread = null;
- // 指定清单树
- let specificTree = null;
- // 指定清单窗口界面设置
- const specificTreeSetting = {
- emptyRowHeader: true,
- rowHeaderWidth: 15,
- treeCol: 0,
- emptyRows: 0,
- headRows: 1,
- headRowHeight: [40],
- defaultRowHeight: 21,
- cols: [{
- width: 140,
- readOnly: true,
- head: {
- titleNames: ["编码"],
- spanCols: [1],
- spanRows: [1],
- vAlign: [1],
- hAlign: [1],
- font: ["Arial"]
- },
- data: {
- field: "code",
- vAlign: 1,
- hAlign: 0,
- font: "Arial"
- }
- }, {
- width: 45,
- readOnly: true,
- head: {
- titleNames: ["类型"],
- spanCols: [1],
- spanRows: [1],
- vAlign: [1],
- hAlign: [1],
- font: ["Arial"]
- },
- data: {
- field: "subType",
- vAlign: 1,
- hAlign: 1,
- font: "Arial"
- }
- },
- {
- width: 205,
- readOnly: true,
- head: {
- titleNames: ["名称"],
- spanCols: [1],
- spanRows: [1],
- vAlign: [1],
- hAlign: [1],
- font: ["Arial"]
- },
- data: {
- field: "name",
- vAlign: 1,
- hAlign: 0,
- font: "Arial"
- }
- },
- {
- width: 60,
- readOnly: true,
- head: {
- titleNames: ["具体位置"],
- spanCols: [1],
- spanRows: [1],
- vAlign: [1],
- hAlign: [1],
- font: ["Arial"]
- },
- data: {
- field: "specific",
- vAlign: 1,
- hAlign: 1,
- font: "Arial"
- }
- },
- ]
- };
- // 源数据
- let sourceData;
- // 下拉项
- let comboData;
- // 初始化源数据和下拉项数据
- function init(source) {
- sourceData = source || [];
- comboData = sourceData
- ? sourceData
- .filter(item => !item.extra || !JSON.parse(item.extra))
- .map(item => item.name)
- : [];
- }
- // 获取下拉项
- function getComboData() {
- return comboData;
- }
- // 根据名称获取下拉项索引
- function getIndex(name) {
- return comboData.findIndex(item => item === name);
- }
- function getOverHeightItem(value) {
- return sourceData.find(item => item.name === value);
- }
- // 获取系数
- function getRate(overHeightItem) {
- return overHeightItem.labourRate
- || overHeightItem.machineRate
- || overHeightItem.labourMachineRate
- || null;
- }
- // 下拉项是否需要计算(生成子目)
- function isNeedToCalc(overHeightItem) {
- if (!overHeightItem) {
- return false;
- }
- const rate = getRate(overHeightItem);
- return !!rate;
- }
- // 是否是超高子目
- function isOverHeight(node) {
- return node
- && node.sourceType === projectObj.project.Ration.getSourceType()
- && node.data.type === rationType.overHeight;
- }
- // 获取超高降效列号
- function getOverHeightCol() {
- return projectObj.project.projSetting.main_tree_col.cols.findIndex(item => item.data.field === 'overHeight');
- }
- // 获取指定清单指定列
- function getSpecificCol() {
- return specificTreeSetting.cols.findIndex(item => item.data.field === 'specific');
- }
- // 获取触发动作:选项1、选项2、选项3,选项3时需要指定清单ID(specificID)
- function getAction() {
- return {
- option: projectObj.project.projectInfo.property.overHeightOption || Option.SEPARATION,
- specificID: projectObj.project.projectInfo.property.overHeightSpecificID || null,
- };
- }
- // 超高降效列的控制,右键计取触发
- function switchVisible() {
- const curVisible = colSettingObj.getVisible('overHeight');
- colSettingObj.setVisible('overHeight', !curVisible);
- colSettingObj.updateColSetting(true);
- }
- // 获取系数类型列表
- function getRateTypeList(overHeightItem) {
- const rst = [];
- if (commonUtil.isNumber(overHeightItem.labourRate)) {
- rst.push({ type: RateType.LABOUR, rate: overHeightItem.labourRate });
- }
- if (commonUtil.isNumber(overHeightItem.machineRate)) {
- rst.push({ type: RateType.MACHINE, rate: overHeightItem.machineRate });
- }
- if (commonUtil.isNumber(overHeightItem.labourMachineRate)) {
- rst.push({ type: RateType.LABOUR_MACHINE, rate: overHeightItem.labourMachineRate });
- }
- return rst;
- }
- // 有效化变化节点的值,若值为无效值(下拉项中不存在),则将变化节点的值设成原值
- function validateData(changedData) {
- changedData.forEach(item => {
- if (!comboData.includes(item.value)) {
- item.value = item.node.data.overHeight;
- }
- });
- }
- // 简化变化节点:由于子项值继承父项,且变更节点中可能存在父子关系,因此需要去除子项节点
- function simplifyData(changedData) {
- const rst = [];
- const nodes = changedData.map(item => item.node);
- changedData.forEach(item => {
- let parent = item.parent;
- // 父项不存在变化节点中才将此数据放入返回数组中
- while (parent) {
- if (nodes.includes(parent)) {
- return;
- }
- parent = parent.parent;
- }
- rst.push(item);
- });
- return rst;
- }
- // 设置单元格文本,单元格文本数据为暂存数据,方便后续获取更新、新增数据,若后续操作失败,则可用节点数据恢复单元格文本内容。
- function setTexts(changedData) {
- const sheet = projectObj.mainController.sheet;
- const func = () => {
- const overHeightCol = getOverHeightCol();
- changedData.forEach(item => {
- // 子项值随父项
- const nodes = [item.node, ...item.node.getPosterity()];
- nodes.forEach(node => {
- const row = node.serialNo();
- // 单元格没被锁定才填写暂存值
- const locked = sheet.getCell(row, overHeightCol).locked();
- if (!locked) {
- sheet.setText(row, overHeightCol, item.value)
- }
- });
- });
- };
- TREE_SHEET_HELPER.massOperationSheet(sheet, func);
- }
- // 获取措施技术项目底下固定的节点(011704001...): 选项二时
- function getTechFixedNode() {
- const measureNode = projectObj.project.mainTree.items.find(node => node.getFlag() === fixedFlag.CONSTRUCTION_TECH);
- const measureChildren = measureNode.getPosterity();
- return measureChildren.find(node => node.data.code && fixedCodeReg.test(node.data.code));
- }
- // 获取指定清单节点:选项三时
- function getSpecificNode(specificID) {
- return specificID
- ? projectObj.project.mainTree.nodes[`id_${specificID}`]
- : null;
- }
- // 变更超高降效列的操作检验,若选项为2、3时,需检验指定清单是否还存在,不存在则取消操作和提示
- function checkAction(action) {
- const { option, specificID } = action;
- if (option === Option.SEPARATION) {
- return true;
- } else if (option === Option.MEASURE) {
- const isValid = !!getTechFixedNode();
- if (!isValid) {
- $('#overHeightMeasure').modal('show');
- }
- return isValid;
- } else if (option) {
- const isValid = !!getSpecificNode(specificID);
- if (!isValid) {
- $('#overHeightSpecific').modal('show');
- }
- return isValid;
- }
- }
- // 设置指定清单界面选择框
- function setCheckBox(sheet, specificNodes) {
- const checkBox = new GC.Spread.Sheets.CellTypes.CheckBox();
- const specificCol = getSpecificCol();
- // 只有叶子节点和非计算基数节点才是选择框
- const func = () => {
- specificNodes.forEach((node, index) => {
- if (!node.children.length && !node.data.calcBase) {
- sheet.getCell(index, specificCol).cellType(checkBox);
- }
- });
- };
- TREE_SHEET_HELPER.massOperationSheet(sheet, func);
- }
- // 初始化指定清单选择树
- function initSpecificTree(data, sheet, setting) {
- specificTree = idTree.createNew({ id: 'ID', pid: 'ParentID', nid: 'NextSiblingID', rootId: -1, autoUpdate: true });
- const controller = TREE_SHEET_CONTROLLER.createNew(specificTree, sheet, setting, false);
- specificTree.loadDatas(data);
- controller.showTreeData();
- sheet.setRowCount(data.length);
- setCheckBox(sheet, specificTree.items);
- }
- // 表格点击事件,只有checkbox单元格会触发这个方法
- function handleSpreadButtonClick(e, args) {
- const { sheet, row, col } = args;
- // 只能单选,清空其他单元格的值并设置当前值
- const func = () => {
- const rowCount = sheet.getRowCount();
- const oldValue = sheet.getValue(row, col);
- for (let row = 0; row < rowCount; row++) {
- sheet.setValue(row, col, '');
- }
- sheet.setValue(row, col, !oldValue);
- }
- TREE_SHEET_HELPER.massOperationSheet(sheet, func);
- }
- // 初始化表格
- function initWorkbook() {
- specificSpread = sheetCommonObj.createSpread($('#specificArea')[0], 1);
- specificSpread.options.allowUserDragDrop = false; // 不允许拖填充,影响点击
- sheetCommonObj.spreadDefaultStyle(specificSpread);
- specificSpread.bind(GC.Spread.Sheets.Events.ButtonClicked, handleSpreadButtonClick);
- // 设置表头
- const sheet = specificSpread.getSheet(0);
- const headers = sheetCommonObj.getHeadersFromTreeSetting(specificTreeSetting);
- sheetCommonObj.setHeader(sheet, headers);
- return specificSpread;
- }
- // 从造价书界面筛选获取指定清单界面数据
- function getSpecificData() {
- // 只显示分部分项、措施项目的清单节点
- const billsNodes = projectObj.project.Bills.tree.items;
- const flags = [fixedFlag.SUB_ENGINERRING, fixedFlag.MEASURE];
- const billsData = flags.reduce((allData, flag) => {
- const fixedNode = billsNodes.find(node => node.getFlag() === flag);
- const posterity = fixedNode.getPosterity();
- const data = [fixedNode, ...posterity].map(node =>
- ({
- ID: node.data.ID,
- ParentID: node.data.ParentID,
- NextSiblingID: node.data.NextSiblingID,
- code: node.data.code,
- subType: billText[node.data.type],
- name: node.data.name,
- calcBase: node.data.calcBase,
- }));
- allData.push(...data);
- return allData;
- }, []);
- return billsData;
- }
- // 设置指定ID选中
- function setSpecific(ID) {
- if (!ID) {
- return;
- }
- const sheet = specificSpread.getSheet(0);
- const nodes = specificTree.items;
- const index = nodes.findIndex(node => node.data.ID === ID);
- if (!~index) {
- return;
- }
- const col = getSpecificCol();
- sheet.setValue(index, col, true);
- }
- // 从表格中获取勾选的指定ID
- function getSpecificFromSheet() {
- const sheet = specificSpread.getSheet(0);
- const nodes = specificTree.items;
- const rowCount = sheet.getRowCount();
- const col = getSpecificCol();
- for (let row = 0; row < rowCount; row++) {
- const value = sheet.getValue(row, col);
- if (value) {
- return nodes[row].data.ID;
- }
- }
- return null;
- }
- // 初始化指定清单选择界面
- function initSpecificModal() {
- $('#specificArea').show();
- const spread = initWorkbook();
- const sheet = spread.getSheet(0);
- const data = getSpecificData();
- initSpecificTree(data, sheet, specificTreeSetting);
- const { specificID } = getAction();
- setSpecific(specificID);
- }
- // 隐藏指定清单选择界面
- function hideSpecificModal() {
- $('#specificArea').hide();
- if (specificSpread) {
- specificSpread.destroy();
- specificSpread = null;
- }
- }
- // 初始化子目生成方式选项设置窗口
- function initModal() {
- const { option, specificID } = getAction();
- $(`input[name=cgx][value=${option}]`).prop('checked', 'checked');
- $('#specificArea').hide();
- if (option === Option.SPECIFIC) {
- initSpecificModal(specificID);
- }
- }
- // 超高降效下拉项或选项是否改变了
- function isValueChanged() {
- const updateData = getUpdateData();
- return !!(updateData.bills.length || updateData.ration.length);
- }
- // 对比两个选项行为,获取更新选项数据
- function getUpdateProjectData(oldAction, newAction) {
- if (!oldAction || !newAction) {
- return {};
- }
- const optionDiff = oldAction.option !== newAction.option;
- const specificDiff = oldAction.specificID !== newAction.specificID;
- const updateData = {
- ID: projectObj.project.ID(),
- overHeightOption: newAction.option,
- overHeightSpecificID: newAction.specificID,
- };
- return optionDiff || specificDiff
- ? updateData
- : {};
- }
- /**
- * 获取更新数据:对比项目节点中超高降效的新旧值,新值为暂存的单元格文本,旧值为节点data数据
- * @param {Object} newAction - 选项行为
- * @return {Object} - {
- * project: {ID: Number,overHeightOp: Number, overHeightSpecificID: Number||Null},
- * bills: [{ID: Number, overHeight: String}],
- * ration: [{ID: Number, overHeight: String}]
- * }
- */
- function getUpdateData(newAction) {
- const update = {
- project: {}, // 可能会更改项目属性的超高降效设置
- bills: [],
- ration: [],
- };
- const oldAction = getAction();
- const updateProjectData = getUpdateProjectData(oldAction, newAction);
- Object.assign(update.project, updateProjectData);
- const nodes = projectObj.project.mainTree.items;
- const sheet = projectObj.mainController.sheet;
- const overHeightCol = getOverHeightCol();
- nodes.forEach((node, index) => {
- const newValue = sheet.getText(index, overHeightCol);
- const oldValue = node.data.overHeight;
- // 非严等
- if (!commonUtil.similarEqual(newValue, oldValue)) {
- const type = node.sourceType === projectObj.project.Bills.getSourceType()
- ? 'bills'
- : 'ration';
- update[type].push({
- ID: node.data.ID,
- overHeight: newValue
- });
- }
- });
- return update;
- }
- /**
- * 获取删除数据:项目中所有超高子目
- * @return {Object} - {ration: [{ID: Number}]}
- */
- function getDeleteData() {
- const del = {
- ration: [],
- };
- const rations = projectObj.project.Ration.datas;
- del.ration = rations
- .filter(ration => ration.type === rationType.overHeight)
- .map(ration => ({ ID: ration.ID }));
- return del;
- }
- /**
- * 获取需要生成超高子目的定额节点
- * @return {Array} - [{node: Object, overHeight: String}]
- */
- function getNeedCalcRationItems() {
- // 从整个项目中筛选当前下拉项单元格的文本是需要计算的定额节点
- const nodes = projectObj.project.mainTree.items;
- const sheet = projectObj.mainController.sheet;
- const overHeightCol = getOverHeightCol();
- const rst = [];
- nodes.forEach(node => {
- // 非超高子目的定额节点才生成
- const notOverHeightRationNode = node.sourceType !== projectObj.project.Ration.getSourceType()
- || node.data.type === rationType.overHeight;
- if (notOverHeightRationNode) {
- return;
- }
- const overHeight = sheet.getText(node.serialNo(), overHeightCol);
- const overHeightItem = getOverHeightItem(overHeight);
- if (isNeedToCalc(overHeightItem)) {
- rst.push({ node, overHeight });
- }
- });
- return rst;
- }
- // 根据选项获取超高子目挂载的清单
- function getMountedBills(action) {
- const { option, specificID } = action;
- // 生成清单数据
- function initMountedBills() {
- // 生成的清单位置为施工技术措施项目的最末子项
- const measureNode = projectObj.project.mainTree.items.find(node => node.getFlag() === fixedFlag.CONSTRUCTION_TECH);
- const parent = measureNode.children[measureNode.children.length - 1];
- // 具体完整数据需要在后端跟标准数据对比完善
- return {
- projectID: projectObj.project.ID(),
- billsLibId: +projectObj.project.projectInfo.engineeringInfo.bill_lib[0].id,
- ID: uuid.v1(),
- ParentID: parent.data.ID,
- NextSiblingID: -1,
- type: billType.BILL,
- code: fixedCode,
- name: '超高施工增加',
- unit: 'm2',
- quantity: '1',
- };
- }
- // 选项一
- if (option === Option.SEPARATION) {
- return {
- isNew: false,
- bills: null,
- };
- } else if (option === Option.MEASURE) { // 选项二且造价书没有相关清单,需要插入清单
- const fixedNode = getTechFixedNode();
- return {
- isNew: !fixedNode,
- bills: fixedNode ? fixedNode.data : initMountedBills(),
- };
- } else {
- const specificNode = getSpecificNode(specificID);
- return {
- isNew: false,
- bills: specificNode.data,
- }
- }
- }
- // 获取清单节点下最末非超高子目定额
- function getLastNormalRationNode(billsNode) {
- const nodes = billsNode && billsNode.children
- ? billsNode.children.filter(node => node.data.type !== rationType.overHeight)
- : [];
- return nodes[nodes.length - 1]
- }
- // 将需要生成超高子目的定额数据按照挂载清单和超高名称进行分组,其下数组为关联的主定额
- // @return {Object} {'billsID@overHeight': [node: rationNode]}
- function getGroupData(rationItems, mountedBillsID) {
- // mapping: billsID-overHeightName
- const group = {};
- rationItems.forEach(item => {
- // 无指定的清单ID,则挂载在各自的清单上
- const billsID = mountedBillsID || item.node.data.billsItemID
- const key = `${billsID}@${item.overHeight}`;
- if (!group[key]) {
- group[key] = [];
- }
- group[key].push(item.node);
- });
- return group;
- }
- // 根据系数类型获取汇总的定额金额,这个金额作为计算定额人材机的基数
- function getFeeByRateType(rateType, referenceRations) {
- const feeField = FeeField[rateType];
- // 汇总定额节点相关综合合价
- return referenceRations.reduce((sum, rationNode) => {
- const feeObj = rationNode.data.feesIndex;
- const totalFee = feeObj && feeObj[feeField]
- ? feeObj[feeField].totalFee
- : 0;
- return scMathUtil.roundForObj(sum + totalFee, decimalObj.process);
- }, 0);
- }
- // 通过超高降效计算得来的定额人材机消耗量:消耗量=定额消耗量=算出来的值
- function getQuantity(rate, fee) {
- return String(scMathUtil.roundForObj(rate * fee, decimalObj.glj.quantity));
- }
- /**
- * 获取定额喝定额人材机数据
- * @param {Array} rationItems - 需要生成超高子目的定额数据{node: Object, overHtight: String}
- * @param {Object} mountedBills - 挂载到的清单数据
- * @return {Object} - {ration: Array, rationGLJ: Array}
- */
- function getAddRationAndRationGLJData(rationItems, mountedBills) {
- // 生成定额数据
- function initRation(bills, overHeightItem, serialNo) {
- const programID = projectObj.project.calcProgram.compiledTemplateMaps[programName];
- // 生成的超高子目消耗量为1
- const quantity = '1';
- // 含量为 定额消耗量 / 清单消耗量
- const tempV = quantity / bills.quantity;
- const contain = isFinite(tempV)
- ? scMathUtil.roundForObj(tempV, decimalObj.ration.quantity)
- : '0';
- return {
- projectID: projectObj.project.ID(),
- billsItemID: bills.ID,
- ID: uuid.v1(),
- programID,
- serialNo,
- code: overHeightItem.code,
- name: overHeightItem.name,
- unit: overHeightItem.unit,
- type: rationType.overHeight,
- quantity,
- contain,
- }
- }
- // 根据分组的keys获取定额serialNo值映射表 billsID@overHeight
- function getSerialNoMappig(groupKeys) {
- // 清单ID - serialNo映射
- const billsIDMap = {};
- // 完整的key - serialNo映射
- const keyMap = {};
- const nodes = projectObj.project.mainTree.nodes;
- // 先给根据清单ID设置上第一个serialNo值(基准值),和超高项数据
- groupKeys.forEach(key => {
- const [billsID, overHeight] = key.split('@');
- if (billsIDMap[billsID]) {
- billsIDMap[billsID].items.push(overHeight);
- return;
- }
- const billsNode = nodes[`id_${billsID}`];
- const lastNormalRationNode = billsNode ? getLastNormalRationNode(billsNode) : null;
- const serialNo = lastNormalRationNode ? lastNormalRationNode.data.serialNo + 1 : 1;
- billsIDMap[billsID] = { serialNo, items: [overHeight] };
- });
- // 将同一清单下的超高项按照下拉项位置排序
- Object.entries(billsIDMap).forEach(([billsID, { serialNo, items }]) => {
- items.sort((a, b) => getIndex(a) - getIndex(b));
- items.forEach((overHeight, index) => {
- const key = `${billsID}@${overHeight}`;
- keyMap[key] = serialNo + index;
- });
- });
- return keyMap;
- }
- // 生成超高子目的定额人材机数据,定额人材机的属性只是一部分,还有部分数据需要在后端处理
- function initRationGLJ(ration, typeItem, referenceRationNodes) {
- const { type, rate } = typeItem;
- const sumFee = getFeeByRateType(type, referenceRationNodes);
- // 不同类型的基础人材机属性
- const quantity = getQuantity(rate, sumFee);
- const baseObj = BaseRatoinGLJ[type];
- // 补全定额人材机属性,共性属性
- const extendObj = {
- projectID: projectObj.project.ID(),
- ID: uuid.v1(),
- billsItemID: ration.billsItemID,
- rationID: ration.ID,
- repositoryId: -1,
- GLJID: -1,
- unit: '%',
- specs: '',
- // 定额人材机没有价格字段,但是生成单价文件需要需要这两个价格字段,默认为“1”
- basePrice: '1',
- marketPrice: '1',
- quantity,
- rationItemQuantity: quantity,
- };
- return { ...baseObj, ...extendObj };
- }
- const add = {
- ration: [],
- rationGLJ: []
- };
- if (!rationItems.length) {
- return add;
- }
- const mountedBillsID = mountedBills
- ? mountedBills.ID
- : null;
- // 分析分组数据,获取定额及定额人材机数据
- const group = getGroupData(rationItems, mountedBillsID);
- const rationSerialNoMapping = getSerialNoMappig(Object.keys(group));
- // 获取定额及定额人材机数据
- Object.entries(group).forEach(([key, referenceRationNodes]) => {
- const [billsID, overHeight] = key.split('@');
- const overHeightItem = getOverHeightItem(overHeight);
- const bills = billsID === mountedBillsID
- ? mountedBills
- : projectObj.project.Bills.datas.find(item => item.ID === billsID);
- const serialNo = rationSerialNoMapping[key];
- const overHeightRation = initRation(bills, overHeightItem, serialNo);
- // 给生成的超高子目定额,设置关联定额列表(关联定额工程量等发生变化,需要重算超高子目)
- overHeightRation.referenceRationList = referenceRationNodes.map(node => node.data.ID);
- add.ration.push(overHeightRation);
- // 根据超高项获取系数列表,系数列表一个元素会根据系数类别生成一条定额人材机(人、机、材料)
- const rateTypeList = getRateTypeList(overHeightItem);
- const rationGLJs = rateTypeList.map(rateTypeItem => initRationGLJ(overHeightRation, rateTypeItem, referenceRationNodes));
- add.rationGLJ.push(...rationGLJs);
- });
- return add;
- }
- /**
- * 获取插入数据:超高子目(定额)、清单(选项2、3时可能会插入)
- * @param {Object} action - {option: Number, specificID: Undefined||Null||Number}
- * @return {Array} - {bills: Array, ration: Array, rationGLJ: Array}
- */
- function getAddData(action) {
- const add = {
- bills: [],
- ration: [],
- rationGLJ: [],
- };
- // 挂载到的清单,新增或已有的
- const mountedBills = getMountedBills(action);
- if (mountedBills.isNew) {
- add.bills.push(mountedBills.bills);
- }
- // 获取需要生成超高子目的定额数据
- const needCalcRationItems = getNeedCalcRationItems();
- const subData = getAddRationAndRationGLJData(needCalcRationItems, mountedBills.bills);
- add.ration = subData.ration;
- add.rationGLJ = subData.rationGLJ;
- return add;
- }
- /**
- * 生成传输数据
- * @param {Boolean} isCancelCalc - 是否取消计取
- * @param {Object} action - 新的选项行为
- * @return {Object}
- */
- function generatePostData(isCancelCalc, action) {
- // 默认模板
- const postData = {
- addData: {
- bills: [],
- ration: [],
- rationGLJ: [],
- },
- updateData: {
- project: {},
- bills: [],
- ration: [],
- },
- deleteData: {
- ration: [],
- },
- };
- // 取消计取费用,只删除超高子目
- if (isCancelCalc) {
- postData.deleteData = getDeleteData();
- return postData;
- }
- // 没有新的选项行为,获取当前项目的选项行为
- if (!action) {
- action = getAction();
- }
- const addData = getAddData(action);
- const updateData = getUpdateData(action);
- const deleteData = getDeleteData();
- return Object.assign(postData, { addData, updateData, deleteData });
- }
- /**
- * 更改了超高降效列(edited、rangeChanged),触发事件
- * @param {Array} changedData - 变化的数据 [{node: Object, value: String}]
- * @return {void}
- */
- function handleValueChanged(changedData) {
- validateData(changedData);
- changedData = simplifyData(changedData);
- setTexts(changedData);
- const valuedChanged = isValueChanged();
- if (!valuedChanged) {
- return;
- }
- // 选项2、选项3情况下下拉可能会遇到,相关清单已经被删除,需要检测行为
- const action = getAction();
- const actionValid = checkAction(action);
- // actionValid为false的时候,可能后续需要恢复单元格文本值,根据后续用户在弹窗中的选择(后文ready事件中绑定)
- if (!actionValid) {
- return;
- }
- handleConfirmed();
- }
- // 确认事件
- async function handleConfirmed(isCancelCalc = false, action = null) {
- $.bootstrapLoading.start();
- try {
- const postData = generatePostData(isCancelCalc, action);
- const { addData, updateData, deleteData } = postData;
- // 更新、删除、新增数据
- // 返回的是新增的清单、定额人材机、项目人材机 rst = {bills: [], rationGLJ: [], projectGLJ: []}
- const rst = await ajaxPost('/project/calcOverHeightFee', postData);
- // 后续获取重算节点相关:
- // 新增的定额节点要在同步数据后才有,删除的定额节点在同步前找不到,因此同步前先获取被删除定额节点的清单ID列表
- const rationIDList = deleteData.ration.map(item => item.ID);
- let billsIDList = projectObj.project.Ration.datas
- .filter(item => rationIDList.includes(item.ID))
- .map(item => item.billsItemID);
- billsIDList = [...new Set(billsIDList)];
- // 同步数据
- const newAddData = { ...addData, ...rst };
- syncData(deleteData, updateData, newAddData);
- // 获取重算节点
- const reCalcNodes = getReCalcNodes(billsIDList, newAddData.ration);
- if (!reCalcNodes.length) {
- $.bootstrapLoading.end();
- return;
- }
- // 获取项目人材机数据,更新缓存
- $.bootstrapLoading.end(); // 重算节点相关方法里有loading,防止提前结束了loading
- // 重算相关节点
- projectObj.project.calcProgram.calcNodesAndSave(reCalcNodes);
- } catch (err) {
- console.log(err);
- recoverCellsText();
- $.bootstrapLoading.end();
- }
- }
- /**
- * 获取需要重算的节点
- * @param {Array} billsIDList - 根据删除的定额ID数据获取的清单ID列表
- * @param {Array} rationData - 新增的定额数据
- * @return {Array}
- */
- function getReCalcNodes(billsIDList, rationData) {
- // 获取被删除定额的清单节点
- const billsNodes = billsIDList.map(ID => projectObj.project.mainTree.nodes[`id_${ID}`]);
- // 获取新增的定额数据
- const rationNodes = rationData.map(ration => projectObj.project.mainTree.nodes[`id_${ration.ID}`]);
- return [...billsNodes, ...rationNodes];
- }
- /**
- * 同步、更新缓存的数据、节点
- * @param {Object} delData - 删除的数据,包含定额ID列表
- * @param {Object} updateData - 更新的数据
- * @param {Object} addData - 新增的完整清单和定额人材机数据,经过后端加工返回
- * @return {void}
- */
- function syncData(delData, updateData, addData) {
- // 被删除数据的清单ID@定额serialNo - 价格字段映射
- // 在新增节点时,给上原本该行的feesIndex字段,不然会出现新增节点价格为空,重算后价格有值,造成价格字段闪烁的情况
- // 新增节点赋上原本该行的feesIndex字段只是为了避免闪烁的情况。
- // 新节点fees数组没有赋值,后续计算的时候节点的价格字段会被初始化(calcTools.initFees中),因此这个操作不会对计算结果有影响
- function getSerialNoFeesIndexMapping({ ration }) {
- const mapping = {};
- const rationIDList = ration.map(item => item.ID);
- projectObj.project.Ration.datas
- .filter(item => rationIDList.includes(item.ID))
- .forEach(item => mapping[`${item.billsItemID}@${item.serialNo}`] = item.feesIndex);
- return mapping;
- }
- // 新增定额设置上暂时显示的价格字段
- function setTemporaryFeesIndex({ ration }, feesIndexMapping) {
- ration.forEach(item => {
- const key = `${item.billsItemID}@${item.serialNo}`;
- const oldFeesIndex = feesIndexMapping[key];
- if (oldFeesIndex) {
- item.feesIndex = oldFeesIndex;
- }
- });
- }
- // 删除数据
- function del({ ration }) {
- const sheet = projectObj.mainController.sheet;
- const func = () => {
- // 删除定额数据、定额节点及子数据
- if (ration.length) {
- const rationIDList = ration.map(item => item.ID);
- projectObj.project.Ration.deleteDataSimply(rationIDList);
- // 由于cacheTree delete方法会将preSelected设置成null
- // 会导致变更焦点行时,清除不了原焦点行的选中色,因此这里重设下preSelected,在这里处理避免影响其他已有代码
- projectObj.project.mainTree.preSelected = projectObj.project.mainTree.selected;
- }
- };
- TREE_SHEET_HELPER.massOperationSheet(sheet, func);
- }
- // 更新数据
- function update({ project, bills, ration }) {
- // 更新项目属性
- if (project) {
- const property = projectObj.project.projectInfo.property;
- Object.assign(property, project);
- }
- const mainTree = projectObj.project.mainTree;
- // 更新节点超高降效
- [...bills, ...ration].forEach(({ ID, overHeight }) => {
- const node = mainTree.nodes[`id_${ID}`];
- if (node) {
- node.data.overHeight = overHeight;
- }
- });
- }
- // 插入数据
- function add({ bills, ration, rationGLJ, projectGLJ }) {
- const sheet = projectObj.mainController.sheet;
- const func = () => {
- // 插入清单数据和清单节点主树节点
- if (bills.length) {
- projectObj.project.Bills.addNewDataSimply(bills);
- }
- // 插入定额数据和定额节点
- if (ration.length) {
- // 按照serialNo排序
- ration.sort((a, b) => a.serialNo - b.serialNo);
- projectObj.project.Ration.addNewDataSimply(ration);
- }
- // 插入定额人材机数据
- if (rationGLJ.length) {
- projectObj.project.ration_glj.addDatasToList(rationGLJ);
- }
- // 插入项目人材机数据
- if (projectGLJ.length) {
- projectObj.project.projectGLJ.loadNewProjectGLJToCaches(projectGLJ);
- // 重算消耗量
- projectObj.project.projectGLJ.calcQuantity();
- }
- };
- TREE_SHEET_HELPER.massOperationSheet(sheet, func);
- }
- const feesIndexMapping = getSerialNoFeesIndexMapping(delData);
- setTemporaryFeesIndex(addData, feesIndexMapping);
- del(delData);
- update(updateData);
- add(addData);
- }
- // 恢复暂存的单元格文本
- function recoverCellsText() {
- const sheet = projectObj.mainController.sheet;
- const func = () => {
- const nodes = projectObj.project.mainTree.items;
- const overHeightCol = getOverHeightCol();
- nodes.forEach((node, index) => {
- const newValue = sheet.getText(index, overHeightCol);
- const oldValue = node.data.overHeight;
- if (!commonUtil.similarEqual(newValue, oldValue)) {
- sheet.setText(index, overHeightCol, oldValue);
- }
- });
- };
- TREE_SHEET_HELPER.massOperationSheet(sheet, func);
- }
- // 取消超高降效,删除所有超高子目
- function cancelCalc() {
- handleConfirmed(true);
- }
- // 取消事件,触发了取消操作,需要恢复单元格文本
- function handleCancel() {
- $.bootstrapLoading.start();
- recoverCellsText();
- $.bootstrapLoading.end();
- }
- /**
- * 返回清单与超高子目和其定额人材机映射
- * @param {Array} rations - 全部超高子目数据
- * @param {Array} rationGLJs - 全部超高子目定额人材机数据
- * @return {Object} - {billsItemID@超高子目编码@定额人材机编码: {quanqity, rationItemQuantity}}
- */
- function getRationGLJMap(rations, rationGLJs) {
- const mapping = {};
- rationGLJs.forEach(rGLJ => {
- const ration = rations.find(ration => ration.ID === rGLJ.rationID);
- const rationCode = ration ? ration.code : '';
- // 由于一个清单下不会存在两个相同编号的超高子目(相同编号会被自动汇总),因此这个key能确定唯一一条定额人材机
- const key = `${rGLJ.billsItemID}@${rationCode}@${rGLJ.original_code}`;
- mapping[key] = { quantity: rGLJ.quantity, rationItemQuantity: rGLJ.rationItemQuantity };
- });
- return mapping;
- }
- // 比较两个清单与超高子目和其定额人材机映射表,看是否相同
- function isMappingEqual(oldMapping, newMapping) {
- const oldKeys = Object.keys(oldMapping);
- const newKeys = Object.keys(newMapping);
- if (oldKeys.length !== newKeys.length) {
- return false;
- }
- const isEveryKeySame = oldKeys.every(key => {
- const oldData = oldMapping[key];
- const newData = newMapping[key];
- if (!newData) {
- return false;
- }
- const quantitySame = commonUtil.similarEqual(oldData.quantity, newData.quantity);
- const rationQuantitySame = commonUtil.similarEqual(oldData.rationItemQuantity, newData.rationItemQuantity);
- return quantitySame && rationQuantitySame;
- });
- return isEveryKeySame;
- }
- /**
- * 重新计取项目超高子目,超高子目的值与关联定额相关
- * 因此各种操作下改变了相关定额,都要重新计算超高子目
- * 为了降低复杂度和保证逻辑统一性,重新计取为重新走(删除新增逻辑)
- * 需要尽可能地降低操作的触发率
- * @param {type} -
- * @return: {type} -
- */
- function reCalcOverHeightFee() {
- const project = projectObj.project;
- // 如果项目没有超高降效数据,项目不可用超高降效,返回
- if (!project.isOverHeightProject()) {
- return;
- }
- // 如果没有超高定额,返回(因此删除了选项二三、指定的清单不会触发)
- const overHeightRations = project.Ration.datas.filter(ration => ration.type === rationType.overHeight);
- if (!overHeightRations.length) {
- return;
- }
- // 获取新旧超高数据映射表,不同才需要计算
- const overHeightRationIDs = overHeightRations.map(ration => ration.ID);
- const overHeigtRationGLJs = project.ration_glj.datas.filter(rGLJ => overHeightRationIDs.includes(rGLJ.rationID));
- const action = getAction();
- const { ration, rationGLJ } = getAddData(action);
- const oldMapping = getRationGLJMap(overHeightRations, overHeigtRationGLJs);
- const newMapping = getRationGLJMap(ration, rationGLJ);
- if (isMappingEqual(oldMapping, newMapping)) {
- return;
- }
- // 存在不同,重算
- handleConfirmed();
- }
- // 事件监听
- $(document).ready(() => {
- // 设置窗口显示事件
- $('#overHeightOpt').on('shown.bs.modal', () => {
- initModal();
- });
- // 设置窗口隐藏事件
- $('#overHeightOpt').on('hide.bs.modal', () => {
- if (specificSpread) {
- specificSpread.destroy();
- specificSpread = null;
- }
- specificTree = null;
- // 指定清单时的选项,指定清单被删除,用户下拉改变超高降效列,
- // 弹出选中指定清单窗口,用户没有选定直接关闭窗口,此时需要把造价书暂存的值恢复
- handleCancel();
- });
- // 设置窗口单选变更事件
- $('input[name=cgx]').change(function () {
- const option = +$(this).val();
- switch (option) {
- case Option.SEPARATION:
- case Option.MEASURE:
- hideSpecificModal();
- break;
- case Option.SPECIFIC:
- initSpecificModal();
- break;
- }
- });
- // 设置窗口确认事件
- $('#overHeightOptConfirmed').click(() => {
- const option = +$('input[name=cgx]:checked').val();
- switch (option) {
- case Option.SEPARATION:
- $('#overHeightOpt').modal('hide');
- handleConfirmed(false, { option, specificID: null });
- break;
- case Option.MEASURE:
- const fixedNode = getTechFixedNode();
- // 造价书不存在相关清单,提示是否新增清单,由提示窗口进行后续操作
- if (!fixedNode) {
- $('#overHeightMeasure').modal('show');
- return;
- }
- // 存在相关清单
- $('#overHeightOpt').modal('hide');
- handleConfirmed(false, { option, specificID: null });
- break;
- case Option.SPECIFIC:
- const specificID = getSpecificFromSheet();
- if (!specificID) {
- alert('请指定清单');
- return;
- }
- $('#overHeightOpt').modal('hide');
- handleConfirmed(false, { option, specificID });
- break;
- }
- });
- // 选项二下,改变超高降效的值,且措施项目下指定清单被删除,弹窗按钮事件
- $('#overHeightMeasureY').click(() => { // 确认 - 新增指定清单和相关数据
- const action = { option: Option.MEASURE, specificID: null };
- $('#overHeightOpt').modal('hide');
- $('#overHeightMeasure').modal('hide');
- handleConfirmed(false, action);
- });
- $('#overHeightMeasureN').click(handleCancel); // 取消
- // 选项三下,改变超高降效的值,且指定清单被删除,弹窗按钮事件
- $('#overHeightSpecificY').click(() => { // 确认 - 弹出设置窗口
- $('#overHeightOpt').modal('show');
- });
- $('#overHeightSpecificN').click(handleCancel); // 取消
- });
- return {
- init,
- getComboData,
- isOverHeight,
- switchVisible,
- handleValueChanged,
- cancelCalc,
- reCalcOverHeightFee,
- };
- })();
|