mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-07 22:25:42 +00:00
feat: add eTrade fields to company entity and onboarding process
- Added new fields to the Company entity: licenceNumber, statusDescription, dateRegistered, renewedFrom, renewalDate, renewedTo, region, zone, woreda, kebele, houseNo, and etradePhone. - Updated onboarding wizard to include a new contact step and fetch company information from eTrade using TIN. - Created ETradeInfo component to handle fetching and displaying eTrade data. - Implemented ETradeService to interact with eTrade API and extract relevant company registration data. - Added new DTOs for fetching eTrade data and handling responses. - Updated CompanyProfileForm to integrate new fields and handle eTrade data. - Created hooks for managing eTrade data fetching and error handling.
This commit is contained in:
@@ -0,0 +1,109 @@
|
|||||||
|
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||||
|
|
||||||
|
export class AddETradeFieldsToCompanies1791000000003
|
||||||
|
implements MigrationInterface
|
||||||
|
{
|
||||||
|
name = "AddETradeFieldsToCompanies1791000000003";
|
||||||
|
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(`
|
||||||
|
ALTER TABLE freight.companies
|
||||||
|
ADD COLUMN IF NOT EXISTS licence_number varchar(100);
|
||||||
|
`);
|
||||||
|
await queryRunner.query(`
|
||||||
|
ALTER TABLE freight.companies
|
||||||
|
ADD COLUMN IF NOT EXISTS status_description text;
|
||||||
|
`);
|
||||||
|
await queryRunner.query(`
|
||||||
|
ALTER TABLE freight.companies
|
||||||
|
ADD COLUMN IF NOT EXISTS date_registered varchar(50);
|
||||||
|
`);
|
||||||
|
await queryRunner.query(`
|
||||||
|
ALTER TABLE freight.companies
|
||||||
|
ADD COLUMN IF NOT EXISTS renewed_from varchar(50);
|
||||||
|
`);
|
||||||
|
await queryRunner.query(`
|
||||||
|
ALTER TABLE freight.companies
|
||||||
|
ADD COLUMN IF NOT EXISTS renewal_date varchar(50);
|
||||||
|
`);
|
||||||
|
await queryRunner.query(`
|
||||||
|
ALTER TABLE freight.companies
|
||||||
|
ADD COLUMN IF NOT EXISTS renewed_to varchar(50);
|
||||||
|
`);
|
||||||
|
await queryRunner.query(`
|
||||||
|
ALTER TABLE freight.companies
|
||||||
|
ADD COLUMN IF NOT EXISTS region varchar(100);
|
||||||
|
`);
|
||||||
|
await queryRunner.query(`
|
||||||
|
ALTER TABLE freight.companies
|
||||||
|
ADD COLUMN IF NOT EXISTS zone varchar(100);
|
||||||
|
`);
|
||||||
|
await queryRunner.query(`
|
||||||
|
ALTER TABLE freight.companies
|
||||||
|
ADD COLUMN IF NOT EXISTS woreda varchar(100);
|
||||||
|
`);
|
||||||
|
await queryRunner.query(`
|
||||||
|
ALTER TABLE freight.companies
|
||||||
|
ADD COLUMN IF NOT EXISTS kebele varchar(100);
|
||||||
|
`);
|
||||||
|
await queryRunner.query(`
|
||||||
|
ALTER TABLE freight.companies
|
||||||
|
ADD COLUMN IF NOT EXISTS house_no varchar(100);
|
||||||
|
`);
|
||||||
|
await queryRunner.query(`
|
||||||
|
ALTER TABLE freight.companies
|
||||||
|
ADD COLUMN IF NOT EXISTS etrade_phone varchar(20);
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(`
|
||||||
|
ALTER TABLE freight.companies
|
||||||
|
DROP COLUMN IF EXISTS licence_number;
|
||||||
|
`);
|
||||||
|
await queryRunner.query(`
|
||||||
|
ALTER TABLE freight.companies
|
||||||
|
DROP COLUMN IF EXISTS status_description;
|
||||||
|
`);
|
||||||
|
await queryRunner.query(`
|
||||||
|
ALTER TABLE freight.companies
|
||||||
|
DROP COLUMN IF EXISTS date_registered;
|
||||||
|
`);
|
||||||
|
await queryRunner.query(`
|
||||||
|
ALTER TABLE freight.companies
|
||||||
|
DROP COLUMN IF EXISTS renewed_from;
|
||||||
|
`);
|
||||||
|
await queryRunner.query(`
|
||||||
|
ALTER TABLE freight.companies
|
||||||
|
DROP COLUMN IF EXISTS renewal_date;
|
||||||
|
`);
|
||||||
|
await queryRunner.query(`
|
||||||
|
ALTER TABLE freight.companies
|
||||||
|
DROP COLUMN IF EXISTS renewed_to;
|
||||||
|
`);
|
||||||
|
await queryRunner.query(`
|
||||||
|
ALTER TABLE freight.companies
|
||||||
|
DROP COLUMN IF EXISTS region;
|
||||||
|
`);
|
||||||
|
await queryRunner.query(`
|
||||||
|
ALTER TABLE freight.companies
|
||||||
|
DROP COLUMN IF EXISTS zone;
|
||||||
|
`);
|
||||||
|
await queryRunner.query(`
|
||||||
|
ALTER TABLE freight.companies
|
||||||
|
DROP COLUMN IF EXISTS woreda;
|
||||||
|
`);
|
||||||
|
await queryRunner.query(`
|
||||||
|
ALTER TABLE freight.companies
|
||||||
|
DROP COLUMN IF EXISTS kebele;
|
||||||
|
`);
|
||||||
|
await queryRunner.query(`
|
||||||
|
ALTER TABLE freight.companies
|
||||||
|
DROP COLUMN IF EXISTS house_no;
|
||||||
|
`);
|
||||||
|
await queryRunner.query(`
|
||||||
|
ALTER TABLE freight.companies
|
||||||
|
DROP COLUMN IF EXISTS etrade_phone;
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -38,6 +38,8 @@ import { CompanyInfoResponseDto } from "./dto/company-info-response.dto";
|
|||||||
import { UpdateProfileDto } from "./dto/update-profile.dto";
|
import { UpdateProfileDto } from "./dto/update-profile.dto";
|
||||||
import { ProfileResponseDto } from "./dto/profile-response.dto";
|
import { ProfileResponseDto } from "./dto/profile-response.dto";
|
||||||
import { DashboardSummaryResponseDto } from "./dto/dashboard-summary-response.dto";
|
import { DashboardSummaryResponseDto } from "./dto/dashboard-summary-response.dto";
|
||||||
|
import { FetchETradeDto } from "./dto/fetch-etrade.dto";
|
||||||
|
import { ETradeResponseDto } from "./dto/etrade-response.dto";
|
||||||
|
|
||||||
interface CurrentIamUser {
|
interface CurrentIamUser {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -85,6 +87,15 @@ export class CompaniesController {
|
|||||||
return this.companiesService.getDashboardSummary(user.id);
|
return this.companiesService.getDashboardSummary(user.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Post("fetch-etrade-info")
|
||||||
|
@ApiOperation({ summary: "Fetch company info from eTrade by TIN" })
|
||||||
|
async fetchETradeInfo(
|
||||||
|
@Body() dto: FetchETradeDto,
|
||||||
|
): Promise<ETradeResponseDto> {
|
||||||
|
const data = await this.companiesService.fetchETradeData(dto.tin);
|
||||||
|
return new ETradeResponseDto(data);
|
||||||
|
}
|
||||||
|
|
||||||
@Patch("profile")
|
@Patch("profile")
|
||||||
@ApiOperation({ summary: "Update profile (flattened settings page)" })
|
@ApiOperation({ summary: "Update profile (flattened settings page)" })
|
||||||
async updateProfile(
|
async updateProfile(
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Module } from "@nestjs/common";
|
import { Module } from "@nestjs/common";
|
||||||
import { TypeOrmModule } from "@nestjs/typeorm";
|
import { TypeOrmModule } from "@nestjs/typeorm";
|
||||||
|
import { HttpModule } from "@nestjs/axios";
|
||||||
import { FilesModule } from "../files/files.module";
|
import { FilesModule } from "../files/files.module";
|
||||||
import { MinioModule } from "../minio/minio.module";
|
import { MinioModule } from "../minio/minio.module";
|
||||||
import { CompaniesController } from "./companies.controller";
|
import { CompaniesController } from "./companies.controller";
|
||||||
@@ -12,10 +13,12 @@ import { ExternalProfile } from "./entities/external-profile.entity";
|
|||||||
import { CompanyProfile } from "./entities/company-profile.entity";
|
import { CompanyProfile } from "./entities/company-profile.entity";
|
||||||
import { Booking } from "../bookings/entities/booking.entity";
|
import { Booking } from "../bookings/entities/booking.entity";
|
||||||
import { CompanyProfileRepository } from "./company-profile.repository";
|
import { CompanyProfileRepository } from "./company-profile.repository";
|
||||||
|
import { ETradeService } from "./services/etrade.service";
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
TypeOrmModule.forFeature([Company, ExternalProfile, CompanyProfile, Booking]),
|
TypeOrmModule.forFeature([Company, ExternalProfile, CompanyProfile, Booking]),
|
||||||
|
HttpModule,
|
||||||
FilesModule,
|
FilesModule,
|
||||||
MinioModule,
|
MinioModule,
|
||||||
],
|
],
|
||||||
@@ -26,6 +29,7 @@ import { CompanyProfileRepository } from "./company-profile.repository";
|
|||||||
ExternalProfileRepository,
|
ExternalProfileRepository,
|
||||||
CompanyProfileRepository,
|
CompanyProfileRepository,
|
||||||
CompanyDashboardRepository,
|
CompanyDashboardRepository,
|
||||||
|
ETradeService,
|
||||||
],
|
],
|
||||||
exports: [CompaniesService],
|
exports: [CompaniesService],
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import { CompanyProfileRepository } from "./company-profile.repository";
|
|||||||
import { ExternalProfileRepository } from "./external-profile.repository";
|
import { ExternalProfileRepository } from "./external-profile.repository";
|
||||||
import { CompanyDashboardRepository } from "./company-dashboard.repository";
|
import { CompanyDashboardRepository } from "./company-dashboard.repository";
|
||||||
import { MinioService } from "../minio/minio.service";
|
import { MinioService } from "../minio/minio.service";
|
||||||
|
import { ETradeService } from "./services/etrade.service";
|
||||||
import { CreateCompanyDto } from "./dto/create-company.dto";
|
import { CreateCompanyDto } from "./dto/create-company.dto";
|
||||||
import { UpdateCompanyDto } from "./dto/update-company.dto";
|
import { UpdateCompanyDto } from "./dto/update-company.dto";
|
||||||
import { CreateExternalProfileDto } from "./dto/create-external-profile.dto";
|
import { CreateExternalProfileDto } from "./dto/create-external-profile.dto";
|
||||||
@@ -46,6 +47,7 @@ export class CompaniesService {
|
|||||||
private readonly profilesRepo: ExternalProfileRepository,
|
private readonly profilesRepo: ExternalProfileRepository,
|
||||||
private readonly dashboardRepo: CompanyDashboardRepository,
|
private readonly dashboardRepo: CompanyDashboardRepository,
|
||||||
private readonly minioService: MinioService,
|
private readonly minioService: MinioService,
|
||||||
|
private readonly etradeService: ETradeService,
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
async createCompany(dto: CreateCompanyDto): Promise<Company> {
|
async createCompany(dto: CreateCompanyDto): Promise<Company> {
|
||||||
@@ -496,6 +498,10 @@ export class CompaniesService {
|
|||||||
|
|
||||||
if (dto.contactPersonName !== undefined)
|
if (dto.contactPersonName !== undefined)
|
||||||
attrUpdates.contactPersonName = dto.contactPersonName;
|
attrUpdates.contactPersonName = dto.contactPersonName;
|
||||||
|
if (dto.contactPersonPosition !== undefined)
|
||||||
|
attrUpdates.contactPersonPosition = dto.contactPersonPosition;
|
||||||
|
if (dto.contactPersonEmail !== undefined)
|
||||||
|
attrUpdates.contactPersonEmail = dto.contactPersonEmail;
|
||||||
if (dto.contactPersonPhone !== undefined)
|
if (dto.contactPersonPhone !== undefined)
|
||||||
attrUpdates.contactPersonPhone = dto.contactPersonPhone;
|
attrUpdates.contactPersonPhone = dto.contactPersonPhone;
|
||||||
if (dto.generalManagerName !== undefined)
|
if (dto.generalManagerName !== undefined)
|
||||||
@@ -511,6 +517,31 @@ export class CompaniesService {
|
|||||||
attrUpdates.poaLocation = dto.poaLocation;
|
attrUpdates.poaLocation = dto.poaLocation;
|
||||||
if (dto.poaAddress !== undefined) attrUpdates.poaAddress = dto.poaAddress;
|
if (dto.poaAddress !== undefined) attrUpdates.poaAddress = dto.poaAddress;
|
||||||
|
|
||||||
|
if (dto.licenceNumber !== undefined)
|
||||||
|
companyUpdates.licenceNumber = dto.licenceNumber;
|
||||||
|
if (dto.statusDescription !== undefined)
|
||||||
|
companyUpdates.statusDescription = dto.statusDescription;
|
||||||
|
if (dto.dateRegistered !== undefined)
|
||||||
|
companyUpdates.dateRegistered = dto.dateRegistered;
|
||||||
|
if (dto.renewedFrom !== undefined)
|
||||||
|
companyUpdates.renewedFrom = dto.renewedFrom;
|
||||||
|
if (dto.renewalDate !== undefined)
|
||||||
|
companyUpdates.renewalDate = dto.renewalDate;
|
||||||
|
if (dto.renewedTo !== undefined)
|
||||||
|
companyUpdates.renewedTo = dto.renewedTo;
|
||||||
|
if (dto.region !== undefined)
|
||||||
|
companyUpdates.region = dto.region;
|
||||||
|
if (dto.zone !== undefined)
|
||||||
|
companyUpdates.zone = dto.zone;
|
||||||
|
if (dto.woreda !== undefined)
|
||||||
|
companyUpdates.woreda = dto.woreda;
|
||||||
|
if (dto.kebele !== undefined)
|
||||||
|
companyUpdates.kebele = dto.kebele;
|
||||||
|
if (dto.houseNo !== undefined)
|
||||||
|
companyUpdates.houseNo = dto.houseNo;
|
||||||
|
if (dto.etradePhone !== undefined)
|
||||||
|
companyUpdates.etradePhone = dto.etradePhone;
|
||||||
|
|
||||||
companyUpdates.attributes = attrUpdates;
|
companyUpdates.attributes = attrUpdates;
|
||||||
|
|
||||||
const updated = await this.companiesRepo.update(company.id, companyUpdates);
|
const updated = await this.companiesRepo.update(company.id, companyUpdates);
|
||||||
@@ -892,4 +923,14 @@ export class CompaniesService {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fetchETradeData(tin: string) {
|
||||||
|
const { businessInfo } = await this.etradeService.resolveCompanyData(tin);
|
||||||
|
if (!businessInfo) {
|
||||||
|
throw new BadRequestException(
|
||||||
|
"No business license found for this TIN. Please check the number and try again.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return this.etradeService.extractRegistrationData(businessInfo);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import { CompanyRegistrationData } from "@edr/types";
|
||||||
|
|
||||||
|
export class ETradeResponseDto implements CompanyRegistrationData {
|
||||||
|
licenceNumber!: string;
|
||||||
|
statusDescription!: string;
|
||||||
|
dateRegistered!: string;
|
||||||
|
renewedFrom!: string;
|
||||||
|
renewalDate!: string;
|
||||||
|
renewedTo!: string;
|
||||||
|
region!: string;
|
||||||
|
zone!: string;
|
||||||
|
woreda!: string;
|
||||||
|
kebele!: string;
|
||||||
|
houseNo!: string;
|
||||||
|
mobilePhone!: string;
|
||||||
|
regularPhone!: string;
|
||||||
|
managerName!: string;
|
||||||
|
managerEmail?: string;
|
||||||
|
managerPhone!: string;
|
||||||
|
|
||||||
|
constructor(data: CompanyRegistrationData) {
|
||||||
|
this.licenceNumber = data.licenceNumber;
|
||||||
|
this.statusDescription = data.statusDescription;
|
||||||
|
this.dateRegistered = data.dateRegistered;
|
||||||
|
this.renewedFrom = data.renewedFrom;
|
||||||
|
this.renewalDate = data.renewalDate;
|
||||||
|
this.renewedTo = data.renewedTo;
|
||||||
|
this.region = data.region;
|
||||||
|
this.zone = data.zone;
|
||||||
|
this.woreda = data.woreda;
|
||||||
|
this.kebele = data.kebele;
|
||||||
|
this.houseNo = data.houseNo;
|
||||||
|
this.mobilePhone = data.mobilePhone;
|
||||||
|
this.regularPhone = data.regularPhone;
|
||||||
|
this.managerName = data.managerName;
|
||||||
|
this.managerEmail = data.managerEmail;
|
||||||
|
this.managerPhone = data.managerPhone;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import { IsString, IsNotEmpty, Length } from "class-validator";
|
||||||
|
|
||||||
|
export class FetchETradeDto {
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty()
|
||||||
|
@Length(10, 10, { message: "TIN must be exactly 10 digits" })
|
||||||
|
tin!: string;
|
||||||
|
}
|
||||||
@@ -17,7 +17,22 @@ export class ProfileResponseDto {
|
|||||||
|
|
||||||
companyProfiles: ResponseCompanyProfileDto[];
|
companyProfiles: ResponseCompanyProfileDto[];
|
||||||
|
|
||||||
|
licenceNumber: string | null;
|
||||||
|
statusDescription: string | null;
|
||||||
|
dateRegistered: string | null;
|
||||||
|
renewedFrom: string | null;
|
||||||
|
renewalDate: string | null;
|
||||||
|
renewedTo: string | null;
|
||||||
|
region: string | null;
|
||||||
|
zone: string | null;
|
||||||
|
woreda: string | null;
|
||||||
|
kebele: string | null;
|
||||||
|
houseNo: string | null;
|
||||||
|
etradePhone: string | null;
|
||||||
|
|
||||||
contactPersonName: string | null;
|
contactPersonName: string | null;
|
||||||
|
contactPersonPosition: string | null;
|
||||||
|
contactPersonEmail: string | null;
|
||||||
contactPersonPhone: string | null;
|
contactPersonPhone: string | null;
|
||||||
generalManagerName: string | null;
|
generalManagerName: string | null;
|
||||||
generalManagerEmail: string | null;
|
generalManagerEmail: string | null;
|
||||||
@@ -48,8 +63,23 @@ export class ProfileResponseDto {
|
|||||||
this.fanNumber = company.fanNumber ?? null;
|
this.fanNumber = company.fanNumber ?? null;
|
||||||
this.profileId = profile.id;
|
this.profileId = profile.id;
|
||||||
|
|
||||||
|
this.licenceNumber = company.licenceNumber ?? null;
|
||||||
|
this.statusDescription = company.statusDescription ?? null;
|
||||||
|
this.dateRegistered = company.dateRegistered ?? null;
|
||||||
|
this.renewedFrom = company.renewedFrom ?? null;
|
||||||
|
this.renewalDate = company.renewalDate ?? null;
|
||||||
|
this.renewedTo = company.renewedTo ?? null;
|
||||||
|
this.region = company.region ?? null;
|
||||||
|
this.zone = company.zone ?? null;
|
||||||
|
this.woreda = company.woreda ?? null;
|
||||||
|
this.kebele = company.kebele ?? null;
|
||||||
|
this.houseNo = company.houseNo ?? null;
|
||||||
|
this.etradePhone = company.etradePhone ?? null;
|
||||||
|
|
||||||
const attrs = company.attributes ?? {};
|
const attrs = company.attributes ?? {};
|
||||||
this.contactPersonName = attrs.contactPersonName ?? null;
|
this.contactPersonName = attrs.contactPersonName ?? null;
|
||||||
|
this.contactPersonPosition = attrs.contactPersonPosition ?? null;
|
||||||
|
this.contactPersonEmail = attrs.contactPersonEmail ?? null;
|
||||||
this.contactPersonPhone = attrs.contactPersonPhone ?? null;
|
this.contactPersonPhone = attrs.contactPersonPhone ?? null;
|
||||||
this.generalManagerName = attrs.generalManagerName ?? null;
|
this.generalManagerName = attrs.generalManagerName ?? null;
|
||||||
this.generalManagerEmail = attrs.generalManagerEmail ?? null;
|
this.generalManagerEmail = attrs.generalManagerEmail ?? null;
|
||||||
|
|||||||
@@ -50,6 +50,14 @@ export class UpdateProfileDto {
|
|||||||
@IsString()
|
@IsString()
|
||||||
contactPersonName?: string;
|
contactPersonName?: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
contactPersonPosition?: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsEmail()
|
||||||
|
contactPersonEmail?: string;
|
||||||
|
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsString()
|
@IsString()
|
||||||
contactPersonPhone?: string;
|
contactPersonPhone?: string;
|
||||||
@@ -85,4 +93,63 @@ export class UpdateProfileDto {
|
|||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsString()
|
@IsString()
|
||||||
poaAddress?: string;
|
poaAddress?: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
@MaxLength(100)
|
||||||
|
licenceNumber?: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
statusDescription?: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
@MaxLength(50)
|
||||||
|
dateRegistered?: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
@MaxLength(50)
|
||||||
|
renewedFrom?: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
@MaxLength(50)
|
||||||
|
renewalDate?: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
@MaxLength(50)
|
||||||
|
renewedTo?: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
@MaxLength(100)
|
||||||
|
region?: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
@MaxLength(100)
|
||||||
|
zone?: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
@MaxLength(100)
|
||||||
|
woreda?: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
@MaxLength(100)
|
||||||
|
kebele?: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
@MaxLength(100)
|
||||||
|
houseNo?: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
@MaxLength(20)
|
||||||
|
etradePhone?: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -117,6 +117,67 @@ export class Company extends BaseEntity {
|
|||||||
@Column({ name: "attributes", type: "jsonb", nullable: true })
|
@Column({ name: "attributes", type: "jsonb", nullable: true })
|
||||||
attributes?: Record<string, any> | null;
|
attributes?: Record<string, any> | null;
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
name: "licence_number",
|
||||||
|
type: "varchar",
|
||||||
|
length: 100,
|
||||||
|
nullable: true,
|
||||||
|
})
|
||||||
|
licenceNumber?: string | null;
|
||||||
|
|
||||||
|
@Column({ name: "status_description", type: "text", nullable: true })
|
||||||
|
statusDescription?: string | null;
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
name: "date_registered",
|
||||||
|
type: "varchar",
|
||||||
|
length: 50,
|
||||||
|
nullable: true,
|
||||||
|
})
|
||||||
|
dateRegistered?: string | null;
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
name: "renewed_from",
|
||||||
|
type: "varchar",
|
||||||
|
length: 50,
|
||||||
|
nullable: true,
|
||||||
|
})
|
||||||
|
renewedFrom?: string | null;
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
name: "renewal_date",
|
||||||
|
type: "varchar",
|
||||||
|
length: 50,
|
||||||
|
nullable: true,
|
||||||
|
})
|
||||||
|
renewalDate?: string | null;
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
name: "renewed_to",
|
||||||
|
type: "varchar",
|
||||||
|
length: 50,
|
||||||
|
nullable: true,
|
||||||
|
})
|
||||||
|
renewedTo?: string | null;
|
||||||
|
|
||||||
|
@Column({ name: "region", type: "varchar", length: 100, nullable: true })
|
||||||
|
region?: string | null;
|
||||||
|
|
||||||
|
@Column({ name: "zone", type: "varchar", length: 100, nullable: true })
|
||||||
|
zone?: string | null;
|
||||||
|
|
||||||
|
@Column({ name: "woreda", type: "varchar", length: 100, nullable: true })
|
||||||
|
woreda?: string | null;
|
||||||
|
|
||||||
|
@Column({ name: "kebele", type: "varchar", length: 100, nullable: true })
|
||||||
|
kebele?: string | null;
|
||||||
|
|
||||||
|
@Column({ name: "house_no", type: "varchar", length: 100, nullable: true })
|
||||||
|
houseNo?: string | null;
|
||||||
|
|
||||||
|
@Column({ name: "etrade_phone", type: "varchar", length: 20, nullable: true })
|
||||||
|
etradePhone?: string | null;
|
||||||
|
|
||||||
@OneToMany(() => ExternalProfile, (profile) => profile.company)
|
@OneToMany(() => ExternalProfile, (profile) => profile.company)
|
||||||
profiles?: ExternalProfile[];
|
profiles?: ExternalProfile[];
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,102 @@
|
|||||||
|
import { Injectable, BadRequestException } from "@nestjs/common";
|
||||||
|
import { HttpService } from "@nestjs/axios";
|
||||||
|
import { firstValueFrom } from "rxjs";
|
||||||
|
import {
|
||||||
|
ETradeCompanyInfo,
|
||||||
|
ETradeBusinessInfo,
|
||||||
|
CompanyRegistrationData,
|
||||||
|
} from "@edr/types";
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class ETradeService {
|
||||||
|
private readonly baseUrl = "https://etrade.gov.et/api";
|
||||||
|
private readonly referer = "https://etrade.gov.et/business-license-checker";
|
||||||
|
|
||||||
|
constructor(private readonly httpService: HttpService) {}
|
||||||
|
|
||||||
|
async getCompanyInfoByTin(tin: string): Promise<ETradeCompanyInfo> {
|
||||||
|
const url = `${this.baseUrl}/Registration/GetRegistrationInfoByTin/${tin}/en`;
|
||||||
|
try {
|
||||||
|
const response = await firstValueFrom(
|
||||||
|
this.httpService.get<ETradeCompanyInfo>(url, {
|
||||||
|
headers: { Referer: this.referer },
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
return response.data;
|
||||||
|
} catch (error: any) {
|
||||||
|
throw new BadRequestException(
|
||||||
|
`Failed to fetch company info from eTrade: ${error.message}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getBusinessByLicenseNo(
|
||||||
|
licenseNo: string,
|
||||||
|
tin: string,
|
||||||
|
): Promise<ETradeBusinessInfo> {
|
||||||
|
const url = `${this.baseUrl}/BusinessMain/GetBusinessByLicenseNo`;
|
||||||
|
try {
|
||||||
|
const response = await firstValueFrom(
|
||||||
|
this.httpService.get<ETradeBusinessInfo>(url, {
|
||||||
|
params: {
|
||||||
|
LicenseNo: licenseNo,
|
||||||
|
Tin: tin,
|
||||||
|
Lang: "en",
|
||||||
|
},
|
||||||
|
headers: { Referer: this.referer },
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
return response.data;
|
||||||
|
} catch (error: any) {
|
||||||
|
throw new BadRequestException(
|
||||||
|
`Failed to fetch business info from eTrade: ${error.message}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async resolveCompanyData(tin: string): Promise<{
|
||||||
|
companyInfo: ETradeCompanyInfo;
|
||||||
|
businessInfo: ETradeBusinessInfo | null;
|
||||||
|
}> {
|
||||||
|
const companyInfo = await this.getCompanyInfoByTin(tin);
|
||||||
|
|
||||||
|
if (!companyInfo.Businesses || companyInfo.Businesses.length === 0) {
|
||||||
|
return { companyInfo, businessInfo: null };
|
||||||
|
}
|
||||||
|
|
||||||
|
const latestBusiness = companyInfo.Businesses[0];
|
||||||
|
try {
|
||||||
|
const businessInfo = await this.getBusinessByLicenseNo(
|
||||||
|
latestBusiness.LicenceNumber,
|
||||||
|
tin,
|
||||||
|
);
|
||||||
|
return { companyInfo, businessInfo };
|
||||||
|
} catch {
|
||||||
|
return { companyInfo, businessInfo: null };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extractRegistrationData(
|
||||||
|
businessInfo: ETradeBusinessInfo,
|
||||||
|
): CompanyRegistrationData {
|
||||||
|
const primaryManager = businessInfo.AssociateShortInfos?.[0];
|
||||||
|
|
||||||
|
return {
|
||||||
|
licenceNumber: businessInfo.LicenceNumber,
|
||||||
|
statusDescription: businessInfo.StatusDescription,
|
||||||
|
dateRegistered: businessInfo.DateRegistered,
|
||||||
|
renewedFrom: businessInfo.RenewedFrom,
|
||||||
|
renewalDate: businessInfo.RenewalDate,
|
||||||
|
renewedTo: businessInfo.RenewedTo,
|
||||||
|
region: businessInfo.AddressInfo?.Region || "",
|
||||||
|
zone: businessInfo.AddressInfo?.Zone || "",
|
||||||
|
woreda: businessInfo.AddressInfo?.Woreda || "",
|
||||||
|
kebele: businessInfo.AddressInfo?.Kebele || "",
|
||||||
|
houseNo: businessInfo.AddressInfo?.HouseNo || "",
|
||||||
|
mobilePhone: businessInfo.AddressInfo?.MobilePhone || "",
|
||||||
|
regularPhone: businessInfo.AddressInfo?.RegularPhone || "",
|
||||||
|
managerName: primaryManager?.ManagerNameEng || "",
|
||||||
|
managerPhone: primaryManager?.RegularPhone || "",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
import {
|
||||||
|
Alert,
|
||||||
|
Button,
|
||||||
|
Group,
|
||||||
|
Loader,
|
||||||
|
SimpleGrid,
|
||||||
|
Stack,
|
||||||
|
Text,
|
||||||
|
TextInput,
|
||||||
|
} from "@mantine/core";
|
||||||
|
import { AlertCircle, CheckCircle2, RefreshCw } from "lucide-react";
|
||||||
|
import { useETradeData } from "@/hooks/useETradeData";
|
||||||
|
import type { CompanyRegistrationData } from "@edr/types";
|
||||||
|
|
||||||
|
interface ETradeInfoProps {
|
||||||
|
tin: string;
|
||||||
|
onDataLoaded: (data: CompanyRegistrationData) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function ETradeInfo({ tin, onDataLoaded }: ETradeInfoProps) {
|
||||||
|
const mutation = useETradeData();
|
||||||
|
const isLoading = mutation.isPending;
|
||||||
|
const hasError = mutation.isError;
|
||||||
|
const hasData = mutation.data;
|
||||||
|
|
||||||
|
const handleFetch = async () => {
|
||||||
|
if (!tin || tin.length !== 10) return;
|
||||||
|
const result = await mutation.mutateAsync(tin);
|
||||||
|
if (result) {
|
||||||
|
onDataLoaded(result);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const errorMessage =
|
||||||
|
hasError && mutation.error
|
||||||
|
? (mutation.error as any).message ||
|
||||||
|
"Failed to fetch company information. Please try again."
|
||||||
|
: null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack gap="md">
|
||||||
|
<Group grow>
|
||||||
|
<TextInput
|
||||||
|
label="TIN Number"
|
||||||
|
placeholder="1234567890"
|
||||||
|
value={tin}
|
||||||
|
disabled
|
||||||
|
readOnly
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
variant="filled"
|
||||||
|
color="edr-green"
|
||||||
|
onClick={handleFetch}
|
||||||
|
disabled={!tin || tin.length !== 10 || isLoading}
|
||||||
|
leftSection={isLoading ? <Loader size={16} /> : <RefreshCw size={16} />}
|
||||||
|
mt="24px"
|
||||||
|
>
|
||||||
|
{isLoading ? "Fetching..." : "Fetch Info from eTrade"}
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
|
||||||
|
{hasError && errorMessage && (
|
||||||
|
<Alert
|
||||||
|
icon={<AlertCircle size={16} />}
|
||||||
|
color="red"
|
||||||
|
title="Failed to fetch data"
|
||||||
|
>
|
||||||
|
{errorMessage}
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{hasData && (
|
||||||
|
<Alert
|
||||||
|
icon={<CheckCircle2 size={16} />}
|
||||||
|
color="green"
|
||||||
|
title="Company information loaded"
|
||||||
|
>
|
||||||
|
<Stack gap={0}>
|
||||||
|
<Text size="sm">
|
||||||
|
<strong>License:</strong> {hasData.licenceNumber}
|
||||||
|
</Text>
|
||||||
|
<Text size="sm">
|
||||||
|
<strong>Status:</strong> {hasData.statusDescription}
|
||||||
|
</Text>
|
||||||
|
{hasData.region && (
|
||||||
|
<Text size="sm">
|
||||||
|
<strong>Location:</strong> {hasData.kebele}, {hasData.woreda},{" "}
|
||||||
|
{hasData.zone}, {hasData.region}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -20,10 +20,17 @@ import NationalitySelect from "@/pages/settings/NationalitySelect";
|
|||||||
import OnboardingRoleSelect from "@/pages/settings/OnboardingRoleSelect";
|
import OnboardingRoleSelect from "@/pages/settings/OnboardingRoleSelect";
|
||||||
|
|
||||||
/** Form steps shared by CompanyProfileForm and ForwarderForm. */
|
/** Form steps shared by CompanyProfileForm and ForwarderForm. */
|
||||||
type FormStep = "company" | "personnel" | "poa" | "documents" | "additional";
|
type FormStep =
|
||||||
|
| "company"
|
||||||
|
| "personnel"
|
||||||
|
| "contact"
|
||||||
|
| "poa"
|
||||||
|
| "documents"
|
||||||
|
| "additional";
|
||||||
const FORM_STEPS: FormStep[] = [
|
const FORM_STEPS: FormStep[] = [
|
||||||
"company",
|
"company",
|
||||||
"personnel",
|
"personnel",
|
||||||
|
"contact",
|
||||||
"poa",
|
"poa",
|
||||||
"documents",
|
"documents",
|
||||||
"additional",
|
"additional",
|
||||||
|
|||||||
@@ -90,6 +90,7 @@ export const URL_CONSTANTS = {
|
|||||||
ONBOARDING_STEP: "/api/companies/onboarding-step",
|
ONBOARDING_STEP: "/api/companies/onboarding-step",
|
||||||
ONBOARDING_COMPLETE: "/api/companies/onboarding/complete",
|
ONBOARDING_COMPLETE: "/api/companies/onboarding/complete",
|
||||||
DASHBOARD: "/api/companies/dashboard",
|
DASHBOARD: "/api/companies/dashboard",
|
||||||
|
FETCH_ETRADE_INFO: "/api/companies/fetch-etrade-info",
|
||||||
DOCUMENTS: (id: string) => `/api/companies/${id}/documents`,
|
DOCUMENTS: (id: string) => `/api/companies/${id}/documents`,
|
||||||
PROFILE_LICENSE: (profileId: string) =>
|
PROFILE_LICENSE: (profileId: string) =>
|
||||||
`/api/companies/company-profiles/${profileId}/license`,
|
`/api/companies/company-profiles/${profileId}/license`,
|
||||||
|
|||||||
16
apps/edr-freight-web/portal/src/hooks/useETradeData.ts
Normal file
16
apps/edr-freight-web/portal/src/hooks/useETradeData.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { useMutation } from "@tanstack/react-query";
|
||||||
|
import { companiesService } from "@/services/companies.service";
|
||||||
|
import { extractApiError } from "@/utils/result";
|
||||||
|
import type { CompanyRegistrationData } from "@edr/types";
|
||||||
|
|
||||||
|
export function useETradeData() {
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: async (tin: string): Promise<CompanyRegistrationData> => {
|
||||||
|
return companiesService.fetchETradeInfo({ tin });
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
const { message } = extractApiError(error);
|
||||||
|
console.error("eTrade fetch error:", message);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ import {
|
|||||||
Alert,
|
Alert,
|
||||||
Box,
|
Box,
|
||||||
Button,
|
Button,
|
||||||
|
Checkbox,
|
||||||
Divider,
|
Divider,
|
||||||
Group,
|
Group,
|
||||||
Loader,
|
Loader,
|
||||||
@@ -23,6 +24,7 @@ import {
|
|||||||
FileText,
|
FileText,
|
||||||
UploadCloud,
|
UploadCloud,
|
||||||
User,
|
User,
|
||||||
|
UserCheck,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { useEffect, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
@@ -31,6 +33,7 @@ import { z } from "zod";
|
|||||||
import type { AuthUser } from "@/types/auth";
|
import type { AuthUser } from "@/types/auth";
|
||||||
import type { CreateCompanyPayload } from "@/services/companies.service";
|
import type { CreateCompanyPayload } from "@/services/companies.service";
|
||||||
import type { ProfileResponse, UpdateProfilePayload } from "@/types/profile";
|
import type { ProfileResponse, UpdateProfilePayload } from "@/types/profile";
|
||||||
|
import type { CompanyRegistrationData } from "@edr/types";
|
||||||
import PhoneInput from "@/components/auth/PhoneInput";
|
import PhoneInput from "@/components/auth/PhoneInput";
|
||||||
import { SmartFileInput } from "@edr/ui-common";
|
import { SmartFileInput } from "@edr/ui-common";
|
||||||
import { api } from "@/services/api";
|
import { api } from "@/services/api";
|
||||||
@@ -38,8 +41,15 @@ import { splitPhone } from "@/utils/phone";
|
|||||||
import RoleLicenseStep, {
|
import RoleLicenseStep, {
|
||||||
type RoleLicenseProfile,
|
type RoleLicenseProfile,
|
||||||
} from "@/components/onboarding/RoleLicenseStep";
|
} from "@/components/onboarding/RoleLicenseStep";
|
||||||
|
import ETradeInfo from "@/components/onboarding/ETradeInfo";
|
||||||
|
|
||||||
type CompanyStep = "company" | "personnel" | "poa" | "documents" | "additional";
|
type CompanyStep =
|
||||||
|
| "company"
|
||||||
|
| "personnel"
|
||||||
|
| "contact"
|
||||||
|
| "poa"
|
||||||
|
| "documents"
|
||||||
|
| "additional";
|
||||||
|
|
||||||
const onboardingSchema = z.object({
|
const onboardingSchema = z.object({
|
||||||
companyName: z.string().min(1, "Company name is required"),
|
companyName: z.string().min(1, "Company name is required"),
|
||||||
@@ -54,7 +64,25 @@ const onboardingSchema = z.object({
|
|||||||
.min(1, "VAT number is required")
|
.min(1, "VAT number is required")
|
||||||
.length(10, "VAT number must be exactly 10 digits"),
|
.length(10, "VAT number must be exactly 10 digits"),
|
||||||
fanNumber: z.string().length(16, "FAN must be exactly 16 digits"),
|
fanNumber: z.string().length(16, "FAN must be exactly 16 digits"),
|
||||||
|
licenceNumber: z.string().optional(),
|
||||||
|
statusDescription: z.string().optional(),
|
||||||
|
dateRegistered: z.string().optional(),
|
||||||
|
renewedFrom: z.string().optional(),
|
||||||
|
renewalDate: z.string().optional(),
|
||||||
|
renewedTo: z.string().optional(),
|
||||||
|
region: z.string().optional(),
|
||||||
|
zone: z.string().optional(),
|
||||||
|
woreda: z.string().optional(),
|
||||||
|
kebele: z.string().optional(),
|
||||||
|
houseNo: z.string().optional(),
|
||||||
|
etradePhone: z.string().optional(),
|
||||||
contactPersonName: z.string().min(1, "Contact person name is required"),
|
contactPersonName: z.string().min(1, "Contact person name is required"),
|
||||||
|
contactPersonPosition: z.string().optional(),
|
||||||
|
contactPersonEmail: z
|
||||||
|
.string()
|
||||||
|
.email("Invalid email address")
|
||||||
|
.optional()
|
||||||
|
.or(z.literal("")),
|
||||||
contactPersonPhone: z.string().min(1, "Contact person phone is required"),
|
contactPersonPhone: z.string().min(1, "Contact person phone is required"),
|
||||||
contactPersonPhoneCountryCode: z.string().min(1, "Country code is required"),
|
contactPersonPhoneCountryCode: z.string().min(1, "Country code is required"),
|
||||||
generalManagerName: z.string().min(1, "GM name is required"),
|
generalManagerName: z.string().min(1, "GM name is required"),
|
||||||
@@ -82,16 +110,32 @@ const stepFields: Record<CompanyStep, (keyof FormData)[]> = {
|
|||||||
"tinNumber",
|
"tinNumber",
|
||||||
"vatNumber",
|
"vatNumber",
|
||||||
"fanNumber",
|
"fanNumber",
|
||||||
|
"licenceNumber",
|
||||||
|
"statusDescription",
|
||||||
|
"dateRegistered",
|
||||||
|
"renewedFrom",
|
||||||
|
"renewalDate",
|
||||||
|
"renewedTo",
|
||||||
|
"region",
|
||||||
|
"zone",
|
||||||
|
"woreda",
|
||||||
|
"kebele",
|
||||||
|
"houseNo",
|
||||||
|
"etradePhone",
|
||||||
],
|
],
|
||||||
personnel: [
|
personnel: [
|
||||||
"contactPersonName",
|
|
||||||
"contactPersonPhone",
|
|
||||||
"contactPersonPhoneCountryCode",
|
|
||||||
"generalManagerName",
|
"generalManagerName",
|
||||||
"generalManagerEmail",
|
"generalManagerEmail",
|
||||||
"generalManagerPhone",
|
"generalManagerPhone",
|
||||||
"generalManagerPhoneCountryCode",
|
"generalManagerPhoneCountryCode",
|
||||||
],
|
],
|
||||||
|
contact: [
|
||||||
|
"contactPersonName",
|
||||||
|
"contactPersonPosition",
|
||||||
|
"contactPersonEmail",
|
||||||
|
"contactPersonPhone",
|
||||||
|
"contactPersonPhoneCountryCode",
|
||||||
|
],
|
||||||
poa: [],
|
poa: [],
|
||||||
documents: [],
|
documents: [],
|
||||||
additional: [],
|
additional: [],
|
||||||
@@ -109,6 +153,8 @@ function buildPayload(data: FormData, _user: AuthUser): CreateCompanyPayload {
|
|||||||
fanNumber: data.fanNumber,
|
fanNumber: data.fanNumber,
|
||||||
attributes: {
|
attributes: {
|
||||||
contactPersonName: data.contactPersonName,
|
contactPersonName: data.contactPersonName,
|
||||||
|
contactPersonPosition: data.contactPersonPosition || undefined,
|
||||||
|
contactPersonEmail: data.contactPersonEmail || undefined,
|
||||||
contactPersonPhone: `${data.contactPersonPhoneCountryCode}${data.contactPersonPhone}`,
|
contactPersonPhone: `${data.contactPersonPhoneCountryCode}${data.contactPersonPhone}`,
|
||||||
generalManagerName: data.generalManagerName,
|
generalManagerName: data.generalManagerName,
|
||||||
generalManagerEmail: data.generalManagerEmail,
|
generalManagerEmail: data.generalManagerEmail,
|
||||||
@@ -138,15 +184,32 @@ function stepPayload(step: CompanyStep, d: FormData): Partial<UpdateProfilePaylo
|
|||||||
tin: d.tinNumber,
|
tin: d.tinNumber,
|
||||||
vatNumber: d.vatNumber,
|
vatNumber: d.vatNumber,
|
||||||
fanNumber: d.fanNumber,
|
fanNumber: d.fanNumber,
|
||||||
|
licenceNumber: d.licenceNumber,
|
||||||
|
statusDescription: d.statusDescription,
|
||||||
|
dateRegistered: d.dateRegistered,
|
||||||
|
renewedFrom: d.renewedFrom,
|
||||||
|
renewalDate: d.renewalDate,
|
||||||
|
renewedTo: d.renewedTo,
|
||||||
|
region: d.region,
|
||||||
|
zone: d.zone,
|
||||||
|
woreda: d.woreda,
|
||||||
|
kebele: d.kebele,
|
||||||
|
houseNo: d.houseNo,
|
||||||
|
etradePhone: d.etradePhone,
|
||||||
};
|
};
|
||||||
case "personnel":
|
case "personnel":
|
||||||
return {
|
return {
|
||||||
contactPersonName: d.contactPersonName,
|
|
||||||
contactPersonPhone: `${d.contactPersonPhoneCountryCode}${d.contactPersonPhone}`,
|
|
||||||
generalManagerName: d.generalManagerName,
|
generalManagerName: d.generalManagerName,
|
||||||
generalManagerEmail: d.generalManagerEmail,
|
generalManagerEmail: d.generalManagerEmail,
|
||||||
generalManagerPhone: `${d.generalManagerPhoneCountryCode}${d.generalManagerPhone}`,
|
generalManagerPhone: `${d.generalManagerPhoneCountryCode}${d.generalManagerPhone}`,
|
||||||
};
|
};
|
||||||
|
case "contact":
|
||||||
|
return {
|
||||||
|
contactPersonName: d.contactPersonName,
|
||||||
|
contactPersonPosition: d.contactPersonPosition || undefined,
|
||||||
|
contactPersonEmail: d.contactPersonEmail || undefined,
|
||||||
|
contactPersonPhone: `${d.contactPersonPhoneCountryCode}${d.contactPersonPhone}`,
|
||||||
|
};
|
||||||
case "poa":
|
case "poa":
|
||||||
return {
|
return {
|
||||||
poaName: d.poaName || undefined,
|
poaName: d.poaName || undefined,
|
||||||
@@ -181,7 +244,21 @@ function toFormValues(p: ProfileResponse): FormData {
|
|||||||
tinNumber: tin,
|
tinNumber: tin,
|
||||||
vatNumber: p.vatNumber ?? "",
|
vatNumber: p.vatNumber ?? "",
|
||||||
fanNumber: p.fanNumber ?? "",
|
fanNumber: p.fanNumber ?? "",
|
||||||
|
licenceNumber: p.licenceNumber ?? "",
|
||||||
|
statusDescription: p.statusDescription ?? "",
|
||||||
|
dateRegistered: p.dateRegistered ?? "",
|
||||||
|
renewedFrom: p.renewedFrom ?? "",
|
||||||
|
renewalDate: p.renewalDate ?? "",
|
||||||
|
renewedTo: p.renewedTo ?? "",
|
||||||
|
region: p.region ?? "",
|
||||||
|
zone: p.zone ?? "",
|
||||||
|
woreda: p.woreda ?? "",
|
||||||
|
kebele: p.kebele ?? "",
|
||||||
|
houseNo: p.houseNo ?? "",
|
||||||
|
etradePhone: p.etradePhone ?? "",
|
||||||
contactPersonName: p.contactPersonName ?? "",
|
contactPersonName: p.contactPersonName ?? "",
|
||||||
|
contactPersonPosition: p.contactPersonPosition ?? "",
|
||||||
|
contactPersonEmail: p.contactPersonEmail ?? "",
|
||||||
contactPersonPhone: contactPhone.number,
|
contactPersonPhone: contactPhone.number,
|
||||||
contactPersonPhoneCountryCode: contactPhone.countryCode,
|
contactPersonPhoneCountryCode: contactPhone.countryCode,
|
||||||
generalManagerName: p.generalManagerName ?? "",
|
generalManagerName: p.generalManagerName ?? "",
|
||||||
@@ -281,6 +358,7 @@ export default function CompanyProfileForm({
|
|||||||
handleSubmit,
|
handleSubmit,
|
||||||
trigger,
|
trigger,
|
||||||
watch,
|
watch,
|
||||||
|
setValue,
|
||||||
formState: { errors },
|
formState: { errors },
|
||||||
} = useForm<FormData>({
|
} = useForm<FormData>({
|
||||||
resolver: zodResolver(onboardingSchema),
|
resolver: zodResolver(onboardingSchema),
|
||||||
@@ -294,7 +372,21 @@ export default function CompanyProfileForm({
|
|||||||
tinNumber: "",
|
tinNumber: "",
|
||||||
vatNumber: "",
|
vatNumber: "",
|
||||||
fanNumber: "",
|
fanNumber: "",
|
||||||
|
licenceNumber: "",
|
||||||
|
statusDescription: "",
|
||||||
|
dateRegistered: "",
|
||||||
|
renewedFrom: "",
|
||||||
|
renewalDate: "",
|
||||||
|
renewedTo: "",
|
||||||
|
region: "",
|
||||||
|
zone: "",
|
||||||
|
woreda: "",
|
||||||
|
kebele: "",
|
||||||
|
houseNo: "",
|
||||||
|
etradePhone: "",
|
||||||
contactPersonName: "",
|
contactPersonName: "",
|
||||||
|
contactPersonPosition: "",
|
||||||
|
contactPersonEmail: "",
|
||||||
contactPersonPhone: "",
|
contactPersonPhone: "",
|
||||||
contactPersonPhoneCountryCode: "+251",
|
contactPersonPhoneCountryCode: "+251",
|
||||||
generalManagerName: "",
|
generalManagerName: "",
|
||||||
@@ -312,8 +404,83 @@ export default function CompanyProfileForm({
|
|||||||
values: rehydrate ? toFormValues(rehydrate) : undefined,
|
values: rehydrate ? toFormValues(rehydrate) : undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// The business owner/manager pulled from eTrade — powers "Use owner as
|
||||||
|
// manager" on the General Manager step. Null until a TIN lookup succeeds.
|
||||||
|
const [etradeOwner, setEtradeOwner] = useState<{
|
||||||
|
name: string;
|
||||||
|
phone: string;
|
||||||
|
} | null>(null);
|
||||||
|
|
||||||
|
// Mirror the two "copy from previous person" checkboxes so they can be
|
||||||
|
// re-toggled (re-checking re-pulls the latest values).
|
||||||
|
const [gmIsContact, setGmIsContact] = useState(false);
|
||||||
|
const [contactIsPoa, setContactIsPoa] = useState(false);
|
||||||
|
|
||||||
|
const handleETradeDataLoaded = (data: CompanyRegistrationData) => {
|
||||||
|
setValue("licenceNumber", data.licenceNumber);
|
||||||
|
setValue("statusDescription", data.statusDescription);
|
||||||
|
setValue("dateRegistered", data.dateRegistered);
|
||||||
|
setValue("renewedFrom", data.renewedFrom);
|
||||||
|
setValue("renewalDate", data.renewalDate);
|
||||||
|
setValue("renewedTo", data.renewedTo);
|
||||||
|
setValue("region", data.region);
|
||||||
|
setValue("zone", data.zone);
|
||||||
|
setValue("woreda", data.woreda);
|
||||||
|
setValue("kebele", data.kebele);
|
||||||
|
setValue("houseNo", data.houseNo);
|
||||||
|
setValue("etradePhone", data.regularPhone || data.mobilePhone);
|
||||||
|
setEtradeOwner({
|
||||||
|
name: data.managerName,
|
||||||
|
phone: data.managerPhone || data.regularPhone || data.mobilePhone,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Fill the General Manager from the eTrade business owner. */
|
||||||
|
const useOwnerAsManager = () => {
|
||||||
|
if (!etradeOwner) return;
|
||||||
|
setValue("generalManagerName", etradeOwner.name);
|
||||||
|
const { number, countryCode } = splitPhone(etradeOwner.phone);
|
||||||
|
setValue("generalManagerPhone", number);
|
||||||
|
setValue("generalManagerPhoneCountryCode", countryCode);
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Copy the General Manager into the Contact Person fields (toggleable). */
|
||||||
|
const toggleGmAsContact = (checked: boolean) => {
|
||||||
|
setGmIsContact(checked);
|
||||||
|
if (!checked) return;
|
||||||
|
setValue("contactPersonName", watch("generalManagerName"));
|
||||||
|
setValue("contactPersonEmail", watch("generalManagerEmail"));
|
||||||
|
setValue("contactPersonPhone", watch("generalManagerPhone"));
|
||||||
|
setValue(
|
||||||
|
"contactPersonPhoneCountryCode",
|
||||||
|
watch("generalManagerPhoneCountryCode"),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Copy the Contact Person into the PoA fields (toggleable, still editable). */
|
||||||
|
const toggleContactAsPoa = (checked: boolean) => {
|
||||||
|
setContactIsPoa(checked);
|
||||||
|
if (!checked) return;
|
||||||
|
setValue("poaName", watch("contactPersonName"));
|
||||||
|
setValue("poaEmail", watch("contactPersonEmail"));
|
||||||
|
setValue("poaPhone", watch("contactPersonPhone"));
|
||||||
|
setValue("poaPhoneCountryCode", watch("contactPersonPhoneCountryCode"));
|
||||||
|
};
|
||||||
|
|
||||||
const hasDocuments = Boolean(uploadSetting?.fields?.length);
|
const hasDocuments = Boolean(uploadSetting?.fields?.length);
|
||||||
const totalSteps = 5;
|
|
||||||
|
// Single source of truth for step sequence — navigation, labels and the
|
||||||
|
// progress bar all derive from this so adding/removing a step is one edit.
|
||||||
|
const stepOrder: CompanyStep[] = [
|
||||||
|
"company",
|
||||||
|
"personnel",
|
||||||
|
"contact",
|
||||||
|
"poa",
|
||||||
|
"documents",
|
||||||
|
"additional",
|
||||||
|
];
|
||||||
|
const totalSteps = stepOrder.length;
|
||||||
|
const currentIdx = stepOrder.indexOf(step);
|
||||||
|
|
||||||
/** Validate + persist the current step, returning whether we may advance. */
|
/** Validate + persist the current step, returning whether we may advance. */
|
||||||
const saveCurrentStep = async (): Promise<boolean> => {
|
const saveCurrentStep = async (): Promise<boolean> => {
|
||||||
@@ -351,55 +518,44 @@ export default function CompanyProfileForm({
|
|||||||
handleSubmit((data) => onSubmit(buildPayload(data, user)))();
|
handleSubmit((data) => onSubmit(buildPayload(data, user)))();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (step === "documents") {
|
// The documents step has nothing to persist; field steps validate + save
|
||||||
setStep("additional");
|
// before advancing.
|
||||||
return;
|
if (step !== "documents") {
|
||||||
|
const ok = await saveCurrentStep();
|
||||||
|
if (!ok) return;
|
||||||
}
|
}
|
||||||
// company / personnel / poa: validate + save before advancing.
|
setStep(stepOrder[currentIdx + 1]);
|
||||||
const ok = await saveCurrentStep();
|
|
||||||
if (!ok) return;
|
|
||||||
setStep(
|
|
||||||
step === "company" ? "personnel" : step === "personnel" ? "poa" : "documents",
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const prevStep = () => {
|
const prevStep = () => {
|
||||||
setSaveError(null);
|
setSaveError(null);
|
||||||
if (step === "company") onBack();
|
if (currentIdx === 0) onBack();
|
||||||
else if (step === "personnel") setStep("company");
|
else setStep(stepOrder[currentIdx - 1]);
|
||||||
else if (step === "poa") setStep("personnel");
|
|
||||||
else if (step === "documents") setStep("poa");
|
|
||||||
else setStep("documents");
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Back is hidden on the first step during onboarding (can't return to role
|
// Back is hidden on the first step during onboarding (can't return to role
|
||||||
// selection); otherwise always available.
|
// selection); otherwise always available.
|
||||||
const showBack = !(hideFirstStepBack && step === "company");
|
const showBack = !(hideFirstStepBack && step === "company");
|
||||||
|
|
||||||
const STEPS: { key: CompanyStep; icon: React.ReactNode }[] = [
|
const STEP_ICONS: Record<CompanyStep, React.ReactNode> = {
|
||||||
{ key: "company", icon: <Building2 size={18} /> },
|
company: <Building2 size={18} />,
|
||||||
{ key: "personnel", icon: <User size={18} /> },
|
personnel: <User size={18} />,
|
||||||
{ key: "poa", icon: <FileText size={18} /> },
|
contact: <UserCheck size={18} />,
|
||||||
{ key: "documents", icon: <UploadCloud size={18} /> },
|
poa: <FileText size={18} />,
|
||||||
{ key: "additional", icon: <CheckCircle2 size={18} /> },
|
documents: <UploadCloud size={18} />,
|
||||||
];
|
additional: <CheckCircle2 size={18} />,
|
||||||
|
|
||||||
const STEP_LABELS: Record<CompanyStep, string> = {
|
|
||||||
company: `Step 1 of ${totalSteps} — Company Information`,
|
|
||||||
personnel: `Step 2 of ${totalSteps} — Personnel Details`,
|
|
||||||
poa: `Step 3 of ${totalSteps} — Power of Attorney (Optional)`,
|
|
||||||
documents: `Step 4 of ${totalSteps} — Upload Documents`,
|
|
||||||
additional: `Step 5 of ${totalSteps} — Business License`,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const stepOrder: CompanyStep[] = [
|
const STEP_TITLES: Record<CompanyStep, string> = {
|
||||||
"company",
|
company: "Company Information",
|
||||||
"personnel",
|
personnel: "General Manager",
|
||||||
"poa",
|
contact: "Contact Person",
|
||||||
"documents",
|
poa: "Power of Attorney (Optional)",
|
||||||
"additional",
|
documents: "Upload Documents",
|
||||||
];
|
additional: "Business License",
|
||||||
const currentIdx = stepOrder.indexOf(step);
|
};
|
||||||
|
|
||||||
|
const stepLabel = `Step ${currentIdx + 1} of ${totalSteps} — ${STEP_TITLES[step]}`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -421,7 +577,7 @@ export default function CompanyProfileForm({
|
|||||||
className="relative max-w-lg mx-auto px-2"
|
className="relative max-w-lg mx-auto px-2"
|
||||||
>
|
>
|
||||||
<Box className="absolute top-1/2 left-2 right-2 h-0.5 bg-edr-border -translate-y-1/2 z-0" />
|
<Box className="absolute top-1/2 left-2 right-2 h-0.5 bg-edr-border -translate-y-1/2 z-0" />
|
||||||
{STEPS.map(({ key, icon }, i) => {
|
{stepOrder.map((key, i) => {
|
||||||
const done = i < currentIdx;
|
const done = i < currentIdx;
|
||||||
const active = i === currentIdx;
|
const active = i === currentIdx;
|
||||||
return done || active ? (
|
return done || active ? (
|
||||||
@@ -433,7 +589,7 @@ export default function CompanyProfileForm({
|
|||||||
color="edr-green"
|
color="edr-green"
|
||||||
className="relative z-10"
|
className="relative z-10"
|
||||||
>
|
>
|
||||||
{done ? <CheckCircle2 size={18} /> : icon}
|
{done ? <CheckCircle2 size={18} /> : STEP_ICONS[key]}
|
||||||
</ThemeIcon>
|
</ThemeIcon>
|
||||||
) : (
|
) : (
|
||||||
<Box
|
<Box
|
||||||
@@ -443,14 +599,14 @@ export default function CompanyProfileForm({
|
|||||||
c="edr-slate"
|
c="edr-slate"
|
||||||
className="relative z-10 flex items-center justify-center rounded-full border-2 border-edr-border bg-edr-card"
|
className="relative z-10 flex items-center justify-center rounded-full border-2 border-edr-border bg-edr-card"
|
||||||
>
|
>
|
||||||
{icon}
|
{STEP_ICONS[key]}
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</Group>
|
</Group>
|
||||||
|
|
||||||
<Text size="sm" c="edr-muted" ta="center" mt="sm">
|
<Text size="sm" c="edr-muted" ta="center" mt="sm">
|
||||||
{STEP_LABELS[step]}
|
{stepLabel}
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
@@ -520,38 +676,133 @@ export default function CompanyProfileForm({
|
|||||||
error={errors.fanNumber?.message}
|
error={errors.fanNumber?.message}
|
||||||
{...register("fanNumber")}
|
{...register("fanNumber")}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<Divider my="sm" />
|
||||||
|
<Text fw={600} size="sm" c="edr-text">
|
||||||
|
Fetch Company Information from eTrade
|
||||||
|
</Text>
|
||||||
|
<ETradeInfo
|
||||||
|
tin={watch("tinNumber")}
|
||||||
|
onDataLoaded={handleETradeDataLoaded}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{watch("licenceNumber") && (
|
||||||
|
<>
|
||||||
|
<Divider my="sm" />
|
||||||
|
<Text fw={600} size="sm" c="edr-text">
|
||||||
|
Registration Details from eTrade
|
||||||
|
</Text>
|
||||||
|
<SimpleGrid cols={2} spacing="md">
|
||||||
|
<TextInput
|
||||||
|
label="License Number"
|
||||||
|
placeholder="01/23/01/19786/2006"
|
||||||
|
error={errors.licenceNumber?.message}
|
||||||
|
{...register("licenceNumber")}
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
label="Status"
|
||||||
|
placeholder="Not renewed for 2 years"
|
||||||
|
error={errors.statusDescription?.message}
|
||||||
|
{...register("statusDescription")}
|
||||||
|
/>
|
||||||
|
</SimpleGrid>
|
||||||
|
<SimpleGrid cols={2} spacing="md">
|
||||||
|
<TextInput
|
||||||
|
label="Date Registered"
|
||||||
|
placeholder="12/17/2013"
|
||||||
|
error={errors.dateRegistered?.message}
|
||||||
|
{...register("dateRegistered")}
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
label="Renewal Date"
|
||||||
|
placeholder="3/17/2016"
|
||||||
|
error={errors.renewalDate?.message}
|
||||||
|
{...register("renewalDate")}
|
||||||
|
/>
|
||||||
|
</SimpleGrid>
|
||||||
|
<SimpleGrid cols={2} spacing="md">
|
||||||
|
<TextInput
|
||||||
|
label="Renewed From"
|
||||||
|
placeholder="3/17/2016"
|
||||||
|
error={errors.renewedFrom?.message}
|
||||||
|
{...register("renewedFrom")}
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
label="Renewed To"
|
||||||
|
placeholder="7/7/2016"
|
||||||
|
error={errors.renewedTo?.message}
|
||||||
|
{...register("renewedTo")}
|
||||||
|
/>
|
||||||
|
</SimpleGrid>
|
||||||
|
|
||||||
|
<Text fw={600} size="sm" c="edr-text" mt="md">
|
||||||
|
Address Information
|
||||||
|
</Text>
|
||||||
|
<SimpleGrid cols={2} spacing="md">
|
||||||
|
<TextInput
|
||||||
|
label="Region"
|
||||||
|
placeholder="Tigray"
|
||||||
|
error={errors.region?.message}
|
||||||
|
{...register("region")}
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
label="Zone"
|
||||||
|
placeholder="EASTERN TIGRAY"
|
||||||
|
error={errors.zone?.message}
|
||||||
|
{...register("zone")}
|
||||||
|
/>
|
||||||
|
</SimpleGrid>
|
||||||
|
<SimpleGrid cols={2} spacing="md">
|
||||||
|
<TextInput
|
||||||
|
label="Woreda"
|
||||||
|
placeholder="EROB"
|
||||||
|
error={errors.woreda?.message}
|
||||||
|
{...register("woreda")}
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
label="Kebele"
|
||||||
|
placeholder="ARAS"
|
||||||
|
error={errors.kebele?.message}
|
||||||
|
{...register("kebele")}
|
||||||
|
/>
|
||||||
|
</SimpleGrid>
|
||||||
|
<SimpleGrid cols={2} spacing="md">
|
||||||
|
<TextInput
|
||||||
|
label="House No"
|
||||||
|
placeholder="House Number"
|
||||||
|
error={errors.houseNo?.message}
|
||||||
|
{...register("houseNo")}
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
label="Phone"
|
||||||
|
placeholder="0355235416"
|
||||||
|
error={errors.etradePhone?.message}
|
||||||
|
{...register("etradePhone")}
|
||||||
|
/>
|
||||||
|
</SimpleGrid>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{step === "personnel" && (
|
{step === "personnel" && (
|
||||||
<>
|
<>
|
||||||
<Text fw={600} size="sm" c="edr-text">
|
<Group justify="space-between" align="center">
|
||||||
Contact Person
|
<Text fw={600} size="sm" c="edr-text">
|
||||||
</Text>
|
General Manager
|
||||||
<SimpleGrid cols={2} spacing="md">
|
</Text>
|
||||||
<TextInput
|
{etradeOwner && (
|
||||||
label="Name"
|
<Button
|
||||||
placeholder="Jane Smith"
|
variant="light"
|
||||||
error={errors.contactPersonName?.message}
|
color="edr-green"
|
||||||
{...register("contactPersonName")}
|
size="xs"
|
||||||
/>
|
leftSection={<UserCheck size={14} />}
|
||||||
<PhoneInput
|
onClick={useOwnerAsManager}
|
||||||
countryCode={{ ...register("contactPersonPhoneCountryCode") }}
|
>
|
||||||
phone={{
|
Use owner as manager
|
||||||
...register("contactPersonPhone"),
|
</Button>
|
||||||
placeholder: "912345678",
|
)}
|
||||||
}}
|
</Group>
|
||||||
countryCodeError={errors.contactPersonPhoneCountryCode}
|
|
||||||
phoneError={errors.contactPersonPhone}
|
|
||||||
label="Phone"
|
|
||||||
/>
|
|
||||||
</SimpleGrid>
|
|
||||||
|
|
||||||
<Divider color="edr-border" />
|
|
||||||
|
|
||||||
<Text fw={600} size="sm" c="edr-text">
|
|
||||||
General Manager
|
|
||||||
</Text>
|
|
||||||
<TextInput
|
<TextInput
|
||||||
label="Name"
|
label="Name"
|
||||||
placeholder="Abebe Bikila"
|
placeholder="Abebe Bikila"
|
||||||
@@ -582,12 +833,65 @@ export default function CompanyProfileForm({
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{step === "contact" && (
|
||||||
|
<>
|
||||||
|
<Text fw={600} size="sm" c="edr-text">
|
||||||
|
Contact Person
|
||||||
|
</Text>
|
||||||
|
<Checkbox
|
||||||
|
color="edr-green"
|
||||||
|
label="Use General Manager as contact person"
|
||||||
|
checked={gmIsContact}
|
||||||
|
onChange={(e) => toggleGmAsContact(e.currentTarget.checked)}
|
||||||
|
/>
|
||||||
|
<SimpleGrid cols={2} spacing="md">
|
||||||
|
<TextInput
|
||||||
|
label="Name"
|
||||||
|
placeholder="Jane Smith"
|
||||||
|
error={errors.contactPersonName?.message}
|
||||||
|
{...register("contactPersonName")}
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
label="Position (Optional)"
|
||||||
|
placeholder="Operations Lead"
|
||||||
|
error={errors.contactPersonPosition?.message}
|
||||||
|
{...register("contactPersonPosition")}
|
||||||
|
/>
|
||||||
|
</SimpleGrid>
|
||||||
|
<SimpleGrid cols={2} spacing="md">
|
||||||
|
<TextInput
|
||||||
|
label="Email (Optional)"
|
||||||
|
type="email"
|
||||||
|
placeholder="contact@company.com"
|
||||||
|
error={errors.contactPersonEmail?.message}
|
||||||
|
{...register("contactPersonEmail")}
|
||||||
|
/>
|
||||||
|
<PhoneInput
|
||||||
|
countryCode={{ ...register("contactPersonPhoneCountryCode") }}
|
||||||
|
phone={{
|
||||||
|
...register("contactPersonPhone"),
|
||||||
|
placeholder: "912345678",
|
||||||
|
}}
|
||||||
|
countryCodeError={errors.contactPersonPhoneCountryCode}
|
||||||
|
phoneError={errors.contactPersonPhone}
|
||||||
|
label="Phone"
|
||||||
|
/>
|
||||||
|
</SimpleGrid>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
{step === "poa" && (
|
{step === "poa" && (
|
||||||
<>
|
<>
|
||||||
<Text size="sm" c="edr-muted">
|
<Text size="sm" c="edr-muted">
|
||||||
Power of Attorney details are optional. Fill them in if you have
|
Power of Attorney details are optional. Fill them in if you have
|
||||||
them, or skip to continue.
|
them, or skip to continue.
|
||||||
</Text>
|
</Text>
|
||||||
|
<Checkbox
|
||||||
|
color="edr-green"
|
||||||
|
label="Use contact person as Power of Attorney"
|
||||||
|
checked={contactIsPoa}
|
||||||
|
onChange={(e) => toggleContactAsPoa(e.currentTarget.checked)}
|
||||||
|
/>
|
||||||
<TextInput
|
<TextInput
|
||||||
label="PoA Name"
|
label="PoA Name"
|
||||||
placeholder="Authorized Representative Name"
|
placeholder="Authorized Representative Name"
|
||||||
|
|||||||
@@ -266,4 +266,13 @@ export const companiesService = {
|
|||||||
);
|
);
|
||||||
return unwrap(response.data);
|
return unwrap(response.data);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/** Fetch company registration data from eTrade by TIN. */
|
||||||
|
fetchETradeInfo: async (payload: { tin: string }): Promise<any> => {
|
||||||
|
const response = await client.post<ApiResponse<any>>(
|
||||||
|
URL_CONSTANTS.COMPANIES_API.FETCH_ETRADE_INFO,
|
||||||
|
payload,
|
||||||
|
);
|
||||||
|
return unwrap(response.data);
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -13,7 +13,21 @@ export interface ProfileResponse {
|
|||||||
tinNumber: string;
|
tinNumber: string;
|
||||||
vatNumber: string | null;
|
vatNumber: string | null;
|
||||||
fanNumber: string | null;
|
fanNumber: string | null;
|
||||||
|
licenceNumber?: string | null;
|
||||||
|
statusDescription?: string | null;
|
||||||
|
dateRegistered?: string | null;
|
||||||
|
renewedFrom?: string | null;
|
||||||
|
renewalDate?: string | null;
|
||||||
|
renewedTo?: string | null;
|
||||||
|
region?: string | null;
|
||||||
|
zone?: string | null;
|
||||||
|
woreda?: string | null;
|
||||||
|
kebele?: string | null;
|
||||||
|
houseNo?: string | null;
|
||||||
|
etradePhone?: string | null;
|
||||||
contactPersonName: string | null;
|
contactPersonName: string | null;
|
||||||
|
contactPersonPosition: string | null;
|
||||||
|
contactPersonEmail: string | null;
|
||||||
contactPersonPhone: string | null;
|
contactPersonPhone: string | null;
|
||||||
generalManagerName: string | null;
|
generalManagerName: string | null;
|
||||||
generalManagerEmail: string | null;
|
generalManagerEmail: string | null;
|
||||||
@@ -36,7 +50,21 @@ export interface UpdateProfilePayload {
|
|||||||
tin?: string;
|
tin?: string;
|
||||||
vatNumber?: string;
|
vatNumber?: string;
|
||||||
fanNumber?: string;
|
fanNumber?: string;
|
||||||
|
licenceNumber?: string;
|
||||||
|
statusDescription?: string;
|
||||||
|
dateRegistered?: string;
|
||||||
|
renewedFrom?: string;
|
||||||
|
renewalDate?: string;
|
||||||
|
renewedTo?: string;
|
||||||
|
region?: string;
|
||||||
|
zone?: string;
|
||||||
|
woreda?: string;
|
||||||
|
kebele?: string;
|
||||||
|
houseNo?: string;
|
||||||
|
etradePhone?: string;
|
||||||
contactPersonName?: string;
|
contactPersonName?: string;
|
||||||
|
contactPersonPosition?: string;
|
||||||
|
contactPersonEmail?: string;
|
||||||
contactPersonPhone?: string;
|
contactPersonPhone?: string;
|
||||||
generalManagerName?: string;
|
generalManagerName?: string;
|
||||||
generalManagerEmail?: string;
|
generalManagerEmail?: string;
|
||||||
|
|||||||
79
packages/types/src/freight/etrade.ts
Normal file
79
packages/types/src/freight/etrade.ts
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
export interface ETradeAddressInfo {
|
||||||
|
Region: string;
|
||||||
|
Zone: string;
|
||||||
|
Woreda: string;
|
||||||
|
Kebele: string;
|
||||||
|
HouseNo: string;
|
||||||
|
MobilePhone: string;
|
||||||
|
RegularPhone: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ETradeAssociateInfo {
|
||||||
|
Position: string | null;
|
||||||
|
ManagerName: string;
|
||||||
|
ManagerNameEng: string;
|
||||||
|
Photo: string | null;
|
||||||
|
MobilePhone: string | null;
|
||||||
|
RegularPhone: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ETradeBusinessInfo {
|
||||||
|
MainGuid: string;
|
||||||
|
OwnerTIN: string;
|
||||||
|
DateRegistered: string;
|
||||||
|
TradeName: string;
|
||||||
|
LicenceNumber: string;
|
||||||
|
Status: number;
|
||||||
|
StatusDescription: string;
|
||||||
|
Capital: number;
|
||||||
|
AssociateShortInfos: ETradeAssociateInfo[];
|
||||||
|
AddressInfo: ETradeAddressInfo;
|
||||||
|
RenewedTo: string;
|
||||||
|
RenewedToDateString: string;
|
||||||
|
RenewalDate: string;
|
||||||
|
RenewedFrom: string;
|
||||||
|
CancellationDate: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ETradeCompanyInfo {
|
||||||
|
Tin: string;
|
||||||
|
LegalCondtion: string;
|
||||||
|
RegNo: string;
|
||||||
|
RegDate: string;
|
||||||
|
BusinessName: string;
|
||||||
|
BusinessNameAmh: string;
|
||||||
|
PaidUpCapital: number;
|
||||||
|
AssociateShortInfos: ETradeAssociateInfo[];
|
||||||
|
Businesses: Array<{
|
||||||
|
MainGuid: string;
|
||||||
|
OwnerTIN: string;
|
||||||
|
DateRegistered: string;
|
||||||
|
TradeNameAmh: string;
|
||||||
|
TradesName: string;
|
||||||
|
LicenceNumber: string;
|
||||||
|
RenewalDate: string;
|
||||||
|
RenewedFrom: string;
|
||||||
|
RenewedTo: string;
|
||||||
|
BusinessLicensingGroupMain: string | null;
|
||||||
|
SubGroups: string | null;
|
||||||
|
}>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CompanyRegistrationData {
|
||||||
|
licenceNumber: string;
|
||||||
|
statusDescription: string;
|
||||||
|
dateRegistered: string;
|
||||||
|
renewedFrom: string;
|
||||||
|
renewalDate: string;
|
||||||
|
renewedTo: string;
|
||||||
|
region: string;
|
||||||
|
zone: string;
|
||||||
|
woreda: string;
|
||||||
|
kebele: string;
|
||||||
|
houseNo: string;
|
||||||
|
mobilePhone: string;
|
||||||
|
regularPhone: string;
|
||||||
|
managerName: string;
|
||||||
|
managerEmail?: string;
|
||||||
|
managerPhone: string;
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ import type { BaseEntity } from "../common";
|
|||||||
export * from "./dropdown_settings";
|
export * from "./dropdown_settings";
|
||||||
export * from "./file_upload_settings";
|
export * from "./file_upload_settings";
|
||||||
export * from "./overview";
|
export * from "./overview";
|
||||||
|
export * from "./etrade";
|
||||||
|
|
||||||
export enum TradeDirection {
|
export enum TradeDirection {
|
||||||
IMPORT = "IMPORT",
|
IMPORT = "IMPORT",
|
||||||
|
|||||||
Reference in New Issue
Block a user