tableInfoNew copy.vue 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690
  1. <template>
  2. <div>
  3. <el-tabs type="border-card">
  4. <el-tab-pane :label="item.key" v-for="(item, idx) in tableForm.detailList" :key="idx">
  5. <el-form :ref="'form' + idx" :model="tableForm">
  6. <ele-pro-table
  7. ref="table"
  8. :needPage="false"
  9. :columns="columns"
  10. :datasource="item.details"
  11. row-key="id"
  12. class="time-form"
  13. >
  14. <template v-slot:toolbar>
  15. <span>本次开票合计:{{ item.amountTotalPrice }}</span>
  16. </template>
  17. <template v-slot:invoiceAmount="scope">
  18. <el-form-item
  19. style="width: 100%;"
  20. :prop="'detailList.' + idx + '.details.' + scope.$index + '.invoiceAmount'"
  21. :rules="[
  22. {
  23. required: true,
  24. message: '请输入开票金额',
  25. trigger: 'blur'
  26. },
  27. {
  28. validator: (rule, value, callback) => {
  29. if (value === undefined || value === null || value === '') {
  30. callback('请输入开票金额');
  31. } else if (parseFloat(value) < 0 || parseFloat(value) > item.amountTotalPrice) {
  32. callback('开票金额不能小于0且小于等于本次开票合计');
  33. } else {
  34. callback();
  35. }
  36. },
  37. trigger: 'blur'
  38. }
  39. ]"
  40. >
  41. <span v-if="dialogType === 'view'">{{ scope.row.invoiceAmount }}</span>
  42. <el-input v-else v-model="scope.row.invoiceAmount" type="number" @input="(val) => invoiceAmountChange(val, scope.row, idx, scope.$index)"></el-input>
  43. </el-form-item>
  44. </template>
  45. <template v-slot:taxRate="scope">
  46. <el-form-item
  47. style="width: 100%;"
  48. :prop="'detailList.' + idx + '.details.' + scope.$index + '.taxRate'"
  49. :rules="[
  50. {
  51. required: true,
  52. message: '请输入税率',
  53. trigger: 'blur'
  54. },
  55. {
  56. validator: (rule, value, callback) => {
  57. if (value === undefined || value === null || value === '') {
  58. callback('请输入税率');
  59. } else if (parseFloat(value) < 0) {
  60. callback('税率不能小于0');
  61. } else {
  62. callback();
  63. }
  64. },
  65. trigger: 'blur'
  66. }
  67. ]"
  68. >
  69. <span v-if="dialogType === 'view'">{{ scope.row.taxRate }}</span>
  70. <el-input v-else v-model="scope.row.taxRate" type="number" @input="getNotaxSinglePrice(scope.row, idx, scope.$index)" style="width: 80%;"></el-input>%
  71. </el-form-item>
  72. </template>
  73. </ele-pro-table>
  74. </el-form>
  75. </el-tab-pane>
  76. </el-tabs>
  77. </div>
  78. </template>
  79. <script>
  80. export default {
  81. name: 'tableInfo',
  82. components: {},
  83. props: {
  84. form: {
  85. type: Object,
  86. default: () => {
  87. return {
  88. productMap: [],
  89. receiptPayments: []
  90. };
  91. }
  92. },
  93. dialogType: {
  94. type: String,
  95. default: ''
  96. },
  97. isOtherSourceFlag: {
  98. type: Boolean,
  99. default: false
  100. },
  101. contactData: {
  102. type: Object,
  103. default: () => {
  104. return {};
  105. }
  106. },
  107. invoiceAmount: {
  108. type: Number,
  109. default: 0
  110. }
  111. },
  112. data() {
  113. return {
  114. columns: [
  115. {
  116. width: 45,
  117. type: 'index',
  118. columnKey: 'index',
  119. align: 'center',
  120. fixed: 'left'
  121. },
  122. {
  123. width: 100,
  124. prop: 'typeName',
  125. label: '类型',
  126. slot: 'typeName',
  127. align: 'center'
  128. },
  129. // {
  130. // minWidth: 100,
  131. // prop: 'sourceCode',
  132. // label: '类型编码',
  133. // slot: 'sourceCode',
  134. // align: 'center'
  135. // },
  136. // {
  137. // width: 100,
  138. // prop: 'productCategoryName',
  139. // label: '分类',
  140. // slot: 'productCategoryName',
  141. // align: "center"
  142. // },
  143. // {
  144. // width: 100,
  145. // prop: 'productCode',
  146. // label: '编码',
  147. // slot: 'productCode',
  148. // align: "center"
  149. // },
  150. {
  151. minWidth: 100,
  152. prop: 'productName',
  153. label: '产品名称',
  154. slot: 'productName',
  155. align: 'center'
  156. },
  157. {
  158. minWidth: 100,
  159. prop: 'modelType',
  160. label: '型号',
  161. slot: 'modelType',
  162. align: 'center'
  163. },
  164. {
  165. minWidth: 100,
  166. prop: 'specification',
  167. label: '规格',
  168. slot: 'specification',
  169. align: 'center'
  170. },
  171. // {
  172. // width: 100,
  173. // prop: 'unInvoiceAmount',
  174. // label: '未开票金额',
  175. // slot: 'unInvoiceAmount',
  176. // align: "center"
  177. // },
  178. // {
  179. // width: 80,
  180. // prop: 'singleWeight',
  181. // label: '单量',
  182. // align: 'center'
  183. // },
  184. {
  185. minWidth: 150,
  186. prop: 'invoiceAmount',
  187. label: '开票金额',
  188. slot: 'invoiceAmount',
  189. align: 'center'
  190. },
  191. {
  192. minWidth: 150,
  193. prop: 'taxRate',
  194. label: '税率',
  195. align: 'center',
  196. slot: 'taxRate',
  197. // formatter: (_row, _column, cellValue) => {
  198. // return _row.taxRate
  199. // ? _row.taxRate+'%'
  200. // : '';
  201. // },
  202. },
  203. {
  204. minWidth: 100,
  205. prop: 'noTaxSinglePrice',
  206. label: '不含税单价',
  207. slot: 'noTaxSinglePrice',
  208. align: 'center'
  209. },
  210. {
  211. width: 100,
  212. prop: 'totalCount',
  213. label: '订单数量',
  214. // slot: 'totalCount',
  215. align: 'center',
  216. },
  217. {
  218. width: 150,
  219. prop: 'quoteWay',
  220. label: '报价方式',
  221. formatter: (row, column) => {
  222. return row.quoteWay == 1
  223. ? '常规价'
  224. : row.quoteWay == 2
  225. ? '内部价'
  226. : row.quoteWay == 3 ? '议价' : '';
  227. },
  228. align: 'center'
  229. },
  230. {
  231. minWidth: 80,
  232. prop: 'singlePrice',
  233. label: '单价',
  234. slot: 'singlePrice',
  235. align: 'center'
  236. },
  237. {
  238. width: 100,
  239. prop: 'totalPrice',
  240. label: '合计',
  241. align: 'center'
  242. },
  243. {
  244. width: 100,
  245. prop: 'discountRatio',
  246. label: '折让比例',
  247. align: 'center'
  248. },
  249. {
  250. width: 100,
  251. prop: 'discountSinglePrice',
  252. label: '折让单价',
  253. align: 'center'
  254. },
  255. {
  256. width: 100,
  257. prop: 'discountAmount',
  258. label: '折让合计',
  259. align: 'center'
  260. },
  261. // {
  262. // width: 100,
  263. // prop: 'sourceType',
  264. // label: '来源类型',
  265. // slot: 'sourceType',
  266. // align: "center",
  267. // formatter: (row, column) => {
  268. // return row.sourceType == 2 ? '对账销售订单' : '对账采购订单';
  269. // }
  270. // },
  271. ],
  272. tableForm: {
  273. detailList: [
  274. {
  275. key: '',
  276. amountTotalPrice: 0,
  277. details: [{
  278. invoiceAmount: 0,
  279. taxRate: 0,
  280. noTaxSinglePrice: 0,
  281. totalCount: 0,
  282. quoteWay: '',
  283. singlePrice: 0,
  284. totalPrice: 0,
  285. discountRatio: 0,
  286. discountSinglePrice: 0,
  287. discountAmount: 0,
  288. }]
  289. }
  290. ],
  291. productMap: [],
  292. link: []
  293. },
  294. typeList: [
  295. {
  296. label: '销售发货',
  297. value: '10'
  298. },
  299. {
  300. label: '销售退货',
  301. value: '11'
  302. },
  303. {
  304. label: '采购收货',
  305. value: '20'
  306. },
  307. {
  308. label: '采购退货',
  309. value: '21'
  310. }
  311. ],
  312. };
  313. },
  314. mounted() {
  315. // this.tableForm = this.form;
  316. },
  317. watch: {
  318. form: {
  319. handler(val) {
  320. console.log('tableInfoNew form', val);
  321. const data = JSON.parse(JSON.stringify(val));
  322. data.detailList = data.productMap ? this.convertToArrayFormat(data) : [
  323. {
  324. key: '',
  325. amountTotalPrice: 0,
  326. details: []
  327. }
  328. ];
  329. console.log('tableInfoNew detailList', data.detailList);
  330. this.tableForm = data;
  331. },
  332. deep: true
  333. }
  334. },
  335. methods: {
  336. convertToArrayFormat(data) {
  337. return Object.keys(data.productMap).map(key => {
  338. console.log('key', key);
  339. const paymentItem = data.receiptPayments?.filter(item => item?.relatedOrderNo == key)|| [];
  340. return {
  341. key: key,
  342. amountTotalPrice: paymentItem.length ? paymentItem.reduce((acc, cur) => acc + cur.invoiceAmount, 0) : 0,
  343. defaultAmountTotalPrice: paymentItem.length ? paymentItem.reduce((acc, cur) => acc + cur.invoiceAmount, 0) : 0,
  344. details: data.productMap[key] || []
  345. };
  346. });
  347. },
  348. // 校验发票金额
  349. invoiceAmountChange(val, item, idx, index) {
  350. console.log('invoiceAmountChange', val, item, idx, index);
  351. console.log('this.tableForm.detailList', this.tableForm.detailList)
  352. if(index !=null) {
  353. let newData = this.tableForm.detailList[idx].details;
  354. console.log('newData', newData);
  355. let sum =0;
  356. newData.forEach((r) => {
  357. if (r.invoiceAmount) {
  358. sum += Number(r.invoiceAmount);
  359. }
  360. });
  361. if(val && +val > +this.tableForm.detailList[idx].details[index].discountAmount) {
  362. this.$message.error('物品开票金额不能大于折让合计');
  363. this.$set(this.tableForm.detailList[idx].details[index], 'invoiceAmount', '');
  364. return;
  365. }
  366. if(sum > this.tableForm.detailList[idx].defaultAmountTotalPrice) {
  367. this.$message.error('物品开票金额不能大于本次开票合计');
  368. this.$set(this.tableForm.detailList[idx].details[index], 'invoiceAmount', '');
  369. return;
  370. }
  371. this.$set(this.tableForm.detailList[idx].details[index], 'invoiceAmount', val);
  372. this.$set(this.tableForm.detailList[idx], 'amountTotalPrice', sum);
  373. console.log('sum', sum, this.tableForm.detailList[idx].details[index].invoiceAmount);
  374. }
  375. },
  376. //计算不含税单价
  377. getNotaxSinglePrice(item, idx, index) {
  378. if (item.singlePrice && item.taxRate) {
  379. this.$set(
  380. this.tableForm.detailList[idx].details[index],
  381. 'noTaxSinglePrice',
  382. parseFloat(
  383. (item.singlePrice / (1 + item.taxRate / 100)).toFixed(2)
  384. )
  385. );
  386. } else {
  387. this.$set(this.tableForm.detailList[idx].details[index], 'noTaxSinglePrice', '');
  388. }
  389. },
  390. //获取选择的对账单数据
  391. async getAccountData(params) {
  392. if (params.children.orderType == 6) {
  393. this.tableForm.productMap = params.children.productMap;
  394. this.tableForm.productMap.forEach((item, index) => {
  395. item.sourceCode = params.children.orderNo;
  396. item.sourceId = params.children.id;
  397. item.sourceType = params.type == 1 ? 2 : 3;
  398. item.type = 12;
  399. item.singlePrice = item.discountSinglePrice;
  400. item.totalPrice = item.discountTotalPrice;
  401. item.typeName = '销售赔付';
  402. });
  403. } else {
  404. this.tableForm.productMap = [];
  405. params.children.subList.forEach((item, index) => {
  406. item.productMap.forEach((i, n) => {
  407. i.sourceCode = item.statementSubOrderCode;
  408. i.sourceId = params.children.id;
  409. i.sourceType = params.type == 1 ? 2 : 3;
  410. i.type = item.subType;
  411. console.log(item.subType);
  412. i.typeName = this.typeList.find(
  413. (i) => i.value == item.subType
  414. ).label;
  415. // i.singlePrice = item.discountSinglePrice
  416. i.totalPrice = i.discountTotalPrice;
  417. });
  418. this.tableForm.productMap.push(...item.productMap);
  419. });
  420. this.$refs.table.reload();
  421. }
  422. this.$emit(
  423. 'setPrice',
  424. params.children.amountTotalPrice - this.invoiceAmount
  425. );
  426. let row = {
  427. id: params.id,
  428. name: params.statementNo,
  429. code: params.statementNo,
  430. linkType: params.type == 1 ? 190 : 290,
  431. linkTypeName: params.type == 1 ? '销售对账单' : '采购对账单'
  432. };
  433. this.setSelectData(row);
  434. },
  435. putValue(data) {
  436. console.log('tableInfoNew putValue', data);
  437. data.detailList = this.convertToArrayFormat(data);
  438. this.tableForm = data;
  439. },
  440. setValue(data) {
  441. let tempData = JSON.parse(JSON.stringify(data));
  442. tempData.detailList = this.convertToArrayFormat(tempData);
  443. // data.detailList = this.allocateInvoiceAmount(data.detailList);
  444. for(let item of tempData.detailList) {
  445. const total = item.amountTotalPrice;
  446. const details = item.details;
  447. const count = details.length;
  448. if (count === 0) continue;
  449. // 计算基础金额(向下取整)
  450. const baseAmount = Math.floor(total / count);
  451. // 计算余数
  452. const remainder = total - (baseAmount * count);
  453. // 为所有项设置基础金额
  454. details.forEach((detail) => {
  455. detail.invoiceAmount = baseAmount;
  456. });
  457. // 将余数加到最后一项
  458. if (remainder > 0 && details.length > 0) {
  459. details[details.length - 1].invoiceAmount += remainder;
  460. }
  461. }
  462. // this.$set(this, 'tableForm', data);
  463. this.tableForm = tempData;
  464. // console.log('tableInfoNew detailList~~~', tempData.detailList);
  465. // this.$refs.table.reload();
  466. },
  467. /**
  468. * 分配开票金额到明细项
  469. * @param {Array|Object} data - 明细列表或单个明细对象
  470. * @returns {Array|Object} 处理后的数据
  471. */
  472. allocateInvoiceAmount(data) {
  473. if (Array.isArray(data)) {
  474. // 处理数组
  475. data.forEach(item => {
  476. if (item && typeof item === 'object') {
  477. const total = Number(item.amountTotalPrice) || 0;
  478. const details = item.details || [];
  479. const count = details.length;
  480. if (count > 0) {
  481. const baseAmount = Math.floor(total / count);
  482. const remainder = total - (baseAmount * count);
  483. details.forEach((detail) => {
  484. detail.invoiceAmount = baseAmount;
  485. });
  486. if (remainder > 0) {
  487. details[count - 1].invoiceAmount += remainder;
  488. }
  489. }
  490. }
  491. });
  492. return data;
  493. } else if (data && typeof data === 'object') {
  494. // 处理单个对象
  495. const total = Number(data.amountTotalPrice) || 0;
  496. const details = data.details || [];
  497. const count = details.length;
  498. if (count > 0) {
  499. const baseAmount = Math.floor(total / count);
  500. const remainder = total - (baseAmount * count);
  501. details.forEach((detail) => {
  502. detail.invoiceAmount = baseAmount;
  503. });
  504. if (remainder > 0) {
  505. details[count - 1].invoiceAmount += remainder;
  506. }
  507. }
  508. return data;
  509. }
  510. return data;
  511. },
  512. getSingleInvoiceAmount(detailList) {
  513. for(let item of detailList) {
  514. const total = item.amountTotalPrice;
  515. const details = item.details;
  516. const count = details.length;
  517. if (count === 0) continue;
  518. // 计算基础金额(向下取整)
  519. const baseAmount = Math.floor(total / count);
  520. // 计算余数
  521. const remainder = total - (baseAmount * count);
  522. // 为所有项设置基础金额
  523. details.forEach((detail) => {
  524. detail.invoiceAmount = baseAmount;
  525. });
  526. // 将余数加到最后一项
  527. if (remainder > 0 && details.length > 0) {
  528. details[details.length - 1].invoiceAmount += remainder;
  529. }
  530. }
  531. return detailList;
  532. },
  533. setSinglePrice(row, data) {
  534. let index = this.tableForm.detailList.findIndex(item => item.key == row.relatedOrderNo);
  535. // let detail = this.tableForm.detailList[index];
  536. // console.log('tableInfoNew setPrice', row, data, index);
  537. const paymentItem = data.filter(item => item?.relatedOrderNo == row.relatedOrderNo);
  538. // console.log('tableInfoNew paymentItem', paymentItem);
  539. this.tableForm.detailList[index].amountTotalPrice = paymentItem.length ? paymentItem.reduce((acc, cur) => acc + +cur.invoiceAmount, 0) : 0;
  540. this.tableForm.detailList[index].defaultAmountTotalPrice = paymentItem.length ? paymentItem.reduce((acc, cur) => acc + +cur.invoiceAmount, 0) : 0;
  541. // console.log('tableInfoNew tableForm', this.tableForm.detailList);
  542. // detail =this.allocateInvoiceAmount(detail);
  543. this.tableForm.detailList[index] = this.allocateInvoiceAmount(this.tableForm.detailList[index]);
  544. // this.tableForm.detailList[index] = detail;
  545. this.tableForm = {...this.tableForm};
  546. this.$forceUpdate();
  547. },
  548. clearTable() {
  549. this.tableForm = {
  550. detailList: [],
  551. productMap: [],
  552. receiptPayments: [],
  553. link: []
  554. };
  555. },
  556. // 当数量变化时更新金额
  557. updateTotalPrice(row) {
  558. if (row.totalCount && row.singlePrice) {
  559. row.totalPrice = (parseFloat(row.totalCount) * parseFloat(row.singlePrice)).toFixed(2);
  560. }
  561. },
  562. getTableValidate() {
  563. return new Promise(async (resolve, reject) => {
  564. // 2. 验证每个表格表单
  565. const validationPromises = [];
  566. this.tableForm.detailList.forEach((item, index) => {
  567. validationPromises.push(
  568. new Promise((formResolve, formReject) => {
  569. // 正确获取表单实例
  570. const formInstance = this.$refs['form' + index];
  571. if (formInstance) {
  572. // 确保formInstance有validate方法
  573. if (typeof formInstance.validate === 'function') {
  574. formInstance.validate((valid, errors) => {
  575. if (!valid && errors) {
  576. // 显示第一个错误信息
  577. const firstError = Object.values(errors)[0][0];
  578. if (firstError) {
  579. this.$message.warning(firstError.message);
  580. }
  581. formReject(false);
  582. } else {
  583. formResolve(true);
  584. }
  585. });
  586. } else {
  587. // 如果没有validate方法,检查是否是数组(多个表单实例)
  588. if (Array.isArray(formInstance) && formInstance.length > 0) {
  589. const firstForm = formInstance[0];
  590. if (typeof firstForm.validate === 'function') {
  591. firstForm.validate((valid, errors) => {
  592. if (!valid && errors) {
  593. const firstError = Object.values(errors)[0][0];
  594. if (firstError) {
  595. this.$message.warning(firstError.message);
  596. }
  597. formReject(false);
  598. } else {
  599. formResolve(true);
  600. }
  601. });
  602. } else {
  603. formResolve(true); // 没有validate方法时默认通过
  604. }
  605. } else {
  606. formResolve(true); // 不是预期的表单实例时默认通过
  607. }
  608. }
  609. } else {
  610. formResolve(true); // 表单不存在时默认通过
  611. }
  612. })
  613. );
  614. });
  615. console.log('validationPromises~~~',validationPromises);
  616. // 3. 处理验证结果
  617. try {
  618. await Promise.all(validationPromises);
  619. resolve(this.tableForm);
  620. console.log('this.tableForm~~~',this.tableForm);
  621. } catch (error) {
  622. reject(false);
  623. }
  624. });
  625. },
  626. validateForm(callback) {
  627. //开始表单校验
  628. this.$refs.form.validate((valid, obj) => {
  629. if (obj) {
  630. let messages = Object.keys(obj).map((key) => obj[key][0]);
  631. if (messages.length > 0) {
  632. this.$message.warning(messages[0].message);
  633. }
  634. }
  635. callback(valid);
  636. });
  637. },
  638. setSelectData(val) {
  639. this.tableForm.link = [{}];
  640. this.$set(this.tableForm.link[0], 'linkId', val.id);
  641. this.$set(this.tableForm.link[0], 'linkName', val.name);
  642. this.$set(this.tableForm.link[0], 'linkCode', val.code);
  643. this.$set(this.tableForm.link[0], 'linkType', val.linkType);
  644. this.$set(this.tableForm.link[0], 'linkTypeName', val.linkTypeName);
  645. },
  646. getTableData() {
  647. const data = JSON.parse(JSON.stringify(this.tableForm.detailList));
  648. const transformedData = data.reduce((result, item) => {
  649. result[item.key] = item.details;
  650. return result;
  651. }, {});
  652. return transformedData;
  653. }
  654. }
  655. };
  656. </script>
  657. <style scoped lang="scss">
  658. .time-form .el-form-item {
  659. margin-bottom: 0 !important;
  660. }
  661. </style>