feat(fulfillment): 收敛履约状态写入口并追加状态事件

This commit is contained in:
yml2213
2026-11-05 20:52:22 +08:00
parent ca214854bb
commit fcd0c137d5
16 changed files with 1386 additions and 402 deletions
@@ -6,6 +6,10 @@ import { formatShanghaiDate, shiftDate } from '../../utils/finance-date-range.js
import { nowIso } from '../../utils/time.js'
import { upsertInternalSkuChannel } from '../fulfillment/internal-sku-channel-service.js'
import { reconcilePendingCarriers } from '../fulfillment/carrier-reconciliation-service.js'
import {
transitionFulfillmentStateBatch,
transitionFulfillmentStateBatchWithExecutor,
} from '../fulfillment/fulfillment-state-service.js'
import { canonicalizeInternalSku, containsCjk } from '../fulfillment/internal-sku-resolver.js'
import { refreshFactsForDates } from '../finance/refresh-facts.js'
import {
@@ -581,12 +585,13 @@ async function retryPendingManualItems(skuKey: string): Promise<number> {
)
const ids = candidates.rows.map((row) => Number(row.id))
if (ids.length === 0) return 0
await query(
`UPDATE order_items
SET fulfillment_locked = FALSE, updated_at = NOW()
WHERE id = ANY($1::bigint[]) AND fulfillment_status = 'pending'`,
[ids],
)
await transitionFulfillmentStateBatch({
orderItemIds: ids,
expected: { status: 'pending', locked: true },
patch: { locked: false },
eventType: 'manual_config_retry_unlocked',
actor: { type: 'system' },
})
const result = await reconcilePendingCarriers({
orderItemIds: ids,
limit: ids.length,
@@ -763,29 +768,28 @@ export async function deleteManualSkuClassification(
)
if (itemIds.length > 0) {
await client.query(
`
UPDATE order_items
SET fulfillment_channel = NULL,
fulfillment_status = 'pending',
fulfillment_source = '',
fulfillment_locked = FALSE,
fulfillment_config_id = NULL,
fulfillment_pending_reason = 'no_channel_capability',
fulfillment_pending_detail = jsonb_build_object(
'reason', 'manual_classification_removed',
'removedAt', NOW()
),
fulfillment_reason = '运营移除错误的人工分类',
fulfillment_decided_by = NULL,
fulfillment_decided_at = NULL,
updated_at = NOW()
WHERE id = ANY($1::bigint[])
AND fulfillment_status = 'assigned'
AND fulfillment_channel = 'manual'
`,
[itemIds],
)
await transitionFulfillmentStateBatchWithExecutor(client, {
orderItemIds: itemIds,
expected: { status: 'assigned', channel: 'manual' },
patch: {
status: 'pending',
channel: null,
source: '',
locked: false,
configId: null,
pendingReason: 'no_channel_capability',
pendingDetail: {
reason: 'manual_classification_removed',
removedAt: nowIso(),
},
reason: '运营移除错误的人工分类',
decidedBy: null,
decidedAt: null,
},
eventType: 'manual_classification_removed',
actor: { type: 'operator' },
reason: key,
})
}
return {