mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-28 10:10:57 +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:
@@ -38,6 +38,8 @@ import { CompanyInfoResponseDto } from "./dto/company-info-response.dto";
|
||||
import { UpdateProfileDto } from "./dto/update-profile.dto";
|
||||
import { ProfileResponseDto } from "./dto/profile-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 {
|
||||
id: string;
|
||||
@@ -85,6 +87,15 @@ export class CompaniesController {
|
||||
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")
|
||||
@ApiOperation({ summary: "Update profile (flattened settings page)" })
|
||||
async updateProfile(
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Module } from "@nestjs/common";
|
||||
import { TypeOrmModule } from "@nestjs/typeorm";
|
||||
import { HttpModule } from "@nestjs/axios";
|
||||
import { FilesModule } from "../files/files.module";
|
||||
import { MinioModule } from "../minio/minio.module";
|
||||
import { CompaniesController } from "./companies.controller";
|
||||
@@ -12,10 +13,12 @@ import { ExternalProfile } from "./entities/external-profile.entity";
|
||||
import { CompanyProfile } from "./entities/company-profile.entity";
|
||||
import { Booking } from "../bookings/entities/booking.entity";
|
||||
import { CompanyProfileRepository } from "./company-profile.repository";
|
||||
import { ETradeService } from "./services/etrade.service";
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([Company, ExternalProfile, CompanyProfile, Booking]),
|
||||
HttpModule,
|
||||
FilesModule,
|
||||
MinioModule,
|
||||
],
|
||||
@@ -26,6 +29,7 @@ import { CompanyProfileRepository } from "./company-profile.repository";
|
||||
ExternalProfileRepository,
|
||||
CompanyProfileRepository,
|
||||
CompanyDashboardRepository,
|
||||
ETradeService,
|
||||
],
|
||||
exports: [CompaniesService],
|
||||
})
|
||||
|
||||
@@ -9,6 +9,7 @@ import { CompanyProfileRepository } from "./company-profile.repository";
|
||||
import { ExternalProfileRepository } from "./external-profile.repository";
|
||||
import { CompanyDashboardRepository } from "./company-dashboard.repository";
|
||||
import { MinioService } from "../minio/minio.service";
|
||||
import { ETradeService } from "./services/etrade.service";
|
||||
import { CreateCompanyDto } from "./dto/create-company.dto";
|
||||
import { UpdateCompanyDto } from "./dto/update-company.dto";
|
||||
import { CreateExternalProfileDto } from "./dto/create-external-profile.dto";
|
||||
@@ -46,6 +47,7 @@ export class CompaniesService {
|
||||
private readonly profilesRepo: ExternalProfileRepository,
|
||||
private readonly dashboardRepo: CompanyDashboardRepository,
|
||||
private readonly minioService: MinioService,
|
||||
private readonly etradeService: ETradeService,
|
||||
) { }
|
||||
|
||||
async createCompany(dto: CreateCompanyDto): Promise<Company> {
|
||||
@@ -496,6 +498,10 @@ export class CompaniesService {
|
||||
|
||||
if (dto.contactPersonName !== undefined)
|
||||
attrUpdates.contactPersonName = dto.contactPersonName;
|
||||
if (dto.contactPersonPosition !== undefined)
|
||||
attrUpdates.contactPersonPosition = dto.contactPersonPosition;
|
||||
if (dto.contactPersonEmail !== undefined)
|
||||
attrUpdates.contactPersonEmail = dto.contactPersonEmail;
|
||||
if (dto.contactPersonPhone !== undefined)
|
||||
attrUpdates.contactPersonPhone = dto.contactPersonPhone;
|
||||
if (dto.generalManagerName !== undefined)
|
||||
@@ -511,6 +517,31 @@ export class CompaniesService {
|
||||
attrUpdates.poaLocation = dto.poaLocation;
|
||||
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;
|
||||
|
||||
const updated = await this.companiesRepo.update(company.id, companyUpdates);
|
||||
@@ -892,4 +923,14 @@ export class CompaniesService {
|
||||
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[];
|
||||
|
||||
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;
|
||||
contactPersonPosition: string | null;
|
||||
contactPersonEmail: string | null;
|
||||
contactPersonPhone: string | null;
|
||||
generalManagerName: string | null;
|
||||
generalManagerEmail: string | null;
|
||||
@@ -48,8 +63,23 @@ export class ProfileResponseDto {
|
||||
this.fanNumber = company.fanNumber ?? null;
|
||||
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 ?? {};
|
||||
this.contactPersonName = attrs.contactPersonName ?? null;
|
||||
this.contactPersonPosition = attrs.contactPersonPosition ?? null;
|
||||
this.contactPersonEmail = attrs.contactPersonEmail ?? null;
|
||||
this.contactPersonPhone = attrs.contactPersonPhone ?? null;
|
||||
this.generalManagerName = attrs.generalManagerName ?? null;
|
||||
this.generalManagerEmail = attrs.generalManagerEmail ?? null;
|
||||
|
||||
@@ -50,6 +50,14 @@ export class UpdateProfileDto {
|
||||
@IsString()
|
||||
contactPersonName?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
contactPersonPosition?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsEmail()
|
||||
contactPersonEmail?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
contactPersonPhone?: string;
|
||||
@@ -85,4 +93,63 @@ export class UpdateProfileDto {
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
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 })
|
||||
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)
|
||||
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 || "",
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user