feat:出库单分配功能
This commit is contained in:
@@ -57,6 +57,11 @@ export namespace WmsIostorInvApi {
|
||||
|
||||
/** 出库明细页面展示模型(提交时应转换为 OutboundDetail) */
|
||||
export interface OutboundDisplayDetail extends OutboundDetail {
|
||||
iostorinvdtlId?: string;
|
||||
seqNo?: number;
|
||||
billStatus?: string;
|
||||
assignQty?: number;
|
||||
unassignQty?: number;
|
||||
materialName?: string;
|
||||
rowKey?: string;
|
||||
sapBatchNo?: string;
|
||||
@@ -109,6 +114,39 @@ export namespace WmsIostorInvApi {
|
||||
storId: string; // 仓库标识
|
||||
vehicleCodes: string[]; // 箱号列表
|
||||
}
|
||||
|
||||
export interface OutboundAllocation {
|
||||
iostorinvdisId: string;
|
||||
iostorinvdtlId: string;
|
||||
workStatus: string;
|
||||
materialCode: string;
|
||||
materialName?: string;
|
||||
pcsn?: string;
|
||||
storagevehicleCode?: string;
|
||||
planQty: number;
|
||||
qtyUnitName?: string;
|
||||
sectId?: string;
|
||||
sectName?: string;
|
||||
structCode?: string;
|
||||
structName?: string;
|
||||
}
|
||||
|
||||
export interface ManualAllocationInventory {
|
||||
groupId: string;
|
||||
vehicleCode: string;
|
||||
materialId: string;
|
||||
materialCode: string;
|
||||
materialName?: string;
|
||||
pcsn?: string;
|
||||
availableQty: number;
|
||||
qtyUnitId?: string;
|
||||
qtyUnitName?: string;
|
||||
sectId?: string;
|
||||
sectName?: string;
|
||||
structId?: string;
|
||||
structCode?: string;
|
||||
structName?: string;
|
||||
}
|
||||
}
|
||||
|
||||
/** 查询出入库单主表分页 */
|
||||
@@ -133,6 +171,75 @@ export function getIostorInvDetails(iostorinvId: string) {
|
||||
);
|
||||
}
|
||||
|
||||
export function getOutboundAllocations(
|
||||
iostorinvId: string,
|
||||
iostorinvdtlId?: string,
|
||||
sectId?: string,
|
||||
) {
|
||||
return requestClient.get<WmsIostorInvApi.OutboundAllocation[]>(
|
||||
'/wms/iostor-inv/allocations',
|
||||
{ params: { iostorinvId, iostorinvdtlId, sectId } },
|
||||
);
|
||||
}
|
||||
|
||||
export function allocateAll(iostorinvId: string, sectId?: string) {
|
||||
return requestClient.post('/wms/iostor-inv/allocate-all', {
|
||||
iostorinvId,
|
||||
sectId,
|
||||
});
|
||||
}
|
||||
|
||||
export function cancelAllAllocations(iostorinvId: string, sectId?: string) {
|
||||
return requestClient.post('/wms/iostor-inv/cancel-all-allocations', {
|
||||
iostorinvId,
|
||||
sectId,
|
||||
});
|
||||
}
|
||||
|
||||
export function autoAllocate(
|
||||
iostorinvId: string,
|
||||
iostorinvdtlId: string,
|
||||
sectId?: string,
|
||||
) {
|
||||
return requestClient.post('/wms/iostor-inv/auto-allocate', {
|
||||
iostorinvId,
|
||||
iostorinvdtlId,
|
||||
sectId,
|
||||
});
|
||||
}
|
||||
|
||||
export function autoCancelAllocation(
|
||||
iostorinvId: string,
|
||||
iostorinvdtlId: string,
|
||||
sectId?: string,
|
||||
) {
|
||||
return requestClient.post('/wms/iostor-inv/auto-cancel-allocation', {
|
||||
iostorinvId,
|
||||
iostorinvdtlId,
|
||||
sectId,
|
||||
});
|
||||
}
|
||||
|
||||
export function getManualAllocationInventory(
|
||||
iostorinvId: string,
|
||||
iostorinvdtlId: string,
|
||||
sectId?: string,
|
||||
) {
|
||||
return requestClient.get<WmsIostorInvApi.ManualAllocationInventory[]>(
|
||||
'/wms/iostor-inv/manual-allocation-inventory',
|
||||
{ params: { iostorinvId, iostorinvdtlId, sectId } },
|
||||
);
|
||||
}
|
||||
|
||||
export function manualAllocate(data: {
|
||||
groupIds: string[];
|
||||
iostorinvdtlId: string;
|
||||
iostorinvId: string;
|
||||
sectId?: string;
|
||||
}) {
|
||||
return requestClient.post('/wms/iostor-inv/manual-allocate', data);
|
||||
}
|
||||
|
||||
/** 新增出入库单主表 */
|
||||
export function createIostorInv(data: WmsIostorInvApi.IostorInv) {
|
||||
return requestClient.post('/wms/iostor-inv/create', data);
|
||||
@@ -149,12 +256,12 @@ export function updateOutbound(data: WmsIostorInvApi.OutboundUpdateReq) {
|
||||
}
|
||||
|
||||
/** 删除出入库单主表 */
|
||||
export function deleteIostorInv(id: number) {
|
||||
export function deleteIostorInv(id: string) {
|
||||
return requestClient.delete(`/wms/iostor-inv/delete?id=${id}`);
|
||||
}
|
||||
|
||||
/** 批量删除出入库单主表 */
|
||||
export function deleteIostorInvList(ids: number[]) {
|
||||
export function deleteIostorInvList(ids: string[]) {
|
||||
return requestClient.delete(
|
||||
`/wms/iostor-inv/delete-list?ids=${ids.join(',')}`,
|
||||
);
|
||||
|
||||
@@ -335,6 +335,7 @@ export function useGridColumns(): VxeTableGridOptions<WmsIostorInvApi.IostorInv>
|
||||
field: 'billCode',
|
||||
title: '单据编号',
|
||||
minWidth: 120,
|
||||
slots: { default: 'billCode' },
|
||||
},
|
||||
{
|
||||
field: 'billType',
|
||||
@@ -356,21 +357,6 @@ export function useGridColumns(): VxeTableGridOptions<WmsIostorInvApi.IostorInv>
|
||||
title: '仓库',
|
||||
minWidth: 120,
|
||||
},
|
||||
{
|
||||
field: 'sourceId',
|
||||
title: '来源方标识',
|
||||
minWidth: 120,
|
||||
},
|
||||
{
|
||||
field: 'sourceName',
|
||||
title: '来源方名称',
|
||||
minWidth: 120,
|
||||
},
|
||||
{
|
||||
field: 'sourceType',
|
||||
title: '来源方类型',
|
||||
minWidth: 120,
|
||||
},
|
||||
{
|
||||
field: 'totalWeight',
|
||||
title: '总重量',
|
||||
@@ -391,8 +377,18 @@ export function useGridColumns(): VxeTableGridOptions<WmsIostorInvApi.IostorInv>
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'remark',
|
||||
title: '备注',
|
||||
field: 'sourceId',
|
||||
title: '来源方标识',
|
||||
minWidth: 120,
|
||||
},
|
||||
{
|
||||
field: 'sourceName',
|
||||
title: '来源方名称',
|
||||
minWidth: 120,
|
||||
},
|
||||
{
|
||||
field: 'sourceType',
|
||||
title: '来源方类型',
|
||||
minWidth: 120,
|
||||
},
|
||||
{
|
||||
@@ -437,16 +433,6 @@ export function useGridColumns(): VxeTableGridOptions<WmsIostorInvApi.IostorInv>
|
||||
minWidth: 120,
|
||||
formatter: 'formatDateTime',
|
||||
},
|
||||
{
|
||||
field: 'sysdeptid',
|
||||
title: '部门ID',
|
||||
minWidth: 120,
|
||||
},
|
||||
{
|
||||
field: 'syscompanyid',
|
||||
title: '公司ID',
|
||||
minWidth: 120,
|
||||
},
|
||||
{
|
||||
field: 'isUpload',
|
||||
title: '是否已上传',
|
||||
@@ -462,6 +448,11 @@ export function useGridColumns(): VxeTableGridOptions<WmsIostorInvApi.IostorInv>
|
||||
title: '回传时间',
|
||||
minWidth: 120,
|
||||
},
|
||||
{
|
||||
field: 'remark',
|
||||
title: '备注',
|
||||
minWidth: 120,
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
width: 200,
|
||||
|
||||
@@ -7,7 +7,7 @@ import { ref } from 'vue';
|
||||
import { confirm, Page, useVbenModal } from '@vben/common-ui';
|
||||
import { downloadFileFromBlobPart, isEmpty } from '@vben/utils';
|
||||
|
||||
import { message } from 'antdv-next';
|
||||
import { Button, message } from 'antdv-next';
|
||||
|
||||
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||
import {
|
||||
@@ -19,12 +19,22 @@ import {
|
||||
import { $t } from '#/locales';
|
||||
|
||||
import { useGridColumns, useGridFormSchema } from './data';
|
||||
import Allocation from './modules/allocation.vue';
|
||||
import Detail from './modules/detail.vue';
|
||||
import Form from './modules/form.vue';
|
||||
|
||||
const [FormModal, formModalApi] = useVbenModal({
|
||||
connectedComponent: Form,
|
||||
destroyOnClose: true,
|
||||
});
|
||||
const [AllocationModal, allocationModalApi] = useVbenModal({
|
||||
connectedComponent: Allocation,
|
||||
destroyOnClose: true,
|
||||
});
|
||||
const [DetailModal, detailModalApi] = useVbenModal({
|
||||
connectedComponent: Detail,
|
||||
destroyOnClose: true,
|
||||
});
|
||||
|
||||
/** 刷新表格 */
|
||||
function handleRefresh() {
|
||||
@@ -41,6 +51,14 @@ function handleEdit(row: WmsIostorInvApi.IostorInv) {
|
||||
formModalApi.setData(row).open();
|
||||
}
|
||||
|
||||
function handleOutbound(row: WmsIostorInvApi.IostorInv) {
|
||||
allocationModalApi.setData(row).open();
|
||||
}
|
||||
|
||||
function handleView(row: WmsIostorInvApi.IostorInv) {
|
||||
detailModalApi.setData(row).open();
|
||||
}
|
||||
|
||||
/** 删除出入库单主表 */
|
||||
async function handleDelete(row: WmsIostorInvApi.IostorInv) {
|
||||
const hideLoading = message.loading({
|
||||
@@ -73,7 +91,7 @@ async function handleDeleteBatch() {
|
||||
}
|
||||
}
|
||||
|
||||
const checkedIds = ref<number[]>([]);
|
||||
const checkedIds = ref<string[]>([]);
|
||||
function handleRowCheckboxChange({
|
||||
records,
|
||||
}: {
|
||||
@@ -125,9 +143,16 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Page auto-content-height>
|
||||
<Page auto-content-height class="iostorinv-page">
|
||||
<FormModal @success="handleRefresh" />
|
||||
<Grid table-title="出入库单主表列表">
|
||||
<AllocationModal @success="handleRefresh" />
|
||||
<DetailModal />
|
||||
<Grid>
|
||||
<template #billCode="{ row }">
|
||||
<Button type="link" class="p-0" @click="handleView(row)">
|
||||
{{ row.billCode }}
|
||||
</Button>
|
||||
</template>
|
||||
<template #toolbar-tools>
|
||||
<TableAction
|
||||
:actions="[
|
||||
@@ -160,6 +185,13 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
||||
<template #actions="{ row }">
|
||||
<TableAction
|
||||
:actions="[
|
||||
{
|
||||
label: '分配',
|
||||
type: 'link',
|
||||
icon: ACTION_ICON.SEND,
|
||||
auth: ['wms:iostor-inv:update'],
|
||||
onClick: handleOutbound.bind(null, row),
|
||||
},
|
||||
{
|
||||
label: $t('common.edit'),
|
||||
type: 'link',
|
||||
@@ -184,3 +216,23 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
||||
</Grid>
|
||||
</Page>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.iostorinv-page :deep(.vxe-cell--checkbox .vxe-checkbox--icon) {
|
||||
color: #2563eb;
|
||||
filter: drop-shadow(0 0 1px rgb(255 255 255 / 90%));
|
||||
}
|
||||
|
||||
.iostorinv-page
|
||||
:deep(.vxe-body--row.row--hover .vxe-cell--checkbox .vxe-checkbox--icon),
|
||||
.iostorinv-page
|
||||
:deep(.vxe-body--row.row--current .vxe-cell--checkbox .vxe-checkbox--icon) {
|
||||
color: #1d4ed8;
|
||||
filter: drop-shadow(0 0 2px #fff);
|
||||
}
|
||||
|
||||
.iostorinv-page :deep(.vxe-cell--checkbox.is--checked .vxe-checkbox--icon),
|
||||
.iostorinv-page :deep(.vxe-cell--checkbox.is--indeterminate .vxe-checkbox--icon) {
|
||||
color: #1d4ed8;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,304 @@
|
||||
<script lang="ts" setup>
|
||||
import type { WmsIostorInvApi } from '#/api/wms/iostorinv';
|
||||
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
import { useVbenModal } from '@vben/common-ui';
|
||||
|
||||
import { Button, Cascader, message, Space, Table } from 'antdv-next';
|
||||
|
||||
import {
|
||||
allocateAll,
|
||||
autoAllocate,
|
||||
autoCancelAllocation,
|
||||
cancelAllAllocations,
|
||||
getIostorInvDetails,
|
||||
getOutboundAllocations,
|
||||
} from '#/api/wms/iostorinv';
|
||||
import { getSectAttrSimpleList } from '#/api/wms/sectattr';
|
||||
|
||||
import ManualAllocationComponent from './manual-allocation.vue';
|
||||
|
||||
const emit = defineEmits(['success']);
|
||||
const header = ref<WmsIostorInvApi.IostorInv>();
|
||||
const details = ref<WmsIostorInvApi.OutboundDisplayDetail[]>([]);
|
||||
const allocations = ref<WmsIostorInvApi.OutboundAllocation[]>([]);
|
||||
const warehouseSect = ref<string[]>([]);
|
||||
const warehouseSectOptions = ref<any[]>([]);
|
||||
const loading = ref(false);
|
||||
const selectedDetailId = ref<string>();
|
||||
const sectId = computed(() => warehouseSect.value?.[1]);
|
||||
const selectedDetail = computed(() =>
|
||||
details.value.find(
|
||||
(row) => row.iostorinvdtlId?.toString() === selectedDetailId.value,
|
||||
),
|
||||
);
|
||||
const detailRowSelection = computed(() => ({
|
||||
type: 'radio' as const,
|
||||
selectedRowKeys: selectedDetailId.value ? [selectedDetailId.value] : [],
|
||||
onChange: (keys: (number | string)[]) => {
|
||||
selectedDetailId.value = keys[0]?.toString();
|
||||
void loadSelectedAllocations();
|
||||
},
|
||||
}));
|
||||
|
||||
const detailColumns = [
|
||||
{ dataIndex: 'billStatus', title: '状态', width: 90 },
|
||||
{ dataIndex: 'materialCode', title: '物料编码', width: 130 },
|
||||
{ dataIndex: 'materialName', title: '物料名称', width: 170 },
|
||||
{ dataIndex: 'pcsn', title: '批次', width: 130 },
|
||||
{ dataIndex: 'planQty', title: '重量', width: 110 },
|
||||
{ dataIndex: 'assignQty', title: '已分配重量', width: 120 },
|
||||
{ dataIndex: 'unassignQty', title: '未分配重量', width: 120 },
|
||||
{ dataIndex: 'sourceBillCode', title: '源单编号', width: 150 },
|
||||
{ dataIndex: 'remark', title: '备注', width: 160 },
|
||||
];
|
||||
|
||||
const allocationColumns = [
|
||||
{ key: 'index', title: '序号', width: 60 },
|
||||
{ dataIndex: 'workStatus', title: '状态', width: 90 },
|
||||
{ dataIndex: 'materialCode', title: '物料编码', width: 130 },
|
||||
{ dataIndex: 'materialName', title: '物料名称', width: 170 },
|
||||
{ dataIndex: 'storagevehicleCode', title: '箱号', width: 140 },
|
||||
{ dataIndex: 'pcsn', title: '批次', width: 130 },
|
||||
{ dataIndex: 'planQty', title: '出库重量', width: 120 },
|
||||
{ dataIndex: 'structCode', title: '仓位编码', width: 130 },
|
||||
{ dataIndex: 'structName', title: '仓位名称', width: 160 },
|
||||
];
|
||||
|
||||
async function loadWarehouseSectOptions() {
|
||||
if (!header.value?.storId) {
|
||||
warehouseSectOptions.value = [];
|
||||
return;
|
||||
}
|
||||
const currentHeader = header.value;
|
||||
const sects = await getSectAttrSimpleList(currentHeader.storId);
|
||||
warehouseSectOptions.value = [
|
||||
{
|
||||
label: currentHeader.storName || currentHeader.storCode || '当前仓库',
|
||||
value: currentHeader.storId,
|
||||
children: (sects || []).map((sect) => ({
|
||||
label: sect.sectName,
|
||||
value: sect.sectId,
|
||||
})),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
async function loadDetails() {
|
||||
if (!header.value) return;
|
||||
details.value =
|
||||
(await getIostorInvDetails(header.value.iostorinvId)) || [];
|
||||
}
|
||||
|
||||
async function loadSelectedAllocations() {
|
||||
if (!header.value || !selectedDetailId.value) {
|
||||
allocations.value = [];
|
||||
return;
|
||||
}
|
||||
allocations.value =
|
||||
(await getOutboundAllocations(
|
||||
header.value.iostorinvId,
|
||||
selectedDetailId.value,
|
||||
sectId.value,
|
||||
)) || [];
|
||||
}
|
||||
|
||||
async function handleSectChange() {
|
||||
await loadSelectedAllocations();
|
||||
}
|
||||
|
||||
async function handleAllocateAll() {
|
||||
if (!header.value) return;
|
||||
loading.value = true;
|
||||
try {
|
||||
await allocateAll(header.value.iostorinvId, sectId.value);
|
||||
await loadDetails();
|
||||
await loadSelectedAllocations();
|
||||
emit('success');
|
||||
message.success('全部分配完成');
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function handleCancelAll() {
|
||||
if (!header.value) return;
|
||||
loading.value = true;
|
||||
try {
|
||||
await cancelAllAllocations(header.value.iostorinvId, sectId.value);
|
||||
await loadDetails();
|
||||
await loadSelectedAllocations();
|
||||
emit('success');
|
||||
message.success('已取消生成状态的分配明细');
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function requireSelectedDetail() {
|
||||
if (selectedDetailId.value) return true;
|
||||
message.warning('请先选择一条出库明细');
|
||||
return false;
|
||||
}
|
||||
|
||||
async function handleAutoAllocate() {
|
||||
if (!header.value || !requireSelectedDetail()) return;
|
||||
loading.value = true;
|
||||
try {
|
||||
await autoAllocate(
|
||||
header.value.iostorinvId,
|
||||
selectedDetailId.value!,
|
||||
sectId.value,
|
||||
);
|
||||
await loadDetails();
|
||||
await loadSelectedAllocations();
|
||||
emit('success');
|
||||
message.success('自动分配完成');
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function handleAutoCancel() {
|
||||
if (!header.value || !requireSelectedDetail()) return;
|
||||
loading.value = true;
|
||||
try {
|
||||
await autoCancelAllocation(
|
||||
header.value.iostorinvId,
|
||||
selectedDetailId.value!,
|
||||
sectId.value,
|
||||
);
|
||||
await loadDetails();
|
||||
await loadSelectedAllocations();
|
||||
emit('success');
|
||||
message.success('已取消当前明细的生成状态分配记录');
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
const [ManualAllocation, manualAllocationApi] = useVbenModal({
|
||||
connectedComponent: ManualAllocationComponent,
|
||||
destroyOnClose: true,
|
||||
});
|
||||
|
||||
function handleManualAllocate() {
|
||||
if (!header.value || !requireSelectedDetail() || !selectedDetail.value) return;
|
||||
if (!['10', '20'].includes(selectedDetail.value.billStatus || '')) {
|
||||
message.warning('只有生成或分配中的明细允许手工分配');
|
||||
return;
|
||||
}
|
||||
manualAllocationApi
|
||||
.setData({
|
||||
detail: selectedDetail.value,
|
||||
header: header.value,
|
||||
sectId: sectId.value,
|
||||
})
|
||||
.open();
|
||||
}
|
||||
|
||||
async function handleManualSuccess() {
|
||||
await loadDetails();
|
||||
await loadSelectedAllocations();
|
||||
emit('success');
|
||||
}
|
||||
|
||||
function todo(name: string) {
|
||||
message.info(`${name}功能待开发`);
|
||||
}
|
||||
|
||||
function statusLabel(value?: string) {
|
||||
const labels: Record<string, string> = {
|
||||
'10': '生成',
|
||||
'20': '分配中',
|
||||
'30': '分配完',
|
||||
'99': '完成',
|
||||
};
|
||||
return labels[value || ''] || value;
|
||||
}
|
||||
|
||||
const [Modal, modalApi] = useVbenModal({
|
||||
footer: false,
|
||||
fullscreen: true,
|
||||
async onOpenChange(open) {
|
||||
if (!open) return;
|
||||
header.value = modalApi.getData<WmsIostorInvApi.IostorInv>();
|
||||
warehouseSect.value = [];
|
||||
selectedDetailId.value = undefined;
|
||||
allocations.value = [];
|
||||
loading.value = true;
|
||||
try {
|
||||
await Promise.all([loadWarehouseSectOptions(), loadDetails()]);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal class="w-[1600px] max-w-[98vw]" title="出库分配">
|
||||
<div class="px-3 pb-3">
|
||||
<div class="mb-3 flex flex-wrap items-center justify-between gap-3">
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="text-base font-medium">出库明细</span>
|
||||
<span>库区</span>
|
||||
<Cascader
|
||||
v-model:value="warehouseSect"
|
||||
class="w-64"
|
||||
:options="warehouseSectOptions"
|
||||
placeholder="请选择仓库和库区"
|
||||
@change="handleSectChange"
|
||||
/>
|
||||
</div>
|
||||
<Space wrap>
|
||||
<Button type="primary" :loading="loading" @click="handleAllocateAll">全部分配</Button>
|
||||
<Button type="primary" :loading="loading" @click="handleCancelAll">全部取消</Button>
|
||||
<Button :loading="loading" @click="handleAutoAllocate">自动分配</Button>
|
||||
<Button :loading="loading" @click="handleAutoCancel">自动取消</Button>
|
||||
<Button @click="handleManualAllocate">手工分配</Button>
|
||||
<Button type="primary" ghost @click="todo('一键设置')">一键设置</Button>
|
||||
</Space>
|
||||
</div>
|
||||
|
||||
<Table
|
||||
bordered
|
||||
:columns="detailColumns"
|
||||
:data-source="details"
|
||||
:loading="loading"
|
||||
:pagination="false"
|
||||
:row-selection="detailRowSelection"
|
||||
row-key="iostorinvdtlId"
|
||||
:scroll="{ x: 1250, y: 330 }"
|
||||
size="small"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.dataIndex === 'billStatus'">
|
||||
{{ statusLabel(record.billStatus) }}
|
||||
</template>
|
||||
</template>
|
||||
</Table>
|
||||
|
||||
<div class="mb-2 mt-4 text-base font-medium">分配明细</div>
|
||||
<Table
|
||||
bordered
|
||||
:columns="allocationColumns"
|
||||
:data-source="allocations"
|
||||
:loading="loading"
|
||||
:pagination="false"
|
||||
row-key="iostorinvdisId"
|
||||
:scroll="{ x: 1320, y: 260 }"
|
||||
size="small"
|
||||
>
|
||||
<template #bodyCell="{ column, index, record }">
|
||||
<template v-if="column.key === 'index'">{{ index + 1 }}</template>
|
||||
<template v-else-if="column.dataIndex === 'workStatus'">
|
||||
{{ statusLabel(record.workStatus) }}
|
||||
</template>
|
||||
</template>
|
||||
</Table>
|
||||
</div>
|
||||
<ManualAllocation @success="handleManualSuccess" />
|
||||
</Modal>
|
||||
</template>
|
||||
@@ -0,0 +1,137 @@
|
||||
<script lang="ts" setup>
|
||||
import type { WmsIostorInvApi } from '#/api/wms/iostorinv';
|
||||
|
||||
import { ref } from 'vue';
|
||||
|
||||
import { useVbenModal } from '@vben/common-ui';
|
||||
|
||||
import { Descriptions, DescriptionsItem, Table } from 'antdv-next';
|
||||
|
||||
import {
|
||||
getIostorInv,
|
||||
getIostorInvDetails,
|
||||
getOutboundAllocations,
|
||||
} from '#/api/wms/iostorinv';
|
||||
|
||||
const header = ref<WmsIostorInvApi.IostorInv>();
|
||||
const details = ref<WmsIostorInvApi.OutboundDisplayDetail[]>([]);
|
||||
const allocations = ref<WmsIostorInvApi.OutboundAllocation[]>([]);
|
||||
const loading = ref(false);
|
||||
|
||||
const detailColumns = [
|
||||
{ key: 'index', title: '序号', width: 60 },
|
||||
{ dataIndex: 'billStatus', title: '状态', width: 90 },
|
||||
{ dataIndex: 'materialCode', title: '物料编码', width: 130 },
|
||||
{ dataIndex: 'materialName', title: '物料名称', width: 170 },
|
||||
{ dataIndex: 'pcsn', title: '批次', width: 140 },
|
||||
{ dataIndex: 'planQty', title: '计划重量', width: 110 },
|
||||
{ dataIndex: 'assignQty', title: '已分配重量', width: 120 },
|
||||
{ dataIndex: 'unassignQty', title: '未分配重量', width: 120 },
|
||||
{ dataIndex: 'sourceBillCode', title: '来源单号', width: 150 },
|
||||
{ dataIndex: 'remark', title: '备注', width: 160 },
|
||||
];
|
||||
|
||||
const allocationColumns = [
|
||||
{ key: 'index', title: '序号', width: 60 },
|
||||
{ dataIndex: 'workStatus', title: '状态', width: 90 },
|
||||
{ dataIndex: 'materialCode', title: '物料编码', width: 130 },
|
||||
{ dataIndex: 'materialName', title: '物料名称', width: 170 },
|
||||
{ dataIndex: 'storagevehicleCode', title: '箱号', width: 150 },
|
||||
{ dataIndex: 'pcsn', title: '批次', width: 140 },
|
||||
{ dataIndex: 'planQty', title: '出库重量', width: 120 },
|
||||
{ dataIndex: 'sectName', title: '库区', width: 130 },
|
||||
{ dataIndex: 'structCode', title: '仓位编码', width: 130 },
|
||||
{ dataIndex: 'structName', title: '仓位名称', width: 160 },
|
||||
];
|
||||
|
||||
function statusLabel(value?: string) {
|
||||
const labels: Record<string, string> = {
|
||||
'10': '生成',
|
||||
'20': '分配中',
|
||||
'30': '分配完',
|
||||
'99': '完成',
|
||||
};
|
||||
return labels[value || ''] || value || '-';
|
||||
}
|
||||
|
||||
const [Modal, modalApi] = useVbenModal({
|
||||
footer: false,
|
||||
async onOpenChange(open) {
|
||||
if (!open) return;
|
||||
const row = modalApi.getData<WmsIostorInvApi.IostorInv>();
|
||||
if (!row?.iostorinvId) return;
|
||||
loading.value = true;
|
||||
details.value = [];
|
||||
allocations.value = [];
|
||||
try {
|
||||
const [headerResult, detailResult, allocationResult] = await Promise.all([
|
||||
getIostorInv(row.iostorinvId),
|
||||
getIostorInvDetails(row.iostorinvId),
|
||||
getOutboundAllocations(row.iostorinvId),
|
||||
]);
|
||||
header.value = headerResult;
|
||||
details.value = detailResult || [];
|
||||
allocations.value = allocationResult || [];
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal class="w-[1500px] max-w-[98vw]" title="出入库单详情">
|
||||
<div class="px-3 pb-3">
|
||||
<Descriptions bordered class="mb-4" size="small" :column="4">
|
||||
<DescriptionsItem label="单据编号">{{ header?.billCode }}</DescriptionsItem>
|
||||
<DescriptionsItem label="仓库">{{ header?.storName }}</DescriptionsItem>
|
||||
<DescriptionsItem label="业务日期">{{ header?.bizDate }}</DescriptionsItem>
|
||||
<DescriptionsItem label="单据状态">
|
||||
{{ statusLabel(header?.billStatus) }}
|
||||
</DescriptionsItem>
|
||||
<DescriptionsItem label="总重量">{{ header?.totalWeight }}</DescriptionsItem>
|
||||
<DescriptionsItem label="明细数">{{ header?.detailCount }}</DescriptionsItem>
|
||||
<DescriptionsItem label="创建人">{{ header?.creatorName }}</DescriptionsItem>
|
||||
<DescriptionsItem label="备注">{{ header?.remark }}</DescriptionsItem>
|
||||
</Descriptions>
|
||||
|
||||
<div class="mb-2 text-base font-medium">单据明细</div>
|
||||
<Table
|
||||
bordered
|
||||
:columns="detailColumns"
|
||||
:data-source="details"
|
||||
:loading="loading"
|
||||
:pagination="false"
|
||||
row-key="iostorinvdtlId"
|
||||
:scroll="{ x: 1250, y: 300 }"
|
||||
size="small"
|
||||
>
|
||||
<template #bodyCell="{ column, index, record }">
|
||||
<template v-if="column.key === 'index'">{{ index + 1 }}</template>
|
||||
<template v-else-if="column.dataIndex === 'billStatus'">
|
||||
{{ statusLabel(record.billStatus) }}
|
||||
</template>
|
||||
</template>
|
||||
</Table>
|
||||
|
||||
<div class="mb-2 mt-4 text-base font-medium">分配明细</div>
|
||||
<Table
|
||||
bordered
|
||||
:columns="allocationColumns"
|
||||
:data-source="allocations"
|
||||
:loading="loading"
|
||||
:pagination="false"
|
||||
row-key="iostorinvdisId"
|
||||
:scroll="{ x: 1350, y: 300 }"
|
||||
size="small"
|
||||
>
|
||||
<template #bodyCell="{ column, index, record }">
|
||||
<template v-if="column.key === 'index'">{{ index + 1 }}</template>
|
||||
<template v-else-if="column.dataIndex === 'workStatus'">
|
||||
{{ statusLabel(record.workStatus) }}
|
||||
</template>
|
||||
</template>
|
||||
</Table>
|
||||
</div>
|
||||
</Modal>
|
||||
</template>
|
||||
@@ -4,6 +4,8 @@ import type { WmsIostorInvApi } from '#/api/wms/iostorinv';
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
import { useVbenModal } from '@vben/common-ui';
|
||||
import { DICT_TYPE } from '@vben/constants';
|
||||
import { getDictOptions } from '@vben/hooks';
|
||||
|
||||
import { Button, Input, InputNumber, message, Space, Table } from 'antdv-next';
|
||||
import dayjs from 'dayjs';
|
||||
@@ -28,8 +30,6 @@ import {
|
||||
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>();
|
||||
@@ -235,6 +235,7 @@ function validateDetails() {
|
||||
}
|
||||
|
||||
const [Modal, modalApi] = useVbenModal({
|
||||
fullscreen: true,
|
||||
async onConfirm() {
|
||||
const { valid } = await outboundFormApi.validate();
|
||||
if (!valid || !validateDetails()) return;
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
<script lang="ts" setup>
|
||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
import type { WmsIostorInvApi } from '#/api/wms/iostorinv';
|
||||
|
||||
import { ref } from 'vue';
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
import { useVbenModal } from '@vben/common-ui';
|
||||
|
||||
import { message } from 'antdv-next';
|
||||
import { Button, Input, message, Pagination, Space, Table } from 'antdv-next';
|
||||
|
||||
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||
import {
|
||||
expandAvailableInventory,
|
||||
getAvailableInventoryPage,
|
||||
@@ -17,112 +15,111 @@ import {
|
||||
import {
|
||||
collectVehicleCodes,
|
||||
mapExpandedInventory,
|
||||
mergeInventorySelections,
|
||||
} from './detail-builders';
|
||||
|
||||
type InventoryGridRow = WmsIostorInvApi.AvailableInventory;
|
||||
type InventoryRow = WmsIostorInvApi.AvailableInventory;
|
||||
|
||||
const emit = defineEmits<{
|
||||
select: [rows: WmsIostorInvApi.OutboundDisplayDetail[]];
|
||||
}>();
|
||||
|
||||
const storId = ref('');
|
||||
const modalOpen = ref(false);
|
||||
let openToken = 0;
|
||||
let pageAbortController: AbortController | undefined;
|
||||
const rows = ref<InventoryRow[]>([]);
|
||||
const total = ref(0);
|
||||
const pageNo = ref(1);
|
||||
const pageSize = ref(10);
|
||||
const loading = ref(false);
|
||||
const materialCode = ref('');
|
||||
const vehicleCode = ref('');
|
||||
const pcsn = ref('');
|
||||
const selectedKeys = ref<string[]>([]);
|
||||
const selectedRows = new Map<string, InventoryRow>();
|
||||
let requestSequence = 0;
|
||||
let requestController: AbortController | undefined;
|
||||
|
||||
function abortPageRequest() {
|
||||
pageAbortController?.abort();
|
||||
pageAbortController = undefined;
|
||||
const columns = [
|
||||
{ dataIndex: 'materialCode', title: '物料编码', width: 140 },
|
||||
{ dataIndex: 'materialName', title: '物料名称', width: 180 },
|
||||
{ dataIndex: 'vehicleCode', title: '箱号', width: 160 },
|
||||
{ dataIndex: 'pcsn', title: '子卷号', width: 150 },
|
||||
{ dataIndex: 'availableQty', title: '可用重量', width: 120 },
|
||||
{ dataIndex: 'qtyUnitName', title: '单位', width: 90 },
|
||||
];
|
||||
|
||||
async function loadInventory() {
|
||||
if (!storId.value) {
|
||||
rows.value = [];
|
||||
total.value = 0;
|
||||
return;
|
||||
}
|
||||
const sequence = ++requestSequence;
|
||||
requestController?.abort();
|
||||
const controller = new AbortController();
|
||||
requestController = controller;
|
||||
loading.value = true;
|
||||
try {
|
||||
const result = await getAvailableInventoryPage(
|
||||
{
|
||||
materialCode: materialCode.value || undefined,
|
||||
pageNo: pageNo.value,
|
||||
pageSize: pageSize.value,
|
||||
pcsn: pcsn.value || undefined,
|
||||
storId: storId.value,
|
||||
vehicleCode: vehicleCode.value || undefined,
|
||||
},
|
||||
controller.signal,
|
||||
);
|
||||
if (sequence !== requestSequence) return;
|
||||
rows.value = result?.list || [];
|
||||
total.value = result?.total || 0;
|
||||
} catch (error) {
|
||||
if ((error as Error).name !== 'AbortError') throw error;
|
||||
} finally {
|
||||
if (sequence === requestSequence) loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function clearGridState(token: number) {
|
||||
await gridApi.grid.clearCheckboxRow();
|
||||
if (token !== openToken) return;
|
||||
await gridApi.grid.clearCheckboxReserve();
|
||||
if (token !== openToken) return;
|
||||
await gridApi.grid.loadData([]);
|
||||
if (token !== openToken) return;
|
||||
async function handleSearch() {
|
||||
pageNo.value = 1;
|
||||
await loadInventory();
|
||||
}
|
||||
|
||||
const [Grid, gridApi] = useVbenVxeGrid({
|
||||
formOptions: {
|
||||
schema: [
|
||||
{
|
||||
component: 'Input',
|
||||
componentProps: { allowClear: true, placeholder: '请输入物料编码' },
|
||||
fieldName: 'materialCode',
|
||||
label: '物料编码',
|
||||
},
|
||||
{
|
||||
component: 'Input',
|
||||
componentProps: { allowClear: true, placeholder: '请输入箱号' },
|
||||
fieldName: 'vehicleCode',
|
||||
label: '箱号',
|
||||
},
|
||||
{
|
||||
component: 'Input',
|
||||
componentProps: { allowClear: true, placeholder: '请输入子卷号' },
|
||||
fieldName: 'pcsn',
|
||||
label: '子卷号',
|
||||
},
|
||||
],
|
||||
async function handlePageChange(current: number, size: number) {
|
||||
pageNo.value = current;
|
||||
pageSize.value = size;
|
||||
await loadInventory();
|
||||
}
|
||||
|
||||
const rowSelection = computed(() => ({
|
||||
preserveSelectedRowKeys: true,
|
||||
selectedRowKeys: selectedKeys.value,
|
||||
onSelect(record: InventoryRow, selected: boolean) {
|
||||
const key = record.groupId.toString();
|
||||
if (selected) {
|
||||
selectedRows.set(key, record);
|
||||
if (!selectedKeys.value.includes(key)) selectedKeys.value.push(key);
|
||||
} else {
|
||||
selectedRows.delete(key);
|
||||
selectedKeys.value = selectedKeys.value.filter((item) => item !== key);
|
||||
}
|
||||
},
|
||||
gridOptions: {
|
||||
columns: [
|
||||
{ type: 'checkbox', width: 44 },
|
||||
{ field: 'materialCode', minWidth: 130, title: '物料编码' },
|
||||
{ field: 'materialName', minWidth: 160, title: '物料名称' },
|
||||
{ field: 'vehicleCode', minWidth: 130, title: '箱号' },
|
||||
{ field: 'pcsn', minWidth: 130, title: '子卷号' },
|
||||
{ field: 'availableQty', minWidth: 110, title: '可用重量' },
|
||||
{ field: 'qtyUnitName', minWidth: 90, title: '单位' },
|
||||
],
|
||||
height: 'auto',
|
||||
keepSource: true,
|
||||
checkboxConfig: { highlight: true, reserve: true },
|
||||
proxyConfig: {
|
||||
ajax: {
|
||||
query: async ({ page }, formValues) => {
|
||||
const requestToken = openToken;
|
||||
const requestStorId = storId.value;
|
||||
if (!modalOpen.value || !requestStorId) {
|
||||
throw new DOMException('库存选择弹窗已关闭', 'AbortError');
|
||||
}
|
||||
abortPageRequest();
|
||||
const controller = new AbortController();
|
||||
pageAbortController = controller;
|
||||
const result = await getAvailableInventoryPage({
|
||||
...formValues,
|
||||
pageNo: page.currentPage,
|
||||
pageSize: page.pageSize,
|
||||
storId: requestStorId,
|
||||
}, controller.signal);
|
||||
if (
|
||||
requestToken !== openToken ||
|
||||
!modalOpen.value ||
|
||||
requestStorId !== storId.value
|
||||
) {
|
||||
throw new DOMException('库存分页请求已失效', 'AbortError');
|
||||
}
|
||||
if (pageAbortController === controller) pageAbortController = undefined;
|
||||
return result;
|
||||
},
|
||||
},
|
||||
},
|
||||
rowConfig: { isHover: true, keyField: 'groupId' },
|
||||
toolbarConfig: { refresh: true, search: true },
|
||||
} as VxeTableGridOptions<InventoryGridRow>,
|
||||
});
|
||||
onSelectAll(selected: boolean, _selected: InventoryRow[], changed: InventoryRow[]) {
|
||||
changed.forEach((record) => {
|
||||
const key = record.groupId.toString();
|
||||
if (selected) {
|
||||
selectedRows.set(key, record);
|
||||
if (!selectedKeys.value.includes(key)) selectedKeys.value.push(key);
|
||||
} else {
|
||||
selectedRows.delete(key);
|
||||
selectedKeys.value = selectedKeys.value.filter((item) => item !== key);
|
||||
}
|
||||
});
|
||||
},
|
||||
}));
|
||||
|
||||
const [Modal, modalApi] = useVbenModal({
|
||||
async onConfirm() {
|
||||
const confirmToken = openToken;
|
||||
const confirmStorId = storId.value;
|
||||
if (!modalOpen.value || !confirmStorId) return;
|
||||
const selected = mergeInventorySelections(
|
||||
gridApi.grid.getCheckboxRecords() as InventoryGridRow[],
|
||||
gridApi.grid.getCheckboxReserveRecords() as InventoryGridRow[],
|
||||
);
|
||||
const selected = [...selectedRows.values()];
|
||||
if (selected.length === 0) {
|
||||
message.warning('请至少选择一条库存信息');
|
||||
return;
|
||||
@@ -137,16 +134,9 @@ const [Modal, modalApi] = useVbenModal({
|
||||
modalApi.lock();
|
||||
try {
|
||||
const expanded = await expandAvailableInventory({
|
||||
storId: confirmStorId,
|
||||
storId: storId.value,
|
||||
vehicleCodes,
|
||||
});
|
||||
if (
|
||||
confirmToken !== openToken ||
|
||||
!modalOpen.value ||
|
||||
confirmStorId !== storId.value
|
||||
) {
|
||||
return;
|
||||
}
|
||||
emit('select', mapExpandedInventory(expanded));
|
||||
await modalApi.close();
|
||||
} finally {
|
||||
@@ -154,32 +144,84 @@ const [Modal, modalApi] = useVbenModal({
|
||||
}
|
||||
},
|
||||
async onOpenChange(open) {
|
||||
const token = ++openToken;
|
||||
abortPageRequest();
|
||||
requestController?.abort();
|
||||
if (!open) {
|
||||
modalOpen.value = false;
|
||||
storId.value = '';
|
||||
await clearGridState(token);
|
||||
++requestSequence;
|
||||
return;
|
||||
}
|
||||
modalOpen.value = true;
|
||||
storId.value = '';
|
||||
await clearGridState(token);
|
||||
if (token !== openToken || !modalOpen.value) return;
|
||||
const data = modalApi.getData<{ storId: string }>();
|
||||
if (!data?.storId) {
|
||||
message.warning('请先选择仓库');
|
||||
await modalApi.close();
|
||||
return;
|
||||
}
|
||||
storId.value = data.storId;
|
||||
await gridApi.query();
|
||||
storId.value = data.storId.toString();
|
||||
materialCode.value = '';
|
||||
vehicleCode.value = '';
|
||||
pcsn.value = '';
|
||||
pageNo.value = 1;
|
||||
pageSize.value = 10;
|
||||
selectedKeys.value = [];
|
||||
selectedRows.clear();
|
||||
await loadInventory();
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal class="w-[1100px]" title="选择可用库存">
|
||||
<Grid />
|
||||
<Modal class="w-[1200px] max-w-[96vw]" title="选择可用库存">
|
||||
<div class="px-3 pb-3">
|
||||
<Space class="mb-3" wrap>
|
||||
<Input
|
||||
v-model:value="materialCode"
|
||||
allow-clear
|
||||
class="w-48"
|
||||
placeholder="物料编码"
|
||||
@press-enter="handleSearch"
|
||||
/>
|
||||
<Input
|
||||
v-model:value="vehicleCode"
|
||||
allow-clear
|
||||
class="w-48"
|
||||
placeholder="箱号"
|
||||
@press-enter="handleSearch"
|
||||
/>
|
||||
<Input
|
||||
v-model:value="pcsn"
|
||||
allow-clear
|
||||
class="w-48"
|
||||
placeholder="子卷号"
|
||||
@press-enter="handleSearch"
|
||||
/>
|
||||
<Button :loading="loading" type="primary" @click="handleSearch">
|
||||
查询
|
||||
</Button>
|
||||
</Space>
|
||||
|
||||
<Table
|
||||
bordered
|
||||
:columns="columns"
|
||||
:data-source="rows"
|
||||
:loading="loading"
|
||||
:pagination="false"
|
||||
:row-selection="rowSelection"
|
||||
row-key="groupId"
|
||||
:scroll="{ x: 950, y: 480 }"
|
||||
size="small"
|
||||
/>
|
||||
|
||||
<div class="mt-3 flex justify-end">
|
||||
<Pagination
|
||||
:current="pageNo"
|
||||
:page-size="pageSize"
|
||||
:page-size-options="['10', '20', '50']"
|
||||
show-size-changer
|
||||
:show-total="(value: number) => `共 ${value} 条`"
|
||||
:total="total"
|
||||
@change="handlePageChange"
|
||||
@show-size-change="handlePageChange"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
@@ -0,0 +1,173 @@
|
||||
<script lang="ts" setup>
|
||||
import type { WmsIostorInvApi } from '#/api/wms/iostorinv';
|
||||
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
import { useVbenModal } from '@vben/common-ui';
|
||||
|
||||
import { Input, message, Space, Table } from 'antdv-next';
|
||||
|
||||
import {
|
||||
getManualAllocationInventory,
|
||||
manualAllocate,
|
||||
} from '#/api/wms/iostorinv';
|
||||
|
||||
type OpenData = {
|
||||
detail: WmsIostorInvApi.OutboundDisplayDetail;
|
||||
header: WmsIostorInvApi.IostorInv;
|
||||
sectId?: string;
|
||||
};
|
||||
|
||||
const emit = defineEmits(['success']);
|
||||
const openData = ref<OpenData>();
|
||||
const rows = ref<WmsIostorInvApi.ManualAllocationInventory[]>([]);
|
||||
const selectedKeys = ref<string[]>([]);
|
||||
const keyword = ref('');
|
||||
const pcsn = ref('');
|
||||
const loading = ref(false);
|
||||
|
||||
const filteredRows = computed(() => {
|
||||
const keywordValue = keyword.value.trim().toLowerCase();
|
||||
const pcsnValue = pcsn.value.trim().toLowerCase();
|
||||
return rows.value.filter((row) => {
|
||||
const matchesKeyword =
|
||||
!keywordValue ||
|
||||
[row.vehicleCode, row.materialCode, row.materialName, row.structCode]
|
||||
.some((value) => value?.toLowerCase().includes(keywordValue));
|
||||
const matchesPcsn =
|
||||
!pcsnValue || row.pcsn?.toLowerCase().includes(pcsnValue);
|
||||
return matchesKeyword && matchesPcsn;
|
||||
});
|
||||
});
|
||||
|
||||
const selectedWeight = computed(() => {
|
||||
const selected = new Set(selectedKeys.value);
|
||||
return rows.value
|
||||
.filter((row) => selected.has(row.groupId.toString()))
|
||||
.reduce((sum, row) => sum + Number(row.availableQty || 0), 0);
|
||||
});
|
||||
|
||||
const rowSelection = computed(() => ({
|
||||
selectedRowKeys: selectedKeys.value,
|
||||
onSelect: (
|
||||
record: WmsIostorInvApi.ManualAllocationInventory,
|
||||
selected: boolean,
|
||||
) => {
|
||||
const vehicleKeys = rows.value
|
||||
.filter((row) => row.vehicleCode === record.vehicleCode)
|
||||
.map((row) => row.groupId.toString());
|
||||
const next = new Set(selectedKeys.value);
|
||||
vehicleKeys.forEach((key) => (selected ? next.add(key) : next.delete(key)));
|
||||
selectedKeys.value = [...next];
|
||||
},
|
||||
onSelectAll: (selected: boolean) => {
|
||||
const visibleKeys = filteredRows.value.map((row) => row.groupId.toString());
|
||||
const next = new Set(selectedKeys.value);
|
||||
visibleKeys.forEach((key) => (selected ? next.add(key) : next.delete(key)));
|
||||
selectedKeys.value = [...next];
|
||||
},
|
||||
}));
|
||||
|
||||
const columns = [
|
||||
{ key: 'index', title: '序号', width: 65 },
|
||||
{ dataIndex: 'sectName', title: '库区', width: 120 },
|
||||
{ dataIndex: 'structCode', title: '仓位', width: 130 },
|
||||
{ dataIndex: 'vehicleCode', title: '箱号', width: 150 },
|
||||
{ dataIndex: 'materialName', title: '物料名称', width: 180 },
|
||||
{ dataIndex: 'pcsn', title: '子卷号', width: 150 },
|
||||
{ dataIndex: 'availableQty', title: '可出重量', width: 120 },
|
||||
{ dataIndex: 'qtyUnitName', title: '单位', width: 90 },
|
||||
];
|
||||
|
||||
const [Modal, modalApi] = useVbenModal({
|
||||
async onConfirm() {
|
||||
if (!openData.value || selectedKeys.value.length === 0) {
|
||||
message.warning('请至少选择一箱库存');
|
||||
return;
|
||||
}
|
||||
modalApi.lock();
|
||||
try {
|
||||
await manualAllocate({
|
||||
groupIds: selectedKeys.value,
|
||||
iostorinvId: openData.value.header.iostorinvId,
|
||||
iostorinvdtlId: openData.value.detail.iostorinvdtlId!,
|
||||
sectId: openData.value.sectId,
|
||||
});
|
||||
emit('success');
|
||||
message.success('手工分配保存成功');
|
||||
await modalApi.close();
|
||||
} finally {
|
||||
modalApi.unlock();
|
||||
}
|
||||
},
|
||||
async onOpenChange(open) {
|
||||
if (!open) return;
|
||||
openData.value = modalApi.getData<OpenData>();
|
||||
selectedKeys.value = [];
|
||||
keyword.value = '';
|
||||
pcsn.value = '';
|
||||
loading.value = true;
|
||||
try {
|
||||
rows.value =
|
||||
(await getManualAllocationInventory(
|
||||
openData.value.header.iostorinvId,
|
||||
openData.value.detail.iostorinvdtlId!,
|
||||
openData.value.sectId,
|
||||
)) || [];
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal
|
||||
class="w-[1500px] max-w-[98vw]"
|
||||
confirm-text="保存"
|
||||
title="出库手工分配"
|
||||
>
|
||||
<div class="px-3 pb-3">
|
||||
<Space class="mb-3" wrap>
|
||||
<span class="text-base font-medium">可分配库存</span>
|
||||
<span>待分配</span>
|
||||
<Input
|
||||
class="w-32"
|
||||
disabled
|
||||
:value="openData?.detail.unassignQty ?? 0"
|
||||
/>
|
||||
<span>已选择</span>
|
||||
<Input class="w-32" disabled :value="selectedWeight" />
|
||||
<span>关键字</span>
|
||||
<Input
|
||||
v-model:value="keyword"
|
||||
allow-clear
|
||||
class="w-56"
|
||||
placeholder="箱号/物料/仓位"
|
||||
/>
|
||||
<span>子卷号</span>
|
||||
<Input
|
||||
v-model:value="pcsn"
|
||||
allow-clear
|
||||
class="w-48"
|
||||
placeholder="请输入子卷号"
|
||||
/>
|
||||
</Space>
|
||||
<Table
|
||||
bordered
|
||||
:columns="columns"
|
||||
:data-source="filteredRows"
|
||||
:loading="loading"
|
||||
:pagination="false"
|
||||
:row-selection="rowSelection"
|
||||
row-key="groupId"
|
||||
:scroll="{ x: 1100, y: 520 }"
|
||||
size="small"
|
||||
>
|
||||
<template #bodyCell="{ column, index }">
|
||||
<template v-if="column.key === 'index'">{{ index + 1 }}</template>
|
||||
</template>
|
||||
</Table>
|
||||
</div>
|
||||
</Modal>
|
||||
</template>
|
||||
Reference in New Issue
Block a user