QUAL: API contract: RestException update, check if thirdparty exists before creating/updating contract (#36007)

* QUAL: API contract: RestException update, check if thirdparty exists before creating contract

* also checking if socid exists during put

* using the entity of this contract

* also fixing entity during post

* some hurl tests of contract api

* check if contract id is 0, because that is not possible

* check (un)activate permissions and getEntity()

---------

Co-authored-by: Jon Bendtsen <xcodeauthor@jonb.dk>
Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>
This commit is contained in:
Jon Bendtsen 2025-11-01 22:47:18 +01:00 committed by GitHub
parent 7655c259f8
commit 20a428e2de
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 196 additions and 8 deletions

View file

@ -21,6 +21,7 @@
use Luracast\Restler\RestException;
require_once DOL_DOCUMENT_ROOT.'/contrat/class/contrat.class.php';
require_once DOL_DOCUMENT_ROOT.'/societe/class/societe.class.php';
/**
* API class for contracts
@ -64,14 +65,19 @@ class Contracts extends DolibarrApi
* @param string $properties Restrict the data returned to these properties. Ignored if empty. Comma separated list of properties names
* @param bool $withLines true or false to display or hide lines
* @return Object Object with cleaned properties
* @throws RestException
*
* @throws RestException 403 Access denied
* @throws RestException 404 Not found
* @throws RestException 503 Error
*/
public function get($id, $properties = '', $withLines = true)
{
if (!DolibarrApiAccess::$user->hasRight('contrat', 'lire')) {
throw new RestException(403);
}
if ($id == 0) {
throw new RestException(400, 'No contract with id=0 can exist');
}
$result = $this->contract->fetch($id);
if (!$result) {
throw new RestException(404, 'Contract not found');
@ -108,7 +114,8 @@ class Contracts extends DolibarrApi
* @phan-return Contrat[]|array{data:Contrat[],pagination:array{total:int,page:int,page_count:int,limit:int}}
* @phpstan-return Contrat[]|array{data:Contrat[],pagination:array{total:int,page:int,page_count:int,limit:int}}
*
* @throws RestException 404 Not found
* @throws RestException 400 Bad Request
* @throws RestException 403 Access denied
* @throws RestException 503 Error
*/
public function index($sortfield = "t.rowid", $sortorder = 'ASC', $limit = 100, $page = 0, $thirdparty_ids = '', $sqlfilters = '', $properties = '', $pagination_data = false, $withLines = true)
@ -217,6 +224,11 @@ class Contracts extends DolibarrApi
* @phan-param ?array<string,string> $request_data
* @phpstan-param ?array<string,string> $request_data
* @return int ID of contrat
*
* @throws RestException 400 Bad Request
* @throws RestException 403 Access denied
* @throws RestException 404 Not found
* @throws RestException 500 Error
*/
public function post($request_data = null)
{
@ -232,6 +244,20 @@ class Contracts extends DolibarrApi
$this->contract->context['caller'] = sanitizeVal($request_data['caller'], 'aZ09');
continue;
}
if ($field == 'id') {
throw new RestException(400, 'Creating with id field is forbidden');
}
if ($field == 'entity' && $value != getEntity('contrat')) {
throw new RestException(403, 'Creating entity not the same as your API user is forbidden');
}
if ($field == 'socid') {
$thirdparty = new Societe($this->db);
$result = $thirdparty->fetch((int) $value);
if ($result < 1) {
throw new RestException(404, 'Thirdparty with id='.((int) $value).' not found');
}
}
$this->contract->$field = $this->_checkValForAPI($field, $value, $this->contract);
}
@ -568,10 +594,13 @@ class Contracts extends DolibarrApi
* @url PUT {id}/lines/{lineid}/activate
*
* @return Object|bool
*
* @throws RestException 403 Access denied
* @throws RestException 404 Not found
*/
public function activateLine($id, $lineid, $datestart, $dateend = null, $comment = null)
{
if (!DolibarrApiAccess::$user->hasRight('contrat', 'creer')) {
if (!DolibarrApiAccess::$user->hasRight('contrat', 'activer')) {
throw new RestException(403);
}
@ -606,10 +635,13 @@ class Contracts extends DolibarrApi
* @url PUT {id}/lines/{lineid}/unactivate
*
* @return Object|bool
*
* @throws RestException 403 Access denied
* @throws RestException 404 Not found
*/
public function unactivateLine($id, $lineid, $datestart, $comment = null)
{
if (!DolibarrApiAccess::$user->hasRight('contrat', 'creer')) {
if (!DolibarrApiAccess::$user->hasRight('contrat', 'desactiver')) {
throw new RestException(403);
}
@ -680,13 +712,20 @@ class Contracts extends DolibarrApi
* @phan-param ?array<string,string> $request_data
* @phpstan-param ?array<string,string> $request_data
* @return Object Updated object
*
* @throws RestException 400 Bad Request
* @throws RestException 403 Access denied
* @throws RestException 404 Not found
* @throws RestException 500 Error
*/
public function put($id, $request_data = null)
{
if (!DolibarrApiAccess::$user->hasRight('contrat', 'creer')) {
throw new RestException(403);
}
if ($id == 0) {
throw new RestException(400, 'No contract with id=0 can exist');
}
$result = $this->contract->fetch($id);
if (!$result) {
throw new RestException(404, 'Contrat not found');
@ -697,13 +736,25 @@ class Contracts extends DolibarrApi
}
foreach ($request_data as $field => $value) {
if ($field == 'id') {
continue;
throw new RestException(400, 'Updating with id field is forbidden');
}
if ($field == 'entity' && $value != $this->contract->entity) {
throw new RestException(400, 'Changing entity of a contract using the APIs is not possible');
}
if ($field === 'caller') {
// 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
$this->contract->context['caller'] = sanitizeVal($request_data['caller'], 'aZ09');
continue;
}
if ($field == 'socid') {
$thirdparty = new Societe($this->db);
$result = $thirdparty->fetch((int) $value);
if ($result < 1) {
throw new RestException(404, 'Thirdparty with id='.((int) $value).' not found');
}
}
if ($field == 'array_options' && is_array($value)) {
foreach ($value as $index => $val) {
$this->contract->array_options[$index] = $this->_checkValForAPI($field, $val, $this->contract);
@ -729,12 +780,20 @@ class Contracts extends DolibarrApi
* @return array
* @phan-return array{success:array{code:int,message:string}}
* @phpstan-return array{success:array{code:int,message:string}}
*
* @throws RestException 400 Bad Request
* @throws RestException 403 Access denied
* @throws RestException 404 Not found
* @throws RestException 500 Error
*/
public function delete($id)
{
if (!DolibarrApiAccess::$user->hasRight('contrat', 'supprimer')) {
throw new RestException(403);
}
if ($id == 0) {
throw new RestException(400, 'No contract with id=0 can exist');
}
$result = $this->contract->fetch($id);
if (!$result) {
throw new RestException(404, 'Contract not found');
@ -774,12 +833,20 @@ class Contracts extends DolibarrApi
* {
* "notrigger": 0
* }
*
* @throws RestException 304 Not Modified
* @throws RestException 403 Access denied
* @throws RestException 404 Not found
* @throws RestException 500 Error
*/
public function validate($id, $notrigger = 0)
{
if (!DolibarrApiAccess::$user->hasRight('contrat', 'creer')) {
throw new RestException(403);
}
if ($id == 0) {
throw new RestException(400, 'No contract with id=0 can exist');
}
$result = $this->contract->fetch($id);
if (!$result) {
throw new RestException(404, 'Contract not found');
@ -823,12 +890,20 @@ class Contracts extends DolibarrApi
* {
* "notrigger": 0
* }
*
* @throws RestException 304 Not Modified
* @throws RestException 403 Access denied
* @throws RestException 404 Not found
* @throws RestException 500 Error
*/
public function close($id, $notrigger = 0)
{
if (!DolibarrApiAccess::$user->hasRight('contrat', 'creer')) {
throw new RestException(403);
}
if ($id == 0) {
throw new RestException(400, 'No contract with id=0 can exist');
}
$result = $this->contract->fetch($id);
if (!$result) {
throw new RestException(404, 'Contract not found');
@ -879,7 +954,8 @@ class Contracts extends DolibarrApi
*
* @param ?array<string,string> $data Array with data to verify
* @return array<string,string>
* @throws RestException
*
* @throws RestException 400 Bad Request
*/
private function _validate($data)
{

View file

@ -0,0 +1,112 @@
# GET contracts
GET http://{{hostnport}}/api/index.php/contracts
HTTP 200
# GET sorted contracts
GET http://{{hostnport}}/api/index.php/contracts?sortfield=t.rowid&sortorder=ASC
HTTP 200
# GET sorted contracts
GET http://{{hostnport}}/api/index.php/contracts?sortfield=t.rowid&sortorder=DSC
HTTP 200
# GET with limit and page
GET http://{{hostnport}}/api/index.php/contracts?limit=100&page=1
HTTP 200
# GET with fk_user
GET http://{{hostnport}}/api/index.php/contracts?fk_user=0
HTTP 200
# GET with properties=id%2Cstatus
GET http://{{hostnport}}/api/index.php/contracts?properties=id%2Cstatus
HTTP 200
# GET with pagination_data=false
GET http://{{hostnport}}/api/index.php/contracts?pagination_data=false
HTTP 200
# GET with pagination_data=true
GET http://{{hostnport}}/api/index.php/contracts?pagination_data=true
HTTP 200
[Asserts]
jsonpath "$.data" exists
jsonpath "$.pagination.total" >= 0
jsonpath "$.pagination.page" >= 0
jsonpath "$.pagination.page_count" >= 0
jsonpath "$.pagination.limit" == 100
# GET contract with ID 0 - which should not exist
GET http://{{hostnport}}/api/index.php/contracts/0
HTTP 400
{"error":{"code":400,"message":"Bad Request: No contract with id=0 can exist"}}
# POST {}
POST http://{{hostnport}}/api/index.php/contracts
{}
HTTP 400
{"error":{"code":400,"message":"Bad Request: socid field missing"}}
# POST topic required
POST http://{{hostnport}}/api/index.php/contracts
{ "id" : 42, "socid" : "22"}
HTTP 400
{"error":{"code":400,"message":"Bad Request: date_contrat field missing"}}
# POST type_template field required
POST http://{{hostnport}}/api/index.php/contracts
{ "id" : 42, "socid" : "22", "date_contrat" : "873223122" }
HTTP 400
{"error":{"code":400,"message":"Bad Request: commercial_signature_id field missing"}}
# POST No id in post request data
POST http://{{hostnport}}/api/index.php/contracts
{ "id" : 42, "socid" : "22", "date_contrat" : "873223122", "commercial_signature_id" : "1" }
HTTP 400
{"error":{"code":400,"message":"Bad Request: commercial_suivi_id field missing"}}
# POST No id in post request data
POST http://{{hostnport}}/api/index.php/contracts
{ "id" : 42, "socid" : "22", "date_contrat" : "873223122", "commercial_signature_id" : "1", "commercial_suivi_id" : "1" }
HTTP 400
{"error":{"code":400,"message":"Bad Request: Creating with id field is forbidden"}}
# DELETE
DELETE http://{{hostnport}}/api/index.php/contracts/
HTTP 405
# DELETE contract with ID 0 - which should not exist
DELETE http://{{hostnport}}/api/index.php/contracts/0
HTTP 400
{"error":{"code":400,"message":"Bad Request: No contract with id=0 can exist"}}
# PUT
PUT http://{{hostnport}}/api/index.php/contracts/
{}
HTTP 405
# PUT
PUT http://{{hostnport}}/api/index.php/contracts/0
{}
HTTP 400
{"error":{"code":400,"message":"Bad Request: No contract with id=0 can exist"}}
# GET contracts DOLAPIENTITY: 1
GET http://{{hostnport}}/api/index.php/contracts
DOLAPIENTITY: 1
HTTP 200
# GET contracts DOLAPIENTITY: 2
GET http://{{hostnport}}/api/index.php/contracts
DOLAPIENTITY: 2
HTTP 401
{"error":{"code":401,"message":"Unauthorized: Error user not valid (not found with api key or bad status or bad validity dates) (conf->entity=2)"}}
# http://localhost:8080/api/index.php/contracts?sortfield=t.rowid&sortorder=ASC&limit=1
# Capture a contract ID number
GET http://{{hostnport}}/api/index.php/contracts?limit=1
HTTP 200
[Captures]
contract_id: jsonpath "$[0]['id']"