172 lines
5.5 KiB
TypeScript
172 lines
5.5 KiB
TypeScript
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
|