accountingjournal.class.php 39 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062
  1. <?php
  2. /* Copyright (C) 2017-2022 OpenDSI <support@open-dsi.fr>
  3. *
  4. * This program is free software; you can redistribute it and/or modify
  5. * it under the terms of the GNU General Public License as published by
  6. * the Free Software Foundation; either version 3 of the License, or
  7. * (at your option) any later version.
  8. *
  9. * This program is distributed in the hope that it will be useful,
  10. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. * GNU General Public License for more details.
  13. *
  14. * You should have received a copy of the GNU General Public License
  15. * along with this program. If not, see <https://www.gnu.org/licenses/>.
  16. */
  17. /**
  18. * \file htdocs/accountancy/class/accountingjournal.class.php
  19. * \ingroup Accountancy (Double entries)
  20. * \brief File of class to manage accounting journals
  21. */
  22. /**
  23. * Class to manage accounting accounts
  24. */
  25. class AccountingJournal extends CommonObject
  26. {
  27. /**
  28. * @var string ID to identify managed object
  29. */
  30. public $element = 'accounting_journal';
  31. /**
  32. * @var string Name of table without prefix where object is stored
  33. */
  34. public $table_element = 'accounting_journal';
  35. /**
  36. * @var string Fieldname with ID of parent key if this field has a parent
  37. */
  38. public $fk_element = '';
  39. /**
  40. * @var int 0=No test on entity, 1=Test with field entity, 2=Test with link by societe
  41. */
  42. public $ismultientitymanaged = 0;
  43. /**
  44. * @var string String with name of icon for myobject. Must be the part after the 'object_' into object_myobject.png
  45. */
  46. public $picto = 'generic';
  47. /**
  48. * @var int ID
  49. */
  50. public $rowid;
  51. /**
  52. * @var string Accounting journal code
  53. */
  54. public $code;
  55. /**
  56. * @var string Accounting Journal label
  57. */
  58. public $label;
  59. /**
  60. * @var int 1:various operations, 2:sale, 3:purchase, 4:bank, 5:expense-report, 8:inventory, 9: has-new
  61. */
  62. public $nature;
  63. /**
  64. * @var int is active or not
  65. */
  66. public $active;
  67. /**
  68. * @var array array of lines
  69. */
  70. public $lines;
  71. /**
  72. * @var array Accounting account cached
  73. */
  74. static public $accounting_account_cached = array();
  75. /**
  76. * @var array Nature mapping
  77. */
  78. static public $nature_maps = array(
  79. 1 => 'variousoperations',
  80. 2 => 'sells',
  81. 3 => 'purchases',
  82. 4 => 'bank',
  83. 5 => 'expensereports',
  84. 8 => 'inventories',
  85. 9 => 'hasnew',
  86. );
  87. /**
  88. * Constructor
  89. *
  90. * @param DoliDB $db Database handle
  91. */
  92. public function __construct($db)
  93. {
  94. $this->db = $db;
  95. }
  96. /**
  97. * Load an object from database
  98. *
  99. * @param int $rowid Id of record to load
  100. * @param string $journal_code Journal code
  101. * @return int <0 if KO, Id of record if OK and found
  102. */
  103. public function fetch($rowid = null, $journal_code = null)
  104. {
  105. global $conf;
  106. if ($rowid || $journal_code) {
  107. $sql = "SELECT rowid, code, label, nature, active";
  108. $sql .= " FROM ".MAIN_DB_PREFIX."accounting_journal";
  109. $sql .= " WHERE";
  110. if ($rowid) {
  111. $sql .= " rowid = ".((int) $rowid);
  112. } elseif ($journal_code) {
  113. $sql .= " code = '".$this->db->escape($journal_code)."'";
  114. $sql .= " AND entity = ".$conf->entity;
  115. }
  116. dol_syslog(get_class($this)."::fetch", LOG_DEBUG);
  117. $result = $this->db->query($sql);
  118. if ($result) {
  119. $obj = $this->db->fetch_object($result);
  120. if ($obj) {
  121. $this->id = $obj->rowid;
  122. $this->rowid = $obj->rowid;
  123. $this->code = $obj->code;
  124. $this->ref = $obj->code;
  125. $this->label = $obj->label;
  126. $this->nature = $obj->nature;
  127. $this->active = $obj->active;
  128. return $this->id;
  129. } else {
  130. return 0;
  131. }
  132. } else {
  133. $this->error = "Error ".$this->db->lasterror();
  134. $this->errors[] = "Error ".$this->db->lasterror();
  135. }
  136. }
  137. return -1;
  138. }
  139. /**
  140. * Load object in memory from the database
  141. *
  142. * @param string $sortorder Sort Order
  143. * @param string $sortfield Sort field
  144. * @param int $limit offset limit
  145. * @param int $offset offset limit
  146. * @param array $filter filter array
  147. * @param string $filtermode filter mode (AND or OR)
  148. *
  149. * @return int <0 if KO, >0 if OK
  150. */
  151. public function fetchAll($sortorder = '', $sortfield = '', $limit = 0, $offset = 0, array $filter = array(), $filtermode = 'AND')
  152. {
  153. $sql = "SELECT rowid, code, label, nature, active";
  154. $sql .= ' FROM '.MAIN_DB_PREFIX.$this->table_element.' as t';
  155. // Manage filter
  156. $sqlwhere = array();
  157. if (count($filter) > 0) {
  158. foreach ($filter as $key => $value) {
  159. if ($key == 't.code' || $key == 't.label' || $key == 't.nature') {
  160. $sqlwhere[] = $key.'\''.$this->db->escape($value).'\'';
  161. } elseif ($key == 't.rowid' || $key == 't.active') {
  162. $sqlwhere[] = $key.'='.$value;
  163. }
  164. }
  165. }
  166. $sql .= ' WHERE 1 = 1';
  167. $sql .= " AND entity IN (".getEntity('accountancy').")";
  168. if (count($sqlwhere) > 0) {
  169. $sql .= " AND ".implode(" ".$filtermode." ", $sqlwhere);
  170. }
  171. if (!empty($sortfield)) {
  172. $sql .= $this->db->order($sortfield, $sortorder);
  173. }
  174. if (!empty($limit)) {
  175. $sql .= $this->db->plimit($limit + 1, $offset);
  176. }
  177. $this->lines = array();
  178. dol_syslog(get_class($this)."::fetch", LOG_DEBUG);
  179. $resql = $this->db->query($sql);
  180. if ($resql) {
  181. $num = $this->db->num_rows($resql);
  182. while ($obj = $this->db->fetch_object($resql)) {
  183. $line = new self($this->db);
  184. $line->id = $obj->rowid;
  185. $line->code = $obj->code;
  186. $line->label = $obj->label;
  187. $line->nature = $obj->nature;
  188. $line->active = $obj->active;
  189. $this->lines[] = $line;
  190. }
  191. $this->db->free($resql);
  192. return $num;
  193. } else {
  194. $this->errors[] = 'Error '.$this->db->lasterror();
  195. dol_syslog(__METHOD__.' '.join(',', $this->errors), LOG_ERR);
  196. return -1;
  197. }
  198. }
  199. /**
  200. * Return clicable name (with picto eventually)
  201. *
  202. * @param int $withpicto 0=No picto, 1=Include picto into link, 2=Only picto
  203. * @param int $withlabel 0=No label, 1=Include label of journal, 2=Include nature of journal
  204. * @param int $nourl 1=Disable url
  205. * @param string $moretitle Add more text to title tooltip
  206. * @param int $notooltip 1=Disable tooltip
  207. * @return string String with URL
  208. */
  209. public function getNomUrl($withpicto = 0, $withlabel = 0, $nourl = 0, $moretitle = '', $notooltip = 0)
  210. {
  211. global $langs, $conf, $user, $hookmanager;
  212. if (!empty($conf->dol_no_mouse_hover)) {
  213. $notooltip = 1; // Force disable tooltips
  214. }
  215. $result = '';
  216. $url = DOL_URL_ROOT.'/accountancy/admin/journals_list.php?id=35';
  217. $label = '<u>'.$langs->trans("ShowAccountingJournal").'</u>';
  218. if (!empty($this->code)) {
  219. $label .= '<br><b>'.$langs->trans('Code').':</b> '.$this->code;
  220. }
  221. if (!empty($this->label)) {
  222. $label .= '<br><b>'.$langs->trans('Label').':</b> '.$langs->transnoentities($this->label);
  223. }
  224. if ($moretitle) {
  225. $label .= ' - '.$moretitle;
  226. }
  227. $linkclose = '';
  228. if (empty($notooltip)) {
  229. if (!empty($conf->global->MAIN_OPTIMIZEFORTEXTBROWSER)) {
  230. $label = $langs->trans("ShowAccountingJournal");
  231. $linkclose .= ' alt="'.dol_escape_htmltag($label, 1).'"';
  232. }
  233. $linkclose .= ' title="'.dol_escape_htmltag($label, 1).'"';
  234. $linkclose .= ' class="classfortooltip"';
  235. }
  236. $linkstart = '<a href="'.$url.'"';
  237. $linkstart .= $linkclose.'>';
  238. $linkend = '</a>';
  239. if ($nourl) {
  240. $linkstart = '';
  241. $linkclose = '';
  242. $linkend = '';
  243. }
  244. $label_link = $this->code;
  245. if ($withlabel != 2 && !empty($this->label)) {
  246. $label_link .= ' - '.($nourl ? '<span class="opacitymedium">' : '').$langs->transnoentities($this->label).($nourl ? '</span>' : '');
  247. }
  248. if ($withlabel == 2 && !empty($this->nature)) {
  249. $key = $langs->trans("AccountingJournalType".strtoupper($this->nature));
  250. $transferlabel = ($this->nature && $key != "AccountingJournalType".strtoupper($langs->trans($this->nature)) ? $key : $this->label);
  251. $label_link .= ' - '.($nourl ? '<span class="opacitymedium">' : '').$transferlabel.($nourl ? '</span>' : '');
  252. }
  253. $result .= $linkstart;
  254. if ($withpicto) {
  255. $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);
  256. }
  257. if ($withpicto != 2) {
  258. $result .= $label_link;
  259. }
  260. $result .= $linkend;
  261. global $action;
  262. $hookmanager->initHooks(array('accountingjournaldao'));
  263. $parameters = array('id'=>$this->id, 'getnomurl' => &$result);
  264. $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
  265. if ($reshook > 0) {
  266. $result = $hookmanager->resPrint;
  267. } else {
  268. $result .= $hookmanager->resPrint;
  269. }
  270. return $result;
  271. }
  272. /**
  273. * Retourne le libelle du statut d'un user (actif, inactif)
  274. *
  275. * @param int $mode 0=libelle long, 1=libelle court
  276. * @return string Label of type
  277. */
  278. public function getLibType($mode = 0)
  279. {
  280. return $this->LibType($this->nature, $mode);
  281. }
  282. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
  283. /**
  284. * Return type of an accounting journal
  285. *
  286. * @param int $nature Id type
  287. * @param int $mode 0=libelle long, 1=libelle court
  288. * @return string Label of type
  289. */
  290. public function LibType($nature, $mode = 0)
  291. {
  292. // phpcs:enable
  293. global $langs;
  294. $langs->loadLangs(array("accountancy"));
  295. if ($mode == 0) {
  296. $prefix = '';
  297. if ($nature == 9) {
  298. return $langs->trans('AccountingJournalType9');
  299. } elseif ($nature == 5) {
  300. return $langs->trans('AccountingJournalType5');
  301. } elseif ($nature == 4) {
  302. return $langs->trans('AccountingJournalType4');
  303. } elseif ($nature == 3) {
  304. return $langs->trans('AccountingJournalType3');
  305. } elseif ($nature == 2) {
  306. return $langs->trans('AccountingJournalType2');
  307. } elseif ($nature == 1) {
  308. return $langs->trans('AccountingJournalType1');
  309. }
  310. } elseif ($mode == 1) {
  311. if ($nature == 9) {
  312. return $langs->trans('AccountingJournalType9');
  313. } elseif ($nature == 5) {
  314. return $langs->trans('AccountingJournalType5');
  315. } elseif ($nature == 4) {
  316. return $langs->trans('AccountingJournalType4');
  317. } elseif ($nature == 3) {
  318. return $langs->trans('AccountingJournalType3');
  319. } elseif ($nature == 2) {
  320. return $langs->trans('AccountingJournalType2');
  321. } elseif ($nature == 1) {
  322. return $langs->trans('AccountingJournalType1');
  323. }
  324. }
  325. }
  326. /**
  327. * Get journal data
  328. *
  329. * @param User $user User who get infos
  330. * @param string $type Type data returned ('view', 'bookkeeping', 'csv')
  331. * @param int $date_start Filter 'start date'
  332. * @param int $date_end Filter 'end date'
  333. * @param string $in_bookkeeping Filter 'in bookkeeping' ('already', 'notyet')
  334. * @return array|int <0 if KO, >0 if OK
  335. */
  336. public function getData(User $user, $type = 'view', $date_start = null, $date_end = null, $in_bookkeeping = 'notyet')
  337. {
  338. global $hookmanager;
  339. // Clean parameters
  340. if (empty($type)) $type = 'view';
  341. if (empty($in_bookkeeping)) $in_bookkeeping = 'notyet';
  342. // Hook
  343. if (!is_object($hookmanager)) {
  344. include_once DOL_DOCUMENT_ROOT . '/core/class/hookmanager.class.php';
  345. $hookmanager = new HookManager($this->db);
  346. }
  347. $data = array();
  348. $hookmanager->initHooks(array('accountingjournaldao'));
  349. $parameters = array('data' => &$data, 'user' => $user, 'type' => $type, 'date_start' => $date_start, 'date_end' => $date_end, 'in_bookkeeping' => $in_bookkeeping);
  350. $reshook = $hookmanager->executeHooks('getData', $parameters, $this); // Note that $action and $object may have been
  351. if ($reshook < 0) {
  352. $this->error = $hookmanager->error;
  353. $this->errors = $hookmanager->errors;
  354. return -1;
  355. } elseif (empty($reshook)) {
  356. switch ($this->nature) {
  357. case 1: // Various Journal
  358. $data = $this->getAssetData($user, $type, $date_start, $date_end, $in_bookkeeping);
  359. break;
  360. // case 2: // Sells Journal
  361. // case 3: // Purchases Journal
  362. // case 4: // Bank Journal
  363. // case 5: // Expense reports Journal
  364. // case 8: // Inventory Journal
  365. // case 9: // hasnew Journal
  366. }
  367. }
  368. return $data;
  369. }
  370. /**
  371. * Get asset data for various journal
  372. *
  373. * @param User $user User who get infos
  374. * @param string $type Type data returned ('view', 'bookkeeping', 'csv')
  375. * @param int $date_start Filter 'start date'
  376. * @param int $date_end Filter 'end date'
  377. * @param string $in_bookkeeping Filter 'in bookkeeping' ('already', 'notyet')
  378. * @return array|int <0 if KO, >0 if OK
  379. */
  380. public function getAssetData(User $user, $type = 'view', $date_start = null, $date_end = null, $in_bookkeeping = 'notyet')
  381. {
  382. global $conf, $langs;
  383. if (!isModEnabled('asset')) {
  384. return array();
  385. }
  386. require_once DOL_DOCUMENT_ROOT . '/core/lib/accounting.lib.php';
  387. require_once DOL_DOCUMENT_ROOT . '/asset/class/asset.class.php';
  388. require_once DOL_DOCUMENT_ROOT . '/asset/class/assetaccountancycodes.class.php';
  389. require_once DOL_DOCUMENT_ROOT . '/asset/class/assetdepreciationoptions.class.php';
  390. $langs->loadLangs(array("assets"));
  391. // Clean parameters
  392. if (empty($type)) {
  393. $type = 'view';
  394. }
  395. if (empty($in_bookkeeping)) {
  396. $in_bookkeeping = 'notyet';
  397. }
  398. $sql = "";
  399. // FIXME sql error with Mysql 5.7
  400. /*if ($in_bookkeeping == 'already' || $in_bookkeeping == 'notyet') {
  401. $sql .= "WITH in_accounting_bookkeeping(fk_docdet) AS (";
  402. $sql .= " SELECT DISTINCT fk_docdet";
  403. $sql .= " FROM " . MAIN_DB_PREFIX . "accounting_bookkeeping";
  404. $sql .= " WHERE doc_type = 'asset'";
  405. $sql .= ") ";
  406. }*/
  407. $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";
  408. $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";
  409. $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";
  410. $sql .= " FROM " . MAIN_DB_PREFIX . "asset_depreciation as ad";
  411. $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "asset as a ON a.rowid = ad.fk_asset";
  412. // FIXME sql error with Mysql 5.7
  413. /*if ($in_bookkeeping == 'already' || $in_bookkeeping == 'notyet') {
  414. $sql .= " LEFT JOIN in_accounting_bookkeeping as iab ON iab.fk_docdet = ad.rowid";
  415. }*/
  416. $sql .= " WHERE a.entity IN (" . getEntity('asset', 0) . ')'; // We don't share object for accountancy, we use source object sharing
  417. // Compatibility with Mysql 5.7
  418. if ($in_bookkeeping == 'already') {
  419. $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')";
  420. } elseif ($in_bookkeeping == 'notyet') {
  421. $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')";
  422. }
  423. $sql .= " AND ad.ref != ''"; // not reversal lines
  424. if ($date_start && $date_end) {
  425. $sql .= " AND ad.depreciation_date >= '" . $this->db->idate($date_start) . "' AND ad.depreciation_date <= '" . $this->db->idate($date_end) . "'";
  426. }
  427. // Define begin binding date
  428. if (!empty($conf->global->ACCOUNTING_DATE_START_BINDING)) {
  429. $sql .= " AND ad.depreciation_date >= '" . $this->db->idate($conf->global->ACCOUNTING_DATE_START_BINDING) . "'";
  430. }
  431. // Already in bookkeeping or not
  432. // FIXME sql error with Mysql 5.7
  433. /*if ($in_bookkeeping == 'already' || $in_bookkeeping == 'notyet') {
  434. $sql .= " AND iab.fk_docdet IS" . ($in_bookkeeping == 'already' ? " NOT" : "") . " NULL";
  435. }*/
  436. $sql .= " ORDER BY ad.depreciation_date";
  437. dol_syslog(__METHOD__, LOG_DEBUG);
  438. $resql = $this->db->query($sql);
  439. if (!$resql) {
  440. $this->errors[] = $this->db->lasterror();
  441. return -1;
  442. }
  443. $pre_data = array(
  444. 'elements' => array(),
  445. );
  446. while ($obj = $this->db->fetch_object($resql)) {
  447. if (!isset($pre_data['elements'][$obj->rowid])) {
  448. $pre_data['elements'][$obj->rowid] = array(
  449. 'ref' => $obj->asset_ref,
  450. 'label' => $obj->asset_label,
  451. 'acquisition_value_ht' => $obj->asset_acquisition_value_ht,
  452. 'depreciation' => array(),
  453. );
  454. // Disposal infos
  455. if (isset($obj->asset_disposal_date)) {
  456. $pre_data['elements'][$obj->rowid]['disposal'] = array(
  457. 'date' => $this->db->jdate($obj->asset_disposal_date),
  458. 'amount' => $obj->asset_disposal_amount_ht,
  459. 'subject_to_vat' => !empty($obj->asset_disposal_subject_to_vat),
  460. );
  461. }
  462. }
  463. $compta_debit = empty($obj->accountancy_code_debit) ? 'NotDefined' : $obj->accountancy_code_debit;
  464. $compta_credit = empty($obj->accountancy_code_credit) ? 'NotDefined' : $obj->accountancy_code_credit;
  465. $pre_data['elements'][$obj->rowid]['depreciation'][$obj->depreciation_id] = array(
  466. 'date' => $this->db->jdate($obj->depreciation_date),
  467. 'ref' => $obj->depreciation_ref,
  468. 'lines' => array(
  469. $compta_debit => -$obj->depreciation_ht,
  470. $compta_credit => $obj->depreciation_ht,
  471. ),
  472. );
  473. }
  474. $disposal_ref = $langs->transnoentitiesnoconv('AssetDisposal');
  475. $journal = $this->code;
  476. $journal_label = $this->label;
  477. $journal_label_formatted = $langs->transnoentities($journal_label);
  478. $now = dol_now();
  479. $element_static = new Asset($this->db);
  480. $journal_data = array();
  481. foreach ($pre_data['elements'] as $pre_data_id => $pre_data_info) {
  482. $element_static->id = $pre_data_id;
  483. $element_static->ref = (string) $pre_data_info["ref"];
  484. $element_static->label = (string) $pre_data_info["label"];
  485. $element_static->acquisition_value_ht = $pre_data_info["acquisition_value_ht"];
  486. $element_link = $element_static->getNomUrl(1, 'with_label');
  487. $element_name_formatted_0 = dol_trunc($element_static->label, 16);
  488. $element_name_formatted_1 = utf8_decode(dol_trunc($element_static->label, 32));
  489. $element_name_formatted_2 = utf8_decode(dol_trunc($element_static->label, 16));
  490. $label_operation = $element_static->getNomUrl(0, 'label', 16);
  491. $element = array(
  492. 'ref' => dol_trunc($element_static->ref, 16, 'right', 'UTF-8', 1),
  493. 'error' => $pre_data_info['error'],
  494. 'blocks' => array(),
  495. );
  496. // Depreciation lines
  497. //--------------------
  498. foreach ($pre_data_info['depreciation'] as $depreciation_id => $line) {
  499. $depreciation_ref = $line["ref"];
  500. $depreciation_date = $line["date"];
  501. $depreciation_date_formatted = dol_print_date($depreciation_date, 'day');
  502. // lines
  503. $blocks = array();
  504. foreach ($line['lines'] as $account => $mt) {
  505. $account_infos = $this->getAccountingAccountInfos($account);
  506. if ($type == 'view') {
  507. $account_to_show = length_accounta($account);
  508. if (($account_to_show == "") || $account_to_show == 'NotDefined') {
  509. $account_to_show = '<span class="error">' . $langs->trans("AssetInAccountNotDefined") . '</span>';
  510. }
  511. $blocks[] = array(
  512. 'date' => $depreciation_date_formatted,
  513. 'piece' => $element_link,
  514. 'account_accounting' => $account_to_show,
  515. 'subledger_account' => '',
  516. 'label_operation' => $label_operation . ' - ' . $depreciation_ref,
  517. 'debit' => $mt < 0 ? price(-$mt) : '',
  518. 'credit' => $mt >= 0 ? price($mt) : '',
  519. );
  520. } elseif ($type == 'bookkeeping') {
  521. if ($account_infos['found']) {
  522. $blocks[] = array(
  523. 'doc_date' => $depreciation_date,
  524. 'date_lim_reglement' => '',
  525. 'doc_ref' => $element_static->ref,
  526. 'date_creation' => $now,
  527. 'doc_type' => 'asset',
  528. 'fk_doc' => $element_static->id,
  529. 'fk_docdet' => $depreciation_id, // Useless, can be several lines that are source of this record to add
  530. 'thirdparty_code' => '',
  531. 'subledger_account' => '',
  532. 'subledger_label' => '',
  533. 'numero_compte' => $account,
  534. 'label_compte' => $account_infos['label'],
  535. 'label_operation' => $element_name_formatted_0 . ' - ' . $depreciation_ref,
  536. 'montant' => $mt,
  537. 'sens' => $mt < 0 ? 'D' : 'C',
  538. 'debit' => $mt < 0 ? -$mt : 0,
  539. 'credit' => $mt >= 0 ? $mt : 0,
  540. 'code_journal' => $journal,
  541. 'journal_label' => $journal_label_formatted,
  542. 'piece_num' => '',
  543. 'import_key' => '',
  544. 'fk_user_author' => $user->id,
  545. 'entity' => $conf->entity,
  546. );
  547. }
  548. } else { // $type == 'csv'
  549. $blocks[] = array(
  550. $depreciation_date, // Date
  551. $element_static->ref, // Piece
  552. $account_infos['code_formatted_1'], // AccountAccounting
  553. $element_name_formatted_0 . ' - ' . $depreciation_ref, // LabelOperation
  554. $mt < 0 ? price(-$mt) : '', // Debit
  555. $mt >= 0 ? price($mt) : '', // Credit
  556. );
  557. }
  558. }
  559. $element['blocks'][] = $blocks;
  560. }
  561. // Disposal line
  562. //--------------------
  563. if (!empty($pre_data_info['disposal'])) {
  564. $disposal_date = $pre_data_info['disposal']['date'];
  565. if ((!($date_start && $date_end) || ($date_start <= $disposal_date && $disposal_date <= $date_end)) &&
  566. (empty($conf->global->ACCOUNTING_DATE_START_BINDING) || $conf->global->ACCOUNTING_DATE_START_BINDING <= $disposal_date)
  567. ) {
  568. $disposal_amount = $pre_data_info['disposal']['amount'];
  569. $disposal_subject_to_vat = $pre_data_info['disposal']['subject_to_vat'];
  570. $disposal_date_formatted = dol_print_date($disposal_date, 'day');
  571. $disposal_vat = $conf->global->ASSET_DISPOSAL_VAT > 0 ? $conf->global->ASSET_DISPOSAL_VAT : 20;
  572. // Get accountancy codes
  573. //---------------------------
  574. require_once DOL_DOCUMENT_ROOT . '/asset/class/assetaccountancycodes.class.php';
  575. $accountancy_codes = new AssetAccountancyCodes($this->db);
  576. $result = $accountancy_codes->fetchAccountancyCodes($element_static->id);
  577. if ($result < 0) {
  578. $element['error'] = $accountancy_codes->errorsToString();
  579. } else {
  580. // Get last depreciation cumulative amount
  581. $element_static->fetchDepreciationLines();
  582. foreach ($element_static->depreciation_lines as $mode_key => $depreciation_lines) {
  583. $accountancy_codes_list = $accountancy_codes->accountancy_codes[$mode_key];
  584. if (!isset($accountancy_codes_list['value_asset_sold'])) {
  585. continue;
  586. }
  587. $accountancy_code_value_asset_sold = empty($accountancy_codes_list['value_asset_sold']) ? 'NotDefined' : $accountancy_codes_list['value_asset_sold'];
  588. $accountancy_code_depreciation_asset = empty($accountancy_codes_list['depreciation_asset']) ? 'NotDefined' : $accountancy_codes_list['depreciation_asset'];
  589. $accountancy_code_asset = empty($accountancy_codes_list['asset']) ? 'NotDefined' : $accountancy_codes_list['asset'];
  590. $accountancy_code_receivable_on_assignment = empty($accountancy_codes_list['receivable_on_assignment']) ? 'NotDefined' : $accountancy_codes_list['receivable_on_assignment'];
  591. $accountancy_code_vat_collected = empty($accountancy_codes_list['vat_collected']) ? 'NotDefined' : $accountancy_codes_list['vat_collected'];
  592. $accountancy_code_proceeds_from_sales = empty($accountancy_codes_list['proceeds_from_sales']) ? 'NotDefined' : $accountancy_codes_list['proceeds_from_sales'];
  593. $last_cumulative_amount_ht = 0;
  594. $depreciated_ids = array_keys($pre_data_info['depreciation']);
  595. foreach ($depreciation_lines as $line) {
  596. $last_cumulative_amount_ht = $line['cumulative_depreciation_ht'];
  597. if (!in_array($line['id'], $depreciated_ids) && empty($line['bookkeeping']) && !empty($line['ref'])) {
  598. break;
  599. }
  600. }
  601. $lines = array();
  602. $lines[0][$accountancy_code_value_asset_sold] = -($element_static->acquisition_value_ht - $last_cumulative_amount_ht);
  603. $lines[0][$accountancy_code_depreciation_asset] = -$last_cumulative_amount_ht;
  604. $lines[0][$accountancy_code_asset] = $element_static->acquisition_value_ht;
  605. $disposal_amount_vat = $disposal_subject_to_vat ? (double) price2num($disposal_amount * $disposal_vat / 100, 'MT') : 0;
  606. $lines[1][$accountancy_code_receivable_on_assignment] = -($disposal_amount + $disposal_amount_vat);
  607. if ($disposal_subject_to_vat) $lines[1][$accountancy_code_vat_collected] = $disposal_amount_vat;
  608. $lines[1][$accountancy_code_proceeds_from_sales] = $disposal_amount;
  609. foreach ($lines as $lines_block) {
  610. $blocks = array();
  611. foreach ($lines_block as $account => $mt) {
  612. $account_infos = $this->getAccountingAccountInfos($account);
  613. if ($type == 'view') {
  614. $account_to_show = length_accounta($account);
  615. if (($account_to_show == "") || $account_to_show == 'NotDefined') {
  616. $account_to_show = '<span class="error">' . $langs->trans("AssetInAccountNotDefined") . '</span>';
  617. }
  618. $blocks[] = array(
  619. 'date' => $disposal_date_formatted,
  620. 'piece' => $element_link,
  621. 'account_accounting' => $account_to_show,
  622. 'subledger_account' => '',
  623. 'label_operation' => $label_operation . ' - ' . $disposal_ref,
  624. 'debit' => $mt < 0 ? price(-$mt) : '',
  625. 'credit' => $mt >= 0 ? price($mt) : '',
  626. );
  627. } elseif ($type == 'bookkeeping') {
  628. if ($account_infos['found']) {
  629. $blocks[] = array(
  630. 'doc_date' => $disposal_date,
  631. 'date_lim_reglement' => '',
  632. 'doc_ref' => $element_static->ref,
  633. 'date_creation' => $now,
  634. 'doc_type' => 'asset',
  635. 'fk_doc' => $element_static->id,
  636. 'fk_docdet' => 0, // Useless, can be several lines that are source of this record to add
  637. 'thirdparty_code' => '',
  638. 'subledger_account' => '',
  639. 'subledger_label' => '',
  640. 'numero_compte' => $account,
  641. 'label_compte' => $account_infos['label'],
  642. 'label_operation' => $element_name_formatted_0 . ' - ' . $disposal_ref,
  643. 'montant' => $mt,
  644. 'sens' => $mt < 0 ? 'D' : 'C',
  645. 'debit' => $mt < 0 ? -$mt : 0,
  646. 'credit' => $mt >= 0 ? $mt : 0,
  647. 'code_journal' => $journal,
  648. 'journal_label' => $journal_label_formatted,
  649. 'piece_num' => '',
  650. 'import_key' => '',
  651. 'fk_user_author' => $user->id,
  652. 'entity' => $conf->entity,
  653. );
  654. }
  655. } else { // $type == 'csv'
  656. $blocks[] = array(
  657. $disposal_date, // Date
  658. $element_static->ref, // Piece
  659. $account_infos['code_formatted_1'], // AccountAccounting
  660. $element_name_formatted_0 . ' - ' . $disposal_ref, // LabelOperation
  661. $mt < 0 ? price(-$mt) : '', // Debit
  662. $mt >= 0 ? price($mt) : '', // Credit
  663. );
  664. }
  665. }
  666. $element['blocks'][] = $blocks;
  667. }
  668. }
  669. }
  670. }
  671. }
  672. $journal_data[$pre_data_id] = $element;
  673. }
  674. unset($pre_data);
  675. return $journal_data;
  676. }
  677. /**
  678. * Write bookkeeping
  679. *
  680. * @param User $user User who write in the bookkeeping
  681. * @param array $journal_data Journal data to write in the bookkeeping
  682. * $journal_data = array(
  683. * id_element => array(
  684. * 'ref' => 'ref',
  685. * 'error' => '',
  686. * 'blocks' => array(
  687. * pos_block => array(
  688. * num_line => array(
  689. * 'doc_date' => '',
  690. * 'date_lim_reglement' => '',
  691. * 'doc_ref' => '',
  692. * 'date_creation' => '',
  693. * 'doc_type' => '',
  694. * 'fk_doc' => '',
  695. * 'fk_docdet' => '',
  696. * 'thirdparty_code' => '',
  697. * 'subledger_account' => '',
  698. * 'subledger_label' => '',
  699. * 'numero_compte' => '',
  700. * 'label_compte' => '',
  701. * 'label_operation' => '',
  702. * 'montant' => '',
  703. * 'sens' => '',
  704. * 'debit' => '',
  705. * 'credit' => '',
  706. * 'code_journal' => '',
  707. * 'journal_label' => '',
  708. * 'piece_num' => '',
  709. * 'import_key' => '',
  710. * 'fk_user_author' => '',
  711. * 'entity' => '',
  712. * ),
  713. * ),
  714. * ),
  715. * ),
  716. * );
  717. * @param int $max_nb_errors Nb error authorized before stop the process
  718. * @return int <0 if KO, >0 if OK
  719. */
  720. public function writeIntoBookkeeping(User $user, &$journal_data = array(), $max_nb_errors = 10)
  721. {
  722. global $conf, $langs, $hookmanager;
  723. require_once DOL_DOCUMENT_ROOT . '/accountancy/class/bookkeeping.class.php';
  724. // Hook
  725. if (!is_object($hookmanager)) {
  726. include_once DOL_DOCUMENT_ROOT . '/core/class/hookmanager.class.php';
  727. $hookmanager = new HookManager($this->db);
  728. }
  729. $error = 0;
  730. $hookmanager->initHooks(array('accountingjournaldao'));
  731. $parameters = array('journal_data' => &$journal_data);
  732. $reshook = $hookmanager->executeHooks('writeBookkeeping', $parameters, $this); // Note that $action and $object may have been
  733. if ($reshook < 0) {
  734. $this->error = $hookmanager->error;
  735. $this->errors = $hookmanager->errors;
  736. return -1;
  737. } elseif (empty($reshook)) {
  738. // Clean parameters
  739. $journal_data = is_array($journal_data) ? $journal_data : array();
  740. foreach ($journal_data as $element_id => $element) {
  741. $error_for_line = 0;
  742. $total_credit = 0;
  743. $total_debit = 0;
  744. $this->db->begin();
  745. if ($element['error'] == 'somelinesarenotbound') {
  746. $error++;
  747. $error_for_line++;
  748. $this->errors[] = $langs->trans('ErrorInvoiceContainsLinesNotYetBounded', $element['ref']);
  749. }
  750. if (!$error_for_line) {
  751. foreach ($element['blocks'] as $lines) {
  752. foreach ($lines as $line) {
  753. $bookkeeping = new BookKeeping($this->db);
  754. $bookkeeping->doc_date = $line['doc_date'];
  755. $bookkeeping->date_lim_reglement = $line['date_lim_reglement'];
  756. $bookkeeping->doc_ref = $line['doc_ref'];
  757. $bookkeeping->date_creation = $line['date_creation']; // not used
  758. $bookkeeping->doc_type = $line['doc_type'];
  759. $bookkeeping->fk_doc = $line['fk_doc'];
  760. $bookkeeping->fk_docdet = $line['fk_docdet'];
  761. $bookkeeping->thirdparty_code = $line['thirdparty_code'];
  762. $bookkeeping->subledger_account = $line['subledger_account'];
  763. $bookkeeping->subledger_label = $line['subledger_label'];
  764. $bookkeeping->numero_compte = $line['numero_compte'];
  765. $bookkeeping->label_compte = $line['label_compte'];
  766. $bookkeeping->label_operation = $line['label_operation'];
  767. $bookkeeping->montant = $line['montant'];
  768. $bookkeeping->sens = $line['sens'];
  769. $bookkeeping->debit = $line['debit'];
  770. $bookkeeping->credit = $line['credit'];
  771. $bookkeeping->code_journal = $line['code_journal'];
  772. $bookkeeping->journal_label = $line['journal_label'];
  773. $bookkeeping->piece_num = $line['piece_num'];
  774. $bookkeeping->import_key = $line['import_key'];
  775. $bookkeeping->fk_user_author = $user->id;
  776. $bookkeeping->entity = $conf->entity;
  777. $total_debit += $bookkeeping->debit;
  778. $total_credit += $bookkeeping->credit;
  779. $result = $bookkeeping->create($user);
  780. if ($result < 0) {
  781. if ($bookkeeping->error == 'BookkeepingRecordAlreadyExists') { // Already exists
  782. $error++;
  783. $error_for_line++;
  784. $journal_data[$element_id]['error'] = 'alreadyjournalized';
  785. } else {
  786. $error++;
  787. $error_for_line++;
  788. $journal_data[$element_id]['error'] = 'other';
  789. $this->errors[] = $bookkeeping->errorsToString();
  790. }
  791. }
  792. //
  793. // if (!$error_for_line && isModEnabled('asset') && $this->nature == 1 && $bookkeeping->fk_doc > 0) {
  794. // // Set last cumulative depreciation
  795. // require_once DOL_DOCUMENT_ROOT . '/asset/class/asset.class.php';
  796. // $asset = new Asset($this->db);
  797. // $result = $asset->setLastCumulativeDepreciation($bookkeeping->fk_doc);
  798. // if ($result < 0) {
  799. // $error++;
  800. // $error_for_line++;
  801. // $journal_data[$element_id]['error'] = 'other';
  802. // $this->errors[] = $asset->errorsToString();
  803. // }
  804. // }
  805. }
  806. if ($error_for_line) {
  807. break;
  808. }
  809. }
  810. }
  811. // Protection against a bug on lines before
  812. if (!$error_for_line && (price2num($total_debit, 'MT') != price2num($total_credit, 'MT'))) {
  813. $error++;
  814. $error_for_line++;
  815. $journal_data[$element_id]['error'] = 'amountsnotbalanced';
  816. $this->errors[] = 'Try to insert a non balanced transaction in book for ' . $element['blocks'] . '. Canceled. Surely a bug.';
  817. }
  818. if (!$error_for_line) {
  819. $this->db->commit();
  820. } else {
  821. $this->db->rollback();
  822. if ($error >= $max_nb_errors) {
  823. $this->errors[] = $langs->trans("ErrorTooManyErrorsProcessStopped");
  824. break; // Break in the foreach
  825. }
  826. }
  827. }
  828. }
  829. return $error ? -$error : 1;
  830. }
  831. /**
  832. * Export journal CSV
  833. * ISO and not UTF8 !
  834. *
  835. * @param array $journal_data Journal data to write in the bookkeeping
  836. * $journal_data = array(
  837. * id_element => array(
  838. * 'continue' => false,
  839. * 'blocks' => array(
  840. * pos_block => array(
  841. * num_line => array(
  842. * data to write in the CSV line
  843. * ),
  844. * ),
  845. * ),
  846. * ),
  847. * );
  848. * @param int $search_date_end Search date end
  849. * @param string $sep CSV separator
  850. * @return int|string <0 if KO, >0 if OK
  851. */
  852. public function exportCsv(&$journal_data = array(), $search_date_end = 0, $sep = '')
  853. {
  854. global $conf, $langs, $hookmanager;
  855. if (empty($sep)) $sep = $conf->global->ACCOUNTING_EXPORT_SEPARATORCSV;
  856. $out = '';
  857. // Hook
  858. if (!is_object($hookmanager)) {
  859. include_once DOL_DOCUMENT_ROOT . '/core/class/hookmanager.class.php';
  860. $hookmanager = new HookManager($this->db);
  861. }
  862. $hookmanager->initHooks(array('accountingjournaldao'));
  863. $parameters = array('journal_data' => &$journal_data, 'search_date_end' => &$search_date_end, 'sep' => &$sep, 'out' => &$out);
  864. $reshook = $hookmanager->executeHooks('exportCsv', $parameters, $this); // Note that $action and $object may have been
  865. if ($reshook < 0) {
  866. $this->error = $hookmanager->error;
  867. $this->errors = $hookmanager->errors;
  868. return -1;
  869. } elseif (empty($reshook)) {
  870. // Clean parameters
  871. $journal_data = is_array($journal_data) ? $journal_data : array();
  872. // CSV header line
  873. $header = array();
  874. if ($this->nature == 4) {
  875. $header = array(
  876. $langs->transnoentitiesnoconv("BankId"),
  877. $langs->transnoentitiesnoconv("Date"),
  878. $langs->transnoentitiesnoconv("PaymentMode"),
  879. $langs->transnoentitiesnoconv("AccountAccounting"),
  880. $langs->transnoentitiesnoconv("LedgerAccount"),
  881. $langs->transnoentitiesnoconv("SubledgerAccount"),
  882. $langs->transnoentitiesnoconv("Label"),
  883. $langs->transnoentitiesnoconv("AccountingDebit"),
  884. $langs->transnoentitiesnoconv("AccountingCredit"),
  885. $langs->transnoentitiesnoconv("Journal"),
  886. $langs->transnoentitiesnoconv("Note"),
  887. );
  888. } elseif ($this->nature == 5) {
  889. $header = array(
  890. $langs->transnoentitiesnoconv("Date"),
  891. $langs->transnoentitiesnoconv("Piece"),
  892. $langs->transnoentitiesnoconv("AccountAccounting"),
  893. $langs->transnoentitiesnoconv("LabelOperation"),
  894. $langs->transnoentitiesnoconv("AccountingDebit"),
  895. $langs->transnoentitiesnoconv("AccountingCredit"),
  896. );
  897. } elseif ($this->nature == 1) {
  898. $header = array(
  899. $langs->transnoentitiesnoconv("Date"),
  900. $langs->transnoentitiesnoconv("Piece"),
  901. $langs->transnoentitiesnoconv("AccountAccounting"),
  902. $langs->transnoentitiesnoconv("LabelOperation"),
  903. $langs->transnoentitiesnoconv("AccountingDebit"),
  904. $langs->transnoentitiesnoconv("AccountingCredit"),
  905. );
  906. }
  907. if (!empty($header)) $out .= '"' . implode('"' . $sep . '"', $header) . '"' . "\n";
  908. foreach ($journal_data as $element_id => $element) {
  909. foreach ($element['blocks'] as $lines) {
  910. foreach ($lines as $line) {
  911. $out .= '"' . implode('"' . $sep . '"', $line) . '"' . "\n";
  912. }
  913. }
  914. }
  915. }
  916. return $out;
  917. }
  918. /**
  919. * Get accounting account infos
  920. *
  921. * @param string $account Accounting account number
  922. * @return array Accounting account infos
  923. */
  924. public function getAccountingAccountInfos($account)
  925. {
  926. if (!isset(self::$accounting_account_cached[$account])) {
  927. require_once DOL_DOCUMENT_ROOT . '/core/lib/accounting.lib.php';
  928. require_once DOL_DOCUMENT_ROOT . '/accountancy/class/accountingaccount.class.php';
  929. $accountingaccount = new AccountingAccount($this->db);
  930. $result = $accountingaccount->fetch(null, $account, true);
  931. if ($result > 0) {
  932. self::$accounting_account_cached[$account] = array(
  933. 'found' => true,
  934. 'label' => $accountingaccount->label,
  935. 'code_formatted_1' => length_accounta(html_entity_decode($account)),
  936. 'label_formatted_1' => utf8_decode(dol_trunc($accountingaccount->label, 32)),
  937. 'label_formatted_2' => dol_trunc($accountingaccount->label, 32),
  938. );
  939. } else {
  940. self::$accounting_account_cached[$account] = array(
  941. 'found' => false,
  942. 'label' => '',
  943. 'code_formatted_1' => length_accounta(html_entity_decode($account)),
  944. 'label_formatted_1' => '',
  945. 'label_formatted_2' => '',
  946. );
  947. }
  948. }
  949. return self::$accounting_account_cached[$account];
  950. }
  951. }