feat(M08-C): 补管理员订单处置与代下单

This commit is contained in:
Codex
2026-08-10 10:20:32 +08:00
parent 8b2156cdd5
commit 566e61b5d9
20 changed files with 701 additions and 96 deletions
@@ -671,6 +671,57 @@ async function assertPricingAndReservations(pool, context) {
startAt, endAt, pricingMode: 'FULL_DAY'
});
assert.equal(replacement.quote.unitPriceCents, 9000);
const [adminRows] = await pool.query(
`SELECT u.id FROM qipai_users u
INNER JOIN qipai_user_roles ur ON ur.tenant_id = u.tenant_id AND ur.user_id = u.id
INNER JOIN qipai_roles r ON r.id = ur.role_id AND r.tenant_id = ur.tenant_id
WHERE u.tenant_id = ? AND r.code = 'TENANT_ADMIN' LIMIT 1`,
[context.tenantId]
);
const adminId = String(adminRows[0].id);
const managedStartAt = new Date(startAt.getTime() + 24 * 3600000);
const managedEndAt = new Date(managedStartAt.getTime() + 2 * 3600000);
const managed = await repository.reserve({
tenantId: context.tenantId, userId: customerId, roomId,
startAt: managedStartAt, endAt: managedEndAt, pricingMode: 'HOURLY',
actor: {
userId: adminId, source: 'ADMIN', traceId: 'm08c-reserve-on-behalf',
ip: '127.0.0.1', userAgent: 'M08-C live manager booking test'
}
});
const [managedHistory] = await pool.query(
`SELECT actor_id AS actorId, source, CAST(metadata AS CHAR) AS metadata
FROM qipai_order_status_history WHERE tenant_id = ? AND order_id = ?`,
[context.tenantId, managed.orderId]
);
assert.equal(String(managedHistory[0].actorId), adminId);
assert.equal(managedHistory[0].source, 'ADMIN');
assert.equal(String(JSON.parse(managedHistory[0].metadata).targetUserId), customerId);
const [managedAudit] = await pool.query(
`SELECT actor_id AS actorId, action, CAST(metadata AS CHAR) AS metadata
FROM qipai_audit_logs
WHERE tenant_id = ? AND trace_id = 'm08c-reserve-on-behalf'`,
[context.tenantId]
);
assert.equal(String(managedAudit[0].actorId), adminId);
assert.equal(managedAudit[0].action, 'ORDER_CREATED_ON_BEHALF');
assert.equal(String(JSON.parse(managedAudit[0].metadata).targetUserId), customerId);
const [foreignUser] = await pool.query(
`INSERT INTO qipai_users (tenant_id, user_type, status)
VALUES (?, 'CUSTOMER', 'ACTIVE')`,
[String(Number(context.tenantId) + 1)]
);
await assert.rejects(
() => repository.reserve({
tenantId: context.tenantId, userId: String(foreignUser.insertId), roomId,
startAt: new Date(managedStartAt.getTime() + 24 * 3600000),
endAt: new Date(managedEndAt.getTime() + 24 * 3600000),
pricingMode: 'HOURLY'
}),
(error) => error instanceof PricingError && error.code === 'USER_NOT_FOUND'
);
}
async function assertOrderStateMachine(pool, context) {
@@ -1947,6 +1998,8 @@ try {
'room price difference and manager time adjustment',
'configured cancellation fee quote',
'store-scoped on-behalf booking rejection',
'manager on-behalf booking history and audit attribution',
'cross-tenant on-behalf target user rejection',
'share token stored as SHA-256 only',
'default view and door permissions',
'renew permission denied by default',