Enhance pagination and ordering capabilities in API responses
- Updated pagination-response and read-write-controllers documentation to include `orderBy` and `orderType` parameters for sorting results. - Introduced new `order-clause` module to handle ordering logic, including validation for order types and columns. - Enhanced `PaginationQueryDto` to support ordering fields in API requests. - Updated various repository and service classes to implement ordering in database queries. - Added unit tests for new ordering functionality and ensured existing tests cover the updated behavior. - Refactored related DTOs to include user and code relations for better data representation in responses.
This commit is contained in:
@@ -4,7 +4,13 @@ import {
|
||||
Injectable,
|
||||
NotFoundException,
|
||||
} from '@nestjs/common';
|
||||
import { and, asc, count, eq, ilike, inArray, or, SQL } from 'drizzle-orm';
|
||||
import { and, count, eq, ilike, inArray, or, SQL } from 'drizzle-orm';
|
||||
import { toOrderClauses } from '../../../common/http/response';
|
||||
import {
|
||||
codeRelationFromMap,
|
||||
loadSalesInvoiceRelationMap,
|
||||
} from '../../../database/load-catalog-refs';
|
||||
import { attachAuditUsers } from '../../../database/load-user-refs';
|
||||
import { DateTime } from '../../../common/value-objects/date-time/date-time';
|
||||
import { Decimal } from '../../../common/value-objects/decimal/decimal';
|
||||
import { Status } from '../../../common/value-objects/status/status';
|
||||
@@ -29,6 +35,15 @@ import type {
|
||||
UpdateSalesPaymentInput,
|
||||
} from './sales-payment';
|
||||
|
||||
const SALES_PAYMENT_ORDER_COLUMNS = {
|
||||
id: salesPayments.id,
|
||||
code: salesPayments.code,
|
||||
date: salesPayments.date,
|
||||
status: salesPayments.status,
|
||||
createdAt: salesPayments.createdAt,
|
||||
updatedAt: salesPayments.updatedAt,
|
||||
};
|
||||
|
||||
type QueryExecutor = Pick<DrizzleDB, 'select' | 'insert' | 'delete'>;
|
||||
|
||||
@Injectable()
|
||||
@@ -50,11 +65,15 @@ export class SalesPaymentsRepository {
|
||||
.select()
|
||||
.from(salesPayments)
|
||||
.where(where)
|
||||
.orderBy(asc(salesPayments.code))
|
||||
.orderBy(
|
||||
...toOrderClauses(SALES_PAYMENT_ORDER_COLUMNS, filters, [
|
||||
{ column: 'code', type: 'ASC' },
|
||||
]),
|
||||
)
|
||||
.limit(filters.limit)
|
||||
.offset(filters.offset);
|
||||
return {
|
||||
data: rows.map((row) => this.toDomain(row, [], [])),
|
||||
data: await this.hydrate(rows.map((row) => this.toDomain(row, [], []))),
|
||||
total: Number(totalRows[0]?.total ?? 0),
|
||||
};
|
||||
}
|
||||
@@ -71,7 +90,7 @@ export class SalesPaymentsRepository {
|
||||
}
|
||||
const invoices = await this.selectAllocations(this.db, id);
|
||||
const images = await this.selectImages(this.db, id);
|
||||
return this.toDomain(row, invoices, images);
|
||||
return this.hydrateOne(row, invoices, images);
|
||||
}
|
||||
|
||||
async create(input: CreateSalesPaymentInput): Promise<SalesPayment> {
|
||||
@@ -96,7 +115,7 @@ export class SalesPaymentsRepository {
|
||||
await this.replaceImages(tx, row.id, input.images ?? []);
|
||||
const invoices = await this.selectAllocations(tx, row.id);
|
||||
const images = await this.selectImages(tx, row.id);
|
||||
return this.toDomain(row, invoices, images);
|
||||
return this.hydrateOne(row, invoices, images);
|
||||
});
|
||||
} catch (error) {
|
||||
this.rethrowConstraintViolation(error);
|
||||
@@ -147,7 +166,7 @@ export class SalesPaymentsRepository {
|
||||
}
|
||||
const invoices = await this.selectAllocations(tx, id);
|
||||
const images = await this.selectImages(tx, id);
|
||||
return this.toDomain(row, invoices, images);
|
||||
return this.hydrateOne(row, invoices, images);
|
||||
});
|
||||
} catch (error) {
|
||||
this.rethrowConstraintViolation(error);
|
||||
@@ -175,7 +194,7 @@ export class SalesPaymentsRepository {
|
||||
}
|
||||
const invoices = await this.selectAllocations(this.db, id);
|
||||
const images = await this.selectImages(this.db, id);
|
||||
return this.toDomain(row, invoices, images);
|
||||
return this.hydrateOne(row, invoices, images);
|
||||
}
|
||||
|
||||
async bulkUpdateStatus(
|
||||
@@ -335,6 +354,7 @@ export class SalesPaymentsRepository {
|
||||
invoices: allocationRows.map((line) => ({
|
||||
id: line.id,
|
||||
invoiceId: line.salesInvoiceId,
|
||||
invoice: null,
|
||||
amount: Decimal.create(line.amount),
|
||||
})),
|
||||
images: imageRows.map((image) => ({
|
||||
@@ -347,9 +367,35 @@ export class SalesPaymentsRepository {
|
||||
updatedAt: DateTime.fromUnixMs(row.updatedAt),
|
||||
createdBy: row.createdBy,
|
||||
updatedBy: row.updatedBy,
|
||||
createdByUser: { id: row.createdBy, username: '' },
|
||||
updatedByUser: { id: row.updatedBy, username: '' },
|
||||
};
|
||||
}
|
||||
|
||||
private async hydrate(items: SalesPayment[]): Promise<SalesPayment[]> {
|
||||
const withAudit = await attachAuditUsers(this.db, items);
|
||||
const invoices = await loadSalesInvoiceRelationMap(
|
||||
this.db,
|
||||
withAudit.flatMap((item) => item.invoices.map((line) => line.invoiceId)),
|
||||
);
|
||||
return withAudit.map((item) => ({
|
||||
...item,
|
||||
invoices: item.invoices.map((line) => ({
|
||||
...line,
|
||||
invoice: codeRelationFromMap(invoices, line.invoiceId),
|
||||
})),
|
||||
}));
|
||||
}
|
||||
|
||||
private async hydrateOne(
|
||||
row: SalesPaymentRow,
|
||||
invoices: SalesPaymentInvoiceRow[],
|
||||
images: SalesPaymentImageRow[],
|
||||
): Promise<SalesPayment> {
|
||||
const [item] = await this.hydrate([this.toDomain(row, invoices, images)]);
|
||||
return item;
|
||||
}
|
||||
|
||||
private rethrowConstraintViolation(error: unknown): never {
|
||||
if (error instanceof NotFoundException) {
|
||||
throw error;
|
||||
|
||||
Reference in New Issue
Block a user