overHeight.js 47 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186
  1. /*
  2. * @Descripttion: 超高降效相关
  3. * @Author: Zhong
  4. * @Date: 2019-12-19 17:45:40
  5. */
  6. /*
  7. * 整体逻辑为:
  8. * 1.一个请求中
  9. * {删除}所有超高子目
  10. * {更新}清单、定额下拉列文本
  11. * {新增}清单、定额、定额人材机、项目人材机
  12. * 2.一个请求{重算被删除的定额清单节点、新增的定额节点
  13. */
  14. const OVER_HEIGHT = (() => {
  15. // 选项类型,生成的超高子目所在位置
  16. const Option = {
  17. // 对应清单或分部下(默认)
  18. SEPARATION: 1,
  19. // 指定措施清单011704001
  20. MEASURE: 2,
  21. // 指定具体位置,显示分部分项以及措施项目的树结构显示叶子清单(分项)供勾选
  22. SPECIFIC: 3,
  23. };
  24. // 系数类别,类别不同,生成的定额人材机和算法不同
  25. const RateType = {
  26. LABOUR: 1,
  27. MACHINE: 2,
  28. LABOUR_MACHINE: 3,
  29. };
  30. // 系数类别对应的汇总定额金额字段(feeIndex里的字段)
  31. const FeeField = {
  32. [RateType.LABOUR]: 'labour',
  33. [RateType.MACHINE]: 'machine',
  34. [RateType.LABOUR_MACHINE]: 'material',
  35. };
  36. // 系数类别的定额人材机基础数据,不同系数类别,对应的人材机编码、名称等不同
  37. const BaseRatoinGLJ = {
  38. [RateType.LABOUR]: {
  39. code: '00010052',
  40. original_code: '00010052',
  41. name: '人工降效',
  42. type: gljType.LABOUR,
  43. shorName: '人',
  44. },
  45. [RateType.MACHINE]: {
  46. code: '99918004',
  47. original_code: '99918004',
  48. name: '机械降效',
  49. type: gljType.GENERAL_MACHINE,
  50. shorName: '机',
  51. },
  52. [RateType.LABOUR_MACHINE]: {
  53. code: '00010053',
  54. original_code: '00010053',
  55. name: '人工、机械降效增加费',
  56. type: gljType.GENERAL_MATERIAL,
  57. shorName: '材',
  58. },
  59. }
  60. // 选项二时的前九位清单编号
  61. const fixedCode = '011704001';
  62. const fixedCodeReg = new RegExp(`^${fixedCode}`);
  63. // 取费专业名称
  64. const programName = '超高降效';
  65. //const programName = '公共建筑工程'; // for Test
  66. // 指定清单表格
  67. let specificSpread = null;
  68. // 指定清单树
  69. let specificTree = null;
  70. // 指定清单窗口界面设置
  71. const specificTreeSetting = {
  72. emptyRowHeader: true,
  73. rowHeaderWidth: 15,
  74. treeCol: 0,
  75. emptyRows: 0,
  76. headRows: 1,
  77. headRowHeight: [40],
  78. defaultRowHeight: 21,
  79. cols: [{
  80. width: 140,
  81. readOnly: true,
  82. head: {
  83. titleNames: ["编码"],
  84. spanCols: [1],
  85. spanRows: [1],
  86. vAlign: [1],
  87. hAlign: [1],
  88. font: ["Arial"]
  89. },
  90. data: {
  91. field: "code",
  92. vAlign: 1,
  93. hAlign: 0,
  94. font: "Arial"
  95. }
  96. }, {
  97. width: 45,
  98. readOnly: true,
  99. head: {
  100. titleNames: ["类型"],
  101. spanCols: [1],
  102. spanRows: [1],
  103. vAlign: [1],
  104. hAlign: [1],
  105. font: ["Arial"]
  106. },
  107. data: {
  108. field: "subType",
  109. vAlign: 1,
  110. hAlign: 1,
  111. font: "Arial"
  112. }
  113. },
  114. {
  115. width: 205,
  116. readOnly: true,
  117. head: {
  118. titleNames: ["名称"],
  119. spanCols: [1],
  120. spanRows: [1],
  121. vAlign: [1],
  122. hAlign: [1],
  123. font: ["Arial"]
  124. },
  125. data: {
  126. field: "name",
  127. vAlign: 1,
  128. hAlign: 0,
  129. font: "Arial"
  130. }
  131. },
  132. {
  133. width: 60,
  134. readOnly: true,
  135. head: {
  136. titleNames: ["具体位置"],
  137. spanCols: [1],
  138. spanRows: [1],
  139. vAlign: [1],
  140. hAlign: [1],
  141. font: ["Arial"]
  142. },
  143. data: {
  144. field: "specific",
  145. vAlign: 1,
  146. hAlign: 1,
  147. font: "Arial"
  148. }
  149. },
  150. ]
  151. };
  152. // 源数据
  153. let sourceData;
  154. // 下拉项
  155. let comboData;
  156. // 初始化源数据和下拉项数据
  157. function init(source) {
  158. sourceData = source || [];
  159. comboData = sourceData
  160. ? sourceData
  161. .filter(item => !item.extra || !JSON.parse(item.extra))
  162. .map(item => item.name)
  163. : [];
  164. }
  165. // 获取下拉项
  166. function getComboData() {
  167. return comboData;
  168. }
  169. // 根据名称获取下拉项索引
  170. function getIndex(name) {
  171. return comboData.findIndex(item => item === name);
  172. }
  173. function getOverHeightItem(value) {
  174. return sourceData.find(item => item.name === value);
  175. }
  176. // 获取系数
  177. function getRate(overHeightItem) {
  178. return overHeightItem.labourRate
  179. || overHeightItem.machineRate
  180. || overHeightItem.labourMachineRate
  181. || null;
  182. }
  183. // 下拉项是否需要计算(生成子目)
  184. function isNeedToCalc(overHeightItem) {
  185. if (!overHeightItem) {
  186. return false;
  187. }
  188. const rate = getRate(overHeightItem);
  189. return !!rate;
  190. }
  191. // 是否是超高子目
  192. function isOverHeight(node) {
  193. return node
  194. && node.sourceType === projectObj.project.Ration.getSourceType()
  195. && node.data.type === rationType.overHeight;
  196. }
  197. // 获取超高降效列号
  198. function getOverHeightCol() {
  199. return projectObj.project.projSetting.main_tree_col.cols.findIndex(item => item.data.field === 'overHeight');
  200. }
  201. // 获取指定清单指定列
  202. function getSpecificCol() {
  203. return specificTreeSetting.cols.findIndex(item => item.data.field === 'specific');
  204. }
  205. // 获取触发动作:选项1、选项2、选项3,选项3时需要指定清单ID(specificID)
  206. function getAction() {
  207. return {
  208. option: projectObj.project.projectInfo.property.overHeightOption || Option.SEPARATION,
  209. specificID: projectObj.project.projectInfo.property.overHeightSpecificID || null,
  210. };
  211. }
  212. // 超高降效列的控制,右键计取触发
  213. function switchVisible() {
  214. const curVisible = colSettingObj.getVisible('overHeight');
  215. colSettingObj.setVisible('overHeight', !curVisible);
  216. colSettingObj.updateColSetting(true);
  217. }
  218. // 获取系数类型列表
  219. function getRateTypeList(overHeightItem) {
  220. const rst = [];
  221. if (commonUtil.isNumber(overHeightItem.labourRate)) {
  222. rst.push({ type: RateType.LABOUR, rate: overHeightItem.labourRate });
  223. }
  224. if (commonUtil.isNumber(overHeightItem.machineRate)) {
  225. rst.push({ type: RateType.MACHINE, rate: overHeightItem.machineRate });
  226. }
  227. if (commonUtil.isNumber(overHeightItem.labourMachineRate)) {
  228. rst.push({ type: RateType.LABOUR_MACHINE, rate: overHeightItem.labourMachineRate });
  229. }
  230. return rst;
  231. }
  232. // 有效化变化节点的值,若值为无效值(下拉项中不存在),则将变化节点的值设成原值
  233. function validateData(changedData) {
  234. changedData.forEach(item => {
  235. if (!comboData.includes(item.value)) {
  236. item.value = item.node.data.overHeight;
  237. }
  238. });
  239. }
  240. // 简化变化节点:由于子项值继承父项,且变更节点中可能存在父子关系,因此需要去除子项节点
  241. function simplifyData(changedData) {
  242. const rst = [];
  243. const nodes = changedData.map(item => item.node);
  244. changedData.forEach(item => {
  245. let parent = item.parent;
  246. // 父项不存在变化节点中才将此数据放入返回数组中
  247. while (parent) {
  248. if (nodes.includes(parent)) {
  249. return;
  250. }
  251. parent = parent.parent;
  252. }
  253. rst.push(item);
  254. });
  255. return rst;
  256. }
  257. // 设置单元格文本,单元格文本数据为暂存数据,方便后续获取更新、新增数据,若后续操作失败,则可用节点数据恢复单元格文本内容。
  258. function setTexts(changedData) {
  259. const sheet = projectObj.mainController.sheet;
  260. const func = () => {
  261. const overHeightCol = getOverHeightCol();
  262. changedData.forEach(item => {
  263. // 子项值随父项
  264. const nodes = [item.node, ...item.node.getPosterity()];
  265. nodes.forEach(node => {
  266. const row = node.serialNo();
  267. // 单元格没被锁定才填写暂存值
  268. const locked = sheet.getCell(row, overHeightCol).locked();
  269. if (!locked) {
  270. sheet.setText(row, overHeightCol, item.value)
  271. }
  272. });
  273. });
  274. };
  275. TREE_SHEET_HELPER.massOperationSheet(sheet, func);
  276. }
  277. // 获取其他措施费项目底下固定的节点(011704001...): 选项二时
  278. function getMeasureFixedNode() {
  279. const otherMeasureNode = projectObj.project.mainTree.items.find(node => node.getFlag() === fixedFlag.OTHER_MEASURE_FEE);
  280. const measureChildren = otherMeasureNode.getPosterity();
  281. return measureChildren.find(node => node.data.code && fixedCodeReg.test(node.data.code));
  282. }
  283. // 获取指定清单节点:选项三时
  284. function getSpecificNode(specificID) {
  285. return specificID
  286. ? projectObj.project.mainTree.nodes[`id_${specificID}`]
  287. : null;
  288. }
  289. // 变更超高降效列的操作检验,若选项为2、3时,需检验指定清单是否还存在,不存在则取消操作和提示
  290. function checkAction(action) {
  291. const { option, specificID } = action;
  292. if (option === Option.SEPARATION) {
  293. return true;
  294. } else if (option === Option.MEASURE) {
  295. const isValid = !!getMeasureFixedNode();
  296. if (!isValid) {
  297. $('#overHeightMeasure').modal('show');
  298. }
  299. return isValid;
  300. } else if (option) {
  301. const isValid = !!getSpecificNode(specificID);
  302. if (!isValid) {
  303. $('#overHeightSpecific').modal('show');
  304. }
  305. return isValid;
  306. }
  307. }
  308. // 设置指定清单界面选择框
  309. function setCheckBox(sheet, specificNodes) {
  310. const checkBox = new GC.Spread.Sheets.CellTypes.CheckBox();
  311. const specificCol = getSpecificCol();
  312. // 只有叶子节点和非计算基数节点才是选择框
  313. const func = () => {
  314. specificNodes.forEach((node, index) => {
  315. if (!node.children.length && !node.data.calcBase) {
  316. sheet.getCell(index, specificCol).cellType(checkBox);
  317. }
  318. });
  319. };
  320. TREE_SHEET_HELPER.massOperationSheet(sheet, func);
  321. }
  322. // 初始化指定清单选择树
  323. function initSpecificTree(data, sheet, setting) {
  324. specificTree = idTree.createNew({ id: 'ID', pid: 'ParentID', nid: 'NextSiblingID', rootId: -1, autoUpdate: true });
  325. const controller = TREE_SHEET_CONTROLLER.createNew(specificTree, sheet, setting, false);
  326. specificTree.loadDatas(data);
  327. controller.showTreeData();
  328. sheet.setRowCount(data.length);
  329. setCheckBox(sheet, specificTree.items);
  330. }
  331. // 表格点击事件,只有checkbox单元格会触发这个方法
  332. function handleSpreadButtonClick(e, args) {
  333. const { sheet, row, col } = args;
  334. // 只能单选,清空其他单元格的值并设置当前值
  335. const func = () => {
  336. const rowCount = sheet.getRowCount();
  337. const oldValue = sheet.getValue(row, col);
  338. for (let row = 0; row < rowCount; row++) {
  339. sheet.setValue(row, col, '');
  340. }
  341. sheet.setValue(row, col, !oldValue);
  342. }
  343. TREE_SHEET_HELPER.massOperationSheet(sheet, func);
  344. }
  345. // 初始化表格
  346. function initWorkbook() {
  347. specificSpread = sheetCommonObj.createSpread($('#specificArea')[0], 1);
  348. specificSpread.options.allowUserDragDrop = false; // 不允许拖填充,影响点击
  349. sheetCommonObj.spreadDefaultStyle(specificSpread);
  350. specificSpread.bind(GC.Spread.Sheets.Events.ButtonClicked, handleSpreadButtonClick);
  351. // 设置表头
  352. const sheet = specificSpread.getSheet(0);
  353. const headers = sheetCommonObj.getHeadersFromTreeSetting(specificTreeSetting);
  354. sheetCommonObj.setHeader(sheet, headers);
  355. return specificSpread;
  356. }
  357. // 从造价书界面筛选获取指定清单界面数据
  358. function getSpecificData() {
  359. // 只显示分部分项、措施项目的清单节点
  360. const billsNodes = projectObj.project.Bills.tree.items;
  361. const flags = [fixedFlag.SUB_ENGINERRING, fixedFlag.MEASURE];
  362. const billsData = flags.reduce((allData, flag) => {
  363. const fixedNode = billsNodes.find(node => node.getFlag() === flag);
  364. const posterity = fixedNode.getPosterity();
  365. const data = [fixedNode, ...posterity].map(node =>
  366. ({
  367. ID: node.data.ID,
  368. ParentID: node.data.ParentID,
  369. NextSiblingID: node.data.NextSiblingID,
  370. code: node.data.code,
  371. subType: billText[node.data.type],
  372. name: node.data.name,
  373. calcBase: node.data.calcBase,
  374. }));
  375. allData.push(...data);
  376. return allData;
  377. }, []);
  378. return billsData;
  379. }
  380. // 设置指定ID选中
  381. function setSpecific(ID) {
  382. if (!ID) {
  383. return;
  384. }
  385. const sheet = specificSpread.getSheet(0);
  386. const nodes = specificTree.items;
  387. const index = nodes.findIndex(node => node.data.ID === ID);
  388. if (!~index) {
  389. return;
  390. }
  391. const col = getSpecificCol();
  392. sheet.setValue(index, col, true);
  393. }
  394. // 从表格中获取勾选的指定ID
  395. function getSpecificFromSheet() {
  396. const sheet = specificSpread.getSheet(0);
  397. const nodes = specificTree.items;
  398. const rowCount = sheet.getRowCount();
  399. const col = getSpecificCol();
  400. for (let row = 0; row < rowCount; row++) {
  401. const value = sheet.getValue(row, col);
  402. if (value) {
  403. return nodes[row].data.ID;
  404. }
  405. }
  406. return null;
  407. }
  408. // 初始化指定清单选择界面
  409. function initSpecificModal() {
  410. $('#specificArea').show();
  411. const spread = initWorkbook();
  412. const sheet = spread.getSheet(0);
  413. const data = getSpecificData();
  414. initSpecificTree(data, sheet, specificTreeSetting);
  415. const { specificID } = getAction();
  416. setSpecific(specificID);
  417. }
  418. // 隐藏指定清单选择界面
  419. function hideSpecificModal() {
  420. $('#specificArea').hide();
  421. if (specificSpread) {
  422. specificSpread.destroy();
  423. specificSpread = null;
  424. }
  425. }
  426. // 初始化子目生成方式选项设置窗口
  427. function initModal() {
  428. const { option, specificID } = getAction();
  429. $(`input[name=cgx][value=${option}]`).prop('checked', 'checked');
  430. $('#specificArea').hide();
  431. if (option === Option.SPECIFIC) {
  432. initSpecificModal(specificID);
  433. }
  434. }
  435. // 超高降效下拉项或选项是否改变了
  436. function isValueChanged() {
  437. const updateData = getUpdateData();
  438. return !!(updateData.bills.length || updateData.ration.length);
  439. }
  440. // 对比两个选项行为,获取更新选项数据
  441. function getUpdateProjectData(oldAction, newAction) {
  442. if (!oldAction || !newAction) {
  443. return {};
  444. }
  445. const optionDiff = oldAction.option !== newAction.option;
  446. const specificDiff = oldAction.specificID !== newAction.specificID;
  447. const updateData = {
  448. ID: projectObj.project.ID(),
  449. overHeightOption: newAction.option,
  450. overHeightSpecificID: newAction.specificID,
  451. };
  452. return optionDiff || specificDiff
  453. ? updateData
  454. : {};
  455. }
  456. /**
  457. * 获取更新数据:对比项目节点中超高降效的新旧值,新值为暂存的单元格文本,旧值为节点data数据
  458. * @param {Object} newAction - 选项行为
  459. * @return {Object} - {
  460. * project: {ID: Number,overHeightOp: Number, overHeightSpecificID: Number||Null},
  461. * bills: [{ID: Number, overHeight: String}],
  462. * ration: [{ID: Number, overHeight: String}]
  463. * }
  464. */
  465. function getUpdateData(newAction) {
  466. const update = {
  467. project: {}, // 可能会更改项目属性的超高降效设置
  468. bills: [],
  469. ration: [],
  470. };
  471. const oldAction = getAction();
  472. const updateProjectData = getUpdateProjectData(oldAction, newAction);
  473. Object.assign(update.project, updateProjectData);
  474. const nodes = projectObj.project.mainTree.items;
  475. const sheet = projectObj.mainController.sheet;
  476. const overHeightCol = getOverHeightCol();
  477. nodes.forEach((node, index) => {
  478. const newValue = sheet.getText(index, overHeightCol);
  479. const oldValue = node.data.overHeight;
  480. // 非严等
  481. if (!commonUtil.similarEqual(newValue, oldValue)) {
  482. const type = node.sourceType === projectObj.project.Bills.getSourceType()
  483. ? 'bills'
  484. : 'ration';
  485. update[type].push({
  486. ID: node.data.ID,
  487. overHeight: newValue
  488. });
  489. }
  490. });
  491. return update;
  492. }
  493. /**
  494. * 获取删除数据:项目中所有超高子目
  495. * @return {Object} - {ration: [{ID: Number}]}
  496. */
  497. function getDeleteData() {
  498. const del = {
  499. ration: [],
  500. };
  501. const rations = projectObj.project.Ration.datas;
  502. del.ration = rations
  503. .filter(ration => ration.type === rationType.overHeight)
  504. .map(ration => ({ ID: ration.ID }));
  505. return del;
  506. }
  507. /**
  508. * 获取需要生成超高子目的定额节点
  509. * @return {Array} - [{node: Object, overHeight: String}]
  510. */
  511. function getNeedCalcRationItems() {
  512. // 从整个项目中筛选当前下拉项单元格的文本是需要计算的定额节点
  513. const nodes = projectObj.project.mainTree.items;
  514. const sheet = projectObj.mainController.sheet;
  515. const overHeightCol = getOverHeightCol();
  516. const rst = [];
  517. nodes.forEach(node => {
  518. // 非超高子目的定额节点才生成
  519. const notOverHeightRationNode = node.sourceType !== projectObj.project.Ration.getSourceType()
  520. || node.data.type === rationType.overHeight;
  521. if (notOverHeightRationNode) {
  522. return;
  523. }
  524. const overHeight = sheet.getText(node.serialNo(), overHeightCol);
  525. const overHeightItem = getOverHeightItem(overHeight);
  526. if (isNeedToCalc(overHeightItem)) {
  527. rst.push({ node, overHeight });
  528. }
  529. });
  530. return rst;
  531. }
  532. // 根据选项获取超高子目挂载的清单
  533. function getMountedBills(action) {
  534. const { option, specificID } = action;
  535. // 生成清单数据
  536. function initMountedBills() {
  537. // 生成的清单位置为其他措施项目的首项
  538. const measureNode = projectObj.project.mainTree.items.find(node => node.getFlag() === fixedFlag.OTHER_MEASURE_FEE);
  539. const firstNode = measureNode.children[0];
  540. //const parent = measureNode.children[measureNode.children.length - 1];
  541. // 具体完整数据需要在后端跟标准数据对比完善
  542. return {
  543. projectID: projectObj.project.ID(),
  544. billsLibId: +projectObj.project.projectInfo.engineeringInfo.bill_lib[0].id,
  545. ID: uuid.v1(),
  546. ParentID: measureNode.data.ID,
  547. NextSiblingID: firstNode ? firstNode.data.ID : -1,
  548. type: billType.BILL,
  549. code: fixedCode,
  550. name: '超高施工增加',
  551. unit: 'm2',
  552. quantity: '1',
  553. };
  554. }
  555. // 选项一
  556. if (option === Option.SEPARATION) {
  557. return {
  558. isNew: false,
  559. bills: null,
  560. };
  561. } else if (option === Option.MEASURE) { // 选项二且造价书没有相关清单,需要插入清单
  562. const fixedNode = getMeasureFixedNode();
  563. return {
  564. isNew: !fixedNode,
  565. bills: fixedNode ? fixedNode.data : initMountedBills(),
  566. };
  567. } else {
  568. const specificNode = getSpecificNode(specificID);
  569. return {
  570. isNew: false,
  571. bills: specificNode.data,
  572. }
  573. }
  574. }
  575. // 获取清单节点下最末非超高子目定额
  576. function getLastNormalRationNode(billsNode) {
  577. const nodes = billsNode && billsNode.children
  578. ? billsNode.children.filter(node => node.data.type !== rationType.overHeight)
  579. : [];
  580. return nodes[nodes.length - 1]
  581. }
  582. // 将需要生成超高子目的定额数据按照挂载清单和超高名称进行分组,其下数组为关联的主定额
  583. // @return {Object} {'billsID@overHeight': [node: rationNode]}
  584. function getGroupData(rationItems, mountedBillsID) {
  585. // mapping: billsID-overHeightName
  586. const group = {};
  587. rationItems.forEach(item => {
  588. // 无指定的清单ID,则挂载在各自的清单上
  589. const billsID = mountedBillsID || item.node.data.billsItemID
  590. const key = `${billsID}@${item.overHeight}`;
  591. if (!group[key]) {
  592. group[key] = [];
  593. }
  594. group[key].push(item.node);
  595. });
  596. return group;
  597. }
  598. // 根据系数类型获取汇总的定额金额,这个金额作为计算定额人材机的基数
  599. function getFeeByRateType(rateType, referenceRations) {
  600. const feeField = FeeField[rateType];
  601. // 汇总定额节点相关综合合价
  602. return referenceRations.reduce((sum, rationNode) => {
  603. const feeObj = rationNode.data.feesIndex;
  604. const totalFee = feeObj && feeObj[feeField]
  605. ? feeObj[feeField].totalFee
  606. : 0;
  607. return scMathUtil.roundForObj(sum + totalFee, decimalObj.process);
  608. }, 0);
  609. }
  610. // 通过超高降效计算得来的定额人材机消耗量:消耗量=定额消耗量=算出来的值
  611. function getQuantity(rate, fee) {
  612. return String(scMathUtil.roundForObj(rate * fee, decimalObj.glj.quantity));
  613. }
  614. /**
  615. * 获取定额喝定额人材机数据
  616. * @param {Array} rationItems - 需要生成超高子目的定额数据{node: Object, overHtight: String}
  617. * @param {Object} mountedBills - 挂载到的清单数据
  618. * @return {Object} - {ration: Array, rationGLJ: Array}
  619. */
  620. function getAddRationAndRationGLJData(rationItems, mountedBills) {
  621. // 生成定额数据
  622. function initRation(bills, overHeightItem, serialNo) {
  623. const programID = projectObj.project.calcProgram.compiledTemplateMaps[programName];
  624. // 生成的超高子目消耗量为1
  625. const quantity = '1';
  626. // 含量为 定额消耗量 / 清单消耗量
  627. const tempV = quantity / bills.quantity;
  628. const contain = isFinite(tempV)
  629. ? scMathUtil.roundForObj(tempV, decimalObj.ration.quantity)
  630. : '0';
  631. return {
  632. projectID: projectObj.project.ID(),
  633. billsItemID: bills.ID,
  634. ID: uuid.v1(),
  635. programID,
  636. serialNo,
  637. code: overHeightItem.code,
  638. name: overHeightItem.name,
  639. unit: overHeightItem.unit,
  640. type: rationType.overHeight,
  641. quantity,
  642. contain,
  643. }
  644. }
  645. // 根据分组的keys获取定额serialNo值映射表 billsID@overHeight
  646. function getSerialNoMappig(groupKeys) {
  647. // 清单ID - serialNo映射
  648. const billsIDMap = {};
  649. // 完整的key - serialNo映射
  650. const keyMap = {};
  651. const nodes = projectObj.project.mainTree.nodes;
  652. // 先给根据清单ID设置上第一个serialNo值(基准值),和超高项数据
  653. groupKeys.forEach(key => {
  654. const [billsID, overHeight] = key.split('@');
  655. if (billsIDMap[billsID]) {
  656. billsIDMap[billsID].items.push(overHeight);
  657. return;
  658. }
  659. const billsNode = nodes[`id_${billsID}`];
  660. const lastNormalRationNode = billsNode ? getLastNormalRationNode(billsNode) : null;
  661. const serialNo = lastNormalRationNode ? lastNormalRationNode.data.serialNo + 1 : 1;
  662. billsIDMap[billsID] = { serialNo, items: [overHeight] };
  663. });
  664. // 将同一清单下的超高项按照下拉项位置排序
  665. Object.entries(billsIDMap).forEach(([billsID, { serialNo, items }]) => {
  666. items.sort((a, b) => getIndex(a) - getIndex(b));
  667. items.forEach((overHeight, index) => {
  668. const key = `${billsID}@${overHeight}`;
  669. keyMap[key] = serialNo + index;
  670. });
  671. });
  672. return keyMap;
  673. }
  674. // 生成超高子目的定额人材机数据,定额人材机的属性只是一部分,还有部分数据需要在后端处理
  675. function initRationGLJ(ration, typeItem, referenceRationNodes) {
  676. const { type, rate } = typeItem;
  677. const sumFee = getFeeByRateType(type, referenceRationNodes);
  678. // 不同类型的基础人材机属性
  679. const quantity = getQuantity(rate, sumFee);
  680. const baseObj = BaseRatoinGLJ[type];
  681. // 补全定额人材机属性,共性属性
  682. const extendObj = {
  683. projectID: projectObj.project.ID(),
  684. ID: uuid.v1(),
  685. billsItemID: ration.billsItemID,
  686. rationID: ration.ID,
  687. repositoryId: -1,
  688. GLJID: -1,
  689. unit: '%',
  690. specs: '',
  691. // 定额人材机没有价格字段,但是生成单价文件需要需要这两个价格字段,默认为“1”
  692. basePrice: '1',
  693. marketPrice: '1',
  694. quantity,
  695. rationItemQuantity: quantity,
  696. };
  697. return { ...baseObj, ...extendObj };
  698. }
  699. const add = {
  700. ration: [],
  701. rationGLJ: []
  702. };
  703. if (!rationItems.length) {
  704. return add;
  705. }
  706. const mountedBillsID = mountedBills
  707. ? mountedBills.ID
  708. : null;
  709. // 分析分组数据,获取定额及定额人材机数据
  710. const group = getGroupData(rationItems, mountedBillsID);
  711. const rationSerialNoMapping = getSerialNoMappig(Object.keys(group));
  712. // 获取定额及定额人材机数据
  713. Object.entries(group).forEach(([key, referenceRationNodes]) => {
  714. const [billsID, overHeight] = key.split('@');
  715. const overHeightItem = getOverHeightItem(overHeight);
  716. const bills = billsID === mountedBillsID
  717. ? mountedBills
  718. : projectObj.project.Bills.datas.find(item => item.ID === billsID);
  719. const serialNo = rationSerialNoMapping[key];
  720. const overHeightRation = initRation(bills, overHeightItem, serialNo);
  721. // 给生成的超高子目定额,设置关联定额列表(关联定额工程量等发生变化,需要重算超高子目)
  722. overHeightRation.referenceRationList = referenceRationNodes.map(node => node.data.ID);
  723. add.ration.push(overHeightRation);
  724. // 根据超高项获取系数列表,系数列表一个元素会根据系数类别生成一条定额人材机(人、机、材料)
  725. const rateTypeList = getRateTypeList(overHeightItem);
  726. const rationGLJs = rateTypeList.map(rateTypeItem => initRationGLJ(overHeightRation, rateTypeItem, referenceRationNodes));
  727. add.rationGLJ.push(...rationGLJs);
  728. });
  729. return add;
  730. }
  731. /**
  732. * 获取插入数据:超高子目(定额)、清单(选项2、3时可能会插入)
  733. * @param {Object} action - {option: Number, specificID: Undefined||Null||Number}
  734. * @return {Array} - {bills: Array, ration: Array, rationGLJ: Array}
  735. */
  736. function getAddData(action) {
  737. const add = {
  738. bills: [],
  739. ration: [],
  740. rationGLJ: [],
  741. };
  742. // 挂载到的清单,新增或已有的
  743. const mountedBills = getMountedBills(action);
  744. if (mountedBills.isNew) {
  745. add.bills.push(mountedBills.bills);
  746. }
  747. // 获取需要生成超高子目的定额数据
  748. const needCalcRationItems = getNeedCalcRationItems();
  749. const subData = getAddRationAndRationGLJData(needCalcRationItems, mountedBills.bills);
  750. add.ration = subData.ration;
  751. add.rationGLJ = subData.rationGLJ;
  752. return add;
  753. }
  754. /**
  755. * 生成传输数据
  756. * @param {Boolean} isCancelCalc - 是否取消计取
  757. * @param {Object} action - 新的选项行为
  758. * @return {Object}
  759. */
  760. function generatePostData(isCancelCalc, action) {
  761. // 默认模板
  762. const postData = {
  763. addData: {
  764. bills: [],
  765. ration: [],
  766. rationGLJ: [],
  767. },
  768. updateData: {
  769. project: {},
  770. bills: [],
  771. ration: [],
  772. },
  773. deleteData: {
  774. ration: [],
  775. },
  776. };
  777. // 取消计取费用,只删除超高子目
  778. if (isCancelCalc) {
  779. postData.deleteData = getDeleteData();
  780. return postData;
  781. }
  782. // 没有新的选项行为,获取当前项目的选项行为
  783. if (!action) {
  784. action = getAction();
  785. }
  786. const addData = getAddData(action);
  787. const updateData = getUpdateData(action);
  788. const deleteData = getDeleteData();
  789. return Object.assign(postData, { addData, updateData, deleteData });
  790. }
  791. /**
  792. * 更改了超高降效列(edited、rangeChanged),触发事件
  793. * @param {Array} changedData - 变化的数据 [{node: Object, value: String}]
  794. * @return {void}
  795. */
  796. function handleValueChanged(changedData) {
  797. validateData(changedData);
  798. changedData = simplifyData(changedData);
  799. setTexts(changedData);
  800. const valuedChanged = isValueChanged();
  801. if (!valuedChanged) {
  802. return;
  803. }
  804. // 选项2、选项3情况下下拉可能会遇到,相关清单已经被删除,需要检测行为
  805. const action = getAction();
  806. const actionValid = checkAction(action);
  807. // actionValid为false的时候,可能后续需要恢复单元格文本值,根据后续用户在弹窗中的选择(后文ready事件中绑定)
  808. if (!actionValid) {
  809. return;
  810. }
  811. handleConfirmed();
  812. }
  813. // 确认事件
  814. async function handleConfirmed(isCancelCalc = false, action = null) {
  815. $.bootstrapLoading.start();
  816. try {
  817. const postData = generatePostData(isCancelCalc, action);
  818. const { addData, updateData, deleteData } = postData;
  819. // 更新、删除、新增数据
  820. // 返回的是新增的清单、定额人材机、项目人材机 rst = {bills: [], rationGLJ: [], projectGLJ: []}
  821. const rst = await ajaxPost('/project/calcOverHeightFee', postData);
  822. // 后续获取重算节点相关:
  823. // 新增的定额节点要在同步数据后才有,删除的定额节点在同步前找不到,因此同步前先获取被删除定额节点的清单ID列表
  824. const rationIDList = deleteData.ration.map(item => item.ID);
  825. let billsIDList = projectObj.project.Ration.datas
  826. .filter(item => rationIDList.includes(item.ID))
  827. .map(item => item.billsItemID);
  828. billsIDList = [...new Set(billsIDList)];
  829. // 同步数据
  830. const newAddData = { ...addData, ...rst };
  831. syncData(deleteData, updateData, newAddData);
  832. // 获取重算节点
  833. const reCalcNodes = getReCalcNodes(billsIDList, newAddData.ration);
  834. if (!reCalcNodes.length) {
  835. $.bootstrapLoading.end();
  836. return;
  837. }
  838. // 获取项目人材机数据,更新缓存
  839. $.bootstrapLoading.end(); // 重算节点相关方法里有loading,防止提前结束了loading
  840. // 重算相关节点
  841. projectObj.project.calcProgram.calcNodesAndSave(reCalcNodes);
  842. } catch (err) {
  843. alert(err);
  844. console.log(err);
  845. recoverCellsText();
  846. $.bootstrapLoading.end();
  847. }
  848. }
  849. /**
  850. * 获取需要重算的节点
  851. * @param {Array} billsIDList - 根据删除的定额ID数据获取的清单ID列表
  852. * @param {Array} rationData - 新增的定额数据
  853. * @return {Array}
  854. */
  855. function getReCalcNodes(billsIDList, rationData) {
  856. // 获取被删除定额的清单节点
  857. const billsNodes = billsIDList.map(ID => projectObj.project.mainTree.nodes[`id_${ID}`]);
  858. // 获取新增的定额数据
  859. const rationNodes = rationData.map(ration => projectObj.project.mainTree.nodes[`id_${ration.ID}`]);
  860. return [...billsNodes, ...rationNodes];
  861. }
  862. /**
  863. * 同步、更新缓存的数据、节点
  864. * @param {Object} delData - 删除的数据,包含定额ID列表
  865. * @param {Object} updateData - 更新的数据
  866. * @param {Object} addData - 新增的完整清单和定额人材机数据,经过后端加工返回
  867. * @return {void}
  868. */
  869. function syncData(delData, updateData, addData) {
  870. // 被删除数据的清单ID@定额serialNo - 价格字段映射
  871. // 在新增节点时,给上原本该行的feesIndex字段,不然会出现新增节点价格为空,重算后价格有值,造成价格字段闪烁的情况
  872. // 新增节点赋上原本该行的feesIndex字段只是为了避免闪烁的情况。
  873. // 新节点fees数组没有赋值,后续计算的时候节点的价格字段会被初始化(calcTools.initFees中),因此这个操作不会对计算结果有影响
  874. function getSerialNoFeesIndexMapping({ ration }) {
  875. const mapping = {};
  876. const rationIDList = ration.map(item => item.ID);
  877. projectObj.project.Ration.datas
  878. .filter(item => rationIDList.includes(item.ID))
  879. .forEach(item => mapping[`${item.billsItemID}@${item.serialNo}`] = item.feesIndex);
  880. return mapping;
  881. }
  882. // 新增定额设置上暂时显示的价格字段
  883. function setTemporaryFeesIndex({ ration }, feesIndexMapping) {
  884. ration.forEach(item => {
  885. const key = `${item.billsItemID}@${item.serialNo}`;
  886. const oldFeesIndex = feesIndexMapping[key];
  887. if (oldFeesIndex) {
  888. item.feesIndex = oldFeesIndex;
  889. }
  890. });
  891. }
  892. // 删除数据
  893. function del({ ration }) {
  894. const sheet = projectObj.mainController.sheet;
  895. const func = () => {
  896. // 删除定额数据、定额节点及子数据
  897. if (ration.length) {
  898. const rationIDList = ration.map(item => item.ID);
  899. projectObj.project.Ration.deleteDataSimply(rationIDList);
  900. // 由于cacheTree delete方法会将preSelected设置成null
  901. // 会导致变更焦点行时,清除不了原焦点行的选中色,因此这里重设下preSelected,在这里处理避免影响其他已有代码
  902. projectObj.project.mainTree.preSelected = projectObj.project.mainTree.selected;
  903. }
  904. };
  905. TREE_SHEET_HELPER.massOperationSheet(sheet, func);
  906. }
  907. // 更新数据
  908. function update({ project, bills, ration }) {
  909. // 更新项目属性
  910. if (project) {
  911. const property = projectObj.project.projectInfo.property;
  912. Object.assign(property, project);
  913. // 更新节点(控制基数只读性,指定清单基数列只读)
  914. const specificNode = getSpecificNode(property.overHeightSpecificID);
  915. if (specificNode) {
  916. TREE_SHEET_HELPER.refreshTreeNodeData(projectObj.mainController.setting, projectObj.mainController.sheet, [specificNode], false);
  917. }
  918. }
  919. const mainTree = projectObj.project.mainTree;
  920. // 更新节点超高降效
  921. [...bills, ...ration].forEach(({ ID, overHeight }) => {
  922. const node = mainTree.nodes[`id_${ID}`];
  923. if (node) {
  924. node.data.overHeight = overHeight;
  925. }
  926. });
  927. }
  928. // 插入数据
  929. function add({ bills, ration, rationGLJ, projectGLJ }) {
  930. const sheet = projectObj.mainController.sheet;
  931. const func = () => {
  932. // 插入清单数据和清单节点主树节点
  933. if (bills.length) {
  934. projectObj.project.Bills.addNewDataSimply(bills);
  935. }
  936. // 插入定额数据和定额节点
  937. if (ration.length) {
  938. // 按照serialNo排序
  939. ration.sort((a, b) => a.serialNo - b.serialNo);
  940. projectObj.project.Ration.addNewDataSimply(ration);
  941. }
  942. // 插入定额人材机数据
  943. if (rationGLJ.length) {
  944. projectObj.project.ration_glj.addDatasToList(rationGLJ);
  945. }
  946. // 插入项目人材机数据
  947. if (projectGLJ.length) {
  948. projectObj.project.projectGLJ.loadNewProjectGLJToCaches(projectGLJ);
  949. // 重算消耗量
  950. projectObj.project.projectGLJ.calcQuantity();
  951. }
  952. };
  953. TREE_SHEET_HELPER.massOperationSheet(sheet, func);
  954. }
  955. const feesIndexMapping = getSerialNoFeesIndexMapping(delData);
  956. setTemporaryFeesIndex(addData, feesIndexMapping);
  957. del(delData);
  958. update(updateData);
  959. add(addData);
  960. }
  961. // 恢复暂存的单元格文本
  962. function recoverCellsText() {
  963. const sheet = projectObj.mainController.sheet;
  964. const func = () => {
  965. const nodes = projectObj.project.mainTree.items;
  966. const overHeightCol = getOverHeightCol();
  967. nodes.forEach((node, index) => {
  968. const newValue = sheet.getText(index, overHeightCol);
  969. const oldValue = node.data.overHeight;
  970. if (!commonUtil.similarEqual(newValue, oldValue)) {
  971. sheet.setText(index, overHeightCol, oldValue);
  972. }
  973. });
  974. };
  975. TREE_SHEET_HELPER.massOperationSheet(sheet, func);
  976. }
  977. // 取消超高降效,删除所有超高子目
  978. function cancelCalc() {
  979. handleConfirmed(true);
  980. }
  981. // 取消事件,触发了取消操作,需要恢复单元格文本
  982. function handleCancel() {
  983. $.bootstrapLoading.start();
  984. recoverCellsText();
  985. $.bootstrapLoading.end();
  986. }
  987. /**
  988. * 返回清单与超高子目和其定额人材机映射
  989. * @param {Array} rations - 全部超高子目数据
  990. * @param {Array} rationGLJs - 全部超高子目定额人材机数据
  991. * @return {Object} - {billsItemID@超高子目编码@定额人材机编码: {quanqity, rationItemQuantity}}
  992. */
  993. function getRationGLJMap(rations, rationGLJs) {
  994. const mapping = {};
  995. rationGLJs.forEach(rGLJ => {
  996. const ration = rations.find(ration => ration.ID === rGLJ.rationID);
  997. const rationCode = ration ? ration.code : '';
  998. // 由于一个清单下不会存在两个相同编号的超高子目(相同编号会被自动汇总),因此这个key能确定唯一一条定额人材机
  999. const key = `${rGLJ.billsItemID}@${rationCode}@${rGLJ.original_code}`;
  1000. mapping[key] = { quantity: rGLJ.quantity, rationItemQuantity: rGLJ.rationItemQuantity };
  1001. });
  1002. return mapping;
  1003. }
  1004. // 比较两个清单与超高子目和其定额人材机映射表,看是否相同
  1005. function isMappingEqual(oldMapping, newMapping) {
  1006. const oldKeys = Object.keys(oldMapping);
  1007. const newKeys = Object.keys(newMapping);
  1008. if (oldKeys.length !== newKeys.length) {
  1009. return false;
  1010. }
  1011. const isEveryKeySame = oldKeys.every(key => {
  1012. const oldData = oldMapping[key];
  1013. const newData = newMapping[key];
  1014. if (!newData) {
  1015. return false;
  1016. }
  1017. const quantitySame = commonUtil.similarEqual(oldData.quantity, newData.quantity);
  1018. const rationQuantitySame = commonUtil.similarEqual(oldData.rationItemQuantity, newData.rationItemQuantity);
  1019. return quantitySame && rationQuantitySame;
  1020. });
  1021. return isEveryKeySame;
  1022. }
  1023. /**
  1024. * 重新计取项目超高子目,超高子目的值与关联定额相关
  1025. * 因此各种操作下改变了相关定额,都要重新计算超高子目
  1026. * 为了降低复杂度和保证逻辑统一性,重新计取为重新走(删除新增逻辑)
  1027. * 需要尽可能地降低操作的触发率
  1028. */
  1029. async function reCalcOverHeightFee() {
  1030. const project = projectObj.project;
  1031. // 如果项目没有超高降效数据,项目不可用超高降效,返回
  1032. if (!project.isOverHeightProject()) {
  1033. return;
  1034. }
  1035. // 如果没有超高定额,返回(因此删除了选项二三、指定的清单不会触发)
  1036. const overHeightRations = project.Ration.datas.filter(ration => ration.type === rationType.overHeight);
  1037. if (!overHeightRations.length) {
  1038. return;
  1039. }
  1040. // 获取新旧超高数据映射表,不同才需要计算
  1041. const overHeightRationIDs = overHeightRations.map(ration => ration.ID);
  1042. const overHeigtRationGLJs = project.ration_glj.datas.filter(rGLJ => overHeightRationIDs.includes(rGLJ.rationID));
  1043. const action = getAction();
  1044. const { ration, rationGLJ } = getAddData(action);
  1045. const oldMapping = getRationGLJMap(overHeightRations, overHeigtRationGLJs);
  1046. const newMapping = getRationGLJMap(ration, rationGLJ);
  1047. if (isMappingEqual(oldMapping, newMapping)) {
  1048. return;
  1049. }
  1050. // 存在不同,重算
  1051. await handleConfirmed();
  1052. }
  1053. // 事件监听
  1054. $(document).ready(() => {
  1055. // 设置窗口显示事件
  1056. $('#overHeightOpt').on('shown.bs.modal', () => {
  1057. initModal();
  1058. });
  1059. // 设置窗口隐藏事件
  1060. $('#overHeightOpt').on('hide.bs.modal', () => {
  1061. if (specificSpread) {
  1062. specificSpread.destroy();
  1063. specificSpread = null;
  1064. }
  1065. specificTree = null;
  1066. // 指定清单时的选项,指定清单被删除,用户下拉改变超高降效列,
  1067. // 弹出选中指定清单窗口,用户没有选定直接关闭窗口,此时需要把造价书暂存的值恢复
  1068. handleCancel();
  1069. });
  1070. // 设置窗口单选变更事件
  1071. $('input[name=cgx]').change(function () {
  1072. const option = +$(this).val();
  1073. switch (option) {
  1074. case Option.SEPARATION:
  1075. case Option.MEASURE:
  1076. hideSpecificModal();
  1077. break;
  1078. case Option.SPECIFIC:
  1079. initSpecificModal();
  1080. break;
  1081. }
  1082. });
  1083. // 设置窗口确认事件
  1084. $('#overHeightOptConfirmed').click(() => {
  1085. const option = +$('input[name=cgx]:checked').val();
  1086. switch (option) {
  1087. case Option.SEPARATION:
  1088. $('#overHeightOpt').modal('hide');
  1089. handleConfirmed(false, { option, specificID: null });
  1090. break;
  1091. case Option.MEASURE:
  1092. const fixedNode = getMeasureFixedNode();
  1093. // 造价书不存在相关清单,提示是否新增清单,由提示窗口进行后续操作
  1094. if (!fixedNode) {
  1095. $('#overHeightMeasure').modal('show');
  1096. return;
  1097. }
  1098. // 存在相关清单
  1099. $('#overHeightOpt').modal('hide');
  1100. handleConfirmed(false, { option, specificID: null });
  1101. break;
  1102. case Option.SPECIFIC:
  1103. const specificID = getSpecificFromSheet();
  1104. if (!specificID) {
  1105. alert('请指定清单');
  1106. return;
  1107. }
  1108. $('#overHeightOpt').modal('hide');
  1109. handleConfirmed(false, { option, specificID });
  1110. break;
  1111. }
  1112. });
  1113. // 选项二下,改变超高降效的值,且措施项目下指定清单被删除,弹窗按钮事件
  1114. $('#overHeightMeasureY').click(() => { // 确认 - 新增指定清单和相关数据
  1115. const action = { option: Option.MEASURE, specificID: null };
  1116. $('#overHeightOpt').modal('hide');
  1117. $('#overHeightMeasure').modal('hide');
  1118. handleConfirmed(false, action);
  1119. });
  1120. $('#overHeightMeasureN').click(handleCancel); // 取消
  1121. // 选项三下,改变超高降效的值,且指定清单被删除,弹窗按钮事件
  1122. $('#overHeightSpecificY').click(() => { // 确认 - 弹出设置窗口
  1123. $('#overHeightOpt').modal('show');
  1124. });
  1125. $('#overHeightSpecificN').click(handleCancel); // 取消
  1126. });
  1127. return {
  1128. init,
  1129. getComboData,
  1130. isOverHeight,
  1131. switchVisible,
  1132. handleValueChanged,
  1133. cancelCalc,
  1134. reCalcOverHeightFee,
  1135. };
  1136. })();