feat(M08-B): 补后台协作与结算详情
This commit is contained in:
@@ -96,6 +96,21 @@ interface SettlementRow extends RowDataPacket {
|
||||
note: string;
|
||||
createdAt: Date;
|
||||
}
|
||||
interface SettlementItemRow extends RowDataPacket {
|
||||
id: string;
|
||||
settlementId: string;
|
||||
taskId: string;
|
||||
taskNo: string;
|
||||
orderNo: string | null;
|
||||
storeName: string;
|
||||
roomName: string;
|
||||
roomNo: string;
|
||||
cleanerUserId: string;
|
||||
cleanerName: string;
|
||||
rewardCents: number;
|
||||
completedAt: Date | null;
|
||||
createdAt: Date;
|
||||
}
|
||||
|
||||
export class CleaningTaskRepository {
|
||||
constructor(private readonly pool: MySqlPool) {}
|
||||
@@ -370,6 +385,31 @@ export class CleaningTaskRepository {
|
||||
};
|
||||
}
|
||||
|
||||
async getSettlementDetail(input: CleaningActor & { settlementId: string }) {
|
||||
this.assertSettlement(input.access, 'read');
|
||||
const settlement = await this.getSettlementScoped(input);
|
||||
const [items] = await this.pool.execute<SettlementItemRow[]>(
|
||||
`SELECT i.id, i.settlement_id AS settlementId, i.task_id AS taskId,
|
||||
t.task_no AS taskNo, o.order_no AS orderNo,
|
||||
st.name AS storeName, r.name AS roomName, r.room_no AS roomNo,
|
||||
i.cleaner_user_id AS cleanerUserId, u.nickname AS cleanerName,
|
||||
i.reward_cents AS rewardCents, t.completed_at AS completedAt,
|
||||
i.created_at AS createdAt
|
||||
FROM qipai_cleaning_settlement_items i
|
||||
INNER JOIN qipai_cleaning_settlements s ON s.tenant_id = i.tenant_id
|
||||
AND s.id = i.settlement_id AND s.deleted_at IS NULL
|
||||
INNER JOIN qipai_cleaning_tasks t ON t.tenant_id = i.tenant_id AND t.id = i.task_id
|
||||
INNER JOIN qipai_stores st ON st.tenant_id = t.tenant_id AND st.id = t.store_id
|
||||
INNER JOIN qipai_rooms r ON r.tenant_id = t.tenant_id AND r.id = t.room_id
|
||||
INNER JOIN qipai_users u ON u.tenant_id = i.tenant_id AND u.id = i.cleaner_user_id
|
||||
LEFT JOIN qipai_orders o ON o.tenant_id = t.tenant_id AND o.id = t.order_id
|
||||
WHERE i.tenant_id = ? AND i.settlement_id = ?
|
||||
ORDER BY t.completed_at ASC, i.id ASC`,
|
||||
[input.tenantId, input.settlementId]
|
||||
);
|
||||
return { settlement, items: items.map(publicSettlementItem) };
|
||||
}
|
||||
|
||||
async generateSettlement(input: CleaningActor & {
|
||||
cleanerUserId: string; storeId?: string; note?: string;
|
||||
}) {
|
||||
@@ -963,6 +1003,29 @@ export class CleaningTaskRepository {
|
||||
return publicSettlement(rows[0]);
|
||||
}
|
||||
|
||||
private async getSettlementScoped(input: CleaningActor & { settlementId: string }) {
|
||||
const [rows] = await this.pool.execute<SettlementRow[]>(
|
||||
`SELECT s.id, s.settlement_no AS settlementNo,
|
||||
s.cleaner_user_id AS cleanerUserId, u.nickname AS cleanerName,
|
||||
s.store_id AS storeId, st.name AS storeName, s.status,
|
||||
s.task_count AS taskCount, s.total_reward_cents AS totalRewardCents,
|
||||
s.period_start AS periodStart, s.period_end AS periodEnd,
|
||||
s.paid_by AS paidBy, s.confirmed_at AS confirmedAt, s.paid_at AS paidAt,
|
||||
s.payout_channel AS payoutChannel, s.payout_reference AS payoutReference,
|
||||
s.payout_state AS payoutState, s.payout_package_info AS payoutPackageInfo,
|
||||
s.payout_error AS payoutError,
|
||||
s.note, s.created_at AS createdAt
|
||||
FROM qipai_cleaning_settlements s
|
||||
INNER JOIN qipai_users u ON u.tenant_id = s.tenant_id AND u.id = s.cleaner_user_id
|
||||
LEFT JOIN qipai_stores st ON st.tenant_id = s.tenant_id AND st.id = s.store_id
|
||||
WHERE s.tenant_id = ? AND s.id = ? AND s.deleted_at IS NULL
|
||||
AND ${storeScopeSql(input.access, 'COALESCE(s.store_id, 0)')}`,
|
||||
[input.tenantId, input.settlementId]
|
||||
);
|
||||
if (!rows[0]) throw new CleaningTaskError('CLEANING_SETTLEMENT_NOT_FOUND');
|
||||
return publicSettlement(rows[0]);
|
||||
}
|
||||
|
||||
private async transaction<T>(work: (connection: PoolConnection) => Promise<T>) {
|
||||
const connection = await this.pool.getConnection();
|
||||
try {
|
||||
@@ -1055,6 +1118,24 @@ function publicSettlement(row: SettlementRow) {
|
||||
};
|
||||
}
|
||||
|
||||
function publicSettlementItem(row: SettlementItemRow) {
|
||||
return {
|
||||
id: String(row.id),
|
||||
settlementId: String(row.settlementId),
|
||||
taskId: String(row.taskId),
|
||||
taskNo: row.taskNo,
|
||||
orderNo: row.orderNo,
|
||||
storeName: row.storeName,
|
||||
roomName: row.roomName,
|
||||
roomNo: row.roomNo,
|
||||
cleanerUserId: String(row.cleanerUserId),
|
||||
cleanerName: row.cleanerName,
|
||||
rewardCents: Number(row.rewardCents),
|
||||
completedAt: row.completedAt,
|
||||
createdAt: row.createdAt
|
||||
};
|
||||
}
|
||||
|
||||
function minDate(values: Array<Date | null>) {
|
||||
const dates = values.filter((value): value is Date => value instanceof Date);
|
||||
if (dates.length === 0) return null;
|
||||
|
||||
Reference in New Issue
Block a user