fix(admin): update submitted measurement calculation strategy
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
export { getInboundMeasurement, saveInboundMeasurementDraft } from './measurement/api'
|
export { getInboundMeasurement, saveInboundMeasurementDraft, updateMeasurementCalculationStrategy } from './measurement/api'
|
||||||
export { createInboundHoldIdempotencyKey, downloadPlatformInboundHoldEvidence, getPlatformInboundHold, holdPlatformInbound, releasePlatformInbound, uploadPlatformInboundHoldEvidence } from './core/api'
|
export { createInboundHoldIdempotencyKey, downloadPlatformInboundHoldEvidence, getPlatformInboundHold, holdPlatformInbound, releasePlatformInbound, uploadPlatformInboundHoldEvidence } from './core/api'
|
||||||
export { default as InboundMeasurementPanel } from './measurement/InboundMeasurementPanel.vue'
|
export { default as InboundMeasurementPanel } from './measurement/InboundMeasurementPanel.vue'
|
||||||
export { default as InboundMeasurementDialog } from './measurement/InboundMeasurementDialog.vue'
|
export { default as InboundMeasurementDialog } from './measurement/InboundMeasurementDialog.vue'
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||||
import { http } from '@/shared/api/http'
|
import { http } from '@/shared/api/http'
|
||||||
import { createManualCartonScan, downloadInboundMeasurementImage, getInboundMeasurement, saveInboundMeasurementDraft, submitInboundMeasurement, updateConfirmedPackageCount } from './api'
|
import { createManualCartonScan, downloadInboundMeasurementImage, getInboundMeasurement, saveInboundMeasurementDraft, submitInboundMeasurement, updateConfirmedPackageCount, updateMeasurementCalculationStrategy } from './api'
|
||||||
import type { SaveInboundMeasurementDraftRequest } from './types'
|
import type { SaveInboundMeasurementDraftRequest } from './types'
|
||||||
|
|
||||||
vi.mock('@/shared/api/http', () => ({
|
vi.mock('@/shared/api/http', () => ({
|
||||||
@@ -76,6 +76,19 @@ describe('inbound measurement API', () => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('updates calculation strategy without resaving the frozen measurement batch', async () => {
|
||||||
|
const payload = { measurementId: '9001', version: 3, coefficientMode: 'ADD' as const, chargeRuleId: null }
|
||||||
|
const saved = { ...payload, version: 4, status: 'SUBMITTED' }
|
||||||
|
vi.mocked(http.put).mockResolvedValue({ code: '200', message: 'success', data: saved })
|
||||||
|
|
||||||
|
await expect(updateMeasurementCalculationStrategy('10001', '7001', payload)).resolves.toEqual(saved)
|
||||||
|
expect(http.put).toHaveBeenCalledWith(
|
||||||
|
'/platform/v1/first-leg/inbounds/7001/measurement/calculation-strategy',
|
||||||
|
payload,
|
||||||
|
{ params: { tenantId: '10001' } },
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
it('downloads the current measurement image using the scoped file route', async () => {
|
it('downloads the current measurement image using the scoped file route', async () => {
|
||||||
const image = new Blob(['image-data'], { type: 'image/jpeg' })
|
const image = new Blob(['image-data'], { type: 'image/jpeg' })
|
||||||
vi.mocked(http.get).mockResolvedValue(image)
|
vi.mocked(http.get).mockResolvedValue(image)
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import type {
|
|||||||
ConfirmedPackageCountRequest,
|
ConfirmedPackageCountRequest,
|
||||||
SaveInboundMeasurementDraftRequest,
|
SaveInboundMeasurementDraftRequest,
|
||||||
SubmitInboundMeasurementRequest,
|
SubmitInboundMeasurementRequest,
|
||||||
|
UpdateMeasurementCalculationStrategyRequest,
|
||||||
|
MeasurementCalculationStrategyUpdated,
|
||||||
ManualCartonScanRequest,
|
ManualCartonScanRequest,
|
||||||
} from './types'
|
} from './types'
|
||||||
|
|
||||||
@@ -57,6 +59,19 @@ export async function saveInboundMeasurementDraft(
|
|||||||
return unwrapApiResponse(response)
|
return unwrapApiResponse(response)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function updateMeasurementCalculationStrategy(
|
||||||
|
customerId: string,
|
||||||
|
inboundId: string,
|
||||||
|
payload: UpdateMeasurementCalculationStrategyRequest,
|
||||||
|
) {
|
||||||
|
const response = await http.put<ApiEnvelope<MeasurementCalculationStrategyUpdated>, ApiEnvelope<MeasurementCalculationStrategyUpdated>>(
|
||||||
|
`${measurementPath(inboundId)}/calculation-strategy`,
|
||||||
|
payload,
|
||||||
|
{ params: { tenantId: customerId } },
|
||||||
|
)
|
||||||
|
return unwrapApiResponse(response)
|
||||||
|
}
|
||||||
|
|
||||||
export async function submitInboundMeasurement(
|
export async function submitInboundMeasurement(
|
||||||
customerId: string,
|
customerId: string,
|
||||||
inboundId: string,
|
inboundId: string,
|
||||||
|
|||||||
@@ -9,7 +9,12 @@ export type InboundMeasurementProcessStatus =
|
|||||||
| 'WAIT_PRICING'
|
| 'WAIT_PRICING'
|
||||||
| 'FEE_CONFIRMING'
|
| 'FEE_CONFIRMING'
|
||||||
| 'INBOUNDING'
|
| 'INBOUNDING'
|
||||||
|
| 'WAIT_PALLET'
|
||||||
|
| 'WAIT_PALLETIZE'
|
||||||
| 'WAIT_SPLIT'
|
| 'WAIT_SPLIT'
|
||||||
|
| 'LOADING'
|
||||||
|
| 'LOADED'
|
||||||
|
| 'DELIVERED'
|
||||||
| 'VOIDED'
|
| 'VOIDED'
|
||||||
|
|
||||||
export interface InboundMeasurementItem {
|
export interface InboundMeasurementItem {
|
||||||
@@ -158,6 +163,18 @@ export interface InboundMeasurementDraftSaved {
|
|||||||
status: string
|
status: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface UpdateMeasurementCalculationStrategyRequest {
|
||||||
|
measurementId: MeasurementIdentifier
|
||||||
|
version: number
|
||||||
|
coefficientMode: 'ACTUAL' | 'ADD' | 'MULTIPLY'
|
||||||
|
chargeRuleId?: MeasurementIdentifier | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MeasurementCalculationStrategyUpdated extends InboundMeasurementDraftSaved {
|
||||||
|
coefficientMode: 'ACTUAL' | 'ADD' | 'MULTIPLY'
|
||||||
|
chargeRuleId: MeasurementIdentifier | null
|
||||||
|
}
|
||||||
|
|
||||||
export interface ConfirmedPackageCountRequest {
|
export interface ConfirmedPackageCountRequest {
|
||||||
measurementVersion: number
|
measurementVersion: number
|
||||||
confirmedPackageCount: number
|
confirmedPackageCount: number
|
||||||
|
|||||||
@@ -7,11 +7,11 @@ const source = readFileSync(resolve(process.cwd(), 'src/modules/unified-orders/p
|
|||||||
const routeSource = readFileSync(resolve(process.cwd(), 'src/modules/unified-orders/routes.ts'), 'utf8').replace(/\r\n/g, '\n')
|
const routeSource = readFileSync(resolve(process.cwd(), 'src/modules/unified-orders/routes.ts'), 'utf8').replace(/\r\n/g, '\n')
|
||||||
|
|
||||||
describe('UnifiedOrdersView contract', () => {
|
describe('UnifiedOrdersView contract', () => {
|
||||||
it('loads the reference unit price while rechecking the confirmation lock before sending', () => {
|
it('loads the reference unit price and lets the backend enforce the combined final lock', () => {
|
||||||
const openDialogSource = source.slice(source.indexOf('async function openFeeConfirmDialog()'), source.indexOf('async function executeConfirmAndSendFee()'))
|
const openDialogSource = source.slice(source.indexOf('async function openFeeConfirmDialog()'), source.indexOf('async function executeConfirmAndSendFee()'))
|
||||||
const sendFeeSource = source.slice(source.indexOf('async function executeConfirmAndSendFee()'), source.indexOf('function handleOrderSelectionChange'))
|
const sendFeeSource = source.slice(source.indexOf('async function executeConfirmAndSendFee()'), source.indexOf('function handleOrderSelectionChange'))
|
||||||
expect(openDialogSource).toContain('getLatestInboundFee')
|
expect(openDialogSource).toContain('getLatestInboundFee')
|
||||||
expect(sendFeeSource).toContain('getLatestInboundFee')
|
expect(sendFeeSource).not.toContain('getLatestInboundFee')
|
||||||
expect(sendFeeSource).toContain('adjustPublishedInboundFee')
|
expect(sendFeeSource).toContain('adjustPublishedInboundFee')
|
||||||
expect(source).toContain('价格表参考单价')
|
expect(source).toContain('价格表参考单价')
|
||||||
expect(source).toContain('本次对客单价')
|
expect(source).toContain('本次对客单价')
|
||||||
@@ -385,7 +385,7 @@ describe('UnifiedOrdersView contract', () => {
|
|||||||
expect(source).toContain('label="箱规系数" value="MULTIPLY"')
|
expect(source).toContain('label="箱规系数" value="MULTIPLY"')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('keeps preview modes independent and only persists outer-carton selectors while the measurement draft is editable', () => {
|
it('keeps preview modes independent and persists outer-carton strategy through its dedicated endpoint', () => {
|
||||||
expect(source).toContain("measurementSectionModes = reactive<Record<MeasurementSection, MeasurementCalculationMode>>")
|
expect(source).toContain("measurementSectionModes = reactive<Record<MeasurementSection, MeasurementCalculationMode>>")
|
||||||
expect(source).toContain(':model-value="measurementSectionModes.customer"')
|
expect(source).toContain(':model-value="measurementSectionModes.customer"')
|
||||||
expect(source).toContain(':model-value="measurementSectionModes.outer"')
|
expect(source).toContain(':model-value="measurementSectionModes.outer"')
|
||||||
@@ -395,10 +395,10 @@ describe('UnifiedOrdersView contract', () => {
|
|||||||
expect(source).not.toContain("updateMeasurementSectionMode('outer', $event)")
|
expect(source).not.toContain("updateMeasurementSectionMode('outer', $event)")
|
||||||
expect(source.match(/@change="selectAndSaveOuterMeasurementMode\(\$event\)"/g)).toHaveLength(2)
|
expect(source.match(/@change="selectAndSaveOuterMeasurementMode\(\$event\)"/g)).toHaveLength(2)
|
||||||
expect(source).not.toContain('@visible-change="handleOuterModeDropdownVisibleChange($event)"')
|
expect(source).not.toContain('@visible-change="handleOuterModeDropdownVisibleChange($event)"')
|
||||||
expect(source.match(/:disabled="Boolean\(measurementStrategySavingId\) \|\| !measurementDraftEditable"/g)).toHaveLength(2)
|
expect(source.match(/:disabled="Boolean\(measurementStrategySavingId\) \|\| !measurementStrategyEditable"/g)).toHaveLength(2)
|
||||||
expect(source).toContain("measurementDetail.value?.processStatus === 'MEASURE_PENDING'")
|
expect(source).toContain('const measurementStrategyEditable = computed')
|
||||||
expect(source).toContain('测量已提交待审核;如需修改箱规计算规则,请先退回测量')
|
expect(source).toContain('客户已确认且该票已装柜完成后,箱规计算规则将冻结')
|
||||||
expect(source).toContain('if (!measurementDraftEditable.value)')
|
expect(source).toContain('if (!measurementStrategyEditable.value)')
|
||||||
expect(source).toContain('const previousMode = measurementSectionModes.outer')
|
expect(source).toContain('const previousMode = measurementSectionModes.outer')
|
||||||
expect(source).toContain('if (!saved) measurementSectionModes.outer = previousMode')
|
expect(source).toContain('if (!saved) measurementSectionModes.outer = previousMode')
|
||||||
expect(source).not.toContain('handleOuterModeDropdownVisibleChange')
|
expect(source).not.toContain('handleOuterModeDropdownVisibleChange')
|
||||||
@@ -432,11 +432,8 @@ describe('UnifiedOrdersView contract', () => {
|
|||||||
expect(source).not.toContain(':disabled="confirmFeeSending || measurementLoading || !hasActualMeasurementData"')
|
expect(source).not.toContain(':disabled="confirmFeeSending || measurementLoading || !hasActualMeasurementData"')
|
||||||
expect(source).toContain('selectAndSaveOuterMeasurementMode($event)')
|
expect(source).toContain('selectAndSaveOuterMeasurementMode($event)')
|
||||||
expect(source).toContain('const saved = await updateMeasurementCalculationMode(null, mode, ruleId)')
|
expect(source).toContain('const saved = await updateMeasurementCalculationMode(null, mode, ruleId)')
|
||||||
expect(source).toContain('coefficientMode: targetIds.has(String(item.id)) ? mode')
|
expect(source).toContain('coefficientMode: mode')
|
||||||
expect(source).toContain('outerLengthCm: item.outerLengthCm')
|
expect(source).toContain('const savedDraft = await updateMeasurementCalculationStrategy')
|
||||||
expect(source).toContain('outerWidthCm: item.outerWidthCm')
|
|
||||||
expect(source).toContain('outerHeightCm: item.outerHeightCm')
|
|
||||||
expect(source).toContain('const savedDraft = await saveInboundMeasurementDraft')
|
|
||||||
expect(source).toContain('version: savedDraft.version')
|
expect(source).toContain('version: savedDraft.version')
|
||||||
expect(source).toContain('waitForSavedMeasurementVersion(')
|
expect(source).toContain('waitForSavedMeasurementVersion(')
|
||||||
expect(source).toContain('Number(refreshed.version) >= savedVersion')
|
expect(source).toContain('Number(refreshed.version) >= savedVersion')
|
||||||
|
|||||||
@@ -73,6 +73,7 @@ import {
|
|||||||
releasePlatformInbound,
|
releasePlatformInbound,
|
||||||
uploadPlatformInboundHoldEvidence,
|
uploadPlatformInboundHoldEvidence,
|
||||||
saveInboundMeasurementDraft,
|
saveInboundMeasurementDraft,
|
||||||
|
updateMeasurementCalculationStrategy,
|
||||||
InboundFeePanel,
|
InboundFeePanel,
|
||||||
InboundMeasurementDialog,
|
InboundMeasurementDialog,
|
||||||
MeasurementEvidenceThumbnail,
|
MeasurementEvidenceThumbnail,
|
||||||
@@ -231,10 +232,15 @@ const measurementDraftEditable = computed(() => (
|
|||||||
can('platform:firstleg:inbound:measure:edit')
|
can('platform:firstleg:inbound:measure:edit')
|
||||||
&& measurementDraftEditableStatuses.has(measurementDetail.value?.processStatus || '')
|
&& measurementDraftEditableStatuses.has(measurementDetail.value?.processStatus || '')
|
||||||
))
|
))
|
||||||
const measurementDraftLockedHint = computed(() => {
|
const measurementStrategyEditable = computed(() => (
|
||||||
|
can('platform:firstleg:inbound:measure:edit')
|
||||||
|
&& Boolean(measurementDetail.value?.measurementId)
|
||||||
|
&& measurementDetail.value?.version != null
|
||||||
|
))
|
||||||
|
const measurementStrategyLockedHint = computed(() => {
|
||||||
if (!can('platform:firstleg:inbound:measure:edit')) return '当前账号缺少测量编辑权限'
|
if (!can('platform:firstleg:inbound:measure:edit')) return '当前账号缺少测量编辑权限'
|
||||||
if (measurementDetail.value?.processStatus === 'MEASURE_PENDING') return '测量已提交待审核;如需修改箱规计算规则,请先退回测量'
|
if (!measurementDetail.value?.measurementId) return '当前没有可修改的测量批次'
|
||||||
return '当前测量状态已冻结,不能修改箱规计算规则'
|
return '客户已确认且该票已装柜完成后,箱规计算规则将冻结'
|
||||||
})
|
})
|
||||||
const cargoImagePreviewUrls = new Set<string>()
|
const cargoImagePreviewUrls = new Set<string>()
|
||||||
const measurementDialogOpen = ref(false)
|
const measurementDialogOpen = ref(false)
|
||||||
@@ -528,8 +534,8 @@ function updateMeasurementSectionMode(section: MeasurementSection, mode: Measure
|
|||||||
|
|
||||||
async function selectAndSaveOuterMeasurementMode(mode: MeasurementCalculationMode, ruleId?: string) {
|
async function selectAndSaveOuterMeasurementMode(mode: MeasurementCalculationMode, ruleId?: string) {
|
||||||
if (measurementStrategySavingId.value) return
|
if (measurementStrategySavingId.value) return
|
||||||
if (!measurementDraftEditable.value) {
|
if (!measurementStrategyEditable.value) {
|
||||||
ElMessage.warning(measurementDraftLockedHint.value)
|
ElMessage.warning(measurementStrategyLockedHint.value)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const previousMode = measurementSectionModes.outer
|
const previousMode = measurementSectionModes.outer
|
||||||
@@ -985,7 +991,7 @@ function measurementCalculationModeForRow(row: Record<string, unknown>): Measure
|
|||||||
async function updateMeasurementCalculationMode(row: Record<string, unknown> | null, mode: MeasurementCalculationMode, selectedRuleId?: string): Promise<boolean> {
|
async function updateMeasurementCalculationMode(row: Record<string, unknown> | null, mode: MeasurementCalculationMode, selectedRuleId?: string): Promise<boolean> {
|
||||||
const summary = detail.value?.summary
|
const summary = detail.value?.summary
|
||||||
const measurement = measurementDetail.value
|
const measurement = measurementDetail.value
|
||||||
if (!summary?.inboundId || !measurement?.measurementId || measurement.version == null || !measurementDraftEditable.value) return false
|
if (!summary?.inboundId || !measurement?.measurementId || measurement.version == null || !measurementStrategyEditable.value) return false
|
||||||
const itemId = normalizedIdentifier(row?.id)
|
const itemId = normalizedIdentifier(row?.id)
|
||||||
const itemNo = normalizedIdentifier(row?.itemNo)
|
const itemNo = normalizedIdentifier(row?.itemNo)
|
||||||
const targetIds = new Set(measurement.items
|
const targetIds = new Set(measurement.items
|
||||||
@@ -1014,35 +1020,11 @@ async function updateMeasurementCalculationMode(row: Record<string, unknown> | n
|
|||||||
ElMessage.warning('该客户尚未启用系数配置,请先到客户计费规则中设置默认规则')
|
ElMessage.warning('该客户尚未启用系数配置,请先到客户计费规则中设置默认规则')
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
const savedDraft = await saveInboundMeasurementDraft(String(summary.tenantId), String(summary.inboundId), {
|
const savedDraft = await updateMeasurementCalculationStrategy(String(summary.tenantId), String(summary.inboundId), {
|
||||||
measurementId: String(measurement.measurementId),
|
measurementId: String(measurement.measurementId),
|
||||||
version: measurement.version,
|
version: measurement.version,
|
||||||
actualGoodsName: measurement.actualGoodsName,
|
coefficientMode: mode,
|
||||||
items: measurement.items.filter((item) => Number(item.packageCount) > 0).map((item) => ({
|
chargeRuleId: mode === 'MULTIPLY' ? activeRule!.id : null,
|
||||||
inboundDetailId: item.inboundDetailId ?? undefined,
|
|
||||||
boxCode: item.boxCode,
|
|
||||||
itemNo: item.itemNo,
|
|
||||||
boxSpecification: item.boxSpecification,
|
|
||||||
packageCount: item.packageCount,
|
|
||||||
lengthCm: item.lengthCm,
|
|
||||||
widthCm: item.widthCm,
|
|
||||||
heightCm: item.heightCm,
|
|
||||||
unitWeightKg: item.unitWeightKg,
|
|
||||||
intelligentImageFileId: item.intelligentImageFileId,
|
|
||||||
machineNo: item.machineNo,
|
|
||||||
measuredAt: item.deviceMeasuredAt,
|
|
||||||
overLimitWarning: item.overLimitWarning,
|
|
||||||
// 人工外箱值是本次策略的计算基数,切换策略时必须一并保留。
|
|
||||||
outerLengthCm: item.outerLengthCm,
|
|
||||||
outerWidthCm: item.outerWidthCm,
|
|
||||||
outerHeightCm: item.outerHeightCm,
|
|
||||||
chargeRuleId: (targetIds.has(String(item.id)) ? mode : item.coefficientMode) === 'MULTIPLY'
|
|
||||||
? (targetIds.has(String(item.id)) || !measurementChargeRules.value.some((rule) => rule.id === String(item.chargeRuleId) && rule.status === 'ACTIVE')
|
|
||||||
? activeRule!.id : item.chargeRuleId)
|
|
||||||
: null,
|
|
||||||
coefficientMode: targetIds.has(String(item.id)) ? mode : item.coefficientMode || 'ACTUAL',
|
|
||||||
remark: item.remark,
|
|
||||||
})),
|
|
||||||
})
|
})
|
||||||
// PUT 返回的新版本已经代表事务保存成功。详情查询可能经过读副本并短暂返回旧版本,
|
// PUT 返回的新版本已经代表事务保存成功。详情查询可能经过读副本并短暂返回旧版本,
|
||||||
// 不能用紧随其后的旧数据否定本次成功保存;先按新版本和本次规则更新本地视图。
|
// 不能用紧随其后的旧数据否定本次成功保存;先按新版本和本次规则更新本地视图。
|
||||||
@@ -1691,11 +1673,6 @@ async function openFeeConfirmDialog() {
|
|||||||
volumeSource: 'ACTUAL',
|
volumeSource: 'ACTUAL',
|
||||||
}, createFeeActionIdempotencyKey('calculate'))
|
}, createFeeActionIdempotencyKey('calculate'))
|
||||||
}
|
}
|
||||||
if (snapshot.customerConfirmationStatus === 'CONFIRMED' || snapshot.customerConfirmedAt) {
|
|
||||||
ElMessage.warning('客户已确认该费用,费用已锁定,不允许再修改或重新发送')
|
|
||||||
feeConfirmOpen.value = false
|
|
||||||
return
|
|
||||||
}
|
|
||||||
feeConfirmReference.value = snapshot
|
feeConfirmReference.value = snapshot
|
||||||
feeConfirmUnitPrice.value = Number(snapshot.unitPrice)
|
feeConfirmUnitPrice.value = Number(snapshot.unitPrice)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -1728,20 +1705,6 @@ async function executeConfirmAndSendFee() {
|
|||||||
try {
|
try {
|
||||||
const tenantId = String(summary.tenantId)
|
const tenantId = String(summary.tenantId)
|
||||||
const inboundId = String(summary.inboundId)
|
const inboundId = String(summary.inboundId)
|
||||||
try {
|
|
||||||
const snapshot = await getLatestInboundFee(tenantId, inboundId)
|
|
||||||
if (snapshot.customerConfirmationStatus === 'CONFIRMED' || snapshot.customerConfirmedAt) {
|
|
||||||
ElMessage.warning('客户已确认该费用,费用已锁定,不允许再修改或重新发送')
|
|
||||||
feeConfirmOpen.value = false
|
|
||||||
return
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
const details = getApiErrorDetails(error, '费用状态查询失败,请刷新后重试')
|
|
||||||
if (details.code !== 'A0404') {
|
|
||||||
ElMessage.error(details.message)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let snapshot = await calculateInboundFee(tenantId, inboundId, {
|
let snapshot = await calculateInboundFee(tenantId, inboundId, {
|
||||||
selectedWeightKg,
|
selectedWeightKg,
|
||||||
selectedVolumeCbm,
|
selectedVolumeCbm,
|
||||||
@@ -2453,7 +2416,7 @@ onBeforeUnmount(() => {
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<section class="measurement-section">
|
<section class="measurement-section">
|
||||||
<div class="measurement-section-title"><button type="button" class="measurement-section-toggle" :aria-expanded="!measurementCollapsed.outer" @click="toggleMeasurementSection('outer')"><h3>二、外箱规数据</h3><span class="measurement-section-summary"><span>总件数:{{ measurementTotals(outerMeasurementRows).count ?? '—' }}</span><span>总毛重:{{ formatNumber(measurementTotals(outerMeasurementRows).gross) }}</span><span>总体积:{{ formatNumber(measurementTotals(outerMeasurementRows).volume, 6) }}</span><span>总材积重:{{ formatNumber(measurementTotals(outerMeasurementRows).volumetric) }}</span><span>重量体积:{{ formatNumber(measurementTotals(outerMeasurementRows).weightVolume, 6) }}</span></span><ChevronUp v-if="!measurementCollapsed.outer" :size="18" /><ChevronDown v-else :size="18" /></button><el-tooltip :content="measurementDraftLockedHint" :disabled="measurementDraftEditable"><span><el-select class="measurement-section-mode" :model-value="measurementSectionModes.outer" aria-label="外箱规数据计算方式" :loading="Boolean(measurementStrategySavingId)" :disabled="Boolean(measurementStrategySavingId) || !measurementDraftEditable" @change="selectAndSaveOuterMeasurementMode($event)"><el-option label="实测原尺寸(不调整)" value="ACTUAL" /><el-option label="箱规尺寸" value="ADD" /><el-option label="箱规系数" value="MULTIPLY" :disabled="multiplyModeDisabled" /></el-select></span></el-tooltip></div>
|
<div class="measurement-section-title"><button type="button" class="measurement-section-toggle" :aria-expanded="!measurementCollapsed.outer" @click="toggleMeasurementSection('outer')"><h3>二、外箱规数据</h3><span class="measurement-section-summary"><span>总件数:{{ measurementTotals(outerMeasurementRows).count ?? '—' }}</span><span>总毛重:{{ formatNumber(measurementTotals(outerMeasurementRows).gross) }}</span><span>总体积:{{ formatNumber(measurementTotals(outerMeasurementRows).volume, 6) }}</span><span>总材积重:{{ formatNumber(measurementTotals(outerMeasurementRows).volumetric) }}</span><span>重量体积:{{ formatNumber(measurementTotals(outerMeasurementRows).weightVolume, 6) }}</span></span><ChevronUp v-if="!measurementCollapsed.outer" :size="18" /><ChevronDown v-else :size="18" /></button><el-tooltip :content="measurementStrategyLockedHint" :disabled="measurementStrategyEditable"><span><el-select class="measurement-section-mode" :model-value="measurementSectionModes.outer" aria-label="外箱规数据计算方式" :loading="Boolean(measurementStrategySavingId)" :disabled="Boolean(measurementStrategySavingId) || !measurementStrategyEditable" @change="selectAndSaveOuterMeasurementMode($event)"><el-option label="实测原尺寸(不调整)" value="ACTUAL" /><el-option label="箱规尺寸" value="ADD" /><el-option label="箱规系数" value="MULTIPLY" :disabled="multiplyModeDisabled" /></el-select></span></el-tooltip></div>
|
||||||
<div v-if="!measurementCollapsed.outer">
|
<div v-if="!measurementCollapsed.outer">
|
||||||
<div v-loading="measurementLoading" class="detail-table-wrap"><el-table :data="outerMeasurementRows" border stripe empty-text="暂无外箱规数据"><el-table-column label="#" prop="itemNo" min-width="70" /><el-table-column v-for="column in outerColumns.filter((item) => item.key !== 'itemNo')" :key="column.key" :label="column.label" :prop="column.key" min-width="150"><template #default="{ row }"><LineClampText v-if="column.key === 'remark'" :content="measurementValue(row as Record<string, unknown>, column.key)" /><template v-else>{{ measurementValue(row as Record<string, unknown>, column.key) }}</template></template></el-table-column></el-table></div>
|
<div v-loading="measurementLoading" class="detail-table-wrap"><el-table :data="outerMeasurementRows" border stripe empty-text="暂无外箱规数据"><el-table-column label="#" prop="itemNo" min-width="70" /><el-table-column v-for="column in outerColumns.filter((item) => item.key !== 'itemNo')" :key="column.key" :label="column.label" :prop="column.key" min-width="150"><template #default="{ row }"><LineClampText v-if="column.key === 'remark'" :content="measurementValue(row as Record<string, unknown>, column.key)" /><template v-else>{{ measurementValue(row as Record<string, unknown>, column.key) }}</template></template></el-table-column></el-table></div>
|
||||||
<div class="measurement-total"><strong>合计</strong><span>箱数:{{ measurementTotals(outerMeasurementRows).count ?? '—' }}</span><span>总毛重:{{ formatNumber(measurementTotals(outerMeasurementRows).gross) }}</span><span>总体积:{{ formatNumber(measurementTotals(outerMeasurementRows).volume, 6) }}</span><span>总材积:{{ formatNumber(measurementTotals(outerMeasurementRows).volumetric) }}</span></div>
|
<div class="measurement-total"><strong>合计</strong><span>箱数:{{ measurementTotals(outerMeasurementRows).count ?? '—' }}</span><span>总毛重:{{ formatNumber(measurementTotals(outerMeasurementRows).gross) }}</span><span>总体积:{{ formatNumber(measurementTotals(outerMeasurementRows).volume, 6) }}</span><span>总材积:{{ formatNumber(measurementTotals(outerMeasurementRows).volumetric) }}</span></div>
|
||||||
@@ -2461,12 +2424,12 @@ onBeforeUnmount(() => {
|
|||||||
</section>
|
</section>
|
||||||
<div class="measurement-rule-selector">
|
<div class="measurement-rule-selector">
|
||||||
<span class="measurement-rule-selector__label">箱规计算规则选择</span>
|
<span class="measurement-rule-selector__label">箱规计算规则选择</span>
|
||||||
<el-tooltip :content="measurementDraftLockedHint" :disabled="measurementDraftEditable"><span><el-select class="measurement-rule-selector__control" :model-value="measurementSectionModes.outer" aria-label="外箱规数据计算方式" :loading="Boolean(measurementStrategySavingId)" :disabled="Boolean(measurementStrategySavingId) || !measurementDraftEditable" @change="selectAndSaveOuterMeasurementMode($event)">
|
<el-tooltip :content="measurementStrategyLockedHint" :disabled="measurementStrategyEditable"><span><el-select class="measurement-rule-selector__control" :model-value="measurementSectionModes.outer" aria-label="外箱规数据计算方式" :loading="Boolean(measurementStrategySavingId)" :disabled="Boolean(measurementStrategySavingId) || !measurementStrategyEditable" @change="selectAndSaveOuterMeasurementMode($event)">
|
||||||
<el-option label="实测原尺寸(不调整)" value="ACTUAL" />
|
<el-option label="实测原尺寸(不调整)" value="ACTUAL" />
|
||||||
<el-option label="箱规尺寸" value="ADD" />
|
<el-option label="箱规尺寸" value="ADD" />
|
||||||
<el-option label="箱规系数" value="MULTIPLY" :disabled="multiplyModeDisabled" />
|
<el-option label="箱规系数" value="MULTIPLY" :disabled="multiplyModeDisabled" />
|
||||||
</el-select></span></el-tooltip>
|
</el-select></span></el-tooltip>
|
||||||
<span class="measurement-rule-selector__hint">{{ measurementDraftEditable ? '规则参数来源:客户管理' : measurementDraftLockedHint }}</span>
|
<span class="measurement-rule-selector__hint">{{ measurementStrategyEditable ? '规则参数来源:客户管理' : measurementStrategyLockedHint }}</span>
|
||||||
</div>
|
</div>
|
||||||
<section class="measurement-section">
|
<section class="measurement-section">
|
||||||
<div class="measurement-section-title"><button type="button" class="measurement-section-toggle" :aria-expanded="!measurementCollapsed.actual" @click="toggleMeasurementSection('actual')"><h3>三、实际测量数据</h3><span class="measurement-section-summary"><span>总件数:{{ measurementTotals(actualMeasurementRows).count ?? '—' }}</span><span>总毛重:{{ formatNumber(measurementTotals(actualMeasurementRows).gross) }}</span><span>总体积:{{ formatNumber(measurementTotals(actualMeasurementRows).volume, 6) }}</span><span>总材积重:{{ formatNumber(measurementTotals(actualMeasurementRows).volumetric) }}</span><span>重量体积:{{ formatNumber(measurementTotals(actualMeasurementRows).weightVolume, 6) }}</span></span><ChevronUp v-if="!measurementCollapsed.actual" :size="18" /><ChevronDown v-else :size="18" /></button><el-select class="measurement-section-mode" :model-value="measurementSectionModes.actual" aria-label="实际测量数据计算方式" @change="updateMeasurementSectionMode('actual', $event)"><el-option label="实测原尺寸(不调整)" value="ACTUAL" /><el-option label="箱规尺寸" value="ADD" /><el-option label="箱规系数" value="MULTIPLY" :disabled="multiplyModeDisabled" /></el-select></div>
|
<div class="measurement-section-title"><button type="button" class="measurement-section-toggle" :aria-expanded="!measurementCollapsed.actual" @click="toggleMeasurementSection('actual')"><h3>三、实际测量数据</h3><span class="measurement-section-summary"><span>总件数:{{ measurementTotals(actualMeasurementRows).count ?? '—' }}</span><span>总毛重:{{ formatNumber(measurementTotals(actualMeasurementRows).gross) }}</span><span>总体积:{{ formatNumber(measurementTotals(actualMeasurementRows).volume, 6) }}</span><span>总材积重:{{ formatNumber(measurementTotals(actualMeasurementRows).volumetric) }}</span><span>重量体积:{{ formatNumber(measurementTotals(actualMeasurementRows).weightVolume, 6) }}</span></span><ChevronUp v-if="!measurementCollapsed.actual" :size="18" /><ChevronDown v-else :size="18" /></button><el-select class="measurement-section-mode" :model-value="measurementSectionModes.actual" aria-label="实际测量数据计算方式" @change="updateMeasurementSectionMode('actual', $event)"><el-option label="实测原尺寸(不调整)" value="ACTUAL" /><el-option label="箱规尺寸" value="ADD" /><el-option label="箱规系数" value="MULTIPLY" :disabled="multiplyModeDisabled" /></el-select></div>
|
||||||
|
|||||||
Reference in New Issue
Block a user