增加分组库存与客服

This commit is contained in:
yml2213
2026-04-23 13:38:21 +08:00
parent 2a05a68959
commit f0b31121d7
31 changed files with 675 additions and 80 deletions
@@ -19,7 +19,7 @@ import { createTaskEvent } from '../../repositories/task-event-repo.js'
import { nowIso } from '../../utils/time.js'
import { randomId } from '../../utils/random.js'
import { closeAdminTask, confirmAdminTaskAssistedRole, redeemAdminTaskAssisted } from './admin-write-service.js'
import { isAssistedClaimTask, parseTaskContext } from './admin-read-shared-helpers.js'
import { createAdminViewerContext, isAssistedClaimTask, parseTaskContext } from './admin-read-shared-helpers.js'
const MANUAL_PROVIDER = 'manual'
const MANUAL_PLATFORM = 'manual_redeem'
@@ -36,6 +36,7 @@ export async function createAdminManualRedeemTask(
payload = /** @type {AdminManualRedeemCreateInput} */ ({}),
session = null,
) {
const viewerContext = createAdminViewerContext(session)
const proofValue = normalizeManualProofValue(payload.proofValue)
const skuCode = String(payload.skuCode || '').trim()
const skuName = String(payload.skuName || '').trim() || skuCode
@@ -81,9 +82,20 @@ export async function createAdminManualRedeemTask(
const primaryRequirement = requirements.find((item) => item.is_required !== false) || requirements[0] || null
const credentialType = String(primaryRequirement?.credential_type || primaryRequirement?.credentialType || 'tencent_code').trim() || 'tencent_code'
const roleKey = String(primaryRequirement?.role_key || primaryRequirement?.roleKey || 'primary_code').trim() || 'primary_code'
const availableInventory = await findFirstAvailableInventoryItemBySkuCode(skuCode, credentialType)
const availableInventory = await findFirstAvailableInventoryItemBySkuCode(
skuCode,
credentialType,
viewerContext.allowedInventoryGroupCodes,
)
if (!availableInventory) {
if (viewerContext.restrictInventoryGroups && viewerContext.allowedInventoryGroupCodes?.length === 0) {
throw createHttpError('当前客服未绑定任何库存组,无法创建人工兑换任务', {
statusCode: 403,
errorCode: 'admin_manual_redeem_inventory_group_unbound',
})
}
throw createHttpError('当前 SKU 没有可用库存,无法创建人工兑换任务', {
statusCode: 409,
errorCode: 'admin_manual_redeem_inventory_unavailable',
@@ -208,6 +220,7 @@ export async function createAdminManualRedeemTask(
credentialType,
}
: null,
inventoryGroupCode: String(availableInventory.inventory_group_code || '').trim(),
manualRedeem: manualContext,
}),
createdAt: now,
@@ -226,6 +239,7 @@ export async function createAdminManualRedeemTask(
taskId: createdTask.id,
credentialType,
roleKey,
inventoryGroupCodes: viewerContext.allowedInventoryGroupCodes,
})
if (!reservedInventory) {
@@ -250,6 +264,10 @@ export async function createAdminManualRedeemTask(
claim_expires_at: claimToken.expired_at,
user_action_status: 'pending_claim',
last_error: '',
context_json: JSON.stringify({
...parseTaskContext(createdTask),
inventoryGroupCode: String(reservedInventory.inventory_group_code || '').trim(),
}),
updated_at: nowIso(),
})
@@ -259,73 +277,80 @@ export async function createAdminManualRedeemTask(
skuCode,
skuName,
inventoryItemId: Number(reservedInventory.id || 0) || null,
inventoryGroupCode: String(reservedInventory.inventory_group_code || '').trim(),
createdBy: manualContext.createdBy,
remark,
}, nowIso())
return getAdminManualRedeemDetail(createdTask.id)
return getAdminManualRedeemDetail(createdTask.id, session)
}
/** @param {AdminEntityIdInput} taskId */
export async function getAdminManualRedeemDetail(taskId) {
const task = await getRequiredManualRedeemTask(taskId)
/** @param {AdminViewerSessionInput | null} [session] */
export async function getAdminManualRedeemDetail(taskId, session = null) {
const task = await getRequiredManualRedeemTask(taskId, session)
const detail = await getClaimDetailForAdminTask(task.id)
return decorateManualRedeemDetail(task, detail)
}
/** @param {AdminEntityIdInput} taskId */
/** @param {{ loginType?: string, forceRecreate?: boolean }} [payload] */
export async function createAdminManualRedeemSession(taskId, payload = {}) {
const task = await getRequiredManualRedeemTask(taskId)
/** @param {AdminViewerSessionInput | null} [session] */
export async function createAdminManualRedeemSession(taskId, payload = {}, session = null) {
const task = await getRequiredManualRedeemTask(taskId, session)
const detail = await createClaimSessionForAdminTask(task.id, payload)
return decorateManualRedeemDetail(task, detail)
}
/** @param {AdminEntityIdInput} taskId */
export async function getAdminManualRedeemSessionSummary(taskId) {
const task = await getRequiredManualRedeemTask(taskId)
/** @param {AdminViewerSessionInput | null} [session] */
export async function getAdminManualRedeemSessionSummary(taskId, session = null) {
const task = await getRequiredManualRedeemTask(taskId, session)
const detail = await getClaimSessionSummaryForAdminTask(task.id)
return decorateManualRedeemDetail(task, detail)
}
/** @param {AdminEntityIdInput} taskId */
export async function reloadAdminManualRedeemSession(taskId) {
const task = await getRequiredManualRedeemTask(taskId)
/** @param {AdminViewerSessionInput | null} [session] */
export async function reloadAdminManualRedeemSession(taskId, session = null) {
const task = await getRequiredManualRedeemTask(taskId, session)
const detail = await reloadClaimSessionForAdminTask(task.id)
return decorateManualRedeemDetail(task, detail)
}
/** @param {AdminEntityIdInput} taskId */
export async function closeAdminManualRedeemSession(taskId) {
const task = await getRequiredManualRedeemTask(taskId)
/** @param {AdminViewerSessionInput | null} [session] */
export async function closeAdminManualRedeemSession(taskId, session = null) {
const task = await getRequiredManualRedeemTask(taskId, session)
const detail = await closeClaimSessionForAdminTask(task.id)
return decorateManualRedeemDetail(task, detail)
}
/** @param {AdminEntityIdInput} taskId */
export async function closeAdminManualRedeemTask(taskId) {
const task = await getRequiredManualRedeemTask(taskId)
/** @param {AdminViewerSessionInput | null} [session] */
export async function closeAdminManualRedeemTask(taskId, session = null) {
const task = await getRequiredManualRedeemTask(taskId, session)
await closeAdminTask(task.id)
return getAdminManualRedeemDetail(task.id)
return getAdminManualRedeemDetail(task.id, session)
}
/** @param {AdminEntityIdInput} taskId */
/** @param {AdminViewerSessionInput | null} [session] */
export async function confirmAdminManualRedeemRole(taskId, session = null) {
const task = await getRequiredManualRedeemTask(taskId)
const task = await getRequiredManualRedeemTask(taskId, session)
await confirmAdminTaskAssistedRole(task.id, session)
return getAdminManualRedeemDetail(task.id)
return getAdminManualRedeemDetail(task.id, session)
}
/** @param {AdminEntityIdInput} taskId */
/** @param {AdminViewerSessionInput | null} [session] */
export async function redeemAdminManualRedeemTask(taskId, session = null) {
const task = await getRequiredManualRedeemTask(taskId)
const task = await getRequiredManualRedeemTask(taskId, session)
await redeemAdminTaskAssisted(task.id, session)
return getAdminManualRedeemDetail(task.id)
return getAdminManualRedeemDetail(task.id, session)
}
async function getRequiredManualRedeemTask(taskId) {
async function getRequiredManualRedeemTask(taskId, session = null) {
const task = await getTaskById(Number(taskId))
if (!task) {
@@ -345,6 +370,25 @@ async function getRequiredManualRedeemTask(taskId) {
})
}
const viewerContext = createAdminViewerContext(session)
if (viewerContext.restrictInventoryGroups) {
if (!Array.isArray(viewerContext.allowedInventoryGroupCodes) || viewerContext.allowedInventoryGroupCodes.length === 0) {
throw createHttpError('当前客服未绑定任何库存组,无法操作人工兑换任务', {
statusCode: 403,
errorCode: 'admin_manual_redeem_inventory_group_unbound',
})
}
const inventoryGroupCode = String(taskContext?.inventoryGroupCode || '').trim()
if (inventoryGroupCode && !viewerContext.allowedInventoryGroupCodes.includes(inventoryGroupCode)) {
throw createHttpError('当前客服无权操作该库存组下的人工兑换任务', {
statusCode: 403,
errorCode: 'admin_manual_redeem_inventory_group_denied',
})
}
}
return task
}