fix(order-service): 修复 Mock 工单未绑定自动发布配置
This commit is contained in:
@@ -0,0 +1,48 @@
|
||||
-- 136_backfill_mock_order_service_snapshots.sql
|
||||
-- 修复历史 Mock 工单未绑定订单服务版本,导致资料提交后无法自动发布的问题。
|
||||
|
||||
UPDATE work_orders wo
|
||||
SET service_profile_id = p.id,
|
||||
service_profile_version_id = v.id,
|
||||
service_profile_snapshot_json = jsonb_build_object(
|
||||
'profileId', p.id,
|
||||
'categoryId', p.category_id,
|
||||
'versionId', v.id,
|
||||
'versionNo', v.version_no,
|
||||
'config', v.config_json
|
||||
),
|
||||
requirement_json = jsonb_build_object(
|
||||
'fields', COALESCE(v.config_json -> 'fields', '[]'::jsonb)
|
||||
),
|
||||
updated_at = NOW()
|
||||
FROM order_service_profiles p
|
||||
JOIN order_service_profile_versions v ON v.id = p.current_version_id
|
||||
WHERE wo.category_id = p.category_id
|
||||
AND wo.service_profile_snapshot_json IS NULL
|
||||
AND wo.status IN ('pending_material', 'unassigned')
|
||||
AND wo.assigned_worker_id IS NULL
|
||||
AND wo.material_json -> 'source' ->> 'mock' = 'true'
|
||||
AND NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM work_order_shares share
|
||||
WHERE share.work_order_id = wo.id
|
||||
AND share.status <> 'cancelled'
|
||||
);
|
||||
|
||||
INSERT INTO order_service_auto_publish_jobs (work_order_id, max_attempts)
|
||||
SELECT
|
||||
wo.id,
|
||||
GREATEST(
|
||||
1,
|
||||
COALESCE(
|
||||
NULLIF(wo.service_profile_snapshot_json -> 'config' -> 'autoPublish' ->> 'retryMax', '')::int,
|
||||
3
|
||||
) + 1
|
||||
)
|
||||
FROM work_orders wo
|
||||
WHERE wo.material_json -> 'source' ->> 'mock' = 'true'
|
||||
AND wo.status = 'unassigned'
|
||||
AND wo.assigned_worker_id IS NULL
|
||||
AND wo.material_json -> 'collect' ->> 'complete' = 'true'
|
||||
AND wo.service_profile_snapshot_json -> 'config' -> 'autoPublish' ->> 'enabled' = 'true'
|
||||
ON CONFLICT DO NOTHING;
|
||||
@@ -29,6 +29,14 @@ import {
|
||||
} from './mappers.js'
|
||||
import { rememberAdminMockCustomShop } from './admin-mock-custom-shop-service.js'
|
||||
import { resolveMockWorkOrderProductRule } from './mock-work-order-rule-match.js'
|
||||
import {
|
||||
getPublishedOrderServiceProfileSnapshotByCategory,
|
||||
isOrderServiceMaterialRequired,
|
||||
} from './order-service-profile-service.js'
|
||||
import {
|
||||
enqueueOrderServiceAutoPublish,
|
||||
processDueOrderServiceAutoPublishJobs,
|
||||
} from './order-service-auto-publish-service.js'
|
||||
import { ensureWorkerPlatformDefaults } from './worker-platform-defaults.js'
|
||||
import { normalizeWorkOrderTimeoutPolicy } from './work-order-timeout-policy.js'
|
||||
|
||||
@@ -63,8 +71,17 @@ export async function createAdminMockWorkOrder(payload: JsonObject = {}) {
|
||||
}
|
||||
categoryId = Number(category.id)
|
||||
}
|
||||
const materialComplete = payload.materialComplete === true || payload.materialComplete === 'true'
|
||||
const fields = normalizeRequirementFields(payload.fields)
|
||||
const serviceProfile = categoryId
|
||||
? await getPublishedOrderServiceProfileSnapshotByCategory(categoryId)
|
||||
: null
|
||||
const requestedMaterialComplete =
|
||||
payload.materialComplete === true || payload.materialComplete === 'true'
|
||||
const materialComplete =
|
||||
requestedMaterialComplete ||
|
||||
(serviceProfile ? !isOrderServiceMaterialRequired(serviceProfile.config) : false)
|
||||
const fields = serviceProfile
|
||||
? normalizeRequirementFields(serviceProfile.config.fields)
|
||||
: normalizeRequirementFields(payload.fields)
|
||||
const { platformOrderId, productName, shop } = input
|
||||
// Mock 工单同样要挂到接单模板上:后台「同步模板价格」按 product_rule_id 精确匹配,
|
||||
// 不绑定的话改模板价永远命中不到 Mock 单(真实单在 sync-worker-orders-service 已绑定)。
|
||||
@@ -93,7 +110,7 @@ export async function createAdminMockWorkOrder(payload: JsonObject = {}) {
|
||||
complete: true,
|
||||
submittedAt: now,
|
||||
fields: fields.reduce<Record<string, string>>((result, field) => {
|
||||
result[field.key] = String(field.mockValue || `${field.label}测试值`)
|
||||
result[field.key] = resolveMockMaterialFieldValue(field)
|
||||
return result
|
||||
}, {}),
|
||||
},
|
||||
@@ -129,6 +146,17 @@ export async function createAdminMockWorkOrder(payload: JsonObject = {}) {
|
||||
giftCooldownMinutes: normalizeWorkOrderGiftCooldownMinutes(payload.giftCooldownMinutes),
|
||||
materialJson: JSON.stringify(material),
|
||||
requirementJson: JSON.stringify({ fields }),
|
||||
serviceProfileId: serviceProfile?.profileId || null,
|
||||
serviceProfileVersionId: serviceProfile?.versionId || null,
|
||||
serviceProfileSnapshotJson: serviceProfile
|
||||
? JSON.stringify({
|
||||
profileId: serviceProfile.profileId,
|
||||
categoryId: serviceProfile.categoryId,
|
||||
versionId: serviceProfile.versionId,
|
||||
versionNo: serviceProfile.versionNo,
|
||||
config: serviceProfile.config,
|
||||
})
|
||||
: null,
|
||||
now,
|
||||
})
|
||||
if (!workOrder) {
|
||||
@@ -157,10 +185,44 @@ export async function createAdminMockWorkOrder(payload: JsonObject = {}) {
|
||||
productRuleId: productRuleMatch.rule ? Number(productRuleMatch.rule.id) : 0,
|
||||
productRuleKey: productRuleMatch.rule ? String(productRuleMatch.rule.rule_key || '') : '',
|
||||
productRuleMatch: productRuleMatch.reason,
|
||||
serviceProfileId: serviceProfile?.profileId || 0,
|
||||
serviceProfileVersionId: serviceProfile?.versionId || 0,
|
||||
}),
|
||||
now,
|
||||
})
|
||||
return { order: mapWorkOrderAdmin(workOrder) }
|
||||
let autoPublishQueued = false
|
||||
if (materialComplete && serviceProfile?.config.autoPublish.enabled) {
|
||||
const job = await enqueueOrderServiceAutoPublish({
|
||||
workOrderId: Number(workOrder.id),
|
||||
maxAttempts: serviceProfile.config.autoPublish.retryMax + 1,
|
||||
})
|
||||
autoPublishQueued = Boolean(job)
|
||||
if (autoPublishQueued) {
|
||||
void processDueOrderServiceAutoPublishJobs(1).catch(() => undefined)
|
||||
}
|
||||
}
|
||||
return { order: mapWorkOrderAdmin(workOrder), autoPublishQueued }
|
||||
}
|
||||
|
||||
const MOCK_FIELD_DEFAULT_VALUES: Record<string, string> = {
|
||||
gameId: '10000001',
|
||||
gameNickname: '测试角色',
|
||||
serverZone: '微信区',
|
||||
system: '安卓',
|
||||
gameAccount: 'test_account',
|
||||
roleLevel: '100',
|
||||
contactPhone: '13800138000',
|
||||
orderNote: 'Mock 订单备注',
|
||||
}
|
||||
|
||||
function resolveMockMaterialFieldValue(field: {
|
||||
key: string
|
||||
label: string
|
||||
validation?: 'none' | 'digits'
|
||||
mockValue?: string
|
||||
}) {
|
||||
if (field.validation === 'digits') return '10000001'
|
||||
return String(field.mockValue || MOCK_FIELD_DEFAULT_VALUES[field.key] || `${field.label}测试值`)
|
||||
}
|
||||
|
||||
/** 本地 Mock 与真实订单保持相同的关键字段约束,避免生成无法追溯的测试工单。 */
|
||||
|
||||
@@ -893,6 +893,17 @@ test('本地 Mock 工单要求订单、店铺、商品与两个金额完整有
|
||||
assert.deepEqual(resolveAdminMockWorkOrderInput(baseInput, shops).rewardAmount, 2500)
|
||||
})
|
||||
|
||||
test('Mock 建单必须绑定已发布订单服务快照,并在资料完整时进入自动发布队列', async () => {
|
||||
const { readFile } = await import('node:fs/promises')
|
||||
const source = await readFile(
|
||||
new URL('./admin-mock-work-order-service.ts', import.meta.url),
|
||||
'utf8',
|
||||
)
|
||||
assert.match(source, /getPublishedOrderServiceProfileSnapshotByCategory/)
|
||||
assert.match(source, /serviceProfileSnapshotJson/)
|
||||
assert.match(source, /enqueueOrderServiceAutoPublish/)
|
||||
})
|
||||
|
||||
test('排行榜打手名称仅展示首字符', () => {
|
||||
assert.equal(maskWorkerLeaderboardDisplayName('张三'), '张****')
|
||||
assert.equal(maskWorkerLeaderboardDisplayName('Alice'), 'A****')
|
||||
|
||||
Reference in New Issue
Block a user