mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-26 18:42:49 +00:00
feat(billing): search invoices by PNR and transaction ref
Both are numbers a customer or a provider support desk quotes back, so they belong in the free-text box rather than behind a filter pill. The transaction id and merchant order id are plain ORs — the payment alias is already joined by every caller of applyInvoiceFilters. The PNR folds into the existing booking EXISTS block instead of adding a second subquery, so it inherits that block's correlation and also matches warehouse-, first-mile- and last-mile-sourced invoices, not just booking-sourced ones. bk is promoted to alwaysJoin now that the export's scope() references it.
This commit is contained in:
@@ -351,20 +351,24 @@ export class BillingService {
|
||||
qb.andWhere("invoice.balanceAmount > 0 AND invoice.dueAt < now()");
|
||||
}
|
||||
if (filter.search) {
|
||||
// Searches what the row actually shows: its number, who it bills, and
|
||||
// the source record behind it (booking reference, GRN, shipping line).
|
||||
// Searches what the row actually shows: its number, who it bills, the
|
||||
// source record behind it (booking reference, PNR, GRN, shipping line)
|
||||
// and the payment references a customer or a provider support desk would
|
||||
// quote back — the gateway transaction id and our merchant order id.
|
||||
// The raw `sourceId` stays matchable so a pasted UUID still resolves.
|
||||
// Requires the `company` alias — every caller of this joins it.
|
||||
// Requires the `company` and `payment` aliases — every caller joins both.
|
||||
qb.andWhere(
|
||||
`(invoice.invoiceNumber ILIKE :search
|
||||
OR invoice.sourceId ILIKE :search
|
||||
OR company.name ILIKE :search
|
||||
OR payment.transactionId ILIKE :search
|
||||
OR payment.merchantOrderId ILIKE :search
|
||||
OR EXISTS (
|
||||
SELECT 1 FROM freight.bookings b
|
||||
LEFT JOIN freight.warehouse_inventory wi ON wi.booking_id = b.id
|
||||
LEFT JOIN freight.first_mile fm ON fm.booking_id = b.id
|
||||
LEFT JOIN freight.last_mile lm ON lm.booking_id = b.id
|
||||
WHERE b.reference ILIKE :search
|
||||
WHERE (b.reference ILIKE :search OR b.pnr_code ILIKE :search)
|
||||
AND (b.id::text = invoice.source_id
|
||||
OR wi.id::text = invoice.source_id
|
||||
OR fm.id::text = invoice.source_id
|
||||
|
||||
@@ -41,7 +41,7 @@ export const invoicesDataset: ExportDataset = {
|
||||
// other way throws on those rows.
|
||||
{ alias: 'bk', entity: Booking, on: "bk.id::text = i.source_id AND i.source = 'booking'" },
|
||||
],
|
||||
alwaysJoin: ['c', 'p'],
|
||||
alwaysJoin: ['c', 'p', 'bk'],
|
||||
|
||||
groups: [
|
||||
{ id: 'invoice', label: 'Invoice' },
|
||||
@@ -134,7 +134,7 @@ export const invoicesDataset: ExportDataset = {
|
||||
{ key: 'hasBalance', label: 'Outstanding only', type: 'text' },
|
||||
{ key: 'overdue', label: 'Overdue only', type: 'text' },
|
||||
{ key: 'companyId', label: 'Customer', type: 'text' },
|
||||
{ key: 'search', label: 'Search invoice no. or customer', type: 'text' },
|
||||
{ key: 'search', label: 'Search invoice no., customer, PNR or transaction ref', type: 'text' },
|
||||
],
|
||||
|
||||
defaultSort: { key: 'issuedAt', dir: 'DESC' },
|
||||
@@ -171,7 +171,18 @@ export const invoicesDataset: ExportDataset = {
|
||||
if (params.overdue === 'true') qb.andWhere('i.balance_amount > 0 AND i.due_at < now()');
|
||||
if (params.companyId) qb.andWhere('i.company_id = :companyId', { companyId: params.companyId });
|
||||
if (params.search) {
|
||||
qb.andWhere('(i.invoice_number ILIKE :search OR c.name ILIKE :search)', { search: `%${params.search as string}%` });
|
||||
// Same reach as the list page's search box, minus the source-record
|
||||
// lookups it does with correlated subqueries: number, customer, the PNR
|
||||
// the customer pays against, and the payment references support desks
|
||||
// quote back.
|
||||
qb.andWhere(
|
||||
`(i.invoice_number ILIKE :search
|
||||
OR c.name ILIKE :search
|
||||
OR bk.pnr_code ILIKE :search
|
||||
OR p.transaction_id ILIKE :search
|
||||
OR p.merchant_order_id ILIKE :search)`,
|
||||
{ search: `%${params.search as string}%` },
|
||||
);
|
||||
}
|
||||
// ACL: invoices.source_id is a varchar pointer at the originating booking.
|
||||
applyBookingRefDirectionScope(qb, 'i.source_id', directions);
|
||||
|
||||
@@ -370,7 +370,7 @@ export default function InvoicesPanel() {
|
||||
<FilterBar
|
||||
defs={INVOICE_FILTER_DEFS}
|
||||
controls={controls}
|
||||
searchPlaceholder="Search invoice, customer, booking ref, GRN or shipping line…"
|
||||
searchPlaceholder="Search invoice, customer, booking ref, PNR, transaction ref, GRN or shipping line…"
|
||||
sortOptions={SORT_OPTIONS}
|
||||
viewId="invoices"
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user