dolibarr 25.0.0-alpha
api_proposals.class.php
1<?php
2/* Copyright (C) 2015 Jean-François Ferry <jfefe@aternatik.fr>
3 * Copyright (C) 2016 Laurent Destailleur <eldy@users.sourceforge.net>
4 * Copyright (C) 2020 Thibault FOUCART <support@ptibogxiv.net>
5 * Copyright (C) 2022 ATM Consulting <contact@atm-consulting.fr>
6 * Copyright (C) 2022 OpenDSI <support@open-dsi.fr>
7 * Copyright (C) 2024-2026 MDW <mdeweerd@users.noreply.github.com>
8 * Copyright (C) 2024-2025 Frédéric France <frederic.france@free.fr>
9 * Copyright (C) 2025 William Mead <william@m34d.com>
10 * Copyright (C) 2025 Charlene Benke <charlene@patas-monkey.com>
11 *
12 * This program is free software; you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation; either version 3 of the License, or
15 * (at your option) any later version.
16 *
17 * This program is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program. If not, see <https://www.gnu.org/licenses/>.
24 */
25
26use Luracast\Restler\RestException;
27
28require_once DOL_DOCUMENT_ROOT.'/comm/propal/class/propal.class.php';
29require_once DOL_DOCUMENT_ROOT.'/core/lib/company.lib.php';
30
31
41{
45 public static $FIELDS = array(
46 'socid'
47 );
48
52 public $propal;
53
57 public function __construct()
58 {
59 global $db;
60 $this->db = $db;
61 $this->propal = new Propal($this->db);
62 }
63
77 public function get($id, $contact_list = 1)
78 {
79 return $this->_fetch($id, '', '', $contact_list);
80 }
81
97 public function getByRef($ref, $contact_list = 1)
98 {
99 return $this->_fetch(0, $ref, '', $contact_list);
100 }
101
117 public function getByRefExt($ref_ext, $contact_list = 1)
118 {
119 return $this->_fetch(0, '', $ref_ext, $contact_list);
120 }
121
135 private function _fetch($id, $ref = '', $ref_ext = '', $contact_list = 1)
136 {
137 if (!DolibarrApiAccess::$user->hasRight('propal', 'lire')) {
138 throw new RestException(403);
139 }
140 if (empty($id) && empty($ref) && empty($ref_ext)) {
141 throw new RestException(400, 'No ID or Ref provided');
142 }
143 $result = $this->propal->fetch($id, $ref, $ref_ext);
144 if (!$result) {
145 throw new RestException(404, 'Commercial Proposal not found');
146 }
147
148 if (!DolibarrApi::_checkAccessToResource('propal', $this->propal->id)) {
149 throw new RestException(403, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
150 }
151
152 if ($contact_list > -1) {
153 // Add external contacts ids.
154 $tmparray = $this->propal->liste_contact(-1, 'external', $contact_list);
155 if (is_array($tmparray)) {
156 $this->propal->contacts_ids = $tmparray;
157 }
158 $tmparray = $this->propal->liste_contact(-1, 'internal', $contact_list);
159 if (is_array($tmparray)) {
160 $this->propal->contacts_ids_internal = $tmparray;
161 }
162 }
163
164 $this->propal->fetchObjectLinked();
165
166 return $this->_cleanObjectDatas($this->propal);
167 }
168
191 public function index($sortfield = "t.rowid", $sortorder = 'ASC', $limit = 100, $page = 0, $thirdparty_ids = '', $sqlfilters = '', $properties = '', $pagination_data = false, $loadlinkedobjects = 0)
192 {
193 global $hookmanager;
194
195 if (!DolibarrApiAccess::$user->hasRight('propal', 'lire')) {
196 throw new RestException(403);
197 }
198
199 $obj_ret = array();
200
201 // case of external user, $thirdparty_ids param is ignored and replaced by user's socid
202 $socids = DolibarrApiAccess::$user->socid ?: $thirdparty_ids;
203
204 // If the internal user must only see his customers, force searching by him
205 $search_sale = 0;
206 if (!DolibarrApiAccess::$user->hasRight('societe', 'client', 'voir') && !$socids) {
207 $search_sale = DolibarrApiAccess::$user->id;
208 }
209 $sql = "SELECT t.rowid";
210 $sql .= " FROM ".MAIN_DB_PREFIX."propal AS t";
211 $sql .= " INNER JOIN ".MAIN_DB_PREFIX."societe AS s ON (s.rowid = t.fk_soc)";
212 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."propal_extrafields AS ef ON (ef.fk_object = t.rowid)"; // Modification VMR Global Solutions to include extrafields as search parameters in the API GET call, so we will be able to filter on extrafields
213 $sql .= ' WHERE t.entity IN ('.getEntity('propal').')';
214 if ($socids) {
215 $sql .= " AND t.fk_soc IN (".$this->db->sanitize($socids).")";
216 }
217 // Search on sale representative
218 if ($search_sale && $search_sale != '-1') {
219 if ($search_sale == -2) {
220 $sql .= " AND ".getSalesRepresentativeSqlFilter('t.fk_soc', 0, 1);
221 } elseif ($search_sale > 0) {
222 $sql .= " AND ".getSalesRepresentativeSqlFilter('t.fk_soc', (int) $search_sale);
223 }
224 }
225 $parameters = array();
226 $hookmanager->executeHooks('printFieldListWhere', $parameters, $this->propal); // Note that $action and $object may have been modified by hook
227 $sql .= $hookmanager->resPrint;
228 // Add sql filters
229 if ($sqlfilters) {
230 $errormessage = '';
231 $sql .= forgeSQLFromUniversalSearchCriteria($sqlfilters, $errormessage);
232 if ($errormessage) {
233 throw new RestException(400, 'Error when validating parameter sqlfilters -> '.$errormessage);
234 }
235 }
236
237 //this query will return total proposals with the filters given
238 $sqlTotals = str_replace('SELECT t.rowid', 'SELECT count(t.rowid) as total', $sql);
239
240 $sql .= $this->db->order($sortfield, $sortorder);
241 if ($limit) {
242 if ($page < 0) {
243 $page = 0;
244 }
245 $offset = $limit * $page;
246
247 $sql .= $this->db->plimit($limit + 1, $offset);
248 }
249
250 dol_syslog("API Rest request");
251 $result = $this->db->query($sql);
252
253 if ($result) {
254 $num = $this->db->num_rows($result);
255 $min = min($num, ($limit <= 0 ? $num : $limit));
256 $i = 0;
257 while ($i < $min) {
258 $obj = $this->db->fetch_object($result);
259 $proposal_static = new Propal($this->db);
260 if ($proposal_static->fetch($obj->rowid) > 0) {
261 // Add external contacts ids
262 $tmparray = $proposal_static->liste_contact(-1, 'external', 1);
263 if (is_array($tmparray)) {
264 $proposal_static->contacts_ids = $tmparray;
265 }
266
267 if ($loadlinkedobjects) {
268 // retrieve linked objects
269 $proposal_static->fetchObjectLinked();
270 }
271
272 $obj_ret[] = $this->_filterObjectProperties($this->_cleanObjectDatas($proposal_static), $properties);
273 }
274 $i++;
275 }
276 } else {
277 throw new RestException(503, 'Error when retrieve propal list : '.$this->db->lasterror());
278 }
279
280 //if $pagination_data is true the response will contain element data with all values and element pagination with pagination data(total,page,limit)
281 if ($pagination_data) {
282 $totalsResult = $this->db->query($sqlTotals);
283 $total = $this->db->fetch_object($totalsResult)->total;
284
285 $tmp = $obj_ret;
286 $obj_ret = [];
287
288 $obj_ret['data'] = $tmp;
289 $obj_ret['pagination'] = [
290 'total' => (int) $total,
291 'page' => $page, //count starts from 0
292 'page_count' => ceil((int) $total / $limit),
293 'limit' => $limit
294 ];
295 }
296
297 return $obj_ret;
298 }
299
312 public function post($request_data = null)
313 {
314 global $conf;
315 if (!DolibarrApiAccess::$user->hasRight('propal', 'creer')) {
316 throw new RestException(403, "Insufficiant rights");
317 }
318
319 // Check mandatory fields
320 $this->_validate($request_data);
321
322 // Check thirdparty validity
323 $socid = (int) $request_data['socid'];
324 $thirdpartytmp = new Societe($this->db);
325 $thirdparty_result = $thirdpartytmp->fetch($socid);
326 if ($thirdparty_result < 1) {
327 throw new RestException(404, 'Thirdparty with id='.$socid.' not found or not allowed');
328 }
329 if (!DolibarrApi::_checkAccessToResource('societe', $thirdpartytmp->id)) {
330 throw new RestException(404, 'Thirdparty with id='.$thirdpartytmp->id.' not found or not allowed');
331 }
332
333 foreach ($request_data as $field => $value) {
334 if ($field === 'caller') {
335 // Add a mention of caller so on trigger called after action, we can filter to avoid a loop if we try to sync back again with the caller
336 $this->propal->context['caller'] = sanitizeVal($request_data['caller'], 'aZ09');
337 continue;
338 }
339 if ($field == 'id') {
340 throw new RestException(400, 'Creating with id field is forbidden');
341 }
342 if ($field == 'entity' && ((int) $value) != ((int) $conf->entity)) {
343 throw new RestException(403, 'Creating with entity='.((int) $value).' MUST be the same entity='.((int) $conf->entity).' as your API user/key belongs to');
344 }
345
346 $this->propal->$field = $this->_checkValForAPI($field, $value, $this->propal);
347 }
348 if ($this->propal->create(DolibarrApiAccess::$user) < 0) {
349 throw new RestException(500, "Error creating proposal", array_merge(array($this->propal->error), $this->propal->errors));
350 }
351
352 return ((int) $this->propal->id);
353 }
354
371 public function getLines($id, $sqlfilters = '')
372 {
373 if (!DolibarrApiAccess::$user->hasRight('propal', 'lire')) {
374 throw new RestException(403);
375 }
376
377 $result = $this->propal->fetch($id);
378 if (!$result) {
379 throw new RestException(404, 'Commercial Proposal not found');
380 }
381
382 if (!DolibarrApi::_checkAccessToResource('propal', $this->propal->id)) {
383 throw new RestException(403, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
384 }
385
386 $sql = '';
387 if (!empty($sqlfilters)) {
388 $errormessage = '';
389 $sql = forgeSQLFromUniversalSearchCriteria($sqlfilters, $errormessage);
390 if ($errormessage) {
391 throw new RestException(400, 'Error when validating parameter sqlfilters -> '.$errormessage);
392 }
393 }
394
395 $this->propal->getLinesArray($sql);
396 $result = array();
397 foreach ($this->propal->lines as $line) {
398 array_push($result, $this->_cleanObjectDatas($line));
399 }
400 return $result;
401 }
402
419 public function postLine($id, $request_data = null)
420 {
421 if (!DolibarrApiAccess::$user->hasRight('propal', 'creer')) {
422 throw new RestException(403);
423 }
424
425 $result = $this->propal->fetch($id);
426 if (!$result) {
427 throw new RestException(404, 'Commercial Proposal not found');
428 }
429
430 if (!DolibarrApi::_checkAccessToResource('propal', $this->propal->id)) {
431 throw new RestException(403, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
432 }
433
434 $request_data = (object) $request_data;
435
436 $request_data->desc = sanitizeVal($request_data->desc, 'restricthtml');
437 $request_data->label = sanitizeVal($request_data->label);
438
439 $updateRes = $this->propal->addline(
440 $request_data->desc,
441 $request_data->subprice,
442 $request_data->qty,
443 $request_data->tva_tx,
444 $request_data->localtax1_tx,
445 $request_data->localtax2_tx,
446 $request_data->fk_product,
447 $request_data->remise_percent,
448 $request_data->price_base_type ? $request_data->price_base_type : 'HT',
449 $request_data->subprice,
450 $request_data->info_bits,
451 $request_data->product_type,
452 $request_data->rang,
453 $request_data->special_code,
454 $request_data->fk_parent_line,
455 $request_data->fk_fournprice,
456 $request_data->pa_ht,
457 $request_data->label,
458 $request_data->date_start,
459 $request_data->date_end,
460 $request_data->array_options,
461 $request_data->fk_unit,
462 $request_data->origin,
463 $request_data->origin_id,
464 $request_data->multicurrency_subprice,
465 $request_data->fk_remise_except
466 );
467
468 if ($updateRes > 0) {
469 return $updateRes;
470 } else {
471 throw new RestException(400, $this->propal->errorsToString());
472 }
473 }
474
489 public function postLines($id, $request_data = null)
490 {
491 if (!DolibarrApiAccess::$user->hasRight('propal', 'creer')) {
492 throw new RestException(403);
493 }
494
495 $result = $this->propal->fetch($id);
496 if (!$result) {
497 throw new RestException(404, 'Commercial Proposal not found');
498 }
499
500 if (!DolibarrApi::_checkAccessToResource('propal', $this->propal->id)) {
501 throw new RestException(403, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
502 }
503
504 $errors = [];
505 $updateRes = 0;
506 $this->db->begin();
507
508 foreach ($request_data as $TData) {
509 if (empty($TData[0])) {
510 $TData = array($TData);
511 }
512
513 foreach ($TData as $lineData) {
514 $line = (object) $lineData;
515
516 $updateRes = $this->propal->addline(
517 $line->desc,
518 $line->subprice,
519 $line->qty,
520 $line->tva_tx,
521 $line->localtax1_tx,
522 $line->localtax2_tx,
523 $line->fk_product,
524 $line->remise_percent,
525 'HT',
526 0,
527 $line->info_bits,
528 $line->product_type,
529 $line->rang,
530 $line->special_code,
531 $line->fk_parent_line,
532 $line->fk_fournprice,
533 $line->pa_ht,
534 $line->label,
535 $line->date_start,
536 $line->date_end,
537 $line->array_options,
538 $line->fk_unit,
539 $line->origin,
540 $line->origin_id,
541 $line->multicurrency_subprice,
542 $line->fk_remise_except
543 );
544
545 if ($updateRes < 0) {
546 $errors['lineLabel'] = $line->label;
547 $errors['msg'] = $this->propal->errors;
548 }
549 }
550 }
551 if (empty($errors)) {
552 $this->db->commit();
553 return $updateRes;
554 } else {
555 $this->db->rollback();
556 throw new RestException(400, implode(", ", $errors));
557 }
558 }
559
576 public function putLine($id, $lineid, $request_data = null)
577 {
578 if (!DolibarrApiAccess::$user->hasRight('propal', 'creer')) {
579 throw new RestException(403);
580 }
581
582 $result = $this->propal->fetch($id);
583 if ($result <= 0) {
584 throw new RestException(404, 'Proposal not found');
585 }
586
587 if (!DolibarrApi::_checkAccessToResource('propal', $this->propal->id)) {
588 throw new RestException(403, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
589 }
590
591 $request_data = (object) $request_data;
592
593 if (isset($request_data->desc)) {
594 $request_data->desc = sanitizeVal($request_data->desc, 'restricthtml');
595 }
596 if (isset($request_data->label)) {
597 $request_data->label = sanitizeVal($request_data->label);
598 }
599
600 $propalline = new PropaleLigne($this->db);
601 $result = $propalline->fetch($lineid);
602 if ($result <= 0) {
603 throw new RestException(404, 'Proposal line not found');
604 }
605
606 if ($propalline->fk_propal != $id) {
607 throw new RestException(403, 'Line does not belong to this proposal');
608 }
609
610 $updateRes = $this->propal->updateline(
611 $lineid,
612 isset($request_data->subprice) ? $request_data->subprice : $propalline->subprice,
613 isset($request_data->qty) ? $request_data->qty : $propalline->qty,
614 isset($request_data->remise_percent) ? $request_data->remise_percent : $propalline->remise_percent,
615 isset($request_data->tva_tx) ? $request_data->tva_tx : $propalline->tva_tx,
616 isset($request_data->localtax1_tx) ? $request_data->localtax1_tx : $propalline->localtax1_tx,
617 isset($request_data->localtax2_tx) ? $request_data->localtax2_tx : $propalline->localtax2_tx,
618 isset($request_data->desc) ? $request_data->desc : $propalline->desc,
619 isset($request_data->price_base_type) ? $request_data->price_base_type : 'HT',
620 isset($request_data->info_bits) ? $request_data->info_bits : $propalline->info_bits,
621 isset($request_data->special_code) ? $request_data->special_code : $propalline->special_code,
622 isset($request_data->fk_parent_line) ? $request_data->fk_parent_line : $propalline->fk_parent_line,
623 0,
624 isset($request_data->fk_fournprice) ? $request_data->fk_fournprice : $propalline->fk_fournprice,
625 isset($request_data->pa_ht) ? $request_data->pa_ht : $propalline->pa_ht,
626 isset($request_data->label) ? $request_data->label : $propalline->label,
627 isset($request_data->product_type) ? $request_data->product_type : $propalline->product_type,
628 isset($request_data->date_start) ? $request_data->date_start : $propalline->date_start,
629 isset($request_data->date_end) ? $request_data->date_end : $propalline->date_end,
630 isset($request_data->array_options) ? $request_data->array_options : $propalline->array_options,
631 isset($request_data->fk_unit) ? $request_data->fk_unit : $propalline->fk_unit,
632 isset($request_data->multicurrency_subprice) ? $request_data->multicurrency_subprice : $propalline->subprice,
633 0,
634 isset($request_data->rang) ? $request_data->rang : $propalline->rang
635 );
636
637 if ($updateRes > 0) {
638 $result = $this->get($id);
639 unset($result->line);
640 return $this->_cleanObjectDatas($result);
641 }
642 return false;
643 }
644
659 public function deleteLine($id, $lineid)
660 {
661 if (!DolibarrApiAccess::$user->hasRight('propal', 'creer')) {
662 throw new RestException(403);
663 }
664
665 $result = $this->propal->fetch($id);
666 if (!$result) {
667 throw new RestException(404, 'Proposal not found');
668 }
669
670 if (!DolibarrApi::_checkAccessToResource('propal', $this->propal->id)) {
671 throw new RestException(403, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
672 }
673
674 $updateRes = $this->propal->deleteLine($lineid, $id);
675 if ($updateRes > 0) {
676 return $this->get($id);
677 } else {
678 throw new RestException(405, $this->propal->errorsToString());
679 }
680 }
694 public function getContacts($id, $type = '')
695 {
696 if (!DolibarrApiAccess::$user->hasRight('propal', 'lire')) {
697 throw new RestException(403);
698 }
699
700 $result = $this->propal->fetch($id);
701 if (!$result) {
702 throw new RestException(404, 'Proposal not found');
703 }
704
705 if (!DolibarrApi::_checkAccessToResource('propal', $this->propal->id)) {
706 throw new RestException(403, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
707 }
708
709 $contacts = $this->propal->liste_contact(-1, 'external', 0, $type);
710 $socpeoples = $this->propal->liste_contact(-1, 'internal', 0, $type);
711
712 $contacts = array_merge($contacts, $socpeoples);
713
714 return $contacts;
715 }
716
738 public function postContact($id, $contactid, $type, $source = 'external', $notrigger = 0)
739 {
740 if (!DolibarrApiAccess::$user->hasRight('propal', 'creer')) {
741 throw new RestException(403);
742 }
743
744 // test source
745 if (empty($source)) {
746 throw new RestException(400, 'Source can not be empty');
747 }
748 $sql_distinct_source = "SELECT DISTINCT source";
749 $sql_distinct_source .= " FROM ".MAIN_DB_PREFIX."c_type_contact";
750 $sql_distinct_source .= " WHERE element LIKE 'propal'";
751 $sql_distinct_source .= " AND source is NOT NULL";
752 $sql_distinct_source .= " AND active != 0";
753 $source_result = $this->db->query($sql_distinct_source);
754 $source_array = array();
755
756 if ($source_result) {
757 $num = $this->db->num_rows($source_result);
758 $i = 0;
759 while ($i < $num) {
760 $obj = $this->db->fetch_object($source_result);
761 $source_kind = (string) $obj->source;
762 array_push($source_array, $source_kind);
763 dol_syslog("source_kind=".$source_kind);
764 $i++;
765 }
766 } else {
767 throw new RestException(503, 'Error when retrieving a list of propal contact sources: '.$this->db->lasterror());
768 }
769 if (!in_array($source, (array) $source_array, true)) {
770 throw new RestException(400, 'Combo of Source='.$source.' and Type='.$type.' not found in dictionary with active proposal contact types');
771 }
772
773 // test type
774 if (empty($type)) {
775 throw new RestException(400, 'type can not be empty');
776 }
777 // variable called type here, but code in dictionary and database
778 $sql_distinct_type = "SELECT DISTINCT code";
779 $sql_distinct_type .= " FROM ".MAIN_DB_PREFIX."c_type_contact";
780 $sql_distinct_type .= " WHERE element LIKE 'propal'";
781 $sql_distinct_type .= " AND source='".$this->db->escape($source)."'";
782 $sql_distinct_type .= " AND code is NOT NULL";
783 $sql_distinct_type .= " AND active != 0";
784 $type_result = $this->db->query($sql_distinct_type);
785 $type_array = array();
786
787 if ($type_result) {
788 $num = $this->db->num_rows($type_result);
789 $i = 0;
790 while ($i < $num) {
791 $obj = $this->db->fetch_object($type_result);
792 // variable called type here, but code in dictionary and database
793 $type_kind = (string) $obj->code;
794 array_push($type_array, $type_kind);
795 dol_syslog("type_kind=".$type_kind);
796 $i++;
797 }
798 } else {
799 throw new RestException(503, 'Error when retrieving a list of propal contact types: '.$this->db->lasterror());
800 }
801 if (!in_array($type, (array) $type_array, true)) {
802 throw new RestException(400, 'Combo of Type='.$type.' and Source='.$source.' not found in dictionary with active proposal contact types');
803 }
804
805 // tests done, let's get it
806 $result = $this->propal->fetch($id);
807 if (!$result) {
808 throw new RestException(404, 'Proposal not found');
809 }
810 if (!DolibarrApi::_checkAccessToResource('propal', $this->propal->id)) {
811 throw new RestException(403, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
812 }
813
814 $result = $this->propal->add_contact($contactid, $type, $source, $notrigger);
815
816 if ($result == 0) {
817 throw new RestException(400, 'Already exists: Contact='.$contactid.' is already linked to the proposal='.$id.' as source='.$source.' and type='.$type);
818 } elseif ($result == -1) {
819 throw new RestException(400, 'Wrong contact='.$contactid);
820 } elseif ($result == -2) {
821 throw new RestException(400, 'Wrong type='.$type);
822 } elseif ($result == -3) {
823 throw new RestException(400, 'Not allowed contacts');
824 } elseif ($result == -4) {
825 throw new RestException(400, 'ErrorCommercialNotAllowedForThirdparty');
826 } elseif ($result == -5) {
827 throw new RestException(400, 'Trigger failed');
828 } elseif ($result == -6) {
829 throw new RestException(400, 'DB_ERROR_RECORD_ALREADY_EXISTS');
830 } elseif ($result == -7) {
831 throw new RestException(400, 'Some other error');
832 }
833
834 if (!$result) {
835 throw new RestException(500, 'Error when added the contact');
836 }
837
838 return array(
839 'success' => array(
840 'code' => 200,
841 'message' => 'Contact='.$contactid.' linked to the proposal='.$id.' as '.$source.' '.$type
842 )
843 );
844 }
845
862 public function deleteContact($id, $contactid, $type)
863 {
864 if (!DolibarrApiAccess::$user->hasRight('propal', 'creer')) {
865 throw new RestException(403);
866 }
867
868 $result = $this->propal->fetch($id);
869
870 if (!$result) {
871 throw new RestException(404, 'Proposal not found');
872 }
873
874 if (!DolibarrApi::_checkAccessToResource('propal', $this->propal->id)) {
875 throw new RestException(403, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
876 }
877 foreach (array('internal', 'external') as $source) {
878 $contacts = $this->propal->liste_contact(-1, $source);
879 foreach ($contacts as $contact) {
880 if ($contact['id'] == $contactid && $contact['code'] == $type) {
881 $result = $this->propal->delete_contact($contact['rowid']);
882
883 if (!$result) {
884 throw new RestException(500, 'Error when deleted the contact');
885 }
886 }
887 }
888 }
889 return $this->_cleanObjectDatas($this->propal);
890 }
891
905 public function put($id, $request_data = null)
906 {
907 if (!DolibarrApiAccess::$user->hasRight('propal', 'creer')) {
908 throw new RestException(403);
909 }
910 if ($id == 0) {
911 throw new RestException(400, 'No proposal with id=0 can exist');
912 }
913 $result = $this->propal->fetch($id);
914 if (!$result) {
915 throw new RestException(404, 'Proposal not found');
916 }
917
918 if (!DolibarrApi::_checkAccessToResource('propal', $this->propal->id)) {
919 throw new RestException(403, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
920 }
921 foreach ($request_data as $field => $value) {
922 if ($field == 'id') {
923 continue;
924 }
925 if ($field === 'caller') {
926 // Add a mention of caller so on trigger called after action, we can filter to avoid a loop if we try to sync back again with the caller
927 $this->propal->context['caller'] = sanitizeVal($request_data['caller'], 'aZ09');
928 continue;
929 }
930 if ($field == 'array_options' && is_array($value)) {
931 foreach ($value as $index => $val) {
932 $this->propal->array_options[$index] = $this->_checkValExtrafieldsForAPI($index, $val, $this->propal);
933 }
934 continue;
935 }
936
937 $this->propal->$field = $this->_checkValForAPI($field, $value, $this->propal);
938 }
939
940 // update end of validity date
941 if (empty($this->propal->fin_validite) && !empty($this->propal->duree_validite) && !empty($this->propal->date_creation)) {
942 $this->propal->fin_validite = $this->propal->date_creation + (int) ($this->propal->duree_validite * 24 * 3600);
943 }
944 if (!empty($this->propal->fin_validite)) {
945 if ($this->propal->set_echeance(DolibarrApiAccess::$user, $this->propal->fin_validite) < 0) {
946 throw new RestException(500, $this->propal->errorsToString());
947 }
948 }
949
950 if ($this->propal->update(DolibarrApiAccess::$user) > 0) {
951 return $this->get($id);
952 } else {
953 throw new RestException(500, $this->propal->errorsToString());
954 }
955 }
956
969 public function delete($id)
970 {
971 if (!DolibarrApiAccess::$user->hasRight('propal', 'supprimer')) {
972 throw new RestException(403);
973 }
974 if ($id == 0) {
975 throw new RestException(400, 'No proposal with id=0 can exist');
976 }
977
978 $result = $this->propal->fetch($id);
979 if (!$result) {
980 throw new RestException(404, 'Commercial Proposal not found');
981 }
982
983 if (!DolibarrApi::_checkAccessToResource('propal', $this->propal->id)) {
984 throw new RestException(403, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
985 }
986
987 if (!$this->propal->delete(DolibarrApiAccess::$user)) {
988 throw new RestException(500, 'Error when delete Commercial Proposal : '.$this->propal->errorsToString());
989 }
990
991 return array(
992 'success' => array(
993 'code' => 200,
994 'message' => 'Commercial Proposal deleted'
995 )
996 );
997 }
998
1011 public function settodraft($id)
1012 {
1013 if (!DolibarrApiAccess::$user->hasRight('propal', 'creer')) {
1014 throw new RestException(403);
1015 }
1016 $result = $this->propal->fetch($id);
1017 if (!$result) {
1018 throw new RestException(404, 'Proposal not found');
1019 }
1020
1021 if (!DolibarrApi::_checkAccessToResource('propal', $this->propal->id)) {
1022 throw new RestException(403, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
1023 }
1024
1025 $result = $this->propal->setDraft(DolibarrApiAccess::$user);
1026 if ($result == 0) {
1027 throw new RestException(304, 'Nothing done. May be object is already draft');
1028 }
1029 if ($result < 0) {
1030 throw new RestException(500, 'Error : '.$this->propal->errorsToString());
1031 }
1032
1033 $result = $this->propal->fetch($id);
1034 if (!$result) {
1035 throw new RestException(404, 'Proposal not found');
1036 }
1037
1038 // test already done
1039 // if (!DolibarrApi::_checkAccessToResource('propal', $this->propal->id)) {
1040 // throw new RestException(403, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
1041 // }
1042
1043 $this->propal->fetchObjectLinked();
1044
1045 return $this->_cleanObjectDatas($this->propal);
1046 }
1047
1048
1070 public function validate($id, $notrigger = 0)
1071 {
1072 if (!DolibarrApiAccess::$user->hasRight('propal', 'creer')) {
1073 throw new RestException(403);
1074 }
1075 $result = $this->propal->fetch($id);
1076 if (!$result) {
1077 throw new RestException(404, 'Commercial Proposal not found');
1078 }
1079
1080 if (!DolibarrApi::_checkAccessToResource('propal', $this->propal->id)) {
1081 throw new RestException(403, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
1082 }
1083
1084 $result = $this->propal->valid(DolibarrApiAccess::$user, $notrigger);
1085 if ($result == 0) {
1086 throw new RestException(304, 'Error nothing done. May be object is already validated');
1087 }
1088 if ($result < 0) {
1089 throw new RestException(500, 'Error when validating Commercial Proposal: '.$this->propal->errorsToString());
1090 }
1091
1092 $result = $this->propal->fetch($id);
1093 if (!$result) {
1094 throw new RestException(404, 'Commercial Proposal not found');
1095 }
1096
1097 // test already done
1098 // if (!DolibarrApi::_checkAccessToResource('propal', $this->propal->id)) {
1099 // throw new RestException(403, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
1100 // }
1101
1102 $this->propal->fetchObjectLinked();
1103
1104 return $this->_cleanObjectDatas($this->propal);
1105 }
1106
1123 public function close($id, $status, $note_private = '', $notrigger = 0, $note_public = '')
1124 {
1125 if (!DolibarrApiAccess::$user->hasRight('propal', 'creer')) {
1126 throw new RestException(403);
1127 }
1128 $result = $this->propal->fetch($id);
1129 if (!$result) {
1130 throw new RestException(404, 'Commercial Proposal not found');
1131 }
1132
1133 if (!DolibarrApi::_checkAccessToResource('propal', $this->propal->id)) {
1134 throw new RestException(403, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
1135 }
1136
1137 $result = $this->propal->closeProposal(DolibarrApiAccess::$user, $status, $note_private, $notrigger, $note_public);
1138 if ($result == 0) {
1139 throw new RestException(304, 'Error nothing done. May be object is already closed');
1140 }
1141 if ($result < 0) {
1142 throw new RestException(500, 'Error when closing Commercial Proposal: '.$this->propal->errorsToString());
1143 }
1144
1145 $result = $this->propal->fetch($id);
1146 if (!$result) {
1147 throw new RestException(404, 'Proposal not found');
1148 }
1149
1150 // test already done
1151 // if (!DolibarrApi::_checkAccessToResource('propal', $this->propal->id)) {
1152 // throw new RestException(403, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
1153 // }
1154
1155 $this->propal->fetchObjectLinked();
1156
1157 return $this->_cleanObjectDatas($this->propal);
1158 }
1159
1172 public function setinvoiced($id)
1173 {
1174 if (!DolibarrApiAccess::$user->hasRight('propal', 'creer')) {
1175 throw new RestException(403);
1176 }
1177 $result = $this->propal->fetch($id);
1178 if (!$result) {
1179 throw new RestException(404, 'Commercial Proposal not found');
1180 }
1181
1182 if (!DolibarrApi::_checkAccessToResource('propal', $this->propal->id)) {
1183 throw new RestException(403, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
1184 }
1185
1186 $result = $this->propal->classifyBilled(DolibarrApiAccess::$user);
1187 if ($result < 0) {
1188 throw new RestException(500, 'Error : '.$this->propal->errorsToString());
1189 }
1190
1191 $result = $this->propal->fetch($id);
1192 if (!$result) {
1193 throw new RestException(404, 'Proposal not found');
1194 }
1195
1196 // test already done
1197 // if (!DolibarrApi::_checkAccessToResource('propal', $this->propal->id)) {
1198 // throw new RestException(403, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
1199 // }
1200
1201 $this->propal->fetchObjectLinked();
1202
1203 return $this->_cleanObjectDatas($this->propal);
1204 }
1205
1206
1215 private function _validate($data)
1216 {
1217 if ($data === null) {
1218 $data = array();
1219 }
1220 $propal = array();
1221 foreach (Proposals::$FIELDS as $field) {
1222 if (!isset($data[$field])) {
1223 throw new RestException(400, "$field field missing");
1224 }
1225 $propal[$field] = $data[$field];
1226 }
1227 return $propal;
1228 }
1229
1230
1231 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.PublicUnderscore
1241 protected function _cleanObjectDatas($object)
1242 {
1243 // phpcs:enable
1244 $object = parent::_cleanObjectDatas($object);
1245
1246 unset($object->note);
1247 unset($object->name);
1248 unset($object->lastname);
1249 unset($object->firstname);
1250 unset($object->civility_id);
1251 unset($object->address);
1252
1253 return $object;
1254 }
1255}
$id
Support class for third parties, contacts, members, users or resources.
Definition account.php:47
if(! $sortfield) if(! $sortorder) $object
Definition account.php:100
Class for API REST v1.
Definition api.class.php:35
_checkValExtrafieldsForAPI($field, $value, $object)
Check and convert a string depending on its type/name.
_filterObjectProperties($object, $properties)
Filter properties that will be returned on object.
_checkValForAPI($field, $value, $object)
Check and convert a string depending on its type/name.
static _checkAccessToResource($resource, $resource_id=0, $dbtablename='', $feature2='', $dbt_keyfield='fk_soc', $dbt_select='rowid', $parenttableforentity='')
Check access by user to a given resource.
Class to manage proposals.
Class to manage commercial proposal lines.
_fetch($id, $ref='', $ref_ext='', $contact_list=1)
Get properties of an proposal object.
getLines($id, $sqlfilters='')
Get lines of a commercial proposal.
settodraft($id)
Set a commercial proposal to draft.
put($id, $request_data=null)
Update a commercial proposal general fields (won't change lines of commercial proposal)
setinvoiced($id)
Set a commercial proposal to billed.
post($request_data=null)
Create a commercial proposal.
index($sortfield="t.rowid", $sortorder='ASC', $limit=100, $page=0, $thirdparty_ids='', $sqlfilters='', $properties='', $pagination_data=false, $loadlinkedobjects=0)
List commercial proposals.
postContact($id, $contactid, $type, $source='external', $notrigger=0)
Add (link) a contact to a commercial proposal.
getContacts($id, $type='')
Get contacts of given proposal.
close($id, $status, $note_private='', $notrigger=0, $note_public='')
Close (accept or refuse) a commercial proposal.
getByRefExt($ref_ext, $contact_list=1)
Get a commercial proposal by ref_ext.
postLine($id, $request_data=null)
Add a line to a commercial proposal.
_cleanObjectDatas($object)
Clean sensible object datas @phpstan-template T.
postLines($id, $request_data=null)
Add lines to a commercial proposal.
_validate($data)
Validate fields before create or update object.
deleteLine($id, $lineid)
Delete a line of a commercial proposal.
deleteContact($id, $contactid, $type)
Remove (unlink) a contact from commercial proposal.
validate($id, $notrigger=0)
Validate a commercial proposal.
__construct()
Constructor.
getByRef($ref, $contact_list=1)
Get a commercial proposal by ref.
putLine($id, $lineid, $request_data=null)
Update a line of a commercial proposal.
Class to manage third parties objects (customers, suppliers, prospects...)
if(!isModEnabled('ai')||!getDolGlobalString('AI_ASSISTANT_ENABLED')) global $conf
The main.inc.php has been included so the following variable are now defined:
forgeSQLFromUniversalSearchCriteria($filter, &$errorstr='', $noand=0, $nopar=0, $noerror=0, $forbiddenfields=array())
forgeSQLFromUniversalSearchCriteria
sanitizeVal($out='', $check='alphanohtml', $filter=null, $options=null)
Return a sanitized or empty value after checking value against a rule.
dol_syslog($message, $level=LOG_INFO, $ident=0, $suffixinfilename='', $restricttologhandler='', $logcontext=null)
Write log message into outputs.