Files
Adam Daley e8f375db3b Invoice immutability, issuing model, and audit journal (#4423)
* Add invoice cancel and immutability flow

Introduce admin invoice cancel/void support with new before/after events, UI actions, and API/service handling for approved unpaid invoices. Consolidate legacy invoice edit/delete toggles into a single `invoice_immutability` setting, tighten issued-invoice edit rules, update expired unpaid invoice cleanup to void issued invoices instead of deleting them.

* Rename invoice approval flow to issuing

* Refine invoice draft and void workflows

* Add invoice journal history tracking

Add an invoice event journal to capture lifecycle changes with trimmed snapshots, expose it through the admin API, and replace the invoice billing-parties tab with a history view and JSON download.

This also adds upgrade patches and a backfill so existing installs get a baseline journal entry for invoices that predate the feature, plus tests and schema snapshot updates for the new entity.

* Fix client page rendering and JSON encoding

Move client related-list queries to the tab scope so shared footer blocks can access them under Twig strict_variables, and add a regression test for the manage page render path.

Also harden JSON output in invoice/update patch flows by throwing on journal encoding failures and substituting invalid UTF-8 in legacy invoice snapshots so one bad row does not block updates.

* Harden invoice journal follow-ups: consumed-keys changes, activity log lines, translatable labels, E2E coverage

* Fix datepicker clear button pushing calendar icon over the field value

* Use DBAL 4.5 introspectTableByUnquotedName in invoice patch helpers

* Harden invoice issuing and payment recording

Remove the orphaned tax_setup_eu endpoint left behind by #1826, whitelist
draft statuses, guard first issuance to unpaid drafts, snapshot auto-issued
funds invoices, and record the paid journal after the admin paid_at override

* Harden invoice money paths

Manual refunds no longer log, journal, or dispatch as performed: the skip
is recorded distinctly. payInvoiceWithCredits returns the real outcome and
the batch variant counts covered/skipped/failed with per-invoice warnings.
Processed transactions freeze their history fields, refuse deletion, and
clean up orphaned balance rows on unprocessed deletes; transaction linkage
refuses canceled/refunded/replaced invoices. processInvoice rejects paid
invoices, and the expiry cron rechecks deletability under the row lock.
rmByClient is documented as intentional erasure and pinned by test.

* Remove dead legacy IPN pipeline, serialize transaction processing

The legacy process() pipeline (_parseIpnAndApprove, _debit/_refund/_subscribe/
_unsubscribe, amount/currency validation, debitTransaction) had no production
callers: live processing runs through gateway adapters via preProcess. Delete
~350 lines of unreachable payment code plus its tests and the now-unused
findOneProcessedByTxnId finder, and wire the atomic claimForProcessing into
preProcess, createAndProcess, and processAndCatchErrors so concurrent
deliveries converge on a single processor.

* Expand invoice coverage and finish consistency sweep

Number-series fallback paths, non-default currency payment and missing-rate
refusal, tax-rate effect on totals, batch activation isolation and batch
delete fail-fast, client pay/PDF controller auth matrix, expiry-cron
disabled and isolation paths, zero-total and already-paid email skips, and
the reissue replacement email variable. Billing-channel debug lines and the
prepare instant-issue warning now carry structured context.

* Pin no-gap guarantee for rolled-back number reservations

* Document journal retention, log base-currency rewrites on issued invoices

* Record offline refund documents in manual refund mode

Manual mode no longer returns null while the UI reports success: refunding a
paid invoice now generates a credit-note-series document marked as an offline
refund (note plus journal flag), so money moved outside FOSSBilling leaves a
paper trail. Unknown refund-logic values fail loudly instead of silently
skipping.

* Complete GDPR erasure: anonymize transactions, cascade support cleanup, refuse deletion with active services

Detached transactions are anonymized in place on client erasure (IP, raw
IPN payloads, notes scrubbed; financial record kept). Support erasure now
removes notes, messages, and edit history instead of orphaning them.
Client deletion is refused while provisioned service rows exist, forcing
explicit cancellation first. Also quotes the reserved  table
name portably: invoice deletion and the transaction counter/pickup were
syntax errors on SQLite.

* Add live client-erasure walkthrough test

* Bypass rate limiter in development, fix antispam E2E state pollution

* Remove stale Invoice draft comments

* Fix spellcheck findings: rename UPDATEABLE_FIELDS, reword coinages

* Tolerate missing service tables in client-deletion gate

* Exercise promo add-remove on drafts in E2E

* Apply Rector suggestions in invoice tests

* Address PR review: single-claim transactions, frozen seller snapshot, issuance guard

* Trim repeated no-reclaim comments in Stripe adapter

* Run schema sync before journal backfill on upgrade

* Address second review round: claim quoting, patch recovery, bounded journal healing

* Serialize journal baseline writes with a per-invoice claim

* Surface instant-issue failures instead of returning a silent draft
2026-09-28 18:51:24 +01:00
..

Demo seed

Repeatable, additive test-data seeder for a FOSSBilling instance. All records use a Demo prefix so they can be found and removed with the batch_delete endpoints.

Use

APP_URL=https://fossbilling.ddev.site TEST_API_KEY=... php tools/demo-seed/demo-seed.php
APP_URL=https://fossbilling.ddev.site TEST_API_KEY=... php tools/demo-seed/demo-seed.php --dry-run
APP_URL=https://fossbilling.ddev.site TEST_API_KEY=... php tools/demo-seed/demo-seed.php --limit-clients=5 --limit-orders=5 --limit-tickets=5

The admin API key must be provided through TEST_API_KEY; command-line arguments can be visible to other users on the system.

Writes tools/demo-seed/manifest.json with every created ID plus the shared test passwords (DemoPass123! for clients and demo staff).

Export / reuse

ddev export-db --file=.ddev/fossbilling-demo-<date>.sql.gz
ddev import-db --file=.ddev/fossbilling-demo-<date>.sql.gz

The SQL dump is the portable snapshot; this script is the repeatable recipe. Re-running skips existing foundation/catalog records (emails/titles/codes), but appends new orders, invoices and tickets each time.

Safety

  • Additive only; never wipes.
  • send_welcome_email=0 for clients.
  • Only offline-safe gateways (Custom, ClientBalance) are enabled.
  • Dummy hosting server uses TEST-NET 192.0.2.10 + Custom manager.