feat:出库单修改功能
This commit is contained in:
@@ -27,6 +27,7 @@ export namespace WmsIostorInvApi {
|
||||
creator?: string; // 创建者标识
|
||||
creatorName?: string; // 创建者名称
|
||||
createTime?: Dayjs | string; // 创建时间
|
||||
details?: OutboundDetail[]; // 出入库单明细
|
||||
disOptid: string; // 分配人
|
||||
disTime?: Dayjs | string; // 分配时间
|
||||
confirmOptid: string; // 确认人
|
||||
@@ -64,12 +65,18 @@ export namespace WmsIostorInvApi {
|
||||
/** 出库单新增请求 */
|
||||
export interface OutboundCreateReq {
|
||||
billType: string; // 单据类型
|
||||
billStatus: string; // 单据状态
|
||||
storId: string; // 仓库标识
|
||||
bizDate: number; // 业务日期(毫秒时间戳)
|
||||
remark?: string; // 备注
|
||||
details: OutboundDetail[]; // 出库单明细
|
||||
}
|
||||
|
||||
/** 出库单修改请求 */
|
||||
export interface OutboundUpdateReq extends OutboundCreateReq {
|
||||
iostorinvId: string;
|
||||
}
|
||||
|
||||
/** 可用库存 */
|
||||
export interface AvailableInventory {
|
||||
groupId: number | string; // 组盘记录标识
|
||||
@@ -119,6 +126,13 @@ export function getIostorInv(id: string) {
|
||||
);
|
||||
}
|
||||
|
||||
/** 查询出入库单全部明细 */
|
||||
export function getIostorInvDetails(iostorinvId: string) {
|
||||
return requestClient.get<WmsIostorInvApi.OutboundDisplayDetail[]>(
|
||||
`/wms/iostor-inv/details?iostorinvId=${encodeURIComponent(iostorinvId)}`,
|
||||
);
|
||||
}
|
||||
|
||||
/** 新增出入库单主表 */
|
||||
export function createIostorInv(data: WmsIostorInvApi.IostorInv) {
|
||||
return requestClient.post('/wms/iostor-inv/create', data);
|
||||
@@ -129,6 +143,11 @@ export function updateIostorInv(data: WmsIostorInvApi.IostorInv) {
|
||||
return requestClient.put('/wms/iostor-inv/update', data);
|
||||
}
|
||||
|
||||
/** 修改出库单主表及明细 */
|
||||
export function updateOutbound(data: WmsIostorInvApi.OutboundUpdateReq) {
|
||||
return requestClient.put('/wms/iostor-inv/update', data);
|
||||
}
|
||||
|
||||
/** 删除出入库单主表 */
|
||||
export function deleteIostorInv(id: number) {
|
||||
return requestClient.delete(`/wms/iostor-inv/delete?id=${id}`);
|
||||
|
||||
@@ -9,37 +9,37 @@ import { Button, Input, InputNumber, message, Space, Table } from 'antdv-next';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
import { useVbenForm } from '#/adapter/form';
|
||||
import { getSimpleDictDataList } from '#/api/system/dict/data';
|
||||
import { getBsrealStorAttrSimpleList } from '#/api/wms/bsrealstorattr';
|
||||
import {
|
||||
createOutbound,
|
||||
getIostorInv,
|
||||
updateIostorInv,
|
||||
getIostorInvDetails,
|
||||
updateOutbound,
|
||||
} from '#/api/wms/iostorinv';
|
||||
import { $t } from '#/locales';
|
||||
|
||||
import { useFormSchema } from '../data';
|
||||
import { summarizeDetails } from './detail-summary';
|
||||
import InventorySelectComponent from './inventory-select.vue';
|
||||
import ManualDetailComponent from './manual-detail.vue';
|
||||
import {
|
||||
buildOutboundPayload,
|
||||
filterOutboundBillTypeOptions,
|
||||
mergeOutboundInventory,
|
||||
type OutboundFormDetail,
|
||||
shouldApplyEditResult,
|
||||
shouldClearDetailsForWarehouseChange,
|
||||
} from './outbound-form';
|
||||
import { DICT_TYPE } from '@vben/constants';
|
||||
import { getDictOptions } from '@vben/hooks';
|
||||
|
||||
const emit = defineEmits(['success']);
|
||||
const formData = ref<WmsIostorInvApi.IostorInv>();
|
||||
const details = ref<OutboundFormDetail[]>([]);
|
||||
const currentStorId = ref<string>();
|
||||
let manualRowSequence = 0;
|
||||
let billTypeRequest: null | ReturnType<typeof getSimpleDictDataList> = null;
|
||||
let mainModalOpen = false;
|
||||
let mainOpenToken = 0;
|
||||
let activeEditId: string | undefined;
|
||||
let suppressWarehouseChange = false;
|
||||
|
||||
const isEdit = computed(() => Boolean(formData.value?.iostorinvId));
|
||||
const getTitle = computed(() =>
|
||||
@@ -48,17 +48,6 @@ const getTitle = computed(() =>
|
||||
: '出库单新增',
|
||||
);
|
||||
|
||||
const [LegacyForm, legacyFormApi] = useVbenForm({
|
||||
commonConfig: {
|
||||
componentProps: { class: 'w-full' },
|
||||
formItemClass: 'col-span-2',
|
||||
labelWidth: 80,
|
||||
},
|
||||
layout: 'horizontal',
|
||||
schema: useFormSchema(),
|
||||
showDefaultActions: false,
|
||||
});
|
||||
|
||||
async function handleWarehouseChange(nextStorId?: string) {
|
||||
const shouldClear = shouldClearDetailsForWarehouseChange(
|
||||
currentStorId.value,
|
||||
@@ -78,7 +67,7 @@ const [OutboundForm, outboundFormApi] = useVbenForm({
|
||||
labelWidth: 88,
|
||||
},
|
||||
handleValuesChange: (values) => {
|
||||
if (Object.hasOwn(values, 'storId')) {
|
||||
if (!suppressWarehouseChange && Object.hasOwn(values, 'storId')) {
|
||||
void handleWarehouseChange(values.storId);
|
||||
}
|
||||
},
|
||||
@@ -108,7 +97,7 @@ const [OutboundForm, outboundFormApi] = useVbenForm({
|
||||
component: 'Select',
|
||||
componentProps: {
|
||||
allowClear: true,
|
||||
options: [],
|
||||
options: getDictOptions(DICT_TYPE.WMS_OUT_BILL_TYPE),
|
||||
placeholder: '请选择业务类型',
|
||||
},
|
||||
fieldName: 'billType',
|
||||
@@ -116,11 +105,15 @@ const [OutboundForm, outboundFormApi] = useVbenForm({
|
||||
rules: 'required',
|
||||
},
|
||||
{
|
||||
component: 'Input',
|
||||
componentProps: { disabled: true },
|
||||
defaultValue: '生成',
|
||||
component: 'Select',
|
||||
componentProps: {
|
||||
allowClear: true,
|
||||
options: getDictOptions(DICT_TYPE.WMS_IO_BILL_STATUS),
|
||||
placeholder: '请选择单据状态',
|
||||
},
|
||||
fieldName: 'billStatus',
|
||||
label: '单据状态',
|
||||
rules: 'required',
|
||||
},
|
||||
{
|
||||
component: 'InputNumber',
|
||||
@@ -164,28 +157,6 @@ const [ManualDetail, manualDetailApi] = useVbenModal({
|
||||
destroyOnClose: true,
|
||||
});
|
||||
|
||||
async function loadBillTypeOptions() {
|
||||
billTypeRequest ??= getSimpleDictDataList();
|
||||
const request = billTypeRequest;
|
||||
try {
|
||||
const dictItems = await request;
|
||||
outboundFormApi.updateSchema([
|
||||
{
|
||||
componentProps: {
|
||||
allowClear: true,
|
||||
options: filterOutboundBillTypeOptions(dictItems),
|
||||
placeholder: '请选择业务类型',
|
||||
},
|
||||
fieldName: 'billType',
|
||||
},
|
||||
]);
|
||||
} catch {
|
||||
message.error('出库业务类型加载失败,请稍后重试');
|
||||
} finally {
|
||||
if (billTypeRequest === request) billTypeRequest = null;
|
||||
}
|
||||
}
|
||||
|
||||
const columns = [
|
||||
{ key: 'index', title: '序号', width: 60 },
|
||||
{ dataIndex: 'materialCode', key: 'materialCode', title: '物料编码', width: 130 },
|
||||
@@ -265,34 +236,23 @@ function validateDetails() {
|
||||
|
||||
const [Modal, modalApi] = useVbenModal({
|
||||
async onConfirm() {
|
||||
if (isEdit.value) {
|
||||
const { valid } = await legacyFormApi.validate();
|
||||
if (!valid) return;
|
||||
modalApi.lock();
|
||||
try {
|
||||
await updateIostorInv(
|
||||
(await legacyFormApi.getValues()) as WmsIostorInvApi.IostorInv,
|
||||
);
|
||||
await modalApi.close();
|
||||
emit('success');
|
||||
message.success($t('ui.actionMessage.operationSuccess'));
|
||||
} finally {
|
||||
modalApi.unlock();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const { valid } = await outboundFormApi.validate();
|
||||
if (!valid || !validateDetails()) return;
|
||||
const values = await outboundFormApi.getValues();
|
||||
modalApi.lock();
|
||||
try {
|
||||
await createOutbound(
|
||||
buildOutboundPayload(
|
||||
{ ...values, bizDate: dayjs(values.bizDate).valueOf() },
|
||||
details.value,
|
||||
),
|
||||
const payload = buildOutboundPayload(
|
||||
{ ...values, bizDate: dayjs(values.bizDate).valueOf() },
|
||||
details.value,
|
||||
);
|
||||
if (isEdit.value) {
|
||||
await updateOutbound({
|
||||
...payload,
|
||||
iostorinvId: formData.value!.iostorinvId,
|
||||
});
|
||||
} else {
|
||||
await createOutbound(payload);
|
||||
}
|
||||
await modalApi.close();
|
||||
emit('success');
|
||||
message.success($t('ui.actionMessage.operationSuccess'));
|
||||
@@ -318,17 +278,32 @@ const [Modal, modalApi] = useVbenModal({
|
||||
formData.value = undefined;
|
||||
details.value = [];
|
||||
currentStorId.value = undefined;
|
||||
await outboundFormApi.resetForm();
|
||||
suppressWarehouseChange = true;
|
||||
try {
|
||||
await outboundFormApi.resetForm();
|
||||
await outboundFormApi.updateSchema([
|
||||
{
|
||||
componentProps: {
|
||||
allowClear: true,
|
||||
disabled: false,
|
||||
options: getDictOptions(DICT_TYPE.WMS_IO_BILL_STATUS),
|
||||
placeholder: '请选择单据状态',
|
||||
},
|
||||
fieldName: 'billStatus',
|
||||
},
|
||||
]);
|
||||
if (!mainModalOpen || mainOpenToken !== token || activeEditId) return;
|
||||
await outboundFormApi.setValues({
|
||||
billCode: '保存时自动生成',
|
||||
billStatus: '10',
|
||||
bizDate: dayjs(),
|
||||
detailCount: 0,
|
||||
totalWeight: '0.000',
|
||||
});
|
||||
} finally {
|
||||
suppressWarehouseChange = false;
|
||||
}
|
||||
if (!mainModalOpen || mainOpenToken !== token || activeEditId) return;
|
||||
await outboundFormApi.setValues({
|
||||
billCode: '保存时自动生成',
|
||||
billStatus: '生成',
|
||||
bizDate: dayjs(),
|
||||
detailCount: 0,
|
||||
totalWeight: '0.000',
|
||||
});
|
||||
if (!mainModalOpen || mainOpenToken !== token || activeEditId) return;
|
||||
await loadBillTypeOptions();
|
||||
return;
|
||||
}
|
||||
const editId = data.iostorinvId;
|
||||
@@ -336,7 +311,10 @@ const [Modal, modalApi] = useVbenModal({
|
||||
formData.value = data;
|
||||
modalApi.lock();
|
||||
try {
|
||||
const editResult = await getIostorInv(editId);
|
||||
const [editResult, editDetails] = await Promise.all([
|
||||
getIostorInv(editId),
|
||||
getIostorInvDetails(editId),
|
||||
]);
|
||||
if (
|
||||
!shouldApplyEditResult(
|
||||
mainModalOpen,
|
||||
@@ -349,7 +327,38 @@ const [Modal, modalApi] = useVbenModal({
|
||||
return;
|
||||
}
|
||||
formData.value = editResult;
|
||||
await legacyFormApi.setValues(editResult);
|
||||
suppressWarehouseChange = true;
|
||||
try {
|
||||
await outboundFormApi.resetForm();
|
||||
await outboundFormApi.updateSchema([
|
||||
{
|
||||
componentProps: {
|
||||
allowClear: true,
|
||||
disabled: true,
|
||||
options: getDictOptions(DICT_TYPE.WMS_IO_BILL_STATUS),
|
||||
placeholder: '请选择单据状态',
|
||||
},
|
||||
fieldName: 'billStatus',
|
||||
},
|
||||
]);
|
||||
details.value = editDetails.map((detail, index) => ({
|
||||
...detail,
|
||||
rowKey: `edit-${editId}-${index}`,
|
||||
}));
|
||||
await outboundFormApi.setValues({
|
||||
billCode: editResult.billCode,
|
||||
billStatus: editResult.billStatus,
|
||||
billType: editResult.billType,
|
||||
bizDate: editResult.bizDate ? dayjs(editResult.bizDate) : undefined,
|
||||
detailCount: details.value.length,
|
||||
remark: editResult.remark,
|
||||
storId: editResult.storId,
|
||||
totalWeight: Number(editResult.totalWeight || 0).toFixed(3),
|
||||
});
|
||||
currentStorId.value = editResult.storId;
|
||||
} finally {
|
||||
suppressWarehouseChange = false;
|
||||
}
|
||||
} finally {
|
||||
if (
|
||||
shouldApplyEditResult(
|
||||
@@ -369,8 +378,7 @@ const [Modal, modalApi] = useVbenModal({
|
||||
|
||||
<template>
|
||||
<Modal class="w-[1400px] max-w-[96vw]" :title="getTitle">
|
||||
<LegacyForm v-if="isEdit" class="mx-4" />
|
||||
<div v-else class="px-4">
|
||||
<div class="px-4">
|
||||
<OutboundForm />
|
||||
<div class="mb-3 mt-1 flex items-center justify-between">
|
||||
<span class="text-base font-medium">出库明细</span>
|
||||
|
||||
@@ -34,7 +34,13 @@ describe('buildOutboundPayload', () => {
|
||||
it('将业务日期转换成毫秒,并剥离所有仅展示字段和汇总字段', () => {
|
||||
const bizDate = new Date('2026-07-22T08:30:00+08:00');
|
||||
const payload = buildOutboundPayload(
|
||||
{ billType: 'SALE', bizDate, remark: '主备注', storId: 'S-1' },
|
||||
{
|
||||
billStatus: '10',
|
||||
billType: 'SALE',
|
||||
bizDate,
|
||||
remark: '主备注',
|
||||
storId: 'S-1',
|
||||
},
|
||||
[
|
||||
{
|
||||
groupId: 11,
|
||||
@@ -79,7 +85,7 @@ describe('buildOutboundPayload', () => {
|
||||
|
||||
it('手工汇总行不附带库存引用字段', () => {
|
||||
const payload = buildOutboundPayload(
|
||||
{ billType: 'SALE', bizDate: 1, storId: 'S-1' },
|
||||
{ billStatus: '10', billType: 'SALE', bizDate: 1, storId: 'S-1' },
|
||||
[{ materialCode: 'M-1', planQty: 2 }],
|
||||
);
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ export function shouldApplyEditResult(
|
||||
|
||||
export interface OutboundFormValues {
|
||||
billType?: string;
|
||||
billStatus?: string;
|
||||
bizDate?: number | string | { valueOf: () => number };
|
||||
remark?: string;
|
||||
storId?: string;
|
||||
@@ -48,6 +49,7 @@ export function buildOutboundPayload(
|
||||
): WmsIostorInvApi.OutboundCreateReq {
|
||||
return {
|
||||
billType: values.billType!,
|
||||
billStatus: values.billStatus!,
|
||||
bizDate: Number(values.bizDate?.valueOf()),
|
||||
details: details.map((detail) => ({
|
||||
groupId: detail.groupId,
|
||||
|
||||
Reference in New Issue
Block a user