Passed
Push — EXTRACT_CLASSES ( 0382f2...c25e41 )
by Rafael
52:18
created

Contrat::getLibStatut()   A

Complexity

Conditions 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nop 1
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
3
/* Copyright (C) 2003       Rodolphe Quiedeville        <[email protected]>
4
 * Copyright (C) 2004-2012	Destailleur Laurent		    <[email protected]>
5
 * Copyright (C) 2005-2014	Regis Houssin			    <[email protected]>
6
 * Copyright (C) 2006		Andre Cianfarani		    <[email protected]>
7
 * Copyright (C) 2008		Raphael Bertrand		    <[email protected]>
8
 * Copyright (C) 2010-2016	Juanjo Menent			    <[email protected]>
9
 * Copyright (C) 2013		Christophe Battarel		    <[email protected]>
10
 * Copyright (C) 2013		Florian Henry			    <[email protected]>
11
 * Copyright (C) 2014-2015	Marcos García			    <[email protected]>
12
 * Copyright (C) 2018   	Nicolas ZABOURI			    <[email protected]>
13
 * Copyright (C) 2018-2024  Frédéric France             <[email protected]>
14
 * Copyright (C) 2015-2018	Ferran Marcet			    <[email protected]>
15
 * Copyright (C) 2024		William Mead			    <[email protected]>
16
 * Copyright (C) 2024		MDW							<[email protected]>
17
 * Copyright (C) 2024       Rafael San José             <[email protected]>
18
 *
19
 * This program is free software; you can redistribute it and/or modify
20
 * it under the terms of the GNU General Public License as published by
21
 * the Free Software Foundation; either version 3 of the License, or
22
 * (at your option) any later version.
23
 *
24
 * This program is distributed in the hope that it will be useful,
25
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
26
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
27
 * GNU General Public License for more details.
28
 *
29
 * You should have received a copy of the GNU General Public License
30
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
31
 */
32
33
namespace Dolibarr\Code\Contrat\Classes;
34
35
use Dolibarr\Core\Base\CommonObject;
36
37
/**
38
 *  \file       htdocs/contrat/class/contrat.class.php
39
 *  \ingroup    contrat
40
 *  \brief      File of class to manage contracts
41
 */
42
43
require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/price.lib.php';
44
require_once constant('DOL_DOCUMENT_ROOT') . '/margin/lib/margins.lib.php';
45
46
/**
47
 *  Class to manage contracts
48
 */
49
class Contrat extends CommonObject
50
{
51
    /**
52
     * @var string ID to identify managed object
53
     */
54
    public $element = 'contrat';
55
56
    /**
57
     * @var string Name of table without prefix where object is stored
58
     */
59
    public $table_element = 'contrat';
60
61
    /**
62
     * @var string    Name of subtable line
63
     */
64
    public $table_element_line = 'contratdet';
65
66
    /**
67
     * @var string Fieldname with ID of parent key if this field has a parent
68
     */
69
    public $fk_element = 'fk_contrat';
70
71
    /**
72
     * @var string String with name of icon for myobject. Must be the part after the 'object_' into object_myobject.png
73
     */
74
    public $picto = 'contract';
75
76
    /**
77
     * 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
78
     * @var integer
79
     */
80
    public $restrictiononfksoc = 1;
81
82
    /**
83
     * {@inheritdoc}
84
     */
85
    protected $table_ref_field = 'ref';
86
87
    /**
88
     * Customer reference of the contract
89
     * @var string
90
     */
91
    public $ref_customer;
92
    public $from;
93
94
    /**
95
     * Supplier reference of the contract
96
     * @var string
97
     */
98
    public $ref_supplier;
99
100
    /**
101
     * Entity of the contract
102
     * @var int
103
     */
104
    public $entity;
105
106
    /**
107
     * Client id linked to the contract
108
     * @var int
109
     */
110
    public $socid;
111
112
    /**
113
     * Client id linked to the contract
114
     * @var int
115
     * @deprecated Use $socid
116
     */
117
    public $fk_soc;
118
119
120
    public $societe; // Object societe
121
122
    /**
123
     * Status of the contract
124
     * @var int
125
     * @deprecated
126
     */
127
    public $statut = 0;
128
    /**
129
     * Status of the contract (0=Draft, 1=Validated)
130
     * @var int
131
     */
132
    public $status = 0;
133
134
    public $product;
135
136
    /**
137
     * @var int     Id of user author of the contract
138
     */
139
    public $fk_user_author;
140
141
    /**
142
     * TODO: Which is the correct one?
143
     * Author of the contract
144
     * @var int
145
     */
146
    public $user_author_id;
147
148
149
    /**
150
     * @var User    Object user that create the contract. Set by the info method.
0 ignored issues
show
Bug introduced by
The type Dolibarr\Code\Contrat\Classes\User was not found. Did you mean User? If so, make sure to prefix the type with \.
Loading history...
151
     * @deprecated
152
     */
153
    public $user_creation;
154
155
    /**
156
     * @var User    Object user that close the contract. Set by the info method.
157
     */
158
    public $user_cloture;
159
160
    /**
161
     * @var integer|string      Date of creation
162
     */
163
    public $date_creation;
164
165
    /**
166
     * @var integer|string      Date of last modification. Not filled until you call ->info()
167
     */
168
    public $date_modification;
169
170
    /**
171
     * @var integer|string      Date of validation
172
     */
173
    public $date_validation;
174
175
    /**
176
     * @var integer|string      Date when contract was signed
177
     */
178
    public $date_contrat;
179
180
    /**
181
     * Status of the contract (0=NoSignature, 1=SignedBySender, 2=SignedByReceiver, 9=SignedByAll)
182
     * @var int
183
     */
184
    public $signed_status = 0;
185
186
    public $commercial_signature_id;
187
    public $fk_commercial_signature;
188
    public $commercial_suivi_id;
189
    public $fk_commercial_suivi;
190
191
    /**
192
     * @deprecated Use fk_project instead
193
     * @see $fk_project
194
     */
195
    public $fk_projet;
196
197
    public $extraparams = array();
198
199
    /**
200
     * @var ContratLigne[]      Contract lines
201
     */
202
    public $lines = array();
203
204
    public $nbofservices;
205
    public $nbofserviceswait;
206
    public $nbofservicesopened;
207
    public $nbofservicesexpired;
208
    public $nbofservicesclosed;
209
    //public $lower_planned_end_date;
210
    //public $higher_planner_end_date;
211
212
    /**
213
     * Maps ContratLigne IDs to $this->lines indexes
214
     * @var int[]
215
     */
216
    protected $lines_id_index_mapper = array();
217
218
219
    /**
220
     *  'type' if the field format ('integer', 'integer:ObjectClass:PathToClass[:AddCreateButtonOrNot[:Filter]]', 'varchar(x)', 'double(24,8)', 'real', 'price', 'text', 'html', 'date', 'datetime', 'timestamp', 'duration', 'mail', 'phone', 'url', 'password')
221
     *         Note: Filter can be a string like "(t.ref:like:'SO-%') or (t.date_creation:<:'20160101') or (t.nature:is:NULL)"
222
     *  'label' the translation key.
223
     *  'enabled' is a condition when the field must be managed.
224
     *  'position' is the sort order of field.
225
     *  'notnull' is set to 1 if not null in database. Set to -1 if we must set data to null if empty ('' or 0).
226
     *  'visible' says if field is visible in list (Examples: 0=Not visible, 1=Visible on list and create/update/view forms, 2=Visible on list only, 3=Visible on create/update/view form only (not list), 4=Visible on list and update/view form only (not create). 5=Visible on list and view only (not create/not update). Using a negative value means field is not shown by default on list but can be selected for viewing)
227
     *  'noteditable' says if field is not editable (1 or 0)
228
     *  'default' is a default value for creation (can still be overwrote by the Setup of Default Values if field is editable in creation form). Note: If default is set to '(PROV)' and field is 'ref', the default value will be set to '(PROVid)' where id is rowid when a new record is created.
229
     *  'index' if we want an index in database.
230
     *  'foreignkey'=>'tablename.field' if the field is a foreign key (it is recommended to name the field fk_...).
231
     *  'searchall' is 1 if we want to search in this field when making a search from the quick search button.
232
     *  'isameasure' must be set to 1 if you want to have a total on list for this field. Field type must be summable like integer or double(24,8).
233
     *  'css' is the CSS style to use on field. For example: 'maxwidth200'
234
     *  'help' is a string visible as a tooltip on field
235
     *  'showoncombobox' if value of the field must be visible into the label of the combobox that list record
236
     *  'disabled' is 1 if we want to have the field locked by a 'disabled' attribute. In most cases, this is never set into the definition of $fields into class, but is set dynamically by some part of code.
237
     *  'arrayofkeyval' to set list of value if type is a list of predefined values. For example: array("0"=>"Draft","1"=>"Active","-1"=>"Cancel")
238
     *  'comment' is not used. You can store here any text of your choice. It is not used by application.
239
     *
240
     *  Note: To have value dynamic, you can set value to 0 in definition and edit the value on the fly into the constructor.
241
     */
242
243
    // BEGIN MODULEBUILDER PROPERTIES
244
    /**
245
     * @var array<string,array{type:string,label:string,enabled:int<0,2>|string,position:int,notnull?:int,visible:int,noteditable?:int,default?:string,index?:int,foreignkey?:string,searchall?:int,isameasure?:int,css?:string,csslist?:string,help?:string,showoncombobox?:int,disabled?:int,arrayofkeyval?:array<int,string>,comment?:string}>  Array with all fields and their property. Do not use it as a static var. It may be modified by constructor.
0 ignored issues
show
Documentation Bug introduced by
The doc comment array<string,array{type:...ring>,comment?:string}> at position 16 could not be parsed: Expected '}' at position 16, but found 'int'.
Loading history...
246
     */
247
    public $fields = array(
248
        'rowid' => array('type' => 'integer', 'label' => 'TechnicalID', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'position' => 10),
249
        'ref' => array('type' => 'varchar(50)', 'label' => 'Ref', 'enabled' => 1, 'visible' => -1, 'showoncombobox' => 1, 'position' => 15, 'searchall' => 1),
250
        'ref_ext' => array('type' => 'varchar(255)', 'label' => 'Ref ext', 'enabled' => 1, 'visible' => 0, 'position' => 20),
251
        'ref_customer' => array('type' => 'varchar(50)', 'label' => 'RefCustomer', 'enabled' => 1, 'visible' => -1, 'position' => 25, 'searchall' => 1),
252
        'ref_supplier' => array('type' => 'varchar(50)', 'label' => 'RefSupplier', 'enabled' => 1, 'visible' => -1, 'position' => 26, 'searchall' => 1),
253
        'entity' => array('type' => 'integer', 'label' => 'Entity', 'default' => '1', 'enabled' => 1, 'visible' => -2, 'notnull' => 1, 'position' => 30, 'index' => 1),
254
        'tms' => array('type' => 'timestamp', 'label' => 'DateModification', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'position' => 35),
255
        'datec' => array('type' => 'datetime', 'label' => 'DateCreation', 'enabled' => 1, 'visible' => -1, 'position' => 40),
256
        'date_contrat' => array('type' => 'datetime', 'label' => 'Date contrat', 'enabled' => 1, 'visible' => -1, 'position' => 45),
257
        'signed_status' => array('type' => 'smallint(6)', 'label' => 'SignedStatus', 'enabled' => 1, 'visible' => -1, 'position' => 50, 'arrayofkeyval' => array(0 => 'NoSignature', 1 => 'SignedSender', 2 => 'SignedReceiver', 9 => 'SignedAll')),
258
        'fk_soc' => array('type' => 'integer:Societe:societe/class/societe.class.php', 'label' => 'ThirdParty', 'enabled' => 'isModEnabled("societe")', 'visible' => -1, 'notnull' => 1, 'position' => 70),
259
        'fk_projet' => array('type' => 'integer:Project:projet/class/project.class.php:1:(fk_statut:=:1)', 'label' => 'Project', 'enabled' => "isModEnabled('project')", 'visible' => -1, 'position' => 75),
260
        'fk_commercial_signature' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'SaleRepresentative Signature', 'enabled' => 1, 'visible' => -1, 'position' => 80),
261
        'fk_commercial_suivi' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'SaleRepresentative follower', 'enabled' => 1, 'visible' => -1, 'position' => 85),
262
        'fk_user_author' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'UserAuthor', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'position' => 90),
263
        'note_public' => array('type' => 'html', 'label' => 'NotePublic', 'enabled' => 1, 'visible' => 0, 'position' => 105, 'searchall' => 1),
264
        'note_private' => array('type' => 'html', 'label' => 'NotePrivate', 'enabled' => 1, 'visible' => 0, 'position' => 110, 'searchall' => 1),
265
        'model_pdf' => array('type' => 'varchar(255)', 'label' => 'Model pdf', 'enabled' => 1, 'visible' => 0, 'position' => 115),
266
        'import_key' => array('type' => 'varchar(14)', 'label' => 'ImportId', 'enabled' => 1, 'visible' => -2, 'position' => 120),
267
        'extraparams' => array('type' => 'varchar(255)', 'label' => 'Extraparams', 'enabled' => 1, 'visible' => -1, 'position' => 125),
268
        'fk_user_modif' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'UserModif', 'enabled' => 1, 'visible' => -2, 'notnull' => -1, 'position' => 135),
269
        'last_main_doc' => array('type' => 'varchar(255)', 'label' => 'Last main doc', 'enabled' => 1, 'visible' => -1, 'position' => 140),
270
        'statut' => array('type' => 'smallint(6)', 'label' => 'Statut', 'enabled' => 1, 'visible' => -1, 'position' => 500, 'notnull' => 1, 'arrayofkeyval' => array(0 => 'Draft', 1 => 'Validated', 2 => 'Closed'))
271
    );
272
    // END MODULEBUILDER PROPERTIES
273
274
    const STATUS_DRAFT = 0;
275
    const STATUS_VALIDATED = 1;
276
    const STATUS_CLOSED = 2;
277
278
    /*
279
     * No signature
280
     */
281
    const STATUS_NO_SIGNATURE    = 0;
282
283
    /*
284
     * Signed by sender
285
     */
286
    const STATUS_SIGNED_SENDER   = 1;
287
288
    /*
289
     * Signed by receiver
290
     */
291
    const STATUS_SIGNED_RECEIVER = 2;
292
293
    /*
294
     * Signed by all
295
     */
296
    const STATUS_SIGNED_ALL      = 9; // To handle future kind of signature (ex: tripartite contract)
297
298
299
    /**
300
     *  Constructor
301
     *
302
     *  @param      DoliDB      $db      Database handler
0 ignored issues
show
Bug introduced by
The type Dolibarr\Code\Contrat\Classes\DoliDB was not found. Did you mean DoliDB? If so, make sure to prefix the type with \.
Loading history...
303
     */
304
    public function __construct($db)
305
    {
306
        $this->db = $db;
307
308
        $this->ismultientitymanaged = 1;
309
        $this->isextrafieldmanaged = 1;
310
    }
311
312
    /**
313
     *  Return next contract ref
314
     *
315
     *  @param  Societe     $soc        Thirdparty object
0 ignored issues
show
Bug introduced by
The type Dolibarr\Code\Contrat\Classes\Societe was not found. Did you mean Societe? If so, make sure to prefix the type with \.
Loading history...
316
     *  @return string                  free reference for contract
317
     */
318
    public function getNextNumRef($soc)
319
    {
320
        global $db, $langs, $conf;
321
        $langs->load("contracts");
322
323
        if (getDolGlobalString('CONTRACT_ADDON')) {
324
            $mybool = false;
325
326
            $file = getDolGlobalString('CONTRACT_ADDON') . ".php";
327
            $classname = getDolGlobalString('CONTRACT_ADDON');
328
329
            // Include file with class
330
            $dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']);
331
332
            foreach ($dirmodels as $reldir) {
333
                $dir = dol_buildpath($reldir . "core/modules/contract/");
334
335
                // Load file with numbering class (if found)
336
                $mybool = ((bool) @include_once $dir . $file) || $mybool;
337
            }
338
339
            if (!$mybool) {
340
                dol_print_error(null, "Failed to include file " . $file);
341
                return '';
342
            }
343
344
            $obj = new $classname();
345
            '@phan-var-force CommonNumRefGenerator $obj';
346
            $numref = $obj->getNextValue($soc, $this);
347
348
            if ($numref != "") {
349
                return $numref;
350
            } else {
351
                $this->error = $obj->error;
352
                dol_print_error($db, get_class($this) . "::getNextValue " . $obj->error);
353
                return "";
354
            }
355
        } else {
356
            $langs->load("errors");
357
            print $langs->trans("Error") . " " . $langs->trans("ErrorModuleSetupNotComplete", $langs->transnoentitiesnoconv("Contract"));
358
            return "";
359
        }
360
    }
361
362
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
363
    /**
364
     *  Activate a contract line
365
     *
366
     *  @param  User        $user       Object User who activate contract
367
     *  @param  int         $line_id    Id of line to activate
368
     *  @param  int         $date_start Opening date
369
     *  @param  int|string  $date_end   Expected end date
370
     *  @param  string      $comment    A comment typed by user
371
     *  @return int                     Return integer <0 if KO, >0 if OK
372
     */
373
    public function active_line($user, $line_id, $date_start, $date_end = '', $comment = '')
374
    {
375
		// phpcs:enable
376
        $result = $this->lines[$this->lines_id_index_mapper[$line_id]]->active_line($user, $date_start, $date_end, $comment);
377
        if ($result < 0) {
378
            $this->error = $this->lines[$this->lines_id_index_mapper[$line_id]]->error;
379
            $this->errors = $this->lines[$this->lines_id_index_mapper[$line_id]]->errors;
380
        }
381
        return $result;
382
    }
383
384
385
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
386
    /**
387
     *  Close a contract line
388
     *
389
     *  @param  User        $user       Object User who close contract
390
     *  @param  int         $line_id    Id of line to close
391
     *  @param  int         $date_end   End date
392
     *  @param  string      $comment    A comment typed by user
393
     *  @return int                     Return integer <0 if KO, >0 if OK
394
     */
395
    public function close_line($user, $line_id, $date_end, $comment = '')
396
    {
397
		// phpcs:enable
398
        $result = $this->lines[$this->lines_id_index_mapper[$line_id]]->close_line($user, $date_end, $comment);
399
        if ($result < 0) {
400
            $this->error = $this->lines[$this->lines_id_index_mapper[$line_id]]->error;
401
            $this->errors = $this->lines[$this->lines_id_index_mapper[$line_id]]->errors;
402
        }
403
        return $result;
404
    }
405
406
407
    /**
408
     *  Open all lines of a contract
409
     *
410
     *  @param  User        $user           Object User making action
411
     *  @param  int|string  $date_start     Date start (now if empty)
412
     *  @param  int         $notrigger      1=Does not execute triggers, 0=Execute triggers
413
     *  @param  string      $comment        Comment
414
     *  @param  int|string  $date_end       Date end
415
     *  @return int                         Return integer <0 if KO, >0 if OK
416
     *  @see ()
417
     */
418
    public function activateAll($user, $date_start = '', $notrigger = 0, $comment = '', $date_end = '')
419
    {
420
        if (empty($date_start)) {
421
            $date_start = dol_now();
422
        }
423
424
        $this->db->begin();
425
426
        $error = 0;
427
428
        // Load lines
429
        $this->fetch_lines();
430
431
        foreach ($this->lines as $contratline) {
432
            // Open lines not already open
433
            if ($contratline->statut != ContratLigne::STATUS_OPEN) {
434
                $contratline->context = $this->context;
435
436
                $result = $contratline->active_line($user, $date_start, !empty($date_end) ? $date_end : -1, $comment);  // This call trigger LINECONTRACT_ACTIVATE
437
                if ($result < 0) {
438
                    $error++;
439
                    $this->error = $contratline->error;
440
                    $this->errors = $contratline->errors;
441
                    break;
442
                }
443
            }
444
        }
445
446
        if (!$error && $this->statut == 0) {
447
            $result = $this->validate($user, '', $notrigger);
448
            if ($result < 0) {
449
                $error++;
450
            }
451
        }
452
453
        if (!$error) {
454
            $this->db->commit();
455
            return 1;
456
        } else {
457
            $this->db->rollback();
458
            return -1;
459
        }
460
    }
461
462
    /**
463
     * Close all lines of a contract
464
     *
465
     * @param   User        $user           Object User making action
466
     * @param   int         $notrigger      1=Does not execute triggers, 0=Execute triggers
467
     * @param   string      $comment        Comment
468
     * @return  int                         Return integer <0 if KO, >0 if OK
469
     * @see activateAll()
470
     */
471
    public function closeAll(User $user, $notrigger = 0, $comment = '')
472
    {
473
        $this->db->begin();
474
475
        // Load lines
476
        $this->fetch_lines();
477
478
        $now = dol_now();
479
480
        $error = 0;
481
482
        foreach ($this->lines as $contratline) {
483
            // Close lines not already closed
484
            if ($contratline->statut != ContratLigne::STATUS_CLOSED) {
485
                $contratline->date_end_real = $now;
486
                $contratline->date_cloture = $now;  // For backward compatibility
487
                $contratline->user_closing_id = $user->id;
488
                $contratline->statut = ContratLigne::STATUS_CLOSED;
489
                $result = $contratline->close_line($user, $now, $comment, $notrigger);
490
                if ($result < 0) {
491
                    $error++;
492
                    $this->error = $contratline->error;
493
                    $this->errors = $contratline->errors;
494
                    break;
495
                }
496
            }
497
        }
498
499
        if (!$error && $this->statut == 0) {
500
            $result = $this->validate($user, '', $notrigger);
501
            if ($result < 0) {
502
                $error++;
503
            }
504
        }
505
506
        if (!$error) {
507
            $this->db->commit();
508
            return 1;
509
        } else {
510
            $this->db->rollback();
511
            return -1;
512
        }
513
    }
514
515
    /**
516
     * Validate a contract
517
     *
518
     * @param   User    $user           Object User
519
     * @param   string  $force_number   Reference to force on contract (not implemented yet)
520
     * @param   int     $notrigger      1=Does not execute triggers, 0= execute triggers
521
     * @return  int                     Return integer <0 if KO, >0 if OK
522
     */
523
    public function validate(User $user, $force_number = '', $notrigger = 0)
524
    {
525
        require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/files.lib.php';
526
        global $conf;
527
528
        $now = dol_now();
529
530
        $error = 0;
531
        dol_syslog(get_class($this) . '::validate user=' . $user->id . ', force_number=' . $force_number);
532
533
534
        $this->db->begin();
535
536
        $this->fetch_thirdparty();
537
538
        // A contract is validated so we can move thirdparty to status customer
539
        if (!getDolGlobalString('CONTRACT_DISABLE_AUTOSET_AS_CLIENT_ON_CONTRACT_VALIDATION') && $this->thirdparty->fournisseur == 0) {
540
            $result = $this->thirdparty->setAsCustomer();
0 ignored issues
show
Bug introduced by
The method setAsCustomer() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

540
            /** @scrutinizer ignore-call */ 
541
            $result = $this->thirdparty->setAsCustomer();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
541
        }
542
543
        // Define new ref
544
        if ($force_number) {
545
            $num = $force_number;
546
        } elseif (!$error && (preg_match('/^[\(]?PROV/i', $this->ref) || empty($this->ref))) { // empty should not happened, but when it occurs, the test save life
547
            $num = $this->getNextNumRef($this->thirdparty);
548
        } else {
549
            $num = $this->ref;
550
        }
551
        $this->newref = dol_sanitizeFileName($num);
552
553
        if ($num) {
554
            $sql = "UPDATE " . MAIN_DB_PREFIX . "contrat SET ref = '" . $this->db->escape($num) . "', statut = 1";
555
            //$sql.= ", fk_user_valid = ".$user->id.", date_valid = '".$this->db->idate($now)."'";
556
            $sql .= " WHERE rowid = " . ((int) $this->id) . " AND statut = 0";
557
558
            dol_syslog(get_class($this) . "::validate", LOG_DEBUG);
559
            $resql = $this->db->query($sql);
560
            if (!$resql) {
561
                dol_print_error($this->db);
562
                $error++;
563
                $this->error = $this->db->lasterror();
564
            }
565
566
            // Trigger calls
567
            if (!$error && !$notrigger) {
568
                // Call trigger
569
                $result = $this->call_trigger('CONTRACT_VALIDATE', $user);
570
                if ($result < 0) {
571
                    $error++;
572
                }
573
                // End call triggers
574
            }
575
576
            if (!$error) {
577
                $this->oldref = $this->ref;
578
579
                // Rename directory if dir was a temporary ref
580
                if (preg_match('/^[\(]?PROV/i', $this->ref)) {
581
                    // Now we rename also files into index
582
                    $sql = 'UPDATE ' . MAIN_DB_PREFIX . "ecm_files set filename = CONCAT('" . $this->db->escape($this->newref) . "', SUBSTR(filename, " . (strlen($this->ref) + 1) . ")), filepath = 'contract/" . $this->db->escape($this->newref) . "'";
583
                    $sql .= " WHERE filename LIKE '" . $this->db->escape($this->ref) . "%' AND filepath = 'contract/" . $this->db->escape($this->ref) . "' and entity = " . $conf->entity;
584
                    $resql = $this->db->query($sql);
585
                    if (!$resql) {
586
                        $error++;
587
                        $this->error = $this->db->lasterror();
588
                    }
589
                    $sql = 'UPDATE ' . MAIN_DB_PREFIX . "ecm_files set filepath = 'contract/" . $this->db->escape($this->newref) . "'";
590
                    $sql .= " WHERE filepath = 'contract/" . $this->db->escape($this->ref) . "' and entity = " . $conf->entity;
591
                    $resql = $this->db->query($sql);
592
                    if (!$resql) {
593
                        $error++;
594
                        $this->error = $this->db->lasterror();
595
                    }
596
597
                    // We rename directory ($this->ref = old ref, $num = new ref) in order not to lose the attachments
598
                    $oldref = dol_sanitizeFileName($this->ref);
599
                    $newref = dol_sanitizeFileName($num);
600
                    $dirsource = $conf->contract->dir_output . '/' . $oldref;
601
                    $dirdest = $conf->contract->dir_output . '/' . $newref;
602
                    if (!$error && file_exists($dirsource)) {
603
                        dol_syslog(get_class($this) . "::validate rename dir " . $dirsource . " into " . $dirdest);
604
605
                        if (@rename($dirsource, $dirdest)) {
606
                            dol_syslog("Rename ok");
607
                            // Rename docs starting with $oldref with $newref
608
                            $listoffiles = dol_dir_list($conf->contract->dir_output . '/' . $newref, 'files', 1, '^' . preg_quote($oldref, '/'));
609
                            foreach ($listoffiles as $fileentry) {
610
                                $dirsource = $fileentry['name'];
611
                                $dirdest = preg_replace('/^' . preg_quote($oldref, '/') . '/', $newref, $dirsource);
612
                                $dirsource = $fileentry['path'] . '/' . $dirsource;
613
                                $dirdest = $fileentry['path'] . '/' . $dirdest;
614
                                @rename($dirsource, $dirdest);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for rename(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

614
                                /** @scrutinizer ignore-unhandled */ @rename($dirsource, $dirdest);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
615
                            }
616
                        }
617
                    }
618
                }
619
            }
620
621
            // Set new ref and define current statut
622
            if (!$error) {
623
                $this->ref = $num;
624
                $this->status = self::STATUS_VALIDATED;
625
                $this->statut = self::STATUS_VALIDATED; // deprecated
626
                $this->date_validation = $now;
627
            }
628
        } else {
629
            $error++;
630
        }
631
632
        if (!$error) {
633
            $this->db->commit();
634
            return 1;
635
        } else {
636
            $this->db->rollback();
637
            return -1;
638
        }
639
    }
640
641
    /**
642
     * Unvalidate a contract
643
     *
644
     * @param   User    $user           Object User
645
     * @param   int     $notrigger      1=Does not execute triggers, 0=execute triggers
646
     * @return  int                     Return integer <0 if KO, >0 if OK
647
     */
648
    public function reopen($user, $notrigger = 0)
649
    {
650
        require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/files.lib.php';
651
652
        $now = dol_now();
653
654
        $error = 0;
655
        dol_syslog(get_class($this) . '::reopen user=' . $user->id);
656
657
        $this->db->begin();
658
659
        $this->fetch_thirdparty();
660
661
        $sql = "UPDATE " . MAIN_DB_PREFIX . "contrat SET statut = 0";
662
        //$sql.= ", fk_user_valid = null, date_valid = null";
663
        $sql .= " WHERE rowid = " . ((int) $this->id) . " AND statut = 1";
664
665
        dol_syslog(get_class($this) . "::validate", LOG_DEBUG);
666
        $resql = $this->db->query($sql);
667
        if (!$resql) {
668
            dol_print_error($this->db);
669
            $error++;
670
            $this->error = $this->db->lasterror();
671
        }
672
673
        // Trigger calls
674
        if (!$error && !$notrigger) {
675
            // Call trigger
676
            $result = $this->call_trigger('CONTRACT_REOPEN', $user);
677
            if ($result < 0) {
678
                $error++;
679
            }
680
            // End call triggers
681
        }
682
683
        // Set new ref and define current status
684
        if (!$error) {
685
            $this->statut = self::STATUS_DRAFT;
686
            $this->status = self::STATUS_DRAFT;
687
            $this->date_validation = $now;
688
        }
689
690
        if (!$error) {
691
            $this->db->commit();
692
            return 1;
693
        } else {
694
            $this->db->rollback();
695
            return -1;
696
        }
697
    }
698
699
    /**
700
     *  Load a contract from database
701
     *
702
     *  @param  int     $id             Id of contract to load
703
     *  @param  string  $ref            Ref
704
     *  @param  string  $ref_customer   Customer ref
705
     *  @param  string  $ref_supplier   Supplier ref
706
     *  @param  int     $noextrafields  0=Default to load extrafields, 1=No extrafields
707
     *  @param  int     $nolines        0=Default to load lines, 1=No lines
708
     *  @return int                     Return integer <0 if KO, 0 if not found or if two records found for same ref, Id of contract if OK
709
     */
710
    public function fetch($id, $ref = '', $ref_customer = '', $ref_supplier = '', $noextrafields = 0, $nolines = 0)
711
    {
712
        $sql = "SELECT rowid, statut as status, ref, fk_soc as thirdpartyid,";
713
        $sql .= " ref_supplier, ref_customer,";
714
        $sql .= " ref_ext,";
715
        $sql .= " entity,";
716
        $sql .= " date_contrat as datecontrat,";
717
        $sql .= " fk_user_author,";
718
        $sql .= " fk_projet as fk_project,";
719
        $sql .= " fk_commercial_signature, fk_commercial_suivi,";
720
        $sql .= " note_private, note_public, model_pdf, last_main_doc, extraparams";
721
        $sql .= " FROM " . MAIN_DB_PREFIX . "contrat";
722
        if (!$id) {
723
            $sql .= " WHERE entity IN (" . getEntity('contract') . ")";
724
        } else {
725
            $sql .= " WHERE rowid = " . (int) $id;
726
        }
727
        if ($ref_customer) {
728
            $sql .= " AND ref_customer = '" . $this->db->escape($ref_customer) . "'";
729
        }
730
        if ($ref_supplier) {
731
            $sql .= " AND ref_supplier = '" . $this->db->escape($ref_supplier) . "'";
732
        }
733
        if ($ref) {
734
            $sql .= " AND ref = '" . $this->db->escape($ref) . "'";
735
        }
736
737
        dol_syslog(get_class($this) . "::fetch", LOG_DEBUG);
738
        $resql = $this->db->query($sql);
739
        if ($resql) {
740
            $num = $this->db->num_rows($resql);
741
            if ($num > 1) {
742
                $this->error = 'Fetch found several records.';
743
                dol_syslog($this->error, LOG_ERR);
744
                $result = -2;
745
                return 0;
746
            } elseif ($num) {   // $num = 1
747
                $obj = $this->db->fetch_object($resql);
748
                if ($obj) {
749
                    $this->id = $obj->rowid;
750
                    $this->ref = (!isset($obj->ref) || !$obj->ref) ? $obj->rowid : $obj->ref;
751
                    $this->ref_customer = $obj->ref_customer;
752
                    $this->ref_supplier = $obj->ref_supplier;
753
                    $this->ref_ext = $obj->ref_ext;
754
                    $this->entity = $obj->entity;
755
                    $this->statut = $obj->status;
756
                    $this->status = $obj->status;
757
758
                    $this->date_contrat = $this->db->jdate($obj->datecontrat);
759
                    $this->date_creation = $this->db->jdate($obj->datecontrat);
760
761
                    $this->user_author_id = $obj->fk_user_author;
762
763
                    $this->commercial_signature_id = $obj->fk_commercial_signature;
764
                    $this->commercial_suivi_id = $obj->fk_commercial_suivi;
765
766
                    $this->note_private = $obj->note_private;
767
                    $this->note_public = $obj->note_public;
768
                    $this->model_pdf = $obj->model_pdf;
769
770
                    $this->fk_projet = $obj->fk_project; // deprecated
0 ignored issues
show
Deprecated Code introduced by
The property Dolibarr\Code\Contrat\Classes\Contrat::$fk_projet has been deprecated: Use fk_project instead ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

770
                    /** @scrutinizer ignore-deprecated */ $this->fk_projet = $obj->fk_project; // deprecated

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
771
                    $this->fk_project = $obj->fk_project;
772
773
                    $this->socid = $obj->thirdpartyid;
774
                    $this->fk_soc = $obj->thirdpartyid;
0 ignored issues
show
Deprecated Code introduced by
The property Dolibarr\Code\Contrat\Classes\Contrat::$fk_soc has been deprecated: Use $socid ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

774
                    /** @scrutinizer ignore-deprecated */ $this->fk_soc = $obj->thirdpartyid;

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
775
                    $this->last_main_doc = $obj->last_main_doc;
776
                    $this->extraparams = (isset($obj->extraparams) ? (array) json_decode($obj->extraparams, true) : null);
777
778
                    $this->db->free($resql);
779
780
                    // Retrieve all extrafields
781
                    // fetch optionals attributes and labels
782
                    if (empty($noextrafields)) {
783
                        $result = $this->fetch_optionals();
784
                        if ($result < 0) {
785
                            $this->error = $this->db->lasterror();
786
                            return -4;
787
                        }
788
                    }
789
790
                    // Lines
791
                    if (empty($nolines)) {
792
                        if ($result >= 0 && !empty($this->table_element_line)) {
793
                            $result = $this->fetch_lines();
794
                            if ($result < 0) {
795
                                $this->error = $this->db->lasterror();
796
                                return -3;
797
                            }
798
                        }
799
                    }
800
801
                    return $this->id;
802
                } else {
803
                    dol_syslog(get_class($this) . "::fetch Contract failed");
804
                    $this->error = "Fetch contract failed";
805
                    return -1;
806
                }
807
            } else {
808
                dol_syslog(get_class($this) . "::fetch Contract not found");
809
                $this->error = "Contract not found";
810
                return 0;
811
            }
812
        } else {
813
            dol_syslog(get_class($this) . "::fetch Error searching contract");
814
            $this->error = $this->db->error();
815
            return -1;
816
        }
817
    }
818
819
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
820
    /**
821
     *  Load lines array into this->lines.
822
     *  This set also nbofserviceswait, nbofservicesopened, nbofservicesexpired and nbofservicesclosed
823
     *
824
     *  @param      int             $only_services          0=Default for all, 1=Force only services (depending on setup, we may also have physical products in a contract)
825
     *  @param      int             $loadalsotranslation    0=Default to not load translations, 1=Load also translations of product descriptions
826
     *  @param      int             $noextrafields          0=Default to load extrafields, 1=Do not load the extrafields of lines
827
     *  @return     array|int                               Return array of contract lines
828
     */
829
    public function fetch_lines($only_services = 0, $loadalsotranslation = 0, $noextrafields = 0)
830
    {
831
		// phpcs:enable
832
        $this->nbofservices = 0;
833
        $this->nbofserviceswait = 0;
834
        $this->nbofservicesopened = 0;
835
        $this->nbofservicesexpired = 0;
836
        $this->nbofservicesclosed = 0;
837
838
        $total_ttc = 0;
839
        $total_vat = 0;
840
        $total_ht = 0;
841
842
        $now = dol_now();
843
844
        $this->lines = array();
845
        $pos = 0;
846
847
        // Selects contract lines related to a product
848
        $sql = "SELECT p.label as product_label, p.description as product_desc, p.ref as product_ref, p.fk_product_type as product_type,";
849
        $sql .= " d.rowid, d.fk_contrat, d.statut as status, d.description, d.price_ht, d.vat_src_code, d.tva_tx, d.localtax1_tx, d.localtax2_tx, d.localtax1_type, d.localtax2_type, d.qty, d.remise_percent, d.subprice, d.fk_product_fournisseur_price as fk_fournprice, d.buy_price_ht as pa_ht,";
850
        $sql .= " d.total_ht,";
851
        $sql .= " d.total_tva,";
852
        $sql .= " d.total_localtax1,";
853
        $sql .= " d.total_localtax2,";
854
        $sql .= " d.total_ttc,";
855
        $sql .= " d.info_bits, d.fk_product,";
856
        $sql .= " d.date_ouverture_prevue as date_start,";
857
        $sql .= " d.date_ouverture as date_start_real,";
858
        $sql .= " d.date_fin_validite as date_end,";
859
        $sql .= " d.date_cloture as date_end_real,";
860
        $sql .= " d.fk_user_author,";
861
        $sql .= " d.fk_user_ouverture,";
862
        $sql .= " d.fk_user_cloture,";
863
        $sql .= " d.fk_unit,";
864
        $sql .= " d.product_type as type,";
865
        $sql .= " d.rang";
866
        $sql .= " FROM " . MAIN_DB_PREFIX . "contratdet as d LEFT JOIN " . MAIN_DB_PREFIX . "product as p ON d.fk_product = p.rowid";
867
        $sql .= " WHERE d.fk_contrat = " . ((int) $this->id);
868
        if ($only_services == 1) {
869
            $sql .= " AND d.product_type = 1";
870
        }
871
        $sql .= " ORDER by d.rang ASC";
872
873
        dol_syslog(get_class($this) . "::fetch_lines", LOG_DEBUG);
874
        $result = $this->db->query($sql);
875
        if ($result) {
876
            $num = $this->db->num_rows($result);
877
            $i = 0;
878
879
            while ($i < $num) {
880
                $objp = $this->db->fetch_object($result);
881
882
                $line = new ContratLigne($this->db);
883
884
                $line->id = $objp->rowid;
885
                $line->ref              = $objp->rowid;
886
                $line->fk_contrat = $objp->fk_contrat;
887
                $line->desc = $objp->description; // Description line
888
                $line->qty              = $objp->qty;
889
                $line->vat_src_code     = $objp->vat_src_code;
890
                $line->tva_tx = $objp->tva_tx;
891
                $line->localtax1_tx     = $objp->localtax1_tx;
892
                $line->localtax2_tx     = $objp->localtax2_tx;
893
                $line->localtax1_type   = $objp->localtax1_type;
894
                $line->localtax2_type   = $objp->localtax2_type;
895
                $line->subprice         = $objp->subprice;
896
                $line->statut = $objp->status;
897
                $line->status = $objp->status;
898
                $line->remise_percent   = $objp->remise_percent;
899
                $line->price_ht         = $objp->price_ht;
900
                $line->price = $objp->price_ht; // For backward compatibility
0 ignored issues
show
Deprecated Code introduced by
The property Dolibarr\Code\Contrat\Classes\ContratLigne::$price has been deprecated: Use $price_ht instead ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

900
                /** @scrutinizer ignore-deprecated */ $line->price = $objp->price_ht; // For backward compatibility

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
901
                $line->total_ht         = $objp->total_ht;
902
                $line->total_tva        = $objp->total_tva;
903
                $line->total_localtax1  = $objp->total_localtax1;
904
                $line->total_localtax2  = $objp->total_localtax2;
905
                $line->total_ttc        = $objp->total_ttc;
906
                $line->fk_product = (($objp->fk_product > 0) ? $objp->fk_product : 0);
907
                $line->info_bits        = $objp->info_bits;
908
                $line->type = $objp->type;
909
910
                $line->fk_fournprice = $objp->fk_fournprice;
911
                $marginInfos = getMarginInfos($objp->subprice, $objp->remise_percent, $objp->tva_tx, $objp->localtax1_tx, $objp->localtax2_tx, $objp->fk_fournprice, $objp->pa_ht);
912
                $line->pa_ht = $marginInfos[0];
913
914
                $line->fk_user_author = $objp->fk_user_author;
915
                $line->fk_user_ouverture = $objp->fk_user_ouverture;
916
                $line->fk_user_cloture = $objp->fk_user_cloture;
917
                $line->fk_unit = $objp->fk_unit;
918
919
                $line->ref = $objp->product_ref; // deprecated
920
                $line->product_ref = $objp->product_ref; // Product Ref
921
                $line->product_type     = $objp->product_type; // Product Type
922
                $line->product_desc     = $objp->product_desc; // Product Description
923
                $line->product_label    = $objp->product_label; // Product Label
924
925
                $line->description = $objp->description;
926
927
                $line->date_start            = $this->db->jdate($objp->date_start);
928
                $line->date_start_real       = $this->db->jdate($objp->date_start_real);
929
                $line->date_end              = $this->db->jdate($objp->date_end);
930
                $line->date_end_real         = $this->db->jdate($objp->date_end_real);
931
                // For backward compatibility
932
                //$line->date_ouverture_prevue = $this->db->jdate($objp->date_ouverture_prevue);
933
                //$line->date_ouverture        = $this->db->jdate($objp->date_ouverture);
934
                //$line->date_fin_validite     = $this->db->jdate($objp->date_fin_validite);
935
                //$line->date_cloture          = $this->db->jdate($objp->date_cloture);
936
                //$line->date_debut_prevue = $this->db->jdate($objp->date_ouverture_prevue);
937
                //$line->date_debut_reel   = $this->db->jdate($objp->date_ouverture);
938
                //$line->date_fin_prevue   = $this->db->jdate($objp->date_fin_validite);
939
                //$line->date_fin_reel     = $this->db->jdate($objp->date_cloture);
940
941
                $line->rang     = $objp->rang;
942
943
                // Retrieve all extrafields for contract line
944
                // fetch optionals attributes and labels
945
                if (empty($noextrafields)) {
946
                    $line->fetch_optionals();
947
                }
948
949
                // multilangs
950
                if (getDolGlobalInt('MAIN_MULTILANGS') && !empty($objp->fk_product) && !empty($loadalsotranslation)) {
951
                    $tmpproduct = new Product($this->db);
0 ignored issues
show
Bug introduced by
The type Dolibarr\Code\Contrat\Classes\Product was not found. Did you mean Product? If so, make sure to prefix the type with \.
Loading history...
952
                    $tmpproduct->fetch($objp->fk_product);
953
                    $tmpproduct->getMultiLangs();
954
955
                    $line->multilangs = $tmpproduct->multilangs;
956
                }
957
958
                $this->lines[$pos] = $line;
959
960
                $this->lines_id_index_mapper[$line->id] = $pos;
961
962
                //dol_syslog("1 ".$line->desc);
963
                //dol_syslog("2 ".$line->product_desc);
964
965
                if ($line->statut == ContratLigne::STATUS_INITIAL) {
966
                    $this->nbofserviceswait++;
967
                }
968
                if ($line->statut == ContratLigne::STATUS_OPEN && (empty($line->date_end) || $line->date_end >= $now)) {
969
                    $this->nbofservicesopened++;
970
                }
971
                if ($line->statut == ContratLigne::STATUS_OPEN && (!empty($line->date_end) && $line->date_end < $now)) {
972
                    $this->nbofservicesexpired++;
973
                }
974
                if ($line->statut == ContratLigne::STATUS_CLOSED) {
975
                    $this->nbofservicesclosed++;
976
                }
977
978
                $total_ttc += $objp->total_ttc; // TODO Not saved into database
979
                $total_vat += $objp->total_tva;
980
                $total_ht += $objp->total_ht;
981
982
                $i++;
983
                $pos++;
984
            }
985
            $this->db->free($result);
986
        } else {
987
            dol_syslog(get_class($this) . "::Fetch Error when reading lines of contracts linked to products");
988
            return -3;
989
        }
990
991
        // Now set the global properties on contract not stored into database.
992
        $this->nbofservices = count($this->lines);
993
        $this->total_ttc = (float) price2num($total_ttc);
994
        $this->total_tva = (float) price2num($total_vat);
995
        $this->total_ht = (float) price2num($total_ht);
996
997
        return $this->lines;
998
    }
999
1000
    /**
1001
     *  Create a contract into database
1002
     *
1003
     *  @param  User    $user       User that create
1004
     *  @return int                 Return integer <0 if KO, id of contract if OK
1005
     */
1006
    public function create($user)
1007
    {
1008
        global $conf, $langs, $mysoc;
1009
1010
        // Check parameters
1011
        $paramsok = 1;
1012
        if ($this->commercial_signature_id <= 0) {
1013
            $langs->load("commercial");
1014
            $this->error .= $langs->trans("ErrorFieldRequired", $langs->transnoentitiesnoconv("SalesRepresentativeSignature"));
1015
            $paramsok = 0;
1016
        }
1017
        if ($this->commercial_suivi_id <= 0) {
1018
            $langs->load("commercial");
1019
            $this->error .= ($this->error ? "<br>" : '');
1020
            $this->error .= $langs->trans("ErrorFieldRequired", $langs->transnoentitiesnoconv("SalesRepresentativeFollowUp"));
1021
            $paramsok = 0;
1022
        }
1023
        if (!$paramsok) {
1024
            return -1;
1025
        }
1026
1027
1028
        $this->db->begin();
1029
1030
        $now = dol_now();
1031
1032
        // Insert contract
1033
        $sql = "INSERT INTO " . MAIN_DB_PREFIX . "contrat (datec, fk_soc, fk_user_author, date_contrat,";
1034
        $sql .= " fk_commercial_signature, fk_commercial_suivi, fk_projet,";
1035
        $sql .= " ref, entity, note_private, note_public, ref_customer, ref_supplier, ref_ext)";
1036
        $sql .= " VALUES ('" . $this->db->idate($now) . "', " . ((int) $this->socid) . ", " . ((int) $user->id);
1037
        $sql .= ", " . (dol_strlen($this->date_contrat) != 0 ? "'" . $this->db->idate($this->date_contrat) . "'" : "NULL");
1038
        $sql .= "," . ($this->commercial_signature_id > 0 ? ((int) $this->commercial_signature_id) : "NULL");
1039
        $sql .= "," . ($this->commercial_suivi_id > 0 ? ((int) $this->commercial_suivi_id) : "NULL");
1040
        $sql .= "," . ($this->fk_project > 0 ? ((int) $this->fk_project) : "NULL");
1041
        $sql .= ", " . (dol_strlen($this->ref) <= 0 ? "null" : "'" . $this->db->escape($this->ref) . "'");
1042
        $sql .= ", " . ((int) $conf->entity);
1043
        $sql .= ", " . (!empty($this->note_private) ? ("'" . $this->db->escape($this->note_private) . "'") : "NULL");
1044
        $sql .= ", " . (!empty($this->note_public) ? ("'" . $this->db->escape($this->note_public) . "'") : "NULL");
1045
        $sql .= ", " . (!empty($this->ref_customer) ? ("'" . $this->db->escape($this->ref_customer) . "'") : "NULL");
1046
        $sql .= ", " . (!empty($this->ref_supplier) ? ("'" . $this->db->escape($this->ref_supplier) . "'") : "NULL");
1047
        $sql .= ", " . (!empty($this->ref_ext) ? ("'" . $this->db->escape($this->ref_ext) . "'") : "NULL");
1048
        $sql .= ")";
1049
        $resql = $this->db->query($sql);
1050
1051
        if ($resql) {
1052
            $error = 0;
1053
1054
            $this->id = $this->db->last_insert_id(MAIN_DB_PREFIX . "contrat");
1055
1056
            // Load object modContract
1057
            $module = (getDolGlobalString('CONTRACT_ADDON') ? $conf->global->CONTRACT_ADDON : 'mod_contract_serpis');
1058
            if (substr($module, 0, 13) == 'mod_contract_' && substr($module, -3) == 'php') {
1059
                $module = substr($module, 0, dol_strlen($module) - 4);
1060
            }
1061
            $result = dol_include_once('/core/modules/contract/' . $module . '.php');
1062
            if ($result > 0) {
1063
                $modCodeContract = new $module();
1064
                '@phan-var-force CommonNumRefGenerator $modCodeContrat';
1065
1066
                if (!empty($modCodeContract->code_auto)) {
1067
                    // Force the ref to a draft value if numbering module is an automatic numbering
1068
                    $sql = 'UPDATE ' . MAIN_DB_PREFIX . "contrat SET ref='(PROV" . $this->id . ")' WHERE rowid=" . ((int) $this->id);
1069
                    if ($this->db->query($sql)) {
1070
                        if ($this->id) {
1071
                            $this->ref = "(PROV" . $this->id . ")";
1072
                        }
1073
                    }
1074
                }
1075
            }
1076
1077
            if (!$error) {
1078
                $result = $this->insertExtraFields();
1079
                if ($result < 0) {
1080
                    $error++;
1081
                }
1082
            }
1083
1084
            // Insert business contacts ('SALESREPSIGN','contrat')
1085
            if (!$error) {
1086
                $result = $this->add_contact($this->commercial_signature_id, 'SALESREPSIGN', 'internal');
1087
                if ($result < 0) {
1088
                    $error++;
1089
                }
1090
            }
1091
1092
            // Insert business contacts ('SALESREPFOLL','contrat')
1093
            if (!$error) {
1094
                $result = $this->add_contact($this->commercial_suivi_id, 'SALESREPFOLL', 'internal');
1095
                if ($result < 0) {
1096
                    $error++;
1097
                }
1098
            }
1099
1100
            if (!$error) {
1101
                if (!empty($this->linkedObjectsIds) && empty($this->linked_objects)) {  // To use new linkedObjectsIds instead of old linked_objects
1102
                    $this->linked_objects = $this->linkedObjectsIds; // TODO Replace linked_objects with linkedObjectsIds
1103
                }
1104
1105
                // Add object linked
1106
                if (!$error && $this->id && !empty($this->linked_objects) && is_array($this->linked_objects)) {
1107
                    foreach ($this->linked_objects as $origin => $tmp_origin_id) {
1108
                        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, ...))
1109
                            foreach ($tmp_origin_id as $origin_id) {
1110
                                $ret = $this->add_object_linked($origin, $origin_id);
1111
                                if (!$ret) {
1112
                                    $this->error = $this->db->lasterror();
1113
                                    $error++;
1114
                                }
1115
                            }
1116
                        } else { // Old behaviour, if linked_object has only one link per type, so is something like array('contract'=>id1))
1117
                            $origin_id = $tmp_origin_id;
1118
                            $ret = $this->add_object_linked($origin, $origin_id);
1119
                            if (!$ret) {
1120
                                $this->error = $this->db->lasterror();
1121
                                $error++;
1122
                            }
1123
                        }
1124
                    }
1125
                }
1126
1127
                if (!$error && $this->id && getDolGlobalString('MAIN_PROPAGATE_CONTACTS_FROM_ORIGIN') && !empty($this->origin) && !empty($this->origin_id)) {   // Get contact from origin object
0 ignored issues
show
Deprecated Code introduced by
The property Dolibarr\Core\Base\CommonObject::$origin has been deprecated: Use $origin_type and $origin_id instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

1127
                if (!$error && $this->id && getDolGlobalString('MAIN_PROPAGATE_CONTACTS_FROM_ORIGIN') && !empty(/** @scrutinizer ignore-deprecated */ $this->origin) && !empty($this->origin_id)) {   // Get contact from origin object

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
1128
                    $originforcontact = $this->origin;
0 ignored issues
show
Deprecated Code introduced by
The property Dolibarr\Core\Base\CommonObject::$origin has been deprecated: Use $origin_type and $origin_id instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

1128
                    $originforcontact = /** @scrutinizer ignore-deprecated */ $this->origin;

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
1129
                    $originidforcontact = $this->origin_id;
1130
                    if ($originforcontact == 'shipping') {     // shipment and order share the same contacts. If creating from shipment we take data of order
1131
                        require_once constant('DOL_DOCUMENT_ROOT') . '/expedition/class/expedition.class.php';
1132
                        $exp = new Expedition($this->db);
0 ignored issues
show
Bug introduced by
The type Dolibarr\Code\Contrat\Classes\Expedition was not found. Did you mean Expedition? If so, make sure to prefix the type with \.
Loading history...
1133
                        $exp->fetch($this->origin_id);
1134
                        $exp->fetchObjectLinked();
1135
                        if (count($exp->linkedObjectsIds['commande']) > 0) {
1136
                            foreach ($exp->linkedObjectsIds['commande'] as $key => $value) {
1137
                                $originforcontact = 'commande';
1138
                                $originidforcontact = $value;
1139
                                break; // We take first one
1140
                            }
1141
                        }
1142
                    }
1143
1144
                    $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";
1145
                    $sqlcontact .= " WHERE element_id = " . ((int) $originidforcontact) . " AND ec.fk_c_type_contact = ctc.rowid AND ctc.element = '" . $this->db->escape($originforcontact) . "'";
1146
1147
                    $resqlcontact = $this->db->query($sqlcontact);
1148
                    if ($resqlcontact) {
1149
                        while ($objcontact = $this->db->fetch_object($resqlcontact)) {
1150
                            if ($objcontact->source == 'internal' && in_array($objcontact->code, array('SALESREPSIGN', 'SALESREPFOLL'))) {
1151
                                continue; // ignore this, already forced previously
1152
                            }
1153
1154
                            $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
1155
                        }
1156
                    } else {
1157
                        dol_print_error($resqlcontact);
1158
                    }
1159
                }
1160
            }
1161
1162
            if (!$error) {
1163
                // Call trigger
1164
                $result = $this->call_trigger('CONTRACT_CREATE', $user);
1165
                if ($result < 0) {
1166
                    $error++;
1167
                }
1168
                // End call triggers
1169
1170
                if (!$error) {
1171
                    $this->db->commit();
1172
                    return $this->id;
1173
                } else {
1174
                    dol_syslog(get_class($this) . "::create - 30 - " . $this->error, LOG_ERR);
1175
                    $this->db->rollback();
1176
                    return -3;
1177
                }
1178
            } else {
1179
                $this->error = "Failed to add contract";
1180
                dol_syslog(get_class($this) . "::create - 20 - " . $this->error, LOG_ERR);
1181
                $this->db->rollback();
1182
                return -2;
1183
            }
1184
        } else {
1185
            $this->error = $langs->trans("UnknownError") . ": " . $this->db->error();
1186
            dol_syslog(get_class($this) . "::create - 10 - " . $this->error, LOG_ERR);
1187
1188
            $this->db->rollback();
1189
            return -1;
1190
        }
1191
    }
1192
1193
1194
    /**
1195
     *  Delete object
1196
     *
1197
     *  @param  User        $user       User that deletes
1198
     *  @return int                     Return integer < 0 if KO, > 0 if OK
1199
     */
1200
    public function delete($user)
1201
    {
1202
        global $conf;
1203
1204
        require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/files.lib.php';
1205
1206
        $error = 0;
1207
1208
        $this->db->begin();
1209
1210
        // Call trigger
1211
        $result = $this->call_trigger('CONTRACT_DELETE', $user);
1212
        if ($result < 0) {
1213
            $error++;
1214
        }
1215
        // End call triggers
1216
1217
        if (!$error) {
1218
            // Delete linked contacts
1219
            $res = $this->delete_linked_contact();
1220
            if ($res < 0) {
1221
                dol_syslog(get_class($this) . "::delete error", LOG_ERR);
1222
                $error++;
1223
            }
1224
        }
1225
1226
        if (!$error) {
1227
            // Delete linked object
1228
            $res = $this->deleteObjectLinked();
1229
            if ($res < 0) {
1230
                $error++;
1231
            }
1232
        }
1233
1234
        // Delete lines
1235
        if (!$error) {
1236
            // Delete contratdet extrafields
1237
            $main = MAIN_DB_PREFIX . 'contratdet';
1238
            $ef = $main . "_extrafields";
1239
            $sql = "DELETE FROM " . $ef . " WHERE fk_object IN (SELECT rowid FROM " . $main . " WHERE fk_contrat = " . ((int) $this->id) . ")";
1240
1241
            dol_syslog(get_class($this) . "::delete contratdet_extrafields", LOG_DEBUG);
1242
            $resql = $this->db->query($sql);
1243
            if (!$resql) {
1244
                $this->error = $this->db->error();
1245
                $error++;
1246
            }
1247
        }
1248
1249
        if (!$error) {
1250
            // Delete contratdet
1251
            $sql = "DELETE FROM " . MAIN_DB_PREFIX . "contratdet";
1252
            $sql .= " WHERE fk_contrat=" . ((int) $this->id);
1253
1254
            dol_syslog(get_class($this) . "::delete contratdet", LOG_DEBUG);
1255
            $resql = $this->db->query($sql);
1256
            if (!$resql) {
1257
                $this->error = $this->db->error();
1258
                $error++;
1259
            }
1260
        }
1261
1262
        // Delete llx_ecm_files
1263
        if (!$error) {
1264
            $sql = 'DELETE FROM ' . MAIN_DB_PREFIX . "ecm_files WHERE src_object_type = '" . $this->db->escape($this->table_element . (empty($this->module) ? "" : "@" . $this->module)) . "' AND src_object_id = " . ((int) $this->id);
1265
            $resql = $this->db->query($sql);
1266
            if (!$resql) {
1267
                $this->error = $this->db->lasterror();
1268
                $this->errors[] = $this->error;
1269
                $error++;
1270
            }
1271
        }
1272
1273
        // Delete contract
1274
        if (!$error) {
1275
            $sql = "DELETE FROM " . MAIN_DB_PREFIX . "contrat";
1276
            $sql .= " WHERE rowid=" . ((int) $this->id);
1277
1278
            dol_syslog(get_class($this) . "::delete contrat", LOG_DEBUG);
1279
            $resql = $this->db->query($sql);
1280
            if (!$resql) {
1281
                $this->error = $this->db->error();
1282
                $error++;
1283
            }
1284
        }
1285
1286
        // Removed extrafields
1287
        if (!$error) {
1288
            $result = $this->deleteExtraFields();
1289
            if ($result < 0) {
1290
                $error++;
1291
                dol_syslog(get_class($this) . "::delete error -3 " . $this->error, LOG_ERR);
1292
            }
1293
        }
1294
1295
        if (!$error) {
1296
            // We remove directory
1297
            $ref = dol_sanitizeFileName($this->ref);
1298
            if ($conf->contrat->dir_output) {
1299
                $dir = $conf->contrat->multidir_output[$this->entity] . "/" . $ref;
1300
                if (file_exists($dir)) {
1301
                    $res = @dol_delete_dir_recursive($dir);
1302
                    if (!$res) {
1303
                        $this->error = 'ErrorFailToDeleteDir';
1304
                        $error++;
1305
                    }
1306
                }
1307
            }
1308
        }
1309
1310
        if (!$error) {
1311
            $this->db->commit();
1312
            return 1;
1313
        } else {
1314
            $this->error = $this->db->lasterror();
1315
            $this->db->rollback();
1316
            return -1;
1317
        }
1318
    }
1319
1320
    /**
1321
     *  Update object into database
1322
     *
1323
     *  @param  User    $user        User that modifies
1324
     *  @param  int     $notrigger   0=launch triggers after, 1=disable triggers
1325
     *  @return int                  Return integer <0 if KO, >0 if OK
1326
     */
1327
    public function update($user, $notrigger = 0)
1328
    {
1329
        global $conf;
1330
        $error = 0;
1331
1332
        // Clean parameters
1333
        if (empty($this->fk_commercial_signature) && $this->commercial_signature_id > 0) {
1334
            $this->fk_commercial_signature = $this->commercial_signature_id;
1335
        }
1336
        if (empty($this->fk_commercial_suivi) && $this->commercial_suivi_id > 0) {
1337
            $this->fk_commercial_suivi = $this->commercial_suivi_id;
1338
        }
1339
        if (empty($this->socid) && $this->fk_soc > 0) {
0 ignored issues
show
Deprecated Code introduced by
The property Dolibarr\Code\Contrat\Classes\Contrat::$fk_soc has been deprecated: Use $socid ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

1339
        if (empty($this->socid) && /** @scrutinizer ignore-deprecated */ $this->fk_soc > 0) {

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
1340
            $this->socid = (int) $this->fk_soc;
0 ignored issues
show
Deprecated Code introduced by
The property Dolibarr\Code\Contrat\Classes\Contrat::$fk_soc has been deprecated: Use $socid ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

1340
            $this->socid = (int) /** @scrutinizer ignore-deprecated */ $this->fk_soc;

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
1341
        }
1342
        if (empty($this->fk_project) && $this->projet > 0) {
0 ignored issues
show
Bug Best Practice introduced by
The property $projet is declared private in Dolibarr\Core\Base\CommonObject. Since you implement __get, consider adding a @property or @property-read.
Loading history...
Deprecated Code introduced by
The property Dolibarr\Core\Base\CommonObject::$projet has been deprecated: Use $project instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

1342
        if (empty($this->fk_project) && /** @scrutinizer ignore-deprecated */ $this->projet > 0) {

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
1343
            $this->fk_project = (int) $this->projet;
0 ignored issues
show
Deprecated Code introduced by
The property Dolibarr\Core\Base\CommonObject::$projet has been deprecated: Use $project instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

1343
            $this->fk_project = (int) /** @scrutinizer ignore-deprecated */ $this->projet;

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
1344
        }
1345
1346
        if (isset($this->ref)) {
1347
            $this->ref = trim($this->ref);
1348
        }
1349
        if (isset($this->ref_customer)) {
1350
            $this->ref_customer = trim($this->ref_customer);
1351
        }
1352
        if (isset($this->ref_supplier)) {
1353
            $this->ref_supplier = trim($this->ref_supplier);
1354
        }
1355
        if (isset($this->ref_ext)) {
1356
            $this->ref_ext = trim($this->ref_ext);
1357
        }
1358
        if (isset($this->entity)) {
1359
            $this->entity = (int) $this->entity;
1360
        }
1361
        if (isset($this->statut)) {
1362
            $this->statut = (int) $this->statut;
1363
        }
1364
        if (isset($this->status)) {
1365
            $this->status = (int) $this->status;
1366
        }
1367
        if (isset($this->socid)) {
1368
            $this->socid = (int) $this->socid;
1369
        }
1370
        if (isset($this->fk_commercial_signature)) {
1371
            $this->fk_commercial_signature = trim($this->fk_commercial_signature);
1372
        }
1373
        if (isset($this->fk_commercial_suivi)) {
1374
            $this->fk_commercial_suivi = trim($this->fk_commercial_suivi);
1375
        }
1376
        if (isset($this->note_private)) {
1377
            $this->note_private = trim($this->note_private);
1378
        }
1379
        if (isset($this->note_public)) {
1380
            $this->note_public = trim($this->note_public);
1381
        }
1382
        if (isset($this->import_key)) {
1383
            $this->import_key = trim($this->import_key);
1384
        }
1385
        //if (isset($this->extraparams)) $this->extraparams=trim($this->extraparams);
1386
1387
        // $this->oldcopy must have been set by the caller of update
1388
1389
        // Update request
1390
        $sql = "UPDATE " . MAIN_DB_PREFIX . "contrat SET";
1391
        $sql .= " ref=" . (isset($this->ref) ? "'" . $this->db->escape($this->ref) . "'" : "null") . ",";
1392
        $sql .= " ref_customer=" . (isset($this->ref_customer) ? "'" . $this->db->escape($this->ref_customer) . "'" : "null") . ",";
1393
        $sql .= " ref_supplier=" . (isset($this->ref_supplier) ? "'" . $this->db->escape($this->ref_supplier) . "'" : "null") . ",";
1394
        $sql .= " ref_ext=" . (isset($this->ref_ext) ? "'" . $this->db->escape($this->ref_ext) . "'" : "null") . ",";
1395
        $sql .= " entity=" . $conf->entity . ",";
1396
        $sql .= " date_contrat=" . (dol_strlen($this->date_contrat) != 0 ? "'" . $this->db->idate($this->date_contrat) . "'" : 'null') . ",";
1397
        $sql .= " statut=" . (isset($this->statut) ? $this->statut : (isset($this->status) ? $this->status : "null")) . ",";
1398
        $sql .= " fk_soc=" . ($this->socid > 0 ? $this->socid : "null") . ",";
1399
        $sql .= " fk_projet=" . ($this->fk_project > 0 ? $this->fk_project : "null") . ",";
1400
        $sql .= " fk_commercial_signature=" . (isset($this->fk_commercial_signature) ? $this->fk_commercial_signature : "null") . ",";
1401
        $sql .= " fk_commercial_suivi=" . (isset($this->fk_commercial_suivi) ? $this->fk_commercial_suivi : "null") . ",";
1402
        $sql .= " note_private=" . (isset($this->note_private) ? "'" . $this->db->escape($this->note_private) . "'" : "null") . ",";
1403
        $sql .= " note_public=" . (isset($this->note_public) ? "'" . $this->db->escape($this->note_public) . "'" : "null") . ",";
1404
        $sql .= " import_key=" . (isset($this->import_key) ? "'" . $this->db->escape($this->import_key) . "'" : "null");
1405
        //$sql.= " extraparams=".(isset($this->extraparams)?"'".$this->db->escape($this->extraparams)."'":"null");
1406
        $sql .= " WHERE rowid=" . ((int) $this->id);
1407
1408
        $this->db->begin();
1409
1410
        $resql = $this->db->query($sql);
1411
        if (!$resql) {
1412
            $error++;
1413
            $this->errors[] = "Error " . $this->db->lasterror();
1414
        }
1415
1416
        if (!$error) {
1417
            $result = $this->insertExtraFields();   // This delete and reinsert extrafields
1418
            if ($result < 0) {
1419
                $error++;
1420
            }
1421
        }
1422
1423
        if (!$error && !$notrigger) {
1424
            // Call triggers
1425
            $result = $this->call_trigger('CONTRACT_MODIFY', $user);
1426
            if ($result < 0) {
1427
                $error++;
1428
            }
1429
            // End call triggers
1430
        }
1431
1432
        // Commit or rollback
1433
        if ($error) {
1434
            foreach ($this->errors as $errmsg) {
1435
                dol_syslog(get_class($this) . "::update " . $errmsg, LOG_ERR);
1436
                $this->error .= ($this->error ? ', ' . $errmsg : $errmsg);
1437
            }
1438
            $this->db->rollback();
1439
            return -1 * $error;
1440
        } else {
1441
            $this->db->commit();
1442
            return 1;
1443
        }
1444
    }
1445
1446
1447
    /**
1448
     *  Ajoute une ligne de contrat en base
1449
     *
1450
     *  @param  string      $desc               Description of line
1451
     *  @param  float       $pu_ht              Unit price net
1452
     *  @param  float       $qty                Quantity
1453
     *  @param  float       $txtva              Vat rate
1454
     *  @param  float       $txlocaltax1        Local tax 1 rate
1455
     *  @param  float       $txlocaltax2        Local tax 2 rate
1456
     *  @param  int         $fk_product         Id produit
1457
     *  @param  float       $remise_percent     Percentage discount of the line
1458
     *  @param  int         $date_start         Date de debut prevue
1459
     *  @param  int         $date_end           Date de fin prevue
1460
     *  @param  string      $price_base_type    HT or TTC
1461
     *  @param  float       $pu_ttc             Prix unitaire TTC
1462
     *  @param  int         $info_bits          Bits of type of lines
1463
     *  @param  int         $fk_fournprice      Fourn price id
1464
     *  @param  int         $pa_ht              Buying price HT
1465
     *  @param  array       $array_options      extrafields array
1466
     *  @param  string      $fk_unit            Code of the unit to use. Null to use the default one
1467
     *  @param  int         $rang               Position
1468
     *  @return int                             Return integer <0 if KO, >0 if OK
1469
     */
1470
    public function addline($desc, $pu_ht, $qty, $txtva, $txlocaltax1, $txlocaltax2, $fk_product, $remise_percent, $date_start, $date_end, $price_base_type = 'HT', $pu_ttc = 0.0, $info_bits = 0, $fk_fournprice = null, $pa_ht = 0, $array_options = array(), $fk_unit = null, $rang = 0)
1471
    {
1472
        global $user, $langs, $conf, $mysoc;
1473
        $error = 0;
1474
1475
        dol_syslog(get_class($this) . "::addline $desc, $pu_ht, $qty, $txtva, $txlocaltax1, $txlocaltax2, $fk_product, $remise_percent, $date_start, $date_end, $price_base_type, $pu_ttc, $info_bits, $rang");
1476
1477
        // Check parameters
1478
        if ($fk_product <= 0 && empty($desc)) {
1479
            $this->error = "ErrorDescRequiredForFreeProductLines";
1480
            return -1;
1481
        }
1482
1483
        if ($this->statut >= 0) {
1484
            // Clean parameters
1485
            $pu_ht = price2num($pu_ht);
1486
            $pu_ttc = price2num($pu_ttc);
1487
            $pa_ht = price2num($pa_ht);
1488
1489
            // Clean vat code
1490
            $reg = array();
1491
            $vat_src_code = '';
1492
            if (preg_match('/\((.*)\)/', (string) $txtva, $reg)) {
1493
                $vat_src_code = $reg[1];
1494
                $txtva = preg_replace('/\s*\(.*\)/', '', (string) $txtva); // Remove code into vatrate.
1495
            }
1496
            $txtva = price2num($txtva);
1497
            $txlocaltax1 = price2num($txlocaltax1);
1498
            $txlocaltax2 = price2num($txlocaltax2);
1499
1500
            $remise_percent = price2num($remise_percent);
1501
            $qty = price2num($qty);
1502
            if (empty($qty)) {
1503
                $qty = 1;
1504
            }
1505
            if (empty($info_bits)) {
1506
                $info_bits = 0;
1507
            }
1508
            if (empty($pu_ht) || !is_numeric($pu_ht)) {
1509
                $pu_ht = 0;
1510
            }
1511
            if (empty($pu_ttc)) {
1512
                $pu_ttc = 0;
1513
            }
1514
            if (empty($txtva) || !is_numeric($txtva)) {
1515
                $txtva = 0;
1516
            }
1517
            if (empty($txlocaltax1) || !is_numeric($txlocaltax1)) {
1518
                $txlocaltax1 = 0;
1519
            }
1520
            if (empty($txlocaltax2) || !is_numeric($txlocaltax2)) {
1521
                $txlocaltax2 = 0;
1522
            }
1523
1524
            if ($price_base_type == 'HT') {
1525
                $pu = $pu_ht;
1526
            } else {
1527
                $pu = $pu_ttc;
1528
            }
1529
1530
            // Check parameters
1531
            if (empty($remise_percent)) {
1532
                $remise_percent = 0;
1533
            }
1534
            if (empty($rang)) {
1535
                $rang = 0;
1536
            }
1537
1538
            if ($date_start && $date_end && $date_start > $date_end) {
1539
                $langs->load("errors");
1540
                $this->error = $langs->trans('ErrorStartDateGreaterEnd');
1541
                return -1;
1542
            }
1543
1544
            $this->db->begin();
1545
1546
            $localtaxes_type = getLocalTaxesFromRate($txtva . ($vat_src_code ? ' (' . $vat_src_code . ')' : ''), 0, $this->societe, $mysoc);
1547
1548
            // Calcul du total TTC et de la TVA pour la ligne a partir de
1549
            // qty, pu, remise_percent et txtva
1550
            // TRES IMPORTANT: C'est au moment de l'insertion ligne qu'on doit stocker
1551
            // la part ht, tva et ttc, et ce au niveau de la ligne qui a son propre taux tva.
1552
1553
            $tabprice = calcul_price_total($qty, $pu, $remise_percent, $txtva, $txlocaltax1, $txlocaltax2, 0, $price_base_type, $info_bits, 1, $mysoc, $localtaxes_type);
1554
            $total_ht  = $tabprice[0];
1555
            $total_tva = $tabprice[1];
1556
            $total_ttc = $tabprice[2];
1557
            $total_localtax1 = $tabprice[9];
1558
            $total_localtax2 = $tabprice[10];
1559
1560
            $localtax1_type = $localtaxes_type[0];
1561
            $localtax2_type = $localtaxes_type[2];
1562
1563
            // TODO A virer
1564
            // Anciens indicateurs: $price, $remise (a ne plus utiliser)
1565
            $remise = 0;
1566
            $price = price2num(round($pu_ht, 2));
1567
            if (dol_strlen($remise_percent) > 0) {
1568
                $remise = round(($pu_ht * $remise_percent / 100), 2);
1569
                $price = $pu_ht - $remise;
1570
            }
1571
1572
            if (empty($pa_ht)) {
1573
                $pa_ht = 0;
1574
            }
1575
1576
1577
            // if buy price not defined, define buyprice as configured in margin admin
1578
            if ($pa_ht == 0) {
1579
                $result = $this->defineBuyPrice($pu_ht, $remise_percent, $fk_product);
1580
                if ($result < 0) {
1581
                    return -1;
1582
                } else {
1583
                    $pa_ht = $result;
1584
                }
1585
            }
1586
1587
            // Insertion dans la base
1588
            $sql = "INSERT INTO " . MAIN_DB_PREFIX . "contratdet";
1589
            $sql .= " (fk_contrat, label, description, fk_product, qty, tva_tx, vat_src_code,";
1590
            $sql .= " localtax1_tx, localtax2_tx, localtax1_type, localtax2_type, remise_percent, subprice,";
1591
            $sql .= " total_ht, total_tva, total_localtax1, total_localtax2, total_ttc,";
1592
            $sql .= " info_bits,";
1593
            $sql .= " price_ht, remise, fk_product_fournisseur_price, buy_price_ht";
1594
            if ($date_start > 0) {
1595
                $sql .= ",date_ouverture_prevue";
1596
            }
1597
            if ($date_end > 0) {
1598
                $sql .= ",date_fin_validite";
1599
            }
1600
            $sql .= ", fk_unit";
1601
            $sql .= ", rang";
1602
            $sql .= ") VALUES (";
1603
            $sql .= $this->id . ", '', '" . $this->db->escape($desc) . "',";
1604
            $sql .= ($fk_product > 0 ? $fk_product : "null") . ",";
1605
            $sql .= " " . ((float) $qty) . ",";
1606
            $sql .= " " . ((float) $txtva) . ",";
1607
            $sql .= " " . ($vat_src_code ? "'" . $this->db->escape($vat_src_code) . "'" : "null") . ",";
1608
            $sql .= " " . ((float) $txlocaltax1) . ",";
1609
            $sql .= " " . ((float) $txlocaltax2) . ",";
1610
            $sql .= " '" . $this->db->escape($localtax1_type) . "',";
1611
            $sql .= " '" . $this->db->escape($localtax2_type) . "',";
1612
            $sql .= " " . price2num($remise_percent) . ",";
1613
            $sql .= " " . price2num($pu_ht) . ",";
1614
            $sql .= " " . price2num($total_ht) . "," . price2num($total_tva) . "," . price2num($total_localtax1) . "," . price2num($total_localtax2) . "," . price2num($total_ttc) . ",";
1615
            $sql .= " " . ((int) $info_bits) . ",";
1616
            $sql .= " " . price2num($price) . "," . price2num($remise) . ",";
1617
            if (isset($fk_fournprice)) {
1618
                $sql .= ' ' . ((int) $fk_fournprice) . ',';
1619
            } else {
1620
                $sql .= ' null,';
1621
            }
1622
            if (isset($pa_ht)) {
1623
                $sql .= ' ' . price2num($pa_ht);
1624
            } else {
1625
                $sql .= ' null';
1626
            }
1627
            if ($date_start > 0) {
1628
                $sql .= ",'" . $this->db->idate($date_start) . "'";
1629
            }
1630
            if ($date_end > 0) {
1631
                $sql .= ",'" . $this->db->idate($date_end) . "'";
1632
            }
1633
            $sql .= ", " . ($fk_unit ? "'" . $this->db->escape($fk_unit) . "'" : "null");
1634
            $sql .= ", " . (!empty($rang) ? (int) $rang : "0");
1635
            $sql .= ")";
1636
1637
            $resql = $this->db->query($sql);
1638
            if ($resql) {
1639
                $contractlineid = $this->db->last_insert_id(MAIN_DB_PREFIX . "contratdet");
1640
1641
                if (!$error) {
1642
                    $contractline = new ContratLigne($this->db);
1643
                    $contractline->array_options = $array_options;
1644
                    $contractline->id = $contractlineid;
1645
                    $result = $contractline->insertExtraFields();
1646
                    if ($result < 0) {
1647
                        $this->errors[] = $contractline->error;
1648
                        $error++;
1649
                    }
1650
                }
1651
1652
                if (empty($error)) {
1653
                    // Call trigger
1654
                    $result = $this->call_trigger('LINECONTRACT_INSERT', $user);
1655
                    if ($result < 0) {
1656
                        $error++;
1657
                    }
1658
                    // End call triggers
1659
                }
1660
1661
                if ($error) {
1662
                    $this->db->rollback();
1663
                    return -1;
1664
                } else {
1665
                    $this->db->commit();
1666
                    return $contractlineid;
1667
                }
1668
            } else {
1669
                $this->db->rollback();
1670
                $this->error = $this->db->error() . " sql=" . $sql;
1671
                return -1;
1672
            }
1673
        } else {
1674
            dol_syslog(get_class($this) . "::addline ErrorTryToAddLineOnValidatedContract", LOG_ERR);
1675
            return -2;
1676
        }
1677
    }
1678
1679
    /**
1680
     *  Mets a jour une ligne de contrat
1681
     *
1682
     *  @param  int         $rowid              Id de la ligne de facture
1683
     *  @param  string      $desc               Description de la ligne
1684
     *  @param  float       $pu                 Prix unitaire
1685
     *  @param  float       $qty                Quantite
1686
     *  @param  float       $remise_percent     Percentage discount of the line
1687
     *  @param  int         $date_start         Date de debut prevue
1688
     *  @param  int         $date_end           Date de fin prevue
1689
     *  @param  float       $tvatx              Taux TVA
1690
     *  @param  float       $localtax1tx        Local tax 1 rate
1691
     *  @param  float       $localtax2tx        Local tax 2 rate
1692
     *  @param  int|string  $date_start_real    Date de debut reelle
1693
     *  @param  int|string  $date_end_real      Date de fin reelle
1694
     *  @param  string      $price_base_type    HT or TTC
1695
     *  @param  int         $info_bits          Bits of type of lines
1696
     *  @param  int         $fk_fournprice      Fourn price id
1697
     *  @param  int         $pa_ht              Buying price HT
1698
     *  @param  array       $array_options      extrafields array
1699
     *  @param  string      $fk_unit            Code of the unit to use. Null to use the default one
1700
     *  @param  int         $rang               Position
1701
     *  @return int                             Return integer <0 if KO, >0 if OK
1702
     */
1703
    public function updateline($rowid, $desc, $pu, $qty, $remise_percent, $date_start, $date_end, $tvatx, $localtax1tx = 0.0, $localtax2tx = 0.0, $date_start_real = '', $date_end_real = '', $price_base_type = 'HT', $info_bits = 0, $fk_fournprice = null, $pa_ht = 0, $array_options = array(), $fk_unit = null, $rang = 0)
1704
    {
1705
        global $user, $conf, $langs, $mysoc;
1706
1707
        $error = 0;
1708
1709
        // Clean parameters
1710
        $qty = trim((string) $qty);
1711
        $desc = trim($desc);
1712
        $desc = trim($desc);
1713
        $price = price2num($pu);
1714
        $tvatx = price2num($tvatx);
1715
        $localtax1tx = price2num($localtax1tx);
1716
        $localtax2tx = price2num($localtax2tx);
1717
        $pa_ht = price2num($pa_ht);
1718
        if (empty($fk_fournprice)) {
1719
            $fk_fournprice = 0;
1720
        }
1721
        if (empty($rang)) {
1722
            $rang = 0;
1723
        }
1724
1725
        $subprice = $price;
1726
        $remise = 0;
1727
        if (dol_strlen($remise_percent) > 0) {
1728
            $remise = round(($pu * $remise_percent / 100), 2);
1729
            $price = $pu - $remise;
1730
        } else {
1731
            $remise_percent = 0;
1732
        }
1733
1734
        if ($date_start && $date_end && $date_start > $date_end) {
1735
            $langs->load("errors");
1736
            $this->error = $langs->trans('ErrorStartDateGreaterEnd');
1737
            return -1;
1738
        }
1739
1740
        dol_syslog(get_class($this) . "::updateline $rowid, $desc, $pu, $qty, $remise_percent, $date_start, $date_end, $date_start_real, $date_end_real, $tvatx, $localtax1tx, $localtax2tx, $price_base_type, $info_bits, $rang");
1741
1742
        $this->db->begin();
1743
1744
        // Calcul du total TTC et de la TVA pour la ligne a partir de
1745
        // qty, pu, remise_percent et tvatx
1746
        // TRES IMPORTANT: C'est au moment de l'insertion ligne qu'on doit stocker
1747
        // la part ht, tva et ttc, et ce au niveau de la ligne qui a son propre taux tva.
1748
1749
        $localtaxes_type = getLocalTaxesFromRate($tvatx, 0, $this->societe, $mysoc);
1750
        $tvatx = preg_replace('/\s*\(.*\)/', '', $tvatx); // Remove code into vatrate.
1751
1752
        $tabprice = calcul_price_total($qty, $pu, $remise_percent, $tvatx, $localtax1tx, $localtax2tx, 0, $price_base_type, $info_bits, 1, $mysoc, $localtaxes_type);
1753
        $total_ht  = $tabprice[0];
1754
        $total_tva = $tabprice[1];
1755
        $total_ttc = $tabprice[2];
1756
        $total_localtax1 = $tabprice[9];
1757
        $total_localtax2 = $tabprice[10];
1758
1759
        $localtax1_type = (empty($localtaxes_type[0]) ? '' : $localtaxes_type[0]);
1760
        $localtax2_type = (empty($localtaxes_type[2]) ? '' : $localtaxes_type[2]);
1761
1762
        // TODO A virer
1763
        // Anciens indicateurs: $price, $remise (a ne plus utiliser)
1764
        $remise = 0;
1765
        $price = price2num(round($pu, 2));
1766
        if (dol_strlen($remise_percent) > 0) {
1767
            $remise = round(($pu * $remise_percent / 100), 2);
1768
            $price = $pu - $remise;
1769
        }
1770
1771
        if (empty($pa_ht)) {
1772
            $pa_ht = 0;
1773
        }
1774
1775
        // if buy price not defined, define buyprice as configured in margin admin
1776
        if ($pa_ht == 0) {
1777
            $result = $this->defineBuyPrice($pu, $remise_percent);
1778
            if ($result < 0) {
1779
                return -1;
1780
            } else {
1781
                $pa_ht = $result;
1782
            }
1783
        }
1784
1785
        $sql = "UPDATE " . MAIN_DB_PREFIX . "contratdet set description = '" . $this->db->escape($desc) . "'";
1786
        $sql .= ",price_ht = " . ((float) price2num($price));
1787
        $sql .= ",subprice = " . ((float) price2num($subprice));
1788
        $sql .= ",remise = " . ((float) price2num($remise));
1789
        $sql .= ",remise_percent = " . ((float) price2num($remise_percent));
1790
        $sql .= ",qty = " . ((float) $qty);
1791
        $sql .= ",tva_tx = " . ((float) price2num($tvatx));
1792
        $sql .= ",localtax1_tx = " . ((float) price2num($localtax1tx));
1793
        $sql .= ",localtax2_tx = " . ((float) price2num($localtax2tx));
1794
        $sql .= ",localtax1_type='" . $this->db->escape($localtax1_type) . "'";
1795
        $sql .= ",localtax2_type='" . $this->db->escape($localtax2_type) . "'";
1796
        $sql .= ", total_ht = " . ((float) price2num($total_ht));
1797
        $sql .= ", total_tva = " . ((float) price2num($total_tva));
1798
        $sql .= ", total_localtax1 = " . ((float) price2num($total_localtax1));
1799
        $sql .= ", total_localtax2 = " . ((float) price2num($total_localtax2));
1800
        $sql .= ", total_ttc = " . ((float) price2num($total_ttc));
1801
        $sql .= ", fk_product_fournisseur_price=" . ($fk_fournprice > 0 ? $fk_fournprice : "null");
1802
        $sql .= ", buy_price_ht = " . ((float) price2num($pa_ht));
1803
        if ($date_start > 0) {
1804
            $sql .= ",date_ouverture_prevue = '" . $this->db->idate($date_start) . "'";
1805
        } else {
1806
            $sql .= ",date_ouverture_prevue = null";
1807
        }
1808
        if ($date_end > 0) {
1809
            $sql .= ",date_fin_validite = '" . $this->db->idate($date_end) . "'";
1810
        } else {
1811
            $sql .= ",date_fin_validite = null";
1812
        }
1813
        if ($date_start_real > 0) {
1814
            $sql .= ",date_ouverture = '" . $this->db->idate($date_start_real) . "'";
1815
        } else {
1816
            $sql .= ",date_ouverture = null";
1817
        }
1818
        if ($date_end_real > 0) {
1819
            $sql .= ",date_cloture = '" . $this->db->idate($date_end_real) . "'";
1820
        } else {
1821
            $sql .= ",date_cloture = null";
1822
        }
1823
        $sql .= ", fk_unit = " . ($fk_unit > 0 ? ((int) $fk_unit) : "null");
1824
        $sql .= ", rang = " . (!empty($rang) ? ((int) $rang) : "0");
1825
        $sql .= " WHERE rowid = " . ((int) $rowid);
1826
1827
        dol_syslog(get_class($this) . "::updateline", LOG_DEBUG);
1828
        $result = $this->db->query($sql);
1829
        if ($result) {
1830
            if (is_array($array_options) && count($array_options) > 0) { // For avoid conflicts if trigger used
1831
                $contractline = new ContratLigne($this->db);
1832
                $contractline->fetch($rowid);
1833
1834
                // We replace values in $contractline->array_options only for entries defined into $array_options
1835
                foreach ($array_options as $key => $value) {
1836
                    $contractline->array_options[$key] = $array_options[$key];
1837
                }
1838
1839
                $result = $contractline->insertExtraFields();
1840
                if ($result < 0) {
1841
                    $this->errors[] = $contractline->error;
1842
                    $error++;
1843
                }
1844
            }
1845
1846
            if (empty($error)) {
1847
                // Call trigger
1848
                $result = $this->call_trigger('LINECONTRACT_MODIFY', $user);
1849
                if ($result < 0) {
1850
                    $this->db->rollback();
1851
                    return -3;
1852
                }
1853
                // End call triggers
1854
1855
                $this->db->commit();
1856
                return 1;
1857
            } else {
1858
                $this->db->rollback();
1859
                return -1;
1860
            }
1861
        } else {
1862
            $this->db->rollback();
1863
            $this->error = $this->db->error();
1864
            dol_syslog(get_class($this) . "::updateline Erreur -1");
1865
            return -1;
1866
        }
1867
    }
1868
1869
    /**
1870
     *  Delete a contract line
1871
     *
1872
     *  @param  int     $idline     Id of line to delete
1873
     *  @param  User    $user       User that delete
1874
     *  @return int                 >0 if OK, <0 if KO
1875
     */
1876
    public function deleteLine($idline, User $user)
1877
    {
1878
        $error = 0;
1879
1880
        if ($this->statut >= 0) {
1881
            // Call trigger
1882
            $result = $this->call_trigger('LINECONTRACT_DELETE', $user);
1883
            if ($result < 0) {
1884
                return -1;
1885
            }
1886
            // End call triggers
1887
1888
            $this->db->begin();
1889
1890
            $sql = "DELETE FROM " . MAIN_DB_PREFIX . $this->table_element_line;
1891
            $sql .= " WHERE rowid = " . ((int) $idline);
1892
1893
            dol_syslog(get_class($this) . "::deleteline", LOG_DEBUG);
1894
            $resql = $this->db->query($sql);
1895
            if (!$resql) {
1896
                $this->error = "Error " . $this->db->lasterror();
1897
                $error++;
1898
            }
1899
1900
            if (!$error) {
1901
                // Remove extrafields
1902
                $contractline = new ContratLigne($this->db);
1903
                $contractline->id = $idline;
1904
                $result = $contractline->deleteExtraFields();
1905
                if ($result < 0) {
1906
                    $error++;
1907
                    $this->error = "Error " . get_class($this) . "::deleteline deleteExtraFields error -4 " . $contractline->error;
1908
                }
1909
            }
1910
1911
            if (empty($error)) {
1912
                $this->db->commit();
1913
                return 1;
1914
            } else {
1915
                dol_syslog(get_class($this) . "::deleteline ERROR:" . $this->error, LOG_ERR);
1916
                $this->db->rollback();
1917
                return -1;
1918
            }
1919
        } else {
1920
            $this->error = 'ErrorDeleteLineNotAllowedByObjectStatus';
1921
            return -2;
1922
        }
1923
    }
1924
1925
1926
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1927
    /**
1928
     *  Update statut of contract according to services
1929
     *
1930
     *  @param  User    $user       Object user
1931
     *  @return int                 Return integer <0 if KO, >0 if OK
1932
     *  @deprecated                 This function will never be used. Status of a contract is status of its lines.
1933
     */
1934
    public function update_statut($user)
1935
    {
1936
		// phpcs:enable
1937
        dol_syslog(__METHOD__ . " is deprecated", LOG_WARNING);
1938
1939
        // If draft, we keep it (should not happen)
1940
        if ($this->statut == 0) {
1941
            return 1;
1942
        }
1943
1944
        // Load $this->lines array
1945
        //      $this->fetch_lines();
1946
1947
        //      $newstatut=1;
1948
        //      foreach($this->lines as $key => $contractline)
1949
        //      {
1950
        //          //          if ($contractline)         // Loop on each service
1951
        //      }
1952
1953
        return 1;
1954
    }
1955
1956
1957
    /**
1958
     *  Return label of a contract status
1959
     *
1960
     *  @param  int     $mode           0=long label, 1=short label, 2=Picto + short label, 3=Picto, 4=Picto + long label, 5=Short label + Picto, 6=Long label + Picto, 7=Same than 6 with fixed length
1961
     *  @return string                  Label
1962
     */
1963
    public function getLibStatut($mode)
1964
    {
1965
        return $this->LibStatut($this->statut, $mode);
1966
    }
1967
1968
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1969
    /**
1970
     *  Return the label of a given contrat status
1971
     *
1972
     *  @param  int     $status         Id status
1973
     *  @param  int     $mode           0=long label, 1=short label, 2=Picto + short label, 3=Picto, 4=Picto + long label (status of services), 5=Short label + Picto, 6=Long label + Picto (status of services), 7=Same than 6 with fixed length (status of services)
1974
     *  @return string                  Label
1975
     */
1976
    public function LibStatut($status, $mode)
1977
    {
1978
		// phpcs:enable
1979
        global $langs;
1980
1981
        if (empty($this->labelStatus) || empty($this->labelStatusShort)) {
1982
            global $langs;
1983
            $langs->load("contracts");
1984
            $this->labelStatus[self::STATUS_DRAFT] = $langs->transnoentitiesnoconv('ContractStatusDraft');
1985
            $this->labelStatus[self::STATUS_VALIDATED] = $langs->transnoentitiesnoconv('ContractStatusValidated');
1986
            $this->labelStatus[self::STATUS_CLOSED] = $langs->transnoentitiesnoconv('ContractStatusClosed');
1987
            $this->labelStatusShort[self::STATUS_DRAFT] = $langs->transnoentitiesnoconv('ContractStatusDraft');
1988
            $this->labelStatusShort[self::STATUS_VALIDATED] = $langs->transnoentitiesnoconv('ContractStatusValidated');
1989
            $this->labelStatusShort[self::STATUS_CLOSED] = $langs->transnoentitiesnoconv('ContractStatusClosed');
1990
        }
1991
1992
        $statusType = 'status' . $status;
1993
        if ($status == self::STATUS_VALIDATED) {
1994
            $statusType = 'status6';
1995
        }
1996
1997
        if ($mode == 4 || $mode == 6 || $mode == 7) {
1998
            $text = '';
1999
            if ($mode == 4) {
2000
                $text = '<span class="hideonsmartphone">';
2001
                $text .= ($this->nbofserviceswait + $this->nbofservicesopened + $this->nbofservicesexpired + $this->nbofservicesclosed);
2002
                $text .= ' ' . $langs->trans("Services");
2003
                $text .= ': &nbsp; &nbsp; ';
2004
                $text .= '</span>';
2005
            }
2006
            $text .= ($mode == 7 ? '<span class="nowraponall">' : '');
2007
            $text .= ($mode != 7 || $this->nbofserviceswait > 0) ? ($this->nbofserviceswait . ContratLigne::LibStatut(0, 3, -1, 'class="marginleft2"')) . (($mode != 7 || $this->nbofservicesopened || $this->nbofservicesexpired || $this->nbofservicesclosed) ? ' &nbsp; ' : '') : '';
2008
            $text .= ($mode == 7 ? '</span><span class="nowraponall">' : '');
2009
            $text .= ($mode != 7 || $this->nbofservicesopened > 0) ? ($this->nbofservicesopened . ContratLigne::LibStatut(4, 3, 0, 'class="marginleft2"')) . (($mode != 7 || $this->nbofservicesexpired || $this->nbofservicesclosed) ? ' &nbsp; ' : '') : '';
2010
            $text .= ($mode == 7 ? '</span><span class="nowraponall">' : '');
2011
            $text .= ($mode != 7 || $this->nbofservicesexpired > 0) ? ($this->nbofservicesexpired . ContratLigne::LibStatut(4, 3, 1, 'class="marginleft2"')) . (($mode != 7 || $this->nbofservicesclosed) ? ' &nbsp; ' : '') : '';
2012
            $text .= ($mode == 7 ? '</span><span class="nowraponall">' : '');
2013
            $text .= ($mode != 7 || $this->nbofservicesclosed > 0) ? ($this->nbofservicesclosed . ContratLigne::LibStatut(5, 3, -1, 'class="marginleft2"')) : '';
2014
            $text .= ($mode == 7 ? '</span>' : '');
2015
            return $text;
2016
        } else {
2017
            return dolGetStatus($this->labelStatus[$status], $this->labelStatusShort[$status], '', $statusType, $mode);
2018
        }
2019
    }
2020
2021
    /**
2022
     * getTooltipContentArray
2023
     * @param array $params params to construct tooltip data
2024
     * @since v18
2025
     * @return array
2026
     */
2027
    public function getTooltipContentArray($params)
2028
    {
2029
        global $conf, $langs, $user;
2030
2031
        $langs->load('contracts');
2032
2033
        $datas = [];
2034
        $nofetch = !empty($params['nofetch']);
2035
2036
        if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
2037
            return ['optimize' => $langs->trans("ShowContract")];
2038
        }
2039
        if ($user->hasRight('contrat', 'lire')) {
2040
            $datas['picto'] = img_picto('', $this->picto) . ' <u class="paddingrightonly">' . $langs->trans("Contract") . '</u>';
2041
            /* Status of a contract is status of all services, so disabled
2042
            if (isset($this->statut)) {
2043
                $label .= ' '.$this->getLibStatut(5);
2044
            }*/
2045
            $datas['ref'] = '<br><b>' . $langs->trans('Ref') . ':</b> ' . ($this->ref ? $this->ref : $this->id);
2046
            if (!$nofetch) {
2047
                $langs->load('companies');
2048
                if (empty($this->thirdparty)) {
2049
                    $this->fetch_thirdparty();
2050
                }
2051
                $datas['customer'] = '<br><b>' . $langs->trans('Customer') . ':</b> ' . $this->thirdparty->getNomUrl(1, '', 0, 1);
2052
            }
2053
            $datas['refcustomer'] = '<br><b>' . $langs->trans('RefCustomer') . ':</b> ' . $this->ref_customer;
2054
            if (!$nofetch) {
2055
                $langs->load('project');
2056
                if (is_null($this->project) || (is_object($this->project) && $this->project->isEmpty())) {
2057
                    $res = $this->fetch_project();
2058
                    if ($res > 0 && $this->project instanceof Project) {
0 ignored issues
show
Bug introduced by
The type Dolibarr\Code\Contrat\Classes\Project was not found. Did you mean Project? If so, make sure to prefix the type with \.
Loading history...
2059
                        $datas['project'] = '<br><b>' . $langs->trans('Project') . ':</b> ' . $this->project->getNomUrl(1, '', 0, 1);
2060
                    }
2061
                }
2062
            }
2063
            $datas['refsupplier'] = '<br><b>' . $langs->trans('RefSupplier') . ':</b> ' . $this->ref_supplier;
2064
            if (!empty($this->total_ht)) {
2065
                $datas['amountht'] = '<br><b>' . $langs->trans('AmountHT') . ':</b> ' . price($this->total_ht, 0, $langs, 0, -1, -1, $conf->currency);
2066
            }
2067
            if (!empty($this->total_tva)) {
2068
                $datas['vatamount'] = '<br><b>' . $langs->trans('VAT') . ':</b> ' . price($this->total_tva, 0, $langs, 0, -1, -1, $conf->currency);
2069
            }
2070
            if (!empty($this->total_ttc)) {
2071
                $datas['amounttc'] = '<br><b>' . $langs->trans('AmountTTC') . ':</b> ' . price($this->total_ttc, 0, $langs, 0, -1, -1, $conf->currency);
2072
            }
2073
        }
2074
        return $datas;
2075
    }
2076
2077
    /**
2078
     *  Return clicable name (with picto eventually)
2079
     *
2080
     *  @param  int     $withpicto                  0=No picto, 1=Include picto into link, 2=Only picto
2081
     *  @param  int     $maxlength                  Max length of ref
2082
     *  @param  int     $notooltip                  1=Disable tooltip
2083
     *  @param  int     $save_lastsearch_value      -1=Auto, 0=No save of lastsearch_values when clicking, 1=Save lastsearch_values whenclicking
2084
     *  @return string                              Chaine avec URL
2085
     */
2086
    public function getNomUrl($withpicto = 0, $maxlength = 0, $notooltip = 0, $save_lastsearch_value = -1)
2087
    {
2088
        global $conf, $langs, $user, $hookmanager;
2089
2090
        $result = '';
2091
2092
        $url = constant('BASE_URL') . '/contrat/card.php?id=' . $this->id;
2093
2094
        //if ($option !== 'nolink')
2095
        //{
2096
        // Add param to save lastsearch_values or not
2097
        $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
2098
        if ($save_lastsearch_value == -1 && isset($_SERVER["PHP_SELF"]) && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
2099
            $add_save_lastsearch_values = 1;
2100
        }
2101
        if ($add_save_lastsearch_values) {
2102
            $url .= '&save_lastsearch_values=1';
2103
        }
2104
        //}
2105
        $params = [
2106
            'id' => $this->id,
2107
            'objecttype' => $this->element,
2108
            'nofetch' => 1,
2109
        ];
2110
        $classfortooltip = 'classfortooltip';
2111
        $dataparams = '';
2112
        if (getDolGlobalInt('MAIN_ENABLE_AJAX_TOOLTIP')) {
2113
            $classfortooltip = 'classforajaxtooltip';
2114
            $dataparams = ' data-params="' . dol_escape_htmltag(json_encode($params)) . '"';
2115
            $label = '';
2116
        } else {
2117
            $label = implode($this->getTooltipContentArray($params));
2118
        }
2119
2120
        $linkclose = '';
2121
        if (empty($notooltip) && $user->hasRight('contrat', 'lire')) {
2122
            if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
2123
                $label = $langs->trans("ShowContract");
2124
                $linkclose .= ' alt="' . dol_escape_htmltag($label, 1) . '"';
2125
            }
2126
            $linkclose .= ($label ? ' title="' . dol_escape_htmltag($label, 1) . '"' : ' title="tocomplete"');
2127
            $linkclose .= $dataparams . ' class="' . $classfortooltip . '"';
2128
        }
2129
        $linkstart = '<a href="' . $url . '"';
2130
        $linkstart .= $linkclose . '>';
2131
        $linkend = '</a>';
2132
2133
        $result .= $linkstart;
2134
        if ($withpicto) {
2135
            $result .= img_object(($notooltip ? '' : $label), ($this->picto ? $this->picto : 'generic'), (($withpicto != 2) ? 'class="paddingright"' : ''), 0, 0, $notooltip ? 0 : 1);
2136
        }
2137
        if ($withpicto != 2) {
2138
            $result .= ($this->ref ? $this->ref : $this->id);
2139
        }
2140
        $result .= $linkend;
2141
2142
        global $action;
2143
        $hookmanager->initHooks(array('contractdao'));
2144
        $parameters = array('id' => $this->id, 'getnomurl' => &$result);
2145
        $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
2146
        if ($reshook > 0) {
2147
            $result = $hookmanager->resPrint;
2148
        } else {
2149
            $result .= $hookmanager->resPrint;
2150
        }
2151
2152
        return $result;
2153
    }
2154
2155
    /**
2156
     *  Charge les information d'ordre info dans l'objet contrat
2157
     *
2158
     *  @param  int     $id     id du contrat a charger
2159
     *  @return void
2160
     */
2161
    public function info($id)
2162
    {
2163
        $sql = "SELECT c.rowid, c.ref, c.datec,";
2164
        $sql .= " c.tms as date_modification,";
2165
        $sql .= " fk_user_author";
2166
        $sql .= " FROM " . MAIN_DB_PREFIX . "contrat as c";
2167
        $sql .= " WHERE c.rowid = " . ((int) $id);
2168
2169
        $result = $this->db->query($sql);
2170
        if ($result) {
2171
            if ($this->db->num_rows($result)) {
2172
                $obj = $this->db->fetch_object($result);
2173
2174
                $this->id = $obj->rowid;
2175
                $this->ref = (!$obj->ref) ? $obj->rowid : $obj->ref;
2176
2177
                $this->user_creation_id = $obj->fk_user_author;
2178
                $this->date_creation     = $this->db->jdate($obj->datec);
2179
                $this->date_modification = $this->db->jdate($obj->date_modification);
2180
            }
2181
2182
            $this->db->free($result);
2183
        } else {
2184
            dol_print_error($this->db);
2185
        }
2186
    }
2187
2188
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2189
    /**
2190
     *  Return list of line rowid
2191
     *
2192
     *  @param  int     $status     Status of lines to get
2193
     *  @return array|int           Array of line's rowid or <0 if error
2194
     */
2195
    public function array_detail($status = -1)
2196
    {
2197
		// phpcs:enable
2198
        $tab = array();
2199
2200
        $sql = "SELECT cd.rowid";
2201
        $sql .= " FROM " . MAIN_DB_PREFIX . "contratdet as cd";
2202
        $sql .= " WHERE fk_contrat =" . ((int) $this->id);
2203
        if ($status >= 0) {
2204
            $sql .= " AND statut = " . ((int) $status);
2205
        }
2206
2207
        dol_syslog(get_class($this) . "::array_detail()", LOG_DEBUG);
2208
        $resql = $this->db->query($sql);
2209
        if ($resql) {
2210
            $num = $this->db->num_rows($resql);
2211
            $i = 0;
2212
            while ($i < $num) {
2213
                $obj = $this->db->fetch_object($resql);
2214
                $tab[$i] = $obj->rowid;
2215
                $i++;
2216
            }
2217
            return $tab;
2218
        } else {
2219
            $this->error = $this->db->error();
2220
            return -1;
2221
        }
2222
    }
2223
2224
    /**
2225
     *  Return list of other contracts for the same company than current contract
2226
     *
2227
     *  @param  string      $option                 'all' or 'others'
2228
     *  @param  array       $status                 sort contracts having these status
2229
     *  @param  array       $product_categories     sort contracts containing these product categories
2230
     *  @param  array       $line_status            sort contracts where lines have these status
2231
     *  @return array|int                           Array of contracts id or <0 if error
2232
     */
2233
    public function getListOfContracts($option = 'all', $status = [], $product_categories = [], $line_status = [])
2234
    {
2235
        $tab = array();
2236
2237
        $sql = "SELECT c.rowid";
2238
        $sql .= " FROM " . MAIN_DB_PREFIX . "contrat as c";
2239
        if (!empty($product_categories)) {
2240
            $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "contratdet as cd ON cd.fk_contrat = c.rowid";
2241
            $sql .= " INNER JOIN " . MAIN_DB_PREFIX . "categorie_product as cp ON cp.fk_product = cd.fk_product AND cp.fk_categorie IN (" . $this->db->sanitize(implode(', ', $product_categories)) . ")";
2242
        }
2243
        $sql .= " WHERE c.fk_soc =" . ((int) $this->socid);
2244
        $sql .= ($option == 'others') ? " AND c.rowid <> " . ((int) $this->id) : "";
2245
        $sql .= (!empty($status)) ? " AND c.statut IN (" . $this->db->sanitize(implode(', ', $status)) . ")" : "";
2246
        $sql .= (!empty($line_status)) ? " AND cd.statut IN (" . $this->db->sanitize(implode(', ', $line_status)) . ")" : "";
2247
        $sql .= " GROUP BY c.rowid";
2248
2249
        dol_syslog(get_class($this) . "::getOtherContracts()", LOG_DEBUG);
2250
        $resql = $this->db->query($sql);
2251
        if ($resql) {
2252
            $num = $this->db->num_rows($resql);
2253
            $i = 0;
2254
            while ($i < $num) {
2255
                $obj = $this->db->fetch_object($resql);
2256
                $contrat = new Contrat($this->db);
2257
                $contrat->fetch($obj->rowid);
2258
                $tab[$contrat->id] = $contrat;
2259
                $i++;
2260
            }
2261
            return $tab;
2262
        } else {
2263
            $this->error = $this->db->lasterror();
2264
            return -1;
2265
        }
2266
    }
2267
2268
2269
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2270
    /**
2271
     *      Load indicators for dashboard (this->nbtodo and this->nbtodolate)
2272
     *
2273
     *      @param  User    $user           Object user
2274
     *      @param  string  $mode           "inactive" pour services a activer, "expired" pour services expires
2275
     *      @return WorkboardResponse|int Return integer <0 if KO, WorkboardResponse if OK
2276
     */
2277
    public function load_board($user, $mode)
2278
    {
2279
		// phpcs:enable
2280
        global $conf, $langs;
2281
2282
        $this->from = " FROM " . MAIN_DB_PREFIX . "contrat as c";
2283
        $this->from .= ", " . MAIN_DB_PREFIX . "contratdet as cd";
2284
        $this->from .= ", " . MAIN_DB_PREFIX . "societe as s";
2285
        if (!$user->hasRight('societe', 'client', 'voir')) {
2286
            $this->from .= ", " . MAIN_DB_PREFIX . "societe_commerciaux as sc";
2287
        }
2288
2289
        if ($mode == 'inactive') {
2290
            $sql = "SELECT cd.rowid, cd.date_ouverture_prevue as datefin";
2291
            $sql .= $this->from;
2292
            $sql .= " WHERE c.statut = 1";
2293
            $sql .= " AND c.rowid = cd.fk_contrat";
2294
            $sql .= " AND cd.statut = 0";
2295
        } elseif ($mode == 'expired') {
2296
            $sql = "SELECT cd.rowid, cd.date_fin_validite as datefin";
2297
            $sql .= $this->from;
2298
            $sql .= " WHERE c.statut = 1";
2299
            $sql .= " AND c.rowid = cd.fk_contrat";
2300
            $sql .= " AND cd.statut = 4";
2301
            $sql .= " AND cd.date_fin_validite < '" . $this->db->idate(dol_now()) . "'";
2302
        } elseif ($mode == 'active') {
2303
            $sql = "SELECT cd.rowid, cd.date_fin_validite as datefin";
2304
            $sql .= $this->from;
2305
            $sql .= " WHERE c.statut = 1";
2306
            $sql .= " AND c.rowid = cd.fk_contrat";
2307
            $sql .= " AND cd.statut = 4";
2308
            //$datetouse = dol_now();
2309
            //$sql.= " AND cd.date_fin_validite < '".$this->db->idate($datetouse)."'";
2310
        }
2311
        $sql .= " AND c.fk_soc = s.rowid";
2312
        $sql .= " AND c.entity = " . ((int) $conf->entity);
2313
        if ($user->socid) {
2314
            $sql .= " AND c.fk_soc = " . ((int) $user->socid);
2315
        }
2316
        if (!$user->hasRight('societe', 'client', 'voir')) {
2317
            $sql .= " AND c.fk_soc = sc.fk_soc AND sc.fk_user = " . ((int) $user->id);
2318
        }
2319
2320
        $resql = $this->db->query($sql);
2321
        if ($resql) {
2322
            $langs->load("contracts");
2323
            $now = dol_now();
2324
2325
            if ($mode == 'inactive') {
2326
                $warning_delay = $conf->contrat->services->inactifs->warning_delay;
2327
                $label = $langs->trans("BoardNotActivatedServices");
2328
                $labelShort = $langs->trans("BoardNotActivatedServicesShort");
2329
                $url = constant('BASE_URL') . '/contrat/services_list.php?mainmenu=commercial&leftmenu=contracts&search_status=0&sortfield=cd.date_fin_validite&sortorder=asc';
2330
            } elseif ($mode == 'expired') {
2331
                $warning_delay = $conf->contrat->services->expires->warning_delay;
2332
                $url = constant('BASE_URL') . '/contrat/services_list.php?mainmenu=commercial&leftmenu=contracts&search_status=4&filter=expired&sortfield=cd.date_fin_validite&sortorder=asc';
2333
                $label = $langs->trans("BoardExpiredServices");
2334
                $labelShort = $langs->trans("BoardExpiredServicesShort");
2335
            } else {
2336
                $warning_delay = $conf->contrat->services->expires->warning_delay;
2337
                $url = constant('BASE_URL') . '/contrat/services_list.php?mainmenu=commercial&leftmenu=contracts&search_status=4&sortfield=cd.date_fin_validite&sortorder=asc';
2338
                //$url.= '&op2day='.$arraydatetouse['mday'].'&op2month='.$arraydatetouse['mon'].'&op2year='.$arraydatetouse['year'];
2339
                //if ($warning_delay >= 0) $url.='&amp;filter=expired';
2340
                $label = $langs->trans("BoardRunningServices");
2341
                $labelShort = $langs->trans("BoardRunningServicesShort");
2342
            }
2343
2344
            $response = new WorkboardResponse();
2345
            $response->warning_delay = $warning_delay / 60 / 60 / 24;
2346
            $response->label = $label;
2347
            $response->labelShort = $labelShort;
2348
            $response->url = $url;
2349
            $response->img = img_object('', "contract");
2350
2351
            while ($obj = $this->db->fetch_object($resql)) {
2352
                $response->nbtodo++;
2353
2354
                if ($obj->datefin && $this->db->jdate($obj->datefin) < ($now - $warning_delay)) {
2355
                    $response->nbtodolate++;
2356
                }
2357
            }
2358
2359
            return $response;
2360
        } else {
2361
            dol_print_error($this->db);
2362
            $this->error = $this->db->error();
2363
            return -1;
2364
        }
2365
    }
2366
2367
    /**
2368
     *   Load the indicators this->nb for state board
2369
     *
2370
     *   @return     int         Return integer <0 si ko, >0 si ok
2371
     */
2372
    public function loadStateBoard()
2373
    {
2374
        global $conf, $user;
2375
2376
        $this->nb = array();
2377
        $clause = "WHERE";
2378
2379
        $sql = "SELECT count(c.rowid) as nb";
2380
        $sql .= " FROM " . MAIN_DB_PREFIX . "contrat as c";
2381
        $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "societe as s ON c.fk_soc = s.rowid";
2382
        if (!$user->hasRight('societe', 'client', 'voir')) {
2383
            $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "societe_commerciaux as sc ON s.rowid = sc.fk_soc";
2384
            $sql .= " WHERE sc.fk_user = " . ((int) $user->id);
2385
            $clause = "AND";
2386
        }
2387
        $sql .= " " . $clause . " c.entity = " . $conf->entity;
2388
2389
        $resql = $this->db->query($sql);
2390
        if ($resql) {
2391
            while ($obj = $this->db->fetch_object($resql)) {
2392
                $this->nb["contracts"] = $obj->nb;
2393
            }
2394
            $this->db->free($resql);
2395
            return 1;
2396
        } else {
2397
            dol_print_error($this->db);
2398
            $this->error = $this->db->error();
2399
            return -1;
2400
        }
2401
    }
2402
2403
2404
    /* gestion des contacts d'un contrat */
2405
2406
    /**
2407
     *  Return id des contacts clients de facturation
2408
     *
2409
     *  @return     array       Liste des id contacts facturation
2410
     */
2411
    public function getIdBillingContact()
2412
    {
2413
        return $this->getIdContact('external', 'BILLING');
2414
    }
2415
2416
    /**
2417
     *  Return id des contacts clients de prestation
2418
     *
2419
     *  @return     array       Liste des id contacts prestation
2420
     */
2421
    public function getIdServiceContact()
2422
    {
2423
        return $this->getIdContact('external', 'SERVICE');
2424
    }
2425
2426
2427
    /**
2428
     *  Initialise an instance with random values.
2429
     *  Used to build previews or test instances.
2430
     *  id must be 0 if object instance is a specimen.
2431
     *
2432
     *  @return int
2433
     */
2434
    public function initAsSpecimen()
2435
    {
2436
        global $user, $langs, $conf;
2437
2438
        // Load array of products prodids
2439
        $num_prods = 0;
2440
        $prodids = array();
2441
        $sql = "SELECT rowid";
2442
        $sql .= " FROM " . MAIN_DB_PREFIX . "product";
2443
        $sql .= " WHERE entity IN (" . getEntity('product') . ")";
2444
        $sql .= " AND tosell = 1";
2445
        $sql .= $this->db->plimit(100);
2446
2447
        $resql = $this->db->query($sql);
2448
        if ($resql) {
2449
            $num_prods = $this->db->num_rows($resql);
2450
            $i = 0;
2451
            while ($i < $num_prods) {
2452
                $i++;
2453
                $row = $this->db->fetch_row($resql);
2454
                $prodids[$i] = $row[0];
2455
            }
2456
        }
2457
2458
        // Initialise parameters
2459
        $this->id = 0;
2460
        $this->specimen = 1;
2461
2462
        $this->ref = 'SPECIMEN';
2463
        $this->ref_customer = 'SPECIMENCUST';
2464
        $this->ref_supplier = 'SPECIMENSUPP';
2465
        $this->socid = 1;
2466
        $this->status = 0;
2467
        $this->date_creation = (dol_now() - 3600 * 24 * 7);
2468
        $this->date_contrat = dol_now();
2469
        $this->commercial_signature_id = 1;
2470
        $this->commercial_suivi_id = 1;
2471
        $this->note_private = 'This is a comment (private)';
2472
        $this->note_public = 'This is a comment (public)';
2473
        $this->fk_project = 0;
2474
        // Lines
2475
        $nbp = 5;
2476
        $xnbp = 0;
2477
        while ($xnbp < $nbp) {
2478
            $line = new ContratLigne($this->db);
2479
            $line->qty = 1;
2480
            $line->subprice = 100;
2481
            $line->tva_tx = 19.6;
2482
            $line->remise_percent = 10;
2483
            $line->total_ht = 90;
2484
            $line->total_ttc = 107.64; // 90 * 1.196
2485
            $line->total_tva = 17.64;
2486
            $line->date_start = dol_now() - 500000;
2487
            $line->date_start_real = dol_now() - 200000;
2488
            $line->date_end = dol_now() + 500000;
2489
            $line->date_end_real = dol_now() - 100000;
2490
            if ($num_prods > 0) {
2491
                $prodid = mt_rand(1, $num_prods);
2492
                $line->fk_product = $prodids[$prodid];
2493
            }
2494
            $this->lines[$xnbp] = $line;
2495
            $xnbp++;
2496
        }
2497
2498
        return 1;
2499
    }
2500
2501
    /**
2502
     *  Create an array of order lines
2503
     *
2504
     *  @return int     >0 if OK, <0 if KO
2505
     */
2506
    public function getLinesArray()
2507
    {
2508
        return $this->fetch_lines();
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->fetch_lines() also could return the type array which is incompatible with the documented return type integer.
Loading history...
2509
    }
2510
2511
    /**
2512
     *  Create an array of associated tickets
2513
     *
2514
     *  @return array|int       Array o tickets or <0 if KO
2515
     */
2516
    public function getTicketsArray()
2517
    {
2518
        global $user;
2519
2520
        $ticket = new Ticket($this->db);
2521
        $nbTicket =  $ticket->fetchAll($user, 'ASC', 't.datec', '', 0, '', array('t.fk_contract' => $this->id));
2522
2523
        return ($nbTicket < 0 ? $nbTicket : $ticket->lines);
2524
    }
2525
2526
2527
    /**
2528
     *  Create a document onto disk according to template module.
2529
     *
2530
     *  @param      string      $modele         Force model to use ('' to not force)
2531
     *  @param      Translate   $outputlangs    Object langs to use for output
2532
     *  @param      int         $hidedetails    Hide details of lines
2533
     *  @param      int         $hidedesc       Hide description
2534
     *  @param      int         $hideref        Hide ref
2535
     *  @param      null|array  $moreparams     Array to provide more information
2536
     *  @return     int                         Return integer < 0 if KO, 0 = no doc generated, > 0 if OK
2537
     */
2538
    public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0, $moreparams = null)
2539
    {
2540
        global $conf, $langs;
2541
2542
        if (!dol_strlen($modele)) {
2543
            $modele = '';   // No doc template/generation by default
2544
2545
            if (!empty($this->model_pdf)) {
2546
                $modele = $this->model_pdf;
2547
            } elseif (getDolGlobalString('CONTRACT_ADDON_PDF')) {
2548
                $modele = getDolGlobalString('CONTRACT_ADDON_PDF');
2549
            }
2550
        }
2551
2552
        if (empty($modele)) {
2553
            return 0;
2554
        } else {
2555
            $langs->load("contracts");
2556
            $outputlangs->load("products");
2557
2558
            $modelpath = "core/modules/contract/doc/";
2559
            return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams);
2560
        }
2561
    }
2562
2563
    /**
2564
     * Function used to replace a thirdparty id with another one.
2565
     *
2566
     * @param   DoliDB  $dbs        Database handler, because function is static we name it $dbs not $db to avoid breaking coding test
2567
     * @param   int     $origin_id  Old thirdparty id
2568
     * @param   int     $dest_id    New thirdparty id
2569
     * @return  bool
2570
     */
2571
    public static function replaceThirdparty(DoliDB $dbs, $origin_id, $dest_id)
2572
    {
2573
        $tables = array(
2574
            'contrat'
2575
        );
2576
2577
        return CommonObject::commonReplaceThirdparty($dbs, $origin_id, $dest_id, $tables);
2578
    }
2579
2580
    /**
2581
     * Function used to replace a product id with another one.
2582
     *
2583
     * @param DoliDB $db Database handler
2584
     * @param int $origin_id Old product id
2585
     * @param int $dest_id New product id
2586
     * @return bool
2587
     */
2588
    public static function replaceProduct(DoliDB $db, $origin_id, $dest_id)
2589
    {
2590
        $tables = array(
2591
            'contratdet'
2592
        );
2593
2594
        return CommonObject::commonReplaceProduct($db, $origin_id, $dest_id, $tables);
2595
    }
2596
2597
    /**
2598
     * Load an object from its id and create a new one in database
2599
     *
2600
     * @param   User    $user         User making the clone
2601
     * @param   int     $socid        Id of thirdparty
2602
     * @param   int     $notrigger    1=Does not execute triggers, 0= execute triggers
2603
     * @return  int                   New id of clone
2604
     */
2605
    public function createFromClone(User $user, $socid = 0, $notrigger = 0)
2606
    {
2607
        global $db, $langs, $conf, $hookmanager, $extrafields;
2608
2609
        dol_include_once('/projet/class/project.class.php');
2610
2611
        $error = 0;
2612
2613
        $this->fetch($this->id);
2614
2615
        // Load dest object
2616
        $clonedObj = clone $this;
2617
        $clonedObj->socid = $socid;
2618
2619
        $this->db->begin();
2620
2621
        $objsoc = new Societe($this->db);
2622
2623
        $objsoc->fetch($clonedObj->socid);
2624
2625
        // Clean data
2626
        $clonedObj->statut = 0;
2627
        // Clean extrafields
2628
        if (is_array($clonedObj->array_options) && count($clonedObj->array_options) > 0) {
2629
            $extrafields->fetch_name_optionals_label($this->table_element);
2630
            foreach ($clonedObj->array_options as $key => $option) {
2631
                $shortkey = preg_replace('/options_/', '', $key);
2632
                //var_dump($shortkey); var_dump($extrafields->attributes[$this->element]['unique'][$shortkey]);
2633
                if (!empty($extrafields->attributes[$this->element]['unique'][$shortkey])) {
2634
                    //var_dump($key); var_dump($clonedObj->array_options[$key]); exit;
2635
                    unset($clonedObj->array_options[$key]);
2636
                }
2637
            }
2638
        }
2639
2640
        if (!getDolGlobalString('CONTRACT_ADDON') || !is_readable(DOL_DOCUMENT_ROOT . "/core/modules/contract/" . getDolGlobalString('CONTRACT_ADDON') . ".php")) {
2641
            $this->error = 'ErrorSetupNotComplete';
2642
            dol_syslog($this->error);
2643
            return -1;
2644
        }
2645
2646
        // Set ref
2647
        require_once DOL_DOCUMENT_ROOT . "/core/modules/contract/" . getDolGlobalString('CONTRACT_ADDON') . '.php';
2648
        $obj = getDolGlobalString('CONTRACT_ADDON');
2649
        $modContract = new $obj();
2650
        '@phan-var-force CommonNumRefGenerator $modContrat';
2651
        $clonedObj->ref = $modContract->getNextValue($objsoc, $clonedObj);
2652
2653
        // get extrafields so they will be clone
2654
        foreach ($this->lines as $line) {
2655
            $line->fetch_optionals($line->id);
2656
        }
2657
2658
        // Create clone
2659
        $clonedObj->context['createfromclone'] = 'createfromclone';
2660
        $result = $clonedObj->create($user);
2661
        if ($result < 0) {
2662
            $error++;
2663
            $this->error = $clonedObj->error;
2664
            $this->errors[] = $clonedObj->error;
2665
        } else {
2666
            // copy external contacts if same company
2667
            if ($this->socid == $clonedObj->socid) {
2668
                if ($clonedObj->copy_linked_contact($this, 'external') < 0) {
2669
                    $error++;
2670
                }
2671
            }
2672
        }
2673
2674
        if (!$error) {
2675
            foreach ($this->lines as $line) {
2676
                $result = $clonedObj->addline($line->description, $line->subprice, $line->qty, $line->tva_tx, $line->localtax1_tx, $line->localtax2_tx, $line->fk_product, $line->remise_percent, $line->date_start, $line->date_cloture, 'HT', 0, $line->info_bits, $line->fk_fournprice, $line->pa_ht, $line->array_options, $line->fk_unit, $line->rang);
2677
                if ($result < 0) {
2678
                    $error++;
2679
                    $this->error = $clonedObj->error;
2680
                    $this->errors[] = $clonedObj->error;
2681
                }
2682
            }
2683
        }
2684
2685
        if (!$error) {
2686
            // Hook of thirdparty module
2687
            if (is_object($hookmanager)) {
2688
                $parameters = array(
2689
                        'objFrom' => $this,
2690
                        'clonedObj' => $clonedObj
2691
                );
2692
                $action = '';
2693
                $reshook = $hookmanager->executeHooks('createFrom', $parameters, $clonedObj, $action); // Note that $action and $object may have been modified by some hooks
2694
                if ($reshook < 0) {
2695
                    $this->setErrorsFromObject($hookmanager);
2696
                    $error++;
2697
                }
2698
            }
2699
        }
2700
2701
        unset($clonedObj->context['createfromclone']);
2702
2703
        // End
2704
        if (!$error) {
2705
            $this->db->commit();
2706
            return $clonedObj->id;
2707
        } else {
2708
            $this->db->rollback();
2709
            return -1;
2710
        }
2711
    }
2712
2713
2714
    /**
2715
     * Action executed by scheduler
2716
     * CAN BE A CRON TASK
2717
     * Loop on each contract lines and update the end of date. Do not execute the update if there is one pending invoice linked to contract.
2718
     *
2719
     * @param   int     $thirdparty_id          Thirdparty id
2720
     * @param   int     $delayindaysshort       To renew the resources x day before (positive value) or after (negative value) the end of date (default is 0)
2721
     * @return  int                             0 if OK, <>0 if KO (this function is used also by cron so only 0 is OK)
2722
     */
2723
    public function doAutoRenewContracts($thirdparty_id = 0, $delayindaysshort = 0)
2724
    {
2725
        global $langs, $user;
2726
2727
        $langs->load("agenda");
2728
2729
        $now = dol_now();
2730
2731
        $enddatetoscan = dol_time_plus_duree($now, -1 * abs($delayindaysshort), 'd');
2732
2733
        $error = 0;
2734
        $this->output = '';
2735
        $this->error = '';
2736
2737
        $contractlineprocessed = array();
2738
        $contractignored = array();
2739
        $contracterror = array();
2740
2741
        dol_syslog(__METHOD__, LOG_DEBUG);
2742
2743
        $sql = 'SELECT c.rowid, c.ref_customer, cd.rowid as lid, cd.date_fin_validite, p.duration';
2744
        $sql .= ' FROM ' . MAIN_DB_PREFIX . 'contrat as c, ' . MAIN_DB_PREFIX . 'contratdet as cd';
2745
        $sql .= ' LEFT JOIN ' . MAIN_DB_PREFIX . 'product as p ON p.rowid = cd.fk_product';
2746
        $sql .= ' WHERE cd.fk_contrat = c.rowid';
2747
        $sql .= " AND date_format(cd.date_fin_validite, '%Y-%m-%d') <= date_format('" . $this->db->idate($enddatetoscan) . "', '%Y-%m-%d')";
2748
        $sql .= " AND cd.statut = 4";
2749
        if ($thirdparty_id > 0) {
2750
            $sql .= " AND c.fk_soc = " . ((int) $thirdparty_id);
2751
        }
2752
        //print $sql;
2753
2754
        $resql = $this->db->query($sql);
2755
        if ($resql) {
2756
            $num = $this->db->num_rows($resql);
2757
2758
            include_once DOL_DOCUMENT_ROOT . '/core/class/html.formmail.class.php';
2759
2760
            $i = 0;
2761
            while ($i < $num) {
2762
                $obj = $this->db->fetch_object($resql);
2763
                if ($obj) {
2764
                    if (!empty($contractlineprocessed[$obj->lid]) || !empty($contractignored[$obj->rowid]) || !empty($contracterror[$obj->rowid])) {
2765
                        continue;
2766
                    }
2767
2768
                    // Load contract
2769
                    $object = new Contrat($this->db);
2770
                    $object->fetch($obj->rowid);        // fetch also lines
2771
                    //$object->fetch_thirdparty();
2772
2773
                    if ($object->id <= 0) {
2774
                        $error++;
2775
                        $this->errors[] = 'Failed to load contract with id=' . $obj->rowid;
2776
                        continue;
2777
                    }
2778
2779
                    dol_syslog("* Process contract line in doRenewalContracts for contract id=" . $object->id . " ref=" . $object->ref . " ref_customer=" . $object->ref_customer . " contract line id=" . $obj->lid);
2780
2781
                    // Update expiration date of line
2782
                    $expirationdate = $this->db->jdate($obj->date_fin_validite);
2783
                    $duration_value = preg_replace('/[^0-9]/', '', $obj->duration);
2784
                    $duration_unit = preg_replace('/\d/', '', $obj->duration);
2785
                    //var_dump($expirationdate.' '.$enddatetoscan);
2786
2787
                    // Load linked ->linkedObjects (objects linked)
2788
                    // @TODO Comment this line and then make the search if there is n open invoice(s) by doing a dedicated SQL COUNT request to fill $contractcanceled.
2789
                    $object->fetchObjectLinked(null, '', null, '', 'OR', 1, 'sourcetype', 1);
2790
2791
                    // Test if there is at least 1 open invoice
2792
                    if (isset($object->linkedObjects['facture']) && is_array($object->linkedObjects['facture']) && count($object->linkedObjects['facture']) > 0) {
2793
                        // Sort array of linked invoices by ascending date
2794
                        usort($object->linkedObjects['facture'], array('Contrat', 'contractCmpDate'));
2795
                        //dol_sort_array($object->linkedObjects['facture'], 'date');
2796
2797
                        $someinvoicenotpaid = 0;
2798
                        foreach ($object->linkedObjects['facture'] as $idinvoice => $invoice) {
2799
                            if ($invoice->statut == Facture::STATUS_DRAFT) {
0 ignored issues
show
Bug introduced by
The type Dolibarr\Code\Contrat\Classes\Facture was not found. Did you mean Facture? If so, make sure to prefix the type with \.
Loading history...
2800
                                continue;
2801
                            }   // Draft invoice are not invoice not paid
2802
2803
                            if (empty($invoice->paye)) {
2804
                                $someinvoicenotpaid++;
2805
                            }
2806
                        }
2807
                        if ($someinvoicenotpaid) {
2808
                            $this->output .= 'Contract ' . $object->ref . ' is qualified for renewal but there is ' . $someinvoicenotpaid . ' invoice(s) unpayed so we cancel renewal' . "\n";
2809
                            $contractignored[$object->id] = $object->ref;
2810
                            continue;
2811
                        }
2812
                    }
2813
2814
                    if ($expirationdate && $expirationdate < $enddatetoscan) {
2815
                        dol_syslog("Define the newdate of end of services from expirationdate=" . $expirationdate);
2816
                        $newdate = $expirationdate;
2817
                        $protecti = 0;  //$protecti is to avoid infinite loop
2818
                        while ($newdate < $enddatetoscan && $protecti < 1000) {
2819
                            $newdate = dol_time_plus_duree($newdate, $duration_value, $duration_unit);
2820
                            $protecti++;
2821
                        }
2822
2823
                        if ($protecti < 1000) { // If not, there is a pb
2824
                            // We will update the end of date of contrat, so first we refresh contract data
2825
                            dol_syslog("We will update the end of date of contract with newdate = " . dol_print_date($newdate, 'dayhourrfc'));
2826
2827
                            $this->db->begin();
2828
2829
                            $errorforlocaltransaction = 0;
2830
2831
                            $label = 'Renewal of contrat ' . $object->ref . ' line ' . $obj->lid;
2832
                            $comment = 'Renew date of contract ' . $object->ref . ' line ' . $obj->lid . ' by doAutoRenewContracts';
2833
2834
                            $sqlupdate = 'UPDATE ' . MAIN_DB_PREFIX . "contratdet SET date_fin_validite = '" . $this->db->idate($newdate) . "'";
2835
                            $sqlupdate .= ' WHERE rowid = ' . ((int) $obj->lid);
2836
                            $resqlupdate = $this->db->query($sqlupdate);
2837
                            if ($resqlupdate) {
2838
                                $contractlineprocessed[$obj->lid] = $object->ref;
2839
2840
                                $actioncode = 'RENEW_CONTRACT';
2841
                                $now = dol_now();
2842
2843
                                // Create an event
2844
                                $actioncomm = new ActionComm($this->db);
2845
                                $actioncomm->type_code    = 'AC_OTH_AUTO';      // Type of event ('AC_OTH', 'AC_OTH_AUTO', 'AC_XXX'...)
2846
                                $actioncomm->code         = 'AC_' . $actioncode;
2847
                                $actioncomm->label        = $label;
2848
                                $actioncomm->datep        = $now;
2849
                                $actioncomm->datef        = $now;
2850
                                $actioncomm->percentage   = -1;   // Not applicable
2851
                                $actioncomm->socid        = $object->socid;
2852
                                $actioncomm->authorid     = $user->id;   // User saving action
2853
                                $actioncomm->userownerid  = $user->id;  // Owner of action
2854
                                $actioncomm->fk_element   = $object->id;
2855
                                $actioncomm->elementtype  = 'contract';
2856
                                $actioncomm->note_private = $comment;
2857
2858
                                $ret = $actioncomm->create($user);       // User creating action
2859
                            } else {
2860
                                $contracterror[$object->id] = $object->ref;
2861
2862
                                $error++;
2863
                                $errorforlocaltransaction++;
2864
                                $this->error = $this->db->lasterror();
2865
                            }
2866
2867
                            if (! $errorforlocaltransaction) {
2868
                                $this->db->commit();
2869
                            } else {
2870
                                $this->db->rollback();
2871
                            }
2872
                        } else {
2873
                            $error++;
2874
                            $this->error = "Bad value for newdate in doAutoRenewContracts - expirationdate=" . $expirationdate . " enddatetoscan=" . $enddatetoscan . " duration_value=" . $duration_value . " duration_unit=" . $duration_value;
2875
                            dol_syslog($this->error, LOG_ERR);
2876
                        }
2877
                    }
2878
                }
2879
                $i++;
2880
            }
2881
        } else {
2882
            $error++;
2883
            $this->error = $this->db->lasterror();
2884
        }
2885
2886
        $this->output .= count($contractlineprocessed) . ' contract line(s) with end date before ' . dol_print_date($enddatetoscan, 'day') . ' were renewed' . (count($contractlineprocessed) > 0 ? ' : ' . implode(',', $contractlineprocessed) : '');
2887
2888
        return ($error ? 1 : 0);
2889
    }
2890
2891
    /**
2892
     * Used to sort lines by date
2893
     *
2894
     * @param   Object  $a      1st element to test
2895
     * @param   Object  $b      2nd element to test
2896
     * @return int
2897
     */
2898
    public static function contractCmpDate($a, $b)
2899
    {
2900
        if ($a->date == $b->date) {
2901
            return strcmp((string) $a->id, (string) $b->id);
2902
        }
2903
        return ($a->date < $b->date) ? -1 : 1;
2904
    }
2905
2906
    /**
2907
     *  Return clicable link of object (with eventually picto)
2908
     *
2909
     *  @param      string      $option                 Where point the link (0=> main card, 1,2 => shipment, 'nolink'=>No link)
2910
     *  @param      array       $arraydata              Array of data
2911
     *  @return     string                              HTML Code for Kanban thumb.
2912
     */
2913
    public function getKanbanView($option = '', $arraydata = null)
2914
    {
2915
        global $langs;
2916
2917
        $selected = (empty($arraydata['selected']) ? 0 : $arraydata['selected']);
2918
2919
        $return = '<div class="box-flex-item box-flex-grow-zero">';
2920
        $return .= '<div class="info-box info-box-sm">';
2921
        $return .= '<span class="info-box-icon bg-infobox-action">';
2922
        $return .= img_picto('', $this->picto);
2923
        $return .= '</span>';
2924
        $return .= '<div class="info-box-content">';
2925
        $return .= '<span class="info-box-ref inline-block tdoverflowmax150 valignmiddle">' . (method_exists($this, 'getNomUrl') ? $this->getNomUrl() : $this->ref) . '</span>';
2926
        if ($selected >= 0) {
2927
            $return .= '<input id="cb' . $this->id . '" class="flat checkforselect fright" type="checkbox" name="toselect[]" value="' . $this->id . '"' . ($selected ? ' checked="checked"' : '') . '>';
2928
        }
2929
        if (!empty($arraydata['thirdparty'])) {
2930
            $tmpthirdparty = $arraydata['thirdparty'];
2931
            $return .= '<br><div class="info-box-label inline-block valignmiddle">' . $tmpthirdparty->getNomUrl(1) . '</div>';
2932
        }
2933
        if (property_exists($this, 'date_contrat')) {
2934
            $return .= '<br><span class="opacitymedium valignmiddle">' . $langs->trans("DateContract") . ' : </span><span class="info-box-label valignmiddle">' . dol_print_date($this->date_contrat, 'day') . '</span>';
2935
        }
2936
        if (method_exists($this, 'getLibStatut')) {
2937
            $return .= '<br><div class="info-box-status valignmiddle">' . $this->getLibStatut(7) . '</div>';
2938
        }
2939
        $return .= '</div>';
2940
        $return .= '</div>';
2941
        $return .= '</div>';
2942
2943
        return $return;
2944
    }
2945
2946
    // @Todo getLibSignedStatus, LibSignedStatus
2947
2948
    /**
2949
     * Set signed status
2950
     *
2951
     * @param  User   $user        Object user that modify
2952
     * @param  int    $status      Newsigned  status to set (often a constant like self::STATUS_XXX)
2953
     * @param  int    $notrigger   1 = Does not execute triggers, 0 = Execute triggers
2954
     * @param  string $triggercode Trigger code to use
2955
     * @return int                 0 < if KO, > 0 if OK
2956
     */
2957
    public function setSignedStatus(User $user, int $status = 0, int $notrigger = 0, $triggercode = ''): int
2958
    {
2959
        return $this->setSignedStatusCommon($user, $status, $notrigger, $triggercode);
2960
    }
2961
}
2962