diff --git a/src/core/navigation/adminNavigation.test.ts b/src/core/navigation/adminNavigation.test.ts index ec14da23..e8eeda8e 100644 --- a/src/core/navigation/adminNavigation.test.ts +++ b/src/core/navigation/adminNavigation.test.ts @@ -48,6 +48,7 @@ describe('adminNavigation', () => { expect(adminNavigation.find((item) => item.key === 'overseas-inventory')?.children?.map((item) => item.title)).toEqual([ '尾程订单', '入库单', + '废弃单', '库位管理', '排车列表', '出库单', @@ -334,3 +335,5 @@ describe('adminNavigation', () => { expect(getFirstNavigationLeaf(nodes)?.path).toBe('/tracking') }) }) + + diff --git a/src/core/navigation/adminNavigation.ts b/src/core/navigation/adminNavigation.ts index 91e825cd..0bc1b7f4 100644 --- a/src/core/navigation/adminNavigation.ts +++ b/src/core/navigation/adminNavigation.ts @@ -114,6 +114,7 @@ export const adminNavigation: AdminNavigationNode[] = [ children: [ { key: 'last-mile-orders', title: '尾程订单', icon: ClipboardList, path: '/overseas-warehouse/last-mile-orders', permission: 'platform:overseas-warehouse:last-mile-order:list' }, { key: 'fba-inbound-order', title: '入库单', icon: ClipboardList, path: '/overseas-warehouse/inbound-orders', permission: 'platform:overseas-warehouse:inbound-order:list' }, + { key: 'fba-cargo-discard', title: '废弃单', icon: ClipboardList, path: '/overseas-warehouse/cargo-discards', permission: 'platform:overseas-warehouse:discard:list' }, { key: 'overseas-locations', title: '库位管理', icon: WarehouseIcon, path: '/overseas-warehouse/locations', permission: 'platform:overseas-warehouse:location:list' }, { key: 'fba-dispatch', title: '排车列表', icon: Truck, path: '/overseas-warehouse/dispatches', permission: 'platform:overseas-warehouse:dispatch:list' }, { key: 'fba-outbound', title: '出库单', icon: ClipboardList, path: '/overseas-warehouse/outbounds', permission: 'platform:overseas-warehouse:outbound:list' }, diff --git a/src/modules/overseas-warehouse/api/cargoDiscards.ts b/src/modules/overseas-warehouse/api/cargoDiscards.ts new file mode 100644 index 00000000..1883512d --- /dev/null +++ b/src/modules/overseas-warehouse/api/cargoDiscards.ts @@ -0,0 +1,26 @@ +import { http, unwrapApiResponse, type ApiEnvelope } from '@/shared/api/http' +import { normalizeOverseasPage, type OverseasPagePayload } from './lastMileOrders' + +export interface CargoDiscardQuery { page: number; size: number; customerId?: string; discardNo?: string; inboundCode?: string; overseasWarehouseId?: string; status?: string } +export interface CargoDiscardSummary { id: string; customerId?: string | null; discardNo: string; inboundCodes?: string | null; overseasWarehouseId?: string | null; overseasWarehouseName?: string | null; totalPackages?: number | null; totalFee?: number | string | null; currency?: string | null; status?: string | null; createdBy?: string | null; createdAt?: string | null; reviewedAt?: string | null } +export interface CargoDiscardItem { id: string; orderId?: string | null; inboundCode?: string | null; skuCode?: string | null; productName?: string | null; availablePackagesSnapshot?: number | null; discardPackages?: number | null } +export interface CargoDiscardDetail { summary: CargoDiscardSummary; feePerPackage?: number | string | null; remark?: string | null; reviewedBy?: string | null; reviewRemark?: string | null; items: CargoDiscardItem[]; histories: { fromStatus?: string | null; toStatus?: string | null; operatorId?: string | null; remark?: string | null; createdAt?: string | null }[] } +export interface CargoDiscardCandidate { orderId?: string | null; sourceType: string; returnCartonId?: string | null; cartonMark?: string | null; inboundCode?: string | null; inboundOrderNo?: string | null; overseasWarehouseId?: string | null; overseasWarehouseName?: string | null; availablePackages?: number; skus: { skuCode: string; productName?: string | null; availablePackages?: number }[] } + +export async function listCargoDiscards(query: CargoDiscardQuery) { + const response = await http.get>, ApiEnvelope>>('/platform/v1/last-leg/cargo-discards', { params: query, headers: query.customerId ? { 'X-Customer-Id': query.customerId } : undefined }) + return normalizeOverseasPage(unwrapApiResponse(response)) +} +export async function getCargoDiscard(discardNo: string, customerId?: string) { + const response = await http.get, ApiEnvelope>(`/platform/v1/last-leg/cargo-discards/${encodeURIComponent(discardNo)}`, { params: customerId ? { customerId } : undefined }) + return unwrapApiResponse(response) +} +export async function reviewCargoDiscard(discardNo: string, decision: 'APPROVE' | 'REJECT', remark?: string, customerId?: string) { + const response = await http.post, ApiEnvelope>(`/platform/v1/last-leg/cargo-discards/${encodeURIComponent(discardNo)}/review`, { decision, remark }, { params: customerId ? { customerId } : undefined }) + return unwrapApiResponse(response) +} +export async function completeCargoDiscard(discardNo: string, customerId?: string) { + const response = await http.post, ApiEnvelope>(`/platform/v1/last-leg/cargo-discards/${encodeURIComponent(discardNo)}/complete`, undefined, { params: customerId ? { customerId } : undefined }) + return unwrapApiResponse(response) +} + diff --git a/src/modules/overseas-warehouse/api/relabelOrders.ts b/src/modules/overseas-warehouse/api/relabelOrders.ts index 33942dc3..0b7b24ab 100644 --- a/src/modules/overseas-warehouse/api/relabelOrders.ts +++ b/src/modules/overseas-warehouse/api/relabelOrders.ts @@ -98,7 +98,7 @@ export interface RelabelItemDto { relabelFileUrl?: string | null shipmentNo?: string | null trackingCode?: string | null - remark?: string | null + remark?: string cartonMark?: string | null fbaCartonMark?: string | null destinationWarehouseId?: string | null @@ -109,7 +109,7 @@ export interface RelabelHistoryDto { fromStatus?: string | null toStatus?: string | null operatorId?: string | null - remark?: string | null + remark?: string createdAt?: string | null } @@ -137,16 +137,24 @@ export interface ReturnPackingPlanBox { export interface ReturnPackingPlanPayload { returnNos: string[] - mode: 'CUSTOMER_INSTRUCTION' | 'WAREHOUSE_RECOMMENDATION' + mode: 'BOX' | 'SKU' | 'CUSTOMER_INSTRUCTION' | 'WAREHOUSE_RECOMMENDATION' boxes: ReturnPackingPlanBox[] remark?: string idempotencyKey: string + sourceCartonMarks?: string[] } -export interface ReturnPackingPlanDto extends ReturnPackingPlanPayload { +export interface ReturnPackingPlanDto extends Omit { planNo: string + customerId?: string + overseasWarehouseId?: string status: string + boxes: ReturnPackingPlanBox[] + remark?: string | null + createdAt?: string | null pushedAt?: string | null + completedAt?: string | null + resultCartons?: import('./returnInventory').ReturnCartonDetail[] } export async function createReturnPackingPlan(payload: ReturnPackingPlanPayload) { @@ -156,6 +164,21 @@ export async function createReturnPackingPlan(payload: ReturnPackingPlanPayload) return unwrapApiResponse(response) as ReturnPackingPlanDto } +export interface ReturnPackingPlanQuery { page: number; size: number; status?: string; planNo?: string; customerId?: string; overseasWarehouseId?: string; skuCode?: string } +export async function listReturnPackingPlans(query: ReturnPackingPlanQuery) { + const response = await http.get>, ApiEnvelope>>('/platform/v1/last-leg/return-packing-plans', { params: query }) + return normalizeOverseasPage(unwrapApiResponse(response)) +} +export async function reviewReturnPackingPlan(planNo: string) { + const response = await http.post, ApiEnvelope>(`/platform/v1/last-leg/return-packing-plans/${encodeURIComponent(planNo)}/review`) + return unwrapApiResponse(response) as ReturnPackingPlanDto +} + +export async function cancelReturnPackingPlan(planNo: string) { + const response = await http.post, ApiEnvelope>(`/platform/v1/last-leg/return-packing-plans/${encodeURIComponent(planNo)}/cancel`) + return unwrapApiResponse(response) as ReturnPackingPlanDto +} + export async function listRelabelOrders(query: RelabelOrderQuery) { const response = await http.get>, ApiEnvelope>>( '/platform/v1/last-leg/relabel-applications', { params: query }, diff --git a/src/modules/overseas-warehouse/api/returnInventory.ts b/src/modules/overseas-warehouse/api/returnInventory.ts index da468685..ffcdeb35 100644 --- a/src/modules/overseas-warehouse/api/returnInventory.ts +++ b/src/modules/overseas-warehouse/api/returnInventory.ts @@ -2,14 +2,17 @@ import { http, unwrapApiResponse, type ApiEnvelope } from '@/shared/api/http' import { normalizeOverseasPage, type OverseasPagePayload } from './lastMileOrders' -export interface ReturnStockRow { customerId: string; overseasWarehouseId: string; skuCode: string; productNameZh?: string; totalQuantity: number; availableQuantity: number; cartonQuantity: number; updatedAt: string } -export interface ReturnCartonRow { customerId: string; overseasWarehouseId: string; cartonMark: string; sourceReturnNo?: string; sourceBatchNo?: string; status: string; fbaCartonMark?: string; destinationWarehouseCode?: string; destinationWarehouseName?: string; relabelNo?: string; relabelStatus?: string; skuTypes: number; totalQuantity: number; length?: number; width?: number; height?: number; weight?: number; createdAt: string } -export interface ReturnCartonDetail extends ReturnCartonRow { customerId: string; items: Array<{ skuCode: string; productNameZh?: string; quantity: number }> } +export interface ReturnStockRow { customerId: string; overseasWarehouseId: string; skuCode: string; productNameZh?: string; totalQuantity: number; availableQuantity: number; looseAvailableQuantity?: number; cartonQuantity: number; updatedAt: string } +export interface ReturnCartonRow { customerId: string; overseasWarehouseId: string; cartonMark: string; sourceReturnNo?: string; sourceUnitType?: 'PALLET' | 'BOX' | 'PARCEL' | string; sourceBatchNo?: string; status: string; fbaCartonMark?: string; destinationWarehouseCode?: string; destinationWarehouseName?: string; relabelNo?: string; relabelStatus?: string; skuTypes: number; totalQuantity: number; length?: number; width?: number; height?: number; weight?: number; createdAt: string } +export interface ReturnCartonMergeHistory { planNo?: string; sourceCartonMark: string; targetCartonMark: string; returnNo: string; skuCode: string; quantity: number; createdAt: string } +export interface ReturnCartonDetail extends ReturnCartonRow { customerId: string; items: Array<{ skuCode: string; productNameZh?: string; quantity: number; availableQuantity?: number }>; mergeHistory?: ReturnCartonMergeHistory[] } export interface ReturnCartonLabel extends ReturnCartonDetail { printableHtml: string } +export interface ReturnPackingSource { cartonMark: string; sourceReturnNo?: string; sourceUnitType?: string; items: ReturnCartonDetail['items'] } export interface ReturnInventoryMovement { customerId: string; overseasWarehouseId: string; skuCode: string; productNameZh?: string; movementType: 'RECEIPT' | 'MERGE' | 'OUTBOUND'; referenceNo: string; totalDelta: number; availableDelta: number; occurredAt: string } export interface ReturnInventoryQuery { page: number; size: number; customerId?: string; overseasWarehouseId?: string; skuCode?: string; cartonMark?: string; returnNo?: string; status?: string } export async function listReturnStock(params: ReturnInventoryQuery) { const response = await http.get>, ApiEnvelope>>('/platform/v1/last-leg/return-inventory', { params }); return normalizeOverseasPage(unwrapApiResponse(response)) } +export async function listReturnPackingSources(params: ReturnInventoryQuery) { const response = await http.get>, ApiEnvelope>>('/platform/v1/last-leg/return-inventory/packing-sources', { params }); return normalizeOverseasPage(unwrapApiResponse(response)) } export async function listReturnCartons(params: ReturnInventoryQuery) { const response = await http.get>, ApiEnvelope>>('/platform/v1/last-leg/return-inventory/cartons', { params }); return normalizeOverseasPage(unwrapApiResponse(response)) } export async function listReturnInventoryMovements(params: ReturnInventoryQuery) { const response = await http.get>, ApiEnvelope>>('/platform/v1/last-leg/return-inventory/movements', { params }); return normalizeOverseasPage(unwrapApiResponse(response)) } export async function getReturnCarton(cartonMark: string) { const response = await http.get, ApiEnvelope>(`/platform/v1/last-leg/return-inventory/cartons/${encodeURIComponent(cartonMark)}`); return unwrapApiResponse(response) } diff --git a/src/modules/overseas-warehouse/pages/CargoDiscardsView.vue b/src/modules/overseas-warehouse/pages/CargoDiscardsView.vue new file mode 100644 index 00000000..fbe8d43c --- /dev/null +++ b/src/modules/overseas-warehouse/pages/CargoDiscardsView.vue @@ -0,0 +1,32 @@ + + + + + diff --git a/src/modules/overseas-warehouse/pages/ReturnInventoryManagementView.test.ts b/src/modules/overseas-warehouse/pages/ReturnInventoryManagementView.test.ts index e8b266ba..28851d13 100644 --- a/src/modules/overseas-warehouse/pages/ReturnInventoryManagementView.test.ts +++ b/src/modules/overseas-warehouse/pages/ReturnInventoryManagementView.test.ts @@ -4,7 +4,8 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import ElementPlus from 'element-plus' import DataTable from '@/shared/components/DataTable.vue' import { UI_PREFERENCE_SCOPE, preferenceStorageKey } from '@/shared/utils/uiLayoutPreference' -import { getReturnCarton, listReturnCartons, listReturnInventoryMovements, listReturnStock } from '../api/returnInventory' +import { getReturnCarton, listReturnCartons, listReturnInventoryMovements, listReturnStock, listReturnPackingSources } from '../api/returnInventory' +import { listReturnPackingPlans } from '../api/relabelOrders' import ReturnInventoryManagementView from './ReturnInventoryManagementView.vue' vi.mock('@/modules/iam', () => ({ @@ -14,6 +15,11 @@ vi.mock('@/modules/iam', () => ({ vi.mock('../api/returnInventory', () => ({ listReturnStock: vi.fn(), listReturnCartons: vi.fn(), listReturnInventoryMovements: vi.fn(), getReturnCarton: vi.fn(), getReturnCartonLabel: vi.fn(), + listReturnPackingSources: vi.fn(), +})) +vi.mock('../api/relabelOrders', () => ({ + listReturnPackingPlans: vi.fn(), createReturnPackingPlan: vi.fn(), + reviewReturnPackingPlan: vi.fn(), cancelReturnPackingPlan: vi.fn(), })) const wrappers: VueWrapper[] = [] const stock = { customerId: 'customer-1', overseasWarehouseId: 'warehouse-1', skuCode: 'SKU-TEST', totalQuantity: 8, availableQuantity: 6, cartonQuantity: 1, updatedAt: '2026-09-30' } @@ -34,6 +40,11 @@ beforeEach(() => { vi.mocked(listReturnCartons).mockResolvedValue({ records: [carton], page: 1, size: 20, total: 1, pages: 1 }) vi.mocked(listReturnInventoryMovements).mockResolvedValue({ records: [{ ...stock, movementType: 'MERGE', referenceNo: 'MERGE-1', totalDelta: -2, availableDelta: -2, occurredAt: '2026-09-30' }], page: 1, size: 20, total: 1, pages: 1 }) vi.mocked(getReturnCarton).mockResolvedValue({ ...carton, items: [{ skuCode: 'SKU-TEST', quantity: 8 }] }) + vi.mocked(listReturnPackingPlans).mockImplementation(async query => ({ + records: query.status ? [] : [{ planNo: 'PLAN-NEW', returnNos: ['RETURN-1'], mode: 'SKU', boxes: [], status: 'PENDING_REVIEW', idempotencyKey: 'plan-key' }], + page: query.page, size: query.size, total: query.status ? 0 : 45, pages: 3, + })) + vi.mocked(listReturnPackingSources).mockResolvedValue({ records: [], page: 1, size: 200, total: 0, pages: 0 }) }) afterEach(() => { wrappers.splice(0).forEach(wrapper => wrapper.unmount()) @@ -41,6 +52,127 @@ afterEach(() => { }) describe('ReturnInventoryManagementView shared list', () => { + it('finds boxes by contained SKU while retaining mixed-box contents and selected cartons', async () => { + vi.mocked(listReturnPackingSources).mockResolvedValue({records:[ + {cartonMark:'BOX-A',sourceUnitType:'BOX',items:[{skuCode:'SKU-APPLE',quantity:2},{skuCode:'SKU-OTHER',quantity:3}]}, + {cartonMark:'BOX-B',sourceUnitType:'BOX',items:[{skuCode:'SKU-BANANA',quantity:4}]}, + ],page:1,size:200,total:2,pages:1}) + const wrapper=await mountView('inventory') + await wrapper.findAll('button').find(button=>button.text().includes('创建合箱计划'))!.trigger('click');await flushPromises() + const form=wrapper.findAllComponents({name:'ElForm'}).find(form=>form.classes().includes('packing-form'))! + const customer=form.getComponent({name:'ElSelect'}) + customer.vm.$emit('update:modelValue','customer-1');customer.vm.$emit('change','customer-1');await flushPromises() + const input=form.get('input[placeholder="按箱内 SKU 查找,多个 SKU 请用英文逗号分隔"]') + await input.setValue('APPLE') + expect(form.text()).toContain('BOX-A');expect(form.text()).toContain('SKU-OTHER');expect(form.text()).not.toContain('BOX-B') + const selection=form.getComponent({name:'ElCheckboxGroup'}) + selection.vm.$emit('update:modelValue',['BOX-A']);await flushPromises() + await input.setValue('APPLE, BANANA') + expect(form.text()).toContain('BOX-A');expect(form.text()).toContain('BOX-B') + await input.setValue('NO-MATCH') + expect(form.text()).toContain('没有包含该 SKU 的可用箱子') + await input.setValue('') + expect(form.getComponent({name:'ElCheckboxGroup'}).props('modelValue')).toContain('BOX-A') + }) + it('queries packing plan numbers through server pagination and clears the filter', async () => { + const wrapper = await mountView('inventory') + const tabs = wrapper.getComponent({ name: 'ElTabs' }) + tabs.vm.$emit('update:modelValue', 'plans'); tabs.vm.$emit('tab-change', 'plans') + await flushPromises() + const selects = wrapper.findAllComponents({ name: 'ElSelect' }).filter(select => ['全部客户','全部海外仓'].includes(select.props('placeholder'))) + expect(selects.map(select => select.props('placeholder'))).toEqual(['全部客户','全部海外仓']) + expect(wrapper.find('input[placeholder="SKU"]').exists()).toBe(true) + await wrapper.get('input[placeholder="合箱计划号"]').setValue(' PLAN-NEW ') + await wrapper.get('.query-panel button').trigger('click'); await flushPromises() + expect(listReturnPackingPlans).toHaveBeenLastCalledWith({page:1,size:20,planNo:'PLAN-NEW'}) + wrapper.getComponent(DataTable).vm.$emit('update:page', 2); await flushPromises() + expect(listReturnPackingPlans).toHaveBeenLastCalledWith({page:2,size:20,planNo:'PLAN-NEW'}) + await wrapper.get('input[placeholder="合箱计划号"]').setValue('') + await wrapper.get('.query-panel button').trigger('click'); await flushPromises() + expect(listReturnPackingPlans).toHaveBeenLastCalledWith({page:1,size:20}) + selects[0]!.vm.$emit('update:modelValue','customer-1') + selects[1]!.vm.$emit('update:modelValue','warehouse-1') + await wrapper.get('input[placeholder="SKU"]').setValue(' SKU-TEST ') + await wrapper.get('.query-panel button').trigger('click'); await flushPromises() + expect(listReturnPackingPlans).toHaveBeenLastCalledWith({page:1,size:20,customerId:'customer-1',overseasWarehouseId:'warehouse-1',skuCode:'SKU-TEST'}) + }) + it('shows source carton, return ownership and moved quantities in carton merge history', async () => { + vi.mocked(getReturnCarton).mockResolvedValue({ ...carton, items: [{ skuCode: 'SKU-TEST', quantity: 8 }], mergeHistory: [{planNo:'PLAN-MERGED',sourceCartonMark:'SOURCE-BOX',targetCartonMark:carton.cartonMark,returnNo:'RETURN-SOURCE',skuCode:'SKU-TEST',quantity:2,createdAt:'2026-10-08T14:00:00'}] }) + const wrapper = await mountView('cartons') + await wrapper.findAll('button').find(button => button.text() === '详情')!.trigger('click') + await flushPromises() + expect(document.body.textContent).toContain('合箱明细') + expect(document.body.textContent).toContain('SOURCE-BOX') + expect(document.body.textContent).toContain('RETURN-SOURCE') + expect(document.body.textContent).toContain('PLAN-MERGED') + expect(document.body.textContent).toContain('合入') + }) + + it('shows pending plans without applying the carton AVAILABLE status on tab changes and pagination', async () => { + const wrapper = await mountView('inventory') + const tabs = wrapper.getComponent({ name: 'ElTabs' }) + tabs.vm.$emit('update:modelValue', 'plans') + tabs.vm.$emit('tab-change', 'plans') + await flushPromises() + expect(listReturnPackingPlans).toHaveBeenLastCalledWith({ page: 1, size: 20 }) + expect(wrapper.text()).toContain('PLAN-NEW') + wrapper.getComponent(DataTable).vm.$emit('update:page', 2) + await flushPromises() + expect(listReturnPackingPlans).toHaveBeenLastCalledWith({ page: 2, size: 20 }) + tabs.vm.$emit('update:modelValue', 'stock') + tabs.vm.$emit('tab-change', 'stock') + await flushPromises() + expect(listReturnStock).toHaveBeenLastCalledWith(expect.objectContaining({ status: 'AVAILABLE' })) + }) + + it('loads packing sources in pages without per-carton or stock requests and excludes reserved parcel quantities', async () => { + const wrapper = await mountView('inventory') + vi.mocked(listReturnPackingSources) + .mockResolvedValueOnce({ records: [{ cartonMark: 'PARCEL-1', sourceReturnNo: 'RETURN-1', sourceUnitType: 'PARCEL', items: [{ skuCode: 'PACK-SKU', quantity: 9, availableQuantity: 2 }] }], page: 1, size: 200, total: 2, pages: 2 }) + .mockResolvedValueOnce({ records: [{ cartonMark: 'BOX-2', sourceReturnNo: 'RETURN-2', sourceUnitType: 'BOX', items: [{ skuCode: 'PACK-SKU', quantity: 100, availableQuantity: 100 }] }], page: 2, size: 200, total: 2, pages: 2 }) + await wrapper.findAll('button').find(button => button.text().includes('创建合箱计划'))!.trigger('click') + await flushPromises() + const dialog = wrapper.findAllComponents({ name: 'ElForm' }).find(form => form.classes().includes('packing-form'))! + const selects = dialog.findAllComponents({ name: 'ElSelect' }) + selects[0].vm.$emit('update:modelValue', 'customer-1') + selects[0].vm.$emit('change', 'customer-1') + await flushPromises() + expect(listReturnPackingSources).toHaveBeenCalledTimes(2) + expect(listReturnPackingSources).toHaveBeenLastCalledWith({ page: 2, size: 200, customerId: 'customer-1', overseasWarehouseId: 'warehouse-1' }) + expect(getReturnCarton).not.toHaveBeenCalled() + expect(listReturnCartons).not.toHaveBeenCalled() + expect(listReturnStock).toHaveBeenCalledTimes(1) + const modes = dialog.findComponent({ name: 'ElRadioGroup' }) + modes.vm.$emit('update:modelValue', 'SKU') + modes.vm.$emit('change', 'SKU') + await flushPromises() + expect(dialog.get('.packing-sku-row').text()).toContain('PACK-SKU') + expect(dialog.get('.packing-sku-row').text()).toContain('可用 2') + }) + + it('stops old pagination and ignores its result after the customer changes', async () => { + const wrapper = await mountView('inventory') + let resolveOld!: (value: Awaited>) => void + vi.mocked(listReturnPackingSources) + .mockImplementationOnce(() => new Promise(resolve => { resolveOld = resolve })) + .mockResolvedValueOnce({ records: [{ cartonMark: 'NEW-BOX', sourceUnitType: 'BOX', items: [] }], page: 1, size: 200, total: 1, pages: 1 }) + await wrapper.findAll('button').find(button => button.text().includes('创建合箱计划'))!.trigger('click') + await flushPromises() + const dialog = wrapper.findAllComponents({ name: 'ElForm' }).find(form => form.classes().includes('packing-form'))! + const select = dialog.getComponent({ name: 'ElSelect' }) + select.vm.$emit('update:modelValue', 'customer-1') + select.vm.$emit('change', 'customer-1') + await flushPromises() + select.vm.$emit('update:modelValue', 'customer-2') + select.vm.$emit('change', 'customer-2') + await flushPromises() + resolveOld({ records: [{ cartonMark: 'OLD-BOX', sourceUnitType: 'BOX', items: [] }], page: 1, size: 200, total: 400, pages: 2 }) + await flushPromises() + expect(listReturnPackingSources).toHaveBeenCalledTimes(2) + expect(dialog.text()).toContain('NEW-BOX') + expect(dialog.text()).not.toContain('OLD-BOX') + }) + it('keeps query submission and server pagination when using the shared table', async () => { const wrapper = await mountView('inventory') expect(wrapper.get('h1').text()).toBe('退货库存管理') diff --git a/src/modules/overseas-warehouse/pages/ReturnInventoryManagementView.vue b/src/modules/overseas-warehouse/pages/ReturnInventoryManagementView.vue index 3fbc91ab..5f185eac 100644 --- a/src/modules/overseas-warehouse/pages/ReturnInventoryManagementView.vue +++ b/src/modules/overseas-warehouse/pages/ReturnInventoryManagementView.vue @@ -1,117 +1,1083 @@ - - + + diff --git a/src/modules/overseas-warehouse/permissions.ts b/src/modules/overseas-warehouse/permissions.ts index e73a8a7c..71aa23d0 100644 --- a/src/modules/overseas-warehouse/permissions.ts +++ b/src/modules/overseas-warehouse/permissions.ts @@ -8,6 +8,8 @@ export const overseasWarehousePermissions = { inboundOrderEdit: 'platform:overseas-warehouse:inbound-order:edit', inboundOrderAppointment: 'platform:overseas-warehouse:inbound-order:fba-appointment', inboundOrderSmartDispatch: 'platform:overseas-warehouse:inbound-order:smart-dispatch', + discardList: 'platform:overseas-warehouse:discard:list', + discardApprove: 'platform:overseas-warehouse:discard:approve', dispatchList: 'platform:overseas-warehouse:dispatch:list', dispatchEdit: 'platform:overseas-warehouse:dispatch:edit', dispatchCancel: 'platform:overseas-warehouse:dispatch:cancel', diff --git a/src/modules/overseas-warehouse/routes.ts b/src/modules/overseas-warehouse/routes.ts index 4984a511..da1d29a5 100644 --- a/src/modules/overseas-warehouse/routes.ts +++ b/src/modules/overseas-warehouse/routes.ts @@ -20,6 +20,12 @@ export const overseasWarehouseRoutes = [ component: () => import('./pages/InboundOrdersView.vue'), meta: { showPageHeading: false, title: '入库单', description: '管理海外合作仓入库、预约与排车衔接', permission: permission.inboundOrderList }, }, + { + path: 'overseas-warehouse/cargo-discards', + name: 'overseas-cargo-discards', + component: () => import('./pages/CargoDiscardsView.vue'), + meta: { showPageHeading: false, title: '废弃单', description: '审核货物废弃申请并跟进 PDA 废弃与客户确认', permission: permission.discardList }, + }, { path: 'overseas-warehouse/inbound-orders/:orderId', name: 'overseas-inbound-order-detail',