重构:硬切履约状态唯一真源

This commit is contained in:
yml2213
2026-11-08 19:44:27 +08:00
parent 6bff1971dd
commit bae9a5ae5e
54 changed files with 2096 additions and 437 deletions
@@ -23,7 +23,7 @@ export async function cleanupDirtyAffiliateDashProducts() {
oi.id AS order_item_id,
clean.id AS clean_product_id,
dirty.id AS expected_product_id
FROM order_items oi
FROM v_order_item_current oi
JOIN product_catalog dirty ON dirty.id = oi.canonical_product_id
JOIN fulfillment_tasks ft ON ft.order_item_id = oi.id AND ft.executor_key = 'affiliate_dash'
JOIN product_catalog clean ON clean.canonical_sku = 'aff:' || COALESCE(
@@ -58,7 +58,7 @@ export async function cleanupDirtyAffiliateDashProducts() {
await query(`
DELETE FROM product_catalog
WHERE canonical_sku ~ '^aff:[^a-z0-9_]'
AND NOT EXISTS (SELECT 1 FROM order_items WHERE canonical_product_id = product_catalog.id)
AND NOT EXISTS (SELECT 1 FROM fulfillments WHERE canonical_product_id = product_catalog.id)
AND NOT EXISTS (SELECT 1 FROM cost_versions WHERE canonical_product_id = product_catalog.id)
AND NOT EXISTS (SELECT 1 FROM cost_snapshots WHERE canonical_product_id = product_catalog.id)
AND NOT EXISTS (SELECT 1 FROM sales_daily_facts WHERE canonical_product_id = product_catalog.id)
@@ -207,7 +207,7 @@ export async function backfillAdminAffiliateDashSkus() {
FROM affiliate_item ai
JOIN product_catalog p
ON p.canonical_sku = 'aff:' || ai.sku_code
JOIN order_items oi ON oi.id = ai.id
JOIN v_order_item_current oi ON oi.id = ai.id
)
SELECT id, canonical_product_id, expected_product_id
FROM target
@@ -240,7 +240,7 @@ export async function backfillAdminAffiliateDashSkus() {
'YYYY-MM-DD'
)
FROM affiliate_item
JOIN order_items oi ON oi.id = affiliate_item.id
JOIN v_order_item_current oi ON oi.id = affiliate_item.id
JOIN orders o ON o.id = oi.order_id
) AS from_date,
(
@@ -249,7 +249,7 @@ export async function backfillAdminAffiliateDashSkus() {
'YYYY-MM-DD'
)
FROM affiliate_item
JOIN order_items oi ON oi.id = affiliate_item.id
JOIN v_order_item_current oi ON oi.id = affiliate_item.id
JOIN orders o ON o.id = oi.order_id
) AS to_date
`,
@@ -275,7 +275,7 @@ export async function backfillAdminAffiliateDashSkus() {
const remainingResult = await query<{ total: number }>(
`
SELECT COUNT(*)::int AS total
FROM order_items oi
FROM v_order_item_current oi
WHERE oi.canonical_product_id IS NULL
AND EXISTS (
SELECT 1 FROM fulfillment_tasks ft
@@ -58,7 +58,7 @@ export const COST_MODEL_CODE_BASE: Record<string, string> = {
other: 'other_cost',
}
/** 履约渠道:唯一真源是 order_items.fulfillment_channel + fulfillment_status。 */
/** 履约渠道:唯一真源是 fulfillments.channel + status。 */
export type FulfillmentChannel =
| 'work_order'
| 'affiliate_dash'
@@ -80,7 +80,7 @@ export async function getAdminCostCenterOverview(params: { scope?: string } = {}
(
SELECT COUNT(*)::int
FROM cost_exceptions ce
JOIN order_items oi ON oi.id = ce.order_item_id
JOIN v_order_item_current oi ON oi.id = ce.order_item_id
WHERE ce.status = 'open'
AND (
$1::text IS NULL
@@ -89,7 +89,7 @@ export async function getAdminCostCenterOverview(params: { scope?: string } = {}
) AS open_exception_count,
(
SELECT COUNT(*)::int
FROM order_items oi
FROM v_order_item_current oi
WHERE oi.canonical_product_id IS NULL
-- 只统计已定渠道的单:待分类单还没有渠道,不能算「未映射」。
AND oi.fulfillment_status = 'assigned'
@@ -2,7 +2,7 @@
* 成本中心共享层入口。
*
* 口径(2026-10-23 重写):
* 1. 履约渠道只有 order_items.fulfillment_channel/status 一个真源;
* 1. 履约渠道只有 fulfillments.channel/status 一个真源;
* 2. 成本项用 cost_model 表达「成本怎么算」,与履约方式正交;
* 3. 商品来源由 canonical_sku 前缀推导,商品表不再存履约载体;
* 4. 快照与异常表都不再存履约载体,join order_items 即可拿到。
@@ -332,7 +332,7 @@ test('成本中心总览异常统计必须走异常表关联,不能全表扫
new URL('./cost-center-overview-service.ts', import.meta.url),
'utf8',
)
assert.ok(source.includes('JOIN order_items oi ON oi.id = ce.order_item_id'))
assert.ok(source.includes('JOIN v_order_item_current oi ON oi.id = ce.order_item_id'))
assert.ok(!source.includes('ce.order_item_id IN ('))
assert.ok(source.includes('productChannelFilterSql(1)'))
})
@@ -343,7 +343,7 @@ export async function listAdminCostVersions(payload: JsonObject = {}) {
ROUND(AVG(wo.reward_amount))::bigint AS reward_avg_fen,
SUM(wo.reward_amount)::bigint AS reward_fen
FROM work_orders wo
JOIN order_items oi ON oi.id = wo.order_item_id
JOIN v_order_item_current oi ON oi.id = wo.order_item_id
JOIN orders o ON o.id = oi.order_id
WHERE o.pay_status IN ('paid', 'refunded')
GROUP BY wo.product_rule_id
@@ -583,7 +583,7 @@ export async function listAdminCostExceptions(payload: JsonObject = {}) {
params.push(channel)
clauses.push(
`ce.order_item_id IN (
SELECT oi.id FROM order_items oi WHERE COALESCE(oi.fulfillment_channel, CASE WHEN oi.fulfillment_status = 'archived' THEN 'archived' ELSE 'pending' END) = $${params.length}
SELECT oi.id FROM v_order_item_current oi WHERE COALESCE(oi.fulfillment_channel, CASE WHEN oi.fulfillment_status = 'archived' THEN 'archived' ELSE 'pending' END) = $${params.length}
)`,
)
}
@@ -600,7 +600,7 @@ export async function listAdminCostExceptions(payload: JsonObject = {}) {
ce.*,
COALESCE(oi.fulfillment_channel, CASE WHEN oi.fulfillment_status = 'archived' THEN 'archived' ELSE 'pending' END) AS channel
FROM cost_exceptions ce
LEFT JOIN order_items oi ON oi.id = ce.order_item_id
LEFT JOIN v_order_item_current oi ON oi.id = ce.order_item_id
${where}
ORDER BY ce.status ASC, ce.created_at DESC, ce.id DESC
LIMIT $${listParams.length - 1} OFFSET $${listParams.length}
@@ -642,7 +642,7 @@ export async function summarizeAdminCostExceptions(
params.push(channel)
clauses.push(
`ce.order_item_id IN (
SELECT oi.id FROM order_items oi WHERE COALESCE(oi.fulfillment_channel, CASE WHEN oi.fulfillment_status = 'archived' THEN 'archived' ELSE 'pending' END) = $${params.length}
SELECT oi.id FROM v_order_item_current oi WHERE COALESCE(oi.fulfillment_channel, CASE WHEN oi.fulfillment_status = 'archived' THEN 'archived' ELSE 'pending' END) = $${params.length}
)`,
)
}
@@ -129,7 +129,7 @@ async function resolveManualCostHint(orderItemId: number | null) {
cfg.credit_quantity,
cfg.cost_mode,
COALESCE((fp.config_json->>'rechargeUnitPriceFen')::bigint, 1) AS unit_price_fen
FROM order_items oi
FROM v_order_item_current oi
JOIN manual_fulfillment_configs cfg ON cfg.id = oi.fulfillment_config_id
LEFT JOIN fulfillment_profiles fp ON fp.profile_key = 'manual_' || cfg.platform_key
WHERE oi.id = $1
@@ -317,7 +317,7 @@ async function resolveCostContext(payload: JsonObject): Promise<OrderItemCostCon
wo.id AS work_order_id,
wo.product_rule_id AS work_product_rule_id,
COALESCE(oi.fulfillment_channel, CASE WHEN oi.fulfillment_status = 'archived' THEN 'archived' ELSE 'pending' END) AS channel
FROM order_items oi
FROM v_order_item_current oi
JOIN orders o ON o.id = oi.order_id
LEFT JOIN LATERAL (
SELECT id, product_rule_id
@@ -394,7 +394,7 @@ async function resolveCostContext(payload: JsonObject): Promise<OrderItemCostCon
}
/**
* 履约渠道只读 order_items.fulfillment_channel / fulfillment_status。
* 履约渠道只读 fulfillments.channel / status。
* 订单项还没判定时按人工处理,不允许用 provider 或核销码兜底。
*/
function resolveFulfillmentMode(value: unknown): FulfillmentChannel {
@@ -317,7 +317,7 @@ const CANDIDATE_SQL = `
wpr.pricing_mode AS work_product_rule_pricing_mode,
wo.status AS work_order_status,
wo.reward_amount
FROM order_items oi
FROM v_order_item_current oi
JOIN orders o ON o.id = oi.order_id
LEFT JOIN work_orders wo ON wo.order_item_id = oi.id AND wo.status <> 'cancelled'
LEFT JOIN work_product_rules wpr ON wpr.id = wo.product_rule_id
@@ -156,7 +156,7 @@ export async function scanManualSkus(options: ScanOptions) {
COALESCE(oi.revenue_amount_fen, 0)::bigint AS revenue_fen,
(COALESCE(o.paid_at, o.created_at) AT TIME ZONE 'Asia/Shanghai')::date AS stat_date,
resolve_internal_sku(oi.manual_sku_key, oi.sku_name, oi.sku_code) AS sku_key
FROM order_items oi
FROM v_order_item_current oi
JOIN orders o ON o.id = oi.order_id
WHERE oi.fulfillment_status = 'assigned' AND oi.fulfillment_channel = 'manual'
AND (COALESCE(o.paid_at, o.created_at) AT TIME ZONE 'Asia/Shanghai')::date
@@ -273,7 +273,7 @@ export async function listManualSkus(input: Record<string, unknown> = {}) {
if (provider) {
params.push(provider)
conditions.push(`EXISTS (
SELECT 1 FROM order_items oi
SELECT 1 FROM v_order_item_current oi
JOIN orders o ON o.id = oi.order_id
WHERE oi.fulfillment_status = 'assigned' AND oi.fulfillment_channel = 'manual'
AND o.provider = $${params.length}
@@ -314,7 +314,7 @@ export async function listManualSkus(input: Record<string, unknown> = {}) {
AS first_seen_date,
MAX((COALESCE(o.paid_at, o.created_at) AT TIME ZONE 'Asia/Shanghai')::date)
AS last_seen_date
FROM order_items oi
FROM v_order_item_current oi
JOIN orders o ON o.id = oi.order_id
WHERE oi.fulfillment_status = 'assigned'
AND oi.fulfillment_channel = 'manual'
@@ -381,7 +381,7 @@ export async function getManualSkuOverview() {
COUNT(*)::int AS item_count,
MAX((COALESCE(o.paid_at, o.created_at) AT TIME ZONE 'Asia/Shanghai')::date)
AS last_seen_date
FROM order_items oi
FROM v_order_item_current oi
JOIN orders o ON o.id = oi.order_id
WHERE oi.fulfillment_status = 'assigned'
AND oi.fulfillment_channel = 'manual'
@@ -602,7 +602,7 @@ async function retryPendingManualItems(skuKey: string): Promise<number> {
if (!key) return 0
const candidates = await query<{ id: number }>(
`SELECT oi.id
FROM order_items oi
FROM v_order_item_current oi
WHERE oi.fulfillment_status = 'pending'
AND resolve_internal_sku(oi.manual_sku_key, oi.sku_name, oi.sku_code) = $1
ORDER BY oi.id
@@ -688,7 +688,7 @@ export async function deleteManualSkuClassification(
oi.id,
(COALESCE(o.paid_at, o.created_at) AT TIME ZONE 'Asia/Shanghai')::date::text
AS basis_date
FROM order_items oi
FROM v_order_item_current oi
JOIN orders o ON o.id = oi.order_id
WHERE oi.fulfillment_status = 'assigned'
AND oi.fulfillment_channel = 'manual'
@@ -867,7 +867,7 @@ export async function rematchManualFulfillments(input: Record<string, unknown> =
oi.id AS order_item_id,
cfg.id AS config_id,
cfg.canonical_product_id
FROM order_items oi
FROM v_order_item_current oi
JOIN orders o ON o.id = oi.order_id
JOIN manual_fulfillment_configs cfg
ON canonicalize_internal_sku(cfg.sku_key) = resolve_internal_sku(
@@ -410,7 +410,7 @@ export async function deleteAdminProduct(productId: number | string) {
(SELECT COUNT(*)::int FROM channel_product_mappings WHERE canonical_product_id = $1) AS mapping_count,
(SELECT COUNT(*)::int FROM cost_versions WHERE canonical_product_id = $1) AS version_count,
(SELECT COUNT(*)::int FROM cost_snapshots WHERE canonical_product_id = $1) AS snapshot_count,
(SELECT COUNT(*)::int FROM order_items WHERE canonical_product_id = $1) AS order_item_count
(SELECT COUNT(*)::int FROM fulfillments WHERE canonical_product_id = $1) AS order_item_count
`,
[id],
)
@@ -474,7 +474,7 @@ export async function deleteAdminVoucherProduct(productId: number | string) {
)
)
AS snapshot_count,
(SELECT COUNT(*)::int FROM order_items WHERE canonical_product_id = $1)
(SELECT COUNT(*)::int FROM fulfillments WHERE canonical_product_id = $1)
AS order_item_count
`,
[id],
@@ -779,7 +779,7 @@ export async function getAdminChannelMappingDiagnostics(payload: JsonObject = {}
COALESCE(oi.fulfillment_channel, CASE WHEN oi.fulfillment_status = 'archived' THEN 'archived' ELSE 'pending' END) AS channel,
COUNT(*)::int AS item_count
FROM cost_exceptions ce
JOIN order_items oi ON oi.id = ce.order_item_id
JOIN v_order_item_current oi ON oi.id = ce.order_item_id
WHERE ce.reason = 'product_mapping_missing'
AND ce.status = 'open'
AND COALESCE(oi.fulfillment_channel, CASE WHEN oi.fulfillment_status = 'archived' THEN 'archived' ELSE 'pending' END) = $1
@@ -874,7 +874,7 @@ export async function listAdminUnmappedMappingGroups(payload: JsonObject = {}) {
COALESCE(ce.detail_json->>'skuCode', '') AS sku_code,
COUNT(*)::int AS item_count
FROM cost_exceptions ce
JOIN order_items oi ON oi.id = ce.order_item_id
JOIN v_order_item_current oi ON oi.id = ce.order_item_id
WHERE ${clauses.join(' AND ')}
AND COALESCE(oi.fulfillment_channel, CASE WHEN oi.fulfillment_status = 'archived' THEN 'archived' ELSE 'pending' END) = $${totalParams.length}
GROUP BY 1, 2, 3
@@ -927,7 +927,7 @@ export async function listAdminUnmappedMappingGroups(payload: JsonObject = {}) {
MAX(NULLIF(ce.detail_json->>'basisAt', '')) AS last_seen_at,
COUNT(*)::int AS item_count
FROM cost_exceptions ce
JOIN order_items oi ON oi.id = ce.order_item_id
JOIN v_order_item_current oi ON oi.id = ce.order_item_id
WHERE ${clauses.join(' AND ')}
${modeClause}
GROUP BY 1, 2, 3, 4
@@ -1192,7 +1192,7 @@ export async function backfillAdminProductMappings(payload: JsonObject = {}) {
oi.sku_code,
oi.normalized_sku_name,
oi.normalized_sku_series
FROM order_items oi
FROM v_order_item_current oi
JOIN orders o ON o.id = oi.order_id
WHERE oi.canonical_product_id IS NULL
AND oi.id > $1
@@ -1241,7 +1241,7 @@ export async function backfillAdminProductMappings(payload: JsonObject = {}) {
AND ce.subject_type = 'order_item'
AND ce.reason = 'product_mapping_missing'
AND EXISTS (
SELECT 1 FROM order_items oi
SELECT 1 FROM v_order_item_current oi
WHERE oi.id = ce.subject_id AND oi.canonical_product_id IS NOT NULL
)
`,
@@ -1252,7 +1252,7 @@ export async function backfillAdminProductMappings(payload: JsonObject = {}) {
const remainingResult = await query<{ total: number }>(
`
SELECT COUNT(*)::int AS total
FROM order_items oi
FROM v_order_item_current oi
JOIN orders o ON o.id = oi.order_id
WHERE oi.canonical_product_id IS NULL
AND ($1 = '' OR o.provider = $1)
@@ -1354,7 +1354,7 @@ export async function backfillAdminNormalizedQuantities(payload: JsonObject = {}
`
SELECT oi.id, oi.sku_name, oi.sku_code, oi.normalized_sku_name,
oi.normalized_sku_series, oi.normalized_quantity
FROM order_items oi
FROM v_order_item_current oi
${range ? 'JOIN orders o ON o.id = oi.order_id' : ''}
WHERE (oi.normalized_quantity IS NULL
OR oi.normalized_sku_name IS NULL
@@ -1404,7 +1404,7 @@ export async function backfillAdminNormalizedQuantities(payload: JsonObject = {}
const remainingResult = await query<{ total: number }>(
`SELECT COUNT(*)::int AS total
FROM order_items oi
FROM v_order_item_current oi
${range ? 'JOIN orders o ON o.id = oi.order_id' : ''}
WHERE (oi.normalized_quantity IS NULL
OR oi.normalized_sku_name IS NULL
@@ -1758,7 +1758,7 @@ export async function setAdminProductPrice(
const earliest = await query<{ basis_date: string | null }>(
`
SELECT MIN((COALESCE(o.paid_at, o.created_at) AT TIME ZONE 'Asia/Shanghai')::date)::text AS basis_date
FROM order_items oi
FROM v_order_item_current oi
JOIN orders o ON o.id = oi.order_id
WHERE oi.canonical_product_id = $1
AND oi.fulfillment_status = 'assigned'
@@ -1840,7 +1840,7 @@ export async function setAdminProductPrice(
const missingItems = await query<{ id: number }>(
`
SELECT oi.id
FROM order_items oi
FROM v_order_item_current oi
WHERE oi.canonical_product_id = $1
AND oi.fulfillment_status = 'assigned'
AND NOT EXISTS (
@@ -1866,7 +1866,7 @@ export async function setAdminProductPrice(
const coveredItems = await query<{ id: number }>(
`
SELECT oi.id
FROM order_items oi
FROM v_order_item_current oi
WHERE oi.canonical_product_id = $1
AND oi.fulfillment_status = 'assigned'
AND EXISTS (
@@ -11,7 +11,7 @@ import {
/**
* 管道 B:商品归一。
*
* 只写 `order_items.canonical_product_id`,不改渠道、不碰成本;
* 只写 `fulfillments.canonical_product_id`,不改渠道、不碰成本;
* 例外:三方卡券缺商品时按内部中文 SKU 自动建 `tpv:` 商品并补渠道规则。
*
* 解析顺序:
@@ -339,7 +339,7 @@ export async function normalizeOrderItemProducts(
wpr.rule_key AS work_rule_key,
cfg.canonical_product_id AS manual_config_product_id,
cfg_product.canonical_sku AS manual_config_product_sku
FROM order_items oi
FROM v_order_item_current oi
JOIN orders o ON o.id = oi.order_id
LEFT JOIN work_orders wo ON wo.order_item_id = oi.id AND wo.status <> 'cancelled'
LEFT JOIN work_product_rules wpr ON wpr.id = wo.product_rule_id
@@ -529,7 +529,7 @@ export async function normalizeProductsForItems(itemIds: number[]): Promise<{
wpr.rule_key AS work_rule_key,
cfg.canonical_product_id AS manual_config_product_id,
cfg_product.canonical_sku AS manual_config_product_sku
FROM order_items oi
FROM v_order_item_current oi
JOIN orders o ON o.id = oi.order_id
LEFT JOIN work_orders wo ON wo.order_item_id = oi.id AND wo.status <> 'cancelled'
LEFT JOIN work_product_rules wpr ON wpr.id = wo.product_rule_id
@@ -626,7 +626,7 @@ async function resetImportedTemplateProductBootstrap(range: { fromDate: string;
`
WITH candidates AS (
SELECT oi.id, oi.canonical_product_id AS expected_product_id
FROM order_items oi
FROM v_order_item_current oi
JOIN orders o ON o.id = oi.order_id
JOIN channel_product_mappings m
ON m.provider = o.provider
@@ -680,7 +680,7 @@ async function resetImportedTemplateProductBootstrap(range: { fromDate: string;
AND m.note LIKE '接单模板 %自动初始化%'
AND EXISTS (
SELECT 1
FROM order_items oi
FROM v_order_item_current oi
JOIN orders o ON o.id = oi.order_id
WHERE oi.canonical_product_id = m.canonical_product_id
AND (COALESCE(o.paid_at, o.created_at) AT TIME ZONE 'Asia/Shanghai')::date
@@ -732,7 +732,7 @@ async function loadTemplateProductBootstrapData(range: { fromDate: string; toDat
wo.product_rule_id,
r.rule_key,
COUNT(DISTINCT oi.id)::int AS item_count
FROM order_items oi
FROM v_order_item_current oi
JOIN orders o ON o.id = oi.order_id
JOIN work_orders wo ON wo.order_item_id = oi.id AND wo.status <> 'cancelled'
JOIN work_product_rules r ON r.id = wo.product_rule_id AND r.enabled = TRUE
@@ -8,7 +8,8 @@ test('卡券 SKU 一个入口完成商品、规则、成本版本和订单回填
assert.ok(service.includes('withTransaction('))
assert.ok(service.includes("channel: 'third_party_voucher'"))
assert.ok(service.includes('sys_third_party_voucher_cost'))
assert.ok(service.includes('UPDATE order_items oi'))
assert.ok(service.includes('attachFulfillmentConfigurationsWithExecutor'))
assert.ok(!service.includes('UPDATE order_items oi'))
assert.ok(service.includes('syncCostSnapshotsForItems'))
assert.ok(service.includes('refreshFactsForDates'))
assert.ok(service.includes("resolveExceptionsForItemsByReason(result.itemIds, 'cost_missing')"))
@@ -6,6 +6,7 @@ import type { AdminSession } from '../admin/admin-auth-service.js'
import { orderItemBasisDates, refreshFactsForDates } from '../finance/refresh-facts.js'
import { upsertInternalSkuChannelWithClient } from '../fulfillment/internal-sku-channel-service.js'
import { canonicalizeInternalSku, containsCjk } from '../fulfillment/internal-sku-resolver.js'
import { attachFulfillmentConfigurationsWithExecutor } from '../fulfillment/fulfillment-state-service.js'
import { resolvePriceEffectiveFrom } from './cost-center-normalizers.js'
import {
resolveExceptionsForItemsByReason,
@@ -103,24 +104,31 @@ export async function upsertAdminVoucherProduct(payload: JsonObject, session: Ad
replacePriority: true,
})
const repairedItems = await client.query<VoucherProductItemRow>(
const repairCandidates = await client.query<VoucherProductItemRow>(
`
UPDATE order_items oi
SET canonical_product_id = $2,
updated_at = $3
FROM orders o
WHERE o.id = oi.order_id
AND oi.fulfillment_channel = 'third_party_voucher'
AND resolve_internal_sku(oi.manual_sku_key, oi.sku_name, oi.sku_code) = $1
AND oi.canonical_product_id IS DISTINCT FROM $2
RETURNING
SELECT
oi.id AS order_item_id,
(COALESCE(o.paid_at, o.created_at) AT TIME ZONE 'Asia/Shanghai')::date::text
AS basis_date
FROM v_order_item_current oi
JOIN orders o ON o.id = oi.order_id
WHERE TRUE
AND oi.fulfillment_channel = 'third_party_voucher'
AND resolve_internal_sku(oi.manual_sku_key, oi.sku_name, oi.sku_code) = $1
AND oi.canonical_product_id IS DISTINCT FROM $2
`,
[skuKey, productId, now],
[skuKey, productId],
)
const itemIds = repairedItems.rows.map((row) => Number(row.order_item_id))
const itemIds = repairCandidates.rows.map((row) => Number(row.order_item_id))
if (itemIds.length > 0) {
await attachFulfillmentConfigurationsWithExecutor(client, {
mappings: itemIds.map((orderItemId) => ({
orderItemId,
canonicalProductId: productId,
})),
onlyWhenCanonicalProductDifferent: true,
})
}
if (itemIds.length > 0) {
await client.query(
`UPDATE cost_snapshots
@@ -145,13 +153,13 @@ export async function upsertAdminVoucherProduct(payload: JsonObject, session: Ad
}
let effectiveFrom = initialEffectiveFrom
if (!requestedEffectiveFrom && repairedItems.rows.length > 0) {
if (!requestedEffectiveFrom && repairCandidates.rows.length > 0) {
const missingDates = await client.query<{ basis_date: string }>(
`
SELECT MIN(
(COALESCE(o.paid_at, o.created_at) AT TIME ZONE 'Asia/Shanghai')::date
)::text AS basis_date
FROM order_items oi
FROM v_order_item_current oi
JOIN orders o ON o.id = oi.order_id
WHERE oi.id = ANY($1::bigint[])
AND NOT EXISTS (
@@ -272,7 +280,7 @@ export async function upsertAdminVoucherProduct(payload: JsonObject, session: Ad
return {
product: mapProduct(product),
itemIds,
dates: [...new Set(repairedItems.rows.map((row) => row.basis_date).filter(Boolean))],
dates: [...new Set(repairCandidates.rows.map((row) => row.basis_date).filter(Boolean))],
costVersionId,
effectiveFrom,
amountFen,
@@ -49,7 +49,7 @@ export async function backfillAdminWorkOrderProductSkus(payload: Record<string,
'YYYY-MM-DD'
) AS to_date
FROM work_orders wo
JOIN order_items oi ON oi.id = wo.order_item_id
JOIN v_order_item_current oi ON oi.id = wo.order_item_id
JOIN orders o ON o.id = oi.order_id
WHERE wo.order_item_id IS NOT NULL
AND wo.status <> 'cancelled'
@@ -77,7 +77,7 @@ export async function backfillAdminWorkOrderProductSkus(payload: Record<string,
wo.product_rule_id,
oi.canonical_product_id AS expected_product_id
FROM work_orders wo
JOIN order_items oi ON oi.id = wo.order_item_id
JOIN v_order_item_current oi ON oi.id = wo.order_item_id
JOIN orders o ON o.id = oi.order_id
WHERE wo.order_item_id IS NOT NULL
AND wo.status <> 'cancelled'
@@ -120,7 +120,7 @@ export async function backfillAdminWorkOrderProductSkus(payload: Record<string,
SELECT
oi.normalized_sku_name AS name,
MIN(oi.sku_name) AS sample_name
FROM order_items oi
FROM v_order_item_current oi
JOIN orders o ON o.id = oi.order_id
WHERE oi.canonical_product_id IS NULL
AND btrim(COALESCE(oi.normalized_sku_name, '')) <> ''
@@ -174,7 +174,7 @@ export async function backfillAdminWorkOrderProductSkus(payload: Record<string,
oi.sku_code,
MIN(oi.sku_name) AS sku_name,
oi.normalized_sku_name
FROM order_items oi
FROM v_order_item_current oi
JOIN orders o ON o.id = oi.order_id
WHERE oi.canonical_product_id IS NULL
AND btrim(COALESCE(oi.normalized_sku_name, '')) <> ''
@@ -219,7 +219,7 @@ export async function backfillAdminWorkOrderProductSkus(payload: Record<string,
`
WITH tail AS (
SELECT oi.id, oi.normalized_sku_name
FROM order_items oi
FROM v_order_item_current oi
JOIN orders o ON o.id = oi.order_id
WHERE oi.canonical_product_id IS NULL
AND btrim(COALESCE(oi.normalized_sku_name, '')) <> ''
@@ -267,7 +267,7 @@ export async function backfillAdminWorkOrderProductSkus(payload: Record<string,
const remainingResult = await query<{ total: number }>(
`
SELECT COUNT(*)::int AS total
FROM order_items oi
FROM v_order_item_current oi
JOIN orders o ON o.id = oi.order_id
WHERE oi.canonical_product_id IS NULL
AND (COALESCE(o.paid_at, o.created_at) AT TIME ZONE 'Asia/Shanghai')::date