mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-30 11:08:12 +00:00
Backoffice can now "Request changes" on a pending settings change request without rejecting it outright: a new ChangesRequested status keeps the row open so the customer's next edit appends into the same request instead of starting a fresh cycle, and the reviewer's note persists across that round instead of being cleared on resubmit. Version History and Review History (previously two separate, differently-shaped lists) are merged into one chronological timeline under a new History tab, including document changes shown as a real previous-vs-current diff (both files openable). Bug fixes surfaced while wiring this up: - Replacing a single-file document slot left the old file live alongside the new one instead of retiring it (customer settings + onboarding uploads). - The "previous" file in a document diff 404'd once superseded — the preview route now also matches soft-deleted records. - A document replace was recorded twice in the timeline (once at upload, once again at change-request approval).
114 lines
4.2 KiB
TypeScript
114 lines
4.2 KiB
TypeScript
import { BaseEntity } from "@edr/api-common";
|
|
import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm";
|
|
import { Company } from "./company.entity";
|
|
|
|
/**
|
|
* Lifecycle of a customer's proposed profile change. Edits made on the portal
|
|
* settings page by an already-approved company are staged here (not written to
|
|
* the live Company row) until a backoffice reviewer resolves it:
|
|
* - Approved — the snapshot is applied to the live Company row.
|
|
* - Rejected — terminal for this row; the customer's next edit starts a fresh one.
|
|
* - ChangesRequested — soft: the row stays open with the reviewer's note attached,
|
|
* so the customer's next edit is appended (merged) into this SAME row instead
|
|
* of starting a new cycle.
|
|
*/
|
|
export enum ChangeRequestStatus {
|
|
Pending = "pending",
|
|
Approved = "approved",
|
|
Rejected = "rejected",
|
|
ChangesRequested = "changes_requested",
|
|
}
|
|
|
|
/**
|
|
* A single staged business-license change on one company profile, awaiting
|
|
* review. `add` → a new file was uploaded under the pending code and becomes
|
|
* live on approval; `remove` → an existing live file is deleted on approval.
|
|
* A "replace" is recorded as a `remove` of the old file plus an `add` of the
|
|
* new one. `fileId` is the FileRecord id the op targets.
|
|
*/
|
|
export interface LicenseChangeIntent {
|
|
profileId: string;
|
|
op: "add" | "remove";
|
|
fileId: string;
|
|
/** File name, snapshotted for the backoffice review screen. */
|
|
fileName?: string;
|
|
}
|
|
|
|
/**
|
|
* A staged change to a company-level document, awaiting review. Same semantics
|
|
* as {@link LicenseChangeIntent} but keyed by the document's FileRecord `code`
|
|
* (e.g. `poa_delegation_letter`) rather than a profile: `add` → uploaded under
|
|
* the pending code, promoted to `code` on approval; `remove` → a live file that
|
|
* is deleted on approval. A replace is a `remove` plus an `add`.
|
|
*/
|
|
export interface DocumentChangeIntent {
|
|
op: "add" | "remove";
|
|
fileId: string;
|
|
/** The live FileRecord code this op targets (the upload setting's fileKey). */
|
|
code: string;
|
|
/** File name, snapshotted for the backoffice review screen. */
|
|
fileName?: string;
|
|
}
|
|
|
|
/** File references staged alongside a change request (documents/licenses). */
|
|
export interface ChangeRequestDocuments {
|
|
/**
|
|
* FileRecord ids uploaded against the company while this request was open.
|
|
* These go live immediately — only their ids are recorded, for the reviewer.
|
|
* Contrast `documentChanges`, which stages the file behind the pending code.
|
|
*/
|
|
documentFileIds?: string[];
|
|
/** Staged per-profile business-license add/remove intents. */
|
|
licenseChanges?: LicenseChangeIntent[];
|
|
/** Staged company-level document add/remove intents (e.g. the PoA letter). */
|
|
documentChanges?: DocumentChangeIntent[];
|
|
}
|
|
|
|
@Entity({ schema: "freight", name: "company_change_request" })
|
|
@Index(["companyId"])
|
|
@Index(["status"])
|
|
export class CompanyChangeRequest extends BaseEntity {
|
|
@Column({ name: "company_id", type: "uuid" })
|
|
companyId!: string;
|
|
|
|
@ManyToOne(() => Company, { onDelete: "CASCADE" })
|
|
@JoinColumn({ name: "company_id" })
|
|
company?: Company;
|
|
|
|
/**
|
|
* Proposed profile field values, shaped as `Partial<UpdateProfileDto>`. Covers
|
|
* the Company / Contact / General Manager / Power-of-Attorney tabs (contact/GM/
|
|
* PoA fields land in `Company.attributes` on approval).
|
|
*/
|
|
@Column({ name: "snapshot", type: "jsonb" })
|
|
snapshot!: Record<string, any>;
|
|
|
|
/** Staged document/license file references (see {@link ChangeRequestDocuments}). */
|
|
@Column({ name: "documents", type: "jsonb", nullable: true })
|
|
documents?: ChangeRequestDocuments | null;
|
|
|
|
@Column({
|
|
name: "status",
|
|
type: "varchar",
|
|
length: 20,
|
|
default: ChangeRequestStatus.Pending,
|
|
})
|
|
status!: ChangeRequestStatus;
|
|
|
|
/** Backoffice reviewer's rejection note. */
|
|
@Column({ name: "note", type: "text", nullable: true })
|
|
note?: string | null;
|
|
|
|
@Column({ name: "submitted_by", type: "uuid", nullable: true })
|
|
submittedBy?: string | null;
|
|
|
|
@Column({ name: "submitted_at", type: "timestamptz", nullable: true })
|
|
submittedAt?: Date | null;
|
|
|
|
@Column({ name: "reviewed_by", type: "uuid", nullable: true })
|
|
reviewedBy?: string | null;
|
|
|
|
@Column({ name: "reviewed_at", type: "timestamptz", nullable: true })
|
|
reviewedAt?: Date | null;
|
|
}
|