Accountancy

This commit is contained in:
Alexandre SPANGARO 2022-02-14 04:27:52 +01:00
parent 6593449806
commit d5e521c87b
2 changed files with 1005 additions and 1 deletions

View File

@ -1,5 +1,5 @@
<?php
/* Copyright (C) 2017 Alexandre Spangaro <aspangaro@open-dsi.fr>
/* Copyright (C) 2017-2022 OpenDSI <support@open-dsi.fr>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -81,6 +81,24 @@ class AccountingJournal extends CommonObject
*/
public $lines;
/**
* @var array Accounting account cached
*/
static public $accounting_account_cached = array();
/**
* @var array Nature mapping
*/
static public $nature_maps = array(
1 => 'variousoperations',
2 => 'sells',
3 => 'purchases',
4 => 'bank',
5 => 'expensereports',
8 => 'inventories',
9 => 'hasnew',
);
/**
* Constructor
*
@ -336,4 +354,679 @@ class AccountingJournal extends CommonObject
}
}
}
/**
* Get journal data
*
* @param User $user User who get infos
* @param string $type Type data returned ('view', 'bookkeeping', 'csv')
* @param int $date_start Filter 'start date'
* @param int $date_end Filter 'end date'
* @param string $in_bookkeeping Filter 'in bookkeeping' ('already', 'notyet')
* @return array|int <0 if KO, >0 if OK
*/
public function getData(User $user, $type = 'view', $date_start = null, $date_end = null, $in_bookkeeping = 'notyet')
{
global $hookmanager;
// Clean parameters
if (empty($type)) $type = 'view';
if (empty($in_bookkeeping)) $in_bookkeeping = 'notyet';
// Hook
if (!is_object($hookmanager)) {
include_once DOL_DOCUMENT_ROOT . '/core/class/hookmanager.class.php';
$hookmanager = new HookManager($this->db);
}
$data = array();
$hookmanager->initHooks(array('accountingjournaldao'));
$parameters = array('data' => &$data, 'user' => $user, 'type' => $type, 'date_start' => $date_start, 'date_end' => $date_end, 'in_bookkeeping' => $in_bookkeeping);
$reshook = $hookmanager->executeHooks('getData', $parameters, $this); // Note that $action and $object may have been
if ($reshook < 0) {
$this->error = $hookmanager->error;
$this->errors = $hookmanager->errors;
return -1;
} elseif (empty($reshook)) {
switch ($this->nature) {
case 1: // Various Journal
$data = $this->getAssetData($user, $type, $date_start, $date_end, $in_bookkeeping);
break;
// case 2: // Sells Journal
// case 3: // Purchases Journal
// case 4: // Bank Journal
// case 5: // Expense reports Journal
// case 8: // Inventory Journal
// case 9: // hasnew Journal
}
}
return $data;
}
/**
* Get asset data for various journal
*
* @param User $user User who get infos
* @param string $type Type data returned ('view', 'bookkeeping', 'csv')
* @param int $date_start Filter 'start date'
* @param int $date_end Filter 'end date'
* @param string $in_bookkeeping Filter 'in bookkeeping' ('already', 'notyet')
* @return array|int <0 if KO, >0 if OK
*/
public function getAssetData(User $user, $type = 'view', $date_start = null, $date_end = null, $in_bookkeeping = 'notyet')
{
global $conf, $langs;
if (empty($conf->asset->enabled)) {
return array();
}
require_once DOL_DOCUMENT_ROOT . '/core/lib/accounting.lib.php';
require_once DOL_DOCUMENT_ROOT . '/asset/class/asset.class.php';
require_once DOL_DOCUMENT_ROOT . '/asset/class/assetaccountancycodes.class.php';
require_once DOL_DOCUMENT_ROOT . '/asset/class/assetdepreciationoptions.class.php';
$langs->loadLangs(array("assets"));
// Clean parameters
if (empty($type)) $type = 'view';
if (empty($in_bookkeeping)) $in_bookkeeping = 'notyet';
$sql = "";
if ($in_bookkeeping == 'already' || $in_bookkeeping == 'notyet') {
$sql .= "WITH in_accounting_bookkeeping(fk_docdet) AS (";
$sql .= " SELECT DISTINCT fk_docdet";
$sql .= " FROM " . MAIN_DB_PREFIX . "accounting_bookkeeping";
$sql .= " WHERE doc_type = 'asset'";
$sql .= ")";
}
$sql .= "SELECT ad.fk_asset AS rowid, a.ref AS asset_ref, a.label AS asset_label, a.acquisition_value_ht AS asset_acquisition_value_ht";
$sql .= ", a.disposal_date AS asset_disposal_date, a.disposal_amount_ht AS asset_disposal_amount_ht, a.disposal_subject_to_vat AS asset_disposal_subject_to_vat";
$sql .= ", ad.rowid AS depreciation_id, ad.depreciation_mode, ad.ref AS depreciation_ref, ad.depreciation_date, ad.depreciation_ht, ad.accountancy_code_debit, ad.accountancy_code_credit";
$sql .= " FROM " . MAIN_DB_PREFIX . "asset_depreciation as ad";
$sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "asset as a ON a.rowid = ad.fk_asset";
if ($in_bookkeeping == 'already' || $in_bookkeeping == 'notyet') {
$sql .= " LEFT JOIN in_accounting_bookkeeping as iab ON iab.fk_docdet = ad.rowid";
}
$sql .= " WHERE a.entity IN (" . getEntity('asset', 0) . ')'; // We don't share object for accountancy, we use source object sharing
$sql .= " AND ad.ref != ''"; // not reversal lines
if ($date_start && $date_end) {
$sql .= " AND ad.depreciation_date >= '" . $this->db->idate($date_start) . "' AND ad.depreciation_date <= '" . $this->db->idate($date_end) . "'";
}
// Define begin binding date
if (!empty($conf->global->ACCOUNTING_DATE_START_BINDING)) {
$sql .= " AND ad.depreciation_date >= '" . $this->db->idate($conf->global->ACCOUNTING_DATE_START_BINDING) . "'";
}
// Already in bookkeeping or not
if ($in_bookkeeping == 'already' || $in_bookkeeping == 'notyet') {
$sql .= " AND iab.fk_docdet IS" . ($in_bookkeeping == 'already' ? " NOT" : "") . " NULL";
}
$sql .= " ORDER BY ad.depreciation_date";
dol_syslog(__METHOD__, LOG_DEBUG);
$resql = $this->db->query($sql);
if (!$resql) {
$this->errors[] = $this->db->lasterror();
return -1;
}
$pre_data = array(
'elements' => array(),
);
while ($obj = $this->db->fetch_object($resql)) {
if (!isset($pre_data['elements'][$obj->rowid])) {
$pre_data['elements'][$obj->rowid] = array(
'ref' => $obj->asset_ref,
'label' => $obj->asset_label,
'acquisition_value_ht' => $obj->asset_acquisition_value_ht,
'depreciation' => array(),
);
// Disposal infos
if (isset($obj->asset_disposal_date)) {
$pre_data['elements'][$obj->rowid]['disposal'] = array(
'date' => $this->db->jdate($obj->asset_disposal_date),
'amount' => $obj->asset_disposal_amount_ht,
'subject_to_vat' => !empty($obj->asset_disposal_subject_to_vat),
);
}
}
$compta_debit = empty($obj->accountancy_code_debit) ? 'NotDefined' : $obj->accountancy_code_debit;
$compta_credit = empty($obj->accountancy_code_credit) ? 'NotDefined' : $obj->accountancy_code_credit;
$pre_data['elements'][$obj->rowid]['depreciation'][$obj->depreciation_id] = array(
'date' => $this->db->jdate($obj->depreciation_date),
'ref' => $obj->depreciation_ref,
'lines' => array(
$compta_debit => -$obj->depreciation_ht,
$compta_credit => $obj->depreciation_ht,
),
);
}
$disposal_ref = $langs->transnoentitiesnoconv('AssetDisposal');
$journal = $this->code;
$journal_label = $this->label;
$journal_label_formatted = $langs->transnoentities($journal_label);
$now = dol_now();
$element_static = new Asset($this->db);
$journal_data = array();
foreach ($pre_data['elements'] as $pre_data_id => $pre_data_info) {
$element_static->id = $pre_data_id;
$element_static->ref = (string)$pre_data_info["ref"];
$element_static->label = (string)$pre_data_info["label"];
$element_static->acquisition_value_ht = $pre_data_info["acquisition_value_ht"];
$element_link = $element_static->getNomUrl(1, 'with_label');
$element_name_formatted_0 = dol_trunc($element_static->label, 16);
$element_name_formatted_1 = utf8_decode(dol_trunc($element_static->label, 32));
$element_name_formatted_2 = utf8_decode(dol_trunc($element_static->label, 16));
$label_operation = $element_static->getNomUrl(0, 'label', 16);
$element = array(
'ref' => dol_trunc($element_static->ref, 16, 'right', 'UTF-8', 1),
'error' => $pre_data_info['error'],
'blocks' => array(),
);
// Depreciation lines
//--------------------
foreach ($pre_data_info['depreciation'] as $depreciation_id => $line) {
$depreciation_ref = $line["ref"];
$depreciation_date = $line["date"];
$depreciation_date_formatted = dol_print_date($depreciation_date, 'day');
// lines
$blocks = array();
foreach ($line['lines'] as $account => $mt) {
$account_infos = $this->getAccountingAccountInfos($account);
if ($type == 'view') {
$account_to_show = length_accounta($account);
if (($account_to_show == "") || $account_to_show == 'NotDefined') {
$account_to_show = '<span class="error">' . $langs->trans("AssetInAccountNotDefined") . '</span>';
}
$blocks[] = array(
'date' => $depreciation_date_formatted,
'piece' => $element_link,
'account_accounting' => $account_to_show,
'subledger_account' => '',
'label_operation' => $label_operation . ' - ' . $depreciation_ref,
'debit' => $mt < 0 ? price(-$mt) : '',
'credit' => $mt >= 0 ? price($mt) : '',
);
} elseif ($type == 'bookkeeping') {
if ($account_infos['found']) {
$blocks[] = array(
'doc_date' => $depreciation_date,
'date_lim_reglement' => '',
'doc_ref' => $element_static->ref,
'date_creation' => $now,
'doc_type' => 'asset',
'fk_doc' => $element_static->id,
'fk_docdet' => $depreciation_id, // Useless, can be several lines that are source of this record to add
'thirdparty_code' => '',
'subledger_account' => '',
'subledger_label' => '',
'numero_compte' => $account,
'label_compte' => $account_infos['label'],
'label_operation' => $element_name_formatted_0 . ' - ' . $depreciation_ref,
'montant' => $mt,
'sens' => $mt < 0 ? 'D' : 'C',
'debit' => $mt < 0 ? -$mt : 0,
'credit' => $mt >= 0 ? $mt : 0,
'code_journal' => $journal,
'journal_label' => $journal_label_formatted,
'piece_num' => '',
'import_key' => '',
'fk_user_author' => $user->id,
'entity' => $conf->entity,
);
}
} else { // $type == 'csv'
$blocks[] = array(
$depreciation_date, // Date
$element_static->ref, // Piece
$account_infos['code_formatted_1'], // AccountAccounting
$element_name_formatted_0 . ' - ' . $depreciation_ref, // LabelOperation
$mt < 0 ? price(-$mt) : '', // Debit
$mt >= 0 ? price($mt) : '', // Credit
);
}
}
$element['blocks'][] = $blocks;
}
// Disposal line
//--------------------
if (!empty($pre_data_info['disposal'])) {
$disposal_date = $pre_data_info['disposal']['date'];
if ((!($date_start && $date_end) || ($date_start <= $disposal_date && $disposal_date <= $date_end)) &&
(empty($conf->global->ACCOUNTING_DATE_START_BINDING) || $conf->global->ACCOUNTING_DATE_START_BINDING <= $disposal_date)
) {
$disposal_amount = $pre_data_info['disposal']['amount'];
$disposal_subject_to_vat = $pre_data_info['disposal']['subject_to_vat'];
$disposal_date_formatted = dol_print_date($disposal_date, 'day');
$disposal_vat = $conf->global->ASSET_DISPOSAL_VAT > 0 ? $conf->global->ASSET_DISPOSAL_VAT : 20;
// Get accountancy codes
//---------------------------
require_once DOL_DOCUMENT_ROOT . '/asset/class/assetaccountancycodes.class.php';
$accountancy_codes = new AssetAccountancyCodes($this->db);
$result = $accountancy_codes->fetchAccountancyCodes($element_static->id);
if ($result < 0) {
$element['error'] = $accountancy_codes->errorsToString();
} else {
// Get last depreciation cumulative amount
$element_static->fetchDepreciationLines();
foreach ($element_static->depreciation_lines as $mode_key => $depreciation_lines) {
$accountancy_codes_list = $accountancy_codes->accountancy_codes[$mode_key];
if (!isset($accountancy_codes_list['value_asset_sold'])) {
continue;
}
$accountancy_code_value_asset_sold = empty($accountancy_codes_list['value_asset_sold']) ? 'NotDefined' : $accountancy_codes_list['value_asset_sold'];
$accountancy_code_depreciation_asset = empty($accountancy_codes_list['depreciation_asset']) ? 'NotDefined' : $accountancy_codes_list['depreciation_asset'];
$accountancy_code_asset = empty($accountancy_codes_list['asset']) ? 'NotDefined' : $accountancy_codes_list['asset'];
$accountancy_code_receivable_on_assignment = empty($accountancy_codes_list['receivable_on_assignment']) ? 'NotDefined' : $accountancy_codes_list['receivable_on_assignment'];
$accountancy_code_vat_collected = empty($accountancy_codes_list['vat_collected']) ? 'NotDefined' : $accountancy_codes_list['vat_collected'];
$accountancy_code_proceeds_from_sales = empty($accountancy_codes_list['proceeds_from_sales']) ? 'NotDefined' : $accountancy_codes_list['proceeds_from_sales'];
$last_cumulative_amount_ht = 0;
$depreciated_ids = array_keys($pre_data_info['depreciation']);
foreach ($depreciation_lines as $line) {
$last_cumulative_amount_ht = $line['cumulative_depreciation_ht'];
if (!in_array($line['id'], $depreciated_ids) && empty($line['bookkeeping']) && !empty($line['ref'])) {
break;
}
}
$lines = array();
$lines[0][$accountancy_code_value_asset_sold] = -($element_static->acquisition_value_ht - $last_cumulative_amount_ht);
$lines[0][$accountancy_code_depreciation_asset] = -$last_cumulative_amount_ht;
$lines[0][$accountancy_code_asset] = $element_static->acquisition_value_ht;
$disposal_amount_vat = $disposal_subject_to_vat ? (double)price2num($disposal_amount * $disposal_vat / 100, 'MT') : 0;
$lines[1][$accountancy_code_receivable_on_assignment] = -($disposal_amount + $disposal_amount_vat);
if ($disposal_subject_to_vat) $lines[1][$accountancy_code_vat_collected] = $disposal_amount_vat;
$lines[1][$accountancy_code_proceeds_from_sales] = $disposal_amount;
foreach ($lines as $lines_block) {
$blocks = array();
foreach ($lines_block as $account => $mt) {
$account_infos = $this->getAccountingAccountInfos($account);
if ($type == 'view') {
$account_to_show = length_accounta($account);
if (($account_to_show == "") || $account_to_show == 'NotDefined') {
$account_to_show = '<span class="error">' . $langs->trans("AssetInAccountNotDefined") . '</span>';
}
$blocks[] = array(
'date' => $disposal_date_formatted,
'piece' => $element_link,
'account_accounting' => $account_to_show,
'subledger_account' => '',
'label_operation' => $label_operation . ' - ' . $disposal_ref,
'debit' => $mt < 0 ? price(-$mt) : '',
'credit' => $mt >= 0 ? price($mt) : '',
);
} elseif ($type == 'bookkeeping') {
if ($account_infos['found']) {
$blocks[] = array(
'doc_date' => $disposal_date,
'date_lim_reglement' => '',
'doc_ref' => $element_static->ref,
'date_creation' => $now,
'doc_type' => 'asset',
'fk_doc' => $element_static->id,
'fk_docdet' => 0, // Useless, can be several lines that are source of this record to add
'thirdparty_code' => '',
'subledger_account' => '',
'subledger_label' => '',
'numero_compte' => $account,
'label_compte' => $account_infos['label'],
'label_operation' => $element_name_formatted_0 . ' - ' . $disposal_ref,
'montant' => $mt,
'sens' => $mt < 0 ? 'D' : 'C',
'debit' => $mt < 0 ? -$mt : 0,
'credit' => $mt >= 0 ? $mt : 0,
'code_journal' => $journal,
'journal_label' => $journal_label_formatted,
'piece_num' => '',
'import_key' => '',
'fk_user_author' => $user->id,
'entity' => $conf->entity,
);
}
} else { // $type == 'csv'
$blocks[] = array(
$disposal_date, // Date
$element_static->ref, // Piece
$account_infos['code_formatted_1'], // AccountAccounting
$element_name_formatted_0 . ' - ' . $disposal_ref, // LabelOperation
$mt < 0 ? price(-$mt) : '', // Debit
$mt >= 0 ? price($mt) : '', // Credit
);
}
}
$element['blocks'][] = $blocks;
}
}
}
}
}
$journal_data[$pre_data_id] = $element;
}
unset($pre_data);
return $journal_data;
}
/**
* Write bookkeeping
*
* @param User $user User who write in the bookkeeping
* @param array $journal_data Journal data to write in the bookkeeping
* $journal_data = array(
* id_element => array(
* 'ref' => 'ref',
* 'error' => '',
* 'blocks' => array(
* pos_block => array(
* num_line => array(
* 'doc_date' => '',
* 'date_lim_reglement' => '',
* 'doc_ref' => '',
* 'date_creation' => '',
* 'doc_type' => '',
* 'fk_doc' => '',
* 'fk_docdet' => '',
* 'thirdparty_code' => '',
* 'subledger_account' => '',
* 'subledger_label' => '',
* 'numero_compte' => '',
* 'label_compte' => '',
* 'label_operation' => '',
* 'montant' => '',
* 'sens' => '',
* 'debit' => '',
* 'credit' => '',
* 'code_journal' => '',
* 'journal_label' => '',
* 'piece_num' => '',
* 'import_key' => '',
* 'fk_user_author' => '',
* 'entity' => '',
* ),
* ),
* ),
* ),
* );
* @param int $max_nb_errors Nb error authorized before stop the process
* @return int <0 if KO, >0 if OK
*/
public function writeIntoBookkeeping(User $user, &$journal_data = array(), $max_nb_errors = 10)
{
global $conf, $langs, $hookmanager;
require_once DOL_DOCUMENT_ROOT . '/accountancy/class/bookkeeping.class.php';
// Hook
if (!is_object($hookmanager)) {
include_once DOL_DOCUMENT_ROOT . '/core/class/hookmanager.class.php';
$hookmanager = new HookManager($this->db);
}
$error = 0;
$hookmanager->initHooks(array('accountingjournaldao'));
$parameters = array('journal_data' => &$journal_data);
$reshook = $hookmanager->executeHooks('writeBookkeeping', $parameters, $this); // Note that $action and $object may have been
if ($reshook < 0) {
$this->error = $hookmanager->error;
$this->errors = $hookmanager->errors;
return -1;
} elseif (empty($reshook)) {
// Clean parameters
$journal_data = is_array($journal_data) ? $journal_data : array();
foreach ($journal_data as $element_id => $element) {
$error_for_line = 0;
$total_credit = 0;
$total_debit = 0;
$this->db->begin();
if ($element['error'] == 'somelinesarenotbound') {
$error++;
$error_for_line++;
$this->errors[] = $langs->trans('ErrorInvoiceContainsLinesNotYetBounded', $element['ref']);
}
if (!$error_for_line) {
foreach ($element['blocks'] as $lines) {
foreach ($lines as $line) {
$bookkeeping = new BookKeeping($this->db);
$bookkeeping->doc_date = $line['doc_date'];
$bookkeeping->date_lim_reglement = $line['date_lim_reglement'];
$bookkeeping->doc_ref = $line['doc_ref'];
$bookkeeping->date_creation = $line['date_creation']; // not used
$bookkeeping->doc_type = $line['doc_type'];
$bookkeeping->fk_doc = $line['fk_doc'];
$bookkeeping->fk_docdet = $line['fk_docdet'];
$bookkeeping->thirdparty_code = $line['thirdparty_code'];
$bookkeeping->subledger_account = $line['subledger_account'];
$bookkeeping->subledger_label = $line['subledger_label'];
$bookkeeping->numero_compte = $line['numero_compte'];
$bookkeeping->label_compte = $line['label_compte'];
$bookkeeping->label_operation = $line['label_operation'];
$bookkeeping->montant = $line['montant'];
$bookkeeping->sens = $line['sens'];
$bookkeeping->debit = $line['debit'];
$bookkeeping->credit = $line['credit'];
$bookkeeping->code_journal = $line['code_journal'];
$bookkeeping->journal_label = $line['journal_label'];
$bookkeeping->piece_num = $line['piece_num'];
$bookkeeping->import_key = $line['import_key'];
$bookkeeping->fk_user_author = $user->id;
$bookkeeping->entity = $conf->entity;
$total_debit += $bookkeeping->debit;
$total_credit += $bookkeeping->credit;
$result = $bookkeeping->create($user);
if ($result < 0) {
if ($bookkeeping->error == 'BookkeepingRecordAlreadyExists') { // Already exists
$error++;
$error_for_line++;
$journal_data[$element_id]['error'] = 'alreadyjournalized';
} else {
$error++;
$error_for_line++;
$journal_data[$element_id]['error'] = 'other';
$this->errors[] = $bookkeeping->errorsToString();
}
}
//
// if (!$error_for_line && !empty($conf->asset->enabled) && $this->nature == 1 && $bookkeeping->fk_doc > 0) {
// // Set last cumulative depreciation
// require_once DOL_DOCUMENT_ROOT . '/asset/class/asset.class.php';
// $asset = new Asset($this->db);
// $result = $asset->setLastCumulativeDepreciation($bookkeeping->fk_doc);
// if ($result < 0) {
// $error++;
// $error_for_line++;
// $journal_data[$element_id]['error'] = 'other';
// $this->errors[] = $asset->errorsToString();
// }
// }
}
if ($error_for_line) {
break;
}
}
}
// Protection against a bug on lines before
if (!$error_for_line && (price2num($total_debit, 'MT') != price2num($total_credit, 'MT'))) {
$error++;
$error_for_line++;
$journal_data[$element_id]['error'] = 'amountsnotbalanced';
$this->errors[] = 'Try to insert a non balanced transaction in book for ' . $element['blocks'] . '. Canceled. Surely a bug.';
}
if (!$error_for_line) {
$this->db->commit();
} else {
$this->db->rollback();
if ($error >= $max_nb_errors) {
$this->errors[] = $langs->trans("ErrorTooManyErrorsProcessStopped");
break; // Break in the foreach
}
}
}
}
return $error ? -$error : 1;
}
/**
* Export journal CSV
* ISO and not UTF8 !
*
* @param array $journal_data Journal data to write in the bookkeeping
* $journal_data = array(
* id_element => array(
* 'continue' => false,
* 'blocks' => array(
* pos_block => array(
* num_line => array(
* data to write in the CSV line
* ),
* ),
* ),
* ),
* );
* @param int $search_date_end Search date end
* @param string $sep CSV separator
* @return int|string <0 if KO, >0 if OK
*/
public function exportCsv(&$journal_data = array(), $search_date_end = 0, $sep = '')
{
global $conf, $langs, $hookmanager;
if (empty($sep)) $sep = $conf->global->ACCOUNTING_EXPORT_SEPARATORCSV;
$out = '';
// Hook
if (!is_object($hookmanager)) {
include_once DOL_DOCUMENT_ROOT . '/core/class/hookmanager.class.php';
$hookmanager = new HookManager($this->db);
}
$hookmanager->initHooks(array('accountingjournaldao'));
$parameters = array('journal_data' => &$journal_data, 'search_date_end' => &$search_date_end, 'sep' => &$sep, 'out' => &$out);
$reshook = $hookmanager->executeHooks('exportCsv', $parameters, $this); // Note that $action and $object may have been
if ($reshook < 0) {
$this->error = $hookmanager->error;
$this->errors = $hookmanager->errors;
return -1;
} elseif (empty($reshook)) {
// Clean parameters
$journal_data = is_array($journal_data) ? $journal_data : array();
// CSV header line
$header = array();
if ($this->nature == 4) {
$header = array(
$langs->transnoentitiesnoconv("BankId"),
$langs->transnoentitiesnoconv("Date"),
$langs->transnoentitiesnoconv("PaymentMode"),
$langs->transnoentitiesnoconv("AccountAccounting"),
$langs->transnoentitiesnoconv("LedgerAccount"),
$langs->transnoentitiesnoconv("SubledgerAccount"),
$langs->transnoentitiesnoconv("Label"),
$langs->transnoentitiesnoconv("Debit"),
$langs->transnoentitiesnoconv("Credit"),
$langs->transnoentitiesnoconv("Journal"),
$langs->transnoentitiesnoconv("Note"),
);
} elseif ($this->nature == 5) {
$header = array(
$langs->transnoentitiesnoconv("Date"),
$langs->transnoentitiesnoconv("Piece"),
$langs->transnoentitiesnoconv("AccountAccounting"),
$langs->transnoentitiesnoconv("LabelOperation"),
$langs->transnoentitiesnoconv("Debit"),
$langs->transnoentitiesnoconv("Credit"),
);
} elseif ($this->nature == 1) {
$header = array(
$langs->transnoentitiesnoconv("Date"),
$langs->transnoentitiesnoconv("Piece"),
$langs->transnoentitiesnoconv("AccountAccounting"),
$langs->transnoentitiesnoconv("LabelOperation"),
$langs->transnoentitiesnoconv("Debit"),
$langs->transnoentitiesnoconv("Credit"),
);
}
if (!empty($header)) $out .= '"' . implode('"' . $sep . '"', $header) . '"' . "\n";
foreach ($journal_data as $element_id => $element) {
foreach ($element['blocks'] as $lines) {
foreach ($lines as $line) {
$out .= '"' . implode('"' . $sep . '"', $line) . '"' . "\n";
}
}
}
}
return $out;
}
/**
* Get accounting account infos
*
* @param string $account Accounting account number
* @return array Accounting account infos
*/
function getAccountingAccountInfos($account) {
if (!isset(self::$accounting_account_cached[$account])) {
require_once DOL_DOCUMENT_ROOT . '/core/lib/accounting.lib.php';
require_once DOL_DOCUMENT_ROOT . '/accountancy/class/accountingaccount.class.php';
$accountingaccount = new AccountingAccount($this->db);
$result = $accountingaccount->fetch(null, $account, true);
if ($result > 0) {
self::$accounting_account_cached[$account] = array(
'found' => true,
'label' => $accountingaccount->label,
'code_formatted_1' => length_accounta(html_entity_decode($account)),
'label_formatted_1' => utf8_decode(dol_trunc($accountingaccount->label, 32)),
'label_formatted_2' => dol_trunc($accountingaccount->label, 32),
);
} else {
self::$accounting_account_cached[$account] = array(
'found' => false,
'label' => '',
'code_formatted_1' => length_accounta(html_entity_decode($account)),
'label_formatted_1' => '',
'label_formatted_2' => '',
);
}
}
return self::$accounting_account_cached[$account];
}
}

View File

@ -0,0 +1,311 @@
<?php
/* Copyright (C) 2021-2022 Open-DSI <support@open-dsi.fr>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
/**
* \file htdocs/accountancy/journal/journal.php
* \ingroup Accountancy (Double entries)
* \brief Page of a journal
*/
require '../../main.inc.php';
require_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php';
require_once DOL_DOCUMENT_ROOT.'/core/lib/accounting.lib.php';
require_once DOL_DOCUMENT_ROOT.'/accountancy/class/accountingjournal.class.php';
// Load translation files required by the page
$langs->loadLangs(array("banks", "accountancy", "compta", "other", "errors"));
$id_journal = GETPOST('id_journal', 'int');
$action = GETPOST('action', 'aZ09');
$date_startmonth = GETPOST('date_startmonth');
$date_startday = GETPOST('date_startday');
$date_startyear = GETPOST('date_startyear');
$date_endmonth = GETPOST('date_endmonth');
$date_endday = GETPOST('date_endday');
$date_endyear = GETPOST('date_endyear');
$in_bookkeeping = GETPOST('in_bookkeeping');
if ($in_bookkeeping == '') {
$in_bookkeeping = 'notyet';
}
// Security check
if (empty($conf->accounting->enabled)) {
accessforbidden();
}
if ($user->socid > 0) {
accessforbidden();
}
if (empty($user->rights->accounting->mouvements->lire)) {
accessforbidden();
}
// Get information of journal
$object = new AccountingJournal($db);
$result = $object->fetch($id_journal);
if ($result > 0) {
$id_journal = $object->id;
} elseif ($result < 0) {
dol_print_error('', $object->error, $object->errors);
} elseif ($result == 0) {
accessforbidden($langs->trans('ErrorRecordNotFound'));
}
$hookmanager->initHooks(array('globaljournal', $object->nature_text . 'journal'));
$parameters = array();
$date_start = dol_mktime(0, 0, 0, $date_startmonth, $date_startday, $date_startyear);
$date_end = dol_mktime(23, 59, 59, $date_endmonth, $date_endday, $date_endyear);
if (empty($date_startmonth) || empty($date_endmonth)) {
// Period by default on transfer
$dates = getDefaultDatesForTransfer();
$date_start = $dates['date_start'];
$date_end = $dates['date_end'];
$pastmonthyear = $dates['pastmonthyear'];
$pastmonth = $dates['pastmonth'];
}
if (!GETPOSTISSET('date_startmonth') && (empty($date_start) || empty($date_end))) { // We define date_start and date_end, only if we did not submit the form
$date_start = dol_get_first_day($pastmonthyear, $pastmonth, false);
$date_end = dol_get_last_day($pastmonthyear, $pastmonth, false);
}
$data_type = 'view';
if ($action == 'writebookkeeping') $data_type = 'bookkeeping';
if ($action == 'exportcsv') $data_type = 'csv';
$journal_data = $object->getData($user, $data_type, $date_start, $date_end, $in_bookkeeping);
if (!is_array($journal_data)) {
setEventMessages($object->error, $object->errors, 'errors');
}
/*
* Actions
*/
$reshook = $hookmanager->executeHooks('doActions', $parameters, $user, $action); // Note that $action and $object may have been modified by some hooks
$reload = false;
// Bookkeeping Write
if ($action == 'writebookkeeping') {
$error = 0;
$result = $object->writeIntoBookkeeping($user, $journal_data);
if ($result < 0) {
setEventMessages($object->error, $object->errors, 'errors');
$error = abs($result);
}
$nb_elements = count($journal_data);
if (empty($error) && $nb_elements > 0) {
setEventMessages($langs->trans("GeneralLedgerIsWritten"), null, 'mesgs');
} elseif ($nb_elements == $error) {
setEventMessages($langs->trans("NoNewRecordSaved"), null, 'warnings');
} else {
setEventMessages($langs->trans("GeneralLedgerSomeRecordWasNotRecorded"), null, 'warnings');
}
$reload = true;
}
// Export
elseif ($action == 'exportcsv') {
$result = $object->exportCsv($journal_data, $date_end);
if ($result < 0) {
setEventMessages($object->error, $object->errors, 'errors');
$reload = true;
} else {
$filename = 'journal';
$type_export = 'journal';
require_once DOL_DOCUMENT_ROOT . '/core/lib/date.lib.php';
include DOL_DOCUMENT_ROOT.'/accountancy/tpl/export_journal.tpl.php';
print $result;
$db->close();
exit();
}
}
// Must reload data, so we make a redirect
if ($reload) {
$param = 'id_journal=' . $id_journal;
$param .= '&date_startday=' . $date_startday;
$param .= '&date_startmonth=' . $date_startmonth;
$param .= '&date_startyear=' . $date_startyear;
$param .= '&date_endday=' . $date_endday;
$param .= '&date_endmonth=' . $date_endmonth;
$param .= '&date_endyear=' . $date_endyear;
$param .= '&in_bookkeeping=' . $in_bookkeeping;
header("Location: " . $_SERVER['PHP_SELF'] . ($param ? '?' . $param : ''));
exit;
}
/*
* View
*/
$form = new Form($db);
if ($object->nature == 2) {
$title = $langs->trans("SellsJournal");
$some_mandatory_steps_of_setup_were_not_done = $conf->global->ACCOUNTING_ACCOUNT_CUSTOMER == "" || $conf->global->ACCOUNTING_ACCOUNT_CUSTOMER == '-1';
$account_accounting_not_defined = $conf->global->ACCOUNTING_ACCOUNT_CUSTOMER == "" || $conf->global->ACCOUNTING_ACCOUNT_CUSTOMER == '-1';
} elseif ($object->nature == 3) {
$title = $langs->trans("PurchasesJournal");
$some_mandatory_steps_of_setup_were_not_done = $conf->global->ACCOUNTING_ACCOUNT_SUPPLIER == "" || $conf->global->ACCOUNTING_ACCOUNT_SUPPLIER == '-1';
$account_accounting_not_defined = $conf->global->ACCOUNTING_ACCOUNT_SUPPLIER == "" || $conf->global->ACCOUNTING_ACCOUNT_SUPPLIER == '-1';
} elseif ($object->nature == 4) {
$title = $langs->trans("FinanceJournal");
$some_mandatory_steps_of_setup_were_not_done = $conf->global->ACCOUNTING_ACCOUNT_CUSTOMER == "" || $conf->global->ACCOUNTING_ACCOUNT_CUSTOMER == '-1'
|| $conf->global->ACCOUNTING_ACCOUNT_SUPPLIER == "" || $conf->global->ACCOUNTING_ACCOUNT_SUPPLIER == '-1'
|| empty($conf->global->SALARIES_ACCOUNTING_ACCOUNT_PAYMENT) || $conf->global->SALARIES_ACCOUNTING_ACCOUNT_PAYMENT == '-1';
$account_accounting_not_defined = $conf->global->ACCOUNTING_ACCOUNT_CUSTOMER == "" || $conf->global->ACCOUNTING_ACCOUNT_CUSTOMER == '-1'
|| $conf->global->ACCOUNTING_ACCOUNT_SUPPLIER == "" || $conf->global->ACCOUNTING_ACCOUNT_SUPPLIER == '-1';
} elseif ($object->nature == 5) {
$title = $langs->trans("ExpenseReportsJournal");
$some_mandatory_steps_of_setup_were_not_done = empty($conf->global->SALARIES_ACCOUNTING_ACCOUNT_PAYMENT) || $conf->global->SALARIES_ACCOUNTING_ACCOUNT_PAYMENT == '-1';
$account_accounting_not_defined = empty($conf->global->SALARIES_ACCOUNTING_ACCOUNT_PAYMENT) || $conf->global->SALARIES_ACCOUNTING_ACCOUNT_PAYMENT == '-1';
} else {
$title = $object->getLibType();
$some_mandatory_steps_of_setup_were_not_done = false;
$account_accounting_not_defined = false;
}
$nom = $title . ' | ' . $object->getNomUrl(0, 1, 1, '', 1);
$nomlink = '';
$periodlink = '';
$exportlink = '';
$builddate = dol_now();
$description = $langs->trans("DescJournalOnlyBindedVisible") . '<br>';
if ($object->nature == 2 || $object->nature == 3) {
if (!empty($conf->global->FACTURE_DEPOSITS_ARE_JUST_PAYMENTS)) {
$description .= $langs->trans("DepositsAreNotIncluded");
} else {
$description .= $langs->trans("DepositsAreIncluded");
}
}
$listofchoices = array('notyet' => $langs->trans("NotYetInGeneralLedger"), 'already' => $langs->trans("AlreadyInGeneralLedger"));
$period = $form->selectDate($date_start ? $date_start : -1, 'date_start', 0, 0, 0, '', 1, 0) . ' - ' . $form->selectDate($date_end ? $date_end : -1, 'date_end', 0, 0, 0, '', 1, 0);
$period .= ' - ' . $langs->trans("JournalizationInLedgerStatus") . ' ' . $form->selectarray('in_bookkeeping', $listofchoices, $in_bookkeeping, 1);
$varlink = 'id_journal=' . $id_journal;
llxHeader('', $title);
journalHead($nom, $nomlink, $period, $periodlink, $description, $builddate, $exportlink, array('action' => ''), '', $varlink);
if ($object->nature == 4) { // Bank journal
// Test that setup is complete (we are in accounting, so test on entity is always on $conf->entity only, no sharing allowed)
$sql = 'SELECT COUNT(rowid) as nb FROM ' . MAIN_DB_PREFIX . 'bank_account WHERE entity = ' . $conf->entity . ' AND fk_accountancy_journal IS NULL AND clos=0';
$resql = $db->query($sql);
if ($resql) {
$obj = $db->fetch_object($resql);
if ($obj->nb > 0) {
print '<br>' . img_warning() . ' ' . $langs->trans("TheJournalCodeIsNotDefinedOnSomeBankAccount");
print ' : ' . $langs->trans("AccountancyAreaDescBank", 9, '<strong>' . $langs->transnoentitiesnoconv("MenuAccountancy") . '-' . $langs->transnoentitiesnoconv("Setup") . "-" . $langs->transnoentitiesnoconv("BankAccounts") . '</strong>');
}
} else dol_print_error($db);
}
// Button to write into Ledger
if ($some_mandatory_steps_of_setup_were_not_done) {
print '<br><div class="warning">' . img_warning() . ' ' . $langs->trans("SomeMandatoryStepsOfSetupWereNotDone");
print ' : ' . $langs->trans("AccountancyAreaDescMisc", 4, '<strong>' . $langs->transnoentitiesnoconv("MenuAccountancy") . '-' . $langs->transnoentitiesnoconv("Setup") . "-" . $langs->transnoentitiesnoconv("MenuDefaultAccounts") . '</strong>');
print '</div>';
}
print '<div class="tabsAction tabsActionNoBottom">';
if (!empty($conf->global->ACCOUNTING_ENABLE_EXPORT_DRAFT_JOURNAL) && $in_bookkeeping == 'notyet') {
print '<input type="button" class="butAction" name="exportcsv" value="' . $langs->trans("ExportDraftJournal") . '" onclick="launch_export();" />';
}
if ($account_accounting_not_defined) {
print '<input type="button" class="butActionRefused classfortooltip" title="' . dol_escape_htmltag($langs->trans("SomeMandatoryStepsOfSetupWereNotDone")) . '" value="' . $langs->trans("WriteBookKeeping") . '" />';
} else {
if ($in_bookkeeping == 'notyet') {
print '<input type="button" class="butAction" name="writebookkeeping" value="' . $langs->trans("WriteBookKeeping") . '" onclick="writebookkeeping();" />';
} else {
print '<a href="#" class="butActionRefused classfortooltip" name="writebookkeeping">' . $langs->trans("WriteBookKeeping") . '</a>';
}
}
print '</div>';
// TODO Avoid using js. We can use a direct link with $param
print '
<script type="text/javascript">
function launch_export() {
$("div.fiche form input[name=\"action\"]").val("exportcsv");
$("div.fiche form input[type=\"submit\"]").click();
$("div.fiche form input[name=\"action\"]").val("");
}
function writebookkeeping() {
console.log("click on writebookkeeping");
$("div.fiche form input[name=\"action\"]").val("writebookkeeping");
$("div.fiche form input[type=\"submit\"]").click();
$("div.fiche form input[name=\"action\"]").val("");
}
</script>';
$object_label = $langs->trans("ObjectsRef");
if ($object->nature == 2 || $object->nature == 3) $object_label = $langs->trans("InvoiceRef");
if ($object->nature == 5) $object_label = $langs->trans("ExpenseReportRef");
/*
* Show result array
*/
print '<br>';
print '<div class="div-table-responsive">';
print '<table class="noborder centpercent">';
print '<tr class="liste_titre">';
print '<td>' . $langs->trans("Date") . '</td>';
print '<td>' . $langs->trans("Piece") . ' (' . $object_label . ')</td>';
print '<td>' . $langs->trans("AccountAccounting") . '</td>';
print '<td>' . $langs->trans("SubledgerAccount") . '</td>';
print '<td>' . $langs->trans("LabelOperation") . '</td>';
if ($object->nature == 4) print '<td class="center">' . $langs->trans("PaymentMode") . '</td>'; // bank
print '<td class="right">' . $langs->trans("Debit") . '</td>';
print '<td class="right">' . $langs->trans("Credit") . '</td>';
print "</tr>\n";
foreach ($journal_data as $element_id => $element) {
foreach ($element['blocks'] as $lines) {
foreach ($lines as $line) {
print '<tr class="oddeven">';
print '<td>' . $line['date'] . '</td>';
print '<td>' . $line['piece'] . '</td>';
print '<td>' . $line['account_accounting'] . '</td>';
print '<td>' . $line['subledger_account'] . '</td>';
print '<td>' . $line['label_operation'] . '</td>';
if ($object->nature == 4) print '<td class="center">' . $line['payment_mode'] . '</td>';
print '<td class="right nowraponall">' . $line['debit'] . '</td>';
print '<td class="right nowraponall">' . $line['credit'] . '</td>';
print '</tr>';
}
}
}
print '</table>';
print '</div>';
llxFooter();
$db->close();