From 9e28e10e5f85b59f7850a6e42acb8d2290177b7e Mon Sep 17 00:00:00 2001 From: ouyangxq <775153912@qq.com> Date: Mon, 14 Sep 2026 16:56:11 +0800 Subject: [PATCH] feat: edit collaboration remarks across order loading --- .../api/platformFirstLegOrders.test.ts | 3 -- .../api/platformFirstLegOrders.ts | 4 -- .../api/platformLoadingLists.test.ts | 16 +++--- .../loading-lists/api/platformLoadingLists.ts | 15 +++--- .../components/LoadingListDetailDrawer.vue | 6 ++- .../components/LoadingListsView.test.ts | 7 +-- .../PreloadPlanDetailView.lifecycle.test.ts | 2 +- .../pages/PreloadPlanDetailView.test.ts | 12 +++-- .../pages/PreloadPlanDetailView.vue | 52 +++++++++++-------- .../unified-orders/api/unifiedOrders.test.ts | 11 ++-- .../unified-orders/api/unifiedOrders.ts | 25 +++++++-- .../pages/UnifiedOrdersView.coLoad.test.ts | 2 +- .../UnifiedOrdersView.fulfillment.test.ts | 2 +- .../pages/UnifiedOrdersView.requests.test.ts | 2 +- .../pages/UnifiedOrdersView.test.ts | 10 ++++ .../pages/UnifiedOrdersView.vue | 34 +++++++----- 16 files changed, 125 insertions(+), 78 deletions(-) diff --git a/src/modules/first-leg-orders/api/platformFirstLegOrders.test.ts b/src/modules/first-leg-orders/api/platformFirstLegOrders.test.ts index c76bf7a8..c141fa9c 100644 --- a/src/modules/first-leg-orders/api/platformFirstLegOrders.test.ts +++ b/src/modules/first-leg-orders/api/platformFirstLegOrders.test.ts @@ -3,7 +3,6 @@ import { http } from '@/shared/api/http' import { getPlatformTenantOrderStatistics, acceptPlatformFirstLegOrder, - addPlatformFirstLegOrderNote, approvePlatformFirstLegOrder, cancelPlatformFirstLegOrder, completePlatformFirstLegOrder, @@ -85,7 +84,6 @@ describe('platform first-leg order API', () => { await startPlatformFirstLegOrder('10001', '90001') await completePlatformFirstLegOrder('10001', '90001') await cancelPlatformFirstLegOrder('10001', '90001', '客户取消') - await addPlatformFirstLegOrderNote('10001', '90001', '已联系客户确认') expect(http.get).toHaveBeenCalledWith('/platform/v1/orders/first-leg/90001', { params: { tenantId: '10001' } }) expect(http.post).toHaveBeenNthCalledWith(1, '/platform/v1/orders/first-leg/90001/approve', undefined, { params: { tenantId: '10001' } }) @@ -93,7 +91,6 @@ describe('platform first-leg order API', () => { expect(http.post).toHaveBeenNthCalledWith(3, '/platform/v1/orders/first-leg/90001/start', undefined, { params: { tenantId: '10001' } }) expect(http.post).toHaveBeenNthCalledWith(4, '/platform/v1/orders/first-leg/90001/complete', undefined, { params: { tenantId: '10001' } }) expect(http.post).toHaveBeenNthCalledWith(5, '/platform/v1/orders/first-leg/90001/cancel', { reason: '客户取消' }, { params: { tenantId: '10001' } }) - expect(http.post).toHaveBeenNthCalledWith(6, '/platform/v1/orders/first-leg/90001/internal-notes', { content: '已联系客户确认' }, { params: { tenantId: '10001' } }) }) it('writes the backend express contract as names, not a guessed master-data ID', async () => { diff --git a/src/modules/first-leg-orders/api/platformFirstLegOrders.ts b/src/modules/first-leg-orders/api/platformFirstLegOrders.ts index 22ce4117..45a9d704 100644 --- a/src/modules/first-leg-orders/api/platformFirstLegOrders.ts +++ b/src/modules/first-leg-orders/api/platformFirstLegOrders.ts @@ -192,10 +192,6 @@ export function cancelPlatformFirstLegOrder(tenantId: string, orderId: string, r return postData(`/platform/v1/orders/first-leg/${orderId}/cancel`, { reason }, { tenantId }) } -export function addPlatformFirstLegOrderNote(tenantId: string, orderId: string, content: string) { - return postData(`/platform/v1/orders/first-leg/${orderId}/internal-notes`, { content }, { tenantId }) -} - export function updatePlatformFirstLegOrderExpress( tenantId: string, orderId: string, diff --git a/src/modules/loading-lists/api/platformLoadingLists.test.ts b/src/modules/loading-lists/api/platformLoadingLists.test.ts index 9f3da821..14a8a716 100644 --- a/src/modules/loading-lists/api/platformLoadingLists.test.ts +++ b/src/modules/loading-lists/api/platformLoadingLists.test.ts @@ -9,7 +9,7 @@ import { preparePalletLabels, printPalletLabels, updatePreloadTransport, - updateLoadingItemWarehouseRemark, + updateLoadingItemRemarks, } from './platformLoadingLists' const preloadSummary = { @@ -53,7 +53,7 @@ const detail = { customerName: '测试客户', customsTaxMode: 'TAX_REFUND', taxRefundFlag: true, inboundCode: 'RK001', skuCode: 'SKU001', destinationWarehouse: 'ONT8', chineseName: '产品', englishName: null, goodsImageUrl: null, unitType: 'SEALED_PALLET', palletId: 9007199254740993n, palletNo: 'PLT-001', indivisible: true, packageCount: 2, palletCount: 1, grossWeightKg: 10, netWeightKg: 9, volumeCbm: 1, dimensionalWeightKg: 2, unitsPerPackage: 10, fbaTrackingNo: null, shipmentNo: null, shippingMark: null, - internalRemark: null, customerRemark: null, warehouseRemark: null, loadSequence: 1, frozenAt: null, + internalRemark: null, customerRemark: null, warehouseRemark: null, customerServiceRemark: null, loadSequence: 1, frozenAt: null, }], histories: [{ historyId: 21, itemId: 11, operationId: 'transport-21', actionType: 'UPDATE_TRANSPORT', fromStatus: 'LOADING', toStatus: 'LOADING', beforeSnapshot: null, afterSnapshot: null, operatorId: 22, operatorName: '操作员', operationReason: '调整', occurredAt: '2026-08-10T11:00:00' }], } @@ -108,18 +108,20 @@ describe('platform loading lists API', () => { expect(loaded.summary.status).toBe('COMPLETED') }) - it('updates one item warehouse remark with both optimistic-lock versions and operation id', async () => { - const patch = vi.spyOn(http, 'patch').mockResolvedValue({ code: '200', message: 'ok', data: { id: 100, version: 4, items: [{ id: 11, warehouseRemark: '装柜时注意外箱', version: 5 }] } }) - const result = await updateLoadingItemWarehouseRemark('100', '11', { + it('updates one item editable remarks with both optimistic-lock versions and operation id', async () => { + const patch = vi.spyOn(http, 'patch').mockResolvedValue({ code: '200', message: 'ok', data: { id: 100, version: 4, items: [{ id: 11, internalRemark: '内部', warehouseRemark: '装柜时注意外箱', customerServiceRemark: '客服', version: 5 }] } }) + const result = await updateLoadingItemRemarks('100', '11', { + internalRemark: '内部', warehouseRemark: '装柜时注意外箱', + customerServiceRemark: '客服', expectedPlanVersion: 3, expectedItemVersion: 4, operationId: 'warehouse-remark-1', }) expect(patch).toHaveBeenCalledWith( - '/platform/v1/first-leg/smart-loading/plans/100/items/11/warehouse-remark', - { warehouseRemark: '装柜时注意外箱', expectedPlanVersion: 3, expectedItemVersion: 4, operationId: 'warehouse-remark-1' }, + '/platform/v1/first-leg/smart-loading/plans/100/items/11/remarks', + { internalRemark: '内部', warehouseRemark: '装柜时注意外箱', customerServiceRemark: '客服', expectedPlanVersion: 3, expectedItemVersion: 4, operationId: 'warehouse-remark-1' }, ) expect(result).toMatchObject({ id: '100', version: 4, items: [{ id: '11', version: 5, warehouseRemark: '装柜时注意外箱' }] }) }) diff --git a/src/modules/loading-lists/api/platformLoadingLists.ts b/src/modules/loading-lists/api/platformLoadingLists.ts index eea28d36..4c409c78 100644 --- a/src/modules/loading-lists/api/platformLoadingLists.ts +++ b/src/modules/loading-lists/api/platformLoadingLists.ts @@ -111,6 +111,7 @@ export interface LoadingItem { internalRemark: string | null customerRemark: string | null warehouseRemark: string | null + customerServiceRemark: string | null loadSequence: number frozenAt: string | null } @@ -161,17 +162,19 @@ export interface TransportUpdateRequest { operationReason: string | null } -export interface WarehouseRemarkUpdateRequest { +export interface LoadingItemRemarksUpdateRequest { + internalRemark: string | null warehouseRemark: string | null + customerServiceRemark: string | null expectedPlanVersion: number expectedItemVersion: number operationId: string } -export interface WarehouseRemarkUpdateResponse { +export interface LoadingItemRemarksUpdateResponse { id: string version: number - items: Array<{ id: string; version: number; warehouseRemark: string | null }> + items: Array<{ id: string; version: number; internalRemark: string | null; warehouseRemark: string | null; customerServiceRemark: string | null }> } export interface PreloadConfirmationResponse { @@ -342,9 +345,9 @@ export async function updatePreloadTransport(planId: string, warehouseId: string } } -export async function updateLoadingItemWarehouseRemark(planId: string, itemId: string, request: WarehouseRemarkUpdateRequest) { - const response = await http.patch, ApiEnvelope>( - `${basePath}/smart-loading/plans/${encodeURIComponent(planId)}/items/${encodeURIComponent(itemId)}/warehouse-remark`, +export async function updateLoadingItemRemarks(planId: string, itemId: string, request: LoadingItemRemarksUpdateRequest) { + const response = await http.patch, ApiEnvelope>( + `${basePath}/smart-loading/plans/${encodeURIComponent(planId)}/items/${encodeURIComponent(itemId)}/remarks`, request, ) const result = unwrapApiResponse(response) diff --git a/src/modules/loading-lists/components/LoadingListDetailDrawer.vue b/src/modules/loading-lists/components/LoadingListDetailDrawer.vue index b5c2d379..45084210 100644 --- a/src/modules/loading-lists/components/LoadingListDetailDrawer.vue +++ b/src/modules/loading-lists/components/LoadingListDetailDrawer.vue @@ -24,7 +24,7 @@ const props = defineProps<{ confirmSaving?: boolean }>() -const emit = defineEmits<{ back: []; retry: []; edit: []; editWarehouseRemark: [item: LoadingItem]; confirmPreload: [] }>() +const emit = defineEmits<{ back: []; retry: []; edit: []; editRemarks: [item: LoadingItem]; confirmPreload: [] }>() const actionColumn: DataTableColumn = { key: 'actions', label: '操作', fixed: 'left', width: 132, slotName: 'actions', hideable: false, movable: false } const baseItemColumns: DataTableColumn[] = [ @@ -54,6 +54,7 @@ const baseItemColumns: DataTableColumn[] = [ { key: 'customerRemark', prop: 'customerRemark', label: '备注(客户)', minWidth: 190, slotName: 'customerRemark' }, { key: 'internalRemark', prop: 'internalRemark', label: '备注(内部)', minWidth: 190, slotName: 'internalRemark' }, { key: 'warehouseRemark', prop: 'warehouseRemark', label: '备注(仓库)', minWidth: 210, slotName: 'warehouseRemark' }, + { key: 'customerServiceRemark', prop: 'customerServiceRemark', label: '备注(客服)', minWidth: 210, slotName: 'customerServiceRemark' }, { key: 'frozenAt', label: '冻结时间', minWidth: 150, slotName: 'frozenAt' }, ] @@ -203,7 +204,7 @@ function formatNumber(value?: number | null, digits = 3) { > @@ -214,6 +215,7 @@ function formatNumber(value?: number | null, digits = 3) { + diff --git a/src/modules/loading-lists/components/LoadingListsView.test.ts b/src/modules/loading-lists/components/LoadingListsView.test.ts index 0109eaff..ab90c7b6 100644 --- a/src/modules/loading-lists/components/LoadingListsView.test.ts +++ b/src/modules/loading-lists/components/LoadingListsView.test.ts @@ -169,6 +169,7 @@ describe('LoadingListsView', () => { 'customerRemark', 'internalRemark', 'warehouseRemark', + 'customerServiceRemark', ] let previous = detailSource.indexOf("key: 'customerName'") for (const field of fields) { @@ -180,14 +181,14 @@ describe('LoadingListsView', () => { expect(detailSource).not.toContain("label: '退税资料'") }) - it('keeps customer and internal remarks read-only while exposing per-item warehouse remark editing', () => { + it('keeps customer remarks read-only while exposing three editable collaboration remarks', () => { expect(detailSource).toContain('来源:客户下单') expect(detailSource).toContain('来源:订单内部备注') expect(detailSource).toContain('仓库备注将随装柜完成资料下发海外仓') - expect(detailSource).toContain("emit('editWarehouseRemark', row as LoadingItem)") + expect(detailSource).toContain("emit('editRemarks', row as LoadingItem)") expect(detailSource).toContain("props.mode === 'preload' && props.canEdit && ['EDITING', 'LOADING'].includes(summary.value?.status || '')") expect(detailSource).not.toContain("emit('editCustomerRemark'") - expect(detailSource).not.toContain("emit('editInternalRemark'") + expect(detailSource).toContain("key: 'customerServiceRemark'") }) it('uses one white detail surface, a readable four-column summary and dense item table', () => { diff --git a/src/modules/loading-lists/pages/PreloadPlanDetailView.lifecycle.test.ts b/src/modules/loading-lists/pages/PreloadPlanDetailView.lifecycle.test.ts index 2ef9aa5c..b81b4870 100644 --- a/src/modules/loading-lists/pages/PreloadPlanDetailView.lifecycle.test.ts +++ b/src/modules/loading-lists/pages/PreloadPlanDetailView.lifecycle.test.ts @@ -17,7 +17,7 @@ vi.mock('../api/platformLoadingLists', () => ({ confirmPreloadPlan: vi.fn(), getConfirmedLoadingList: vi.fn(), getPreloadPlan: vi.fn(), - updateLoadingItemWarehouseRemark: vi.fn(), + updateLoadingItemRemarks: vi.fn(), updatePreloadTransport: vi.fn(), })) diff --git a/src/modules/loading-lists/pages/PreloadPlanDetailView.test.ts b/src/modules/loading-lists/pages/PreloadPlanDetailView.test.ts index d8f62fac..3d0bbc0c 100644 --- a/src/modules/loading-lists/pages/PreloadPlanDetailView.test.ts +++ b/src/modules/loading-lists/pages/PreloadPlanDetailView.test.ts @@ -39,20 +39,22 @@ describe('PreloadPlanDetailView prototype contract', () => { expect(source).not.toContain('saveMfNote') }) - it('edits only the selected item warehouse remark with optimistic locking', () => { - expect(source).toContain('@edit-warehouse-remark="openWarehouseRemark"') - expect(source).toContain('updateLoadingItemWarehouseRemark(current.summary.planId, target.itemId') + it('edits the selected item internal, warehouse and customer-service remarks with optimistic locking', () => { + expect(source).toContain('@edit-remarks="openRemarks"') + expect(source).toContain('updateLoadingItemRemarks(current.summary.planId, target.itemId') expect(source).toContain('expectedPlanVersion: current.summary.version') expect(source).toContain('expectedItemVersion: target.itemVersion') expect(source).toContain('operationId: remarkOperationId.value') expect(source).toContain('maxlength="1000"') - expect(source).toContain("warehouseRemark.value.trim() || null") + expect(source).toContain("remarkForm.internalRemark.trim() || null") + expect(source).toContain("remarkForm.warehouseRemark.trim() || null") + expect(source).toContain("remarkForm.customerServiceRemark.trim() || null") expect(source).toContain('summary: { ...current.summary, version: result.version }') expect(source).toContain('updatedItem?.version') expect(source).not.toContain('plan.remark') }) - it('preserves warehouse remark input and refreshes current data on conflicts', () => { + it('preserves remark input and refreshes current data on conflicts', () => { expect(source).toContain("details.code === 'A0409'") expect(source).toContain('await loadDetail()') expect(source).toContain("detail.value?.items.find((item) => item.itemId === target.itemId) || target") diff --git a/src/modules/loading-lists/pages/PreloadPlanDetailView.vue b/src/modules/loading-lists/pages/PreloadPlanDetailView.vue index ff59e51d..a1240fd2 100644 --- a/src/modules/loading-lists/pages/PreloadPlanDetailView.vue +++ b/src/modules/loading-lists/pages/PreloadPlanDetailView.vue @@ -12,7 +12,7 @@ import { getApiErrorDetails, http, unwrapApiResponse, type ApiEnvelope, type Api import { useAccessPreviewStore } from '@/core/access/accessPreview' import { useWorkspaceTabsStore } from '@/core/workspace-tabs/workspaceTabs' import LoadingListDetailDrawer from '../components/LoadingListDetailDrawer.vue' -import { confirmPreloadPlan, getConfirmedLoadingList, getPreloadPlan, updateLoadingItemWarehouseRemark, updatePreloadTransport, type LoadingDetail, type LoadingItem, type PreloadSummary } from '../api/platformLoadingLists' +import { confirmPreloadPlan, getConfirmedLoadingList, getPreloadPlan, updateLoadingItemRemarks, updatePreloadTransport, type LoadingDetail, type LoadingItem, type PreloadSummary } from '../api/platformLoadingLists' const route = useRoute() const router = useRouter() @@ -34,7 +34,7 @@ const remarkOpen = ref(false) const remarkSaving = ref(false) const remarkError = ref(null) const remarkTarget = ref(null) -const warehouseRemark = ref('') +const remarkForm = reactive({ internalRemark: '', warehouseRemark: '', customerServiceRemark: '' }) const remarkOperationId = ref('') let loadSequence = 0 const planId = computed(() => String(route.params.planId || '')) @@ -163,28 +163,32 @@ async function saveTransport() { } } -function openWarehouseRemark(item: LoadingItem) { +function openRemarks(item: LoadingItem) { if (!canEdit.value || !detail.value || !['EDITING', 'LOADING'].includes(detail.value.summary.status)) return remarkTarget.value = item - warehouseRemark.value = item.warehouseRemark || '' + remarkForm.internalRemark = item.internalRemark || '' + remarkForm.warehouseRemark = item.warehouseRemark || '' + remarkForm.customerServiceRemark = item.customerServiceRemark || '' remarkError.value = null - remarkOperationId.value = `admin-preload-warehouse-remark-${detail.value.summary.planId}-${item.itemId}-${Date.now()}-${Math.random().toString(36).slice(2, 10)}` + remarkOperationId.value = `admin-preload-remarks-${detail.value.summary.planId}-${item.itemId}-${Date.now()}-${Math.random().toString(36).slice(2, 10)}` remarkOpen.value = true } -async function saveWarehouseRemark() { +async function saveRemarks() { const target = remarkTarget.value const current = detail.value if (!target || !current || !canEdit.value || remarkSaving.value) return - if (warehouseRemark.value.length > 1000) { - remarkError.value = { code: 'WAREHOUSE_REMARK_TOO_LONG', message: '仓库备注不能超过 1000 个字符' } + if (Object.values(remarkForm).some((value) => value.length > 1000)) { + remarkError.value = { code: 'REMARK_TOO_LONG', message: '每项备注不能超过 1000 个字符' } return } remarkSaving.value = true remarkError.value = null try { - const result = await updateLoadingItemWarehouseRemark(current.summary.planId, target.itemId, { - warehouseRemark: warehouseRemark.value.trim() || null, + const result = await updateLoadingItemRemarks(current.summary.planId, target.itemId, { + internalRemark: remarkForm.internalRemark.trim() || null, + warehouseRemark: remarkForm.warehouseRemark.trim() || null, + customerServiceRemark: remarkForm.customerServiceRemark.trim() || null, expectedPlanVersion: current.summary.version, expectedItemVersion: target.itemVersion, operationId: remarkOperationId.value, @@ -194,15 +198,18 @@ async function saveWarehouseRemark() { ...current, summary: { ...current.summary, version: result.version }, items: current.items.map((item) => item.itemId === target.itemId - ? { ...item, itemVersion: updatedItem?.version ?? item.itemVersion, warehouseRemark: updatedItem?.warehouseRemark ?? null } + ? { ...item, itemVersion: updatedItem?.version ?? item.itemVersion, + internalRemark: updatedItem?.internalRemark ?? null, + warehouseRemark: updatedItem?.warehouseRemark ?? null, + customerServiceRemark: updatedItem?.customerServiceRemark ?? null } : item), } remarkOpen.value = false remarkTarget.value = null remarkOperationId.value = '' - ElMessage.success('仓库备注已更新') + ElMessage.success('备注已更新') } catch (cause) { - const details = getApiErrorDetails(cause, '仓库备注保存失败,请稍后重试') + const details = getApiErrorDetails(cause, '备注保存失败,请稍后重试') if (details.code === 'A0409' || /版本|并发|状态.*变化/.test(details.message)) { await loadDetail() remarkTarget.value = detail.value?.items.find((item) => item.itemId === target.itemId) || target @@ -247,7 +254,7 @@ watch([planId, warehouseId], () => void loadDetail())
- +
@@ -258,14 +265,15 @@ watch([planId, warehouseId], () => void loadDetail()) - - - + + + 来源:客户下单 - 来源:订单内部备注 - + + + - + @@ -286,8 +294,8 @@ watch([planId, warehouseId], () => void loadDetail()) .transport-form { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0 16px; } .transport-form :deep(.el-form-item:last-child) { grid-column: 1 / -1; } .confirm-content { display: grid; gap: 12px; } -.warehouse-remark-form { margin-top: 14px; } -.warehouse-remark-form small { display: block; margin-top: 4px; color: var(--cc-muted); } +.remarks-form { margin-top: 14px; } +.remarks-form small { display: block; margin-top: 4px; color: var(--cc-muted); } :global(.loading-transport-dialog.el-dialog) { max-height: 90vh; overflow: hidden; } @media (min-width: 1920px) { .preload-detail-page { width: 100%; max-width: none; gap: 12px; } } @media (max-width: 720px) { .transport-form { grid-template-columns: 1fr; } .transport-form :deep(.el-form-item:last-child) { grid-column: auto; } } diff --git a/src/modules/unified-orders/api/unifiedOrders.test.ts b/src/modules/unified-orders/api/unifiedOrders.test.ts index 8b6060ac..b72014c0 100644 --- a/src/modules/unified-orders/api/unifiedOrders.test.ts +++ b/src/modules/unified-orders/api/unifiedOrders.test.ts @@ -4,7 +4,7 @@ import { approvePlatformUnifiedOrder, batchApprovePlatformUnifiedOrders, getPlatformUnifiedOrderBatchApprovalPreview, - addPlatformUnifiedOrderNote, + updatePlatformUnifiedOrderRemarks, getPlatformUnifiedOrder, downloadPlatformProductImage, listPlatformUnifiedOrders, @@ -172,10 +172,11 @@ describe('unified orders API contract', () => { ) }) - it('writes platform internal notes through the unified order boundary', async () => { - vi.mocked(http.post).mockResolvedValue({ data: '91001' }) - await expect(addPlatformUnifiedOrderNote('208', '90001', '已确认船期')).resolves.toBe('91001') - expect(http.post).toHaveBeenCalledWith('/platform/v1/orders/90001/internal-notes', { content: '已确认船期' }, { params: { tenantId: '208' } }) + it('updates the three editable remarks while keeping customer remark outside the request', async () => { + const request = { expectedVersion: 7, internalRemark: '内部', warehouseRemark: '仓库', customerServiceRemark: '客服' } + vi.mocked(http.put).mockResolvedValue({ data: { ...request, version: 8, customerRemark: '客户只读' } }) + await expect(updatePlatformUnifiedOrderRemarks('208', '90001', request)).resolves.toMatchObject({ version: 8 }) + expect(http.put).toHaveBeenCalledWith('/platform/v1/orders/90001/remarks', request, { params: { tenantId: '208' } }) }) it('updates customer declaration fields through the versioned tenant-scoped contract', async () => { diff --git a/src/modules/unified-orders/api/unifiedOrders.ts b/src/modules/unified-orders/api/unifiedOrders.ts index a3004609..fc8c4162 100644 --- a/src/modules/unified-orders/api/unifiedOrders.ts +++ b/src/modules/unified-orders/api/unifiedOrders.ts @@ -160,6 +160,8 @@ export interface UnifiedOrderResource { customerRemark?: string | null latestInternalNote?: string | null internalRemark?: string | null + warehouseRemark?: string | null + customerServiceRemark?: string | null createdBy?: string | null } @@ -599,10 +601,25 @@ export function batchApprovePlatformUnifiedOrders(tenantId: string, items: Unifi ).then(unwrapApiResponse) } -export function addPlatformUnifiedOrderNote(tenantId: string, orderId: string, content: string) { - return http.post, ApiEnvelope>( - `${unifiedOrdersApiPath}/${orderId}/internal-notes`, - { content }, +export interface UnifiedOrderRemarksUpdateRequest { + expectedVersion: number + internalRemark: string | null + warehouseRemark: string | null + customerServiceRemark: string | null +} + +export interface UnifiedOrderRemarksUpdateResponse { + version: number + customerRemark: string | null + internalRemark: string | null + warehouseRemark: string | null + customerServiceRemark: string | null +} + +export function updatePlatformUnifiedOrderRemarks(tenantId: string, orderId: string, request: UnifiedOrderRemarksUpdateRequest) { + return http.put, ApiEnvelope>( + `${unifiedOrdersApiPath}/${orderId}/remarks`, + request, { params: { tenantId } }, ).then(unwrapApiResponse) } diff --git a/src/modules/unified-orders/pages/UnifiedOrdersView.coLoad.test.ts b/src/modules/unified-orders/pages/UnifiedOrdersView.coLoad.test.ts index 0aa5a1b2..3852fc22 100644 --- a/src/modules/unified-orders/pages/UnifiedOrdersView.coLoad.test.ts +++ b/src/modules/unified-orders/pages/UnifiedOrdersView.coLoad.test.ts @@ -13,7 +13,7 @@ vi.mock('@/modules/warehouses', () => ({ listAllPlatformWarehouses: vi.fn().mock vi.mock('@/modules/inbounds', () => ({ downloadWeightImage: vi.fn(), getInboundMeasurement: vi.fn() })) vi.mock('@/modules/unified-orders/workflows', () => ({ UnifiedOrderWorkflowPanel: { template: '
' } })) vi.mock('@/modules/unified-orders/api/unifiedOrders', () => ({ - addPlatformUnifiedOrderNote: vi.fn(), approvePlatformUnifiedOrder: vi.fn(), batchApprovePlatformUnifiedOrders: vi.fn(), + updatePlatformUnifiedOrderRemarks: vi.fn(), approvePlatformUnifiedOrder: vi.fn(), batchApprovePlatformUnifiedOrders: vi.fn(), getPlatformUnifiedOrder: vi.fn(), listPlatformUnifiedOrderFeeDetails: vi.fn(), listPlatformUnifiedOrders: vi.fn(), unifiedOrderStatusOptions: [{ value: 'WAIT_SPLIT', label: '待分柜' }], })) diff --git a/src/modules/unified-orders/pages/UnifiedOrdersView.fulfillment.test.ts b/src/modules/unified-orders/pages/UnifiedOrdersView.fulfillment.test.ts index 3802f3c8..08be6a85 100644 --- a/src/modules/unified-orders/pages/UnifiedOrdersView.fulfillment.test.ts +++ b/src/modules/unified-orders/pages/UnifiedOrdersView.fulfillment.test.ts @@ -26,7 +26,7 @@ vi.mock('@/shared/api/inboundCodeRelations', () => ({ getLoadingAssociationSummary: vi.fn(), getOverseasInboundCodeRelationship: vi.fn(), })) vi.mock('@/modules/unified-orders/api/unifiedOrders', () => ({ - addPlatformUnifiedOrderNote: vi.fn(), approvePlatformUnifiedOrder: vi.fn(), batchApprovePlatformUnifiedOrders: vi.fn(), + updatePlatformUnifiedOrderRemarks: vi.fn(), approvePlatformUnifiedOrder: vi.fn(), batchApprovePlatformUnifiedOrders: vi.fn(), downloadPlatformProductImage: vi.fn(), getPlatformUnifiedOrder: vi.fn(), listPlatformUnifiedOrderFeeDetails: vi.fn(), listPlatformUnifiedOrders: vi.fn(), unifiedOrderStatusOptions: [], diff --git a/src/modules/unified-orders/pages/UnifiedOrdersView.requests.test.ts b/src/modules/unified-orders/pages/UnifiedOrdersView.requests.test.ts index eec5212d..2295b4c4 100644 --- a/src/modules/unified-orders/pages/UnifiedOrdersView.requests.test.ts +++ b/src/modules/unified-orders/pages/UnifiedOrdersView.requests.test.ts @@ -24,7 +24,7 @@ vi.mock('@/modules/unified-orders/api/coLoadGroups', () => ({ getPlatformCoLoadGroupByInboundCode: vi.fn(), })) vi.mock('@/modules/unified-orders/api/unifiedOrders', () => ({ - addPlatformUnifiedOrderNote: vi.fn(), + updatePlatformUnifiedOrderRemarks: vi.fn(), approvePlatformUnifiedOrder: vi.fn(), getPlatformUnifiedOrder: vi.fn(), listPlatformUnifiedOrderFeeDetails: vi.fn(), diff --git a/src/modules/unified-orders/pages/UnifiedOrdersView.test.ts b/src/modules/unified-orders/pages/UnifiedOrdersView.test.ts index 5ca2e046..fe754275 100644 --- a/src/modules/unified-orders/pages/UnifiedOrdersView.test.ts +++ b/src/modules/unified-orders/pages/UnifiedOrdersView.test.ts @@ -375,6 +375,16 @@ describe('UnifiedOrdersView contract', () => { expect(source).not.toContain("value.replaceAll('_', ' ')") }) + it('keeps customer remarks read-only and edits the three collaboration remarks together', () => { + expect(source).toContain('title="编辑订单备注"') + expect(source).toContain('label="客户备注(只读)"') + expect(source).toContain(':model-value="noteOrder?.customerRemark') + expect(source).toContain('v-model="noteForm.internalRemark"') + expect(source).toContain('v-model="noteForm.warehouseRemark"') + expect(source).toContain('v-model="noteForm.customerServiceRemark"') + expect(source).toContain('await updatePlatformUnifiedOrderRemarks') + }) + it('keeps the customer order package count independent from measured inbound totals', () => { expect(source).toContain("{ source: '客户下单数据', count: customerTotals.count") expect(source).not.toContain("{ source: '客户下单数据', count: summary?.totalPackageCount") diff --git a/src/modules/unified-orders/pages/UnifiedOrdersView.vue b/src/modules/unified-orders/pages/UnifiedOrdersView.vue index b260d343..5e8bc2db 100644 --- a/src/modules/unified-orders/pages/UnifiedOrdersView.vue +++ b/src/modules/unified-orders/pages/UnifiedOrdersView.vue @@ -9,7 +9,7 @@ import { approvePlatformUnifiedOrder, batchApprovePlatformUnifiedOrders, getPlatformUnifiedOrderBatchApprovalPreview, - addPlatformUnifiedOrderNote, + updatePlatformUnifiedOrderRemarks, listPlatformUnifiedOrders, updatePlatformUnifiedOrderPlanning, unifiedOrderStatusOptions, @@ -179,7 +179,7 @@ const coLoadError = ref(null) const approvalOrder = ref(null) const noteOpen = ref(false) const noteSaving = ref(false) -const noteContent = ref('') +const noteForm = reactive({ internalRemark: '', warehouseRemark: '', customerServiceRemark: '' }) const noteOrder = ref(null) const holdDetail = ref(null) const holdLoading = ref(false) @@ -1360,27 +1360,30 @@ function openNoteDialog(order?: UnifiedOrderResource) { const target = order || detail.value?.summary || null if (!target) return noteOrder.value = target - noteContent.value = '' + noteForm.internalRemark = target.latestInternalNote || target.internalRemark || '' + noteForm.warehouseRemark = target.warehouseRemark || '' + noteForm.customerServiceRemark = target.customerServiceRemark || '' noteOpen.value = true } async function saveNote() { - const content = noteContent.value.trim() - if (!noteOrder.value || !content) { - ElMessage.warning('请填写内部备注') - return - } + if (!noteOrder.value) return noteSaving.value = true try { const target = noteOrder.value - await addPlatformUnifiedOrderNote(String(target.tenantId), String(target.orderId), content) + await updatePlatformUnifiedOrderRemarks(String(target.tenantId), String(target.orderId), { + expectedVersion: Number(target.orderVersion || 0), + internalRemark: noteForm.internalRemark.trim() || null, + warehouseRemark: noteForm.warehouseRemark.trim() || null, + customerServiceRemark: noteForm.customerServiceRemark.trim() || null, + }) noteOpen.value = false await loadOrders() if (detail.value?.summary.orderId === target.orderId) await loadDetail(true) noteOrder.value = null - ElMessage.success('内部备注已保存') + ElMessage.success('订单备注已保存') } catch (error) { - ElMessage.error(getApiErrorDetails(error, '内部备注保存失败,请稍后重试').message) + ElMessage.error(getApiErrorDetails(error, '订单备注保存失败,请稍后重试').message) } finally { noteSaving.value = false } @@ -2725,8 +2728,13 @@ onBeforeUnmount(() => { /> - - + + + + + + +