feat: edit collaboration remarks across order loading

This commit is contained in:
2026-09-14 16:56:11 +08:00
parent 33520e8fd3
commit 9e28e10e5f
16 changed files with 125 additions and 78 deletions
@@ -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 () => {
@@ -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<number | string>, ApiEnvelope<number | string>>(
`${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<UnifiedOrderRemarksUpdateResponse>, ApiEnvelope<UnifiedOrderRemarksUpdateResponse>>(
`${unifiedOrdersApiPath}/${orderId}/remarks`,
request,
{ params: { tenantId } },
).then(unwrapApiResponse)
}
@@ -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: '<div />' } }))
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: '待分柜' }],
}))
@@ -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: [],
@@ -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(),
@@ -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")
@@ -9,7 +9,7 @@ import {
approvePlatformUnifiedOrder,
batchApprovePlatformUnifiedOrders,
getPlatformUnifiedOrderBatchApprovalPreview,
addPlatformUnifiedOrderNote,
updatePlatformUnifiedOrderRemarks,
listPlatformUnifiedOrders,
updatePlatformUnifiedOrderPlanning,
unifiedOrderStatusOptions,
@@ -179,7 +179,7 @@ const coLoadError = ref<ApiErrorDetails | null>(null)
const approvalOrder = ref<UnifiedOrderResource | null>(null)
const noteOpen = ref(false)
const noteSaving = ref(false)
const noteContent = ref('')
const noteForm = reactive({ internalRemark: '', warehouseRemark: '', customerServiceRemark: '' })
const noteOrder = ref<UnifiedOrderResource | null>(null)
const holdDetail = ref<InboundHoldDetail | null>(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(() => {
/>
</AppDrawer>
<AppDialog class="admin-density-dialog" v-model="noteOpen" title="添加内部备注" confirm-text="保存备注" :confirm-loading="noteSaving" @confirm="saveNote">
<el-input v-model="noteContent" type="textarea" :rows="5" maxlength="2000" show-word-limit placeholder="请输入仅平台内部可见的备注" aria-label="内部备注内容" />
<AppDialog class="admin-density-dialog" v-model="noteOpen" title="编辑订单备注" width="min(680px, calc(100vw - 28px))" confirm-text="保存备注" :confirm-loading="noteSaving" @confirm="saveNote">
<el-form label-position="top" class="order-remarks-form">
<el-form-item label="客户备注(只读)"><el-input :model-value="noteOrder?.customerRemark || '—'" disabled /></el-form-item>
<el-form-item label="内部备注"><el-input v-model="noteForm.internalRemark" type="textarea" :rows="3" maxlength="1000" show-word-limit placeholder="可填写、修改或清空内部备注" /></el-form-item>
<el-form-item label="仓库备注"><el-input v-model="noteForm.warehouseRemark" type="textarea" :rows="3" maxlength="1000" show-word-limit placeholder="可填写、修改或清空仓库备注" /></el-form-item>
<el-form-item label="客服备注"><el-input v-model="noteForm.customerServiceRemark" type="textarea" :rows="3" maxlength="1000" show-word-limit placeholder="可填写、修改或清空客服备注" /></el-form-item>
</el-form>
</AppDialog>
<AppDialog class="admin-density-dialog" v-model="holdDialogOpen" :title="holdAction === 'HOLD' ? '确认扣货' : '确认解除扣货'" :confirm-text="holdAction === 'HOLD' ? '确认扣货' : '确认解除'" :confirm-type="holdAction === 'HOLD' ? 'danger' : 'primary'" :confirm-loading="holdSaving" :confirm-disabled="!holdReason.trim() || holdReason.trim().length > 1000" width="min(600px, calc(100vw - 28px))" destroy-on-close @confirm="submitHoldAction">