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 (isset($request_data["lines"])) {
349 $lines = array();
350 foreach ($request_data["lines"] as $line) {
351 array_push($lines, (object) $line);
352 }
353 $this->propal->lines = $lines;
354 }*/
355 if ($this->propal->create(DolibarrApiAccess::$user) < 0) {
356 throw new RestException(500, "Error creating order", array_merge(array($this->propal->error), $this->propal->errors));
357 }
358
359 return ((int) $this->propal->id);
360 }
361
378 public function getLines($id, $sqlfilters = '')
379 {
380 if (!DolibarrApiAccess::$user->hasRight('propal', 'lire')) {
381 throw new RestException(403);
382 }
383
384 $result = $this->propal->fetch($id);
385 if (!$result) {
386 throw new RestException(404, 'Commercial Proposal not found');
387 }
388
389 if (!DolibarrApi::_checkAccessToResource('propal', $this->propal->id)) {
390 throw new RestException(403, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
391 }
392
393 $sql = '';
394 if (!empty($sqlfilters)) {
395 $errormessage = '';
396 $sql = forgeSQLFromUniversalSearchCriteria($sqlfilters, $errormessage);
397 if ($errormessage) {
398 throw new RestException(400, 'Error when validating parameter sqlfilters -> '.$errormessage);
399 }
400 }
401
402 $this->propal->getLinesArray($sql);
403 $result = array();
404 foreach ($this->propal->lines as $line) {
405 array_push($result, $this->_cleanObjectDatas($line));
406 }
407 return $result;
408 }
409
426 public function postLine($id, $request_data = null)
427 {
428 if (!DolibarrApiAccess::$user->hasRight('propal', 'creer')) {
429 throw new RestException(403);
430 }
431
432 $result = $this->propal->fetch($id);
433 if (!$result) {
434 throw new RestException(404, 'Commercial Proposal not found');
435 }
436
437 if (!DolibarrApi::_checkAccessToResource('propal', $this->propal->id)) {
438 throw new RestException(403, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
439 }
440
441 $request_data = (object) $request_data;
442
443 $request_data->desc = sanitizeVal($request_data->desc, 'restricthtml');
444 $request_data->label = sanitizeVal($request_data->label);
445
446 $updateRes = $this->propal->addline(
447 $request_data->desc,
448 $request_data->subprice,
449 $request_data->qty,
450 $request_data->tva_tx,
451 $request_data->localtax1_tx,
452 $request_data->localtax2_tx,
453 $request_data->fk_product,
454 $request_data->remise_percent,
455 $request_data->price_base_type ? $request_data->price_base_type : 'HT',
456 $request_data->subprice,
457 $request_data->info_bits,
458 $request_data->product_type,
459 $request_data->rang,
460 $request_data->special_code,
461 $request_data->fk_parent_line,
462 $request_data->fk_fournprice,
463 $request_data->pa_ht,
464 $request_data->label,
465 $request_data->date_start,
466 $request_data->date_end,
467 $request_data->array_options,
468 $request_data->fk_unit,
469 $request_data->origin,
470 $request_data->origin_id,
471 $request_data->multicurrency_subprice,
472 $request_data->fk_remise_except
473 );
474
475 if ($updateRes > 0) {
476 return $updateRes;
477 } else {
478 throw new RestException(400, $this->propal->error);
479 }
480 }
481
496 public function postLines($id, $request_data = null)
497 {
498 if (!DolibarrApiAccess::$user->hasRight('propal', 'creer')) {
499 throw new RestException(403);
500 }
501
502 $result = $this->propal->fetch($id);
503 if (!$result) {
504 throw new RestException(404, 'Commercial Proposal not found');
505 }
506
507 if (!DolibarrApi::_checkAccessToResource('propal', $this->propal->id)) {
508 throw new RestException(403, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
509 }
510
511 $errors = [];
512 $updateRes = 0;
513 $this->db->begin();
514
515 foreach ($request_data as $TData) {
516 if (empty($TData[0])) {
517 $TData = array($TData);
518 }
519
520 foreach ($TData as $lineData) {
521 $line = (object) $lineData;
522
523 $updateRes = $this->propal->addline(
524 $line->desc,
525 $line->subprice,
526 $line->qty,
527 $line->tva_tx,
528 $line->localtax1_tx,
529 $line->localtax2_tx,
530 $line->fk_product,
531 $line->remise_percent,
532 'HT',
533 0,
534 $line->info_bits,
535 $line->product_type,
536 $line->rang,
537 $line->special_code,
538 $line->fk_parent_line,
539 $line->fk_fournprice,
540 $line->pa_ht,
541 $line->label,
542 $line->date_start,
543 $line->date_end,
544 $line->array_options,
545 $line->fk_unit,
546 $line->origin,
547 $line->origin_id,
548 $line->multicurrency_subprice,
549 $line->fk_remise_except
550 );
551
552 if ($updateRes < 0) {
553 $errors['lineLabel'] = $line->label;
554 $errors['msg'] = $this->propal->errors;
555 }
556 }
557 }
558 if (empty($errors)) {
559 $this->db->commit();
560 return $updateRes;
561 } else {
562 $this->db->rollback();
563 throw new RestException(400, implode(", ", $errors));
564 }
565 }
566
583 public function putLine($id, $lineid, $request_data = null)
584 {
585 if (!DolibarrApiAccess::$user->hasRight('propal', 'creer')) {
586 throw new RestException(403);
587 }
588
589 $result = $this->propal->fetch($id);
590 if ($result <= 0) {
591 throw new RestException(404, 'Proposal not found');
592 }
593
594 if (!DolibarrApi::_checkAccessToResource('propal', $this->propal->id)) {
595 throw new RestException(403, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
596 }
597
598 $request_data = (object) $request_data;
599
600 if (isset($request_data->desc)) {
601 $request_data->desc = sanitizeVal($request_data->desc, 'restricthtml');
602 }
603 if (isset($request_data->label)) {
604 $request_data->label = sanitizeVal($request_data->label);
605 }
606
607 $propalline = new PropaleLigne($this->db);
608 $result = $propalline->fetch($lineid);
609 if ($result <= 0) {
610 throw new RestException(404, 'Proposal line not found');
611 }
612
613 $updateRes = $this->propal->updateline(
614 $lineid,
615 isset($request_data->subprice) ? $request_data->subprice : $propalline->subprice,
616 isset($request_data->qty) ? $request_data->qty : $propalline->qty,
617 isset($request_data->remise_percent) ? $request_data->remise_percent : $propalline->remise_percent,
618 isset($request_data->tva_tx) ? $request_data->tva_tx : $propalline->tva_tx,
619 isset($request_data->localtax1_tx) ? $request_data->localtax1_tx : $propalline->localtax1_tx,
620 isset($request_data->localtax2_tx) ? $request_data->localtax2_tx : $propalline->localtax2_tx,
621 isset($request_data->desc) ? $request_data->desc : $propalline->desc,
622 isset($request_data->price_base_type) ? $request_data->price_base_type : 'HT',
623 isset($request_data->info_bits) ? $request_data->info_bits : $propalline->info_bits,
624 isset($request_data->special_code) ? $request_data->special_code : $propalline->special_code,
625 isset($request_data->fk_parent_line) ? $request_data->fk_parent_line : $propalline->fk_parent_line,
626 0,
627 isset($request_data->fk_fournprice) ? $request_data->fk_fournprice : $propalline->fk_fournprice,
628 isset($request_data->pa_ht) ? $request_data->pa_ht : $propalline->pa_ht,
629 isset($request_data->label) ? $request_data->label : $propalline->label,
630 isset($request_data->product_type) ? $request_data->product_type : $propalline->product_type,
631 isset($request_data->date_start) ? $request_data->date_start : $propalline->date_start,
632 isset($request_data->date_end) ? $request_data->date_end : $propalline->date_end,
633 isset($request_data->array_options) ? $request_data->array_options : $propalline->array_options,
634 isset($request_data->fk_unit) ? $request_data->fk_unit : $propalline->fk_unit,
635 isset($request_data->multicurrency_subprice) ? $request_data->multicurrency_subprice : $propalline->subprice,
636 0,
637 isset($request_data->rang) ? $request_data->rang : $propalline->rang
638 );
639
640 if ($updateRes > 0) {
641 $result = $this->get($id);
642 unset($result->line);
643 return $this->_cleanObjectDatas($result);
644 }
645 return false;
646 }
647
662 public function deleteLine($id, $lineid)
663 {
664 if (!DolibarrApiAccess::$user->hasRight('propal', 'creer')) {
665 throw new RestException(403);
666 }
667
668 $result = $this->propal->fetch($id);
669 if (!$result) {
670 throw new RestException(404, 'Proposal not found');
671 }
672
673 if (!DolibarrApi::_checkAccessToResource('propal', $this->propal->id)) {
674 throw new RestException(403, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
675 }
676
677 $updateRes = $this->propal->deleteLine($lineid, $id);
678 if ($updateRes > 0) {
679 return $this->get($id);
680 } else {
681 throw new RestException(405, $this->propal->error);
682 }
683 }
697 public function getContacts($id, $type = '')
698 {
699 if (!DolibarrApiAccess::$user->hasRight('propal', 'lire')) {
700 throw new RestException(403);
701 }
702
703 $result = $this->propal->fetch($id);
704 if (!$result) {
705 throw new RestException(404, 'Proposal not found');
706 }
707
708 if (!DolibarrApi::_checkAccessToResource('propal', $this->propal->id)) {
709 throw new RestException(403, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
710 }
711
712 $contacts = $this->propal->liste_contact(-1, 'external', 0, $type);
713 $socpeoples = $this->propal->liste_contact(-1, 'internal', 0, $type);
714
715 $contacts = array_merge($contacts, $socpeoples);
716
717 return $contacts;
718 }
719
741 public function postContact($id, $contactid, $type, $source = 'external', $notrigger = 0)
742 {
743 if (!DolibarrApiAccess::$user->hasRight('propal', 'creer')) {
744 throw new RestException(403);
745 }
746
747 // test source
748 if (empty($source)) {
749 throw new RestException(400, 'Source can not be empty');
750 }
751 $sql_distinct_source = "SELECT DISTINCT source";
752 $sql_distinct_source .= " FROM ".MAIN_DB_PREFIX."c_type_contact";
753 $sql_distinct_source .= " WHERE element LIKE 'propal'";
754 $sql_distinct_source .= " AND source is NOT NULL";
755 $sql_distinct_source .= " AND active != 0";
756 $source_result = $this->db->query($sql_distinct_source);
757 $source_array = array();
758
759 if ($source_result) {
760 $num = $this->db->num_rows($source_result);
761 $i = 0;
762 while ($i < $num) {
763 $obj = $this->db->fetch_object($source_result);
764 $source_kind = (string) $obj->source;
765 array_push($source_array, $source_kind);
766 dol_syslog("source_kind=".$source_kind);
767 $i++;
768 }
769 } else {
770 throw new RestException(503, 'Error when retrieving a list of propal contact sources: '.$this->db->lasterror());
771 }
772 if (!in_array($source, (array) $source_array, true)) {
773 throw new RestException(400, 'Combo of Source='.$source.' and Type='.$type.' not found in dictionary with active proposal contact types');
774 }
775
776 // test type
777 if (empty($type)) {
778 throw new RestException(400, 'type can not be empty');
779 }
780 // variable called type here, but code in dictionary and database
781 $sql_distinct_type = "SELECT DISTINCT code";
782 $sql_distinct_type .= " FROM ".MAIN_DB_PREFIX."c_type_contact";
783 $sql_distinct_type .= " WHERE element LIKE 'propal'";
784 $sql_distinct_type .= " AND source='".$this->db->escape($source)."'";
785 $sql_distinct_type .= " AND code is NOT NULL";
786 $sql_distinct_type .= " AND active != 0";
787 $type_result = $this->db->query($sql_distinct_type);
788 $type_array = array();
789
790 if ($type_result) {
791 $num = $this->db->num_rows($type_result);
792 $i = 0;
793 while ($i < $num) {
794 $obj = $this->db->fetch_object($type_result);
795 // variable called type here, but code in dictionary and database
796 $type_kind = (string) $obj->code;
797 array_push($type_array, $type_kind);
798 dol_syslog("type_kind=".$type_kind);
799 $i++;
800 }
801 } else {
802 throw new RestException(503, 'Error when retrieving a list of propal contact types: '.$this->db->lasterror());
803 }
804 if (!in_array($type, (array) $type_array, true)) {
805 throw new RestException(400, 'Combo of Type='.$type.' and Source='.$source.' not found in dictionary with active proposal contact types');
806 }
807
808 // tests done, let's get it
809 $result = $this->propal->fetch($id);
810 if (!$result) {
811 throw new RestException(404, 'Proposal not found');
812 }
813 if (!DolibarrApi::_checkAccessToResource('propal', $this->propal->id)) {
814 throw new RestException(403, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
815 }
816
817 $result = $this->propal->add_contact($contactid, $type, $source, $notrigger);
818
819 if ($result == 0) {
820 throw new RestException(400, 'Already exists: Contact='.$contactid.' is already linked to the proposal='.$id.' as source='.$source.' and type='.$type);
821 } elseif ($result == -1) {
822 throw new RestException(400, 'Wrong contact='.$contactid);
823 } elseif ($result == -2) {
824 throw new RestException(400, 'Wrong type='.$type);
825 } elseif ($result == -3) {
826 throw new RestException(400, 'Not allowed contacts');
827 } elseif ($result == -4) {
828 throw new RestException(400, 'ErrorCommercialNotAllowedForThirdparty');
829 } elseif ($result == -5) {
830 throw new RestException(400, 'Trigger failed');
831 } elseif ($result == -6) {
832 throw new RestException(400, 'DB_ERROR_RECORD_ALREADY_EXISTS');
833 } elseif ($result == -7) {
834 throw new RestException(400, 'Some other error');
835 }
836
837 if (!$result) {
838 throw new RestException(500, 'Error when added the contact');
839 }
840
841 return array(
842 'success' => array(
843 'code' => 200,
844 'message' => 'Contact='.$contactid.' linked to the proposal='.$id.' as '.$source.' '.$type
845 )
846 );
847 }
848
865 public function deleteContact($id, $contactid, $type)
866 {
867 if (!DolibarrApiAccess::$user->hasRight('propal', 'creer')) {
868 throw new RestException(403);
869 }
870
871 $result = $this->propal->fetch($id);
872
873 if (!$result) {
874 throw new RestException(404, 'Proposal not found');
875 }
876
877 if (!DolibarrApi::_checkAccessToResource('propal', $this->propal->id)) {
878 throw new RestException(403, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
879 }
880 foreach (array('internal', 'external') as $source) {
881 $contacts = $this->propal->liste_contact(-1, $source);
882 foreach ($contacts as $contact) {
883 if ($contact['id'] == $contactid && $contact['code'] == $type) {
884 $result = $this->propal->delete_contact($contact['rowid']);
885
886 if (!$result) {
887 throw new RestException(500, 'Error when deleted the contact');
888 }
889 }
890 }
891 }
892 return $this->_cleanObjectDatas($this->propal);
893 }
894
908 public function put($id, $request_data = null)
909 {
910 if (!DolibarrApiAccess::$user->hasRight('propal', 'creer')) {
911 throw new RestException(403);
912 }
913 if ($id == 0) {
914 throw new RestException(400, 'No proposal with id=0 can exist');
915 }
916 $result = $this->propal->fetch($id);
917 if (!$result) {
918 throw new RestException(404, 'Proposal not found');
919 }
920
921 if (!DolibarrApi::_checkAccessToResource('propal', $this->propal->id)) {
922 throw new RestException(403, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
923 }
924 foreach ($request_data as $field => $value) {
925 if ($field == 'id') {
926 continue;
927 }
928 if ($field === 'caller') {
929 // 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
930 $this->propal->context['caller'] = sanitizeVal($request_data['caller'], 'aZ09');
931 continue;
932 }
933 if ($field == 'array_options' && is_array($value)) {
934 foreach ($value as $index => $val) {
935 $this->propal->array_options[$index] = $this->_checkValExtrafieldsForAPI($index, $val, $this->propal);
936 }
937 continue;
938 }
939
940 $this->propal->$field = $this->_checkValForAPI($field, $value, $this->propal);
941 }
942
943 // update end of validity date
944 if (empty($this->propal->fin_validite) && !empty($this->propal->duree_validite) && !empty($this->propal->date_creation)) {
945 $this->propal->fin_validite = $this->propal->date_creation + (int) ($this->propal->duree_validite * 24 * 3600);
946 }
947 if (!empty($this->propal->fin_validite)) {
948 if ($this->propal->set_echeance(DolibarrApiAccess::$user, $this->propal->fin_validite) < 0) {
949 throw new RestException(500, $this->propal->error);
950 }
951 }
952
953 if ($this->propal->update(DolibarrApiAccess::$user) > 0) {
954 return $this->get($id);
955 } else {
956 throw new RestException(500, $this->propal->error);
957 }
958 }
959
972 public function delete($id)
973 {
974 if (!DolibarrApiAccess::$user->hasRight('propal', 'supprimer')) {
975 throw new RestException(403);
976 }
977 if ($id == 0) {
978 throw new RestException(400, 'No proposal with id=0 can exist');
979 }
980
981 $result = $this->propal->fetch($id);
982 if (!$result) {
983 throw new RestException(404, 'Commercial Proposal not found');
984 }
985
986 if (!DolibarrApi::_checkAccessToResource('propal', $this->propal->id)) {
987 throw new RestException(403, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
988 }
989
990 if (!$this->propal->delete(DolibarrApiAccess::$user)) {
991 throw new RestException(500, 'Error when delete Commercial Proposal : '.$this->propal->error);
992 }
993
994 return array(
995 'success' => array(
996 'code' => 200,
997 'message' => 'Commercial Proposal deleted'
998 )
999 );
1000 }
1001
1014 public function settodraft($id)
1015 {
1016 if (!DolibarrApiAccess::$user->hasRight('propal', 'creer')) {
1017 throw new RestException(403);
1018 }
1019 $result = $this->propal->fetch($id);
1020 if (!$result) {
1021 throw new RestException(404, 'Proposal not found');
1022 }
1023
1024 if (!DolibarrApi::_checkAccessToResource('propal', $this->propal->id)) {
1025 throw new RestException(403, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
1026 }
1027
1028 $result = $this->propal->setDraft(DolibarrApiAccess::$user);
1029 if ($result == 0) {
1030 throw new RestException(304, 'Nothing done. May be object is already draft');
1031 }
1032 if ($result < 0) {
1033 throw new RestException(500, 'Error : '.$this->propal->error);
1034 }
1035
1036 $result = $this->propal->fetch($id);
1037 if (!$result) {
1038 throw new RestException(404, 'Proposal not found');
1039 }
1040
1041 // test already done
1042 // if (!DolibarrApi::_checkAccessToResource('propal', $this->propal->id)) {
1043 // throw new RestException(403, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
1044 // }
1045
1046 $this->propal->fetchObjectLinked();
1047
1048 return $this->_cleanObjectDatas($this->propal);
1049 }
1050
1051
1073 public function validate($id, $notrigger = 0)
1074 {
1075 if (!DolibarrApiAccess::$user->hasRight('propal', 'creer')) {
1076 throw new RestException(403);
1077 }
1078 $result = $this->propal->fetch($id);
1079 if (!$result) {
1080 throw new RestException(404, 'Commercial Proposal not found');
1081 }
1082
1083 if (!DolibarrApi::_checkAccessToResource('propal', $this->propal->id)) {
1084 throw new RestException(403, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
1085 }
1086
1087 $result = $this->propal->valid(DolibarrApiAccess::$user, $notrigger);
1088 if ($result == 0) {
1089 throw new RestException(304, 'Error nothing done. May be object is already validated');
1090 }
1091 if ($result < 0) {
1092 throw new RestException(500, 'Error when validating Commercial Proposal: '.$this->propal->error);
1093 }
1094
1095 $result = $this->propal->fetch($id);
1096 if (!$result) {
1097 throw new RestException(404, 'Commercial Proposal not found');
1098 }
1099
1100 // test already done
1101 // if (!DolibarrApi::_checkAccessToResource('propal', $this->propal->id)) {
1102 // throw new RestException(403, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
1103 // }
1104
1105 $this->propal->fetchObjectLinked();
1106
1107 return $this->_cleanObjectDatas($this->propal);
1108 }
1109
1126 public function close($id, $status, $note_private = '', $notrigger = 0, $note_public = '')
1127 {
1128 if (!DolibarrApiAccess::$user->hasRight('propal', 'creer')) {
1129 throw new RestException(403);
1130 }
1131 $result = $this->propal->fetch($id);
1132 if (!$result) {
1133 throw new RestException(404, 'Commercial Proposal not found');
1134 }
1135
1136 if (!DolibarrApi::_checkAccessToResource('propal', $this->propal->id)) {
1137 throw new RestException(403, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
1138 }
1139
1140 $result = $this->propal->closeProposal(DolibarrApiAccess::$user, $status, $note_private, $notrigger, $note_public);
1141 if ($result == 0) {
1142 throw new RestException(304, 'Error nothing done. May be object is already closed');
1143 }
1144 if ($result < 0) {
1145 throw new RestException(500, 'Error when closing Commercial Proposal: '.$this->propal->error);
1146 }
1147
1148 $result = $this->propal->fetch($id);
1149 if (!$result) {
1150 throw new RestException(404, 'Proposal not found');
1151 }
1152
1153 // test already done
1154 // if (!DolibarrApi::_checkAccessToResource('propal', $this->propal->id)) {
1155 // throw new RestException(403, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
1156 // }
1157
1158 $this->propal->fetchObjectLinked();
1159
1160 return $this->_cleanObjectDatas($this->propal);
1161 }
1162
1175 public function setinvoiced($id)
1176 {
1177 if (!DolibarrApiAccess::$user->hasRight('propal', 'creer')) {
1178 throw new RestException(403);
1179 }
1180 $result = $this->propal->fetch($id);
1181 if (!$result) {
1182 throw new RestException(404, 'Commercial Proposal not found');
1183 }
1184
1185 if (!DolibarrApi::_checkAccessToResource('propal', $this->propal->id)) {
1186 throw new RestException(403, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
1187 }
1188
1189 $result = $this->propal->classifyBilled(DolibarrApiAccess::$user);
1190 if ($result < 0) {
1191 throw new RestException(500, 'Error : '.$this->propal->error);
1192 }
1193
1194 $result = $this->propal->fetch($id);
1195 if (!$result) {
1196 throw new RestException(404, 'Proposal not found');
1197 }
1198
1199 // test already done
1200 // if (!DolibarrApi::_checkAccessToResource('propal', $this->propal->id)) {
1201 // throw new RestException(403, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
1202 // }
1203
1204 $this->propal->fetchObjectLinked();
1205
1206 return $this->_cleanObjectDatas($this->propal);
1207 }
1208
1209
1218 private function _validate($data)
1219 {
1220 if ($data === null) {
1221 $data = array();
1222 }
1223 $propal = array();
1224 foreach (Proposals::$FIELDS as $field) {
1225 if (!isset($data[$field])) {
1226 throw new RestException(400, "$field field missing");
1227 }
1228 $propal[$field] = $data[$field];
1229 }
1230 return $propal;
1231 }
1232
1233
1234 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.PublicUnderscore
1244 protected function _cleanObjectDatas($object)
1245 {
1246 // phpcs:enable
1247 $object = parent::_cleanObjectDatas($object);
1248
1249 unset($object->note);
1250 unset($object->name);
1251 unset($object->lastname);
1252 unset($object->firstname);
1253 unset($object->civility_id);
1254 unset($object->address);
1255
1256 return $object;
1257 }
1258}
$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:
if(!isModEnabled('ai')||!getDolGlobalString('AI_ASSISTANT_ENABLED')) global $db
API class for accounts.
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.
print $langs trans("Show") . '< td style="' . $timeColor . '" align="center"> s</td > badge status0 badge status4 badge status3 Error badge status8< td align="center">< span class="badge ' . $badge . '"></span ></td >< td align="center">< a href="#" class="button button-small" onclick="openLogModal(this)" data-req="' . dol_escape_htmltag($reqSafe) . '" data-res="' . dol_escape_htmltag($resSafe) . '" data-err="' . dol_escape_htmltag($errSafe) . '">< span class="fa fa-search-plus"></span ></a ></td ></tr >< tr >< td colspan="' . $colspan . '" class="opacitymedium"></td ></tr ></table ></div ></form > logModal none logModal none s a JSON string