Refactor PlansRepository to optimize data loading and enhance error handling

- Replaced the previous hydration logic with a new method, `loadChildrenForRows`, to efficiently load related data for plans in a single query.
- Implemented a `groupByPlanId` utility to organize related entities by plan ID, improving data retrieval performance.
- Updated error handling in the `getPlan` method to throw a `NotFoundException` if a plan is not found.
- Modified end-to-end tests to validate the new structure of the response, ensuring that related destinations are correctly included in the API output.
This commit is contained in:
shancheas
2026-09-01 19:25:19 +07:00
parent 6b3ddfcff9
commit 9428a983f5
2 changed files with 74 additions and 22 deletions
+56 -17
View File
@@ -79,9 +79,7 @@ export class PlansRepository {
.limit(filters.limit) .limit(filters.limit)
.offset(filters.offset); .offset(filters.offset);
return { return {
data: await this.hydrate( data: await this.loadChildrenForRows(this.db, rows),
rows.map((row) => this.toDomain(row, [], [], [])),
),
total: Number(totalRows[0]?.total ?? 0), total: Number(totalRows[0]?.total ?? 0),
}; };
} }
@@ -340,20 +338,61 @@ export class PlansRepository {
executor: QueryExecutor, executor: QueryExecutor,
row: PlanRow, row: PlanRow,
): Promise<Plan> { ): Promise<Plan> {
const destinations = await executor const [plan] = await this.loadChildrenForRows(executor, [row]);
.select() if (!plan) {
.from(planDestinations) throw new NotFoundException('Plan not found');
.where(eq(planDestinations.planId, row.id)) }
.orderBy(asc(planDestinations.sortOrder)); return plan;
const invoices = await executor }
.select()
.from(planInvoices) private async loadChildrenForRows(
.where(eq(planInvoices.planId, row.id)); executor: QueryExecutor,
const packingSlips = await executor rows: PlanRow[],
.select() ): Promise<Plan[]> {
.from(planPackingSlips) if (rows.length === 0) {
.where(eq(planPackingSlips.planId, row.id)); return [];
return this.hydrateOne(row, destinations, invoices, packingSlips); }
const ids = rows.map((row) => row.id);
const [destinationRows, invoiceRows, packingSlipRows] = await Promise.all([
executor
.select()
.from(planDestinations)
.where(inArray(planDestinations.planId, ids))
.orderBy(asc(planDestinations.sortOrder)),
executor
.select()
.from(planInvoices)
.where(inArray(planInvoices.planId, ids)),
executor
.select()
.from(planPackingSlips)
.where(inArray(planPackingSlips.planId, ids)),
]);
const destinationsByPlan = this.groupByPlanId(destinationRows);
const invoicesByPlan = this.groupByPlanId(invoiceRows);
const packingByPlan = this.groupByPlanId(packingSlipRows);
return this.hydrate(
rows.map((row) =>
this.toDomain(
row,
destinationsByPlan.get(row.id) ?? [],
invoicesByPlan.get(row.id) ?? [],
packingByPlan.get(row.id) ?? [],
),
),
);
}
private groupByPlanId<T extends { planId: string }>(
rows: T[],
): Map<string, T[]> {
const ids = [...new Set(rows.map((row) => row.planId))];
return new Map(
ids.map((planId) => [
planId,
rows.filter((row) => row.planId === planId),
]),
);
} }
private async replaceChildren( private async replaceChildren(
+18 -5
View File
@@ -251,7 +251,17 @@ describe('Plans (e2e)', () => {
.query({ employeeId, purpose: 'sales', date: from }) .query({ employeeId, purpose: 'sales', date: from })
.set('Authorization', `Bearer ${adminAccessToken}`) .set('Authorization', `Bearer ${adminAccessToken}`)
.expect(200); .expect(200);
const planId = (list.body.data as Array<{ id: string }>)[0].id; const listed = (
list.body as {
data: Array<{
id: string;
destinations: Array<{ customer: { id: string } | null }>;
}>;
}
).data;
expect(listed[0].destinations).toHaveLength(1);
expect(listed[0].destinations[0].customer?.id).toBe(customerId);
const planId = listed[0].id;
const added = await request(app.getHttpServer()) const added = await request(app.getHttpServer())
.post(`/plans/${planId}/destinations`) .post(`/plans/${planId}/destinations`)
@@ -259,13 +269,16 @@ describe('Plans (e2e)', () => {
.send({ customerId: extraCustomerId }) .send({ customerId: extraCustomerId })
.expect(201); .expect(201);
expect( expect(
(added.body as { destinations: Array<{ customerId: string }> }) (added.body as { destinations: unknown[] }).destinations,
.destinations,
).toHaveLength(2); ).toHaveLength(2);
const extraDest = ( const extraDest = (
added.body as { destinations: Array<{ id: string; customerId: string }> } added.body as {
).destinations.find((d) => d.customerId === extraCustomerId); destinations: Array<{ id: string; customer: { id: string } | null }>;
}
).destinations.find(
(destination) => destination.customer?.id === extraCustomerId,
);
const removed = await request(app.getHttpServer()) const removed = await request(app.getHttpServer())
.delete(`/plans/${planId}/destinations/${extraDest?.id}`) .delete(`/plans/${planId}/destinations/${extraDest?.id}`)
.set('Authorization', `Bearer ${adminAccessToken}`) .set('Authorization', `Bearer ${adminAccessToken}`)