payablesInfo.vue 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479
  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="总对账预付款确收金额">
  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:nowConfirmedPrepayAmt="scope">
  85. <el-form-item
  86. :prop="'payablesList.' + scope.$index + '.nowConfirmedPrepayAmt'"
  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.nowConfirmedPrepayAmt"
  95. type="number"
  96. :disabled="isView"
  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. },
  149. data() {
  150. return {
  151. cacheKeyUrl: 'eom-202608181000-purchase-payables-table',
  152. paymentTypeOp,
  153. transactionMethodsOp,
  154. paymentStatusOp: paymentStatus,
  155. rules: {},
  156. // 本地维护的应付明细,避免每次编辑都同步回父组件
  157. localList: [],
  158. columns: [
  159. {
  160. width: 60,
  161. label: '序号',
  162. type: 'index',
  163. columnKey: 'index',
  164. align: 'center',
  165. fixed: 'left'
  166. },
  167. {
  168. minWidth: 140,
  169. prop: 'purchaseCode',
  170. label: '采购订单编码',
  171. align: 'center',
  172. showOverflowTooltip: true
  173. },
  174. {
  175. minWidth: 140,
  176. prop: 'advanceCode',
  177. label: '预付编码',
  178. align: 'center',
  179. showOverflowTooltip: true
  180. },
  181. {
  182. prop: 'fundType',
  183. minWidth: 140,
  184. label: '款项类型',
  185. align: 'center',
  186. slot: 'fundType',
  187. headerSlot: 'columnRequired'
  188. },
  189. {
  190. prop: 'transactionMode',
  191. minWidth: 140,
  192. label: '交易方式',
  193. align: 'center',
  194. slot: 'transactionMode',
  195. headerSlot: 'columnRequired'
  196. },
  197. {
  198. prop: 'paymentConditionName',
  199. minWidth: 160,
  200. label: '付款条件',
  201. align: 'center',
  202. slot: 'paymentConditionName'
  203. },
  204. {
  205. prop: 'prepayTotalAmt',
  206. minWidth: 160,
  207. label: '预付合计金额',
  208. align: 'center',
  209. },
  210. {
  211. prop: 'confirmedPrepayAmt',
  212. minWidth: 160,
  213. label: '累计预付确付金额',
  214. align: 'center',
  215. },
  216. {
  217. prop: 'unpaidPrepayAmt',
  218. minWidth: 160,
  219. label: '未付预付余额',
  220. align: 'center',
  221. },
  222. // {
  223. // minWidth: 170,
  224. // prop: 'unreconciledAdvanceConfirmAmount',
  225. // label: '未对账预付款确收金额',
  226. // align: 'center',
  227. // showOverflowTooltip: true
  228. // },
  229. // {
  230. // minWidth: 190,
  231. // prop: 'nowConfirmedPrepayAmt',
  232. // label: '本次对账预付款确收金额',
  233. // align: 'center',
  234. // // slot: 'nowConfirmedPrepayAmt'
  235. // },
  236. {
  237. minWidth: 190,
  238. headerSlot: 'columnRequired',
  239. prop: 'nowConfirmedPrepayAmt',
  240. label: '本次对账预付款确收金额',
  241. align: 'center',
  242. slot: 'nowConfirmedPrepayAmt'
  243. },
  244. {
  245. minWidth: 160,
  246. prop: 'lastPayableAmt',
  247. label: '上期累计应付金额',
  248. align: 'center'
  249. },
  250. {
  251. minWidth: 120,
  252. prop: 'statementAmount',
  253. label: '本次对账金额',
  254. align: 'center',
  255. showOverflowTooltip: true
  256. },
  257. {
  258. minWidth: 120,
  259. prop: 'payablePrice',
  260. label: '本次应付金额',
  261. align: 'center',
  262. showOverflowTooltip: true
  263. },
  264. {
  265. minWidth: 160,
  266. prop: 'payableAmt',
  267. label: '累计应付金额',
  268. align: 'center',
  269. showOverflowTooltip: true
  270. }
  271. ]
  272. };
  273. },
  274. computed: {
  275. isView() {
  276. return this.dialogType === 'view';
  277. },
  278. statementAdvanceTotalPrice() {
  279. return (
  280. this.localList.reduce(
  281. (pre, cur) =>
  282. pre + Math.round((+cur.nowConfirmedPrepayAmt || 0) * 100),
  283. 0
  284. ) / 100
  285. );
  286. },
  287. receivedTotalPrice() {
  288. return (
  289. Math.round(
  290. ((+this.dataForm.amountTotalPrice || 0) -
  291. this.statementAdvanceTotalPrice) *
  292. 100
  293. ) / 100
  294. );
  295. }
  296. },
  297. watch: {
  298. // 父传入初始值(查询/编辑回填)时同步到本地副本
  299. payablesList: {
  300. handler(val) {
  301. this.localList = (val || []).map((item) => ({ ...item }));
  302. this.recalcAllRows();
  303. },
  304. immediate: true
  305. },
  306. 'dataForm.amountTotalPrice': {
  307. handler() {
  308. this.recalcAllRows();
  309. },
  310. immediate: true
  311. }
  312. },
  313. methods: {
  314. openPaymentTerms(index) {
  315. this.currentRowIndex = index;
  316. this.$refs.receiveTermsDialogRef.open();
  317. },
  318. changeReceiveTerms(row, index) {
  319. const targetIndex = index != null ? index : this.currentRowIndex;
  320. if (targetIndex == null) return;
  321. this.updateRowField(targetIndex, 'collectionConditionId', row.id);
  322. this.updateRowField(targetIndex, 'collectionConditionName', row.name);
  323. },
  324. // 行内字段修改:仅更新本地副本
  325. // 注意:必须用 $set 直接修改原对象属性,保持引用不变,
  326. // 否则 ele-pro-table 内部缓存的 tableData 仍引用旧对象,导致表格不更新
  327. updateRowField(index, key, val) {
  328. const item = this.localList[index];
  329. if (!item) return;
  330. this.$set(item, key, val);
  331. },
  332. // 新增一行空白预付
  333. addRow() {
  334. this.localList.push({
  335. id: 'local_' + Date.now() + '_' + this.localList.length,
  336. purchaseCode: '',
  337. advanceCode: '',
  338. fundType: '',
  339. transactionMode: '',
  340. paymentConditionId: '',
  341. paymentConditionName: '',
  342. unreconciledAdvanceConfirmAmount: 0,
  343. nowConfirmedPrepayAmt: 0,
  344. lastPayableAmt: 0,
  345. statementAmount: 0,
  346. payablePrice: 0,
  347. payableAmt: 0
  348. });
  349. },
  350. // 由父组件预付款选择器回调调用:追加一行已映射好的应付明细(本地维护)
  351. addPayableRow(row) {
  352. this.localList.push({ ...row });
  353. },
  354. removeRow(index) {
  355. if (index < 0 || index >= this.localList.length) return;
  356. this.localList.splice(index, 1);
  357. },
  358. // 本次对账预付款确收金额上限 = min(未对账预付款确收金额, 本次对账金额)
  359. getMaxConfirmAmount(row) {
  360. return Math.min(
  361. +row.unreconciledAdvanceConfirmAmount || 0,
  362. +row.statementAmount || 0
  363. );
  364. },
  365. handleConfirmAmountChange(row, index) {
  366. // 限制本次确收金额不能超过未对账金额和本次对账金额
  367. let val = +row.nowConfirmedPrepayAmt || 0;
  368. const max = this.getMaxConfirmAmount(row);
  369. if (val > max) {
  370. val = max;
  371. this.$set(row, 'nowConfirmedPrepayAmt', val);
  372. this.$message.warning(
  373. '本次对账预付款确收金额不能超过未对账金额和本次对账金额'
  374. );
  375. }
  376. this.recalcRow(row);
  377. },
  378. // 根据本次对账金额重算单行:
  379. // 确收金额 = min(未对账预付款确收金额, 本次对账金额)
  380. // 本次应付金额 = 本次对账金额 - 确收金额
  381. // 累计应付金额 = 本次对账金额 - 累计预付确付金额 + 上期累计应付金额
  382. recalcRow(row) {
  383. const statementAmount = +row.statementAmount || 0;
  384. let val = +row.nowConfirmedPrepayAmt || 0;
  385. const max = this.getMaxConfirmAmount(row);
  386. if (val > max) val = max;
  387. this.$set(row, 'nowConfirmedPrepayAmt', val);
  388. // 本次应付金额 = 本次对账金额 - 确收金额
  389. this.$set(
  390. row,
  391. 'payablePrice',
  392. Math.round((statementAmount - val) * 100) / 100
  393. );
  394. // 累计应付金额=本次对账金额-累计预付确付金额+上期累计应付金额
  395. const totalPayableAmount =
  396. statementAmount -
  397. (+row.confirmedPrepayAmt || 0) +
  398. (+row.lastPayableAmt || 0);
  399. this.$set(
  400. row,
  401. 'payableAmt',
  402. Math.round(totalPayableAmount * 100) / 100
  403. );
  404. },
  405. // 明细表按采购单号汇总本次对账金额后,同步到对应采购单号的应付行
  406. syncStatementAmount(summaryMap) {
  407. if (!summaryMap || !Object.keys(summaryMap).length) return;
  408. this.localList.forEach((row) => {
  409. const amount = summaryMap[row.purchaseCode];
  410. if (amount !== undefined) {
  411. this.$set(row, 'statementAmount', amount);
  412. this.recalcRow(row);
  413. }
  414. });
  415. },
  416. recalcAllRows() {
  417. this.localList.forEach((row) => {
  418. this.recalcRow(row);
  419. });
  420. },
  421. validateConfirmAmount(rule, value, callback, row) {
  422. const val = +value || 0;
  423. const max = this.getMaxConfirmAmount(row);
  424. if (val < 0) {
  425. callback(new Error('不能小于0'));
  426. } else if (val > max) {
  427. callback(new Error(`不能超过${max}`));
  428. } else {
  429. callback();
  430. }
  431. },
  432. // 提交时由父组件调用,一次性取回整个应付表单(本地明细 + 汇总)
  433. getSubmitData() {
  434. return {
  435. payablesList: this.localList.map((item) => ({ ...item })),
  436. statementAdvanceTotalPrice: this.statementAdvanceTotalPrice,
  437. receivedTotalPrice: this.receivedTotalPrice
  438. };
  439. },
  440. validate() {
  441. return new Promise((resolve, reject) => {
  442. const promises = [this.$refs.form.validate()];
  443. if (this.$refs.tableForm) {
  444. promises.push(this.$refs.tableForm.validate());
  445. }
  446. Promise.all(promises)
  447. .then(() => resolve(true))
  448. .catch(() => reject(false));
  449. });
  450. }
  451. }
  452. };
  453. </script>
  454. <style scoped lang="scss">
  455. .payables-info {
  456. .payables-table-header {
  457. display: flex;
  458. justify-content: flex-end;
  459. margin-bottom: 10px;
  460. }
  461. .el-form-item {
  462. margin-bottom: 0 !important;
  463. }
  464. }
  465. ::v-deep .is-required::before {
  466. content: '*';
  467. color: #f56c6c;
  468. margin-right: 4px;
  469. }
  470. </style>