FIX never bring a validated invoice back to draft when it must stay unalterable - #38
Open
Pichinov-Jose wants to merge 1 commit into
Open
Pichinov-Jose wants to merge 1 commit into
Pichinov-Jose wants to merge 1 commit into
Conversation
…nalterable setStatusFields() calls setStatusDraft() unconditionally: "Unknown" and "PaymentDraft" set draft "whatever", and "PaymentDue" sets a paid or canceled invoice back to draft before validating it again. The connector then rewrites the lines, deletes the payment and validates again under the same reference. A source reports "Unknown" for any status it does not map (a custom WooCommerce status such as a partial-refund one is enough). On a real shop, a discount typed on a completed order turned an invoice validated and paid at 250.00 into 137.50 under the same reference, and its card payment was replaced by 137.50 — while the gateway had captured 250.00. The unalterable log recorded twenty invoices validated again with another amount. With the unalterable log (blockedlog) active, mandatory in France, a validated invoice must never be edited; on an invoice carrying real payments, rewriting it drops recorded money. A change made in the source after invoicing must become a credit note. Before any back-to-draft transition, refuse when blockedlog is active or when the invoice carries payment lines (supplier payment table handled). The source request is ignored and logged; a line rewrite that follows then fails on the validated status, so the sync reports it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This was referenced Sep 13, 2026
Author
|
Correction to the description above: I claimed that, once the back-to-draft is refused, a following line rewrite would fail on Dolibarr's validated-status checks. It does not. The connector writes lines through
The payments were untouched. #40 closes that gap: the lines of an invoice validated when loaded, and still not a draft when its lines arrive, are kept as issued. This PR and #40 should be reviewed together. The description is updated accordingly. 🤖 Generated with Claude Code |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Bug
StatusTrait::setStatusFields()callssetStatusDraft()unconditionally:Unknown/PaymentDraft→ "Whatever => Set Draft";PaymentDueon a paid or canceled invoice → "If Already Paid => Set Draft", then validate again.Back to draft, the connector rewrites the lines, deletes the payment and validates again under the same reference. A source reports
Unknownfor any status it does not map — a custom WooCommerce status (e.g. a partial refund one) is enough.Seen in production (Dolibarr 23, Splash 2.23.3, blockedlog active): a discount typed on a completed WooCommerce order turned an invoice validated and paid at 250.00 into 137.50, same reference; its card payment was deleted and replaced by a 137.50 one, while the gateway had actually captured 250.00. The unalterable log shows 20 invoices validated again with another amount.
With the unalterable log active — mandatory in France — a validated invoice must never be edited. On an invoice carrying real payments, rewriting it silently drops recorded money. A change made in the source after invoicing must become a credit note.
Mapping every source status is not a mitigation. A second production instance maps all its custom WooCommerce statuses, which closes the
Unknownpath — yet its unalterable log still shows invoices rewritten by the Splash webservice user (validated at one amount by a human, then validated again at a lower amount by Splash). The other two paths stay open whatever the mapping:on-hold/failed→PaymentDraft(unconditional draft), and thePaymentDuefamily on an already paid invoice. The guard has to live in the Dolibarr side.Fix
Before any back-to-draft transition (both paths above),
isDraftForbidden()refuses when:blockedlogmodule is active, orpaiement_facture, orpaiementfourn_facturefournfor supplier invoices, same idiom as FIX never unpay an invoice that carries real payments #30).The source request is ignored and logged (
dol_syslogwarning + Splash warning). Draft invoices and validated invoices without payments on instances without blockedlog behave exactly as before.This guard alone does not stop the line rewrite. The connector writes invoice lines with
FactureLigne::update()/insert()andupdate_price(), which do not check the invoice status. Once the back-to-draft is refused, the source's lines are still written into the validated invoice, with no trace in the unalterable log. That gap is closed by #40, which should go together with this PR. (Corrected on 13/09: an earlier version of this description said the rewrite would fail on Dolibarr's validated-status checks. A production test showed it does not.)Complements #30 (never unpay an invoice with payments) and #31 (never close an uncovered invoice): same principle — recorded money and issued documents outrank the source's opinion.
Tests
Run against real invoices, in a rolled-back transaction, Splash triggers disabled:
PaymentDraftPaymentDraftPaymentDraftPaymentDueDeployed in production on the instance where the issue was found.
🤖 Generated with Claude Code