增加卡券销毁冻结状态校验
This commit is contained in:
@@ -12,7 +12,12 @@ import { getOrderById } from '../../repositories/order-repo.js'
|
|||||||
import { getTaskById } from '../../repositories/task-repo.js'
|
import { getTaskById } from '../../repositories/task-repo.js'
|
||||||
import { createHttpError } from '../../utils/http.js'
|
import { createHttpError } from '../../utils/http.js'
|
||||||
import { maskSecret } from '../../utils/masking.js'
|
import { maskSecret } from '../../utils/masking.js'
|
||||||
import { checkKuaishouIndustryEticketAvailable } from '../platforms/kuaishou-industry/check-available-service.js'
|
import {
|
||||||
|
checkKuaishouIndustryEticketAvailable,
|
||||||
|
isKuaishouIndustryEticketDestroyableStatus,
|
||||||
|
KUAISHOU_INDUSTRY_DESTROYABLE_DISPLAY_STATUS,
|
||||||
|
resolveKuaishouIndustryEticketDisplayStatus,
|
||||||
|
} from '../platforms/kuaishou-industry/check-available-service.js'
|
||||||
import {
|
import {
|
||||||
getKuaishouIndustrySourceConfig,
|
getKuaishouIndustrySourceConfig,
|
||||||
listKuaishouIndustryShopConfigs,
|
listKuaishouIndustryShopConfigs,
|
||||||
@@ -108,7 +113,10 @@ export async function checkAdminKuaishouIndustryVoucherAvailable(input: JsonObje
|
|||||||
const payload = buildVoucherOpenApiPayload(input, voucher)
|
const payload = buildVoucherOpenApiPayload(input, voucher)
|
||||||
|
|
||||||
assertRequired(payload.sellerId, '卖家编号未填写')
|
assertRequired(payload.sellerId, '卖家编号未填写')
|
||||||
assertRequired(payload.eticketType || payload.bizTypeCode, '电子凭证类型未填写(请选择券码或手动填写类型)')
|
assertRequired(
|
||||||
|
payload.eticketType || payload.bizTypeCode,
|
||||||
|
'电子凭证类型未填写(请选择券码或手动填写类型)',
|
||||||
|
)
|
||||||
assertEtickets(payload.etickets, '电子凭证列表未填写')
|
assertEtickets(payload.etickets, '电子凭证列表未填写')
|
||||||
|
|
||||||
const result = mapOpenApiResult(await checkKuaishouIndustryEticketAvailable(payload))
|
const result = mapOpenApiResult(await checkKuaishouIndustryEticketAvailable(payload))
|
||||||
@@ -200,6 +208,45 @@ export async function resendAdminKuaishouIndustryVoucherCode(input: JsonObject =
|
|||||||
|
|
||||||
export async function destroyAdminKuaishouIndustryVoucherByCode(input: JsonObject = {}) {
|
export async function destroyAdminKuaishouIndustryVoucherByCode(input: JsonObject = {}) {
|
||||||
const voucher = await resolveRequiredVoucher(input)
|
const voucher = await resolveRequiredVoucher(input)
|
||||||
|
const checkPayload = buildVoucherOpenApiPayload(input, voucher)
|
||||||
|
|
||||||
|
assertRequired(checkPayload.sellerId, '卖家编号未填写,无法在销毁前查询卡券状态')
|
||||||
|
assertRequired(
|
||||||
|
checkPayload.eticketType || checkPayload.bizTypeCode,
|
||||||
|
'电子凭证类型未填写,无法在销毁前查询卡券状态',
|
||||||
|
)
|
||||||
|
assertEtickets(checkPayload.etickets, '电子凭证列表未填写,无法在销毁前查询卡券状态')
|
||||||
|
|
||||||
|
const checkResult = await checkKuaishouIndustryEticketAvailable(checkPayload)
|
||||||
|
if (!checkResult.success || checkResult.skippedReason) {
|
||||||
|
const platformMessage = String(
|
||||||
|
checkResult.error || checkResult.response?.error_msg || checkResult.response?.msg || '',
|
||||||
|
).trim()
|
||||||
|
throw createHttpError(
|
||||||
|
`销毁前查询卡券状态失败,已取消销毁${platformMessage ? `:${platformMessage}` : ''}`,
|
||||||
|
{
|
||||||
|
statusCode: 409,
|
||||||
|
errorCode: 'admin_kuaishou_industry_destroy_status_check_failed',
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const displayStatus = resolveKuaishouIndustryEticketDisplayStatus(
|
||||||
|
checkResult.response,
|
||||||
|
voucher.voucher_code,
|
||||||
|
)
|
||||||
|
if (!isKuaishouIndustryEticketDestroyableStatus(displayStatus)) {
|
||||||
|
throw createHttpError(
|
||||||
|
displayStatus
|
||||||
|
? `卡券当前展示状态为 ${displayStatus},仅 ${KUAISHOU_INDUSTRY_DESTROYABLE_DISPLAY_STATUS}(冻结)状态可以销毁`
|
||||||
|
: `未获取到卡券展示状态,仅 ${KUAISHOU_INDUSTRY_DESTROYABLE_DISPLAY_STATUS}(冻结)状态可以销毁`,
|
||||||
|
{
|
||||||
|
statusCode: 409,
|
||||||
|
errorCode: 'admin_kuaishou_industry_voucher_not_frozen',
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const task = voucher.task_id ? await getTaskById(voucher.task_id) : null
|
const task = voucher.task_id ? await getTaskById(voucher.task_id) : null
|
||||||
const destroyInput: Parameters<typeof destroyKuaishouIndustryVoucher>[1] = {
|
const destroyInput: Parameters<typeof destroyKuaishouIndustryVoucher>[1] = {
|
||||||
source: 'admin_tool_manual_destroy',
|
source: 'admin_tool_manual_destroy',
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
import test from 'node:test'
|
||||||
|
import assert from 'node:assert/strict'
|
||||||
|
|
||||||
|
import {
|
||||||
|
isKuaishouIndustryEticketDestroyableStatus,
|
||||||
|
resolveKuaishouIndustryEticketDisplayStatus,
|
||||||
|
} from './check-available-service.js'
|
||||||
|
|
||||||
|
test('resolveKuaishouIndustryEticketDisplayStatus 按券码读取展示状态', () => {
|
||||||
|
const response = {
|
||||||
|
etickets: [
|
||||||
|
{ code: 'OTHER-CODE', displayStatus: 'available' },
|
||||||
|
{ code: 'TARGET-CODE', displayStatus: ' Frozen ' },
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.equal(resolveKuaishouIndustryEticketDisplayStatus(response, 'target-code'), 'frozen')
|
||||||
|
assert.equal(isKuaishouIndustryEticketDestroyableStatus('frozen'), true)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('resolveKuaishouIndustryEticketDisplayStatus 兼容 data 内的 status 字段', () => {
|
||||||
|
const response = {
|
||||||
|
data: {
|
||||||
|
eTicketList: [{ id: 'TARGET-CODE', status: 'FROZEN' }],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.equal(resolveKuaishouIndustryEticketDisplayStatus(response, 'TARGET-CODE'), 'frozen')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('isKuaishouIndustryEticketDestroyableStatus 拒绝非冻结和缺失状态', () => {
|
||||||
|
assert.equal(isKuaishouIndustryEticketDestroyableStatus('available'), false)
|
||||||
|
assert.equal(isKuaishouIndustryEticketDestroyableStatus(''), false)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('resolveKuaishouIndustryEticketDisplayStatus 拒绝其他券码的冻结状态', () => {
|
||||||
|
assert.equal(
|
||||||
|
resolveKuaishouIndustryEticketDisplayStatus(
|
||||||
|
{ etickets: [{ code: 'OTHER-CODE', displayStatus: 'frozen' }] },
|
||||||
|
'TARGET-CODE',
|
||||||
|
),
|
||||||
|
'',
|
||||||
|
)
|
||||||
|
})
|
||||||
@@ -6,6 +6,8 @@ import {
|
|||||||
type JsonObject,
|
type JsonObject,
|
||||||
} from './openapi-values.js'
|
} from './openapi-values.js'
|
||||||
|
|
||||||
|
export const KUAISHOU_INDUSTRY_DESTROYABLE_DISPLAY_STATUS = 'frozen'
|
||||||
|
|
||||||
export type KuaishouIndustryAvailableEticketInput = {
|
export type KuaishouIndustryAvailableEticketInput = {
|
||||||
id?: unknown
|
id?: unknown
|
||||||
code?: unknown
|
code?: unknown
|
||||||
@@ -44,6 +46,41 @@ export function checkKuaishouIndustryEticketAvailable(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function resolveKuaishouIndustryEticketDisplayStatus(
|
||||||
|
response: unknown,
|
||||||
|
voucherCode: unknown,
|
||||||
|
): string {
|
||||||
|
const etickets = extractAvailableEtickets(response)
|
||||||
|
const normalizedVoucherCode = String(voucherCode || '')
|
||||||
|
.trim()
|
||||||
|
.toLowerCase()
|
||||||
|
const matched = normalizedVoucherCode
|
||||||
|
? etickets.find((eticket) =>
|
||||||
|
[eticket.code, eticket.id].some(
|
||||||
|
(value) =>
|
||||||
|
String(value || '')
|
||||||
|
.trim()
|
||||||
|
.toLowerCase() === normalizedVoucherCode,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: undefined
|
||||||
|
const soleEticket = etickets.length === 1 ? etickets[0] : undefined
|
||||||
|
const soleEticketHasIdentity = Boolean(String(soleEticket?.code || soleEticket?.id || '').trim())
|
||||||
|
const target = matched || (!soleEticketHasIdentity ? soleEticket : undefined)
|
||||||
|
|
||||||
|
return String(target?.displayStatus || target?.status || '')
|
||||||
|
.trim()
|
||||||
|
.toLowerCase()
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isKuaishouIndustryEticketDestroyableStatus(displayStatus: unknown): boolean {
|
||||||
|
return (
|
||||||
|
String(displayStatus || '')
|
||||||
|
.trim()
|
||||||
|
.toLowerCase() === KUAISHOU_INDUSTRY_DESTROYABLE_DISPLAY_STATUS
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
function normalizeAvailableEtickets(value: unknown): JsonObject[] {
|
function normalizeAvailableEtickets(value: unknown): JsonObject[] {
|
||||||
if (!Array.isArray(value)) {
|
if (!Array.isArray(value)) {
|
||||||
return []
|
return []
|
||||||
@@ -70,3 +107,35 @@ function normalizeAvailableEticket(value: unknown): JsonObject | null {
|
|||||||
|
|
||||||
return pickDefinedBizParams({ id, code, num })
|
return pickDefinedBizParams({ id, code, num })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function extractAvailableEtickets(value: unknown): JsonObject[] {
|
||||||
|
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
const current = value as JsonObject
|
||||||
|
const data =
|
||||||
|
current.data && typeof current.data === 'object' && !Array.isArray(current.data)
|
||||||
|
? (current.data as JsonObject)
|
||||||
|
: null
|
||||||
|
const candidates = [
|
||||||
|
current.etickets,
|
||||||
|
current.eTicketList,
|
||||||
|
current.eticketList,
|
||||||
|
current.list,
|
||||||
|
data?.etickets,
|
||||||
|
data?.eTicketList,
|
||||||
|
data?.eticketList,
|
||||||
|
data?.list,
|
||||||
|
]
|
||||||
|
|
||||||
|
for (const candidate of candidates) {
|
||||||
|
if (Array.isArray(candidate)) {
|
||||||
|
return candidate.filter((item): item is JsonObject =>
|
||||||
|
Boolean(item && typeof item === 'object' && !Array.isArray(item)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user