PageRenderTime 69ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 1ms

/htdocs/facture/class/facture.class.php

https://bitbucket.org/speedealing/speedealing
PHP | 3658 lines | 2637 code | 423 blank | 598 comment | 484 complexity | 15c3b06c75557c8ec7a3a29545f890f6 MD5 | raw file
Possible License(s): LGPL-3.0, LGPL-2.1, GPL-3.0, MIT
  1. <?php
  2. /* Copyright (C) 2002-2007 Rodolphe Quiedeville <rodolphe@quiedeville.org>
  3. * Copyright (C) 2004-2012 Laurent Destailleur <eldy@users.sourceforge.net>
  4. * Copyright (C) 2004 Sebastien Di Cintio <sdicintio@ressource-toi.org>
  5. * Copyright (C) 2004 Benoit Mortier <benoit.mortier@opensides.be>
  6. * Copyright (C) 2005 Marc Barilley / Ocebo <marc@ocebo.com>
  7. * Copyright (C) 2005-2012 Regis Houssin <regis.houssin@capnetworks.com>
  8. * Copyright (C) 2006 Andre Cianfarani <acianfa@free.fr>
  9. * Copyright (C) 2007 Franky Van Liedekerke <franky.van.liedekerke@telenet.be>
  10. * Copyright (C) 2010-2012 Juanjo Menent <jmenent@2byte.es>
  11. * Copyright (C) 2012 Christophe Battarel <christophe.battarel@altairis.fr>
  12. * Copyright (C) 2012 Marcos García <marcosgdf@gmail.com>
  13. * Copyright (C) 2012 David Moothen <dmoothen@websitti.fr>
  14. *
  15. * This program is free software; you can redistribute it and/or modify
  16. * it under the terms of the GNU General Public License as published by
  17. * the Free Software Foundation; either version 3 of the License, or
  18. * (at your option) any later version.
  19. *
  20. * This program is distributed in the hope that it will be useful,
  21. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  22. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  23. * GNU General Public License for more details.
  24. *
  25. * You should have received a copy of the GNU General Public License
  26. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  27. */
  28. /**
  29. * \file htdocs/compta/facture/class/facture.class.php
  30. * \ingroup facture
  31. * \brief File of class to manage invoices
  32. */
  33. include_once DOL_DOCUMENT_ROOT . '/core/class/abstractinvoice.class.php';
  34. require_once DOL_DOCUMENT_ROOT . '/product/class/product.class.php';
  35. require_once DOL_DOCUMENT_ROOT . '/societe/class/client.class.php';
  36. require_once DOL_DOCUMENT_ROOT . '/margin/lib/margins.lib.php';
  37. /**
  38. * Class to manage invoices
  39. */
  40. class Facture extends AbstractInvoice {
  41. public $element = 'facture';
  42. public $table_element = 'facture';
  43. // public $table_element_line = 'facturedet';
  44. // public $fk_element = 'fk_facture';
  45. protected $ismultientitymanaged = 1; // 0=No test on entity, 1=Test with field entity, 2=Test with link by societe
  46. var $id;
  47. //! Id client
  48. var $socid;
  49. //! Objet societe client (to load with fetch_client method)
  50. var $client;
  51. var $author;
  52. var $fk_user_author;
  53. var $fk_user_valid;
  54. //! Invoice date
  55. var $date; // Invoice date
  56. var $date_creation; // Creation date
  57. var $date_validation; // Validation date
  58. var $datem;
  59. var $ref;
  60. var $ref_client;
  61. var $ref_ext;
  62. var $ref_int;
  63. public $type = "INVOICE_STANDARD";
  64. //var $amount;
  65. var $remise_absolue;
  66. var $remise_percent;
  67. var $total_ht = 0;
  68. var $total_tva = 0;
  69. var $total_ttc = 0;
  70. var $note; // deprecated
  71. var $note_private;
  72. var $note_public;
  73. //! 0=draft,
  74. //! 1=validated (need to be paid),
  75. //! 2=classified paid partially (close_code='discount_vat','badcustomer') or completely (close_code=null),
  76. //! 3=classified abandoned and no payment done (close_code='badcustomer','abandon' or 'replaced')
  77. public $Status = "DRAFT";
  78. //! Fermeture apres paiement partiel: discount_vat, badcustomer, abandon
  79. //! Fermeture alors que aucun paiement: replaced (si remplace), abandon
  80. var $close_code;
  81. //! Commentaire si mis a paye sans paiement complet
  82. var $close_note;
  83. //! 1 if invoice paid COMPLETELY, 0 otherwise (do not use it anymore, use statut and close_code
  84. var $paye;
  85. //! id of source invoice if replacement invoice or credit note
  86. var $fk_facture_source;
  87. var $origin;
  88. var $origin_id;
  89. var $linked_objects = array();
  90. var $fk_project;
  91. var $date_lim_reglement;
  92. var $cond_reglement_code;
  93. var $mode_reglement_code;
  94. var $modelpdf;
  95. var $products = array(); // deprecated
  96. var $lines = array();
  97. var $line;
  98. var $extraparams = array();
  99. //! Pour board
  100. var $nbtodo;
  101. var $nbtodolate;
  102. var $specimen;
  103. var $fac_rec;
  104. /**
  105. * Constructor
  106. *
  107. * @param DoliDB $db Database handler
  108. */
  109. function __construct($db = '') {
  110. parent::__construct($db);
  111. $this->no_save[] = 'thirdparty';
  112. $this->no_save[] = 'line';
  113. $this->fk_extrafields = new ExtraFields($db);
  114. $this->fk_extrafields->fetch(get_class($this));
  115. $this->remise = 0;
  116. $this->remise_percent = 0;
  117. $this->products = array();
  118. }
  119. /**
  120. * Create invoice in database
  121. * Note: this->ref can be set or empty. If empty, we will use "(PROV)"
  122. *
  123. * @param User $user Object user that create
  124. * @param int $notrigger 1=Does not execute triggers, 0 otherwise
  125. * @param int $forceduedate 1=Do not recalculate due date from payment condition but force it with value
  126. * @return int <0 if KO, >0 if OK
  127. */
  128. function create($user, $notrigger = 0, $forceduedate = 0) {
  129. global $langs, $conf, $mysoc, $user;
  130. $error = 0;
  131. // Clean parameters
  132. if (empty($this->type))
  133. $this->type = "INVOICE_STANDARD";
  134. $this->ref_client = trim($this->ref_client);
  135. $this->note = (isset($this->note) ? trim($this->note) : trim($this->note_private)); // deprecated
  136. $this->note_private = (isset($this->note_private) ? trim($this->note_private) : trim($this->note));
  137. $this->note_public = trim($this->note_public);
  138. $this->Status = "DRAFT";
  139. dol_syslog(get_class($this) . "::create user=" . $user->id);
  140. // Check parameters
  141. if (empty($this->date) || empty($user->id)) {
  142. $this->error = "ErrorBadParameter";
  143. dol_syslog(get_class($this) . "::create Try to create an invoice with an empty parameter (user, date, ...)", LOG_ERR);
  144. return -3;
  145. }
  146. $soc = new Societe($this->db);
  147. $result = $soc->fetch($this->socid);
  148. unset($this->socid);
  149. if ($result < 0) {
  150. $this->error = "Failed to fetch company";
  151. dol_syslog(get_class($this) . "::create " . $this->error, LOG_ERR);
  152. return -2;
  153. }
  154. $this->client = new stdClass();
  155. $this->client->id = $soc->id;
  156. $this->client->name = $soc->name;
  157. // author
  158. $this->author = new stdClass();
  159. $this->author->id = $user->id;
  160. $this->author->name = $user->login;
  161. $this->ref = $this->getNextNumRef($soc);
  162. $now = dol_now();
  163. $this->record();
  164. // Appel des triggers
  165. include_once DOL_DOCUMENT_ROOT . '/core/class/interfaces.class.php';
  166. $interface = new Interfaces($this->db);
  167. $result = $interface->run_triggers('BILL_CREATE', $this, $user, $langs, $conf);
  168. if ($result < 0) {
  169. $error++;
  170. $this->errors = $interface->errors;
  171. }
  172. // Fin appel triggers
  173. return $this->id;
  174. // Create invoice from a predefined invoice
  175. // if ($this->fac_rec > 0) {
  176. // require_once DOL_DOCUMENT_ROOT . '/compta/facture/class/facture-rec.class.php';
  177. // $_facrec = new FactureRec($this->db);
  178. // $result = $_facrec->fetch($this->fac_rec);
  179. //
  180. // $this->fk_project = $_facrec->fk_project;
  181. // $this->cond_reglement = $_facrec->cond_reglement_id;
  182. // $this->cond_reglement_id = $_facrec->cond_reglement_id;
  183. // $this->mode_reglement = $_facrec->mode_reglement_id;
  184. // $this->mode_reglement_id = $_facrec->mode_reglement_id;
  185. // $this->remise_absolue = $_facrec->remise_absolue;
  186. // $this->remise_percent = $_facrec->remise_percent;
  187. //
  188. // // Clean parametres
  189. // if (!$this->type)
  190. // $this->type = 0;
  191. // $this->ref_client = trim($this->ref_client);
  192. // $this->note = trim($this->note);
  193. // $this->note_public = trim($this->note_public);
  194. // //if (! $this->remise) $this->remise = 0;
  195. // if (!$this->mode_reglement_id)
  196. // $this->mode_reglement_id = 0;
  197. // $this->brouillon = 1;
  198. // }
  199. // Define due date if not already defined
  200. $datelim = (empty($forceduedate) ? $this->calculate_date_lim_reglement() : $forceduedate);
  201. // Insert into database
  202. $socid = $this->socid;
  203. $sql = "INSERT INTO " . MAIN_DB_PREFIX . "facture (";
  204. $sql.= " facnumber";
  205. $sql.= ", entity";
  206. $sql.= ", ref_ext";
  207. $sql.= ", type";
  208. $sql.= ", fk_soc";
  209. $sql.= ", datec";
  210. $sql.= ", remise_absolue";
  211. $sql.= ", remise_percent";
  212. $sql.= ", datef";
  213. $sql.= ", note";
  214. $sql.= ", note_public";
  215. $sql.= ", ref_client, ref_int";
  216. $sql.= ", fk_facture_source, fk_user_author, fk_projet";
  217. $sql.= ", fk_cond_reglement, fk_mode_reglement, date_lim_reglement, model_pdf";
  218. $sql.= ")";
  219. $sql.= " VALUES (";
  220. $sql.= "'(PROV)'";
  221. $sql.= ", " . $conf->entity;
  222. $sql.= ", " . ($this->ref_ext ? "'" . $this->db->escape($this->ref_ext) . "'" : "null");
  223. $sql.= ", '" . $this->type . "'";
  224. $sql.= ", '" . $socid . "'";
  225. $sql.= ", '" . $this->db->idate($now) . "'";
  226. $sql.= "," . ($this->remise_absolue > 0 ? $this->remise_absolue : 'NULL');
  227. $sql.= "," . ($this->remise_percent > 0 ? $this->remise_percent : 'NULL');
  228. $sql.= ", '" . $this->db->idate($this->date) . "'";
  229. $sql.= "," . ($this->note_private ? "'" . $this->db->escape($this->note_private) . "'" : "null");
  230. $sql.= "," . ($this->note_public ? "'" . $this->db->escape($this->note_public) . "'" : "null");
  231. $sql.= "," . ($this->ref_client ? "'" . $this->db->escape($this->ref_client) . "'" : "null");
  232. $sql.= "," . ($this->ref_int ? "'" . $this->db->escape($this->ref_int) . "'" : "null");
  233. $sql.= "," . ($this->fk_facture_source ? "'" . $this->db->escape($this->fk_facture_source) . "'" : "null");
  234. $sql.= "," . ($user->id > 0 ? "'" . $user->id . "'" : "null");
  235. $sql.= "," . ($this->fk_project ? $this->fk_project : "null");
  236. $sql.= ',' . $this->cond_reglement_id;
  237. $sql.= "," . $this->mode_reglement_id;
  238. $sql.= ", '" . $this->db->idate($datelim) . "', '" . $this->modelpdf . "')";
  239. dol_syslog(get_class($this) . "::create sql=" . $sql);
  240. $resql = $this->db->query($sql);
  241. if ($resql) {
  242. $this->id = $this->db->last_insert_id(MAIN_DB_PREFIX . 'facture');
  243. // Update ref with new one
  244. $this->ref = '(PROV' . $this->id . ')';
  245. $sql = 'UPDATE ' . MAIN_DB_PREFIX . "facture SET facnumber='" . $this->ref . "' WHERE rowid=" . $this->id;
  246. dol_syslog(get_class($this) . "::create sql=" . $sql);
  247. $resql = $this->db->query($sql);
  248. if (!$resql)
  249. $error++;
  250. // Add object linked
  251. if (!$error && $this->id && is_array($this->linked_objects) && !empty($this->linked_objects)) {
  252. foreach ($this->linked_objects as $origin => $origin_id) {
  253. $ret = $this->add_object_linked($origin, $origin_id);
  254. if (!$ret) {
  255. dol_print_error($this->db);
  256. $error++;
  257. }
  258. // TODO mutualiser
  259. if ($origin == 'commande') {
  260. // On recupere les differents contact interne et externe
  261. $order = new Commande($this->db);
  262. $order->id = $origin_id;
  263. // On recupere le commercial suivi propale
  264. $this->userid = $order->getIdcontact('internal', 'SALESREPFOLL');
  265. if ($this->userid) {
  266. //On passe le commercial suivi commande en commercial suivi paiement
  267. $this->add_contact($this->userid[0], 'SALESREPFOLL', 'internal');
  268. }
  269. // On recupere le contact client facturation commande
  270. $this->contactid = $order->getIdcontact('external', 'BILLING');
  271. if ($this->contactid) {
  272. //On passe le contact client facturation commande en contact client facturation
  273. $this->add_contact($this->contactid[0], 'BILLING', 'external');
  274. }
  275. }
  276. }
  277. }
  278. /*
  279. * Insert lines of invoices into database
  280. */
  281. if (count($this->lines) && is_object($this->lines[0])) {
  282. $fk_parent_line = 0;
  283. dol_syslog("There is " . count($this->lines) . " lines that are invoice lines objects");
  284. foreach ($this->lines as $i => $val) {
  285. $newinvoiceline = new FactureLigne($this->db);
  286. $newinvoiceline = $this->lines[$i];
  287. $newinvoiceline->fk_facture = $this->id;
  288. if ($result >= 0 && ($newinvoiceline->info_bits & 0x01) == 0) { // We keep only lines with first bit = 0
  289. // Reset fk_parent_line for no child products and special product
  290. if (($newinvoiceline->product_type != 9 && empty($newinvoiceline->fk_parent_line)) || $newinvoiceline->product_type == 9) {
  291. $fk_parent_line = 0;
  292. }
  293. $newinvoiceline->fk_parent_line = $fk_parent_line;
  294. $result = $newinvoiceline->insert();
  295. // Defined the new fk_parent_line
  296. if ($result > 0 && $newinvoiceline->product_type == 9) {
  297. $fk_parent_line = $result;
  298. }
  299. }
  300. if ($result < 0) {
  301. $this->error = $newinvoiceline->error;
  302. $error++;
  303. break;
  304. }
  305. }
  306. } else {
  307. $fk_parent_line = 0;
  308. dol_syslog("There is " . count($this->lines) . " lines that are array lines");
  309. foreach ($this->lines as $i => $val) {
  310. if (($this->lines[$i]->info_bits & 0x01) == 0) { // We keep only lines with first bit = 0
  311. // Reset fk_parent_line for no child products and special product
  312. if (($this->lines[$i]->product_type != 9 && empty($this->lines[$i]->fk_parent_line)) || $this->lines[$i]->product_type == 9) {
  313. $fk_parent_line = 0;
  314. }
  315. $result = $this->addline(
  316. $this->id, $this->lines[$i]->desc, $this->lines[$i]->subprice, $this->lines[$i]->qty, $this->lines[$i]->tva_tx, $this->lines[$i]->localtax1_tx, $this->lines[$i]->localtax2_tx, $this->lines[$i]->fk_product, $this->lines[$i]->remise_percent, $this->lines[$i]->date_start, $this->lines[$i]->date_end, $this->lines[$i]->fk_code_ventilation, $this->lines[$i]->info_bits, $this->lines[$i]->fk_remise_except, 'HT', 0, $this->lines[$i]->product_type, $this->lines[$i]->rang, $this->lines[$i]->special_code, '', 0, $fk_parent_line, $this->lines[$i]->fk_fournprice, $this->lines[$i]->pa_ht, $this->lines[$i]->label
  317. );
  318. if ($result < 0) {
  319. $this->error = $this->db->lasterror();
  320. dol_print_error($this->db);
  321. $this->db->rollback();
  322. return -1;
  323. }
  324. // Defined the new fk_parent_line
  325. if ($result > 0 && $this->lines[$i]->product_type == 9) {
  326. $fk_parent_line = $result;
  327. }
  328. }
  329. }
  330. }
  331. /*
  332. * Insert lines of predefined invoices
  333. */
  334. if (!$error && $this->fac_rec > 0) {
  335. foreach ($_facrec->lines as $i => $val) {
  336. if ($_facrec->lines[$i]->fk_product) {
  337. $prod = new Product($this->db);
  338. $res = $prod->fetch($_facrec->lines[$i]->fk_product);
  339. }
  340. $tva_tx = get_default_tva($mysoc, $soc, $prod->id);
  341. $localtax1_tx = get_localtax($tva_tx, 1, $soc);
  342. $localtax2_tx = get_localtax($tva_tx, 2, $soc);
  343. $result_insert = $this->addline(
  344. $this->id, $_facrec->lines[$i]->desc, $_facrec->lines[$i]->subprice, $_facrec->lines[$i]->qty, $tva_tx, $localtax1_tx, $localtax2_tx, $_facrec->lines[$i]->fk_product, $_facrec->lines[$i]->remise_percent, '', '', 0, 0, '', 'HT', 0, $_facrec->lines[$i]->product_type, $_facrec->lines[$i]->rang, $_facrec->lines[$i]->special_code, '', 0, 0, null, 0, $_facrec->lines[$i]->label
  345. );
  346. if ($result_insert < 0) {
  347. $error++;
  348. $this->error = $this->db->error();
  349. break;
  350. }
  351. }
  352. }
  353. if (!$error) {
  354. $result = $this->update_price(1);
  355. if ($result > 0) {
  356. // Appel des triggers
  357. include_once DOL_DOCUMENT_ROOT . '/core/class/interfaces.class.php';
  358. $interface = new Interfaces($this->db);
  359. $result = $interface->run_triggers('BILL_CREATE', $this, $user, $langs, $conf);
  360. if ($result < 0) {
  361. $error++;
  362. $this->errors = $interface->errors;
  363. }
  364. // Fin appel triggers
  365. if (!$error) {
  366. $this->db->commit();
  367. return $this->id;
  368. } else {
  369. $this->db->rollback();
  370. return -4;
  371. }
  372. } else {
  373. $this->error = $langs->trans('FailedToUpdatePrice');
  374. $this->db->rollback();
  375. return -3;
  376. }
  377. } else {
  378. dol_syslog(get_class($this) . "::create error " . $this->error, LOG_ERR);
  379. $this->db->rollback();
  380. return -2;
  381. }
  382. } else {
  383. $this->error = $this->db->error();
  384. dol_syslog(get_class($this) . "::create error " . $this->error . " sql=" . $sql, LOG_ERR);
  385. $this->db->rollback();
  386. return -1;
  387. }
  388. }
  389. /**
  390. * Create a new invoice in database from current invoice
  391. *
  392. * @param User $user Object user that ask creation
  393. * @param int $invertdetail Reverse sign of amounts for lines
  394. * @return int <0 if KO, >0 if OK
  395. */
  396. function createFromCurrent($user, $invertdetail = 0) {
  397. // Charge facture source
  398. $facture = new Facture($this->db);
  399. $facture->fk_facture_source = $this->fk_facture_source;
  400. $facture->type = $this->type;
  401. $facture->socid = $this->socid;
  402. $facture->date = $this->date;
  403. $facture->note_public = $this->note_public;
  404. $facture->note = $this->note;
  405. $facture->ref_client = $this->ref_client;
  406. $facture->modelpdf = $this->modelpdf;
  407. $facture->fk_project = $this->fk_project;
  408. $facture->cond_reglement_code = $this->cond_reglement_code;
  409. $facture->mode_reglement_code = $this->mode_reglement_code;
  410. $facture->remise_absolue = $this->remise_absolue;
  411. $facture->remise_percent = $this->remise_percent;
  412. $facture->Status = "NOT_PAID";
  413. $facture->ref = $this->getNextNumRef($this->socid);
  414. // $facture->lines = $this->lines; // Tableau des lignes de factures
  415. // $facture->products = $this->lines; // Tant que products encore utilise
  416. // // Loop on each line of new invoice
  417. // foreach ($facture->lines as $i => $line) {
  418. // if ($invertdetail) {
  419. // $facture->lines[$i]->subprice = -$facture->lines[$i]->subprice;
  420. // $facture->lines[$i]->total_ht = -$facture->lines[$i]->total_ht;
  421. // $facture->lines[$i]->total_tva = -$facture->lines[$i]->total_tva;
  422. // $facture->lines[$i]->total_localtax1 = -$facture->lines[$i]->total_localtax1;
  423. // $facture->lines[$i]->total_localtax2 = -$facture->lines[$i]->total_localtax2;
  424. // $facture->lines[$i]->total_ttc = -$facture->lines[$i]->total_ttc;
  425. // }
  426. // }
  427. dol_syslog(get_class($this) . "::createFromCurrent invertdetail=" . $invertdetail . " socid=" . $this->socid . " nboflines=" . count($facture->lines));
  428. $facid = $facture->create($user);
  429. // Copier les lignes de la facture
  430. $this->getLinesArray();
  431. foreach ($this->lines as $line) {
  432. unset($line->id);
  433. unset($line->_id);
  434. unset($line->_rev);
  435. $line->fk_facture = $facid;
  436. $line->record();
  437. }
  438. $facture->update_price();
  439. // if ($facid <= 0) {
  440. // $this->error = $facture->error;
  441. // $this->errors = $facture->errors;
  442. // }
  443. return $facid;
  444. }
  445. /**
  446. * Load an object from its id and create a new one in database
  447. *
  448. * @param int $socid Id of thirdparty
  449. * @return int New id of clone
  450. */
  451. function createFromClone($socid = 0) {
  452. global $conf, $user, $langs, $hookmanager;
  453. $error = 0;
  454. $this->db->begin();
  455. // Load source object
  456. $objFrom = dol_clone($this);
  457. // Change socid if needed
  458. if (!empty($socid) && $socid != $this->socid) {
  459. $objsoc = new Societe($this->db);
  460. if ($objsoc->fetch($socid) > 0) {
  461. $this->socid = $objsoc->id;
  462. $this->cond_reglement_id = (!empty($objsoc->cond_reglement_id) ? $objsoc->cond_reglement_id : 0);
  463. $this->mode_reglement_id = (!empty($objsoc->mode_reglement_id) ? $objsoc->mode_reglement_id : 0);
  464. $this->fk_project = '';
  465. $this->fk_delivery_address = '';
  466. }
  467. // TODO Change product price if multi-prices
  468. }
  469. $this->id = 0;
  470. $this->statut = 0;
  471. // Clear fields
  472. $this->user_author = $user->id;
  473. $this->user_valid = '';
  474. $this->fk_facture_source = 0;
  475. $this->date_creation = '';
  476. $this->date_validation = '';
  477. $this->ref_client = '';
  478. $this->close_code = '';
  479. $this->close_note = '';
  480. $this->products = $this->lines; // Tant que products encore utilise
  481. // Loop on each line of new invoice
  482. foreach ($this->lines as $i => $line) {
  483. if (($this->lines[$i]->info_bits & 0x02) == 0x02) { // We do not clone line of discounts
  484. unset($this->lines[$i]);
  485. unset($this->products[$i]); // Tant que products encore utilise
  486. }
  487. }
  488. // Create clone
  489. $result = $this->create($user);
  490. if ($result < 0)
  491. $error++;
  492. if (!$error) {
  493. // Hook of thirdparty module
  494. if (is_object($hookmanager)) {
  495. $parameters = array('objFrom' => $objFrom);
  496. $action = '';
  497. $reshook = $hookmanager->executeHooks('createFrom', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
  498. if ($reshook < 0)
  499. $error++;
  500. }
  501. // Appel des triggers
  502. include_once DOL_DOCUMENT_ROOT . '/core/class/interfaces.class.php';
  503. $interface = new Interfaces($this->db);
  504. $result = $interface->run_triggers('BILL_CLONE', $this, $user, $langs, $conf);
  505. if ($result < 0) {
  506. $error++;
  507. $this->errors = $interface->errors;
  508. }
  509. // Fin appel triggers
  510. }
  511. // End
  512. if (!$error) {
  513. $this->db->commit();
  514. return $this->id;
  515. } else {
  516. $this->db->rollback();
  517. return -1;
  518. }
  519. }
  520. /**
  521. * Load an object from an order and create a new invoice into database
  522. *
  523. * @param Object $object Object source
  524. * @return int <0 if KO, 0 if nothing done, 1 if OK
  525. */
  526. function createFromOrder($object) {
  527. global $conf, $user, $langs, $hookmanager;
  528. $error = 0;
  529. // Closed order
  530. $this->date = dol_now();
  531. $this->source = 0;
  532. $num = count($object->lines);
  533. for ($i = 0; $i < $num; $i++) {
  534. $line = new FactureLigne($this->db);
  535. $line->libelle = $object->lines[$i]->libelle;
  536. $line->label = $object->lines[$i]->label;
  537. $line->desc = $object->lines[$i]->desc;
  538. $line->subprice = $object->lines[$i]->subprice;
  539. $line->total_ht = $object->lines[$i]->total_ht;
  540. $line->total_tva = $object->lines[$i]->total_tva;
  541. $line->total_ttc = $object->lines[$i]->total_ttc;
  542. $line->tva_tx = $object->lines[$i]->tva_tx;
  543. $line->localtax1_tx = $object->lines[$i]->localtax1_tx;
  544. $line->localtax2_tx = $object->lines[$i]->localtax2_tx;
  545. $line->qty = $object->lines[$i]->qty;
  546. $line->fk_remise_except = $object->lines[$i]->fk_remise_except;
  547. $line->remise_percent = $object->lines[$i]->remise_percent;
  548. $line->fk_product = $object->lines[$i]->fk_product;
  549. $line->info_bits = $object->lines[$i]->info_bits;
  550. $line->product_type = $object->lines[$i]->product_type;
  551. $line->rang = $object->lines[$i]->rang;
  552. $line->special_code = $object->lines[$i]->special_code;
  553. $line->fk_parent_line = $object->lines[$i]->fk_parent_line;
  554. $this->lines[$i] = $line;
  555. }
  556. $this->socid = $object->socid;
  557. $this->fk_project = $object->fk_project;
  558. $this->cond_reglement_id = $object->cond_reglement_id;
  559. $this->mode_reglement_id = $object->mode_reglement_id;
  560. $this->availability_id = $object->availability_id;
  561. $this->demand_reason_id = $object->demand_reason_id;
  562. $this->date_livraison = $object->date_livraison;
  563. $this->fk_delivery_address = $object->fk_delivery_address;
  564. $this->contact_id = $object->contactid;
  565. $this->ref_client = $object->ref_client;
  566. $this->note = $object->note;
  567. $this->note_public = $object->note_public;
  568. $this->origin = $object->element;
  569. $this->origin_id = $object->id;
  570. // Possibility to add external linked objects with hooks
  571. $this->linked_objects[$this->origin] = $this->origin_id;
  572. if (!empty($object->other_linked_objects) && is_array($object->other_linked_objects)) {
  573. $this->linked_objects = array_merge($this->linked_objects, $object->other_linked_objects);
  574. }
  575. $ret = $this->create($user);
  576. if ($ret > 0) {
  577. // Actions hooked (by external module)
  578. $hookmanager->initHooks(array('invoicedao'));
  579. $parameters = array('objFrom' => $object);
  580. $action = '';
  581. $reshook = $hookmanager->executeHooks('createFrom', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
  582. if ($reshook < 0)
  583. $error++;
  584. if (!$error) {
  585. return 1;
  586. }
  587. else
  588. return -1;
  589. }
  590. else
  591. return -1;
  592. }
  593. /**
  594. * Return clicable link of object (with eventually picto)
  595. *
  596. * @param int $withpicto Add picto into link
  597. * @param string $option Where point the link
  598. * @param int $max Maxlength of ref
  599. * @param int $short 1=Return just URL
  600. * @param string $moretitle Add more text to title tooltip
  601. * @return string String with URL
  602. */
  603. function getNomUrl($withpicto = 0, $option = '', $max = 0, $short = 0, $moretitle = '') {
  604. global $langs;
  605. $result = '';
  606. if ($option == 'withdraw')
  607. $url = DOL_URL_ROOT . '/compta/facture/prelevement.php?facid=' . $this->id;
  608. else
  609. $url = DOL_URL_ROOT . '/facture/fiche.php?id=' . $this->id;
  610. if ($short)
  611. return $url;
  612. $picto = 'bill';
  613. if ($this->type == 1)
  614. $picto.='r'; // Replacement invoice
  615. if ($this->type == 2)
  616. $picto.='a'; // Credit note
  617. if ($this->type == 3)
  618. $picto.='d'; // Deposit invoice
  619. $label = $langs->trans("ShowInvoice") . ': ' . $this->ref;
  620. if ($this->type == 1)
  621. $label = $langs->transnoentitiesnoconv("ShowInvoiceReplace") . ': ' . $this->ref;
  622. if ($this->type == 2)
  623. $label = $langs->transnoentitiesnoconv("ShowInvoiceAvoir") . ': ' . $this->ref;
  624. if ($this->type == 3)
  625. $label = $langs->transnoentitiesnoconv("ShowInvoiceDeposit") . ': ' . $this->ref;
  626. if ($moretitle)
  627. $label.=' - ' . $moretitle;
  628. //$linkstart='<a href="'.$url.'" title="'.dol_escape_htmltag($label).'">';
  629. $linkstart = '<a href="' . $url . '">';
  630. $linkend = '</a>';
  631. if ($withpicto)
  632. $result.=($linkstart . img_object(($max ? dol_trunc($label, $max) : $label), $picto) . $linkend);
  633. if ($withpicto && $withpicto != 2)
  634. $result.=' ';
  635. if ($withpicto != 2)
  636. $result.=$linkstart . ($max ? dol_trunc($this->ref, $max) : $this->ref) . $linkend;
  637. return $result;
  638. }
  639. /**
  640. * Get object and lines from database
  641. *
  642. * @param int $rowid Id of object to load
  643. * @param string $ref Reference of invoice
  644. * @param string $ref_ext External reference of invoice
  645. * @param int $ref_int Internal reference of other object
  646. * @return int >0 if OK, <0 if KO, 0 if not found
  647. */
  648. function fetch($rowid, $ref = '', $ref_ext = '', $ref_int = '') {
  649. global $conf;
  650. return parent::fetch($rowid);
  651. }
  652. /**
  653. * Load all detailed lines into this->lines
  654. *
  655. * @return int 1 if OK, < 0 if KO
  656. */
  657. function fetch_lines() {
  658. $this->lines = array();
  659. $sql = 'SELECT l.rowid, l.fk_product, l.fk_parent_line, l.label as custom_label, l.description, l.product_type, l.price, l.qty, l.tva_tx, ';
  660. $sql.= ' l.localtax1_tx, l.localtax2_tx, l.remise, l.remise_percent, l.fk_remise_except, l.subprice,';
  661. $sql.= ' l.rang, l.special_code,';
  662. $sql.= ' l.date_start as date_start, l.date_end as date_end,';
  663. $sql.= ' l.info_bits, l.total_ht, l.total_tva, l.total_localtax1, l.total_localtax2, l.total_ttc, l.fk_code_ventilation, l.fk_export_compta, l.fk_product_fournisseur_price as fk_fournprice, l.buy_price_ht as pa_ht,';
  664. $sql.= ' p.ref as product_ref, p.fk_product_type as fk_product_type, p.label as product_label, p.description as product_desc';
  665. $sql.= ' FROM ' . MAIN_DB_PREFIX . 'facturedet as l';
  666. $sql.= ' LEFT JOIN ' . MAIN_DB_PREFIX . 'product as p ON l.fk_product = p.rowid';
  667. $sql.= ' WHERE l.fk_facture = ' . $this->id;
  668. $sql.= ' ORDER BY l.rang';
  669. dol_syslog(get_class($this) . '::fetch_lines sql=' . $sql, LOG_DEBUG);
  670. $result = $this->db->query($sql);
  671. if ($result) {
  672. $num = $this->db->num_rows($result);
  673. $i = 0;
  674. while ($i < $num) {
  675. $objp = $this->db->fetch_object($result);
  676. $line = new FactureLigne($this->db);
  677. $line->rowid = $objp->rowid;
  678. $line->label = $objp->custom_label;
  679. $line->desc = $objp->description; // Description line
  680. $line->product_type = $objp->product_type; // Type of line
  681. $line->product_ref = $objp->product_ref; // Ref product
  682. $line->libelle = $objp->product_label; // TODO deprecated
  683. $line->product_label = $objp->product_label; // Label product
  684. $line->product_desc = $objp->product_desc; // Description product
  685. $line->fk_product_type = $objp->fk_product_type; // Type of product
  686. $line->qty = $objp->qty;
  687. $line->subprice = $objp->subprice;
  688. $line->tva_tx = $objp->tva_tx;
  689. $line->localtax1_tx = $objp->localtax1_tx;
  690. $line->localtax2_tx = $objp->localtax2_tx;
  691. $line->remise_percent = $objp->remise_percent;
  692. $line->fk_remise_except = $objp->fk_remise_except;
  693. $line->fk_product = $objp->fk_product;
  694. $line->date_start = $this->db->jdate($objp->date_start);
  695. $line->date_end = $this->db->jdate($objp->date_end);
  696. $line->date_start = $this->db->jdate($objp->date_start);
  697. $line->date_end = $this->db->jdate($objp->date_end);
  698. $line->info_bits = $objp->info_bits;
  699. $line->total_ht = $objp->total_ht;
  700. $line->total_tva = $objp->total_tva;
  701. $line->total_localtax1 = $objp->total_localtax1;
  702. $line->total_localtax2 = $objp->total_localtax2;
  703. $line->total_ttc = $objp->total_ttc;
  704. $line->export_compta = $objp->fk_export_compta;
  705. $line->code_ventilation = $objp->fk_code_ventilation;
  706. $line->fk_fournprice = $objp->fk_fournprice;
  707. $marginInfos = getMarginInfos($objp->subprice, $objp->remise_percent, $objp->tva_tx, $objp->localtax1_tx, $objp->localtax2_tx, $line->fk_fournprice, $objp->pa_ht);
  708. $line->pa_ht = $marginInfos[0];
  709. $line->marge_tx = $marginInfos[1];
  710. $line->marque_tx = $marginInfos[2];
  711. $line->rang = $objp->rang;
  712. $line->special_code = $objp->special_code;
  713. $line->fk_parent_line = $objp->fk_parent_line;
  714. // Ne plus utiliser
  715. //$line->price = $objp->price;
  716. //$line->remise = $objp->remise;
  717. $this->lines[$i] = $line;
  718. $i++;
  719. }
  720. $this->db->free($result);
  721. return 1;
  722. } else {
  723. $this->error = $this->db->error();
  724. dol_syslog(get_class($this) . '::fetch_lines ' . $this->error, LOG_ERR);
  725. return -3;
  726. }
  727. }
  728. /**
  729. * Update database
  730. *
  731. * @param User $user User that modify
  732. * @param int $notrigger 0=launch triggers after, 1=disable triggers
  733. * @return int <0 if KO, >0 if OK
  734. */
  735. function update($user = 0, $notrigger = 0) {
  736. global $conf, $langs;
  737. $error = 0;
  738. // Clean parameters
  739. if (empty($this->type))
  740. $this->type = "INVOICE_STANDARD";
  741. if (isset($this->facnumber))
  742. $this->facnumber = trim($this->ref);
  743. if (isset($this->ref_client))
  744. $this->ref_client = trim($this->ref_client);
  745. if (isset($this->increment))
  746. $this->increment = trim($this->increment);
  747. if (isset($this->close_code))
  748. $this->close_code = trim($this->close_code);
  749. if (isset($this->close_note))
  750. $this->close_note = trim($this->close_note);
  751. if (isset($this->note) || isset($this->note_private))
  752. $this->note = (isset($this->note) ? trim($this->note) : trim($this->note_private)); // deprecated
  753. if (isset($this->note) || isset($this->note_private))
  754. $this->note_private = (isset($this->note_private) ? trim($this->note_private) : trim($this->note));
  755. if (isset($this->note_public))
  756. $this->note_public = trim($this->note_public);
  757. if (isset($this->modelpdf))
  758. $this->modelpdf = trim($this->modelpdf);
  759. if (isset($this->import_key))
  760. $this->import_key = trim($this->import_key);
  761. $soc = new Societe($db);
  762. $soc->fetch($this->socid);
  763. if ($this->client->id != $soc->id) {
  764. $this->client->id = $soc->id;
  765. $this->client->name = $soc->name;
  766. }
  767. $this->record();
  768. if (!$notrigger) {
  769. // Call triggers
  770. include_once DOL_DOCUMENT_ROOT . '/core/class/interfaces.class.php';
  771. $interface = new Interfaces($this->db);
  772. $result = $interface->run_triggers('BILL_MODIFY', $this, $user, $langs, $conf);
  773. if ($result < 0) {
  774. $error++;
  775. $this->errors = $interface->errors;
  776. }
  777. }
  778. // End call triggers
  779. return 1;
  780. // Check parameters
  781. // Put here code to add control on parameters values
  782. // Update request
  783. $sql = "UPDATE " . MAIN_DB_PREFIX . "facture SET";
  784. $sql.= " facnumber=" . (isset($this->ref) ? "'" . $this->db->escape($this->ref) . "'" : "null") . ",";
  785. $sql.= " type=" . (isset($this->type) ? $this->type : "null") . ",";
  786. $sql.= " ref_client=" . (isset($this->ref_client) ? "'" . $this->db->escape($this->ref_client) . "'" : "null") . ",";
  787. $sql.= " increment=" . (isset($this->increment) ? "'" . $this->db->escape($this->increment) . "'" : "null") . ",";
  788. $sql.= " fk_soc=" . (isset($this->socid) ? $this->socid : "null") . ",";
  789. $sql.= " datec=" . (strval($this->date_creation) != '' ? "'" . $this->db->idate($this->date_creation) . "'" : 'null') . ",";
  790. $sql.= " datef=" . (strval($this->date) != '' ? "'" . $this->db->idate($this->date) . "'" : 'null') . ",";
  791. $sql.= " date_valid=" . (strval($this->date_validation) != '' ? "'" . $this->db->idate($this->date_validation) . "'" : 'null') . ",";
  792. $sql.= " paye=" . (isset($this->paye) ? $this->paye : "null") . ",";
  793. $sql.= " remise_percent=" . (isset($this->remise_percent) ? $this->remise_percent : "null") . ",";
  794. $sql.= " remise_absolue=" . (isset($this->remise_absolue) ? $this->remise_absolue : "null") . ",";
  795. //$sql.= " remise=".(isset($this->remise)?$this->remise:"null").",";
  796. $sql.= " close_code=" . (isset($this->close_code) ? "'" . $this->db->escape($this->close_code) . "'" : "null") . ",";
  797. $sql.= " close_note=" . (isset($this->close_note) ? "'" . $this->db->escape($this->close_note) . "'" : "null") . ",";
  798. $sql.= " tva=" . (isset($this->total_tva) ? $this->total_tva : "null") . ",";
  799. $sql.= " localtax1=" . (isset($this->total_localtax1) ? $this->total_localtax1 : "null") . ",";
  800. $sql.= " localtax2=" . (isset($this->total_localtax2) ? $this->total_localtax2 : "null") . ",";
  801. $sql.= " total=" . (isset($this->total_ht) ? $this->total_ht : "null") . ",";
  802. $sql.= " total_ttc=" . (isset($this->total_ttc) ? $this->total_ttc : "null") . ",";
  803. $sql.= " fk_statut=" . (isset($this->statut) ? $this->statut : "null") . ",";
  804. $sql.= " fk_user_author=" . (isset($this->user_author) ? $this->user_author : "null") . ",";
  805. $sql.= " fk_user_valid=" . (isset($this->fk_user_valid) ? $this->fk_user_valid : "null") . ",";
  806. $sql.= " fk_facture_source=" . (isset($this->fk_facture_source) ? $this->fk_facture_source : "null") . ",";
  807. $sql.= " fk_projet=" . (isset($this->fk_project) ? $this->fk_project : "null") . ",";
  808. $sql.= " fk_cond_reglement=" . (isset($this->cond_reglement_id) ? $this->cond_reglement_id : "null") . ",";
  809. $sql.= " fk_mode_reglement=" . (isset($this->mode_reglement_id) ? $this->mode_reglement_id : "null") . ",";
  810. $sql.= " date_lim_reglement=" . (strval($this->date_lim_reglement) != '' ? "'" . $this->db->idate($this->date_lim_reglement) . "'" : 'null') . ",";
  811. $sql.= " note=" . (isset($this->note_private) ? "'" . $this->db->escape($this->note_private) . "'" : "null") . ",";
  812. $sql.= " note_public=" . (isset($this->note_public) ? "'" . $this->db->escape($this->note_public) . "'" : "null") . ",";
  813. $sql.= " model_pdf=" . (isset($this->modelpdf) ? "'" . $this->db->escape($this->modelpdf) . "'" : "null") . ",";
  814. $sql.= " import_key=" . (isset($this->import_key) ? "'" . $this->db->escape($this->import_key) . "'" : "null") . "";
  815. $sql.= " WHERE rowid=" . $this->id;
  816. $this->db->begin();
  817. dol_syslog(get_class($this) . "::update sql=" . $sql, LOG_DEBUG);
  818. $resql = $this->db->query($sql);
  819. if (!$resql) {
  820. $error++;
  821. $this->errors[] = "Error " . $this->db->lasterror();
  822. }
  823. if (!$error) {
  824. if (!$notrigger) {
  825. // Call triggers
  826. include_once DOL_DOCUMENT_ROOT . '/core/class/interfaces.class.php';
  827. $interface = new Interfaces($this->db);
  828. $result = $interface->run_triggers('BILL_MODIFY', $this, $user, $langs, $conf);
  829. if ($result < 0) {
  830. $error++;
  831. $this->errors = $interface->errors;
  832. }
  833. // End call triggers
  834. }
  835. }
  836. // Commit or rollback
  837. if ($error) {
  838. foreach ($this->errors as $errmsg) {
  839. dol_syslog(get_class($this) . "::update " . $errmsg, LOG_ERR);
  840. $this->error.=($this->error ? ', ' . $errmsg : $errmsg);
  841. }
  842. $this->db->rollback();
  843. return -1 * $error;
  844. } else {
  845. $this->db->commit();
  846. return 1;
  847. }
  848. }
  849. /**
  850. * Add a discount line into invoice using an existing absolute discount
  851. *
  852. * @param int $idremise Id of absolute discount
  853. * @return int >0 if OK, <0 if KO
  854. */
  855. function insert_discount($idremise) {
  856. global $langs;
  857. include_once DOL_DOCUMENT_ROOT . '/core/lib/price.lib.php';
  858. include_once DOL_DOCUMENT_ROOT . '/core/class/discount.class.php';
  859. $remise = new DiscountAbsolute($this->db);
  860. $result = $remise->fetch($idremise);
  861. if (!empty($result)) {
  862. if ($remise->fk_facture) { // Protection against multiple submission
  863. $this->error = $langs->trans("ErrorDiscountAlreadyUsed");
  864. return -5;
  865. }
  866. $facligne = new FactureLigne($this->db);
  867. $facligne->fk_facture = $this->id;
  868. $facligne->fk_remise_except = $remise->id;
  869. $facligne->description = $remise->description; // Description ligne
  870. $facligne->tva_tx = $remise->tva_tx;
  871. $facligne->subprice = -$remise->amount_ht;
  872. $facligne->fk_product = 0; // Id produit predefini
  873. $facligne->qty = 1;
  874. $facligne->remise_percent = 0;
  875. $facligne->rang = -1;
  876. $facligne->info_bits = 2;
  877. $facligne->total_ht = -$remise->amount_ht;
  878. $facligne->total_tva = -$remise->amount_tva;
  879. $facligne->total_ttc = -$remise->amount_ttc;
  880. $lineid = $facligne->insert();
  881. if (!empty($lineid)) {
  882. $result = $this->update_price();
  883. if ($result > 0) {
  884. // Create linke between discount and invoice line
  885. $result = $remise->link_to_invoice($lineid, 0);
  886. if ($result < 0) {
  887. $this->error = $remise->error;
  888. return -4;
  889. }
  890. return 1;
  891. } else {
  892. $this->error = $facligne->error;
  893. return -1;
  894. }
  895. } else {
  896. $this->error = $facligne->error;
  897. return -2;
  898. }
  899. } else {
  900. return -3;
  901. }
  902. }
  903. /**
  904. * Set customer ref
  905. *
  906. * @param string $ref_client Customer ref
  907. * @return int <0 if KO, >0 if OK
  908. */
  909. function set_ref_client($ref_client) {
  910. $sql = 'UPDATE ' . MAIN_DB_PREFIX . 'facture';
  911. if (empty($ref_client))
  912. $sql .= ' SET ref_client = NULL';
  913. else
  914. $sql .= ' SET ref_client = \'' . $this->db->escape($ref_client) . '\'';
  915. $sql .= ' WHERE rowid = ' . $this->id;
  916. if ($this->db->query($sql)) {
  917. $this->ref_client = $ref_client;
  918. return 1;
  919. } else {
  920. dol_print_error($this->db);
  921. return -1;
  922. }
  923. }
  924. /**
  925. * Delete invoice
  926. *
  927. * @param int $rowid Id of invoice to delete. If empty, we delete current instance of invoice
  928. * @param int $notrigger 1=Does not execute triggers, 0= execute triggers
  929. * @return int <0 if KO, >0 if OK
  930. */
  931. function delete($rowid = 0, $notrigger = 0) {
  932. global $user, $langs, $conf;
  933. require_once DOL_DOCUMENT_ROOT . '/core/lib/files.lib.php';
  934. if (!$rowid)
  935. $rowid = $this->id;
  936. dol_syslog(get_class($this) . "::delete rowid=" . $rowid, LOG_DEBUG);
  937. // TODO Test if there is at least on payment. If yes, refuse to delete.
  938. $error = 0;
  939. // Supprimer les lignes de la facture
  940. $this->getLinesArray();
  941. foreach ($this->lines as $line) {
  942. $line->delete();
  943. }
  944. $this->deleteDoc();
  945. if (!$error && !$notrigger) {
  946. // Appel des triggers
  947. include_once DOL_DOCUMENT_ROOT . '/core/class/interfaces.class.php';
  948. $interface = new Interfaces($this->db);
  949. $result = $interface->run_triggers('BILL_DELETE', $this, $user, $langs, $conf);
  950. if ($result < 0) {
  951. $error++;
  952. $this->errors = $interface->errors;
  953. }
  954. // Fin appel triggers
  955. }
  956. return 1;
  957. if (!$error) {
  958. // Delete linked object
  959. $res = $this->deleteObjectLinked();
  960. if ($res < 0)
  961. $error++;
  962. }
  963. if (!$error) {
  964. // If invoice was converted into a discount not yet consumed, we remove discount
  965. $sql = 'DELETE FROM ' . MAIN_DB_PREFIX . 'societe_remise_except';
  966. $sql.= ' WHERE fk_facture_source = ' . $rowid;
  967. $sql.= ' AND fk_facture_line IS NULL';
  968. $resql = $this->db->query($sql);
  969. // If invoice has consumned discounts
  970. $this->fetch_lines();
  971. $list_rowid_det = array();
  972. foreach ($this->lines as $key => $invoiceline) {
  973. $list_rowid_det[] = $invoiceline->rowid;
  974. }
  975. // Consumned discounts are freed
  976. if (count($list_rowid_det)) {
  977. $sql = 'UPDATE ' . MAIN_DB_PREFIX . 'societe_remise_except';
  978. $sql.= ' SET fk_facture = NULL, fk_facture_line = NULL';
  979. $sql.= ' WHERE fk_facture_line IN (' . join(',', $list_rowid_det) . ')';
  980. dol_syslog(get_class($this) . "::delete sql=" . $sql);
  981. if (!$this->db->query($sql)) {
  982. $this->error = $this->db->error() . " sql=" . $sql;
  983. dol_syslog(get_class($this) . "::delete " . $this->error, LOG_ERR);
  984. $this->db->rollback();
  985. return -5;
  986. }
  987. }
  988. // Delete invoice line
  989. $sql = 'DELETE FROM ' . MAIN_DB_PREFIX . 'facturedet WHERE fk_facture = ' . $rowid;
  990. if ($this->db->query($sql) && $this->delete_linked_contact()) {
  991. $sql = 'DELETE FROM ' . MAIN_DB_PREFIX . 'facture WHERE rowid = ' . $rowid;
  992. $resql = $this->db->query($sql);
  993. if ($resql) {
  994. // On efface le repertoire de pdf provisoire
  995. $ref = dol_sanitizeFileName($this->ref);
  996. if ($conf->facture->dir_output) {
  997. $dir = $conf->facture->dir_output . "/" . $ref;
  998. $file = $conf->facture->dir_output . "/" . $ref . "/" . $ref . ".pdf";
  999. if (file_exists($file)) { // We must delete all files before deleting directory
  1000. $ret = dol_delete_preview($this);
  1001. if (!dol_delete_file($file, 0, 0, 0, $this)) { // For triggers
  1002. $this->error = $langs->trans("ErrorCanNotDeleteFile", $file);
  1003. $this->db->rollback();
  1004. return 0;
  1005. }
  1006. }
  1007. if (file_exists($dir)) {
  1008. if (!dol_delete_dir_recursive($dir)) { // For remove dir and meta
  1009. $this->error = $langs->trans("ErrorCanNotDeleteDir", $dir);
  1010. $this->db->rollback();
  1011. return 0;
  1012. }
  1013. }
  1014. }
  1015. $this->db->commit();
  1016. return 1;
  1017. } else {
  1018. $this->error = $this->db->lasterror() . " sql=" . $sql;
  1019. dol_syslog(get_class($this) . "::delete " . $this->error, LOG_ERR);
  1020. $this->db->rollback();
  1021. return -6;
  1022. }
  1023. } else {
  1024. $this->error = $this->db->lasterror() . " sql=" . $sql;
  1025. dol_syslog(get_class($this) . "::delete " . $this->error, LOG_ERR);
  1026. $this->db->rollback();
  1027. return -4;
  1028. }
  1029. } else {
  1030. $this->error = $this->db->lasterror();
  1031. dol_syslog(get_class($this) . "::delete " . $this->error, LOG_ERR);
  1032. $this->db->rollback();
  1033. return -2;
  1034. }
  1035. }
  1036. /**
  1037. * Renvoi une date limite de reglement de facture en fonction des
  1038. * conditions de reglements de la facture et date de facturation
  1039. *
  1040. * @param string $cond_reglement Condition of payment (code or id) to use. If 0, we use current condition.
  1041. * @return date Date limite de reglement si ok, <0 si ko
  1042. */
  1043. function calculate_date_lim_reglement($cond_reglement = 0) {
  1044. if (empty($cond_reglement))
  1045. $cond_reglement = $this->cond_reglement_code;
  1046. $data = $this->fk_extrafields->fields->cond_reglement_code->values->{$cond_reglement};
  1047. $cdr_nbjour = $data->nbjour;
  1048. $cdr_fdm = $data->fdm;
  1049. $cdr_decalage = $data->decalage;
  1050. /* Definition de la date limite */
  1051. // 1 : ajout du nombre de jours
  1052. $datelim = $this->date + ($cdr_nbjour * 3600 * 24);
  1053. // 2 : application de la regle "fin de mois"
  1054. if ($cdr_fdm) {
  1055. $mois = date('m', $datelim);
  1056. $annee = date('Y', $datelim);
  1057. if ($mois == 12) {
  1058. $mois = 1;
  1059. $annee += 1;
  1060. } else {
  1061. $mois += 1;
  1062. }
  1063. // On se deplace au debut du mois suivant, et on retire un jour
  1064. $datelim = dol_mktime(12, 0, 0, $mois, 1, $annee);
  1065. $datelim -= (3600 * 24);
  1066. }
  1067. // 3 : application du decalage
  1068. $datelim += ($cdr_decalage * 3600 * 24);
  1069. return $datelim;
  1070. }
  1071. /**
  1072. * Tag la facture comme paye completement (close_code non renseigne) ou partiellement (close_code renseigne) + appel trigger BILL_PAYED
  1073. *
  1074. * @param User $user Objet utilisateur qui modifie
  1075. * @param string $close_code Code renseigne si on classe a payee completement alors que paiement incomplet (cas escompte par exemple)
  1076. * @param string $close_note Commentaire renseigne si on classe a payee alors que paiement incomplet (cas escompte par exemple)
  1077. * @return int <0 if KO, >0 if OK
  1078. */
  1079. function set_paid($user, $close_code = '', $close_note = '') {
  1080. global $conf, $langs;
  1081. $error = 0;
  1082. if ($close_code)
  1083. $this->Status = "PAID_PARTIALLY";
  1084. else $this->Status = "PAID";
  1085. $this->record();
  1086. // Appel des triggers
  1087. include_once DOL_DOCUMENT_ROOT . '/core/class/interfaces.class.php';
  1088. $interface = new Interfaces($this->db);
  1089. $result = $interface->run_triggers('BILL_PAYED', $this, $user, $langs, $conf);
  1090. if ($result < 0) {
  1091. $error++;
  1092. $this->errors = $interface->errors;
  1093. }
  1094. // Fin appel triggers
  1095. return 1;
  1096. if ($this->paye != 1) {
  1097. $this->db->begin();
  1098. dol_syslog(get_class($this) . "::set_paid rowid=" . $this->id, LOG_DEBUG);
  1099. $sql = 'UPDATE ' . MAIN_DB_PREFIX . 'facture SET';
  1100. $sql.= ' fk_statut=2';
  1101. if (!$close_code)
  1102. $sql.= ', paye=1';
  1103. if ($close_code)
  1104. $sql.= ", close_code='" . $this->db->escape($close_code) . "'";
  1105. if ($close_note)
  1106. $sql.= ", close_note='" . $this->db->escape($close_note) . "'";
  1107. $sql.= ' WHERE rowid = ' . $this->id;
  1108. $resql = $this->db->query($sql);
  1109. if ($resql) {
  1110. // Appel des triggers
  1111. include_once DOL_DOCUMENT_ROOT . '/core/class/interfaces.class.php';
  1112. $interface = new Interfaces($this->db);
  1113. $result = $interface->run_triggers('BILL_PAYED', $this, $user, $langs, $conf);
  1114. if ($result < 0) {
  1115. $error++;
  1116. $this->errors = $interface->errors;
  1117. }
  1118. // Fin appel triggers
  1119. } else {
  1120. $error++;
  1121. $this->error = $this->db->error();
  1122. dol_print_error($this->db);
  1123. }
  1124. if (!$error) {
  1125. $this->db->commit();
  1126. return 1;
  1127. } else {
  1128. $this->db->rollback();
  1129. return -1;
  1130. }
  1131. } else {
  1132. return 0;
  1133. }
  1134. }
  1135. /**
  1136. * Tag la facture comme non payee completement + appel trigger BILL_UNPAYED
  1137. * Fonction utilisee quand un paiement prelevement est refuse,
  1138. * ou quand une facture annulee et reouverte.
  1139. *
  1140. * @param User $user Object user that change status
  1141. * @return int <0 if KO, >0 if OK
  1142. */
  1143. function set_unpaid($user) {
  1144. global $conf, $langs;
  1145. $error = 0;
  1146. if ($this->getSommePaiement() > 0)
  1147. $this->Status = "STARTED";
  1148. else
  1149. $this->Status = "NOT_PAID";
  1150. $this->record();
  1151. // Appel des triggers
  1152. include_once DOL_DOCUMENT_ROOT . '/core/class/interfaces.class.php';
  1153. $interface = new Interfaces($this->db);
  1154. $result = $interface->run_triggers('BILL_UNPAYED', $this, $user, $langs, $conf);
  1155. if ($result < 0) {
  1156. $error++;
  1157. $this->errors = $interface->errors;
  1158. }
  1159. // Fin appel triggers
  1160. return 1;
  1161. $this->db->begin();
  1162. $sql = 'UPDATE ' . MAIN_DB_PREFIX . 'facture';
  1163. $sql.= ' SET paye=0, fk_statut=1, close_code=null, close_note=null';
  1164. $sql.= ' WHERE rowid = ' . $this->id;
  1165. dol_syslog(get_class($this) . "::set_unpaid sql=" . $sql);
  1166. $resql = $this->db->query($sql);
  1167. if ($resql) {
  1168. // Appel des triggers
  1169. include_once DOL_DOCUMENT_ROOT . '/core/class/interfaces.class.php';
  1170. $interface = new Interfaces($this->db);
  1171. $result = $interface->run_triggers('BILL_UNPAYED', $this, $user, $langs, $conf);
  1172. if ($result < 0) {
  1173. $error++;
  1174. $this->errors = $interface->errors;
  1175. }
  1176. // Fin appel triggers
  1177. } else {
  1178. $error++;
  1179. $this->error = $this->db->error();
  1180. dol_print_error($this->db);
  1181. }
  1182. if (!$error) {
  1183. $this->db->commit();
  1184. return 1;
  1185. } else {
  1186. $this->db->rollback();
  1187. return -1;
  1188. }
  1189. }
  1190. /**
  1191. * Tag invoice as canceled, with no payment on it (example for replacement invoice or payment never received) + call trigger BILL_CANCEL
  1192. * Warning, if option to decrease stock on invoice was set, this function does not change stock (it might be a cancel because
  1193. * of no payment even if merchandises were sent).
  1194. *
  1195. * @param User $user Object user making change
  1196. * @param string $close_code Code de fermeture
  1197. * @param string $close_note Comment
  1198. * @return int <0 if KO, >0 if OK
  1199. */
  1200. function set_canceled($user, $close_code = '', $close_note = '') {
  1201. global $conf, $langs;
  1202. $error = 0;
  1203. $this->Status = "CANCELED";
  1204. $this->record();
  1205. // Appel des triggers
  1206. include_once DOL_DOCUMENT_ROOT . '/core/class/interfaces.class.php';
  1207. $interface = new Interfaces($this->db);
  1208. $result = $interface->run_triggers('BILL_CANCEL', $this, $user, $langs, $conf);
  1209. if ($result < 0) {
  1210. $error++;
  1211. $this->errors = $interface->errors;
  1212. }
  1213. // Fin appel triggers
  1214. return 1;
  1215. dol_syslog(get_class($this) . "::set_canceled rowid=" . $this->id, LOG_DEBUG);
  1216. $this->db->begin();
  1217. $sql = 'UPDATE ' . MAIN_DB_PREFIX . 'facture SET';
  1218. $sql.= ' fk_statut=3';
  1219. if ($close_code)
  1220. $sql.= ", close_code='" . $this->db->escape($close_code) . "'";
  1221. if ($close_note)
  1222. $sql.= ", close_note='" . $this->db->escape($close_note) . "'";
  1223. $sql.= ' WHERE rowid = ' . $this->id;
  1224. $resql = $this->db->query($sql);
  1225. if ($resql) {
  1226. // On desaffecte de la facture les remises liees
  1227. // car elles n'ont pas ete utilisees vu que la facture est abandonnee.
  1228. $sql = 'UPDATE ' . MAIN_DB_PREFIX . 'societe_remise_except';
  1229. $sql.= ' SET fk_facture = NULL';
  1230. $sql.= ' WHERE fk_facture = ' . $this->id;
  1231. $resql = $this->db->query($sql);
  1232. if ($resql) {
  1233. // Appel des triggers
  1234. include_once DOL_DOCUMENT_ROOT . '/core/class/interfaces.class.php';
  1235. $interface = new Interfaces($this->db);
  1236. $result = $interface->run_triggers('BILL_CANCEL', $this, $user, $langs, $conf);
  1237. if ($result < 0) {
  1238. $error++;
  1239. $this->errors = $interface->errors;
  1240. }
  1241. // Fin appel triggers
  1242. $this->db->commit();
  1243. return 1;
  1244. } else {
  1245. $this->error = $this->db->error() . " sql=" . $sql;
  1246. $this->db->rollback();
  1247. return -1;
  1248. }
  1249. } else {
  1250. $this->error = $this->db->error() . " sql=" . $sql;
  1251. $this->db->rollback();
  1252. return -2;
  1253. }
  1254. }
  1255. /**
  1256. * Tag invoice as validated + call trigger BILL_VALIDATE
  1257. * Object must have lines loaded with fetch_lines
  1258. *
  1259. * @param User $user Object user that validate
  1260. * @param string $force_number Reference to force on invoice
  1261. * @param int $idwarehouse Id of warehouse to use for stock decrease
  1262. * @return int <0 if KO, >0 if OK
  1263. */
  1264. function validate($user, $force_number = '', $idwarehouse = 0) {
  1265. global $conf, $langs;
  1266. require_once DOL_DOCUMENT_ROOT . '/core/lib/files.lib.php';
  1267. $now = dol_now();
  1268. $error = 0;
  1269. dol_syslog(get_class($this) . '::validate user=' . $user->id . ', force_number=' . $force_number . ', idwarehouse=' . $idwarehouse, LOG_WARNING);
  1270. // Check parameters
  1271. if ($this->Status != "DRAFT") {
  1272. dol_syslog(get_class($this) . "::validate no draft status", LOG_WARNING);
  1273. return 0;
  1274. }
  1275. if (!$user->rights->facture->valider) {
  1276. $this->error = 'Permission denied';
  1277. dol_syslog(get_class($this) . "::validate " . $this->error, LOG_ERR);
  1278. return -1;
  1279. }
  1280. $this->Status = "NOT_PAID";
  1281. $this->date_validation = $now;
  1282. $this->record();
  1283. // Si facture de remplacement, abandonner la facture remplacée
  1284. if ($this->type == "INVOICE_REPLACEMENT") {
  1285. $replacedInvoice = new Facture($this->db);
  1286. $replacedInvoice->fetch($this->fk_facture_source);
  1287. $replacedInvoice->Status = "CANCELED";
  1288. $replacedInvoice->record();
  1289. }
  1290. // Appel des triggers
  1291. include_once DOL_DOCUMENT_ROOT . '/core/class/interfaces.class.php';
  1292. $interface = new Interfaces($this->db);
  1293. $result = $interface->run_triggers('BILL_VALIDATE', $this, $user, $langs, $conf);
  1294. if ($result < 0) {
  1295. $error++;
  1296. $this->errors = $interface->errors;
  1297. }
  1298. // Fin appel triggers
  1299. return 1;
  1300. $this->fetch_thirdparty();
  1301. $this->fetch_lines();
  1302. // Check parameters
  1303. if ($this->type == 1) { // si facture de remplacement
  1304. // Controle que facture source connue
  1305. if ($this->fk_facture_source <= 0) {
  1306. $this->error = $langs->trans("ErrorFieldRequired", $langs->trans("InvoiceReplacement"));
  1307. $this->db->rollback();
  1308. return -10;
  1309. }
  1310. // Charge la facture source a remplacer
  1311. $facreplaced = new Facture($this->db);
  1312. $result = $facreplaced->fetch($this->fk_facture_source);
  1313. if ($result <= 0) {
  1314. $this->error = $langs->trans("ErrorBadInvoice");
  1315. $this->db->rollback();
  1316. return -11;
  1317. }
  1318. // Controle que facture source non deja remplacee par une autre
  1319. $idreplacement = $facreplaced->getIdReplacingInvoice('validated');
  1320. if ($idreplacement && $idreplacement != $this->id) {
  1321. $facreplacement = new Facture($this->db);
  1322. $facreplacement->fetch($idreplacement);
  1323. $this->error = $langs->trans("ErrorInvoiceAlreadyReplaced", $facreplaced->ref, $facreplacement->ref);
  1324. $this->db->rollback();
  1325. return -12;
  1326. }
  1327. $result = $facreplaced->set_canceled($user, 'replaced', '');
  1328. if ($result < 0) {
  1329. $this->error = $facreplaced->error;
  1330. $this->db->rollback();
  1331. return -13;
  1332. }
  1333. }
  1334. // Define new ref
  1335. if ($force_number) {
  1336. $num = $force_number;
  1337. } else if (preg_match('/^[\(]?PROV/i', $this->ref)) {
  1338. if (!empty($conf->global->FAC_FORCE_DATE_VALIDATION)) { // If option enabled, we force invoice date
  1339. $this->date = dol_now();
  1340. $this->date_lim_reglement = $this->calculate_date_lim_reglement();
  1341. }
  1342. $num = $this->getNextNumRef($this->client);
  1343. } else {
  1344. $num = $this->ref;
  1345. }
  1346. if ($num) {
  1347. $this->update_price(1);
  1348. // Validate
  1349. $sql = 'UPDATE ' . MAIN_DB_PREFIX . 'facture';
  1350. $sql.= " SET facnumber='" . $num . "', fk_statut = 1, fk_user_valid = " . $user->id . ", date_valid = '" . $this->db->idate($now) . "'";
  1351. if (!empty($conf->global->FAC_FORCE_DATE_VALIDATION)) { // If option enabled, we force invoice date
  1352. $sql.= ', datef=' . $this->db->idate($this->date);
  1353. $sql.= ', date_lim_reglement=' . $this->db->idate($this->date_lim_reglement);
  1354. }
  1355. $sql.= ' WHERE rowid = ' . $this->id;
  1356. dol_syslog(get_class($this) . "::validate sql=" . $sql);
  1357. $resql = $this->db->query($sql);
  1358. if (!$resql) {
  1359. dol_syslog(get_class($this) . "::validate Echec update - 10 - sql=" . $sql, LOG_ERR);
  1360. dol_print_error($this->db);
  1361. $error++;
  1362. }
  1363. // On verifie si la facture etait une provisoire
  1364. if (!$error && (preg_match('/^[\(]?PROV/i', $this->ref))) {
  1365. // La verif qu'une remise n'est pas utilisee 2 fois est faite au moment de l'insertion de ligne
  1366. }
  1367. if (!$error) {
  1368. // Define third party as a customer
  1369. $result = $this->client->set_as_client();
  1370. // Si active on decremente le produit principal et ses composants a la validation de facture
  1371. if ($this->type != 3 && $result >= 0 && !empty($conf->stock->enabled) && !empty($conf->global->STOCK_CALCULATE_ON_BILL)) {
  1372. require_once DOL_DOCUMENT_ROOT . '/product/stock/class/mouvementstock.class.php';
  1373. $langs->load("agenda");
  1374. // Loop on each line
  1375. $cpt = count($this->lines);
  1376. for ($i = 0; $i < $cpt; $i++) {
  1377. if ($this->lines[$i]->fk_product > 0) {
  1378. $mouvP = new MouvementStock($this->db);
  1379. // We decrease stock for product
  1380. if ($this->type == 2)
  1381. $result = $mouvP->reception($user, $this->lines[$i]->fk_product, $idwarehouse, $this->lines[$i]->qty, $this->lines[$i]->subprice, $langs->trans("InvoiceValidatedInSpeedealing", $num));
  1382. else
  1383. $result = $mouvP->livraison($user, $this->lines[$i]->fk_product, $idwarehouse, $this->lines[$i]->qty, $this->lines[$i]->subprice, $langs->trans("InvoiceValidatedInSpeedealing", $num));
  1384. if ($result < 0) {
  1385. $error++;
  1386. }
  1387. }
  1388. }
  1389. }
  1390. }
  1391. if (!$error) {
  1392. $this->oldref = '';
  1393. // Rename directory if dir was a temporary ref
  1394. if (preg_match('/^[\(]?PROV/i', $this->ref)) {
  1395. // On renomme repertoire facture ($this->ref = ancienne ref, $num = nouvelle ref)
  1396. // afin de ne pas perdre les fichiers attaches
  1397. $facref = dol_sanitizeFileName($this->ref);
  1398. $snumfa = dol_sanitizeFileName($num);
  1399. $dirsource = $conf->facture->dir_output . '/' . $facref;
  1400. $dirdest = $conf->facture->dir_output . '/' . $snumfa;
  1401. if (file_exists($dirsource)) {
  1402. dol_syslog(get_class($this) . "::validate rename dir " . $dirsource . " into " . $dirdest);
  1403. if (@rename($dirsource, $dirdest)) {
  1404. $this->oldref = $facref;
  1405. dol_syslog("Rename ok");
  1406. // Suppression ancien fichier PDF dans nouveau rep
  1407. dol_delete_file($conf->facture->dir_output . '/' . $snumfa . '/' . $facref . '*.*');
  1408. }
  1409. }
  1410. }
  1411. }
  1412. // Set new ref and define current statut
  1413. if (!$error) {
  1414. $this->ref = $num;
  1415. $this->facnumber = $num;
  1416. $this->statut = 1;
  1417. $this->brouillon = 0;
  1418. $this->date_validation = $now;
  1419. }
  1420. // Trigger calls
  1421. if (!$error) {
  1422. // Appel des triggers
  1423. include_once DOL_DOCUMENT_ROOT . '/core/class/interfaces.class.php';
  1424. $interface = new Interfaces($this->db);
  1425. $result = $interface->run_triggers('BILL_VALIDATE', $this, $user, $langs, $conf);
  1426. if ($result < 0) {
  1427. $error++;
  1428. $this->errors = $interface->errors;
  1429. }
  1430. // Fin appel triggers
  1431. }
  1432. } else {
  1433. $error++;
  1434. }
  1435. if (!$error) {
  1436. $this->db->commit();
  1437. return 1;
  1438. } else {
  1439. $this->db->rollback();
  1440. $this->error = $this->db->lasterror();
  1441. return -1;
  1442. }
  1443. }
  1444. /**
  1445. * Set draft status
  1446. *
  1447. * @param User $user Object user that modify
  1448. * @param int $idwarehouse Id warehouse to use for stock change.
  1449. * @return int <0 if KO, >0 if OK
  1450. */
  1451. function set_draft($user, $idwarehouse = -1) {
  1452. global $conf, $langs;
  1453. $error = 0;
  1454. $this->Status = "DRAFT";
  1455. $this->record();
  1456. return 1;
  1457. if ($this->statut == 0) {
  1458. dol_syslog(get_class($this) . "::set_draft already draft status", LOG_WARNING);
  1459. return 0;
  1460. }
  1461. $this->db->begin();
  1462. $sql = "UPDATE " . MAIN_DB_PREFIX . "facture";
  1463. $sql.= " SET fk_statut = 0";
  1464. $sql.= " WHERE rowid = " . $this->id;
  1465. dol_syslog(get_class($this) . "::set_draft sql=" . $sql, LOG_DEBUG);
  1466. $result = $this->db->query($sql);
  1467. if ($result) {
  1468. // Si on decremente le produit principal et ses composants a la validation de facture, on réincrement
  1469. if ($this->type != 3 && $result >= 0 && !empty($conf->stock->enabled) && !empty($conf->global->STOCK_CALCULATE_ON_BILL)) {
  1470. require_once DOL_DOCUMENT_ROOT . '/product/stock/class/mouvementstock.class.php';
  1471. $langs->load("agenda");
  1472. $num = count($this->lines);
  1473. for ($i = 0; $i < $num; $i++) {
  1474. if ($this->lines[$i]->fk_product > 0) {
  1475. $mouvP = new MouvementStock($this->db);
  1476. // We decrease stock for product
  1477. if ($this->type == 2)
  1478. $result = $mouvP->livraison($user, $this->lines[$i]->fk_product, $idwarehouse, $this->lines[$i]->qty, $this->lines[$i]->subprice, $langs->trans("InvoiceBackToDraftInSpeedealing", $this->ref));
  1479. else
  1480. $result = $mouvP->reception($user, $this->lines[$i]->fk_product, $idwarehouse, $this->lines[$i]->qty, $this->lines[$i]->subprice, $langs->trans("InvoiceBackToDraftInSpeedealing", $this->ref));
  1481. }
  1482. }
  1483. }
  1484. if ($error == 0) {
  1485. $this->db->commit();
  1486. return 1;
  1487. } else {
  1488. $this->db->rollback();
  1489. return -1;
  1490. }
  1491. } else {
  1492. $this->error = $this->db->error();
  1493. $this->db->rollback();
  1494. return -1;
  1495. }
  1496. }
  1497. /**
  1498. * Set percent discount
  1499. *
  1500. * @param User $user User that set discount
  1501. * @param double $remise Discount
  1502. * @return int <0 if ko, >0 if ok
  1503. */
  1504. function set_remise($user, $remise) {
  1505. // Clean parameters
  1506. if (empty($remise))
  1507. $remise = 0;
  1508. if ($user->rights->facture->creer) {
  1509. $remise = price2num($remise);
  1510. $sql = 'UPDATE ' . MAIN_DB_PREFIX . 'facture';
  1511. $sql.= ' SET remise_percent = ' . $remise;
  1512. $sql.= ' WHERE rowid = ' . $this->id;
  1513. $sql.= ' AND fk_statut = 0 ;';
  1514. if ($this->db->query($sql)) {
  1515. $this->remise_percent = $remise;
  1516. $this->update_price(1);
  1517. return 1;
  1518. } else {
  1519. $this->error = $this->db->error();
  1520. return -1;
  1521. }
  1522. }
  1523. }
  1524. /**
  1525. * Set absolute discount
  1526. *
  1527. * @param User $user User that set discount
  1528. * @param double $remise Discount
  1529. * @return int <0 if KO, >0 if OK
  1530. */
  1531. function set_remise_absolue($user, $remise) {
  1532. if (empty($remise))
  1533. $remise = 0;
  1534. if ($user->rights->facture->creer) {
  1535. $remise = price2num($remise);
  1536. $sql = 'UPDATE ' . MAIN_DB_PREFIX . 'facture';
  1537. $sql.= ' SET remise_absolue = ' . $remise;
  1538. $sql.= ' WHERE rowid = ' . $this->id;
  1539. $sql.= ' AND fk_statut = 0 ;';
  1540. dol_syslog(get_class($this) . "::set_remise_absolue sql=$sql");
  1541. if ($this->db->query($sql)) {
  1542. $this->remise_absolue = $remise;
  1543. $this->update_price(1);
  1544. return 1;
  1545. } else {
  1546. $this->error = $this->db->error();
  1547. return -1;
  1548. }
  1549. }
  1550. }
  1551. /**
  1552. * Return list of payments
  1553. *
  1554. * @param string $filtertype 1 to filter on type of payment == 'PRE'
  1555. * @return array Array with list of payments
  1556. */
  1557. function getListOfPayments($filtertype = '') {
  1558. $retarray = array();
  1559. $table = 'paiement_facture';
  1560. $table2 = 'paiement';
  1561. $field = 'fk_facture';
  1562. $field2 = 'fk_paiement';
  1563. if ($this->element == 'facture_fourn' || $this->element == 'invoice_supplier') {
  1564. $table = 'paiementfourn_facturefourn';
  1565. $table2 = 'paiementfourn';
  1566. $field = 'fk_facturefourn';
  1567. $field2 = 'fk_paiementfourn';
  1568. }
  1569. $sql = 'SELECT pf.amount, p.fk_paiement, p.datep, t.code';
  1570. $sql.= ' FROM ' . MAIN_DB_PREFIX . $table . ' as pf, ' . MAIN_DB_PREFIX . $table2 . ' as p, ' . MAIN_DB_PREFIX . 'c_paiement as t';
  1571. $sql.= ' WHERE pf.' . $field . ' = ' . $this->id;
  1572. $sql.= ' AND pf.' . $field2 . ' = p.rowid';
  1573. $sql.= ' AND p.fk_paiement = t.id';
  1574. if ($filtertype)
  1575. $sql.=" AND t.code='PRE'";
  1576. dol_syslog(get_class($this) . "::getListOfPayments sql=" . $sql, LOG_DEBUG);
  1577. $resql = $this->db->query($sql);
  1578. if ($resql) {
  1579. $num = $this->db->num_rows($resql);
  1580. $i = 0;
  1581. while ($i < $num) {
  1582. $obj = $this->db->fetch_object($resql);
  1583. $retarray[] = array('amount' => $obj->amount, 'type' => $obj->code, 'date' => $obj->datep);
  1584. $i++;
  1585. }
  1586. $this->db->free($resql);
  1587. return $retarray;
  1588. } else {
  1589. $this->error = $this->db->lasterror();
  1590. dol_print_error($this->db);
  1591. return array();
  1592. }
  1593. }
  1594. /**
  1595. * Return amount (with tax) of all credit notes and deposits invoices used by invoice
  1596. *
  1597. * @return int <0 if KO, Sum of credit notes and deposits amount otherwise
  1598. */
  1599. function getSumCreditNotesUsed() {
  1600. require_once DOL_DOCUMENT_ROOT . '/core/class/discount.class.php';
  1601. return 0;
  1602. $discountstatic = new DiscountAbsolute($this->db);
  1603. $result = $discountstatic->getSumCreditNotesUsed($this);
  1604. if ($result >= 0) {
  1605. return $result;
  1606. } else {
  1607. $this->error = $discountstatic->error;
  1608. return -1;
  1609. }
  1610. }
  1611. /**
  1612. * Return amount (with tax) of all deposits invoices used by invoice
  1613. *
  1614. * @return int <0 if KO, Sum of deposits amount otherwise
  1615. */
  1616. function getSumDepositsUsed() {
  1617. require_once DOL_DOCUMENT_ROOT . '/core/class/discount.class.php';
  1618. return 0;
  1619. $discountstatic = new DiscountAbsolute($this->db);
  1620. $result = $discountstatic->getSumDepositsUsed($this);
  1621. if ($result >= 0) {
  1622. return $result;
  1623. } else {
  1624. $this->error = $discountstatic->error;
  1625. return -1;
  1626. }
  1627. }
  1628. function getSommePaiement(){
  1629. $res = $this->getView("sumPaymentsPerFacture", array("key" => $this->id));
  1630. return (float)$res->rows[0]->value;
  1631. }
  1632. function getPaymentsList(){
  1633. $res = $this->getView("paymentsPerFacture", array("key" => $this->id));
  1634. $payments = array();
  1635. foreach ($res->rows as $p) {
  1636. $payments[] = $p->value;
  1637. }
  1638. return $payments;
  1639. }
  1640. function addPayment(){
  1641. $amountPaid = $this->getSommePaiement();
  1642. if ($amountPaid > 0) {
  1643. if ($this->type == "INVOICE_DEPOSIT")
  1644. $this->Status = "STARTED";
  1645. else if (($this->type == "INVOICE_STANDARD" || $this->type == "INVOICE_REPLACEMENT") && $amountPaid < $this->total_ttc)
  1646. $this->Status = "STARTED";
  1647. else if (($this->type == "INVOICE_STANDARD" || $this->type == "INVOICE_REPLACEMENT") && $amountPaid == $this->total_ttc)
  1648. $this->Status = "PAID";
  1649. } else {
  1650. if ($this->type == "INVOICE_AVOIR" && $amountPaid == $this->total_ttc) {
  1651. $this->Status = "PAID";
  1652. }
  1653. }
  1654. $this->record();
  1655. return 1;
  1656. }
  1657. /**
  1658. * Return next reference of invoice not already used (or last reference)
  1659. * according to numbering module defined into constant FACTURE_ADDON
  1660. *
  1661. * @param Society $soc object company
  1662. * @param string $mode 'next' for next value or 'last' for last value
  1663. * @return string free ref or last ref
  1664. */
  1665. function getNextNumRef($soc, $mode = 'next') {
  1666. global $conf, $db, $langs;
  1667. $langs->load("bills");
  1668. // Clean parameters (if not defined or using deprecated value)
  1669. if (empty($conf->global->FACTURE_ADDON))
  1670. $conf->global->FACTURE_ADDON = 'mod_facture_terre';
  1671. else if ($conf->global->FACTURE_ADDON == 'terre')
  1672. $conf->global->FACTURE_ADDON = 'mod_facture_terre';
  1673. else if ($conf->global->FACTURE_ADDON == 'mercure')
  1674. $conf->global->FACTURE_ADDON = 'mod_facture_mercure';
  1675. $mybool = false;
  1676. $file = $conf->global->FACTURE_ADDON . ".php";
  1677. $classname = $conf->global->FACTURE_ADDON;
  1678. // Include file with class
  1679. foreach ($conf->file->dol_document_root as $dirroot) {
  1680. $dir = $dirroot . "/facture/core/modules/facture/";
  1681. // Load file with numbering class (if found)
  1682. $mybool|=@include_once $dir . $file;
  1683. }
  1684. // For compatibility
  1685. if (!$mybool) {
  1686. $file = $conf->global->FACTURE_ADDON . "/" . $conf->global->FACTURE_ADDON . ".modules.php";
  1687. $classname = "mod_facture_" . $conf->global->FACTURE_ADDON;
  1688. // Include file with class
  1689. foreach ($conf->file->dol_document_root as $dirroot) {
  1690. $dir = $dirroot . "/facture/core/modules/facture/";
  1691. // Load file with numbering class (if found)
  1692. $mybool|=@include_once $dir . $file;
  1693. }
  1694. }
  1695. //print "xx".$mybool.$dir.$file."-".$classname;
  1696. if (!$mybool) {
  1697. dol_print_error('', "Failed to include file " . $file);
  1698. return '';
  1699. }
  1700. $obj = new $classname();
  1701. $numref = "";
  1702. $numref = $obj->getNumRef($soc, $this, $mode);
  1703. if ($numref != "") {
  1704. return $numref;
  1705. } else {
  1706. //dol_print_error($db,get_class($this)."::getNextNumRef ".$obj->error);
  1707. return false;
  1708. }
  1709. }
  1710. /**
  1711. * Load miscellaneous information for tab "Info"
  1712. *
  1713. * @param int $id Id of object to load
  1714. * @return void
  1715. */
  1716. function info($id) {
  1717. $sql = 'SELECT c.rowid, datec, date_valid as datev, tms as datem,';
  1718. $sql.= ' fk_user_author, fk_user_valid';
  1719. $sql.= ' FROM ' . MAIN_DB_PREFIX . 'facture as c';
  1720. $sql.= ' WHERE c.rowid = ' . $id;
  1721. $result = $this->db->query($sql);
  1722. if ($result) {
  1723. if ($this->db->num_rows($result)) {
  1724. $obj = $this->db->fetch_object($result);
  1725. $this->id = $obj->rowid;
  1726. if ($obj->fk_user_author) {
  1727. $cuser = new User($this->db);
  1728. $cuser->fetch($obj->fk_user_author);
  1729. $this->user_creation = $cuser;
  1730. }
  1731. if ($obj->fk_user_valid) {
  1732. $vuser = new User($this->db);
  1733. $vuser->fetch($obj->fk_user_valid);
  1734. $this->user_validation = $vuser;
  1735. }
  1736. $this->date_creation = $this->db->jdate($obj->datec);
  1737. $this->date_modification = $this->db->jdate($obj->datem);
  1738. $this->date_validation = $this->db->jdate($obj->datev); // Should be in log table
  1739. }
  1740. $this->db->free($result);
  1741. } else {
  1742. dol_print_error($this->db);
  1743. }
  1744. }
  1745. /**
  1746. * Renvoi si les lignes de facture sont ventilees et/ou exportees en compta
  1747. *
  1748. * @return int <0 if KO, 0=no, 1=yes
  1749. */
  1750. function getVentilExportCompta() {
  1751. // On verifie si les lignes de factures ont ete exportees en compta et/ou ventilees
  1752. $ventilExportCompta = 0;
  1753. $num = count($this->lines);
  1754. for ($i = 0; $i < $num; $i++) {
  1755. if ($this->lines[$i]->export_compta <> 0 && $this->lines[$i]->code_ventilation <> 0) {
  1756. $ventilExportCompta++;
  1757. }
  1758. }
  1759. if ($ventilExportCompta <> 0) {
  1760. return 1;
  1761. } else {
  1762. return 0;
  1763. }
  1764. }
  1765. /**
  1766. * Return if an invoice can be deleted
  1767. * Rule is:
  1768. * If hidden option FACTURE_CAN_BE_REMOVED is on, we can
  1769. * If invoice has a definitive ref, is last, without payment and not dipatched into accountancy -> yes end of rule
  1770. * If invoice is draft and ha a temporary ref -> yes
  1771. *
  1772. * @return int <0 if KO, 0=no, 1=yes
  1773. */
  1774. function is_erasable() {
  1775. global $conf;
  1776. if (!empty($conf->global->FACTURE_CAN_BE_REMOVED))
  1777. return 1;
  1778. // on verifie si la facture est en numerotation provisoire
  1779. $facref = substr($this->ref, 1, 4);
  1780. // If not a draft invoice and not temporary invoice
  1781. if ($facref != 'PROV') {
  1782. $maxfacnumber = $this->getNextNumRef($this->client, 'last');
  1783. $ventilExportCompta = $this->getVentilExportCompta();
  1784. // Si derniere facture et si non ventilee, on peut supprimer
  1785. if ($maxfacnumber == $this->ref && $ventilExportCompta == 0) {
  1786. return 1;
  1787. }
  1788. } else if ($this->statut == 0 && $facref == 'PROV') { // Si facture brouillon et provisoire
  1789. return 1;
  1790. }
  1791. return 0;
  1792. }
  1793. /**
  1794. * Renvoi liste des factures remplacables
  1795. * Statut validee ou abandonnee pour raison autre + non payee + aucun paiement + pas deja remplacee
  1796. *
  1797. * @param int $socid Id societe
  1798. * @return array Tableau des factures ('id'=>id, 'ref'=>ref, 'status'=>status, 'paymentornot'=>0/1)
  1799. */
  1800. function list_replacable_invoices($socid = 0) {
  1801. global $conf;
  1802. $return = array();
  1803. $sql = "SELECT f.rowid as rowid, f.facnumber, f.fk_statut,";
  1804. $sql.= " ff.rowid as rowidnext";
  1805. $sql.= " FROM " . MAIN_DB_PREFIX . "facture as f";
  1806. $sql.= " LEFT JOIN " . MAIN_DB_PREFIX . "paiement_facture as pf ON f.rowid = pf.fk_facture";
  1807. $sql.= " LEFT JOIN " . MAIN_DB_PREFIX . "facture as ff ON f.rowid = ff.fk_facture_source";
  1808. $sql.= " WHERE (f.fk_statut = 1 OR (f.fk_statut = 3 AND f.close_code = 'abandon'))";
  1809. $sql.= " AND f.entity = " . $conf->entity;
  1810. $sql.= " AND f.paye = 0"; // Pas classee payee completement
  1811. $sql.= " AND pf.fk_paiement IS NULL"; // Aucun paiement deja fait
  1812. $sql.= " AND ff.fk_statut IS NULL"; // Renvoi vrai si pas facture de remplacement
  1813. if ($socid > 0)
  1814. $sql.=" AND f.fk_soc = " . $socid;
  1815. $sql.= " ORDER BY f.facnumber";
  1816. dol_syslog(get_class($this) . "::list_replacable_invoices sql=$sql");
  1817. $resql = $this->db->query($sql);
  1818. if ($resql) {
  1819. while ($obj = $this->db->fetch_object($resql)) {
  1820. $return[$obj->rowid] = array('id' => $obj->rowid,
  1821. 'ref' => $obj->facnumber,
  1822. 'status' => $obj->fk_statut);
  1823. }
  1824. //print_r($return);
  1825. return $return;
  1826. } else {
  1827. $this->error = $this->db->error();
  1828. dol_syslog(get_class($this) . "::list_replacable_invoices " . $this->error, LOG_ERR);
  1829. return -1;
  1830. }
  1831. }
  1832. /**
  1833. * Renvoi liste des factures qualifiables pour correction par avoir
  1834. * Les factures qui respectent les regles suivantes sont retournees:
  1835. * (validee + paiement en cours) ou classee (payee completement ou payee partiellement) + pas deja remplacee + pas deja avoir
  1836. *
  1837. * @param int $socid Id societe
  1838. * @return array Tableau des factures ($id => array('ref'=>,'paymentornot'=>,'status'=>,'paye'=>)
  1839. */
  1840. function list_qualified_avoir_invoices($socid = 0) {
  1841. global $conf;
  1842. $return = array();
  1843. $sql = "SELECT f.rowid as rowid, f.facnumber, f.fk_statut, f.type, f.paye, pf.fk_paiement";
  1844. $sql.= " FROM " . MAIN_DB_PREFIX . "facture as f";
  1845. $sql.= " LEFT JOIN " . MAIN_DB_PREFIX . "paiement_facture as pf ON f.rowid = pf.fk_facture";
  1846. $sql.= " LEFT JOIN " . MAIN_DB_PREFIX . "facture as ff ON (f.rowid = ff.fk_facture_source AND ff.type=1)";
  1847. $sql.= " WHERE f.entity = " . $conf->entity;
  1848. $sql.= " AND f.fk_statut in (1,2)";
  1849. // $sql.= " WHERE f.fk_statut >= 1";
  1850. // $sql.= " AND (f.paye = 1"; // Classee payee completement
  1851. // $sql.= " OR f.close_code IS NOT NULL)"; // Classee payee partiellement
  1852. $sql.= " AND ff.type IS NULL"; // Renvoi vrai si pas facture de remplacement
  1853. $sql.= " AND f.type != 2"; // Type non 2 si facture non avoir
  1854. if ($socid > 0)
  1855. $sql.=" AND f.fk_soc = " . $socid;
  1856. $sql.= " ORDER BY f.facnumber";
  1857. dol_syslog(get_class($this) . "::list_qualified_avoir_invoices sql=" . $sql);
  1858. $resql = $this->db->query($sql);
  1859. if ($resql) {
  1860. while ($obj = $this->db->fetch_object($resql)) {
  1861. $qualified = 0;
  1862. if ($obj->fk_statut == 1)
  1863. $qualified = 1;
  1864. if ($obj->fk_statut == 2)
  1865. $qualified = 1;
  1866. if ($qualified) {
  1867. //$ref=$obj->facnumber;
  1868. $paymentornot = ($obj->fk_paiement ? 1 : 0);
  1869. $return[$obj->rowid] = array('ref' => $obj->facnumber, 'status' => $obj->fk_statut, 'type' => $obj->type, 'paye' => $obj->paye, 'paymentornot' => $paymentornot);
  1870. }
  1871. }
  1872. return $return;
  1873. } else {
  1874. $this->error = $this->db->error();
  1875. dol_syslog(get_class($this) . "::list_avoir_invoices " . $this->error, LOG_ERR);
  1876. return -1;
  1877. }
  1878. }
  1879. /**
  1880. * Create a withdrawal request for a standing order
  1881. *
  1882. * @param User $user User asking standing order
  1883. * @return int <0 if KO, >0 if OK
  1884. */
  1885. function demande_prelevement($user) {
  1886. dol_syslog(get_class($this) . "::demande_prelevement", LOG_DEBUG);
  1887. $soc = new Societe($this->db);
  1888. $soc->id = $this->socid;
  1889. $soc->load_ban();
  1890. if ($this->statut > 0 && $this->paye == 0) {
  1891. $sql = 'SELECT count(*)';
  1892. $sql.= ' FROM ' . MAIN_DB_PREFIX . 'prelevement_facture_demande';
  1893. $sql.= ' WHERE fk_facture = ' . $this->id;
  1894. $sql.= ' AND traite = 0';
  1895. $resql = $this->db->query($sql);
  1896. if ($resql) {
  1897. $row = $this->db->fetch_row($resql);
  1898. if ($row[0] == 0) {
  1899. $now = dol_now();
  1900. $sql = 'INSERT INTO ' . MAIN_DB_PREFIX . 'prelevement_facture_demande';
  1901. $sql .= ' (fk_facture, amount, date_demande, fk_user_demande, code_banque, code_guichet, number, cle_rib)';
  1902. $sql .= ' VALUES (' . $this->id;
  1903. $sql .= ",'" . price2num($this->total_ttc) . "'";
  1904. $sql .= "," . $this->db->idate($now) . "," . $user->id;
  1905. $sql .= ",'" . $soc->bank_account->code_banque . "'";
  1906. $sql .= ",'" . $soc->bank_account->code_guichet . "'";
  1907. $sql .= ",'" . $soc->bank_account->number . "'";
  1908. $sql .= ",'" . $soc->bank_account->cle_rib . "')";
  1909. if ($this->db->query($sql)) {
  1910. return 1;
  1911. } else {
  1912. $this->error = $this->db->error();
  1913. dol_syslog(get_class($this) . '::demandeprelevement Erreur');
  1914. return -1;
  1915. }
  1916. } else {
  1917. $this->error = "A request already exists";
  1918. dol_syslog(get_class($this) . '::demandeprelevement Impossible de creer une demande, demande deja en cours');
  1919. }
  1920. } else {
  1921. $this->error = $this->db->error();
  1922. dol_syslog(get_class($this) . '::demandeprelevement Erreur -2');
  1923. return -2;
  1924. }
  1925. } else {
  1926. $this->error = "Status of invoice does not allow this";
  1927. dol_syslog(get_class($this) . "::demandeprelevement " . $this->error . " $this->statut, $this->paye, $this->mode_reglement_id");
  1928. return -3;
  1929. }
  1930. }
  1931. /**
  1932. * Supprime une demande de prelevement
  1933. *
  1934. * @param Use $user utilisateur creant la demande
  1935. * @param int $did id de la demande a supprimer
  1936. * @return int <0 if OK, >0 if KO
  1937. */
  1938. function demande_prelevement_delete($user, $did) {
  1939. $sql = 'DELETE FROM ' . MAIN_DB_PREFIX . 'prelevement_facture_demande';
  1940. $sql .= ' WHERE rowid = ' . $did;
  1941. $sql .= ' AND traite = 0';
  1942. if ($this->db->query($sql)) {
  1943. return 0;
  1944. } else {
  1945. $this->error = $this->db->lasterror();
  1946. dol_syslog(get_class($this) . '::demande_prelevement_delete Error ' . $this->error);
  1947. return -1;
  1948. }
  1949. }
  1950. /**
  1951. * Load indicators for dashboard (this->nbtodo and this->nbtodolate)
  1952. *
  1953. * @param User $user Object user
  1954. * @return int <0 if KO, >0 if OK
  1955. */
  1956. function load_board($user) {
  1957. global $conf, $user;
  1958. $now = dol_now();
  1959. $this->nbtodo = $this->nbtodolate = 0;
  1960. $clause = " WHERE";
  1961. $sql = "SELECT f.rowid, f.date_lim_reglement as datefin";
  1962. $sql.= " FROM " . MAIN_DB_PREFIX . "facture as f";
  1963. if (!$user->rights->societe->client->voir && !$user->societe_id) {
  1964. $sql.= " LEFT JOIN " . MAIN_DB_PREFIX . "societe_commerciaux as sc ON f.fk_soc = sc.fk_soc";
  1965. $sql.= " WHERE sc.fk_user = " . $user->id;
  1966. $clause = " AND";
  1967. }
  1968. $sql.= $clause . " f.paye=0";
  1969. $sql.= " AND f.entity = " . $conf->entity;
  1970. $sql.= " AND f.fk_statut = 1";
  1971. if ($user->societe_id)
  1972. $sql.= " AND f.fk_soc = " . $user->societe_id;
  1973. $resql = $this->db->query($sql);
  1974. if ($resql) {
  1975. while ($obj = $this->db->fetch_object($resql)) {
  1976. $this->nbtodo++;
  1977. if ($this->db->jdate($obj->datefin) < ($now - $conf->facture->client->warning_delay))
  1978. $this->nbtodolate++;
  1979. }
  1980. return 1;
  1981. }
  1982. else {
  1983. dol_print_error($this->db);
  1984. $this->error = $this->db->error();
  1985. return -1;
  1986. }
  1987. }
  1988. /* gestion des contacts d'une facture */
  1989. /**
  1990. * Retourne id des contacts clients de facturation
  1991. *
  1992. * @return array Liste des id contacts facturation
  1993. */
  1994. function getIdBillingContact() {
  1995. return $this->getIdContact('external', 'BILLING');
  1996. }
  1997. /**
  1998. * Retourne id des contacts clients de livraison
  1999. *
  2000. * @return array Liste des id contacts livraison
  2001. */
  2002. function getIdShippingContact() {
  2003. return $this->getIdContact('external', 'SHIPPING');
  2004. }
  2005. /**
  2006. * Initialise an instance with random values.
  2007. * Used to build previews or test instances.
  2008. * id must be 0 if object instance is a specimen.
  2009. *
  2010. * @param string $option ''=Create a specimen invoice with lines, 'nolines'=No lines
  2011. * @return void
  2012. */
  2013. function initAsSpecimen($option = '') {
  2014. global $user, $langs, $conf;
  2015. $now = dol_now();
  2016. $arraynow = dol_getdate($now);
  2017. $nownotime = dol_mktime(0, 0, 0, $arraynow['mon'], $arraynow['mday'], $arraynow['year']);
  2018. $prodids = array();
  2019. $sql = "SELECT rowid";
  2020. $sql.= " FROM " . MAIN_DB_PREFIX . "product";
  2021. $sql.= " WHERE entity IN (" . getEntity('product', 1) . ")";
  2022. $resql = $this->db->query($sql);
  2023. if ($resql) {
  2024. $num_prods = $this->db->num_rows($resql);
  2025. $i = 0;
  2026. while ($i < $num_prods) {
  2027. $i++;
  2028. $row = $this->db->fetch_row($resql);
  2029. $prodids[$i] = $row[0];
  2030. }
  2031. }
  2032. // Initialize parameters
  2033. $this->id = 0;
  2034. $this->ref = 'SPECIMEN';
  2035. $this->specimen = 1;
  2036. $this->socid = 1;
  2037. $this->date = $nownotime;
  2038. $this->date_lim_reglement = $nownotime + 3600 * 24 * 30;
  2039. $this->cond_reglement_id = 1;
  2040. $this->cond_reglement_code = 'RECEP';
  2041. $this->date_lim_reglement = $this->calculate_date_lim_reglement();
  2042. $this->mode_reglement_id = 7;
  2043. $this->mode_reglement_code = 'CHQ';
  2044. $this->note_public = 'This is a comment (public)';
  2045. $this->note_private = 'This is a comment (private)';
  2046. $this->note = 'This is a comment (private)';
  2047. if (empty($option) || $option != 'nolines') {
  2048. // Lines
  2049. $nbp = 5;
  2050. $xnbp = 0;
  2051. while ($xnbp < $nbp) {
  2052. $line = new FactureLigne($this->db);
  2053. $line->desc = $langs->trans("Description") . " " . $xnbp;
  2054. $line->qty = 1;
  2055. $line->subprice = 100;
  2056. $line->tva_tx = 19.6;
  2057. $line->localtax1_tx = 0;
  2058. $line->localtax2_tx = 0;
  2059. $line->remise_percent = 0;
  2060. if ($xnbp == 1) { // Qty is negative (product line)
  2061. $prodid = rand(1, $num_prods);
  2062. $line->fk_product = $prodids[$prodid];
  2063. $line->qty = -1;
  2064. $line->total_ht = -100;
  2065. $line->total_ttc = -119.6;
  2066. $line->total_tva = -19.6;
  2067. } else if ($xnbp == 2) { // UP is negative (free line)
  2068. $line->subprice = -100;
  2069. $line->total_ht = -100;
  2070. $line->total_ttc = -119.6;
  2071. $line->total_tva = -19.6;
  2072. $line->remise_percent = 0;
  2073. } else if ($xnbp == 3) { // Discount is 50% (product line)
  2074. $prodid = rand(1, $num_prods);
  2075. $line->fk_product = $prodids[$prodid];
  2076. $line->total_ht = 50;
  2077. $line->total_ttc = 59.8;
  2078. $line->total_tva = 9.8;
  2079. $line->remise_percent = 50;
  2080. } else { // (product line)
  2081. $prodid = rand(1, $num_prods);
  2082. $line->fk_product = $prodids[$prodid];
  2083. $line->total_ht = 100;
  2084. $line->total_ttc = 119.6;
  2085. $line->total_tva = 19.6;
  2086. $line->remise_percent = 00;
  2087. }
  2088. $this->lines[$xnbp] = $line;
  2089. $xnbp++;
  2090. $this->total_ht += $line->total_ht;
  2091. $this->total_tva += $line->total_tva;
  2092. $this->total_ttc += $line->total_ttc;
  2093. }
  2094. // Add a line "offered"
  2095. $line = new FactureLigne($this->db);
  2096. $line->desc = $langs->trans("Description") . " (offered line)";
  2097. $line->qty = 1;
  2098. $line->subprice = 100;
  2099. $line->tva_tx = 19.6;
  2100. $line->localtax1_tx = 0;
  2101. $line->localtax2_tx = 0;
  2102. $line->remise_percent = 100;
  2103. $line->total_ht = 0;
  2104. $line->total_ttc = 0; // 90 * 1.196
  2105. $line->total_tva = 0;
  2106. $prodid = rand(1, $num_prods);
  2107. $line->fk_product = $prodids[$prodid];
  2108. $this->lines[$xnbp] = $line;
  2109. $xnbp++;
  2110. }
  2111. }
  2112. /**
  2113. * Load indicators for dashboard (this->nbtodo and this->nbtodolate)
  2114. *
  2115. * @return int <0 if KO, >0 if OK
  2116. */
  2117. function load_state_board() {
  2118. global $conf, $user;
  2119. $this->nb = array();
  2120. $clause = "WHERE";
  2121. $sql = "SELECT count(f.rowid) as nb";
  2122. $sql.= " FROM " . MAIN_DB_PREFIX . "facture as f";
  2123. $sql.= " LEFT JOIN " . MAIN_DB_PREFIX . "societe as s ON f.fk_soc = s.rowid";
  2124. if (!$user->rights->societe->client->voir && !$user->societe_id) {
  2125. $sql.= " LEFT JOIN " . MAIN_DB_PREFIX . "societe_commerciaux as sc ON s.rowid = sc.fk_soc";
  2126. $sql.= " WHERE sc.fk_user = " . $user->id;
  2127. $clause = "AND";
  2128. }
  2129. $sql.= " " . $clause . " f.entity = " . $conf->entity;
  2130. $resql = $this->db->query($sql);
  2131. if ($resql) {
  2132. while ($obj = $this->db->fetch_object($resql)) {
  2133. $this->nb["invoices"] = $obj->nb;
  2134. }
  2135. return 1;
  2136. } else {
  2137. dol_print_error($this->db);
  2138. $this->error = $this->db->error();
  2139. return -1;
  2140. }
  2141. }
  2142. /**
  2143. * Create an array of invoice lines
  2144. *
  2145. * @return int >0 if OK, <0 if KO
  2146. */
  2147. function getLinesArray() {
  2148. $this->lines = array();
  2149. $result = $this->getView("linesPerFacture", array("key" => $this->id));
  2150. foreach ($result->rows as $res) {
  2151. $l = new FactureLigne($this->db);
  2152. $l->fetch($res->value->_id);
  2153. $this->lines[] = $l;
  2154. }
  2155. return 1;
  2156. $sql = 'SELECT l.rowid, l.label as custom_label, l.description, l.fk_product, l.product_type, l.qty, l.tva_tx,';
  2157. $sql.= ' l.fk_remise_except, l.localtax1_tx, l.localtax2_tx,';
  2158. $sql.= ' l.remise_percent, l.subprice, l.info_bits, l.rang, l.special_code, l.fk_parent_line,';
  2159. $sql.= ' l.total_ht, l.total_tva, l.total_ttc, l.fk_product_fournisseur_price as fk_fournprice, l.buy_price_ht as pa_ht,';
  2160. $sql.= ' l.date_start, l.date_end,';
  2161. $sql.= ' p.ref as product_ref, p.fk_product_type, p.label as product_label,';
  2162. $sql.= ' p.description as product_desc';
  2163. $sql.= ' FROM ' . MAIN_DB_PREFIX . 'facturedet as l';
  2164. $sql.= ' LEFT JOIN ' . MAIN_DB_PREFIX . 'product p ON l.fk_product=p.rowid';
  2165. $sql.= ' WHERE l.fk_facture = ' . $this->id;
  2166. $sql.= ' ORDER BY l.rang ASC, l.rowid';
  2167. $resql = $this->db->query($sql);
  2168. if ($resql) {
  2169. $num = $this->db->num_rows($resql);
  2170. $i = 0;
  2171. while ($i < $num) {
  2172. $obj = $this->db->fetch_object($resql);
  2173. $this->lines[$i]->id = $obj->rowid;
  2174. $this->lines[$i]->label = $obj->custom_label;
  2175. $this->lines[$i]->description = $obj->description;
  2176. $this->lines[$i]->fk_product = $obj->fk_product;
  2177. $this->lines[$i]->ref = $obj->product_ref;
  2178. $this->lines[$i]->product_label = $obj->product_label;
  2179. $this->lines[$i]->product_desc = $obj->product_desc;
  2180. $this->lines[$i]->fk_product_type = $obj->fk_product_type;
  2181. $this->lines[$i]->product_type = $obj->product_type;
  2182. $this->lines[$i]->qty = $obj->qty;
  2183. $this->lines[$i]->subprice = $obj->subprice;
  2184. $this->lines[$i]->fk_remise_except = $obj->fk_remise_except;
  2185. $this->lines[$i]->remise_percent = $obj->remise_percent;
  2186. $this->lines[$i]->tva_tx = $obj->tva_tx;
  2187. $this->lines[$i]->info_bits = $obj->info_bits;
  2188. $this->lines[$i]->total_ht = $obj->total_ht;
  2189. $this->lines[$i]->total_tva = $obj->total_tva;
  2190. $this->lines[$i]->total_ttc = $obj->total_ttc;
  2191. $this->lines[$i]->fk_parent_line = $obj->fk_parent_line;
  2192. $this->lines[$i]->special_code = $obj->special_code;
  2193. $this->lines[$i]->rang = $obj->rang;
  2194. $this->lines[$i]->date_start = $this->db->jdate($obj->date_start);
  2195. $this->lines[$i]->date_end = $this->db->jdate($obj->date_end);
  2196. $this->lines[$i]->fk_fournprice = $obj->fk_fournprice;
  2197. $marginInfos = getMarginInfos($obj->subprice, $obj->remise_percent, $obj->tva_tx, $obj->localtax1_tx, $obj->localtax2_tx, $this->lines[$i]->fk_fournprice, $obj->pa_ht);
  2198. $this->lines[$i]->pa_ht = $marginInfos[0];
  2199. $this->lines[$i]->marge_tx = $marginInfos[1];
  2200. $this->lines[$i]->marque_tx = $marginInfos[2];
  2201. $i++;
  2202. }
  2203. $this->db->free($resql);
  2204. return 1;
  2205. } else {
  2206. $this->error = $this->db->error();
  2207. dol_syslog("Error sql=" . $sql . ", error=" . $this->error, LOG_ERR);
  2208. return -1;
  2209. }
  2210. }
  2211. /**
  2212. * Create standard invoice in database
  2213. * Note: this->ref can be set or empty. If empty, we will use "(PROV)"
  2214. *
  2215. * @param User $user Object user that create
  2216. * @param int $notrigger 1=Does not execute triggers, 0 otherwise
  2217. * @param int $forceduedate 1=Do not recalculate due date from payment condition but force it with value
  2218. * @return int <0 if KO, >0 if OK
  2219. */
  2220. function createStandardInvoice($user, $notrigger = 0, $forceduedate = 0) {
  2221. global $langs, $conf, $mysoc, $user;
  2222. $error = 0;
  2223. // Clean parameters
  2224. if (empty($this->type))
  2225. $this->type = "INVOICE_STANDARD";
  2226. $this->ref_client = trim($this->ref_client);
  2227. $this->note = (isset($this->note) ? trim($this->note) : trim($this->note_private)); // deprecated
  2228. $this->note_private = (isset($this->note_private) ? trim($this->note_private) : trim($this->note));
  2229. $this->note_public = trim($this->note_public);
  2230. $this->Status = "DRAFT";
  2231. dol_syslog(get_class($this) . "::create user=" . $user->id);
  2232. // Check parameters
  2233. if (empty($this->date) || empty($user->id)) {
  2234. $this->error = "ErrorBadParameter";
  2235. dol_syslog(get_class($this) . "::create Try to create an invoice with an empty parameter (user, date, ...)", LOG_ERR);
  2236. return -3;
  2237. }
  2238. $this->date_lim_reglement = $this->calculate_date_lim_reglement();
  2239. $soc = new Societe($this->db);
  2240. $result = $soc->fetch($this->socid);
  2241. unset($this->socid);
  2242. if ($result < 0) {
  2243. $this->error = "Failed to fetch company";
  2244. dol_syslog(get_class($this) . "::create " . $this->error, LOG_ERR);
  2245. return -2;
  2246. }
  2247. $this->client = new stdClass();
  2248. $this->client->id = $soc->id;
  2249. $this->client->name = $soc->name;
  2250. // Author
  2251. $this->author = new stdClass();
  2252. $this->author->id = $user->id;
  2253. $this->author->name = $user->name;
  2254. $this->ref = $this->getNextNumRef($soc);
  2255. $now = dol_now();
  2256. $this->record();
  2257. // Appel des triggers
  2258. include_once DOL_DOCUMENT_ROOT . '/core/class/interfaces.class.php';
  2259. $interface = new Interfaces($this->db);
  2260. $result = $interface->run_triggers('BILL_CREATE', $this, $user, $langs, $conf);
  2261. if ($result < 0) {
  2262. $error++;
  2263. $this->errors = $interface->errors;
  2264. }
  2265. // Fin appel triggers
  2266. return $this->id;
  2267. }
  2268. public function getExtraFieldLabel($field) {
  2269. global $langs;
  2270. return $langs->trans($this->fk_extrafields->fields->{$field}->values->{$this->$field}->label);
  2271. }
  2272. /*
  2273. * Graph comptes by status
  2274. *
  2275. */
  2276. function graphPieStatus($json = false) {
  2277. global $user, $conf, $langs;
  2278. //$color = array(-1 => "#A51B00", 0 => "#CCC", 1 => "#000", 2 => "#FEF4AE", 3 => "#666", 4 => "#1f17c1", 5 => "#DE7603", 6 => "#D40000", 7 => "#7ac52e", 8 => "#1b651b", 9 => "#66c18c", 10 => "#2e99a0");
  2279. if ($json) { // For Data see viewgraph.php
  2280. $langs->load("orders");
  2281. $params = array('group' => true);
  2282. $result = $this->getView("count_status", $params);
  2283. $filter = false;
  2284. //print_r($result);
  2285. $i = 0;
  2286. foreach ($result->rows as $aRow) {
  2287. if ($filter)
  2288. $key = $aRow->key[1];
  2289. else
  2290. $key = $aRow->key;
  2291. $label = $langs->trans($this->fk_extrafields->fields->Status->values->$key->label);
  2292. if (empty($label))
  2293. $label = $langs->trans($aRow->key);
  2294. if ($i == 0) { // first element
  2295. $output[$i]->name = $label;
  2296. $output[$i]->y = $aRow->value;
  2297. $output[$i]->sliced = true;
  2298. $output[$i]->selected = true;
  2299. }
  2300. else
  2301. $output[$i] = array($label, $aRow->value);
  2302. $i++;
  2303. }
  2304. return $output;
  2305. } else {
  2306. $total = 0;
  2307. $i = 0;
  2308. ?>
  2309. <div id="pie-status" style="min-width: 100px; height: 280px; margin: 0 auto"></div>
  2310. <script type="text/javascript">
  2311. $(document).ready(function() {
  2312. (function($){ // encapsulate jQuery
  2313. $(function() {
  2314. var seriesOptions = [],
  2315. yAxisOptions = [],
  2316. seriesCounter = 0,
  2317. colors = Highcharts.getOptions().colors;
  2318. $.getJSON('<?php echo DOL_URL_ROOT . '/core/ajax/viewgraph.php'; ?>?json=graphPieStatus&class=<?php echo get_class($this); ?>&callback=?', function(data) {
  2319. seriesOptions = data;
  2320. createChart();
  2321. });
  2322. // create the chart when all data is loaded
  2323. function createChart() {
  2324. var chart;
  2325. chart = new Highcharts.Chart({
  2326. chart: {
  2327. renderTo: "pie-status",
  2328. //defaultSeriesType: "bar",
  2329. margin: 0,
  2330. plotBackgroundColor: null,
  2331. plotBorderWidth: null,
  2332. plotShadow: false
  2333. },
  2334. legend: {
  2335. layout: "vertical", backgroundColor: Highcharts.theme.legendBackgroundColor || "#FFFFFF", align: "left", verticalAlign: "bottom", x: 0, y: 20, floating: true, shadow: true,
  2336. enabled: false
  2337. },
  2338. title: {
  2339. text: null
  2340. },
  2341. tooltip: {
  2342. enabled:true,
  2343. pointFormat: '{series.name}: <b>{point.percentage}%</b>',
  2344. percentageDecimals: 2
  2345. },
  2346. navigator: {
  2347. margin: 30
  2348. },
  2349. plotOptions: {
  2350. pie: {
  2351. allowPointSelect: true,
  2352. cursor: 'pointer',
  2353. dataLabels: {
  2354. enabled: true,
  2355. color: '#FFF',
  2356. connectorColor: '#FFF',
  2357. distance : 30,
  2358. formatter: function() {
  2359. return '<b>'+ this.point.name +'</b><br> '+ Math.round(this.percentage) +' %';
  2360. }
  2361. }
  2362. }
  2363. },
  2364. series: [{
  2365. type: "pie",
  2366. name: "<?php echo $langs->trans("Quantity"); ?>",
  2367. size: 100,
  2368. data: seriesOptions
  2369. }]
  2370. });
  2371. }
  2372. });
  2373. })(jQuery);
  2374. });
  2375. </script>
  2376. <?php
  2377. }
  2378. }
  2379. /*
  2380. * Graph comptes by status
  2381. *
  2382. */
  2383. function graphBarStatus($json = false) {
  2384. global $user, $conf, $langs;
  2385. $langs->load("orders");
  2386. if ($json) { // For Data see viewgraph.php
  2387. $keystart[0] = $_GET["name"];
  2388. $keyend[0] = $_GET["name"];
  2389. $keyend[1] = new stdClass();
  2390. $params = array('group' => true, 'group_level' => 2, 'startkey' => $keystart, 'endkey' => $keyend);
  2391. $result = $this->getView("count_status", $params);
  2392. foreach ($this->fk_extrafields->fields->Status->values as $key => $aRow) {
  2393. //print_r($aRow);exit;
  2394. $label = $langs->trans($key);
  2395. if ($aRow->enable) {
  2396. $tab[$key]->label = $label;
  2397. $tab[$key]->value = 0;
  2398. }
  2399. }
  2400. foreach ($result->rows as $aRow) // Update counters from view
  2401. $tab[$aRow->key[1]]->value+=$aRow->value;
  2402. foreach ($tab as $aRow)
  2403. $output[] = array($aRow->label, $aRow->value);
  2404. return $output;
  2405. } else {
  2406. $total = 0;
  2407. $i = 0;
  2408. ?>
  2409. <div id="bar-status" style="min-width: 100px; height: 280px; margin: 0 auto"></div>
  2410. <script type="text/javascript">
  2411. $(document).ready(function() {
  2412. (function($){ // encapsulate jQuery
  2413. $(function() {
  2414. var seriesOptions = [],
  2415. yAxisOptions = [],
  2416. seriesCounter = 0,
  2417. names = [<?php
  2418. $params = array('group' => true, 'group_level' => 1);
  2419. $result = $this->getView("commercial_status", $params);
  2420. if (count($result->rows)) {
  2421. foreach ($result->rows as $aRow) {
  2422. if ($i == 0)
  2423. echo "'" . $aRow->key[0] . "'";
  2424. else
  2425. echo ",'" . $aRow->key[0] . "'";
  2426. $i++;
  2427. }
  2428. }
  2429. ?>],
  2430. colors = Highcharts.getOptions().colors;
  2431. $.each(names, function(i, name) {
  2432. $.getJSON('<?php echo DOL_URL_ROOT . '/core/ajax/viewgraph.php'; ?>?json=graphBarStatus&class=<?php echo get_class($this); ?>&name='+ name.toString() +'&callback=?', function(data) {
  2433. seriesOptions[i] = {
  2434. name: name,
  2435. data: data
  2436. };
  2437. // As we're loading the data asynchronously, we don't know what order it will arrive. So
  2438. // we keep a counter and create the chart when all the data is loaded.
  2439. seriesCounter++;
  2440. if (seriesCounter == names.length) {
  2441. createChart();
  2442. }
  2443. });
  2444. });
  2445. // create the chart when all data is loaded
  2446. function createChart() {
  2447. var chart;
  2448. chart = new Highcharts.Chart({
  2449. chart: {
  2450. renderTo: 'bar-status',
  2451. defaultSeriesType: "column",
  2452. zoomType: "x",
  2453. marginBottom: 30
  2454. },
  2455. credits: {
  2456. enabled:false
  2457. },
  2458. xAxis: {
  2459. categories: [<?php
  2460. $i = 0;
  2461. foreach ($this->fk_extrafields->fields->Status->values as $key => $aRow) {
  2462. $label = $langs->trans($aRow->label);
  2463. if (empty($label))
  2464. $label = $langs->trans($key);
  2465. if ($aRow->enable) {
  2466. if ($i == 0)
  2467. echo "'" . $label . "'";
  2468. else
  2469. echo ",'" . $label . "'";
  2470. $i++;
  2471. }
  2472. }
  2473. ?>],
  2474. maxZoom: 1
  2475. //labels: {rotation: 90, align: "left"}
  2476. },
  2477. yAxis: {
  2478. title: {text: "Total"},
  2479. allowDecimals: false,
  2480. min: 0
  2481. },
  2482. title: {
  2483. //text: "<?php echo $langs->trans("SalesRepresentatives"); ?>"
  2484. text: null
  2485. },
  2486. legend: {
  2487. layout: 'vertical',
  2488. align: 'right',
  2489. verticalAlign: 'top',
  2490. x: -5,
  2491. y: 5,
  2492. floating: true,
  2493. borderWidth: 1,
  2494. backgroundColor: Highcharts.theme.legendBackgroundColor || '#FFFFFF',
  2495. shadow: true
  2496. },
  2497. tooltip: {
  2498. enabled:true,
  2499. formatter: function() {
  2500. //return this.point.name + ' : ' + this.y;
  2501. return '<b>'+ this.x +'</b><br/>'+
  2502. this.series.name +': '+ this.y;
  2503. }
  2504. },
  2505. series: seriesOptions
  2506. });
  2507. }
  2508. });
  2509. })(jQuery);
  2510. });
  2511. </script>
  2512. <?php
  2513. }
  2514. }
  2515. public function selectReplaceableInvoiceOptions($socid){
  2516. $options = '';
  2517. $result = $this->getView('listNotPaidPerSociete', array('key' => $socid));
  2518. if (!empty($result->rows)) {
  2519. foreach ($result->rows as $f) {
  2520. $options .= '<option value="' . $f->value->_id . '">' . $f->value->ref . '</option>';
  2521. }
  2522. }
  2523. return $options;
  2524. }
  2525. public function selectAvoirableInvoiceOptions($socid){
  2526. $options = '';
  2527. $result = $this->getView('listAvoirableInvoicesPerSociete', array('key' => $socid));
  2528. if (!empty($result->rows)) {
  2529. foreach ($result->rows as $f) {
  2530. $tmp = new Facture($this->db);
  2531. $tmp->fetch($f->value->_id);
  2532. $options .= '<option value="' . $f->value->_id . '">' . $f->value->ref . ' ( ' . $tmp->getExtraFieldLabel('Status') . ')</option>';
  2533. }
  2534. }
  2535. return $options;
  2536. }
  2537. public function getReplacingInvoice(){
  2538. $result = $this->getView('listReplacedInvoices', array('key' => $this->id));
  2539. if (empty($result->rows)) return null;
  2540. $facture = new Facture($this->db);
  2541. $facture->fetch($result->rows[0]->value);
  2542. return $facture;
  2543. }
  2544. public function getLinkedObject(){
  2545. $objects = array();
  2546. // Object stored in $this->linked_objects;
  2547. foreach ($this->linked_objects as $obj) {
  2548. switch ($obj->type) {
  2549. case 'commande':
  2550. $classname = 'Commande';
  2551. require_once(DOL_DOCUMENT_ROOT . '/commande/class/commande.class.php');
  2552. break;
  2553. }
  2554. $tmp = new $classname($this->db);
  2555. $tmp->fetch($obj->id);
  2556. $objects[$obj->type][] = $tmp;
  2557. }
  2558. return $objects;
  2559. }
  2560. public function printLinkedObjects(){
  2561. global $langs;
  2562. $objects = $this->getLinkedObject();
  2563. // Displaying linked propals
  2564. if (isset($objects['commande'])) {
  2565. $this->printLinkedObjectsType('commande', $objects['commande']);
  2566. }
  2567. }
  2568. public function printLinkedObjectsType($type, $data){
  2569. global $langs;
  2570. $title = 'LinkedObjects';
  2571. if ($type == 'commande')
  2572. $title = 'LinkedOrders';
  2573. print start_box($langs->trans($title), "six", $this->fk_extrafields->ico, false);
  2574. print '<table id="tablelines" class="noborder" width="100%">';
  2575. print '<tr>';
  2576. print '<th align="left">' . $langs->trans('Ref') . '</th>';
  2577. print '<th align="left">' . $langs->trans('Date') . '</th>';
  2578. print '<th align="left">' . $langs->trans('PriceHT') . '</th>';
  2579. print '<th align="left">' . $langs->trans('Status') . '</th>';
  2580. print '</tr>';
  2581. foreach ($data as $p) {
  2582. print '<tr>';
  2583. print '<td>' . $p->getNomUrl(1) . '</td>';
  2584. print '<td>' . dol_print_date($p->date) . '</td>';
  2585. print '<td>' . price($p->total_ht) . '</td>';
  2586. print '<td>' . $p->getExtraFieldLabel('Status') . '</td>';
  2587. print '</tr>';
  2588. }
  2589. print '</table>';
  2590. print end_box();
  2591. }
  2592. public function showLinkedObjects() {
  2593. global $langs;
  2594. print start_box($langs->trans("LinkedObjects"), "six", $this->fk_extrafields->ico, false);
  2595. print '<table class="display dt_act" id="listlinkedobjects" >';
  2596. // Ligne des titres
  2597. print '<thead>';
  2598. print'<tr>';
  2599. print'<th>';
  2600. print'</th>';
  2601. $obj->aoColumns[$i] = new stdClass();
  2602. $obj->aoColumns[$i]->mDataProp = "_id";
  2603. $obj->aoColumns[$i]->bUseRendered = false;
  2604. $obj->aoColumns[$i]->bSearchable = false;
  2605. $obj->aoColumns[$i]->bVisible = false;
  2606. $i++;
  2607. print'<th class="essential">';
  2608. print $langs->trans("Ref");
  2609. print'</th>';
  2610. $obj->aoColumns[$i] = new stdClass();
  2611. $obj->aoColumns[$i]->mDataProp = "ref";
  2612. $obj->aoColumns[$i]->bUseRendered = false;
  2613. $obj->aoColumns[$i]->bSearchable = true;
  2614. // $obj->aoColumns[$i]->fnRender = $this->datatablesFnRender("ref", "url");
  2615. $i++;
  2616. print'<th class="essential">';
  2617. print $langs->trans('Date');
  2618. print'</th>';
  2619. $obj->aoColumns[$i] = new stdClass();
  2620. $obj->aoColumns[$i]->mDataProp = "date";
  2621. $obj->aoColumns[$i]->sDefaultContent = "";
  2622. $obj->aoColumns[$i]->fnRender = $this->datatablesFnRender("date", "date");
  2623. $i++;
  2624. print'<th class="essential">';
  2625. print $langs->trans('PriceHT');
  2626. print'</th>';
  2627. $obj->aoColumns[$i] = new stdClass();
  2628. $obj->aoColumns[$i]->mDataProp = "total_ht";
  2629. $obj->aoColumns[$i]->sDefaultContent = "";
  2630. $obj->aoColumns[$i]->fnRender = $this->datatablesFnRender("total_ht", "price");
  2631. $i++;
  2632. print'<th class="essential">';
  2633. print $langs->trans('Status');
  2634. print'</th>';
  2635. $obj->aoColumns[$i] = new stdClass();
  2636. $obj->aoColumns[$i]->mDataProp = "Status";
  2637. $obj->aoColumns[$i]->sDefaultContent = "";
  2638. $obj->aoColumns[$i]->fnRender = $this->datatablesFnRender("Status", "status");
  2639. $i++;
  2640. print '</tr>';
  2641. print '</thead>';
  2642. print'<tfoot>';
  2643. print'</tfoot>';
  2644. print'<tbody>';
  2645. print'</tbody>';
  2646. print "</table>";
  2647. $obj->iDisplayLength = $max;
  2648. $obj->sAjaxSource = DOL_URL_ROOT . "/core/ajax/listdatatables.php?json=listLinkedObjects&class=" . get_class($this) . "&key=" . $this->id;
  2649. $this->datatablesCreate($obj, "listlinkedobjects", true);
  2650. print end_box();
  2651. }
  2652. public function showPayments(){
  2653. global $conf, $langs;
  2654. print start_box($langs->trans("Payments"), "six", $this->fk_extrafields->ico, false);
  2655. print '<table class="display dt_act" id="listpayments" >';
  2656. // Ligne des titres
  2657. print '<thead>';
  2658. print'<tr>';
  2659. print'<th>';
  2660. print'</th>';
  2661. $obj->aoColumns[$i] = new stdClass();
  2662. $obj->aoColumns[$i]->mDataProp = "id";
  2663. $obj->aoColumns[$i]->bUseRendered = false;
  2664. $obj->aoColumns[$i]->bSearchable = false;
  2665. $obj->aoColumns[$i]->bVisible = false;
  2666. $i++;
  2667. print'<th class="essential">';
  2668. print $langs->trans("Date");
  2669. print'</th>';
  2670. $obj->aoColumns[$i] = new stdClass();
  2671. $obj->aoColumns[$i]->mDataProp = "datepaye";
  2672. $obj->aoColumns[$i]->bUseRendered = false;
  2673. $obj->aoColumns[$i]->bSearchable = true;
  2674. $obj->aoColumns[$i]->fnRender = $this->datatablesFnRender("datepaye", "date");
  2675. $i++;
  2676. print'<th class="essential">';
  2677. print $langs->trans('Amount');
  2678. print'</th>';
  2679. $obj->aoColumns[$i] = new stdClass();
  2680. $obj->aoColumns[$i]->mDataProp = "amount";
  2681. $obj->aoColumns[$i]->sDefaultContent = "";
  2682. $obj->aoColumns[$i]->fnRender = $this->datatablesFnRender("amount", "price");
  2683. $i++;
  2684. print '</tr>';
  2685. print '</thead>';
  2686. print'<tfoot>';
  2687. print'</tfoot>';
  2688. print'<tbody>';
  2689. print'</tbody>';
  2690. print "</table>";
  2691. $obj->iDisplayLength = $max;
  2692. $obj->sAjaxSource = DOL_URL_ROOT . "/core/ajax/listdatatables.php?json=paymentsPerFacture&class=" . get_class($this) . "&key=" . $this->id;
  2693. $this->datatablesCreate($obj, "listpayments", true);
  2694. print '<br />';
  2695. $amountPaid = $this->getSommePaiement();
  2696. print $langs->trans("AlreadyPaid") . ': ' . price($amountPaid) . $langs->trans('Currency' . $conf->currency) . '<br />';
  2697. print $langs->trans("RemainderToPay") . ': ' . price($this->total_ttc - $amountPaid) . $langs->trans('Currency' . $conf->currency) . '<br />';
  2698. print end_box();
  2699. }
  2700. public function addInPlace($obj){
  2701. global $user;
  2702. // Converting date to timestamp
  2703. $date = explode('/', $this->date);
  2704. $this->date = $obj->date = dol_mktime(0, 0, 0, $date[1], $date[0], $date[2]);
  2705. // Generating next ref
  2706. $this->ref = $obj->ref = $this->getNextNumRef();
  2707. // Setting author of propal
  2708. $this->author = new stdClass();
  2709. $this->author->id = $user->id;
  2710. $this->author->name = $user->login;
  2711. }
  2712. public function fetch_thirdparty(){
  2713. $thirdparty = new Societe($this->db);
  2714. $thirdparty->fetch($this->client->id);
  2715. $this->thirdparty = $thirdparty;
  2716. }
  2717. public function show($id) {
  2718. global $langs;
  2719. require_once(DOL_DOCUMENT_ROOT . '/facture/class/facture.class.php');
  2720. $facture = new Facture($this->db);
  2721. print start_box($langs->trans("Bills"), $this->fk_extrafields->ico);
  2722. print '<table class="display dt_act" id="listfactures" >';
  2723. // Ligne des titres
  2724. print '<thead>';
  2725. print'<tr>';
  2726. print'<th>';
  2727. print'</th>';
  2728. $obj->aoColumns[$i] = new stdClass();
  2729. $obj->aoColumns[$i]->mDataProp = "_id";
  2730. $obj->aoColumns[$i]->bUseRendered = false;
  2731. $obj->aoColumns[$i]->bSearchable = false;
  2732. $obj->aoColumns[$i]->bVisible = false;
  2733. $i++;
  2734. print'<th class="essential">';
  2735. print $langs->trans("Ref");
  2736. print'</th>';
  2737. $obj->aoColumns[$i] = new stdClass();
  2738. $obj->aoColumns[$i]->mDataProp = "ref";
  2739. $obj->aoColumns[$i]->bUseRendered = false;
  2740. $obj->aoColumns[$i]->bSearchable = true;
  2741. $obj->aoColumns[$i]->fnRender = $facture->datatablesFnRender("ref", "url");
  2742. $i++;
  2743. print'<th class="essential">';
  2744. print $langs->trans('Date');
  2745. print'</th>';
  2746. $obj->aoColumns[$i] = new stdClass();
  2747. $obj->aoColumns[$i]->mDataProp = "date";
  2748. $obj->aoColumns[$i]->sDefaultContent = "";
  2749. $obj->aoColumns[$i]->fnRender = $facture->datatablesFnRender("date", "date");
  2750. $i++;
  2751. print'<th class="essential">';
  2752. print $langs->trans('PriceHT');
  2753. print'</th>';
  2754. $obj->aoColumns[$i] = new stdClass();
  2755. $obj->aoColumns[$i]->mDataProp = "total_ht";
  2756. $obj->aoColumns[$i]->sDefaultContent = "";
  2757. $obj->aoColumns[$i]->fnRender = $facture->datatablesFnRender("total_ht", "price");
  2758. $i++;
  2759. print'<th class="essential">';
  2760. print $langs->trans('Status');
  2761. print'</th>';
  2762. $obj->aoColumns[$i] = new stdClass();
  2763. $obj->aoColumns[$i]->mDataProp = "Status";
  2764. $obj->aoColumns[$i]->sDefaultContent = "";
  2765. $obj->aoColumns[$i]->fnRender = $facture->datatablesFnRender("Status", "status");
  2766. $i++;
  2767. print '</tr>';
  2768. print '</thead>';
  2769. print'<tfoot>';
  2770. print'</tfoot>';
  2771. print'<tbody>';
  2772. print'</tbody>';
  2773. print "</table>";
  2774. $obj->iDisplayLength = $max;
  2775. $obj->sAjaxSource = DOL_URL_ROOT . "/core/ajax/listdatatables.php?json=listBySociete&class=" . get_class($this) . "&key=" . $id;
  2776. $this->datatablesCreate($obj, "listfactures", true);
  2777. print end_box();
  2778. }
  2779. }
  2780. /**
  2781. * \class FactureLigne
  2782. * \brief Classe permettant la gestion des lignes de factures
  2783. * Gere des lignes de la table llx_facturedet
  2784. */
  2785. class FactureLigne extends nosqlDocument {
  2786. var $db;
  2787. var $error;
  2788. var $oldline;
  2789. //! From llx_facturedet
  2790. var $rowid;
  2791. //! Id facture
  2792. var $fk_facture;
  2793. //! Id parent line
  2794. var $fk_parent_line;
  2795. var $label;
  2796. //! Description ligne
  2797. var $desc;
  2798. var $fk_product; // Id of predefined product
  2799. var $product_type = 0; // Type 0 = product, 1 = Service
  2800. var $qty; // Quantity (example 2)
  2801. var $tva_tx; // Taux tva produit/service (example 19.6)
  2802. var $localtax1_tx; // Local tax 1
  2803. var $localtax2_tx; // Local tax 2
  2804. var $subprice; // P.U. HT (example 100)
  2805. var $remise_percent; // % de la remise ligne (example 20%)
  2806. var $fk_remise_except; // Link to line into llx_remise_except
  2807. var $rang = 0;
  2808. var $fk_fournprice;
  2809. var $pa_ht;
  2810. var $marge_tx;
  2811. var $marque_tx;
  2812. var $info_bits = 0; // Liste d'options cumulables:
  2813. // Bit 0: 0 si TVA normal - 1 si TVA NPR
  2814. // Bit 1: 0 si ligne normal - 1 si bit discount (link to line into llx_remise_except)
  2815. var $special_code; // Liste d'options non cumulabels:
  2816. // 1: frais de port
  2817. // 2: ecotaxe
  2818. // 3: ??
  2819. var $origin;
  2820. var $origin_id;
  2821. //! Total HT de la ligne toute quantite et incluant la remise ligne
  2822. var $total_ht;
  2823. //! Total TVA de la ligne toute quantite et incluant la remise ligne
  2824. var $total_tva;
  2825. var $total_localtax1; //Total Local tax 1 de la ligne
  2826. var $total_localtax2; //Total Local tax 2 de la ligne
  2827. //! Total TTC de la ligne toute quantite et incluant la remise ligne
  2828. var $total_ttc;
  2829. var $fk_code_ventilation = 0;
  2830. var $fk_export_compta = 0;
  2831. var $date_start;
  2832. var $date_end;
  2833. // Ne plus utiliser
  2834. //var $price; // P.U. HT apres remise % de ligne (exemple 80)
  2835. //var $remise; // Montant calcule de la remise % sur PU HT (exemple 20)
  2836. // From llx_product
  2837. var $ref; // Product ref (deprecated)
  2838. var $product_ref; // Product ref
  2839. var $libelle; // Product label (deprecated)
  2840. var $product_label; // Product label
  2841. var $product_desc; // Description produit
  2842. var $skip_update_total; // Skip update price total for special lines
  2843. /**
  2844. * Constructor
  2845. *
  2846. * @param DoliDB $db Database handler
  2847. */
  2848. function __construct($db = '') {
  2849. parent::__construct($db);
  2850. }
  2851. /**
  2852. * Load invoice line from database
  2853. *
  2854. * @param int $rowid id of invoice line to get
  2855. * @return int <0 if KO, >0 if OK
  2856. */
  2857. function fetch($rowid) {
  2858. return parent::fetch($rowid);
  2859. $sql = 'SELECT fd.rowid, fd.fk_facture, fd.fk_parent_line, fd.fk_product, fd.product_type, fd.label as custom_label, fd.description, fd.price, fd.qty, fd.tva_tx,';
  2860. $sql.= ' fd.localtax1_tx, fd. localtax2_tx, fd.remise, fd.remise_percent, fd.fk_remise_except, fd.subprice,';
  2861. $sql.= ' fd.date_start as date_start, fd.date_end as date_end, fd.fk_product_fournisseur_price as fk_fournprice, fd.buy_price_ht as pa_ht,';
  2862. $sql.= ' fd.info_bits, fd.total_ht, fd.total_tva, fd.total_ttc, fd.total_localtax1, fd.total_localtax2, fd.rang,';
  2863. $sql.= ' fd.fk_code_ventilation, fd.fk_export_compta,';
  2864. $sql.= ' p.ref as product_ref, p.label as product_libelle, p.description as product_desc';
  2865. $sql.= ' FROM ' . MAIN_DB_PREFIX . 'facturedet as fd';
  2866. $sql.= ' LEFT JOIN ' . MAIN_DB_PREFIX . 'product as p ON fd.fk_product = p.rowid';
  2867. $sql.= ' WHERE fd.rowid = ' . $rowid;
  2868. $result = $this->db->query($sql);
  2869. if ($result) {
  2870. $objp = $this->db->fetch_object($result);
  2871. $this->rowid = $objp->rowid;
  2872. $this->fk_facture = $objp->fk_facture;
  2873. $this->fk_parent_line = $objp->fk_parent_line;
  2874. $this->label = $objp->label;
  2875. $this->desc = $objp->description;
  2876. $this->qty = $objp->qty;
  2877. $this->subprice = $objp->subprice;
  2878. $this->tva_tx = $objp->tva_tx;
  2879. $this->localtax1_tx = $objp->localtax1_tx;
  2880. $this->localtax2_tx = $objp->localtax2_tx;
  2881. $this->remise_percent = $objp->remise_percent;
  2882. $this->fk_remise_except = $objp->fk_remise_except;
  2883. $this->fk_product = $objp->fk_product;
  2884. $this->product_type = $objp->product_type;
  2885. $this->date_start = $this->db->jdate($objp->date_start);
  2886. $this->date_end = $this->db->jdate($objp->date_end);
  2887. $this->info_bits = $objp->info_bits;
  2888. $this->total_ht = $objp->total_ht;
  2889. $this->total_tva = $objp->total_tva;
  2890. $this->total_localtax1 = $objp->total_localtax1;
  2891. $this->total_localtax2 = $objp->total_localtax2;
  2892. $this->total_ttc = $objp->total_ttc;
  2893. $this->fk_code_ventilation = $objp->fk_code_ventilation;
  2894. $this->fk_export_compta = $objp->fk_export_compta;
  2895. $this->rang = $objp->rang;
  2896. $this->fk_fournprice = $objp->fk_fournprice;
  2897. $marginInfos = getMarginInfos($objp->subprice, $objp->remise_percent, $objp->tva_tx, $objp->localtax1_tx, $objp->localtax2_tx, $this->fk_fournprice, $objp->pa_ht);
  2898. $this->pa_ht = $marginInfos[0];
  2899. $this->marge_tx = $marginInfos[1];
  2900. $this->marque_tx = $marginInfos[2];
  2901. $this->ref = $objp->product_ref; // deprecated
  2902. $this->product_ref = $objp->product_ref;
  2903. $this->libelle = $objp->product_libelle; // deprecated
  2904. $this->product_label = $objp->product_libelle;
  2905. $this->product_desc = $objp->product_desc;
  2906. $this->db->free($result);
  2907. } else {
  2908. dol_print_error($this->db);
  2909. }
  2910. }
  2911. /**
  2912. * Insert line in database
  2913. *
  2914. * @param int $notrigger 1 no triggers
  2915. * @return int <0 if KO, >0 if OK
  2916. */
  2917. function insert($notrigger = 0) {
  2918. global $langs, $user, $conf;
  2919. $error = 0;
  2920. dol_syslog(get_class($this) . "::insert rang=" . $this->rang, LOG_DEBUG);
  2921. // Clean parameters
  2922. $this->desc = trim($this->desc);
  2923. if (empty($this->tva_tx))
  2924. $this->tva_tx = 0;
  2925. if (empty($this->localtax1_tx))
  2926. $this->localtax1_tx = 0;
  2927. if (empty($this->localtax2_tx))
  2928. $this->localtax2_tx = 0;
  2929. if (empty($this->total_localtax1))
  2930. $this->total_localtax1 = 0;
  2931. if (empty($this->total_localtax2))
  2932. $this->total_localtax2 = 0;
  2933. if (empty($this->rang))
  2934. $this->rang = 0;
  2935. if (empty($this->remise_percent))
  2936. $this->remise_percent = 0;
  2937. if (empty($this->info_bits))
  2938. $this->info_bits = 0;
  2939. if (empty($this->subprice))
  2940. $this->subprice = 0;
  2941. if (empty($this->special_code))
  2942. $this->special_code = 0;
  2943. if (empty($this->fk_parent_line))
  2944. $this->fk_parent_line = 0;
  2945. if (empty($this->pa_ht))
  2946. $this->pa_ht = 0;
  2947. // si prix d'achat non renseigne et utilise pour calcul des marges alors prix achat = prix vente
  2948. if ($this->pa_ht == 0) {
  2949. if ($this->subprice > 0 && (isset($conf->global->ForceBuyingPriceIfNull) && $conf->global->ForceBuyingPriceIfNull == 1))
  2950. $this->pa_ht = $this->subprice * (1 - $this->remise_percent / 100);
  2951. }
  2952. // Check parameters
  2953. if ($this->product_type < 0)
  2954. return -1;
  2955. $this->record();
  2956. if (!$notrigger) {
  2957. // Appel des triggers
  2958. include_once DOL_DOCUMENT_ROOT . '/core/class/interfaces.class.php';
  2959. $interface = new Interfaces($this->db);
  2960. $result = $interface->run_triggers('LINEBILL_INSERT', $this, $user, $langs, $conf);
  2961. if ($result < 0) {
  2962. $error++;
  2963. $this->errors = $interface->errors;
  2964. }
  2965. // Fin appel triggers
  2966. }
  2967. return $this->id;
  2968. $this->db->begin();
  2969. // Insertion dans base de la ligne
  2970. $sql = 'INSERT INTO ' . MAIN_DB_PREFIX . 'facturedet';
  2971. $sql.= ' (fk_facture, fk_parent_line, label, description, qty, tva_tx, localtax1_tx, localtax2_tx,';
  2972. $sql.= ' fk_product, product_type, remise_percent, subprice, fk_remise_except,';
  2973. $sql.= ' date_start, date_end, fk_code_ventilation, fk_export_compta, ';
  2974. $sql.= ' rang, special_code, fk_product_fournisseur_price, buy_price_ht,';
  2975. $sql.= ' info_bits, total_ht, total_tva, total_ttc, total_localtax1, total_localtax2)';
  2976. $sql.= " VALUES (" . $this->fk_facture . ",";
  2977. $sql.= " " . ($this->fk_parent_line > 0 ? "'" . $this->fk_parent_line . "'" : "null") . ",";
  2978. $sql.= " " . (!empty($this->label) ? "'" . $this->db->escape($this->label) . "'" : "null") . ",";
  2979. $sql.= " '" . $this->db->escape($this->desc) . "',";
  2980. $sql.= " " . price2num($this->qty) . ",";
  2981. $sql.= " " . price2num($this->tva_tx) . ",";
  2982. $sql.= " " . price2num($this->localtax1_tx) . ",";
  2983. $sql.= " " . price2num($this->localtax2_tx) . ",";
  2984. $sql.= ' ' . (!empty($this->fk_product) ? $this->fk_product : "null") . ',';
  2985. $sql.= " " . $this->product_type . ",";
  2986. $sql.= " " . price2num($this->remise_percent) . ",";
  2987. $sql.= " " . price2num($this->subprice) . ",";
  2988. $sql.= ' ' . (!empty($this->fk_remise_except) ? $this->fk_remise_except : "null") . ',';
  2989. $sql.= " " . (!empty($this->date_start) ? "'" . $this->db->idate($this->date_start) . "'" : "null") . ",";
  2990. $sql.= " " . (!empty($this->date_end) ? "'" . $this->db->idate($this->date_end) . "'" : "null") . ",";
  2991. $sql.= ' ' . $this->fk_code_ventilation . ',';
  2992. $sql.= ' ' . $this->fk_export_compta . ',';
  2993. $sql.= ' ' . $this->rang . ',';
  2994. $sql.= ' ' . $this->special_code . ',';
  2995. $sql.= ' ' . (!empty($this->fk_fournprice) ? $this->fk_fournprice : "null") . ',';
  2996. $sql.= ' ' . price2num($this->pa_ht) . ',';
  2997. $sql.= " '" . $this->info_bits . "',";
  2998. $sql.= " " . price2num($this->total_ht) . ",";
  2999. $sql.= " " . price2num($this->total_tva) . ",";
  3000. $sql.= " " . price2num($this->total_ttc) . ",";
  3001. $sql.= " " . price2num($this->total_localtax1) . ",";
  3002. $sql.= " " . price2num($this->total_localtax2);
  3003. $sql.= ')';
  3004. dol_syslog(get_class($this) . "::insert sql=" . $sql);
  3005. $resql = $this->db->query($sql);
  3006. if ($resql) {
  3007. $this->rowid = $this->db->last_insert_id(MAIN_DB_PREFIX . 'facturedet');
  3008. // Si fk_remise_except defini, on lie la remise a la facture
  3009. // ce qui la flague comme "consommee".
  3010. if ($this->fk_remise_except) {
  3011. $discount = new DiscountAbsolute($this->db);
  3012. $result = $discount->fetch($this->fk_remise_except);
  3013. if ($result >= 0) {
  3014. // Check if discount was found
  3015. if ($result > 0) {
  3016. // Check if discount not already affected to another invoice
  3017. if ($discount->fk_facture) {
  3018. $this->error = $langs->trans("ErrorDiscountAlreadyUsed", $discount->id);
  3019. dol_syslog(get_class($this) . "::insert Error " . $this->error, LOG_ERR);
  3020. $this->db->rollback();
  3021. return -3;
  3022. } else {
  3023. $result = $discount->link_to_invoice($this->rowid, 0);
  3024. if ($result < 0) {
  3025. $this->error = $discount->error;
  3026. dol_syslog(get_class($this) . "::insert Error " . $this->error, LOG_ERR);
  3027. $this->db->rollback();
  3028. return -3;
  3029. }
  3030. }
  3031. } else {
  3032. $this->error = $langs->trans("ErrorADiscountThatHasBeenRemovedIsIncluded");
  3033. dol_syslog(get_class($this) . "::insert Error " . $this->error, LOG_ERR);
  3034. $this->db->rollback();
  3035. return -3;
  3036. }
  3037. } else {
  3038. $this->error = $discount->error;
  3039. dol_syslog(get_class($this) . "::insert Error " . $this->error, LOG_ERR);
  3040. $this->db->rollback();
  3041. return -3;
  3042. }
  3043. }
  3044. if (!$notrigger) {
  3045. // Appel des triggers
  3046. include_once DOL_DOCUMENT_ROOT . '/core/class/interfaces.class.php';
  3047. $interface = new Interfaces($this->db);
  3048. $result = $interface->run_triggers('LINEBILL_INSERT', $this, $user, $langs, $conf);
  3049. if ($result < 0) {
  3050. $error++;
  3051. $this->errors = $interface->errors;
  3052. }
  3053. // Fin appel triggers
  3054. }
  3055. $this->db->commit();
  3056. return $this->rowid;
  3057. } else {
  3058. $this->error = $this->db->error();
  3059. dol_syslog(get_class($this) . "::insert Error " . $this->error, LOG_ERR);
  3060. $this->db->rollback();
  3061. return -2;
  3062. }
  3063. }
  3064. /**
  3065. * Update line into database
  3066. *
  3067. * @param User $user User object
  3068. * @param int $notrigger Disable triggers
  3069. * @return int <0 if KO, >0 if OK
  3070. */
  3071. function update($user = '', $notrigger = 0) {
  3072. global $user, $langs, $conf;
  3073. $error = 0;
  3074. // Clean parameters
  3075. $this->desc = trim($this->desc);
  3076. if (empty($this->tva_tx))
  3077. $this->tva_tx = 0;
  3078. if (empty($this->localtax1_tx))
  3079. $this->localtax1_tx = 0;
  3080. if (empty($this->localtax2_tx))
  3081. $this->localtax2_tx = 0;
  3082. if (empty($this->total_localtax1))
  3083. $this->total_localtax1 = 0;
  3084. if (empty($this->total_localtax2))
  3085. $this->total_localtax2 = 0;
  3086. if (empty($this->remise_percent))
  3087. $this->remise_percent = 0;
  3088. if (empty($this->info_bits))
  3089. $this->info_bits = 0;
  3090. if (empty($this->special_code))
  3091. $this->special_code = 0;
  3092. if (empty($this->product_type))
  3093. $this->product_type = 0;
  3094. if (empty($this->fk_parent_line))
  3095. $this->fk_parent_line = 0;
  3096. // Check parameters
  3097. if ($this->product_type < 0)
  3098. return -1;
  3099. if (empty($this->pa_ht))
  3100. $this->pa_ht = 0;
  3101. // si prix d'achat non renseigne et utilise pour calcul des marges alors prix achat = prix vente
  3102. if ($this->pa_ht == 0) {
  3103. if ($this->subprice > 0 && (isset($conf->global->ForceBuyingPriceIfNull) && $conf->global->ForceBuyingPriceIfNull == 1))
  3104. $this->pa_ht = $this->subprice * (1 - $this->remise_percent / 100);
  3105. }
  3106. $this->db->begin();
  3107. // Mise a jour ligne en base
  3108. $sql = "UPDATE " . MAIN_DB_PREFIX . "facturedet SET";
  3109. $sql.= " description='" . $this->db->escape($this->desc) . "'";
  3110. $sql.= ",label=" . (!empty($this->label) ? "'" . $this->db->escape($this->label) . "'" : "null");
  3111. $sql.= ",subprice=" . price2num($this->subprice) . "";
  3112. $sql.= ",remise_percent=" . price2num($this->remise_percent) . "";
  3113. if ($this->fk_remise_except)
  3114. $sql.= ",fk_remise_except=" . $this->fk_remise_except;
  3115. else
  3116. $sql.= ",fk_remise_except=null";
  3117. $sql.= ",tva_tx=" . price2num($this->tva_tx) . "";
  3118. $sql.= ",localtax1_tx=" . price2num($this->localtax1_tx) . "";
  3119. $sql.= ",localtax2_tx=" . price2num($this->localtax2_tx) . "";
  3120. $sql.= ",qty=" . price2num($this->qty) . "";
  3121. $sql.= ",date_start=" . (!empty($this->date_start) ? "'" . $this->db->idate($this->date_start) . "'" : "null");
  3122. $sql.= ",date_end=" . (!empty($this->date_end) ? "'" . $this->db->idate($this->date_end) . "'" : "null");
  3123. $sql.= ",product_type=" . $this->product_type;
  3124. $sql.= ",info_bits='" . $this->info_bits . "'";
  3125. $sql.= ",special_code='" . $this->special_code . "'";
  3126. if (empty($this->skip_update_total)) {
  3127. $sql.= ",total_ht=" . price2num($this->total_ht) . "";
  3128. $sql.= ",total_tva=" . price2num($this->total_tva) . "";
  3129. $sql.= ",total_ttc=" . price2num($this->total_ttc) . "";
  3130. $sql.= ",total_localtax1=" . price2num($this->total_localtax1) . "";
  3131. $sql.= ",total_localtax2=" . price2num($this->total_localtax2) . "";
  3132. }
  3133. $sql.= " , fk_product_fournisseur_price='" . $this->fk_fournprice . "'";
  3134. $sql.= " , buy_price_ht='" . price2num($this->pa_ht) . "'";
  3135. $sql.= ",fk_parent_line=" . ($this->fk_parent_line > 0 ? $this->fk_parent_line : "null");
  3136. if (!empty($this->rang))
  3137. $sql.= ", rang=" . $this->rang;
  3138. $sql.= " WHERE rowid = " . $this->rowid;
  3139. dol_syslog(get_class($this) . "::update sql=" . $sql, LOG_DEBUG);
  3140. $resql = $this->db->query($sql);
  3141. if ($resql) {
  3142. if (!$notrigger) {
  3143. // Appel des triggers
  3144. include_once DOL_DOCUMENT_ROOT . '/core/class/interfaces.class.php';
  3145. $interface = new Interfaces($this->db);
  3146. $result = $interface->run_triggers('LINEBILL_UPDATE', $this, $user, $langs, $conf);
  3147. if ($result < 0) {
  3148. $error++;
  3149. $this->errors = $interface->errors;
  3150. }
  3151. // Fin appel triggers
  3152. }
  3153. $this->db->commit();
  3154. return 1;
  3155. } else {
  3156. $this->error = $this->db->error();
  3157. dol_syslog(get_class($this) . "::update Error " . $this->error, LOG_ERR);
  3158. $this->db->rollback();
  3159. return -2;
  3160. }
  3161. }
  3162. /**
  3163. * Delete line in database
  3164. *
  3165. * @return int <0 if KO, >0 if OK
  3166. */
  3167. function delete() {
  3168. global $conf, $langs, $user;
  3169. $error = 0;
  3170. // Si la ligne correspond à une remise, réactiver celle-ci
  3171. if (isset($this->fk_remise_except) && !empty($this->fk_remise_except)) {
  3172. $discount = new DiscountAbsolute($this->db);
  3173. $discount->fetch($this->fk_remise_except);
  3174. $discount->fk_facture_line = null;
  3175. $discount->record();
  3176. }
  3177. $this->deleteDoc();
  3178. return 1;
  3179. $this->db->begin();
  3180. $sql = "DELETE FROM " . MAIN_DB_PREFIX . "facturedet WHERE rowid = " . $this->rowid;
  3181. dol_syslog(get_class($this) . "::delete sql=" . $sql, LOG_DEBUG);
  3182. if ($this->db->query($sql)) {
  3183. // Appel des triggers
  3184. include_once DOL_DOCUMENT_ROOT . '/core/class/interfaces.class.php';
  3185. $interface = new Interfaces($this->db);
  3186. $result = $interface->run_triggers('LINEBILL_DELETE', $this, $user, $langs, $conf);
  3187. if ($result < 0) {
  3188. $error++;
  3189. $this->errors = $interface->errors;
  3190. }
  3191. // Fin appel triggers
  3192. $this->db->commit();
  3193. return 1;
  3194. } else {
  3195. $this->error = $this->db->error() . " sql=" . $sql;
  3196. dol_syslog(get_class($this) . "::delete Error " . $this->error, LOG_ERR);
  3197. $this->db->rollback();
  3198. return -1;
  3199. }
  3200. }
  3201. /**
  3202. * Mise a jour en base des champs total_xxx de ligne de facture
  3203. *
  3204. * @return int <0 if KO, >0 if OK
  3205. */
  3206. function update_total() {
  3207. $this->db->begin();
  3208. dol_syslog(get_class($this) . "::update_total", LOG_DEBUG);
  3209. // Clean parameters
  3210. if (empty($this->total_localtax1))
  3211. $this->total_localtax1 = 0;
  3212. if (empty($this->total_localtax2))
  3213. $this->total_localtax2 = 0;
  3214. // Mise a jour ligne en base
  3215. $sql = "UPDATE " . MAIN_DB_PREFIX . "facturedet SET";
  3216. $sql.= " total_ht=" . price2num($this->total_ht) . "";
  3217. $sql.= ",total_tva=" . price2num($this->total_tva) . "";
  3218. $sql.= ",total_localtax1=" . price2num($this->total_localtax1) . "";
  3219. $sql.= ",total_localtax2=" . price2num($this->total_localtax2) . "";
  3220. $sql.= ",total_ttc=" . price2num($this->total_ttc) . "";
  3221. $sql.= " WHERE rowid = " . $this->rowid;
  3222. dol_syslog(get_class($this) . "::update_total sql=" . $sql, LOG_DEBUG);
  3223. $resql = $this->db->query($sql);
  3224. if ($resql) {
  3225. $this->db->commit();
  3226. return 1;
  3227. } else {
  3228. $this->error = $this->db->error();
  3229. dol_syslog(get_class($this) . "::update_total Error " . $this->error, LOG_ERR);
  3230. $this->db->rollback();
  3231. return -2;
  3232. }
  3233. }
  3234. }
  3235. ?>