budgetSummarySheet.js 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925
  1. let budgetSummaryTreeSetting;
  2. /* 建设其他费表格相关 */
  3. const budgetSummaryObj = (() => {
  4. const { isEmptyVal, isDef, isNumber } = window.commonUtil;
  5. const { fixedFlag, BudgetArea, BudgetType } = window.commonConstants;
  6. let curBudgetType = BudgetType.BUILDING;
  7. // 原始数据
  8. let rawData = [];
  9. // ID与原始数据映射表(主要是恢复用)
  10. const orgMap = {};
  11. // 建设其他费表格对象
  12. let spread = null;
  13. // 建设其他费树
  14. let tree = null;
  15. // 计算相关
  16. const calcSetting = {
  17. costGrowthRate: 0,
  18. growthPeriod: 0,
  19. };
  20. // 单位设置下拉框
  21. const setUnitCombo = (sheet, data) => {
  22. const unitCol = budgetSummaryTreeSetting.cols.findIndex(item => item.data.field === 'unit');
  23. if (unitCol >= 0) {
  24. TREE_SHEET_HELPER.massOperationSheet(sheet, () => {
  25. const comboBox = sheetCommonObj.getDynamicCombo();
  26. comboBox
  27. .itemHeight(10)
  28. .items(['m', 'm2', 'm3', 'km', 't', 'kg', '台班', '工日', '昼夜', '元', '项', '处', '个', '件',
  29. '根', '组', '系统', '台', '套', '株', '丛', '缸', '支', '只', '块', '座', '对', '份', '樘', '攒', '榀'])
  30. .editable(true);
  31. data.forEach((item, index) => {
  32. sheet.getCell(index, unitCol).cellType(comboBox);
  33. })
  34. });
  35. }
  36. }
  37. const getFieldByCol = (col) => {
  38. const item = budgetSummaryTreeSetting.cols[col];
  39. return item && item.data && item.data.field || null;
  40. }
  41. // 单元格值验证器
  42. const validator = {
  43. text() {
  44. return true
  45. },
  46. number(val) {
  47. return !isDef(val) || isNumber(val);
  48. },
  49. };
  50. const getValidator = (col) => {
  51. const item = budgetSummaryTreeSetting.cols[col];
  52. if (!item) {
  53. return 'text';
  54. }
  55. return validator[item.data.type || 'text'];
  56. }
  57. // 单元格文本转换处理
  58. const textFactory = {
  59. calcBase(node) {
  60. if (node.data.calcBase && node.data.calcBase !== "") {
  61. return cbParser.toFExpr(node.data.calcBase);
  62. }
  63. return '';
  64. },
  65. 'feesIndex.common.unitFee': (node) => {
  66. if (node && node.data.area === BudgetArea.CONSTRUCTION_FEE) {
  67. // 实时计算显示单价
  68. const totalFee = _.get(node, 'data.feesIndex.common.totalFee', 0);
  69. const quantity = node.data.quantity || 0;
  70. if (!totalFee || !quantity) {
  71. return '';
  72. }
  73. return scMathUtil.roundForObj(totalFee / quantity, 2); // 小数位数写死2位置
  74. }
  75. if (node && node.data.area === BudgetArea.CONSTRUCTION_OTHER_FEE) {
  76. return _.get(node, 'data.feesIndex.common.unitFee', '') || '';
  77. }
  78. return '';
  79. },
  80. 'feesIndex.common.totalFee': (node) => {
  81. return _.get(node, 'data.feesIndex.common.totalFee', '') || '';
  82. },
  83. 'feesIndex.estimation.totalFee': (node) => {
  84. return _.get(node, 'data.feesIndex.estimation.totalFee', '') || '';
  85. },
  86. 'feesIndex.building.totalFee': (node) => {
  87. return _.get(node, 'data.feesIndex.building.totalFee', '') || '';
  88. },
  89. 'feesIndex.installation.totalFee': (node) => {
  90. return _.get(node, 'data.feesIndex.installation.totalFee', '') || '';
  91. },
  92. 'feesIndex.equipment.totalFee': (node) => {
  93. return _.get(node, 'data.feesIndex.equipment.totalFee', '') || '';
  94. },
  95. 'feesIndex.other.totalFee': (node) => {
  96. return _.get(node, 'data.feesIndex.other.totalFee', '') || '';
  97. },
  98. };
  99. /* 表格事件相关 */
  100. // 根据节点数据刷新表格数据
  101. const refreshData = (sheet, changedCells) => {
  102. if (!tree) {
  103. return;
  104. }
  105. TREE_SHEET_HELPER.massOperationSheet(sheet, () => {
  106. changedCells.forEach(({ row, col }) => {
  107. const node = tree.items[row];
  108. const field = getFieldByCol(col);
  109. if (!field || !node) {
  110. return;
  111. }
  112. const textFunc = textFactory[field];
  113. const val = textFunc ? textFunc(node) : node.data[field] || '';
  114. sheet.setValue(row, col, val);
  115. });
  116. });
  117. }
  118. // 刷新整个表格
  119. const refreshAll = (sheet) => {
  120. const changedCells = [];
  121. const colCount = budgetSummaryTreeSetting.cols.length;
  122. for (let row = 0; row < tree.items.length; row++) {
  123. for (let col = 0; col < colCount; col++) {
  124. changedCells.push({ row, col })
  125. }
  126. }
  127. refreshData(sheet, changedCells);
  128. }
  129. // 更新数据
  130. const bulkOperation = async (bulkData) => {
  131. if (bulkData.length) {
  132. await ajaxPost('/bills/bulkOperation', { bulkData });
  133. }
  134. };
  135. // 计算汇总估算总额
  136. const calcEstimate = (nodes) => {
  137. const dataArr = [];
  138. const parentIDs = new Set();
  139. nodes.forEach(node => parentIDs.add(node.data.ParentID));
  140. const parentNodes = [];
  141. parentIDs.forEach(parentID => {
  142. const node = tree.findNode(parentID);
  143. if (!node) {
  144. return;
  145. }
  146. parentNodes.push(node);
  147. if (!node.children || !node.children.length) {
  148. return;
  149. }
  150. let estimateFee = 0;
  151. node.children.forEach(child => {
  152. const childEstimateItem = child.data.fees && child.data.fees.find(item => item.fieldName === 'estimation') || null;
  153. const childEstimateFee = childEstimateItem ? childEstimateItem.totalFee : 0;
  154. estimateFee = scMathUtil.roundForObj(estimateFee + childEstimateFee, decimalObj.bills.totalPrice);
  155. });
  156. const estimateItem = node.data.fees && node.data.fees.find(item => item.fieldName === 'estimation') || null;
  157. const orgEstimateFee = estimateItem ? estimateItem.totalFee : 0;
  158. if (orgEstimateFee !== estimateFee) {
  159. if (estimateItem) {
  160. estimateItem.totalFee = estimateFee;
  161. } else {
  162. node.data.fees.push({ fieldName: 'estimation', totalFee: estimateFee, unitFee: 0 });
  163. }
  164. dataArr.push({ ID: node.data.ID, fees: node.data.fees });
  165. }
  166. });
  167. if (parentNodes.length) {
  168. dataArr.push(...calcEstimate(parentNodes));
  169. }
  170. return dataArr;
  171. };
  172. // 编辑相关
  173. const edit = async (sheet, changedCells, calcNodes = null) => {
  174. if (!changedCells.length && !calcNodes) {
  175. return;
  176. }
  177. if (changedCells.length) {
  178. // 单元格值验证
  179. const isValid = changedCells.every(({ row, col }) => {
  180. const val = sheet.getValue(row, col);
  181. return getValidator(col)(val);
  182. });
  183. // 验证不通过,恢复
  184. if (!isValid) {
  185. refreshData(sheet, changedCells);
  186. return;
  187. }
  188. }
  189. let needCalc = false;
  190. const calcEstimateNodes = [];
  191. const nodes = [];
  192. try {
  193. $.bootstrapLoading.start();
  194. const IDMap = {};
  195. const bulkData = [];
  196. changedCells.forEach(({ row, col }) => {
  197. const node = tree.items[row];
  198. if (!node) {
  199. return;
  200. }
  201. if (!nodes.find(n => n.data.ID !== node.data.ID)) {
  202. nodes.push(node);
  203. }
  204. const field = getFieldByCol(col);
  205. const value = sheet.getValue(row, col) || '';
  206. const data = (IDMap[node.data.ID] || (IDMap[node.data.ID] = {}));
  207. if (['feesIndex.common.unitFee'].includes(field)) {
  208. const fees = node.data.fees || [];
  209. const feeItem = fees.find(item => item.fieldName === 'common');
  210. if (feeItem) {
  211. feeItem.unitFee = value;
  212. } else {
  213. fees.push({ fieldName: 'common', totalFee: 0, unitFee: +value });
  214. }
  215. data[field] = fees;
  216. node.data[field] = fees;
  217. node.data.feesIndex = getFeeIndex(node.data.fees);
  218. } else if (field === 'feesIndex.estimation.totalFee') {
  219. const fees = node.data.fees || [];
  220. const feeItem = fees.find(item => item.fieldName === 'estimation');
  221. if (feeItem) {
  222. feeItem.totalFee = value;
  223. } else {
  224. fees.push({ fieldName: 'estimation', totalFee: +value, unitFee: 0 });
  225. }
  226. data.fees = fees;
  227. node.data.fees = fees;
  228. node.data.feesIndex = getFeeIndex(node.data.fees);
  229. calcEstimateNodes.push(node);
  230. } else {
  231. data[field] = value;
  232. node.data[field] = value;
  233. }
  234. if (field === 'calcBase') {
  235. node.data.userCalcBase = value;
  236. projectObj.project.calcBase.calculate(node, null, false);
  237. if (!projectObj.project.calcBase.success) {
  238. throw projectObj.project.calcBase.errMsg;
  239. } else if (isEmptyVal(value)) {
  240. // 删除清单基数,单价要清空
  241. calcTools.setFieldValue(node, 'feesIndex.common.unitFee', 0);
  242. }
  243. data.calcBase = node.data.calcBase;
  244. data.calcBaseValue = node.data.calcBaseValue;
  245. data.tenderCalcBaseValue = node.data.tenderCalcBaseValue;
  246. }
  247. if (['quantity', 'feesIndex.common.unitFee', 'calcBase', 'feeRate'].includes(field) && node.data.area !== BudgetArea.CONSTRUCTION_FEE) {
  248. needCalc = true;
  249. }
  250. });
  251. calcNodes = calcNodes ? calcNodes : needCalc ? nodes : [];
  252. // 重算节点
  253. const dataArr = [];
  254. if (calcNodes && calcNodes.length) {
  255. const changedNodes = projectObj.project.calcProgram.calcNodes(calcNodes, false, tree);
  256. for (const node of changedNodes) {
  257. nodes.push(node);
  258. if (node.changed) {
  259. const data = calcTools.cutNodeForSave(node);
  260. dataArr.push(data);
  261. }
  262. }
  263. }
  264. // 汇总估算总额
  265. if (calcEstimateNodes.length) {
  266. dataArr.push(...calcEstimate(calcEstimateNodes));
  267. }
  268. dataArr.forEach(item => {
  269. delete item.projectID;
  270. const data = IDMap[item.ID];
  271. if (data) {
  272. Object.assign(data, item);
  273. } else {
  274. IDMap[item.ID] = item;
  275. }
  276. });
  277. // 保存节点
  278. Object
  279. .entries(IDMap)
  280. .forEach(([ID, data]) => {
  281. const node = tree.findNode(ID);
  282. if (!node) {
  283. return;
  284. }
  285. // 处理被计算程序计算冲掉的估算总额
  286. const orgData = orgMap[ID];
  287. const orgEstimationItem = orgData.fees && orgData.fees.find(item => item.fieldName === 'estimation');
  288. const orgEstimationFee = orgEstimationItem ? orgEstimationItem.totalFee : 0;
  289. const estimationItem = data.fees && data.fees.find(item => item.fieldName === 'estimation');
  290. const estimationFee = estimationItem ? estimationItem.totalFee : 0;
  291. if (orgEstimationFee !== estimationFee) {
  292. if (!estimationItem && data.fees) {
  293. data.fees.push({ fieldName: 'estimation', totalFee: orgEstimationFee });
  294. }
  295. }
  296. // 处理其他费用
  297. if (node.isBelongToFlags([fixedFlag.CONSTRUCTION_OTHER_FEE])) {
  298. const fees = data.fees || [];
  299. const commonFeeItem = fees.find(item => item.fieldName === 'common');
  300. const otherFeeItem = fees.find(item => item.fieldName === 'other');
  301. if (otherFeeItem) {
  302. otherFeeItem.totalFee = commonFeeItem && commonFeeItem.totalFee || 0;
  303. } else {
  304. fees.push({ fieldName: 'other', totalFee: commonFeeItem && commonFeeItem.totalFee || 0 });
  305. }
  306. }
  307. const actualID = node.data.area === BudgetArea.CONSTRUCTION_FEE ? node.data.orgProjectID : ID;
  308. const updateType = node.data.area === BudgetArea.CONSTRUCTION_FEE ? 'updateProject' : 'update';
  309. bulkData.push({ type: updateType, data: { ...data, ID: actualID } });
  310. });
  311. await bulkOperation(bulkData);
  312. Object
  313. .entries(IDMap)
  314. .forEach(([ID, data]) => {
  315. const node = tree.findNode(ID);
  316. if (node) {
  317. Object.assign(node.data, data);
  318. node.data.feesIndex = getFeeIndex(node.data.fees);
  319. orgMap[ID] = _.cloneDeep(node.data);
  320. }
  321. });
  322. refreshAll(sheet);
  323. } catch (err) {
  324. console.log(err);
  325. nodes.forEach(node => {
  326. const orgItem = orgMap[node.data.ID];
  327. if (orgItem) {
  328. node.data = _.cloneDeep(orgItem);
  329. // Object.assign(node.data, orgItem);
  330. }
  331. });
  332. refreshData(sheet, changedCells);
  333. alert(err);
  334. } finally {
  335. $.bootstrapLoading.end();
  336. }
  337. }
  338. // 是否是属于工程费用区域的节点
  339. const isConstructionFeeArea = (node) => {
  340. return node && node.data && node.data.area === BudgetArea.CONSTRUCTION_FEE;
  341. }
  342. // 工具栏可操作性
  343. let upLevelDisabled = false;
  344. let downLevelDisabled = false;
  345. let upMoveDisabled = false;
  346. let downMoveDisabled = false;
  347. const refreshToolsBar = (node) => {
  348. upLevelDisabled = !node || !node.canUpLevel() || isConstructionFeeArea(node) || (node.nextSibling && node.data.calcBase);
  349. downLevelDisabled = !node || !node.canDownLevel() || isConstructionFeeArea(node) || isConstructionFeeArea(node.preSibling) || (node.preSibling && node.preSibling.data.calcBase);
  350. upMoveDisabled = !node || !node.canUpMove() || isConstructionFeeArea(node) || isConstructionFeeArea(node.preSibling);
  351. downMoveDisabled = !node || !node.canDownMove() || isConstructionFeeArea(node) || isConstructionFeeArea(node.nextSibling);
  352. if (upLevelDisabled) {
  353. $('#budget-upLevel').addClass('disabled');
  354. } else {
  355. $('#budget-upLevel').removeClass('disabled');
  356. }
  357. if (downLevelDisabled) {
  358. $('#budget-downLevel').addClass('disabled');
  359. } else {
  360. $('#budget-downLevel').removeClass('disabled');
  361. }
  362. if (upMoveDisabled) {
  363. $('#budget-upMove').addClass('disabled');
  364. } else {
  365. $('#budget-upMove').removeClass('disabled');
  366. }
  367. if (downMoveDisabled) {
  368. $('#budget-downMove').addClass('disabled');
  369. } else {
  370. $('#budget-downMove').removeClass('disabled');
  371. }
  372. };
  373. // 表格选中相关
  374. const selectCell = (row, col, setCell = false) => {
  375. const node = tree.items[row];
  376. refreshToolsBar(node);
  377. tree.selected = node;
  378. console.log(node);
  379. const sheet = spread.getSheet(0);
  380. if (setCell) {
  381. sheet.setActiveCell(row, col);
  382. }
  383. //设置选中行底色和恢复前选中行底色
  384. const refreshNodes = [node];
  385. if (!tree.preSelected) {
  386. refreshNodes.push(tree.items[0]);
  387. } else {
  388. refreshNodes.push(tree.preSelected);
  389. }
  390. tree.preSelected = node;
  391. projectObj.setNodesStyle(sheet, refreshNodes, tree);
  392. };
  393. // 事件列表
  394. const events = {
  395. EnterCell(sender, args) {
  396. args.sheet.repaint();
  397. },
  398. SelectionChanged(sender, args) {
  399. const newSel = args.newSelections[0] ? { row: args.newSelections[0].row, col: args.newSelections[0].col } : { row: 0, col: 0 };
  400. selectCell(newSel.row, newSel.col);
  401. },
  402. ValueChanged(sender, args) {
  403. if (isEmptyVal(args.oldValue) && isEmptyVal(args.newValue)) {
  404. return;
  405. }
  406. edit(args.sheet, [{ row: args.row, col: args.col }]);
  407. },
  408. RangeChanged(sender, args) {
  409. edit(args.sheet, args.changedCells);
  410. }
  411. }
  412. const bindEvents = (sheet) => {
  413. Object.entries(events).forEach(([ev, evFunc]) => {
  414. sheet.bind(GC.Spread.Sheets.Events[ev], evFunc);
  415. });
  416. }
  417. /* 只读相关 */
  418. // 单元格锁定判断
  419. const lockFactory = {
  420. code(node) {
  421. return !!(node && (node.getFlag() || node.data.area === BudgetArea.CONSTRUCTION_FEE));
  422. },
  423. name(node) {
  424. return !!(node && (node.getFlag() || node.data.area === BudgetArea.CONSTRUCTION_FEE));
  425. },
  426. 'feesIndex.estimation.totalFee'(node) {
  427. return !!(!node || node.children.length);
  428. },
  429. 'feesIndex.common.unitFee'(node) {
  430. return !!(node && (node.data.area === BudgetArea.CONSTRUCTION_FEE));
  431. },
  432. calcBase(node) {
  433. return !!(node && (node.children.length || node.data.area === BudgetArea.CONSTRUCTION_FEE));
  434. },
  435. feeRate(node) {
  436. return !!(node && (node.children.length || node.data.area === BudgetArea.CONSTRUCTION_FEE));
  437. },
  438. remark(node) {
  439. return !!(node && node.data.area === BudgetArea.CONSTRUCTION_FEE);
  440. }
  441. };
  442. const lockData = (sheet, nodes, isMass = true) => {
  443. const lock = () => {
  444. if (projectReadOnly) {
  445. sheet.getRange(0, 0, nodes.length, budgetSummaryTreeSetting.cols.length, GC.Spread.Sheets.SheetArea.viewport).locked(true);
  446. return 0;
  447. }
  448. // 工程费用区域,只读
  449. /* const constructionFeeNode = nodes.find(node => node.getFlag() === fixedFlag.CONSTRUCTION_FEE);
  450. if (!constructionFeeNode) {
  451. return 0;
  452. }
  453. const endIndex = constructionFeeNode.posterityCount() + 1;
  454. sheet.getRange(0, 0, endIndex, budgetSummaryTreeSetting.cols.length, GC.Spread.Sheets.SheetArea.viewport).locked(true);
  455. // 除第一行,章编号 节编号可编辑
  456. if (curBudgetType === BudgetType.RAIL) {
  457. const chapterCodeCol = budgetSummaryTreeSetting.cols.findIndex(item => item.data.field === 'chapterCode');
  458. const sectionCodeCol = budgetSummaryTreeSetting.cols.findIndex(item => item.data.field === 'sectionCode');
  459. for (let row = 1; row < endIndex; row++) {
  460. if (chapterCodeCol > -1) {
  461. sheet.getCell(row, chapterCodeCol).locked(false);
  462. }
  463. if (sectionCodeCol > -1) {
  464. sheet.getCell(row, sectionCodeCol).locked(false);
  465. }
  466. }
  467. }
  468. return endIndex; */
  469. }
  470. if (isMass) {
  471. TREE_SHEET_HELPER.massOperationSheet(sheet, () => {
  472. return lock();
  473. });
  474. } else {
  475. return lock();
  476. }
  477. }
  478. /* 单元格类型 */
  479. const calcBaseButtonCallback = (hitInfo) => {
  480. calcBaseView.onCalcBaseButtonClick(hitInfo, 'budget');
  481. };
  482. const cellTypeFactory = {
  483. calcBase(node) {
  484. const locked = lockFactory.calcBase(node);
  485. return sheetCommonObj.getCusButtonCellType(calcBaseButtonCallback, locked);
  486. }
  487. }
  488. /* 设置可编辑区域节点的只读性和单元格类型 */
  489. const setCells = (sheet, startRow, nodes) => {
  490. for (let row = startRow; row < nodes.length; row++) {
  491. const node = nodes[row];
  492. budgetSummaryTreeSetting.cols.forEach((item, col) => {
  493. const field = item.data.field;
  494. // 锁定单元格
  495. const lockFunc = lockFactory[field];
  496. if (lockFunc) {
  497. const isLocked = lockFunc(node);
  498. sheet.getCell(row, col).locked(isLocked);
  499. }
  500. // 设置单元格类型
  501. const cellTypeFunc = cellTypeFactory[field];
  502. if (cellTypeFunc) {
  503. const cellType = cellTypeFunc(node);
  504. sheet.getCell(row, col).cellType(cellType);
  505. }
  506. // 单元格文本转换
  507. const textFunc = textFactory[field];
  508. if (textFunc) {
  509. sheet.setValue(row, col, textFunc(node));
  510. }
  511. });
  512. }
  513. }
  514. /* 初始化表格 */
  515. const initSpread = () => {
  516. if (!spread) {
  517. // spread = sheetCommonObj.createSpread($('#budget-summary-sheet')[0], 1);
  518. spread = SheetDataHelper.createNewSpread($('#budget-summary-sheet')[0]);
  519. sheetCommonObj.spreadDefaultStyle(spread);
  520. // 设置表头
  521. const sheet = spread.getSheet(0);
  522. bindEvents(sheet);
  523. const headers = sheetCommonObj.getHeadersFromTreeSetting(budgetSummaryTreeSetting);
  524. sheetCommonObj.setHeader(sheet, headers);
  525. // 右键菜单
  526. initContextMenu();
  527. } else {
  528. spread.refresh();
  529. }
  530. return spread;
  531. }
  532. // 初始化树
  533. const initTree = (data, sheet, setting) => {
  534. tree = idTree.createNew({ id: 'ID', pid: 'ParentID', nid: 'NextSiblingID', rootId: -1, autoUpdate: true });
  535. const controller = TREE_SHEET_CONTROLLER.createNew(tree, sheet, setting, false, true);
  536. tree.loadDatas(data);
  537. tree.items.forEach(node => {
  538. node.source = node;
  539. node.sourceType = ModuleNames.bills;
  540. });
  541. controller.showTreeData();
  542. sheet.setRowCount(data.length);
  543. setUnitCombo(sheet, data);
  544. TREE_SHEET_HELPER.massOperationSheet(sheet, () => {
  545. const startRow = lockData(sheet, tree.items, false);
  546. setCells(sheet, 0, tree.items);
  547. // 表格格式化
  548. budgetSummaryTreeSetting.cols.forEach((item, index) => {
  549. sheet.setFormatter(-1, index, item.formatter || '@', GC.Spread.Sheets.SheetArea.viewport);
  550. });
  551. });
  552. selectCell(sheet.getActiveRowIndex(), sheet.getActiveColumnIndex());
  553. }
  554. /* 右键菜单 */
  555. // 更新树结构数据
  556. const updateTree = (sheet, updateData) => {
  557. // 更新数据
  558. updateData.forEach(item => {
  559. if (item.type === 'new') {
  560. orgMap[item.data.ID] = _.cloneDeep(item.data);
  561. rawData.push(item.data)
  562. } else if (item.type === 'update') {
  563. if (orgMap[item.data.ID]) {
  564. Object.assign(orgMap[item.data.ID], item.data);
  565. }
  566. const node = tree.findNode(item.data.ID);
  567. if (node) {
  568. Object.assign(node.data, item.data);
  569. }
  570. } else {
  571. delete orgMap[item.data.ID];
  572. const removeIndex = rawData.findIndex(d => d.ID === item.data.ID);
  573. if (removeIndex > -1) {
  574. rawData.splice(removeIndex, 1);
  575. }
  576. }
  577. });
  578. // 重新初始化树
  579. initTree(rawData, sheet, budgetSummaryTreeSetting);
  580. }
  581. let loading = false;
  582. // 插入
  583. const insert = async (sheet, selected) => {
  584. try {
  585. if (loading) {
  586. return;
  587. }
  588. loading = true;
  589. $.bootstrapLoading.start();
  590. const updateData = tree.getInsertData(selected.data.ParentID, selected.data.NextSiblingID, uuid.v1());
  591. const newData = updateData.filter(item => item.type === 'new');
  592. newData.forEach(item => {
  593. item.data.fees = [];
  594. item.data.flags = [];
  595. item.feesIndex = {};
  596. item.flagsIndex = {};
  597. item.data.type = selected.data.type;
  598. item.data.projectID = projectObj.project.property.rootProjectID;
  599. });
  600. await bulkOperation(updateData);
  601. updateTree(sheet, updateData);
  602. selectCell(sheet.getActiveRowIndex() + selected.posterityCount() + 1, sheet.getActiveColumnIndex(), true);
  603. } catch (err) {
  604. alert(err);
  605. } finally {
  606. $.bootstrapLoading.end();
  607. loading = false;
  608. }
  609. }
  610. // 删除
  611. const remove = async (sheet, selected) => {
  612. try {
  613. if (loading) {
  614. return;
  615. }
  616. loading = true;
  617. $.bootstrapLoading.start();
  618. const updateData = tree.getDeleteData(selected);
  619. await bulkOperation(updateData);
  620. updateTree(sheet, updateData);
  621. } catch (err) {
  622. alert(err);
  623. } finally {
  624. $.bootstrapLoading.end();
  625. loading = false;
  626. }
  627. }
  628. // 升级
  629. const upLevel = async (selected) => {
  630. if (!spread || !tree) {
  631. return;
  632. }
  633. const sheet = spread.getSheet(0);
  634. selected = selected ? selected : tree.selected;
  635. try {
  636. if (loading || upLevelDisabled) {
  637. return;
  638. }
  639. loading = true;
  640. $.bootstrapLoading.start();
  641. const updateData = selected.getUpLevelData();
  642. await bulkOperation(updateData);
  643. updateTree(sheet, updateData);
  644. } catch (err) {
  645. alert(err);
  646. } finally {
  647. $.bootstrapLoading.end();
  648. loading = false;
  649. }
  650. }
  651. // 降级
  652. const downLevel = async (selected) => {
  653. if (!spread || !tree) {
  654. return;
  655. }
  656. const sheet = spread.getSheet(0);
  657. selected = selected ? selected : tree.selected;
  658. try {
  659. if (loading || downLevelDisabled) {
  660. return;
  661. }
  662. loading = true;
  663. $.bootstrapLoading.start();
  664. const updateData = selected.getDownLevelData();
  665. await bulkOperation(updateData);
  666. updateTree(sheet, updateData);
  667. } catch (err) {
  668. alert(err);
  669. } finally {
  670. $.bootstrapLoading.end();
  671. loading = false;
  672. }
  673. }
  674. // 上移
  675. const upMove = async (selected) => {
  676. if (!spread || !tree) {
  677. return;
  678. }
  679. const sheet = spread.getSheet(0);
  680. selected = selected ? selected : tree.selected;
  681. try {
  682. if (loading || upMoveDisabled) {
  683. return;
  684. }
  685. loading = true;
  686. $.bootstrapLoading.start();
  687. const updateData = selected.getUpMoveData();
  688. await bulkOperation(updateData);
  689. updateTree(sheet, updateData);
  690. const prev = selected.preSibling;
  691. const row = sheet.getActiveRowIndex() - prev.posterityCount() - 1;
  692. selectCell(row, sheet.getActiveColumnIndex(), true);
  693. sheet.showRow(row, GC.Spread.Sheets.VerticalPosition.center);
  694. } catch (err) {
  695. alert(err);
  696. } finally {
  697. $.bootstrapLoading.end();
  698. loading = false;
  699. }
  700. }
  701. // 下移
  702. const downMove = async (selected) => {
  703. if (!spread || !tree) {
  704. return;
  705. }
  706. const sheet = spread.getSheet(0);
  707. selected = selected ? selected : tree.selected;
  708. try {
  709. if (loading || downMoveDisabled) {
  710. return;
  711. }
  712. loading = true;
  713. $.bootstrapLoading.start();
  714. const updateData = selected.getDownMoveData();
  715. await bulkOperation(updateData);
  716. updateTree(sheet, updateData);
  717. const next = selected.nextSibling;
  718. const row = sheet.getActiveRowIndex() + next.posterityCount() + 1;
  719. selectCell(row, sheet.getActiveColumnIndex(), true);
  720. sheet.showRow(row, GC.Spread.Sheets.VerticalPosition.center);
  721. } catch (err) {
  722. alert(err);
  723. } finally {
  724. $.bootstrapLoading.end();
  725. loading = false;
  726. }
  727. }
  728. // 初始化右键菜单
  729. const initContextMenu = () => {
  730. if (!spread) {
  731. return;
  732. }
  733. let curRow;
  734. let curNode;
  735. const sheet = spread.getSheet(0);
  736. $.contextMenu({
  737. selector: '#budget-summary-sheet',
  738. build: function ($trigger, e) {
  739. const target = SheetDataHelper.safeRightClickSelection($trigger, e, spread);
  740. curRow = target.row;
  741. curNode = tree && tree.items[curRow] || null;
  742. selectCell(target.row, target.col, true);
  743. return target.hitTestType === GC.Spread.Sheets.SheetArea.viewport || target.hitTestType === GC.Spread.Sheets.SheetArea.rowHeader;
  744. },
  745. items: {
  746. insert: {
  747. name: '插入行',
  748. icon: 'fa-sign-in',
  749. disabled() {
  750. return !curNode || (curNode.data.area === BudgetArea.CONSTRUCTION_FEE && curNode.getFlag() !== fixedFlag.CONSTRUCTION_FEE);
  751. },
  752. callback() {
  753. insert(sheet, curNode);
  754. }
  755. },
  756. remove: {
  757. name: '删除行',
  758. icon: 'fa-remove',
  759. disabled() {
  760. return !curNode || isConstructionFeeArea(curNode) || curNode.getFlag();
  761. },
  762. callback() {
  763. remove(sheet, curNode);
  764. }
  765. },
  766. refresh: {
  767. name: '刷新数据',
  768. icon: 'fa-refresh',
  769. callback() {
  770. init(projectObj.project.property.rootProjectID);
  771. }
  772. },
  773. }
  774. });
  775. }
  776. // 初始化
  777. const init = async (constructionID) => {
  778. try {
  779. $.bootstrapLoading.start();
  780. // 得先计算费用汇总(概算汇总计算基于费用汇总算出来的总金额)
  781. await projectObj.project.calcProgram.getGatherFeeData();
  782. const { budgetType, treeData, costGrowthRate, growthPeriod } = await ajaxPost('/bills/initialBudgetSummary', { constructionID });
  783. budgetSummaryTreeSetting = budgetType === BudgetType.BUILDING ? budgetInstallationSetting : budgetRailSetting;
  784. curBudgetType = budgetType;
  785. calcSetting.costGrowthRate = costGrowthRate;
  786. calcSetting.growthPeriod = growthPeriod;
  787. $('#costGrowthRate').val(costGrowthRate);
  788. $('#growthPeriod').val(growthPeriod);
  789. rawData = treeData;
  790. rawData.forEach((item) => {
  791. if (item.quantity) {
  792. item.quantity = parseFloat(item.quantity);
  793. }
  794. item.feesIndex = getFeeIndex(item.fees);
  795. item.flagsIndex = {};
  796. if (item.flags) {
  797. item.flags.forEach((flag) => {
  798. item.flagsIndex[flag.fieldName] = flag;
  799. });
  800. }
  801. });
  802. const spread = initSpread();
  803. const sheet = spread.getSheet(0);
  804. rawData.forEach(item => {
  805. orgMap[item.ID] = _.cloneDeep(item);
  806. });
  807. initTree(rawData, sheet, budgetSummaryTreeSetting);
  808. calcBase.initBudget();
  809. // 造价计算
  810. await edit(sheet, [], tree.roots.slice(1));
  811. } catch (err) {
  812. console.log(err);
  813. alert(err);
  814. } finally {
  815. $.bootstrapLoading.end();
  816. }
  817. }
  818. // 点击tab,重新初始化
  819. $('#tab-budget-summary').click(function () {
  820. if (!$(this).hasClass('active')) {
  821. init(projectObj.project.property.rootProjectID);
  822. }
  823. });
  824. $('#budget-upLevel').click(() => {
  825. upLevel();
  826. });
  827. $('#budget-downLevel').click(() => {
  828. downLevel();
  829. });
  830. $('#budget-upMove').click(() => {
  831. upMove();
  832. });
  833. $('#budget-downMove').click(() => {
  834. downMove();
  835. });
  836. /* 建设项目设置 */
  837. $('#openConstructionSet').click(() => {
  838. $('#constructionSet').modal('show');
  839. });
  840. function isKeyNumber(keyCode) {
  841. // 数字
  842. if (keyCode >= 48 && keyCode <= 57) {
  843. return true;
  844. } else if (keyCode >= 96 && keyCode <= 105) { //小键盘数字
  845. return true;
  846. } else if (keyCode == 8 || keyCode == 46 || keyCode == 37 || keyCode == 39 || keyCode == 108 || keyCode == 110) { // Backspace, del, 左右方向键
  847. return true;
  848. } else if (keyCode >= 112 && keyCode <= 123) { //F1 -F12
  849. return true;
  850. }
  851. return false;
  852. }
  853. //年造价增涨率
  854. $('#costGrowthRate').keydown(function (e) {
  855. return isKeyNumber(e.keyCode);
  856. });
  857. //计费年限
  858. $("#growthPeriod").keydown(function (e) {
  859. return isKeyNumber(e.keyCode);
  860. });
  861. // 确认设置
  862. $('#construction-set-ok').click(async () => {
  863. try {
  864. $.bootstrapLoading.start();
  865. const curCostGrowthRate = $('#costGrowthRate').val();
  866. const curGrowthPeriod = $('#growthPeriod').val();
  867. const bulkData = [
  868. {
  869. type: 'updateProject',
  870. data: {
  871. ID: projectObj.project.property.rootProjectID,
  872. 'property.costGrowthRate': curCostGrowthRate,
  873. 'property.growthPeriod': curGrowthPeriod,
  874. }
  875. }
  876. ];
  877. await bulkOperation(bulkData);
  878. calcSetting.costGrowthRate = curCostGrowthRate;
  879. calcSetting.growthPeriod = curGrowthPeriod;
  880. } catch (err) {
  881. alert(err);
  882. $('#costGrowthRate').val(calcSetting.costGrowthRate);
  883. $('#growthPeriod').val(calcSetting.growthPeriod);
  884. } finally {
  885. $.bootstrapLoading.end();
  886. }
  887. });
  888. // 对外暴露
  889. return {
  890. getTree: () => tree,
  891. getSheet: () => spread.getSheet(0),
  892. calcSetting,
  893. edit,
  894. };
  895. })();