From 7c5d96795cc349bd95f7235b5a038d8f69c2fadb Mon Sep 17 00:00:00 2001 From: Nathnael Date: Fri, 31 Jul 2026 11:45:09 +0000 Subject: [PATCH] feat(freight): track onboarding-phase edit history for companies MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ticket #238 — pre-approval edits and document uploads write straight to the live company row with no approval gate and, until now, no trace. Adds an append-only company_revisions log (diffed field changes, document uploads) recorded from updateProfile and uploadCompanyDocuments, exposed via GET /companies/:id/revisions and shown as "Version history" on the backoffice customer detail page. --- .../3130000000000-CreateCompanyRevisions.ts | 48 ++++++++++ .../modules/companies/companies.controller.ts | 12 +++ .../companies.fayda-identity.spec.ts | 8 ++ .../src/modules/companies/companies.module.ts | 4 + .../companies.poa-delegation.spec.ts | 8 ++ .../companies/companies.role-deselect.spec.ts | 1 + .../modules/companies/companies.service.ts | 52 +++++++++++ .../companies/company-revision-diff.util.ts | 90 +++++++++++++++++++ .../companies/company-revision.repository.ts | 23 +++++ .../dto/company-revision-response.dto.ts | 23 +++++ .../entities/company-revision.entity.ts | 39 ++++++++ .../customers/CompanyRevisionHistory.tsx | 53 +++++++++++ .../src/components/customers/index.ts | 1 + .../backoffice/src/constants/QUERY_KEYS.ts | 2 + .../backoffice/src/constants/URLS.ts | 1 + .../pages/customers/CustomerDetailPage.tsx | 2 + .../backoffice/src/services/api.ts | 8 ++ .../src/services/customers.service.ts | 8 ++ 18 files changed, 383 insertions(+) create mode 100644 apps/edr-freight-api/src/migrations/3130000000000-CreateCompanyRevisions.ts create mode 100644 apps/edr-freight-api/src/modules/companies/company-revision-diff.util.ts create mode 100644 apps/edr-freight-api/src/modules/companies/company-revision.repository.ts create mode 100644 apps/edr-freight-api/src/modules/companies/dto/company-revision-response.dto.ts create mode 100644 apps/edr-freight-api/src/modules/companies/entities/company-revision.entity.ts create mode 100644 apps/edr-freight-web/backoffice/src/components/customers/CompanyRevisionHistory.tsx diff --git a/apps/edr-freight-api/src/migrations/3130000000000-CreateCompanyRevisions.ts b/apps/edr-freight-api/src/migrations/3130000000000-CreateCompanyRevisions.ts new file mode 100644 index 000000000..01c1c6991 --- /dev/null +++ b/apps/edr-freight-api/src/migrations/3130000000000-CreateCompanyRevisions.ts @@ -0,0 +1,48 @@ +import { MigrationInterface, QueryRunner, Table, TableIndex } from 'typeorm'; + +/** + * Append-only audit of company edits made before the company reaches Active + * (the onboarding phase) — that write path has no approval gate and, until + * now, left no trace of what changed (e.g. a phone number or a document). + */ +export class CreateCompanyRevisions3130000000000 implements MigrationInterface { + name = 'CreateCompanyRevisions3130000000000'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.createTable( + new Table({ + schema: 'freight', + name: 'company_revisions', + columns: [ + { name: 'id', type: 'uuid', isPrimary: true, generationStrategy: 'uuid', default: 'gen_random_uuid()' }, + { name: 'company_id', type: 'uuid' }, + { name: 'actor_id', type: 'uuid', isNullable: true }, + { name: 'summary', type: 'varchar', length: '255' }, + { name: 'changes', type: 'jsonb', default: "'[]'::jsonb" }, + { name: 'created_at', type: 'timestamptz', default: 'now()' }, + { name: 'updated_at', type: 'timestamptz', default: 'now()' }, + { name: 'deleted_at', type: 'timestamptz', isNullable: true }, + ], + foreignKeys: [ + { + columnNames: ['company_id'], + referencedSchema: 'freight', + referencedTableName: 'companies', + referencedColumnNames: ['id'], + onDelete: 'CASCADE', + }, + ], + }), + true, + ); + + await queryRunner.createIndex( + 'freight.company_revisions', + new TableIndex({ name: 'idx_company_revisions_company', columnNames: ['company_id'] }), + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.dropTable('freight.company_revisions', true); + } +} diff --git a/apps/edr-freight-api/src/modules/companies/companies.controller.ts b/apps/edr-freight-api/src/modules/companies/companies.controller.ts index ee75460c9..fdfd4e344 100644 --- a/apps/edr-freight-api/src/modules/companies/companies.controller.ts +++ b/apps/edr-freight-api/src/modules/companies/companies.controller.ts @@ -62,6 +62,7 @@ import { UpdateCompanyProfileStatusDto } from "./dto/update-company-profile-stat import { RejectChangeRequestDto } from "./dto/reject-change-request.dto"; import { RequestDocumentChangeDto } from "./dto/request-document-change.dto"; import { ChangeRequestResponseDto } from "./dto/change-request-response.dto"; +import { CompanyRevisionResponseDto } from "./dto/company-revision-response.dto"; import { FetchETradeDto } from "./dto/fetch-etrade.dto"; import { ETradeResponseDto } from "./dto/etrade-response.dto"; @@ -685,6 +686,17 @@ export class CompaniesController { return requests.map((r) => new ChangeRequestResponseDto(r)); } + @Get(":companyId/revisions") + @BookingStaff(FREIGHT_PERMS.customers.view) + @ApiOperation({ summary: "Onboarding-phase edit history (version history)" }) + async listCompanyRevisions( + @Param("companyId", ParseUUIDPipe) companyId: string, + ): Promise { + const revisions = + await this.companiesService.listCompanyRevisions(companyId); + return revisions.map((r) => new CompanyRevisionResponseDto(r)); + } + @Post("change-requests/:id/approve") @BookingStaff(FREIGHT_PERMS.customers.verify) @ApiOperation({ diff --git a/apps/edr-freight-api/src/modules/companies/companies.fayda-identity.spec.ts b/apps/edr-freight-api/src/modules/companies/companies.fayda-identity.spec.ts index c13f79f5e..511c3aa25 100644 --- a/apps/edr-freight-api/src/modules/companies/companies.fayda-identity.spec.ts +++ b/apps/edr-freight-api/src/modules/companies/companies.fayda-identity.spec.ts @@ -120,6 +120,13 @@ function makeService(overrides: Partial = {}) { })), update: jest.fn(async () => ({ id: "cr-1" })), }, + revisionRepo: { + create: jest.fn(async (row: Record) => ({ + id: "rev-1", + ...row, + })), + findByCompanyId: jest.fn(async () => []), + }, profilesRepo: { findByCompanyId: jest.fn(async () => []), findByUserId: jest.fn(async () => ({ @@ -144,6 +151,7 @@ function makeService(overrides: Partial = {}) { deps.companiesRepo as never, deps.companyProfilesRepo as never, deps.changeRequestRepo as never, + deps.revisionRepo as never, deps.profilesRepo as never, {} as never, deps.filesService as never, diff --git a/apps/edr-freight-api/src/modules/companies/companies.module.ts b/apps/edr-freight-api/src/modules/companies/companies.module.ts index 450222657..f0a443ba8 100644 --- a/apps/edr-freight-api/src/modules/companies/companies.module.ts +++ b/apps/edr-freight-api/src/modules/companies/companies.module.ts @@ -15,9 +15,11 @@ import { Company } from "./entities/company.entity"; import { ExternalProfile } from "./entities/external-profile.entity"; import { CompanyProfile } from "./entities/company-profile.entity"; import { CompanyChangeRequest } from "./entities/company-change-request.entity"; +import { CompanyRevision } from "./entities/company-revision.entity"; import { Booking } from "../bookings/entities/booking.entity"; import { CompanyProfileRepository } from "./company-profile.repository"; import { CompanyChangeRequestRepository } from "./company-change-request.repository"; +import { CompanyRevisionRepository } from "./company-revision.repository"; import { ETradeService } from "./services/etrade.service"; import { CompanyNotifierService } from "./company-notifier.service"; import { VerifaydaModule } from "../verifayda/verifayda.module"; @@ -29,6 +31,7 @@ import { VerifaydaModule } from "../verifayda/verifayda.module"; ExternalProfile, CompanyProfile, CompanyChangeRequest, + CompanyRevision, Booking, ]), HttpModule, @@ -49,6 +52,7 @@ import { VerifaydaModule } from "../verifayda/verifayda.module"; ExternalProfileRepository, CompanyProfileRepository, CompanyChangeRequestRepository, + CompanyRevisionRepository, CompanyDashboardRepository, ETradeService, CompanyNotifierService, diff --git a/apps/edr-freight-api/src/modules/companies/companies.poa-delegation.spec.ts b/apps/edr-freight-api/src/modules/companies/companies.poa-delegation.spec.ts index 27c42e581..365bd4639 100644 --- a/apps/edr-freight-api/src/modules/companies/companies.poa-delegation.spec.ts +++ b/apps/edr-freight-api/src/modules/companies/companies.poa-delegation.spec.ts @@ -91,6 +91,13 @@ function makeService(overrides: Partial = {}) { })), update: jest.fn(async () => ({ id: "cr-1" })), }, + revisionRepo: { + create: jest.fn(async (row: Record) => ({ + id: "rev-1", + ...row, + })), + findByCompanyId: jest.fn(async () => []), + }, profilesRepo: { findByCompanyId: jest.fn(async () => []), findByUserId: jest.fn(async () => ({ @@ -121,6 +128,7 @@ function makeService(overrides: Partial = {}) { deps.companiesRepo as never, deps.companyProfilesRepo as never, deps.changeRequestRepo as never, + deps.revisionRepo as never, deps.profilesRepo as never, {} as never, deps.filesService as never, diff --git a/apps/edr-freight-api/src/modules/companies/companies.role-deselect.spec.ts b/apps/edr-freight-api/src/modules/companies/companies.role-deselect.spec.ts index f22123f61..5961be90f 100644 --- a/apps/edr-freight-api/src/modules/companies/companies.role-deselect.spec.ts +++ b/apps/edr-freight-api/src/modules/companies/companies.role-deselect.spec.ts @@ -39,6 +39,7 @@ function makeService(existing: ExistingProfile[]) { companiesRepo as never, companyProfilesRepo as never, {} as never, + {} as never, profilesRepo as never, {} as never, {} as never, diff --git a/apps/edr-freight-api/src/modules/companies/companies.service.ts b/apps/edr-freight-api/src/modules/companies/companies.service.ts index 32c481873..5dae44f2b 100644 --- a/apps/edr-freight-api/src/modules/companies/companies.service.ts +++ b/apps/edr-freight-api/src/modules/companies/companies.service.ts @@ -1,5 +1,6 @@ import { Injectable, + Logger, NotFoundException, ConflictException, BadRequestException, @@ -9,6 +10,11 @@ import { DataSource, EntityManager } from "typeorm"; import { CompaniesRepository } from "./companies.repository"; import { CompanyProfileRepository } from "./company-profile.repository"; import { CompanyChangeRequestRepository } from "./company-change-request.repository"; +import { CompanyRevisionRepository } from "./company-revision.repository"; +import { + diffCompanyUpdate, + summarizeCompanyChanges, +} from "./company-revision-diff.util"; import { ExternalProfileRepository } from "./external-profile.repository"; import { CompanyDashboardRepository, @@ -64,6 +70,7 @@ import { DocumentChangeIntent, LicenseChangeIntent, } from "./entities/company-change-request.entity"; +import { CompanyRevision } from "./entities/company-revision.entity"; /** FileRecord `resource` + `code` slots for business-license documents. */ const LICENSE_RESOURCE = "company_profiles"; @@ -176,10 +183,13 @@ export interface UserIdentity { @Injectable() export class CompaniesService { + private readonly logger = new Logger(CompaniesService.name); + constructor( private readonly companiesRepo: CompaniesRepository, private readonly companyProfilesRepo: CompanyProfileRepository, private readonly changeRequestRepo: CompanyChangeRequestRepository, + private readonly revisionRepo: CompanyRevisionRepository, private readonly profilesRepo: ExternalProfileRepository, private readonly dashboardRepo: CompanyDashboardRepository, private readonly filesService: FilesService, @@ -849,6 +859,33 @@ export class CompaniesService { return companyUpdates; } + /** + * Append a version-history entry for an onboarding-phase edit (the company + * is not yet Active, so the change went straight to the live row with no + * approval gate to carry a record of it). Best-effort: a no-op patch or a + * failure to write history must never break the edit that triggered it. + */ + private async recordCompanyRevision( + before: Company, + patch: Record, + actorId?: string | null, + ): Promise { + try { + const changes = diffCompanyUpdate(before, patch); + if (changes.length === 0) return; + await this.revisionRepo.create({ + companyId: before.id, + actorId: actorId ?? null, + summary: summarizeCompanyChanges(changes), + changes, + }); + } catch (err) { + this.logger.error( + `Failed to record company revision for ${before.id}: ${String(err)}`, + ); + } + } + /** Reject a TIN already registered to a *different* company. */ private async assertTinAvailable( company: Company, @@ -911,6 +948,7 @@ export class CompaniesService { ); if (!updated) throw new NotFoundException(`Company ${company.id} not found`); + await this.recordCompanyRevision(company, companyUpdates, userId); return new ProfileResponseDto(profile, updated); } @@ -995,6 +1033,12 @@ export class CompaniesService { return this.changeRequestRepo.findByCompanyId(companyId); } + /** Onboarding-phase edit history (see {@link recordCompanyRevision}), newest first. */ + async listCompanyRevisions(companyId: string): Promise { + await this.findCompanyById(companyId); + return this.revisionRepo.findByCompanyId(companyId); + } + /** * Approve a pending change request: apply its snapshot to the live Company and * mark the request approved. Any staged documents are already attached to the @@ -1064,6 +1108,14 @@ export class CompaniesService { uploaded.map((f) => f.id), submittedBy, ); + } else if (uploaded.length > 0) { + await this.recordCompanyRevision( + company, + { + documents: uploaded.map((f) => f.name).join(", "), + }, + submittedBy, + ); } return uploaded; } diff --git a/apps/edr-freight-api/src/modules/companies/company-revision-diff.util.ts b/apps/edr-freight-api/src/modules/companies/company-revision-diff.util.ts new file mode 100644 index 000000000..83fa539e3 --- /dev/null +++ b/apps/edr-freight-api/src/modules/companies/company-revision-diff.util.ts @@ -0,0 +1,90 @@ +import type { Company } from "./entities/company.entity"; +import type { CompanyRevisionChange } from "./entities/company-revision.entity"; + +/** Human label per audited company field — anything not listed here is skipped (internal/lock fields like `*FaydaSub`). */ +export const COMPANY_FIELD_LABELS: Record = { + name: "Company name", + phone: "Phone", + email: "Email", + address: "Address", + country: "Country", + tin: "TIN", + vatNumber: "VAT number", + fanNumber: "FAN number", + nationality: "Nationality", + website: "Website", + licenceNumber: "Licence number", + region: "Region", + zone: "Zone", + woreda: "Woreda", + kebele: "Kebele", + houseNo: "House No", + contactPersonName: "Contact person name", + contactPersonPhone: "Contact person phone", + contactPersonEmail: "Contact person email", + contactPersonPosition: "Contact person position", + generalManagerName: "General manager name", + generalManagerPhone: "General manager phone", + generalManagerEmail: "General manager email", + poaName: "PoA name", + poaPhone: "PoA phone", + poaEmail: "PoA email", + poaLocation: "PoA location", + poaAddress: "PoA address", + documents: "Document", +}; + +function displayValue(value: unknown): string | null { + if (value === null || value === undefined || value === "") return null; + if (typeof value === "boolean") return value ? "Yes" : "No"; + return String(value); +} + +/** + * Compare the company row before a write against the patch about to be + * applied (the same shape `mapProfileDtoToCompanyUpdates` returns: scalar + * columns plus a merged `attributes` blob). Only fields with a known label + * are reported, so identity-lock bookkeeping (`ownerFaydaSub`, etc.) never + * shows up as noise. + */ +export function diffCompanyUpdate( + before: Company, + patch: Record, +): CompanyRevisionChange[] { + const changes: CompanyRevisionChange[] = []; + const { attributes: attrPatch, ...columnPatch } = patch; + + for (const [field, nextRaw] of Object.entries(columnPatch)) { + const label = COMPANY_FIELD_LABELS[field]; + if (!label) continue; + const next = displayValue(nextRaw); + const previous = displayValue((before as unknown as Record)[field]); + if (next === previous) continue; + changes.push({ field, label, from: previous, to: next }); + } + + if (attrPatch) { + const beforeAttrs = before.attributes ?? {}; + for (const [field, nextRaw] of Object.entries(attrPatch)) { + const label = COMPANY_FIELD_LABELS[field]; + if (!label) continue; + const next = displayValue(nextRaw); + const previous = displayValue(beforeAttrs[field]); + if (next === previous) continue; + changes.push({ field, label, from: previous, to: next }); + } + } + + return changes; +} + +/** Short human summary of a change set, e.g. "phone, address changed". */ +export function summarizeCompanyChanges( + changes: CompanyRevisionChange[], +): string { + if (changes.length === 0) return "No changes"; + const labels = changes.map((c) => c.label.toLowerCase()); + return labels.length <= 3 + ? `${labels.join(", ")} changed` + : `${labels.length} fields changed`; +} diff --git a/apps/edr-freight-api/src/modules/companies/company-revision.repository.ts b/apps/edr-freight-api/src/modules/companies/company-revision.repository.ts new file mode 100644 index 000000000..b04bb35de --- /dev/null +++ b/apps/edr-freight-api/src/modules/companies/company-revision.repository.ts @@ -0,0 +1,23 @@ +import { Injectable } from "@nestjs/common"; +import { InjectRepository } from "@nestjs/typeorm"; +import { Repository } from "typeorm"; +import { BaseRepository } from "@edr/api-common"; +import { CompanyRevision } from "./entities/company-revision.entity"; + +@Injectable() +export class CompanyRevisionRepository extends BaseRepository { + constructor( + @InjectRepository(CompanyRevision) + repo: Repository, + ) { + super(repo); + } + + /** Revision history for a company, newest first. */ + async findByCompanyId(companyId: string): Promise { + return this.repository.find({ + where: { companyId }, + order: { createdAt: "DESC" }, + }); + } +} diff --git a/apps/edr-freight-api/src/modules/companies/dto/company-revision-response.dto.ts b/apps/edr-freight-api/src/modules/companies/dto/company-revision-response.dto.ts new file mode 100644 index 000000000..c93c7387c --- /dev/null +++ b/apps/edr-freight-api/src/modules/companies/dto/company-revision-response.dto.ts @@ -0,0 +1,23 @@ +import { + CompanyRevision, + CompanyRevisionChange, +} from "../entities/company-revision.entity"; + +/** One version-history entry, shown on the backoffice customer detail page. */ +export class CompanyRevisionResponseDto { + id: string; + companyId: string; + actorId: string | null; + summary: string; + changes: CompanyRevisionChange[]; + createdAt: Date; + + constructor(revision: CompanyRevision) { + this.id = revision.id; + this.companyId = revision.companyId; + this.actorId = revision.actorId ?? null; + this.summary = revision.summary; + this.changes = revision.changes ?? []; + this.createdAt = revision.createdAt; + } +} diff --git a/apps/edr-freight-api/src/modules/companies/entities/company-revision.entity.ts b/apps/edr-freight-api/src/modules/companies/entities/company-revision.entity.ts new file mode 100644 index 000000000..4872dfff9 --- /dev/null +++ b/apps/edr-freight-api/src/modules/companies/entities/company-revision.entity.ts @@ -0,0 +1,39 @@ +import { BaseEntity } from "@edr/api-common"; +import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm"; +import { Company } from "./company.entity"; + +/** One recorded field/document change, as shown on the customer's version history. */ +export interface CompanyRevisionChange { + field: string; + label: string; + from: string | null; + to: string | null; +} + +/** + * Append-only audit of edits made to a company record BEFORE it reaches + * `Active` (the onboarding phase), where {@link CompaniesService.updateProfile} + * and {@link CompaniesService.uploadCompanyDocuments} write straight to the + * live row with no approval gate — and, until this entity, no trace at all. + * Post-approval edits already get history via `CompanyChangeRequest`; this + * covers the gap before that gate exists. + */ +@Entity({ schema: "freight", name: "company_revisions" }) +@Index(["companyId"]) +export class CompanyRevision extends BaseEntity { + @Column({ name: "company_id", type: "uuid" }) + companyId!: string; + + @ManyToOne(() => Company, { onDelete: "CASCADE" }) + @JoinColumn({ name: "company_id" }) + company?: Company; + + @Column({ name: "actor_id", type: "uuid", nullable: true }) + actorId?: string | null; + + @Column({ name: "summary", type: "varchar", length: 255 }) + summary!: string; + + @Column({ name: "changes", type: "jsonb", default: () => `'[]'::jsonb` }) + changes!: CompanyRevisionChange[]; +} diff --git a/apps/edr-freight-web/backoffice/src/components/customers/CompanyRevisionHistory.tsx b/apps/edr-freight-web/backoffice/src/components/customers/CompanyRevisionHistory.tsx new file mode 100644 index 000000000..a933e003d --- /dev/null +++ b/apps/edr-freight-web/backoffice/src/components/customers/CompanyRevisionHistory.tsx @@ -0,0 +1,53 @@ +import { Badge, Box, Card, Group, Stack, Text } from "@mantine/core"; +import { useQuery } from "@tanstack/react-query"; +import { History } from "lucide-react"; + +import { api } from "@/services/api"; +import { formatDate } from "./format"; + +/** + * Onboarding-phase edit history: what changed on the company record before it + * reached Active, the write path that has no approval gate (unlike edits made + * after approval, which go through {@link ChangeRequestReview} instead). + */ +export function CompanyRevisionHistory({ companyId }: { companyId: string }) { + const query = useQuery( + api.customers.revisions.queryOptions({ input: { id: companyId } }), + ); + const revisions = query.data ?? []; + if (revisions.length === 0) return null; + + return ( + + + + + + Version history + + + {revisions.map((rev) => ( + + + + {formatDate(rev.createdAt)} + + + {rev.summary} + + + {rev.changes.length > 0 && ( + + {rev.changes.map((c, i) => ( + + {c.label}: {c.from ?? "—"} → {c.to ?? "—"} + + ))} + + )} + + ))} + + + ); +} diff --git a/apps/edr-freight-web/backoffice/src/components/customers/index.ts b/apps/edr-freight-web/backoffice/src/components/customers/index.ts index dd35c3c48..a4cc985bd 100644 --- a/apps/edr-freight-web/backoffice/src/components/customers/index.ts +++ b/apps/edr-freight-web/backoffice/src/components/customers/index.ts @@ -13,6 +13,7 @@ export { ChangeRequestReview, ChangeRequestPendingBadge, } from "./ChangeRequestReview"; +export { CompanyRevisionHistory } from "./CompanyRevisionHistory"; export { RequestDocumentChangeModal, type RequestDocumentChangeModalProps, diff --git a/apps/edr-freight-web/backoffice/src/constants/QUERY_KEYS.ts b/apps/edr-freight-web/backoffice/src/constants/QUERY_KEYS.ts index c57a607d5..6e95a27fc 100644 --- a/apps/edr-freight-web/backoffice/src/constants/QUERY_KEYS.ts +++ b/apps/edr-freight-web/backoffice/src/constants/QUERY_KEYS.ts @@ -42,6 +42,8 @@ export const QUERY_KEYS = { ["customers", "detail", id, "reset-target"] as const, changeRequests: (id: string) => ["customers", "detail", id, "change-requests"] as const, + revisions: (id: string) => + ["customers", "detail", id, "revisions"] as const, }, INVOICES: { diff --git a/apps/edr-freight-web/backoffice/src/constants/URLS.ts b/apps/edr-freight-web/backoffice/src/constants/URLS.ts index 142e6c3e7..7651e30d1 100644 --- a/apps/edr-freight-web/backoffice/src/constants/URLS.ts +++ b/apps/edr-freight-web/backoffice/src/constants/URLS.ts @@ -78,6 +78,7 @@ export const URL_CONSTANTS = { `/companies/company-profiles/${profileId}/status`, CHANGE_REQUESTS: (companyId: string) => `/companies/${companyId}/change-requests`, + REVISIONS: (companyId: string) => `/companies/${companyId}/revisions`, CHANGE_REQUEST_APPROVE: (id: string) => `/companies/change-requests/${id}/approve`, CHANGE_REQUEST_REJECT: (id: string) => diff --git a/apps/edr-freight-web/backoffice/src/pages/customers/CustomerDetailPage.tsx b/apps/edr-freight-web/backoffice/src/pages/customers/CustomerDetailPage.tsx index 79f559858..681ab343e 100644 --- a/apps/edr-freight-web/backoffice/src/pages/customers/CustomerDetailPage.tsx +++ b/apps/edr-freight-web/backoffice/src/pages/customers/CustomerDetailPage.tsx @@ -39,6 +39,7 @@ import { BookingStatusBadge, ChangeRequestPendingBadge, ChangeRequestReview, + CompanyRevisionHistory, CompanyStatusBadge, CompanyTypeBadge, InvoiceStatusBadge, @@ -710,6 +711,7 @@ export default function CustomerDetailPage() { )} + QUERY_KEYS.CUSTOMERS.changeRequests(id), ), + revisions: endpoint<{ id: string }, CompanyRevision[]>( + "customers", + "revisions", + ({ id }) => customersService.revisions(id), + ({ id }) => QUERY_KEYS.CUSTOMERS.revisions(id), + ), + approveChangeRequest: endpoint<{ id: string }, CompanyChangeRequest>( "customers", "approveChangeRequest", diff --git a/apps/edr-freight-web/backoffice/src/services/customers.service.ts b/apps/edr-freight-web/backoffice/src/services/customers.service.ts index 6c6d94304..044952cf5 100644 --- a/apps/edr-freight-web/backoffice/src/services/customers.service.ts +++ b/apps/edr-freight-web/backoffice/src/services/customers.service.ts @@ -5,6 +5,7 @@ import type { CompanyChangeRequest, CompanyListFilter, CompanyProfile, + CompanyRevision, CompanyStats, CustomerBooking, CustomerDocument, @@ -146,6 +147,13 @@ export const customersService = { .then((r) => r.data); }, + /** Onboarding-phase edit history for a company (version history), newest first. */ + revisions(companyId: string): Promise { + return apiClient + .get(URL_CONSTANTS.COMPANIES.REVISIONS(companyId)) + .then((r) => r.data); + }, + /** Approve a pending change request — applies the proposed changes. */ approveChangeRequest(id: string): Promise { return apiClient