优化券码操作类型, 增加手动销毁
This commit is contained in:
@@ -0,0 +1,17 @@
|
||||
-- 003_kuaishou_industry_voucher_eticket_type.sql
|
||||
-- 电子凭证类型落库,避免后台默认 DINING_OPEN_TICKET 覆盖真实类目类型。
|
||||
|
||||
ALTER TABLE kuaishou_industry_vouchers
|
||||
ADD COLUMN IF NOT EXISTS eticket_type TEXT NOT NULL DEFAULT '';
|
||||
|
||||
COMMENT ON COLUMN kuaishou_industry_vouchers.eticket_type IS
|
||||
'快手电子凭证类型,发码请求 eticketType(如 GAME_OPEN_TICKET_CONSUME)';
|
||||
|
||||
-- 从历史 raw_payload 回填
|
||||
UPDATE kuaishou_industry_vouchers
|
||||
SET eticket_type = COALESCE(
|
||||
NULLIF(BTRIM(raw_payload_json #>> '{body,eticketType}'), ''),
|
||||
NULLIF(BTRIM(raw_payload_json ->> 'eticketType'), ''),
|
||||
eticket_type
|
||||
)
|
||||
WHERE BTRIM(COALESCE(eticket_type, '')) = '';
|
||||
@@ -66,6 +66,7 @@ async function insertKuaishouIndustryVoucher(
|
||||
unit_index,
|
||||
seller_id,
|
||||
token,
|
||||
eticket_type,
|
||||
status,
|
||||
valid_start_time,
|
||||
valid_end_time,
|
||||
@@ -94,17 +95,18 @@ async function insertKuaishouIndustryVoucher(
|
||||
$9,
|
||||
$10,
|
||||
$11,
|
||||
$12::jsonb,
|
||||
$13,
|
||||
$12,
|
||||
$13::jsonb,
|
||||
$14,
|
||||
$15,
|
||||
$16,
|
||||
$17,
|
||||
$18::jsonb,
|
||||
$19,
|
||||
$20::jsonb,
|
||||
$21,
|
||||
$22
|
||||
$18,
|
||||
$19::jsonb,
|
||||
$20,
|
||||
$21::jsonb,
|
||||
$22,
|
||||
$23
|
||||
)
|
||||
ON CONFLICT (oid, unit_index) DO UPDATE
|
||||
SET
|
||||
@@ -116,6 +118,10 @@ async function insertKuaishouIndustryVoucher(
|
||||
WHEN EXCLUDED.token <> '' THEN EXCLUDED.token
|
||||
ELSE kuaishou_industry_vouchers.token
|
||||
END,
|
||||
eticket_type = CASE
|
||||
WHEN EXCLUDED.eticket_type <> '' THEN EXCLUDED.eticket_type
|
||||
ELSE kuaishou_industry_vouchers.eticket_type
|
||||
END,
|
||||
order_id = COALESCE(kuaishou_industry_vouchers.order_id, EXCLUDED.order_id),
|
||||
task_id = COALESCE(kuaishou_industry_vouchers.task_id, EXCLUDED.task_id),
|
||||
valid_start_time = EXCLUDED.valid_start_time,
|
||||
@@ -157,6 +163,7 @@ async function insertKuaishouIndustryVoucher(
|
||||
input.unitIndex,
|
||||
input.sellerId || '',
|
||||
input.token || '',
|
||||
input.eticketType || '',
|
||||
input.status || 'UNUSED',
|
||||
input.validStartTime || 0,
|
||||
input.validEndTime || 0,
|
||||
@@ -407,6 +414,10 @@ function normalizeVoucherPatchColumns(
|
||||
columns.push({ column: 'seller_id', value: patch.sellerId || '' })
|
||||
}
|
||||
|
||||
if (patch.eticketType !== undefined) {
|
||||
columns.push({ column: 'eticket_type', value: patch.eticketType || '' })
|
||||
}
|
||||
|
||||
if (patch.status !== undefined) {
|
||||
columns.push({ column: 'status', value: patch.status || 'UNUSED' })
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
approveAdminKuaishouIndustryRefund,
|
||||
checkAdminKuaishouIndustryVoucherAvailable,
|
||||
consumeAdminKuaishouIndustryVoucherByCode,
|
||||
destroyAdminKuaishouIndustryVoucherByCode,
|
||||
disagreeAdminKuaishouIndustryRefund,
|
||||
listAdminKuaishouIndustryRefunds,
|
||||
listAdminKuaishouIndustryShops,
|
||||
@@ -17,6 +18,7 @@ import type {
|
||||
AdminKuaishouIndustryRefundListRouteBody,
|
||||
AdminKuaishouIndustryVoucherCheckAvailableRouteBody,
|
||||
AdminKuaishouIndustryVoucherConsumeRouteBody,
|
||||
AdminKuaishouIndustryVoucherDestroyRouteBody,
|
||||
AdminKuaishouIndustryVoucherResendRouteBody,
|
||||
AdminKuaishouIndustryVoucherReverseRouteBody,
|
||||
} from '../../types/admin/route-inputs.js'
|
||||
@@ -156,6 +158,23 @@ router.post(
|
||||
),
|
||||
)
|
||||
|
||||
router.post(
|
||||
'/kuaishou-industry/vouchers/destroy',
|
||||
requireAdminRoles(['admin', 'operator']),
|
||||
createJsonHandler(
|
||||
(req) =>
|
||||
destroyAdminKuaishouIndustryVoucherByCode(
|
||||
req.body as AdminKuaishouIndustryVoucherDestroyRouteBody,
|
||||
),
|
||||
{
|
||||
successMessage: '电子凭证已手动销毁',
|
||||
errorMessage: '手动销毁电子凭证失败',
|
||||
scope: '[admin/kuaishou-industry/vouchers/destroy]',
|
||||
audit: (req, data) => buildKuaishouIndustryAudit('kuaishou_industry_voucher_destroy', req.body, data),
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
function buildKuaishouIndustryAudit(action: string, body: unknown, data: unknown) {
|
||||
const payload = body && typeof body === 'object' ? body as Record<string, unknown> : {}
|
||||
const result = data && typeof data === 'object' ? data as Record<string, unknown> : {}
|
||||
|
||||
@@ -19,13 +19,18 @@ import {
|
||||
type KuaishouIndustrySourceConfig,
|
||||
} from '../platforms/kuaishou-industry/source-config-service.js'
|
||||
import { resendKuaishouIndustryVoucherSendCallback } from '../platforms/kuaishou-industry/send-code-service.js'
|
||||
import { consumeKuaishouIndustryVoucher } from '../platforms/kuaishou-industry/voucher-service.js'
|
||||
import {
|
||||
consumeKuaishouIndustryVoucher,
|
||||
destroyKuaishouIndustryVoucher,
|
||||
resolveKuaishouIndustryEticketType,
|
||||
} from '../platforms/kuaishou-industry/voucher-service.js'
|
||||
import {
|
||||
approveKuaishouIndustryRefund,
|
||||
disagreeKuaishouIndustryRefund,
|
||||
listKuaishouIndustryRefunds,
|
||||
} from '../platforms/kuaishou-industry/refund-service.js'
|
||||
import { reverseKuaishouIndustryCallback } from '../platforms/kuaishou-industry/reverse-callback-service.js'
|
||||
import { attachKuaishouIndustryVoucherToTask } from '../platforms/kuaishou-industry/voucher-binding-service.js'
|
||||
import type { KuaishouIndustryOpenApiCallResult } from '../platforms/kuaishou-industry/openapi-client.js'
|
||||
import type { KuaishouIndustryVoucherRow, TaskRow } from '../../types/repository/rows.js'
|
||||
|
||||
@@ -103,10 +108,15 @@ export async function checkAdminKuaishouIndustryVoucherAvailable(input: JsonObje
|
||||
const payload = buildVoucherOpenApiPayload(input, voucher)
|
||||
|
||||
assertRequired(payload.sellerId, '卖家编号未填写')
|
||||
assertRequired(payload.eticketType || payload.bizTypeCode, '电子凭证类型未填写')
|
||||
assertRequired(payload.eticketType || payload.bizTypeCode, '电子凭证类型未填写(请选择券码或手动填写类型)')
|
||||
assertEtickets(payload.etickets, '电子凭证列表未填写')
|
||||
|
||||
return mapOpenApiResult(await checkKuaishouIndustryEticketAvailable(payload))
|
||||
const result = mapOpenApiResult(await checkKuaishouIndustryEticketAvailable(payload))
|
||||
return {
|
||||
...result,
|
||||
eticketType: String(payload.eticketType || payload.bizTypeCode || ''),
|
||||
voucher: voucher ? mapAdminKuaishouIndustryVoucher(voucher) : null,
|
||||
}
|
||||
}
|
||||
|
||||
export async function reverseAdminKuaishouIndustryVoucher(input: JsonObject = {}) {
|
||||
@@ -188,12 +198,55 @@ export async function resendAdminKuaishouIndustryVoucherCode(input: JsonObject =
|
||||
}
|
||||
}
|
||||
|
||||
export async function destroyAdminKuaishouIndustryVoucherByCode(input: JsonObject = {}) {
|
||||
const voucher = await resolveRequiredVoucher(input)
|
||||
const task = voucher.task_id ? await getTaskById(voucher.task_id) : null
|
||||
const destroyInput: Parameters<typeof destroyKuaishouIndustryVoucher>[1] = {
|
||||
source: 'admin_tool_manual_destroy',
|
||||
reason: String(input.reason || '').trim(),
|
||||
token: String(input.token || voucher.token || '').trim(),
|
||||
eticketType: resolveKuaishouIndustryEticketType(voucher, input.eticketType),
|
||||
}
|
||||
if (task) {
|
||||
destroyInput.task = task
|
||||
}
|
||||
|
||||
const result = await destroyKuaishouIndustryVoucher(voucher, destroyInput)
|
||||
|
||||
if (!result.ok || !result.voucher) {
|
||||
throw createHttpError(result.errorMessage || '电子凭证销毁失败', {
|
||||
statusCode: 409,
|
||||
errorCode: 'admin_kuaishou_industry_destroy_failed',
|
||||
})
|
||||
}
|
||||
|
||||
const nextTask = result.task || task
|
||||
if (nextTask && result.voucher && !result.alreadyDestroyed) {
|
||||
await attachKuaishouIndustryVoucherToTask(nextTask, result.voucher, {
|
||||
source: 'admin_tool_manual_destroy',
|
||||
now: new Date().toISOString(),
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
alreadyDestroyed: Boolean(result.alreadyDestroyed),
|
||||
reason: result.reason || '',
|
||||
voucher: mapAdminKuaishouIndustryVoucher(result.voucher),
|
||||
task: nextTask ? mapTaskReference(nextTask) : null,
|
||||
}
|
||||
}
|
||||
|
||||
function buildVoucherOpenApiPayload(
|
||||
input: JsonObject,
|
||||
voucher: KuaishouIndustryVoucherRow | null,
|
||||
): JsonObject {
|
||||
const etickets = normalizeAdminEtickets(input.etickets)
|
||||
const voucherCode = String(voucher?.voucher_code || input.voucherCode || '').trim()
|
||||
const eticketType = resolveKuaishouIndustryEticketType(
|
||||
voucher,
|
||||
input.eticketType || input.bizTypeCode,
|
||||
)
|
||||
|
||||
return {
|
||||
...input,
|
||||
@@ -202,6 +255,8 @@ function buildVoucherOpenApiPayload(
|
||||
orderId: String(input.orderId || input.oid || voucher?.oid || '').trim(),
|
||||
token: String(input.token || voucher?.token || '').trim(),
|
||||
serialNum: String(input.serialNum || voucher?.consume_serial_num || '').trim(),
|
||||
eticketType,
|
||||
...(eticketType ? { bizTypeCode: eticketType } : {}),
|
||||
etickets:
|
||||
etickets.length > 0
|
||||
? etickets
|
||||
@@ -414,6 +469,7 @@ function mapAdminKuaishouIndustryVoucher(
|
||||
skuCode: String(listRow.sku_code || '').trim(),
|
||||
skuName: String(listRow.sku_name || '').trim(),
|
||||
tokenMasked: maskSecret(voucher.token),
|
||||
eticketType: resolveKuaishouIndustryEticketType(voucher),
|
||||
status: voucher.status,
|
||||
validStartTime: Number(voucher.valid_start_time || 0) || 0,
|
||||
validEndTime: Number(voucher.valid_end_time || 0) || 0,
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
} from './response.js'
|
||||
import { destroyCallback } from './destroy-callback-service.js'
|
||||
import { attachKuaishouIndustryVoucherToTask } from './voucher-binding-service.js'
|
||||
import { resolveKuaishouIndustryEticketType } from './voucher-service.js'
|
||||
|
||||
export async function handleDestroyCode(rawBody: JsonObject = {}) {
|
||||
const config = getKuaishouIndustryConfig()
|
||||
@@ -39,6 +40,9 @@ export async function handleDestroyCode(rawBody: JsonObject = {}) {
|
||||
const callbackSellerId = params.sellerId
|
||||
|| String(targetVouchers.find((voucher) => String(voucher.seller_id || '').trim())?.seller_id || '').trim()
|
||||
const callbackToken = params.token || resolveDestroyCallbackToken(targetVouchers)
|
||||
const eticketType = resolveKuaishouIndustryEticketType(
|
||||
targetVouchers[0] || null,
|
||||
)
|
||||
|
||||
for (const voucher of targetVouchers) {
|
||||
const status = String(voucher.status || '').trim().toUpperCase()
|
||||
@@ -81,6 +85,7 @@ export async function handleDestroyCode(rawBody: JsonObject = {}) {
|
||||
})),
|
||||
reason: params.reason,
|
||||
token: callbackToken,
|
||||
...(eticketType ? { eticketType } : {}),
|
||||
})
|
||||
|
||||
return buildIndustrySuccessResponse({ oid: normalizedOid })
|
||||
|
||||
@@ -28,6 +28,7 @@ import {
|
||||
buildKuaishouIndustryEticketFromVoucher,
|
||||
isKuaishouIndustryVoucherSendCallbackSuccess,
|
||||
KUAISHOU_INDUSTRY_SEND_CALLBACK_STATUS,
|
||||
resolveKuaishouIndustryEticketType,
|
||||
resolveKuaishouIndustryVoucherValidity,
|
||||
} from './voucher-service.js'
|
||||
import { bindKuaishouIndustryVouchersToOrderTasks } from './voucher-binding-service.js'
|
||||
@@ -83,6 +84,7 @@ export async function handleSendCode(rawBody: JsonObject = {}) {
|
||||
unitIndex,
|
||||
sellerId: params.sellerId,
|
||||
token: params.token,
|
||||
eticketType: params.eticketType,
|
||||
orderId: order?.id || null,
|
||||
taskId: task?.id || null,
|
||||
status: 'UNUSED',
|
||||
@@ -558,7 +560,7 @@ function resolveSendCodeCallbackParams(
|
||||
sendType: String(body.sendType || 'VIRTUAL').trim() || 'VIRTUAL',
|
||||
sellerId: String(body.sellerId || voucher.seller_id || '').trim(),
|
||||
token: String(body.token || voucher.token || '').trim(),
|
||||
eticketType: String(body.eticketType || '').trim(),
|
||||
eticketType: resolveKuaishouIndustryEticketType(voucher, body.eticketType),
|
||||
ext: String(body.ext || '').trim(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@ import { buildKuaishouIndustryVoucherContext } from './voucher-binding-service.j
|
||||
import {
|
||||
buildKuaishouIndustryEticketFromVoucher,
|
||||
normalizeKuaishouIndustryConsumeType,
|
||||
normalizeKuaishouIndustryDestroyReason,
|
||||
resolveKuaishouIndustryEticketType,
|
||||
resolveKuaishouIndustryVoucherValidity,
|
||||
} from './voucher-service.js'
|
||||
|
||||
@@ -82,6 +84,41 @@ test('buildKuaishouIndustryEticketFromVoucher mirrors voucher code into id and c
|
||||
assert.equal(eticket.eticketType, 'GAME_OPEN_TICKET_CONSUME')
|
||||
})
|
||||
|
||||
test('resolveKuaishouIndustryEticketType prefers explicit then column then raw payload', () => {
|
||||
assert.equal(
|
||||
resolveKuaishouIndustryEticketType(
|
||||
{
|
||||
eticket_type: 'GAME_OPEN_TICKET_CONSUME',
|
||||
raw_payload_json: { body: { eticketType: 'DINING_OPEN_TICKET' } },
|
||||
} as any,
|
||||
'EXPLICIT_TYPE',
|
||||
),
|
||||
'EXPLICIT_TYPE',
|
||||
)
|
||||
|
||||
assert.equal(
|
||||
resolveKuaishouIndustryEticketType({
|
||||
eticket_type: 'GAME_OPEN_TICKET_CONSUME',
|
||||
raw_payload_json: { body: { eticketType: 'DINING_OPEN_TICKET' } },
|
||||
} as any),
|
||||
'GAME_OPEN_TICKET_CONSUME',
|
||||
)
|
||||
|
||||
assert.equal(
|
||||
resolveKuaishouIndustryEticketType({
|
||||
eticket_type: '',
|
||||
raw_payload_json: { body: { eticketType: 'GAME_OPEN_TICKET_CONSUME' } },
|
||||
} as any),
|
||||
'GAME_OPEN_TICKET_CONSUME',
|
||||
)
|
||||
})
|
||||
|
||||
test('normalizeKuaishouIndustryDestroyReason defaults to SYS_ADMIN_DESTROY', () => {
|
||||
assert.equal(normalizeKuaishouIndustryDestroyReason(''), 'SYS_ADMIN_DESTROY')
|
||||
assert.equal(normalizeKuaishouIndustryDestroyReason('supply_destroy'), 'SUPPLY_DESTROY')
|
||||
assert.equal(normalizeKuaishouIndustryDestroyReason('unknown'), 'SYS_ADMIN_DESTROY')
|
||||
})
|
||||
|
||||
test('buildKuaishouIndustryVoucherContext maps consumed voucher state', () => {
|
||||
const context = buildKuaishouIndustryVoucherContext(
|
||||
{
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
import { createTaskEvent } from '../../../repositories/task-event-repo.js'
|
||||
import { getTaskById, updateTask } from '../../../repositories/task-repo.js'
|
||||
import { asJsonObject, type JsonObject } from '../../../types/json.js'
|
||||
import {
|
||||
findKuaishouIndustryVoucherByCode,
|
||||
listKuaishouIndustryVouchersByTaskId,
|
||||
updateKuaishouIndustryVoucherByCode,
|
||||
} from '../../../repositories/kuaishou-industry-voucher-repo.js'
|
||||
import { TASK_STATUS } from '../../../domain/task-status.js'
|
||||
import { logWarn } from '../../../utils/logger.js'
|
||||
import { normalizeTimestampIso } from '../../../utils/time.js'
|
||||
import { buildIndustryEticketItem } from './response.js'
|
||||
import { consumeCallback } from './consume-callback-service.js'
|
||||
import { destroyCallback } from './destroy-callback-service.js'
|
||||
import type { KuaishouIndustryVoucherRow, TaskRow } from '../../../types/repository/rows.js'
|
||||
|
||||
export const KUAISHOU_INDUSTRY_DEFAULT_VALID_DAYS = 3
|
||||
@@ -17,6 +21,12 @@ export const KUAISHOU_INDUSTRY_SEND_CALLBACK_STATUS = {
|
||||
FAILED: 'failed',
|
||||
} as const
|
||||
export const KUAISHOU_INDUSTRY_DEFAULT_CONSUME_TYPE = 'consume'
|
||||
export const KUAISHOU_INDUSTRY_DESTROY_REASONS = {
|
||||
ETICKET_EXPIRED: 'ETICKET_EXPIRED',
|
||||
USER_APPLY_REFUND: 'USER_APPLY_REFUND',
|
||||
SUPPLY_DESTROY: 'SUPPLY_DESTROY',
|
||||
SYS_ADMIN_DESTROY: 'SYS_ADMIN_DESTROY',
|
||||
} as const
|
||||
|
||||
export function resolveKuaishouIndustryVoucherValidity(
|
||||
input: {
|
||||
@@ -50,6 +60,33 @@ export function resolveKuaishouIndustryVoucherValidity(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析电子凭证类型。优先级:
|
||||
* 显式入参 > voucher.eticket_type > raw_payload.body.eticketType > raw_payload.eticketType
|
||||
*/
|
||||
export function resolveKuaishouIndustryEticketType(
|
||||
voucher?: Pick<KuaishouIndustryVoucherRow, 'eticket_type' | 'raw_payload_json'> | null,
|
||||
explicit?: unknown,
|
||||
): string {
|
||||
const fromInput = String(explicit || '').trim()
|
||||
if (fromInput) {
|
||||
return fromInput
|
||||
}
|
||||
|
||||
if (!voucher) {
|
||||
return ''
|
||||
}
|
||||
|
||||
const fromColumn = String(voucher.eticket_type || '').trim()
|
||||
if (fromColumn) {
|
||||
return fromColumn
|
||||
}
|
||||
|
||||
const rawPayload = parseJsonObject(voucher.raw_payload_json)
|
||||
const body = parseJsonObject(rawPayload.body)
|
||||
return String(body.eticketType || rawPayload.eticketType || '').trim()
|
||||
}
|
||||
|
||||
export function buildKuaishouIndustryEticketFromVoucher(
|
||||
voucher: KuaishouIndustryVoucherRow,
|
||||
eticketType = '',
|
||||
@@ -61,7 +98,7 @@ export function buildKuaishouIndustryEticketFromVoucher(
|
||||
num: 1,
|
||||
validStartTime: Number(voucher.valid_start_time || 0) || 0,
|
||||
validEndTime: Number(voucher.valid_end_time || 0) || 0,
|
||||
eticketType,
|
||||
eticketType: resolveKuaishouIndustryEticketType(voucher, eticketType),
|
||||
consumeDetails: resolveVoucherConsumeDetails(voucher),
|
||||
})
|
||||
}
|
||||
@@ -274,6 +311,128 @@ export async function consumeKuaishouIndustryVoucher(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 主动销毁电子凭证(管理端/运维补救)。
|
||||
* 先调快手销毁回调,成功后再落本地 DESTROYED 并关闭关联任务。
|
||||
* 已 DESTROYED 时仅重试回调(幂等)。
|
||||
*/
|
||||
export async function destroyKuaishouIndustryVoucher(
|
||||
voucher: KuaishouIndustryVoucherRow,
|
||||
input: {
|
||||
source?: string
|
||||
reason?: string
|
||||
token?: string
|
||||
eticketType?: string
|
||||
task?: TaskRow | null
|
||||
skipCallback?: boolean
|
||||
goodsValue?: number
|
||||
} = {},
|
||||
) {
|
||||
const currentStatus = normalizeVoucherStatus(voucher.status)
|
||||
if (currentStatus === 'CONSUMED') {
|
||||
return {
|
||||
ok: false,
|
||||
voucher,
|
||||
callbackSuccess: false,
|
||||
errorMessage: '电子凭证已核销,不能销毁;如需作废请先冲正',
|
||||
}
|
||||
}
|
||||
|
||||
const reason = normalizeKuaishouIndustryDestroyReason(input.reason)
|
||||
const now = normalizeTimestampIso(new Date().toISOString())
|
||||
const token = String(input.token || voucher.token || '').trim()
|
||||
const eticketType = resolveKuaishouIndustryEticketType(voucher, input.eticketType)
|
||||
const alreadyDestroyed = currentStatus === 'DESTROYED'
|
||||
|
||||
const callbackResult = input.skipCallback
|
||||
? { success: true as const }
|
||||
: await destroyCallback({
|
||||
oid: voucher.oid,
|
||||
sellerId: String(voucher.seller_id || '').trim(),
|
||||
etickets: [{
|
||||
id: voucher.voucher_code,
|
||||
code: voucher.voucher_code,
|
||||
num: 1,
|
||||
...(input.goodsValue != null ? { goodsValue: input.goodsValue } : {}),
|
||||
}],
|
||||
reason,
|
||||
token,
|
||||
...(eticketType ? { eticketType } : {}),
|
||||
})
|
||||
|
||||
if (!callbackResult.success) {
|
||||
return {
|
||||
ok: false,
|
||||
voucher,
|
||||
callbackSuccess: false,
|
||||
errorMessage: callbackResult.error || '电子凭证销毁回调失败',
|
||||
}
|
||||
}
|
||||
|
||||
let updated = voucher
|
||||
if (!alreadyDestroyed) {
|
||||
const next = await updateKuaishouIndustryVoucherByCode(voucher.voucher_code, {
|
||||
status: 'DESTROYED',
|
||||
destroyedAt: now,
|
||||
updatedAt: now,
|
||||
...(eticketType && !String(voucher.eticket_type || '').trim()
|
||||
? { eticketType }
|
||||
: {}),
|
||||
})
|
||||
updated = next || voucher
|
||||
}
|
||||
|
||||
let task =
|
||||
input.task ||
|
||||
(voucher.task_id ? await getTaskById(voucher.task_id).catch(() => null) : null)
|
||||
|
||||
if (task && !alreadyDestroyed) {
|
||||
task = await updateTask(task.id, {
|
||||
task_status: TASK_STATUS.CLOSED,
|
||||
delivery_status: 'cancelled',
|
||||
result_code: reason,
|
||||
result_message: `电子凭证销毁: ${reason}`,
|
||||
updated_at: now,
|
||||
}) || task
|
||||
|
||||
await createTaskEvent(
|
||||
task.id,
|
||||
'kuaishou_industry_voucher_destroyed',
|
||||
{
|
||||
oid: voucher.oid,
|
||||
voucherCode: voucher.voucher_code,
|
||||
reason,
|
||||
source: input.source || 'admin_tool_manual_destroy',
|
||||
eticketType,
|
||||
},
|
||||
now,
|
||||
)
|
||||
}
|
||||
|
||||
return {
|
||||
ok: true,
|
||||
voucher: updated,
|
||||
callbackSuccess: true,
|
||||
alreadyDestroyed,
|
||||
reason,
|
||||
task: task || null,
|
||||
}
|
||||
}
|
||||
|
||||
export function normalizeKuaishouIndustryDestroyReason(value: unknown): string {
|
||||
const normalized = String(value || '').trim().toUpperCase()
|
||||
if (
|
||||
normalized === KUAISHOU_INDUSTRY_DESTROY_REASONS.ETICKET_EXPIRED ||
|
||||
normalized === KUAISHOU_INDUSTRY_DESTROY_REASONS.USER_APPLY_REFUND ||
|
||||
normalized === KUAISHOU_INDUSTRY_DESTROY_REASONS.SUPPLY_DESTROY ||
|
||||
normalized === KUAISHOU_INDUSTRY_DESTROY_REASONS.SYS_ADMIN_DESTROY
|
||||
) {
|
||||
return normalized
|
||||
}
|
||||
|
||||
return KUAISHOU_INDUSTRY_DESTROY_REASONS.SYS_ADMIN_DESTROY
|
||||
}
|
||||
|
||||
function resolveTaskVouchers(task: TaskRow): Promise<KuaishouIndustryVoucherRow[]> {
|
||||
const taskId = Number(task?.id || 0)
|
||||
if (taskId > 0) {
|
||||
|
||||
@@ -19,6 +19,7 @@ import type {
|
||||
AdminKuaishouIndustrySourceConfigInput,
|
||||
AdminKuaishouIndustryVoucherCheckAvailableInput,
|
||||
AdminKuaishouIndustryVoucherConsumeInput,
|
||||
AdminKuaishouIndustryVoucherDestroyInput,
|
||||
AdminKuaishouIndustryVoucherResendInput,
|
||||
AdminKuaishouIndustryVoucherReverseInput,
|
||||
AdminCloudtentaclesTestLoginInput,
|
||||
@@ -44,6 +45,7 @@ export type AdminKuaishouIndustryVoucherCheckAvailableRouteBody = AdminKuaishouI
|
||||
export type AdminKuaishouIndustryVoucherReverseRouteBody = AdminKuaishouIndustryVoucherReverseInput
|
||||
export type AdminKuaishouIndustryVoucherConsumeRouteBody = AdminKuaishouIndustryVoucherConsumeInput
|
||||
export type AdminKuaishouIndustryVoucherResendRouteBody = AdminKuaishouIndustryVoucherResendInput
|
||||
export type AdminKuaishouIndustryVoucherDestroyRouteBody = AdminKuaishouIndustryVoucherDestroyInput
|
||||
export type AdminNotificationConfigRouteBody = AdminNotificationConfigInput
|
||||
export type AdminNotificationTestRouteBody = AdminNotificationTestInput
|
||||
export type AdminScheduledJobsConfigRouteBody = AdminScheduledJobsConfigInput
|
||||
|
||||
@@ -327,3 +327,13 @@ export type AdminKuaishouIndustryVoucherResendInput = {
|
||||
taskId?: number | string
|
||||
voucherCode?: string
|
||||
}
|
||||
|
||||
export type AdminKuaishouIndustryVoucherDestroyInput = {
|
||||
oid?: string
|
||||
orderId?: string
|
||||
taskId?: number | string
|
||||
voucherCode?: string
|
||||
token?: string
|
||||
eticketType?: string
|
||||
reason?: string
|
||||
}
|
||||
|
||||
@@ -133,6 +133,7 @@ export type KuaishouIndustryVoucherUpsertInput = {
|
||||
unitIndex: number
|
||||
sellerId?: string
|
||||
token?: string
|
||||
eticketType?: string
|
||||
orderId?: number | string | null
|
||||
taskId?: number | string | null
|
||||
status?: string
|
||||
|
||||
@@ -113,6 +113,7 @@ export type KuaishouIndustryVoucherRow = {
|
||||
unit_index: number
|
||||
seller_id: string
|
||||
token: string
|
||||
eticket_type: string
|
||||
status: string
|
||||
valid_start_time: number | string
|
||||
valid_end_time: number | string
|
||||
|
||||
Reference in New Issue
Block a user