dolibarr 25.0.0-alpha
accountingjournal.class.php
Go to the documentation of this file.
1<?php
2/* Copyright (C) 2017-2022 OpenDSI <support@open-dsi.fr>
3 * Copyright (C) 2026 Jose Martinez <jose.martinez@pichinov.com>
4 * Copyright (C) 2024-2026 MDW <mdeweerd@users.noreply.github.com>
5 * Copyright (C) 2024-2025 Frédéric France <frederic.france@free.fr>
6 * Copyright (C) 2024 Alexandre Janniaux <alexandre.janniaux@gmail.com>
7 * Copyright (C) 2025-2026 Alexandre Spangaro <alexandre@inovea-conseil.com>
8 *
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 3 of the License, or
12 * (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program. If not, see <https://www.gnu.org/licenses/>.
21 */
22
33{
37 public $element = 'accounting_journal';
38
42 public $table_element = 'accounting_journal';
43
47 public $fk_element = '';
48
52 public $picto = 'generic';
53
57 public $rowid;
58
62 public $code;
63
67 public $label;
68
72 public $nature;
73
77 public $active;
78
82 public static $accounting_account_cached = array();
83
87 public static $nature_maps = array(
88 1 => 'variousoperations',
89 2 => 'sells',
90 3 => 'purchases',
91 4 => 'bank',
92 5 => 'expensereports',
93 6 => 'cash_sells',
94 8 => 'inventories',
95 9 => 'hasnew',
96 );
97
103 public function __construct($db)
104 {
105 $this->db = $db;
106
107 $this->ismultientitymanaged = 0;
108 }
109
116 public function create($user)
117 {
118 $valid_nature = array(1, 2, 3, 4, 5, 8, 9);
119 if (!in_array((int) $this->nature, $valid_nature)) {
120 $this->error = get_class($this)."::Create Error invalid field nature '" . strval($this->nature) . "'";
121 dol_syslog($this->error, LOG_ERR);
122 return -1;
123 }
124
125 $sql = "INSERT INTO ".MAIN_DB_PREFIX."accounting_journal";
126 $sql .= " (entity, code, label, nature, active)";
127 $sql .= " VALUES ("
128 . ((int) $this->entity) .",'"
129 . $this->db->escape($this->code) ."','"
130 . $this->db->escape($this->label) ."',"
131 . ((int) $this->nature) .","
132 . ((int) $this->active) .")";
133
134 dol_syslog(get_class($this)."::create", LOG_DEBUG);
135 $resql = $this->db->query($sql);
136 if (!$resql) {
137 $this->error = get_class($this)."::Create Error: " . $this->db->lasterror();
138 dol_syslog($this->error, LOG_ERR);
139 return -1;
140 }
141
142 $id = $this->db->last_insert_id(MAIN_DB_PREFIX."accounting_journal");
143 if ($id <= 0) {
144 $this->error = get_class($this)."::Create Error " . $id . ": " . $this->db->lasterror();
145 dol_syslog($this->error, LOG_ERR);
146 return -2;
147 }
148
149 $this->id = $id;
150 $this->rowid = $id;
151 return $id;
152 }
153
161 public function fetch($rowid = 0, $journal_code = null)
162 {
163 global $conf;
164
165 if ($rowid || $journal_code) {
166 $sql = "SELECT rowid, code, label, nature, active";
167 $sql .= " FROM ".MAIN_DB_PREFIX."accounting_journal";
168 $sql .= " WHERE";
169 if ($rowid) {
170 $sql .= " rowid = ".((int) $rowid);
171 } elseif ($journal_code) {
172 $sql .= " code = '".$this->db->escape($journal_code)."'";
173 $sql .= " AND entity = ".((int) $conf->entity);
174 }
175
176 dol_syslog(get_class($this)."::fetch", LOG_DEBUG);
177 $result = $this->db->query($sql);
178 if ($result) {
179 $obj = $this->db->fetch_object($result);
180
181 if ($obj) {
182 $this->id = $obj->rowid;
183 $this->rowid = $obj->rowid;
184
185 $this->code = $obj->code;
186 $this->ref = $obj->code;
187 $this->label = $obj->label;
188 $this->nature = $obj->nature;
189 $this->active = $obj->active;
190
191 return $this->id;
192 } else {
193 return 0;
194 }
195 } else {
196 $this->error = "Error ".$this->db->lasterror();
197 $this->errors[] = "Error ".$this->db->lasterror();
198 }
199 }
200 return -1;
201 }
202
213 public function getNomUrl($withpicto = 0, $withlabel = 0, $nourl = 0, $moretitle = '', $notooltip = 0)
214 {
215 global $langs, $conf, $hookmanager;
216
217 if (!empty($conf->dol_no_mouse_hover)) {
218 $notooltip = 1; // Force disable tooltips
219 }
220
221 $result = '';
222
223 $url = dolBuildUrl(DOL_URL_ROOT.'/accountancy/admin/journals_list.php', ['id' => 35]);
224
225 $label = '<u>'.$langs->trans("ShowAccountingJournal").'</u>';
226 if (!empty($this->code)) {
227 $label .= '<br><b>'.$langs->trans('Code').':</b> '.$this->code;
228 }
229 if (!empty($this->label)) {
230 $label .= '<br><b>'.$langs->trans('Label').':</b> '.$langs->transnoentities($this->label);
231 }
232 if ($moretitle) {
233 $label .= ' - '.$moretitle;
234 }
235
236 $linkclose = '';
237 if (empty($notooltip)) {
238 if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
239 $label = $langs->trans("ShowAccountingJournal");
240 $linkclose .= ' alt="'.dolPrintHTMLForAttribute($label).'"';
241 }
242 $linkclose .= ' title="'.dolPrintHTMLForAttribute($label).'"';
243 $linkclose .= ' class="classfortooltip"';
244 }
245
246 $linkstart = '<a href="'.$url.'"';
247 $linkstart .= $linkclose.'>';
248 $linkend = '</a>';
249
250 if ($nourl) {
251 $linkstart = '';
252 $linkclose = '';
253 $linkend = '';
254 }
255
256 $label_link = $this->code;
257 if ($withlabel == 1 && !empty($this->label)) {
258 $label_link .= ' - '.($nourl ? '<span class="opacitymedium">' : '').$langs->transnoentities($this->label).($nourl ? '</span>' : '');
259 }
260 if ($withlabel == 2 && !empty($this->nature)) {
261 $key = $langs->trans("AccountingJournalType".$this->nature);
262 $transferlabel = ($key != "AccountingJournalType".strtoupper($langs->trans((string) $this->nature)) ? $key : $this->label);
263 $label_link .= ' - '.($nourl ? '<span class="opacitymedium">' : '').$transferlabel.($nourl ? '</span>' : '');
264 }
265
266 $result .= $linkstart;
267 if ($withpicto) {
268 $result .= img_object(($notooltip ? '' : $label), ($this->picto ? $this->picto : 'generic'), ($notooltip ? (($withpicto != 2) ? 'class="paddingright"' : '') : 'class="'.(($withpicto != 2) ? 'paddingright ' : '').'classfortooltip"'), 0, 0, $notooltip ? 0 : 1);
269 }
270 if ($withpicto != 2) {
271 $result .= $label_link;
272 }
273 $result .= $linkend;
274
275 global $action;
276 $hookmanager->initHooks(array('accountingjournaldao'));
277 $parameters = array('id' => $this->id, 'getnomurl' => &$result);
278 $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
279 if ($reshook > 0) {
280 $result = $hookmanager->resPrint;
281 } else {
282 $result .= $hookmanager->resPrint;
283 }
284 return $result;
285 }
286
293 public function getLibType($mode = 0)
294 {
295 return $this->LibType($this->nature, $mode);
296 }
297
298 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
306 public function LibType($nature, $mode = 0)
307 {
308 // phpcs:enable
309 global $langs;
310
311 $langs->loadLangs(array("accountancy"));
312
313 if ($mode == 0) {
314 $prefix = '';
315 if ($nature == 9) {
316 return $langs->trans('AccountingJournalType9');
317 } elseif ($nature == 6 && getDolGlobalInt('ACCOUNTING_DISSOCIATE_CASH_SALES')) {
318 return $langs->trans('AccountingJournalType6');
319 } elseif ($nature == 5) {
320 return $langs->trans('AccountingJournalType5');
321 } elseif ($nature == 4) {
322 return $langs->trans('AccountingJournalType4');
323 } elseif ($nature == 3) {
324 return $langs->trans('AccountingJournalType3');
325 } elseif ($nature == 2) {
326 return $langs->trans('AccountingJournalType2');
327 } elseif ($nature == 1) {
328 return $langs->trans('AccountingJournalType1');
329 }
330 } elseif ($mode == 1) {
331 if ($nature == 9) {
332 return $langs->trans('AccountingJournalType9');
333 } elseif ($nature == 6 && getDolGlobalInt('ACCOUNTING_DISSOCIATE_CASH_SALES')) {
334 return $langs->trans('AccountingJournalType6');
335 } elseif ($nature == 5) {
336 return $langs->trans('AccountingJournalType5');
337 } elseif ($nature == 4) {
338 return $langs->trans('AccountingJournalType4');
339 } elseif ($nature == 3) {
340 return $langs->trans('AccountingJournalType3');
341 } elseif ($nature == 2) {
342 return $langs->trans('AccountingJournalType2');
343 } elseif ($nature == 1) {
344 return $langs->trans('AccountingJournalType1');
345 }
346 }
347 return "";
348 }
349
350
361 public function getData(User $user, $type = 'view', $date_start = null, $date_end = null, $in_bookkeeping = 'notyet')
362 {
363 global $hookmanager;
364
365 // Clean parameters
366 if (empty($type)) {
367 $type = 'view';
368 }
369 if (empty($in_bookkeeping)) {
370 $in_bookkeeping = 'notyet';
371 }
372
373 $data = array();
374
375 $hookmanager->initHooks(array('accountingjournaldao'));
376 $parameters = array('data' => &$data, 'user' => $user, 'type' => $type, 'date_start' => $date_start, 'date_end' => $date_end, 'in_bookkeeping' => $in_bookkeeping);
377 $reshook = $hookmanager->executeHooks('getData', $parameters, $this); // Note that $action and $object may have been
378 if ($reshook < 0) {
379 $this->error = $hookmanager->error;
380 $this->errors = $hookmanager->errors;
381 return -1;
382 } elseif (empty($reshook)) {
383 switch ($this->nature) {
384 case 1: // Various Journal
385 if (isModEnabled('asset') && !getDolGlobalInt('ACCOUNTING_DISABLE_TRANSFER_ON_ASSETS')) {
386 $tmp = $this->getAssetData($user, $type, $date_start, $date_end, $in_bookkeeping);
387 if (is_array($tmp)) {
388 $data = array_merge($data, $tmp);
389 }
390 }
391 if (isModEnabled('invoice') && !getDolGlobalInt('ACCOUNTING_DISABLE_TRANSFER_ON_DISCOUNTS')) {
392 $tmp = $this->getDiscountCustomer($user, $type, $date_start, $date_end, $in_bookkeeping);
393 if (is_array($tmp)) {
394 $data = array_merge($data, $tmp);
395 }
396 }
397 if (isModEnabled('supplier_invoice') && !getDolGlobalInt('ACCOUNTING_DISABLE_TRANSFER_ON_DISCOUNTS')) {
398 $tmp = $this->getDiscountSupplier($user, $type, $date_start, $date_end, $in_bookkeeping);
399 if (is_array($tmp)) {
400 $data = array_merge($data, $tmp);
401 }
402 }
403 break;
404 // case 2: // Sells Journal
405 // case 3: // Purchases Journal
406 // case 4: // Bank Journal
407 // case 5: // Expense reports Journal
408 // case 8: // Inventory Journal
409 // case 9: // hasnew Journal
410 }
411 }
412
413 return $data;
414 }
415
426 public function getAssetData(User $user, $type = 'view', $date_start = null, $date_end = null, $in_bookkeeping = 'notyet')
427 {
428 global $conf, $langs;
429
430 if (!isModEnabled('asset')) {
431 return array();
432 }
433
434 require_once DOL_DOCUMENT_ROOT . '/core/lib/accounting.lib.php';
435 require_once DOL_DOCUMENT_ROOT . '/asset/class/asset.class.php';
436 require_once DOL_DOCUMENT_ROOT . '/asset/class/assetaccountancycodes.class.php';
437 require_once DOL_DOCUMENT_ROOT . '/asset/class/assetdepreciationoptions.class.php';
438
439 $langs->loadLangs(array("assets"));
440
441 // Clean parameters
442 if (empty($type)) {
443 $type = 'view';
444 }
445 if (empty($in_bookkeeping)) {
446 $in_bookkeeping = 'notyet';
447 }
448
449 $sql = "";
450 $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";
451 $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";
452 $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";
453 $sql .= " FROM " . MAIN_DB_PREFIX . "asset_depreciation as ad";
454 $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "asset as a ON a.rowid = ad.fk_asset";
455 $sql .= " WHERE a.entity IN (" . getEntity('asset', 0) . ')'; // We don't share object for accountancy, we use source object sharing
456 $sql .= " AND a.status > 0";
457 if ($in_bookkeeping == 'already') {
458 $sql .= " AND EXISTS (SELECT iab.fk_docdet FROM " . MAIN_DB_PREFIX . "accounting_bookkeeping AS iab WHERE iab.fk_docdet = ad.rowid AND doc_type = 'asset')";
459 } elseif ($in_bookkeeping == 'notyet') {
460 $sql .= " AND NOT EXISTS (SELECT iab.fk_docdet FROM " . MAIN_DB_PREFIX . "accounting_bookkeeping AS iab WHERE iab.fk_docdet = ad.rowid AND doc_type = 'asset')";
461 }
462 $sql .= " AND ad.ref != ''"; // not reversal lines
463 if ($date_start && $date_end) {
464 $sql .= " AND ad.depreciation_date >= '" . $this->db->idate($date_start) . "' AND ad.depreciation_date <= '" . $this->db->idate($date_end) . "'";
465 }
466 // Define begin binding date
467 if (getDolGlobalString('ACCOUNTING_DATE_START_BINDING')) {
468 $sql .= " AND ad.depreciation_date >= '" . $this->db->idate(getDolGlobalInt('ACCOUNTING_DATE_START_BINDING')) . "'";
469 }
470 $sql .= " ORDER BY ad.depreciation_date";
471
472 dol_syslog(__METHOD__, LOG_DEBUG);
473 $resql = $this->db->query($sql);
474 if (!$resql) {
475 $this->errors[] = $this->db->lasterror();
476 return -1;
477 }
478
479 $pre_data = array(
480 'elements' => array(),
481 );
482 while ($obj = $this->db->fetch_object($resql)) {
483 if (!isset($pre_data['elements'][$obj->rowid])) {
484 $pre_data['elements'][$obj->rowid] = array(
485 'ref' => $obj->asset_ref,
486 'label' => $obj->asset_label,
487 'acquisition_value_ht' => $obj->asset_acquisition_value_ht,
488 'depreciation' => array(),
489 );
490
491 // Disposal infos
492 if (isset($obj->asset_disposal_date)) {
493 $pre_data['elements'][$obj->rowid]['disposal'] = array(
494 'date' => $this->db->jdate($obj->asset_disposal_date),
495 'amount' => $obj->asset_disposal_amount_ht,
496 'subject_to_vat' => !empty($obj->asset_disposal_subject_to_vat),
497 );
498 }
499 }
500
501 $compta_debit = empty($obj->accountancy_code_debit) ? 'NotDefined' : $obj->accountancy_code_debit;
502 $compta_credit = empty($obj->accountancy_code_credit) ? 'NotDefined' : $obj->accountancy_code_credit;
503
504 $pre_data['elements'][$obj->rowid]['depreciation'][$obj->depreciation_id] = array(
505 'date' => $this->db->jdate($obj->depreciation_date),
506 'ref' => $obj->depreciation_ref,
507 'lines' => array(
508 $compta_debit => -$obj->depreciation_ht,
509 $compta_credit => $obj->depreciation_ht,
510 ),
511 );
512 }
513
514 $disposal_ref = $langs->transnoentitiesnoconv('AssetDisposal');
515 $journal = $this->code;
516 $journal_label = $this->label;
517 $journal_label_formatted = $langs->transnoentities($journal_label);
518 $now = dol_now();
519
520 $element_static = new Asset($this->db);
521
522 $journal_data = array();
523 foreach ($pre_data['elements'] as $pre_data_id => $pre_data_info) {
524 $element_static->id = (int) $pre_data_id;
525 $element_static->ref = (string) $pre_data_info["ref"];
526 $element_static->label = (string) $pre_data_info["label"];
527 $element_static->acquisition_value_ht = $pre_data_info["acquisition_value_ht"];
528 $element_link = $element_static->getNomUrl(1, 'with_label');
529
530 $element_name_formatted_0 = dol_trunc($element_static->label, 16);
531 $label_operation = $element_static->getNomUrl(0, 'label', 16);
532
533 $element = array(
534 'ref' => dol_trunc($element_static->ref, 16, 'right', 'UTF-8', 1),
535 'error' => array_key_exists('error', $pre_data_info) ? $pre_data_info['error'] : '', // @phpstan-ignore-line
536 'blocks' => array(),
537 );
538
539 // Depreciation lines
540 //--------------------
541 foreach ($pre_data_info['depreciation'] as $depreciation_id => $line) {
542 $depreciation_ref = $line["ref"];
543 $depreciation_date = $line["date"];
544 $depreciation_date_formatted = dol_print_date($depreciation_date, 'day');
545
546 // lines
547 $blocks = array();
548 foreach ($line['lines'] as $account => $mt) {
549 $account_infos = $this->getAccountingAccountInfos($account);
550
551 if ($type == 'view') {
552 $account_to_show = length_accountg($account);
553 if (($account_to_show == "") || $account_to_show == 'NotDefined') {
554 $account_to_show = '<span class="error">' . $langs->trans("AssetInAccountNotDefined") . '</span>';
555 }
556
557 $blocks[] = array(
558 'date' => $depreciation_date_formatted,
559 'piece' => $element_link,
560 'account_accounting' => $account_to_show,
561 'subledger_account' => '',
562 'label_operation' => $label_operation . ' - ' . $depreciation_ref,
563 'debit' => $mt < 0 ? price(-$mt) : '',
564 'credit' => $mt >= 0 ? price($mt) : '',
565 );
566 } elseif ($type == 'bookkeeping') {
567 if ($account_infos['found']) {
568 $blocks[] = array(
569 'doc_date' => $depreciation_date,
570 'date_lim_reglement' => '',
571 'doc_ref' => $element_static->ref,
572 'date_creation' => $now,
573 'doc_type' => 'asset',
574 'fk_doc' => $element_static->id,
575 'fk_docdet' => (int) $depreciation_id, // Useless, can be several lines that are source of this record to add
576 'thirdparty_code' => '',
577 'subledger_account' => '',
578 'subledger_label' => '',
579 'numero_compte' => (string) $account,
580 'label_compte' => $account_infos['label'],
581 'label_operation' => $element_name_formatted_0 . ' - ' . $depreciation_ref,
582 'montant' => (float) $mt,
583 'sens' => $mt < 0 ? 'D' : 'C',
584 'debit' => $mt < 0 ? -$mt : 0,
585 'credit' => $mt >= 0 ? $mt : 0,
586 'code_journal' => $journal,
587 'journal_label' => $journal_label_formatted,
588 'piece_num' => '',
589 'import_key' => '',
590 'fk_user_author' => $user->id,
591 'entity' => $conf->entity,
592 );
593 }
594 } else { // $type == 'csv'
595 $blocks[] = array(
596 $depreciation_date, // Date
597 $element_static->ref, // Piece
598 $account_infos['code_formatted_1'], // AccountAccounting
599 $element_name_formatted_0 . ' - ' . $depreciation_ref, // LabelOperation
600 $mt < 0 ? price(-$mt) : '', // Debit
601 $mt >= 0 ? price($mt) : '', // Credit
602 );
603 }
604 }
605 $element['blocks'][] = $blocks;
606 }
607
608 // Disposal line
609 //--------------------
610 if (!empty($pre_data_info['disposal'])) {
611 $disposal_date = $pre_data_info['disposal']['date'];
612
613 if ((!($date_start && $date_end) || ($date_start <= $disposal_date && $disposal_date <= $date_end)) &&
614 (!getDolGlobalString('ACCOUNTING_DATE_START_BINDING') || getDolGlobalInt('ACCOUNTING_DATE_START_BINDING') <= $disposal_date)
615 ) {
616 $disposal_amount = $pre_data_info['disposal']['amount'];
617 $disposal_subject_to_vat = $pre_data_info['disposal']['subject_to_vat'];
618 $disposal_date_formatted = dol_print_date($disposal_date, 'day');
619 $disposal_vat = getDolGlobalInt('ASSET_DISPOSAL_VAT') > 0 ? getDolGlobalInt('ASSET_DISPOSAL_VAT') : 20;
620
621 // Get accountancy codes
622 //---------------------------
623 require_once DOL_DOCUMENT_ROOT . '/asset/class/assetaccountancycodes.class.php';
624 $accountancy_codes = new AssetAccountancyCodes($this->db);
625 $result = $accountancy_codes->fetchAccountancyCodes($element_static->id);
626 if ($result < 0) {
627 $element['error'] = $accountancy_codes->errorsToString();
628 } else {
629 // Get last depreciation cumulative amount
630 $element_static->fetchDepreciationLines();
631 foreach ($element_static->depreciation_lines as $mode_key => $depreciation_lines) {
632 $accountancy_codes_list = $accountancy_codes->accountancy_codes[$mode_key];
633
634 if (!isset($accountancy_codes_list['value_asset_sold'])) {
635 continue;
636 }
637
638 $accountancy_code_value_asset_sold = empty($accountancy_codes_list['value_asset_sold']) ? 'NotDefined' : $accountancy_codes_list['value_asset_sold'];
639 $accountancy_code_depreciation_asset = empty($accountancy_codes_list['depreciation_asset']) ? 'NotDefined' : $accountancy_codes_list['depreciation_asset'];
640 $accountancy_code_asset = empty($accountancy_codes_list['asset']) ? 'NotDefined' : $accountancy_codes_list['asset'];
641 $accountancy_code_receivable_on_assignment = empty($accountancy_codes_list['receivable_on_assignment']) ? 'NotDefined' : $accountancy_codes_list['receivable_on_assignment'];
642 $accountancy_code_vat_collected = empty($accountancy_codes_list['vat_collected']) ? 'NotDefined' : $accountancy_codes_list['vat_collected'];
643 $accountancy_code_proceeds_from_sales = empty($accountancy_codes_list['proceeds_from_sales']) ? 'NotDefined' : $accountancy_codes_list['proceeds_from_sales'];
644
645 $last_cumulative_amount_ht = 0;
646 $depreciated_ids = array_keys($pre_data_info['depreciation']);
647 foreach ($depreciation_lines as $line) {
648 $last_cumulative_amount_ht = $line['cumulative_depreciation_ht'];
649 if (!in_array($line['id'], $depreciated_ids) && empty($line['bookkeeping']) && !empty($line['ref'])) {
650 break;
651 }
652 }
653
654 $lines = array();
655 $lines[0][$accountancy_code_value_asset_sold] = -((float) $element_static->acquisition_value_ht - $last_cumulative_amount_ht);
656 $lines[0][$accountancy_code_depreciation_asset] = - (float) $last_cumulative_amount_ht;
657 $lines[0][$accountancy_code_asset] = $element_static->acquisition_value_ht;
658
659 $disposal_amount_vat = $disposal_subject_to_vat ? (float) price2num($disposal_amount * $disposal_vat / 100, 'MT') : 0;
660 $lines[1][$accountancy_code_receivable_on_assignment] = -($disposal_amount + $disposal_amount_vat);
661 if ($disposal_subject_to_vat) {
662 $lines[1][$accountancy_code_vat_collected] = $disposal_amount_vat;
663 }
664 $lines[1][$accountancy_code_proceeds_from_sales] = $disposal_amount;
665
666 foreach ($lines as $lines_block) {
667 $blocks = array();
668 foreach ($lines_block as $account => $mt) {
669 $account_infos = $this->getAccountingAccountInfos($account);
670
671 if ($type == 'view') {
672 $account_to_show = length_accountg($account);
673 if (($account_to_show == "") || $account_to_show == 'NotDefined') {
674 $account_to_show = '<span class="error">' . $langs->trans("AssetInAccountNotDefined") . '</span>';
675 }
676
677 $blocks[] = array(
678 'date' => $disposal_date_formatted,
679 'piece' => $element_link,
680 'account_accounting' => $account_to_show,
681 'subledger_account' => '',
682 'label_operation' => $label_operation . ' - ' . $disposal_ref,
683 'debit' => $mt < 0 ? price(-$mt) : '',
684 'credit' => $mt >= 0 ? price($mt) : '',
685 );
686 } elseif ($type == 'bookkeeping') {
687 if ($account_infos['found']) {
688 $blocks[] = array(
689 'doc_date' => $disposal_date,
690 'date_lim_reglement' => '',
691 'doc_ref' => $element_static->ref,
692 'date_creation' => $now,
693 'doc_type' => 'asset',
694 'fk_doc' => $element_static->id,
695 'fk_docdet' => 0, // Useless, can be several lines that are source of this record to add
696 'thirdparty_code' => '',
697 'subledger_account' => '',
698 'subledger_label' => '',
699 'numero_compte' => (string) $account,
700 'label_compte' => $account_infos['label'],
701 'label_operation' => $element_name_formatted_0 . ' - ' . $disposal_ref,
702 'montant' => (float) $mt,
703 'sens' => $mt < 0 ? 'D' : 'C',
704 'debit' => $mt < 0 ? -$mt : 0,
705 'credit' => $mt >= 0 ? $mt : 0,
706 'code_journal' => $journal,
707 'journal_label' => $journal_label_formatted,
708 'piece_num' => '',
709 'import_key' => '',
710 'fk_user_author' => $user->id,
711 'entity' => $conf->entity,
712 );
713 }
714 } else { // $type == 'csv'
715 $blocks[] = array(
716 $disposal_date, // Date
717 $element_static->ref, // Piece
718 $account_infos['code_formatted_1'], // AccountAccounting
719 $element_name_formatted_0 . ' - ' . $disposal_ref, // LabelOperation
720 $mt < 0 ? price(-$mt) : '', // Debit
721 $mt >= 0 ? price($mt) : '', // Credit
722 );
723 }
724 }
725 $element['blocks'][] = $blocks;
726 }
727 }
728 }
729 }
730 }
731
732 $journal_data[(int) $pre_data_id] = $element;
733 }
734 unset($pre_data);
735
736 return $journal_data;
737 }
738
749 public function getDiscountCustomer(User $user, $type = 'view', $date_start = null, $date_end = null, $in_bookkeeping = 'notyet')
750 {
751 global $conf, $langs, $hookmanager;
752
753 require_once DOL_DOCUMENT_ROOT.'/compta/facture/class/facture.class.php';
754 require_once DOL_DOCUMENT_ROOT.'/societe/class/societe.class.php';
755 require_once DOL_DOCUMENT_ROOT.'/core/lib/accounting.lib.php';
756
757 $langs->loadLangs(array('bills'));
758
759 // Clean parameters
760 if (empty($type)) {
761 $type = 'view';
762 }
763 if (empty($in_bookkeeping)) {
764 $in_bookkeeping = 'notyet';
765 }
766
767 // Build SQL - Customer invoices closed by discount
768 $sql = "SELECT f.rowid, f.ref, f.datef, f.date_closing, f.fk_soc, f.total_ttc";
769 $sql .= " FROM ".MAIN_DB_PREFIX."facture as f";
770 $sql .= " WHERE f.entity IN (".getEntity('invoice', 0).')'; // We don't share object for accountancy, we use source object sharing
771 $sql .= " AND f.fk_statut > 0";
772 if (getDolGlobalString('FACTURE_DEPOSITS_ARE_JUST_PAYMENTS')) { // Non common setup
773 $sql .= " AND f.type IN (".Facture::TYPE_STANDARD.",".Facture::TYPE_REPLACEMENT.",".Facture::TYPE_CREDIT_NOTE.",".Facture::TYPE_SITUATION.")";
774 } else {
775 $sql .= " AND f.type IN (".Facture::TYPE_STANDARD.",".Facture::TYPE_REPLACEMENT.",".Facture::TYPE_CREDIT_NOTE.",".Facture::TYPE_DEPOSIT.",".Facture::TYPE_SITUATION.")";
776 }
777 // Add SQL restrictions from hooks (context accountingjournaldao), e.g. a deposit pivot date restricting deposits by their date
778 $hookmanager->initHooks(array('accountingjournaldao'));
779 $parameters = array('invoicealias' => 'f', 'issupplier' => 0, 'datefield' => 'datef');
780 $reshook = $hookmanager->executeHooks('printFieldListWhere', $parameters, $this); // Note that $action and $object may have been modified by some hooks
781 $sql .= $hookmanager->resPrint;
782 $sql .= " AND f.close_code = 'discount_vat'";
783 if ($date_start && $date_end) {
784 $sql .= " AND f.date_closing >= '".$this->db->idate($date_start)."' AND f.date_closing <= '".$this->db->idate($date_end)."'";
785 }
786 if (getDolGlobalString('ACCOUNTING_DATE_START_BINDING')) {
787 $sql .= " AND f.date_closing >= '".$this->db->idate(getDolGlobalInt('ACCOUNTING_DATE_START_BINDING'))."'";
788 }
789 if ($in_bookkeeping == 'already') {
790 $sql .= " AND EXISTS (SELECT 1 FROM ".MAIN_DB_PREFIX."accounting_bookkeeping ab";
791 $sql .= " WHERE ab.doc_type = 'customer_invoice' AND ab.fk_doc = f.rowid";
792 $sql .= " AND ab.code_journal = '".$this->db->escape($this->code)."')";
793 } elseif ($in_bookkeeping == 'notyet') {
794 $sql .= " AND NOT EXISTS (SELECT 1 FROM ".MAIN_DB_PREFIX."accounting_bookkeeping ab";
795 $sql .= " WHERE ab.doc_type = 'customer_invoice' AND ab.fk_doc = f.rowid";
796 $sql .= " AND ab.code_journal = '".$this->db->escape($this->code)."')";
797 }
798 $sql .= " ORDER BY f.date_closing";
799
800 dol_syslog(__METHOD__, LOG_DEBUG);
801 $resql = $this->db->query($sql);
802 if (!$resql) {
803 $this->errors[] = $this->db->lasterror();
804 return -1;
805 }
806
807 $journal = $this->code;
808 $journal_label_formatted = $langs->transnoentities($this->label);
809 $now = dol_now();
810
811 $journal_data = array();
812 $invoice_static = new Facture($this->db);
813 $customer_static = new Societe($this->db);
814
815 // Accounting accounts
816 $acc_disc_granted = getDolGlobalString('ACCOUNTING_ACCOUNT_DISCOUNT_GRANTED');
817 $acc_vat_coll_def = getDolGlobalString('ACCOUNTING_VAT_BUY_ACCOUNT'); // Normal to apply vat default account for buy with customer's discount
818
819 while ($obj = $this->db->fetch_object($resql)) {
820 if ($invoice_static->fetch((int) $obj->rowid) <= 0) {
821 continue;
822 }
823
824 $customer_static->fetch($invoice_static->socid);
825 $account_customer_general = !empty($customer_static->accountancy_code_customer_general) ? $customer_static->accountancy_code_customer_general : getDolGlobalString('ACCOUNTING_ACCOUNT_CUSTOMER');
826 $account_customer_subsidiary = !empty($customer_static->code_compta_client) ? $customer_static->code_compta_client : '';
827
828 $piece_link = $invoice_static->getNomUrl(1, 'withlabel');
829
830 // Discounted amount including tax
831 $paid = (float) price2num($invoice_static->getSommePaiement(), 'MT');
832 $usedcn = (float) price2num($invoice_static->getSumCreditNotesUsed(), 'MT');
833 $useddep = (float) price2num($invoice_static->getSumDepositsUsed(), 'MT');
834 $ttc_inv = (float) price2num($invoice_static->total_ttc, 'MT');
835 $escompte_ttc = (float) price2num(max(0, $ttc_inv - $paid - $usedcn - $useddep), 'MT');
836 if ($escompte_ttc <= 0) {
837 continue;
838 }
839
840 $bookkeeping_static = new BookKeeping($this->db);
841 $thirdpartyname = (string) $customer_static->name;
842 $label_discount = $bookkeeping_static->accountingLabelForOperation($thirdpartyname, $invoice_static->ref, $langs->trans('DiscountGranted'));
843
844 // Distribution including VAT by rate
845 $ttcByRate = array();
846 $totalTTC = 0.0;
847 foreach ((array) $invoice_static->lines as $li) {
848 $ttc = (float) $li->total_ttc;
849 if (!$ttc) {
850 continue;
851 }
852 $key = number_format((float) $li->tva_tx, 3, '.', '');
853 if (!isset($ttcByRate[$key])) {
854 $ttcByRate[$key] = 0.0;
855 }
856 $ttcByRate[$key] += $ttc;
857 $totalTTC += $ttc;
858 }
859 if ($totalTTC <= 0) {
860 $ttcByRate = array("0.000" => $escompte_ttc);
861 $totalTTC = $escompte_ttc;
862 }
863
864 $element = array(
865 'ref' => dol_trunc($invoice_static->ref, 16, 'right', 'UTF-8', 1),
866 'error' => '',
867 'blocks' => array(),
868 );
869
870 $closingdate = !empty($obj->date_closing) ? $obj->date_closing : $obj->datef;
871
872 $docdate = $this->db->jdate($closingdate);
873 $docdate_fmt = dol_print_date($docdate, 'day');
874
875 $sumTTC = 0.0;
876 $i = 0;
877 $n = count($ttcByRate);
878 foreach ($ttcByRate as $rateStr => $ttcRateOnInvoice) {
879 $i++;
880 $rate = (float) $rateStr;
881
882 $ttc_part = (float) $escompte_ttc * ($ttcRateOnInvoice / $totalTTC);
883 if ($i == $n) {
884 $ttc_part = (float) price2num($escompte_ttc - $sumTTC, 'MT');
885 } else {
886 $ttc_part = (float) price2num($ttc_part, 'MT');
887 $sumTTC = (float) price2num($sumTTC + $ttc_part, 'MT');
888 }
889
890 if ($rate > 0) {
891 $ht_part = (float) price2num($ttc_part / (1 + $rate / 100), 'MT');
892 $tva_part = (float) price2num($ttc_part - $ht_part, 'MT');
893 } else {
894 $ht_part = $ttc_part;
895 $tva_part = 0.0;
896 }
897
898 // VAT deductible account (by rate if available)
899 // TODO write function to search the same vat code like the invoice
900 $acc_vat_coll = $acc_vat_coll_def;
901
902 $lines_view = array();
903 $lines_book = array();
904
905 // Discount granted
906 $acc_info_discountgranted = $this->getAccountingAccountInfos($acc_disc_granted);
907 if ($type == 'view') {
908 $lines_view[] = array(
909 'date' => $docdate_fmt,
910 'piece' => $piece_link,
911 'account_accounting' => length_accountg($acc_disc_granted),
912 'subledger_account' => '',
913 'label_operation' => $label_discount." - " .$langs->transnoentitiesnoconv('HT') . " (".$rateStr."%)",
914 'debit' => price($ht_part),
915 'credit' => '',
916 );
917 } elseif ($type == 'bookkeeping' && $acc_info_discountgranted['found']) {
918 $lines_book[] = array(
919 'doc_date' => $docdate,
920 'date_lim_reglement' => '',
921 'doc_ref' => $invoice_static->ref,
922 'date_creation' => $now,
923 'doc_type' => 'customer_invoice',
924 'fk_doc' => $invoice_static->id,
925 'fk_docdet' => 0,
926 'thirdparty_code' => $customer_static->code_client,
927 'subledger_account' => '',
928 'subledger_label' => '',
929 'numero_compte' => (string) $acc_disc_granted,
930 'label_compte' => $acc_info_discountgranted['label'],
931 'label_operation' => $label_discount." - " .$langs->transnoentitiesnoconv('HT') . " (".$rateStr."%)",
932 'montant' => $ht_part,
933 'sens' => 'D',
934 'debit' => $ht_part,
935 'credit' => 0,
936 'code_journal' => $journal,
937 'journal_label' => $journal_label_formatted,
938 'piece_num' => 'OD-ESC-'.$invoice_static->ref,
939 'import_key' => '',
940 'fk_user_author' => $user->id,
941 'entity' => $conf->entity,
942 );
943 }
944
945 // VAT
946 if ($tva_part > 0) {
947 $acc_info_vatbuy = $this->getAccountingAccountInfos($acc_vat_coll);
948 if ($type == 'view') {
949 $lines_view[] = array(
950 'date' => $docdate_fmt,
951 'piece' => $piece_link,
952 'account_accounting' => length_accountg($acc_vat_coll),
953 'subledger_account' => '',
954 'label_operation' => $label_discount." - " .$langs->transnoentitiesnoconv('VAT') . " (".$rateStr."%)",
955 'debit' => price($tva_part),
956 'credit' => '',
957 );
958 } elseif ($type == 'bookkeeping' && $acc_info_vatbuy['found']) {
959 $lines_book[] = array(
960 'doc_date' => $docdate,
961 'date_lim_reglement' => '',
962 'doc_ref' => $invoice_static->ref,
963 'date_creation' => $now,
964 'doc_type' => 'customer_invoice',
965 'fk_doc' => $invoice_static->id,
966 'fk_docdet' => 0,
967 'thirdparty_code' => $customer_static->code_client,
968 'subledger_account' => '',
969 'subledger_label' => '',
970 'numero_compte' => (string) $acc_vat_coll,
971 'label_compte' => $acc_info_vatbuy['label'],
972 'label_operation' => $label_discount." - " .$langs->transnoentitiesnoconv('VAT') . " (".$rateStr."%)",
973 'montant' => $tva_part,
974 'sens' => 'D',
975 'debit' => $tva_part,
976 'credit' => 0,
977 'code_journal' => $journal,
978 'journal_label' => $journal_label_formatted,
979 'piece_num' => 'OD-ESC-'.$invoice_static->ref,
980 'import_key' => '',
981 'fk_user_author' => $user->id,
982 'entity' => $conf->entity,
983 );
984 }
985 }
986
987 // Thirdparty
988 $acc_info_customeraccount = $this->getAccountingAccountInfos($account_customer_general);
989 if ($type == 'view') {
990 $lines_view[] = array(
991 'date' => $docdate_fmt,
992 'piece' => $piece_link,
993 'account_accounting' => length_accountg($account_customer_general),
994 'subledger_account' => length_accounta($account_customer_subsidiary),
995 'label_operation' => $label_discount.' - '.$langs->transnoentitiesnoconv('Customer'),
996 'debit' => '',
997 'credit' => price($ttc_part),
998 );
999 $element['blocks'][] = $lines_view;
1000 } elseif ($type == 'bookkeeping' && $acc_info_customeraccount['found']) {
1001 $lines_book[] = array(
1002 'doc_date' => $docdate,
1003 'date_lim_reglement' => '',
1004 'doc_ref' => $invoice_static->ref,
1005 'date_creation' => $now,
1006 'doc_type' => 'customer_invoice',
1007 'fk_doc' => $invoice_static->id,
1008 'fk_docdet' => 0,
1009 'thirdparty_code' => $customer_static->code_client,
1010 'subledger_account' => $account_customer_subsidiary,
1011 'subledger_label' => $customer_static->name,
1012 'numero_compte' => (string) $account_customer_general,
1013 'label_compte' => $acc_info_customeraccount['label'],
1014 'label_operation' => $label_discount.' - '.$langs->transnoentitiesnoconv('Customer'),
1015 'montant' => $ttc_part,
1016 'sens' => 'C',
1017 'debit' => 0,
1018 'credit' => $ttc_part,
1019 'code_journal' => $journal,
1020 'journal_label' => $journal_label_formatted,
1021 'piece_num' => 'OD-ESC-'.$invoice_static->ref,
1022 'import_key' => '',
1023 'fk_user_author' => $user->id,
1024 'entity' => $conf->entity,
1025 );
1026 $element['blocks'][] = $lines_book;
1027 } else { // CSV
1028 $element['blocks'][] = array(
1029 $docdate, // Date
1030 $invoice_static->ref, // Piece
1031 length_accountg($acc_disc_granted), // Account
1032 $label_discount." (".$rateStr."%)", // Label
1033 price($ht_part), // Debit
1034 '', // Credit
1035 );
1036 if ($tva_part > 0) {
1037 $element['blocks'][] = array(
1038 $docdate, $invoice_static->ref, length_accountg($acc_vat_coll), $label_discount." ". $langs->transnoentitiesnoconv('VAT') . " (".$rateStr."%)", price($tva_part), ''
1039 );
1040 }
1041 $element['blocks'][] = array(
1042 $docdate, $invoice_static->ref, length_accountg($account_customer_general), $label_discount.' - '.$langs->transnoentitiesnoconv('Customer'), '', price($ttc_part)
1043 );
1044 }
1045 }
1046
1047 $journal_data[(int) $invoice_static->id] = $element;
1048 }
1049
1050 return $journal_data;
1051 }
1052
1063 public function getDiscountSupplier(User $user, $type = 'view', $date_start = null, $date_end = null, $in_bookkeeping = 'notyet')
1064 {
1065 global $conf, $langs, $hookmanager;
1066
1067 require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.facture.class.php';
1068 require_once DOL_DOCUMENT_ROOT.'/societe/class/societe.class.php';
1069 require_once DOL_DOCUMENT_ROOT.'/core/lib/accounting.lib.php';
1070
1071 $langs->loadLangs(array('suppliers'));
1072
1073 // Clean parameters
1074 if (empty($type)) {
1075 $type = 'view';
1076 }
1077 if (empty($in_bookkeeping)) {
1078 $in_bookkeeping = 'notyet';
1079 }
1080
1081 // SQL - Supplier invoices closed by discount
1082 $sql = "SELECT ff.rowid, ff.ref, ff.datef, ff.date_closing, ff.fk_soc, ff.total_ttc";
1083 $sql .= " FROM ".MAIN_DB_PREFIX."facture_fourn as ff";
1084 $sql .= " WHERE ff.entity IN (".getEntity('facture_fourn', 0).")"; // We don't share object for accountancy
1085 $sql .= " AND ff.fk_statut > 0";
1086 if (getDolGlobalString('FACTURE_SUPPLIER_DEPOSITS_ARE_JUST_PAYMENTS')) {
1087 $sql .= " AND ff.type IN (".FactureFournisseur::TYPE_STANDARD.",".FactureFournisseur::TYPE_REPLACEMENT.",".FactureFournisseur::TYPE_CREDIT_NOTE.",".FactureFournisseur::TYPE_SITUATION.")";
1088 } else {
1089 $sql .= " AND ff.type IN (".FactureFournisseur::TYPE_STANDARD.",".FactureFournisseur::TYPE_REPLACEMENT.",".FactureFournisseur::TYPE_CREDIT_NOTE.",".FactureFournisseur::TYPE_DEPOSIT.",".FactureFournisseur::TYPE_SITUATION.")";
1090 }
1091 // Add SQL restrictions from hooks (context accountingjournaldao), e.g. a deposit pivot date restricting deposits by their date
1092 $hookmanager->initHooks(array('accountingjournaldao'));
1093 $parameters = array('invoicealias' => 'ff', 'issupplier' => 1, 'datefield' => 'datef');
1094 $reshook = $hookmanager->executeHooks('printFieldListWhere', $parameters, $this); // Note that $action and $object may have been modified by some hooks
1095 $sql .= $hookmanager->resPrint;
1096 $sql .= " AND ff.close_code = 'discount_vat'";
1097 if ($date_start && $date_end) {
1098 $sql .= " AND ff.date_closing >= '".$this->db->idate($date_start)."' AND ff.date_closing <= '".$this->db->idate($date_end)."'";
1099 }
1100 if (getDolGlobalString('ACCOUNTING_DATE_START_BINDING')) {
1101 $sql .= " AND ff.date_closing >= '".$this->db->idate(getDolGlobalInt('ACCOUNTING_DATE_START_BINDING'))."'";
1102 }
1103 if ($in_bookkeeping == 'already') {
1104 $sql .= " AND EXISTS (SELECT 1 FROM ".MAIN_DB_PREFIX."accounting_bookkeeping ab";
1105 $sql .= " WHERE ab.doc_type = 'supplier_invoice' AND ab.fk_doc = ff.rowid";
1106 $sql .= " AND ab.code_journal = '".$this->db->escape($this->code)."')";
1107 } elseif ($in_bookkeeping == 'notyet') {
1108 $sql .= " AND NOT EXISTS (SELECT 1 FROM ".MAIN_DB_PREFIX."accounting_bookkeeping ab";
1109 $sql .= " WHERE ab.doc_type = 'supplier_invoice' AND ab.fk_doc = ff.rowid";
1110 $sql .= " AND ab.code_journal = '".$this->db->escape($this->code)."')";
1111 }
1112 $sql .= " ORDER BY ff.date_closing";
1113
1114 dol_syslog(__METHOD__, LOG_DEBUG);
1115 $resql = $this->db->query($sql);
1116 if (!$resql) {
1117 $this->errors[] = $this->db->lasterror();
1118 return -1;
1119 }
1120
1121 $journal = $this->code;
1122 $journal_label_formatted = $langs->transnoentities($this->label);
1123 $now = dol_now();
1124
1125 $journal_data = array();
1126 $invoicesupplier_static = new FactureFournisseur($this->db);
1127 $supplier_static = new Societe($this->db);
1128
1129 // Accounting accounts
1130 $acc_disc_recv = getDolGlobalString('ACCOUNTING_ACCOUNT_DISCOUNT_RECEIVED');
1131 $acc_vat_ded_def = getDolGlobalString('ACCOUNTING_VAT_SOLD_ACCOUNT'); // Normal to apply vat default account for sold with supplier's discount
1132
1133 while ($obj = $this->db->fetch_object($resql)) {
1134 if ($invoicesupplier_static->fetch((int) $obj->rowid) <= 0) {
1135 continue;
1136 }
1137
1138 $supplier_static->fetch($invoicesupplier_static->socid);
1139 $account_supplier_general = !empty($supplier_static->accountancy_code_supplier_general) ? $supplier_static->accountancy_code_supplier_general : getDolGlobalString('ACCOUNTING_ACCOUNT_SUPPLIER');
1140 $account_supplier_subsidiary = !empty($supplier_static->code_compta_fournisseur) ? $supplier_static->code_compta_fournisseur : '';
1141
1142 $piece_link = $invoicesupplier_static->getNomUrl(1, 'withlabel');
1143
1144 // Discounted amount including tax
1145 $paid = (float) price2num($invoicesupplier_static->getSommePaiement(), 'MT');
1146 $usedcn = (float) price2num($invoicesupplier_static->getSumCreditNotesUsed(), 'MT');
1147 $useddep = (float) price2num($invoicesupplier_static->getSumDepositsUsed(), 'MT');
1148 $ttc_inv = (float) price2num($invoicesupplier_static->total_ttc, 'MT');
1149 $escompte_ttc = (float) price2num(max(0, $ttc_inv - $paid - $usedcn - $useddep), 'MT');
1150 if ($escompte_ttc <= 0) {
1151 continue;
1152 }
1153
1154 $bookkeeping_static = new BookKeeping($this->db);
1155 $thirdpartyname = (string) $supplier_static->name;
1156 $label_discount = $bookkeeping_static->accountingLabelForOperation($thirdpartyname, $invoicesupplier_static->ref, $langs->trans('DiscountReceived'));
1157
1158 // Distribution including VAT by rate
1159 $ttcByRate = array();
1160 $totalTTC = 0.0;
1161 foreach ((array) $invoicesupplier_static->lines as $li) {
1162 $ttc = (float) $li->total_ttc;
1163 if (!$ttc) {
1164 continue;
1165 }
1166 $key = number_format((float) $li->tva_tx, 3, '.', '');
1167 if (!isset($ttcByRate[$key])) {
1168 $ttcByRate[$key] = 0.0;
1169 }
1170 $ttcByRate[$key] += $ttc;
1171 $totalTTC += $ttc;
1172 }
1173 if ($totalTTC <= 0) {
1174 $ttcByRate = array("0.000" => $escompte_ttc);
1175 $totalTTC = $escompte_ttc;
1176 }
1177
1178 $element = array(
1179 'ref' => dol_trunc($invoicesupplier_static->ref, 16, 'right', 'UTF-8', 1),
1180 'error' => '',
1181 'blocks' => array(),
1182 );
1183
1184 $closingdate = !empty($obj->date_closing) ? $obj->date_closing : $obj->datef;
1185
1186 $docdate = $this->db->jdate($closingdate);
1187 $docdate_fmt = dol_print_date($docdate, 'day');
1188
1189 $sumTTC = 0.0;
1190 $i = 0;
1191 $n = count($ttcByRate);
1192 foreach ($ttcByRate as $rateStr => $ttcRateOnInvoice) {
1193 $i++;
1194 $rate = (float) $rateStr;
1195
1196 $ttc_part = $escompte_ttc * ($ttcRateOnInvoice / $totalTTC);
1197 if ($i == $n) {
1198 $ttc_part = (float) price2num($escompte_ttc - $sumTTC, 'MT');
1199 } else {
1200 $ttc_part = (float) price2num($ttc_part, 'MT');
1201 $sumTTC = (float) price2num($sumTTC + $ttc_part, 'MT');
1202 }
1203
1204 if ($rate > 0) {
1205 $ht_part = (float) price2num($ttc_part / (1 + $rate / 100), 'MT');
1206 $tva_part = (float) price2num($ttc_part - $ht_part, 'MT');
1207 } else {
1208 $ht_part = $ttc_part;
1209 $tva_part = 0.0;
1210 }
1211
1212 // VAT collected account (by rate if available)
1213 // TODO write function to search the same vat code like the supplier invoice
1214 $acc_vat_ded = $acc_vat_ded_def;
1215
1216 $lines_view = array();
1217 $lines_book = array();
1218
1219 // Thirdparty
1220 $acc_info_supplieraccount = $this->getAccountingAccountInfos($account_supplier_general);
1221 if ($type == 'view') {
1222 $lines_view[] = array(
1223 'date' => $docdate_fmt,
1224 'piece' => $piece_link,
1225 'account_accounting' => length_accountg($account_supplier_general),
1226 'subledger_account' => length_accounta($account_supplier_subsidiary),
1227 'label_operation' => $label_discount.' - '.$langs->transnoentitiesnoconv('Supplier'),
1228 'debit' => price($ttc_part),
1229 'credit' => '',
1230 );
1231 } elseif ($type == 'bookkeeping' && $acc_info_supplieraccount['found']) {
1232 $lines_book[] = array(
1233 'doc_date' => $docdate,
1234 'date_lim_reglement' => '',
1235 'doc_ref' => $invoicesupplier_static->ref,
1236 'date_creation' => $now,
1237 'doc_type' => 'supplier_invoice',
1238 'fk_doc' => $invoicesupplier_static->id,
1239 'fk_docdet' => 0,
1240 'thirdparty_code' => $supplier_static->code_fournisseur,
1241 'subledger_account' => $account_supplier_subsidiary,
1242 'subledger_label' => $supplier_static->name,
1243 'numero_compte' => $account_supplier_general,
1244 'label_compte' => $acc_info_supplieraccount['label'],
1245 'label_operation' => $label_discount.' - '.$langs->transnoentitiesnoconv('Supplier'),
1246 'montant' => $ttc_part,
1247 'sens' => 'D',
1248 'debit' => $ttc_part,
1249 'credit' => 0,
1250 'code_journal' => $journal,
1251 'journal_label' => $journal_label_formatted,
1252 'piece_num' => 'OD-ESC-FRS-'.$invoicesupplier_static->ref,
1253 'import_key' => '',
1254 'fk_user_author' => $user->id,
1255 'entity' => $conf->entity,
1256 );
1257 }
1258
1259 // Discount received
1260 $acc_info_discountreceived = $this->getAccountingAccountInfos($acc_disc_recv);
1261 if ($type == 'view') {
1262 $lines_view[] = array(
1263 'date' => $docdate_fmt,
1264 'piece' => $piece_link,
1265 'account_accounting' => length_accountg($acc_disc_recv),
1266 'subledger_account' => '',
1267 'label_operation' => $label_discount." - " .$langs->transnoentitiesnoconv('HT') . " (".$rateStr."%)",
1268 'debit' => '',
1269 'credit' => price($ht_part),
1270 );
1271 } elseif ($type == 'bookkeeping' && $acc_info_discountreceived['found']) {
1272 $lines_book[] = array(
1273 'doc_date' => $docdate,
1274 'date_lim_reglement' => '',
1275 'doc_ref' => $invoicesupplier_static->ref,
1276 'date_creation' => $now,
1277 'doc_type' => 'supplier_invoice',
1278 'fk_doc' => $invoicesupplier_static->id,
1279 'fk_docdet' => 0,
1280 'thirdparty_code' => $supplier_static->code_fournisseur,
1281 'subledger_account' => '',
1282 'subledger_label' => '',
1283 'numero_compte' => $acc_disc_recv,
1284 'label_compte' => $acc_info_discountreceived['label'],
1285 'label_operation' => $label_discount." - " .$langs->transnoentitiesnoconv('HT') . " (".$rateStr."%)",
1286 'montant' => $ht_part,
1287 'sens' => 'C',
1288 'debit' => 0,
1289 'credit' => $ht_part,
1290 'code_journal' => $journal,
1291 'journal_label' => $journal_label_formatted,
1292 'piece_num' => 'OD-ESC-FRS-'.$invoicesupplier_static->ref,
1293 'import_key' => '',
1294 'fk_user_author' => $user->id,
1295 'entity' => $conf->entity,
1296 );
1297 }
1298
1299 // VAT
1300 if ($tva_part > 0) {
1301 $acc_info_vatbuy = $this->getAccountingAccountInfos($acc_vat_ded);
1302 if ($type == 'view') {
1303 $lines_view[] = array(
1304 'date' => $docdate_fmt,
1305 'piece' => $piece_link,
1306 'account_accounting' => length_accountg($acc_vat_ded),
1307 'subledger_account' => '',
1308 'label_operation' => $label_discount." - " .$langs->transnoentitiesnoconv('VAT') . " (".$rateStr."%)",
1309 'debit' => '',
1310 'credit' => price($tva_part),
1311 );
1312 $element['blocks'][] = $lines_view;
1313 } elseif ($type == 'bookkeeping' && $acc_info_vatbuy['found']) {
1314 $lines_book[] = array(
1315 'doc_date' => $docdate,
1316 'date_lim_reglement' => '',
1317 'doc_ref' => $invoicesupplier_static->ref,
1318 'date_creation' => $now,
1319 'doc_type' => 'supplier_invoice',
1320 'fk_doc' => $invoicesupplier_static->id,
1321 'fk_docdet' => 0,
1322 'thirdparty_code' => $supplier_static->code_fournisseur,
1323 'subledger_account' => '',
1324 'subledger_label' => '',
1325 'numero_compte' => $acc_vat_ded,
1326 'label_compte' => $acc_info_vatbuy['label'],
1327 'label_operation' => $label_discount." - " .$langs->transnoentitiesnoconv('VAT') . " (".$rateStr."%)",
1328 'montant' => $tva_part,
1329 'sens' => 'C',
1330 'debit' => 0,
1331 'credit' => $tva_part,
1332 'code_journal' => $journal,
1333 'journal_label' => $journal_label_formatted,
1334 'piece_num' => 'OD-ESC-FRS-'.$invoicesupplier_static->ref,
1335 'import_key' => '',
1336 'fk_user_author' => $user->id,
1337 'entity' => $conf->entity,
1338 );
1339 $element['blocks'][] = $lines_book;
1340 }
1341 } else {
1342 // When VAT = 0, push the 2 lines view/bookkeeping already prepared
1343 if ($type == 'view') {
1344 $element['blocks'][] = $lines_view;
1345 } elseif ($type == 'bookkeeping') {
1346 $element['blocks'][] = $lines_book;
1347 } else { // csv
1348 $element['blocks'][] = array($docdate, $invoicesupplier_static->ref, length_accountg($account_supplier_general), $label_discount.' - '.$langs->transnoentitiesnoconv('Supplier'), price($ttc_part), '');
1349 $element['blocks'][] = array($docdate, $invoicesupplier_static->ref, length_accountg($acc_disc_recv), $label_discount.' ('.$rateStr.'%)', '', price($ht_part));
1350 }
1351 }
1352
1353 // CSV
1354 if ($type == 'csv') {
1355 $element['blocks'][] = array(
1356 $docdate, $invoicesupplier_static->ref, length_accountg($acc_vat_ded), $label_discount." ". $langs->transnoentitiesnoconv('VAT') . " (".$rateStr."%)", '', $tva_part > 0 ? price($tva_part) : ''
1357 );
1358 }
1359 }
1360
1361 $journal_data[(int) $invoicesupplier_static->id] = $element;
1362 }
1363
1364 return $journal_data;
1365 }
1366
1410 public function writeIntoBookkeeping(User $user, &$journal_data = array(), $max_nb_errors = 10)
1411 {
1412 global $conf, $langs, $hookmanager;
1413 require_once DOL_DOCUMENT_ROOT . '/accountancy/class/bookkeeping.class.php';
1414
1415 $error = 0;
1416
1417 $hookmanager->initHooks(array('accountingjournaldao'));
1418 $parameters = array('journal_data' => &$journal_data);
1419 $reshook = $hookmanager->executeHooks('writeBookkeeping', $parameters, $this); // Note that $action and $object may have been
1420 if ($reshook < 0) {
1421 $this->error = $hookmanager->error;
1422 $this->errors = $hookmanager->errors;
1423 return -1;
1424 } elseif (empty($reshook)) {
1425 // Clean parameters
1426 if (!is_array($journal_data)) {
1427 $journal_data = array();
1428 }
1429
1430 foreach ($journal_data as $element_id => $element) {
1431 $error_for_line = 0;
1432 $total_credit = 0;
1433 $total_debit = 0;
1434
1435 $this->db->begin();
1436
1437 if ($element['error'] == 'somelinesarenotbound') {
1438 $error++;
1439 $error_for_line++;
1440 $this->errors[] = $langs->trans('ErrorInvoiceContainsLinesNotYetBounded', $element['ref']);
1441 }
1442
1443 if (!$error_for_line) {
1444 foreach ($element['blocks'] as $lines) {
1445 foreach ($lines as $line) {
1446 $bookkeeping = new BookKeeping($this->db);
1447 $bookkeeping->doc_date = (int) $line['doc_date'];
1448 $bookkeeping->date_lim_reglement = (int) $line['date_lim_reglement'];
1449 $bookkeeping->doc_ref = $line['doc_ref'];
1450 $bookkeeping->date_creation = $line['date_creation']; // not used
1451 $bookkeeping->doc_type = $line['doc_type'];
1452 $bookkeeping->fk_doc = $line['fk_doc'];
1453 $bookkeeping->fk_docdet = $line['fk_docdet'];
1454 $bookkeeping->thirdparty_code = $line['thirdparty_code'];
1455 $bookkeeping->subledger_account = $line['subledger_account'];
1456 $bookkeeping->subledger_label = $line['subledger_label'];
1457 $bookkeeping->numero_compte = $line['numero_compte'];
1458 $bookkeeping->label_compte = $line['label_compte'];
1459 $bookkeeping->label_operation = $line['label_operation'];
1460 $bookkeeping->montant = $line['montant']; // Deprecated: sens/debit/credit (and deprecated amount...)
1461 $bookkeeping->sens = $line['sens'];
1462 $bookkeeping->debit = (float) $line['debit'];
1463 $bookkeeping->credit = (float) $line['credit'];
1464 $bookkeeping->code_journal = $line['code_journal'];
1465 $bookkeeping->journal_label = $line['journal_label'];
1466 $bookkeeping->piece_num = (int) $line['piece_num'];
1467 $bookkeeping->import_key = $line['import_key'];
1468 $bookkeeping->fk_user_author = $user->id;
1469 $bookkeeping->entity = $conf->entity;
1470
1471 $total_debit += $bookkeeping->debit;
1472 $total_credit += $bookkeeping->credit;
1473
1474 $result = $bookkeeping->create($user);
1475 if ($result < 0) {
1476 if ($bookkeeping->error == 'BookkeepingRecordAlreadyExists') { // Already exists
1477 $error++;
1478 $error_for_line++;
1479 $journal_data[$element_id]['error'] = 'alreadyjournalized';
1480 } else {
1481 $error++;
1482 $error_for_line++;
1483 $journal_data[$element_id]['error'] = 'other';
1484 $this->errors[] = $bookkeeping->errorsToString();
1485 }
1486 }
1487 //
1488 // if (!$error_for_line && isModEnabled('asset') && $this->nature == 1 && $bookkeeping->fk_doc > 0) {
1489 // // Set last cumulative depreciation
1490 // require_once DOL_DOCUMENT_ROOT . '/asset/class/asset.class.php';
1491 // $asset = new Asset($this->db);
1492 // $result = $asset->setLastCumulativeDepreciation($bookkeeping->fk_doc);
1493 // if ($result < 0) {
1494 // $error++;
1495 // $error_for_line++;
1496 // $journal_data[$element_id]['error'] = 'other';
1497 // $this->errors[] = $asset->errorsToString();
1498 // }
1499 // }
1500 }
1501
1502 if ($error_for_line) {
1503 break;
1504 }
1505 }
1506 }
1507
1508 // Protection against a bug on lines before
1509 if (!$error_for_line && (price2num($total_debit, 'MT') != price2num($total_credit, 'MT'))) {
1510 $error++;
1511 $error_for_line++;
1512 $journal_data[$element_id]['error'] = 'amountsnotbalanced';
1513 $this->errors[] = 'Try to insert a non balanced transaction in book for ' . json_encode($element['blocks']) . '. Canceled. Surely a bug.';
1514 }
1515
1516 if (!$error_for_line) {
1517 $this->db->commit();
1518 } else {
1519 $this->db->rollback();
1520
1521 if ($error >= $max_nb_errors) {
1522 $this->errors[] = $langs->trans("ErrorTooManyErrorsProcessStopped");
1523 break; // Break in the foreach
1524 }
1525 }
1526 }
1527 }
1528
1529 return $error ? -$error : 1;
1530 }
1531
1553 public function exportCsv(&$journal_data = array(), $search_date_end = 0, $sep = '')
1554 {
1555 global $conf, $langs, $hookmanager;
1556
1557 if (empty($sep)) {
1558 $sep = getDolGlobalString('ACCOUNTING_EXPORT_SEPARATORCSV');
1559 }
1560 $out = '';
1561
1562 // Hook
1563 $hookmanager->initHooks(array('accountingjournaldao'));
1564 $parameters = array('journal_data' => &$journal_data, 'search_date_end' => &$search_date_end, 'sep' => &$sep, 'out' => &$out);
1565 $reshook = $hookmanager->executeHooks('exportCsv', $parameters, $this); // Note that $action and $object may have been
1566 if ($reshook < 0) {
1567 $this->error = $hookmanager->error;
1568 $this->errors = $hookmanager->errors;
1569 return -1;
1570 } elseif (empty($reshook)) {
1571 // Clean parameters
1572 $journal_data = is_array($journal_data) ? $journal_data : array();
1573
1574 // CSV header line
1575 $header = array();
1576 if ($this->nature == 4) {
1577 $header = array(
1578 $langs->transnoentitiesnoconv("BankId"),
1579 $langs->transnoentitiesnoconv("Date"),
1580 $langs->transnoentitiesnoconv("PaymentMode"),
1581 $langs->transnoentitiesnoconv("AccountAccounting"),
1582 $langs->transnoentitiesnoconv("LedgerAccount"),
1583 $langs->transnoentitiesnoconv("SubledgerAccount"),
1584 $langs->transnoentitiesnoconv("Label"),
1585 $langs->transnoentitiesnoconv("AccountingDebit"),
1586 $langs->transnoentitiesnoconv("AccountingCredit"),
1587 $langs->transnoentitiesnoconv("Journal"),
1588 $langs->transnoentitiesnoconv("Note"),
1589 );
1590 } elseif ($this->nature == 5) {
1591 $header = array(
1592 $langs->transnoentitiesnoconv("Date"),
1593 $langs->transnoentitiesnoconv("Piece"),
1594 $langs->transnoentitiesnoconv("AccountAccounting"),
1595 $langs->transnoentitiesnoconv("LabelOperation"),
1596 $langs->transnoentitiesnoconv("AccountingDebit"),
1597 $langs->transnoentitiesnoconv("AccountingCredit"),
1598 );
1599 } elseif ($this->nature == 1) {
1600 $header = array(
1601 $langs->transnoentitiesnoconv("Date"),
1602 $langs->transnoentitiesnoconv("Piece"),
1603 $langs->transnoentitiesnoconv("AccountAccounting"),
1604 $langs->transnoentitiesnoconv("LabelOperation"),
1605 $langs->transnoentitiesnoconv("AccountingDebit"),
1606 $langs->transnoentitiesnoconv("AccountingCredit"),
1607 );
1608 }
1609
1610 if (!empty($header)) {
1611 $out .= '"' . implode('"' . $sep . '"', $header) . '"' . "\n";
1612 }
1613 foreach ($journal_data as $element_id => $element) {
1614 foreach ($element['blocks'] as $lines) {
1615 foreach ($lines as $line) {
1616 $out .= '"' . implode('"' . $sep . '"', $line) . '"' . "\n";
1617 }
1618 }
1619 }
1620 }
1621
1622 return $out;
1623 }
1624
1631 public function getAccountingAccountInfos($account)
1632 {
1633 if (!isset(self::$accounting_account_cached[$account])) {
1634 require_once DOL_DOCUMENT_ROOT . '/core/lib/accounting.lib.php';
1635 require_once DOL_DOCUMENT_ROOT . '/accountancy/class/accountingaccount.class.php';
1636 $accountingaccount = new AccountingAccount($this->db);
1637 $result = $accountingaccount->fetch(0, $account, true);
1638 if ($result > 0) {
1639 self::$accounting_account_cached[$account] = array(
1640 'found' => true,
1641 'label' => $accountingaccount->label,
1642 'code_formatted_1' => length_accountg(html_entity_decode($account)),
1643 'label_formatted_1' => mb_convert_encoding(dol_trunc($accountingaccount->label, 32), 'ISO-8859-1'),
1644 'label_formatted_2' => dol_trunc($accountingaccount->label, 32),
1645 );
1646 } else {
1647 self::$accounting_account_cached[$account] = array(
1648 'found' => false,
1649 'label' => '',
1650 'code_formatted_1' => length_accountg(html_entity_decode($account)),
1651 'label_formatted_1' => '',
1652 'label_formatted_2' => '',
1653 );
1654 }
1655 }
1656
1657 return self::$accounting_account_cached[$account];
1658 }
1659}
length_accountg($account)
Return General accounting account with defined length (used for product and miscellaneous)
length_accounta($accounta)
Return Auxiliary accounting account of thirdparties with defined length.
$object ref
Definition info.php:90
Class to manage accounting accounts.
Class to manage accounting journals.
writeIntoBookkeeping(User $user, &$journal_data=array(), $max_nb_errors=10)
Write bookkeeping.
getData(User $user, $type='view', $date_start=null, $date_end=null, $in_bookkeeping='notyet')
Get journal data.
exportCsv(&$journal_data=array(), $search_date_end=0, $sep='')
Export journal CSV ISO and not UTF8 !
getDiscountSupplier(User $user, $type='view', $date_start=null, $date_end=null, $in_bookkeeping='notyet')
Get supplier discount (escompte) data for various journal.
getNomUrl($withpicto=0, $withlabel=0, $nourl=0, $moretitle='', $notooltip=0)
Return clickable name (with picto eventually)
fetch($rowid=0, $journal_code=null)
Load an object from database.
getDiscountCustomer(User $user, $type='view', $date_start=null, $date_end=null, $in_bookkeeping='notyet')
Get customer discount (escompte) data for various journal.
LibType($nature, $mode=0)
Return type of an accounting journal.
getAccountingAccountInfos($account)
Get accounting account info.
create($user)
Create a new Accounting Journal.
getAssetData(User $user, $type='view', $date_start=null, $date_end=null, $in_bookkeeping='notyet')
Get asset data for various journal.
getLibType($mode=0)
Return the label of the status.
Class for AssetAccountancyCodes.
Class for Asset.
Class to manage Ledger (General Ledger and Subledger)
const TYPE_SITUATION
Situation invoice.
Class to manage suppliers invoices.
const TYPE_DEPOSIT
Deposit invoice.
const TYPE_CREDIT_NOTE
Credit note invoice.
const TYPE_REPLACEMENT
Replacement invoice.
Class to manage invoices.
const TYPE_REPLACEMENT
Replacement invoice.
const TYPE_SITUATION
Situation invoice.
const TYPE_DEPOSIT
Deposit invoice.
const TYPE_CREDIT_NOTE
Credit note invoice.
Class to manage third parties objects (customers, suppliers, prospects...)
Class to manage Dolibarr users.
if(!isModEnabled('ai')||!getDolGlobalString('AI_ASSISTANT_ENABLED')) global $conf
The main.inc.php has been included so the following variable are now defined:
$date_start
Variables from include:
dol_now($mode='gmt')
Return date for now.
price2num($amount, $rounding='', $option=0)
Function that return a number with universal decimal format (decimal separator is '.
dolBuildUrl($url, $params=[], $addtoken=false, $anchor='')
Return path of url.
price($amount, $form=0, $outlangs='', $trunc=1, $rounding=-1, $forcerounding=-1, $currency_code='')
Function to format a value into an amount for visual output Function used into PDF and HTML pages.
getDolGlobalInt($key, $default=0)
Return a Dolibarr global constant int value.
dol_print_date($time, $format='', $tzoutput='auto', $outputlangs=null, $encodetooutput=false, $decorate=0)
Output date in a string format according to outputlangs (or langs if not defined).
dol_trunc($string, $size=40, $trunc='right', $stringencoding='UTF-8', $nodot=0, $display=0)
Truncate a string to a particular length adding '...' if string larger than length.
getDolGlobalString($key, $default='')
Return a Dolibarr global constant string value.
isModEnabled($module)
Is Dolibarr module enabled.
dol_syslog($message, $level=LOG_INFO, $ident=0, $suffixinfilename='', $restricttologhandler='', $logcontext=null)
Write log message into outputs.
getEntity($element, $shared=1, $currentobject=null)
Get list of entity id to use.
editval_textarea active