import { ConflictException, Inject, Injectable, NotFoundException, } from "@nestjs/common"; import { CreateFileUploadFieldDto } from "./dto/create-file-upload-field.dto"; import { CreateFileUploadSettingDto } from "./dto/create-file-upload-setting.dto"; import { UpdateFileUploadFieldDto } from "./dto/update-file-upload-field.dto"; import { UpdateFileUploadSettingDto } from "./dto/update-file-upload-setting.dto"; import { FileUploadField } from "./entities/file-upload-field.entity"; import { FileUploadSetting } from "./entities/file-upload-setting.entity"; import { FILE_UPLOAD_SETTINGS_REPOSITORY, IFileUploadSettingsRepository, } from "./interfaces/file-upload-settings.repository.interface"; import { COMPANY_ONBOARDING_CODE_PREFIX, POA_DELEGATION_FILE_KEY, poaDelegationField, } from "./poa-delegation.constants"; @Injectable() export class FileUploadSettingsService { constructor( @Inject(FILE_UPLOAD_SETTINGS_REPOSITORY) private readonly repository: IFileUploadSettingsRepository, ) {} list(): Promise { return this.repository.findAll(); } async getById(id: string): Promise { const setting = await this.repository.findById(id); if (!setting) throw new NotFoundException(`Setting ${id} not found`); return setting; } getByEntity(entity: string): Promise { return this.repository.findByEntity(entity); } async getByCode(code: string): Promise { const setting = await this.repository.findByCode(code); if (!setting) throw new NotFoundException(`Setting "${code}" not found`); return this.withPoaDelegationField(setting); } /** * Company onboarding sets always carry the DARS delegation paper, whether or * not anyone configured a row for it — see poa-delegation.constants.ts. Every * consumer (the portal's PoA step, the onboarding gate) reads the set through * here, so this is the single place the field can be guaranteed. */ private withPoaDelegationField(setting: FileUploadSetting): FileUploadSetting { if (!setting.code.startsWith(COMPANY_ONBOARDING_CODE_PREFIX)) return setting; const fields = setting.fields ?? []; if (fields.some((f) => f.fileKey === POA_DELEGATION_FILE_KEY)) return setting; const lastOrder = fields.reduce((max, f) => Math.max(max, f.displayOrder), 0); setting.fields = [...fields, poaDelegationField(lastOrder + 1)]; return setting; } async create(dto: CreateFileUploadSettingDto): Promise { const existing = await this.repository.findByCode(dto.code); if (existing) { throw new ConflictException( `File upload setting with code "${dto.code}" already exists`, ); } const setting = await this.repository.create({ code: dto.code, label: dto.label, description: dto.description ?? null, entity: dto.entity, }); if (dto.fields && dto.fields.length > 0) { await this.repository.replaceFields(setting.id, dto.fields); } return this.getById(setting.id); } async update( id: string, dto: UpdateFileUploadSettingDto, ): Promise { await this.getById(id); // existence check const updated = await this.repository.update(id, dto); if (!updated) throw new NotFoundException(`Setting ${id} not found`); return updated; } async remove(id: string): Promise { await this.getById(id); await this.repository.softDelete(id); } /* ------------------------ field operations ------------------------ */ async replaceFields( settingId: string, fields: CreateFileUploadFieldDto[], ): Promise { await this.getById(settingId); return this.repository.replaceFields(settingId, fields); } async addField( settingId: string, dto: CreateFileUploadFieldDto, ): Promise { await this.getById(settingId); return this.repository.addField(settingId, dto); } async updateField( fieldId: string, dto: UpdateFileUploadFieldDto, ): Promise { const updated = await this.repository.updateField(fieldId, dto); if (!updated) throw new NotFoundException(`Field ${fieldId} not found`); return updated; } async removeField(fieldId: string): Promise { await this.repository.removeField(fieldId); } }