Files
edr-platform/apps/edr-freight-api/src/modules/companies/entities/company-profile.entity.ts
2026-07-10 12:10:17 +00:00

130 lines
3.9 KiB
TypeScript

import { BaseEntity } from "@edr/api-common";
import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm";
import { Company } from "./company.entity";
export enum ProfileType {
importer = "importer",
exporter = "exporter",
freightForwarder = "freight_forwarder",
djFreightForwarder = "dj_freight_forwarder",
transporter = "transporter",
}
export enum ProfileStatus {
Active = "active",
Pending = "pending",
/** Reviewer declined the role; carries a note. Customer can reapply → Pending. */
Rejected = "rejected",
Suspended = "suspended",
Blacklisted = "blacklisted",
}
/**
* @deprecated Legacy inline shape. Business-license files now live in the
* FileRecord model (`freight.files`, resource `company_profiles`). Kept only for
* the by-reference reuse shape consumed by bookings/contracts snapshots.
*/
export interface BusinessLicenseFile {
name: string;
url: string;
size: number;
mimeType?: string;
}
/**
* `live` — approved & in effect; `pending_add` — uploaded, awaiting approval;
* `pending_remove` — live but flagged for deletion on approval.
*/
export type StagedFileStatus = "live" | "pending_add" | "pending_remove";
/** A business-license file plus its change-review state, surfaced to clients. */
export interface ProfileLicenseFileView {
id: string;
name: string;
size: number;
mimeType: string;
status: StagedFileStatus;
}
/** A company-level document (e.g. the PoA letter) with its change-review state. */
export interface CompanyDocumentFileView {
id: string;
name: string;
size: number;
mimeType: string;
status: StagedFileStatus;
}
@Entity({ schema: "freight", name: "company_profiles" })
@Index(["reference"], { unique: true })
@Index(["type"])
@Index(["companyId"])
export class CompanyProfile extends BaseEntity {
@Column({ name: "company_id", type: "uuid" })
companyId!: string;
@ManyToOne(() => Company, (company) => company.companyProfiles)
@JoinColumn({ name: "company_id" })
company!: Company;
@Column({ name: "type", type: "varchar", length: 32, enum: ProfileType })
type!: ProfileType;
/**
* Official profile reference (e.g. "EX-A00001"). Minted only when the profile
* is approved (status → Active); pending/unapproved profiles carry NULL.
* The unique index tolerates this because Postgres treats NULLs as distinct.
* API responses surface it as "" when absent — see ResponseCompanyProfileDto.
*/
@Column({
name: "reference",
type: "varchar",
length: 20,
nullable: true,
})
reference!: string | null;
/**
* A newly requested operational role is unreviewed, so it defaults to Pending.
* Only {@link CompaniesService.setCompanyProfileStatus} may promote it to
* Active — an approved-by-default role would let a customer self-grant a
* service (e.g. importer) without any documentation review.
*/
@Column({
name: "status",
type: "varchar",
length: 32,
default: ProfileStatus.Pending,
})
status!: ProfileStatus;
@Column({
name: "business_license",
type: "varchar",
length: 100,
nullable: true,
})
businessLicense?: string | null;
/**
* Business-license documents for this profile, stored directly on the profile
* (multi-file). The bytes live in object storage; only the metadata/URLs are
* persisted here — this is intentionally NOT modelled via the FileRecord table.
*/
@Column({ name: "business_license_files", type: "jsonb", nullable: true })
businessLicenseFiles?: BusinessLicenseFile[] | null;
@Column({ name: "attributes", type: "jsonb", nullable: true })
attributes?: Record<string, any> | null;
/** Reviewer's note when the role is Rejected (cleared on reapply). */
@Column({ name: "review_note", type: "text", nullable: true })
reviewNote?: string | null;
@Column({ name: "reviewed_by", type: "uuid", nullable: true })
reviewedBy?: string | null;
@Column({ name: "reviewed_at", type: "timestamptz", nullable: true })
reviewedAt?: Date | null;
}