budgetSummarySheet.js 27 KB

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