Skip to content

I 013


tid: I-013 title: Service Invoice register XLSX β€” Invoice Number hyperlink dies within ~1 hour of export status: fixed source: Records β†’ Receipts: Service Invoice β†’ Export β†’ click on Invoice Number β€” owner report 2026-06-25 area: accounting-records task: T-114 created: 2026-06-25 updated: 2026-06-25


Symptom

The owner exported a Service Invoice register XLSX and clicked the hyperlink attached to the Invoice Number β€” it didn't open.

Owner, 2026-06-25 (verbatim): "I have just exported this file (From Records > Receipts: Service Invoice > Export), and the hyperlink URL attached to the invoice number doesn't work. Can you help me look into why?"

Root cause (proven against the uploaded export)

The exported workbook (ERL_Register_Invoice_Service_20260625.xlsx, 71 hyperlinks) carries two URL shapes:

Count URL shape Lifetime
25 /api/accounting/receipts/<id>?redirect=pdf (same-origin) durable β€” no expiry, auth at click time
45 https://storage.googleapis.com/aote-pms.firebasestorage.app/... with Expires= 1 hour β€” all 45 share the SAME Expires=1782357996
1 (blank) n/a

The 45 short-lived URLs come from vendorInvoiceFeed.resolveVendorPdfUrl calling getReceiptDownloadUrl for legacy Firebase-Storage records (1-hour signed GCS URL). The export wrote that ephemeral URL verbatim into the workbook. Within the first hour: works. After: dead. The owner's click landed on one already past expiry.

curl confirmed the GCS URL itself is currently live (HTTP/2 200, application/pdf) β€” it's not bad signing or wrong bucket; it's "short-lived URL in a long-lived artifact."

Resolution

Structural β†’ escalated to T-114 (fixed there): export now writes the same-origin streaming URL for every record, regardless of underlying storage. The streaming endpoint resolves Drive bytes or mints a fresh GCS signed URL at click time, so workbooks never carry expiring URLs.

Sibling not addressed

The Receipts XLSX export uses a separate File column (Click Here linking to r.fileUrl) and would carry the same class of bug for any legacy-GCS receipt. Deliberately not changed in this PR to keep the diff focused on the reported symptom β€” but if the receipts export shows the same problem, extend the same accessor pattern (route through /api/accounting/receipts/<recordId>?redirect=pdf).