Add reporting features with new report engine and bookmark management

- Introduced a comprehensive report engine for generating and managing reports, including sales and logistics reports.
- Added new API endpoints for retrieving report configurations, data, and metadata, ensuring secure access with privilege checks.
- Implemented a report bookmarks system to allow users to save and manage report filters and configurations.
- Created database migrations for the `report_bookmarks` table and updated the schema to support new report functionalities.
- Developed services and controllers for handling report queries and bookmarks, including CRUD operations for bookmarks.
- Enhanced API documentation to reflect the new reporting features and endpoints.
- Added unit and integration tests to validate the new functionalities and ensure data integrity across report operations.
This commit is contained in:
shancheas
2026-09-01 08:45:52 +07:00
parent 5579cf6566
commit 2955b974d2
43 changed files with 3257 additions and 1 deletions
@@ -0,0 +1,134 @@
import {
BadRequestException,
Injectable,
NotFoundException,
} from '@nestjs/common';
import { toListPage } from '../../../common/http/response';
import type { PaginationResponse } from '../../../common/http/response';
import {
REPORT_BOOKMARK_TYPE,
type ReportBookmarkType,
} from '../shared/constants/bookmark-type';
import { findReportConfig } from '../shared/configs';
import {
ReportBookmarksRepository,
type ReportBookmark,
} from './report-bookmarks.repository';
export type ListReportBookmarksQuery = {
groupName?: string;
uniqueName?: string;
type?: ReportBookmarkType;
label?: string;
page?: number;
limit?: number;
offset?: number;
};
export type CreateReportBookmarkInput = {
groupName: string;
uniqueName: string;
label: string;
type: ReportBookmarkType;
applied?: boolean;
configuration: unknown;
};
@Injectable()
export class ReportBookmarksService {
constructor(private readonly repository: ReportBookmarksRepository) {}
async list(
ownerId: string,
query: ListReportBookmarksQuery,
): Promise<PaginationResponse<ReturnType<ReportBookmarksService['toDto']>>> {
const page = toListPage(query);
const { data, total } = await this.repository.list(ownerId, {
groupName: query.groupName,
uniqueName: query.uniqueName,
type: query.type,
label: query.label,
limit: page.limit,
offset: page.offset,
});
return { data: data.map((item) => this.toDto(item)), total };
}
async labelHistory(ownerId: string, label?: string): Promise<string[]> {
return this.repository.labelHistory(ownerId, label);
}
async findApplied(
ownerId: string,
groupName?: string,
uniqueName?: string,
type?: ReportBookmarkType,
) {
if (!groupName || !uniqueName || !type) {
throw new BadRequestException(
'groupName, uniqueName, and type are required',
);
}
const bookmark = await this.repository.findApplied(
ownerId,
groupName,
uniqueName,
type,
);
return bookmark ? this.toDto(bookmark) : null;
}
async create(ownerId: string, input: CreateReportBookmarkInput) {
this.assertValidBookmarkInput(input);
const bookmark = await this.repository.create(ownerId, {
groupName: input.groupName,
uniqueName: input.uniqueName,
label: input.label,
type: input.type,
applied: input.applied ?? false,
configuration: input.configuration,
});
return this.toDto(bookmark);
}
async apply(ownerId: string, id: string) {
const bookmark = await this.repository.apply(ownerId, id);
return this.toDto(bookmark);
}
async unapply(ownerId: string, id: string) {
const bookmark = await this.repository.unapply(ownerId, id);
return this.toDto(bookmark);
}
async delete(ownerId: string, id: string): Promise<void> {
await this.repository.delete(ownerId, id);
}
private assertValidBookmarkInput(input: CreateReportBookmarkInput) {
if (!findReportConfig(input.groupName, input.uniqueName)) {
throw new NotFoundException('Report not found');
}
if (
input.type !== REPORT_BOOKMARK_TYPE.FILTER_TABLE &&
input.type !== REPORT_BOOKMARK_TYPE.TABLE_CONFIG
) {
throw new BadRequestException('Invalid bookmark type');
}
}
private toDto(bookmark: ReportBookmark) {
return {
id: bookmark.id,
groupName: bookmark.groupName,
uniqueName: bookmark.uniqueName,
label: bookmark.label,
type: bookmark.type,
applied: bookmark.applied,
configuration: bookmark.configuration,
status: bookmark.status,
createdAt: bookmark.createdAt,
updatedAt: bookmark.updatedAt,
};
}
}