payablesInfo.vue 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489
  1. <template>
  2. <div class="payables-info">
  3. <el-form ref="form" :model="dataForm" :rules="rules" label-width="170px">
  4. <el-row>
  5. <el-col :span="8">
  6. <el-form-item :label="statementAdvanceTotalPriceTitle">
  7. <el-input disabled v-model="statementAdvanceTotalPrice">
  8. <template slot="append">元</template>
  9. </el-input>
  10. </el-form-item>
  11. </el-col>
  12. <el-col :span="8">
  13. <el-form-item label="本次对账应付总金额">
  14. <el-input disabled v-model="receivedTotalPrice">
  15. <template slot="append">元</template>
  16. </el-input>
  17. </el-form-item>
  18. </el-col>
  19. </el-row>
  20. </el-form>
  21. <el-form :model="{ payablesList: localList }" ref="tableForm">
  22. <ele-pro-table
  23. ref="payablesTable"
  24. row-key="id"
  25. :needPage="false"
  26. :columns="columns"
  27. max-height="400px"
  28. :datasource="localList"
  29. :cache-key="cacheKeyUrl"
  30. class="time-form"
  31. >
  32. <template v-slot:fundType="scope">
  33. <el-form-item
  34. :prop="'payablesList.' + scope.$index + '.fundType'"
  35. label-width="0"
  36. >
  37. <el-select
  38. v-model="scope.row.fundType"
  39. placeholder="请选择"
  40. class="ele-block"
  41. :disabled="isView"
  42. @change="(val) => updateRowField(scope.$index, 'fundType', val)"
  43. >
  44. <el-option
  45. v-for="item in paymentTypeOp"
  46. :key="item.value"
  47. :label="item.label"
  48. :value="item.value"
  49. />
  50. </el-select>
  51. </el-form-item>
  52. </template>
  53. <template v-slot:transactionMode="scope">
  54. <el-form-item
  55. :prop="'payablesList.' + scope.$index + '.transactionMode'"
  56. label-width="0"
  57. >
  58. <el-select
  59. v-model="scope.row.transactionMode"
  60. placeholder="请选择"
  61. class="ele-block"
  62. :disabled="isView"
  63. @change="(val) => updateRowField(scope.$index, 'transactionMode', val)"
  64. >
  65. <el-option
  66. v-for="item in transactionMethodsOp"
  67. :key="item.value"
  68. :label="item.label"
  69. :value="item.value"
  70. />
  71. </el-select>
  72. </el-form-item>
  73. </template>
  74. <template v-slot:paymentConditionName="scope">
  75. <el-input
  76. clearable
  77. :value="scope.row.paymentConditionName"
  78. @click.native="openPaymentTerms(scope.$index)"
  79. :placeholder="isView ? '' : '请选择'"
  80. :disabled="isView"
  81. readonly
  82. />
  83. </template>
  84. <!-- <template v-slot:confirmedPrepayAmt="scope">
  85. <el-form-item
  86. :prop="'payablesList.' + scope.$index + '.confirmedPrepayAmt'"
  87. :rules="{
  88. validator: (rule, value, cb) =>
  89. validateConfirmAmount(rule, value, cb, scope.row),
  90. trigger: 'change'
  91. }"
  92. >
  93. <el-input
  94. v-model="scope.row.confirmedPrepayAmt"
  95. type="number"
  96. :disabled="true"
  97. @input="handleConfirmAmountChange(scope.row, scope.$index)"
  98. />
  99. </el-form-item>
  100. </template> -->
  101. <template v-slot:action="scope">
  102. <el-link
  103. v-if="!isView"
  104. type="danger"
  105. :underline="false"
  106. icon="el-icon-delete"
  107. @click="removeRow(scope.$index)"
  108. >删除</el-link
  109. >
  110. </template>
  111. <template v-slot:columnRequired="{ column }">
  112. <span class="is-required">{{ column.label }}</span>
  113. </template>
  114. </ele-pro-table>
  115. </el-form>
  116. <!-- 付款条件 -->
  117. <receiveTermsDialog
  118. ref="receiveTermsDialogRef"
  119. conditionType="rules"
  120. @changeParent="changeReceiveTerms"
  121. ></receiveTermsDialog>
  122. </div>
  123. </template>
  124. <script>
  125. import {
  126. paymentTypeOp,
  127. transactionMethodsOp,
  128. paymentStatus
  129. } from '@/enum/dict';
  130. import receiveTermsDialog from '@/views/financialManage/advanceReceipt/components/receiveTermsDialog.vue';
  131. export default {
  132. name: 'payablesInfo',
  133. components: { receiveTermsDialog },
  134. props: {
  135. dataForm: {
  136. type: Object,
  137. default: () => ({})
  138. },
  139. // 父组件传入的初始应付明细,仅作为初始化值,修改不再回写父
  140. payablesList: {
  141. type: Array,
  142. default: () => []
  143. },
  144. dialogType: {
  145. type: String,
  146. default: 'add'
  147. },
  148. statementAdvanceTotalPriceTitle: {
  149. type: String,
  150. default: '总对账预付款确收金额'
  151. },
  152. columnsTitle: {
  153. type: String,
  154. default: '本次对账预付款确收金额'
  155. }
  156. },
  157. data() {
  158. return {
  159. cacheKeyUrl: 'eom-202608181000-purchase-payables-table',
  160. paymentTypeOp,
  161. transactionMethodsOp,
  162. paymentStatusOp: paymentStatus,
  163. rules: {},
  164. // 本地维护的应付明细,避免每次编辑都同步回父组件
  165. localList: [],
  166. columns: [
  167. {
  168. width: 60,
  169. label: '序号',
  170. type: 'index',
  171. columnKey: 'index',
  172. align: 'center',
  173. fixed: 'left'
  174. },
  175. {
  176. minWidth: 140,
  177. prop: 'purchaseCode',
  178. label: '采购订单编码',
  179. align: 'center',
  180. showOverflowTooltip: true
  181. },
  182. {
  183. minWidth: 140,
  184. prop: 'prepayCodes',
  185. label: '预付编码',
  186. align: 'center',
  187. showOverflowTooltip: true
  188. },
  189. {
  190. prop: 'fundType',
  191. minWidth: 140,
  192. label: '款项类型',
  193. align: 'center',
  194. slot: 'fundType',
  195. headerSlot: 'columnRequired'
  196. },
  197. {
  198. prop: 'transactionMode',
  199. minWidth: 140,
  200. label: '交易方式',
  201. align: 'center',
  202. slot: 'transactionMode',
  203. headerSlot: 'columnRequired'
  204. },
  205. {
  206. prop: 'paymentConditionName',
  207. minWidth: 160,
  208. label: '付款条件',
  209. align: 'center',
  210. slot: 'paymentConditionName'
  211. },
  212. {
  213. prop: 'prepayTotalAmt',
  214. minWidth: 160,
  215. label: '预付合计金额',
  216. align: 'center',
  217. },
  218. {
  219. prop: 'nowConfirmedPrepayAmt',
  220. minWidth: 160,
  221. label: '累计预付确付金额',
  222. align: 'center',
  223. },
  224. {
  225. prop: 'unpaidPrepayAmt',
  226. minWidth: 160,
  227. label: '未付预付金额',
  228. align: 'center',
  229. },
  230. // {
  231. // minWidth: 170,
  232. // prop: 'unreconciledAdvanceConfirmAmount',
  233. // label: '未对账预付款确收金额',
  234. // align: 'center',
  235. // showOverflowTooltip: true
  236. // },
  237. {
  238. minWidth: 190,
  239. headerSlot: 'columnRequired',
  240. prop: 'confirmedPrepayAmt',
  241. label: this.columnsTitle,
  242. align: 'center',
  243. slot: 'confirmedPrepayAmt'
  244. },
  245. {
  246. minWidth: 160,
  247. prop: 'lastPayableAmt',
  248. label: '上期累计应付金额',
  249. align: 'center'
  250. },
  251. {
  252. minWidth: 120,
  253. prop: 'statementAmount',
  254. label: '本次对账金额',
  255. align: 'center',
  256. showOverflowTooltip: true
  257. },
  258. {
  259. minWidth: 120,
  260. prop: 'statementAmt',
  261. label: '本次对账应付总金额',
  262. align: 'center',
  263. showOverflowTooltip: true
  264. },
  265. {
  266. minWidth: 160,
  267. prop: 'payableAmt',
  268. label: '累计应付金额',
  269. align: 'center',
  270. showOverflowTooltip: true
  271. }
  272. ]
  273. };
  274. },
  275. computed: {
  276. isView() {
  277. return this.dialogType === 'view';
  278. },
  279. statementAdvanceTotalPrice() {
  280. return this.dataForm?.statPayable?.statementPrepayTotalPrice || 0
  281. // return (
  282. // this.localList.reduce(
  283. // (pre, cur) =>
  284. // pre + Math.round((+cur.confirmedPrepayAmt || 0) * 100),
  285. // 0
  286. // ) / 100
  287. // );
  288. },
  289. receivedTotalPrice() {
  290. return this.dataForm?.statPayable?.payableTotalPrice || 0
  291. // return (
  292. // // Math.round(
  293. // // ((+this.dataForm.amountTotalPrice || 0) -
  294. // // this.statementAdvanceTotalPrice) *
  295. // // 100
  296. // // ) / 100
  297. // this.localList.reduce(
  298. // (pre, cur) =>
  299. // pre + Math.round((+cur.payablePrice || 0) * 100),
  300. // 0
  301. // ) / 100
  302. // );
  303. }
  304. },
  305. watch: {
  306. // 父传入初始值(查询/编辑回填)时同步到本地副本
  307. payablesList: {
  308. handler(val) {
  309. this.localList = (val || []).map((item) => ({ ...item }));
  310. this.recalcAllRows();
  311. },
  312. immediate: true
  313. },
  314. 'dataForm.amountTotalPrice': {
  315. handler() {
  316. this.recalcAllRows();
  317. },
  318. immediate: true
  319. }
  320. },
  321. methods: {
  322. openPaymentTerms(index) {
  323. this.currentRowIndex = index;
  324. this.$refs.receiveTermsDialogRef.open();
  325. },
  326. changeReceiveTerms(row, index) {
  327. const targetIndex = index != null ? index : this.currentRowIndex;
  328. if (targetIndex == null) return;
  329. this.updateRowField(targetIndex, 'collectionConditionId', row.id);
  330. this.updateRowField(targetIndex, 'collectionConditionName', row.name);
  331. },
  332. // 行内字段修改:仅更新本地副本
  333. // 注意:必须用 $set 直接修改原对象属性,保持引用不变,
  334. // 否则 ele-pro-table 内部缓存的 tableData 仍引用旧对象,导致表格不更新
  335. updateRowField(index, key, val) {
  336. const item = this.localList[index];
  337. if (!item) return;
  338. this.$set(item, key, val);
  339. },
  340. // 新增一行空白预付
  341. addRow() {
  342. this.localList.push({
  343. id: 'local_' + Date.now() + '_' + this.localList.length,
  344. purchaseCode: '',
  345. advanceCode: '',
  346. fundType: '',
  347. transactionMode: '',
  348. paymentConditionId: '',
  349. paymentConditionName: '',
  350. unreconciledAdvanceConfirmAmount: 0,
  351. confirmedPrepayAmt: 0,
  352. lastPayableAmt: 0,
  353. statementAmount: 0,
  354. payablePrice: 0,
  355. payableAmt: 0
  356. });
  357. },
  358. // 由父组件预付款选择器回调调用:追加一行已映射好的应付明细(本地维护)
  359. addPayableRow(row) {
  360. this.localList.push({ ...row });
  361. },
  362. removeRow(index) {
  363. if (index < 0 || index >= this.localList.length) return;
  364. this.localList.splice(index, 1);
  365. },
  366. // 本次对账预付款确收金额上限 = min(未对账预付款确收金额, 本次对账金额)
  367. getMaxConfirmAmount(row) {
  368. return Math.min(
  369. +row.unreconciledAdvanceConfirmAmount || 0,
  370. +row.statementAmount || 0
  371. );
  372. },
  373. handleConfirmAmountChange(row, index) {
  374. // 限制本次确收金额不能超过未对账金额和本次对账金额
  375. let val = +row.confirmedPrepayAmt || 0;
  376. const max = this.getMaxConfirmAmount(row);
  377. if (val > max) {
  378. val = max;
  379. // this.$set(row, 'confirmedPrepayAmt', val);
  380. this.$message.warning(
  381. '本次对账预付款确收金额不能超过未对账金额和本次对账金额'
  382. );
  383. }
  384. this.recalcRow(row);
  385. },
  386. // 根据本次对账金额重算单行:
  387. // 确收金额 = min(未对账预付款确收金额, 本次对账金额)
  388. // 本次应付金额 = 本次对账金额 - 确收金额
  389. // 累计应付金额 = 本次对账金额 - 累计预付确付金额 + 上期累计应付金额
  390. recalcRow(row) {
  391. const statementAmount = +row.statementAmount || 0;
  392. let val = +row.confirmedPrepayAmt || 0;
  393. const max = this.getMaxConfirmAmount(row);
  394. if (val > max) val = max;
  395. // this.$set(row, 'confirmedPrepayAmt', val);
  396. // 本次应付金额 = 本次对账金额 - 确收金额
  397. this.$set(
  398. row,
  399. 'payablePrice',
  400. Math.round((statementAmount - val) * 100) / 100
  401. );
  402. // 累计应付金额=本次对账金额-累计预付确付金额+上期累计应付金额
  403. const totalPayableAmount =
  404. statementAmount -
  405. (+row.confirmedPrepayAmt || 0) +
  406. (+row.lastPayableAmt || 0);
  407. this.$set(
  408. row,
  409. 'payableAmt',
  410. Math.round(totalPayableAmount * 100) / 100
  411. );
  412. },
  413. // 明细表按采购单号汇总本次对账金额后,同步到对应采购单号的应付行
  414. syncStatementAmount(summaryMap) {
  415. if (!summaryMap || !Object.keys(summaryMap).length) return;
  416. this.localList.forEach((row) => {
  417. const amount = summaryMap[row.purchaseCode];
  418. if (amount !== undefined) {
  419. this.$set(row, 'statementAmount', amount);
  420. this.recalcRow(row);
  421. }
  422. });
  423. },
  424. recalcAllRows() {
  425. this.localList.forEach((row) => {
  426. this.recalcRow(row);
  427. });
  428. },
  429. validateConfirmAmount(rule, value, callback, row) {
  430. return callback();
  431. const val = +value || 0;
  432. const max = this.getMaxConfirmAmount(row);
  433. if (val < 0) {
  434. callback(new Error('不能小于0'));
  435. } else if (val > max) {
  436. callback(new Error(`不能超过${max}`));
  437. } else {
  438. callback();
  439. }
  440. },
  441. // 提交时由父组件调用,一次性取回整个应付表单(本地明细 + 汇总)
  442. getSubmitData() {
  443. return {
  444. payablesList: this.localList.map((item) => ({ ...item })),
  445. statementAdvanceTotalPrice: this.statementAdvanceTotalPrice,
  446. receivedTotalPrice: this.receivedTotalPrice
  447. };
  448. },
  449. validate() {
  450. return new Promise((resolve, reject) => {
  451. const promises = [this.$refs.form.validate()];
  452. if (this.$refs.tableForm) {
  453. promises.push(this.$refs.tableForm.validate());
  454. }
  455. Promise.all(promises)
  456. .then(() => resolve(true))
  457. .catch(() => reject(false));
  458. });
  459. }
  460. }
  461. };
  462. </script>
  463. <style scoped lang="scss">
  464. .payables-info {
  465. .payables-table-header {
  466. display: flex;
  467. justify-content: flex-end;
  468. margin-bottom: 10px;
  469. }
  470. .el-form-item {
  471. margin-bottom: 0 !important;
  472. }
  473. }
  474. ::v-deep .is-required::before {
  475. content: '*';
  476. color: #f56c6c;
  477. margin-right: 4px;
  478. }
  479. </style>