mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-09 05:58:18 +00:00
A foreign company licensed by the Ethiopian Investment Commission is not on the trade registry, so eTrade holds no record for its TIN and the lookup the company step is built around returns nothing. Those customers could not get past onboarding at all. They now take the same route a co-operative does: an `investorLicence` flag in `attributes`, and `applyEtradeSourcedFields` skips the eTrade re-check for any company `usesManualRegistration` covers, so the registration they type is persisted as sent instead of 400'ing "no registration found for this TIN". Unlike a co-operative they still hold a business licence per operational role, so that requirement is untouched, and the foreign document set already asks for the investment licence itself — no new set. Only a foreign company may carry the flag, and never alongside the co-operative one: the two resolve to different document sets. `POST /companies/onboarding/revert-to-etrade` gives it back. It clears the typed registration rather than keeping it — the wizard treats a populated registration block as a passed lookup, so leaving it would walk the customer straight past the eTrade step the switch exists to reach — and returns the company to pending, since an approval granted against typed data must not carry over to a record that now claims to be eTrade's.
244 lines
7.2 KiB
TypeScript
244 lines
7.2 KiB
TypeScript
import { BaseEntity } from "@edr/api-common";
|
|
import { Column, Entity, Index, OneToMany } from "typeorm";
|
|
import { ExternalProfile } from "./external-profile.entity";
|
|
import { CompanyProfile } from "./company-profile.entity";
|
|
|
|
export enum CompanyType {
|
|
Customer = "customer",
|
|
FreightForwarder = "freight_forwarder",
|
|
DJFreightForwarder = "dj_freight_forwarder",
|
|
Transporter = "transporter",
|
|
}
|
|
|
|
/**
|
|
* Sector of the company — orthogonal to {@link CompanyType} (the trade role).
|
|
* Government bookings are billed to a single seeded `GOVERNMENT` company instead
|
|
* of carrying a null company + free-text institution.
|
|
*/
|
|
export enum CompanyKind {
|
|
Commercial = "commercial",
|
|
Government = "government",
|
|
}
|
|
|
|
export enum CompanyStatus {
|
|
Active = "active",
|
|
Pending = "pending",
|
|
Suspended = "suspended",
|
|
Blacklisted = "blacklisted",
|
|
}
|
|
|
|
export enum CompanyNationality {
|
|
Ethiopian = "ethiopian",
|
|
Foreign = "foreign",
|
|
}
|
|
|
|
/**
|
|
* `attributes` key marking a co-operative union or farm.
|
|
*
|
|
* Such a company has a TIN but no business licence, so there is no eTrade record to
|
|
* look its registration up in — the company name, registered address and the
|
|
* owner are all typed instead of fetched, and the eTrade authenticity check is
|
|
* skipped rather than failed. It is a flag rather than a column because
|
|
* everything it changes is behavioural (which lookup runs, which documents
|
|
* apply, which roles are offered); nothing queries or joins on it.
|
|
*/
|
|
export const COOPERATIVE_KEY = "cooperative";
|
|
|
|
/** Is this a co-operative union or farm (a TIN, but no business licence)? */
|
|
export function isCooperative(
|
|
company: Pick<Company, "attributes"> | null | undefined,
|
|
): boolean {
|
|
return company?.attributes?.[COOPERATIVE_KEY] === true;
|
|
}
|
|
|
|
/**
|
|
* `attributes` key marking a foreign company onboarding on an investment
|
|
* licence.
|
|
*
|
|
* The Ethiopian Investment Commission registers it, not the trade registry, so
|
|
* eTrade holds no record for its TIN: the registration is typed and the eTrade
|
|
* authenticity check is skipped rather than failed — exactly as for a
|
|
* co-operative. What does NOT change is the licence: the company still holds
|
|
* one per operational role, so that requirement stands.
|
|
*/
|
|
export const INVESTOR_LICENCE_KEY = "investorLicence";
|
|
|
|
/** Is this a foreign company registered on an investment licence? */
|
|
export function hasInvestorLicence(
|
|
company: Pick<Company, "attributes"> | null | undefined,
|
|
): boolean {
|
|
return company?.attributes?.[INVESTOR_LICENCE_KEY] === true;
|
|
}
|
|
|
|
/**
|
|
* eTrade holds nothing for this company, so its registration was typed by hand
|
|
* rather than fetched — and the backoffice is told so. Two different companies
|
|
* reach it (a co-operative has no licence at all; a foreign investor's is not
|
|
* the trade registry's), and every consequence they share hangs off this.
|
|
*/
|
|
export function usesManualRegistration(
|
|
company: Pick<Company, "attributes"> | null | undefined,
|
|
): boolean {
|
|
return isCooperative(company) || hasInvestorLicence(company);
|
|
}
|
|
|
|
@Entity({ schema: "freight", name: "companies" })
|
|
@Index(["tin"])
|
|
@Index(["type"])
|
|
@Index(["kind"])
|
|
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;
|
|
|
|
/** Commercial customer vs. the seeded government entity. */
|
|
@Column({
|
|
name: "kind",
|
|
type: "varchar",
|
|
length: 20,
|
|
default: CompanyKind.Commercial,
|
|
enum: CompanyKind,
|
|
})
|
|
kind!: CompanyKind;
|
|
|
|
@Column({
|
|
name: "status",
|
|
type: "varchar",
|
|
length: 32,
|
|
default: CompanyStatus.Pending,
|
|
})
|
|
status!: CompanyStatus;
|
|
|
|
/** Set when the company is first promoted Pending → Active. Null for companies approved before this column existed. */
|
|
@Column({ name: "approved_at", type: "timestamptz", nullable: true })
|
|
approvedAt?: Date | null;
|
|
|
|
@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: "fan_number", type: "varchar", length: 16, nullable: true })
|
|
fanNumber?: string | null;
|
|
|
|
@Column({ name: "country", type: "varchar", length: 32, default: "Ethiopia" })
|
|
country!: string;
|
|
|
|
/** Whether the company is Ethiopian or Foreign — drives the required onboarding documents. */
|
|
@Column({
|
|
name: "nationality",
|
|
type: "varchar",
|
|
length: 32,
|
|
nullable: true,
|
|
enum: CompanyNationality,
|
|
})
|
|
nationality?: CompanyNationality | null;
|
|
|
|
@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: "contact_person_name",
|
|
type: "varchar",
|
|
length: 100,
|
|
nullable: true,
|
|
})
|
|
contactPersonName?: string | null;
|
|
|
|
@Column({
|
|
name: "contact_person_phone",
|
|
type: "varchar",
|
|
length: 20,
|
|
nullable: true,
|
|
})
|
|
contactPersonPhone?: string | null;
|
|
|
|
// The general manager used to live here as three columns. It named who to
|
|
// talk to, gated nothing, and nothing ever populated the columns — the write
|
|
// path put the values in `attributes`. Removed in RemoveGeneralManager; the
|
|
// company's people are now its owner (whoever the eTrade licence names) and
|
|
// its Power of Attorney, both in `attributes`.
|
|
|
|
@Column({ name: "website", type: "varchar", length: 200, nullable: true })
|
|
website?: string | null;
|
|
|
|
@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[];
|
|
|
|
@OneToMany(() => CompanyProfile, (profile) => profile.company)
|
|
companyProfiles?: CompanyProfile[];
|
|
}
|