feat(M08-C): 补管理员验券与经营统计

This commit is contained in:
Codex
2026-08-10 11:08:55 +08:00
parent 3bfccb8513
commit ce52f07588
18 changed files with 1141 additions and 22 deletions
@@ -40,6 +40,7 @@ import { IotMessageService } from '../dist/devices/iot-message-service.js';
import { DeviceCommandService } from '../dist/devices/device-command-service.js';
import { DeviceControlService } from '../dist/devices/device-control-service.js';
import { MemberProfileService } from '../dist/wallets/member-profile-service.js';
import { BusinessStatisticsRepository } from '../dist/operations/business-statistics-repository.js';
import {
executeMigrationPlan,
loadMigrationPlan,
@@ -1322,6 +1323,64 @@ async function assertThirdPartyDomain(pool, context) {
assert.equal(voucherRows[0].voucherMasked.includes('SENSITIVE'), false);
assert.equal(Number(voucherRows[0].redemptionCount), 1);
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 adminAccess = await new RbacRepository(pool).getAccessProfile(context.tenantId, adminId);
const managerStartAt = new Date(endAt.getTime() + 3600000);
const managerEndAt = new Date(managerStartAt.getTime() + 2 * 3600000);
const managerOrder = await pricing.reserve({
tenantId: context.tenantId,
userId: customerId,
roomId,
startAt: managerStartAt,
endAt: managerEndAt,
pricingMode: 'HOURLY'
});
const managerRedeemed = await service.redeemVoucherAsManager({
tenantId: context.tenantId,
actorId: adminId,
access: adminAccess,
provider: 'MEITUAN',
voucherCode: 'M08C-MANAGER-VOUCHER-002',
orderId: managerOrder.orderId,
clientRequestId: 'm08c-manager-redeem-002'
});
assert.equal(managerRedeemed.status, 'SUCCEEDED');
const managerRecords = await service.listRecords({
tenantId: context.tenantId,
access: adminAccess,
provider: 'MEITUAN',
status: 'SUCCEEDED',
storeId
});
const managerRecord = managerRecords.redemptions.find(
(item) => String(item.id) === managerRedeemed.redemptionId
);
assert.equal(String(managerRecord.actorId), adminId);
assert.equal(managerRecord.voucherMasked.includes('MANAGER'), false);
const statistics = await new BusinessStatisticsRepository(pool).overview({
tenantId: context.tenantId,
userId: adminId,
access: adminAccess,
traceId: 'm08c-business-statistics',
ip: '127.0.0.1',
userAgent: 'M08-C MySQL test'
}, {
storeId,
from: new Date(Date.now() - 86400000),
to: new Date(Date.now() + 2 * 86400000)
});
assert.ok(statistics.summary.orderTotal >= 2);
assert.ok(statistics.summary.collectedAmountCents > 0);
assert.ok(statistics.summary.voucherSucceeded >= 2);
assert.ok(statistics.paymentChannels.some((item) => item.channel === 'GROUP_BUY'));
await pool.query(
`INSERT INTO qipai_third_party_mappings
(tenant_id, provider, resource_type, external_ref, local_resource_id)
@@ -2077,6 +2136,8 @@ try {
'Wechat reconciliation request history'
,
'group voucher hash-only storage and single redemption',
'manager voucher redemption actor and store attribution',
'store-scoped business statistics from orders and successful payments',
'third-party booking webhook idempotency',
'mapped booking claim creates a paid order',
'unmapped booking enters manual queue'