Files
edr-platform/apps/edr-freight-api/src/modules/companies/company-change-request.repository.ts
Nathnael 4f81a0bbb8 feat(freight): non-terminal change-request review + unified customer timeline
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).
2026-07-31 14:12:30 +00:00

67 lines
2.3 KiB
TypeScript

import { Injectable } from "@nestjs/common";
import { InjectRepository } from "@nestjs/typeorm";
import { In, Repository } from "typeorm";
import { BaseRepository } from "@edr/api-common";
import {
ChangeRequestStatus,
CompanyChangeRequest,
} from "./entities/company-change-request.entity";
/** Statuses that mean "still open, awaiting the customer's next edit" — Pending and ChangesRequested behave identically here, they just carry a note or not. */
const OPEN_FOR_EDIT_STATUSES = [
ChangeRequestStatus.Pending,
ChangeRequestStatus.ChangesRequested,
];
@Injectable()
export class CompanyChangeRequestRepository extends BaseRepository<CompanyChangeRequest> {
constructor(
@InjectRepository(CompanyChangeRequest)
repo: Repository<CompanyChangeRequest>,
) {
super(repo);
}
/** The company's current open request (Pending or ChangesRequested), if any — the row the next edit appends to. */
async findPendingByCompanyId(
companyId: string,
): Promise<CompanyChangeRequest | null> {
return this.repository.findOne({
where: { companyId, status: In(OPEN_FOR_EDIT_STATUSES) },
order: { createdAt: "DESC" },
});
}
/**
* The company's latest "open" request — pending (locks the customer) or the
* most recent rejected one (drives the reapply banner + prefill).
*
* Only the company's newest request may be open. A rejection is superseded the
* moment the customer resubmits: that resubmit opens a *new* request, so once
* it is approved the newest request is terminal and nothing is open — even
* though the older rejected row still sits in the table as history.
*/
async findLatestOpenByCompanyId(
companyId: string,
): Promise<CompanyChangeRequest | null> {
const pending = await this.findPendingByCompanyId(companyId);
if (pending) return pending;
const latest = await this.repository.findOne({
where: { companyId },
order: { createdAt: "DESC" },
});
return latest?.status === ChangeRequestStatus.Rejected ? latest : null;
}
async findById(id: string): Promise<CompanyChangeRequest | null> {
return this.repository.findOne({ where: { id } });
}
async findByCompanyId(companyId: string): Promise<CompanyChangeRequest[]> {
return this.repository.find({
where: { companyId },
order: { createdAt: "DESC" },
});
}
}