修复旧码同名明细核销选择
This commit is contained in:
@@ -3,7 +3,9 @@ import { test } from 'node:test'
|
||||
import {
|
||||
findWriteOffItem,
|
||||
getWriteOffItemAvailability,
|
||||
getWriteOffItemQuantity
|
||||
getWriteOffItemQuantity,
|
||||
getWriteOffStatusText,
|
||||
resolveWriteOffItemSelection
|
||||
} from '../writeOffItem.ts'
|
||||
|
||||
const items = [
|
||||
@@ -51,3 +53,64 @@ test('marks legacy or unsupported or completed item unavailable', () => {
|
||||
assert.equal(getWriteOffItemAvailability({ ...items[0], snapshotOperationSupported: false }).available, false)
|
||||
assert.equal(getWriteOffItemAvailability({ ...items[0], writeOffCount: 2 }).available, false)
|
||||
})
|
||||
|
||||
test('locks scanned v2 code to the specified write off item id', () => {
|
||||
const selection = resolveWriteOffItemSelection(items, {
|
||||
source: 'scan',
|
||||
orderWriteOffItemId: 'item-2',
|
||||
packageName: '双人套餐'
|
||||
})
|
||||
|
||||
assert.equal(selection.locked, true)
|
||||
assert.deepEqual(selection.options.map((item) => item.orderWriteOffItemId), ['item-2'])
|
||||
assert.equal(selection.selectedItemId, 'item-2')
|
||||
})
|
||||
|
||||
test('does not auto select when legacy package name matches multiple available items', () => {
|
||||
const selection = resolveWriteOffItemSelection(items, {
|
||||
source: 'scan',
|
||||
packageName: '双人套餐'
|
||||
})
|
||||
|
||||
assert.equal(selection.mismatch, false)
|
||||
assert.equal(selection.requiresManualSelection, true)
|
||||
assert.equal(selection.selectedItemId, '')
|
||||
assert.deepEqual(selection.options.map((item) => item.orderWriteOffItemId), ['item-1', 'item-2'])
|
||||
})
|
||||
|
||||
test('auto selects the only available item matched by legacy package name', () => {
|
||||
const selection = resolveWriteOffItemSelection(
|
||||
[
|
||||
{ ...items[0], writeOffCount: 2 },
|
||||
items[1]
|
||||
],
|
||||
{
|
||||
source: 'scan',
|
||||
packageName: '双人套餐'
|
||||
}
|
||||
)
|
||||
|
||||
assert.equal(selection.selectedItemId, 'item-2')
|
||||
assert.equal(selection.requiresManualSelection, false)
|
||||
})
|
||||
|
||||
test('default selection skips unavailable items', () => {
|
||||
const selection = resolveWriteOffItemSelection([
|
||||
{ ...items[0], writeOffCount: 2 },
|
||||
items[1]
|
||||
])
|
||||
|
||||
assert.equal(selection.selectedItemId, 'item-2')
|
||||
})
|
||||
|
||||
test('infers partial write off status from quantity when status is 0', () => {
|
||||
assert.equal(getWriteOffStatusText({ ...items[0], writeOffStatus: 0, writeOffCount: 0 }), '待核销')
|
||||
assert.equal(getWriteOffStatusText({ ...items[0], writeOffStatus: 0, writeOffCount: 1 }), '部分核销')
|
||||
assert.equal(getWriteOffStatusText({ ...items[0], writeOffStatus: 0, writeOffCount: 2 }), '已核销')
|
||||
assert.equal(getWriteOffStatusText({ ...items[0], writeOffStatus: 1, writeOffCount: 0 }), '已核销')
|
||||
})
|
||||
|
||||
test('does not override explicit non-zero write off status with quantity inference', () => {
|
||||
assert.equal(getWriteOffStatusText({ ...items[0], writeOffStatus: 2, writeOffCount: 0 }), '部分核销')
|
||||
assert.equal(getWriteOffStatusText({ ...items[0], writeOffStatus: 'PART_WRITE_OFF', writeOffCount: 0 }), '部分核销')
|
||||
})
|
||||
|
||||
@@ -17,6 +17,21 @@ export interface WriteOffItemAvailability {
|
||||
reason: string
|
||||
}
|
||||
|
||||
export interface WriteOffItemSelectionInput {
|
||||
source?: string
|
||||
orderWriteOffItemId?: string
|
||||
packageName?: string
|
||||
fallbackName?: string
|
||||
}
|
||||
|
||||
export interface WriteOffItemSelection {
|
||||
options: CommodityPackageConfig[]
|
||||
selectedItemId: string
|
||||
locked: boolean
|
||||
mismatch: boolean
|
||||
requiresManualSelection: boolean
|
||||
}
|
||||
|
||||
const statusTextMap: Record<string, string> = {
|
||||
'0': '待核销',
|
||||
'1': '已核销',
|
||||
@@ -86,9 +101,79 @@ export const getFirstAvailableWriteOffItem = (items: CommodityPackageConfig[]) =
|
||||
return items.find((item) => getWriteOffItemAvailability(item).available)
|
||||
}
|
||||
|
||||
const matchesWriteOffItemName = (item: CommodityPackageConfig, packageName: string, fallbackName = '') => {
|
||||
return getWriteOffItemName(item, fallbackName) === packageName || item.packageName === packageName
|
||||
}
|
||||
|
||||
export const resolveWriteOffItemSelection = (
|
||||
items: CommodityPackageConfig[],
|
||||
input: WriteOffItemSelectionInput = {}
|
||||
): WriteOffItemSelection => {
|
||||
const source = String(input.source || '')
|
||||
const itemId = String(input.orderWriteOffItemId || '').trim()
|
||||
const packageName = String(input.packageName || '').trim()
|
||||
const isScan = source === 'scan'
|
||||
const locked = isScan && Boolean(itemId)
|
||||
|
||||
let options = items
|
||||
if (locked) {
|
||||
options = items.filter((item) => getWriteOffItemId(item) === itemId)
|
||||
} else if (isScan && packageName) {
|
||||
options = items.filter((item) => matchesWriteOffItemName(item, packageName, input.fallbackName))
|
||||
}
|
||||
|
||||
const mismatch = isScan && Boolean(itemId || packageName) && options.length === 0
|
||||
const availableOptions = options.filter((item) => getWriteOffItemAvailability(item).available)
|
||||
|
||||
if (locked) {
|
||||
return {
|
||||
options,
|
||||
selectedItemId: getWriteOffItemId(options[0]),
|
||||
locked,
|
||||
mismatch,
|
||||
requiresManualSelection: false
|
||||
}
|
||||
}
|
||||
|
||||
if (isScan && packageName && availableOptions.length > 1) {
|
||||
return {
|
||||
options,
|
||||
selectedItemId: '',
|
||||
locked,
|
||||
mismatch,
|
||||
requiresManualSelection: true
|
||||
}
|
||||
}
|
||||
|
||||
const matchedById = itemId ? options.find((item) => getWriteOffItemId(item) === itemId) : undefined
|
||||
const selectedItem = (
|
||||
matchedById && getWriteOffItemAvailability(matchedById).available
|
||||
? matchedById
|
||||
: availableOptions[0] || matchedById || options[0]
|
||||
)
|
||||
|
||||
return {
|
||||
options,
|
||||
selectedItemId: getWriteOffItemId(selectedItem),
|
||||
locked,
|
||||
mismatch,
|
||||
requiresManualSelection: false
|
||||
}
|
||||
}
|
||||
|
||||
export const getWriteOffStatusText = (item?: CommodityPackageConfig) => {
|
||||
const status = item?.writeOffStatus ?? item?.packageStatus
|
||||
if (status === undefined || status === null || status === '') return ''
|
||||
const key = String(status)
|
||||
const writtenOff = toNumber(item?.writeOffCount)
|
||||
const total = toNumber(item?.count)
|
||||
if (key === '1') return '已核销'
|
||||
if ((status === undefined || status === null || status === '' || key === '0') && (writtenOff !== undefined || total !== undefined)) {
|
||||
const writtenOffCount = Math.max(writtenOff ?? 0, 0)
|
||||
const totalCount = Math.max(total ?? 0, 0)
|
||||
if (writtenOffCount <= 0) return '待核销'
|
||||
if (totalCount > 0 && writtenOffCount >= totalCount) return '已核销'
|
||||
return '部分核销'
|
||||
}
|
||||
if (status === undefined || status === null || status === '') return ''
|
||||
return statusTextMap[key] || key
|
||||
}
|
||||
|
||||
@@ -9,14 +9,13 @@ import StatusTag from '@/components/StatusTag.vue'
|
||||
import type { CommodityPackageConfig, UserOrderDetail } from '@/types/order'
|
||||
import { canWriteOff } from '@/utils/constants'
|
||||
import {
|
||||
findWriteOffItem,
|
||||
getFirstAvailableWriteOffItem,
|
||||
getWriteOffItemAvailability,
|
||||
getWriteOffItemContent,
|
||||
getWriteOffItemId,
|
||||
getWriteOffItemName,
|
||||
getWriteOffItemQuantity,
|
||||
getWriteOffStatusText,
|
||||
resolveWriteOffItemSelection,
|
||||
toNumber
|
||||
} from '@/utils/writeOffItem'
|
||||
import {
|
||||
@@ -36,8 +35,6 @@ const orderId = computed(() => String(route.query.orderId || ''))
|
||||
const routePackageName = computed(() => String(route.query.packageName || '').trim())
|
||||
const routeWriteOffItemId = computed(() => String(route.query.orderWriteOffItemId || '').trim())
|
||||
const isScanSource = computed(() => String(route.query.source || '') === 'scan')
|
||||
const isScanItemLocked = computed(() => isScanSource.value && Boolean(routeWriteOffItemId.value))
|
||||
const hasScanMatchCriteria = computed(() => isScanSource.value && Boolean(routeWriteOffItemId.value || routePackageName.value))
|
||||
|
||||
const packageConfigs = computed<CommodityPackageConfig[]>(() => {
|
||||
if (!detail.value) return []
|
||||
@@ -51,24 +48,22 @@ const packageConfigs = computed<CommodityPackageConfig[]>(() => {
|
||||
]
|
||||
})
|
||||
|
||||
const packageOptions = computed(() => {
|
||||
if (isScanSource.value && routeWriteOffItemId.value) {
|
||||
return packageConfigs.value.filter((item) => getWriteOffItemId(item) === routeWriteOffItemId.value)
|
||||
}
|
||||
if (isScanSource.value && routePackageName.value) {
|
||||
return packageConfigs.value.filter(
|
||||
(item) =>
|
||||
getWriteOffItemName(item, detail.value?.commodityName) === routePackageName.value ||
|
||||
item.packageName === routePackageName.value
|
||||
)
|
||||
}
|
||||
return packageConfigs.value
|
||||
const itemSelection = computed(() => {
|
||||
return resolveWriteOffItemSelection(packageConfigs.value, {
|
||||
source: String(route.query.source || ''),
|
||||
orderWriteOffItemId: routeWriteOffItemId.value,
|
||||
packageName: routePackageName.value,
|
||||
fallbackName: detail.value?.commodityName
|
||||
})
|
||||
})
|
||||
|
||||
const packageMismatch = computed(() => hasScanMatchCriteria.value && packageOptions.value.length === 0)
|
||||
const packageOptions = computed(() => itemSelection.value.options)
|
||||
const isScanItemLocked = computed(() => itemSelection.value.locked)
|
||||
const packageMismatch = computed(() => itemSelection.value.mismatch)
|
||||
const requiresManualSelection = computed(() => itemSelection.value.requiresManualSelection)
|
||||
|
||||
const selectedPackage = computed(() => {
|
||||
return packageOptions.value.find((item) => getWriteOffItemId(item) === selectedItemId.value) || packageOptions.value[0]
|
||||
return packageOptions.value.find((item) => getWriteOffItemId(item) === selectedItemId.value)
|
||||
})
|
||||
|
||||
const quantityInfo = computed(() => getWriteOffItemQuantity(selectedPackage.value))
|
||||
@@ -119,13 +114,7 @@ const loadDetail = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
detail.value = await fetchOrderDetail(orderId.value)
|
||||
const matchedPackage = findWriteOffItem(packageConfigs.value, {
|
||||
orderWriteOffItemId: routeWriteOffItemId.value,
|
||||
packageName: routePackageName.value
|
||||
})
|
||||
const firstAvailablePackage = getFirstAvailableWriteOffItem(packageOptions.value)
|
||||
const fallbackPackage = packageOptions.value[0] || packageConfigs.value[0]
|
||||
selectedItemId.value = getWriteOffItemId(matchedPackage || firstAvailablePackage || fallbackPackage)
|
||||
selectedItemId.value = itemSelection.value.selectedItemId
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
@@ -282,6 +271,10 @@ onMounted(loadDetail)
|
||||
<BadgeCheck :size="20" />
|
||||
<span>该订单当前不可核销,请返回订单详情核对状态。</span>
|
||||
</section>
|
||||
<section v-else-if="requiresManualSelection" class="panel warning-panel">
|
||||
<BadgeCheck :size="20" />
|
||||
<span>扫码套餐匹配到多个可核销明细,请选择后再核销。</span>
|
||||
</section>
|
||||
<section v-else-if="selectedPackage && !selectedItemAvailability.available" class="panel warning-panel">
|
||||
<BadgeCheck :size="20" />
|
||||
<span>{{ selectedItemAvailability.reason }}</span>
|
||||
|
||||
Reference in New Issue
Block a user