Contrat::contractCmpDate()   A
last analyzed

Complexity

Conditions 3

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

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

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

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

771
                    /** @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...
772
                    $this->fk_project = $obj->fk_project;
773
774
                    $this->socid = $obj->thirdpartyid;
775
                    $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

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

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

1128
                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...
1129
                    $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

1129
                    $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...
1130
                    $originidforcontact = $this->origin_id;
1131
                    if ($originforcontact == 'shipping') {     // shipment and order share the same contacts. If creating from shipment we take data of order
1132
                        $exp = new Expedition($this->db);
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_only_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_only_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_only_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_only_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_only_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_only_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_only_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_only_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_only_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_only_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_only_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_only_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_only_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_only_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_only_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_only_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_only_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) {
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_only_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_only_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
2759
            $i = 0;
2760
            while ($i < $num) {
2761
                $obj = $this->db->fetch_object($resql);
2762
                if ($obj) {
2763
                    if (!empty($contractlineprocessed[$obj->lid]) || !empty($contractignored[$obj->rowid]) || !empty($contracterror[$obj->rowid])) {
2764
                        continue;
2765
                    }
2766
2767
                    // Load contract
2768
                    $object = new Contrat($this->db);
2769
                    $object->fetch($obj->rowid);        // fetch also lines
2770
                    //$object->fetch_thirdparty();
2771
2772
                    if ($object->id <= 0) {
2773
                        $error++;
2774
                        $this->errors[] = 'Failed to load contract with id=' . $obj->rowid;
2775
                        continue;
2776
                    }
2777
2778
                    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);
2779
2780
                    // Update expiration date of line
2781
                    $expirationdate = $this->db->jdate($obj->date_fin_validite);
2782
                    $duration_value = preg_replace('/[^0-9]/', '', $obj->duration);
2783
                    $duration_unit = preg_replace('/\d/', '', $obj->duration);
2784
                    //var_dump($expirationdate.' '.$enddatetoscan);
2785
2786
                    // Load linked ->linkedObjects (objects linked)
2787
                    // @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.
2788
                    $object->fetchObjectLinked(null, '', null, '', 'OR', 1, 'sourcetype', 1);
2789
2790
                    // Test if there is at least 1 open invoice
2791
                    if (isset($object->linkedObjects['facture']) && is_array($object->linkedObjects['facture']) && count($object->linkedObjects['facture']) > 0) {
2792
                        // Sort array of linked invoices by ascending date
2793
                        usort($object->linkedObjects['facture'], array('Contrat', 'contractCmpDate'));
2794
                        //dol_sort_array($object->linkedObjects['facture'], 'date');
2795
2796
                        $someinvoicenotpaid = 0;
2797
                        foreach ($object->linkedObjects['facture'] as $idinvoice => $invoice) {
2798
                            if ($invoice->statut == Facture::STATUS_DRAFT) {
2799
                                continue;
2800
                            }   // Draft invoice are not invoice not paid
2801
2802
                            if (empty($invoice->paye)) {
2803
                                $someinvoicenotpaid++;
2804
                            }
2805
                        }
2806
                        if ($someinvoicenotpaid) {
2807
                            $this->output .= 'Contract ' . $object->ref . ' is qualified for renewal but there is ' . $someinvoicenotpaid . ' invoice(s) unpayed so we cancel renewal' . "\n";
2808
                            $contractignored[$object->id] = $object->ref;
2809
                            continue;
2810
                        }
2811
                    }
2812
2813
                    if ($expirationdate && $expirationdate < $enddatetoscan) {
2814
                        dol_syslog("Define the newdate of end of services from expirationdate=" . $expirationdate);
2815
                        $newdate = $expirationdate;
2816
                        $protecti = 0;  //$protecti is to avoid infinite loop
2817
                        while ($newdate < $enddatetoscan && $protecti < 1000) {
2818
                            $newdate = dol_time_plus_duree($newdate, $duration_value, $duration_unit);
2819
                            $protecti++;
2820
                        }
2821
2822
                        if ($protecti < 1000) { // If not, there is a pb
2823
                            // We will update the end of date of contrat, so first we refresh contract data
2824
                            dol_syslog("We will update the end of date of contract with newdate = " . dol_print_date($newdate, 'dayhourrfc'));
2825
2826
                            $this->db->begin();
2827
2828
                            $errorforlocaltransaction = 0;
2829
2830
                            $label = 'Renewal of contrat ' . $object->ref . ' line ' . $obj->lid;
2831
                            $comment = 'Renew date of contract ' . $object->ref . ' line ' . $obj->lid . ' by doAutoRenewContracts';
2832
2833
                            $sqlupdate = 'UPDATE ' . MAIN_DB_PREFIX . "contratdet SET date_fin_validite = '" . $this->db->idate($newdate) . "'";
2834
                            $sqlupdate .= ' WHERE rowid = ' . ((int) $obj->lid);
2835
                            $resqlupdate = $this->db->query($sqlupdate);
2836
                            if ($resqlupdate) {
2837
                                $contractlineprocessed[$obj->lid] = $object->ref;
2838
2839
                                $actioncode = 'RENEW_CONTRACT';
2840
                                $now = dol_now();
2841
2842
                                // Create an event
2843
                                $actioncomm = new ActionComm($this->db);
2844
                                $actioncomm->type_code    = 'AC_OTH_AUTO';      // Type of event ('AC_OTH', 'AC_OTH_AUTO', 'AC_XXX'...)
2845
                                $actioncomm->code         = 'AC_' . $actioncode;
2846
                                $actioncomm->label        = $label;
2847
                                $actioncomm->datep        = $now;
2848
                                $actioncomm->datef        = $now;
2849
                                $actioncomm->percentage   = -1;   // Not applicable
2850
                                $actioncomm->socid        = $object->socid;
2851
                                $actioncomm->authorid     = $user->id;   // User saving action
2852
                                $actioncomm->userownerid  = $user->id;  // Owner of action
2853
                                $actioncomm->fk_element   = $object->id;
2854
                                $actioncomm->elementtype  = 'contract';
2855
                                $actioncomm->note_private = $comment;
2856
2857
                                $ret = $actioncomm->create($user);       // User creating action
2858
                            } else {
2859
                                $contracterror[$object->id] = $object->ref;
2860
2861
                                $error++;
2862
                                $errorforlocaltransaction++;
2863
                                $this->error = $this->db->lasterror();
2864
                            }
2865
2866
                            if (! $errorforlocaltransaction) {
2867
                                $this->db->commit();
2868
                            } else {
2869
                                $this->db->rollback();
2870
                            }
2871
                        } else {
2872
                            $error++;
2873
                            $this->error = "Bad value for newdate in doAutoRenewContracts - expirationdate=" . $expirationdate . " enddatetoscan=" . $enddatetoscan . " duration_value=" . $duration_value . " duration_unit=" . $duration_value;
2874
                            dol_syslog($this->error, LOG_ERR);
2875
                        }
2876
                    }
2877
                }
2878
                $i++;
2879
            }
2880
        } else {
2881
            $error++;
2882
            $this->error = $this->db->lasterror();
2883
        }
2884
2885
        $this->output .= count($contractlineprocessed) . ' contract line(s) with end date before ' . dol_print_date($enddatetoscan, 'day') . ' were renewed' . (count($contractlineprocessed) > 0 ? ' : ' . implode(',', $contractlineprocessed) : '');
2886
2887
        return ($error ? 1 : 0);
2888
    }
2889
2890
    /**
2891
     * Used to sort lines by date
2892
     *
2893
     * @param   Object  $a      1st element to test
2894
     * @param   Object  $b      2nd element to test
2895
     * @return int
2896
     */
2897
    public static function contractCmpDate($a, $b)
2898
    {
2899
        if ($a->date == $b->date) {
2900
            return strcmp((string) $a->id, (string) $b->id);
2901
        }
2902
        return ($a->date < $b->date) ? -1 : 1;
2903
    }
2904
2905
    /**
2906
     *  Return clicable link of object (with eventually picto)
2907
     *
2908
     *  @param      string      $option                 Where point the link (0=> main card, 1,2 => shipment, 'nolink'=>No link)
2909
     *  @param      array       $arraydata              Array of data
2910
     *  @return     string                              HTML Code for Kanban thumb.
2911
     */
2912
    public function getKanbanView($option = '', $arraydata = null)
2913
    {
2914
        global $langs;
2915
2916
        $selected = (empty($arraydata['selected']) ? 0 : $arraydata['selected']);
2917
2918
        $return = '<div class="box-flex-item box-flex-grow-zero">';
2919
        $return .= '<div class="info-box info-box-sm">';
2920
        $return .= '<span class="info-box-icon bg-infobox-action">';
2921
        $return .= img_picto('', $this->picto);
2922
        $return .= '</span>';
2923
        $return .= '<div class="info-box-content">';
2924
        $return .= '<span class="info-box-ref inline-block tdoverflowmax150 valignmiddle">' . (method_exists($this, 'getNomUrl') ? $this->getNomUrl() : $this->ref) . '</span>';
2925
        if ($selected >= 0) {
2926
            $return .= '<input id="cb' . $this->id . '" class="flat checkforselect fright" type="checkbox" name="toselect[]" value="' . $this->id . '"' . ($selected ? ' checked="checked"' : '') . '>';
2927
        }
2928
        if (!empty($arraydata['thirdparty'])) {
2929
            $tmpthirdparty = $arraydata['thirdparty'];
2930
            $return .= '<br><div class="info-box-label inline-block valignmiddle">' . $tmpthirdparty->getNomUrl(1) . '</div>';
2931
        }
2932
        if (property_exists($this, 'date_contrat')) {
2933
            $return .= '<br><span class="opacitymedium valignmiddle">' . $langs->trans("DateContract") . ' : </span><span class="info-box-label valignmiddle">' . dol_print_date($this->date_contrat, 'day') . '</span>';
2934
        }
2935
        if (method_exists($this, 'getLibStatut')) {
2936
            $return .= '<br><div class="info-box-status valignmiddle">' . $this->getLibStatut(7) . '</div>';
2937
        }
2938
        $return .= '</div>';
2939
        $return .= '</div>';
2940
        $return .= '</div>';
2941
2942
        return $return;
2943
    }
2944
2945
    // @Todo getLibSignedStatus, LibSignedStatus
2946
2947
    /**
2948
     * Set signed status
2949
     *
2950
     * @param  User   $user        Object user that modify
2951
     * @param  int    $status      Newsigned  status to set (often a constant like self::STATUS_XXX)
2952
     * @param  int    $notrigger   1 = Does not execute triggers, 0 = Execute triggers
2953
     * @param  string $triggercode Trigger code to use
2954
     * @return int                 0 < if KO, > 0 if OK
2955
     */
2956
    public function setSignedStatus(User $user, int $status = 0, int $notrigger = 0, $triggercode = ''): int
2957
    {
2958
        return $this->setSignedStatusCommon($user, $status, $notrigger, $triggercode);
2959
    }
2960
}
2961