From 3da7cceb81d381ff0e87791ab9dff20e0170a1e3 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Thu, 1 Jan 2026 12:26:35 +0100 Subject: [PATCH 01/18] Debug v23 - Missing cumulative total in export --- .../blockedlog/admin/blockedlog_archives.php | 179 ++++++++++++++---- htdocs/blockedlog/admin/blockedlog_list.php | 64 +++---- htdocs/blockedlog/class/blockedlog.class.php | 5 +- htdocs/blockedlog/lib/blockedlog.lib.php | 72 ++++++- htdocs/compta/facture/card.php | 108 +++++++---- htdocs/core/class/commoninvoice.class.php | 29 +++ htdocs/core/modules/modBlockedLog.class.php | 2 +- htdocs/langs/en_US/bills.lang | 5 +- htdocs/langs/en_US/blockedlog.lang | 2 + 9 files changed, 350 insertions(+), 116 deletions(-) diff --git a/htdocs/blockedlog/admin/blockedlog_archives.php b/htdocs/blockedlog/admin/blockedlog_archives.php index 5a1932d2d07..0d3dfb10fd7 100644 --- a/htdocs/blockedlog/admin/blockedlog_archives.php +++ b/htdocs/blockedlog/admin/blockedlog_archives.php @@ -221,38 +221,7 @@ if (GETPOST('action') == 'export' && $user->hasRight('blockedlog', 'read')) { / } if (!$error) { - // We record the export as a new line into the unalterable logs - require_once DOL_DOCUMENT_ROOT.'/blockedlog/class/blockedlog.class.php'; - $b = new BlockedLog($db); - - $object = new stdClass(); - $object->id = 0; - $object->element = 'module'; - $object->ref = 'systemevent'; - $object->entity = $conf->entity; - $object->date = dol_now(); - - $object->label = 'Export unalterable logs - Period: year='.GETPOSTINT('yeartoexport').(GETPOSTINT('monthtoexport') ? ' month='.GETPOSTINT('monthtoexport') : ''); - - $action = 'BLOCKEDLOG_EXPORT'; - $result = $b->setObjectData($object, $action, 0, $user); - //var_dump($b); exit; - - if ($result < 0) { - setEventMessages('Failed to insert the export int the unalterable log', null, 'errors'); - $error++; - } - - $res = $b->create($user); - - if ($res < 0) { - setEventMessages('Failed to insert the export int the unalterable log', null, 'errors'); - $error++; - } - } - - if (!$error) { - // Now restart request with all data, si without the limit(1) in sql request + // Now restart request with all data, so without the limit(1) in sql request $sql = "SELECT rowid, date_creation, tms, user_fullname, action, amounts, element, fk_object, date_object, ref_object,"; $sql .= " signature, fk_user, object_data, object_version, object_format, debuginfo"; $sql .= " FROM ".MAIN_DB_PREFIX."blockedlog"; @@ -279,8 +248,10 @@ if (GETPOST('action') == 'export' && $user->hasRight('blockedlog', 'read')) { / $fh = fopen($tmpfile, 'w'); + $formatexport = 'V1'; + // Print line with title - fwrite($fh, "BEGIN - date=".$yearmonthdateofexport." - period=".$yearmonthtoexport." - format=V1 - user=".$user->getFullName($langs) + fwrite($fh, "BEGIN - date=".$yearmonthdateofexport." - period=".$yearmonthtoexport." - formatexport=".$formatexport." - user=".$user->getFullName($langs) .';'.$langs->transnoentities('Id') .';'.$langs->transnoentities('DateCreation') .';'.$langs->transnoentities('Action') @@ -302,6 +273,10 @@ if (GETPOST('action') == 'export' && $user->hasRight('blockedlog', 'read')) { / $loweridinerror = 0; $i = 0; + $totalhtamount = array(); + $totalvatamount = array(); + $totalamount = array(); + while ($obj = $db->fetch_object($resql)) { // We set here all data used into signature calculation (see checkSignature method) and more // IMPORTANT: We must have here, the same rule for transformation of data than into the fetch method (db->jdate for date, ...) @@ -366,6 +341,12 @@ if (GETPOST('action') == 'export' && $user->hasRight('blockedlog', 'read')) { / $signatureexport = dol_hash($previoushash.$concatenateddata, 'sha256'); // SHA256 //$signatureexporthmac = 'TODO'; + + // Define $totalhtamount, $totalvatamount, $totalamount for $block->action event code + $total_ht = $total_vat = $total_ttc = 0; + sumAmountsForUnalterableEvent($block_static, $totalhtamount, $totalvatamount, $totalamount, $total_ht, $total_vat, $total_ttc); + + fwrite($fh, ';'.$block_static->id .';'.$block_static->date_creation @@ -392,6 +373,107 @@ if (GETPOST('action') == 'export' && $user->hasRight('blockedlog', 'read')) { / $i++; } + $totalhtamountallines = array('BILL_VALIDATE' => 0); + $totalvatamountallines = array('BILL_VALIDATE' => 0); + $totalamountalllines = array('BILL_VALIDATE' => 0); + if (array_key_exists('BILL_VALIDATE', $totalhtamount)) { + foreach ($totalhtamount['BILL_VALIDATE'] as $key => $val) { + $totalhtamountalllines['BILL_VALIDATE'] += $val; + } + foreach ($totalvatamount['BILL_VALIDATE'] as $key => $val) { + $totalvatamountalllines['BILL_VALIDATE'] += $val; + } + foreach ($totalamount['BILL_VALIDATE'] as $key => $val) { + $totalamountalllines['BILL_VALIDATE'] += $val; + } + } + + + // Add a final line with cumulative total of invoices validated + $block_static->id = ''; + $block_static->date_creation = ''; + $block_static->action = ''; + $block_static->amounts = $langs->transnoentitiesnoconv("HT").': '.$totalhtamountalllines['BILL_VALIDATE'].' | '.$langs->transnoentitiesnoconv("VAT").': '.$totalvatamountalllines['BILL_VALIDATE'].' | '.$langs->transnoentitiesnoconv("TTC").': '.$totalamountalllines['BILL_VALIDATE']; + $block_static->ref_object = ''; + $block_static->date_object = ''; + $block_static->user_fullname = ''; + $block_static->linktoref = ''; + $block_static->linktype = ''; + $block_static->object_data = ''; + $block_static->object_version = ''; + $block_static->signature = ''; + + $statusofrecord = ''; + $signatureexport = ''; + + $block_static->object_format = ''; + + fwrite($fh, 'Cumulative total Invoice Validation;' + .$block_static->id + .';'.$block_static->date_creation + .';'.$block_static->action + .';'.$block_static->amounts // Can be 1.20000000 with 8 digits. TODO Clean to have 8 digits in V1 + .';"'.str_replace('"', '""', $block_static->ref_object).'";' + .$block_static->date_object + .';"'.str_replace('"', '""', $block_static->user_fullname).'";"' + .str_replace('"', '""', $block_static->linktoref).'";"' + .str_replace('"', '""', $block_static->linktype).'";"' + .str_replace('"', '""', $obj->object_data).'"' // We must the string to decode into object with dolDecodeBlockedData + .';"'.str_replace('"', '""', $block_static->object_version).'";"' + .str_replace('"', '""', $block_static->signature).'";"' + .str_replace('"', '""', $statusofrecord).'";"' + .str_replace('"', '""', $signatureexport).'";"' + .str_replace('"', '""', $block_static->object_format).'";' + //.str_replace('"', '""', $signatureexporthmac).'"' + //.';'.$statusofrecordnote + ."\n"); + + + // Add a final line with perpetual total + $totalhtamountlifetime = array(); + $totalvatamountlifetime = array(); + $totalamountlifetime = array(); + + + $block_static->id = ''; + $block_static->date_creation = ''; + $block_static->action = ''; + $block_static->amounts = $totalamountlifetime['BILL_VALIDATE']; + $block_static->ref_object = ''; + $block_static->date_object = ''; + $block_static->user_fullname = ''; + $block_static->linktoref = ''; + $block_static->linktype = ''; + $block_static->object_data = ''; + $block_static->object_version = ''; + $block_static->signature = ''; + + $statusofrecord = ''; + $signatureexport = ''; + + $block_static->object_format = ''; + + fwrite($fh, 'Perpetual total Invoice Validation;' + .$block_static->id + .';'.$block_static->date_creation + .';'.$block_static->action + .';'.$block_static->amounts // Can be 1.20000000 with 8 digits. TODO Clean to have 8 digits in V1 + .';"'.str_replace('"', '""', $block_static->ref_object).'";' + .$block_static->date_object + .';"'.str_replace('"', '""', $block_static->user_fullname).'";"' + .str_replace('"', '""', $block_static->linktoref).'";"' + .str_replace('"', '""', $block_static->linktype).'";"' + .str_replace('"', '""', $obj->object_data).'"' // We must the string to decode into object with dolDecodeBlockedData + .';"'.str_replace('"', '""', $block_static->object_version).'";"' + .str_replace('"', '""', $block_static->signature).'";"' + .str_replace('"', '""', $statusofrecord).'";"' + .str_replace('"', '""', $signatureexport).'";"' + .str_replace('"', '""', $block_static->object_format).'";' + //.str_replace('"', '""', $signatureexporthmac).'"' + //.';'.$statusofrecordnote + ."\n"); + + fclose($fh); // Calculate the md5 of the file (the last line has a return line) @@ -408,6 +490,37 @@ if (GETPOST('action') == 'export' && $user->hasRight('blockedlog', 'read')) { / setEventMessages($db->lasterror, null, 'errors'); } } + + if (!$error) { + // We record the export as a new line into the unalterable logs + require_once DOL_DOCUMENT_ROOT.'/blockedlog/class/blockedlog.class.php'; + $b = new BlockedLog($db); + + $object = new stdClass(); + $object->id = 0; + $object->element = 'module'; + $object->ref = 'systemevent'; + $object->entity = $conf->entity; + $object->date = dol_now(); + + $object->label = 'Export unalterable logs - Period: year='.GETPOSTINT('yeartoexport').(GETPOSTINT('monthtoexport') ? ' month='.GETPOSTINT('monthtoexport') : ''); + + $action = 'BLOCKEDLOG_EXPORT'; + $result = $b->setObjectData($object, $action, 0, $user); + //var_dump($b); exit; + + if ($result < 0) { + setEventMessages('Failed to insert the export int the unalterable log', null, 'errors'); + $error++; + } + + $res = $b->create($user); + + if ($res < 0) { + setEventMessages('Failed to insert the export int the unalterable log', null, 'errors'); + $error++; + } + } } diff --git a/htdocs/blockedlog/admin/blockedlog_list.php b/htdocs/blockedlog/admin/blockedlog_list.php index 6bb1f40cf76..cbedc842903 100644 --- a/htdocs/blockedlog/admin/blockedlog_list.php +++ b/htdocs/blockedlog/admin/blockedlog_list.php @@ -442,6 +442,9 @@ if ($limit > 0 && $limit != $conf->liste_limit) { if ($search_id != '') { $param .= '&search_id='.urlencode($search_id); } +if ($search_ref != '') { + $param .= '&search_ref='.urlencode($search_ref); +} if ($search_fk_user > 0) { $param .= '&search_fk_user='.urlencode($search_fk_user); } @@ -630,12 +633,14 @@ if (is_array($blocks)) { if (empty($search_showonlyerrors) || !$checkresult[$block->id]) { $nbshown++; - if ($nbshown < $MAXFORSHOWNLINKS) { // For performance and memory purpose, we get/show the link of objects only for the 100 first output - $object_link = $block->getObjectLink(); - $object_link_title = ''; - } else { - $object_link = $block->element.'/'.$block->fk_object; - $object_link_title = $langs->trans('LinkHasBeenDisabledForPerformancePurpose'); + if (getDolGlobalString("BLOCKEDLOG_DEBUG")) { + if ($nbshown < $MAXFORSHOWNLINKS) { // For performance and memory purpose, we get/show the debug info link of objects only for the 100 first output + $object_link = $block->getObjectLink(); + $object_link_title = ''; + } else { + $object_link = $block->element.'/'.$block->fk_object; + $object_link_title = $langs->trans('LinkHasBeenDisabledForPerformancePurpose'); + } } print ''; @@ -666,6 +671,14 @@ if (is_array($blocks)) { print ''; if (!empty($block->ref_object)) { print dol_escape_htmltag($block->ref_object); + if ($block->linktype && $block->linktoref) { + if ($block->linktype == 'paymentof') { + print '
'.$langs->trans("PaymentOf").' '.$block->linktoref.''; + } + if ($block->linktype == 'replacedby') { + print '
'.$langs->trans("ReplacedBy").' '.$block->linktoref.''; + } + } } else { // Ref not stored } @@ -673,44 +686,13 @@ if (is_array($blocks)) { //$tmpobj = json_decode($block->object_data); + // Define $totalhtamount, $totalvatamount, $totalamount for $block->action event code + $total_ht = $total_vat = $total_ttc = 0; + sumAmountsForUnalterableEvent($block, $totalhtamount, $totalvatamount, $totalamount, $total_ht, $total_vat, $total_ttc); + // Amount print ''; - // Define $totalhtamount, $totalvatamount, $totalamount - if (empty($totalamount[$block->action])) { - $totalamount[$block->action] = array(); - } - if ($block->action == 'BILL_VALIDATE') { - $total_ht = $block->object_data->total_ht; - $total_vat = $block->object_data->total_tva; - $total_ttc = $block->object_data->total_ttc; - - if (empty($totalamount[$block->action][$block->ref_object])) { // If not, we already met the event for this object, we keep only first one. - $totalhtamount[$block->action][$block->ref_object] = $total_ht; - $totalvatamount[$block->action][$block->ref_object] = $total_vat; - $totalamount[$block->action][$block->ref_object] = $total_ttc; - } - } elseif ($block->action == 'PAYMENT_CUSTOMER_CREATE') { - $total_ht = $block->object_data->amount; - $total_vat = 0; - $total_ttc = $block->object_data->amount; - - if (empty($totalhtamount[$block->action][$block->ref_object])) { - $totalhtamount[$block->action][$block->ref_object] = 0; - } - if (empty($totalvatamount[$block->action][$block->ref_object])) { - $totalvatamount[$block->action][$block->ref_object] = 0; - } - if (empty($totalamount[$block->action][$block->ref_object])) { - $totalamount[$block->action][$block->ref_object] = 0; - } - $totalhtamount[$block->action][$block->ref_object] = $total_ht; - $totalvatamount[$block->action][$block->ref_object] = $total_vat; - $totalamount[$block->action][$block->ref_object] = $total_ttc; - } else { - $total_ttc = $block->amounts; - } - if (empty($total_ttc)) { print ''; } diff --git a/htdocs/blockedlog/class/blockedlog.class.php b/htdocs/blockedlog/class/blockedlog.class.php index cc685870013..6ed57d48901 100644 --- a/htdocs/blockedlog/class/blockedlog.class.php +++ b/htdocs/blockedlog/class/blockedlog.class.php @@ -1512,7 +1512,8 @@ class BlockedLog $sql .= " AND date_creation <= '".$this->db->idate($search_end)."'"; } if ($search_ref != '') { - $sql .= natural_search("ref_object", $search_ref); + $sql .= " AND (".natural_search("ref_object", $search_ref, 0, 1); + $sql .= " OR ".natural_search("linktoref", $search_ref, 0, 1).")"; } if ($search_amount != '') { $sql .= natural_search("amounts", $search_amount, 1); @@ -1602,6 +1603,6 @@ class BlockedLog public function alreadyUsed($ignoresystem = 0) { include_once DOL_DOCUMENT_ROOT.'/blockedlog/lib/blockedlog.lib.php'; - return isBlockedLogused($ignoresystem); + return isBlockedLogUsed($ignoresystem); } } diff --git a/htdocs/blockedlog/lib/blockedlog.lib.php b/htdocs/blockedlog/lib/blockedlog.lib.php index d2f4fefb073..61e30710ae4 100644 --- a/htdocs/blockedlog/lib/blockedlog.lib.php +++ b/htdocs/blockedlog/lib/blockedlog.lib.php @@ -169,7 +169,7 @@ function isALNERunningVersion() if (defined('CERTIF_LNE') && (int) constant('CERTIF_LNE') === 2) { return true; } - if (isModEnabled('blockedlog') && isBlockedLogused()) { + if (isModEnabled('blockedlog') && isBlockedLogUsed()) { return true; } @@ -182,17 +182,21 @@ function isALNERunningVersion() * @param int<0,1> $ignoresystem Ignore system events for the test * @return boolean True if blocked log was already used, false if not */ -function isBlockedLogused($ignoresystem = 0) +function isBlockedLogUsed($ignoresystem = 0) { global $conf, $db; $result = true; // by default restrictions are on, so we can't disable them - // For the moment, we don't need this. We already have a feature that does not allow to disable the LNE rstriction by - // adding an inalterable event in the log. + // Note: if module on, we suppose it is used, if not, we check in case of it was disabled. if (!isModEnabled('blockedlog')) { + // Test the cache key + if (array_key_exists('isblockedlogused', $conf->cache)) { + return $conf->cache['isblockedlogused'.$ignoresystem]; + } + $sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."blockedlog"; - $sql .= " WHERE entity = ".((int) $conf->entity); // Sharing entity in blocked is disallowed + $sql .= " WHERE entity = ".((int) $conf->entity); // Sharing entity in blocked log is disallowed if ($ignoresystem) { $sql .= " AND action NOT IN ('MODULE_SET', 'MODULE_RESET')"; } @@ -207,9 +211,11 @@ function isBlockedLogused($ignoresystem = 0) } else { dol_print_error($db); } + + $conf->cache['isblockedlogused'.$ignoresystem] = $result; } - dol_syslog("isBlockedLogused: ignoresystem=".$ignoresystem." returns ".(string) $result); + dol_syslog("isBlockedLogUsed: ignoresystem=".$ignoresystem." returns ".(string) $result); return $result; } @@ -244,3 +250,57 @@ function pdfCertifMentionblockedLog(&$pdf, $outputlangs, $seller, $default_font_ return $result; } + +/** + * sumAmountsForUnalterableEvent + * + * @param BlockedLog $block Object BlockedLog + * @param array $totalhtamount Array of total per code event + * @param array $totalvatamount Array of total per code event + * @param array $totalamount Array of total per code event + * @param float $total_ht Total HT + * @param float $total_vat Total VAT + * @param float $total_ttc Total TTC + * @return int Return > 0 + */ +function sumAmountsForUnalterableEvent($block, &$totalhtamount, &$totalvatamount, &$totalamount, &$total_ht, &$total_vat, &$total_ttc) +{ + if (empty($totalamount[$block->action])) { + $totalamount[$block->action] = array(); + } + if ($block->action == 'BILL_VALIDATE') { + $total_ht = $block->object_data->total_ht; + $total_vat = $block->object_data->total_tva; + $total_ttc = $block->object_data->total_ttc; + + // We add total for the invoice if "invoice validate event" not yet met. + // If we already met the event for this object, we keep only first one but this should not happen because edition of validated invoice is not allowed on secured versions. + if (empty($totalamount[$block->action][$block->ref_object])) { + $totalhtamount[$block->action][$block->ref_object] = $total_ht; + $totalvatamount[$block->action][$block->ref_object] = $total_vat; + $totalamount[$block->action][$block->ref_object] = $total_ttc; + } + } elseif ($block->action == 'PAYMENT_CUSTOMER_CREATE') { + $total_ht = $block->object_data->amount; + $total_vat = 0; + $total_ttc = $block->object_data->amount; + + if (empty($totalhtamount[$block->action][$block->ref_object])) { + $totalhtamount[$block->action][$block->ref_object] = 0; + } + if (empty($totalvatamount[$block->action][$block->ref_object])) { + $totalvatamount[$block->action][$block->ref_object] = 0; + } + if (empty($totalamount[$block->action][$block->ref_object])) { + $totalamount[$block->action][$block->ref_object] = 0; + } + $totalhtamount[$block->action][$block->ref_object] = $total_ht; + $totalvatamount[$block->action][$block->ref_object] = $total_vat; + $totalamount[$block->action][$block->ref_object] = $total_ttc; + } else { + $total_ttc = $block->amounts; + } + + + return 1; +} diff --git a/htdocs/compta/facture/card.php b/htdocs/compta/facture/card.php index 23e34a2b764..b8ace26aec1 100644 --- a/htdocs/compta/facture/card.php +++ b/htdocs/compta/facture/card.php @@ -4994,42 +4994,86 @@ if ($action == 'create') { // Confirm back to draft status if ($action == 'modif') { - $text = $langs->trans('ConfirmUnvalidateBill', $object->ref); - $formquestion = array(); + $oktomodif = 1; // Assume we can modify by default - if ($object->type != Facture::TYPE_DEPOSIT && getDolGlobalString('STOCK_CALCULATE_ON_BILL')) { - $qualified_for_stock_change = 0; - if (!getDolGlobalString('STOCK_SUPPORTS_SERVICES')) { - $qualified_for_stock_change = $object->hasProductsOrServices(2); - } else { - $qualified_for_stock_change = $object->hasProductsOrServices(1); - } - - if ($qualified_for_stock_change) { - $langs->load("stocks"); - require_once DOL_DOCUMENT_ROOT.'/product/class/html.formproduct.class.php'; - require_once DOL_DOCUMENT_ROOT.'/product/stock/class/entrepot.class.php'; - $formproduct = new FormProduct($db); - $warehouse = new Entrepot($db); - $warehouse_array = $warehouse->list_array(); - if (count($warehouse_array) == 1) { - $label = $object->type == Facture::TYPE_CREDIT_NOTE ? $langs->trans("WarehouseForStockDecrease", current($warehouse_array)) : $langs->trans("WarehouseForStockIncrease", current($warehouse_array)); - $value = ''; - } else { - $label = $object->type == Facture::TYPE_CREDIT_NOTE ? $langs->trans("SelectWarehouseForStockDecrease") : $langs->trans("SelectWarehouseForStockIncrease"); - $value = $formproduct->selectWarehouses(GETPOST('idwarehouse') ? GETPOST('idwarehouse') : 'ifone', 'idwarehouse', '', 1); - } - $formquestion = array( - // 'text' => $langs->trans("ConfirmClone"), - // array('type' => 'checkbox', 'name' => 'clone_content', 'label' => $langs->trans("CloneMainAttributes"), 'value' => - // 1), - // array('type' => 'checkbox', 'name' => 'update_prices', 'label' => $langs->trans("PuttingPricesUpToDate"), 'value' - // => 1), - array('type' => 'other', 'name' => 'idwarehouse', 'label' => $label, 'value' => $value)); + $testvalue = $object->isEditable(); + if ($testvalue < 0) { + switch ($testvalue) { + case -1: + // Dispatched in bookkeeping + setEventMessages($langs->trans("DisabledBecauseDispatchedInBookkeeping"), null, 'errors'); + break; + case -2: + // Not last invoice + setEventMessages($langs->trans("DisabledBecauseNotLastInvoice"), null, 'errors'); + break; + case -3: + // Not last situation invoice + setEventMessages($langs->trans("DisabledBecauseNotLastSituationInvoice"), null, 'errors'); + break; + case -4: + // At least one payment made + setEventMessages($langs->trans("DisabledBecauseThereIsAPayment"), null, 'errors'); + break; + case -5: + // Already sent by email + setEventMessages($langs->trans("DisabledBecauseAlreadySentByEmail"), null, 'errors'); + break; + case -6: + // Already printed once + setEventMessages($langs->trans("DisabledBecauseAlreadyPrintedOnce"), null, 'errors'); + break; + case -7: + // Already validated + setEventMessages($langs->trans("DisabledBecauseVersionProtected"), null, 'errors'); + break; + default: + // Other error + setEventMessages($langs->trans("DisabledBecauseNotErasable"), null, 'errors'); + break; } + $oktomodif = 0; + $action = ''; } - $formconfirm = $form->formconfirm($_SERVER["PHP_SELF"].'?facid='.$object->id, $langs->trans('UnvalidateBill'), $text, 'confirm_modif', $formquestion, "yes", 1); + if ($oktomodif) { + $text = $langs->trans('ConfirmUnvalidateBill', $object->ref); + $formquestion = array(); + + if ($object->type != Facture::TYPE_DEPOSIT && getDolGlobalString('STOCK_CALCULATE_ON_BILL')) { + $qualified_for_stock_change = 0; + if (!getDolGlobalString('STOCK_SUPPORTS_SERVICES')) { + $qualified_for_stock_change = $object->hasProductsOrServices(2); + } else { + $qualified_for_stock_change = $object->hasProductsOrServices(1); + } + + if ($qualified_for_stock_change) { + $langs->load("stocks"); + require_once DOL_DOCUMENT_ROOT.'/product/class/html.formproduct.class.php'; + require_once DOL_DOCUMENT_ROOT.'/product/stock/class/entrepot.class.php'; + $formproduct = new FormProduct($db); + $warehouse = new Entrepot($db); + $warehouse_array = $warehouse->list_array(); + if (count($warehouse_array) == 1) { + $label = $object->type == Facture::TYPE_CREDIT_NOTE ? $langs->trans("WarehouseForStockDecrease", current($warehouse_array)) : $langs->trans("WarehouseForStockIncrease", current($warehouse_array)); + $value = ''; + } else { + $label = $object->type == Facture::TYPE_CREDIT_NOTE ? $langs->trans("SelectWarehouseForStockDecrease") : $langs->trans("SelectWarehouseForStockIncrease"); + $value = $formproduct->selectWarehouses(GETPOST('idwarehouse') ? GETPOST('idwarehouse') : 'ifone', 'idwarehouse', '', 1); + } + $formquestion = array( + // 'text' => $langs->trans("ConfirmClone"), + // array('type' => 'checkbox', 'name' => 'clone_content', 'label' => $langs->trans("CloneMainAttributes"), 'value' => + // 1), + // array('type' => 'checkbox', 'name' => 'update_prices', 'label' => $langs->trans("PuttingPricesUpToDate"), 'value' + // => 1), + array('type' => 'other', 'name' => 'idwarehouse', 'label' => $label, 'value' => $value)); + } + } + + $formconfirm = $form->formconfirm($_SERVER["PHP_SELF"].'?facid='.$object->id, $langs->trans('UnvalidateBill'), $text, 'confirm_modif', $formquestion, "yes", 1); + } } // Confirmation of payment classification diff --git a/htdocs/core/class/commoninvoice.class.php b/htdocs/core/class/commoninvoice.class.php index 4eb833c7d3b..703977c2035 100644 --- a/htdocs/core/class/commoninvoice.class.php +++ b/htdocs/core/class/commoninvoice.class.php @@ -689,6 +689,29 @@ abstract class CommonInvoice extends CommonObject return $retarray; } + // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps + /** + * Return if an invoice can be set back to draft. + * Rule is: + * If invoice is draft and has a temporary ref -> yes (1) + * If hidden option INVOICE_CAN_NEVER_BE_REMOVED is 1 -> no (0) + * If invoice is transferred in bookkeeping -> no (-1) + * If invoice has a definitive ref, is not last in ref -> no (-2) + * If invoice has a definitive ref, is not last in a situation cycle -> no (-3) + * If there is one payment -> no (-4) + * If already sent by email -> no (-5) + * If already printed -> no (-6) + * If running a LNE version and customer invoice was validated -> no (-7) + * Otherwise -> yes (2) + * + * @return int Return integer <=0 if no, >0 if yes + */ + public function isEditable() + { + $test = $this->is_erasable(); + + return $test; + } // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps /** @@ -702,6 +725,7 @@ abstract class CommonInvoice extends CommonObject * If there is one payment -> no (-4) * If already sent by email -> no (-5) * If already printed -> no (-6) + * If running a LNE version and customer invoice was validated -> no (-7) * Otherwise -> yes (2) * * @return int Return integer <=0 if no, >0 if yes @@ -734,6 +758,11 @@ abstract class CommonInvoice extends CommonObject if ((int) $this->pos_print_counter > 0) { return -6; } + + include_once DOL_DOCUMENT_ROOT.'/blockedlog/lib/blockedlog.lib.php'; + if (isALNERunningVersion()) { + return -7; + } } // If in accountancy, we refuse diff --git a/htdocs/core/modules/modBlockedLog.class.php b/htdocs/core/modules/modBlockedLog.class.php index 042b679e0c4..a81ec92a073 100644 --- a/htdocs/core/modules/modBlockedLog.class.php +++ b/htdocs/core/modules/modBlockedLog.class.php @@ -153,7 +153,7 @@ class modBlockedLog extends DolibarrModules { require_once DOL_DOCUMENT_ROOT.'/blockedlog/lib/blockedlog.lib.php'; - return isBlockedLogused(); + return isBlockedLogUsed(); } diff --git a/htdocs/langs/en_US/bills.lang b/htdocs/langs/en_US/bills.lang index 1f7e69e1a12..da698abe169 100644 --- a/htdocs/langs/en_US/bills.lang +++ b/htdocs/langs/en_US/bills.lang @@ -14,8 +14,11 @@ BillsStatisticsSuppliers=Vendors invoices statistics DisabledBecauseDispatchedInBookkeeping=Disabled because invoice was dispatched into bookkeeping DisabledBecauseNotLastInvoice=Disabled because invoice is not erasable. Some invoices were recorded after this one and it will create holes in the counter. DisabledBecauseNotLastSituationInvoice=Disabled because invoice is not erasable. This invoice is not the last one in situation invoice cycle. -DisabledBecauseAlreadyPrintedOnce=Disabled because invoice has already been printed at least once +DisabledBecauseThereIsAPayment=Disabled because invoice has already some payments. +DisabledBecauseAlreadyPrintedOnce=Disabled because invoice has already been downloaded or printed at least once DisabledBecauseAlreadySentByEmail=Disabled because invoice has already been sent by email at least once +DisabledBecauseVersionProtected=Disabled because invoice has already been validated and you are using version secured against deletion of invoices +DisabledBecauseNotDraft=Disabled because invoice is not in draft status DisabledBecauseNotErasable=Disabled because cannot be erased (unknown reason) InvoiceStandard=Standard invoice InvoiceStandardAsk=Standard invoice diff --git a/htdocs/langs/en_US/blockedlog.lang b/htdocs/langs/en_US/blockedlog.lang index af54a1e22d7..12efe7ddedc 100644 --- a/htdocs/langs/en_US/blockedlog.lang +++ b/htdocs/langs/en_US/blockedlog.lang @@ -84,3 +84,5 @@ logPAYMENT_VARIOUS_MODIFY=Payment (not assigned to an invoice) modified logBLOCKEDLOG_EXPORT=Export of unalterable logs into a file DebugInfo=Debug info PreviousFingerprint=Previous fingerprint +PaymentOf=Payment of +ReplacedBy=Replaced by From ada7bd2fd89e8e8d76ba4a1cd5ed5f81d43f2ce8 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Thu, 1 Jan 2026 16:43:14 +0100 Subject: [PATCH 02/18] Debug v23 --- .../blockedlog/admin/blockedlog_archives.php | 449 +++++++++++------- htdocs/blockedlog/admin/blockedlog_list.php | 20 +- htdocs/blockedlog/class/blockedlog.class.php | 30 +- htdocs/core/modules/modBlockedLog.class.php | 4 +- ..._modBlockedlog_ActionsBlockedLog.class.php | 15 +- .../install/mysql/migration/22.0.0-23.0.0.sql | 2 +- .../install/mysql/tables/llx_blockedlog.sql | 2 + htdocs/install/upgrade2.php | 2 +- htdocs/langs/en_US/blockedlog.lang | 13 +- 9 files changed, 346 insertions(+), 191 deletions(-) diff --git a/htdocs/blockedlog/admin/blockedlog_archives.php b/htdocs/blockedlog/admin/blockedlog_archives.php index 0d3dfb10fd7..0ad8db08e92 100644 --- a/htdocs/blockedlog/admin/blockedlog_archives.php +++ b/htdocs/blockedlog/admin/blockedlog_archives.php @@ -42,6 +42,7 @@ require_once DOL_DOCUMENT_ROOT.'/blockedlog/class/blockedlog.class.php'; require_once DOL_DOCUMENT_ROOT.'/core/lib/admin.lib.php'; require_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php'; require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php'; +require_once DOL_DOCUMENT_ROOT.'/core/lib/functions2.lib.php'; require_once DOL_DOCUMENT_ROOT.'/core/class/html.formother.class.php'; // Load translation files required by the page @@ -196,16 +197,18 @@ if (GETPOST('action') == 'export' && $user->hasRight('blockedlog', 'read')) { / if (!$error) { // Get the ID of the first line qualified - $sql = "SELECT rowid,date_creation,tms,user_fullname,action,amounts,element,fk_object,date_object,ref_object,signature,fk_user,object_data"; + $sql = "SELECT rowid"; $sql .= " FROM ".MAIN_DB_PREFIX."blockedlog"; $sql .= " WHERE entity = ".((int) $conf->entity); + // For unalterable log, we are using the date of creation of the log. Note that a bookkeeper may decide to dispatch an invoice + // on different periods for example to manage depreciation. $sql .= " AND date_creation BETWEEN '".$db->idate($dates)."' AND '".$db->idate($datee)."'"; - $sql .= " ORDER BY rowid ASC"; // Required so we get the first one + $sql .= " ORDER BY date_creation ASC, rowid ASC"; // Required so we get the first one $sql .= $db->plimit(1); $res = $db->query($sql); if ($res) { - // Make the first fetch to get first line + // Make the first fetch to get first line and then get the previous hash. $obj = $db->fetch_object($res); if ($obj) { $firstid = $obj->rowid; @@ -220,54 +223,66 @@ if (GETPOST('action') == 'export' && $user->hasRight('blockedlog', 'read')) { / } } + // Define file name + $registrationnumber = getHashUniqueIdOfRegistration(); + $secretkey = $registrationnumber; + + $yearmonthtoexport = GETPOSTINT('yeartoexport').'-'.(GETPOSTINT('monthtoexport') > 0 ? sprintf("%02d", GETPOSTINT('monthtoexport')) : ''); + $yearmonthdateofexport = dol_print_date(dol_now(), 'dayhourrfc', 'gmt'); + $yearmonthdateofexportstandard = dol_print_date(dol_now(), 'dayhourlog', 'gmt'); + + $nameofdownoadedfile = "unalterable-log-archive-".$dolibarr_main_db_name."-".str_replace('-', '', $yearmonthtoexport).'-'.$yearmonthdateofexportstandard.'UTC-DONOTMODIFY.csv'; + + //$tmpfile = $conf->admin->dir_temp.'/unalterable-log-archive-tmp-'.$user->id.'.csv'; + $tmpfile = getMultidirOutput($block_static, 'blockedlog').'/archives/'.$nameofdownoadedfile; + + $formatexport = 'VE1'; + + + // Init var for totals + $totalhtamountalllines = array('BILL_VALIDATE' => 0, 'PAYMENT_CUSTOMER_CREATE' => 0); + $totalvatamountalllines = array('BILL_VALIDATE' => 0, 'PAYMENT_CUSTOMER_CREATE' => 0); + $totalamountalllines = array('BILL_VALIDATE' => 0, 'PAYMENT_CUSTOMER_CREATE' => 0); + $totalhtamountlifetime = array('BILL_VALIDATE' => 0, 'PAYMENT_CUSTOMER_CREATE' => 0); + $totalvatamountlifetime = array('BILL_VALIDATE' => 0, 'PAYMENT_CUSTOMER_CREATE' => 0); + $totalamountlifetime = array('BILL_VALIDATE' => 0, 'PAYMENT_CUSTOMER_CREATE' => 0); + + if (!$error) { + $fh = fopen($tmpfile, 'w'); + } + + if (!$error && $fh) { // Now restart request with all data, so without the limit(1) in sql request - $sql = "SELECT rowid, date_creation, tms, user_fullname, action, amounts, element, fk_object, date_object, ref_object,"; + $sql = "SELECT rowid, date_creation, tms, user_fullname, action, amounts_taxexcl, amounts, element, fk_object, date_object, ref_object,"; $sql .= " signature, fk_user, object_data, object_version, object_format, debuginfo"; $sql .= " FROM ".MAIN_DB_PREFIX."blockedlog"; $sql .= " WHERE entity = ".((int) $conf->entity); - if (GETPOSTINT('monthtoexport') > 0 || GETPOSTINT('yeartoexport') > 0) { - $dates = dol_get_first_day(GETPOSTINT('yeartoexport'), GETPOSTINT('monthtoexport') > 0 ? GETPOSTINT('monthtoexport') : 1); - $datee = dol_get_last_day(GETPOSTINT('yeartoexport'), GETPOSTINT('monthtoexport') > 0 ? GETPOSTINT('monthtoexport') : 12); - $sql .= " AND date_creation BETWEEN '".$db->idate($dates)."' AND '".$db->idate($datee)."'"; - } - $sql .= " ORDER BY rowid ASC"; // Required so later we can use the parameter $previoushash of checkSignature() + // For unalterable log, we are using the date of creation of the log. Note that a bookkeeper may decide to dispatch an invoice + // on different periods for example to manage depreciation. + $sql .= " AND date_creation BETWEEN '".$db->idate($dates)."' AND '".$db->idate($datee)."'"; + $sql .= " ORDER BY date_creation ASC, rowid ASC"; // Required so later we can use the parameter $previoushash of checkSignature() $resql = $db->query($sql); if ($resql) { - $registrationnumber = getHashUniqueIdOfRegistration(); - $secretkey = $registrationnumber; - - $yearmonthtoexport = GETPOSTINT('yeartoexport').(GETPOSTINT('monthtoexport') > 0 ? sprintf("%02d", GETPOSTINT('monthtoexport')) : ''); - $yearmonthdateofexport = dol_print_date(dol_now(), 'dayhourlog', 'gmt'); - - $nameofdownoadedfile = "unalterable-log-archive-".$dolibarr_main_db_name."-".$yearmonthtoexport.'-'.$yearmonthdateofexport.'UTC-DONOTMODIFY.csv'; - - //$tmpfile = $conf->admin->dir_temp.'/unalterable-log-archive-tmp-'.$user->id.'.csv'; - $tmpfile = getMultidirOutput($block_static, 'blockedlog').'/archives/'.$nameofdownoadedfile; - - $fh = fopen($tmpfile, 'w'); - - $formatexport = 'V1'; - // Print line with title fwrite($fh, "BEGIN - date=".$yearmonthdateofexport." - period=".$yearmonthtoexport." - formatexport=".$formatexport." - user=".$user->getFullName($langs) .';'.$langs->transnoentities('Id') .';'.$langs->transnoentities('DateCreation') .';'.$langs->transnoentities('Action') - .';'.$langs->transnoentities('Amounts') + .';'.$langs->transnoentities('AmountHT') + .';'.$langs->transnoentities('AmountTTC') .';'.$langs->transnoentities('Ref') .';'.$langs->transnoentities('Date') .';'.$langs->transnoentities('User') .';'.$langs->transnoentities('LinkTo') .';'.$langs->transnoentities('LinkType') .';'.$langs->transnoentities('FullData') - .';'.$langs->transnoentities('Version') - .';'.$langs->transnoentities('Fingerprint') + .';'.$langs->transnoentities('Version') // Version Dolibarr, example 22.0.0 + .';'.$langs->transnoentities('VersionSignature') // Rule used for fingerprint calculation + .';'.$langs->transnoentities('FingerprintDatabase') // Signature .';'.$langs->transnoentities('Status') .';'.$langs->transnoentities('FingerprintExport') - .';'.$langs->transnoentities('FingerprintFormat') - //.';'.$langs->transnoentities('FingerprintExportHMAC') ."\n"); $loweridinerror = 0; @@ -285,8 +300,8 @@ if (GETPOST('action') == 'export' && $user->hasRight('blockedlog', 'read')) { / $block_static->date_creation = $db->jdate($obj->date_creation); // jdate(date_creation) is UTC + $block_static->amounts_excl = (float) $obj->amounts_excl; // Database store value with 8 digits, we cut ending 0 them with (flow) $block_static->amounts = (float) $obj->amounts; // Database store value with 8 digits, we cut ending 0 them with (flow) - $block_static->vat = $obj->vat; $block_static->action = $obj->action; $block_static->date_object = $db->jdate($obj->date_object); // jdate(date_object) is UTC @@ -347,148 +362,260 @@ if (GETPOST('action') == 'export' && $user->hasRight('blockedlog', 'read')) { / sumAmountsForUnalterableEvent($block_static, $totalhtamount, $totalvatamount, $totalamount, $total_ht, $total_vat, $total_ttc); - fwrite($fh, - ';'.$block_static->id - .';'.$block_static->date_creation - .';'.$block_static->action - .';'.$block_static->amounts // Can be 1.20000000 with 8 digits. TODO Clean to have 8 digits in V1 - .';"'.str_replace('"', '""', $block_static->ref_object).'";' - .$block_static->date_object - .';"'.str_replace('"', '""', $block_static->user_fullname).'";"' - .str_replace('"', '""', $block_static->linktoref).'";"' - .str_replace('"', '""', $block_static->linktype).'";"' - .str_replace('"', '""', $obj->object_data).'"' // We must the string to decode into object with dolDecodeBlockedData - .';"'.str_replace('"', '""', $block_static->object_version).'";"' - .str_replace('"', '""', $block_static->signature).'";"' - .str_replace('"', '""', $statusofrecord).'";"' - .str_replace('"', '""', $signatureexport).'";"' - .str_replace('"', '""', $block_static->object_format).'";' - //.str_replace('"', '""', $signatureexporthmac).'"' - //.';'.$statusofrecordnote - ."\n"); + fwrite($fh, ";" + .csvClean($block_static->id).';' + .csvClean($block_static->date_creation).';' + .csvClean($block_static->action).';' + .csvClean($block_static->amounts_taxexcl).';' // Can be 1.20000000 with 8 digits. TODO Clean to have 8 digits in V1 + .csvClean($block_static->amounts).';' // Can be 1.20000000 with 8 digits. TODO Clean to have 8 digits in V1 + .csvClean($block_static->ref_object).';' + .csvClean($block_static->date_object).';' + .csvClean($block_static->user_fullname).';' + .csvClean($block_static->linktoref).';' + .csvClean($block_static->linktype).';' + .csvClean($obj->object_data).';' // We must the string to decode into object with dolDecodeBlockedData + .csvClean($block_static->object_version).';' + .csvClean($block_static->object_format).';' + .csvClean($block_static->signature).';' + .csvClean($statusofrecord).';' + .csvClean($signatureexport).';'."\n"); // Set new previous hash for next fetch $previoushash = $obj->signature; $i++; } + } else { + $error++; + setEventMessages($db->lasterror, null, 'errors'); + } - $totalhtamountallines = array('BILL_VALIDATE' => 0); - $totalvatamountallines = array('BILL_VALIDATE' => 0); - $totalamountalllines = array('BILL_VALIDATE' => 0); - if (array_key_exists('BILL_VALIDATE', $totalhtamount)) { - foreach ($totalhtamount['BILL_VALIDATE'] as $key => $val) { - $totalhtamountalllines['BILL_VALIDATE'] += $val; + // Now calculate cumulative total of all invoices validated + if (array_key_exists('BILL_VALIDATE', $totalhtamount)) { + foreach ($totalhtamount['BILL_VALIDATE'] as $key => $val) { + $totalhtamountalllines['BILL_VALIDATE'] += $val; + } + foreach ($totalvatamount['BILL_VALIDATE'] as $key => $val) { + $totalvatamountalllines['BILL_VALIDATE'] += $val; + } + foreach ($totalamount['BILL_VALIDATE'] as $key => $val) { + $totalamountalllines['BILL_VALIDATE'] += $val; + } + } + if (array_key_exists('PAYMENT_CUSTOMER_CREATE', $totalhtamount)) { + foreach ($totalhtamount['PAYMENT_CUSTOMER_CREATE'] as $key => $val) { + $totalhtamountalllines['PAYMENT_CUSTOMER_CREATE'] += $val; + } + foreach ($totalvatamount['PAYMENT_CUSTOMER_CREATE'] as $key => $val) { + $totalvatamountalllines['PAYMENT_CUSTOMER_CREATE'] += $val; + } + foreach ($totalamount['PAYMENT_CUSTOMER_CREATE'] as $key => $val) { + $totalamountalllines['PAYMENT_CUSTOMER_CREATE'] += $val; + } + } + + + // Add a final line with cumulative total of invoices validated (BILL_VALIDATE) + $block_static->id = ''; + $block_static->date_creation = ''; + $block_static->action = 'BILL_VALIDATE'; + $block_static->amounts_taxexcl = $totalhtamountalllines['BILL_VALIDATE']; + $block_static->amounts = $totalamountalllines['BILL_VALIDATE']; + $block_static->ref_object = $langs->transnoentitiesnoconv("VAT").': '.$totalvatamountalllines['BILL_VALIDATE']; + $block_static->date_object = ''; + $block_static->user_fullname = ''; + $block_static->linktoref = ''; + $block_static->linktype = ''; + $block_static->object_data = ''; + $block_static->object_version = ''; + $block_static->signature = ''; + + $statusofrecord = ''; + $signatureexport = ''; + + $block_static->object_format = ''; + + fwrite($fh, 'Cumulative total - Invoice validations;' + .csvClean($block_static->id).';' + .csvClean($block_static->date_creation).';' + .csvClean($block_static->action).';' + .csvClean($block_static->amounts_taxexcl).';' // Can be 1.20000000 with 8 digits. TODO Clean to have 8 digits in V1 + .csvClean($block_static->amounts).';' // Can be 1.20000000 with 8 digits. TODO Clean to have 8 digits in V1 + .csvClean($block_static->ref_object).';' + .csvClean($block_static->date_object).';' + .csvClean($block_static->user_fullname).';' + .csvClean($block_static->linktoref).';' + .csvClean($block_static->linktype).';' + .csvClean($obj->object_data).';' // We must the string to decode into object with dolDecodeBlockedData + .csvClean($block_static->object_version).';' + .csvClean($block_static->object_format).';' + .csvClean($block_static->signature).';' + .csvClean($statusofrecord).';' + .csvClean($signatureexport).';'."\n"); + + + // Add a final line with cumulative total of invoices validated (PAYMENT_CUSTOMER_CREATE) + $block_static->id = ''; + $block_static->date_creation = ''; + $block_static->action = 'PAYMENT_CUSTOMER_CREATE'; + $block_static->amounts_taxexcl = ''; + $block_static->amounts = $totalamountalllines['PAYMENT_CUSTOMER_CREATE']; + $block_static->ref_object = ''; + $block_static->date_object = ''; + $block_static->user_fullname = ''; + $block_static->linktoref = ''; + $block_static->linktype = ''; + $block_static->object_data = ''; + $block_static->object_version = ''; + $block_static->signature = ''; + + $statusofrecord = ''; + $signatureexport = ''; + + $block_static->object_format = ''; + + fwrite($fh, 'Cumulative total - Invoice payments;' + .csvClean($block_static->id).';' + .csvClean($block_static->date_creation).';' + .csvClean($block_static->action).';' + .csvClean($block_static->amounts_taxexcl).';' // Can be 1.20000000 with 8 digits. TODO Clean to have 8 digits in V1 + .csvClean($block_static->amounts).';' // Can be 1.20000000 with 8 digits. TODO Clean to have 8 digits in V1 + .csvClean($block_static->ref_object).';' + .csvClean($block_static->date_object).';' + .csvClean($block_static->user_fullname).';' + .csvClean($block_static->linktoref).';' + .csvClean($block_static->linktype).';' + .csvClean($obj->object_data).';' // We must the string to decode into object with dolDecodeBlockedData + .csvClean($block_static->object_version).';' + .csvClean($block_static->object_format).';' + .csvClean($block_static->signature).';' + .csvClean($statusofrecord).';' + .csvClean($signatureexport).';'."\n"); + + + // Calculate perpetual totals + $sql = "SELECT action, object_format, MIN(date_creation) as datemin, SUM(amounts_taxexcl) as sumamounts_taxexcl, SUM(amounts) as sumamounts"; + $sql .= " FROM ".MAIN_DB_PREFIX."blockedlog"; + $sql .= " WHERE entity = ".((int) $conf->entity); + $sql .= " AND action IN ('BILL_VALIDATE', 'PAYMENT_CUSTOMER_CREATE')"; + $sql .= " GROUP BY action, object_format"; + + $foundoldformat = 0; + $firstrecorddate = array(); + $resql = $db->query($sql); + if ($resql) { + while ($obj = $db->fetch_object($resql)) { + if (!empty($firstrecorddate[$obj->action])) { + $firstrecorddate[$obj->action] = min($firstrecorddate[$obj->action], $db->jdate($obj->datemin)); + } else { + $firstrecorddate[$obj->action] = $db->jdate($obj->datemin); } - foreach ($totalvatamount['BILL_VALIDATE'] as $key => $val) { - $totalvatamountalllines['BILL_VALIDATE'] += $val; - } - foreach ($totalamount['BILL_VALIDATE'] as $key => $val) { - $totalamountalllines['BILL_VALIDATE'] += $val; + $totalamountlifetime[$obj->action] += $obj->sumamounts; + // If format of line is old, the sumamounts_taxexcl was not recorded. So we flag this case. + if (empty($obj->object_format) || $obj->object_format == 'V1') { + $foundoldformat = 1; + } else { + $totalhtamountlifetime[$obj->action] += $obj->sumamounts_taxexcl; } } - - - // Add a final line with cumulative total of invoices validated - $block_static->id = ''; - $block_static->date_creation = ''; - $block_static->action = ''; - $block_static->amounts = $langs->transnoentitiesnoconv("HT").': '.$totalhtamountalllines['BILL_VALIDATE'].' | '.$langs->transnoentitiesnoconv("VAT").': '.$totalvatamountalllines['BILL_VALIDATE'].' | '.$langs->transnoentitiesnoconv("TTC").': '.$totalamountalllines['BILL_VALIDATE']; - $block_static->ref_object = ''; - $block_static->date_object = ''; - $block_static->user_fullname = ''; - $block_static->linktoref = ''; - $block_static->linktype = ''; - $block_static->object_data = ''; - $block_static->object_version = ''; - $block_static->signature = ''; - - $statusofrecord = ''; - $signatureexport = ''; - - $block_static->object_format = ''; - - fwrite($fh, 'Cumulative total Invoice Validation;' - .$block_static->id - .';'.$block_static->date_creation - .';'.$block_static->action - .';'.$block_static->amounts // Can be 1.20000000 with 8 digits. TODO Clean to have 8 digits in V1 - .';"'.str_replace('"', '""', $block_static->ref_object).'";' - .$block_static->date_object - .';"'.str_replace('"', '""', $block_static->user_fullname).'";"' - .str_replace('"', '""', $block_static->linktoref).'";"' - .str_replace('"', '""', $block_static->linktype).'";"' - .str_replace('"', '""', $obj->object_data).'"' // We must the string to decode into object with dolDecodeBlockedData - .';"'.str_replace('"', '""', $block_static->object_version).'";"' - .str_replace('"', '""', $block_static->signature).'";"' - .str_replace('"', '""', $statusofrecord).'";"' - .str_replace('"', '""', $signatureexport).'";"' - .str_replace('"', '""', $block_static->object_format).'";' - //.str_replace('"', '""', $signatureexporthmac).'"' - //.';'.$statusofrecordnote - ."\n"); - - - // Add a final line with perpetual total - $totalhtamountlifetime = array(); - $totalvatamountlifetime = array(); - $totalamountlifetime = array(); - - - $block_static->id = ''; - $block_static->date_creation = ''; - $block_static->action = ''; - $block_static->amounts = $totalamountlifetime['BILL_VALIDATE']; - $block_static->ref_object = ''; - $block_static->date_object = ''; - $block_static->user_fullname = ''; - $block_static->linktoref = ''; - $block_static->linktype = ''; - $block_static->object_data = ''; - $block_static->object_version = ''; - $block_static->signature = ''; - - $statusofrecord = ''; - $signatureexport = ''; - - $block_static->object_format = ''; - - fwrite($fh, 'Perpetual total Invoice Validation;' - .$block_static->id - .';'.$block_static->date_creation - .';'.$block_static->action - .';'.$block_static->amounts // Can be 1.20000000 with 8 digits. TODO Clean to have 8 digits in V1 - .';"'.str_replace('"', '""', $block_static->ref_object).'";' - .$block_static->date_object - .';"'.str_replace('"', '""', $block_static->user_fullname).'";"' - .str_replace('"', '""', $block_static->linktoref).'";"' - .str_replace('"', '""', $block_static->linktype).'";"' - .str_replace('"', '""', $obj->object_data).'"' // We must the string to decode into object with dolDecodeBlockedData - .';"'.str_replace('"', '""', $block_static->object_version).'";"' - .str_replace('"', '""', $block_static->signature).'";"' - .str_replace('"', '""', $statusofrecord).'";"' - .str_replace('"', '""', $signatureexport).'";"' - .str_replace('"', '""', $block_static->object_format).'";' - //.str_replace('"', '""', $signatureexporthmac).'"' - //.';'.$statusofrecordnote - ."\n"); - - - fclose($fh); - - // Calculate the md5 of the file (the last line has a return line) - $algo = 'sha256'; - $sha256 = hash_file($algo, $tmpfile); - $hmacsha256 = hash_hmac_file($algo, $tmpfile, $secretkey); - - // Now add a signature to check integrity at end of file - file_put_contents($tmpfile, 'END - sha256='.$sha256.' - hmac_sha256='.$hmacsha256, FILE_APPEND); - dolChmod($tmpfile); - - setEventMessages($langs->trans("FileGenerated"), null); } else { + $error++; setEventMessages($db->lasterror, null, 'errors'); } + + + // Add a final line with perpetual total for invoice validations + $block_static->id = ''; + $block_static->date_creation = ''; + $block_static->action = 'BILL_VALIDATE'; + // if an old format was found, we do not have reliable amount excluding tax for lifetime value, we do not show it + $block_static->amounts_taxexcl = ($foundoldformat ? '' : $totalhtamountlifetime['BILL_VALIDATE']); + $block_static->amounts = $totalamountlifetime['BILL_VALIDATE']; + // if an old format was found, we do not have reliable VAT amount for lifetime value, we do not show it + $block_static->ref_object = ($foundoldformat ? '' : $langs->transnoentitiesnoconv("VAT").': '.($block_static->amounts - $totalhtamountlifetime['BILL_VALIDATE'])); + $block_static->date_object = ''; + $block_static->user_fullname = ''; + $block_static->linktoref = ''; + $block_static->linktype = ''; + $block_static->object_data = ''; + $block_static->object_version = ''; + $block_static->signature = ''; + + $statusofrecord = ''; + $signatureexport = ''; + + $block_static->object_format = ''; + + fwrite($fh, 'Lifetime total (>= '.dol_print_date($firstrecorddate['BILL_VALIDATE'], 'standard').') - Invoice validations;' + .csvClean($block_static->id).';' + .csvClean($block_static->date_creation).';' + .csvClean($block_static->action).';' + .csvClean($block_static->amounts_taxexcl).';' // Can be 1.20000000 with 8 digits. TODO Clean to have 8 digits in V1 + .csvClean($block_static->amounts).';' // Can be 1.20000000 with 8 digits. TODO Clean to have 8 digits in V1 + .csvClean($block_static->ref_object).';' + .csvClean($block_static->date_object).';' + .csvClean($block_static->user_fullname).';' + .csvClean($block_static->linktoref).';' + .csvClean($block_static->linktype).';' + .csvClean($obj->object_data).';' // We must the string to decode into object with dolDecodeBlockedData + .csvClean($block_static->object_version).';' + .csvClean($block_static->object_format).';' + .csvClean($block_static->signature).';' + .csvClean($statusofrecord).';' + .csvClean($signatureexport).';'."\n"); + + // Add a final line with perpetual total for customer payments + $block_static->id = ''; + $block_static->date_creation = ''; + $block_static->action = 'PAYMENT_CUSTOMER_CREATE'; + $block_static->amounts_taxtecl = ''; + $block_static->amounts = $totalamountlifetime['PAYMENT_CUSTOMER_CREATE']; + $block_static->ref_object = ''; + $block_static->date_object = ''; + $block_static->user_fullname = ''; + $block_static->linktoref = ''; + $block_static->linktype = ''; + $block_static->object_data = ''; + $block_static->object_version = ''; + $block_static->signature = ''; + + $statusofrecord = ''; + $signatureexport = ''; + + $block_static->object_format = ''; + + fwrite($fh, 'Lifetime total (>= '.dol_print_date($firstrecorddate['PAYMENT_CUSTOMER_CREATE'], 'standard').') - Invoice payments;' + .csvClean($block_static->id).';' + .csvClean($block_static->date_creation).';' + .csvClean($block_static->action).';' + .csvClean($block_static->amounts_taxexcl).';' // Can be 1.20000000 with 8 digits. TODO Clean to have 8 digits in V1 + .csvClean($block_static->amounts).';' // Can be 1.20000000 with 8 digits. TODO Clean to have 8 digits in V1 + .csvClean($block_static->ref_object).';' + .csvClean($block_static->date_object).';' + .csvClean($block_static->user_fullname).';' + .csvClean($block_static->linktoref).';' + .csvClean($block_static->linktype).';' + .csvClean($obj->object_data).';' // We must the string to decode into object with dolDecodeBlockedData + .csvClean($block_static->object_version).';' + .csvClean($block_static->object_format).';' + .csvClean($block_static->signature).';' + .csvClean($statusofrecord).';' + .csvClean($signatureexport).';'."\n"); + + fclose($fh); + + // Calculate the md5 of the file (the last line has a return line) + $algo = 'sha256'; + $sha256 = hash_file($algo, $tmpfile); + $hmacsha256 = hash_hmac_file($algo, $tmpfile, $secretkey); + + // Now add a signature to check integrity at end of file + file_put_contents($tmpfile, 'END - sha256='.$sha256.' - hmac_sha256='.$hmacsha256, FILE_APPEND); + dolChmod($tmpfile); + + if (!$error) { + setEventMessages($langs->trans("FileGenerated"), null); + } } if (!$error) { @@ -506,7 +633,7 @@ if (GETPOST('action') == 'export' && $user->hasRight('blockedlog', 'read')) { / $object->label = 'Export unalterable logs - Period: year='.GETPOSTINT('yeartoexport').(GETPOSTINT('monthtoexport') ? ' month='.GETPOSTINT('monthtoexport') : ''); $action = 'BLOCKEDLOG_EXPORT'; - $result = $b->setObjectData($object, $action, 0, $user); + $result = $b->setObjectData($object, $action, 0, $user, null); //var_dump($b); exit; if ($result < 0) { diff --git a/htdocs/blockedlog/admin/blockedlog_list.php b/htdocs/blockedlog/admin/blockedlog_list.php index cbedc842903..5da2a53db2c 100644 --- a/htdocs/blockedlog/admin/blockedlog_list.php +++ b/htdocs/blockedlog/admin/blockedlog_list.php @@ -163,10 +163,20 @@ if (GETPOST('downloadcsv', 'alpha')) { setEventMessages($langs->trans("ErrorFieldRequired", $langs->transnoentitiesnoconv("Year")), null, "errors"); $error++; } else { + $dates = dol_get_first_day(GETPOSTINT('yeartoexport'), GETPOSTINT('monthtoexport') > 0 ? GETPOSTINT('monthtoexport') : 1); + $datee = dol_get_last_day(GETPOSTINT('yeartoexport'), GETPOSTINT('monthtoexport') > 0 ? GETPOSTINT('monthtoexport') : 12); + + if ($datee >= dol_now()) { + setEventMessages($langs->trans("ErrorPeriodMustBePastToAllowExport"), null, "errors"); + $error++; + } + // Get the ID of the first line qualified - $sql = "SELECT rowid,date_creation,tms,user_fullname,action,amounts,element,fk_object,date_object,ref_object,signature,fk_user,object_data"; + $sql = "SELECT rowid, date_creation, tms, user_fullname, action, amounts, amounts_taxexcl, element, fk_object, date_object, ref_object, signature, fk_user, object_data"; $sql .= " FROM ".MAIN_DB_PREFIX."blockedlog"; $sql .= " WHERE entity = ".((int) $conf->entity); + // For unalterable log, we are using the date of creation of the log. Note that a bookkeeper may decide to dispatch an invoice + // on different periods for example to manage depreciation. if (GETPOSTINT('monthtoexport') > 0 || GETPOSTINT('yeartoexport') > 0) { $dates = dol_get_first_day(GETPOSTINT('yeartoexport'), GETPOSTINT('monthtoexport') ? GETPOSTINT('monthtoexport') : 1); $datee = dol_get_last_day(GETPOSTINT('yeartoexport'), GETPOSTINT('monthtoexport') ? GETPOSTINT('monthtoexport') : 12); @@ -207,7 +217,7 @@ if (GETPOST('downloadcsv', 'alpha')) { $object->label = 'Export unalterable logs - Period: year='.GETPOSTINT('yeartoexport').(GETPOSTINT('monthtoexport') ? ' month='.GETPOSTINT('monthtoexport') : ''); $action = 'BLOCKEDLOG_EXPORT'; - $result = $b->setObjectData($object, $action, 0, $user); + $result = $b->setObjectData($object, $action, 0, $user, null); //var_dump($b); exit; if ($result < 0) { @@ -225,7 +235,7 @@ if (GETPOST('downloadcsv', 'alpha')) { if (!$error) { // Now restart request with all data, si without the limit(1) in sql request - $sql = "SELECT rowid, date_creation, tms, user_fullname, action, amounts, element, fk_object, date_object, ref_object,"; + $sql = "SELECT rowid, date_creation, tms, user_fullname, action, amounts, amounts_taxexcl, element, fk_object, date_object, ref_object,"; $sql .= " signature, fk_user, object_data, object_version, object_format, debuginfo"; $sql .= " FROM ".MAIN_DB_PREFIX."blockedlog"; $sql .= " WHERE entity = ".((int) $conf->entity); @@ -274,8 +284,8 @@ if (GETPOST('downloadcsv', 'alpha')) { $block_static->date_creation = $db->jdate($obj->date_creation); // TODO Use gmt - $block_static->amounts = (float) $obj->amounts; // Database store value with 8 digits, we cut ending 0 them with (flow) - $block_static->vat = $obj->vat; + $block_static->amounts_taxexcl = (float) $obj->amounts_taxexcl; // Database store value with 8 digits, we cut ending 0 them with (float) + $block_static->amounts = (float) $obj->amounts; // Database store value with 8 digits, we cut ending 0 them with (float) $block_static->action = $obj->action; $block_static->date_object = $db->jdate($obj->date_object); // TODO Use gmt ? diff --git a/htdocs/blockedlog/class/blockedlog.class.php b/htdocs/blockedlog/class/blockedlog.class.php index 6ed57d48901..1f51098a716 100644 --- a/htdocs/blockedlog/class/blockedlog.class.php +++ b/htdocs/blockedlog/class/blockedlog.class.php @@ -71,6 +71,11 @@ class BlockedLog */ public $amounts = null; + /** + * @var float|string|null + */ + public $amounts_taxexcl = null; + /** * @var float|string|null */ @@ -457,16 +462,17 @@ class BlockedLog /** * Populate properties of an unalterable log entry from object data. - * This populates ->object_data but also other fields like ->action, ->amounts and ->linktoref and ->linktype + * This populates ->object_data but also other fields like ->action, ->amounts_taxexcl, ->amounts and ->linktoref and ->linktype * It also populates some debug info like ->element and ->fk_object * - * @param CommonObject|stdClass $object object to store - * @param string $action action - * @param float|int $amounts amounts - * @param ?User $fuser User object (forced) - * @return int<-1,-1>|int<1,1> >0 if OK, <0 if KO + * @param CommonObject|stdClass $object Object to store + * @param string $action Action code + * @param float|int $amounts amounts (incl tax) + * @param ?User $fuser User object (forced) + * @param float|int|null $amounts_taxexcl amounts (excl tax or null if not relevant) + * @return int<-1,-1>|int<1,1> >0 if OK, <0 if KO */ - public function setObjectData(&$object, $action, $amounts, $fuser = null) + public function setObjectData(&$object, $action, $amounts, $fuser = null, $amounts_taxexcl = null) { global $langs, $user, $mysoc; @@ -479,6 +485,7 @@ class BlockedLog // action $this->action = $action; // amount + $this->amounts_taxexcl = $amounts_taxexcl; $this->amounts = $amounts; // date if ($object->element == 'payment' || $object->element == 'payment_supplier') { @@ -1014,7 +1021,7 @@ class BlockedLog return -1; } - $sql = "SELECT b.rowid, b.date_creation, b.signature, b.amounts, b.action, b.element, b.fk_object, b.entity,"; + $sql = "SELECT b.rowid, b.date_creation, b.signature, b.amounts_taxexcl, b.amounts, b.action, b.element, b.fk_object, b.entity,"; $sql .= " b.certified, b.tms, b.fk_user, b.user_fullname, b.date_object, b.ref_object, b.object_data, b.object_version, b.object_format"; $sql .= " FROM ".MAIN_DB_PREFIX."blockedlog as b"; if ($id) { @@ -1031,6 +1038,7 @@ class BlockedLog $this->date_creation = $this->db->jdate($obj->date_creation); // jdate(date_creation)is UTC $this->date_modification = $this->db->jdate($obj->tms); // jdate(tms) is UTC + $this->amounts_taxecl = (is_null($obj->amounts_taxexcl) ? null : (float) $obj->amounts); $this->amounts = (float) $obj->amounts; $this->action = $obj->action; $this->element = $obj->element; @@ -1205,6 +1213,7 @@ class BlockedLog $sql = "INSERT INTO ".MAIN_DB_PREFIX."blockedlog ("; $sql .= " date_creation,"; $sql .= " action,"; + $sql .= " amounts_taxexcl,"; $sql .= " amounts,"; $sql .= " signature,"; $sql .= " element,"; @@ -1222,7 +1231,8 @@ class BlockedLog $sql .= ") VALUES ("; $sql .= "'".$this->db->idate($this->date_creation)."',"; $sql .= "'".$this->db->escape($this->action)."',"; - $sql .= $this->amounts.","; + $sql .= (is_null($this->amounts_taxexcl) ? "null" : (float) $this->amounts_taxexcl).","; + $sql .= (float) $this->amounts.","; $sql .= "'".$this->db->escape($this->signature)."',"; $sql .= "'".$this->db->escape($this->element)."',"; $sql .= (int) $this->fk_object.","; @@ -1339,7 +1349,7 @@ class BlockedLog return $this->date_creation.'|'.$this->action.'|'.$this->amounts.'|'.$this->ref_object.'|'.$this->date_object.'|'.$this->user_fullname; } elseif ($this->object_format == 'V2') { $s = $this->entity; - $s .= '|'.$this->date_creation.'|'.$this->action.'|'.$this->amounts.'|'.$this->ref_object.'|'.$this->date_object.'|'.$this->user_fullname; + $s .= '|'.$this->date_creation.'|'.$this->action.'|'.$this->amounts_taxexcl.'|'.$this->amounts.'|'.$this->ref_object.'|'.$this->date_object.'|'.$this->user_fullname; $s .= '|'.(string) $this->linktoref; $s .= '|'.(string) $this->linktype; return $s; diff --git a/htdocs/core/modules/modBlockedLog.class.php b/htdocs/core/modules/modBlockedLog.class.php index a81ec92a073..bc1469d42da 100644 --- a/htdocs/core/modules/modBlockedLog.class.php +++ b/htdocs/core/modules/modBlockedLog.class.php @@ -230,7 +230,7 @@ class modBlockedLog extends DolibarrModules // Add first entry in unalterable Log to track that module was activated $action = 'MODULE_SET'; - $result = $b->setObjectData($object, $action, 0); + $result = $b->setObjectData($object, $action, 0, $user, null); if ($result < 0) { $this->error = $b->error; @@ -274,7 +274,7 @@ class modBlockedLog extends DolibarrModules $object->label = 'Module disabled'; $b = new BlockedLog($this->db); - $result = $b->setObjectData($object, 'MODULE_RESET', 0); + $result = $b->setObjectData($object, 'MODULE_RESET', 0, $user, null); if ($result < 0) { $this->error = $b->error; $this->errors = $b->errors; diff --git a/htdocs/core/triggers/interface_50_modBlockedlog_ActionsBlockedLog.class.php b/htdocs/core/triggers/interface_50_modBlockedlog_ActionsBlockedLog.class.php index 9f6ee5d48be..064ac81163f 100644 --- a/htdocs/core/triggers/interface_50_modBlockedlog_ActionsBlockedLog.class.php +++ b/htdocs/core/triggers/interface_50_modBlockedlog_ActionsBlockedLog.class.php @@ -90,6 +90,7 @@ class InterfaceActionsBlockedLog extends DolibarrTriggers // Event/record is qualified $qualified = 0; + $amounts_taxexcl = null; $amounts = 0; if ($action === 'BILL_VALIDATE' || (($action === 'BILL_DELETE' || $action === 'BILL_SENTBYMAIL') && ($object->statut != 0 || $object->status != 0)) || $action === 'BILL_SUPPLIER_VALIDATE' || (($action === 'BILL_SUPPLIER_DELETE' || $action === 'BILL_SUPPLIER_SENTBYMAIL') && ($object->statut != 0 || $object->status != 0)) @@ -105,13 +106,18 @@ class InterfaceActionsBlockedLog extends DolibarrTriggers if (in_array($action, array( 'MEMBER_SUBSCRIPTION_CREATE', 'MEMBER_SUBSCRIPTION_MODIFY', 'MEMBER_SUBSCRIPTION_DELETE', 'DON_VALIDATE', 'DON_MODIFY', 'DON_DELETE'))) { - /** @var Don|Subscription $object */ + /** @var Don|Subscription $object */ $amounts = (float) $object->amount; } elseif ($action == 'CASHCONTROL_VALIDATE') { /** @var CashControl $object */ $amounts = (float) $object->cash + (float) $object->cheque + (float) $object->card; - } elseif (property_exists($object, 'total_ttc')) { - $amounts = (float) $object->total_ttc; + } else { + if (property_exists($object, 'total_ht')) { + $amounts_taxexcl = (float) $object->total_ht; + } + if (property_exists($object, 'total_ttc')) { + $amounts = (float) $object->total_ttc; + } } } /*if ($action === 'BILL_PAYED' || $action==='BILL_UNPAYED' @@ -123,7 +129,6 @@ class InterfaceActionsBlockedLog extends DolibarrTriggers if ($action === 'PAYMENT_CUSTOMER_CREATE' || $action === 'PAYMENT_SUPPLIER_CREATE' || $action === 'DONATION_PAYMENT_CREATE' || $action === 'PAYMENT_CUSTOMER_DELETE' || $action === 'PAYMENT_SUPPLIER_DELETE' || $action === 'DONATION_PAYMENT_DELETE') { $qualified++; - $amounts = 0; if (!empty($object->amounts)) { foreach ($object->amounts as $amount) { $amounts += (float) $amount; @@ -143,7 +148,7 @@ class InterfaceActionsBlockedLog extends DolibarrTriggers } // Set field date_object, ref_object, fk_object, element, object_data - $result = $b->setObjectData($object, $action, $amounts, $user); + $result = $b->setObjectData($object, $action, $amounts, $user, $amounts_taxexcl); if ($result < 0) { $this->setErrorsFromObject($b); diff --git a/htdocs/install/mysql/migration/22.0.0-23.0.0.sql b/htdocs/install/mysql/migration/22.0.0-23.0.0.sql index 5960940831d..1046685add2 100644 --- a/htdocs/install/mysql/migration/22.0.0-23.0.0.sql +++ b/htdocs/install/mysql/migration/22.0.0-23.0.0.sql @@ -320,7 +320,7 @@ ALTER TABLE llx_oauth_token ADD COLUMN expire_at datetime NULL AFTER lastaccess; ALTER TABLE llx_blockedlog ADD COLUMN linktoref varchar(255); ALTER TABLE llx_blockedlog ADD COLUMN linktype varchar(16); -ALTER TABLE llx_blockedlog ADD COLUMN vat double(24,8) DEFAULT NULL; +ALTER TABLE llx_blockedlog ADD COLUMN amounts_taxexcl double(24,8) DEFAULT NULL AFTER amounts; -- Incoterms 2025 and specific terms diff --git a/htdocs/install/mysql/tables/llx_blockedlog.sql b/htdocs/install/mysql/tables/llx_blockedlog.sql index ca58d0b1b35..6028e2c8b8f 100644 --- a/htdocs/install/mysql/tables/llx_blockedlog.sql +++ b/htdocs/install/mysql/tables/llx_blockedlog.sql @@ -24,6 +24,7 @@ CREATE TABLE llx_blockedlog date_creation datetime, -- field included into line signature action varchar(50), -- The type of event. field included into line signature amounts double(24,8) NOT NULL, -- field included into line signature (denormalized data from object_data) + amounts_taxexcl double(24,8) NULL, -- field included into line signature (denormalized data from object_data) ref_object varchar(255), -- field included into line signature (denormalized data from object_data) date_object datetime, -- field included into line signature (denormalized data from object_data) user_fullname varchar(255), -- field included into line signature (denormalized data from object_data) @@ -37,6 +38,7 @@ CREATE TABLE llx_blockedlog fk_user integer, fk_object integer, object_version varchar(32) DEFAULT '', -- in which version did the line was recorded + object_format varchar(16) DEFAULT 'V1', -- format of data stored in object_data certified integer, -- not used, reserved for future use tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, debuginfo mediumtext diff --git a/htdocs/install/upgrade2.php b/htdocs/install/upgrade2.php index 7b79975a204..ccd07ac2483 100644 --- a/htdocs/install/upgrade2.php +++ b/htdocs/install/upgrade2.php @@ -3864,7 +3864,7 @@ function migrate_reset_blocked_log($db, $langs, $conf) $object->date = dol_now(); $b = new BlockedLog($db); - $b->setObjectData($object, 'MODULE_SET', 0); + $b->setObjectData($object, 'MODULE_SET', 0, $user, null); $res = $b->create($user); if ($res <= 0) { diff --git a/htdocs/langs/en_US/blockedlog.lang b/htdocs/langs/en_US/blockedlog.lang index 12efe7ddedc..69328bf32d2 100644 --- a/htdocs/langs/en_US/blockedlog.lang +++ b/htdocs/langs/en_US/blockedlog.lang @@ -12,7 +12,7 @@ CompanyInitialKey=Company initial key (hash of genesis block) BrowseBlockedLog=Unalterable logs ShowAllFingerPrintsMightBeTooLong=Show all archived unlaterable logs (might be long) ShowAllFingerPrintsErrorsMightBeTooLong=Show all non-valid unalterable logs (might be long) -DownloadBlockChain=Download fingerprints +DownloadBlockChain=Download blockchain KoCheckFingerprintValidity=Archived log entry is not valid. It means someone (a hacker?) has modified some data of this record after it was recorded, OR has erased the previous archived record (check that the line with previous # exists) OR has modified the checksum of the previous record. OkCheckFingerprintValidity=Archived log record is valid. The data on this line was not modified and the entry follows the previous one. OkCheckFingerprintValidityButChainIsKo=Archived log seems valid compared to previous one but the chain was corrupted previously. @@ -22,10 +22,10 @@ BlockedLogBillDownload=Customer invoice download BlockedLogBillPreview=Customer invoice preview BlockedlogInfoDialog=Log Details ListOfTrackedEvents=List of tracked events -Fingerprint=Fingerprint -FingerprintExport=Fingerprint export -FingerprintExportHMAC=Fingerprint export HMAC -FingerprintFormat=Fingerprint format +Fingerprint=Signature +FingerprintDatabase=Signature in database +FingerprintExport=Signature export +FingerprintExportHMAC=Signature export HMAC DownloadLogCSV=Export unalterable logs (CSV) DataOfArchivedEvent=Complete data of archived event DataOfArchivedEventHelp=This field contains the complementary data that was archived on real time. Even if some parent business event could have been canceled or modified, the data stored here is the original data, and it can't be modified. @@ -83,6 +83,7 @@ logPAYMENT_VARIOUS_DELETE=Payment (not assigned to an invoice) logical deletion logPAYMENT_VARIOUS_MODIFY=Payment (not assigned to an invoice) modified logBLOCKEDLOG_EXPORT=Export of unalterable logs into a file DebugInfo=Debug info -PreviousFingerprint=Previous fingerprint +PreviousFingerprint=Previous signature PaymentOf=Payment of ReplacedBy=Replaced by +VersionSignature=Version signature From ca919942947d851e2a40f0a57807b76dfde929f8 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Thu, 1 Jan 2026 23:15:16 +0100 Subject: [PATCH 03/18] Debug v23 --- htdocs/core/menus/standard/eldy.lib.php | 4 +++- .../core/modules/modExpenseReport.class.php | 2 -- htdocs/expensereport/card.php | 22 +++++++++++++------ 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/htdocs/core/menus/standard/eldy.lib.php b/htdocs/core/menus/standard/eldy.lib.php index c91013c1e90..8f85de1ffe7 100644 --- a/htdocs/core/menus/standard/eldy.lib.php +++ b/htdocs/core/menus/standard/eldy.lib.php @@ -2518,7 +2518,9 @@ function get_left_menu_hrm($mainmenu, &$newmenu, $usemenuhider = 1, $leftmenu = $newmenu->add(dolBuildUrl('/expensereport/list.php', ['search_status' => 4, 'leftmenu' => 'expensereport', 'mainmenu' => 'hrm']), $langs->trans("Canceled"), 2, $user->hasRight('expensereport', 'lire')); $newmenu->add(dolBuildUrl('/expensereport/list.php', ['search_status' => 99, 'leftmenu' => 'expensereport', 'mainmenu' => 'hrm']), $langs->trans("Refused"), 2, $user->hasRight('expensereport', 'lire')); } - $newmenu->add(dolBuildUrl('/expensereport/payment/list.php', ['leftmenu' => 'expensereport_payments', 'mainmenu' => 'hrm']), $langs->trans("Payments"), 1, (int) (int) ($user->hasRight('expensereport', 'lire') && isModEnabled('bank'))); + if (isModEnabled('bank')) { + $newmenu->add(dolBuildUrl('/expensereport/payment/list.php', ['leftmenu' => 'expensereport_payments', 'mainmenu' => 'hrm']), $langs->trans("Payments"), 1, (int) ($user->hasRight('expensereport', 'read'))); + } $newmenu->add(dolBuildUrl('/expensereport/stats/index.php', ['leftmenu' => 'expensereport', 'mainmenu' => 'hrm']), $langs->trans("Statistics"), 1, $user->hasRight('expensereport', 'lire')); } diff --git a/htdocs/core/modules/modExpenseReport.class.php b/htdocs/core/modules/modExpenseReport.class.php index 9d60b72c6dd..5294ea4e0d1 100644 --- a/htdocs/core/modules/modExpenseReport.class.php +++ b/htdocs/core/modules/modExpenseReport.class.php @@ -41,8 +41,6 @@ class modExpenseReport extends DolibarrModules */ public function __construct($db) { - global $conf, $user; // Required by some include code - $this->db = $db; $this->numero = 770; diff --git a/htdocs/expensereport/card.php b/htdocs/expensereport/card.php index 897e66a00b3..356636bd862 100644 --- a/htdocs/expensereport/card.php +++ b/htdocs/expensereport/card.php @@ -1994,6 +1994,7 @@ if ($action == 'create') { $nbcols++; } + print '
'; print ''; print ''; @@ -2003,8 +2004,8 @@ if ($action == 'create') { if ($canSeeBankAccount) { print ''; } - print ''; print ''; + print ''; print ''; // Payments already done (from payment on this expensereport) @@ -2063,8 +2064,8 @@ if ($action == 'create') { } print ''; } - print '"; print ''; + print '"; print ""; $totalpaid += $objp->amount; $i++; @@ -2085,17 +2086,24 @@ if ($action == 'create') { } elseif ($object->paid == 1 && $remaintopay > 0) { $cssforamountpaymentcomplete = 'amountpaymentneutral strikefordisabled'; } - print ''; - print ''; + print ''; + print ''; + print ''; + + print ''; + print ''; + print ''; print ''; - print ''; + print ''; + print ''; $db->free($resql); } else { dol_print_error($db); } print "
'.$langs->trans('BankAccount').''.$langs->trans('Amount').' '.$langs->trans('Amount').'
'.price($objp->amount)."'.price($objp->amount)."
'.$langs->trans("AlreadyPaid").':'.price($totalpaid).'
'.$langs->trans("AmountExpected").':'.price($object->total_ttc).'
'.$langs->trans("AlreadyPaid").':'.price($totalpaid).'
'.$langs->trans("AmountExpected").':'.price($object->total_ttc).'
'.$langs->trans("RemainderToPay").':'.price($resteapayeraffiche).'
'.price($resteapayeraffiche).'
"; + print '
'; print ''; print ''; @@ -2214,7 +2222,7 @@ if ($action == 'create') { } // Comment - print ''.dol_nl2br($line->comments).''; + print ''.dol_nl2br($line->comments).''; // VAT rate $senderissupplier = 0; @@ -2347,7 +2355,7 @@ if ($action == 'create') { print ''; print ''; - print !empty($line->rule_warning_message) ? img_warning(html_entity_decode($line->rule_warning_message)) : ' '; + print !empty($line->rule_warning_message) ? img_warning(html_entity_decode($line->rule_warning_message)) : ''; print ''; // Ajout des boutons de modification/suppression From ea9eddffa5f5a5ba65af6d1b0735d15f4215ab6b Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Thu, 1 Jan 2026 23:21:17 +0100 Subject: [PATCH 04/18] Debug v23 --- htdocs/expensereport/list.php | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/htdocs/expensereport/list.php b/htdocs/expensereport/list.php index 062d92fcfd1..9682030e93b 100644 --- a/htdocs/expensereport/list.php +++ b/htdocs/expensereport/list.php @@ -862,13 +862,9 @@ if ($num > 0) { } // Ref if (!empty($arrayfields['d.ref']['checked'])) { - print ''; - print ''; - print ''; // Warning late icon and note - print ''; - print ''; - print '
'; + print ''; print $expensereportstatic->getNomUrl(1); - print ''; if ($expensereportstatic->status == 2 && $expensereportstatic->hasDelay('toapprove')) { print img_warning($langs->trans("Late")); } @@ -880,15 +876,11 @@ if ($num > 0) { print ''.img_picto($langs->trans("ViewPrivateNote"), 'object_generic').''; print ''; } - print ''; $filename = dol_sanitizeFileName($obj->ref); $filedir = $conf->expensereport->dir_output.'/'.dol_sanitizeFileName($obj->ref); $urlsource = $_SERVER['PHP_SELF'].'?id='.$obj->rowid; print $formfile->getDocumentsLink($expensereportstatic->element, $filename, $filedir); print '
'; - print ''; if (!$i) { $totalarray['nbfield']++; } From b348745b42551506a2057a49727baa9896a76653 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Thu, 1 Jan 2026 23:24:39 +0100 Subject: [PATCH 05/18] Debug v23 --- htdocs/expensereport/list.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/htdocs/expensereport/list.php b/htdocs/expensereport/list.php index 9682030e93b..44c60b66e85 100644 --- a/htdocs/expensereport/list.php +++ b/htdocs/expensereport/list.php @@ -607,8 +607,8 @@ if (getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) { print ''; } if (!empty($arrayfields['d.ref']['checked'])) { - print ''; - print ''; + print ''; + print ''; print ''; } // User From e3f7947cc5a402fd1721910a5f39e8cd0ae4feeb Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Fri, 2 Jan 2026 02:49:33 +0100 Subject: [PATCH 06/18] Debug v23 --- htdocs/admin/company.php | 35 +++++++++++++++++------------------ 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/htdocs/admin/company.php b/htdocs/admin/company.php index c5f11806f63..40018d81336 100644 --- a/htdocs/admin/company.php +++ b/htdocs/admin/company.php @@ -32,6 +32,14 @@ // Load Dolibarr environment require '../main.inc.php'; +/** + * @var Conf $conf + * @var DoliDB $db + * @var HookManager $hookmanager + * @var Societe $mysoc + * @var Translate $langs + * @var User $user + */ require_once DOL_DOCUMENT_ROOT.'/core/lib/admin.lib.php'; require_once DOL_DOCUMENT_ROOT.'/core/lib/company.lib.php'; require_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php'; @@ -46,15 +54,6 @@ $action = GETPOST('action', 'aZ09'); $contextpage = GETPOST('contextpage', 'aZ') ? GETPOST('contextpage', 'aZ') : 'admincompany'; // To manage different context of search $page_y = GETPOSTINT('page_y'); -/** - * @var Conf $conf - * @var DoliDB $db - * @var HookManager $hookmanager - * @var Societe $mysoc - * @var Translate $langs - * @var User $user - */ - // Load translation files required by the page $langs->loadLangs(array('admin', 'companies', 'bills')); @@ -604,15 +603,6 @@ print ''; print ''; -// Juridical Status -print ''; -if ($mysoc->country_code) { - print $formcompany->select_juridicalstatus(getDolGlobalInt('MAIN_INFO_SOCIETE_FORME_JURIDIQUE'), $mysoc->country_code, '', 'forme_juridique_code'); -} else { - print $countrynotdefined; -} -print ''; - // Object of the company print ''; print ''; @@ -623,6 +613,15 @@ print '