feat(billing): show the buyer's trade name on invoice documents

An invoice is billed to one company profile, and that profile's eTrade
licence is usually a different business from the one the company
registered under — so the buyer's name alone does not say which business
was billed.

Both document paths gain a row: the shared invoice/receipt model reads it
off the already-loaded `companyProfile` relation, and the warehouse fee
invoice joins `company_profiles` through the booking.

`sameCompanyName` suppresses the row when it merely repeats the buyer
name, which is the common case. It compares loosely because eTrade spells
one legal suffix three ways (PLC / P L C / PRIVATE LIMITED COMPANY) and
pads names with double spaces; it decides whether a row is worth printing
and nothing else.

The EIMS buyer `LegalName` is deliberately untouched — a MoR filing
carries the registered entity, same rule as the seller side.
This commit is contained in:
Nathnael
2026-08-27 09:29:25 +00:00
parent 604710bd25
commit ab5e7e6db6
4 changed files with 96 additions and 1 deletions

View File

@@ -22,6 +22,7 @@ import { InvoiceLine } from "../billing/entities/invoice-line.entity";
import { PayInvoiceDto as GatewayPayInvoiceDto } from "../billing/dto/pay-invoice.dto";
import {
InvoiceDocumentModel,
sameCompanyName,
InvoiceDocumentService,
} from "../billing/documents/invoice-document.service";
import { NotificationsService } from "../notifications/notifications.service";
@@ -71,6 +72,11 @@ const ACTIVE_STATUSES: Freight.InvoiceStatus[] = [
export interface InvoiceDocumentDetails {
bookingReference: string | null;
customerName: string | null;
/**
* Trade name of the eTrade licence the billed company profile operates as.
* Null when nothing is attached, or for a company with no eTrade record.
*/
customerTradeName: string | null;
inventoryReference: string | null;
inventoryInfo: string | null;
inventoryStatus: string | null;
@@ -745,6 +751,17 @@ export class WarehouseInvoiceService {
},
{ label: "Booking reference", value: invoice.bookingReference ?? null },
{ label: "Customer", value: invoice.customerName ?? null },
// Which of the TIN's eTrade businesses was billed. Omitted when it just
// repeats the customer name — see sameCompanyName.
...(invoice.customerTradeName &&
!sameCompanyName(invoice.customerTradeName, invoice.customerName)
? [
{
label: "Customer trade name",
value: invoice.customerTradeName,
},
]
: []),
{
label: "Inventory reference",
value: invoice.inventoryReference ?? null,
@@ -795,6 +812,7 @@ export class WarehouseInvoiceService {
const [row] = await this.dataSource.query(
`SELECT b.reference AS "bookingReference",
company.name AS "customerName",
cp.etrade_business->>'tradeName' AS "customerTradeName",
COALESCE(inv.release_order_reference, b.reference) AS "inventoryReference",
inv.status AS "inventoryStatus",
inv.release_date AS "releaseDate",
@@ -812,6 +830,7 @@ export class WarehouseInvoiceService {
FROM freight.warehouse_inventory inv
LEFT JOIN freight.bookings b ON b.id = inv.booking_id AND b.deleted_at IS NULL
LEFT JOIN freight.companies company ON company.id = b.company_id
LEFT JOIN freight.company_profiles cp ON cp.id = b.company_profile_id AND cp.deleted_at IS NULL
LEFT JOIN freight.containers container ON container.id = inv.container_id AND container.deleted_at IS NULL
LEFT JOIN freight.booking_container booking_container ON (
booking_container.booking_id = b.id
@@ -837,6 +856,7 @@ export class WarehouseInvoiceService {
return {
bookingReference: row?.bookingReference ?? null,
customerName: row?.customerName ?? null,
customerTradeName: row?.customerTradeName ?? null,
inventoryReference: row?.inventoryReference ?? null,
inventoryInfo: row?.inventoryInfo ?? null,
inventoryStatus: row?.inventoryStatus ?? null,