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:
@@ -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]);
|
||||||
|
if (!plan) {
|
||||||
|
throw new NotFoundException('Plan not found');
|
||||||
|
}
|
||||||
|
return plan;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async loadChildrenForRows(
|
||||||
|
executor: QueryExecutor,
|
||||||
|
rows: PlanRow[],
|
||||||
|
): Promise<Plan[]> {
|
||||||
|
if (rows.length === 0) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
const ids = rows.map((row) => row.id);
|
||||||
|
const [destinationRows, invoiceRows, packingSlipRows] = await Promise.all([
|
||||||
|
executor
|
||||||
.select()
|
.select()
|
||||||
.from(planDestinations)
|
.from(planDestinations)
|
||||||
.where(eq(planDestinations.planId, row.id))
|
.where(inArray(planDestinations.planId, ids))
|
||||||
.orderBy(asc(planDestinations.sortOrder));
|
.orderBy(asc(planDestinations.sortOrder)),
|
||||||
const invoices = await executor
|
executor
|
||||||
.select()
|
.select()
|
||||||
.from(planInvoices)
|
.from(planInvoices)
|
||||||
.where(eq(planInvoices.planId, row.id));
|
.where(inArray(planInvoices.planId, ids)),
|
||||||
const packingSlips = await executor
|
executor
|
||||||
.select()
|
.select()
|
||||||
.from(planPackingSlips)
|
.from(planPackingSlips)
|
||||||
.where(eq(planPackingSlips.planId, row.id));
|
.where(inArray(planPackingSlips.planId, ids)),
|
||||||
return this.hydrateOne(row, destinations, invoices, packingSlips);
|
]);
|
||||||
|
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
@@ -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}`)
|
||||||
|
|||||||
Reference in New Issue
Block a user