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