# Task 0242 investigation — invoice 1115903 ## Finding The invoice was valid when entered. It was later corrupted by an edit to the petty-cash expense created during its credit-card payment workflow. The source AP invoice had two details totaling 1,247.77. Credit-card payment created a petty-cash deposit and generated CCI invoice. Each original petty-cash item deliberately retained `ap_invoice_detail_id` pointing at the source AP invoice detail so payment allocation could verify the parent invoice. On August 12, the two expense lines were replaced by one combined line. The save controller treated the absent old item IDs as deletions and called `PettyCashDepositItem#destroy_with_ap_invoice_detail`. That method checked whether the deposit's generated CCI invoice was paid, but then destroyed the detail from `self.ap_invoice_detail`. In this data shape that detail belonged to the already paid parent invoice, not the generated CCI invoice. Result: - both parent invoice details were destroyed; - parent invoice amount callbacks reduced 1,247.77 to zero; - the two old CCI details were not removed; - a new combined CCI detail was added, doubling the CCI amount to 2,495.54; and - the parent detail financial transactions were reversed by destroy callbacks. The AP page then hit a separate type bug. `ApInvoice#written_off_entries` used `flatten!`; for an invoice with no details that returns `nil`. The React `Matches` component called `reduce` on the resulting null value and crashed. The null caused the visible crash, while the earlier cross-invoice deletion caused the data loss. ## Ownership failure The core model assumption is false: a petty-cash item's `ap_invoice_detail_id` is not always owned by `petty_cash_deposit.ap_invoice`. During credit-card payment of an existing invoice, it can intentionally point to a source parent detail. Update and destroy code must distinguish provenance from ownership before mutating the referenced row. Checking only whether the generated CCI invoice is paid also misses the actual business lock. The incident deposit's generated invoice was unpaid, while the source invoice linked through `ap_invoices_petty_cash_deposits` was paid by credit card and had processed payments. ## Repair decision The [repair SQL](../repairs/task-0242-repair-ap-invoice-1115903-simple.sql) preserves the user's August 12 combined expense line and its finalized financial reconciliation. It restores the two original parent details, un-reverses their original financial transactions, removes the two obsolete CCI details that the edit should have replaced, reverses only those obsolete unreconciled CCI transactions, and corrects all three stored amounts to 1,247.77. The script spans the `financial` and `purchasing` databases, so its two plain SQL transactions must be run separately: financial first, purchasing second. This artifact does not authorize production execution. ## Code recommendation Use both protections: 1. Lock financial petty-cash edits/deletion after the source AP invoice has a processed credit-card allocation. The user must reverse that payment first. 2. Independently reject any item update/delete whose referenced AP detail is not owned by the deposit's generated invoice. Also normalize `written_off_entries` to an array in Ruby and defensively in React. See the [plan](../plans/task-0242-petty-cash-cci-detail-ownership-plan.md) and [workflow diagram](../diagrams/task-0242-petty-cash-cci-detail-ownership/workflow.html). ## Scope check A read-only query found other zero-detail invoices, including a small number marked as credit-card paid, but the evidence does not establish that they share this exact failure. Do not bulk-repair them from this incident script. Investigate each record separately or build a validated repair report before any broader backfill.