budgetSummarySheet.js 30 KB

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