feat(M08-A): 接入顾客端订单查询
This commit is contained in:
@@ -0,0 +1,163 @@
|
||||
import type { RowDataPacket } from 'mysql2/promise';
|
||||
import type { AccessProfile } from '../auth/rbac-repository.js';
|
||||
import type { MySqlPool } from '../db/mysql.js';
|
||||
import type { OrderStatus } from './order-state-repository.js';
|
||||
|
||||
export class OrderQueryError extends Error {
|
||||
constructor(public readonly code: string) { super(code); }
|
||||
}
|
||||
|
||||
export interface OrderQueryAccess {
|
||||
tenantId: string;
|
||||
userId: string;
|
||||
access: AccessProfile;
|
||||
}
|
||||
|
||||
interface OrderListRow extends RowDataPacket {
|
||||
id: string;
|
||||
orderNo: string;
|
||||
storeId: string;
|
||||
storeName: string;
|
||||
roomId: string;
|
||||
roomName: string;
|
||||
roomNo: string;
|
||||
status: OrderStatus;
|
||||
startAt: Date;
|
||||
endAt: Date;
|
||||
totalAmountCents: number;
|
||||
paidAmountCents: number;
|
||||
latestPaymentId: string | null;
|
||||
latestPaymentProvider: string | null;
|
||||
latestPaymentStatus: string | null;
|
||||
createdAt: Date;
|
||||
}
|
||||
|
||||
interface CountRow extends RowDataPacket { total: number }
|
||||
|
||||
export class OrderQueryRepository {
|
||||
constructor(private readonly pool: MySqlPool) {}
|
||||
|
||||
async listMine(input: OrderQueryAccess & {
|
||||
page: number;
|
||||
pageSize: number;
|
||||
status?: OrderStatus;
|
||||
}) {
|
||||
const where = [
|
||||
'o.tenant_id = ?',
|
||||
'o.deleted_at IS NULL',
|
||||
'(a.user_id = ? OR ' + managerScopeSql(input.access, 'o.store_id') + ')'
|
||||
];
|
||||
const params: Array<string | number> = [input.tenantId, input.userId];
|
||||
if (input.status) {
|
||||
where.push('o.status = ?');
|
||||
params.push(input.status);
|
||||
}
|
||||
const whereSql = where.join(' AND ');
|
||||
const offset = (input.page - 1) * input.pageSize;
|
||||
const [counts] = await this.pool.execute<CountRow[]>(
|
||||
`SELECT COUNT(DISTINCT o.id) AS total
|
||||
FROM qipai_orders o
|
||||
LEFT JOIN qipai_order_user_access a
|
||||
ON a.tenant_id = o.tenant_id AND a.order_id = o.id AND a.revoked_at IS NULL
|
||||
WHERE ${whereSql}`,
|
||||
params
|
||||
);
|
||||
const [rows] = await this.pool.execute<OrderListRow[]>(
|
||||
`SELECT DISTINCT o.id, o.order_no AS orderNo, o.store_id AS storeId, s.name AS storeName,
|
||||
o.room_id AS roomId, r.name AS roomName, r.room_no AS roomNo,
|
||||
o.status, o.start_at AS startAt, o.end_at AS endAt,
|
||||
o.total_amount_cents AS totalAmountCents,
|
||||
o.paid_amount_cents AS paidAmountCents, p.id AS latestPaymentId,
|
||||
p.provider AS latestPaymentProvider, p.status AS latestPaymentStatus,
|
||||
o.created_at AS createdAt
|
||||
FROM qipai_orders o
|
||||
INNER JOIN qipai_stores s ON s.tenant_id = o.tenant_id AND s.id = o.store_id
|
||||
INNER JOIN qipai_rooms r ON r.tenant_id = o.tenant_id AND r.id = o.room_id
|
||||
LEFT JOIN qipai_order_user_access a
|
||||
ON a.tenant_id = o.tenant_id AND a.order_id = o.id AND a.revoked_at IS NULL
|
||||
LEFT JOIN qipai_payments p
|
||||
ON p.tenant_id = o.tenant_id AND p.order_id = o.id
|
||||
AND p.id = (
|
||||
SELECT MAX(p2.id) FROM qipai_payments p2
|
||||
WHERE p2.tenant_id = o.tenant_id AND p2.order_id = o.id
|
||||
AND p2.deleted_at IS NULL
|
||||
)
|
||||
WHERE ${whereSql}
|
||||
ORDER BY o.created_at DESC, o.id DESC
|
||||
LIMIT ? OFFSET ?`,
|
||||
[...params, input.pageSize, offset]
|
||||
);
|
||||
return {
|
||||
items: rows.map(publicOrder),
|
||||
total: Number(counts[0]?.total ?? 0),
|
||||
page: input.page,
|
||||
pageSize: input.pageSize
|
||||
};
|
||||
}
|
||||
|
||||
async getMine(input: OrderQueryAccess & { orderId: string }) {
|
||||
const result = await this.listMine({ ...input, page: 1, pageSize: 1 });
|
||||
const order = result.items.find((item) => item.id === input.orderId);
|
||||
if (order) return order;
|
||||
const [rows] = await this.pool.execute<OrderListRow[]>(
|
||||
`SELECT o.id, o.order_no AS orderNo, o.store_id AS storeId, s.name AS storeName,
|
||||
o.room_id AS roomId, r.name AS roomName, r.room_no AS roomNo,
|
||||
o.status, o.start_at AS startAt, o.end_at AS endAt,
|
||||
o.total_amount_cents AS totalAmountCents,
|
||||
o.paid_amount_cents AS paidAmountCents, p.id AS latestPaymentId,
|
||||
p.provider AS latestPaymentProvider, p.status AS latestPaymentStatus,
|
||||
o.created_at AS createdAt
|
||||
FROM qipai_orders o
|
||||
INNER JOIN qipai_stores s ON s.tenant_id = o.tenant_id AND s.id = o.store_id
|
||||
INNER JOIN qipai_rooms r ON r.tenant_id = o.tenant_id AND r.id = o.room_id
|
||||
LEFT JOIN qipai_order_user_access a
|
||||
ON a.tenant_id = o.tenant_id AND a.order_id = o.id AND a.revoked_at IS NULL
|
||||
LEFT JOIN qipai_payments p
|
||||
ON p.tenant_id = o.tenant_id AND p.order_id = o.id
|
||||
AND p.id = (
|
||||
SELECT MAX(p2.id) FROM qipai_payments p2
|
||||
WHERE p2.tenant_id = o.tenant_id AND p2.order_id = o.id
|
||||
AND p2.deleted_at IS NULL
|
||||
)
|
||||
WHERE o.tenant_id = ? AND o.id = ? AND o.deleted_at IS NULL
|
||||
AND (a.user_id = ? OR ${managerScopeSql(input.access, 'o.store_id')})
|
||||
LIMIT 1`,
|
||||
[input.tenantId, input.orderId, input.userId]
|
||||
);
|
||||
if (!rows[0]) throw new OrderQueryError('ORDER_NOT_FOUND');
|
||||
return publicOrder(rows[0]);
|
||||
}
|
||||
}
|
||||
|
||||
function publicOrder(row: OrderListRow) {
|
||||
return {
|
||||
id: String(row.id),
|
||||
orderNo: row.orderNo,
|
||||
storeId: String(row.storeId),
|
||||
storeName: row.storeName,
|
||||
roomId: String(row.roomId),
|
||||
roomName: row.roomName,
|
||||
roomNo: row.roomNo,
|
||||
status: row.status,
|
||||
startAt: row.startAt,
|
||||
endAt: row.endAt,
|
||||
totalAmountCents: row.totalAmountCents,
|
||||
paidAmountCents: row.paidAmountCents,
|
||||
latestPayment: row.latestPaymentId === null ? null : {
|
||||
id: String(row.latestPaymentId),
|
||||
provider: row.latestPaymentProvider,
|
||||
status: row.latestPaymentStatus
|
||||
},
|
||||
createdAt: row.createdAt
|
||||
};
|
||||
}
|
||||
|
||||
function managerScopeSql(access: AccessProfile, storeExpression: string) {
|
||||
if (access.capabilities.includes('tenant.manage') || access.roles.includes('PLATFORM_ADMIN')) {
|
||||
return '1 = 1';
|
||||
}
|
||||
if (!access.capabilities.includes('store.operation.read') || access.storeIds.length === 0) {
|
||||
return '1 = 0';
|
||||
}
|
||||
return `${storeExpression} IN (${access.storeIds.map((id) => Number(id)).join(',')})`;
|
||||
}
|
||||
Reference in New Issue
Block a user