FIX never unpay an invoice that carries real payments - #30
Pichinov-Jose wants to merge 2 commits into
Conversation
setPaidFlag(false) reopened closed invoices even when they carried real, sometimes bank-reconciled, payment entries — the e-commerce source's opinion of the payment state was allowed to outrank recorded money. Combined with a source that mis-reports paid flags, invoices flip-flopped between paid and unpaid on every sync. Skip the unpay when payment lines exist, and log a warning instead. Same philosophy as the existing protections on the payments list side. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Two adaptations were needed to run this on a live 2.23.3 install — worth folding into the branch, because the diff as written does not execute. 1.
2. This trait also serves supplier invoices.
Counting customer payment links by a supplier invoice id matches unrelated rows — the two id sequences are independent, so a supplier invoice would be kept paid because some unrelated customer invoice happens to share its rowid. That is the opposite of the intent. What is running here: private function setPaidFlag($data): bool
{
global $user, $db;
...
} else {
$isSupplier = is_a($this, Local::CLASS_SUPPLIER_INVOICE);
$sqlNbPay = "SELECT COUNT(*) as nb FROM ".MAIN_DB_PREFIX
.($isSupplier ? "paiementfourn_facturefourn" : "paiement_facture")
." WHERE ".($isSupplier ? "fk_facturefourn" : "fk_facture")." = ".((int) $this->object->id);
$resNbPay = $db->query($sqlNbPay);
$objNbPay = $resNbPay ? $db->fetch_object($resNbPay) : null;
if ($objNbPay && ((int) $objNbPay->nb) > 0) {
dol_syslog(...);
return true;
}
//====================================================================//
// Set UnPaid using Dolibarr Function
...
Deployed in production on a Dolibarr 23.0.0 shop on 12/09. Verified against the case it is meant to protect: 173 orders change their announced payment status under SplashSync/Wordpress#19, 80 of them have a Dolibarr invoice, 59 are closed — and all 59 carry at least one payment, so this guard covers every one of them. Without it, #19 alone reopens 136 refunded invoices. Also opened #31, the symmetric half: never close an invoice the recorded payments do not cover. The two touch different branches of the same |
The object classes carry no $db property — database access in this module goes through global $db, as the neighbouring traits do. And the trait can serve supplier invoices, whose settlements live in paiementfourn_facturefourn. Spotted during a peer review of the deployed patch. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Correction to my own note above, on point 2. I checked the claim properly after @seriousupgrade pushed back, and they are right: the supplier-invoice concern does not apply here. I generalised from the Point 1 stands unchanged and is the one that matters: Apologies for the noise on the second point — better to have it on the record than to leave an incorrect claim standing in the thread. |
Bug
setPaidFlag(false)(InvoiceMainTrait) reopens a closed invoice whenever the source announces not paid — even when the invoice carries real, sometimes bank-reconciled, payment entries. The e-commerce side's opinion of the payment state is allowed to outrank recorded money.Combined with a source that mis-reports its paid flag (see SplashSync/Wordpress#19:
ispaidderived from the delivery workflow), invoices flip-flop between paid and unpaid on every sync, and accounting states drift.Fix
Before unpaying, count the invoice's payment lines: if any exist, keep the invoice paid and log a warning. Mirrors the philosophy of the existing payments-list protections (a source cannot destroy richer local payment knowledge).
🤖 Generated with Claude Code