收口接单大厅模板价格同步与本地开发容器
This commit is contained in:
@@ -0,0 +1,171 @@
|
||||
import { closeDb, query } from '../src/db/client.js'
|
||||
import type { WorkOrderRow, WorkProductRuleRow } from '../src/repositories/worker-platform/types.js'
|
||||
import { resolveWorkOrderTemplatePriceProjection } from '../src/services/worker-platform/work-order-hall-pricing-service.js'
|
||||
|
||||
type ShareAggregate = {
|
||||
work_order_id: number
|
||||
joined_quantity: number
|
||||
locked_share_reward: number
|
||||
}
|
||||
|
||||
type AuditIssue = {
|
||||
severity: 'critical' | 'warning'
|
||||
code: string
|
||||
workOrderId: number
|
||||
status: string
|
||||
detail: string
|
||||
}
|
||||
|
||||
const rows = await query<WorkOrderRow>(
|
||||
`SELECT *
|
||||
FROM work_orders
|
||||
WHERE product_rule_id IS NOT NULL
|
||||
AND assigned_worker_id IS NULL
|
||||
AND status IN ('pending_material', 'unassigned', 'open')
|
||||
AND NOT (COALESCE(material_json, '{}'::jsonb) ? 'consignment')
|
||||
ORDER BY id`,
|
||||
)
|
||||
const workOrders = rows.rows
|
||||
const ruleIds = [...new Set(workOrders.map((row) => Number(row.product_rule_id || 0)))]
|
||||
const workOrderIds = workOrders.map((row) => Number(row.id))
|
||||
|
||||
const rulesResult =
|
||||
ruleIds.length > 0
|
||||
? await query<WorkProductRuleRow>(
|
||||
'SELECT * FROM work_product_rules WHERE id = ANY($1::bigint[])',
|
||||
[ruleIds],
|
||||
)
|
||||
: { rows: [] as WorkProductRuleRow[] }
|
||||
const versionsResult =
|
||||
ruleIds.length > 0
|
||||
? await query<{ work_product_rule_id: number; id: number }>(
|
||||
`SELECT DISTINCT ON (work_product_rule_id)
|
||||
work_product_rule_id, id
|
||||
FROM cost_versions
|
||||
WHERE work_product_rule_id = ANY($1::bigint[])
|
||||
AND status = 'active'
|
||||
AND effective_from <= NOW()
|
||||
AND (effective_to IS NULL OR effective_to > NOW())
|
||||
ORDER BY work_product_rule_id, effective_from DESC, id DESC`,
|
||||
[ruleIds],
|
||||
)
|
||||
: { rows: [] as Array<{ work_product_rule_id: number; id: number }> }
|
||||
const sharesResult =
|
||||
workOrderIds.length > 0
|
||||
? await query<ShareAggregate>(
|
||||
`SELECT
|
||||
work_order_id,
|
||||
COALESCE(SUM(quantity), 0)::int AS joined_quantity,
|
||||
COALESCE(SUM(share_reward), 0)::bigint AS locked_share_reward
|
||||
FROM work_order_shares
|
||||
WHERE work_order_id = ANY($1::bigint[])
|
||||
AND status != 'cancelled'
|
||||
GROUP BY work_order_id`,
|
||||
[workOrderIds],
|
||||
)
|
||||
: { rows: [] as ShareAggregate[] }
|
||||
|
||||
const rules = new Map(rulesResult.rows.map((rule) => [Number(rule.id), rule]))
|
||||
const activeVersions = new Map(
|
||||
versionsResult.rows.map((version) => [Number(version.work_product_rule_id), Number(version.id)]),
|
||||
)
|
||||
const shareAggregates = new Map(
|
||||
sharesResult.rows.map((share) => [Number(share.work_order_id), share]),
|
||||
)
|
||||
const issues: AuditIssue[] = []
|
||||
|
||||
for (const workOrder of workOrders) {
|
||||
const ruleId = Number(workOrder.product_rule_id || 0)
|
||||
const rule = rules.get(ruleId)
|
||||
const activeVersionId = activeVersions.get(ruleId) || 0
|
||||
const share = shareAggregates.get(Number(workOrder.id))
|
||||
const joinedQuantity = Number(share?.joined_quantity || 0)
|
||||
const lockedShareReward = Number(share?.locked_share_reward || 0)
|
||||
const severity = workOrder.status === 'open' ? 'critical' : 'warning'
|
||||
|
||||
if (!rule) {
|
||||
issues.push({
|
||||
severity,
|
||||
code: 'template_missing',
|
||||
workOrderId: Number(workOrder.id),
|
||||
status: workOrder.status,
|
||||
detail: `product_rule_id=${ruleId} 不存在`,
|
||||
})
|
||||
continue
|
||||
}
|
||||
if (!activeVersionId) {
|
||||
issues.push({
|
||||
severity,
|
||||
code: 'active_cost_version_missing',
|
||||
workOrderId: Number(workOrder.id),
|
||||
status: workOrder.status,
|
||||
detail: `模板 ${ruleId} 没有当前生效成本版本`,
|
||||
})
|
||||
continue
|
||||
}
|
||||
if (Number(workOrder.hall_price_version_id || 0) !== activeVersionId) {
|
||||
issues.push({
|
||||
severity,
|
||||
code: 'hall_price_version_stale',
|
||||
workOrderId: Number(workOrder.id),
|
||||
status: workOrder.status,
|
||||
detail: `当前=${Number(workOrder.hall_price_version_id || 0) || 'NULL'} 应=${activeVersionId}`,
|
||||
})
|
||||
}
|
||||
|
||||
const projection = resolveWorkOrderTemplatePriceProjection({ rule, workOrder })
|
||||
if (!projection) {
|
||||
issues.push({
|
||||
severity,
|
||||
code: 'template_price_invalid',
|
||||
workOrderId: Number(workOrder.id),
|
||||
status: workOrder.status,
|
||||
detail: '模板当前配置无法计算工单价格',
|
||||
})
|
||||
continue
|
||||
}
|
||||
const totalQuantity = Math.max(1, Number(workOrder.sharing_total_quantity || 1))
|
||||
const expectedReward = workOrder.sharing_enabled
|
||||
? lockedShareReward +
|
||||
Math.max(0, totalQuantity - joinedQuantity) * Number(projection.sharingUnitReward || 0)
|
||||
: projection.rewardAmount
|
||||
if (Number(workOrder.reward_amount || 0) !== expectedReward) {
|
||||
issues.push({
|
||||
severity,
|
||||
code: 'reward_amount_stale',
|
||||
workOrderId: Number(workOrder.id),
|
||||
status: workOrder.status,
|
||||
detail: `当前=${Number(workOrder.reward_amount || 0)} 应=${expectedReward}`,
|
||||
})
|
||||
}
|
||||
if (
|
||||
workOrder.sharing_enabled &&
|
||||
Number(workOrder.sharing_unit_reward || 0) !== Number(projection.sharingUnitReward || 0)
|
||||
) {
|
||||
issues.push({
|
||||
severity,
|
||||
code: 'sharing_unit_reward_stale',
|
||||
workOrderId: Number(workOrder.id),
|
||||
status: workOrder.status,
|
||||
detail: `当前=${Number(workOrder.sharing_unit_reward || 0)} 应=${Number(projection.sharingUnitReward || 0)}`,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const critical = issues.filter((issue) => issue.severity === 'critical')
|
||||
const warnings = issues.filter((issue) => issue.severity === 'warning')
|
||||
console.log(
|
||||
JSON.stringify(
|
||||
{
|
||||
ok: critical.length === 0,
|
||||
checkedAt: new Date().toISOString(),
|
||||
scannedWorkOrders: workOrders.length,
|
||||
critical,
|
||||
warnings,
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
)
|
||||
await closeDb()
|
||||
if (critical.length > 0) process.exitCode = 1
|
||||
Reference in New Issue
Block a user