This commit is contained in:
2026-09-03 17:49:01 +08:00
parent afb534d4ee
commit 4ae5598c2d
10 changed files with 176 additions and 153 deletions
@@ -43,6 +43,8 @@ const snapshot: InboundFeeSnapshot = {
rateEffectiveFrom: '2026-08-01T00:00:00', rateEffectiveTo: null,
calculatedAt: '2026-08-04T10:00:00', confirmedAt: '2026-08-04T10:01:00', sentAt: '2026-08-04T10:02:00',
nextProcessStatus: 'WAIT_SPLIT',
customerConfirmationStatus: 'PENDING_CONFIRMATION', customerConfirmedBy: null,
customerConfirmedByName: null, customerConfirmedAt: null,
}
enableAutoUnmount(afterEach)
+6
View File
@@ -8,6 +8,8 @@ export interface InboundFeeSnapshot {
feeStatus: InboundFeeStatus
inboundId: string
inboundCode: string | null
measurementId?: string | null
measurementSource?: string | null
chargeRuleId: string
ruleCode: string
chargeRuleVersion: number
@@ -44,6 +46,10 @@ export interface InboundFeeSnapshot {
confirmedAt: string | null
sentAt: string | null
nextProcessStatus: string | null
customerConfirmationStatus: 'PENDING_CONFIRMATION' | 'CONFIRMED'
customerConfirmedBy: string | null
customerConfirmedByName: string | null
customerConfirmedAt: string | null
}
export interface AdjustPublishedInboundFeeRequest {
@@ -10,7 +10,6 @@ import LoadErrorState from '@/shared/components/LoadErrorState.vue'
import { getApiErrorDetails, type ApiErrorDetails } from '@/shared/api/http'
import { inboundProcessStatusLabels, inboundStatusLabel } from '../status'
import { createManualCartonScan, downloadInboundMeasurementImage, getInboundMeasurement, saveInboundMeasurementDraft, submitInboundMeasurement, updateConfirmedPackageCount } from './api'
import { listCustomerChargeRules, type CustomerChargeRule } from '@/modules/fba-pricing'
import type {
InboundMeasurementDetail,
InboundMeasurementItem,
@@ -21,7 +20,6 @@ const props = defineProps<{
customerId?: string
tenantId?: string
inboundId: string
openWarehouseSuggestion?: boolean
}>()
const emit = defineEmits<{
@@ -62,15 +60,10 @@ const loading = ref(false)
const saving = ref(false)
const submitting = ref(false)
const manualEntryOpen = ref(false)
const warehouseSuggestionOpen = ref(false)
const warehouseSuggestionAutoOpened = ref(false)
const warehouseSuggestion = reactive({ itemId: '', packageCount: 1, remark: '', coefficientMode: 'ADD' as 'ADD' | 'MULTIPLY' })
const manualEntrySubmitting = ref(false)
const manualEntryImage = ref<File | null>(null)
const manualEntryErrors = ref<string[]>([])
const detail = ref<InboundMeasurementDetail | null>(null)
const chargeRules = ref<CustomerChargeRule[]>([])
const chargeRulesLoading = ref(false)
const loadError = ref<ApiErrorDetails | null>(null)
const operationError = ref<ApiErrorDetails | null>(null)
const conflictDetected = ref(false)
@@ -105,7 +98,6 @@ const canView = computed(() => access.can('platform:firstleg:inbound:measure:vie
const canEdit = computed(() => access.can('platform:firstleg:inbound:measure:edit'))
const canManualEntry = computed(() => access.can('platform:firstleg:inbound:measure:manual-entry'))
const canSubmit = computed(() => access.can('platform:firstleg:inbound:measure:submit'))
const canWarehouseSuggestion = computed(() => access.can('platform:firstleg:inbound:warehouse-suggestion:edit'))
const customerId = computed(() => props.customerId || props.tenantId || '')
const hasContext = computed(() => Boolean(customerId.value && props.inboundId))
const countEditableProcess = computed(() => ['WAIT_MEASURE', 'MEASURING', 'MEASURE_PENDING', 'MEASURE_REJECTED'].includes(detail.value?.processStatus || ''))
@@ -123,9 +115,6 @@ const isDirty = computed(() => Boolean(detail.value) && snapshot.value !== basel
const registrationOpen = computed(() => hasContext.value && canView.value && Boolean(detail.value))
const requiredPermission = computed(() => canEdit.value ? 'platform:firstleg:inbound:measure:edit' : null)
const sourceLabel = computed(() => ({ THIRD_PARTY: '第三方设备', MANUAL: '平台人工', OPENAPI: '第三方设备' }[detail.value?.sourceType || ''] || (detail.value?.sourceType ? '未知来源' : '尚未测量')))
const suggestionItem = computed(() => form.items.find((item) => item.itemId === warehouseSuggestion.itemId) || null)
const suggestionTotalVolume = computed(() => Number(suggestionItem.value?.serverValues.unitVolumeCbm || 0) * warehouseSuggestion.packageCount)
const suggestionGirth = computed(() => suggestionItem.value ? suggestionItem.value.lengthCm + 2 * (suggestionItem.value.widthCm + suggestionItem.value.heightCm) : 0)
interface MeasurementDeviationWarning {
key: string
@@ -305,36 +294,6 @@ function editableItem(item: InboundMeasurementItem): EditableMeasurementItem {
}
}
function openWarehouseSuggestion() {
if (!canWarehouseSuggestion.value && !canEdit.value) return
const first = form.items[0]
warehouseSuggestion.itemId = first?.itemId || ''
warehouseSuggestion.packageCount = first?.packageCount || 1
warehouseSuggestion.remark = first?.remark || ''
warehouseSuggestion.coefficientMode = first?.coefficientMode === 'MULTIPLY' ? 'MULTIPLY' : 'ADD'
warehouseSuggestionOpen.value = true
}
function applyWarehouseSuggestion() {
const item = suggestionItem.value
if (!item) { ElMessage.warning('请选择当前实测 SKU'); return }
if (!Number.isInteger(warehouseSuggestion.packageCount) || warehouseSuggestion.packageCount < 1) { ElMessage.warning('箱数必须是大于 0 的整数'); return }
item.packageCount = warehouseSuggestion.packageCount
item.remark = warehouseSuggestion.remark.trim() || null
item.coefficientMode = warehouseSuggestion.coefficientMode
warehouseSuggestionOpen.value = false
ElMessage.success('仓库建议数据已加入,请保存测量数据')
}
async function loadChargeRules() {
if (!customerId.value || !access.can('firstleg:fba-pricing:rule:view')) return
chargeRulesLoading.value = true
try {
chargeRules.value = await listCustomerChargeRules(customerId.value)
} catch { chargeRules.value = [] }
finally { chargeRulesLoading.value = false }
}
function clearForm() {
form.measurementId = null
form.version = 0
@@ -366,11 +325,6 @@ async function loadMeasurement() {
const resource = await getInboundMeasurement(customerId.value, props.inboundId)
if (sequence !== requestSequence) return
applyDetail(resource)
void loadChargeRules()
if (props.openWarehouseSuggestion && !warehouseSuggestionAutoOpened.value && form.items.length) {
warehouseSuggestionAutoOpened.value = true
openWarehouseSuggestion()
}
} catch (error) {
if (sequence !== requestSequence) return
detail.value = null
@@ -603,17 +557,6 @@ function measurementRowClassName({ row }: { row: EditableMeasurementItem }) {
return validationErrors.value[row.clientKey]?.length ? 'measurement-table__row--error' : ''
}
function chargeRuleName(ruleId: string | null | undefined) {
if (!ruleId) return '默认规则'
const rule = chargeRules.value.find((item) => item.id === ruleId)
return rule ? chargeRuleLabel(rule) : ruleId
}
function chargeRuleLabel(rule: CustomerChargeRule) {
const remark = rule.remark?.trim()
return `系数 ${rule.lengthFactor}${remark ? ` · ${remark}` : ''}${rule.defaultRule ? '(默认)' : ''}`
}
watch(
() => [customerId.value, props.inboundId, canView.value] as const,
() => {
@@ -623,7 +566,6 @@ watch(
operationError.value = null
conflictDetected.value = false
validationErrors.value = {}
warehouseSuggestionAutoOpened.value = false
clearForm()
if (hasContext.value && canView.value) void loadMeasurement()
},
@@ -733,7 +675,6 @@ defineExpose({ saveDraft, submitForReview, updateActualWeights, mayMaintain, may
<div class="measurement-section__heading">
<div><h3>箱规明细</h3></div>
<div class="measurement-section-actions">
<el-button v-if="canWarehouseSuggestion" type="primary" plain :disabled="!mayMaintain || !form.items.length" @click="openWarehouseSuggestion"><Plus :size="15" />添加仓库建议数据</el-button>
<el-button v-if="canManualEntry" type="primary" plain :disabled="!mayCreateManualEntry" @click="openManualEntry"><Plus :size="15" />手动录入箱规</el-button>
</div>
</div>
@@ -789,15 +730,6 @@ defineExpose({ saveDraft, submitForReview, updateActualWeights, mayMaintain, may
/>
</template>
</el-table-column>
<el-table-column label="计算方式" min-width="150">
<template #default="{ row }">
<el-select v-model="row.coefficientMode" placeholder="选择计算方式">
<el-option label="实测原尺寸(不调整)" value="ACTUAL" />
<el-option label="按箱规尺寸策略" value="ADD" />
<el-option label="按箱规尺寸系数" value="MULTIPLY" :disabled="!chargeRules.some((rule) => rule.defaultRule && rule.status === 'ACTIVE')" />
</el-select>
</template>
</el-table-column>
<el-table-column label="总体积(m³)" width="108" align="right">
<template #default="{ row }">{{ formatNumber(row.serverValues.totalVolumeCbm, 4) }}</template>
</el-table-column>
@@ -812,18 +744,6 @@ defineExpose({ saveDraft, submitForReview, updateActualWeights, mayMaintain, may
</el-table-column>
</el-table>
<el-empty v-else description="暂无扫描箱测量数据" />
<div v-if="form.items.length" class="warehouse-suggestion-table">
<h3>仓库建议数据</h3>
<el-table :data="form.items" border stripe empty-text="暂无仓库建议数据">
<el-table-column prop="itemNo" label="当前实测 SKU" min-width="150" />
<el-table-column label="外箱规 长×宽×高(cm)" min-width="190"><template #default="{ row }">{{ formatNumber(row.outerLengthCm) }} × {{ formatNumber(row.outerWidthCm) }} × {{ formatNumber(row.outerHeightCm) }}</template></el-table-column>
<el-table-column prop="packageCount" label="箱数" width="90" />
<el-table-column label="周长(cm)" width="110"><template #default="{ row }">{{ formatNumber(row.serverValues.girthCm) }}</template></el-table-column>
<el-table-column label="总体积(m³)" width="120"><template #default="{ row }">{{ formatNumber(row.serverValues.unitVolumeCbm == null ? null : row.serverValues.unitVolumeCbm * row.packageCount, 6) }}</template></el-table-column>
<el-table-column label="计算策略" min-width="190"><template #default="{ row }">{{ row.coefficientMode === 'MULTIPLY' ? '按箱规尺寸系数' : '按箱规尺寸策略' }}<span v-if="row.coefficientMode === 'MULTIPLY'">({{ chargeRuleName(row.chargeRuleId) }})</span></template></el-table-column>
<el-table-column prop="remark" label="备注" min-width="160" show-overflow-tooltip />
</el-table>
</div>
</section>
</fieldset>
</form>
@@ -837,21 +757,6 @@ defineExpose({ saveDraft, submitForReview, updateActualWeights, mayMaintain, may
</div>
</footer>
<AppDialog v-model="warehouseSuggestionOpen" title="添加仓库建议数据" width="min(680px, calc(100vw - 28px))" confirm-text="加入建议" @confirm="applyWarehouseSuggestion">
<el-form label-position="top" class="warehouse-suggestion-form">
<el-form-item label="当前实测 SKU" required><el-select v-model="warehouseSuggestion.itemId" filterable><el-option v-for="item in form.items" :key="item.itemId" :label="`${item.itemNo} · ${item.boxSpecification || item.boxCode || '实测SKU'}`" :value="item.itemId" /></el-select></el-form-item>
<el-form-item label="箱数" required><el-input-number v-model="warehouseSuggestion.packageCount" :min="1" :max="1000000" step-strictly /></el-form-item>
<el-form-item label="计算方式"><el-select v-model="warehouseSuggestion.coefficientMode"><el-option label="按箱规尺寸策略" value="ADD" /><el-option label="按箱规尺寸系数" value="MULTIPLY" /></el-select></el-form-item>
<el-form-item label="备注"><el-input v-model="warehouseSuggestion.remark" type="textarea" :rows="3" maxlength="500" show-word-limit /></el-form-item>
</el-form>
<dl v-if="suggestionItem" class="suggestion-preview">
<div><dt>长 × 宽 × 高</dt><dd>{{ formatNumber(suggestionItem.lengthCm) }} × {{ formatNumber(suggestionItem.widthCm) }} × {{ formatNumber(suggestionItem.heightCm) }} cm</dd></div>
<div><dt>箱规周长</dt><dd>{{ formatNumber(suggestionGirth) }} cm</dd></div>
<div><dt>单箱体积</dt><dd>{{ formatNumber(suggestionItem.serverValues.unitVolumeCbm, 6) }} m³</dd></div>
<div><dt>总体积</dt><dd>{{ formatNumber(suggestionTotalVolume, 6) }} m³</dd></div>
</dl>
</AppDialog>
<el-dialog v-model="imagePreviewOpen" title="当前有效测量图片" width="min(720px, 92vw)" destroy-on-close @closed="releaseImagePreview">
<div class="measurement-image-preview">
<el-skeleton v-if="imagePreviewLoading" :rows="5" animated />
+6 -2
View File
@@ -34,8 +34,12 @@ export interface InboundMeasurementItem {
outerLengthCm: number | null
outerWidthCm: number | null
outerHeightCm: number | null
calculatedOuterLengthCm?: number | null
calculatedOuterWidthCm?: number | null
calculatedOuterHeightCm?: number | null
calculationRuleVersion?: string | null
chargeRuleId?: MeasurementIdentifier | null
coefficientMode?: 'ADD' | 'MULTIPLY' | null
coefficientMode?: 'ACTUAL' | 'ADD' | 'MULTIPLY' | null
remark: string | null
}
@@ -133,7 +137,7 @@ export interface SaveInboundMeasurementItemRequest {
outerWidthCm: number | null
outerHeightCm: number | null
chargeRuleId?: MeasurementIdentifier | null
coefficientMode?: 'ADD' | 'MULTIPLY' | null
coefficientMode?: 'ACTUAL' | 'ADD' | 'MULTIPLY' | null
remark: string | null
}