@@ -9,7 +9,7 @@ import { getApiErrorDetails, type ApiErrorDetails } from '@/shared/api/http'
import { useUnsavedWorkRegistration } from '@/core/unsaved-work/useUnsavedWorkRegistration'
import { listOverseasPurchaseWarehouseOptions } from '../api/platformOverseasBusiness'
import { submitProcurementCommand , uploadProcurementFile } from '../api/procurementCommands'
import { submitProcurementWorkflow , type ProcurementWorkflow , type ProcurementOutbound , type ProcurementWorkflowReceipt } from '../api/procurementWorkflow'
import { editProcurementOutbound , submitProcurementWorkflow , type ProcurementWorkflow , type ProcurementOutbound , type ProcurementWorkflowReceipt } from '../api/procurementWorkflow'
import { dateTime , statusLabel } from '../procurementPresentation'
import type { PlatformProcurement } from '../procurementTypes'
import type { OverseasPurchaseWarehouseOption } from '../types'
@@ -21,7 +21,7 @@ const open = ref(false), saving = ref(false), uploading = ref(false), checkingFe
const action = ref ( '' ) , batchId = ref ( '' ) , warehouses = ref < OverseasPurchaseWarehouseOption [ ] > ( [ ] ) , warehouseLoading = ref ( false )
const form = reactive ( { quantity : 1 , trackingNo : '' , fileIds : [ ] as string [ ] , warehouseId : '' , decision : 'APPROVE' as 'APPROVE' | 'REJECT' , reason : '' } )
const uploadedNames = ref < string [ ] > ( [ ] )
const title = computed ( ( ) => ( { review : '审核采购单' , receipts : '登记收货' , 'request-confirmation' : '采购完成' , outbounds : '创建出库单' , dispatch : '开始派送 ', complete : '上传签收凭证' } ) [ action . value ] || '采购操作' )
const title = computed ( ( ) => ( { review : '审核采购单' , receipts : '登记收货' , 'request-confirmation' : '采购完成' , outbounds : '创建出库单' , 'edit-outbound' : '编辑出库单' , dispatch : '开始派送' , cancel : '取消出库单 ', complete : '上传签收凭证' } ) [ action . value ] || '采购操作' )
const permission = computed ( ( ) => ` platform:overseas-business:purchase: ${ action . value === 'review' ? 'review' : [ 'receipts' , 'request-confirmation' ] . includes ( action . value ) ? 'receive-overseas' : 'ship-customer' } ` )
const can = ( name : string ) => access . can ( ` platform:overseas-business:purchase: ${ name } ` )
const permissionHint = computed ( ( ) => props . workflow . status === 'RECEIVING' && ! can ( 'receive-overseas' )
@@ -49,6 +49,7 @@ async function begin(name: string, batch?: ProcurementOutbound) {
}
action . value = name ; batchId . value = batch ? . id || '' ; error . value = null ; warehouseLoading . value = false
Object . assign ( form , { quantity : 1 , trackingNo : '' , fileIds : [ ] , warehouseId : '' , decision : 'APPROVE' , reason : '' } ) ; uploadedNames . value = [ ]
if ( name === 'edit-outbound' && batch ) Object . assign ( form , { quantity : batch . quantity , trackingNo : batch . trackingNo } )
fingerprint = '' ; requestKey = '' ; openedScope = scope ( ) ; open . value = true ; captureBaseline ( )
if ( name === 'review' ) {
warehouseLoading . value = true
@@ -75,17 +76,18 @@ async function save() {
if ( saving . value || uploading . value || openedScope !== scope ( ) || ! access . can ( permission . value ) ) return
if ( action . value === 'review' && form . decision === 'APPROVE' && ! form . warehouseId ) { ElMessage . warning ( '请选择收货仓' ) ; return }
if ( action . value === 'review' && form . decision === 'REJECT' && ! form . reason . trim ( ) ) { ElMessage . warning ( '请填写拒绝原因' ) ; return }
if ( [ 'receipts' , 'outbounds' ] . includes ( action . value ) && ( ! Number . isSafeInteger ( form . quantity ) || form . quantity < 1 ) ) { ElMessage . warning ( '请输入有效数量' ) ; return }
if ( [ 'receipts' , 'outbounds' , 'edit-outbound' ] . includes ( action . value ) && ( ! Number . isSafeInteger ( form . quantity ) || form . quantity < 1 ) ) { ElMessage . warning ( '请输入有效数量' ) ; return }
if ( action . value === 'receipts' && form . quantity > props . detail . customerView . order . purchaseQuantity - props . workflow . receivedQuantity ) { ElMessage . warning ( '收货数量不能超过剩余采购数量' ) ; return }
if ( action . value === 'outbounds' && ( ! form . trackingNo . trim ( ) || form . quantity > props . workflow . availableQuantity ) ) { ElMessage . warning ( '请填写物流跟踪号,数量不能超过可出库库存' ) ; return }
if ( [ 'outbounds' , 'edit-outbound' ] . includes ( action . value ) && ( ! form . trackingNo . trim ( ) || form . quantity > props . workflow . availableQuantity + ( action . value === 'edit-outbound' ? ( props . workflow . outbounds . find ( row => row . id === batchId . value ) ? . quantity || 0 ) : 0 ) ) ) { ElMessage . warning ( '请填写物流跟踪号,数量不能超过可出库库存' ) ; return }
if ( [ 'receipts' , 'complete' ] . includes ( action . value ) && ! form . fileIds . length ) { ElMessage . warning ( action . value === 'receipts' ? '请上传收货照片' : '请上传签收文件或图片' ) ; return }
const data = { version : props . workflow . version , ... ( action . value === 'receipts' ? { quantity : form . quantity , fileIds : [ ... form . fileIds ] } : action . value === 'outbounds' ? { quantity : form . quantity , trackingNo : form . trackingNo . trim ( ) } : action . value === 'complete' ? { fileIds : [ ... form . fileIds ] } : { } ) }
const data = { version : props . workflow . version , ... ( action . value === 'receipts' ? { quantity : form . quantity , fileIds : [ ... form . fileIds ] } : [ 'outbounds' , 'edit-outbound' ] . includes ( action . value ) ? { quantity : form . quantity , trackingNo : form . trackingNo . trim ( ) } : action . value === 'complete' ? { fileIds : [ ... form . fileIds ] } : { } ) }
const next = JSON . stringify ( [ action . value , batchId . value , form . warehouseId , form . decision , form . reason , data ] )
if ( fingerprint !== next || ! requestKey ) { fingerprint = next ; requestKey = crypto . randomUUID ( ) }
const current = dialogGeneration
saving . value = true ; error . value = null
try {
if ( action . value === 'review' ) await submitProcurementCommand ( props . workflow . orderNo , props . customerId , 'review' , { version : props . workflow . version , idempotencyKey : requestKey , decision : form . decision , purchaseWarehouseId : form . decision === 'APPROVE' ? form . warehouseId : undefined , reason : form . reason . trim ( ) || undefined } )
else if ( action . value === 'edit-outbound' ) await editProcurementOutbound ( props . workflow . orderNo , props . customerId , batchId . value , { ... data , idempotencyKey : requestKey } )
else await submitProcurementWorkflow ( props . workflow . orderNo , props . customerId , batchId . value ? ` outbounds/ ${ encodeURIComponent ( batchId . value ) } / ${ action . value } ` : action . value , { ... data , idempotencyKey : requestKey } )
if ( current !== dialogGeneration ) return
captureBaseline ( ) ; open . value = false ; ElMessage . success ( '操作成功' ) ; emit ( 'changed' )
@@ -94,7 +96,7 @@ async function save() {
}
const receiptColumns : DataTableColumn [ ] = [ { prop : 'quantity' , label : '收货数量' , width : 140 } , { key : 'receivedAt' , label : '收货时间' , minWidth : 170 , formatter : ( row : ProcurementWorkflowReceipt ) => dateTime ( row . receivedAt ) } , { key : 'files' , label : '照片' , minWidth : 180 , slotName : 'files' } ]
const outboundColumns : DataTableColumn [ ] = [
{ key : 'actions' , label : '操作' , width : 180 , slotName : 'actions' , fixed : 'left' } , { prop : 'outboundNo' , label : '出库单号' , minWidth : 190 } ,
{ key : 'actions' , label : '操作' , width : 250 , slotName : 'actions' , fixed : 'left' } , { prop : 'outboundNo' , label : '出库单号' , minWidth : 190 } ,
{ key : 'status' , label : '状态' , width : 120 , formatter : ( row : ProcurementOutbound ) => statusLabel ( row . status ) , afterColumn : 'outboundNo' } ,
{ prop : 'quantity' , label : '出库数量' , width : 110 } , { prop : 'trackingNo' , label : '物流跟踪号' , minWidth : 190 } ,
{ key : 'createdAt' , label : '创建时间' , minWidth : 170 , formatter : ( row : ProcurementOutbound ) => dateTime ( row . createdAt ) } ,
@@ -114,14 +116,14 @@ const fileName = (id: string) => props.detail.files.find(file => file.fileId ===
< el-text v-if = "permissionHint" type="warning" role="status" > {{ permissionHint }} < / el -text >
< dl class = "density-fields inventory" > < div > < dt > 累计收货 < / dt > < dd > { { workflow . receivedQuantity } } < / dd > < / div > < div > < dt > 采购库存 < / dt > < dd > { { workflow . stockQuantity } } < / dd > < / div > < div > < dt > 出库处理中 < / dt > < dd > { { workflow . reservedQuantity } } < / dd > < / div > < div > < dt > 可出库 < / dt > < dd > { { workflow . availableQuantity } } < / dd > < / div > < / dl >
< h3 > 收货记录 < / h3 > < DataTable :data = "workflow.receipts" :columns = "receiptColumns" row -key = " id " empty -text = " 暂无收货记录 " > < template # files = "{ row }" > < el-button v-for = "id in row.fileIds" :key="id" :disabled="!!downloading" link type="primary" @click="emit('download', id)" > {{ fileName ( id ) }} < / el -button > < / template > < / DataTable >
< h3 > 出库单 < / h3 > < DataTable :data = "workflow.outbounds" :columns = "outboundColumns" row -key = " id " empty -text = " 暂无出库单 " > < template # actions = "{ row }" > < el-button v-if = "row.status === 'IN_TRANSIT'" :disabled="!can('ship-customer')" title="开始本批次派送 " link type="primary" @click="begin('dispatch', row as ProcurementOutbound)">开始派送</el-button><el-button v-if="row.status === 'DELIVERING'" :disabled="!can('ship-customer')" title="上传图片或文件并确认本批次签收" link type="primary" @click="begin('complete', row as ProcurementOutbound)">上传签收凭证</el-button></template><template #files="{ row }"><el-button v-for="id in row.fileIds" :key="id" :disabled="!!downloading" link type="primary" @click="emit('download', id)" > {{ fileName ( id ) }} < / el -button > < / template > < / DataTable >
< h3 > 出库单 < / h3 > < DataTable :data = "workflow.outbounds" :columns = "outboundColumns" row -key = " id " empty -text = " 暂无出库单 " > < template # actions = "{ row }" > < template v-if = "row.status === 'IN_TRANSIT'"><el-button :disabled="!can('ship-customer')" link type="primary" @click="begin('edit-outbound', row as ProcurementOutbound)">编辑</el-button><el-button :disabled="!can('ship-customer')" link type="danger" @click="begin('cancel', row as ProcurementOutbound)">取消</el-button><el-button :disabled="!can('ship-customer') " link type="primary" @click="begin('dispatch', row as ProcurementOutbound)">开始派送</el-button></template>< el-button v-if="row.status === 'DELIVERING'" :disabled="!can('ship-customer')" link type="primary" @click="begin('complete', row as ProcurementOutbound)">上传签收凭证</el-button></template><template #files="{ row }"><el-button v-for="id in row.fileIds" :key="id" :disabled="!!downloading" link type="primary" @click="emit('download', id)" > {{ fileName ( id ) }} < / el -button > < / template > < / DataTable >
< AppDialog v-model = "open" class="admin-density-dialog admin-workbench is-dialog" :title="title" width="580px" confirm-text="确认" :confirm-loading="saving" :confirm-disabled="uploading || warehouseLoading" @confirm="save" >
< el -form :model = "form" class = "density-form density-form--outlined" label -position = " top " :disabled = "saving" >
< template v-if = "action === 'review'"><el-form-item label="审核结果"><el-radio-group v-model="form.decision"><el-radio value="APPROVE">通过</el-radio><el-radio value="REJECT">拒绝</el-radio></el-radio-group></el-form-item><el-form-item v-if="form.decision === 'APPROVE'" label="收货仓" required><el-select v-model="form.warehouseId" filterable :loading="warehouseLoading"><el-option v-for="warehouse in warehouses" :key="warehouse.warehouseId" :value="warehouse.warehouseId" :label="warehouse.warehouseName" /></el-select></el-form-item><el-form-item v-else label="拒绝原因" required><el-input v-model="form.reason" type="textarea" :rows="2" maxlength="1000" / > < / el-form-item > < / template >
< el-form-item v-if = "['receipts', 'outbounds'].includes(action)" :label="action === 'receipts' ? '本批收货数量' : '本批出库数量'" required><el-input-number v-model="form.quantity" :min="1" :max="action === 'outbounds' ? workflow.availableQuantity : detail.customerView.order.purchaseQuantity - workflow.receivedQuantity " :precision="0" / > < / el-form-item >
< el-form-item v-if = "action === 'outbounds' " label="物流跟踪号" required><el-input v-model="form.trackingNo" maxlength="100" / > < / el-form-item >
< el-form-item v-if = "['receipts', 'outbounds', 'edit-outbound' ].includes(action)" :label="action === 'receipts' ? '本批收货数量' : '本批出库数量'" required><el-input-number v-model="form.quantity" :min="1" :max="action === 'outbounds' ? workflow.availableQuantity : workflow.availableQuantity + (batchId ? (workflow.outbounds.find(row => row.id === batchId)?.quantity || 0) : 0) " :precision="0" / > < / el-form-item >
< el-form-item v-if = "['outbounds', 'edit-outbound'].includes(action) " label="物流跟踪号" required><el-input v-model="form.trackingNo" maxlength="100" / > < / el-form-item >
< el-form-item v-if = "['receipts', 'complete'].includes(action)" :label="action === 'receipts' ? '收货照片' : '签收文件或图片'" required><input type="file" multiple :accept="action === 'receipts' ? 'image/*' : 'image/*,application/pdf'" :disabled="saving || uploading" @change="upload" /><div v-for="name in uploadedNames" :key="name" > {{ name }} < / div > < / el -form -item >
< p v-if = "action === 'request-confirmation'">确认采购完成并提交客户确认?</p><p v-if="action === 'dispatch'" > 确认本批次开始派送 ? < / p >
< p v-if = "action === 'request-confirmation'">确认采购完成并提交客户确认?</p><p v-if="action === 'dispatch'"> 确认本批次开始派送?</p><p v-if="action === 'cancel'" > 确认取消本批出库单 ? 取消后库存占用将释放 。 < / p >
< / el -form > < el-alert v-if = "error" :title="error.message" type="error" :closable="false"><ApiErrorMeta :details="error" / > < / el-alert >
< / AppDialog >
< / section >