Skip to content

intent: settlements - deleting the CustomerPayment leaves its allocation rows and the invoice PAID (no listener on the payment's -deleted topic) #7061

Description

@delchev

Observed

BusinessIntents base-sales-invoices + base-customer-payments on the 14.46.0 line (staging, 2026-09-06), the settlements: block below. Twice, with a fresh payment each time:

  1. Create a CustomerPayment (Exadel / Acme / EUR, 1 200). AutoAllocateOnPayment allocates it to the oldest open invoice: SalesInvoiceCustomerPayment row created, invoice → PAID, DisplacedStatus = 3 (ISSUED). Correct.
  2. Delete the CustomerPayment (UI trash → confirm; DELETE .../CustomerPaymentController/3 → 200, subsequent GET → 404).
  3. The allocation row survives: GET .../SalesInvoiceCustomerPaymentController still returns {Id: 5, SalesInvoice: 4, CustomerPayment: 3, Amount: 1200} and the invoice stays PAID, Paid 1 200, Balance 0, forever. The invoice's payments panel renders the orphan as a bare id (Customer Payment: 3, no date, no number).

This is the QA-report scenario "a deleted payment leaves the invoice PAID" (the six 2026-09 no-brainers). #7016 / #7022 modelled it as deleting the allocation and fixed that path (verified on the same instance: deleting the orphan row above does relinquish PAID → ISSUED and clears DisplacedStatus). The path a user actually takes - delete or void the payment - was never covered.

Cause

SalesInvoiceCustomerPayment.CustomerPayment is a cross-model reference (model: customer-payments), so there is no FK and no database cascade, and the generated gen/events of the owning model contain listeners only for its own child topics (...-SalesInvoiceCustomerPayment-deleted roll-ups) and for the payment's -created / -updated topics (AutoAllocateOnPayment, AutoAllocateOnPaymentUpdated). Nothing listens on customer-payments-CustomerPayment-CustomerPayment-deleted. The payment side cannot do it either: it does not own the junction rows.

Expected

The settlements: generator emits a third handler, AutoAllocateOnPaymentDeleted, bound to the payment's -deleted topic, that deletes every junction row of that payment through the generated repository so the existing RollupOnDelete handlers fire and the parent relinquishes PAID/PARTIAL via the #7022 path. Same primitive as release() in AutoAllocateOnPaymentUpdated with pot = everything.

Related, same block (lower priority - a UX trap, not data corruption)

Deleting a single allocation row while its payment still exists is silently undone: the delete shrinks CustomerPayment.allocated (roll-up #6930) → the payment's -updated topic fires → AutoAllocateOnPaymentUpdated finds the pot and re-allocates the same amount to the same invoice within a second (row id 3 → 4 → …). The UI shows the row vanish and reappear on reload; the server answered 200. Auto-settlement is by design, but with the bug above there is then no working way to reverse a payment except correcting its amount. Worth a decision: either the junction delete should mark the amount as "released, do not re-allocate", or the panel should not offer a delete the settlement will immediately revert.

settlements:
  - { name: autoAllocate, junction: SalesInvoiceCustomerPayment, invoice: SalesInvoice, payment: CustomerPayment,
      amount: amount, total: payable, paid: paid, pot: amount, order: date,
      match: [Customer, Currency], status: Status, payableStatuses: [3, 4, 6] }

Found by the BusinessIntents release gate (aws#32) on the 14.46.0 train, 2026-09-06.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions