This commit is contained in:
@@ -14,6 +14,8 @@ import { CreateCirculationDto } from './dto/create-circulation.dto';
|
||||
import { UpdateCirculationRoutingDto } from './dto/update-circulation-routing.dto';
|
||||
import { SearchCirculationDto } from './dto/search-circulation.dto';
|
||||
import { DocumentNumberingService } from '../document-numbering/services/document-numbering.service';
|
||||
import { Project } from '../project/entities/project.entity';
|
||||
import { Correspondence } from '../correspondence/entities/correspondence.entity';
|
||||
|
||||
@Injectable()
|
||||
export class CirculationService {
|
||||
@@ -26,6 +28,58 @@ export class CirculationService {
|
||||
private dataSource: DataSource
|
||||
) {}
|
||||
|
||||
/**
|
||||
* ADR-019: Resolve projectId (INT or UUID string) to internal INT ID
|
||||
*/
|
||||
private async resolveProjectId(projectId: number | string): Promise<number> {
|
||||
if (typeof projectId === 'number') return projectId;
|
||||
const num = Number(projectId);
|
||||
if (!isNaN(num)) return num;
|
||||
const project = await this.dataSource.manager.findOne(Project, {
|
||||
where: { uuid: projectId },
|
||||
select: ['id'],
|
||||
});
|
||||
if (!project)
|
||||
throw new NotFoundException(`Project with UUID ${projectId} not found`);
|
||||
return project.id;
|
||||
}
|
||||
|
||||
/**
|
||||
* ADR-019: Resolve correspondenceId (INT or UUID string) to internal INT ID
|
||||
*/
|
||||
private async resolveCorrespondenceId(
|
||||
corrId: number | string
|
||||
): Promise<number> {
|
||||
if (typeof corrId === 'number') return corrId;
|
||||
const num = Number(corrId);
|
||||
if (!isNaN(num)) return num;
|
||||
const corr = await this.dataSource.manager.findOne(Correspondence, {
|
||||
where: { uuid: corrId },
|
||||
select: ['id'],
|
||||
});
|
||||
if (!corr)
|
||||
throw new NotFoundException(
|
||||
`Correspondence with UUID ${corrId} not found`
|
||||
);
|
||||
return corr.id;
|
||||
}
|
||||
|
||||
/**
|
||||
* ADR-019: Resolve userId (INT or UUID string) to internal user_id
|
||||
*/
|
||||
private async resolveUserId(userId: number | string): Promise<number> {
|
||||
if (typeof userId === 'number') return userId;
|
||||
const num = Number(userId);
|
||||
if (!isNaN(num)) return num;
|
||||
const user = await this.dataSource.manager.findOne(User, {
|
||||
where: { uuid: userId },
|
||||
select: ['user_id'],
|
||||
});
|
||||
if (!user)
|
||||
throw new NotFoundException(`User with UUID ${userId} not found`);
|
||||
return user.user_id;
|
||||
}
|
||||
|
||||
async create(createDto: CreateCirculationDto, user: User) {
|
||||
if (!user.primaryOrganizationId) {
|
||||
throw new BadRequestException('User must belong to an organization');
|
||||
@@ -36,9 +90,20 @@ export class CirculationService {
|
||||
await queryRunner.startTransaction();
|
||||
|
||||
try {
|
||||
// ADR-019: Resolve UUID references to internal INT IDs
|
||||
const resolvedProjectId = createDto.projectId
|
||||
? await this.resolveProjectId(createDto.projectId)
|
||||
: 0;
|
||||
const resolvedCorrId = await this.resolveCorrespondenceId(
|
||||
createDto.correspondenceId
|
||||
);
|
||||
const resolvedAssigneeIds = await Promise.all(
|
||||
createDto.assigneeIds.map((id) => this.resolveUserId(id))
|
||||
);
|
||||
|
||||
// Generate No. using DocumentNumberingService (Type 900 - Circulation)
|
||||
const result = await this.numberingService.generateNextNumber({
|
||||
projectId: createDto.projectId || 0, // Use projectId from DTO or 0
|
||||
projectId: resolvedProjectId,
|
||||
originatorOrganizationId: user.primaryOrganizationId,
|
||||
typeId: 900, // Fixed Type ID for Circulation
|
||||
year: new Date().getFullYear(),
|
||||
@@ -50,7 +115,7 @@ export class CirculationService {
|
||||
|
||||
const circulation = queryRunner.manager.create(Circulation, {
|
||||
organizationId: user.primaryOrganizationId,
|
||||
correspondenceId: createDto.correspondenceId,
|
||||
correspondenceId: resolvedCorrId,
|
||||
circulationNo: result.number,
|
||||
subject: createDto.subject,
|
||||
statusCode: 'OPEN',
|
||||
@@ -58,13 +123,13 @@ export class CirculationService {
|
||||
});
|
||||
const savedCirculation = await queryRunner.manager.save(circulation);
|
||||
|
||||
if (createDto.assigneeIds && createDto.assigneeIds.length > 0) {
|
||||
const routings = createDto.assigneeIds.map((userId, index) =>
|
||||
if (resolvedAssigneeIds.length > 0) {
|
||||
const routings = resolvedAssigneeIds.map((assigneeId, index) =>
|
||||
queryRunner.manager.create(CirculationRouting, {
|
||||
circulationId: savedCirculation.id,
|
||||
stepNumber: index + 1,
|
||||
organizationId: user.primaryOrganizationId,
|
||||
assignedTo: userId,
|
||||
assignedTo: assigneeId,
|
||||
status: 'PENDING',
|
||||
})
|
||||
);
|
||||
|
||||
@@ -1,29 +1,25 @@
|
||||
import {
|
||||
IsInt,
|
||||
IsString,
|
||||
IsNotEmpty,
|
||||
IsArray,
|
||||
IsOptional,
|
||||
ArrayMinSize, // ✅ เพิ่ม
|
||||
ArrayMinSize,
|
||||
} from 'class-validator';
|
||||
|
||||
export class CreateCirculationDto {
|
||||
@IsInt()
|
||||
@IsNotEmpty()
|
||||
correspondenceId!: number; // เอกสารต้นเรื่องที่จะเวียน
|
||||
correspondenceId!: number | string; // เอกสารต้นเรื่องที่จะเวียน (INT or UUID)
|
||||
|
||||
@IsInt()
|
||||
@IsOptional()
|
||||
projectId?: number; // Project ID for Numbering
|
||||
projectId?: number | string; // Project ID or UUID for Numbering
|
||||
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
subject!: string; // หัวข้อเรื่อง (Subject)
|
||||
|
||||
@IsArray()
|
||||
@IsInt({ each: true })
|
||||
@ArrayMinSize(1) // ✅ ต้องมีผู้รับอย่างน้อย 1 คน
|
||||
assigneeIds!: number[]; // รายชื่อ User ID ที่ต้องการส่งให้ (ผู้รับผิดชอบ)
|
||||
assigneeIds!: (number | string)[]; // รายชื่อ User ID or UUID ที่ต้องการส่งให้ (ADR-019)
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
|
||||
@@ -35,6 +35,7 @@ import { WorkflowEngineService } from '../workflow-engine/workflow-engine.servic
|
||||
import { UserService } from '../user/user.service';
|
||||
import { SearchService } from '../search/search.service';
|
||||
import { FileStorageService } from '../../common/file-storage/file-storage.service';
|
||||
import { Project } from '../project/entities/project.entity';
|
||||
|
||||
/**
|
||||
* CorrespondenceService - Document management (CRUD)
|
||||
@@ -69,7 +70,52 @@ export class CorrespondenceService {
|
||||
private fileStorageService: FileStorageService
|
||||
) {}
|
||||
|
||||
/**
|
||||
* ADR-019: Resolve projectId (INT or UUID string) to internal INT ID
|
||||
*/
|
||||
private async resolveProjectId(projectId: number | string): Promise<number> {
|
||||
if (typeof projectId === 'number') return projectId;
|
||||
const num = Number(projectId);
|
||||
if (!isNaN(num)) return num;
|
||||
const project = await this.dataSource.manager.findOne(Project, {
|
||||
where: { uuid: projectId },
|
||||
select: ['id'],
|
||||
});
|
||||
if (!project)
|
||||
throw new NotFoundException(`Project with UUID ${projectId} not found`);
|
||||
return project.id;
|
||||
}
|
||||
|
||||
/**
|
||||
* ADR-019: Resolve organizationId (INT or UUID string) to internal INT ID
|
||||
*/
|
||||
private async resolveOrganizationId(orgId: number | string): Promise<number> {
|
||||
if (typeof orgId === 'number') return orgId;
|
||||
const num = Number(orgId);
|
||||
if (!isNaN(num)) return num;
|
||||
const org = await this.orgRepo.findOne({
|
||||
where: { uuid: orgId },
|
||||
select: ['id'],
|
||||
});
|
||||
if (!org)
|
||||
throw new NotFoundException(`Organization with UUID ${orgId} not found`);
|
||||
return org.id;
|
||||
}
|
||||
|
||||
async create(createDto: CreateCorrespondenceDto, user: User) {
|
||||
// ADR-019: Resolve UUID references to internal INT IDs
|
||||
const resolvedProjectId = await this.resolveProjectId(createDto.projectId);
|
||||
const resolvedOriginatorId = createDto.originatorId
|
||||
? await this.resolveOrganizationId(createDto.originatorId)
|
||||
: undefined;
|
||||
const resolvedRecipients = createDto.recipients
|
||||
? await Promise.all(
|
||||
createDto.recipients.map(async (r) => ({
|
||||
organizationId: await this.resolveOrganizationId(r.organizationId),
|
||||
type: r.type,
|
||||
}))
|
||||
)
|
||||
: undefined;
|
||||
const type = await this.typeRepo.findOne({
|
||||
where: { id: createDto.typeId },
|
||||
});
|
||||
@@ -94,7 +140,7 @@ export class CorrespondenceService {
|
||||
}
|
||||
|
||||
// Impersonation Logic
|
||||
if (createDto.originatorId && createDto.originatorId !== userOrgId) {
|
||||
if (resolvedOriginatorId && resolvedOriginatorId !== userOrgId) {
|
||||
const permissions = await this.userService.getUserPermissions(
|
||||
user.user_id
|
||||
);
|
||||
@@ -103,7 +149,7 @@ export class CorrespondenceService {
|
||||
'You do not have permission to create documents on behalf of other organizations.'
|
||||
);
|
||||
}
|
||||
userOrgId = createDto.originatorId;
|
||||
userOrgId = resolvedOriginatorId;
|
||||
}
|
||||
|
||||
if (!userOrgId) {
|
||||
@@ -134,7 +180,7 @@ export class CorrespondenceService {
|
||||
const orgCode = originatorOrg?.organizationCode ?? 'UNK';
|
||||
|
||||
// [v1.5.1] Extract recipient organization from recipients array (Primary TO)
|
||||
const toRecipient = createDto.recipients?.find((r) => r.type === 'TO');
|
||||
const toRecipient = resolvedRecipients?.find((r) => r.type === 'TO');
|
||||
const recipientOrganizationId = toRecipient?.organizationId;
|
||||
|
||||
let recipientCode = '';
|
||||
@@ -146,7 +192,7 @@ export class CorrespondenceService {
|
||||
}
|
||||
|
||||
const docNumber = await this.numberingService.generateNextNumber({
|
||||
projectId: createDto.projectId,
|
||||
projectId: resolvedProjectId,
|
||||
originatorOrganizationId: userOrgId,
|
||||
typeId: createDto.typeId,
|
||||
disciplineId: createDto.disciplineId,
|
||||
@@ -165,7 +211,7 @@ export class CorrespondenceService {
|
||||
correspondenceNumber: docNumber.number,
|
||||
correspondenceTypeId: createDto.typeId,
|
||||
disciplineId: createDto.disciplineId,
|
||||
projectId: createDto.projectId,
|
||||
projectId: resolvedProjectId,
|
||||
originatorId: userOrgId,
|
||||
isInternal: createDto.isInternal || false,
|
||||
createdBy: user.user_id,
|
||||
@@ -195,9 +241,9 @@ export class CorrespondenceService {
|
||||
});
|
||||
await queryRunner.manager.save(revision);
|
||||
|
||||
// Save Recipients
|
||||
if (createDto.recipients && createDto.recipients.length > 0) {
|
||||
const recipients = createDto.recipients.map((r) =>
|
||||
// Save Recipients (using resolved INT IDs)
|
||||
if (resolvedRecipients && resolvedRecipients.length > 0) {
|
||||
const recipients = resolvedRecipients.map((r) =>
|
||||
queryRunner.manager.create(CorrespondenceRecipient, {
|
||||
correspondenceId: savedCorr.id,
|
||||
recipientOrganizationId: r.organizationId,
|
||||
@@ -459,14 +505,30 @@ export class CorrespondenceService {
|
||||
}
|
||||
}
|
||||
|
||||
// ADR-019: Resolve UUID references in update DTO
|
||||
const updResolvedProjectId = updateDto.projectId
|
||||
? await this.resolveProjectId(updateDto.projectId)
|
||||
: undefined;
|
||||
const updResolvedOriginatorId = updateDto.originatorId
|
||||
? await this.resolveOrganizationId(updateDto.originatorId)
|
||||
: undefined;
|
||||
const updResolvedRecipients = updateDto.recipients
|
||||
? await Promise.all(
|
||||
updateDto.recipients.map(async (r) => ({
|
||||
organizationId: await this.resolveOrganizationId(r.organizationId),
|
||||
type: r.type,
|
||||
}))
|
||||
)
|
||||
: undefined;
|
||||
|
||||
// 3. Update Correspondence Entity if needed
|
||||
const correspondenceUpdate: DeepPartial<Correspondence> = {};
|
||||
if (updateDto.disciplineId)
|
||||
correspondenceUpdate.disciplineId = updateDto.disciplineId;
|
||||
if (updateDto.projectId)
|
||||
correspondenceUpdate.projectId = updateDto.projectId;
|
||||
if (updateDto.originatorId)
|
||||
correspondenceUpdate.originatorId = updateDto.originatorId;
|
||||
if (updResolvedProjectId)
|
||||
correspondenceUpdate.projectId = updResolvedProjectId;
|
||||
if (updResolvedOriginatorId)
|
||||
correspondenceUpdate.originatorId = updResolvedOriginatorId;
|
||||
|
||||
if (Object.keys(correspondenceUpdate).length > 0) {
|
||||
await this.correspondenceRepo.update(id, correspondenceUpdate);
|
||||
@@ -506,13 +568,13 @@ export class CorrespondenceService {
|
||||
}
|
||||
|
||||
// 5. Update Recipients if provided
|
||||
if (updateDto.recipients) {
|
||||
if (updResolvedRecipients) {
|
||||
const recipientRepo = this.dataSource.getRepository(
|
||||
CorrespondenceRecipient
|
||||
);
|
||||
await recipientRepo.delete({ correspondenceId: id });
|
||||
|
||||
const newRecipients = updateDto.recipients.map((r) =>
|
||||
const newRecipients = updResolvedRecipients.map((r) =>
|
||||
recipientRepo.create({
|
||||
correspondenceId: id,
|
||||
recipientOrganizationId: r.organizationId,
|
||||
@@ -539,11 +601,11 @@ export class CorrespondenceService {
|
||||
|
||||
// Check for ACTUAL value changes
|
||||
const isProjectChanged =
|
||||
updateDto.projectId !== undefined &&
|
||||
updateDto.projectId !== currentCorr.projectId;
|
||||
updResolvedProjectId !== undefined &&
|
||||
updResolvedProjectId !== currentCorr.projectId;
|
||||
const isOriginatorChanged =
|
||||
updateDto.originatorId !== undefined &&
|
||||
updateDto.originatorId !== currentCorr.originatorId;
|
||||
updResolvedOriginatorId !== undefined &&
|
||||
updResolvedOriginatorId !== currentCorr.originatorId;
|
||||
const isDisciplineChanged =
|
||||
updateDto.disciplineId !== undefined &&
|
||||
updateDto.disciplineId !== currentCorr.disciplineId;
|
||||
@@ -554,15 +616,9 @@ export class CorrespondenceService {
|
||||
let isRecipientChanged = false;
|
||||
let newRecipientId: number | undefined;
|
||||
|
||||
if (updateDto.recipients) {
|
||||
// Safe check for 'type' or 'recipientType' (mismatch safeguard)
|
||||
interface RecipientInput {
|
||||
type?: string;
|
||||
recipientType?: string;
|
||||
organizationId?: number;
|
||||
}
|
||||
const newToRecipient = updateDto.recipients.find(
|
||||
(r: RecipientInput) => r.type === 'TO' || r.recipientType === 'TO'
|
||||
if (updResolvedRecipients) {
|
||||
const newToRecipient = updResolvedRecipients.find(
|
||||
(r) => r.type === 'TO'
|
||||
);
|
||||
newRecipientId = newToRecipient?.organizationId;
|
||||
|
||||
@@ -594,7 +650,7 @@ export class CorrespondenceService {
|
||||
// [Fix #6] Fetch real ORG Code from originator organization
|
||||
const originatorOrgForUpdate = await this.orgRepo.findOne({
|
||||
where: {
|
||||
id: updateDto.originatorId ?? currentCorr.originatorId ?? 0,
|
||||
id: updResolvedOriginatorId ?? currentCorr.originatorId ?? 0,
|
||||
},
|
||||
});
|
||||
const orgCode = originatorOrgForUpdate?.organizationCode ?? 'UNK';
|
||||
@@ -610,9 +666,9 @@ export class CorrespondenceService {
|
||||
};
|
||||
|
||||
const newCtx = {
|
||||
projectId: updateDto.projectId ?? currentCorr.projectId,
|
||||
projectId: updResolvedProjectId ?? currentCorr.projectId,
|
||||
originatorOrganizationId:
|
||||
updateDto.originatorId ?? currentCorr.originatorId ?? 0,
|
||||
updResolvedOriginatorId ?? currentCorr.originatorId ?? 0,
|
||||
typeId: updateDto.typeId ?? currentCorr.correspondenceTypeId,
|
||||
disciplineId: updateDto.disciplineId ?? currentCorr.disciplineId,
|
||||
recipientOrganizationId: targetRecipientId,
|
||||
@@ -650,6 +706,20 @@ export class CorrespondenceService {
|
||||
}
|
||||
|
||||
async previewDocumentNumber(createDto: CreateCorrespondenceDto, user: User) {
|
||||
// ADR-019: Resolve UUID references
|
||||
const previewProjectId = await this.resolveProjectId(createDto.projectId);
|
||||
const previewOriginatorId = createDto.originatorId
|
||||
? await this.resolveOrganizationId(createDto.originatorId)
|
||||
: undefined;
|
||||
const previewRecipients = createDto.recipients
|
||||
? await Promise.all(
|
||||
createDto.recipients.map(async (r) => ({
|
||||
organizationId: await this.resolveOrganizationId(r.organizationId),
|
||||
type: r.type,
|
||||
}))
|
||||
)
|
||||
: undefined;
|
||||
|
||||
const type = await this.typeRepo.findOne({
|
||||
where: { id: createDto.typeId },
|
||||
});
|
||||
@@ -661,13 +731,13 @@ export class CorrespondenceService {
|
||||
if (fullUser) userOrgId = fullUser.primaryOrganizationId;
|
||||
}
|
||||
|
||||
if (createDto.originatorId && createDto.originatorId !== userOrgId) {
|
||||
if (previewOriginatorId && previewOriginatorId !== userOrgId) {
|
||||
// Allow impersonation for preview
|
||||
userOrgId = createDto.originatorId;
|
||||
userOrgId = previewOriginatorId;
|
||||
}
|
||||
|
||||
// Extract recipient from recipients array
|
||||
const toRecipient = createDto.recipients?.find((r) => r.type === 'TO');
|
||||
const toRecipient = previewRecipients?.find((r) => r.type === 'TO');
|
||||
const recipientOrganizationId = toRecipient?.organizationId;
|
||||
|
||||
let recipientCode = '';
|
||||
@@ -679,7 +749,7 @@ export class CorrespondenceService {
|
||||
}
|
||||
|
||||
return this.numberingService.previewNumber({
|
||||
projectId: createDto.projectId,
|
||||
projectId: previewProjectId,
|
||||
originatorOrganizationId: userOrgId!,
|
||||
typeId: createDto.typeId,
|
||||
disciplineId: createDto.disciplineId,
|
||||
|
||||
@@ -11,10 +11,9 @@ import {
|
||||
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
||||
|
||||
export class CreateCorrespondenceDto {
|
||||
@ApiProperty({ description: 'Project ID', example: 1 })
|
||||
@IsInt()
|
||||
@ApiProperty({ description: 'Project ID or UUID', example: 1 })
|
||||
@IsNotEmpty()
|
||||
projectId!: number;
|
||||
projectId!: number | string;
|
||||
|
||||
@ApiProperty({ description: 'Document Type ID', example: 1 })
|
||||
@IsInt()
|
||||
@@ -110,12 +109,11 @@ export class CreateCorrespondenceDto {
|
||||
|
||||
// ✅ เพิ่ม Field สำหรับ Impersonation (เลือกองค์กรผู้ส่ง)
|
||||
@ApiPropertyOptional({
|
||||
description: 'Originator Organization ID (for impersonation)',
|
||||
description: 'Originator Organization ID or UUID (for impersonation)',
|
||||
example: 1,
|
||||
})
|
||||
@IsInt()
|
||||
@IsOptional()
|
||||
originatorId?: number;
|
||||
originatorId?: number | string;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
description: 'Recipients',
|
||||
@@ -123,5 +121,5 @@ export class CreateCorrespondenceDto {
|
||||
})
|
||||
@IsArray()
|
||||
@IsOptional()
|
||||
recipients?: { organizationId: number; type: 'TO' | 'CC' }[];
|
||||
recipients?: { organizationId: number | string; type: 'TO' | 'CC' }[];
|
||||
}
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
import { Injectable, Logger, BadRequestException } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import {
|
||||
Injectable,
|
||||
Logger,
|
||||
BadRequestException,
|
||||
NotFoundException,
|
||||
} from '@nestjs/common';
|
||||
import { InjectRepository, InjectEntityManager } from '@nestjs/typeorm';
|
||||
import { Repository, EntityManager } from 'typeorm';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
|
||||
import { DocumentNumberFormat } from '../entities/document-number-format.entity';
|
||||
@@ -20,6 +25,7 @@ import { CounterKeyDto } from '../dto/counter-key.dto';
|
||||
import { GenerateNumberContext } from '../interfaces/document-numbering.interface';
|
||||
import { ReserveNumberDto } from '../dto/reserve-number.dto';
|
||||
import { ConfirmReservationDto } from '../dto/confirm-reservation.dto';
|
||||
import { Project } from '../../project/entities/project.entity';
|
||||
|
||||
@Injectable()
|
||||
export class DocumentNumberingService {
|
||||
@@ -39,9 +45,27 @@ export class DocumentNumberingService {
|
||||
private lockService: DocumentNumberingLockService,
|
||||
private configService: ConfigService,
|
||||
private manualOverrideService: ManualOverrideService,
|
||||
private metricsService: MetricsService
|
||||
private metricsService: MetricsService,
|
||||
@InjectEntityManager()
|
||||
private entityManager: EntityManager
|
||||
) {}
|
||||
|
||||
/**
|
||||
* ADR-019: Resolve projectId (INT or UUID string) to internal INT ID
|
||||
*/
|
||||
private async resolveProjectId(projectId: number | string): Promise<number> {
|
||||
if (typeof projectId === 'number') return projectId;
|
||||
const num = Number(projectId);
|
||||
if (!isNaN(num)) return num;
|
||||
const project = await this.entityManager.findOne(Project, {
|
||||
where: { uuid: projectId },
|
||||
select: ['id'],
|
||||
});
|
||||
if (!project)
|
||||
throw new NotFoundException(`Project with UUID ${projectId} not found`);
|
||||
return project.id;
|
||||
}
|
||||
|
||||
async generateNextNumber(
|
||||
ctx: GenerateNumberContext
|
||||
): Promise<{ number: string; auditId: number }> {
|
||||
@@ -218,11 +242,15 @@ export class DocumentNumberingService {
|
||||
return this.formatRepo.find();
|
||||
}
|
||||
|
||||
async getTemplatesByProject(projectId: number) {
|
||||
return this.formatRepo.find({ where: { projectId } });
|
||||
async getTemplatesByProject(projectId: number | string) {
|
||||
const internalId = await this.resolveProjectId(projectId);
|
||||
return this.formatRepo.find({ where: { projectId: internalId } });
|
||||
}
|
||||
|
||||
async saveTemplate(dto: any) {
|
||||
if (dto.projectId) {
|
||||
dto.projectId = await this.resolveProjectId(dto.projectId);
|
||||
}
|
||||
return this.formatRepo.save(dto);
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,10 @@ import {
|
||||
UpdateDateColumn,
|
||||
DeleteDateColumn,
|
||||
Unique,
|
||||
ManyToOne,
|
||||
JoinColumn,
|
||||
} from 'typeorm';
|
||||
import { Project } from '../../project/entities/project.entity';
|
||||
|
||||
@Entity('tags')
|
||||
@Unique('ux_tag_project', ['project_id', 'tag_name'])
|
||||
@@ -26,6 +29,11 @@ export class Tag {
|
||||
@Column({ type: 'text', nullable: true })
|
||||
description!: string | null; // เพิ่ม !
|
||||
|
||||
// Relations
|
||||
@ManyToOne(() => Project)
|
||||
@JoinColumn({ name: 'project_id' })
|
||||
project?: Project;
|
||||
|
||||
@CreateDateColumn()
|
||||
created_at!: Date; // เพิ่ม !
|
||||
|
||||
|
||||
@@ -132,6 +132,7 @@ export class MasterService {
|
||||
}
|
||||
return this.rfaTypeRepo.find({
|
||||
where,
|
||||
relations: ['contract'],
|
||||
order: { typeCode: 'ASC' },
|
||||
});
|
||||
}
|
||||
@@ -296,7 +297,9 @@ export class MasterService {
|
||||
}
|
||||
|
||||
async findAllTags(query?: SearchTagDto) {
|
||||
const qb = this.tagRepo.createQueryBuilder('tag');
|
||||
const qb = this.tagRepo
|
||||
.createQueryBuilder('tag')
|
||||
.leftJoinAndSelect('tag.project', 'project');
|
||||
|
||||
if (query?.project_id) {
|
||||
// In Tags, we use project_id (INT) directly or resolve if UUID passed via query
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
import { Entity, PrimaryGeneratedColumn, Column, AfterLoad } from 'typeorm';
|
||||
import {
|
||||
Entity,
|
||||
PrimaryGeneratedColumn,
|
||||
Column,
|
||||
AfterLoad,
|
||||
ManyToOne,
|
||||
JoinColumn,
|
||||
} from 'typeorm';
|
||||
import { Contract } from '../../contract/entities/contract.entity';
|
||||
|
||||
@Entity('rfa_types')
|
||||
export class RfaType {
|
||||
@@ -23,6 +31,11 @@ export class RfaType {
|
||||
@Column({ name: 'is_active', default: true })
|
||||
isActive!: boolean;
|
||||
|
||||
// Relations
|
||||
@ManyToOne(() => Contract)
|
||||
@JoinColumn({ name: 'contract_id' })
|
||||
contract?: Contract;
|
||||
|
||||
// Virtual property for backward compatibility
|
||||
typeName!: string;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user