fix(web-antdv-next): 同步 CRM 回款预填和 ERP 单据弹窗修复

pull/359/head
XuZhiqiang 2026-06-06 23:08:42 +08:00
parent 7bc60c481a
commit 4b77779e60
16 changed files with 162 additions and 97 deletions

View File

@ -57,12 +57,19 @@ export function useFormSchema(): VbenFormSchema[] {
label: '客户名称', label: '客户名称',
component: 'ApiSelect', component: 'ApiSelect',
rules: 'required', rules: 'required',
componentProps: { componentProps: (_values, form) => ({
api: getCustomerSimpleList, api: getCustomerSimpleList,
labelField: 'name', labelField: 'name',
valueField: 'id', valueField: 'id',
placeholder: '请选择客户', placeholder: '请选择客户',
}, onChange: () => {
form.setFieldValue('contractId', undefined);
form.setFieldValue('planId', undefined);
form.setFieldValue('price', undefined);
form.setFieldValue('returnTime', undefined);
form.setFieldValue('returnType', undefined);
},
}),
dependencies: { dependencies: {
triggerFields: ['id'], triggerFields: ['id'],
disabled: (values) => values.id, disabled: (values) => values.id,
@ -76,21 +83,33 @@ export function useFormSchema(): VbenFormSchema[] {
dependencies: { dependencies: {
triggerFields: ['customerId'], triggerFields: ['customerId'],
disabled: (values) => !values.customerId || values.id, disabled: (values) => !values.customerId || values.id,
async componentProps(values) { async componentProps(values, form) {
if (values.customerId) { if (!values.customerId) {
if (!values.id) {
// 特殊:只有在【新增】时,才清空合同编号
values.contractId = undefined;
}
const contracts = await getContractSimpleList(values.customerId);
return { return {
options: contracts.map((item) => ({ options: [],
label: item.name, placeholder: '请选择客户',
value: item.id, };
})),
placeholder: '请选择合同',
} as any;
} }
const contracts = await getContractSimpleList(values.customerId);
return {
options: contracts.map((item) => ({
label: item.name,
value: item.id,
})),
placeholder: '请选择合同',
onChange: (value: number) => {
form.setFieldValue('planId', undefined);
form.setFieldValue('returnTime', undefined);
form.setFieldValue('returnType', undefined);
const contract = contracts.find((item) => item.id === value);
form.setFieldValue(
'price',
contract
? contract.totalPrice - contract.totalReceivablePrice
: undefined,
);
},
} as any;
}, },
}, },
}, },
@ -101,28 +120,38 @@ export function useFormSchema(): VbenFormSchema[] {
rules: 'required', rules: 'required',
dependencies: { dependencies: {
triggerFields: ['contractId'], triggerFields: ['contractId'],
disabled: (values) => !values.contractId, disabled: (values) => !values.contractId || values.id,
async componentProps(values) { async componentProps(values, form) {
if (values.contractId) { if (!values.contractId) {
values.planId = undefined;
const plans = await getReceivablePlanSimpleList(
values.customerId,
values.contractId,
);
return { return {
options: plans.map((item) => ({ options: [],
label: item.period, placeholder: '请选择合同',
value: item.id, };
})),
placeholder: '请选择回款期数',
onChange: async (value: any) => {
const plan = await getReceivablePlan(value);
values.returnTime = plan?.returnTime;
values.price = plan?.price;
values.returnType = plan?.returnType;
},
} as any;
} }
const plans = await getReceivablePlanSimpleList(
values.customerId,
values.contractId,
);
return {
options: plans.map((item) => ({
disabled: !!item.receivableId,
label: `${item.period}`,
value: item.id,
})),
placeholder: '请选择回款期数',
onChange: async (value: any) => {
if (!value) {
form.setFieldValue('returnTime', undefined);
form.setFieldValue('price', undefined);
form.setFieldValue('returnType', undefined);
return;
}
const plan = await getReceivablePlan(value);
form.setFieldValue('returnTime', plan?.returnTime);
form.setFieldValue('price', plan?.price);
form.setFieldValue('returnType', plan?.returnType);
},
} as any;
}, },
}, },
}, },

View File

@ -7,6 +7,7 @@ import { useVbenForm, useVbenModal } from '@vben/common-ui';
import { message } from 'antdv-next'; import { message } from 'antdv-next';
import { getContractSimpleList } from '#/api/crm/contract';
import { import {
createReceivable, createReceivable,
getReceivable, getReceivable,
@ -16,8 +17,20 @@ import { $t } from '#/locales';
import { useFormSchema } from '../data'; import { useFormSchema } from '../data';
type ReceivablePrefillData = Partial<
Pick<
CrmReceivableApi.Receivable,
'contractId' | 'customerId' | 'price' | 'returnType'
>
> & { id?: number };
type ReceivableFormModalData = ReceivablePrefillData & {
plan?: ReceivablePrefillData;
receivable?: Pick<CrmReceivableApi.Receivable, 'id'>;
};
const emit = defineEmits(['success']); const emit = defineEmits(['success']);
const formData = ref<CrmReceivableApi.Receivable>(); const formData = ref<Partial<CrmReceivableApi.Receivable>>();
const getTitle = computed(() => { const getTitle = computed(() => {
return formData.value?.id return formData.value?.id
? $t('ui.actionTitle.edit', ['回款']) ? $t('ui.actionTitle.edit', ['回款'])
@ -36,6 +49,32 @@ const [Form, formApi] = useVbenForm({
showDefaultActions: false, showDefaultActions: false,
}); });
/** 构建新增回款的预填表单 */
async function buildCreateFormData(
plan: ReceivablePrefillData,
): Promise<Partial<CrmReceivableApi.Receivable>> {
const values: Partial<CrmReceivableApi.Receivable> = {
contractId: plan?.contractId,
customerId: plan?.customerId,
};
//
if (plan?.id) {
values.planId = plan.id;
values.price = plan.price;
values.returnType = plan.returnType;
return values;
}
// /
if (values.customerId && values.contractId) {
const contracts = await getContractSimpleList(values.customerId);
const contract = contracts.find((item) => item.id === values.contractId);
if (contract) {
values.price = contract.totalPrice - contract.totalReceivablePrice;
}
}
return values;
}
const [Modal, modalApi] = useVbenModal({ const [Modal, modalApi] = useVbenModal({
async onConfirm() { async onConfirm() {
const { valid } = await formApi.validate(); const { valid } = await formApi.validate();
@ -63,31 +102,26 @@ const [Modal, modalApi] = useVbenModal({
return; return;
} }
// //
const data = modalApi.getData(); formData.value = undefined;
await formApi.resetForm();
const data = modalApi.getData() as null | ReceivableFormModalData;
if (!data) { if (!data) {
return; return;
} }
const { receivable, plan } = data; const { receivable } = data;
const plan =
data.plan ?? (data.customerId || data.contractId ? data : undefined);
modalApi.lock(); modalApi.lock();
try { try {
if (receivable) { if (receivable?.id) {
formData.value = await getReceivable(receivable.id!); formData.value = await getReceivable(receivable.id!);
} else if (plan) { } else if (plan) {
formData.value = plan.id formData.value = await buildCreateFormData(plan);
? { }
planId: plan.id, if (formData.value) {
price: plan.price, // values
returnType: plan.returnType, await formApi.setValues(formData.value as any);
customerId: plan.customerId,
contractId: plan.contractId,
}
: ({
customerId: plan.customerId,
contractId: plan.contractId,
} as any);
} }
// values
await formApi.setValues(formData.value as any);
} finally { } finally {
modalApi.unlock(); modalApi.unlock();
} }

View File

@ -176,6 +176,7 @@ const [Modal, modalApi] = useVbenModal({
<Modal <Modal
:title="getTitle" :title="getTitle"
class="w-3/4" class="w-3/4"
:close-on-click-modal="false"
:show-confirm-button="formType !== 'detail'" :show-confirm-button="formType !== 'detail'"
> >
<Form class="mx-3"> <Form class="mx-3">

View File

@ -26,7 +26,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
}, },
gridOptions: { gridOptions: {
columns: usePurchaseInGridColumns(), columns: usePurchaseInGridColumns(),
height: 'auto', height: 520,
keepSource: true, keepSource: true,
proxyConfig: { proxyConfig: {
ajax: { ajax: {
@ -95,14 +95,12 @@ defineExpose({ open: openModal });
<template> <template>
<Modal <Modal
class="!w-[50vw]"
v-model:open="open" v-model:open="open"
title="选择采购入库单" title="选择采购入库单"
@ok="handleOk" width="80%"
@cancel.stop="open = false"
@ok.stop="handleOk"
> >
<Grid <Grid table-title="()" />
class="max-h-[600px]"
table-title="采购入库单列表(仅展示可付款的单据)"
/>
</Modal> </Modal>
</template> </template>

View File

@ -26,7 +26,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
}, },
gridOptions: { gridOptions: {
columns: useSaleReturnGridColumns(), columns: useSaleReturnGridColumns(),
height: 'auto', height: 520,
keepSource: true, keepSource: true,
proxyConfig: { proxyConfig: {
ajax: { ajax: {
@ -99,14 +99,12 @@ defineExpose({ open: openModal });
<template> <template>
<Modal <Modal
class="!w-[50vw]"
v-model:open="open" v-model:open="open"
title="选择采购退货单" title="选择采购退货单"
@ok="handleOk" width="80%"
@cancel.stop="open = false"
@ok.stop="handleOk"
> >
<Grid <Grid table-title="退(退)" />
class="max-h-[600px]"
table-title="采购退货单列表(仅展示需退款的单据)"
/>
</Modal> </Modal>
</template> </template>

View File

@ -190,6 +190,7 @@ const [Modal, modalApi] = useVbenModal({
<Modal <Modal
:title="getTitle" :title="getTitle"
class="w-3/4" class="w-3/4"
:close-on-click-modal="false"
:show-confirm-button="formType !== 'detail'" :show-confirm-button="formType !== 'detail'"
> >
<Form class="mx-3"> <Form class="mx-3">

View File

@ -26,7 +26,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
}, },
gridOptions: { gridOptions: {
columns: useSaleOutGridColumns(), columns: useSaleOutGridColumns(),
height: 'auto', height: 520,
keepSource: true, keepSource: true,
proxyConfig: { proxyConfig: {
ajax: { ajax: {
@ -91,14 +91,12 @@ defineExpose({ open: openModal });
<template> <template>
<Modal <Modal
class="!w-[50vw]"
v-model:open="open" v-model:open="open"
title="选择销售出库单" title="选择销售出库单"
@ok="handleOk" width="80%"
@cancel.stop="open = false"
@ok.stop="handleOk"
> >
<Grid <Grid table-title="()" />
class="max-h-[600px]"
table-title="销售出库单列表(仅展示可收款的单据)"
/>
</Modal> </Modal>
</template> </template>

View File

@ -26,7 +26,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
}, },
gridOptions: { gridOptions: {
columns: useSaleReturnGridColumns(), columns: useSaleReturnGridColumns(),
height: 'auto', height: 520,
keepSource: true, keepSource: true,
proxyConfig: { proxyConfig: {
ajax: { ajax: {
@ -95,14 +95,12 @@ defineExpose({ open: openModal });
<template> <template>
<Modal <Modal
class="!w-[50vw]"
v-model:open="open" v-model:open="open"
title="选择销售退货单" title="选择销售退货单"
@ok="handleOk" width="80%"
@cancel.stop="open = false"
@ok.stop="handleOk"
> >
<Grid <Grid table-title="退(退)" />
class="max-h-[600px]"
table-title="销售退货单列表(仅展示可退款的单据)"
/>
</Modal> </Modal>
</template> </template>

View File

@ -206,6 +206,7 @@ const [Modal, modalApi] = useVbenModal({
<Modal <Modal
:title="getTitle" :title="getTitle"
class="w-3/4" class="w-3/4"
:close-on-click-modal="false"
:show-confirm-button="formType !== 'detail'" :show-confirm-button="formType !== 'detail'"
> >
<Form class="mx-3"> <Form class="mx-3">

View File

@ -38,7 +38,7 @@ const [Grid] = useVbenVxeGrid({
}, },
gridOptions: { gridOptions: {
columns: useOrderGridColumns(), columns: useOrderGridColumns(),
height: 'auto', height: 520,
keepSource: true, keepSource: true,
proxyConfig: { proxyConfig: {
ajax: { ajax: {
@ -108,12 +108,13 @@ function handleOk() {
</template> </template>
</Input> </Input>
<Modal <Modal
class="!w-[50vw]"
v-model:open="open" v-model:open="open"
title="选择关联订单" title="选择关联订单"
@ok="handleOk" width="80%"
@cancel.stop="open = false"
@ok.stop="handleOk"
> >
<Grid class="max-h-[600px]" table-title="(退)" /> <Grid table-title="(退)" />
</Modal> </Modal>
</div> </div>
</template> </template>

View File

@ -206,6 +206,7 @@ const [Modal, modalApi] = useVbenModal({
<Modal <Modal
:title="getTitle" :title="getTitle"
class="w-3/4" class="w-3/4"
:close-on-click-modal="false"
:show-confirm-button="formType !== 'detail'" :show-confirm-button="formType !== 'detail'"
> >
<Form class="mx-3"> <Form class="mx-3">

View File

@ -38,7 +38,7 @@ const [Grid] = useVbenVxeGrid({
}, },
gridOptions: { gridOptions: {
columns: useOrderGridColumns(), columns: useOrderGridColumns(),
height: 'auto', height: 520,
keepSource: true, keepSource: true,
proxyConfig: { proxyConfig: {
ajax: { ajax: {
@ -108,12 +108,13 @@ function handleOk() {
</template> </template>
</Input> </Input>
<Modal <Modal
class="!w-[50vw]"
v-model:open="open" v-model:open="open"
title="选择关联订单" title="选择关联订单"
@ok="handleOk" width="80%"
@cancel.stop="open = false"
@ok.stop="handleOk"
> >
<Grid class="max-h-[600px]" table-title="(退)" /> <Grid table-title="(退)" />
</Modal> </Modal>
</div> </div>
</template> </template>

View File

@ -201,6 +201,7 @@ const [Modal, modalApi] = useVbenModal({
<Modal <Modal
:title="getTitle" :title="getTitle"
class="w-3/4" class="w-3/4"
:close-on-click-modal="false"
:show-confirm-button="formType !== 'detail'" :show-confirm-button="formType !== 'detail'"
> >
<Form class="mx-3"> <Form class="mx-3">

View File

@ -38,7 +38,7 @@ const [Grid] = useVbenVxeGrid({
}, },
gridOptions: { gridOptions: {
columns: useOrderGridColumns(), columns: useOrderGridColumns(),
height: 'auto', height: 520,
keepSource: true, keepSource: true,
proxyConfig: { proxyConfig: {
ajax: { ajax: {
@ -108,12 +108,13 @@ function handleOk() {
</template> </template>
</Input> </Input>
<Modal <Modal
class="!w-[50vw]"
v-model:open="open" v-model:open="open"
title="选择关联订单" title="选择关联订单"
@ok="handleOk" width="80%"
@cancel.stop="open = false"
@ok.stop="handleOk"
> >
<Grid class="max-h-[600px]" table-title="()" /> <Grid table-title="()" />
</Modal> </Modal>
</div> </div>
</template> </template>

View File

@ -206,6 +206,7 @@ const [Modal, modalApi] = useVbenModal({
<Modal <Modal
:title="getTitle" :title="getTitle"
class="w-3/4" class="w-3/4"
:close-on-click-modal="false"
:show-confirm-button="formType !== 'detail'" :show-confirm-button="formType !== 'detail'"
> >
<Form class="mx-3"> <Form class="mx-3">

View File

@ -38,7 +38,7 @@ const [Grid] = useVbenVxeGrid({
}, },
gridOptions: { gridOptions: {
columns: useOrderGridColumns(), columns: useOrderGridColumns(),
height: 'auto', height: 520,
keepSource: true, keepSource: true,
proxyConfig: { proxyConfig: {
ajax: { ajax: {
@ -108,12 +108,13 @@ function handleOk() {
</template> </template>
</Input> </Input>
<Modal <Modal
class="!w-[50vw]"
v-model:open="open" v-model:open="open"
title="选择关联订单" title="选择关联订单"
@ok="handleOk" width="80%"
@cancel.stop="open = false"
@ok.stop="handleOk"
> >
<Grid class="max-h-[600px]" table-title="(退)" /> <Grid table-title="(退)" />
</Modal> </Modal>
</div> </div>
</template> </template>