* Qual(ci): Fix phan notices (typing)
* Qual: Fix deposit_percent type (escaping)
# Qual: Fix deposit_percent type (escaping)
deposit_percent is a float but was escaped to string resulting in argument mismatch.
Fixed by casting to float.
Follow-up to the 18.0 fix. On 21.0 and later, MoLine::fetchAll no longer
handles fk_mo/origin_id/origin_type as exact matches in the array-filter
path; every non-rowid key falls back to LIKE '%value%'. So the array
filter added for 18.0 turned into origin_id LIKE '%..%' here, which can
match an unrelated MO line and reintroduce the duplicate MO the fix was
meant to prevent.
Build the lookup with the universal-search string filter
(fk_mo:=:.. AND origin_id:=:.. AND origin_type:=:'bomline') so the class
turns it into exact equality, as requested during review of #39163.
Signed-off-by: Dolicraft <contact@dolicraft.com>
Co-authored-by: Dolicraft <contact@dolicraft.com>
The asset disposal amount was read with GETPOSTINT, casting it to int
and dropping the decimals, so a disposal at 1500.50 was stored as 1500.
disposal_amount_ht is a 'price' field, so use GETPOSTFLOAT to keep the
decimal amount, both when storing it and when re-displaying it in the
disposal confirmation form.
Signed-off-by: Dolicraft <contact@dolicraft.com>
Co-authored-by: Dolicraft <contact@dolicraft.com>
On 23.0 the generic CommonObject methods (setPaymentMethods,
setBankAccount, ...) build the trigger name from
$this->TRIGGER_PREFIX, falling back to get_class() when it is empty.
Facture does not declare it, so it fires FACTURE_MODIFY instead of the
BILL_* events the class uses everywhere else (BILL_CREATE, BILL_MODIFY,
...). Modules listening on BILL_MODIFY miss those changes.
Declare public $TRIGGER_PREFIX = 'BILL', like the other objects do
(Holiday='HOLIDAY', Workstation='WORKSTATION', BOM='BOM').
Signed-off-by: Dolicraft <contact@dolicraft.com>
Co-authored-by: Dolicraft <contact@dolicraft.com>
The origin multicurrency exchange rate was read with GETPOSTINT('originmulticurrency_tx'), which truncates any fractional rate (e.g. 0.83, 1.27) down to an integer. As a result, creating a Supplier Invoice from a Supplier Order (or other origin) always produced an exchange rate of 0 or 1 instead of the real rate, even when the multi-currency setting to keep the source object's original rate was enabled.
This was already fixed for one of the four occurrences in this file (the credit-note/replacement-invoice path) but the other three were never backported to this branch. This commit switches all three remaining occurrences from GETPOSTINT() to GETPOSTFLOAT(), matching develop/23.0.
Fixes#39423
Columns llx_holiday.date_debut and date_fin are pure DATE columns: they hold a
calendar fact, not an absolute instant, so they carry no time and no timezone.
The agenda read them with jdate() defaulting to 'tzserver', then projected them
back with dol_print_date(..., 'tzuserrel') to pick the calendar day box. The
resulting shift equals TZ_user - TZ_server, so any user whose timezone differs
from the server got leaves rendered on the wrong day. Users aligned with the
server saw nothing wrong, which is why this went unnoticed. It also triggers with
no user timezone set at all, since 'tzuserrel' falls back to UTC when
$_SESSION['dol_tz_string'] is empty, shifting leaves on any server east of UTC.
Read and project those dates in GMT instead. This follows the documented core
convention for dates without time ("We suppose dates without time are always gmt
(storage of course + output)", commonobject.class.php; "For date without hour,
date is always GMT for storage and output", extrafields.class.php) and honors the
$daykey contract of $eventarray, which must be midnight GMT of the displayed
calendar day. It also makes the half-day comparison consistent, since
dol_get_first_hour() is already called with 'gmt' a few lines below and only
supports gmt/tzserver anyway.
Two further defects disappear as a side effect: the loop condition
"while ($daykey <= $event->date_end_in_calendar)" compared a user-shifted $daykey
against a server-anchored $datef and rendered one extra day box, and halfday=-1
happened to be correct while halfday=0/1 were not, because the +12h offset
accidentally compensated the timezone shift.
Regular events are untouched: llx_actioncomm.datep is a DATETIME holding a real
instant, and its block still uses 'tzuserrel', so a 9am meeting stays correctly
shifted for a viewer in another timezone. Every other render path already
excludes type_code 'HOLIDAY', so there is no display change elsewhere.
peruser.php carries a copy of the same block and is fixed identically, otherwise
the two views of the same page would contradict each other.
Tested with a real browser (Playwright) against a server running in UTC, driving
the month view as Europe/Paris, America/Cayenne and Pacific/Auckland users on
three existing leaves: 3 failures before, none after, with identical day boxes in
every timezone. A single-day leave of 2026-07-28 was rendered on both 2026-07-27
and 2026-07-28 for the America/Cayenne user before the fix.
ChargeSociales::update() set fk_user_modif twice in the same UPDATE
statement. PostgreSQL rejects this with "multiple assignments to the
same column", so editing a social contribution failed. Remove the
duplicate assignment. Backport of the develop fix#39389 to the 22.0
and 23.0 maintenance branches, where the duplicate is still present.
Signed-off-by: Dolicraft <contact@dolicraft.com>
Co-authored-by: Dolicraft <contact@dolicraft.com>
dol_string_unaccent() did not map the Hungarian letters o-double-acute
and u-double-acute (U+0150/U+0151/U+0170/U+0171), so they were left
untouched in sanitized filenames. Add the four missing entries so they
transliterate to O/o/U/u like the other accented variants.
Signed-off-by: Dolicraft <contact@dolicraft.com>
Co-authored-by: Dolicraft <contact@dolicraft.com>
The POST /documents endpoint with generateThumbs=1 called
image_format_supported() after requiring only files.lib.php, but that
function is defined in images.lib.php, which was never included on this
path. The call raised a fatal error and the request returned a bare
HTTP 500 with no JSON body. Add the missing require_once.
Signed-off-by: Dolicraft <contact@dolicraft.com>
Co-authored-by: Dolicraft <contact@dolicraft.com>
The substr offsets in dol_print_phone did not line up with the segment
lengths, so formatted numbers repeated one digit and lost another.
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
A PaymentIntent already used to record a payment on one invoice/order
could be resubmitted with a different fulltag/ref to fraudulently
record and validate a payment on a different object, since only its
Stripe status was checked, never whether it had already been consumed.
In theme md, the rule in btn.inc.php that gives action buttons their
bottom margin only lists direct children of div.tabsAction and children
of div.divButAction. Dropdown action buttons are not in that list.
dolGetButtonAction() renders a dropdown button as
<div class="dropdown inline-block dropdown-holder"><a class="dropdown-toggle ... butAction">,
so the anchor is a grandchild of div.tabsAction and matches none of the
existing selectors. It therefore gets no bottom margin.
When the action bar wraps onto more than one line and a line is made of
dropdown buttons, the rows have no vertical spacing at all and the
buttons visually touch each other.
Theme eldy already fixes this with the same selector
(div.tabsAction > .dropdown > .dropdown-toggle in theme/eldy/btn.inc.php);
theme md was never aligned. This adds the identical selector to md.
Measured with the compiled md stylesheet, six dropdown buttons wrapping
onto two rows: gap between rows 0px before, 22px after, which matches
the 1.4em the plain buttons already get. Layout of rows that mix plain
and dropdown buttons is unchanged.
Signed-off-by: Florian Hödl <florian@hoedl.co>
Co-authored-by: Florian Hödl <florian@hoedl.co>
Contrat::doAutoRenewContracts() instantiates ActionComm to log the
RENEW_CONTRACT event, but contrat.class.php never loads
comm/action/class/actioncomm.class.php.
From a web request the class is usually already loaded by some other
include, so the problem stays hidden. When the method is run as a
scheduled job it is not: scripts/cron/cron_run_jobs.php loads only
master.inc.php, functionscli.lib.php, cronjob.class.php and
user.class.php, and cronjob.class.php then loads only the job class
itself via dol_include_once($this->classesname). ActionComm is not part
of that include closure, so the method fails with
"Class ActionComm not found".
The fatal error happens after the UPDATE on llx_contratdet has been
issued but before $this->db->commit(), so the transaction is never
committed and the renewal is lost. The job also aborts, so every
remaining contract line in the loop is left unprocessed and the failure
is only visible as a fatal error in the log.
Verified on 23.0.3: after the cron bootstrap, class_exists('Contrat')
is true while class_exists('ActionComm') is false. Adding the include
next to the existing html.formmail.class.php include makes it true.
Signed-off-by: Florian Hödl <florian@hoedl.co>
Co-authored-by: Florian Hödl <florian@hoedl.co>
The "Check NIE T" branch compared the control character of the TIN to the
return value of preg_match() instead of using preg_match() as the condition:
if ($num[8] == preg_match('/^[T]{1}[A-Z0-9]{8}$/', $str)) {
Since execution only reaches that branch when the string already matched the
format, preg_match() always returns 1, so the test collapsed to
$num[8] == 1. A NIE of type T therefore validated only when its last
character was literally "1", and every other one was reported as -3
(invalid control key). Out of the 36 possible control characters, 35 were
wrongly rejected.
A NIE starting with T has no control key algorithm, so matching the
documented syntax is the only check to perform.
Also fill in and enable the testIsValidTinForES() unit test that was left
commented out with empty input strings.
Co-authored-by: Claude <noreply@anthropic.com>
* FIX: Show error when clone order fail
* Fix missing variable
* Fix foreach loop to reference correct object
Updated foreach loop to use the correct object reference for lines.
---------
Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>
With the MySQL 8 default sql_mode (which includes ONLY_FULL_GROUP_BY),
the supplier invoices query of the treasury journal fails with
DB_ERROR_1055 ("Expression #5 of SELECT list is not in GROUP BY clause
and contains nonaggregated column 'pff.amount' which is not
functionally dependent on columns in GROUP BY clause"). Supplier
payments are then missing from the journal view and from the transfer
into the ledger, while customer invoices still pass, so a treasury
ledger is produced with revenues only and no supplier expense, with no
other signal than one error line in the transfer report.
This is the MySQL counterpart of PostgreSQL error 42803 fixed on
develop by #38910, then simplified by commit 42840b47a which removed
the GROUP BY of the three queries entirely: they contain no aggregate
function, and the fetch loops already deduplicate rows (isset() guards
on the object/payment keys and the already_sum check on the detail
line id), so the GROUP BY is useless. This backports that resolution,
with lines identical to develop so the upward merge stays clean.
Verified on a 23.0.2 instance on MySQL 8 with factory sql_mode: the
vanilla supplier query returns error 1055, the same query without
GROUP BY succeeds and returns the same rows, row for row, as the
exhaustive GROUP BY variant of #38910; the full year transfer produces
balanced pieces and a re-transfer creates no duplicate.
Signed-off-by: Gregory Aliot <greg.aliot@gmail.com>
Co-authored-by: Gregory Aliot <greg.aliot@gmail.com>