importBills.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394
  1. 'use strict';
  2. /**
  3. *
  4. *
  5. * @author Zhong
  6. * @date 2018/8/2
  7. * @version
  8. */
  9. /*
  10. * 清单导入模块,前端导入excel,进行数据提取,用lz-string进行压缩上传处理
  11. * */
  12. const importBills = (function () {
  13. //单元格数据是否存在
  14. function _isDef(data) {
  15. return typeof data !== 'undefined' && data !== null && data !== '';
  16. }
  17. //去除转义字符
  18. function _deESC(data) {
  19. return _isDef(data) ? data.toString().replace(/[\r\n\s\t]/g, '') : data;
  20. }
  21. function _deNR(data) {
  22. return _isDef(data) ? data.toString().replace(/[\r\n]/g, '') : data;
  23. }
  24. //find 返回最后匹配
  25. function findLast(datas, func) {
  26. let filter = datas.filter(func);
  27. if (filter.length > 0) {
  28. return filter[filter.length - 1];
  29. }
  30. return null;
  31. }
  32. const fileType = {
  33. gcl: 0, //工程量清单
  34. qdsl: 1, //清单示例
  35. gclex: 2, // 单机版导出的工程量清单预算表,需要转换成清单示例表来处理
  36. };
  37. //获取列字段对应
  38. function getColMapping(type) {
  39. if (type === 0) { //工程量清单
  40. return { code: 0, name: 1, unit: 2, quantity: 4, unitPrice: 5 };
  41. } else { //清单示例表
  42. return { code: 0, name: 1, unit: 2, quantity: 3, unitPrice: 4 };
  43. }
  44. }
  45. function isGCLHead(dataRow, nextDataRow) {
  46. const cell = dataRow[0];
  47. const nextCell = nextDataRow && nextDataRow[0];
  48. return cell && cell.value === '工程量清单' && (!nextCell || !/建设项目名称/.test(nextCell.value)); // 兼容招清单01-1表
  49. }
  50. function isGCLExtendHead(dataRow, nextDataRow) {
  51. const cell = dataRow[0];
  52. const nextCell = nextDataRow && nextDataRow[0];
  53. if ((cell && cell.value === '工程量清单预算表') || (nextCell && /建设项目名称/.test(nextCell.value))) { // 兼容招清单01-1表
  54. return true;
  55. }
  56. }
  57. //分析文件,1、工程量清单 2、清单示例表
  58. function getFileType(sheetData) {
  59. let dataTable = sheetData.data.dataTable,
  60. // rowCount = sheetData.rowCount;
  61. rowCount = sheetData.rows.length;
  62. for (let row = 0; row < rowCount; row++) {
  63. if (isGCLHead(dataTable[row], dataTable[row + 1])) {
  64. return fileType.gcl;
  65. }
  66. if (isGCLExtendHead(dataTable[row], dataTable[row + 1])) {
  67. return fileType.gclex;
  68. }
  69. }
  70. return fileType.qdsl;
  71. }
  72. //提取工程量清单数据
  73. //层级由depth确定,表格里最顶层depth为0(表头里一清单),表格内容里数据的depth为空格数+1
  74. function extractGCLDatas(sheetData, colMapping) {
  75. let dataTable = sheetData.data.dataTable,
  76. // rowCount = sheetData.rowCount;
  77. rowCount = sheetData.rows.length;
  78. let rst = [];
  79. for (let row = 0; row < rowCount; row++) {
  80. //表格中顶层节点
  81. if (isGCLHead(dataTable[row], dataTable[row + 1])) {
  82. let rootRow = dataTable[row + 2];
  83. let name = rootRow[0].value ? _deNR(rootRow[0].value) : '';
  84. let existsRoot = findLast(rst, x => x.name === name && x.depth === 0);
  85. if (!existsRoot) {
  86. let root = {
  87. ID: uuid.v1(),
  88. NextSiblingID: -1,
  89. ParentID: -1,
  90. name: name,
  91. depth: 0,
  92. parent: null,
  93. unitPriceAnalysis: 1
  94. };
  95. let preData = findLast(rst, x => x.depth === root.depth);
  96. if (preData) {
  97. preData.NextSiblingID = root.ID;
  98. }
  99. rst.push(root);
  100. }
  101. row += 3;
  102. continue;
  103. }
  104. let code = dataTable[row][colMapping.code] ? dataTable[row][colMapping.code].value : null,
  105. name = dataTable[row][colMapping.name] ? _deNR(dataTable[row][colMapping.name].value) : null,
  106. unit = dataTable[row][colMapping.unit] ? dataTable[row][colMapping.unit].value : null,
  107. quantity = dataTable[row][colMapping.quantity] ? dataTable[row][colMapping.quantity].value : null,
  108. unitPrice = dataTable[row][colMapping.unitPrice] ? dataTable[row][colMapping.unitPrice].value : null;
  109. if (!code && !name || /合计/.test(code)) { //过滤掉同时没有编号和名称的、过滤合计行
  110. continue;
  111. }
  112. // “子目号”、“单位”、“数量”都为空,“子目名称”不为空时,应将此行清单名称合并到上一行
  113. let lastData = rst[rst.length - 1];
  114. if (!code && !unit && !quantity && name) {
  115. lastData.name += name;
  116. continue;
  117. }
  118. //表格内的数据
  119. code = String(code);
  120. let depth = getDepth(code);
  121. let data = {
  122. ID: uuid.v1(),
  123. NextSiblingID: -1,
  124. ParentID: -1,
  125. code: code,
  126. name: name,
  127. unit: unit,
  128. quantity: quantity,
  129. depth: depth,
  130. unitPriceAnalysis: 1,
  131. };
  132. if (+unitPrice && +quantity) {
  133. unitPrice = scMathUtil.roundForObj(unitPrice, decimalObj.bills.unitPrice);
  134. const totalPrice = scMathUtil.roundForObj(unitPrice * +quantity, decimalObj.bills.totalPrice);
  135. data.fees = [
  136. {
  137. fieldName: "common",
  138. tenderTotalFee: totalPrice,
  139. tenderUnitFee: unitPrice,
  140. totalFee: totalPrice,
  141. unitFee: unitPrice,
  142. }
  143. ];
  144. data.calcFlag = treeNodeCalcFlag.customUnitPrice;
  145. }
  146. //获取data的父节点链,成为兄弟节点,只能在父链里找前兄弟(不能跨父链)
  147. let parents = getParents(lastData);
  148. let preData = findLast(parents, x => x.depth === depth);
  149. if (preData) {
  150. preData.NextSiblingID = data.ID;
  151. data.ParentID = preData.ParentID;
  152. data.parent = preData.parent;
  153. } else {
  154. data.ParentID = lastData.ID;
  155. data.parent = lastData;
  156. }
  157. rst.push(data);
  158. }
  159. console.log(rst);
  160. return rst;
  161. function getDepth(code) {
  162. if (!code) {
  163. return 1;
  164. }
  165. let match = code.match(/\s/g);
  166. return match ? match.length + 1 : 1;
  167. }
  168. }
  169. function getParents(data) {
  170. let rst = [];
  171. let parent = data.parent;
  172. while (parent) {
  173. rst.push(parent);
  174. parent = parent.parent;
  175. }
  176. rst.push(data);
  177. return rst;
  178. }
  179. //获取编号前缀: 101-1 => 101 101-1-1 => 101-1
  180. function getPrefix(v) {
  181. if (!v) {
  182. return null;
  183. }
  184. let reg = /(.*)-/;
  185. let match = reg.exec(v);
  186. return match ? match[1] : null;
  187. }
  188. // 示例列映射
  189. const slColMap = { code: 0, name: 1, unit: 2, quantity: 3, unitPrice: 4 };
  190. function isValidGCLExRow(rowData) {
  191. if (rowData[0] && /编制[::]/.test(rowData[0].value)) {
  192. return false;
  193. }
  194. if (rowData[1] && /合计/.test(rowData[1].value)) {
  195. return false;
  196. }
  197. if ((!rowData[slColMap.code] || !rowData[slColMap.code].value) &&
  198. (!rowData[slColMap.name] || !rowData[slColMap.name].value) &&
  199. (!rowData[slColMap.unit] || !rowData[slColMap.unit].value) &&
  200. (!rowData[slColMap.quantity] || !rowData[slColMap.quantity].value)) {
  201. return false;
  202. }
  203. return true;
  204. }
  205. // 将“工程量清单预算表”去掉表头表尾,并转换成清单示例表。
  206. // 工程量清单预算表的格式可参考需求:BUG #3037
  207. function transformGCLExToSL(sheetData) {
  208. const rst = {
  209. data: { dataTable: [] },
  210. rowCount: 0,
  211. };
  212. const dataTable = sheetData.data.dataTable;
  213. const rowCount = sheetData.rows.length;
  214. let preRootName;
  215. for (let row = 0; row < rowCount; row++) {
  216. const rowData = dataTable[row];
  217. if (isGCLExtendHead(rowData, dataTable[row + 1])) {
  218. const rootRowdata = dataTable[row + 3];
  219. const name = rootRowdata[0].value;
  220. if (name) {
  221. const rootName = name.replace('工程量清单', '清单');
  222. if (rootName !== preRootName) {
  223. preRootName = rootName;
  224. rst.data.dataTable.push({
  225. [slColMap.name]: { value: rootName }
  226. });
  227. }
  228. }
  229. row += 4;
  230. continue;
  231. }
  232. if (isValidGCLExRow(rowData)) {
  233. const cellData = {
  234. [slColMap.code]: { value: rowData[slColMap.code] && rowData[slColMap.code].value || null },
  235. [slColMap.name]: { value: rowData[slColMap.name] && rowData[slColMap.name].value || null },
  236. [slColMap.unit]: { value: rowData[slColMap.unit] && rowData[slColMap.unit].value || null },
  237. [slColMap.quantity]: { value: rowData[slColMap.quantity] && rowData[slColMap.quantity].value || null },
  238. [slColMap.unitPrice]: { value: rowData[slColMap.unitPrice] && rowData[slColMap.unitPrice].value || null },
  239. };
  240. rst.data.dataTable.push(cellData);
  241. }
  242. }
  243. rst.rowCount = rst.data.dataTable.length;
  244. return rst;
  245. }
  246. //提取清单示例数据
  247. function extractSLDatas(sheetData) {
  248. let dataTable = sheetData.data.dataTable,
  249. rowCount = sheetData.rows.length;
  250. let rst = [];
  251. let curRoot = null;
  252. for (let row = 0; row < rowCount; row++) {
  253. let code = dataTable[row][slColMap.code] && dataTable[row][slColMap.code].value ? String(dataTable[row][slColMap.code].value).trim() : null,
  254. name = dataTable[row][slColMap.name] ? _deNR(dataTable[row][slColMap.name].value) : null,
  255. unit = dataTable[row][slColMap.unit] ? dataTable[row][slColMap.unit].value : null,
  256. quantity = dataTable[row][slColMap.quantity] ? dataTable[row][slColMap.quantity].value : null,
  257. unitPrice = dataTable[row][slColMap.unitPrice] ? dataTable[row][slColMap.unitPrice].value : null;
  258. if (!code) { //没有编号的数据,名称必须为:清单 第xx章,认为新的表根节点
  259. const reg = /清单\s+第[^章]+章/;
  260. //if (name && /清单 第\d+章/.test(name)) {
  261. if (name && reg.test(name)) {
  262. curRoot = {
  263. code: null,
  264. name: name,
  265. ID: uuid.v1(),
  266. ParentID: -1,
  267. NextSiblingID: -1,
  268. parent: null,
  269. unitPriceAnalysis: 1
  270. };
  271. rst.push(curRoot);
  272. } else {
  273. curRoot = null;
  274. }
  275. } else if (!curRoot) { //根节点为无效根节点,其下子数据全部过滤掉
  276. continue;
  277. } else {
  278. //有code且有有效表根节点
  279. let prefix = getPrefix(code);
  280. let data = {
  281. code: code,
  282. name: name,
  283. unit: unit,
  284. quantity: quantity,
  285. ID: uuid.v1(),
  286. NextSiblingID: -1,
  287. unitPriceAnalysis: 1
  288. };
  289. if (+unitPrice && +quantity) {
  290. unitPrice = scMathUtil.roundForObj(unitPrice, decimalObj.bills.unitPrice);
  291. const totalPrice = scMathUtil.roundForObj(unitPrice * +quantity, decimalObj.bills.totalPrice);
  292. data.fees = [
  293. {
  294. fieldName: "common",
  295. tenderTotalFee: totalPrice,
  296. tenderUnitFee: unitPrice,
  297. totalFee: totalPrice,
  298. unitFee: unitPrice,
  299. }
  300. ];
  301. data.calcFlag = treeNodeCalcFlag.customUnitPrice;
  302. }
  303. let lastData = rst[rst.length - 1];
  304. let parents = getParents(lastData);
  305. //某数据编号为此数据的前缀,则某数据为此数据的父节点
  306. let parentData = findLast(parents, x => prefix === x.code);
  307. if (!parentData && prefix === '') { // -x的数据,在父链上找不到编号与prefix相同的数据时,父链上-x的数据,则这两数据为兄弟节点,没有则上一行数据为其父节点
  308. let samePrefixData = findLast(parents, x => getPrefix(x.code) === prefix);
  309. parentData = samePrefixData ? samePrefixData.parent : lastData;
  310. } else if (!parentData && prefix !== '') { //不是-x的数据,在父链上找不到编号与prefix相同的数据时,表根节点为其父节点
  311. parentData = curRoot;
  312. }
  313. data.ParentID = parentData.ID;
  314. data.parent = parentData;
  315. let preData = findLast(parents, x => x.ParentID === data.ParentID);
  316. if (preData) {
  317. preData.NextSiblingID = data.ID;
  318. }
  319. rst.push(data);
  320. }
  321. }
  322. console.log(rst);
  323. return rst;
  324. }
  325. function extactDatas(sheets) {
  326. let rst = [];
  327. let curSheetType = null;
  328. for (let sheetName in sheets) {
  329. let sheetData = sheets[sheetName];
  330. if (!sheetData.data.dataTable || sheetData.index !== 0) {
  331. continue;
  332. }
  333. let sheetType = getFileType(sheetData);
  334. if (curSheetType !== null && sheetType !== curSheetType) {
  335. throw 'excel文件中存在不同格式的表格。';
  336. }
  337. curSheetType = sheetType;
  338. let colMapping = getColMapping(sheetType);
  339. let datas = [];
  340. if (sheetType === fileType.gcl) {
  341. datas = extractGCLDatas(sheetData, colMapping);
  342. } else if (sheetType === fileType.qdsl) {
  343. datas = extractSLDatas(sheetData, colMapping);
  344. } else {
  345. const slSheetData = transformGCLExToSL(sheetData);
  346. datas = extractSLDatas(slSheetData, colMapping);
  347. }
  348. rst = rst.concat(datas);
  349. }
  350. //编号去除空格 清除多余数据 设置数据
  351. for (let data of rst) {
  352. if (data.code && typeof data.code === 'string') {
  353. data.code = data.code.replace(/\s/g, '');
  354. }
  355. if (data.unit === '㎡') {
  356. data.unit = 'm2';
  357. } else if (data.unit === 'm³') {
  358. data.unit = 'm3';
  359. }
  360. data.projectID = projectObj.project.ID();
  361. data.type = billType.BILL;
  362. delete data.parent;
  363. delete data.depth;
  364. }
  365. //将表根节点的ParentID设置成第100章至700章清单的ID
  366. let fixedBill = projectObj.project.Bills.tree.roots.find(node => node.data &&
  367. node.data.flagsIndex && node.data.flagsIndex.fixed && node.data.flagsIndex.fixed.flag === fixedFlag.ONE_SEVEN_BILLS);
  368. let rootDatas = rst.filter(data => data.ParentID === -1);
  369. for (let root of rootDatas) {
  370. root.ParentID = fixedBill.data.ID;
  371. }
  372. //清单 第100章 总则清单需要加上固定ID
  373. let oneHundredBills = rootDatas.find(data => data.name && /第100章/.test(data.name));
  374. if (oneHundredBills) {
  375. oneHundredBills.flags = [{ flag: fixedFlag.ONE_HUNDRED_BILLS, fieldName: 'fixed' }];
  376. }
  377. return rst;
  378. }
  379. return { extactDatas }
  380. })();