DecimalQuantityGuardTrait
Rejects fractional item quantities while the `quantity` column is still an integer.
Covers the deploy window where the code is new but the column has not been altered to
decimal(8,2) yet. In that window a fractional quantity would pass without an error and
be stored rounded (the database does not reliably reject it; depending on sql_mode
'2.50' may silently become 3).
Totals are recomputed from the stored quantity on every save
(InvoiceCalculator), so the invoice would
silently grow on any re-save (0.50 becomes 1: the line doubles).
The guard is hard and sits in beforeSave() of both managers, the point every invoice
write passes through (admin form via the bridge, Api endpoint, InvoiceComposer,
checkout and payment generators). The rejection is VALIDATION_FAILED, so SaveTrait
rolls the whole operation back and no half-saved document remains.
It switches itself off: once the column is altered, information_schema reports decimal
and everything passes. The column type is cached statically for one request, and the probe
runs only when a fractional quantity is actually found, so a normal integer invoice costs
no extra query.
Table of Contents
Constants
- TABLE_INVOICE_ITEMS : string = 'cms_mod_invoicer_invoice_items'
- Tables whose `quantity` becomes `decimal(8,2)` (elsewhere it stays an integer piece count).
- TABLE_INVOICE_TEMPLATE_ITEMS : string = 'cms_mod_invoicer_invoice_template_items'
Properties
- $decimalQuantityColumns : array<string, bool>
- Whether `quantity` is a decimal column, per table. Valid for this request only (the schema does not change during a request; the alter is a maintenance step between requests).
Methods
- findFractionalQuantity() : string|null
- Returns the first fractional quantity in the collection, or `null`. Compared with `fmod()`, not by string shape: the setter also receives `int`/`float`, and after hydration the getter returns the database spelling, which may differ ('2.5' vs '2.50').
- guardFractionalQuantity() : Result|null
- Rejects the write when a line has a fractional quantity and the column is still an integer.
- hasDecimalQuantityColumn() : bool
- Tells whether `quantity` of the table is already `decimal`; unknown counts as no (rejecting is safer).
Constants
TABLE_INVOICE_ITEMS
Tables whose `quantity` becomes `decimal(8,2)` (elsewhere it stays an integer piece count).
protected
string
TABLE_INVOICE_ITEMS
= 'cms_mod_invoicer_invoice_items'
TABLE_INVOICE_TEMPLATE_ITEMS
protected
string
TABLE_INVOICE_TEMPLATE_ITEMS
= 'cms_mod_invoicer_invoice_template_items'
Properties
$decimalQuantityColumns
Whether `quantity` is a decimal column, per table. Valid for this request only (the schema does not change during a request; the alter is a maintenance step between requests).
private
static array<string, bool>
$decimalQuantityColumns
= []
Methods
findFractionalQuantity()
Returns the first fractional quantity in the collection, or `null`. Compared with `fmod()`, not by string shape: the setter also receives `int`/`float`, and after hydration the getter returns the database spelling, which may differ ('2.5' vs '2.50').
private
findFractionalQuantity(iterable<string|int, mixed> $lines) : string|null
Parameters
- $lines : iterable<string|int, mixed>
Return values
string|nullguardFractionalQuantity()
Rejects the write when a line has a fractional quantity and the column is still an integer.
private
guardFractionalQuantity(Connection $connection, string $table, iterable<string|int, mixed> $lines) : Result|null
Returns null when all is well (all quantities whole, or the column already altered).
Parameters
- $connection : Connection
- $table : string
- $lines : iterable<string|int, mixed>
-
item lines of the invoice or template
Return values
Result|nullhasDecimalQuantityColumn()
Tells whether `quantity` of the table is already `decimal`; unknown counts as no (rejecting is safer).
private
hasDecimalQuantityColumn(Connection $connection, string $table) : bool
Parameters
- $connection : Connection
- $table : string