Total Complexity | 664 |
Total Lines | 4269 |
Duplicated Lines | 0 % |
Changes | 0 |
Complex classes like Facture often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use Facture, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
54 | class Facture extends CommonInvoice |
||
55 | { |
||
56 | /** |
||
57 | * @var string ID to identify managed object |
||
58 | */ |
||
59 | public $element='facture'; |
||
60 | |||
61 | /** |
||
62 | * @var string Name of table without prefix where object is stored |
||
63 | */ |
||
64 | public $table_element='facture'; |
||
65 | |||
66 | /** |
||
67 | * @var int Name of subtable line |
||
68 | */ |
||
69 | public $table_element_line = 'facturedet'; |
||
70 | |||
71 | /** |
||
72 | * @var int Field with ID of parent key if this field has a parent |
||
73 | */ |
||
74 | public $fk_element = 'fk_facture'; |
||
75 | |||
76 | /** |
||
77 | * @var string String with name of icon for myobject. Must be the part after the 'object_' into object_myobject.png |
||
78 | */ |
||
79 | public $picto='bill'; |
||
80 | |||
81 | /** |
||
82 | * 0=No test on entity, 1=Test with field entity, 2=Test with link by societe |
||
83 | * @var int |
||
84 | */ |
||
85 | public $ismultientitymanaged = 1; |
||
86 | |||
87 | /** |
||
88 | * 0=Default, 1=View may be restricted to sales representative only if no permission to see all or to company of external user if external user |
||
89 | * @var integer |
||
90 | */ |
||
91 | public $restrictiononfksoc = 1; |
||
92 | |||
93 | /** |
||
94 | * {@inheritdoc} |
||
95 | */ |
||
96 | protected $table_ref_field = 'ref'; |
||
97 | |||
98 | public $socid; |
||
99 | |||
100 | public $author; |
||
101 | |||
102 | /** |
||
103 | * @var int ID |
||
104 | */ |
||
105 | public $fk_user_author; |
||
106 | |||
107 | /** |
||
108 | * @var int ID |
||
109 | */ |
||
110 | public $fk_user_valid; |
||
111 | |||
112 | public $date; // Date invoice |
||
113 | public $datem; |
||
114 | public $ref_client; |
||
115 | public $ref_int; |
||
116 | //Check constants for types |
||
117 | public $type = self::TYPE_STANDARD; |
||
118 | |||
119 | //var $amount; |
||
120 | public $remise_absolue; |
||
121 | public $remise_percent; |
||
122 | public $total_ht=0; |
||
123 | public $total_tva=0; |
||
124 | public $total_localtax1=0; |
||
125 | public $total_localtax2=0; |
||
126 | public $total_ttc=0; |
||
127 | public $revenuestamp; |
||
128 | |||
129 | //! Fermeture apres paiement partiel: discount_vat, badcustomer, abandon |
||
130 | //! Fermeture alors que aucun paiement: replaced (si remplace), abandon |
||
131 | public $close_code; |
||
132 | //! Commentaire si mis a paye sans paiement complet |
||
133 | public $close_note; |
||
134 | //! 1 if invoice paid COMPLETELY, 0 otherwise (do not use it anymore, use statut and close_code) |
||
135 | public $paye; |
||
136 | //! key of module source when invoice generated from a dedicated module ('cashdesk', 'takepos', ...) |
||
137 | public $module_source; |
||
138 | //! key of pos source ('0', '1', ...) |
||
139 | public $pos_source; |
||
140 | //! id of template invoice when generated from a template invoice |
||
141 | public $fk_fac_rec_source; |
||
142 | //! id of source invoice if replacement invoice or credit note |
||
143 | public $fk_facture_source; |
||
144 | public $linked_objects=array(); |
||
145 | public $date_lim_reglement; |
||
146 | public $cond_reglement_code; // Code in llx_c_paiement |
||
147 | public $mode_reglement_code; // Code in llx_c_paiement |
||
148 | |||
149 | /** |
||
150 | * @var int ID Field to store bank id to use when payment mode is withdraw |
||
151 | */ |
||
152 | public $fk_bank; |
||
153 | |||
154 | /** |
||
155 | * @deprecated |
||
156 | */ |
||
157 | public $products=array(); |
||
158 | |||
159 | /** |
||
160 | * @var FactureLigne[] |
||
161 | */ |
||
162 | public $lines=array(); |
||
163 | |||
164 | public $line; |
||
165 | public $extraparams=array(); |
||
166 | public $specimen; |
||
167 | |||
168 | public $fac_rec; |
||
169 | |||
170 | // Multicurrency |
||
171 | /** |
||
172 | * @var int ID |
||
173 | */ |
||
174 | public $fk_multicurrency; |
||
175 | |||
176 | public $multicurrency_code; |
||
177 | public $multicurrency_tx; |
||
178 | public $multicurrency_total_ht; |
||
179 | public $multicurrency_total_tva; |
||
180 | public $multicurrency_total_ttc; |
||
181 | |||
182 | /** |
||
183 | * @var int Situation cycle reference number |
||
184 | */ |
||
185 | public $situation_cycle_ref; |
||
186 | |||
187 | /** |
||
188 | * @var int Situation counter inside the cycle |
||
189 | */ |
||
190 | public $situation_counter; |
||
191 | |||
192 | /** |
||
193 | * @var int Final situation flag |
||
194 | */ |
||
195 | public $situation_final; |
||
196 | |||
197 | /** |
||
198 | * @var array Table of previous situations |
||
199 | */ |
||
200 | public $tab_previous_situation_invoice=array(); |
||
201 | |||
202 | /** |
||
203 | * @var array Table of next situations |
||
204 | */ |
||
205 | public $tab_next_situation_invoice=array(); |
||
206 | |||
207 | public $oldcopy; |
||
208 | |||
209 | /** |
||
210 | * Standard invoice |
||
211 | */ |
||
212 | const TYPE_STANDARD = 0; |
||
213 | |||
214 | /** |
||
215 | * Replacement invoice |
||
216 | */ |
||
217 | const TYPE_REPLACEMENT = 1; |
||
218 | |||
219 | /** |
||
220 | * Credit note invoice |
||
221 | */ |
||
222 | const TYPE_CREDIT_NOTE = 2; |
||
223 | |||
224 | /** |
||
225 | * Deposit invoice |
||
226 | */ |
||
227 | const TYPE_DEPOSIT = 3; |
||
228 | |||
229 | /** |
||
230 | * Proforma invoice (should not be used. a proforma is an order) |
||
231 | */ |
||
232 | const TYPE_PROFORMA = 4; |
||
233 | |||
234 | /** |
||
235 | * Situation invoice |
||
236 | */ |
||
237 | const TYPE_SITUATION = 5; |
||
238 | |||
239 | /** |
||
240 | * Draft status |
||
241 | */ |
||
242 | const STATUS_DRAFT = 0; |
||
243 | |||
244 | /** |
||
245 | * Validated (need to be paid) |
||
246 | */ |
||
247 | const STATUS_VALIDATED = 1; |
||
248 | |||
249 | /** |
||
250 | * Classified paid. |
||
251 | * If paid partially, $this->close_code can be: |
||
252 | * - CLOSECODE_DISCOUNTVAT |
||
253 | * - CLOSECODE_BADDEBT |
||
254 | * If paid completely, this->close_code will be null |
||
255 | */ |
||
256 | const STATUS_CLOSED = 2; |
||
257 | |||
258 | /** |
||
259 | * Classified abandoned and no payment done. |
||
260 | * $this->close_code can be: |
||
261 | * - CLOSECODE_BADDEBT |
||
262 | * - CLOSECODE_ABANDONED |
||
263 | * - CLOSECODE_REPLACED |
||
264 | */ |
||
265 | const STATUS_ABANDONED = 3; |
||
266 | |||
267 | const CLOSECODE_DISCOUNTVAT = 'discount_vat'; // Abandonned remain - escompte |
||
268 | const CLOSECODE_BADDEBT = 'badcustomer'; // Abandonned - bad |
||
269 | const CLOSECODE_ABANDONED = 'abandon'; // Abandonned - other |
||
270 | const CLOSECODE_REPLACED = 'replaced'; // Closed after doing a replacement invoice |
||
271 | |||
272 | /** |
||
273 | * Constructor |
||
274 | * |
||
275 | * @param DoliDB $db Database handler |
||
276 | */ |
||
277 | function __construct($db) |
||
280 | } |
||
281 | |||
282 | /** |
||
283 | * Create invoice in database. |
||
284 | * Note: this->ref can be set or empty. If empty, we will use "(PROV999)" |
||
285 | * Note: this->fac_rec must be set to create invoice from a recurring invoice |
||
286 | * |
||
287 | * @param User $user Object user that create |
||
288 | * @param int $notrigger 1=Does not execute triggers, 0 otherwise |
||
289 | * @param int $forceduedate 1=Do not recalculate due date from payment condition but force it with value |
||
290 | * @return int <0 if KO, >0 if OK |
||
291 | */ |
||
292 | function create(User $user, $notrigger=0, $forceduedate=0) |
||
293 | { |
||
294 | global $langs,$conf,$mysoc,$hookmanager; |
||
295 | $error=0; |
||
296 | |||
297 | // Clean parameters |
||
298 | if (empty($this->type)) $this->type = self::TYPE_STANDARD; |
||
299 | $this->ref_client=trim($this->ref_client); |
||
300 | $this->note=(isset($this->note) ? trim($this->note) : trim($this->note_private)); // deprecated |
||
301 | $this->note_private=(isset($this->note_private) ? trim($this->note_private) : trim($this->note_private)); |
||
302 | $this->note_public=trim($this->note_public); |
||
303 | if (! $this->cond_reglement_id) $this->cond_reglement_id = 0; |
||
304 | if (! $this->mode_reglement_id) $this->mode_reglement_id = 0; |
||
305 | $this->brouillon = 1; |
||
306 | if (empty($this->entity)) $this->entity = $conf->entity; |
||
307 | |||
308 | // Multicurrency (test on $this->multicurrency_tx because we should take the default rate only if not using origin rate) |
||
309 | if (!empty($this->multicurrency_code) && empty($this->multicurrency_tx)) list($this->fk_multicurrency,$this->multicurrency_tx) = MultiCurrency::getIdAndTxFromCode($this->db, $this->multicurrency_code); |
||
310 | else $this->fk_multicurrency = MultiCurrency::getIdFromCode($this->db, $this->multicurrency_code); |
||
311 | if (empty($this->fk_multicurrency)) |
||
312 | { |
||
313 | $this->multicurrency_code = $conf->currency; |
||
314 | $this->fk_multicurrency = 0; |
||
315 | $this->multicurrency_tx = 1; |
||
316 | } |
||
317 | |||
318 | dol_syslog(get_class($this)."::create user=".$user->id." date=".$this->date); |
||
319 | |||
320 | // Check parameters |
||
321 | if (empty($this->date)) |
||
322 | { |
||
323 | $this->error="Try to create an invoice with an empty parameter (date)"; |
||
324 | dol_syslog(get_class($this)."::create ".$this->error, LOG_ERR); |
||
325 | return -3; |
||
326 | } |
||
327 | $soc = new Societe($this->db); |
||
328 | $result=$soc->fetch($this->socid); |
||
329 | if ($result < 0) |
||
330 | { |
||
331 | $this->error="Failed to fetch company: ".$soc->error; |
||
332 | dol_syslog(get_class($this)."::create ".$this->error, LOG_ERR); |
||
333 | return -2; |
||
334 | } |
||
335 | |||
336 | $now=dol_now(); |
||
337 | |||
338 | $this->db->begin(); |
||
339 | |||
340 | $originaldatewhen=null; |
||
341 | $nextdatewhen=null; |
||
342 | $previousdaynextdatewhen=null; |
||
343 | |||
344 | // Create invoice from a template invoice |
||
345 | if ($this->fac_rec > 0) |
||
346 | { |
||
347 | $this->fk_fac_rec_source = $this->fac_rec; |
||
348 | |||
349 | require_once DOL_DOCUMENT_ROOT.'/compta/facture/class/facture-rec.class.php'; |
||
350 | $_facrec = new FactureRec($this->db); |
||
351 | $result=$_facrec->fetch($this->fac_rec); |
||
352 | $result=$_facrec->fetchObjectLinked(); // This load $_facrec->linkedObjectsIds |
||
353 | |||
354 | // Define some dates |
||
355 | $originaldatewhen = $_facrec->date_when; |
||
356 | $nextdatewhen=dol_time_plus_duree($originaldatewhen, $_facrec->frequency, $_facrec->unit_frequency); |
||
357 | $previousdaynextdatewhen=dol_time_plus_duree($nextdatewhen, -1, 'd'); |
||
358 | |||
359 | $this->socid = $_facrec->socid; // Invoice created on same thirdparty than template |
||
360 | $this->entity = $_facrec->entity; // Invoice created in same entity than template |
||
361 | |||
362 | // Fields coming from GUI (priority on template). TODO Value of template should be used as default value on GUI so we can use here always value from GUI |
||
363 | $this->fk_project = GETPOST('projectid','int') > 0 ? ((int) GETPOST('projectid','int')) : $_facrec->fk_project; |
||
364 | $this->note_public = GETPOST('note_public','none') ? GETPOST('note_public','none') : $_facrec->note_public; |
||
|
|||
365 | $this->note_private = GETPOST('note_private','none') ? GETPOST('note_private','none') : $_facrec->note_private; |
||
366 | $this->modelpdf = GETPOST('model','alpha') ? GETPOST('model','apha') : $_facrec->modelpdf; |
||
367 | $this->cond_reglement_id = GETPOST('cond_reglement_id','int') > 0 ? ((int) GETPOST('cond_reglement_id','int')) : $_facrec->cond_reglement_id; |
||
368 | $this->mode_reglement_id = GETPOST('mode_reglement_id','int') > 0 ? ((int) GETPOST('mode_reglement_id','int')) : $_facrec->mode_reglement_id; |
||
369 | $this->fk_account = GETPOST('fk_account') > 0 ? ((int) GETPOST('fk_account')) : $_facrec->fk_account; |
||
370 | |||
371 | // Set here to have this defined for substitution into notes, should be recalculated after adding lines to get same result |
||
372 | $this->total_ht = $_facrec->total_ht; |
||
373 | $this->total_ttc = $_facrec->total_ttc; |
||
374 | |||
375 | // Fields always coming from template |
||
376 | $this->remise_absolue = $_facrec->remise_absolue; |
||
377 | $this->remise_percent = $_facrec->remise_percent; |
||
378 | $this->fk_incoterms = $_facrec->fk_incoterms; |
||
379 | $this->location_incoterms= $_facrec->location_incoterms; |
||
380 | |||
381 | // Clean parameters |
||
382 | if (! $this->type) $this->type = self::TYPE_STANDARD; |
||
383 | $this->ref_client=trim($this->ref_client); |
||
384 | $this->note_public=trim($this->note_public); |
||
385 | $this->note_private=trim($this->note_private); |
||
386 | $this->note_private=dol_concatdesc($this->note_private, $langs->trans("GeneratedFromRecurringInvoice", $_facrec->ref)); |
||
387 | |||
388 | $this->array_options=$_facrec->array_options; |
||
389 | |||
390 | //if (! $this->remise) $this->remise = 0; |
||
391 | if (! $this->mode_reglement_id) $this->mode_reglement_id = 0; |
||
392 | $this->brouillon = 1; |
||
393 | |||
394 | $this->linked_objects = $_facrec->linkedObjectsIds; |
||
395 | |||
396 | $forceduedate = $this->calculate_date_lim_reglement(); |
||
397 | |||
398 | // For recurring invoices, update date and number of last generation of recurring template invoice, before inserting new invoice |
||
399 | if ($_facrec->frequency > 0) |
||
400 | { |
||
401 | dol_syslog("This is a recurring invoice so we set date_last_gen and next date_when"); |
||
402 | if (empty($_facrec->date_when)) $_facrec->date_when = $now; |
||
403 | $next_date = $_facrec->getNextDate(); // Calculate next date |
||
404 | $result = $_facrec->setValueFrom('date_last_gen', $now, '', null, 'date', '', $user, ''); |
||
405 | //$_facrec->setValueFrom('nb_gen_done', $_facrec->nb_gen_done + 1); // Not required, +1 already included into setNextDate when second param is 1. |
||
406 | $result = $_facrec->setNextDate($next_date,1); |
||
407 | } |
||
408 | |||
409 | // Define lang of customer |
||
410 | $outputlangs = $langs; |
||
411 | $newlang=''; |
||
412 | |||
413 | if ($conf->global->MAIN_MULTILANGS && empty($newlang) && isset($this->thirdparty->default_lang)) $newlang=$this->thirdparty->default_lang; // for proposal, order, invoice, ... |
||
414 | if ($conf->global->MAIN_MULTILANGS && empty($newlang) && isset($this->default_lang)) $newlang=$this->default_lang; // for thirdparty |
||
415 | if (! empty($newlang)) |
||
416 | { |
||
417 | $outputlangs = new Translate("",$conf); |
||
418 | $outputlangs->setDefaultLang($newlang); |
||
419 | } |
||
420 | |||
421 | // Array of possible substitutions (See also file mailing-send.php that should manage same substitutions) |
||
422 | $substitutionarray=getCommonSubstitutionArray($outputlangs, 0, null, $this); |
||
423 | $substitutionarray['__INVOICE_PREVIOUS_MONTH__'] = dol_print_date(dol_time_plus_duree($this->date, -1, 'm'), '%m'); |
||
424 | $substitutionarray['__INVOICE_MONTH__'] = dol_print_date($this->date, '%m'); |
||
425 | $substitutionarray['__INVOICE_NEXT_MONTH__'] = dol_print_date(dol_time_plus_duree($this->date, 1, 'm'), '%m'); |
||
426 | $substitutionarray['__INVOICE_PREVIOUS_MONTH_TEXT__'] = dol_print_date(dol_time_plus_duree($this->date, -1, 'm'), '%B'); |
||
427 | $substitutionarray['__INVOICE_MONTH_TEXT__'] = dol_print_date($this->date, '%B'); |
||
428 | $substitutionarray['__INVOICE_NEXT_MONTH_TEXT__'] = dol_print_date(dol_time_plus_duree($this->date, 1, 'm'), '%B'); |
||
429 | $substitutionarray['__INVOICE_PREVIOUS_YEAR__'] = dol_print_date(dol_time_plus_duree($this->date, -1, 'y'), '%Y'); |
||
430 | $substitutionarray['__INVOICE_YEAR__'] = dol_print_date($this->date, '%Y'); |
||
431 | $substitutionarray['__INVOICE_NEXT_YEAR__'] = dol_print_date(dol_time_plus_duree($this->date, 1, 'y'), '%Y'); |
||
432 | // Only for tempalte invoice |
||
433 | $substitutionarray['__INVOICE_DATE_NEXT_INVOICE_BEFORE_GEN__'] = dol_print_date($originaldatewhen, 'dayhour'); |
||
434 | $substitutionarray['__INVOICE_DATE_NEXT_INVOICE_AFTER_GEN__'] = dol_print_date($nextdatewhen, 'dayhour'); |
||
435 | $substitutionarray['__INVOICE_PREVIOUS_DATE_NEXT_INVOICE_AFTER_GEN__'] = dol_print_date($previousdaynextdatewhen, 'dayhour'); |
||
436 | |||
437 | //var_dump($substitutionarray);exit; |
||
438 | |||
439 | $substitutionisok=true; |
||
440 | complete_substitutions_array($substitutionarray, $outputlangs); |
||
441 | |||
442 | $this->note_public=make_substitutions($this->note_public,$substitutionarray); |
||
443 | $this->note_private=make_substitutions($this->note_private,$substitutionarray); |
||
444 | } |
||
445 | |||
446 | // Define due date if not already defined |
||
447 | $datelim=(empty($forceduedate)?$this->calculate_date_lim_reglement():$forceduedate); |
||
448 | |||
449 | // Insert into database |
||
450 | $socid = $this->socid; |
||
451 | |||
452 | $sql = "INSERT INTO ".MAIN_DB_PREFIX."facture ("; |
||
453 | $sql.= " ref"; |
||
454 | $sql.= ", entity"; |
||
455 | $sql.= ", ref_ext"; |
||
456 | $sql.= ", type"; |
||
457 | $sql.= ", fk_soc"; |
||
458 | $sql.= ", datec"; |
||
459 | $sql.= ", remise_absolue"; |
||
460 | $sql.= ", remise_percent"; |
||
461 | $sql.= ", datef"; |
||
462 | $sql.= ", date_pointoftax"; |
||
463 | $sql.= ", note_private"; |
||
464 | $sql.= ", note_public"; |
||
465 | $sql.= ", ref_client, ref_int"; |
||
466 | $sql.= ", fk_account"; |
||
467 | $sql.= ", module_source, pos_source, fk_fac_rec_source, fk_facture_source, fk_user_author, fk_projet"; |
||
468 | $sql.= ", fk_cond_reglement, fk_mode_reglement, date_lim_reglement, model_pdf"; |
||
469 | $sql.= ", situation_cycle_ref, situation_counter, situation_final"; |
||
470 | $sql.= ", fk_incoterms, location_incoterms"; |
||
471 | $sql.= ", fk_multicurrency"; |
||
472 | $sql.= ", multicurrency_code"; |
||
473 | $sql.= ", multicurrency_tx"; |
||
474 | $sql.= ")"; |
||
475 | $sql.= " VALUES ("; |
||
476 | $sql.= "'(PROV)'"; |
||
477 | $sql.= ", ".$this->entity; |
||
478 | $sql.= ", ".($this->ref_ext?"'".$this->db->escape($this->ref_ext)."'":"null"); |
||
479 | $sql.= ", '".$this->db->escape($this->type)."'"; |
||
480 | $sql.= ", '".$socid."'"; |
||
481 | $sql.= ", '".$this->db->idate($now)."'"; |
||
482 | $sql.= ", ".($this->remise_absolue>0?$this->remise_absolue:'NULL'); |
||
483 | $sql.= ", ".($this->remise_percent>0?$this->remise_percent:'NULL'); |
||
484 | $sql.= ", '".$this->db->idate($this->date)."'"; |
||
485 | $sql.= ", ".(strval($this->date_pointoftax)!='' ? "'".$this->db->idate($this->date_pointoftax)."'" : 'null'); |
||
486 | $sql.= ", ".($this->note_private?"'".$this->db->escape($this->note_private)."'":"null"); |
||
487 | $sql.= ", ".($this->note_public?"'".$this->db->escape($this->note_public)."'":"null"); |
||
488 | $sql.= ", ".($this->ref_client?"'".$this->db->escape($this->ref_client)."'":"null"); |
||
489 | $sql.= ", ".($this->ref_int?"'".$this->db->escape($this->ref_int)."'":"null"); |
||
490 | $sql.= ", ".($this->fk_account>0?$this->fk_account:'NULL'); |
||
491 | $sql.= ", ".($this->module_source ? "'".$this->db->escape($this->module_source)."'" : "null"); |
||
492 | $sql.= ", ".($this->pos_source != '' ? "'".$this->db->escape($this->pos_source)."'" : "null"); |
||
493 | $sql.= ", ".($this->fk_fac_rec_source?"'".$this->db->escape($this->fk_fac_rec_source)."'":"null"); |
||
494 | $sql.= ", ".($this->fk_facture_source?"'".$this->db->escape($this->fk_facture_source)."'":"null"); |
||
495 | $sql.= ", ".($user->id > 0 ? "'".$user->id."'":"null"); |
||
496 | $sql.= ", ".($this->fk_project?$this->fk_project:"null"); |
||
497 | $sql.= ", ".$this->cond_reglement_id; |
||
498 | $sql.= ", ".$this->mode_reglement_id; |
||
499 | $sql.= ", '".$this->db->idate($datelim)."', '".$this->db->escape($this->modelpdf)."'"; |
||
500 | $sql.= ", ".($this->situation_cycle_ref?"'".$this->db->escape($this->situation_cycle_ref)."'":"null"); |
||
501 | $sql.= ", ".($this->situation_counter?"'".$this->db->escape($this->situation_counter)."'":"null"); |
||
502 | $sql.= ", ".($this->situation_final?$this->situation_final:0); |
||
503 | $sql.= ", ".(int) $this->fk_incoterms; |
||
504 | $sql.= ", '".$this->db->escape($this->location_incoterms)."'"; |
||
505 | $sql.= ", ".(int) $this->fk_multicurrency; |
||
506 | $sql.= ", '".$this->db->escape($this->multicurrency_code)."'"; |
||
507 | $sql.= ", ".(double) $this->multicurrency_tx; |
||
508 | $sql.=")"; |
||
509 | |||
510 | $resql=$this->db->query($sql); |
||
511 | if ($resql) |
||
512 | { |
||
513 | $this->id = $this->db->last_insert_id(MAIN_DB_PREFIX.'facture'); |
||
514 | |||
515 | // Update ref with new one |
||
516 | $this->ref='(PROV'.$this->id.')'; |
||
517 | $sql = 'UPDATE '.MAIN_DB_PREFIX."facture SET ref='".$this->db->escape($this->ref)."' WHERE rowid=".$this->id; |
||
518 | |||
519 | $resql=$this->db->query($sql); |
||
520 | if (! $resql) $error++; |
||
521 | |||
522 | if (! empty($this->linkedObjectsIds) && empty($this->linked_objects)) // To use new linkedObjectsIds instead of old linked_objects |
||
523 | { |
||
524 | $this->linked_objects = $this->linkedObjectsIds; // TODO Replace linked_objects with linkedObjectsIds |
||
525 | } |
||
526 | |||
527 | // Add object linked |
||
528 | if (! $error && $this->id && is_array($this->linked_objects) && ! empty($this->linked_objects)) |
||
529 | { |
||
530 | foreach($this->linked_objects as $origin => $tmp_origin_id) |
||
531 | { |
||
532 | if (is_array($tmp_origin_id)) // New behaviour, if linked_object can have several links per type, so is something like array('contract'=>array(id1, id2, ...)) |
||
533 | { |
||
534 | foreach($tmp_origin_id as $origin_id) |
||
535 | { |
||
536 | $ret = $this->add_object_linked($origin, $origin_id); |
||
537 | if (! $ret) |
||
538 | { |
||
539 | $this->error=$this->db->lasterror(); |
||
540 | $error++; |
||
541 | } |
||
542 | } |
||
543 | } |
||
544 | else // Old behaviour, if linked_object has only one link per type, so is something like array('contract'=>id1)) |
||
545 | { |
||
546 | $origin_id = $tmp_origin_id; |
||
547 | $ret = $this->add_object_linked($origin, $origin_id); |
||
548 | if (! $ret) |
||
549 | { |
||
550 | $this->error=$this->db->lasterror(); |
||
551 | $error++; |
||
552 | } |
||
553 | } |
||
554 | } |
||
555 | } |
||
556 | |||
557 | // Propagate contacts |
||
558 | if (! $error && $this->id && ! empty($conf->global->MAIN_PROPAGATE_CONTACTS_FROM_ORIGIN) && ! empty($this->origin) && ! empty($this->origin_id)) // Get contact from origin object |
||
559 | { |
||
560 | $originforcontact = $this->origin; |
||
561 | $originidforcontact = $this->origin_id; |
||
562 | if ($originforcontact == 'shipping') // shipment and order share the same contacts. If creating from shipment we take data of order |
||
563 | { |
||
564 | require_once DOL_DOCUMENT_ROOT . '/expedition/class/expedition.class.php'; |
||
565 | $exp = new Expedition($this->db); |
||
566 | $exp->fetch($this->origin_id); |
||
567 | $exp->fetchObjectLinked(); |
||
568 | if (count($exp->linkedObjectsIds['commande']) > 0) |
||
569 | { |
||
570 | foreach ($exp->linkedObjectsIds['commande'] as $key => $value) |
||
571 | { |
||
572 | $originforcontact = 'commande'; |
||
573 | if (is_object($value)) $originidforcontact = $value->id; |
||
574 | else $originidforcontact = $value; |
||
575 | break; // We take first one |
||
576 | } |
||
577 | } |
||
578 | } |
||
579 | |||
580 | $sqlcontact = "SELECT ctc.code, ctc.source, ec.fk_socpeople FROM ".MAIN_DB_PREFIX."element_contact as ec, ".MAIN_DB_PREFIX."c_type_contact as ctc"; |
||
581 | $sqlcontact.= " WHERE element_id = ".$originidforcontact." AND ec.fk_c_type_contact = ctc.rowid AND ctc.element = '".$originforcontact."'"; |
||
582 | |||
583 | $resqlcontact = $this->db->query($sqlcontact); |
||
584 | if ($resqlcontact) |
||
585 | { |
||
586 | while($objcontact = $this->db->fetch_object($resqlcontact)) |
||
587 | { |
||
588 | //print $objcontact->code.'-'.$objcontact->source.'-'.$objcontact->fk_socpeople."\n"; |
||
589 | $this->add_contact($objcontact->fk_socpeople, $objcontact->code, $objcontact->source); // May failed because of duplicate key or because code of contact type does not exists for new object |
||
590 | } |
||
591 | } |
||
592 | else dol_print_error($resqlcontact); |
||
593 | } |
||
594 | |||
595 | /* |
||
596 | * Insert lines of invoices, if not from template invoice, into database |
||
597 | */ |
||
598 | if (! $error && empty($this->fac_rec) && count($this->lines) && is_object($this->lines[0])) // If this->lines is array of InvoiceLines (preferred mode) |
||
599 | { |
||
600 | $fk_parent_line = 0; |
||
601 | |||
602 | dol_syslog("There is ".count($this->lines)." lines that are invoice lines objects"); |
||
603 | foreach ($this->lines as $i => $val) |
||
604 | { |
||
605 | $newinvoiceline=$this->lines[$i]; |
||
606 | $newinvoiceline->fk_facture=$this->id; |
||
607 | |||
608 | $newinvoiceline->origin = $this->lines[$i]->element; |
||
609 | $newinvoiceline->origin_id = $this->lines[$i]->id; |
||
610 | |||
611 | // Auto set date of service ? |
||
612 | if ($this->lines[$i]->date_start_fill == 1 && $originaldatewhen) // $originaldatewhen is defined when generating from recurring invoice only |
||
613 | { |
||
614 | $newinvoiceline->date_start = $originaldatewhen; |
||
615 | } |
||
616 | if ($this->lines[$i]->date_end_fill == 1 && $previousdaynextdatewhen) // $previousdaynextdatewhen is defined when generating from recurring invoice only |
||
617 | { |
||
618 | $newinvoiceline->date_end = $previousdaynextdatewhen; |
||
619 | } |
||
620 | |||
621 | if ($result >= 0) |
||
622 | { |
||
623 | // Reset fk_parent_line for no child products and special product |
||
624 | if (($newinvoiceline->product_type != 9 && empty($newinvoiceline->fk_parent_line)) || $newinvoiceline->product_type == 9) { |
||
625 | $fk_parent_line = 0; |
||
626 | } |
||
627 | |||
628 | $newinvoiceline->fk_parent_line=$fk_parent_line; |
||
629 | |||
630 | if($this->type === Facture::TYPE_REPLACEMENT && $newinvoiceline->fk_remise_except){ |
||
631 | $discount = new DiscountAbsolute($this->db); |
||
632 | $discount->fetch($newinvoiceline->fk_remise_except); |
||
633 | |||
634 | $discountId = $soc->set_remise_except($discount->amount_ht, $user, $discount->description, $discount->tva_tx); |
||
635 | $newinvoiceline->fk_remise_except = $discountId; |
||
636 | } |
||
637 | |||
638 | $result=$newinvoiceline->insert(); |
||
639 | |||
640 | // Defined the new fk_parent_line |
||
641 | if ($result > 0 && $newinvoiceline->product_type == 9) { |
||
642 | $fk_parent_line = $result; |
||
643 | } |
||
644 | } |
||
645 | if ($result < 0) |
||
646 | { |
||
647 | $this->error=$newinvoiceline->error; |
||
648 | $error++; |
||
649 | break; |
||
650 | } |
||
651 | } |
||
652 | } |
||
653 | elseif (! $error && empty($this->fac_rec)) // If this->lines is an array of invoice line arrays |
||
654 | { |
||
655 | $fk_parent_line = 0; |
||
656 | |||
657 | dol_syslog("There is ".count($this->lines)." lines that are array lines"); |
||
658 | |||
659 | foreach ($this->lines as $i => $val) |
||
660 | { |
||
661 | $line = $this->lines[$i]; |
||
662 | |||
663 | // Test and convert into object this->lines[$i]. When coming from REST API, we may still have an array |
||
664 | //if (! is_object($line)) $line=json_decode(json_encode($line), false); // convert recursively array into object. |
||
665 | if (! is_object($line)) $line = (object) $line; |
||
666 | |||
667 | if ($result >= 0) |
||
668 | { |
||
669 | // Reset fk_parent_line for no child products and special product |
||
670 | if (($line->product_type != 9 && empty($line->fk_parent_line)) || $line->product_type == 9) { |
||
671 | $fk_parent_line = 0; |
||
672 | } |
||
673 | |||
674 | // Complete vat rate with code |
||
675 | $vatrate = $line->tva_tx; |
||
676 | if ($line->vat_src_code && ! preg_match('/\(.*\)/', $vatrate)) $vatrate.=' ('.$line->vat_src_code.')'; |
||
677 | |||
678 | $result = $this->addline( |
||
679 | $line->desc, |
||
680 | $line->subprice, |
||
681 | $line->qty, |
||
682 | $vatrate, |
||
683 | $line->localtax1_tx, |
||
684 | $line->localtax2_tx, |
||
685 | $line->fk_product, |
||
686 | $line->remise_percent, |
||
687 | $line->date_start, |
||
688 | $line->date_end, |
||
689 | $line->fk_code_ventilation, |
||
690 | $line->info_bits, |
||
691 | $line->fk_remise_except, |
||
692 | 'HT', |
||
693 | 0, |
||
694 | $line->product_type, |
||
695 | $line->rang, |
||
696 | $line->special_code, |
||
697 | $this->element, |
||
698 | $line->id, |
||
699 | $fk_parent_line, |
||
700 | $line->fk_fournprice, |
||
701 | $line->pa_ht, |
||
702 | $line->label, |
||
703 | $line->array_options, |
||
704 | $line->situation_percent, |
||
705 | $line->fk_prev_id, |
||
706 | $line->fk_unit, |
||
707 | $line->pu_ht_devise |
||
708 | ); |
||
709 | if ($result < 0) |
||
710 | { |
||
711 | $this->error=$this->db->lasterror(); |
||
712 | dol_print_error($this->db); |
||
713 | $this->db->rollback(); |
||
714 | return -1; |
||
715 | } |
||
716 | |||
717 | // Defined the new fk_parent_line |
||
718 | if ($result > 0 && $line->product_type == 9) { |
||
719 | $fk_parent_line = $result; |
||
720 | } |
||
721 | } |
||
722 | } |
||
723 | } |
||
724 | |||
725 | /* |
||
726 | * Insert lines of predefined invoices |
||
727 | */ |
||
728 | if (! $error && $this->fac_rec > 0) |
||
729 | { |
||
730 | foreach ($_facrec->lines as $i => $val) |
||
731 | { |
||
732 | if ($_facrec->lines[$i]->fk_product) |
||
733 | { |
||
734 | $prod = new Product($this->db); |
||
735 | $res=$prod->fetch($_facrec->lines[$i]->fk_product); |
||
736 | } |
||
737 | |||
738 | // For line from template invoice, we use data from template invoice |
||
739 | /* |
||
740 | $tva_tx = get_default_tva($mysoc,$soc,$prod->id); |
||
741 | $tva_npr = get_default_npr($mysoc,$soc,$prod->id); |
||
742 | if (empty($tva_tx)) $tva_npr=0; |
||
743 | $localtax1_tx=get_localtax($tva_tx,1,$soc,$mysoc,$tva_npr); |
||
744 | $localtax2_tx=get_localtax($tva_tx,2,$soc,$mysoc,$tva_npr); |
||
745 | */ |
||
746 | $tva_tx = $_facrec->lines[$i]->tva_tx.($_facrec->lines[$i]->vat_src_code ? '('.$_facrec->lines[$i]->vat_src_code.')' : ''); |
||
747 | $tva_npr = $_facrec->lines[$i]->info_bits; |
||
748 | if (empty($tva_tx)) $tva_npr=0; |
||
749 | $localtax1_tx = $_facrec->lines[$i]->localtax1_tx; |
||
750 | $localtax2_tx = $_facrec->lines[$i]->localtax2_tx; |
||
751 | |||
752 | $result_insert = $this->addline( |
||
753 | $_facrec->lines[$i]->desc, |
||
754 | $_facrec->lines[$i]->subprice, |
||
755 | $_facrec->lines[$i]->qty, |
||
756 | $tva_tx, |
||
757 | $localtax1_tx, |
||
758 | $localtax2_tx, |
||
759 | $_facrec->lines[$i]->fk_product, |
||
760 | $_facrec->lines[$i]->remise_percent, |
||
761 | ($_facrec->lines[$i]->date_start_fill == 1 && $originaldatewhen)?$originaldatewhen:'', |
||
762 | ($_facrec->lines[$i]->date_end_fill == 1 && $previousdaynextdatewhen)?$previousdaynextdatewhen:'', |
||
763 | 0, |
||
764 | $tva_npr, |
||
765 | '', |
||
766 | 'HT', |
||
767 | 0, |
||
768 | $_facrec->lines[$i]->product_type, |
||
769 | $_facrec->lines[$i]->rang, |
||
770 | $_facrec->lines[$i]->special_code, |
||
771 | '', |
||
772 | 0, |
||
773 | 0, |
||
774 | null, |
||
775 | 0, |
||
776 | $_facrec->lines[$i]->label, |
||
777 | empty($_facrec->lines[$i]->array_options)?null:$_facrec->lines[$i]->array_options, |
||
778 | $_facrec->lines[$i]->situation_percent, |
||
779 | '', |
||
780 | $_facrec->lines[$i]->fk_unit, |
||
781 | $_facrec->lines[$i]->pu_ht_devise |
||
782 | ); |
||
783 | |||
784 | if ( $result_insert < 0) |
||
785 | { |
||
786 | $error++; |
||
787 | $this->error=$this->db->error(); |
||
788 | break; |
||
789 | } |
||
790 | } |
||
791 | } |
||
792 | |||
793 | if (! $error) |
||
794 | { |
||
795 | |||
796 | $result=$this->update_price(1); |
||
797 | if ($result > 0) |
||
798 | { |
||
799 | $action='create'; |
||
800 | |||
801 | // Actions on extra fields |
||
802 | if (! $error) |
||
803 | { |
||
804 | $result=$this->insertExtraFields(); |
||
805 | if ($result < 0) $error++; |
||
806 | } |
||
807 | |||
808 | if (! $error && ! $notrigger) |
||
809 | { |
||
810 | // Call trigger |
||
811 | $result=$this->call_trigger('BILL_CREATE',$user); |
||
812 | if ($result < 0) $error++; |
||
813 | // End call triggers |
||
814 | } |
||
815 | |||
816 | if (! $error) |
||
817 | { |
||
818 | $this->db->commit(); |
||
819 | return $this->id; |
||
820 | } |
||
821 | else |
||
822 | { |
||
823 | $this->db->rollback(); |
||
824 | return -4; |
||
825 | } |
||
826 | } |
||
827 | else |
||
828 | { |
||
829 | $this->error=$langs->trans('FailedToUpdatePrice'); |
||
830 | $this->db->rollback(); |
||
831 | return -3; |
||
832 | } |
||
833 | } |
||
834 | else |
||
835 | { |
||
836 | dol_syslog(get_class($this)."::create error ".$this->error, LOG_ERR); |
||
837 | $this->db->rollback(); |
||
838 | return -2; |
||
839 | } |
||
840 | } |
||
841 | else |
||
842 | { |
||
843 | $this->error=$this->db->error(); |
||
844 | $this->db->rollback(); |
||
845 | return -1; |
||
846 | } |
||
847 | } |
||
848 | |||
849 | |||
850 | /** |
||
851 | * Create a new invoice in database from current invoice |
||
852 | * |
||
853 | * @param User $user Object user that ask creation |
||
854 | * @param int $invertdetail Reverse sign of amounts for lines |
||
855 | * @return int <0 if KO, >0 if OK |
||
856 | */ |
||
857 | function createFromCurrent(User $user, $invertdetail=0) |
||
858 | { |
||
859 | global $conf; |
||
860 | |||
861 | // Charge facture source |
||
862 | $facture=new Facture($this->db); |
||
863 | |||
864 | // Retreive all extrafield |
||
865 | // fetch optionals attributes and labels |
||
866 | $this->fetch_optionals(); |
||
867 | |||
868 | if(!empty($this->array_options)){ |
||
869 | $facture->array_options = $this->array_options; |
||
870 | } |
||
871 | |||
872 | foreach($this->lines as &$line){ |
||
873 | $line->fetch_optionals();//fetch extrafields |
||
874 | } |
||
875 | |||
876 | $facture->fk_facture_source = $this->fk_facture_source; |
||
877 | $facture->type = $this->type; |
||
878 | $facture->socid = $this->socid; |
||
879 | $facture->date = $this->date; |
||
880 | $facture->date_pointoftax = $this->date_pointoftax; |
||
881 | $facture->note_public = $this->note_public; |
||
882 | $facture->note_private = $this->note_private; |
||
883 | $facture->ref_client = $this->ref_client; |
||
884 | $facture->modelpdf = $this->modelpdf; |
||
885 | $facture->fk_project = $this->fk_project; |
||
886 | $facture->cond_reglement_id = $this->cond_reglement_id; |
||
887 | $facture->mode_reglement_id = $this->mode_reglement_id; |
||
888 | $facture->remise_absolue = $this->remise_absolue; |
||
889 | $facture->remise_percent = $this->remise_percent; |
||
890 | |||
891 | $facture->origin = $this->origin; |
||
892 | $facture->origin_id = $this->origin_id; |
||
893 | |||
894 | $facture->lines = $this->lines; // Tableau des lignes de factures |
||
895 | $facture->products = $this->lines; // Tant que products encore utilise |
||
896 | $facture->situation_counter = $this->situation_counter; |
||
897 | $facture->situation_cycle_ref=$this->situation_cycle_ref; |
||
898 | $facture->situation_final = $this->situation_final; |
||
899 | |||
900 | // Loop on each line of new invoice |
||
901 | foreach($facture->lines as $i => $tmpline) |
||
902 | { |
||
903 | $facture->lines[$i]->fk_prev_id = $this->lines[$i]->rowid; |
||
904 | if ($invertdetail) |
||
905 | { |
||
906 | $facture->lines[$i]->subprice = -$facture->lines[$i]->subprice; |
||
907 | $facture->lines[$i]->total_ht = -$facture->lines[$i]->total_ht; |
||
908 | $facture->lines[$i]->total_tva = -$facture->lines[$i]->total_tva; |
||
909 | $facture->lines[$i]->total_localtax1 = -$facture->lines[$i]->total_localtax1; |
||
910 | $facture->lines[$i]->total_localtax2 = -$facture->lines[$i]->total_localtax2; |
||
911 | $facture->lines[$i]->total_ttc = -$facture->lines[$i]->total_ttc; |
||
912 | } |
||
913 | } |
||
914 | |||
915 | dol_syslog(get_class($this)."::createFromCurrent invertdetail=".$invertdetail." socid=".$this->socid." nboflines=".count($facture->lines)); |
||
916 | |||
917 | $facid = $facture->create($user); |
||
918 | if ($facid <= 0) |
||
919 | { |
||
920 | $this->error=$facture->error; |
||
921 | $this->errors=$facture->errors; |
||
922 | } |
||
923 | elseif ($this->type == self::TYPE_SITUATION && !empty($conf->global->INVOICE_USE_SITUATION)) |
||
924 | { |
||
925 | $this->fetchObjectLinked('', '', $facture->id, 'facture'); |
||
926 | |||
927 | foreach ($this->linkedObjectsIds as $typeObject => $Tfk_object) |
||
928 | { |
||
929 | foreach ($Tfk_object as $fk_object) |
||
930 | { |
||
931 | $facture->add_object_linked($typeObject, $fk_object); |
||
932 | } |
||
933 | } |
||
934 | |||
935 | $facture->add_object_linked('facture', $this->fk_facture_source); |
||
936 | } |
||
937 | |||
938 | return $facid; |
||
939 | } |
||
940 | |||
941 | |||
942 | /** |
||
943 | * Load an object from its id and create a new one in database |
||
944 | * |
||
945 | * @param int $socid Id of thirdparty |
||
946 | * @return int New id of clone |
||
947 | */ |
||
948 | function createFromClone($socid=0) |
||
949 | { |
||
950 | global $user,$hookmanager; |
||
951 | |||
952 | $error=0; |
||
953 | |||
954 | $this->context['createfromclone'] = 'createfromclone'; |
||
955 | |||
956 | $this->db->begin(); |
||
957 | |||
958 | // get extrafields so they will be clone |
||
959 | foreach($this->lines as $line) |
||
960 | $line->fetch_optionals($line->rowid); |
||
961 | |||
962 | // Load source object |
||
963 | $objFrom = clone $this; |
||
964 | |||
965 | |||
966 | |||
967 | // Change socid if needed |
||
968 | if (! empty($socid) && $socid != $this->socid) |
||
969 | { |
||
970 | $objsoc = new Societe($this->db); |
||
971 | |||
972 | if ($objsoc->fetch($socid)>0) |
||
973 | { |
||
974 | $this->socid = $objsoc->id; |
||
975 | $this->cond_reglement_id = (! empty($objsoc->cond_reglement_id) ? $objsoc->cond_reglement_id : 0); |
||
976 | $this->mode_reglement_id = (! empty($objsoc->mode_reglement_id) ? $objsoc->mode_reglement_id : 0); |
||
977 | $this->fk_project = ''; |
||
978 | $this->fk_delivery_address = ''; |
||
979 | } |
||
980 | |||
981 | // TODO Change product price if multi-prices |
||
982 | } |
||
983 | |||
984 | $this->id=0; |
||
985 | $this->statut= self::STATUS_DRAFT; |
||
986 | |||
987 | // Clear fields |
||
988 | $this->date = dol_now(); // Date of invoice is set to current date when cloning. // TODO Best is to ask date into confirm box |
||
989 | $this->user_author = $user->id; |
||
990 | $this->user_valid = ''; |
||
991 | $this->fk_facture_source = 0; |
||
992 | $this->date_creation = ''; |
||
993 | $this->date_modification = ''; |
||
994 | $this->date_validation = ''; |
||
995 | $this->ref_client = ''; |
||
996 | $this->close_code = ''; |
||
997 | $this->close_note = ''; |
||
998 | $this->products = $this->lines; // Tant que products encore utilise |
||
999 | |||
1000 | // Loop on each line of new invoice |
||
1001 | foreach($this->lines as $i => $line) |
||
1002 | { |
||
1003 | if (($this->lines[$i]->info_bits & 0x02) == 0x02) // We do not clone line of discounts |
||
1004 | { |
||
1005 | unset($this->lines[$i]); |
||
1006 | unset($this->products[$i]); // Tant que products encore utilise |
||
1007 | } |
||
1008 | } |
||
1009 | |||
1010 | // Create clone |
||
1011 | $result=$this->create($user); |
||
1012 | if ($result < 0) $error++; |
||
1013 | else { |
||
1014 | // copy internal contacts |
||
1015 | if ($this->copy_linked_contact($objFrom, 'internal') < 0) |
||
1016 | $error++; |
||
1017 | |||
1018 | // copy external contacts if same company |
||
1019 | elseif ($objFrom->socid == $this->socid) |
||
1020 | { |
||
1021 | if ($this->copy_linked_contact($objFrom, 'external') < 0) |
||
1022 | $error++; |
||
1023 | } |
||
1024 | } |
||
1025 | |||
1026 | if (! $error) |
||
1027 | { |
||
1028 | // Hook of thirdparty module |
||
1029 | if (is_object($hookmanager)) |
||
1030 | { |
||
1031 | $parameters=array('objFrom'=>$objFrom); |
||
1032 | $action=''; |
||
1033 | $reshook=$hookmanager->executeHooks('createFrom',$parameters,$this,$action); // Note that $action and $object may have been modified by some hooks |
||
1034 | if ($reshook < 0) $error++; |
||
1035 | } |
||
1036 | } |
||
1037 | |||
1038 | unset($this->context['createfromclone']); |
||
1039 | |||
1040 | // End |
||
1041 | if (! $error) |
||
1042 | { |
||
1043 | $this->db->commit(); |
||
1044 | return $this->id; |
||
1045 | } |
||
1046 | else |
||
1047 | { |
||
1048 | $this->db->rollback(); |
||
1049 | return -1; |
||
1050 | } |
||
1051 | } |
||
1052 | |||
1053 | /** |
||
1054 | * Load an object from an order and create a new invoice into database |
||
1055 | * |
||
1056 | * @param Object $object Object source |
||
1057 | * @param User $user Object user |
||
1058 | * @return int <0 if KO, 0 if nothing done, 1 if OK |
||
1059 | */ |
||
1060 | function createFromOrder($object, User $user) |
||
1061 | { |
||
1062 | global $hookmanager; |
||
1063 | |||
1064 | $error=0; |
||
1065 | |||
1066 | // Closed order |
||
1067 | $this->date = dol_now(); |
||
1068 | $this->source = 0; |
||
1069 | |||
1070 | $num=count($object->lines); |
||
1071 | for ($i = 0; $i < $num; $i++) |
||
1072 | { |
||
1073 | $line = new FactureLigne($this->db); |
||
1074 | |||
1075 | $line->libelle = $object->lines[$i]->libelle; |
||
1076 | $line->label = $object->lines[$i]->label; |
||
1077 | $line->desc = $object->lines[$i]->desc; |
||
1078 | $line->subprice = $object->lines[$i]->subprice; |
||
1079 | $line->total_ht = $object->lines[$i]->total_ht; |
||
1080 | $line->total_tva = $object->lines[$i]->total_tva; |
||
1081 | $line->total_localtax1 = $object->lines[$i]->total_localtax1; |
||
1082 | $line->total_localtax2 = $object->lines[$i]->total_localtax2; |
||
1083 | $line->total_ttc = $object->lines[$i]->total_ttc; |
||
1084 | $line->vat_src_code = $object->lines[$i]->vat_src_code; |
||
1085 | $line->tva_tx = $object->lines[$i]->tva_tx; |
||
1086 | $line->localtax1_tx = $object->lines[$i]->localtax1_tx; |
||
1087 | $line->localtax2_tx = $object->lines[$i]->localtax2_tx; |
||
1088 | $line->qty = $object->lines[$i]->qty; |
||
1089 | $line->fk_remise_except = $object->lines[$i]->fk_remise_except; |
||
1090 | $line->remise_percent = $object->lines[$i]->remise_percent; |
||
1091 | $line->fk_product = $object->lines[$i]->fk_product; |
||
1092 | $line->info_bits = $object->lines[$i]->info_bits; |
||
1093 | $line->product_type = $object->lines[$i]->product_type; |
||
1094 | $line->rang = $object->lines[$i]->rang; |
||
1095 | $line->special_code = $object->lines[$i]->special_code; |
||
1096 | $line->fk_parent_line = $object->lines[$i]->fk_parent_line; |
||
1097 | $line->fk_unit = $object->lines[$i]->fk_unit; |
||
1098 | $line->date_start = $object->lines[$i]->date_start; |
||
1099 | $line->date_end = $object->lines[$i]->date_end; |
||
1100 | |||
1101 | $line->fk_fournprice = $object->lines[$i]->fk_fournprice; |
||
1102 | $marginInfos = getMarginInfos($object->lines[$i]->subprice, $object->lines[$i]->remise_percent, $object->lines[$i]->tva_tx, $object->lines[$i]->localtax1_tx, $object->lines[$i]->localtax2_tx, $object->lines[$i]->fk_fournprice, $object->lines[$i]->pa_ht); |
||
1103 | $line->pa_ht = $marginInfos[0]; |
||
1104 | |||
1105 | // get extrafields from original line |
||
1106 | $object->lines[$i]->fetch_optionals(); |
||
1107 | foreach($object->lines[$i]->array_options as $options_key => $value) |
||
1108 | $line->array_options[$options_key] = $value; |
||
1109 | |||
1110 | $this->lines[$i] = $line; |
||
1111 | } |
||
1112 | |||
1113 | $this->socid = $object->socid; |
||
1114 | $this->fk_project = $object->fk_project; |
||
1115 | $this->cond_reglement_id = $object->cond_reglement_id; |
||
1116 | $this->mode_reglement_id = $object->mode_reglement_id; |
||
1117 | $this->availability_id = $object->availability_id; |
||
1118 | $this->demand_reason_id = $object->demand_reason_id; |
||
1119 | $this->date_livraison = $object->date_livraison; |
||
1120 | $this->fk_delivery_address = $object->fk_delivery_address; |
||
1121 | $this->contact_id = $object->contactid; |
||
1122 | $this->ref_client = $object->ref_client; |
||
1123 | $this->note_private = $object->note_private; |
||
1124 | $this->note_public = $object->note_public; |
||
1125 | |||
1126 | $this->origin = $object->element; |
||
1127 | $this->origin_id = $object->id; |
||
1128 | |||
1129 | // get extrafields from original line |
||
1130 | $object->fetch_optionals($object->id); |
||
1131 | foreach($object->array_options as $options_key => $value) |
||
1132 | $this->array_options[$options_key] = $value; |
||
1133 | |||
1134 | // Possibility to add external linked objects with hooks |
||
1135 | $this->linked_objects[$this->origin] = $this->origin_id; |
||
1136 | if (! empty($object->other_linked_objects) && is_array($object->other_linked_objects)) |
||
1137 | { |
||
1138 | $this->linked_objects = array_merge($this->linked_objects, $object->other_linked_objects); |
||
1139 | } |
||
1140 | |||
1141 | $ret = $this->create($user); |
||
1142 | |||
1143 | if ($ret > 0) |
||
1144 | { |
||
1145 | // Actions hooked (by external module) |
||
1146 | $hookmanager->initHooks(array('invoicedao')); |
||
1147 | |||
1148 | $parameters=array('objFrom'=>$object); |
||
1149 | $action=''; |
||
1150 | $reshook=$hookmanager->executeHooks('createFrom',$parameters,$this,$action); // Note that $action and $object may have been modified by some hooks |
||
1151 | if ($reshook < 0) $error++; |
||
1152 | |||
1153 | if (! $error) |
||
1154 | { |
||
1155 | return 1; |
||
1156 | } |
||
1157 | else return -1; |
||
1158 | } |
||
1159 | else return -1; |
||
1160 | } |
||
1161 | |||
1162 | /** |
||
1163 | * Return link to download file from a direct external access |
||
1164 | * |
||
1165 | * @param int $withpicto Add download picto into link |
||
1166 | * @return string HTML link to file |
||
1167 | */ |
||
1168 | function getDirectExternalLink($withpicto=0) |
||
1169 | { |
||
1170 | global $dolibarr_main_url_root; |
||
1171 | |||
1172 | // Define $urlwithroot |
||
1173 | $urlwithouturlroot=preg_replace('/'.preg_quote(DOL_URL_ROOT,'/').'$/i','',trim($dolibarr_main_url_root)); |
||
1174 | $urlwithroot=$urlwithouturlroot.DOL_URL_ROOT; // This is to use external domain name found into config file |
||
1175 | //$urlwithroot=DOL_MAIN_URL_ROOT; // This is to use same domain name than current |
||
1176 | |||
1177 | // TODO Read into ecmfile table to get entry and hash exists (PS: If not found, add it) |
||
1178 | include_once DOL_DOCUMENT_ROOT.'/ecm/class/ecmfiles.class.php'; |
||
1179 | $ecmfile=new EcmFiles($this->db); |
||
1180 | //$result = $ecmfile->get(); |
||
1181 | |||
1182 | $hashp='todo'; |
||
1183 | return '<a href="'.$urlwithroot.'/document.php?modulepart=invoice&hashp='.$hashp.'" target="_download" rel="noindex, nofollow">'.$this->ref.'</a>'; |
||
1184 | } |
||
1185 | |||
1186 | /** |
||
1187 | * Return clicable link of object (with eventually picto) |
||
1188 | * |
||
1189 | * @param int $withpicto Add picto into link |
||
1190 | * @param string $option Where point the link |
||
1191 | * @param int $max Maxlength of ref |
||
1192 | * @param int $short 1=Return just URL |
||
1193 | * @param string $moretitle Add more text to title tooltip |
||
1194 | * @param int $notooltip 1=Disable tooltip |
||
1195 | * @param int $addlinktonotes 1=Add link to notes |
||
1196 | * @param int $save_lastsearch_value -1=Auto, 0=No save of lastsearch_values when clicking, 1=Save lastsearch_values whenclicking |
||
1197 | * @return string String with URL |
||
1198 | */ |
||
1199 | function getNomUrl($withpicto=0, $option='', $max=0, $short=0, $moretitle='', $notooltip=0, $addlinktonotes=0, $save_lastsearch_value=-1) |
||
1200 | { |
||
1201 | global $langs, $conf, $user, $form; |
||
1202 | |||
1203 | if (! empty($conf->dol_no_mouse_hover)) $notooltip=1; // Force disable tooltips |
||
1204 | |||
1205 | $result=''; |
||
1206 | |||
1207 | if ($option == 'withdraw') $url = DOL_URL_ROOT.'/compta/facture/prelevement.php?facid='.$this->id; |
||
1208 | else $url = DOL_URL_ROOT.'/compta/facture/card.php?facid='.$this->id; |
||
1209 | |||
1210 | if (!$user->rights->facture->lire) |
||
1211 | $option = 'nolink'; |
||
1212 | |||
1213 | if ($option !== 'nolink') |
||
1214 | { |
||
1215 | // Add param to save lastsearch_values or not |
||
1216 | $add_save_lastsearch_values=($save_lastsearch_value == 1 ? 1 : 0); |
||
1217 | if ($save_lastsearch_value == -1 && preg_match('/list\.php/',$_SERVER["PHP_SELF"])) $add_save_lastsearch_values=1; |
||
1218 | if ($add_save_lastsearch_values) $url.='&save_lastsearch_values=1'; |
||
1219 | } |
||
1220 | |||
1221 | if ($short) return $url; |
||
1222 | |||
1223 | $picto='bill'; |
||
1224 | if ($this->type == self::TYPE_REPLACEMENT) $picto.='r'; // Replacement invoice |
||
1225 | if ($this->type == self::TYPE_CREDIT_NOTE) $picto.='a'; // Credit note |
||
1226 | if ($this->type == self::TYPE_DEPOSIT) $picto.='d'; // Deposit invoice |
||
1227 | $label=''; |
||
1228 | |||
1229 | if ($user->rights->facture->lire) { |
||
1230 | $label = '<u>' . $langs->trans("ShowInvoice") . '</u>'; |
||
1231 | if ($this->type == self::TYPE_REPLACEMENT) $label='<u>' . $langs->transnoentitiesnoconv("ShowInvoiceReplace") . '</u>'; |
||
1232 | if ($this->type == self::TYPE_CREDIT_NOTE) $label='<u>' . $langs->transnoentitiesnoconv("ShowInvoiceAvoir") . '</u>'; |
||
1233 | if ($this->type == self::TYPE_DEPOSIT) $label='<u>' . $langs->transnoentitiesnoconv("ShowInvoiceDeposit") . '</u>'; |
||
1234 | if ($this->type == self::TYPE_SITUATION) $label='<u>' . $langs->transnoentitiesnoconv("ShowInvoiceSituation") . '</u>'; |
||
1235 | if (! empty($this->ref)) |
||
1236 | $label .= '<br><b>'.$langs->trans('Ref') . ':</b> ' . $this->ref; |
||
1237 | if (! empty($this->ref_client)) |
||
1238 | $label .= '<br><b>' . $langs->trans('RefCustomer') . ':</b> ' . $this->ref_client; |
||
1239 | if (! empty($this->total_ht)) |
||
1240 | $label.= '<br><b>' . $langs->trans('AmountHT') . ':</b> ' . price($this->total_ht, 0, $langs, 0, -1, -1, $conf->currency); |
||
1241 | if (! empty($this->total_tva)) |
||
1242 | $label.= '<br><b>' . $langs->trans('VAT') . ':</b> ' . price($this->total_tva, 0, $langs, 0, -1, -1, $conf->currency); |
||
1243 | if (! empty($this->total_localtax1) && $this->total_localtax1 != 0) // We keep test != 0 because $this->total_localtax1 can be '0.00000000' |
||
1244 | $label.= '<br><b>' . $langs->trans('LT1') . ':</b> ' . price($this->total_localtax1, 0, $langs, 0, -1, -1, $conf->currency); |
||
1245 | if (! empty($this->total_localtax2) && $this->total_localtax2 != 0) |
||
1246 | $label.= '<br><b>' . $langs->trans('LT2') . ':</b> ' . price($this->total_localtax2, 0, $langs, 0, -1, -1, $conf->currency); |
||
1247 | if (! empty($this->total_ttc)) |
||
1248 | $label.= '<br><b>' . $langs->trans('AmountTTC') . ':</b> ' . price($this->total_ttc, 0, $langs, 0, -1, -1, $conf->currency); |
||
1249 | if ($moretitle) $label.=' - '.$moretitle; |
||
1250 | } |
||
1251 | |||
1252 | $linkclose=''; |
||
1253 | if (empty($notooltip) && $user->rights->facture->lire) |
||
1254 | { |
||
1255 | if (! empty($conf->global->MAIN_OPTIMIZEFORTEXTBROWSER)) |
||
1256 | { |
||
1257 | $label=$langs->trans("ShowInvoice"); |
||
1258 | $linkclose.=' alt="'.dol_escape_htmltag($label, 1).'"'; |
||
1259 | } |
||
1260 | $linkclose.= ' title="'.dol_escape_htmltag($label, 1).'"'; |
||
1261 | $linkclose.=' class="classfortooltip"'; |
||
1262 | } |
||
1263 | |||
1264 | $linkstart='<a href="'.$url.'"'; |
||
1265 | $linkstart.=$linkclose.'>'; |
||
1266 | $linkend='</a>'; |
||
1267 | |||
1268 | if ($option == 'nolink') { |
||
1269 | $linkstart = ''; |
||
1270 | $linkend = ''; |
||
1271 | } |
||
1272 | |||
1273 | $result .= $linkstart; |
||
1274 | if ($withpicto) $result.=img_object(($notooltip?'':$label), $picto, ($notooltip?(($withpicto != 2) ? 'class="paddingright"' : ''):'class="'.(($withpicto != 2) ? 'paddingright ' : '').'classfortooltip"'), 0, 0, $notooltip?0:1); |
||
1275 | if ($withpicto != 2) $result.= ($max?dol_trunc($this->ref,$max):$this->ref); |
||
1276 | $result .= $linkend; |
||
1277 | |||
1278 | if ($addlinktonotes) |
||
1279 | { |
||
1280 | $txttoshow=($user->socid > 0 ? $this->note_public : $this->note_private); |
||
1281 | if ($txttoshow) |
||
1282 | { |
||
1283 | $notetoshow=$langs->trans("ViewPrivateNote").':<br>'.dol_string_nohtmltag($txttoshow,1); |
||
1284 | $result.=' <span class="note inline-block">'; |
||
1285 | $result.='<a href="'.DOL_URL_ROOT.'/compta/facture/note.php?id='.$this->id.'" class="classfortooltip" title="'.dol_escape_htmltag($notetoshow).'">'; |
||
1286 | $result.=img_picto('','note'); |
||
1287 | $result.='</a>'; |
||
1288 | //$result.=img_picto($langs->trans("ViewNote"),'object_generic'); |
||
1289 | //$result.='</a>'; |
||
1290 | $result.='</span>'; |
||
1291 | } |
||
1292 | } |
||
1293 | |||
1294 | return $result; |
||
1295 | } |
||
1296 | |||
1297 | /** |
||
1298 | * Get object and lines from database |
||
1299 | * |
||
1300 | * @param int $rowid Id of object to load |
||
1301 | * @param string $ref Reference of invoice |
||
1302 | * @param string $ref_ext External reference of invoice |
||
1303 | * @param int $ref_int Internal reference of other object |
||
1304 | * @param bool $fetch_situation Fetch the previous and next situation in $tab_previous_situation_invoice and $tab_next_situation_invoice |
||
1305 | * @return int >0 if OK, <0 if KO, 0 if not found |
||
1306 | */ |
||
1307 | function fetch($rowid, $ref='', $ref_ext='', $ref_int='', $fetch_situation=false) |
||
1308 | { |
||
1309 | global $conf; |
||
1310 | |||
1311 | if (empty($rowid) && empty($ref) && empty($ref_ext) && empty($ref_int)) return -1; |
||
1312 | |||
1313 | $sql = 'SELECT f.rowid,f.entity,f.ref,f.ref_client,f.ref_ext,f.ref_int,f.type,f.fk_soc,f.amount'; |
||
1314 | $sql.= ', f.tva, f.localtax1, f.localtax2, f.total, f.total_ttc, f.revenuestamp'; |
||
1315 | $sql.= ', f.remise_percent, f.remise_absolue, f.remise'; |
||
1316 | $sql.= ', f.datef as df, f.date_pointoftax'; |
||
1317 | $sql.= ', f.date_lim_reglement as dlr'; |
||
1318 | $sql.= ', f.datec as datec'; |
||
1319 | $sql.= ', f.date_valid as datev'; |
||
1320 | $sql.= ', f.tms as datem'; |
||
1321 | $sql.= ', f.note_private, f.note_public, f.fk_statut, f.paye, f.close_code, f.close_note, f.fk_user_author, f.fk_user_valid, f.model_pdf, f.last_main_doc'; |
||
1322 | $sql.= ', f.fk_facture_source'; |
||
1323 | $sql.= ', f.fk_mode_reglement, f.fk_cond_reglement, f.fk_projet, f.extraparams'; |
||
1324 | $sql.= ', f.situation_cycle_ref, f.situation_counter, f.situation_final'; |
||
1325 | $sql.= ', f.fk_account'; |
||
1326 | $sql.= ", f.fk_multicurrency, f.multicurrency_code, f.multicurrency_tx, f.multicurrency_total_ht, f.multicurrency_total_tva, f.multicurrency_total_ttc"; |
||
1327 | $sql.= ', p.code as mode_reglement_code, p.libelle as mode_reglement_libelle'; |
||
1328 | $sql.= ', c.code as cond_reglement_code, c.libelle as cond_reglement_libelle, c.libelle_facture as cond_reglement_libelle_doc'; |
||
1329 | $sql.= ', f.fk_incoterms, f.location_incoterms'; |
||
1330 | $sql.= ", i.libelle as libelle_incoterms"; |
||
1331 | $sql.= ' FROM '.MAIN_DB_PREFIX.'facture as f'; |
||
1332 | $sql.= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_payment_term as c ON f.fk_cond_reglement = c.rowid'; |
||
1333 | $sql.= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_paiement as p ON f.fk_mode_reglement = p.id'; |
||
1334 | $sql.= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_incoterms as i ON f.fk_incoterms = i.rowid'; |
||
1335 | |||
1336 | if ($rowid) $sql.= " WHERE f.rowid=".$rowid; |
||
1337 | else $sql.= ' WHERE f.entity IN ('.getEntity('invoice').')'; // Dont't use entity if you use rowid |
||
1338 | |||
1339 | if ($ref) $sql.= " AND f.ref='".$this->db->escape($ref)."'"; |
||
1340 | if ($ref_ext) $sql.= " AND f.ref_ext='".$this->db->escape($ref_ext)."'"; |
||
1341 | if ($ref_int) $sql.= " AND f.ref_int='".$this->db->escape($ref_int)."'"; |
||
1342 | |||
1343 | dol_syslog(get_class($this)."::fetch", LOG_DEBUG); |
||
1344 | $result = $this->db->query($sql); |
||
1345 | if ($result) |
||
1346 | { |
||
1347 | if ($this->db->num_rows($result)) |
||
1348 | { |
||
1349 | $obj = $this->db->fetch_object($result); |
||
1350 | |||
1351 | $this->id = $obj->rowid; |
||
1352 | $this->entity = $obj->entity; |
||
1353 | |||
1354 | $this->ref = $obj->ref; |
||
1355 | $this->ref_client = $obj->ref_client; |
||
1356 | $this->ref_ext = $obj->ref_ext; |
||
1357 | $this->ref_int = $obj->ref_int; |
||
1358 | $this->type = $obj->type; |
||
1359 | $this->date = $this->db->jdate($obj->df); |
||
1360 | $this->date_pointoftax = $this->db->jdate($obj->date_pointoftax); |
||
1361 | $this->date_creation = $this->db->jdate($obj->datec); |
||
1362 | $this->date_validation = $this->db->jdate($obj->datev); |
||
1363 | $this->date_modification = $this->db->jdate($obj->datem); |
||
1364 | $this->datem = $this->db->jdate($obj->datem); |
||
1365 | $this->remise_percent = $obj->remise_percent; |
||
1366 | $this->remise_absolue = $obj->remise_absolue; |
||
1367 | $this->total_ht = $obj->total; |
||
1368 | $this->total_tva = $obj->tva; |
||
1369 | $this->total_localtax1 = $obj->localtax1; |
||
1370 | $this->total_localtax2 = $obj->localtax2; |
||
1371 | $this->total_ttc = $obj->total_ttc; |
||
1372 | $this->revenuestamp = $obj->revenuestamp; |
||
1373 | $this->paye = $obj->paye; |
||
1374 | $this->close_code = $obj->close_code; |
||
1375 | $this->close_note = $obj->close_note; |
||
1376 | $this->socid = $obj->fk_soc; |
||
1377 | $this->statut = $obj->fk_statut; |
||
1378 | $this->date_lim_reglement = $this->db->jdate($obj->dlr); |
||
1379 | $this->mode_reglement_id = $obj->fk_mode_reglement; |
||
1380 | $this->mode_reglement_code = $obj->mode_reglement_code; |
||
1381 | $this->mode_reglement = $obj->mode_reglement_libelle; |
||
1382 | $this->cond_reglement_id = $obj->fk_cond_reglement; |
||
1383 | $this->cond_reglement_code = $obj->cond_reglement_code; |
||
1384 | $this->cond_reglement = $obj->cond_reglement_libelle; |
||
1385 | $this->cond_reglement_doc = $obj->cond_reglement_libelle_doc; |
||
1386 | $this->fk_account = ($obj->fk_account>0)?$obj->fk_account:null; |
||
1387 | $this->fk_project = $obj->fk_projet; |
||
1388 | $this->fk_facture_source = $obj->fk_facture_source; |
||
1389 | $this->note = $obj->note_private; // deprecated |
||
1390 | $this->note_private = $obj->note_private; |
||
1391 | $this->note_public = $obj->note_public; |
||
1392 | $this->user_author = $obj->fk_user_author; |
||
1393 | $this->user_valid = $obj->fk_user_valid; |
||
1394 | $this->modelpdf = $obj->model_pdf; |
||
1395 | $this->last_main_doc = $obj->last_main_doc; |
||
1396 | $this->situation_cycle_ref = $obj->situation_cycle_ref; |
||
1397 | $this->situation_counter = $obj->situation_counter; |
||
1398 | $this->situation_final = $obj->situation_final; |
||
1399 | $this->extraparams = (array) json_decode($obj->extraparams, true); |
||
1400 | |||
1401 | //Incoterms |
||
1402 | $this->fk_incoterms = $obj->fk_incoterms; |
||
1403 | $this->location_incoterms = $obj->location_incoterms; |
||
1404 | $this->libelle_incoterms = $obj->libelle_incoterms; |
||
1405 | |||
1406 | // Multicurrency |
||
1407 | $this->fk_multicurrency = $obj->fk_multicurrency; |
||
1408 | $this->multicurrency_code = $obj->multicurrency_code; |
||
1409 | $this->multicurrency_tx = $obj->multicurrency_tx; |
||
1410 | $this->multicurrency_total_ht = $obj->multicurrency_total_ht; |
||
1411 | $this->multicurrency_total_tva = $obj->multicurrency_total_tva; |
||
1412 | $this->multicurrency_total_ttc = $obj->multicurrency_total_ttc; |
||
1413 | |||
1414 | if (($this->type == self::TYPE_SITUATION || ($this->type == self::TYPE_CREDIT_NOTE && $this->situation_cycle_ref > 0)) && $fetch_situation) |
||
1415 | { |
||
1416 | $this->fetchPreviousNextSituationInvoice(); |
||
1417 | } |
||
1418 | |||
1419 | if ($this->statut == self::STATUS_DRAFT) $this->brouillon = 1; |
||
1420 | |||
1421 | // Retreive all extrafield |
||
1422 | // fetch optionals attributes and labels |
||
1423 | $this->fetch_optionals(); |
||
1424 | |||
1425 | /* |
||
1426 | * Lines |
||
1427 | */ |
||
1428 | |||
1429 | $this->lines = array(); |
||
1430 | |||
1431 | $result=$this->fetch_lines(); |
||
1432 | if ($result < 0) |
||
1433 | { |
||
1434 | $this->error=$this->db->error(); |
||
1435 | return -3; |
||
1436 | } |
||
1437 | return 1; |
||
1438 | } |
||
1439 | else |
||
1440 | { |
||
1441 | $this->error='Invoice with id='.$rowid.' or ref='.$ref.' or ref_ext='.$ref_ext.' not found'; |
||
1442 | dol_syslog(get_class($this)."::fetch Error ".$this->error, LOG_ERR); |
||
1443 | return 0; |
||
1444 | } |
||
1445 | } |
||
1446 | else |
||
1447 | { |
||
1448 | $this->error=$this->db->error(); |
||
1449 | return -1; |
||
1450 | } |
||
1451 | } |
||
1452 | |||
1453 | |||
1454 | // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps |
||
1455 | /** |
||
1456 | * Load all detailed lines into this->lines |
||
1457 | * |
||
1458 | * @return int 1 if OK, < 0 if KO |
||
1459 | */ |
||
1460 | function fetch_lines() |
||
1461 | { |
||
1462 | // phpcs:enable |
||
1463 | $this->lines=array(); |
||
1464 | |||
1465 | $sql = 'SELECT l.rowid, l.fk_facture, l.fk_product, l.fk_parent_line, l.label as custom_label, l.description, l.product_type, l.price, l.qty, l.vat_src_code, l.tva_tx,'; |
||
1466 | $sql.= ' l.situation_percent, l.fk_prev_id,'; |
||
1467 | $sql.= ' l.localtax1_tx, l.localtax2_tx, l.localtax1_type, l.localtax2_type, l.remise_percent, l.fk_remise_except, l.subprice,'; |
||
1468 | $sql.= ' l.rang, l.special_code,'; |
||
1469 | $sql.= ' l.date_start as date_start, l.date_end as date_end,'; |
||
1470 | $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_product_fournisseur_price as fk_fournprice, l.buy_price_ht as pa_ht,'; |
||
1471 | $sql.= ' l.fk_unit,'; |
||
1472 | $sql.= ' l.fk_multicurrency, l.multicurrency_code, l.multicurrency_subprice, l.multicurrency_total_ht, l.multicurrency_total_tva, l.multicurrency_total_ttc,'; |
||
1473 | $sql.= ' p.ref as product_ref, p.fk_product_type as fk_product_type, p.label as product_label, p.description as product_desc'; |
||
1474 | $sql.= ' FROM '.MAIN_DB_PREFIX.'facturedet as l'; |
||
1475 | $sql.= ' LEFT JOIN '.MAIN_DB_PREFIX.'product as p ON l.fk_product = p.rowid'; |
||
1476 | $sql.= ' WHERE l.fk_facture = '.$this->id; |
||
1477 | $sql.= ' ORDER BY l.rang, l.rowid'; |
||
1478 | |||
1479 | dol_syslog(get_class($this).'::fetch_lines', LOG_DEBUG); |
||
1480 | $result = $this->db->query($sql); |
||
1481 | if ($result) |
||
1482 | { |
||
1483 | $num = $this->db->num_rows($result); |
||
1484 | $i = 0; |
||
1485 | while ($i < $num) |
||
1486 | { |
||
1487 | $objp = $this->db->fetch_object($result); |
||
1488 | $line = new FactureLigne($this->db); |
||
1489 | |||
1490 | $line->id = $objp->rowid; |
||
1491 | $line->rowid = $objp->rowid; // deprecated |
||
1492 | $line->fk_facture = $objp->fk_facture; |
||
1493 | $line->label = $objp->custom_label; // deprecated |
||
1494 | $line->desc = $objp->description; // Description line |
||
1495 | $line->description = $objp->description; // Description line |
||
1496 | $line->product_type = $objp->product_type; // Type of line |
||
1497 | $line->ref = $objp->product_ref; // Ref product |
||
1498 | $line->product_ref = $objp->product_ref; // Ref product |
||
1499 | $line->libelle = $objp->product_label; // TODO deprecated |
||
1500 | $line->product_label = $objp->product_label; // Label product |
||
1501 | $line->product_desc = $objp->product_desc; // Description product |
||
1502 | $line->fk_product_type = $objp->fk_product_type; // Type of product |
||
1503 | $line->qty = $objp->qty; |
||
1504 | $line->subprice = $objp->subprice; |
||
1505 | |||
1506 | $line->vat_src_code = $objp->vat_src_code; |
||
1507 | $line->tva_tx = $objp->tva_tx; |
||
1508 | $line->localtax1_tx = $objp->localtax1_tx; |
||
1509 | $line->localtax2_tx = $objp->localtax2_tx; |
||
1510 | $line->localtax1_type = $objp->localtax1_type; |
||
1511 | $line->localtax2_type = $objp->localtax2_type; |
||
1512 | $line->remise_percent = $objp->remise_percent; |
||
1513 | $line->fk_remise_except = $objp->fk_remise_except; |
||
1514 | $line->fk_product = $objp->fk_product; |
||
1515 | $line->date_start = $this->db->jdate($objp->date_start); |
||
1516 | $line->date_end = $this->db->jdate($objp->date_end); |
||
1517 | $line->date_start = $this->db->jdate($objp->date_start); |
||
1518 | $line->date_end = $this->db->jdate($objp->date_end); |
||
1519 | $line->info_bits = $objp->info_bits; |
||
1520 | $line->total_ht = $objp->total_ht; |
||
1521 | $line->total_tva = $objp->total_tva; |
||
1522 | $line->total_localtax1 = $objp->total_localtax1; |
||
1523 | $line->total_localtax2 = $objp->total_localtax2; |
||
1524 | $line->total_ttc = $objp->total_ttc; |
||
1525 | $line->code_ventilation = $objp->fk_code_ventilation; |
||
1526 | $line->fk_fournprice = $objp->fk_fournprice; |
||
1527 | $marginInfos = getMarginInfos($objp->subprice, $objp->remise_percent, $objp->tva_tx, $objp->localtax1_tx, $objp->localtax2_tx, $line->fk_fournprice, $objp->pa_ht); |
||
1528 | $line->pa_ht = $marginInfos[0]; |
||
1529 | $line->marge_tx = $marginInfos[1]; |
||
1530 | $line->marque_tx = $marginInfos[2]; |
||
1531 | $line->rang = $objp->rang; |
||
1532 | $line->special_code = $objp->special_code; |
||
1533 | $line->fk_parent_line = $objp->fk_parent_line; |
||
1534 | $line->situation_percent= $objp->situation_percent; |
||
1535 | $line->fk_prev_id = $objp->fk_prev_id; |
||
1536 | $line->fk_unit = $objp->fk_unit; |
||
1537 | |||
1538 | // Accountancy |
||
1539 | $line->fk_accounting_account = $objp->fk_code_ventilation; |
||
1540 | |||
1541 | // Multicurrency |
||
1542 | $line->fk_multicurrency = $objp->fk_multicurrency; |
||
1543 | $line->multicurrency_code = $objp->multicurrency_code; |
||
1544 | $line->multicurrency_subprice = $objp->multicurrency_subprice; |
||
1545 | $line->multicurrency_total_ht = $objp->multicurrency_total_ht; |
||
1546 | $line->multicurrency_total_tva = $objp->multicurrency_total_tva; |
||
1547 | $line->multicurrency_total_ttc = $objp->multicurrency_total_ttc; |
||
1548 | |||
1549 | $line->fetch_optionals(); |
||
1550 | |||
1551 | $this->lines[$i] = $line; |
||
1552 | |||
1553 | $i++; |
||
1554 | } |
||
1555 | $this->db->free($result); |
||
1556 | return 1; |
||
1557 | } |
||
1558 | else |
||
1559 | { |
||
1560 | $this->error=$this->db->error(); |
||
1561 | return -3; |
||
1562 | } |
||
1563 | } |
||
1564 | |||
1565 | /** |
||
1566 | * Fetch previous and next situations invoices |
||
1567 | * |
||
1568 | * @return void |
||
1569 | */ |
||
1570 | function fetchPreviousNextSituationInvoice() |
||
1571 | { |
||
1572 | global $conf; |
||
1573 | |||
1574 | $this->tab_previous_situation_invoice = array(); |
||
1575 | $this->tab_next_situation_invoice = array(); |
||
1576 | |||
1577 | $sql = 'SELECT rowid, situation_counter FROM '.MAIN_DB_PREFIX.'facture WHERE rowid <> '.$this->id.' AND entity = '.$conf->entity.' AND situation_cycle_ref = '.(int) $this->situation_cycle_ref.' ORDER BY situation_counter ASC'; |
||
1578 | |||
1579 | dol_syslog(get_class($this).'::fetchPreviousNextSituationInvoice ', LOG_DEBUG); |
||
1580 | $result = $this->db->query($sql); |
||
1581 | if ($result && $this->db->num_rows($result) > 0) |
||
1582 | { |
||
1583 | while ($objp = $this->db->fetch_object($result)) |
||
1584 | { |
||
1585 | $invoice = new Facture($this->db); |
||
1586 | if ($invoice->fetch($objp->rowid) > 0) |
||
1587 | { |
||
1588 | if ($objp->situation_counter < $this->situation_counter |
||
1589 | || ($objp->situation_counter == $this->situation_counter && $objp->rowid < $this->id) // This case appear when there are credit notes |
||
1590 | ) |
||
1591 | { |
||
1592 | $this->tab_previous_situation_invoice[] = $invoice; |
||
1593 | } |
||
1594 | else |
||
1595 | { |
||
1596 | $this->tab_next_situation_invoice[] = $invoice; |
||
1597 | } |
||
1598 | } |
||
1599 | } |
||
1600 | } |
||
1601 | } |
||
1602 | |||
1603 | /** |
||
1604 | * Update database |
||
1605 | * |
||
1606 | * @param User $user User that modify |
||
1607 | * @param int $notrigger 0=launch triggers after, 1=disable triggers |
||
1608 | * @return int <0 if KO, >0 if OK |
||
1609 | */ |
||
1610 | function update(User $user, $notrigger=0) |
||
1611 | { |
||
1612 | global $conf; |
||
1613 | |||
1614 | $error=0; |
||
1615 | |||
1616 | // Clean parameters |
||
1617 | if (empty($this->type)) $this->type= self::TYPE_STANDARD; |
||
1618 | if (isset($this->ref)) $this->ref=trim($this->ref); |
||
1619 | if (isset($this->ref_client)) $this->ref_client=trim($this->ref_client); |
||
1620 | if (isset($this->increment)) $this->increment=trim($this->increment); |
||
1621 | if (isset($this->close_code)) $this->close_code=trim($this->close_code); |
||
1622 | if (isset($this->close_note)) $this->close_note=trim($this->close_note); |
||
1623 | if (isset($this->note) || isset($this->note_private)) $this->note=(isset($this->note) ? trim($this->note) : trim($this->note_private)); // deprecated |
||
1624 | if (isset($this->note) || isset($this->note_private)) $this->note_private=(isset($this->note_private) ? trim($this->note_private) : trim($this->note)); |
||
1625 | if (isset($this->note_public)) $this->note_public=trim($this->note_public); |
||
1626 | if (isset($this->modelpdf)) $this->modelpdf=trim($this->modelpdf); |
||
1627 | if (isset($this->import_key)) $this->import_key=trim($this->import_key); |
||
1628 | |||
1629 | // Check parameters |
||
1630 | // Put here code to add control on parameters values |
||
1631 | |||
1632 | // Update request |
||
1633 | $sql = "UPDATE ".MAIN_DB_PREFIX."facture SET"; |
||
1634 | $sql.= " ref=".(isset($this->ref)?"'".$this->db->escape($this->ref)."'":"null").","; |
||
1635 | $sql.= " type=".(isset($this->type)?$this->db->escape($this->type):"null").","; |
||
1636 | $sql.= " ref_client=".(isset($this->ref_client)?"'".$this->db->escape($this->ref_client)."'":"null").","; |
||
1637 | $sql.= " increment=".(isset($this->increment)?"'".$this->db->escape($this->increment)."'":"null").","; |
||
1638 | $sql.= " fk_soc=".(isset($this->socid)?$this->db->escape($this->socid):"null").","; |
||
1639 | $sql.= " datec=".(strval($this->date_creation)!='' ? "'".$this->db->idate($this->date_creation)."'" : 'null').","; |
||
1640 | $sql.= " datef=".(strval($this->date)!='' ? "'".$this->db->idate($this->date)."'" : 'null').","; |
||
1641 | $sql.= " date_pointoftax=".(strval($this->date_pointoftax)!='' ? "'".$this->db->idate($this->date_pointoftax)."'" : 'null').","; |
||
1642 | $sql.= " date_valid=".(strval($this->date_validation)!='' ? "'".$this->db->idate($this->date_validation)."'" : 'null').","; |
||
1643 | $sql.= " paye=".(isset($this->paye)?$this->db->escape($this->paye):"null").","; |
||
1644 | $sql.= " remise_percent=".(isset($this->remise_percent)?$this->db->escape($this->remise_percent):"null").","; |
||
1645 | $sql.= " remise_absolue=".(isset($this->remise_absolue)?$this->db->escape($this->remise_absolue):"null").","; |
||
1646 | $sql.= " close_code=".(isset($this->close_code)?"'".$this->db->escape($this->close_code)."'":"null").","; |
||
1647 | $sql.= " close_note=".(isset($this->close_note)?"'".$this->db->escape($this->close_note)."'":"null").","; |
||
1648 | $sql.= " tva=".(isset($this->total_tva)?$this->total_tva:"null").","; |
||
1649 | $sql.= " localtax1=".(isset($this->total_localtax1)?$this->total_localtax1:"null").","; |
||
1650 | $sql.= " localtax2=".(isset($this->total_localtax2)?$this->total_localtax2:"null").","; |
||
1651 | $sql.= " total=".(isset($this->total_ht)?$this->total_ht:"null").","; |
||
1652 | $sql.= " total_ttc=".(isset($this->total_ttc)?$this->total_ttc:"null").","; |
||
1653 | $sql.= " revenuestamp=".((isset($this->revenuestamp) && $this->revenuestamp != '')?$this->db->escape($this->revenuestamp):"null").","; |
||
1654 | $sql.= " fk_statut=".(isset($this->statut)?$this->db->escape($this->statut):"null").","; |
||
1655 | $sql.= " fk_user_author=".(isset($this->user_author)?$this->db->escape($this->user_author):"null").","; |
||
1656 | $sql.= " fk_user_valid=".(isset($this->fk_user_valid)?$this->db->escape($this->fk_user_valid):"null").","; |
||
1657 | $sql.= " fk_facture_source=".(isset($this->fk_facture_source)?$this->db->escape($this->fk_facture_source):"null").","; |
||
1658 | $sql.= " fk_projet=".(isset($this->fk_project)?$this->db->escape($this->fk_project):"null").","; |
||
1659 | $sql.= " fk_cond_reglement=".(isset($this->cond_reglement_id)?$this->db->escape($this->cond_reglement_id):"null").","; |
||
1660 | $sql.= " fk_mode_reglement=".(isset($this->mode_reglement_id)?$this->db->escape($this->mode_reglement_id):"null").","; |
||
1661 | $sql.= " date_lim_reglement=".(strval($this->date_lim_reglement)!='' ? "'".$this->db->idate($this->date_lim_reglement)."'" : 'null').","; |
||
1662 | $sql.= " note_private=".(isset($this->note_private)?"'".$this->db->escape($this->note_private)."'":"null").","; |
||
1663 | $sql.= " note_public=".(isset($this->note_public)?"'".$this->db->escape($this->note_public)."'":"null").","; |
||
1664 | $sql.= " model_pdf=".(isset($this->modelpdf)?"'".$this->db->escape($this->modelpdf)."'":"null").","; |
||
1665 | $sql.= " import_key=".(isset($this->import_key)?"'".$this->db->escape($this->import_key)."'":"null").","; |
||
1666 | $sql.= " situation_cycle_ref=".(empty($this->situation_cycle_ref)?"null":$this->db->escape($this->situation_cycle_ref)).","; |
||
1667 | $sql.= " situation_counter=".(empty($this->situation_counter)?"null":$this->db->escape($this->situation_counter)).","; |
||
1668 | $sql.= " situation_final=".(empty($this->situation_counter)?"0":$this->db->escape($this->situation_counter)); |
||
1669 | $sql.= " WHERE rowid=".$this->id; |
||
1670 | |||
1671 | $this->db->begin(); |
||
1672 | |||
1673 | dol_syslog(get_class($this)."::update", LOG_DEBUG); |
||
1674 | $resql = $this->db->query($sql); |
||
1675 | if (! $resql) { |
||
1676 | $error++; $this->errors[]="Error ".$this->db->lasterror(); |
||
1677 | } |
||
1678 | |||
1679 | if (! $error && empty($conf->global->MAIN_EXTRAFIELDS_DISABLED) && is_array($this->array_options) && count($this->array_options)>0) |
||
1680 | { |
||
1681 | $result=$this->insertExtraFields(); |
||
1682 | if ($result < 0) |
||
1683 | { |
||
1684 | $error++; |
||
1685 | } |
||
1686 | } |
||
1687 | |||
1688 | if (! $error && ! $notrigger) |
||
1689 | { |
||
1690 | // Call trigger |
||
1691 | $result=$this->call_trigger('BILL_MODIFY',$user); |
||
1692 | if ($result < 0) $error++; |
||
1693 | // End call triggers |
||
1694 | } |
||
1695 | |||
1696 | // Commit or rollback |
||
1697 | if ($error) |
||
1698 | { |
||
1699 | foreach($this->errors as $errmsg) |
||
1700 | { |
||
1701 | dol_syslog(get_class($this)."::update ".$errmsg, LOG_ERR); |
||
1702 | $this->error.=($this->error?', '.$errmsg:$errmsg); |
||
1703 | } |
||
1704 | $this->db->rollback(); |
||
1705 | return -1*$error; |
||
1706 | } |
||
1707 | else |
||
1708 | { |
||
1709 | $this->db->commit(); |
||
1710 | return 1; |
||
1711 | } |
||
1712 | } |
||
1713 | |||
1714 | |||
1715 | // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps |
||
1716 | /** |
||
1717 | * Add a discount line into an invoice (as an invoice line) using an existing absolute discount (Consume the discount) |
||
1718 | * |
||
1719 | * @param int $idremise Id of absolute discount |
||
1720 | * @return int >0 if OK, <0 if KO |
||
1721 | */ |
||
1722 | function insert_discount($idremise) |
||
1723 | { |
||
1724 | // phpcs:enable |
||
1725 | global $langs; |
||
1726 | |||
1727 | include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php'; |
||
1728 | include_once DOL_DOCUMENT_ROOT.'/core/class/discount.class.php'; |
||
1729 | |||
1730 | $this->db->begin(); |
||
1731 | |||
1732 | $remise=new DiscountAbsolute($this->db); |
||
1733 | $result=$remise->fetch($idremise); |
||
1734 | |||
1735 | if ($result > 0) |
||
1736 | { |
||
1737 | if ($remise->fk_facture) // Protection against multiple submission |
||
1738 | { |
||
1739 | $this->error=$langs->trans("ErrorDiscountAlreadyUsed"); |
||
1740 | $this->db->rollback(); |
||
1741 | return -5; |
||
1742 | } |
||
1743 | |||
1744 | $facligne=new FactureLigne($this->db); |
||
1745 | $facligne->fk_facture=$this->id; |
||
1746 | $facligne->fk_remise_except=$remise->id; |
||
1747 | $facligne->desc=$remise->description; // Description ligne |
||
1748 | $facligne->vat_src_code=$remise->vat_src_code; |
||
1749 | $facligne->tva_tx=$remise->tva_tx; |
||
1750 | $facligne->subprice = -$remise->amount_ht; |
||
1751 | $facligne->fk_product=0; // Id produit predefini |
||
1752 | $facligne->qty=1; |
||
1753 | $facligne->remise_percent=0; |
||
1754 | $facligne->rang=-1; |
||
1755 | $facligne->info_bits=2; |
||
1756 | |||
1757 | // Get buy/cost price of invoice that is source of discount |
||
1758 | if ($remise->fk_facture_source > 0) |
||
1759 | { |
||
1760 | $srcinvoice=new Facture($this->db); |
||
1761 | $srcinvoice->fetch($remise->fk_facture_source); |
||
1762 | $totalcostpriceofinvoice=0; |
||
1763 | include_once DOL_DOCUMENT_ROOT.'/core/class/html.formmargin.class.php'; // TODO Move this into commonobject |
||
1764 | $formmargin=new FormMargin($this->db); |
||
1765 | $arraytmp=$formmargin->getMarginInfosArray($srcinvoice, false); |
||
1766 | $facligne->pa_ht = $arraytmp['pa_total']; |
||
1767 | } |
||
1768 | |||
1769 | $facligne->total_ht = -$remise->amount_ht; |
||
1770 | $facligne->total_tva = -$remise->amount_tva; |
||
1771 | $facligne->total_ttc = -$remise->amount_ttc; |
||
1772 | |||
1773 | $facligne->multicurrency_subprice = -$remise->multicurrency_subprice; |
||
1774 | $facligne->multicurrency_total_ht = -$remise->multicurrency_amount_ht; |
||
1775 | $facligne->multicurrency_total_tva = -$remise->multicurrency_amount_tva; |
||
1776 | $facligne->multicurrency_total_ttc = -$remise->multicurrency_amount_ttc; |
||
1777 | |||
1778 | $lineid=$facligne->insert(); |
||
1779 | if ($lineid > 0) |
||
1780 | { |
||
1781 | $result=$this->update_price(1); |
||
1782 | if ($result > 0) |
||
1783 | { |
||
1784 | // Create link between discount and invoice line |
||
1785 | $result=$remise->link_to_invoice($lineid,0); |
||
1786 | if ($result < 0) |
||
1787 | { |
||
1788 | $this->error=$remise->error; |
||
1789 | $this->db->rollback(); |
||
1790 | return -4; |
||
1791 | } |
||
1792 | |||
1793 | $this->db->commit(); |
||
1794 | return 1; |
||
1795 | } |
||
1796 | else |
||
1797 | { |
||
1798 | $this->error=$facligne->error; |
||
1799 | $this->db->rollback(); |
||
1800 | return -1; |
||
1801 | } |
||
1802 | } |
||
1803 | else |
||
1804 | { |
||
1805 | $this->error=$facligne->error; |
||
1806 | $this->db->rollback(); |
||
1807 | return -2; |
||
1808 | } |
||
1809 | } |
||
1810 | else |
||
1811 | { |
||
1812 | $this->db->rollback(); |
||
1813 | return -3; |
||
1814 | } |
||
1815 | } |
||
1816 | |||
1817 | // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps |
||
1818 | /** |
||
1819 | * Set customer ref |
||
1820 | * |
||
1821 | * @param string $ref_client Customer ref |
||
1822 | * @param int $notrigger 1=Does not execute triggers, 0= execute triggers |
||
1823 | * @return int <0 if KO, >0 if OK |
||
1824 | */ |
||
1825 | function set_ref_client($ref_client, $notrigger=0) |
||
1826 | { |
||
1827 | // phpcs:enable |
||
1828 | global $user; |
||
1829 | |||
1830 | $error=0; |
||
1831 | |||
1832 | $this->db->begin(); |
||
1833 | |||
1834 | $sql = 'UPDATE '.MAIN_DB_PREFIX.'facture'; |
||
1835 | if (empty($ref_client)) |
||
1836 | $sql .= ' SET ref_client = NULL'; |
||
1837 | else |
||
1838 | $sql .= ' SET ref_client = \''.$this->db->escape($ref_client).'\''; |
||
1839 | $sql .= ' WHERE rowid = '.$this->id; |
||
1840 | |||
1841 | dol_syslog(__METHOD__.' this->id='.$this->id.', ref_client='.$ref_client, LOG_DEBUG); |
||
1842 | $resql=$this->db->query($sql); |
||
1843 | if (!$resql) |
||
1844 | { |
||
1845 | $this->errors[]=$this->db->error(); |
||
1846 | $error++; |
||
1847 | } |
||
1848 | |||
1849 | if (! $error) |
||
1850 | { |
||
1851 | $this->ref_client = $ref_client; |
||
1852 | } |
||
1853 | |||
1854 | if (! $notrigger && empty($error)) |
||
1855 | { |
||
1856 | // Call trigger |
||
1857 | $result=$this->call_trigger('BILL_MODIFY',$user); |
||
1858 | if ($result < 0) $error++; |
||
1859 | // End call triggers |
||
1860 | } |
||
1861 | |||
1862 | if (! $error) |
||
1863 | { |
||
1864 | |||
1865 | $this->ref_client = $ref_client; |
||
1866 | |||
1867 | $this->db->commit(); |
||
1868 | return 1; |
||
1869 | } |
||
1870 | else |
||
1871 | { |
||
1872 | foreach($this->errors as $errmsg) |
||
1873 | { |
||
1874 | dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR); |
||
1875 | $this->error.=($this->error?', '.$errmsg:$errmsg); |
||
1876 | } |
||
1877 | $this->db->rollback(); |
||
1878 | return -1*$error; |
||
1879 | } |
||
1880 | } |
||
1881 | |||
1882 | /** |
||
1883 | * Delete invoice |
||
1884 | * |
||
1885 | * @param User $user User making the deletion. |
||
1886 | * @param int $notrigger 1=Does not execute triggers, 0= execute triggers |
||
1887 | * @param int $idwarehouse Id warehouse to use for stock change. |
||
1888 | * @return int <0 if KO, 0=Refused, >0 if OK |
||
1889 | */ |
||
1890 | function delete($user, $notrigger=0, $idwarehouse=-1) |
||
1891 | { |
||
1892 | global $langs,$conf; |
||
1893 | require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php'; |
||
1894 | |||
1895 | $rowid=$this->id; |
||
1896 | |||
1897 | dol_syslog(get_class($this)."::delete rowid=".$rowid.", ref=".$this->ref.", thirdparty=".$this->thirdparty->name, LOG_DEBUG); |
||
1898 | |||
1899 | // Test to avoid invoice deletion (allowed if draft) |
||
1900 | $result = $this->is_erasable(); |
||
1901 | |||
1902 | if ($result <= 0) return 0; |
||
1903 | |||
1904 | $error=0; |
||
1905 | |||
1906 | $this->db->begin(); |
||
1907 | |||
1908 | if (! $error && ! $notrigger) |
||
1909 | { |
||
1910 | // Call trigger |
||
1911 | $result=$this->call_trigger('BILL_DELETE',$user); |
||
1912 | if ($result < 0) $error++; |
||
1913 | // End call triggers |
||
1914 | } |
||
1915 | |||
1916 | // Removed extrafields |
||
1917 | if (! $error) { |
||
1918 | $result=$this->deleteExtraFields(); |
||
1919 | if ($result < 0) |
||
1920 | { |
||
1921 | $error++; |
||
1922 | dol_syslog(get_class($this)."::delete error deleteExtraFields ".$this->error, LOG_ERR); |
||
1923 | } |
||
1924 | } |
||
1925 | |||
1926 | if (! $error) |
||
1927 | { |
||
1928 | // Delete linked object |
||
1929 | $res = $this->deleteObjectLinked(); |
||
1930 | if ($res < 0) $error++; |
||
1931 | } |
||
1932 | |||
1933 | if (! $error) |
||
1934 | { |
||
1935 | // If invoice was converted into a discount not yet consumed, we remove discount |
||
1936 | $sql = 'DELETE FROM '.MAIN_DB_PREFIX.'societe_remise_except'; |
||
1937 | $sql.= ' WHERE fk_facture_source = '.$rowid; |
||
1938 | $sql.= ' AND fk_facture_line IS NULL'; |
||
1939 | $resql=$this->db->query($sql); |
||
1940 | |||
1941 | // If invoice has consumned discounts |
||
1942 | $this->fetch_lines(); |
||
1943 | $list_rowid_det=array(); |
||
1944 | foreach($this->lines as $key => $invoiceline) |
||
1945 | { |
||
1946 | $list_rowid_det[]=$invoiceline->rowid; |
||
1947 | } |
||
1948 | |||
1949 | // Consumned discounts are freed |
||
1950 | if (count($list_rowid_det)) |
||
1951 | { |
||
1952 | $sql = 'UPDATE '.MAIN_DB_PREFIX.'societe_remise_except'; |
||
1953 | $sql.= ' SET fk_facture = NULL, fk_facture_line = NULL'; |
||
1954 | $sql.= ' WHERE fk_facture_line IN ('.join(',',$list_rowid_det).')'; |
||
1955 | |||
1956 | dol_syslog(get_class($this)."::delete", LOG_DEBUG); |
||
1957 | if (! $this->db->query($sql)) |
||
1958 | { |
||
1959 | $this->error=$this->db->error()." sql=".$sql; |
||
1960 | $this->db->rollback(); |
||
1961 | return -5; |
||
1962 | } |
||
1963 | } |
||
1964 | |||
1965 | // If we decrement stock on invoice validation, we increment |
||
1966 | if ($this->type != self::TYPE_DEPOSIT && $result >= 0 && ! empty($conf->stock->enabled) && ! empty($conf->global->STOCK_CALCULATE_ON_BILL) && $idwarehouse!=-1) |
||
1967 | { |
||
1968 | require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php'; |
||
1969 | $langs->load("agenda"); |
||
1970 | |||
1971 | $num=count($this->lines); |
||
1972 | for ($i = 0; $i < $num; $i++) |
||
1973 | { |
||
1974 | if ($this->lines[$i]->fk_product > 0) |
||
1975 | { |
||
1976 | $mouvP = new MouvementStock($this->db); |
||
1977 | $mouvP->origin = &$this; |
||
1978 | // We decrease stock for product |
||
1979 | if ($this->type == self::TYPE_CREDIT_NOTE) $result=$mouvP->livraison($user, $this->lines[$i]->fk_product, $idwarehouse, $this->lines[$i]->qty, $this->lines[$i]->subprice, $langs->trans("InvoiceDeleteDolibarr",$this->ref)); |
||
1980 | else $result=$mouvP->reception($user, $this->lines[$i]->fk_product, $idwarehouse, $this->lines[$i]->qty, 0, $langs->trans("InvoiceDeleteDolibarr",$this->ref)); // we use 0 for price, to not change the weighted average value |
||
1981 | } |
||
1982 | } |
||
1983 | } |
||
1984 | |||
1985 | |||
1986 | // Delete invoice line |
||
1987 | $sql = 'DELETE FROM '.MAIN_DB_PREFIX.'facturedet WHERE fk_facture = '.$rowid; |
||
1988 | |||
1989 | dol_syslog(get_class($this)."::delete", LOG_DEBUG); |
||
1990 | |||
1991 | if ($this->db->query($sql) && $this->delete_linked_contact()) |
||
1992 | { |
||
1993 | $sql = 'DELETE FROM '.MAIN_DB_PREFIX.'facture WHERE rowid = '.$rowid; |
||
1994 | |||
1995 | dol_syslog(get_class($this)."::delete", LOG_DEBUG); |
||
1996 | |||
1997 | $resql=$this->db->query($sql); |
||
1998 | if ($resql) |
||
1999 | { |
||
2000 | // On efface le repertoire de pdf provisoire |
||
2001 | $ref = dol_sanitizeFileName($this->ref); |
||
2002 | if ($conf->facture->dir_output && !empty($this->ref)) |
||
2003 | { |
||
2004 | $dir = $conf->facture->dir_output . "/" . $ref; |
||
2005 | $file = $conf->facture->dir_output . "/" . $ref . "/" . $ref . ".pdf"; |
||
2006 | if (file_exists($file)) // We must delete all files before deleting directory |
||
2007 | { |
||
2008 | $ret=dol_delete_preview($this); |
||
2009 | |||
2010 | if (! dol_delete_file($file,0,0,0,$this)) // For triggers |
||
2011 | { |
||
2012 | $langs->load("errors"); |
||
2013 | $this->error=$langs->trans("ErrorFailToDeleteFile",$file); |
||
2014 | $this->db->rollback(); |
||
2015 | return 0; |
||
2016 | } |
||
2017 | } |
||
2018 | if (file_exists($dir)) |
||
2019 | { |
||
2020 | if (! dol_delete_dir_recursive($dir)) // For remove dir and meta |
||
2021 | { |
||
2022 | $langs->load("errors"); |
||
2023 | $this->error=$langs->trans("ErrorFailToDeleteDir",$dir); |
||
2024 | $this->db->rollback(); |
||
2025 | return 0; |
||
2026 | } |
||
2027 | } |
||
2028 | } |
||
2029 | |||
2030 | $this->db->commit(); |
||
2031 | return 1; |
||
2032 | } |
||
2033 | else |
||
2034 | { |
||
2035 | $this->error=$this->db->lasterror()." sql=".$sql; |
||
2036 | $this->db->rollback(); |
||
2037 | return -6; |
||
2038 | } |
||
2039 | } |
||
2040 | else |
||
2041 | { |
||
2042 | $this->error=$this->db->lasterror()." sql=".$sql; |
||
2043 | $this->db->rollback(); |
||
2044 | return -4; |
||
2045 | } |
||
2046 | } |
||
2047 | else |
||
2048 | { |
||
2049 | $this->db->rollback(); |
||
2050 | return -2; |
||
2051 | } |
||
2052 | } |
||
2053 | |||
2054 | // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps |
||
2055 | /** |
||
2056 | * Tag la facture comme paye completement (si close_code non renseigne) => this->fk_statut=2, this->paye=1 |
||
2057 | * ou partiellement (si close_code renseigne) + appel trigger BILL_PAYED => this->fk_statut=2, this->paye stay 0 |
||
2058 | * |
||
2059 | * @param User $user Objet utilisateur qui modifie |
||
2060 | * @param string $close_code Code renseigne si on classe a payee completement alors que paiement incomplet (cas escompte par exemple) |
||
2061 | * @param string $close_note Commentaire renseigne si on classe a payee alors que paiement incomplet (cas escompte par exemple) |
||
2062 | * @return int <0 if KO, >0 if OK |
||
2063 | */ |
||
2064 | function set_paid($user, $close_code='', $close_note='') |
||
2065 | { |
||
2066 | // phpcs:enable |
||
2067 | $error=0; |
||
2068 | |||
2069 | if ($this->paye != 1) |
||
2070 | { |
||
2071 | $this->db->begin(); |
||
2072 | |||
2073 | dol_syslog(get_class($this)."::set_paid rowid=".$this->id, LOG_DEBUG); |
||
2074 | |||
2075 | $sql = 'UPDATE '.MAIN_DB_PREFIX.'facture SET'; |
||
2076 | $sql.= ' fk_statut='.self::STATUS_CLOSED; |
||
2077 | if (! $close_code) $sql.= ', paye=1'; |
||
2078 | if ($close_code) $sql.= ", close_code='".$this->db->escape($close_code)."'"; |
||
2079 | if ($close_note) $sql.= ", close_note='".$this->db->escape($close_note)."'"; |
||
2080 | $sql.= ' WHERE rowid = '.$this->id; |
||
2081 | |||
2082 | $resql = $this->db->query($sql); |
||
2083 | if ($resql) |
||
2084 | { |
||
2085 | // Call trigger |
||
2086 | $result=$this->call_trigger('BILL_PAYED',$user); |
||
2087 | if ($result < 0) $error++; |
||
2088 | // End call triggers |
||
2089 | } |
||
2090 | else |
||
2091 | { |
||
2092 | $error++; |
||
2093 | $this->error=$this->db->lasterror(); |
||
2094 | } |
||
2095 | |||
2096 | if (! $error) |
||
2097 | { |
||
2098 | $this->db->commit(); |
||
2099 | return 1; |
||
2100 | } |
||
2101 | else |
||
2102 | { |
||
2103 | $this->db->rollback(); |
||
2104 | return -1; |
||
2105 | } |
||
2106 | } |
||
2107 | else |
||
2108 | { |
||
2109 | return 0; |
||
2110 | } |
||
2111 | } |
||
2112 | |||
2113 | |||
2114 | // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps |
||
2115 | /** |
||
2116 | * Tag la facture comme non payee completement + appel trigger BILL_UNPAYED |
||
2117 | * Fonction utilisee quand un paiement prelevement est refuse, |
||
2118 | * ou quand une facture annulee et reouverte. |
||
2119 | * |
||
2120 | * @param User $user Object user that change status |
||
2121 | * @return int <0 if KO, >0 if OK |
||
2122 | */ |
||
2123 | function set_unpaid($user) |
||
2124 | { |
||
2125 | // phpcs:enable |
||
2126 | $error=0; |
||
2127 | |||
2128 | $this->db->begin(); |
||
2129 | |||
2130 | $sql = 'UPDATE '.MAIN_DB_PREFIX.'facture'; |
||
2131 | $sql.= ' SET paye=0, fk_statut='.self::STATUS_VALIDATED.', close_code=null, close_note=null'; |
||
2132 | $sql.= ' WHERE rowid = '.$this->id; |
||
2133 | |||
2134 | dol_syslog(get_class($this)."::set_unpaid", LOG_DEBUG); |
||
2135 | $resql = $this->db->query($sql); |
||
2136 | if ($resql) |
||
2137 | { |
||
2138 | // Call trigger |
||
2139 | $result=$this->call_trigger('BILL_UNPAYED',$user); |
||
2140 | if ($result < 0) $error++; |
||
2141 | // End call triggers |
||
2142 | } |
||
2143 | else |
||
2144 | { |
||
2145 | $error++; |
||
2146 | $this->error=$this->db->error(); |
||
2147 | dol_print_error($this->db); |
||
2148 | } |
||
2149 | |||
2150 | if (! $error) |
||
2151 | { |
||
2152 | $this->db->commit(); |
||
2153 | return 1; |
||
2154 | } |
||
2155 | else |
||
2156 | { |
||
2157 | $this->db->rollback(); |
||
2158 | return -1; |
||
2159 | } |
||
2160 | } |
||
2161 | |||
2162 | |||
2163 | // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps |
||
2164 | /** |
||
2165 | * Tag invoice as canceled, with no payment on it (example for replacement invoice or payment never received) + call trigger BILL_CANCEL |
||
2166 | * Warning, if option to decrease stock on invoice was set, this function does not change stock (it might be a cancel because |
||
2167 | * of no payment even if merchandises were sent). |
||
2168 | * |
||
2169 | * @param User $user Object user making change |
||
2170 | * @param string $close_code Code of closing invoice (CLOSECODE_REPLACED, CLOSECODE_...) |
||
2171 | * @param string $close_note Comment |
||
2172 | * @return int <0 if KO, >0 if OK |
||
2173 | */ |
||
2174 | function set_canceled($user, $close_code='', $close_note='') |
||
2175 | { |
||
2176 | // phpcs:enable |
||
2177 | |||
2178 | dol_syslog(get_class($this)."::set_canceled rowid=".$this->id, LOG_DEBUG); |
||
2179 | |||
2180 | $this->db->begin(); |
||
2181 | |||
2182 | $sql = 'UPDATE '.MAIN_DB_PREFIX.'facture SET'; |
||
2183 | $sql.= ' fk_statut='.self::STATUS_ABANDONED; |
||
2184 | if ($close_code) $sql.= ", close_code='".$this->db->escape($close_code)."'"; |
||
2185 | if ($close_note) $sql.= ", close_note='".$this->db->escape($close_note)."'"; |
||
2186 | $sql.= ' WHERE rowid = '.$this->id; |
||
2187 | |||
2188 | $resql = $this->db->query($sql); |
||
2189 | if ($resql) |
||
2190 | { |
||
2191 | // On desaffecte de la facture les remises liees |
||
2192 | // car elles n'ont pas ete utilisees vu que la facture est abandonnee. |
||
2193 | $sql = 'UPDATE '.MAIN_DB_PREFIX.'societe_remise_except'; |
||
2194 | $sql.= ' SET fk_facture = NULL'; |
||
2195 | $sql.= ' WHERE fk_facture = '.$this->id; |
||
2196 | |||
2197 | $resql=$this->db->query($sql); |
||
2198 | if ($resql) |
||
2199 | { |
||
2200 | // Call trigger |
||
2201 | $result=$this->call_trigger('BILL_CANCEL',$user); |
||
2202 | if ($result < 0) |
||
2203 | { |
||
2204 | $this->db->rollback(); |
||
2205 | return -1; |
||
2206 | } |
||
2207 | // End call triggers |
||
2208 | |||
2209 | $this->db->commit(); |
||
2210 | return 1; |
||
2211 | } |
||
2212 | else |
||
2213 | { |
||
2214 | $this->error=$this->db->error()." sql=".$sql; |
||
2215 | $this->db->rollback(); |
||
2216 | return -1; |
||
2217 | } |
||
2218 | } |
||
2219 | else |
||
2220 | { |
||
2221 | $this->error=$this->db->error()." sql=".$sql; |
||
2222 | $this->db->rollback(); |
||
2223 | return -2; |
||
2224 | } |
||
2225 | } |
||
2226 | |||
2227 | /** |
||
2228 | * Tag invoice as validated + call trigger BILL_VALIDATE |
||
2229 | * Object must have lines loaded with fetch_lines |
||
2230 | * |
||
2231 | * @param User $user Object user that validate |
||
2232 | * @param string $force_number Reference to force on invoice |
||
2233 | * @param int $idwarehouse Id of warehouse to use for stock decrease if option to decreasenon stock is on (0=no decrease) |
||
2234 | * @param int $notrigger 1=Does not execute triggers, 0= execute triggers |
||
2235 | * @return int <0 if KO, 0=Nothing done because invoice is not a draft, >0 if OK |
||
2236 | */ |
||
2237 | function validate($user, $force_number='', $idwarehouse=0, $notrigger=0) |
||
2238 | { |
||
2239 | global $conf,$langs; |
||
2240 | require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php'; |
||
2241 | |||
2242 | $now=dol_now(); |
||
2243 | |||
2244 | $error=0; |
||
2245 | dol_syslog(get_class($this).'::validate user='.$user->id.', force_number='.$force_number.', idwarehouse='.$idwarehouse); |
||
2246 | |||
2247 | // Force to have object complete for checks |
||
2248 | $this->fetch_thirdparty(); |
||
2249 | $this->fetch_lines(); |
||
2250 | |||
2251 | // Check parameters |
||
2252 | if (! $this->brouillon) |
||
2253 | { |
||
2254 | dol_syslog(get_class($this)."::validate no draft status", LOG_WARNING); |
||
2255 | return 0; |
||
2256 | } |
||
2257 | if (count($this->lines) <= 0) |
||
2258 | { |
||
2259 | $langs->load("errors"); |
||
2260 | $this->error=$langs->trans("ErrorObjectMustHaveLinesToBeValidated", $this->ref); |
||
2261 | return -1; |
||
2262 | } |
||
2263 | if ((empty($conf->global->MAIN_USE_ADVANCED_PERMS) && empty($user->rights->facture->creer)) |
||
2264 | || (! empty($conf->global->MAIN_USE_ADVANCED_PERMS) && empty($user->rights->facture->invoice_advance->validate))) |
||
2265 | { |
||
2266 | $this->error='Permission denied'; |
||
2267 | dol_syslog(get_class($this)."::validate ".$this->error.' MAIN_USE_ADVANCED_PERMS='.$conf->global->MAIN_USE_ADVANCED_PERMS, LOG_ERR); |
||
2268 | return -1; |
||
2269 | } |
||
2270 | |||
2271 | $this->db->begin(); |
||
2272 | |||
2273 | // Check parameters |
||
2274 | if ($this->type == self::TYPE_REPLACEMENT) // si facture de remplacement |
||
2275 | { |
||
2276 | // Controle que facture source connue |
||
2277 | if ($this->fk_facture_source <= 0) |
||
2278 | { |
||
2279 | $this->error=$langs->trans("ErrorFieldRequired",$langs->trans("InvoiceReplacement")); |
||
2280 | $this->db->rollback(); |
||
2281 | return -10; |
||
2282 | } |
||
2283 | |||
2284 | // Charge la facture source a remplacer |
||
2285 | $facreplaced=new Facture($this->db); |
||
2286 | $result=$facreplaced->fetch($this->fk_facture_source); |
||
2287 | if ($result <= 0) |
||
2288 | { |
||
2289 | $this->error=$langs->trans("ErrorBadInvoice"); |
||
2290 | $this->db->rollback(); |
||
2291 | return -11; |
||
2292 | } |
||
2293 | |||
2294 | // Controle que facture source non deja remplacee par une autre |
||
2295 | $idreplacement=$facreplaced->getIdReplacingInvoice('validated'); |
||
2296 | if ($idreplacement && $idreplacement != $this->id) |
||
2297 | { |
||
2298 | $facreplacement=new Facture($this->db); |
||
2299 | $facreplacement->fetch($idreplacement); |
||
2300 | $this->error=$langs->trans("ErrorInvoiceAlreadyReplaced",$facreplaced->ref,$facreplacement->ref); |
||
2301 | $this->db->rollback(); |
||
2302 | return -12; |
||
2303 | } |
||
2304 | |||
2305 | $result=$facreplaced->set_canceled($user, self::CLOSECODE_REPLACED, ''); |
||
2306 | if ($result < 0) |
||
2307 | { |
||
2308 | $this->error=$facreplaced->error; |
||
2309 | $this->db->rollback(); |
||
2310 | return -13; |
||
2311 | } |
||
2312 | } |
||
2313 | |||
2314 | // Define new ref |
||
2315 | if ($force_number) |
||
2316 | { |
||
2317 | $num = $force_number; |
||
2318 | } |
||
2319 | else if (preg_match('/^[\(]?PROV/i', $this->ref) || empty($this->ref)) // empty should not happened, but when it occurs, the test save life |
||
2320 | { |
||
2321 | if (! empty($conf->global->FAC_FORCE_DATE_VALIDATION)) // If option enabled, we force invoice date |
||
2322 | { |
||
2323 | $this->date=dol_now(); |
||
2324 | $this->date_lim_reglement=$this->calculate_date_lim_reglement(); |
||
2325 | } |
||
2326 | $num = $this->getNextNumRef($this->thirdparty); |
||
2327 | } |
||
2328 | else |
||
2329 | { |
||
2330 | $num = $this->ref; |
||
2331 | } |
||
2332 | $this->newref = $num; |
||
2333 | |||
2334 | if ($num) |
||
2335 | { |
||
2336 | $this->update_price(1); |
||
2337 | |||
2338 | // Validate |
||
2339 | $sql = 'UPDATE '.MAIN_DB_PREFIX.'facture'; |
||
2340 | $sql.= " SET ref='".$num."', fk_statut = ".self::STATUS_VALIDATED.", fk_user_valid = ".($user->id > 0 ? $user->id : "null").", date_valid = '".$this->db->idate($now)."'"; |
||
2341 | if (! empty($conf->global->FAC_FORCE_DATE_VALIDATION)) // If option enabled, we force invoice date |
||
2342 | { |
||
2343 | $sql.= ", datef='".$this->db->idate($this->date)."'"; |
||
2344 | $sql.= ", date_lim_reglement='".$this->db->idate($this->date_lim_reglement)."'"; |
||
2345 | } |
||
2346 | $sql.= ' WHERE rowid = '.$this->id; |
||
2347 | |||
2348 | dol_syslog(get_class($this)."::validate", LOG_DEBUG); |
||
2349 | $resql=$this->db->query($sql); |
||
2350 | if (! $resql) |
||
2351 | { |
||
2352 | dol_print_error($this->db); |
||
2353 | $error++; |
||
2354 | } |
||
2355 | |||
2356 | // On verifie si la facture etait une provisoire |
||
2357 | if (! $error && (preg_match('/^[\(]?PROV/i', $this->ref))) |
||
2358 | { |
||
2359 | // La verif qu'une remise n'est pas utilisee 2 fois est faite au moment de l'insertion de ligne |
||
2360 | } |
||
2361 | |||
2362 | if (! $error) |
||
2363 | { |
||
2364 | // Define third party as a customer |
||
2365 | $result=$this->thirdparty->set_as_client(); |
||
2366 | |||
2367 | // Si active on decremente le produit principal et ses composants a la validation de facture |
||
2368 | if ($this->type != self::TYPE_DEPOSIT && $result >= 0 && ! empty($conf->stock->enabled) && ! empty($conf->global->STOCK_CALCULATE_ON_BILL) && $idwarehouse > 0) |
||
2369 | { |
||
2370 | require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php'; |
||
2371 | $langs->load("agenda"); |
||
2372 | |||
2373 | // Loop on each line |
||
2374 | $cpt=count($this->lines); |
||
2375 | for ($i = 0; $i < $cpt; $i++) |
||
2376 | { |
||
2377 | if ($this->lines[$i]->fk_product > 0) |
||
2378 | { |
||
2379 | $mouvP = new MouvementStock($this->db); |
||
2380 | $mouvP->origin = &$this; |
||
2381 | // We decrease stock for product |
||
2382 | if ($this->type == self::TYPE_CREDIT_NOTE) $result=$mouvP->reception($user, $this->lines[$i]->fk_product, $idwarehouse, $this->lines[$i]->qty, 0, $langs->trans("InvoiceValidatedInDolibarr",$num)); |
||
2383 | else $result=$mouvP->livraison($user, $this->lines[$i]->fk_product, $idwarehouse, $this->lines[$i]->qty, $this->lines[$i]->subprice, $langs->trans("InvoiceValidatedInDolibarr",$num)); |
||
2384 | if ($result < 0) { |
||
2385 | $error++; |
||
2386 | $this->error = $mouvP->error; |
||
2387 | } |
||
2388 | } |
||
2389 | } |
||
2390 | } |
||
2391 | } |
||
2392 | |||
2393 | // Trigger calls |
||
2394 | if (! $error && ! $notrigger) |
||
2395 | { |
||
2396 | // Call trigger |
||
2397 | $result=$this->call_trigger('BILL_VALIDATE',$user); |
||
2398 | if ($result < 0) $error++; |
||
2399 | // End call triggers |
||
2400 | } |
||
2401 | |||
2402 | if (! $error) |
||
2403 | { |
||
2404 | $this->oldref = $this->ref; |
||
2405 | |||
2406 | // Rename directory if dir was a temporary ref |
||
2407 | if (preg_match('/^[\(]?PROV/i', $this->ref)) |
||
2408 | { |
||
2409 | // Rename of object directory ($this->ref = old ref, $num = new ref) |
||
2410 | // to not lose the linked files |
||
2411 | $oldref = dol_sanitizeFileName($this->ref); |
||
2412 | $newref = dol_sanitizeFileName($num); |
||
2413 | $dirsource = $conf->facture->dir_output.'/'.$oldref; |
||
2414 | $dirdest = $conf->facture->dir_output.'/'.$newref; |
||
2415 | if (file_exists($dirsource)) |
||
2416 | { |
||
2417 | dol_syslog(get_class($this)."::validate rename dir ".$dirsource." into ".$dirdest); |
||
2418 | |||
2419 | if (@rename($dirsource, $dirdest)) |
||
2420 | { |
||
2421 | dol_syslog("Rename ok"); |
||
2422 | // Rename docs starting with $oldref with $newref |
||
2423 | $listoffiles=dol_dir_list($conf->facture->dir_output.'/'.$newref, 'files', 1, '^'.preg_quote($oldref,'/')); |
||
2424 | foreach($listoffiles as $fileentry) |
||
2425 | { |
||
2426 | $dirsource=$fileentry['name']; |
||
2427 | $dirdest=preg_replace('/^'.preg_quote($oldref,'/').'/',$newref, $dirsource); |
||
2428 | $dirsource=$fileentry['path'].'/'.$dirsource; |
||
2429 | $dirdest=$fileentry['path'].'/'.$dirdest; |
||
2430 | @rename($dirsource, $dirdest); |
||
2431 | } |
||
2432 | } |
||
2433 | } |
||
2434 | } |
||
2435 | } |
||
2436 | |||
2437 | if (! $error && !$this->is_last_in_cycle()) |
||
2438 | { |
||
2439 | if (! $this->updatePriceNextInvoice($langs)) |
||
2440 | { |
||
2441 | $error++; |
||
2442 | } |
||
2443 | } |
||
2444 | |||
2445 | // Set new ref and define current status |
||
2446 | if (! $error) |
||
2447 | { |
||
2448 | $this->ref = $num; |
||
2449 | $this->ref=$num; |
||
2450 | $this->statut= self::STATUS_VALIDATED; |
||
2451 | $this->brouillon=0; |
||
2452 | $this->date_validation=$now; |
||
2453 | $i = 0; |
||
2454 | |||
2455 | if (!empty($conf->global->INVOICE_USE_SITUATION)) |
||
2456 | { |
||
2457 | $final = true; |
||
2458 | $nboflines = count($this->lines); |
||
2459 | while (($i < $nboflines) && $final) { |
||
2460 | $final = ($this->lines[$i]->situation_percent == 100); |
||
2461 | $i++; |
||
2462 | } |
||
2463 | |||
2464 | if (empty($final)) $this->situation_final = 0; |
||
2465 | else $this->situation_final = 1; |
||
2466 | |||
2467 | $this->setFinal($user); |
||
2468 | } |
||
2469 | } |
||
2470 | } |
||
2471 | else |
||
2472 | { |
||
2473 | $error++; |
||
2474 | } |
||
2475 | |||
2476 | if (! $error) |
||
2477 | { |
||
2478 | $this->db->commit(); |
||
2479 | return 1; |
||
2480 | } |
||
2481 | else |
||
2482 | { |
||
2483 | $this->db->rollback(); |
||
2484 | return -1; |
||
2485 | } |
||
2486 | } |
||
2487 | |||
2488 | /** |
||
2489 | * Update price of next invoice |
||
2490 | * |
||
2491 | * @param Translate $langs Translate object |
||
2492 | * @return bool false if KO, true if OK |
||
2493 | */ |
||
2494 | function updatePriceNextInvoice(&$langs) |
||
2495 | { |
||
2496 | foreach ($this->tab_next_situation_invoice as $next_invoice) |
||
2497 | { |
||
2498 | $is_last = $next_invoice->is_last_in_cycle(); |
||
2499 | |||
2500 | if ($next_invoice->brouillon && $is_last != 1) |
||
2501 | { |
||
2502 | $this->error = $langs->trans('updatePriceNextInvoiceErrorUpdateline', $next_invoice->ref); |
||
2503 | return false; |
||
2504 | } |
||
2505 | |||
2506 | $next_invoice->brouillon = 1; |
||
2507 | foreach ($next_invoice->lines as $line) |
||
2508 | { |
||
2509 | $result = $next_invoice->updateline($line->id, $line->desc, $line->subprice, $line->qty, $line->remise_percent, |
||
2510 | $line->date_start, $line->date_end, $line->tva_tx, $line->localtax1_tx, $line->localtax2_tx, 'HT', $line->info_bits, $line->product_type, |
||
2511 | $line->fk_parent_line, 0, $line->fk_fournprice, $line->pa_ht, $line->label, $line->special_code, $line->array_options, $line->situation_percent, |
||
2512 | $line->fk_unit); |
||
2513 | |||
2514 | if ($result < 0) |
||
2515 | { |
||
2516 | $this->error = $langs->trans('updatePriceNextInvoiceErrorUpdateline', $next_invoice->ref); |
||
2517 | return false; |
||
2518 | } |
||
2519 | } |
||
2520 | |||
2521 | break; // Only the next invoice and not each next invoice |
||
2522 | } |
||
2523 | |||
2524 | return true; |
||
2525 | } |
||
2526 | |||
2527 | // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps |
||
2528 | /** |
||
2529 | * Set draft status |
||
2530 | * |
||
2531 | * @param User $user Object user that modify |
||
2532 | * @param int $idwarehouse Id warehouse to use for stock change. |
||
2533 | * @return int <0 if KO, >0 if OK |
||
2534 | */ |
||
2535 | function set_draft($user,$idwarehouse=-1) |
||
2613 | } |
||
2614 | } |
||
2615 | |||
2616 | |||
2617 | /** |
||
2618 | * Add an invoice line into database (linked to product/service or not). |
||
2619 | * Les parametres sont deja cense etre juste et avec valeurs finales a l'appel |
||
2620 | * de cette methode. Aussi, pour le taux tva, il doit deja avoir ete defini |
||
2621 | * par l'appelant par la methode get_default_tva(societe_vendeuse,societe_acheteuse,produit) |
||
2622 | * et le desc doit deja avoir la bonne valeur (a l'appelant de gerer le multilangue) |
||
2623 | * |
||
2624 | * @param string $desc Description of line |
||
2625 | * @param double $pu_ht Unit price without tax (> 0 even for credit note) |
||
2626 | * @param double $qty Quantity |
||
2627 | * @param double $txtva Force Vat rate, -1 for auto (Can contain the vat_src_code too with syntax '9.9 (CODE)') |
||
2628 | * @param double $txlocaltax1 Local tax 1 rate (deprecated, use instead txtva with code inside) |
||
2629 | * @param double $txlocaltax2 Local tax 2 rate (deprecated, use instead txtva with code inside) |
||
2630 | * @param int $fk_product Id of predefined product/service |
||
2631 | * @param double $remise_percent Percent of discount on line |
||
2632 | * @param int $date_start Date start of service |
||
2633 | * @param int $date_end Date end of service |
||
2634 | * @param int $ventil Code of dispatching into accountancy |
||
2635 | * @param int $info_bits Bits de type de lignes |
||
2636 | * @param int $fk_remise_except Id discount used |
||
2637 | * @param string $price_base_type 'HT' or 'TTC' |
||
2638 | * @param double $pu_ttc Unit price with tax (> 0 even for credit note) |
||
2639 | * @param int $type Type of line (0=product, 1=service). Not used if fk_product is defined, the type of product is used. |
||
2640 | * @param int $rang Position of line |
||
2641 | * @param int $special_code Special code (also used by externals modules!) |
||
2642 | * @param string $origin 'order', ... |
||
2643 | * @param int $origin_id Id of origin object |
||
2644 | * @param int $fk_parent_line Id of parent line |
||
2645 | * @param int $fk_fournprice Supplier price id (to calculate margin) or '' |
||
2646 | * @param int $pa_ht Buying price of line (to calculate margin) or '' |
||
2647 | * @param string $label Label of the line (deprecated, do not use) |
||
2648 | * @param array $array_options extrafields array |
||
2649 | * @param int $situation_percent Situation advance percentage |
||
2650 | * @param int $fk_prev_id Previous situation line id reference |
||
2651 | * @param string $fk_unit Code of the unit to use. Null to use the default one |
||
2652 | * @param double $pu_ht_devise Unit price in currency |
||
2653 | * @return int <0 if KO, Id of line if OK |
||
2654 | */ |
||
2655 | function addline($desc, $pu_ht, $qty, $txtva, $txlocaltax1=0, $txlocaltax2=0, $fk_product=0, $remise_percent=0, $date_start='', $date_end='', $ventil=0, $info_bits=0, $fk_remise_except='', $price_base_type='HT', $pu_ttc=0, $type=self::TYPE_STANDARD, $rang=-1, $special_code=0, $origin='', $origin_id=0, $fk_parent_line=0, $fk_fournprice=null, $pa_ht=0, $label='', $array_options=0, $situation_percent=100, $fk_prev_id=0, $fk_unit = null, $pu_ht_devise = 0) |
||
2656 | { |
||
2657 | // Deprecation warning |
||
2658 | if ($label) { |
||
2659 | dol_syslog(__METHOD__ . ": using line label is deprecated", LOG_WARNING); |
||
2660 | //var_dump(debug_backtrace(false));exit; |
||
2661 | } |
||
2662 | |||
2663 | global $mysoc, $conf, $langs; |
||
2664 | |||
2665 | dol_syslog(get_class($this)."::addline id=$this->id,desc=$desc,pu_ht=$pu_ht,qty=$qty,txtva=$txtva, txlocaltax1=$txlocaltax1, txlocaltax2=$txlocaltax2, fk_product=$fk_product,remise_percent=$remise_percent,date_start=$date_start,date_end=$date_end,ventil=$ventil,info_bits=$info_bits,fk_remise_except=$fk_remise_except,price_base_type=$price_base_type,pu_ttc=$pu_ttc,type=$type, fk_unit=$fk_unit", LOG_DEBUG); |
||
2666 | if (! empty($this->brouillon)) |
||
2667 | { |
||
2668 | include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php'; |
||
2669 | |||
2670 | // Clean parameters |
||
2671 | if (empty($remise_percent)) $remise_percent=0; |
||
2672 | if (empty($qty)) $qty=0; |
||
2673 | if (empty($info_bits)) $info_bits=0; |
||
2674 | if (empty($rang)) $rang=0; |
||
2675 | if (empty($ventil)) $ventil=0; |
||
2676 | if (empty($txtva)) $txtva=0; |
||
2677 | if (empty($txlocaltax1)) $txlocaltax1=0; |
||
2678 | if (empty($txlocaltax2)) $txlocaltax2=0; |
||
2679 | if (empty($fk_parent_line) || $fk_parent_line < 0) $fk_parent_line=0; |
||
2680 | if (empty($fk_prev_id)) $fk_prev_id = 'null'; |
||
2681 | if (! isset($situation_percent) || $situation_percent > 100 || (string) $situation_percent == '') $situation_percent = 100; |
||
2682 | |||
2683 | $remise_percent=price2num($remise_percent); |
||
2684 | $qty=price2num($qty); |
||
2685 | $pu_ht=price2num($pu_ht); |
||
2686 | $pu_ht_devise=price2num($pu_ht_devise); |
||
2687 | $pu_ttc=price2num($pu_ttc); |
||
2688 | $pa_ht=price2num($pa_ht); |
||
2689 | if (!preg_match('/\((.*)\)/', $txtva)) { |
||
2690 | $txtva = price2num($txtva); // $txtva can have format '5.0(XXX)' or '5' |
||
2691 | } |
||
2692 | $txlocaltax1=price2num($txlocaltax1); |
||
2693 | $txlocaltax2=price2num($txlocaltax2); |
||
2694 | |||
2695 | if ($price_base_type=='HT') |
||
2696 | { |
||
2697 | $pu=$pu_ht; |
||
2698 | } |
||
2699 | else |
||
2700 | { |
||
2701 | $pu=$pu_ttc; |
||
2702 | } |
||
2703 | |||
2704 | // Check parameters |
||
2705 | if ($type < 0) return -1; |
||
2706 | |||
2707 | $this->db->begin(); |
||
2708 | |||
2709 | $product_type=$type; |
||
2710 | if (!empty($fk_product)) |
||
2711 | { |
||
2712 | $product=new Product($this->db); |
||
2713 | $result=$product->fetch($fk_product); |
||
2714 | $product_type=$product->type; |
||
2715 | |||
2716 | if (! empty($conf->global->STOCK_MUST_BE_ENOUGH_FOR_INVOICE) && $product_type == 0 && $product->stock_reel < $qty) { |
||
2717 | $langs->load("errors"); |
||
2718 | $this->error=$langs->trans('ErrorStockIsNotEnoughToAddProductOnInvoice', $product->ref); |
||
2719 | $this->db->rollback(); |
||
2720 | return -3; |
||
2721 | } |
||
2722 | } |
||
2723 | |||
2724 | $localtaxes_type=getLocalTaxesFromRate($txtva, 0, $this->thirdparty, $mysoc); |
||
2725 | |||
2726 | // Clean vat code |
||
2727 | $vat_src_code=''; |
||
2728 | if (preg_match('/\((.*)\)/', $txtva, $reg)) |
||
2729 | { |
||
2730 | $vat_src_code = $reg[1]; |
||
2731 | $txtva = preg_replace('/\s*\(.*\)/', '', $txtva); // Remove code into vatrate. |
||
2732 | } |
||
2733 | |||
2734 | // Calcul du total TTC et de la TVA pour la ligne a partir de |
||
2735 | // qty, pu, remise_percent et txtva |
||
2736 | // TRES IMPORTANT: C'est au moment de l'insertion ligne qu'on doit stocker |
||
2737 | // la part ht, tva et ttc, et ce au niveau de la ligne qui a son propre taux tva. |
||
2738 | |||
2739 | $tabprice = calcul_price_total($qty, $pu, $remise_percent, $txtva, $txlocaltax1, $txlocaltax2, 0, $price_base_type, $info_bits, $product_type, $mysoc, $localtaxes_type, $situation_percent, $this->multicurrency_tx, $pu_ht_devise); |
||
2740 | |||
2741 | $total_ht = $tabprice[0]; |
||
2742 | $total_tva = $tabprice[1]; |
||
2743 | $total_ttc = $tabprice[2]; |
||
2744 | $total_localtax1 = $tabprice[9]; |
||
2745 | $total_localtax2 = $tabprice[10]; |
||
2746 | $pu_ht = $tabprice[3]; |
||
2747 | |||
2748 | // MultiCurrency |
||
2749 | $multicurrency_total_ht = $tabprice[16]; |
||
2750 | $multicurrency_total_tva = $tabprice[17]; |
||
2751 | $multicurrency_total_ttc = $tabprice[18]; |
||
2752 | $pu_ht_devise = $tabprice[19]; |
||
2753 | |||
2754 | // Rank to use |
||
2755 | $rangtouse = $rang; |
||
2756 | if ($rangtouse == -1) |
||
2757 | { |
||
2758 | $rangmax = $this->line_max($fk_parent_line); |
||
2759 | $rangtouse = $rangmax + 1; |
||
2760 | } |
||
2761 | |||
2762 | // Insert line |
||
2763 | $this->line=new FactureLigne($this->db); |
||
2764 | |||
2765 | $this->line->context = $this->context; |
||
2766 | |||
2767 | $this->line->fk_facture=$this->id; |
||
2768 | $this->line->label=$label; // deprecated |
||
2769 | $this->line->desc=$desc; |
||
2770 | |||
2771 | $this->line->qty= ($this->type==self::TYPE_CREDIT_NOTE?abs($qty):$qty); // For credit note, quantity is always positive and unit price negative |
||
2772 | $this->line->subprice= ($this->type==self::TYPE_CREDIT_NOTE?-abs($pu_ht):$pu_ht); // For credit note, unit price always negative, always positive otherwise |
||
2773 | |||
2774 | $this->line->vat_src_code=$vat_src_code; |
||
2775 | $this->line->tva_tx=$txtva; |
||
2776 | $this->line->localtax1_tx=($total_localtax1?$localtaxes_type[1]:0); |
||
2777 | $this->line->localtax2_tx=($total_localtax2?$localtaxes_type[3]:0); |
||
2778 | $this->line->localtax1_type = $localtaxes_type[0]; |
||
2779 | $this->line->localtax2_type = $localtaxes_type[2]; |
||
2780 | |||
2781 | $this->line->total_ht= (($this->type==self::TYPE_CREDIT_NOTE||$qty<0)?-abs($total_ht):$total_ht); // For credit note and if qty is negative, total is negative |
||
2782 | $this->line->total_ttc= (($this->type==self::TYPE_CREDIT_NOTE||$qty<0)?-abs($total_ttc):$total_ttc); // For credit note and if qty is negative, total is negative |
||
2783 | $this->line->total_tva= (($this->type==self::TYPE_CREDIT_NOTE||$qty<0)?-abs($total_tva):$total_tva); // For credit note and if qty is negative, total is negative |
||
2784 | $this->line->total_localtax1=(($this->type==self::TYPE_CREDIT_NOTE||$qty<0)?-abs($total_localtax1):$total_localtax1); // For credit note and if qty is negative, total is negative |
||
2785 | $this->line->total_localtax2=(($this->type==self::TYPE_CREDIT_NOTE||$qty<0)?-abs($total_localtax2):$total_localtax2); // For credit note and if qty is negative, total is negative |
||
2786 | |||
2787 | $this->line->fk_product=$fk_product; |
||
2788 | $this->line->product_type=$product_type; |
||
2789 | $this->line->remise_percent=$remise_percent; |
||
2790 | $this->line->date_start=$date_start; |
||
2791 | $this->line->date_end=$date_end; |
||
2792 | $this->line->ventil=$ventil; |
||
2793 | $this->line->rang=$rangtouse; |
||
2794 | $this->line->info_bits=$info_bits; |
||
2795 | $this->line->fk_remise_except=$fk_remise_except; |
||
2796 | |||
2797 | $this->line->special_code=$special_code; |
||
2798 | $this->line->fk_parent_line=$fk_parent_line; |
||
2799 | $this->line->origin=$origin; |
||
2800 | $this->line->origin_id=$origin_id; |
||
2801 | $this->line->situation_percent = $situation_percent; |
||
2802 | $this->line->fk_prev_id = $fk_prev_id; |
||
2803 | $this->line->fk_unit=$fk_unit; |
||
2804 | |||
2805 | // infos marge |
||
2806 | $this->line->fk_fournprice = $fk_fournprice; |
||
2807 | $this->line->pa_ht = $pa_ht; |
||
2808 | |||
2809 | // Multicurrency |
||
2810 | $this->line->fk_multicurrency = $this->fk_multicurrency; |
||
2811 | $this->line->multicurrency_code = $this->multicurrency_code; |
||
2812 | $this->line->multicurrency_subprice = $pu_ht_devise; |
||
2813 | $this->line->multicurrency_total_ht = $multicurrency_total_ht; |
||
2814 | $this->line->multicurrency_total_tva = $multicurrency_total_tva; |
||
2815 | $this->line->multicurrency_total_ttc = $multicurrency_total_ttc; |
||
2816 | |||
2817 | if (is_array($array_options) && count($array_options)>0) { |
||
2818 | $this->line->array_options=$array_options; |
||
2819 | } |
||
2820 | |||
2821 | $result=$this->line->insert(); |
||
2822 | if ($result > 0) |
||
2823 | { |
||
2824 | // Reorder if child line |
||
2825 | if (! empty($fk_parent_line)) $this->line_order(true,'DESC'); |
||
2826 | |||
2827 | // Mise a jour informations denormalisees au niveau de la facture meme |
||
2828 | $result=$this->update_price(1,'auto',0,$mysoc); // The addline method is designed to add line from user input so total calculation with update_price must be done using 'auto' mode. |
||
2829 | |||
2830 | if ($result > 0) |
||
2831 | { |
||
2832 | $this->db->commit(); |
||
2833 | return $this->line->id; |
||
2834 | } |
||
2835 | else |
||
2836 | { |
||
2837 | $this->error=$this->db->lasterror(); |
||
2838 | $this->db->rollback(); |
||
2839 | return -1; |
||
2840 | } |
||
2841 | } |
||
2842 | else |
||
2843 | { |
||
2844 | $this->error=$this->line->error; |
||
2845 | $this->db->rollback(); |
||
2846 | return -2; |
||
2847 | } |
||
2848 | } |
||
2849 | else |
||
2850 | { |
||
2851 | dol_syslog(get_class($this)."::addline status of order must be Draft to allow use of ->addline()", LOG_ERR); |
||
2852 | return -3; |
||
2853 | } |
||
2854 | } |
||
2855 | |||
2856 | /** |
||
2857 | * Update a detail line |
||
2858 | * |
||
2859 | * @param int $rowid Id of line to update |
||
2860 | * @param string $desc Description of line |
||
2861 | * @param double $pu Prix unitaire (HT ou TTC selon price_base_type) (> 0 even for credit note lines) |
||
2862 | * @param double $qty Quantity |
||
2863 | * @param double $remise_percent Pourcentage de remise de la ligne |
||
2864 | * @param int $date_start Date de debut de validite du service |
||
2865 | * @param int $date_end Date de fin de validite du service |
||
2866 | * @param double $txtva VAT Rate (Can be '8.5', '8.5 (ABC)') |
||
2867 | * @param double $txlocaltax1 Local tax 1 rate |
||
2868 | * @param double $txlocaltax2 Local tax 2 rate |
||
2869 | * @param string $price_base_type HT or TTC |
||
2870 | * @param int $info_bits Miscellaneous informations |
||
2871 | * @param int $type Type of line (0=product, 1=service) |
||
2872 | * @param int $fk_parent_line Id of parent line (0 in most cases, used by modules adding sublevels into lines). |
||
2873 | * @param int $skip_update_total Keep fields total_xxx to 0 (used for special lines by some modules) |
||
2874 | * @param int $fk_fournprice Id of origin supplier price |
||
2875 | * @param int $pa_ht Price (without tax) of product when it was bought |
||
2876 | * @param string $label Label of the line (deprecated, do not use) |
||
2877 | * @param int $special_code Special code (also used by externals modules!) |
||
2878 | * @param array $array_options extrafields array |
||
2879 | * @param int $situation_percent Situation advance percentage |
||
2880 | * @param string $fk_unit Code of the unit to use. Null to use the default one |
||
2881 | * @param double $pu_ht_devise Unit price in currency |
||
2882 | * @param int $notrigger disable line update trigger |
||
2883 | * @return int < 0 if KO, > 0 if OK |
||
2884 | */ |
||
2885 | function updateline($rowid, $desc, $pu, $qty, $remise_percent, $date_start, $date_end, $txtva, $txlocaltax1=0, $txlocaltax2=0, $price_base_type='HT', $info_bits=0, $type= self::TYPE_STANDARD, $fk_parent_line=0, $skip_update_total=0, $fk_fournprice=null, $pa_ht=0, $label='', $special_code=0, $array_options=0, $situation_percent=100, $fk_unit = null, $pu_ht_devise = 0, $notrigger=0) |
||
2886 | { |
||
2887 | global $conf,$user; |
||
2888 | // Deprecation warning |
||
2889 | if ($label) { |
||
2890 | dol_syslog(__METHOD__ . ": using line label is deprecated", LOG_WARNING); |
||
2891 | } |
||
2892 | |||
2893 | include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php'; |
||
2894 | |||
2895 | global $mysoc,$langs; |
||
2896 | |||
2897 | dol_syslog(get_class($this)."::updateline rowid=$rowid, desc=$desc, pu=$pu, qty=$qty, remise_percent=$remise_percent, date_start=$date_start, date_end=$date_end, txtva=$txtva, txlocaltax1=$txlocaltax1, txlocaltax2=$txlocaltax2, price_base_type=$price_base_type, info_bits=$info_bits, type=$type, fk_parent_line=$fk_parent_line pa_ht=$pa_ht, special_code=$special_code, fk_unit=$fk_unit, pu_ht_devise=$pu_ht_devise", LOG_DEBUG); |
||
2898 | |||
2899 | if ($this->brouillon) |
||
2900 | { |
||
2901 | if (!$this->is_last_in_cycle() && empty($this->error)) |
||
2902 | { |
||
2903 | if (!$this->checkProgressLine($rowid, $situation_percent)) |
||
2904 | { |
||
2905 | if (!$this->error) $this->error=$langs->trans('invoiceLineProgressError'); |
||
2906 | return -3; |
||
2907 | } |
||
2908 | } |
||
2909 | |||
2910 | $this->db->begin(); |
||
2911 | |||
2912 | // Clean parameters |
||
2913 | if (empty($qty)) $qty=0; |
||
2914 | if (empty($fk_parent_line) || $fk_parent_line < 0) $fk_parent_line=0; |
||
2915 | if (empty($special_code) || $special_code == 3) $special_code=0; |
||
2916 | if (! isset($situation_percent) || $situation_percent > 100 || (string) $situation_percent == '') $situation_percent = 100; |
||
2917 | |||
2918 | $remise_percent = price2num($remise_percent); |
||
2919 | $qty = price2num($qty); |
||
2920 | $pu = price2num($pu); |
||
2921 | $pu_ht_devise = price2num($pu_ht_devise); |
||
2922 | $pa_ht = price2num($pa_ht); |
||
2923 | $txtva = price2num($txtva); |
||
2924 | $txlocaltax1 = price2num($txlocaltax1); |
||
2925 | $txlocaltax2 = price2num($txlocaltax2); |
||
2926 | |||
2927 | // Check parameters |
||
2928 | if ($type < 0) return -1; |
||
2929 | |||
2930 | // Calculate total with, without tax and tax from qty, pu, remise_percent and txtva |
||
2931 | // TRES IMPORTANT: C'est au moment de l'insertion ligne qu'on doit stocker |
||
2932 | // la part ht, tva et ttc, et ce au niveau de la ligne qui a son propre taux tva. |
||
2933 | |||
2934 | $localtaxes_type=getLocalTaxesFromRate($txtva,0,$this->thirdparty, $mysoc); |
||
2935 | |||
2936 | // Clean vat code |
||
2937 | $vat_src_code=''; |
||
2938 | if (preg_match('/\((.*)\)/', $txtva, $reg)) |
||
2939 | { |
||
2940 | $vat_src_code = $reg[1]; |
||
2941 | $txtva = preg_replace('/\s*\(.*\)/', '', $txtva); // Remove code into vatrate. |
||
2942 | } |
||
2943 | |||
2944 | $tabprice=calcul_price_total($qty, $pu, $remise_percent, $txtva, $txlocaltax1, $txlocaltax2, 0, $price_base_type, $info_bits, $type, $mysoc, $localtaxes_type, $situation_percent, $this->multicurrency_tx, $pu_ht_devise); |
||
2945 | |||
2946 | $total_ht = $tabprice[0]; |
||
2947 | $total_tva = $tabprice[1]; |
||
2948 | $total_ttc = $tabprice[2]; |
||
2949 | $total_localtax1=$tabprice[9]; |
||
2950 | $total_localtax2=$tabprice[10]; |
||
2951 | $pu_ht = $tabprice[3]; |
||
2952 | $pu_tva = $tabprice[4]; |
||
2953 | $pu_ttc = $tabprice[5]; |
||
2954 | |||
2955 | // MultiCurrency |
||
2956 | $multicurrency_total_ht = $tabprice[16]; |
||
2957 | $multicurrency_total_tva = $tabprice[17]; |
||
2958 | $multicurrency_total_ttc = $tabprice[18]; |
||
2959 | $pu_ht_devise = $tabprice[19]; |
||
2960 | |||
2961 | // Old properties: $price, $remise (deprecated) |
||
2962 | $price = $pu; |
||
2963 | $remise = 0; |
||
2964 | if ($remise_percent > 0) |
||
2965 | { |
||
2966 | $remise = round(($pu * $remise_percent / 100),2); |
||
2967 | $price = ($pu - $remise); |
||
2968 | } |
||
2969 | $price = price2num($price); |
||
2970 | |||
2971 | //Fetch current line from the database and then clone the object and set it in $oldline property |
||
2972 | $line = new FactureLigne($this->db); |
||
2973 | $line->fetch($rowid); |
||
2974 | |||
2975 | if (!empty($line->fk_product)) |
||
2976 | { |
||
2977 | $product=new Product($this->db); |
||
2978 | $result=$product->fetch($line->fk_product); |
||
2979 | $product_type=$product->type; |
||
2980 | |||
2981 | if (! empty($conf->global->STOCK_MUST_BE_ENOUGH_FOR_INVOICE) && $product_type == 0 && $product->stock_reel < $qty) { |
||
2982 | $langs->load("errors"); |
||
2983 | $this->error=$langs->trans('ErrorStockIsNotEnoughToAddProductOnInvoice', $product->ref); |
||
2984 | $this->db->rollback(); |
||
2985 | return -3; |
||
2986 | } |
||
2987 | } |
||
2988 | |||
2989 | $staticline = clone $line; |
||
2990 | |||
2991 | $line->oldline = $staticline; |
||
2992 | $this->line = $line; |
||
2993 | $this->line->context = $this->context; |
||
2994 | |||
2995 | // Reorder if fk_parent_line change |
||
2996 | if (! empty($fk_parent_line) && ! empty($staticline->fk_parent_line) && $fk_parent_line != $staticline->fk_parent_line) |
||
2997 | { |
||
2998 | $rangmax = $this->line_max($fk_parent_line); |
||
2999 | $this->line->rang = $rangmax + 1; |
||
3000 | } |
||
3001 | |||
3002 | $this->line->rowid = $rowid; |
||
3003 | $this->line->label = $label; |
||
3004 | $this->line->desc = $desc; |
||
3005 | $this->line->qty = ($this->type==self::TYPE_CREDIT_NOTE?abs($qty):$qty); // For credit note, quantity is always positive and unit price negative |
||
3006 | |||
3007 | $this->line->vat_src_code = $vat_src_code; |
||
3008 | $this->line->tva_tx = $txtva; |
||
3009 | $this->line->localtax1_tx = $txlocaltax1; |
||
3010 | $this->line->localtax2_tx = $txlocaltax2; |
||
3011 | $this->line->localtax1_type = $localtaxes_type[0]; |
||
3012 | $this->line->localtax2_type = $localtaxes_type[2]; |
||
3013 | |||
3014 | $this->line->remise_percent = $remise_percent; |
||
3015 | $this->line->subprice = ($this->type==2?-abs($pu_ht):$pu_ht); // For credit note, unit price always negative, always positive otherwise |
||
3016 | $this->line->date_start = $date_start; |
||
3017 | $this->line->date_end = $date_end; |
||
3018 | $this->line->total_ht = (($this->type==self::TYPE_CREDIT_NOTE||$qty<0)?-abs($total_ht):$total_ht); // For credit note and if qty is negative, total is negative |
||
3019 | $this->line->total_tva = (($this->type==self::TYPE_CREDIT_NOTE||$qty<0)?-abs($total_tva):$total_tva); |
||
3020 | $this->line->total_localtax1 = $total_localtax1; |
||
3021 | $this->line->total_localtax2 = $total_localtax2; |
||
3022 | $this->line->total_ttc = (($this->type==self::TYPE_CREDIT_NOTE||$qty<0)?-abs($total_ttc):$total_ttc); |
||
3023 | $this->line->info_bits = $info_bits; |
||
3024 | $this->line->special_code = $special_code; |
||
3025 | $this->line->product_type = $type; |
||
3026 | $this->line->fk_parent_line = $fk_parent_line; |
||
3027 | $this->line->skip_update_total = $skip_update_total; |
||
3028 | $this->line->situation_percent = $situation_percent; |
||
3029 | $this->line->fk_unit = $fk_unit; |
||
3030 | |||
3031 | $this->line->fk_fournprice = $fk_fournprice; |
||
3032 | $this->line->pa_ht = $pa_ht; |
||
3033 | |||
3034 | // Multicurrency |
||
3035 | $this->line->multicurrency_subprice = $pu_ht_devise; |
||
3036 | $this->line->multicurrency_total_ht = $multicurrency_total_ht; |
||
3037 | $this->line->multicurrency_total_tva = $multicurrency_total_tva; |
||
3038 | $this->line->multicurrency_total_ttc = $multicurrency_total_ttc; |
||
3039 | |||
3040 | if (is_array($array_options) && count($array_options)>0) { |
||
3041 | $this->line->array_options=$array_options; |
||
3042 | } |
||
3043 | |||
3044 | $result=$this->line->update($user, $notrigger); |
||
3045 | if ($result > 0) |
||
3046 | { |
||
3047 | // Reorder if child line |
||
3048 | if (! empty($fk_parent_line)) $this->line_order(true,'DESC'); |
||
3049 | |||
3050 | // Mise a jour info denormalisees au niveau facture |
||
3051 | $this->update_price(1); |
||
3052 | $this->db->commit(); |
||
3053 | return $result; |
||
3054 | } |
||
3055 | else |
||
3056 | { |
||
3057 | $this->error=$this->line->error; |
||
3058 | $this->db->rollback(); |
||
3059 | return -1; |
||
3060 | } |
||
3061 | } |
||
3062 | else |
||
3063 | { |
||
3064 | $this->error="Invoice statut makes operation forbidden"; |
||
3065 | return -2; |
||
3066 | } |
||
3067 | } |
||
3068 | |||
3069 | /** |
||
3070 | * Check if the percent edited is lower of next invoice line |
||
3071 | * |
||
3072 | * @param int $idline id of line to check |
||
3073 | * @param float $situation_percent progress percentage need to be test |
||
3074 | * @return false if KO, true if OK |
||
3075 | */ |
||
3076 | function checkProgressLine($idline, $situation_percent) |
||
3077 | { |
||
3078 | $sql = 'SELECT fd.situation_percent FROM '.MAIN_DB_PREFIX.'facturedet fd |
||
3079 | INNER JOIN '.MAIN_DB_PREFIX.'facture f ON (fd.fk_facture = f.rowid) |
||
3080 | WHERE fd.fk_prev_id = '.$idline.' |
||
3081 | AND f.fk_statut <> 0'; |
||
3082 | |||
3083 | $result = $this->db->query($sql); |
||
3084 | if (! $result) |
||
3085 | { |
||
3086 | $this->error=$this->db->error(); |
||
3087 | return false; |
||
3088 | } |
||
3089 | |||
3090 | $obj = $this->db->fetch_object($result); |
||
3091 | |||
3092 | if ($obj === null) return true; |
||
3093 | else return $situation_percent < $obj->situation_percent; |
||
3094 | } |
||
3095 | |||
3096 | // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps |
||
3097 | /** |
||
3098 | * Update invoice line with percentage |
||
3099 | * |
||
3100 | * @param FactureLigne $line Invoice line |
||
3101 | * @param int $percent Percentage |
||
3102 | * @return void |
||
3103 | */ |
||
3104 | function update_percent($line, $percent) |
||
3105 | { |
||
3106 | // phpcs:enable |
||
3107 | global $mysoc,$user; |
||
3108 | |||
3109 | include_once DOL_DOCUMENT_ROOT . '/core/lib/price.lib.php'; |
||
3110 | |||
3111 | // Cap percentages to 100 |
||
3112 | if ($percent > 100) $percent = 100; |
||
3113 | $line->situation_percent = $percent; |
||
3114 | $tabprice = calcul_price_total($line->qty, $line->subprice, $line->remise_percent, $line->tva_tx, $line->localtax1_tx, $line->localtax2_tx, 0, 'HT', 0, $line->product_type, $mysoc, '', $percent); |
||
3115 | $line->total_ht = $tabprice[0]; |
||
3116 | $line->total_tva = $tabprice[1]; |
||
3117 | $line->total_ttc = $tabprice[2]; |
||
3118 | $line->total_localtax1 = $tabprice[9]; |
||
3119 | $line->total_localtax2 = $tabprice[10]; |
||
3120 | $line->multicurrency_total_ht = $tabprice[16]; |
||
3121 | $line->multicurrency_total_tva = $tabprice[17]; |
||
3122 | $line->multicurrency_total_ttc = $tabprice[18]; |
||
3123 | $line->update($user); |
||
3124 | $this->update_price(1); |
||
3125 | $this->db->commit(); |
||
3126 | } |
||
3127 | |||
3128 | /** |
||
3129 | * Delete line in database |
||
3130 | * |
||
3131 | * @param int $rowid Id of line to delete |
||
3132 | * @return int <0 if KO, >0 if OK |
||
3133 | */ |
||
3134 | function deleteline($rowid) |
||
3135 | { |
||
3136 | global $user; |
||
3137 | |||
3138 | dol_syslog(get_class($this)."::deleteline rowid=".$rowid, LOG_DEBUG); |
||
3139 | |||
3140 | if (! $this->brouillon) |
||
3141 | { |
||
3142 | $this->error='ErrorDeleteLineNotAllowedByObjectStatus'; |
||
3143 | return -1; |
||
3144 | } |
||
3145 | |||
3146 | $this->db->begin(); |
||
3147 | |||
3148 | // Libere remise liee a ligne de facture |
||
3149 | $sql = 'UPDATE '.MAIN_DB_PREFIX.'societe_remise_except'; |
||
3150 | $sql.= ' SET fk_facture_line = NULL'; |
||
3151 | $sql.= ' WHERE fk_facture_line = '.$rowid; |
||
3152 | |||
3153 | dol_syslog(get_class($this)."::deleteline", LOG_DEBUG); |
||
3154 | $result = $this->db->query($sql); |
||
3155 | if (! $result) |
||
3156 | { |
||
3157 | $this->error=$this->db->error(); |
||
3158 | $this->db->rollback(); |
||
3159 | return -1; |
||
3160 | } |
||
3161 | |||
3162 | $line=new FactureLigne($this->db); |
||
3163 | |||
3164 | $line->context = $this->context; |
||
3165 | |||
3166 | // For triggers |
||
3167 | $result = $line->fetch($rowid); |
||
3168 | if (! ($result > 0)) dol_print_error($this->db, $line->error, $line->errors); |
||
3169 | |||
3170 | if ($line->delete($user) > 0) |
||
3171 | { |
||
3172 | $result=$this->update_price(1); |
||
3173 | |||
3174 | if ($result > 0) |
||
3175 | { |
||
3176 | $this->db->commit(); |
||
3177 | return 1; |
||
3178 | } |
||
3179 | else |
||
3180 | { |
||
3181 | $this->db->rollback(); |
||
3182 | $this->error=$this->db->lasterror(); |
||
3183 | return -1; |
||
3184 | } |
||
3185 | } |
||
3186 | else |
||
3187 | { |
||
3188 | $this->db->rollback(); |
||
3189 | $this->error=$line->error; |
||
3190 | return -1; |
||
3191 | } |
||
3192 | } |
||
3193 | |||
3194 | // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps |
||
3195 | /** |
||
3196 | * Set percent discount |
||
3197 | * |
||
3198 | * @param User $user User that set discount |
||
3199 | * @param double $remise Discount |
||
3200 | * @param int $notrigger 1=Does not execute triggers, 0= execute triggers |
||
3201 | * @return int <0 if ko, >0 if ok |
||
3202 | */ |
||
3203 | function set_remise($user, $remise, $notrigger=0) |
||
3204 | { |
||
3205 | // phpcs:enable |
||
3206 | // Clean parameters |
||
3207 | if (empty($remise)) $remise=0; |
||
3208 | |||
3209 | if ($user->rights->facture->creer) |
||
3210 | { |
||
3211 | $remise=price2num($remise); |
||
3212 | |||
3213 | $error=0; |
||
3214 | |||
3215 | $this->db->begin(); |
||
3216 | |||
3217 | $sql = 'UPDATE '.MAIN_DB_PREFIX.'facture'; |
||
3218 | $sql.= ' SET remise_percent = '.$remise; |
||
3219 | $sql.= ' WHERE rowid = '.$this->id; |
||
3220 | $sql.= ' AND fk_statut = '.self::STATUS_DRAFT; |
||
3221 | |||
3222 | dol_syslog(__METHOD__, LOG_DEBUG); |
||
3223 | $resql=$this->db->query($sql); |
||
3224 | if (!$resql) |
||
3225 | { |
||
3226 | $this->errors[]=$this->db->error(); |
||
3227 | $error++; |
||
3228 | } |
||
3229 | |||
3230 | if (! $notrigger && empty($error)) |
||
3231 | { |
||
3232 | // Call trigger |
||
3233 | $result=$this->call_trigger('BILL_MODIFY',$user); |
||
3234 | if ($result < 0) $error++; |
||
3235 | // End call triggers |
||
3236 | } |
||
3237 | |||
3238 | if (! $error) |
||
3239 | { |
||
3240 | $this->remise_percent = $remise; |
||
3241 | $this->update_price(1); |
||
3242 | |||
3243 | $this->db->commit(); |
||
3244 | return 1; |
||
3245 | } |
||
3246 | else |
||
3247 | { |
||
3248 | foreach($this->errors as $errmsg) |
||
3249 | { |
||
3250 | dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR); |
||
3251 | $this->error.=($this->error?', '.$errmsg:$errmsg); |
||
3252 | } |
||
3253 | $this->db->rollback(); |
||
3254 | return -1*$error; |
||
3255 | } |
||
3256 | } |
||
3257 | } |
||
3258 | |||
3259 | |||
3260 | // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps |
||
3261 | /** |
||
3262 | * Set absolute discount |
||
3263 | * |
||
3264 | * @param User $user User that set discount |
||
3265 | * @param double $remise Discount |
||
3266 | * @param int $notrigger 1=Does not execute triggers, 0= execute triggers |
||
3267 | * @return int <0 if KO, >0 if OK |
||
3268 | */ |
||
3269 | function set_remise_absolue($user, $remise, $notrigger=0) |
||
3270 | { |
||
3271 | // phpcs:enable |
||
3272 | if (empty($remise)) $remise=0; |
||
3273 | |||
3274 | if ($user->rights->facture->creer) |
||
3275 | { |
||
3276 | $error=0; |
||
3277 | |||
3278 | $this->db->begin(); |
||
3279 | |||
3280 | $remise=price2num($remise); |
||
3281 | |||
3282 | $sql = 'UPDATE '.MAIN_DB_PREFIX.'facture'; |
||
3283 | $sql.= ' SET remise_absolue = '.$remise; |
||
3284 | $sql.= ' WHERE rowid = '.$this->id; |
||
3285 | $sql.= ' AND fk_statut = '.self::STATUS_DRAFT; |
||
3286 | |||
3287 | dol_syslog(__METHOD__, LOG_DEBUG); |
||
3288 | $resql=$this->db->query($sql); |
||
3289 | if (!$resql) |
||
3290 | { |
||
3291 | $this->errors[]=$this->db->error(); |
||
3292 | $error++; |
||
3293 | } |
||
3294 | |||
3295 | if (! $error) |
||
3296 | { |
||
3297 | $this->oldcopy= clone $this; |
||
3298 | $this->remise_absolue = $remise; |
||
3299 | $this->update_price(1); |
||
3300 | } |
||
3301 | |||
3302 | if (! $notrigger && empty($error)) |
||
3303 | { |
||
3304 | // Call trigger |
||
3305 | $result=$this->call_trigger('BILL_MODIFY',$user); |
||
3306 | if ($result < 0) $error++; |
||
3307 | // End call triggers |
||
3308 | } |
||
3309 | |||
3310 | if (! $error) |
||
3311 | { |
||
3312 | $this->db->commit(); |
||
3313 | return 1; |
||
3314 | } |
||
3315 | else |
||
3316 | { |
||
3317 | foreach($this->errors as $errmsg) |
||
3318 | { |
||
3319 | dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR); |
||
3320 | $this->error.=($this->error?', '.$errmsg:$errmsg); |
||
3321 | } |
||
3322 | $this->db->rollback(); |
||
3323 | return -1*$error; |
||
3324 | } |
||
3325 | } |
||
3326 | } |
||
3327 | |||
3328 | /** |
||
3329 | * Return next reference of customer invoice not already used (or last reference) |
||
3330 | * according to numbering module defined into constant FACTURE_ADDON |
||
3331 | * |
||
3332 | * @param Societe $soc object company |
||
3333 | * @param string $mode 'next' for next value or 'last' for last value |
||
3334 | * @return string free ref or last ref |
||
3335 | */ |
||
3336 | function getNextNumRef($soc,$mode='next') |
||
3337 | { |
||
3338 | global $conf, $langs; |
||
3339 | $langs->load("bills"); |
||
3340 | |||
3341 | // Clean parameters (if not defined or using deprecated value) |
||
3342 | if (empty($conf->global->FACTURE_ADDON)) $conf->global->FACTURE_ADDON='mod_facture_terre'; |
||
3343 | else if ($conf->global->FACTURE_ADDON=='terre') $conf->global->FACTURE_ADDON='mod_facture_terre'; |
||
3344 | else if ($conf->global->FACTURE_ADDON=='mercure') $conf->global->FACTURE_ADDON='mod_facture_mercure'; |
||
3345 | |||
3346 | if (! empty($conf->global->FACTURE_ADDON)) |
||
3347 | { |
||
3348 | dol_syslog("Call getNextNumRef with FACTURE_ADDON = ".$conf->global->FACTURE_ADDON.", thirdparty=".$soc->nom.", type=".$soc->typent_code, LOG_DEBUG); |
||
3349 | |||
3350 | $mybool=false; |
||
3351 | |||
3352 | $file = $conf->global->FACTURE_ADDON.".php"; |
||
3353 | $classname = $conf->global->FACTURE_ADDON; |
||
3354 | |||
3355 | // Include file with class |
||
3356 | $dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']); |
||
3357 | |||
3358 | foreach ($dirmodels as $reldir) { |
||
3359 | |||
3360 | $dir = dol_buildpath($reldir."core/modules/facture/"); |
||
3361 | |||
3362 | // Load file with numbering class (if found) |
||
3363 | if (is_file($dir.$file) && is_readable($dir.$file)) |
||
3364 | { |
||
3365 | $mybool |= include_once $dir . $file; |
||
3366 | } |
||
3367 | } |
||
3368 | |||
3369 | // For compatibility |
||
3370 | if (! $mybool) |
||
3371 | { |
||
3372 | $file = $conf->global->FACTURE_ADDON."/".$conf->global->FACTURE_ADDON.".modules.php"; |
||
3373 | $classname = "mod_facture_".$conf->global->FACTURE_ADDON; |
||
3374 | $classname = preg_replace('/\-.*$/','',$classname); |
||
3375 | // Include file with class |
||
3376 | foreach ($conf->file->dol_document_root as $dirroot) |
||
3377 | { |
||
3378 | $dir = $dirroot."/core/modules/facture/"; |
||
3379 | |||
3380 | // Load file with numbering class (if found) |
||
3381 | if (is_file($dir.$file) && is_readable($dir.$file)) { |
||
3382 | $mybool |= include_once $dir . $file; |
||
3383 | } |
||
3384 | } |
||
3385 | } |
||
3386 | |||
3387 | if (! $mybool) |
||
3388 | { |
||
3389 | dol_print_error('',"Failed to include file ".$file); |
||
3390 | return ''; |
||
3391 | } |
||
3392 | |||
3393 | $obj = new $classname(); |
||
3394 | $numref = ""; |
||
3395 | $numref = $obj->getNextValue($soc,$this,$mode); |
||
3396 | |||
3397 | /** |
||
3398 | * $numref can be empty in case we ask for the last value because if there is no invoice created with the |
||
3399 | * set up mask. |
||
3400 | */ |
||
3401 | if ($mode != 'last' && !$numref) { |
||
3402 | $this->error=$obj->error; |
||
3403 | //dol_print_error($this->db,"Facture::getNextNumRef ".$obj->error); |
||
3404 | return ""; |
||
3405 | } |
||
3406 | |||
3407 | return $numref; |
||
3408 | } |
||
3409 | else |
||
3410 | { |
||
3411 | $langs->load("errors"); |
||
3412 | print $langs->trans("Error")." ".$langs->trans("ErrorModuleSetupNotComplete"); |
||
3413 | return ""; |
||
3414 | } |
||
3415 | } |
||
3416 | |||
3417 | /** |
||
3418 | * Load miscellaneous information for tab "Info" |
||
3419 | * |
||
3420 | * @param int $id Id of object to load |
||
3421 | * @return void |
||
3422 | */ |
||
3423 | function info($id) |
||
3424 | { |
||
3425 | $sql = 'SELECT c.rowid, datec, date_valid as datev, tms as datem,'; |
||
3426 | $sql.= ' fk_user_author, fk_user_valid'; |
||
3427 | $sql.= ' FROM '.MAIN_DB_PREFIX.'facture as c'; |
||
3428 | $sql.= ' WHERE c.rowid = '.$id; |
||
3429 | |||
3430 | $result=$this->db->query($sql); |
||
3431 | if ($result) |
||
3432 | { |
||
3433 | if ($this->db->num_rows($result)) |
||
3434 | { |
||
3435 | $obj = $this->db->fetch_object($result); |
||
3436 | $this->id = $obj->rowid; |
||
3437 | if ($obj->fk_user_author) |
||
3438 | { |
||
3439 | $cuser = new User($this->db); |
||
3440 | $cuser->fetch($obj->fk_user_author); |
||
3441 | $this->user_creation = $cuser; |
||
3442 | } |
||
3443 | if ($obj->fk_user_valid) |
||
3444 | { |
||
3445 | $vuser = new User($this->db); |
||
3446 | $vuser->fetch($obj->fk_user_valid); |
||
3447 | $this->user_validation = $vuser; |
||
3448 | } |
||
3449 | $this->date_creation = $this->db->jdate($obj->datec); |
||
3450 | $this->date_modification = $this->db->jdate($obj->datem); |
||
3451 | $this->date_validation = $this->db->jdate($obj->datev); // Should be in log table |
||
3452 | } |
||
3453 | $this->db->free($result); |
||
3454 | } |
||
3455 | else |
||
3456 | { |
||
3457 | dol_print_error($this->db); |
||
3458 | } |
||
3459 | } |
||
3460 | |||
3461 | |||
3462 | // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps |
||
3463 | /** |
||
3464 | * Return list of invoices (eventually filtered on a user) into an array |
||
3465 | * |
||
3466 | * @param int $shortlist 0=Return array[id]=ref, 1=Return array[](id=>id,ref=>ref,name=>name) |
||
3467 | * @param int $draft 0=not draft, 1=draft |
||
3468 | * @param User $excluser Objet user to exclude |
||
3469 | * @param int $socid Id third pary |
||
3470 | * @param int $limit For pagination |
||
3471 | * @param int $offset For pagination |
||
3472 | * @param string $sortfield Sort criteria |
||
3473 | * @param string $sortorder Sort order |
||
3474 | * @return int -1 if KO, array with result if OK |
||
3475 | */ |
||
3476 | function liste_array($shortlist=0, $draft=0, $excluser='', $socid=0, $limit=0, $offset=0, $sortfield='f.datef,f.rowid', $sortorder='DESC') |
||
3477 | { |
||
3478 | // phpcs:enable |
||
3479 | global $conf,$user; |
||
3480 | |||
3481 | $ga = array(); |
||
3482 | |||
3483 | $sql = "SELECT s.rowid, s.nom as name, s.client,"; |
||
3484 | $sql.= " f.rowid as fid, f.ref as ref, f.datef as df"; |
||
3485 | if (! $user->rights->societe->client->voir && ! $socid) $sql .= ", sc.fk_soc, sc.fk_user"; |
||
3486 | $sql.= " FROM ".MAIN_DB_PREFIX."societe as s, ".MAIN_DB_PREFIX."facture as f"; |
||
3487 | if (! $user->rights->societe->client->voir && ! $socid) $sql .= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc"; |
||
3488 | $sql.= " WHERE f.entity = ".$conf->entity; |
||
3489 | $sql.= " AND f.fk_soc = s.rowid"; |
||
3490 | if (! $user->rights->societe->client->voir && ! $socid) //restriction |
||
3491 | { |
||
3492 | $sql.= " AND s.rowid = sc.fk_soc AND sc.fk_user = " .$user->id; |
||
3493 | } |
||
3494 | if ($socid) $sql.= " AND s.rowid = ".$socid; |
||
3495 | if ($draft) $sql.= " AND f.fk_statut = ".self::STATUS_DRAFT; |
||
3496 | if (is_object($excluser)) $sql.= " AND f.fk_user_author <> ".$excluser->id; |
||
3497 | $sql.= $this->db->order($sortfield,$sortorder); |
||
3498 | $sql.= $this->db->plimit($limit,$offset); |
||
3499 | |||
3500 | $result=$this->db->query($sql); |
||
3501 | if ($result) |
||
3502 | { |
||
3503 | $numc = $this->db->num_rows($result); |
||
3504 | if ($numc) |
||
3505 | { |
||
3506 | $i = 0; |
||
3507 | while ($i < $numc) |
||
3508 | { |
||
3509 | $obj = $this->db->fetch_object($result); |
||
3510 | |||
3511 | if ($shortlist == 1) |
||
3512 | { |
||
3513 | $ga[$obj->fid] = $obj->ref; |
||
3514 | } |
||
3515 | else if ($shortlist == 2) |
||
3516 | { |
||
3517 | $ga[$obj->fid] = $obj->ref.' ('.$obj->name.')'; |
||
3518 | } |
||
3519 | else |
||
3520 | { |
||
3521 | $ga[$i]['id'] = $obj->fid; |
||
3522 | $ga[$i]['ref'] = $obj->ref; |
||
3523 | $ga[$i]['name'] = $obj->name; |
||
3524 | } |
||
3525 | $i++; |
||
3526 | } |
||
3527 | } |
||
3528 | return $ga; |
||
3529 | } |
||
3530 | else |
||
3531 | { |
||
3532 | dol_print_error($this->db); |
||
3533 | return -1; |
||
3534 | } |
||
3535 | } |
||
3536 | |||
3537 | |||
3538 | // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps |
||
3539 | /** |
||
3540 | * Return list of invoices qualified to be replaced by another invoice. |
||
3541 | * Invoices matching the following rules are returned: |
||
3542 | * (Status validated or abandonned for a reason 'other') + not payed + no payment at all + not already replaced |
||
3543 | * |
||
3544 | * @param int $socid Id thirdparty |
||
3545 | * @return array Array of invoices ('id'=>id, 'ref'=>ref, 'status'=>status, 'paymentornot'=>0/1) |
||
3546 | */ |
||
3547 | function list_replacable_invoices($socid=0) |
||
3548 | { |
||
3549 | // phpcs:enable |
||
3550 | global $conf; |
||
3551 | |||
3552 | $return = array(); |
||
3553 | |||
3554 | $sql = "SELECT f.rowid as rowid, f.ref, f.fk_statut,"; |
||
3555 | $sql.= " ff.rowid as rowidnext"; |
||
3556 | $sql.= " FROM ".MAIN_DB_PREFIX."facture as f"; |
||
3557 | $sql.= " LEFT JOIN ".MAIN_DB_PREFIX."paiement_facture as pf ON f.rowid = pf.fk_facture"; |
||
3558 | $sql.= " LEFT JOIN ".MAIN_DB_PREFIX."facture as ff ON f.rowid = ff.fk_facture_source"; |
||
3559 | $sql.= " WHERE (f.fk_statut = ".self::STATUS_VALIDATED." OR (f.fk_statut = ".self::STATUS_ABANDONED." AND f.close_code = '".self::CLOSECODE_ABANDONED."'))"; |
||
3560 | $sql.= " AND f.entity = ".$conf->entity; |
||
3561 | $sql.= " AND f.paye = 0"; // Pas classee payee completement |
||
3562 | $sql.= " AND pf.fk_paiement IS NULL"; // Aucun paiement deja fait |
||
3563 | $sql.= " AND ff.fk_statut IS NULL"; // Renvoi vrai si pas facture de remplacement |
||
3564 | if ($socid > 0) $sql.=" AND f.fk_soc = ".$socid; |
||
3565 | $sql.= " ORDER BY f.ref"; |
||
3566 | |||
3567 | dol_syslog(get_class($this)."::list_replacable_invoices", LOG_DEBUG); |
||
3568 | $resql=$this->db->query($sql); |
||
3569 | if ($resql) |
||
3570 | { |
||
3571 | while ($obj=$this->db->fetch_object($resql)) |
||
3572 | { |
||
3573 | $return[$obj->rowid]=array( 'id' => $obj->rowid, |
||
3574 | 'ref' => $obj->ref, |
||
3575 | 'status' => $obj->fk_statut); |
||
3576 | } |
||
3577 | //print_r($return); |
||
3578 | return $return; |
||
3579 | } |
||
3580 | else |
||
3581 | { |
||
3582 | $this->error=$this->db->error(); |
||
3583 | return -1; |
||
3584 | } |
||
3585 | } |
||
3586 | |||
3587 | |||
3588 | // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps |
||
3589 | /** |
||
3590 | * Return list of invoices qualified to be corrected by a credit note. |
||
3591 | * Invoices matching the following rules are returned: |
||
3592 | * (validated + payment on process) or classified (payed completely or payed partiely) + not already replaced + not already a credit note |
||
3593 | * |
||
3594 | * @param int $socid Id thirdparty |
||
3595 | * @return array Array of invoices ($id => array('ref'=>,'paymentornot'=>,'status'=>,'paye'=>) |
||
3596 | */ |
||
3597 | function list_qualified_avoir_invoices($socid=0) |
||
3598 | { |
||
3599 | // phpcs:enable |
||
3600 | global $conf; |
||
3601 | |||
3602 | $return = array(); |
||
3603 | |||
3604 | |||
3605 | $sql = "SELECT f.rowid as rowid, f.ref, f.fk_statut, f.type, f.paye, pf.fk_paiement"; |
||
3606 | $sql.= " FROM ".MAIN_DB_PREFIX."facture as f"; |
||
3607 | $sql.= " LEFT JOIN ".MAIN_DB_PREFIX."paiement_facture as pf ON f.rowid = pf.fk_facture"; |
||
3608 | $sql.= " LEFT JOIN ".MAIN_DB_PREFIX."facture as ff ON (f.rowid = ff.fk_facture_source AND ff.type=".self::TYPE_REPLACEMENT.")"; |
||
3609 | $sql.= " WHERE f.entity = ".$conf->entity; |
||
3610 | $sql.= " AND f.fk_statut in (".self::STATUS_VALIDATED.",".self::STATUS_CLOSED.")"; |
||
3611 | // $sql.= " WHERE f.fk_statut >= 1"; |
||
3612 | // $sql.= " AND (f.paye = 1"; // Classee payee completement |
||
3613 | // $sql.= " OR f.close_code IS NOT NULL)"; // Classee payee partiellement |
||
3614 | $sql.= " AND ff.type IS NULL"; // Renvoi vrai si pas facture de remplacement |
||
3615 | $sql.= " AND f.type != ".self::TYPE_CREDIT_NOTE; // Type non 2 si facture non avoir |
||
3616 | |||
3617 | if($conf->global->INVOICE_USE_SITUATION_CREDIT_NOTE){ |
||
3618 | // Select the last situation invoice |
||
3619 | $sqlSit = 'SELECT MAX(fs.rowid)'; |
||
3620 | $sqlSit.= " FROM ".MAIN_DB_PREFIX."facture as fs"; |
||
3621 | $sqlSit.= " WHERE fs.entity = ".$conf->entity; |
||
3622 | $sqlSit.= " AND fs.type = ".self::TYPE_SITUATION; |
||
3623 | $sqlSit.= " AND fs.fk_statut in (".self::STATUS_VALIDATED.",".self::STATUS_CLOSED.")"; |
||
3624 | $sqlSit.= " GROUP BY fs.situation_cycle_ref"; |
||
3625 | $sqlSit.= " ORDER BY fs.situation_counter"; |
||
3626 | $sql.= " AND ( f.type != ".self::TYPE_SITUATION . " OR f.rowid IN (".$sqlSit.") )"; // Type non 5 si facture non avoir |
||
3627 | } |
||
3628 | else |
||
3629 | { |
||
3630 | $sql.= " AND f.type != ".self::TYPE_SITUATION ; // Type non 5 si facture non avoir |
||
3631 | } |
||
3632 | |||
3633 | if ($socid > 0) $sql.=" AND f.fk_soc = ".$socid; |
||
3634 | $sql.= " ORDER BY f.ref"; |
||
3635 | |||
3636 | dol_syslog(get_class($this)."::list_qualified_avoir_invoices", LOG_DEBUG); |
||
3637 | $resql=$this->db->query($sql); |
||
3638 | if ($resql) |
||
3639 | { |
||
3640 | while ($obj=$this->db->fetch_object($resql)) |
||
3641 | { |
||
3642 | $qualified=0; |
||
3643 | if ($obj->fk_statut == self::STATUS_VALIDATED) $qualified=1; |
||
3644 | if ($obj->fk_statut == self::STATUS_CLOSED) $qualified=1; |
||
3645 | if ($qualified) |
||
3646 | { |
||
3647 | //$ref=$obj->ref; |
||
3648 | $paymentornot=($obj->fk_paiement?1:0); |
||
3649 | $return[$obj->rowid]=array('ref'=>$obj->ref,'status'=>$obj->fk_statut,'type'=>$obj->type,'paye'=>$obj->paye,'paymentornot'=>$paymentornot); |
||
3650 | } |
||
3651 | } |
||
3652 | |||
3653 | return $return; |
||
3654 | } |
||
3655 | else |
||
3656 | { |
||
3657 | $this->error=$this->db->error(); |
||
3658 | return -1; |
||
3659 | } |
||
3660 | } |
||
3661 | |||
3662 | |||
3663 | // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps |
||
3664 | /** |
||
3665 | * Create a withdrawal request for a standing order. |
||
3666 | * Use the remain to pay excluding all existing open direct debit requests. |
||
3667 | * |
||
3668 | * @param User $fuser User asking the direct debit transfer |
||
3669 | * @param float $amount Amount we request direct debit for |
||
3670 | * @return int <0 if KO, >0 if OK |
||
3671 | */ |
||
3672 | function demande_prelevement($fuser, $amount=0) |
||
3673 | { |
||
3674 | // phpcs:enable |
||
3675 | |||
3676 | $error=0; |
||
3677 | |||
3678 | dol_syslog(get_class($this)."::demande_prelevement", LOG_DEBUG); |
||
3679 | |||
3680 | if ($this->statut > self::STATUS_DRAFT && $this->paye == 0) |
||
3681 | { |
||
3682 | require_once DOL_DOCUMENT_ROOT . '/societe/class/companybankaccount.class.php'; |
||
3683 | $bac = new CompanyBankAccount($this->db); |
||
3684 | $bac->fetch(0,$this->socid); |
||
3685 | |||
3686 | $sql = 'SELECT count(*)'; |
||
3687 | $sql.= ' FROM '.MAIN_DB_PREFIX.'prelevement_facture_demande'; |
||
3688 | $sql.= ' WHERE fk_facture = '.$this->id; |
||
3689 | $sql.= ' AND traite = 0'; |
||
3690 | |||
3691 | dol_syslog(get_class($this)."::demande_prelevement", LOG_DEBUG); |
||
3692 | $resql=$this->db->query($sql); |
||
3693 | if ($resql) |
||
3694 | { |
||
3695 | $row = $this->db->fetch_row($resql); |
||
3696 | if ($row[0] == 0) |
||
3697 | { |
||
3698 | $now=dol_now(); |
||
3699 | |||
3700 | $totalpaye = $this->getSommePaiement(); |
||
3701 | $totalcreditnotes = $this->getSumCreditNotesUsed(); |
||
3702 | $totaldeposits = $this->getSumDepositsUsed(); |
||
3703 | //print "totalpaye=".$totalpaye." totalcreditnotes=".$totalcreditnotes." totaldeposts=".$totaldeposits; |
||
3704 | |||
3705 | // We can also use bcadd to avoid pb with floating points |
||
3706 | // For example print 239.2 - 229.3 - 9.9; does not return 0. |
||
3707 | //$resteapayer=bcadd($this->total_ttc,$totalpaye,$conf->global->MAIN_MAX_DECIMALS_TOT); |
||
3708 | //$resteapayer=bcadd($resteapayer,$totalavoir,$conf->global->MAIN_MAX_DECIMALS_TOT); |
||
3709 | if (empty($amount)) $amount = price2num($this->total_ttc - $totalpaye - $totalcreditnotes - $totaldeposits,'MT'); |
||
3710 | |||
3711 | if (is_numeric($amount) && $amount != 0) |
||
3712 | { |
||
3713 | $sql = 'INSERT INTO '.MAIN_DB_PREFIX.'prelevement_facture_demande'; |
||
3714 | $sql .= ' (fk_facture, amount, date_demande, fk_user_demande, code_banque, code_guichet, number, cle_rib)'; |
||
3715 | $sql .= ' VALUES ('.$this->id; |
||
3716 | $sql .= ",'".price2num($amount)."'"; |
||
3717 | $sql .= ",'".$this->db->idate($now)."'"; |
||
3718 | $sql .= ",".$fuser->id; |
||
3719 | $sql .= ",'".$bac->code_banque."'"; |
||
3720 | $sql .= ",'".$bac->code_guichet."'"; |
||
3721 | $sql .= ",'".$bac->number."'"; |
||
3722 | $sql .= ",'".$bac->cle_rib."')"; |
||
3723 | |||
3724 | dol_syslog(get_class($this)."::demande_prelevement", LOG_DEBUG); |
||
3725 | $resql=$this->db->query($sql); |
||
3726 | if (! $resql) |
||
3727 | { |
||
3728 | $this->error=$this->db->lasterror(); |
||
3729 | dol_syslog(get_class($this).'::demandeprelevement Erreur'); |
||
3730 | $error++; |
||
3731 | } |
||
3732 | } |
||
3733 | else |
||
3734 | { |
||
3735 | $this->error='WithdrawRequestErrorNilAmount'; |
||
3736 | dol_syslog(get_class($this).'::demandeprelevement WithdrawRequestErrorNilAmount'); |
||
3737 | $error++; |
||
3738 | } |
||
3739 | |||
3740 | if (! $error) |
||
3741 | { |
||
3742 | // Force payment mode of invoice to withdraw |
||
3743 | $payment_mode_id = dol_getIdFromCode($this->db, 'PRE', 'c_paiement', 'code', 'id', 1); |
||
3744 | if ($payment_mode_id > 0) |
||
3745 | { |
||
3746 | $result=$this->setPaymentMethods($payment_mode_id); |
||
3747 | } |
||
3748 | } |
||
3749 | |||
3750 | if ($error) return -1; |
||
3751 | return 1; |
||
3752 | } |
||
3753 | else |
||
3754 | { |
||
3755 | $this->error="A request already exists"; |
||
3756 | dol_syslog(get_class($this).'::demandeprelevement Impossible de creer une demande, demande deja en cours'); |
||
3757 | return 0; |
||
3758 | } |
||
3759 | } |
||
3760 | else |
||
3761 | { |
||
3762 | $this->error=$this->db->error(); |
||
3763 | dol_syslog(get_class($this).'::demandeprelevement Erreur -2'); |
||
3764 | return -2; |
||
3765 | } |
||
3766 | } |
||
3767 | else |
||
3768 | { |
||
3769 | $this->error="Status of invoice does not allow this"; |
||
3770 | dol_syslog(get_class($this)."::demandeprelevement ".$this->error." $this->statut, $this->paye, $this->mode_reglement_id"); |
||
3771 | return -3; |
||
3772 | } |
||
3773 | } |
||
3774 | |||
3775 | // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps |
||
3776 | /** |
||
3777 | * Supprime une demande de prelevement |
||
3778 | * |
||
3779 | * @param User $fuser User making delete |
||
3780 | * @param int $did id de la demande a supprimer |
||
3781 | * @return int <0 if OK, >0 if KO |
||
3782 | */ |
||
3783 | function demande_prelevement_delete($fuser, $did) |
||
3784 | { |
||
3785 | // phpcs:enable |
||
3786 | $sql = 'DELETE FROM '.MAIN_DB_PREFIX.'prelevement_facture_demande'; |
||
3787 | $sql .= ' WHERE rowid = '.$did; |
||
3788 | $sql .= ' AND traite = 0'; |
||
3789 | if ( $this->db->query($sql) ) |
||
3790 | { |
||
3791 | return 0; |
||
3792 | } |
||
3793 | else |
||
3794 | { |
||
3795 | $this->error=$this->db->lasterror(); |
||
3796 | dol_syslog(get_class($this).'::demande_prelevement_delete Error '.$this->error); |
||
3797 | return -1; |
||
3798 | } |
||
3799 | } |
||
3800 | |||
3801 | |||
3802 | // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps |
||
3803 | /** |
||
3804 | * Load indicators for dashboard (this->nbtodo and this->nbtodolate) |
||
3805 | * |
||
3806 | * @param User $user Object user |
||
3807 | * @return WorkboardResponse|int <0 if KO, WorkboardResponse if OK |
||
3808 | */ |
||
3809 | function load_board($user) |
||
3810 | { |
||
3811 | // phpcs:enable |
||
3812 | global $conf, $langs; |
||
3813 | |||
3814 | $clause = " WHERE"; |
||
3815 | |||
3816 | $sql = "SELECT f.rowid, f.date_lim_reglement as datefin,f.fk_statut, f.total"; |
||
3817 | $sql.= " FROM ".MAIN_DB_PREFIX."facture as f"; |
||
3818 | if (!$user->rights->societe->client->voir && !$user->societe_id) |
||
3819 | { |
||
3820 | $sql.= " LEFT JOIN ".MAIN_DB_PREFIX."societe_commerciaux as sc ON f.fk_soc = sc.fk_soc"; |
||
3821 | $sql.= " WHERE sc.fk_user = " .$user->id; |
||
3822 | $clause = " AND"; |
||
3823 | } |
||
3824 | $sql.= $clause." f.paye=0"; |
||
3825 | $sql.= " AND f.entity = ".$conf->entity; |
||
3826 | $sql.= " AND f.fk_statut = ".self::STATUS_VALIDATED; |
||
3827 | if ($user->societe_id) $sql.= " AND f.fk_soc = ".$user->societe_id; |
||
3828 | |||
3829 | $resql=$this->db->query($sql); |
||
3830 | if ($resql) |
||
3831 | { |
||
3832 | $langs->load("bills"); |
||
3833 | $now=dol_now(); |
||
3834 | |||
3835 | $response = new WorkboardResponse(); |
||
3836 | $response->warning_delay=$conf->facture->client->warning_delay/60/60/24; |
||
3837 | $response->label=$langs->trans("CustomerBillsUnpaid"); |
||
3838 | $response->url=DOL_URL_ROOT.'/compta/facture/list.php?search_status=1&mainmenu=billing&leftmenu=customers_bills'; |
||
3839 | $response->img=img_object('',"bill"); |
||
3840 | |||
3841 | $generic_facture = new Facture($this->db); |
||
3842 | |||
3843 | while ($obj=$this->db->fetch_object($resql)) |
||
3844 | { |
||
3845 | $generic_facture->date_lim_reglement = $this->db->jdate($obj->datefin); |
||
3846 | $generic_facture->statut = $obj->fk_statut; |
||
3847 | |||
3848 | $response->nbtodo++; |
||
3849 | $response->total += $obj->total; |
||
3850 | |||
3851 | if ($generic_facture->hasDelay()) { |
||
3852 | $response->nbtodolate++; |
||
3853 | } |
||
3854 | } |
||
3855 | |||
3856 | return $response; |
||
3857 | } |
||
3858 | else |
||
3859 | { |
||
3860 | dol_print_error($this->db); |
||
3861 | $this->error=$this->db->error(); |
||
3862 | return -1; |
||
3863 | } |
||
3864 | } |
||
3865 | |||
3866 | |||
3867 | /* gestion des contacts d'une facture */ |
||
3868 | |||
3869 | /** |
||
3870 | * Retourne id des contacts clients de facturation |
||
3871 | * |
||
3872 | * @return array Liste des id contacts facturation |
||
3873 | */ |
||
3874 | function getIdBillingContact() |
||
3875 | { |
||
3876 | return $this->getIdContact('external','BILLING'); |
||
3877 | } |
||
3878 | |||
3879 | /** |
||
3880 | * Retourne id des contacts clients de livraison |
||
3881 | * |
||
3882 | * @return array Liste des id contacts livraison |
||
3883 | */ |
||
3884 | function getIdShippingContact() |
||
3887 | } |
||
3888 | |||
3889 | |||
3890 | /** |
||
3891 | * Initialise an instance with random values. |
||
3892 | * Used to build previews or test instances. |
||
3893 | * id must be 0 if object instance is a specimen. |
||
3894 | * |
||
3895 | * @param string $option ''=Create a specimen invoice with lines, 'nolines'=No lines |
||
3896 | * @return void |
||
3897 | */ |
||
3898 | function initAsSpecimen($option='') |
||
3899 | { |
||
3900 | global $langs; |
||
3901 | |||
3902 | $now=dol_now(); |
||
3903 | $arraynow=dol_getdate($now); |
||
3904 | $nownotime=dol_mktime(0, 0, 0, $arraynow['mon'], $arraynow['mday'], $arraynow['year']); |
||
3905 | |||
3906 | // Load array of products prodids |
||
3907 | $num_prods = 0; |
||
3908 | $prodids = array(); |
||
3909 | $sql = "SELECT rowid"; |
||
3910 | $sql.= " FROM ".MAIN_DB_PREFIX."product"; |
||
3911 | $sql.= " WHERE entity IN (".getEntity('product').")"; |
||
3912 | $resql = $this->db->query($sql); |
||
3913 | if ($resql) |
||
3914 | { |
||
3915 | $num_prods = $this->db->num_rows($resql); |
||
3916 | $i = 0; |
||
3917 | while ($i < $num_prods) |
||
3918 | { |
||
3919 | $i++; |
||
3920 | $row = $this->db->fetch_row($resql); |
||
3921 | $prodids[$i] = $row[0]; |
||
3922 | } |
||
3923 | } |
||
3924 | //Avoid php warning Warning: mt_rand(): max(0) is smaller than min(1) when no product exists |
||
3925 | if (empty($num_prods)) { |
||
3926 | $num_prods=1; |
||
3927 | } |
||
3928 | |||
3929 | // Initialize parameters |
||
3930 | $this->id=0; |
||
3931 | $this->entity = 1; |
||
3932 | $this->ref = 'SPECIMEN'; |
||
3933 | $this->specimen=1; |
||
3934 | $this->socid = 1; |
||
3935 | $this->date = $nownotime; |
||
3936 | $this->date_lim_reglement = $nownotime + 3600 * 24 *30; |
||
3937 | $this->cond_reglement_id = 1; |
||
3938 | $this->cond_reglement_code = 'RECEP'; |
||
3939 | $this->date_lim_reglement=$this->calculate_date_lim_reglement(); |
||
3940 | $this->mode_reglement_id = 0; // Not forced to show payment mode CHQ + VIR |
||
3941 | $this->mode_reglement_code = ''; // Not forced to show payment mode CHQ + VIR |
||
3942 | $this->note_public='This is a comment (public)'; |
||
3943 | $this->note_private='This is a comment (private)'; |
||
3944 | $this->note='This is a comment (private)'; |
||
3945 | $this->fk_incoterms=0; |
||
3946 | $this->location_incoterms=''; |
||
3947 | |||
3948 | if (empty($option) || $option != 'nolines') |
||
3949 | { |
||
3950 | // Lines |
||
3951 | $nbp = 5; |
||
3952 | $xnbp = 0; |
||
3953 | while ($xnbp < $nbp) |
||
3954 | { |
||
3955 | $line=new FactureLigne($this->db); |
||
3956 | $line->desc=$langs->trans("Description")." ".$xnbp; |
||
3957 | $line->qty=1; |
||
3958 | $line->subprice=100; |
||
3959 | $line->tva_tx=19.6; |
||
3960 | $line->localtax1_tx=0; |
||
3961 | $line->localtax2_tx=0; |
||
3962 | $line->remise_percent=0; |
||
3963 | if ($xnbp == 1) // Qty is negative (product line) |
||
3964 | { |
||
3965 | $prodid = mt_rand(1, $num_prods); |
||
3966 | $line->fk_product=$prodids[$prodid]; |
||
3967 | $line->qty=-1; |
||
3968 | $line->total_ht=-100; |
||
3969 | $line->total_ttc=-119.6; |
||
3970 | $line->total_tva=-19.6; |
||
3971 | $line->multicurrency_total_ht=-200; |
||
3972 | $line->multicurrency_total_ttc=-239.2; |
||
3973 | $line->multicurrency_total_tva=-39.2; |
||
3974 | } |
||
3975 | else if ($xnbp == 2) // UP is negative (free line) |
||
3976 | { |
||
3977 | $line->subprice=-100; |
||
3978 | $line->total_ht=-100; |
||
3979 | $line->total_ttc=-119.6; |
||
3980 | $line->total_tva=-19.6; |
||
3981 | $line->remise_percent=0; |
||
3982 | $line->multicurrency_total_ht=-200; |
||
3983 | $line->multicurrency_total_ttc=-239.2; |
||
3984 | $line->multicurrency_total_tva=-39.2; |
||
3985 | } |
||
3986 | else if ($xnbp == 3) // Discount is 50% (product line) |
||
3987 | { |
||
3988 | $prodid = mt_rand(1, $num_prods); |
||
3989 | $line->fk_product=$prodids[$prodid]; |
||
3990 | $line->total_ht=50; |
||
3991 | $line->total_ttc=59.8; |
||
3992 | $line->total_tva=9.8; |
||
3993 | $line->multicurrency_total_ht=100; |
||
3994 | $line->multicurrency_total_ttc=119.6; |
||
3995 | $line->multicurrency_total_tva=19.6; |
||
3996 | $line->remise_percent=50; |
||
3997 | } |
||
3998 | else // (product line) |
||
3999 | { |
||
4000 | $prodid = mt_rand(1, $num_prods); |
||
4001 | $line->fk_product=$prodids[$prodid]; |
||
4002 | $line->total_ht=100; |
||
4003 | $line->total_ttc=119.6; |
||
4004 | $line->total_tva=19.6; |
||
4005 | $line->multicurrency_total_ht=200; |
||
4006 | $line->multicurrency_total_ttc=239.2; |
||
4007 | $line->multicurrency_total_tva=39.2; |
||
4008 | $line->remise_percent=0; |
||
4009 | } |
||
4010 | |||
4011 | $this->lines[$xnbp]=$line; |
||
4012 | |||
4013 | |||
4014 | $this->total_ht += $line->total_ht; |
||
4015 | $this->total_tva += $line->total_tva; |
||
4016 | $this->total_ttc += $line->total_ttc; |
||
4017 | |||
4018 | $this->multicurrency_total_ht += $line->multicurrency_total_ht; |
||
4019 | $this->multicurrency_total_tva += $line->multicurrency_total_tva; |
||
4020 | $this->multicurrency_total_ttc += $line->multicurrency_total_ttc; |
||
4021 | |||
4022 | $xnbp++; |
||
4023 | } |
||
4024 | $this->revenuestamp = 0; |
||
4025 | |||
4026 | // Add a line "offered" |
||
4027 | $line=new FactureLigne($this->db); |
||
4028 | $line->desc=$langs->trans("Description")." (offered line)"; |
||
4029 | $line->qty=1; |
||
4030 | $line->subprice=100; |
||
4031 | $line->tva_tx=19.6; |
||
4032 | $line->localtax1_tx=0; |
||
4033 | $line->localtax2_tx=0; |
||
4034 | $line->remise_percent=100; |
||
4035 | $line->total_ht=0; |
||
4036 | $line->total_ttc=0; // 90 * 1.196 |
||
4037 | $line->total_tva=0; |
||
4038 | $line->multicurrency_total_ht=0; |
||
4039 | $line->multicurrency_total_ttc=0; |
||
4040 | $line->multicurrency_total_tva=0; |
||
4041 | $prodid = mt_rand(1, $num_prods); |
||
4042 | $line->fk_product=$prodids[$prodid]; |
||
4043 | |||
4044 | $this->lines[$xnbp]=$line; |
||
4045 | $xnbp++; |
||
4046 | } |
||
4047 | } |
||
4048 | |||
4049 | // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps |
||
4050 | /** |
||
4051 | * Load indicators for dashboard (this->nbtodo and this->nbtodolate) |
||
4052 | * |
||
4053 | * @return int <0 if KO, >0 if OK |
||
4054 | */ |
||
4055 | function load_state_board() |
||
4056 | { |
||
4057 | // phpcs:enable |
||
4058 | global $conf, $user; |
||
4059 | |||
4060 | $this->nb=array(); |
||
4061 | |||
4062 | $clause = "WHERE"; |
||
4063 | |||
4064 | $sql = "SELECT count(f.rowid) as nb"; |
||
4065 | $sql.= " FROM ".MAIN_DB_PREFIX."facture as f"; |
||
4066 | $sql.= " LEFT JOIN ".MAIN_DB_PREFIX."societe as s ON f.fk_soc = s.rowid"; |
||
4067 | if (!$user->rights->societe->client->voir && !$user->societe_id) |
||
4068 | { |
||
4069 | $sql.= " LEFT JOIN ".MAIN_DB_PREFIX."societe_commerciaux as sc ON s.rowid = sc.fk_soc"; |
||
4070 | $sql.= " WHERE sc.fk_user = " .$user->id; |
||
4071 | $clause = "AND"; |
||
4072 | } |
||
4073 | $sql.= " ".$clause." f.entity = ".$conf->entity; |
||
4074 | |||
4075 | $resql=$this->db->query($sql); |
||
4076 | if ($resql) |
||
4077 | { |
||
4078 | while ($obj=$this->db->fetch_object($resql)) |
||
4079 | { |
||
4080 | $this->nb["invoices"]=$obj->nb; |
||
4081 | } |
||
4082 | $this->db->free($resql); |
||
4083 | return 1; |
||
4084 | } |
||
4085 | else |
||
4086 | { |
||
4087 | dol_print_error($this->db); |
||
4088 | $this->error=$this->db->error(); |
||
4089 | return -1; |
||
4090 | } |
||
4091 | } |
||
4092 | |||
4093 | /** |
||
4094 | * Create an array of invoice lines |
||
4095 | * |
||
4096 | * @return int >0 if OK, <0 if KO |
||
4097 | */ |
||
4098 | function getLinesArray() |
||
4099 | { |
||
4100 | return $this->fetch_lines(); |
||
4101 | } |
||
4102 | |||
4103 | /** |
||
4104 | * Create a document onto disk according to template module. |
||
4105 | * |
||
4106 | * @param string $modele Generator to use. Caller must set it to obj->modelpdf or GETPOST('modelpdf') for example. |
||
4107 | * @param Translate $outputlangs objet lang a utiliser pour traduction |
||
4108 | * @param int $hidedetails Hide details of lines |
||
4109 | * @param int $hidedesc Hide description |
||
4110 | * @param int $hideref Hide ref |
||
4111 | * @param null|array $moreparams Array to provide more information |
||
4112 | * @return int <0 if KO, >0 if OK |
||
4113 | */ |
||
4114 | public function generateDocument($modele, $outputlangs, $hidedetails=0, $hidedesc=0, $hideref=0, $moreparams=null) |
||
4115 | { |
||
4116 | global $conf,$langs; |
||
4117 | |||
4118 | $langs->load("bills"); |
||
4119 | |||
4120 | if (! dol_strlen($modele)) |
||
4121 | { |
||
4122 | $modele = 'crabe'; |
||
4123 | $thisTypeConfName = 'FACTURE_ADDON_PDF_'.$this->type; |
||
4124 | |||
4125 | if ($this->modelpdf) { |
||
4126 | $modele = $this->modelpdf; |
||
4127 | } elseif (! empty($conf->global->$thisTypeConfName)) { |
||
4128 | $modele = $conf->global->$thisTypeConfName; |
||
4129 | } elseif (! empty($conf->global->FACTURE_ADDON_PDF)) { |
||
4130 | $modele = $conf->global->FACTURE_ADDON_PDF; |
||
4131 | } |
||
4132 | } |
||
4133 | |||
4134 | $modelpath = "core/modules/facture/doc/"; |
||
4135 | |||
4136 | return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams); |
||
4137 | } |
||
4138 | |||
4139 | /** |
||
4140 | * Gets the smallest reference available for a new cycle |
||
4141 | * |
||
4142 | * @return int >= 1 if OK, -1 if error |
||
4143 | */ |
||
4144 | function newCycle() |
||
4145 | { |
||
4146 | $sql = 'SELECT max(situation_cycle_ref) FROM ' . MAIN_DB_PREFIX . 'facture as f'; |
||
4147 | $sql.= " WHERE f.entity in (".getEntity('invoice', 0).")"; |
||
4148 | $resql = $this->db->query($sql); |
||
4149 | if ($resql) { |
||
4150 | if ($resql->num_rows > 0) |
||
4151 | { |
||
4152 | $res = $this->db->fetch_array($resql); |
||
4153 | $ref = $res['max(situation_cycle_ref)']; |
||
4154 | $ref++; |
||
4155 | } else { |
||
4156 | $ref = 1; |
||
4157 | } |
||
4158 | $this->db->free($resql); |
||
4159 | return $ref; |
||
4160 | } else { |
||
4161 | $this->error = $this->db->lasterror(); |
||
4162 | dol_syslog("Error sql=" . $sql . ", error=" . $this->error, LOG_ERR); |
||
4163 | return -1; |
||
4164 | } |
||
4165 | } |
||
4166 | |||
4167 | // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps |
||
4168 | /** |
||
4169 | * Checks if the invoice is the first of a cycle |
||
4170 | * |
||
4171 | * @return boolean |
||
4172 | */ |
||
4173 | function is_first() |
||
4177 | } |
||
4178 | |||
4179 | // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps |
||
4180 | /** |
||
4181 | * Returns an array containing the previous situations as Facture objects |
||
4182 | * |
||
4183 | * @return mixed -1 if error, array of previous situations |
||
4184 | */ |
||
4185 | function get_prev_sits() |
||
4186 | { |
||
4187 | // phpcs:enable |
||
4188 | global $conf; |
||
4189 | |||
4190 | $sql = 'SELECT rowid FROM ' . MAIN_DB_PREFIX . 'facture'; |
||
4191 | $sql .= ' where situation_cycle_ref = ' . $this->situation_cycle_ref; |
||
4192 | $sql .= ' and situation_counter < ' . $this->situation_counter; |
||
4193 | $sql .= ' AND entity = '. ($this->entity > 0 ? $this->entity : $conf->entity); |
||
4194 | $resql = $this->db->query($sql); |
||
4195 | $res = array(); |
||
4196 | if ($resql && $resql->num_rows > 0) { |
||
4197 | while ($row = $this->db->fetch_object($resql)) { |
||
4198 | $id = $row->rowid; |
||
4199 | $situation = new Facture($this->db); |
||
4200 | $situation->fetch($id); |
||
4201 | $res[] = $situation; |
||
4202 | } |
||
4203 | } else { |
||
4204 | $this->error = $this->db->error(); |
||
4205 | dol_syslog("Error sql=" . $sql . ", error=" . $this->error, LOG_ERR); |
||
4206 | return -1; |
||
4207 | } |
||
4208 | |||
4209 | return $res; |
||
4210 | } |
||
4211 | |||
4212 | /** |
||
4213 | * Sets the invoice as a final situation |
||
4214 | * |
||
4215 | * @param User $user Object user |
||
4216 | * @param int $notrigger 1=Does not execute triggers, 0= execute triggers |
||
4217 | * @return int <0 if KO, >0 if OK |
||
4218 | */ |
||
4219 | function setFinal(User $user, $notrigger=0) |
||
4220 | { |
||
4221 | $error=0; |
||
4222 | |||
4223 | $this->db->begin(); |
||
4224 | |||
4225 | $sql = 'UPDATE ' . MAIN_DB_PREFIX . 'facture SET situation_final = ' . $this->situation_final . ' where rowid = ' . $this->id; |
||
4226 | |||
4227 | dol_syslog(__METHOD__, LOG_DEBUG); |
||
4228 | $resql=$this->db->query($sql); |
||
4229 | if (!$resql) |
||
4230 | { |
||
4231 | $this->errors[]=$this->db->error(); |
||
4232 | $error++; |
||
4233 | } |
||
4234 | |||
4235 | if (! $notrigger && empty($error)) |
||
4236 | { |
||
4237 | // Call trigger |
||
4238 | $result=$this->call_trigger('BILL_MODIFY',$user); |
||
4239 | if ($result < 0) $error++; |
||
4240 | // End call triggers |
||
4241 | } |
||
4242 | |||
4243 | if (! $error) |
||
4244 | { |
||
4245 | $this->db->commit(); |
||
4246 | return 1; |
||
4247 | } |
||
4248 | else |
||
4249 | { |
||
4250 | foreach($this->errors as $errmsg) |
||
4251 | { |
||
4252 | dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR); |
||
4253 | $this->error.=($this->error?', '.$errmsg:$errmsg); |
||
4254 | } |
||
4255 | $this->db->rollback(); |
||
4256 | return -1*$error; |
||
4257 | } |
||
4258 | } |
||
4259 | |||
4260 | // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps |
||
4261 | /** |
||
4262 | * Checks if the invoice is the last in its cycle |
||
4263 | * |
||
4264 | * @return bool Last of the cycle status |
||
4265 | * |
||
4266 | */ |
||
4267 | function is_last_in_cycle() |
||
4268 | { |
||
4269 | // phpcs:enable |
||
4270 | global $conf; |
||
4271 | |||
4272 | if (!empty($this->situation_cycle_ref)) { |
||
4273 | // No point in testing anything if we're not inside a cycle |
||
4274 | $sql = 'SELECT max(situation_counter) FROM ' . MAIN_DB_PREFIX . 'facture WHERE situation_cycle_ref = ' . $this->situation_cycle_ref . ' AND entity = ' . ($this->entity > 0 ? $this->entity : $conf->entity); |
||
4275 | $resql = $this->db->query($sql); |
||
4276 | |||
4277 | if ($resql && $resql->num_rows > 0) { |
||
4278 | $res = $this->db->fetch_array($resql); |
||
4279 | $last = $res['max(situation_counter)']; |
||
4280 | return ($last == $this->situation_counter); |
||
4281 | } else { |
||
4282 | $this->error = $this->db->lasterror(); |
||
4283 | dol_syslog(get_class($this) . "::select Error " . $this->error, LOG_ERR); |
||
4284 | return false; |
||
4285 | } |
||
4286 | } else { |
||
4287 | return true; |
||
4288 | } |
||
4289 | } |
||
4290 | |||
4291 | /** |
||
4292 | * Function used to replace a thirdparty id with another one. |
||
4293 | * |
||
4294 | * @param DoliDB $db Database handler |
||
4295 | * @param int $origin_id Old thirdparty id |
||
4296 | * @param int $dest_id New thirdparty id |
||
4297 | * @return bool |
||
4298 | */ |
||
4299 | public static function replaceThirdparty(DoliDB $db, $origin_id, $dest_id) |
||
4300 | { |
||
4301 | $tables = array( |
||
4302 | 'facture' |
||
4303 | ); |
||
4304 | |||
4305 | return CommonObject::commonReplaceThirdparty($db, $origin_id, $dest_id, $tables); |
||
4306 | } |
||
4307 | |||
4308 | /** |
||
4309 | * Is the customer invoice delayed? |
||
4310 | * |
||
4311 | * @return bool |
||
4312 | */ |
||
4313 | public function hasDelay() |
||
4323 | } |
||
4324 | } |
||
4325 | |||
4326 | /** |
||
4327 | * Class to manage invoice lines. |
||
4328 | * Saved into database table llx_facturedet |
||
4329 | */ |
||
4330 | class FactureLigne extends CommonInvoiceLine |
||
4331 | { |
||
4332 | /** |
||
4333 | * @var string ID to identify managed object |
||
4334 | */ |
||
4335 | public $element='facturedet'; |
||
4336 | |||
4337 | /** |
||
4338 | * @var string Name of table without prefix where object is stored |
||
4339 | */ |
||
4340 | public $table_element='facturedet'; |
||
4341 | |||
4342 | public $oldline; |
||
4343 | |||
4344 | //! From llx_facturedet |
||
4993 |
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.
For example, imagine you have a variable
$accountId
that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to theid
property of an instance of theAccount
class. This class holds a proper account, so the id value must no longer be false.Either this assignment is in error or a type check should be added for that assignment.