mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-08 20:38:17 +00:00
feat(core): Introduce company and external profile management with dedicated API, services, and data schema
This commit is contained in:
@@ -14,6 +14,7 @@ import { FilesModule } from "./modules/files/files.module";
|
|||||||
import { ConsignmentsModule } from "./modules/consignments/consignments.module";
|
import { ConsignmentsModule } from "./modules/consignments/consignments.module";
|
||||||
import { TrainsModule } from "./modules/trains/trains.module";
|
import { TrainsModule } from "./modules/trains/trains.module";
|
||||||
import { CustomersModule } from "./modules/customers/customers.module";
|
import { CustomersModule } from "./modules/customers/customers.module";
|
||||||
|
import { CompaniesModule } from "./modules/companies/companies.module";
|
||||||
import { TrackingModule } from "./modules/tracking/tracking.module";
|
import { TrackingModule } from "./modules/tracking/tracking.module";
|
||||||
import { BillingModule } from "./modules/billing/billing.module";
|
import { BillingModule } from "./modules/billing/billing.module";
|
||||||
import { NotificationsModule } from "./modules/notifications/notifications.module";
|
import { NotificationsModule } from "./modules/notifications/notifications.module";
|
||||||
@@ -59,6 +60,7 @@ import { DemoUsersSeeder } from "./seed/demo-users.seeder";
|
|||||||
ConsignmentsModule,
|
ConsignmentsModule,
|
||||||
TrainsModule,
|
TrainsModule,
|
||||||
CustomersModule,
|
CustomersModule,
|
||||||
|
CompaniesModule,
|
||||||
TrackingModule,
|
TrackingModule,
|
||||||
BillingModule,
|
BillingModule,
|
||||||
NotificationsModule,
|
NotificationsModule,
|
||||||
|
|||||||
@@ -0,0 +1,115 @@
|
|||||||
|
import { MigrationInterface, QueryRunner, Table, TableIndex, TableUnique } from 'typeorm';
|
||||||
|
|
||||||
|
export class CreateCompaniesModule1749200000000 implements MigrationInterface {
|
||||||
|
name = 'CreateCompaniesModule1749200000000';
|
||||||
|
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.createTable(
|
||||||
|
new Table({
|
||||||
|
schema: 'freight',
|
||||||
|
name: 'companies',
|
||||||
|
columns: [
|
||||||
|
{ name: 'id', type: 'uuid', isPrimary: true, generationStrategy: 'uuid', default: 'gen_random_uuid()' },
|
||||||
|
{ name: 'name', type: 'varchar', length: '200' },
|
||||||
|
{ name: 'type', type: 'varchar', length: '32' },
|
||||||
|
{ name: 'status', type: 'varchar', length: '32', default: "'pending'" },
|
||||||
|
{ name: 'tin', type: 'varchar', length: '10', isUnique: true },
|
||||||
|
{ name: 'vat_number', type: 'varchar', length: '50', isNullable: true },
|
||||||
|
{ name: 'business_license', type: 'varchar', length: '100', isNullable: true },
|
||||||
|
{ name: 'fan_number', type: 'varchar', length: '16', isNullable: true },
|
||||||
|
{ name: 'country', type: 'varchar', length: '32', default: "'Ethiopia'" },
|
||||||
|
{ name: 'address', type: 'text', isNullable: true },
|
||||||
|
{ name: 'phone', type: 'varchar', length: '20', isNullable: true },
|
||||||
|
{ name: 'email', type: 'varchar', length: '150', isNullable: true },
|
||||||
|
{ name: 'website', type: 'varchar', length: '200', isNullable: true },
|
||||||
|
{ name: 'attributes', type: 'jsonb', isNullable: true },
|
||||||
|
{ name: 'created_at', type: 'timestamptz', default: 'now()' },
|
||||||
|
{ name: 'updated_at', type: 'timestamptz', default: 'now()' },
|
||||||
|
{ name: 'deleted_at', type: 'timestamptz', isNullable: true },
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
|
||||||
|
await queryRunner.createTable(
|
||||||
|
new Table({
|
||||||
|
schema: 'freight',
|
||||||
|
name: 'external_profiles',
|
||||||
|
columns: [
|
||||||
|
{ name: 'id', type: 'uuid', isPrimary: true, generationStrategy: 'uuid', default: 'gen_random_uuid()' },
|
||||||
|
{ name: 'user_id', type: 'uuid' },
|
||||||
|
{ name: 'company_id', type: 'uuid' },
|
||||||
|
{ name: 'first_name', type: 'varchar', length: '100' },
|
||||||
|
{ name: 'last_name', type: 'varchar', length: '100' },
|
||||||
|
{ name: 'email', type: 'varchar', length: '150', isUnique: true },
|
||||||
|
{ name: 'phone', type: 'varchar', length: '20', isNullable: true },
|
||||||
|
{ name: 'national_id', type: 'varchar', length: '50', isNullable: true },
|
||||||
|
{ name: 'job_title', type: 'varchar', length: '100', isNullable: true },
|
||||||
|
{ name: 'is_primary_contact', type: 'boolean', default: false },
|
||||||
|
{ name: 'created_at', type: 'timestamptz', default: 'now()' },
|
||||||
|
{ name: 'updated_at', type: 'timestamptz', default: 'now()' },
|
||||||
|
{ name: 'deleted_at', type: 'timestamptz', isNullable: true },
|
||||||
|
],
|
||||||
|
foreignKeys: [
|
||||||
|
{
|
||||||
|
columnNames: ['company_id'],
|
||||||
|
referencedTableName: 'companies',
|
||||||
|
referencedSchema: 'freight',
|
||||||
|
referencedColumnNames: ['id'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
|
||||||
|
await queryRunner.createTable(
|
||||||
|
new Table({
|
||||||
|
schema: 'freight',
|
||||||
|
name: 'ff_clients',
|
||||||
|
columns: [
|
||||||
|
{ name: 'id', type: 'uuid', isPrimary: true, generationStrategy: 'uuid', default: 'gen_random_uuid()' },
|
||||||
|
{ name: 'forwarder_company_id', type: 'uuid' },
|
||||||
|
{ name: 'client_company_id', type: 'uuid' },
|
||||||
|
{ name: 'relationship_type', type: 'varchar', length: '32', default: "'managed_account'" },
|
||||||
|
{ name: 'can_book_on_behalf', type: 'boolean', default: true },
|
||||||
|
{ name: 'can_view_documents', type: 'boolean', default: true },
|
||||||
|
{ name: 'created_at', type: 'timestamptz', default: 'now()' },
|
||||||
|
{ name: 'updated_at', type: 'timestamptz', default: 'now()' },
|
||||||
|
{ name: 'deleted_at', type: 'timestamptz', isNullable: true },
|
||||||
|
],
|
||||||
|
foreignKeys: [
|
||||||
|
{
|
||||||
|
columnNames: ['forwarder_company_id'],
|
||||||
|
referencedTableName: 'companies',
|
||||||
|
referencedSchema: 'freight',
|
||||||
|
referencedColumnNames: ['id'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
columnNames: ['client_company_id'],
|
||||||
|
referencedTableName: 'companies',
|
||||||
|
referencedSchema: 'freight',
|
||||||
|
referencedColumnNames: ['id'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
|
||||||
|
await queryRunner.createIndex('freight.companies', new TableIndex({ columnNames: ['tin'] }));
|
||||||
|
await queryRunner.createIndex('freight.companies', new TableIndex({ columnNames: ['type'] }));
|
||||||
|
await queryRunner.createIndex('freight.external_profiles', new TableIndex({ columnNames: ['user_id'] }));
|
||||||
|
await queryRunner.createIndex('freight.external_profiles', new TableIndex({ columnNames: ['company_id'] }));
|
||||||
|
await queryRunner.createIndex('freight.ff_clients', new TableIndex({ columnNames: ['forwarder_company_id'] }));
|
||||||
|
await queryRunner.createIndex('freight.ff_clients', new TableIndex({ columnNames: ['client_company_id'] }));
|
||||||
|
|
||||||
|
await queryRunner.createUniqueConstraint('freight.ff_clients', new TableUnique({
|
||||||
|
columnNames: ['forwarder_company_id', 'client_company_id'],
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.dropTable('freight.ff_clients');
|
||||||
|
await queryRunner.dropTable('freight.external_profiles');
|
||||||
|
await queryRunner.dropTable('freight.companies');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,158 @@
|
|||||||
|
import { Controller, Get, Post, Patch, Delete, Body, Param, Query, ParseUUIDPipe, HttpCode, HttpStatus } from '@nestjs/common';
|
||||||
|
import { ApiOperation, ApiTags } from '@nestjs/swagger';
|
||||||
|
import { CurrentUser } from '@edr/api-common';
|
||||||
|
import { CompaniesService } from './companies.service';
|
||||||
|
import { CreateCompanyDto } from './dto/create-company.dto';
|
||||||
|
import { UpdateCompanyDto } from './dto/update-company.dto';
|
||||||
|
import { CreateExternalProfileDto } from './dto/create-external-profile.dto';
|
||||||
|
import { CreateFFClientDto } from './dto/create-ff-client.dto';
|
||||||
|
import { CreateCompanyWithProfileDto } from './dto/create-company-with-profile.dto';
|
||||||
|
import { ResponseCompanyDto } from './dto/response-company.dto';
|
||||||
|
import { ResponseExternalProfileDto } from './dto/response-external-profile.dto';
|
||||||
|
import { ResponseFFClientDto } from './dto/response-ff-client.dto';
|
||||||
|
import { CompanyInfoResponseDto } from './dto/company-info-response.dto';
|
||||||
|
|
||||||
|
interface CurrentIamUser {
|
||||||
|
id: string;
|
||||||
|
name?: { en: string; am: string };
|
||||||
|
email?: string;
|
||||||
|
phoneNumber?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiTags('Companies')
|
||||||
|
@Controller('companies')
|
||||||
|
export class CompaniesController {
|
||||||
|
constructor(private readonly companiesService: CompaniesService) {}
|
||||||
|
|
||||||
|
@Get('getInfo')
|
||||||
|
@ApiOperation({ summary: 'Get company info for the current user' })
|
||||||
|
async getInfo(@CurrentUser() user: CurrentIamUser): Promise<CompanyInfoResponseDto> {
|
||||||
|
const { profile, company } = await this.companiesService.getCompanyInfoByUserId(user.id);
|
||||||
|
return new CompanyInfoResponseDto(profile, company);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('create')
|
||||||
|
@ApiOperation({ summary: 'Create a company with its associated external profile (onboarding)' })
|
||||||
|
async createWithProfile(
|
||||||
|
@CurrentUser() user: CurrentIamUser,
|
||||||
|
@Body() dto: CreateCompanyWithProfileDto,
|
||||||
|
): Promise<CompanyInfoResponseDto> {
|
||||||
|
const nameParts = (user.name?.en ?? '').split(' ');
|
||||||
|
const { profile, company } = await this.companiesService.createCompanyWithProfile(
|
||||||
|
{
|
||||||
|
userId: user.id,
|
||||||
|
firstName: nameParts[0] || '',
|
||||||
|
lastName: nameParts.slice(-1)[0] || '',
|
||||||
|
email: user.email ?? '',
|
||||||
|
phone: user.phoneNumber ?? '',
|
||||||
|
},
|
||||||
|
dto,
|
||||||
|
);
|
||||||
|
return new CompanyInfoResponseDto(profile, company);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post()
|
||||||
|
@ApiOperation({ summary: 'Create a new company (customer, forwarder, transporter, broker)' })
|
||||||
|
async create(@Body() dto: CreateCompanyDto): Promise<ResponseCompanyDto> {
|
||||||
|
const company = await this.companiesService.createCompany(dto);
|
||||||
|
return new ResponseCompanyDto(company);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get()
|
||||||
|
@ApiOperation({ summary: 'List all companies' })
|
||||||
|
async findAll(): Promise<ResponseCompanyDto[]> {
|
||||||
|
const companies = await this.companiesService.findAllCompanies();
|
||||||
|
return companies.map((c) => new ResponseCompanyDto(c));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('type/:type')
|
||||||
|
@ApiOperation({ summary: 'Find companies by type' })
|
||||||
|
async findByType(@Param('type') type: string): Promise<ResponseCompanyDto[]> {
|
||||||
|
const companies = await this.companiesService.findAllCompanies();
|
||||||
|
return companies.filter((c) => c.type === type).map((c) => new ResponseCompanyDto(c));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('search')
|
||||||
|
@ApiOperation({ summary: 'Search companies by name' })
|
||||||
|
async search(@Query('name') name: string): Promise<ResponseCompanyDto[]> {
|
||||||
|
const companies = await this.companiesService.findAllCompanies();
|
||||||
|
return companies
|
||||||
|
.filter((c) => c.name.toLowerCase().includes(name.toLowerCase()))
|
||||||
|
.map((c) => new ResponseCompanyDto(c));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get(':id')
|
||||||
|
@ApiOperation({ summary: 'Get company by ID' })
|
||||||
|
async findById(@Param('id', ParseUUIDPipe) id: string): Promise<ResponseCompanyDto> {
|
||||||
|
const company = await this.companiesService.findCompanyById(id);
|
||||||
|
return new ResponseCompanyDto(company);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Patch(':id')
|
||||||
|
@ApiOperation({ summary: 'Update a company' })
|
||||||
|
async update(
|
||||||
|
@Param('id', ParseUUIDPipe) id: string,
|
||||||
|
@Body() dto: UpdateCompanyDto,
|
||||||
|
): Promise<ResponseCompanyDto> {
|
||||||
|
const company = await this.companiesService.updateCompany(id, dto);
|
||||||
|
return new ResponseCompanyDto(company);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Delete(':id')
|
||||||
|
@ApiOperation({ summary: 'Soft-delete a company' })
|
||||||
|
@HttpCode(HttpStatus.NO_CONTENT)
|
||||||
|
async remove(@Param('id', ParseUUIDPipe) id: string): Promise<void> {
|
||||||
|
await this.companiesService.deleteCompany(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post(':companyId/profiles')
|
||||||
|
@ApiOperation({ summary: 'Add a profile (employee) to a company' })
|
||||||
|
async createProfile(
|
||||||
|
@Param('companyId', ParseUUIDPipe) companyId: string,
|
||||||
|
@Body() dto: CreateExternalProfileDto,
|
||||||
|
): Promise<ResponseExternalProfileDto> {
|
||||||
|
const profile = await this.companiesService.createProfile({ ...dto, companyId });
|
||||||
|
return new ResponseExternalProfileDto(profile);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get(':companyId/profiles')
|
||||||
|
@ApiOperation({ summary: 'List profiles for a company' })
|
||||||
|
async listProfiles(
|
||||||
|
@Param('companyId', ParseUUIDPipe) companyId: string,
|
||||||
|
): Promise<ResponseExternalProfileDto[]> {
|
||||||
|
const profiles = await this.companiesService.findProfilesByCompany(companyId);
|
||||||
|
return profiles.map((p) => new ResponseExternalProfileDto(p));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('profile/user/:userId')
|
||||||
|
@ApiOperation({ summary: 'Get profile by IAM user ID' })
|
||||||
|
async findProfileByUser(
|
||||||
|
@Param('userId', ParseUUIDPipe) userId: string,
|
||||||
|
): Promise<ResponseExternalProfileDto> {
|
||||||
|
const profile = await this.companiesService.findProfileByUserId(userId);
|
||||||
|
return new ResponseExternalProfileDto(profile);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('ff-clients')
|
||||||
|
@ApiOperation({ summary: 'Link a forwarder to a client company' })
|
||||||
|
async createFFClient(@Body() dto: CreateFFClientDto): Promise<ResponseFFClientDto> {
|
||||||
|
const client = await this.companiesService.createFFClient(dto);
|
||||||
|
return new ResponseFFClientDto(client);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get(':forwarderCompanyId/clients')
|
||||||
|
@ApiOperation({ summary: 'List clients of a forwarder' })
|
||||||
|
async listFFClients(
|
||||||
|
@Param('forwarderCompanyId', ParseUUIDPipe) forwarderCompanyId: string,
|
||||||
|
): Promise<ResponseFFClientDto[]> {
|
||||||
|
const clients = await this.companiesService.findForwarderClients(forwarderCompanyId);
|
||||||
|
return clients.map((c) => new ResponseFFClientDto(c));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Delete('ff-clients/:id')
|
||||||
|
@ApiOperation({ summary: 'Remove a forwarder-client relationship' })
|
||||||
|
@HttpCode(HttpStatus.NO_CONTENT)
|
||||||
|
async removeFFClient(@Param('id', ParseUUIDPipe) id: string): Promise<void> {
|
||||||
|
await this.companiesService.deleteFFClient(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
|
import { CompaniesController } from './companies.controller';
|
||||||
|
import { CompaniesService } from './companies.service';
|
||||||
|
import { CompaniesRepository } from './companies.repository';
|
||||||
|
import { ExternalProfileRepository } from './external-profile.repository';
|
||||||
|
import { FFClientRepository } from './ff-client.repository';
|
||||||
|
import { Company } from './entities/company.entity';
|
||||||
|
import { ExternalProfile } from './entities/external-profile.entity';
|
||||||
|
import { FFClient } from './entities/ff-client.entity';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [TypeOrmModule.forFeature([Company, ExternalProfile, FFClient])],
|
||||||
|
controllers: [CompaniesController],
|
||||||
|
providers: [CompaniesService, CompaniesRepository, ExternalProfileRepository, FFClientRepository],
|
||||||
|
exports: [CompaniesService],
|
||||||
|
})
|
||||||
|
export class CompaniesModule {}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
|
import { Repository } from 'typeorm';
|
||||||
|
import { BaseRepository } from '@edr/api-common';
|
||||||
|
import { Company } from './entities/company.entity';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class CompaniesRepository extends BaseRepository<Company> {
|
||||||
|
constructor(
|
||||||
|
@InjectRepository(Company)
|
||||||
|
repo: Repository<Company>,
|
||||||
|
) {
|
||||||
|
super(repo);
|
||||||
|
}
|
||||||
|
|
||||||
|
async findByTin(tin: string): Promise<Company | null> {
|
||||||
|
return this.repository.findOne({ where: { tin } as any });
|
||||||
|
}
|
||||||
|
|
||||||
|
async findByType(type: string): Promise<Company[]> {
|
||||||
|
return this.repository.find({ where: { type } as any, order: { name: 'ASC' } });
|
||||||
|
}
|
||||||
|
|
||||||
|
async findByName(name: string): Promise<Company[]> {
|
||||||
|
return this.repository
|
||||||
|
.createQueryBuilder('company')
|
||||||
|
.where('company.name ILIKE :name', { name: `%${name}%` })
|
||||||
|
.getMany();
|
||||||
|
}
|
||||||
|
|
||||||
|
async existsByTin(tin: string): Promise<boolean> {
|
||||||
|
const count = await this.repository.count({ where: { tin } as any });
|
||||||
|
return count > 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
156
apps/edr-freight-api/src/modules/companies/companies.service.ts
Normal file
156
apps/edr-freight-api/src/modules/companies/companies.service.ts
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
import { Injectable, NotFoundException, ConflictException } from '@nestjs/common';
|
||||||
|
import { CompaniesRepository } from './companies.repository';
|
||||||
|
import { ExternalProfileRepository } from './external-profile.repository';
|
||||||
|
import { FFClientRepository } from './ff-client.repository';
|
||||||
|
import { CreateCompanyDto } from './dto/create-company.dto';
|
||||||
|
import { UpdateCompanyDto } from './dto/update-company.dto';
|
||||||
|
import { CreateExternalProfileDto } from './dto/create-external-profile.dto';
|
||||||
|
import { CreateFFClientDto } from './dto/create-ff-client.dto';
|
||||||
|
import { CreateCompanyWithProfileDto } from './dto/create-company-with-profile.dto';
|
||||||
|
import { Company } from './entities/company.entity';
|
||||||
|
import { ExternalProfile } from './entities/external-profile.entity';
|
||||||
|
import { FFClient } from './entities/ff-client.entity';
|
||||||
|
|
||||||
|
export interface UserIdentity {
|
||||||
|
userId: string;
|
||||||
|
firstName: string;
|
||||||
|
lastName: string;
|
||||||
|
email: string;
|
||||||
|
phone: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class CompaniesService {
|
||||||
|
constructor(
|
||||||
|
private readonly companiesRepo: CompaniesRepository,
|
||||||
|
private readonly profilesRepo: ExternalProfileRepository,
|
||||||
|
private readonly ffClientsRepo: FFClientRepository,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async createCompany(dto: CreateCompanyDto): Promise<Company> {
|
||||||
|
const exists = await this.companiesRepo.existsByTin(dto.tin);
|
||||||
|
if (exists) {
|
||||||
|
throw new ConflictException(`Company with TIN ${dto.tin} already exists`);
|
||||||
|
}
|
||||||
|
return this.companiesRepo.create(dto);
|
||||||
|
}
|
||||||
|
|
||||||
|
async createCompanyWithProfile(identity: UserIdentity, dto: CreateCompanyWithProfileDto): Promise<{ company: Company; profile: ExternalProfile }> {
|
||||||
|
if (dto.tin) {
|
||||||
|
const exists = await this.companiesRepo.existsByTin(dto.tin);
|
||||||
|
if (exists) {
|
||||||
|
throw new ConflictException(`Company with TIN ${dto.tin} already exists`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const existingProfile = await this.profilesRepo.findByEmail(identity.email);
|
||||||
|
if (existingProfile) {
|
||||||
|
throw new ConflictException(`Profile with email ${identity.email} already exists`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const company = await this.companiesRepo.create({
|
||||||
|
name: dto.companyName,
|
||||||
|
type: dto.companyType,
|
||||||
|
tin: dto.tin ?? '',
|
||||||
|
vatNumber: dto.vatNumber ?? null,
|
||||||
|
businessLicense: dto.fanNumber ?? null,
|
||||||
|
fanNumber: dto.fanNumber ?? null,
|
||||||
|
country: dto.companyLocation ?? 'Ethiopia',
|
||||||
|
address: dto.companyAddress ?? null,
|
||||||
|
phone: dto.companyPhone ?? null,
|
||||||
|
email: dto.companyEmail ?? null,
|
||||||
|
attributes: dto.attributes ?? null,
|
||||||
|
});
|
||||||
|
|
||||||
|
const profile = await this.profilesRepo.create({
|
||||||
|
userId: identity.userId,
|
||||||
|
companyId: company.id,
|
||||||
|
firstName: identity.firstName,
|
||||||
|
lastName: identity.lastName,
|
||||||
|
email: identity.email,
|
||||||
|
phone: identity.phone,
|
||||||
|
jobTitle: dto.jobTitle ?? null,
|
||||||
|
isPrimaryContact: dto.isPrimaryContact ?? true,
|
||||||
|
});
|
||||||
|
|
||||||
|
return { company, profile };
|
||||||
|
}
|
||||||
|
|
||||||
|
async findAllCompanies(): Promise<Company[]> {
|
||||||
|
return this.companiesRepo.findAll({ order: { name: 'ASC' as any } });
|
||||||
|
}
|
||||||
|
|
||||||
|
async findCompanyById(id: string): Promise<Company> {
|
||||||
|
const company = await this.companiesRepo.findById(id);
|
||||||
|
if (!company) throw new NotFoundException(`Company ${id} not found`);
|
||||||
|
return company;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getCompanyInfoByUserId(userId: string): Promise<{ profile: ExternalProfile; company: Company }> {
|
||||||
|
const profile = await this.profilesRepo.findByUserId(userId);
|
||||||
|
if (!profile) throw new NotFoundException(`Profile for user ${userId} not found`);
|
||||||
|
|
||||||
|
const company = profile.company;
|
||||||
|
if (!company) throw new NotFoundException(`Company for profile ${profile.id} not found`);
|
||||||
|
|
||||||
|
return { profile, company };
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateCompany(id: string, dto: UpdateCompanyDto): Promise<Company> {
|
||||||
|
await this.findCompanyById(id);
|
||||||
|
const updated = await this.companiesRepo.update(id, dto);
|
||||||
|
if (!updated) throw new NotFoundException(`Company ${id} not found`);
|
||||||
|
return updated;
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteCompany(id: string): Promise<void> {
|
||||||
|
await this.findCompanyById(id);
|
||||||
|
await this.companiesRepo.softDelete(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
async createProfile(dto: CreateExternalProfileDto): Promise<ExternalProfile> {
|
||||||
|
await this.findCompanyById(dto.companyId);
|
||||||
|
|
||||||
|
const existing = await this.profilesRepo.findByEmail(dto.email);
|
||||||
|
if (existing) {
|
||||||
|
throw new ConflictException(`Profile with email ${dto.email} already exists`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.profilesRepo.create(dto);
|
||||||
|
}
|
||||||
|
|
||||||
|
async findProfileByUserId(userId: string): Promise<ExternalProfile> {
|
||||||
|
const profile = await this.profilesRepo.findByUserId(userId);
|
||||||
|
if (!profile) throw new NotFoundException(`Profile for user ${userId} not found`);
|
||||||
|
return profile;
|
||||||
|
}
|
||||||
|
|
||||||
|
async findProfilesByCompany(companyId: string): Promise<ExternalProfile[]> {
|
||||||
|
return this.profilesRepo.findByCompanyId(companyId);
|
||||||
|
}
|
||||||
|
|
||||||
|
async createFFClient(dto: CreateFFClientDto): Promise<FFClient> {
|
||||||
|
await this.findCompanyById(dto.forwarderCompanyId);
|
||||||
|
await this.findCompanyById(dto.clientCompanyId);
|
||||||
|
|
||||||
|
const existing = await this.ffClientsRepo.findRelationship(
|
||||||
|
dto.forwarderCompanyId,
|
||||||
|
dto.clientCompanyId,
|
||||||
|
);
|
||||||
|
if (existing) {
|
||||||
|
throw new ConflictException('This forwarder-client relationship already exists');
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.ffClientsRepo.create(dto);
|
||||||
|
}
|
||||||
|
|
||||||
|
async findForwarderClients(forwarderCompanyId: string): Promise<FFClient[]> {
|
||||||
|
return this.ffClientsRepo.findByForwarder(forwarderCompanyId);
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteFFClient(id: string): Promise<void> {
|
||||||
|
const client = await this.ffClientsRepo.findById(id);
|
||||||
|
if (!client) throw new NotFoundException(`FFClient ${id} not found`);
|
||||||
|
await this.ffClientsRepo.softDelete(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import { Company } from '../entities/company.entity';
|
||||||
|
import { ExternalProfile } from '../entities/external-profile.entity';
|
||||||
|
import { ResponseCompanyDto } from './response-company.dto';
|
||||||
|
import { ResponseExternalProfileDto } from './response-external-profile.dto';
|
||||||
|
|
||||||
|
export class CompanyInfoResponseDto {
|
||||||
|
profile: ResponseExternalProfileDto;
|
||||||
|
company: ResponseCompanyDto;
|
||||||
|
|
||||||
|
constructor(profile: ExternalProfile, company: Company) {
|
||||||
|
this.profile = new ResponseExternalProfileDto(profile);
|
||||||
|
this.company = new ResponseCompanyDto(company);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
import { IsString, IsNotEmpty, IsOptional, IsEmail, MaxLength, IsBoolean, IsEnum } from 'class-validator';
|
||||||
|
import { CompanyType } from '../entities/company.entity';
|
||||||
|
|
||||||
|
export class CreateCompanyWithProfileDto {
|
||||||
|
@IsEnum(CompanyType)
|
||||||
|
companyType!: CompanyType;
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty()
|
||||||
|
@MaxLength(200)
|
||||||
|
companyName!: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsEmail()
|
||||||
|
@MaxLength(150)
|
||||||
|
companyEmail?: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
@MaxLength(20)
|
||||||
|
companyPhone?: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
@MaxLength(32)
|
||||||
|
companyLocation?: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
companyAddress?: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
@MaxLength(10)
|
||||||
|
tin?: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
@MaxLength(50)
|
||||||
|
vatNumber?: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
@MaxLength(16)
|
||||||
|
fanNumber?: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
@MaxLength(100)
|
||||||
|
jobTitle?: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsBoolean()
|
||||||
|
isPrimaryContact?: boolean;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
attributes?: Record<string, any>;
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
import { IsString, IsNotEmpty, IsOptional, IsEnum, MaxLength, Length, Matches, IsEmail } from 'class-validator';
|
||||||
|
import { CompanyType, CompanyStatus } from '../entities/company.entity';
|
||||||
|
|
||||||
|
export class CreateCompanyDto {
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty()
|
||||||
|
@MaxLength(200)
|
||||||
|
name!: string;
|
||||||
|
|
||||||
|
@IsEnum(CompanyType)
|
||||||
|
type!: CompanyType;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsEnum(CompanyStatus)
|
||||||
|
status?: CompanyStatus;
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty()
|
||||||
|
@Length(10, 10)
|
||||||
|
@Matches(/^\d+$/, { message: 'TIN must contain only digits' })
|
||||||
|
tin!: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
@MaxLength(50)
|
||||||
|
vatNumber?: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
@MaxLength(100)
|
||||||
|
businessLicense?: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
@MaxLength(32)
|
||||||
|
country?: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
address?: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
@MaxLength(20)
|
||||||
|
phone?: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsEmail()
|
||||||
|
@MaxLength(150)
|
||||||
|
email?: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
@MaxLength(200)
|
||||||
|
website?: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
attributes?: Record<string, any>;
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
import { IsString, IsNotEmpty, IsOptional, IsEmail, MaxLength, IsBoolean, IsUUID } from 'class-validator';
|
||||||
|
|
||||||
|
export class CreateExternalProfileDto {
|
||||||
|
@IsUUID()
|
||||||
|
@IsNotEmpty()
|
||||||
|
userId!: string;
|
||||||
|
|
||||||
|
@IsUUID()
|
||||||
|
@IsNotEmpty()
|
||||||
|
companyId!: string;
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty()
|
||||||
|
@MaxLength(100)
|
||||||
|
firstName!: string;
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty()
|
||||||
|
@MaxLength(100)
|
||||||
|
lastName!: string;
|
||||||
|
|
||||||
|
@IsEmail()
|
||||||
|
@IsNotEmpty()
|
||||||
|
email!: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
@MaxLength(20)
|
||||||
|
phone?: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
@MaxLength(50)
|
||||||
|
nationalId?: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
@MaxLength(100)
|
||||||
|
jobTitle?: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsBoolean()
|
||||||
|
isPrimaryContact?: boolean;
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import { IsUUID, IsNotEmpty, IsOptional, IsBoolean, IsEnum } from 'class-validator';
|
||||||
|
import { FFClientRelationship } from '../entities/ff-client.entity';
|
||||||
|
|
||||||
|
export class CreateFFClientDto {
|
||||||
|
@IsUUID()
|
||||||
|
@IsNotEmpty()
|
||||||
|
forwarderCompanyId!: string;
|
||||||
|
|
||||||
|
@IsUUID()
|
||||||
|
@IsNotEmpty()
|
||||||
|
clientCompanyId!: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsEnum(FFClientRelationship)
|
||||||
|
relationshipType?: FFClientRelationship;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsBoolean()
|
||||||
|
canBookOnBehalf?: boolean;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsBoolean()
|
||||||
|
canViewDocuments?: boolean;
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
import { Company, CompanyType, CompanyStatus } from '../entities/company.entity';
|
||||||
|
import { ResponseExternalProfileDto } from './response-external-profile.dto';
|
||||||
|
|
||||||
|
export class ResponseCompanyDto {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
type: CompanyType;
|
||||||
|
status: CompanyStatus;
|
||||||
|
tin: string;
|
||||||
|
vatNumber?: string | null;
|
||||||
|
businessLicense?: string | null;
|
||||||
|
fanNumber?: string | null;
|
||||||
|
country: string;
|
||||||
|
address?: string | null;
|
||||||
|
phone?: string | null;
|
||||||
|
email?: string | null;
|
||||||
|
website?: string | null;
|
||||||
|
attributes?: Record<string, any> | null;
|
||||||
|
profiles?: ResponseExternalProfileDto[];
|
||||||
|
createdAt: Date;
|
||||||
|
updatedAt: Date;
|
||||||
|
|
||||||
|
constructor(company: Company) {
|
||||||
|
this.id = company.id;
|
||||||
|
this.name = company.name;
|
||||||
|
this.type = company.type;
|
||||||
|
this.status = company.status;
|
||||||
|
this.tin = company.tin;
|
||||||
|
this.vatNumber = company.vatNumber;
|
||||||
|
this.businessLicense = company.businessLicense;
|
||||||
|
this.fanNumber = company.fanNumber;
|
||||||
|
this.country = company.country;
|
||||||
|
this.address = company.address;
|
||||||
|
this.phone = company.phone;
|
||||||
|
this.email = company.email;
|
||||||
|
this.website = company.website;
|
||||||
|
this.attributes = company.attributes;
|
||||||
|
this.profiles = company.profiles?.map((p) => new ResponseExternalProfileDto(p));
|
||||||
|
this.createdAt = company.createdAt;
|
||||||
|
this.updatedAt = company.updatedAt;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import { ExternalProfile } from '../entities/external-profile.entity';
|
||||||
|
|
||||||
|
export class ResponseExternalProfileDto {
|
||||||
|
id: string;
|
||||||
|
userId: string;
|
||||||
|
companyId: string;
|
||||||
|
firstName: string;
|
||||||
|
lastName: string;
|
||||||
|
email: string;
|
||||||
|
phone?: string | null;
|
||||||
|
nationalId?: string | null;
|
||||||
|
jobTitle?: string | null;
|
||||||
|
isPrimaryContact: boolean;
|
||||||
|
createdAt: Date;
|
||||||
|
updatedAt: Date;
|
||||||
|
|
||||||
|
constructor(profile: ExternalProfile) {
|
||||||
|
this.id = profile.id;
|
||||||
|
this.userId = profile.userId;
|
||||||
|
this.companyId = profile.companyId;
|
||||||
|
this.firstName = profile.firstName;
|
||||||
|
this.lastName = profile.lastName;
|
||||||
|
this.email = profile.email;
|
||||||
|
this.phone = profile.phone;
|
||||||
|
this.nationalId = profile.nationalId;
|
||||||
|
this.jobTitle = profile.jobTitle;
|
||||||
|
this.isPrimaryContact = profile.isPrimaryContact;
|
||||||
|
this.createdAt = profile.createdAt;
|
||||||
|
this.updatedAt = profile.updatedAt;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { FFClient, FFClientRelationship } from '../entities/ff-client.entity';
|
||||||
|
|
||||||
|
export class ResponseFFClientDto {
|
||||||
|
id: string;
|
||||||
|
forwarderCompanyId: string;
|
||||||
|
clientCompanyId: string;
|
||||||
|
relationshipType: FFClientRelationship;
|
||||||
|
canBookOnBehalf: boolean;
|
||||||
|
canViewDocuments: boolean;
|
||||||
|
createdAt: Date;
|
||||||
|
updatedAt: Date;
|
||||||
|
|
||||||
|
constructor(client: FFClient) {
|
||||||
|
this.id = client.id;
|
||||||
|
this.forwarderCompanyId = client.forwarderCompanyId;
|
||||||
|
this.clientCompanyId = client.clientCompanyId;
|
||||||
|
this.relationshipType = client.relationshipType;
|
||||||
|
this.canBookOnBehalf = client.canBookOnBehalf;
|
||||||
|
this.canViewDocuments = client.canViewDocuments;
|
||||||
|
this.createdAt = client.createdAt;
|
||||||
|
this.updatedAt = client.updatedAt;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
import { PartialType } from '@nestjs/mapped-types';
|
||||||
|
import { CreateCompanyDto } from './create-company.dto';
|
||||||
|
|
||||||
|
export class UpdateCompanyDto extends PartialType(CreateCompanyDto) {}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
import { PartialType } from '@nestjs/mapped-types';
|
||||||
|
import { CreateExternalProfileDto } from './create-external-profile.dto';
|
||||||
|
|
||||||
|
export class UpdateExternalProfileDto extends PartialType(CreateExternalProfileDto) {}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
import { PartialType } from '@nestjs/mapped-types';
|
||||||
|
import { CreateFFClientDto } from './create-ff-client.dto';
|
||||||
|
|
||||||
|
export class UpdateFFClientDto extends PartialType(CreateFFClientDto) {}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
import { BaseEntity } from '@edr/api-common';
|
||||||
|
import { Column, Entity, Index, OneToMany } from 'typeorm';
|
||||||
|
import { ExternalProfile } from './external-profile.entity';
|
||||||
|
|
||||||
|
export enum CompanyType {
|
||||||
|
Customer = 'customer',
|
||||||
|
Forwarder = 'forwarder',
|
||||||
|
Transporter = 'transporter',
|
||||||
|
Broker = 'broker',
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum CompanyStatus {
|
||||||
|
Active = 'active',
|
||||||
|
Pending = 'pending',
|
||||||
|
Suspended = 'suspended',
|
||||||
|
Blacklisted = 'blacklisted',
|
||||||
|
}
|
||||||
|
|
||||||
|
@Entity({ schema: 'freight', name: 'companies' })
|
||||||
|
@Index(['tin'])
|
||||||
|
@Index(['type'])
|
||||||
|
export class Company extends BaseEntity {
|
||||||
|
@Column({ name: 'name', type: 'varchar', length: 200 })
|
||||||
|
name!: string;
|
||||||
|
|
||||||
|
@Column({ name: 'type', type: 'varchar', length: 32, enum: CompanyType })
|
||||||
|
type!: CompanyType;
|
||||||
|
|
||||||
|
@Column({ name: 'status', type: 'varchar', length: 32, default: CompanyStatus.Pending })
|
||||||
|
status!: CompanyStatus;
|
||||||
|
|
||||||
|
@Column({ name: 'tin', type: 'varchar', length: 10, unique: true })
|
||||||
|
tin!: string;
|
||||||
|
|
||||||
|
@Column({ name: 'vat_number', type: 'varchar', length: 50, nullable: true })
|
||||||
|
vatNumber?: string | null;
|
||||||
|
|
||||||
|
@Column({ name: 'business_license', type: 'varchar', length: 100, nullable: true })
|
||||||
|
businessLicense?: string | null;
|
||||||
|
|
||||||
|
@Column({ name: 'fan_number', type: 'varchar', length: 16, nullable: true })
|
||||||
|
fanNumber?: string | null;
|
||||||
|
|
||||||
|
@Column({ name: 'country', type: 'varchar', length: 32, default: 'Ethiopia' })
|
||||||
|
country!: string;
|
||||||
|
|
||||||
|
@Column({ name: 'address', type: 'text', nullable: true })
|
||||||
|
address?: string | null;
|
||||||
|
|
||||||
|
@Column({ name: 'phone', type: 'varchar', length: 20, nullable: true })
|
||||||
|
phone?: string | null;
|
||||||
|
|
||||||
|
@Column({ name: 'email', type: 'varchar', length: 150, nullable: true })
|
||||||
|
email?: string | null;
|
||||||
|
|
||||||
|
@Column({ name: 'website', type: 'varchar', length: 200, nullable: true })
|
||||||
|
website?: string | null;
|
||||||
|
|
||||||
|
@Column({ name: 'attributes', type: 'jsonb', nullable: true })
|
||||||
|
attributes?: Record<string, any> | null;
|
||||||
|
|
||||||
|
@OneToMany(() => ExternalProfile, (profile) => profile.company)
|
||||||
|
profiles?: ExternalProfile[];
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import { BaseEntity } from '@edr/api-common';
|
||||||
|
import { Column, Entity, Index, ManyToOne, JoinColumn } from 'typeorm';
|
||||||
|
import { Company } from './company.entity';
|
||||||
|
|
||||||
|
@Entity({ schema: 'freight', name: 'external_profiles' })
|
||||||
|
@Index(['userId'])
|
||||||
|
@Index(['companyId'])
|
||||||
|
export class ExternalProfile extends BaseEntity {
|
||||||
|
@Column({ name: 'user_id', type: 'uuid' })
|
||||||
|
userId!: string;
|
||||||
|
|
||||||
|
@Column({ name: 'company_id', type: 'uuid' })
|
||||||
|
companyId!: string;
|
||||||
|
|
||||||
|
@ManyToOne(() => Company, (company) => company.profiles)
|
||||||
|
@JoinColumn({ name: 'company_id' })
|
||||||
|
company!: Company;
|
||||||
|
|
||||||
|
@Column({ name: 'first_name', type: 'varchar', length: 100 })
|
||||||
|
firstName!: string;
|
||||||
|
|
||||||
|
@Column({ name: 'last_name', type: 'varchar', length: 100 })
|
||||||
|
lastName!: string;
|
||||||
|
|
||||||
|
@Column({ name: 'email', type: 'varchar', length: 150, unique: true })
|
||||||
|
email!: string;
|
||||||
|
|
||||||
|
@Column({ name: 'phone', type: 'varchar', length: 20, nullable: true })
|
||||||
|
phone?: string | null;
|
||||||
|
|
||||||
|
@Column({ name: 'national_id', type: 'varchar', length: 50, nullable: true })
|
||||||
|
nationalId?: string | null;
|
||||||
|
|
||||||
|
@Column({ name: 'job_title', type: 'varchar', length: 100, nullable: true })
|
||||||
|
jobTitle?: string | null;
|
||||||
|
|
||||||
|
@Column({ name: 'is_primary_contact', type: 'boolean', default: false })
|
||||||
|
isPrimaryContact!: boolean;
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
import { BaseEntity } from '@edr/api-common';
|
||||||
|
import { Column, Entity, Index, ManyToOne, JoinColumn, Unique } from 'typeorm';
|
||||||
|
import { Company } from './company.entity';
|
||||||
|
|
||||||
|
export enum FFClientRelationship {
|
||||||
|
ManagedAccount = 'managed_account',
|
||||||
|
SubAgent = 'sub_agent',
|
||||||
|
}
|
||||||
|
|
||||||
|
@Entity({ schema: 'freight', name: 'ff_clients' })
|
||||||
|
@Unique(['forwarderCompanyId', 'clientCompanyId'])
|
||||||
|
@Index(['forwarderCompanyId'])
|
||||||
|
@Index(['clientCompanyId'])
|
||||||
|
export class FFClient extends BaseEntity {
|
||||||
|
@Column({ name: 'forwarder_company_id', type: 'uuid' })
|
||||||
|
forwarderCompanyId!: string;
|
||||||
|
|
||||||
|
@ManyToOne(() => Company)
|
||||||
|
@JoinColumn({ name: 'forwarder_company_id' })
|
||||||
|
forwarderCompany!: Company;
|
||||||
|
|
||||||
|
@Column({ name: 'client_company_id', type: 'uuid' })
|
||||||
|
clientCompanyId!: string;
|
||||||
|
|
||||||
|
@ManyToOne(() => Company)
|
||||||
|
@JoinColumn({ name: 'client_company_id' })
|
||||||
|
clientCompany!: Company;
|
||||||
|
|
||||||
|
@Column({ name: 'relationship_type', type: 'varchar', length: 32, default: FFClientRelationship.ManagedAccount })
|
||||||
|
relationshipType!: FFClientRelationship;
|
||||||
|
|
||||||
|
@Column({ name: 'can_book_on_behalf', type: 'boolean', default: true })
|
||||||
|
canBookOnBehalf!: boolean;
|
||||||
|
|
||||||
|
@Column({ name: 'can_view_documents', type: 'boolean', default: true })
|
||||||
|
canViewDocuments!: boolean;
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
|
import { Repository } from 'typeorm';
|
||||||
|
import { BaseRepository } from '@edr/api-common';
|
||||||
|
import { ExternalProfile } from './entities/external-profile.entity';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class ExternalProfileRepository extends BaseRepository<ExternalProfile> {
|
||||||
|
constructor(
|
||||||
|
@InjectRepository(ExternalProfile)
|
||||||
|
repo: Repository<ExternalProfile>,
|
||||||
|
) {
|
||||||
|
super(repo);
|
||||||
|
}
|
||||||
|
|
||||||
|
async findByUserId(userId: string): Promise<ExternalProfile | null> {
|
||||||
|
return this.repository.findOne({
|
||||||
|
where: { userId } as any,
|
||||||
|
relations: ['company'],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async findByCompanyId(companyId: string): Promise<ExternalProfile[]> {
|
||||||
|
return this.repository.find({ where: { companyId } as any });
|
||||||
|
}
|
||||||
|
|
||||||
|
async findByEmail(email: string): Promise<ExternalProfile | null> {
|
||||||
|
return this.repository.findOne({ where: { email } as any });
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
|
import { Repository } from 'typeorm';
|
||||||
|
import { BaseRepository } from '@edr/api-common';
|
||||||
|
import { FFClient } from './entities/ff-client.entity';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class FFClientRepository extends BaseRepository<FFClient> {
|
||||||
|
constructor(
|
||||||
|
@InjectRepository(FFClient)
|
||||||
|
repo: Repository<FFClient>,
|
||||||
|
) {
|
||||||
|
super(repo);
|
||||||
|
}
|
||||||
|
|
||||||
|
async findByForwarder(forwarderCompanyId: string): Promise<FFClient[]> {
|
||||||
|
return this.repository.find({ where: { forwarderCompanyId } as any });
|
||||||
|
}
|
||||||
|
|
||||||
|
async findByClient(clientCompanyId: string): Promise<FFClient[]> {
|
||||||
|
return this.repository.find({ where: { clientCompanyId } as any });
|
||||||
|
}
|
||||||
|
|
||||||
|
async findRelationship(
|
||||||
|
forwarderCompanyId: string,
|
||||||
|
clientCompanyId: string,
|
||||||
|
): Promise<FFClient | null> {
|
||||||
|
return this.repository.findOne({
|
||||||
|
where: { forwarderCompanyId, clientCompanyId } as any,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user