P0-1/P0-2/P1-1: dimensions form + E2E tests + PDF export

P0-1 Backend: dimensions column on template entity + validation
P0-1 Frontend: dimensions edit UI in TemplateManager
P0-2: routeAfterGrading unit tests (10 cases), service spec fix + certificate tests, jest-e2e.json
P1-1: proper PDF generation with embedded CJK font via pdf-lib low-level API
This commit is contained in:
Developer
2026-05-19 08:42:03 +08:00
parent 0b0a060967
commit 68371922ca
18 changed files with 663 additions and 72 deletions
@@ -2,6 +2,7 @@ import {
Injectable,
NotFoundException,
ForbiddenException,
BadRequestException,
} from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
@@ -18,11 +19,33 @@ export class TemplateService {
private readonly tenantService: TenantService,
) {}
private validateRequiredFields(data: {
linkedGroupIds?: string[];
dimensions?: Array<{ name: string; label?: string; weight?: number }>;
}) {
if (data.linkedGroupIds !== undefined) {
if (!Array.isArray(data.linkedGroupIds) || data.linkedGroupIds.length === 0) {
throw new BadRequestException('At least one knowledge group must be linked');
}
}
if (data.dimensions !== undefined) {
if (!Array.isArray(data.dimensions) || data.dimensions.length === 0) {
throw new BadRequestException('At least one dimension must be defined');
}
for (const d of data.dimensions) {
if (!d.name) {
throw new BadRequestException('Each dimension must have a name');
}
}
}
}
async create(
createDto: CreateTemplateDto,
userId: string,
tenantId: string,
): Promise<AssessmentTemplate> {
this.validateRequiredFields(createDto);
const { ...data } = createDto;
const template = this.templateRepository.create({
...data,
@@ -76,6 +99,8 @@ export class TemplateService {
tenantId: string,
): Promise<AssessmentTemplate> {
const template = await this.findOne(id, userId, tenantId);
const merged = { ...template, ...updateDto };
this.validateRequiredFields(merged);
Object.assign(template, updateDto);
return this.templateRepository.save(template);
}