Repository navigation
feat: give each credit note a language (4.3.0) - #26
Merged
ThomasDaSilva merged 12 commits intoOct 19, 2026
Merged
Conversation
A credit note gets the language of its order, of its customer without an order, or the default language of the shop when it is written, and the back-office modal lets the administrator change it, numbered or not. The document is printed in that language, still within document_locales. The update fills the existing credit notes with the language of their customer, the one they were printed in until now.
Its script inserted the section after #orders_menu, an entry of the Smarty back-office that default-twig does not have, so the menu never showed. It now follows the Orders section, with the markup of the side menu.
The modal script sent that page as success-url in the request body, but the modal only read it from the query string, so a save always landed on the cart tab of the order. The page now carries the tab holding the trigger as fragment, the script reopens that tab, and only a path of the shop is followed: an absolute or protocol-relative success-url sends back to the order.
Browsers drop tabs and line breaks from an address, so "/<tab>/host" passed the filter and became the protocol-relative "//host" of another site; a trailing line break passed the "$" anchor. The success-url is now a path in printable ASCII, without backslash, anchored with the D modifier.
HTML escaping does not protect a style attribute: a status or type color that is not a hexadecimal color now falls back to grey (BadgeColor), in the side menu, the lists, the order lines and the modal.
…es only The update runs with the foreign key checks off: a customer or order pointing to a deleted language would have left a dangling lang_id.
Also say why a credit note without a language still prints in the language of its customer.
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.
A credit note has no language of its own:
CreditNoteDocumentLocaleprints it in the language its customer has atprint time. The back office cannot choose it when it writes the credit note, and a customer who changes language
changes the language of every credit note they already received.
This PR gives each credit note a language, chosen in the back-office modal. On the way it fixes four defects of the
Twig back office: the side menu entry never showed, a save landed on the cart tab of the order,
success-urlwasfollowed without any check, and badge colors reached
styleattributes unchecked.Change
Language of a credit note
credit_note.lang_id(schema.xml,thelia.sql,Config/Update/sql/4.3.0.sql), foreign key tolangwith
ON DELETE SET NULL, indexed.CreditNoteLanguage::defaultFor(): the language of the order, of the customer for a credit note without order, thedefault language of the shop otherwise.
CreditNoteListenersets it before the insert when the writer gave none, soa credit note written outside the controller (another module, the API, a command) gets one too.
turned it off since. On a new credit note the default language comes first, without value, and is not repeated
below: it follows the order or customer picked in the modal until the administrator chooses another one.
performLang()runs outside the frozen block, likethe comment). Number, lines and amounts stay as they are; only the printed document follows the language. A
lang_idthecredit note cannot have (unknown, or inactive and not its own) is ignored.
CreditNoteDocumentLocale::forCreditNote()reads the language of the credit note first, still withindocument_locales. A credit note without a language, written before 4.3.0, keeps the language of its customer.customer, of their order when the customer has none, only when that language exists.
Twig back office
#orders_menu, an entry of the Smarty back officethat default-twig does not have, so the section never showed. It now follows
[data-testid="bo-nav-order"]withthe markup of the side menu (default-twig has no hook between sections).
success-urlin the request body, but themodal only read it from the query string, so a save always landed on the order page, where default-twig ignores
?tab=credit-noteand opens the cart tab. The modal now keeps that page with the tab holding the trigger asfragment (
#credit-noteon an order), the script reopens that tab on load, and create, update and delete share oneredirectAfterSave().success-urlwas passed toRedirectResponseas is. Only a path of the shop in printable ASCII is followed now(
#^/(?!/)[\x21-\x5B\x5D-\x7E]*$#D): an absolute or protocol-relative address, a backslash, or a tab or line breakthat the browser drops from an address (
/<tab>/hostbecomes//host) sends back to the credit notes of the order.empty address, which a database in strict mode refuses with an error 500 (
Field 'firstname' doesn't have a default value). Create and update now stop before any write and go back to the page with a message. The module's messagesuse the
dangertype the Twig back office styles (errorrendered asalert-error, unstyled).styleattributes, a context HTML escaping does not protect.BadgeColor::css()keeps a hexadecimal color and falls back to grey otherwise. The colors the module installs areall hexadecimal, so nothing changes on screen.
Update scripts
update()ran every script ofConfig/Update/sql/above the installed version, compared by file name, whateverthe target. It now runs the scripts above the installed version up to the target one, compared by version (the
file name without
.sql).CreditNoteOrderCeilingTestsimulates a 3.0.2 to 4.1.0 update on a shop installed at4.3.0: without the bound it replays
4.3.0.sqland fails on the existing column.Module version 4.3.0, CHANGELOG entries under Unreleased.
Upgrading
module:refreshruns4.3.0.sql. ItsALTER TABLEstatements are not guarded: if the script stops after the firstone, a second run fails on the existing column. Back up
credit_notebefore the update and restore it if the updatefails. Afterwards,
SELECT COUNT(*) FROM credit_note WHERE lang_id IS NULLreturns 0 on a shop whose customers allhave a language.
Going back to 4.2 leaves the column in place; the previous version ignores it.
Tests
Each new test was seen failing with the code it covers broken, then passing.
CreditNoteLanguageTest: the language of the order, of the customer without order, the default language of theshop; a language given by the writer is kept; active languages selectable, an inactive one refused unless it is
the language of the credit note.
CreditNoteLanguageRouteTest: the language of a numbered credit note changes through_updatewith its number,invoice number, total and lines unchanged; an inactive or unknown language is ignored.
CreditNoteDocumentLocaleTest: the language of the credit note wins over the one of its customer, the document isrendered in it whatever the order and the customer; the existing cases now describe a credit note without a
language. The class sets
document_localesand the default PDF template inside its transaction, so it no longerdepends on the configuration of the shop it runs in.
CreditNoteSaveRedirectTest: back to the page and tab of the modal; no address, an absolute one, aprotocol-relative one, a backslash, a tab, a line break, a space and a trailing line break all send back to the
order.
BadgeColorTest: short, long, upper case and alpha hexadecimal colors kept; a name, an empty string, an injecteddeclaration and a trailing line break fall back to grey.
CreditNoteInvoiceAddressTest: a credit note created without invoice address is refused with a message, andneither the credit note nor an empty address is written (error 500 before the fix).
CreditNoteLanguageRouteTest::theModalOfANewCreditNoteOffersEachLanguageOnce: the default language comes first,without value, and no language is offered twice.
Unit suite: 32 tests. Integration tests above: 24 tests, run against a Thelia 3.3 shop with the module installed.
Checked in a browser on default-twig: the menu and its status filters; a credit note created from an order in
another language gets that language and the save lands back on the "Credit note" tab; edited from the list and from
the customer page, each save lands back on the page it came from; deleted from the order tab, back on that tab; the
document of a numbered credit note switches language with it.
Not in this PR
Found while reading the controller, unchanged here, worth a separate PR:
createAction,viewAction,updateAction,deleteAction,ajaxModalCreateActionand the customer and ordersearches call no
checkAuth(): any administrator session can read, write and delete credit notes whatever itsprofile.
_updateon an unknown{id}creates a new credit note;_deleteon an unknown one ends in an error 500.