feat:【ele】【erp】purchase 的迁移(40%)- in

This commit is contained in:
YunaiV
2025-11-16 21:49:36 +08:00
parent 2973e0b70f
commit cfda6f4ea0
10 changed files with 1292 additions and 3 deletions

View File

@@ -503,7 +503,7 @@ export function useGridColumns(): VxeTableGridOptions['columns'] {
},
{
title: '操作',
width: 220,
width: 260,
fixed: 'right',
slots: { default: 'actions' },
},

View File

@@ -196,6 +196,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
{
label: row.status === 10 ? '审批' : '反审批',
type: 'link',
icon: ACTION_ICON.AUDIT,
auth: ['erp:purchase-in:update-status'],
popConfirm: {
title: `确认${row.status === 10 ? '审批' : '反审批'}${row.no}吗?`,
@@ -210,6 +211,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
label: $t('common.delete'),
type: 'link',
danger: true,
icon: ACTION_ICON.DELETE,
auth: ['erp:purchase-in:delete'],
popConfirm: {
title: $t('ui.actionMessage.deleteConfirm', [row.no]),

View File

@@ -434,7 +434,7 @@ export function useGridColumns(): VxeTableGridOptions['columns'] {
},
{
title: '操作',
width: 220,
width: 260,
fixed: 'right',
slots: { default: 'actions' },
},

View File

@@ -196,6 +196,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
{
label: row.status === 10 ? '审批' : '反审批',
type: 'link',
icon: ACTION_ICON.AUDIT,
auth: ['erp:purchase-order:update-status'],
popConfirm: {
title: `确认${row.status === 10 ? '审批' : '反审批'}${row.no}吗?`,
@@ -210,6 +211,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
label: $t('common.delete'),
type: 'link',
danger: true,
icon: ACTION_ICON.DELETE,
auth: ['erp:purchase-order:delete'],
popConfirm: {
title: $t('ui.actionMessage.deleteConfirm', [row.no]),

View File

@@ -490,12 +490,13 @@ export function useGridColumns(): VxeTableGridOptions['columns'] {
},
{
title: '操作',
width: 220,
width: 260,
fixed: 'right',
slots: { default: 'actions' },
},
];
}
/** 列表的搜索表单 */
export function useOrderGridFormSchema(): VbenFormSchema[] {
return [

View File

@@ -196,6 +196,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
{
label: row.status === 10 ? '审批' : '反审批',
type: 'link',
icon: ACTION_ICON.AUDIT,
auth: ['erp:purchase-return:update-status'],
popConfirm: {
title: `确认${row.status === 10 ? '审批' : '反审批'}${row.no}吗?`,
@@ -210,6 +211,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
label: $t('common.delete'),
type: 'link',
danger: true,
icon: ACTION_ICON.DELETE,
auth: ['erp:purchase-return:delete'],
popConfirm: {
title: $t('ui.actionMessage.deleteConfirm', [row.no]),

View File

@@ -0,0 +1,622 @@
import type { VbenFormSchema } from '#/adapter/form';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import { DICT_TYPE } from '@vben/constants';
import { getDictOptions } from '@vben/hooks';
import { erpNumberFormatter, erpPriceInputFormatter } from '@vben/utils';
import { z } from '#/adapter/form';
import { getAccountSimpleList } from '#/api/erp/finance/account';
import { getProductSimpleList } from '#/api/erp/product/product';
import { getSupplierSimpleList } from '#/api/erp/purchase/supplier';
import { getWarehouseSimpleList } from '#/api/erp/stock/warehouse';
import { getSimpleUserList } from '#/api/system/user';
import { getRangePickerDefaultProps } from '#/utils';
/** 表单的配置项 */
export function useFormSchema(formType: string): VbenFormSchema[] {
return [
{
fieldName: 'id',
component: 'Input',
dependencies: {
triggerFields: [''],
show: () => false,
},
},
{
fieldName: 'no',
label: '入库单号',
component: 'Input',
componentProps: {
placeholder: '系统自动生成',
disabled: true,
},
},
{
fieldName: 'inTime',
label: '入库时间',
component: 'DatePicker',
componentProps: {
disabled: formType === 'detail',
placeholder: '选择入库时间',
showTime: true,
format: 'YYYY-MM-DD HH:mm:ss',
valueFormat: 'x',
class: '!w-full',
},
rules: 'required',
},
{
fieldName: 'orderNo',
label: '关联订单',
component: 'Input',
formItemClass: 'col-span-1',
rules: 'required',
componentProps: {
placeholder: '请选择关联订单',
disabled: formType === 'detail',
},
},
{
fieldName: 'supplierId',
label: '供应商',
component: 'ApiSelect',
componentProps: {
disabled: true,
placeholder: '请选择供应商',
allowClear: true,
showSearch: true,
api: getSupplierSimpleList,
labelField: 'name',
valueField: 'id',
},
rules: 'required',
},
{
fieldName: 'remark',
label: '备注',
component: 'Textarea',
componentProps: {
placeholder: '请输入备注',
autoSize: { minRows: 1, maxRows: 1 },
disabled: formType === 'detail',
},
formItemClass: 'col-span-2',
},
{
fieldName: 'fileUrl',
label: '附件',
component: 'FileUpload',
componentProps: {
maxNumber: 1,
maxSize: 10,
accept: [
'pdf',
'doc',
'docx',
'xls',
'xlsx',
'txt',
'jpg',
'jpeg',
'png',
],
showDescription: formType !== 'detail',
disabled: formType === 'detail',
},
formItemClass: 'col-span-3',
},
{
fieldName: 'items',
label: '入库产品清单',
component: 'Input',
formItemClass: 'col-span-3',
},
{
fieldName: 'discountPercent',
label: '优惠率(%)',
component: 'InputNumber',
componentProps: {
placeholder: '请输入优惠率',
min: 0,
max: 100,
precision: 2,
controlsPosition: 'right',
class: '!w-full',
},
rules: z.number().min(0).optional(),
},
{
fieldName: 'discountPrice',
label: '付款优惠',
component: 'InputNumber',
componentProps: {
placeholder: '付款优惠',
precision: 2,
formatter: erpPriceInputFormatter,
disabled: true,
controlsPosition: 'right',
class: '!w-full',
},
},
{
fieldName: 'discountedPrice',
label: '优惠后金额',
component: 'InputNumber',
componentProps: {
placeholder: '优惠后金额',
precision: 2,
formatter: erpPriceInputFormatter,
disabled: true,
controlsPosition: 'right',
class: '!w-full',
},
dependencies: {
triggerFields: ['totalPrice', 'otherPrice'],
componentProps: (values) => {
const totalPrice = values.totalPrice || 0;
const otherPrice = values.otherPrice || 0;
values.discountedPrice = totalPrice - otherPrice;
return {};
},
},
},
{
fieldName: 'otherPrice',
label: '其他费用',
component: 'InputNumber',
componentProps: {
disabled: formType === 'detail',
placeholder: '请输入其他费用',
precision: 2,
formatter: erpPriceInputFormatter,
controlsPosition: 'right',
class: '!w-full',
},
},
{
fieldName: 'accountId',
label: '结算账户',
component: 'ApiSelect',
componentProps: {
placeholder: '请选择结算账户',
allowClear: true,
showSearch: true,
api: getAccountSimpleList,
labelField: 'name',
valueField: 'id',
},
},
{
fieldName: 'totalPrice',
label: '应付金额',
component: 'InputNumber',
componentProps: {
precision: 2,
min: 0,
disabled: true,
controlsPosition: 'right',
class: '!w-full',
},
rules: z.number().min(0).optional(),
},
];
}
/** 表单的明细表格列 */
export function useFormItemColumns(
formData?: any[],
): VxeTableGridOptions['columns'] {
return [
{ type: 'seq', title: '序号', minWidth: 50, fixed: 'left' },
{
field: 'warehouseId',
title: '仓库名称',
minWidth: 200,
slots: { default: 'warehouseId' },
},
{
field: 'productId',
title: '产品名称',
minWidth: 200,
slots: { default: 'productId' },
},
{
field: 'stockCount',
title: '库存',
minWidth: 80,
},
{
field: 'productBarCode',
title: '条码',
minWidth: 120,
},
{
field: 'productUnitName',
title: '单位',
minWidth: 80,
},
{
field: 'remark',
title: '备注',
minWidth: 150,
slots: { default: 'remark' },
},
{
field: 'totalCount',
title: '原数量',
formatter: 'formatAmount3',
minWidth: 120,
fixed: 'right',
visible: formData && formData[0]?.inCount !== undefined,
},
{
field: 'inCount',
title: '已入库',
formatter: 'formatAmount3',
minWidth: 120,
fixed: 'right',
visible: formData && formData[0]?.returnCount !== undefined,
},
{
field: 'count',
title: '数量',
minWidth: 120,
fixed: 'right',
slots: { default: 'count' },
},
{
field: 'productPrice',
title: '产品单价',
fixed: 'right',
minWidth: 120,
slots: { default: 'productPrice' },
},
{
field: 'totalProductPrice',
fixed: 'right',
title: '产品金额',
minWidth: 120,
formatter: 'formatAmount2',
},
{
fixed: 'right',
field: 'taxPercent',
title: '税率(%)',
minWidth: 105,
slots: { default: 'taxPercent' },
},
{
fixed: 'right',
field: 'taxPrice',
title: '税额',
minWidth: 120,
formatter: 'formatAmount2',
},
{
field: 'totalPrice',
fixed: 'right',
title: '合计金额',
minWidth: 120,
formatter: 'formatAmount2',
},
{
title: '操作',
width: 50,
fixed: 'right',
slots: { default: 'actions' },
},
];
}
/** 列表的搜索表单 */
export function useGridFormSchema(): VbenFormSchema[] {
return [
{
fieldName: 'no',
label: '入库单号',
component: 'Input',
componentProps: {
placeholder: '请输入入库单号',
allowClear: true,
},
},
{
fieldName: 'productId',
label: '产品',
component: 'ApiSelect',
componentProps: {
placeholder: '请选择产品',
allowClear: true,
showSearch: true,
api: getProductSimpleList,
labelField: 'name',
valueField: 'id',
},
},
{
fieldName: 'inTime',
label: '入库时间',
component: 'RangePicker',
componentProps: {
...getRangePickerDefaultProps(),
allowClear: true,
},
},
{
fieldName: 'supplierId',
label: '供应商',
component: 'ApiSelect',
componentProps: {
placeholder: '请选择供应商',
allowClear: true,
showSearch: true,
api: getSupplierSimpleList,
labelField: 'name',
valueField: 'id',
},
},
{
fieldName: 'warehouseId',
label: '仓库',
component: 'ApiSelect',
componentProps: {
placeholder: '请选择仓库',
allowClear: true,
showSearch: true,
api: getWarehouseSimpleList,
labelField: 'name',
valueField: 'id',
},
},
{
fieldName: 'creator',
label: '创建人',
component: 'ApiSelect',
componentProps: {
placeholder: '请选择创建人',
allowClear: true,
showSearch: true,
api: getSimpleUserList,
labelField: 'nickname',
valueField: 'id',
},
},
{
fieldName: 'orderNo',
label: '关联订单',
component: 'Input',
componentProps: {
placeholder: '请输入关联订单号',
allowClear: true,
},
},
{
fieldName: 'accountId',
label: '结算账户',
component: 'ApiSelect',
componentProps: {
placeholder: '请选择结算账户',
allowClear: true,
showSearch: true,
api: getAccountSimpleList,
labelField: 'name',
valueField: 'id',
},
},
{
fieldName: 'paymentStatus',
label: '付款状态',
component: 'Select',
componentProps: {
options: [
{ label: '未付款', value: 0 },
{ label: '部分付款', value: 1 },
{ label: '全部付款', value: 2 },
],
placeholder: '请选择付款状态',
allowClear: true,
},
},
{
fieldName: 'status',
label: '审批状态',
component: 'Select',
componentProps: {
options: getDictOptions(DICT_TYPE.ERP_AUDIT_STATUS, 'number'),
placeholder: '请选择审批状态',
allowClear: true,
},
},
{
fieldName: 'remark',
label: '备注',
component: 'Input',
componentProps: {
placeholder: '请输入备注',
allowClear: true,
},
},
];
}
/** 列表的字段 */
export function useGridColumns(): VxeTableGridOptions['columns'] {
return [
{
type: 'checkbox',
width: 50,
fixed: 'left',
},
{
field: 'no',
title: '入库单号',
width: 200,
fixed: 'left',
},
{
field: 'productNames',
title: '产品信息',
showOverflow: 'tooltip',
minWidth: 120,
},
{
field: 'supplierName',
title: '供应商',
minWidth: 120,
},
{
field: 'inTime',
title: '入库时间',
width: 160,
formatter: 'formatDate',
},
{
field: 'creatorName',
title: '创建人',
minWidth: 120,
},
{
field: 'totalCount',
title: '总数量',
formatter: 'formatAmount3',
minWidth: 120,
},
{
field: 'totalPrice',
title: '应付金额',
formatter: 'formatAmount2',
minWidth: 120,
},
{
field: 'paymentPrice',
title: '已付金额',
formatter: 'formatAmount2',
minWidth: 120,
},
{
field: 'unPaymentPrice',
title: '未付金额',
formatter: ({ row }) => {
return `${erpNumberFormatter(row.totalPrice - row.paymentPrice, 2)}`;
},
minWidth: 120,
},
{
field: 'status',
title: '审批状态',
minWidth: 120,
cellRender: {
name: 'CellDict',
props: { type: DICT_TYPE.ERP_AUDIT_STATUS },
},
},
{
title: '操作',
width: 260,
fixed: 'right',
slots: { default: 'actions' },
},
];
}
/** 列表的搜索表单 */
export function useOrderGridFormSchema(): VbenFormSchema[] {
return [
{
fieldName: 'no',
label: '订单单号',
component: 'Input',
componentProps: {
placeholder: '请输入订单单号',
allowClear: true,
},
},
{
fieldName: 'productId',
label: '产品',
component: 'ApiSelect',
componentProps: {
placeholder: '请选择产品',
allowClear: true,
showSearch: true,
api: getProductSimpleList,
labelField: 'name',
valueField: 'id',
},
},
{
fieldName: 'orderTime',
label: '订单时间',
component: 'RangePicker',
componentProps: {
...getRangePickerDefaultProps(),
allowClear: true,
},
},
];
}
/** 列表的字段 */
export function useOrderGridColumns(): VxeTableGridOptions['columns'] {
return [
{
type: 'radio',
width: 50,
fixed: 'left',
},
{
field: 'no',
title: '订单单号',
width: 200,
fixed: 'left',
},
{
field: 'productNames',
title: '产品信息',
showOverflow: 'tooltip',
minWidth: 120,
},
{
field: 'supplierName',
title: '供应商',
minWidth: 120,
},
{
field: 'orderTime',
title: '订单时间',
width: 160,
formatter: 'formatDate',
},
{
field: 'creatorName',
title: '创建人',
minWidth: 120,
},
{
field: 'totalCount',
title: '总数量',
formatter: 'formatAmount3',
minWidth: 120,
},
{
field: 'inCount',
title: '入库数量',
formatter: 'formatAmount3',
minWidth: 120,
},
{
field: 'totalProductPrice',
title: '金额合计',
formatter: 'formatAmount2',
minWidth: 120,
},
{
field: 'totalPrice',
title: '含税金额',
formatter: 'formatAmount2',
minWidth: 120,
},
];
}

View File

@@ -0,0 +1,226 @@
<script lang="ts" setup>
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { ErpPurchaseInApi } from '#/api/erp/purchase/in';
import { ref } from 'vue';
import { DocAlert, Page, useVbenModal } from '@vben/common-ui';
import { downloadFileFromBlobPart, isEmpty } from '@vben/utils';
import { ElLoading, ElMessage } from 'element-plus';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import {
deletePurchaseIn,
exportPurchaseIn,
getPurchaseInPage,
updatePurchaseInStatus,
} from '#/api/erp/purchase/in';
import { $t } from '#/locales';
import { useGridColumns, useGridFormSchema } from './data';
import Form from './modules/form.vue';
/** ERP 采购入库列表 */
defineOptions({ name: 'ErpPurchaseIn' });
const [FormModal, formModalApi] = useVbenModal({
connectedComponent: Form,
destroyOnClose: true,
});
/** 刷新表格 */
function handleRefresh() {
gridApi.query();
}
/** 导出表格 */
async function handleExport() {
const data = await exportPurchaseIn(await gridApi.formApi.getValues());
downloadFileFromBlobPart({ fileName: '采购入库.xls', source: data });
}
/** 新增采购入库 */
function handleCreate() {
formModalApi.setData({ type: 'create' }).open();
}
/** 编辑采购入库 */
function handleEdit(row: ErpPurchaseInApi.PurchaseIn) {
formModalApi.setData({ type: 'edit', id: row.id }).open();
}
/** 删除采购入库 */
async function handleDelete(ids: number[]) {
const loadingInstance = ElLoading.service({
text: $t('ui.actionMessage.deleting'),
});
try {
await deletePurchaseIn(ids);
ElMessage.success($t('ui.actionMessage.deleteSuccess'));
handleRefresh();
} finally {
loadingInstance.close();
}
}
/** 审批/反审批操作 */
async function handleUpdateStatus(
row: ErpPurchaseInApi.PurchaseIn,
status: number,
) {
const loadingInstance = ElLoading.service({
text: `确定${status === 20 ? '审批' : '反审批'}该订单吗?`,
});
try {
await updatePurchaseInStatus(row.id!, status);
ElMessage.success(`${status === 20 ? '审批' : '反审批'}成功`);
handleRefresh();
} finally {
loadingInstance.close();
}
}
const checkedIds = ref<number[]>([]);
function handleRowCheckboxChange({
records,
}: {
records: ErpPurchaseInApi.PurchaseIn[];
}) {
checkedIds.value = records.map((item) => item.id!);
}
/** 查看详情 */
function handleDetail(row: ErpPurchaseInApi.PurchaseIn) {
formModalApi.setData({ type: 'detail', id: row.id }).open();
}
const [Grid, gridApi] = useVbenVxeGrid({
formOptions: {
schema: useGridFormSchema(),
},
gridOptions: {
columns: useGridColumns(),
height: 'auto',
keepSource: true,
proxyConfig: {
ajax: {
query: async ({ page }, formValues) => {
return await getPurchaseInPage({
pageNo: page.currentPage,
pageSize: page.pageSize,
...formValues,
});
},
},
},
rowConfig: {
keyField: 'id',
isHover: true,
},
toolbarConfig: {
refresh: true,
search: true,
},
} as VxeTableGridOptions<ErpPurchaseInApi.PurchaseIn>,
gridEvents: {
checkboxAll: handleRowCheckboxChange,
checkboxChange: handleRowCheckboxChange,
},
});
</script>
<template>
<Page auto-content-height>
<template #doc>
<DocAlert
title="【采购】采购订单、入库、退货"
url="https://doc.iocoder.cn/erp/purchase/"
/>
</template>
<FormModal @success="handleRefresh" />
<Grid table-title="采购入库列表">
<template #toolbar-tools>
<TableAction
:actions="[
{
label: $t('ui.actionTitle.create', ['采购入库']),
type: 'primary',
icon: ACTION_ICON.ADD,
auth: ['erp:purchase-in:create'],
onClick: handleCreate,
},
{
label: $t('ui.actionTitle.export'),
type: 'primary',
icon: ACTION_ICON.DOWNLOAD,
auth: ['erp:purchase-in:export'],
onClick: handleExport,
},
{
label: '批量删除',
type: 'danger',
disabled: isEmpty(checkedIds),
icon: ACTION_ICON.DELETE,
auth: ['erp:purchase-in:delete'],
popConfirm: {
title: `是否删除所选中数据?`,
confirm: handleDelete.bind(null, checkedIds),
},
},
]"
/>
</template>
<template #actions="{ row }">
<TableAction
:actions="[
{
label: $t('common.detail'),
type: 'primary',
link: true,
icon: ACTION_ICON.VIEW,
auth: ['erp:purchase-in:query'],
onClick: handleDetail.bind(null, row),
},
{
label: $t('common.edit'),
type: 'primary',
link: true,
icon: ACTION_ICON.EDIT,
auth: ['erp:purchase-in:update'],
ifShow: () => row.status !== 20,
onClick: handleEdit.bind(null, row),
},
{
label: row.status === 10 ? '审批' : '反审批',
type: 'primary',
link: true,
icon: ACTION_ICON.AUDIT,
auth: ['erp:purchase-in:update-status'],
popConfirm: {
title: `确认${row.status === 10 ? '审批' : '反审批'}${row.no}吗?`,
confirm: handleUpdateStatus.bind(
null,
row,
row.status === 10 ? 20 : 10,
),
},
},
{
label: $t('common.delete'),
type: 'danger',
link: true,
icon: ACTION_ICON.DELETE,
auth: ['erp:purchase-in:delete'],
popConfirm: {
title: $t('ui.actionMessage.deleteConfirm', [row.no]),
confirm: handleDelete.bind(null, [row.id!]),
},
},
]"
/>
</template>
</Grid>
</Page>
</template>

View File

@@ -0,0 +1,310 @@
<script lang="ts" setup>
import type { ErpPurchaseInApi } from '#/api/erp/purchase/in';
import { computed, nextTick, onMounted, ref, watch } from 'vue';
import {
erpCountInputFormatter,
erpPriceInputFormatter,
erpPriceMultiply,
} from '@vben/utils';
import { ElInput, ElInputNumber, ElSelect } from 'element-plus';
import { TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import { getProductSimpleList } from '#/api/erp/product/product';
import { getWarehouseStockCount } from '#/api/erp/stock/stock';
import { getWarehouseSimpleList } from '#/api/erp/stock/warehouse';
import { useFormItemColumns } from '../data';
interface Props {
items?: ErpPurchaseInApi.PurchaseInItem[];
disabled?: boolean;
discountPercent?: number;
otherPrice?: number;
}
const props = withDefaults(defineProps<Props>(), {
items: () => [],
disabled: false,
discountPercent: 0,
otherPrice: 0,
});
const emit = defineEmits([
'update:items',
'update:discount-price',
'update:other-price',
'update:total-price',
]);
const tableData = ref<ErpPurchaseInApi.PurchaseInItem[]>([]); // 表格数据
const productOptions = ref<any[]>([]); // 产品下拉选项
const warehouseOptions = ref<any[]>([]); // 仓库下拉选项
/** 获取表格合计数据 */
const summaries = computed(() => {
return {
count: tableData.value.reduce((sum, item) => sum + (item.count || 0), 0),
totalProductPrice: tableData.value.reduce(
(sum, item) => sum + (item.totalProductPrice || 0),
0,
),
taxPrice: tableData.value.reduce(
(sum, item) => sum + (item.taxPrice || 0),
0,
),
totalPrice: tableData.value.reduce(
(sum, item) => sum + (item.totalPrice || 0),
0,
),
};
});
/** 表格配置 */
const [Grid, gridApi] = useVbenVxeGrid({
gridOptions: {
columns: useFormItemColumns(tableData.value),
data: tableData.value,
minHeight: 250,
autoResize: true,
border: true,
rowConfig: {
keyField: 'seq',
isHover: true,
},
pagerConfig: {
enabled: false,
},
toolbarConfig: {
enabled: false,
},
},
});
/** 监听外部传入的列数据 */
watch(
() => props.items,
async (items) => {
if (!items) {
return;
}
items.forEach((item) => initRow(item));
tableData.value = [...items];
await nextTick(); // 特殊:保证 gridApi 已经初始化
await gridApi.grid.reloadData(tableData.value);
// 更新表格列配置(目的:原数量、已入库动态列)
const columns = useFormItemColumns(tableData.value);
await gridApi.grid.reloadColumn(columns || []);
},
{
immediate: true,
},
);
/** 计算 discountPrice、otherPrice、totalPrice 价格 */
watch(
() => [tableData.value, props.discountPercent, props.otherPrice],
() => {
if (!tableData.value || tableData.value.length === 0) {
return;
}
const totalPrice = tableData.value.reduce(
(prev, curr) => prev + (curr.totalPrice || 0),
0,
);
const discountPrice =
props.discountPercent === null
? 0
: erpPriceMultiply(totalPrice, props.discountPercent / 100);
const discountedPrice = totalPrice - discountPrice!;
const finalTotalPrice = discountedPrice + (props.otherPrice || 0);
// 通知父组件更新
emit('update:discount-price', discountPrice);
emit('update:other-price', props.otherPrice || 0);
emit('update:total-price', finalTotalPrice);
},
{ deep: true },
);
/** 处理删除 */
function handleDelete(row: ErpPurchaseInApi.PurchaseInItem) {
const index = tableData.value.findIndex((item) => item.seq === row.seq);
if (index !== -1) {
tableData.value.splice(index, 1);
}
// 通知父组件更新
emit('update:items', [...tableData.value]);
}
/** 处理仓库变更 */
async function handleWarehouseChange(row: ErpPurchaseInApi.PurchaseInItem) {
const stockCount = await getWarehouseStockCount({
productId: row.productId!,
warehouseId: row.warehouseId!,
});
row.stockCount = stockCount || 0;
handleRowChange(row);
}
/** 处理行数据变更 */
function handleRowChange(row: any) {
const index = tableData.value.findIndex((item) => item.seq === row.seq);
if (index === -1) {
tableData.value.push(row);
} else {
tableData.value[index] = row;
}
emit('update:items', [...tableData.value]);
}
/** 初始化行数据 */
function initRow(row: ErpPurchaseInApi.PurchaseInItem) {
if (row.productPrice && row.count) {
row.totalProductPrice = erpPriceMultiply(row.productPrice, row.count) ?? 0;
row.taxPrice =
erpPriceMultiply(row.totalProductPrice, (row.taxPercent || 0) / 100) ?? 0;
row.totalPrice = row.totalProductPrice + row.taxPrice;
}
}
/** 表单校验 */
function validate() {
for (let i = 0; i < tableData.value.length; i++) {
const item = tableData.value[i];
if (item) {
if (!item.warehouseId) {
throw new Error(`${i + 1} 行:仓库不能为空`);
}
if (!item.count || item.count <= 0) {
throw new Error(`${i + 1} 行:产品数量不能为空`);
}
}
}
}
defineExpose({
validate,
});
/** 初始化 */
onMounted(async () => {
productOptions.value = await getProductSimpleList();
warehouseOptions.value = await getWarehouseSimpleList();
});
</script>
<template>
<Grid class="w-full">
<template #warehouseId="{ row }">
<ElSelect
v-model="row.warehouseId"
:disabled="disabled"
placeholder="请选择仓库"
filterable
class="w-full"
@change="handleWarehouseChange(row)"
>
<el-option
v-for="item in warehouseOptions"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</ElSelect>
</template>
<template #productId="{ row }">
<ElSelect
v-model="row.productId"
disabled
placeholder="请选择产品"
filterable
class="w-full"
>
<el-option
v-for="item in productOptions"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</ElSelect>
</template>
<template #count="{ row }">
<ElInputNumber
v-if="!disabled"
v-model="row.count"
:min="0"
:precision="2"
controls-position="right"
class="!w-full"
@change="handleRowChange(row)"
/>
<span v-else>{{ erpCountInputFormatter(row.count) || '-' }}</span>
</template>
<template #productPrice="{ row }">
<ElInputNumber
v-if="!disabled"
v-model="row.productPrice"
:min="0"
:precision="2"
controls-position="right"
class="!w-full"
@change="handleRowChange(row)"
/>
<span v-else>{{ erpPriceInputFormatter(row.productPrice) || '-' }}</span>
</template>
<template #remark="{ row }">
<ElInput v-if="!disabled" v-model="row.remark" class="w-full" />
<span v-else>{{ row.remark || '-' }}</span>
</template>
<template #taxPercent="{ row }">
<ElInputNumber
v-if="!disabled"
v-model="row.taxPercent"
:min="0"
:max="100"
:precision="2"
controls-position="right"
class="!w-full"
@change="handleRowChange(row)"
/>
<span v-else>{{ row.taxPercent || '-' }}</span>
</template>
<template #actions="{ row }">
<TableAction
v-if="!disabled"
:actions="[
{
label: '删除',
type: 'danger',
link: true,
popConfirm: {
title: '确认删除该产品吗?',
confirm: handleDelete.bind(null, row),
},
},
]"
/>
</template>
<template #bottom>
<div class="border-border bg-muted mt-2 rounded border p-2">
<div class="text-muted-foreground flex justify-between text-sm">
<span class="text-foreground font-medium">合计</span>
<div class="flex space-x-4">
<span>数量{{ erpCountInputFormatter(summaries.count) }}</span>
<span>
金额{{ erpPriceInputFormatter(summaries.totalProductPrice) }}
</span>
<span>税额{{ erpPriceInputFormatter(summaries.taxPrice) }}</span>
<span>
税额合计{{ erpPriceInputFormatter(summaries.totalPrice) }}
</span>
</div>
</div>
</div>
</template>
</Grid>
</template>

View File

@@ -0,0 +1,124 @@
<script lang="ts" setup>
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { ErpPurchaseOrderApi } from '#/api/erp/purchase/order';
import { ref } from 'vue';
import { IconifyIcon } from '@vben/icons';
import { ElDialog, ElInput, ElMessage } from 'element-plus';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { getPurchaseOrderPage } from '#/api/erp/purchase/order';
import { useOrderGridColumns, useOrderGridFormSchema } from '../data';
defineProps({
orderNo: {
type: String,
default: () => undefined,
},
disabled: {
type: Boolean,
default: false,
},
});
const emit = defineEmits<{
'update:order': [order: ErpPurchaseOrderApi.PurchaseOrder];
}>();
const order = ref<ErpPurchaseOrderApi.PurchaseOrder>(); // 选择的采购订单
const open = ref<boolean>(false); // 选择采购订单弹窗是否打开
/** 表格配置 */
const [Grid] = useVbenVxeGrid({
formOptions: {
schema: useOrderGridFormSchema(),
},
gridOptions: {
columns: useOrderGridColumns(),
height: 'auto',
keepSource: true,
proxyConfig: {
ajax: {
query: async ({ page }, formValues) => {
return await getPurchaseOrderPage({
pageNo: page.currentPage,
pageSize: page.pageSize,
inEnable: true,
...formValues,
});
},
},
},
rowConfig: {
keyField: 'id',
isHover: true,
},
radioConfig: {
trigger: 'row',
highlight: true,
},
toolbarConfig: {
refresh: true,
search: true,
},
} as VxeTableGridOptions<ErpPurchaseOrderApi.PurchaseOrder>,
gridEvents: {
radioChange: ({ row }: { row: ErpPurchaseOrderApi.PurchaseOrder }) => {
handleSelectOrder(row);
},
},
});
/** 选择采购订单 */
function handleSelectOrder(selectOrder: ErpPurchaseOrderApi.PurchaseOrder) {
order.value = selectOrder;
}
/** 确认选择采购订单 */
function handleOk() {
if (!order.value) {
ElMessage.warning('请选择一个采购订单');
return;
}
emit('update:order', order.value);
open.value = false;
}
</script>
<template>
<div>
<ElInput
readonly
:model-value="orderNo"
:disabled="disabled"
@click="() => !disabled && (open = true)"
>
<template #append>
<div>
<IconifyIcon
class="h-full w-6 cursor-pointer"
icon="ant-design:setting-outlined"
:style="{ cursor: disabled ? 'not-allowed' : 'pointer' }"
@click="() => !disabled && (open = true)"
/>
</div>
</template>
</ElInput>
<ElDialog
v-model="open"
title="选择关联订单"
width="50%"
@close="open = false"
:append-to-body="true"
>
<Grid class="max-h-[600px]" table-title="采购订单列表(仅展示可退货)" />
<template #footer>
<el-button @click="open = false">取消</el-button>
<el-button type="primary" @click="handleOk">确定</el-button>
</template>
</ElDialog>
</div>
</template>