CluPilotCloud/app/Services/Billing
nexxo ab4b0311d4
tests / pest (push) Failing after 11m24s Details
tests / assets (push) Successful in 21s Details
tests / release (push) Has been skipped Details
Copy every invoice to the archive when it is issued, and notice when that fails
The application knows nothing about NFS and should not. It writes to a
directory; whether that is a local disk, a bind mount or a NAS over NFS 4.1
belongs to the mount. No protocol library, the target changes without code
changing with it, and the whole thing is testable against a temp directory.

The finding that shaped it: mkdir -p on a path whose mount is NOT there
succeeds. It creates the empty directory beneath the mountpoint, writes the
invoice onto the container's own disk and reports success — so an unmounted NAS
would quietly collect invoices locally while every check said it worked. An
archive that silently is not the archive is worse than none. The root is
therefore never created, only used: a missing root means a missing mount, and
that is a failure. Only the year subdirectory is created, inside a root already
proven to exist.

Written to a .part and renamed. A rename within one filesystem is atomic, so
whatever watches that folder never sees a truncated PDF and takes it for a
finished invoice. The size is read back afterwards rather than the write being
trusted: over a network mount a short write can report success, and an archive
of truncated files looks exactly like one that worked.

Queued, not done during the request. A network mount can block for its whole
timeout, and an invoice must not fail to be issued because a NAS is rebooting.
Dispatched after the commit, or a worker could look for an invoice that has not
landed yet.

And copy-on-issue is not enough on its own: an invoice issued while the office
connection was down never arrives, the queue eventually gives up, and nobody
opens an archive to check whether last Tuesday is in it. An hourly sweep
re-queues whatever is still missing, the failure is recorded on the invoice
rather than only in a log, and the Finance page says how many are outstanding.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-29 02:32:08 +02:00
..
AddonCatalogue.php feat(billing): a proof register, and modules frozen at their booked price 2026-07-26 13:07:34 +02:00
DowngradeCheck.php Measure availability, and let a customer move down again 2026-07-27 16:41:15 +02:00
InvoiceArchive.php Copy every invoice to the archive when it is issued, and notice when that fails 2026-07-29 02:32:08 +02:00
InvoiceDocument.php Render an invoice, with the arithmetic held to integer cents 2026-07-29 01:41:46 +02:00
InvoiceMath.php Print every line at full price and take the discount off once, in euros 2026-07-29 01:59:56 +02:00
InvoiceNumbers.php Lay the foundation for self-issued invoices: series, numbers, frozen documents 2026-07-29 00:57:06 +02:00
InvoiceRenderer.php Render an invoice, with the arithmetic held to integer cents 2026-07-29 01:41:46 +02:00
IssueInvoice.php Copy every invoice to the archive when it is issued, and notice when that fails 2026-07-29 02:32:08 +02:00
PlanCatalogue.php feat(admin): a console for creating, pricing and scheduling plans 2026-07-26 12:41:51 +02:00
PlanChange.php feat(billing): Stripe owns the billing cycle, we own capability 2026-07-26 13:36:28 +02:00
SampleInvoice.php Print every line at full price and take the discount off once, in euros 2026-07-29 01:59:56 +02:00
TaxTreatment.php Decide the VAT rate in one place, before the two disagree 2026-07-29 02:03:29 +02:00