From 76c9a49c3ded285857f0c252def2552469b85825 Mon Sep 17 00:00:00 2001
From: 云 <2163098428@qq.com>
Date: 星期一, 10 八月 2026 17:12:26 +0800
Subject: [PATCH] feat(erp): 新增采购发票功能并优化付款流程
---
src/views/erp/purchase/invoice/modules/purchase-order-select.vue | 4
/dev/null | 110 ------------------
src/views/erp/finance/payment/data.ts | 17 --
src/views/erp/finance/payment/modules/form.vue | 28 ++++
src/packages/constants/src/biz-erp-enum.ts | 1
src/views/erp/finance/payment/modules/item-form.vue | 175 +++++++++-------------------
6 files changed, 93 insertions(+), 242 deletions(-)
diff --git a/src/packages/constants/src/biz-erp-enum.ts b/src/packages/constants/src/biz-erp-enum.ts
index 34bab52..c23356b 100644
--- a/src/packages/constants/src/biz-erp-enum.ts
+++ b/src/packages/constants/src/biz-erp-enum.ts
@@ -2,6 +2,7 @@
PURCHASE_ORDER: 10,
PURCHASE_IN: 11,
PURCHASE_RETURN: 12,
+ PURCHASE_INVOICE: 13,
SALE_ORDER: 20,
SALE_OUT: 21,
SALE_RETURN: 22,
diff --git a/src/views/erp/finance/payment/data.ts b/src/views/erp/finance/payment/data.ts
index 85cf9ad..5e15d1e 100644
--- a/src/views/erp/finance/payment/data.ts
+++ b/src/views/erp/finance/payment/data.ts
@@ -163,6 +163,7 @@
labelField: 'name',
valueField: 'id',
},
+ help: '浠樻璐︽埛鏁版嵁鏉ヨ嚜銆愯储鍔$鐞� 鈫� 缁撶畻璐︽埛銆戣彍鍗曠淮鎶ょ殑寮�鍚姸鎬佽处鎴凤紝鏃犻�夐」鏃惰鍏堝湪缁撶畻璐︽埛椤甸潰鏂板銆�',
},
{
fieldName: 'totalPrice',
@@ -180,6 +181,7 @@
fieldName: 'discountPrice',
label: '浼樻儬閲戦',
component: 'InputNumber',
+ defaultValue: 0,
componentProps: {
class: '!w-full',
disabled: formType === 'detail',
@@ -212,15 +214,13 @@
];
}
-/** 琛ㄥ崟鐨勬槑缁嗚〃鏍煎垪 */
-export function useFormItemColumns(
- disabled: boolean,
-): VxeTableGridOptions['columns'] {
+/** 琛ㄥ崟鐨勬槑缁嗚〃鏍煎垪锛堜粯娆惧熀浜庢潵绁紝鏄庣粏鐢辨潵绁ㄨ嚜鍔ㄧ敓鎴愶紝涓嶅彲鍒犻櫎锛� */
+export function useFormItemColumns(): VxeTableGridOptions['columns'] {
return [
{ type: 'seq', title: '搴忓彿', minWidth: 50, fixed: 'left' },
{
field: 'bizNo',
- title: '閲囪喘鍗曟嵁缂栧彿',
+ title: '鏉ョエ缂栧彿',
minWidth: 200,
},
{
@@ -247,13 +247,6 @@
title: '澶囨敞',
minWidth: 150,
slots: { default: 'remark' },
- },
- {
- title: '鎿嶄綔',
- width: 50,
- fixed: 'right',
- slots: { default: 'actions' },
- visible: !disabled,
},
];
}
diff --git a/src/views/erp/finance/payment/modules/form.vue b/src/views/erp/finance/payment/modules/form.vue
index d00de4a..b7652ed 100644
--- a/src/views/erp/finance/payment/modules/form.vue
+++ b/src/views/erp/finance/payment/modules/form.vue
@@ -17,6 +17,7 @@
getFinancePayment,
updateFinancePayment,
} from '#/api/erp/finance/payment';
+import { getPurchaseInvoiceSimpleList } from '#/api/erp/purchase/invoice';
import { useFormSchema } from '../data';
import ItemForm from './item-form.vue';
@@ -68,6 +69,11 @@
if (changedFields.includes('supplierId')) {
formData.value.supplierId = values.supplierId;
}
+ // 鍏宠仈鏉ョエ鍙樺寲鏃讹紝鍔犺浇鏉ョエ淇℃伅渚涙槑缁嗚嚜鍔ㄧ敓鎴�
+ if (changedFields.includes('invoiceId')) {
+ formData.value.invoiceId = values.invoiceId;
+ handleInvoiceChange(values.invoiceId);
+ }
// 鐩殑锛氬悓姝ュ埌 item-form 缁勪欢锛岃Е鍙戞暣浣撶殑浠锋牸璁$畻
if (changedFields.includes('discountPrice')) {
formData.value.discountPrice = values.discountPrice;
@@ -80,6 +86,27 @@
}
},
});
+
+/** 鍏宠仈鏉ョエ鍙樺寲鏃讹紝鍔犺浇鏉ョエ淇℃伅锛屼緵浠樻鏄庣粏鑷姩鐢熸垚 */
+async function handleInvoiceChange(invoiceId: number | undefined) {
+ if (formType.value === 'detail' || !invoiceId) {
+ if (!invoiceId) {
+ formData.value.invoice = undefined;
+ }
+ return;
+ }
+ const supplierId = formData.value.supplierId;
+ if (!supplierId) {
+ return;
+ }
+ const invoices = await getPurchaseInvoiceSimpleList(supplierId);
+ // 璇锋眰鏈熼棿鏉ョエ宸插啀娆″垏鎹㈡椂锛屼涪寮冭繃鏈熺粨鏋滐紝閬垮厤鍥炴樉閿欎贡
+ if (formData.value.invoiceId !== invoiceId) {
+ return;
+ }
+ const invoice = invoices.find((item) => item.id === invoiceId);
+ formData.value.invoice = invoice;
+}
/** 鏇存柊浠樻椤� */
function handleUpdateItems(items: ErpFinancePaymentApi.FinancePaymentItem[]) {
@@ -198,6 +225,7 @@
:supplier-id="formData?.supplierId"
:disabled="formType === 'detail'"
:discount-price="formData?.discountPrice ?? 0"
+ :invoice="formData?.invoice"
@update:items="handleUpdateItems"
@update:total-price="handleUpdateTotalPrice"
@update:payment-price="handleUpdatePaymentPrice"
diff --git a/src/views/erp/finance/payment/modules/item-form.vue b/src/views/erp/finance/payment/modules/item-form.vue
index 3aadbe5..6843412 100644
--- a/src/views/erp/finance/payment/modules/item-form.vue
+++ b/src/views/erp/finance/payment/modules/item-form.vue
@@ -1,26 +1,30 @@
<script lang="ts" setup>
import type { ErpFinancePaymentApi } from '#/api/erp/finance/payment';
-import type { ErpPurchaseInApi } from '#/api/erp/purchase/in';
-import type { ErpPurchaseReturnApi } from '#/api/erp/purchase/return';
import { computed, nextTick, ref, watch } from 'vue';
import { ErpBizType } from '@vben/constants';
import { erpPriceInputFormatter } from '@vben/utils';
-import { Input, InputNumber, message } from 'ant-design-vue';
+import { Input, InputNumber } from 'ant-design-vue';
-import { TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { useFormItemColumns } from '../data';
-import PurchaseInSelect from './purchase-in-select.vue';
-import SaleReturnSelect from './sale-return-select.vue';
+
+interface InvoiceForItem {
+ id?: number;
+ no?: string;
+ price?: number;
+ remainingInvoicePrice?: number;
+}
interface Props {
items?: ErpFinancePaymentApi.FinancePaymentItem[];
supplierId?: number;
disabled?: boolean;
discountPrice?: number;
+ invoice?: InvoiceForItem;
}
const props = withDefaults(defineProps<Props>(), {
@@ -28,6 +32,7 @@
supplierId: undefined,
disabled: false,
discountPrice: 0,
+ invoice: undefined,
});
const emit = defineEmits([
@@ -37,6 +42,9 @@
]);
const tableData = ref<ErpFinancePaymentApi.FinancePaymentItem[]>([]); // 琛ㄦ牸鏁版嵁
+
+/** 宸叉牴鎹鏉ョエ鐢熸垚鐨勬槑缁嗭紝閬垮厤閲嶅鐢熸垚 */
+const generatedInvoiceId = ref<number | undefined>();
/** 鑾峰彇琛ㄦ牸鍚堣鏁版嵁 */
const summaries = computed(() => {
@@ -59,7 +67,7 @@
/** 琛ㄦ牸閰嶇疆 */
const [Grid, gridApi] = useVbenVxeGrid({
gridOptions: {
- columns: useFormItemColumns(props.disabled),
+ columns: useFormItemColumns(),
data: tableData.value,
minHeight: 250,
autoResize: true,
@@ -85,11 +93,44 @@
return;
}
tableData.value = [...items];
+ // 缂栬緫鍥炴樉鏃讹紝鏍囪宸叉牴鎹綋鍓嶆潵绁ㄧ敓鎴愯繃锛岄伩鍏嶉噸澶嶇敓鎴�
+ generatedInvoiceId.value = props.invoice?.id;
await nextTick(); // 鐗规畩锛氫繚璇� gridApi 宸茬粡鍒濆鍖�
await gridApi.grid.reloadData(tableData.value);
},
{
immediate: true,
+ },
+);
+
+/** 鍏宠仈鏉ョエ鍙樺寲鏃讹紝鑷姩鐢熸垚鍞竴鐨勪粯娆炬槑缁嗭紙浠樻鍩轰簬鏉ョエ锛� */
+watch(
+ () => props.invoice?.id,
+ async (newId) => {
+ if (!newId || !props.invoice) {
+ return;
+ }
+ if (generatedInvoiceId.value === newId) {
+ return;
+ }
+ generatedInvoiceId.value = newId;
+ const invoice = props.invoice;
+ const price = invoice.price ?? 0;
+ const remaining = invoice.remainingInvoicePrice ?? price;
+ tableData.value = [
+ {
+ bizId: invoice.id ?? 0,
+ bizType: ErpBizType.PURCHASE_INVOICE,
+ bizNo: invoice.no ?? '',
+ totalPrice: price,
+ paidPrice: price - remaining,
+ paymentPrice: remaining,
+ remark: '',
+ },
+ ];
+ await nextTick();
+ await gridApi.grid.reloadData(tableData.value);
+ emit('update:items', [...tableData.value]);
},
);
@@ -116,74 +157,6 @@
{ deep: true },
);
-/** 娣诲姞閲囪喘鍏ュ簱鍗� */
-const purchaseInSelectRef = ref();
-const handleOpenPurchaseIn = () => {
- if (!props.supplierId) {
- message.error('璇烽�夋嫨渚涘簲鍟�');
- return;
- }
- purchaseInSelectRef.value?.open(props.supplierId);
-};
-
-const handleAddPurchaseIn = (rows: ErpPurchaseInApi.PurchaseIn[]) => {
- rows.forEach((row) => {
- const totalPrice = row.totalPrice ?? 0;
- const paidPrice = row.paymentPrice ?? 0;
- const newItem: ErpFinancePaymentApi.FinancePaymentItem = {
- bizId: row.id ?? 0,
- bizType: ErpBizType.PURCHASE_IN,
- bizNo: row.no ?? '',
- totalPrice,
- paidPrice,
- paymentPrice: totalPrice - paidPrice,
- remark: undefined,
- };
- tableData.value.push(newItem);
- });
- emit('update:items', [...tableData.value]);
-};
-
-/** 娣诲姞閲囪喘閫�璐у崟 */
-const saleReturnSelectRef = ref();
-const handleOpenSaleReturn = () => {
- if (!props.supplierId) {
- message.error('璇烽�夋嫨渚涘簲鍟�');
- return;
- }
- saleReturnSelectRef.value?.open(props.supplierId);
-};
-
-const handleAddSaleReturn = (rows: ErpPurchaseReturnApi.PurchaseReturn[]) => {
- rows.forEach((row) => {
- const totalPrice = row.totalPrice ?? 0;
- const refundPrice = row.refundPrice ?? 0;
- const newItem: ErpFinancePaymentApi.FinancePaymentItem = {
- bizId: row.id ?? 0,
- bizType: ErpBizType.PURCHASE_RETURN,
- bizNo: row.no ?? '',
- totalPrice: -totalPrice,
- paidPrice: -refundPrice,
- paymentPrice: -totalPrice + refundPrice,
- remark: undefined,
- };
- tableData.value.push(newItem);
- });
- emit('update:items', [...tableData.value]);
-};
-
-/** 鍒犻櫎琛� */
-const handleDelete = async (row: any) => {
- const index = tableData.value.findIndex(
- (item) => item.bizId === row.bizId && item.bizType === row.bizType,
- );
- if (index !== -1) {
- tableData.value.splice(index, 1);
- }
- // 閫氱煡鐖剁粍浠舵洿鏂�
- emit('update:items', [...tableData.value]);
-};
-
/** 澶勭悊琛屾暟鎹彉鏇� */
const handleRowChange = (row: any) => {
const index = tableData.value.findIndex(
@@ -201,7 +174,7 @@
const validate = () => {
// 妫�鏌ユ槸鍚︽湁鏄庣粏
if (tableData.value.length === 0) {
- throw new Error('璇锋坊鍔犱粯娆炬槑缁�');
+ throw new Error('璇峰厛閫夋嫨鍏宠仈鏉ョエ');
}
// 妫�鏌ユ瘡琛岀殑浠樻閲戦
for (let i = 0; i < tableData.value.length; i++) {
@@ -236,21 +209,6 @@
@change="handleRowChange(row)"
/>
</template>
- <template #actions="{ row }">
- <TableAction
- :actions="[
- {
- label: '鍒犻櫎',
- type: 'link',
- danger: true,
- popConfirm: {
- title: '纭鍒犻櫎璇ヤ粯娆炬槑缁嗗悧锛�',
- confirm: handleDelete.bind(null, row),
- },
- },
- ]"
- />
- </template>
<template #bottom>
<div class="mt-2 rounded border border-border bg-muted p-2">
@@ -258,7 +216,7 @@
<span class="font-medium text-foreground">鍚堣锛�</span>
<div class="flex space-x-4">
<span>
- 鍚堣浠樻锛歿{ erpPriceInputFormatter(summaries.totalPrice) }}
+ 搴斾粯閲戦锛歿{ erpPriceInputFormatter(summaries.totalPrice) }}
</span>
<span>
宸蹭粯閲戦锛歿{ erpPriceInputFormatter(summaries.paidPrice) }}
@@ -270,34 +228,13 @@
</div>
</div>
</div>
- <TableAction
- v-if="!disabled"
- class="mt-2 flex justify-center"
- :actions="[
- {
- label: '娣诲姞閲囪喘鍏ュ簱鍗�',
- type: 'default',
- onClick: handleOpenPurchaseIn,
- },
- {
- label: '娣诲姞閲囪喘閫�璐у崟',
- type: 'default',
- onClick: handleOpenSaleReturn,
- },
- ]"
- />
+ <div
+ v-if="!invoice && !disabled"
+ class="mt-2 flex justify-center text-sm text-muted-foreground"
+ >
+ 璇峰厛鍦ㄤ笂鏂硅〃鍗曢�夋嫨鍏宠仈鏉ョエ锛岀郴缁熷皢鑷姩鐢熸垚浠樻鏄庣粏
+ </div>
</template>
</Grid>
-
- <!-- 閲囪喘鍏ュ簱鍗曢�夋嫨缁勪欢 -->
- <PurchaseInSelect
- ref="purchaseInSelectRef"
- @success="handleAddPurchaseIn"
- />
- <!-- 閲囪喘閫�璐у崟閫夋嫨缁勪欢 -->
- <SaleReturnSelect
- ref="saleReturnSelectRef"
- @success="handleAddSaleReturn"
- />
</div>
</template>
diff --git a/src/views/erp/finance/payment/modules/purchase-in-select.vue b/src/views/erp/finance/payment/modules/purchase-in-select.vue
deleted file mode 100644
index deefa82..0000000
--- a/src/views/erp/finance/payment/modules/purchase-in-select.vue
+++ /dev/null
@@ -1,106 +0,0 @@
-<script lang="ts" setup>
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { ErpPurchaseInApi } from '#/api/erp/purchase/in';
-
-import { ref } from 'vue';
-
-import { message, Modal } from 'ant-design-vue';
-
-import { useVbenVxeGrid } from '#/adapter/vxe-table';
-import { getPurchaseInPage } from '#/api/erp/purchase/in';
-
-import { usePurchaseInGridColumns, usePurchaseInGridFormSchema } from '../data';
-
-const emit = defineEmits<{
- success: [rows: ErpPurchaseInApi.PurchaseIn[]];
-}>();
-
-const supplierId = ref<number>(); // 渚涘簲鍟� ID
-const open = ref<boolean>(false); // 寮圭獥鏄惁鎵撳紑
-const selectedRows = ref<ErpPurchaseInApi.PurchaseIn[]>([]); // 閫変腑鐨勮
-
-/** 琛ㄦ牸閰嶇疆 */
-const [Grid, gridApi] = useVbenVxeGrid({
- formOptions: {
- schema: usePurchaseInGridFormSchema(),
- },
- gridOptions: {
- columns: usePurchaseInGridColumns(),
- height: 520,
- keepSource: true,
- proxyConfig: {
- ajax: {
- query: async ({ page }, formValues) => {
- return await getPurchaseInPage({
- pageNo: page.currentPage,
- pageSize: page.pageSize,
- supplierId: supplierId.value,
- paymentEnable: true, // 鍙煡璇㈠彲浠樻鐨�
- ...formValues,
- });
- },
- },
- },
- rowConfig: {
- keyField: 'id',
- isHover: true,
- },
- checkboxConfig: {
- highlight: true,
- range: true,
- },
- toolbarConfig: {
- refresh: true,
- search: true,
- },
- } as VxeTableGridOptions<ErpPurchaseInApi.PurchaseIn>,
- gridEvents: {
- checkboxChange: ({
- records,
- }: {
- records: ErpPurchaseInApi.PurchaseIn[];
- }) => {
- selectedRows.value = records;
- },
- checkboxAll: ({ records }: { records: ErpPurchaseInApi.PurchaseIn[] }) => {
- selectedRows.value = records;
- },
- },
-});
-
-/** 鎵撳紑寮圭獥 */
-function openModal(id: number) {
- // 閲嶇疆鏁版嵁
- supplierId.value = id;
- open.value = true;
- selectedRows.value = [];
- // 鏌ヨ鍒楄〃
- gridApi.formApi?.resetForm();
- gridApi.formApi?.setValues({ supplierId: id });
- gridApi.query();
-}
-
-/** 纭閫夋嫨閲囪喘鍏ュ簱鍗� */
-function handleOk() {
- if (selectedRows.value.length === 0) {
- message.warning('璇烽�夋嫨瑕佹坊鍔犵殑閲囪喘鍏ュ簱鍗�');
- return;
- }
- emit('success', selectedRows.value);
- open.value = false;
-}
-
-defineExpose({ open: openModal });
-</script>
-
-<template>
- <Modal
- v-model:open="open"
- title="閫夋嫨閲囪喘鍏ュ簱鍗�"
- width="80%"
- @cancel.stop="open = false"
- @ok.stop="handleOk"
- >
- <Grid table-title="閲囪喘鍏ュ簱鍗曞垪琛�(浠呭睍绀哄彲浠樻鐨勫崟鎹�)" />
- </Modal>
-</template>
diff --git a/src/views/erp/finance/payment/modules/sale-return-select.vue b/src/views/erp/finance/payment/modules/sale-return-select.vue
deleted file mode 100644
index 71534b3..0000000
--- a/src/views/erp/finance/payment/modules/sale-return-select.vue
+++ /dev/null
@@ -1,110 +0,0 @@
-<script lang="ts" setup>
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { ErpPurchaseReturnApi } from '#/api/erp/purchase/return';
-
-import { ref } from 'vue';
-
-import { message, Modal } from 'ant-design-vue';
-
-import { useVbenVxeGrid } from '#/adapter/vxe-table';
-import { getPurchaseReturnPage } from '#/api/erp/purchase/return';
-
-import { useSaleReturnGridColumns, useSaleReturnGridFormSchema } from '../data';
-
-const emit = defineEmits<{
- success: [rows: ErpPurchaseReturnApi.PurchaseReturn[]];
-}>();
-
-const supplierId = ref<number>(); // 渚涘簲鍟� ID
-const open = ref<boolean>(false); // 寮圭獥鏄惁鎵撳紑
-const selectedRows = ref<ErpPurchaseReturnApi.PurchaseReturn[]>([]); // 閫変腑鐨勮
-
-/** 琛ㄦ牸閰嶇疆 */
-const [Grid, gridApi] = useVbenVxeGrid({
- formOptions: {
- schema: useSaleReturnGridFormSchema(),
- },
- gridOptions: {
- columns: useSaleReturnGridColumns(),
- height: 520,
- keepSource: true,
- proxyConfig: {
- ajax: {
- query: async ({ page }, formValues) => {
- return await getPurchaseReturnPage({
- pageNo: page.currentPage,
- pageSize: page.pageSize,
- supplierId: supplierId.value,
- refundEnable: true, // 鍙煡璇㈠彲閫�娆剧殑
- ...formValues,
- });
- },
- },
- },
- rowConfig: {
- keyField: 'id',
- isHover: true,
- },
- checkboxConfig: {
- highlight: true,
- range: true,
- },
- toolbarConfig: {
- refresh: true,
- search: true,
- },
- } as VxeTableGridOptions<ErpPurchaseReturnApi.PurchaseReturn>,
- gridEvents: {
- checkboxChange: ({
- records,
- }: {
- records: ErpPurchaseReturnApi.PurchaseReturn[];
- }) => {
- selectedRows.value = records;
- },
- checkboxAll: ({
- records,
- }: {
- records: ErpPurchaseReturnApi.PurchaseReturn[];
- }) => {
- selectedRows.value = records;
- },
- },
-});
-
-/** 鎵撳紑寮圭獥 */
-function openModal(id: number) {
- // 閲嶇疆鏁版嵁
- supplierId.value = id;
- open.value = true;
- selectedRows.value = [];
- // 鏌ヨ鍒楄〃
- gridApi.formApi?.resetForm();
- gridApi.formApi?.setValues({ supplierId: id });
- gridApi.query();
-}
-
-/** 纭閫夋嫨 */
-function handleOk() {
- if (selectedRows.value.length === 0) {
- message.warning('璇烽�夋嫨瑕佹坊鍔犵殑閲囪喘閫�璐у崟');
- return;
- }
- emit('success', selectedRows.value);
- open.value = false;
-}
-
-defineExpose({ open: openModal });
-</script>
-
-<template>
- <Modal
- v-model:open="open"
- title="閫夋嫨閲囪喘閫�璐у崟"
- width="80%"
- @cancel.stop="open = false"
- @ok.stop="handleOk"
- >
- <Grid table-title="閲囪喘閫�璐у崟鍒楄〃(浠呭睍绀洪渶閫�娆剧殑鍗曟嵁)" />
- </Modal>
-</template>
diff --git a/src/views/erp/purchase/invoice/modules/purchase-order-select.vue b/src/views/erp/purchase/invoice/modules/purchase-order-select.vue
index 1c6ca0b..a3e1109 100644
--- a/src/views/erp/purchase/invoice/modules/purchase-order-select.vue
+++ b/src/views/erp/purchase/invoice/modules/purchase-order-select.vue
@@ -7,7 +7,7 @@
import { DICT_TYPE } from '@vben/constants';
import { IconifyIcon } from '@vben/icons';
-import { Input, Modal } from 'ant-design-vue';
+import { Input, message, Modal } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import {
@@ -76,6 +76,7 @@
},
gridOptions: {
columns: [
+ { type: 'radio', width: 50, fixed: 'left' },
{ field: 'no', title: '璁㈠崟鍗曞彿', width: 200, fixed: 'left' },
{ field: 'supplierName', title: '渚涘簲鍟�', minWidth: 120 },
{
@@ -147,6 +148,7 @@
/** 纭閫夋嫨閲囪喘璁㈠崟 */
function handleOk() {
if (!order.value?.id) {
+ message.warning('璇烽�夋嫨閲囪喘璁㈠崟');
return;
}
emit('update:modelValue', order.value.id);
--
Gitblit v1.9.3