Files
edr-platform/apps/edr-freight-api/src/modules/file-upload-settings/file-upload-settings.service.ts
Nathnael 68f6ec8c5f feat(freight-api): gate poa paper, fayda identity, foreign passport
- dars delegation paper mandatory wherever poa state changes (named,
  removed, forwarder role applied for/approved), not just onboarding
- ethiopian companies verify owner (and poa, once named) via fayda;
  identity, not general manager, is the verified subject
- foreign companies require a typed owner passport number instead,
  independent of an optional fayda verification
- fanNumber removed from client-writable dtos; server-derived only

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-29 06:57:27 +00:00

135 lines
4.3 KiB
TypeScript

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<FileUploadSetting[]> {
return this.repository.findAll();
}
async getById(id: string): Promise<FileUploadSetting> {
const setting = await this.repository.findById(id);
if (!setting) throw new NotFoundException(`Setting ${id} not found`);
return setting;
}
getByEntity(entity: string): Promise<FileUploadSetting[]> {
return this.repository.findByEntity(entity);
}
async getByCode(code: string): Promise<FileUploadSetting> {
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<FileUploadSetting> {
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<FileUploadSetting> {
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<void> {
await this.getById(id);
await this.repository.softDelete(id);
}
/* ------------------------ field operations ------------------------ */
async replaceFields(
settingId: string,
fields: CreateFileUploadFieldDto[],
): Promise<FileUploadField[]> {
await this.getById(settingId);
return this.repository.replaceFields(settingId, fields);
}
async addField(
settingId: string,
dto: CreateFileUploadFieldDto,
): Promise<FileUploadField> {
await this.getById(settingId);
return this.repository.addField(settingId, dto);
}
async updateField(
fieldId: string,
dto: UpdateFileUploadFieldDto,
): Promise<FileUploadField> {
const updated = await this.repository.updateField(fieldId, dto);
if (!updated) throw new NotFoundException(`Field ${fieldId} not found`);
return updated;
}
async removeField(fieldId: string): Promise<void> {
await this.repository.removeField(fieldId);
}
}