From 0ee848250f85299f5a962010daf0760e136856cb Mon Sep 17 00:00:00 2001 From: Dasc3er Date: Mon, 20 Jul 2026 18:33:55 +0200 Subject: [PATCH 1/2] Aggiunta API per Tipi documento --- composer.json | 1 + .../CreateTipiDocumentoController.php | 44 ++++++++++ .../DeleteTipiDocumentoController.php | 41 +++++++++ .../GetTipiDocumentoController.php | 33 +++++++ .../Models/CreateTipiDocumentoRequest.php | 10 +++ .../Models/CreateTipiDocumentoResponse.php | 9 ++ .../Models/UpdateTipiDocumentoRequest.php | 23 +++++ .../UpdateTipiDocumentoController.php | 49 +++++++++++ .../src/API/TipoDocumentiResource.php | 86 +++++++++++++++++++ src/API/Controllers/BaseController.php | 12 ++- 10 files changed, 305 insertions(+), 3 deletions(-) create mode 100644 modules/tipi_documento/src/API/Controllers/CreateTipiDocumentoController.php create mode 100644 modules/tipi_documento/src/API/Controllers/DeleteTipiDocumentoController.php create mode 100644 modules/tipi_documento/src/API/Controllers/GetTipiDocumentoController.php create mode 100644 modules/tipi_documento/src/API/Controllers/Models/CreateTipiDocumentoRequest.php create mode 100644 modules/tipi_documento/src/API/Controllers/Models/CreateTipiDocumentoResponse.php create mode 100644 modules/tipi_documento/src/API/Controllers/Models/UpdateTipiDocumentoRequest.php create mode 100644 modules/tipi_documento/src/API/Controllers/UpdateTipiDocumentoController.php create mode 100644 modules/tipi_documento/src/API/TipoDocumentiResource.php diff --git a/composer.json b/composer.json index cfb532671..6ef7e2361 100644 --- a/composer.json +++ b/composer.json @@ -127,6 +127,7 @@ "Modules\\StatiContratto\\": ["modules/stati_contratto/custom/src/", "modules/stati_contratto/src/"], "Modules\\StatiOrdine\\": ["modules/stati_ordine/custom/src/", "modules/stati_ordine/src/"], "Modules\\TipiIntervento\\": ["modules/tipi_intervento/custom/src/", "modules/tipi_intervento/src/"], + "Modules\\TipiDocumento\\": ["modules/tipi_documento/custom/src/", "modules/tipi_documento/src/"], "Modules\\CategorieDocumentali\\": ["modules/categorie_documenti/custom/src/", "modules/categorie_documenti/src/"], "Modules\\PianiSconto\\": ["modules/piano_sconto/custom/src/", "modules/piano_sconto/src/"], "Modules\\Impianti\\": ["modules/impianti/custom/src/", "modules/impianti/src/"], diff --git a/modules/tipi_documento/src/API/Controllers/CreateTipiDocumentoController.php b/modules/tipi_documento/src/API/Controllers/CreateTipiDocumentoController.php new file mode 100644 index 000000000..311bd9cb1 --- /dev/null +++ b/modules/tipi_documento/src/API/Controllers/CreateTipiDocumentoController.php @@ -0,0 +1,44 @@ +init($request, CreateTipiDocumentoRequest::class); + + $tipo_new = Tipo::where('name', $data->name)->where('dir', '=', $data->dir)->where('codice_tipo_documento_fe', '=', $data->codice_tipo_documento_fe)->first(); + + if (!empty($tipo_new)) { + throw new ConflictHttpException(tr('Questa combinazione di nome, codice e direzione è già stata utilizzata per un altro tipo di documento.')); + } + + $tipo = Tipo::build($data->dir, $data->codice_tipo_documento_fe); + if (Locale::getDefault()->id == Locale::getPredefined()->id) { + $tipo->name = $data->name; + } + $id_record = database()->lastInsertedID(); + $tipo->save(); + + $response = new CreateTipiDocumentoResponse(); + $response->id = $id_record; + $response->text = $data->name; + + return new JsonResponse($response); + } + + protected function hasAccess($request): bool + { + return $this->hasModuleWriteAccess('Tipi documento'); + } +} diff --git a/modules/tipi_documento/src/API/Controllers/DeleteTipiDocumentoController.php b/modules/tipi_documento/src/API/Controllers/DeleteTipiDocumentoController.php new file mode 100644 index 000000000..0cc381f40 --- /dev/null +++ b/modules/tipi_documento/src/API/Controllers/DeleteTipiDocumentoController.php @@ -0,0 +1,41 @@ +init($request); + $id_record = $request->route('id'); + + $tipo = Tipo::find($id_record); + if (!$tipo) { + throw new NotFoundHttpException(); + } + + $documenti = $dbo->fetchNum('SELECT `id` FROM `co_documenti` WHERE `id_tipo_documento` ='.prepare($id_record)); + + if (empty($documenti)) { + Tipo::destroy($id_record); + } else { + $tipo->deleted_at = date(); + $tipo->predefined = 0; + $tipo->enabled = 0; + $tipo->save(); + } + + return new JsonResponse(); + } + + protected function hasAccess($request): bool + { + return $this->hasModuleReadAccess('Tipi documento'); + } +} diff --git a/modules/tipi_documento/src/API/Controllers/GetTipiDocumentoController.php b/modules/tipi_documento/src/API/Controllers/GetTipiDocumentoController.php new file mode 100644 index 000000000..e55319062 --- /dev/null +++ b/modules/tipi_documento/src/API/Controllers/GetTipiDocumentoController.php @@ -0,0 +1,33 @@ +init($request); + + $id_record = $request->route('id'); + + $tipo = Tipo::find($id_record); + if (empty($tipo)) { + throw new NotFoundHttpException(); + } + $response = TipiDocumentoResource::fromModel($tipo); + + return new JsonResponse($response); + } + + protected function hasAccess($request): bool + { + return $this->hasModuleReadAccess('Tipi documento'); + } +} diff --git a/modules/tipi_documento/src/API/Controllers/Models/CreateTipiDocumentoRequest.php b/modules/tipi_documento/src/API/Controllers/Models/CreateTipiDocumentoRequest.php new file mode 100644 index 000000000..1ea567286 --- /dev/null +++ b/modules/tipi_documento/src/API/Controllers/Models/CreateTipiDocumentoRequest.php @@ -0,0 +1,10 @@ +id; + } +} diff --git a/modules/tipi_documento/src/API/Controllers/UpdateTipiDocumentoController.php b/modules/tipi_documento/src/API/Controllers/UpdateTipiDocumentoController.php new file mode 100644 index 000000000..14ef5c040 --- /dev/null +++ b/modules/tipi_documento/src/API/Controllers/UpdateTipiDocumentoController.php @@ -0,0 +1,49 @@ +init($request, UpdateTipiDocumentoRequest::class); + + $tipo_new = Tipo::where('name', $data->name)->where('dir', '=', $data->dir)->where('codice_tipo_documento_fe', '=', $data->codice_tipo_documento_fe)->first(); + + if (!empty($tipo_new) && $tipo_new->id != $data->getId()) { + throw new ConflictHttpException(tr('Questa combinazione di nome, codice e direzione è già stata utilizzata per un altro tipo di documento.')); + } + if (!empty($predefined)) { + Tipo::where('dir', $data->dir)->update(['predefined' => 0]); + } + + $tipo = Tipo::find($data->getId()); + if (Locale::getDefault()->id == Locale::getPredefined()->id) { + $tipo->name = $data->name; + } + $tipo->dir = $data->dir; + $tipo->codice_tipo_documento_fe = $data->codice_tipo_documento_fe; + $tipo->help = $data->help; + $tipo->predefined = $data->predefined; + $tipo->enabled = $data->enabled; + $tipo->id_segment = $data->id_segment; + $tipo->save(); + + $tipo->setTranslation('title', $data->name); + + return new JsonResponse(); + } + + protected function hasAccess($request): bool + { + return $this->hasModuleWriteAccess('Tipi documento'); + } +} diff --git a/modules/tipi_documento/src/API/TipoDocumentiResource.php b/modules/tipi_documento/src/API/TipoDocumentiResource.php new file mode 100644 index 000000000..7602a3363 --- /dev/null +++ b/modules/tipi_documento/src/API/TipoDocumentiResource.php @@ -0,0 +1,86 @@ +id, + name: $record->name, + reversed: $record->reversed, + dir: $record->dir, + codice_tipo_documento_fe: $record->codice_tipo_documento_fe, + help: $record->help, + predefined: $record->predefined, + enabled: $record->enabled, + id_segment: $record->id_segment, + deleted_at: $record->deleted_at, + created_at: $record->created_at, + updated_at: $record->updated_at, + ); + } +} diff --git a/src/API/Controllers/BaseController.php b/src/API/Controllers/BaseController.php index 7a2f6c23e..813888e35 100644 --- a/src/API/Controllers/BaseController.php +++ b/src/API/Controllers/BaseController.php @@ -2,6 +2,9 @@ namespace API\Controllers; +use CuyZ\Valinor\Mapper\Configurator\ConvertKeysToSnakeCase; +use CuyZ\Valinor\Mapper\MappingError; +use CuyZ\Valinor\MapperBuilder; use Illuminate\Auth\Access\AuthorizationException; use Illuminate\Http\Request; use Illuminate\Routing\Controller; @@ -11,7 +14,7 @@ class InvalidInputException extends \Exception { - public function __construct(\CuyZ\Valinor\Mapper\MappingError $error) + public function __construct(MappingError $error) { $messages = $error->messages(); @@ -86,16 +89,19 @@ protected function init(Request $request, ?string $class_reference = null): mixe protected function _cast(Request $request, string $class_reference): mixed { try { - return (new \CuyZ\Valinor\MapperBuilder()) + return (new MapperBuilder()) ->allowUndefinedValues() ->allowSuperfluousKeys() ->allowScalarValueCasting() + ->configureWith( + new ConvertKeysToSnakeCase(), + ) ->mapper() ->map( $class_reference, [...$request->route()->parameters(), ...$request->all()] ); - } catch (\CuyZ\Valinor\Mapper\MappingError $error) { + } catch (MappingError $error) { throw new InvalidInputException($error); } } From f6fdd12465d0bacdaa8639bc092440d5fa41b7db Mon Sep 17 00:00:00 2001 From: Dasc3er Date: Sat, 15 Aug 2026 14:07:01 +0200 Subject: [PATCH 2/2] Aggiunta base per select options --- .php-cs-fixer.php | 1 + .../src/API/ImpostazioneResource.php | 4 +- modules/tipi_documento/ajax/select.php | 8 +-- .../SelectOptionsTipiDocumentoRequest.php | 10 +++ .../SelectOptionsTipiDocumentoResponse.php | 20 ++++++ .../SelectOptionsTipiDocumentoController.php | 69 +++++++++++++++++++ .../src/API/TipoDocumentiResource.php | 20 ++++-- src/AJAX.php | 2 +- src/API/Controllers/BaseController.php | 36 +++++++++- .../Controllers/SelectOptionsController.php | 36 ++++++++++ src/DTO/SelectOptionsRecord.php | 9 +++ src/DTO/SelectOptionsRequest.php | 13 ++++ src/DTO/SelectOptionsResponse.php | 12 ++++ 13 files changed, 225 insertions(+), 15 deletions(-) create mode 100644 modules/tipi_documento/src/API/Controllers/Models/SelectOptionsTipiDocumentoRequest.php create mode 100644 modules/tipi_documento/src/API/Controllers/Models/SelectOptionsTipiDocumentoResponse.php create mode 100644 modules/tipi_documento/src/API/Controllers/SelectOptionsTipiDocumentoController.php create mode 100644 src/API/Controllers/SelectOptionsController.php create mode 100644 src/DTO/SelectOptionsRecord.php create mode 100644 src/DTO/SelectOptionsRequest.php create mode 100644 src/DTO/SelectOptionsResponse.php diff --git a/.php-cs-fixer.php b/.php-cs-fixer.php index 85e9380be..038ffbb98 100644 --- a/.php-cs-fixer.php +++ b/.php-cs-fixer.php @@ -5,6 +5,7 @@ ->exclude('files') ->exclude('node_modules') ->exclude('vendor') + ->exclude('storage') ->ignoreDotFiles(true) ->ignoreVCS(true) ->in(__DIR__); diff --git a/modules/impostazioni/src/API/ImpostazioneResource.php b/modules/impostazioni/src/API/ImpostazioneResource.php index c416a6c55..2921d147d 100644 --- a/modules/impostazioni/src/API/ImpostazioneResource.php +++ b/modules/impostazioni/src/API/ImpostazioneResource.php @@ -29,8 +29,8 @@ controller: ListImpostazioniController::class, paginationEnabled: false, parameters: [ - 'ricerca' => new QueryParameter(property: 'hydra:freetextQuery', required: false), - 'sezione' => new QueryParameter(property: 'hydra:freetextQuery', required: false), + 'ricerca' => new QueryParameter(required: false), + 'sezione' => new QueryParameter(required: false), ] ), new Get( diff --git a/modules/tipi_documento/ajax/select.php b/modules/tipi_documento/ajax/select.php index d3e8bdfbd..39d695c5c 100644 --- a/modules/tipi_documento/ajax/select.php +++ b/modules/tipi_documento/ajax/select.php @@ -22,16 +22,16 @@ switch ($resource) { case 'tipi_documento': - $query = 'SELECT `co_tipi_documento`.`id`, `co_tipi_documento_lang`.`title` AS descrizione FROM `co_tipi_documento` |where| ORDER BY `title` ASC'; + $query = 'SELECT `co_tipi_documento`.`id`, `co_tipi_documento_lang`.`title` AS descrizione FROM `co_tipi_documento` LEFT JOIN `co_tipi_documento_lang` ON (`co_tipi_documento`.`id` = `co_tipi_documento_lang`.`id_record` AND `co_tipi_documento_lang`.`id_lang` = '.prepare(Locale::getDefault()->id).') |where| ORDER BY `title` ASC'; $where[] = '`co_tipi_documento`.`enabled` = 1'; - $where[] = '`dir`='.prepare($superselect['dir']); + $where[] = '`co_tipi_documento`.`dir`='.prepare($superselect['dir']); foreach ($elements as $element) { - $filter[] = '`id`='.prepare($element); + $filter[] = '`co_tipi_documento`.`id`='.prepare($element); } if (!empty($search)) { - $search_fields[] = '`title` LIKE '.prepare('%'.$search.'%'); + $search_fields[] = '`co_tipi_documento_lang`.`title` LIKE '.prepare('%'.$search.'%'); } $custom['link'] = 'module:Tipi documento'; diff --git a/modules/tipi_documento/src/API/Controllers/Models/SelectOptionsTipiDocumentoRequest.php b/modules/tipi_documento/src/API/Controllers/Models/SelectOptionsTipiDocumentoRequest.php new file mode 100644 index 000000000..b902be37e --- /dev/null +++ b/modules/tipi_documento/src/API/Controllers/Models/SelectOptionsTipiDocumentoRequest.php @@ -0,0 +1,10 @@ +init($request, SelectOptionsTipiDocumentoRequest::class); + $dir = $body->dir; + + if (empty($dir)) { + throw new InvalidMetadataException('Missing dir option'); + } + + $query = 'SELECT `co_tipi_documento`.`id`, `co_tipi_documento_lang`.`title` AS descrizione FROM `co_tipi_documento` LEFT JOIN `co_tipi_documento_lang` ON (`co_tipi_documento`.`id` = `co_tipi_documento_lang`.`id_record` AND `co_tipi_documento_lang`.`id_lang` = '.prepare(Locale::getDefault()->id).') |where| ORDER BY `title` ASC'; + + $where = ['`co_tipi_documento`.`enabled` = 1', '`co_tipi_documento`.`dir`='.prepare($dir)]; + $filter = []; + $search_fields = []; + foreach ((array) $body->retrieve_only_for as $element) { + $filter[] = '`co_tipi_documento`.`id`='.prepare($element); + } + + if (!empty($body->search)) { + $search_fields[] = '`co_tipi_documento_lang`.`title` LIKE '.prepare('%'.$body->search.'%'); + } + + $custom = [ + 'id' => 'id', + 'text' => 'descrizione', + ]; + $length = 200; + $query_results = \AJAX::selectResults($query, $where, $filter, $search_fields, [ + 'offset' => $body->page * $length, + 'length' => $length, + ], $custom); + + $results = $query_results['results']; + + // Applicazione della trasformazione dei link se specificata nelle opzioni + $link = 'module:Tipi documento'; + if (!empty($link) && !empty($results)) { + $results = \AJAX::applyLinkTransformation($results, $link); + } + + return new JsonResponse([ + 'results' => $results ?: [], + 'recordsFiltered' => $query_results['recordsFiltered'], + ]); + } + + protected function hasAccess($request): bool + { + return $this->hasModuleWriteAccess('Tipi documento'); + } +} diff --git a/modules/tipi_documento/src/API/TipoDocumentiResource.php b/modules/tipi_documento/src/API/TipoDocumentiResource.php index 7602a3363..a4fbb25bd 100644 --- a/modules/tipi_documento/src/API/TipoDocumentiResource.php +++ b/modules/tipi_documento/src/API/TipoDocumentiResource.php @@ -5,28 +5,34 @@ use ApiPlatform\Metadata\ApiResource; use ApiPlatform\Metadata\Delete; use ApiPlatform\Metadata\Get; -use ApiPlatform\Metadata\GetCollection; use ApiPlatform\Metadata\Post; use ApiPlatform\Metadata\Put; +use ApiPlatform\Metadata\QueryParameter; use Modules\Fatture\Tipo; use Modules\TipiDocumento\API\Controllers\CreateTipiDocumentoController; use Modules\TipiDocumento\API\Controllers\DeleteTipiDocumentoController; use Modules\TipiDocumento\API\Controllers\GetTipiDocumentoController; use Modules\TipiDocumento\API\Controllers\Models\CreateTipiDocumentoRequest; use Modules\TipiDocumento\API\Controllers\Models\CreateTipiDocumentoResponse; +use Modules\TipiDocumento\API\Controllers\Models\SelectOptionsTipiDocumentoResponse; use Modules\TipiDocumento\API\Controllers\Models\UpdateTipiDocumentoRequest; +use Modules\TipiDocumento\API\Controllers\SelectOptionsTipiDocumentoController; use Modules\TipiDocumento\API\Controllers\UpdateTipiDocumentoController; #[ApiResource( shortName: 'TipiDocumenti', operations: [ - /* - new GetCollection( - uriTemplate: '/tipi-documenti', - controller: ListTipiDocumentoController::class, - paginationEnabled: false, + new Get( + uriTemplate: '/select-options/tipi-documenti', + controller: SelectOptionsTipiDocumentoController::class, + parameters: [ + 'dir' => new QueryParameter(required: false), + 'page' => new QueryParameter(required: false), + 'search' => new QueryParameter(required: false), + 'retrieve_only_for' => new QueryParameter(required: false, schema: ['type' => 'array']), + ], + output: SelectOptionsTipiDocumentoResponse::class, ), - */ new Get( uriTemplate: '/tipo-documenti/{id}', controller: GetTipiDocumentoController::class, diff --git a/src/AJAX.php b/src/AJAX.php index 2c9b72668..a78e121df 100755 --- a/src/AJAX.php +++ b/src/AJAX.php @@ -311,7 +311,7 @@ protected static function getSelectResults($file, $resource, $elements = [], $li * * @return array */ - protected static function applyLinkTransformation($list, $link) + public static function applyLinkTransformation($list, $link) { foreach ($list as &$element) { // Gestione degli elementi con children (optgroup) diff --git a/src/API/Controllers/BaseController.php b/src/API/Controllers/BaseController.php index 813888e35..1b7c53af3 100644 --- a/src/API/Controllers/BaseController.php +++ b/src/API/Controllers/BaseController.php @@ -27,6 +27,39 @@ public function __construct(MappingError $error) } } +// https://www.php.net/manual/en/function.parse-str.php//76792 +function proper_parse_str($str) { + // result array + $arr = array(); + + // split on outer delimiter + $pairs = explode('&', $str); + + // loop through each pair + foreach ($pairs as $i) { + // split into name and value + list($name,$value) = explode('=', $i, 2); + + // if name already exists + if( isset($arr[$name]) ) { + // stick multiple values into an array + if( is_array($arr[$name]) ) { + $arr[$name][] = $value; + } + else { + $arr[$name] = array($arr[$name], $value); + } + } + // otherwise, simply stick it in a scalar + else { + $arr[$name] = $value; + } + } + + // return result array + return $arr; +} + abstract class BaseController extends Controller { abstract protected function hasAccess($request): bool; @@ -88,6 +121,7 @@ protected function init(Request $request, ?string $class_reference = null): mixe */ protected function _cast(Request $request, string $class_reference): mixed { + $query_params = proper_parse_str($_SERVER['QUERY_STRING']); try { return (new MapperBuilder()) ->allowUndefinedValues() @@ -99,7 +133,7 @@ protected function _cast(Request $request, string $class_reference): mixed ->mapper() ->map( $class_reference, - [...$request->route()->parameters(), ...$request->all()] + [...$request->route()->parameters(), ...$request->all(), ...$query_params] ); } catch (MappingError $error) { throw new InvalidInputException($error); diff --git a/src/API/Controllers/SelectOptionsController.php b/src/API/Controllers/SelectOptionsController.php new file mode 100644 index 000000000..04e87ee1d --- /dev/null +++ b/src/API/Controllers/SelectOptionsController.php @@ -0,0 +1,36 @@ +init($request, SelectOptionsRequest::class); + + $where = []; + $filter = []; + $search_fields = []; + + $custom = [ + 'id' => 'id', + 'text' => 'descrizione', + ]; + + require $file; + + if (!isset($results) && !empty($query)) { + $results = \AJAX::selectResults($query, $where, $filter, $search_fields, $limit, $custom); + } + + return new JsonResponse($results ?? null); + } + + + protected function hasAccess($request): bool + { + return true; + } +} \ No newline at end of file diff --git a/src/DTO/SelectOptionsRecord.php b/src/DTO/SelectOptionsRecord.php new file mode 100644 index 000000000..7d587b101 --- /dev/null +++ b/src/DTO/SelectOptionsRecord.php @@ -0,0 +1,9 @@ +|string + */ + public mixed $retrieve_only_for = []; +} diff --git a/src/DTO/SelectOptionsResponse.php b/src/DTO/SelectOptionsResponse.php new file mode 100644 index 000000000..c53506c33 --- /dev/null +++ b/src/DTO/SelectOptionsResponse.php @@ -0,0 +1,12 @@ +