gestione gare pubbliche
Non puoi selezionare più di 25 argomenti Gli argomenti devono iniziare con una lettera o un numero, possono includere trattini ('-') e possono essere lunghi fino a 35 caratteri.
 
 
 
 
 

2335 righe
76 KiB

<?php
/**
* Gestore delle API ANAC NPA
*/
require_once "api.trait.php";
trait NPAAPI
{
use API;
/**
* Retrieves endpoint settings for the given card
*
* @param mixed $card
* @return string|null
*/
public function getCardEndpoint(?String $card = null): ?string
{
if (empty($card)) {
$card = $this->scheda["id_scheda"];
}
foreach ($this->api_config as $key => $config) {
if (!empty($config["schede"]) && in_array($card, $config["schede"])) {
return $key;
}
}
return null;
}
public function cardCanDoRequest(?array &$reason = null)
{
$method = null;
if (!empty($this->scheda["codice"])) {
$apiEndpoint = $this->getCardEndpoint();
if ($this->checkIfCardNeedsXMLDocument() && ($this->scheda["stato"] == 10 || ($this->scheda["stato"] > 10 && $this->scheda["stato"] <= 40 && $this->scheda["errore"] == "SI"))) {
$method = "create";
} elseif (($this->scheda["stato"] == 20 || $this->scheda["stato"] == 40) && $this->scheda["errore"] == "NO") {
$method = "confirm";
} elseif (($this->scheda["stato"] == 50 || $this->scheda["stato"] == 90) && $this->scheda["errore"] == "NO" && self::hasToBePublished($this->scheda)) {
$method = "publish";
$apiEndpoint = "pubblicazioneAvvisi";
}
}
if ($method === null) $apiEndpoint = null;
return $this->canDoRequest($reason, $apiEndpoint, $method);
}
/**
* Risincronizza la scheda con i server ANAC
*
* @return boolean
*/
public function resyncScheda(?string &$message = null): bool {
$message = "";
$readData = $this->read();
$scheda = $readData->_scheda->body ?? null;
$uuid = $readData->_scheda->_idScheda ?? null;
$message = "Impossibile trovare i dati della scheda";
if(empty($scheda) || empty($uuid)) return false;
$scheda = json_decode(json_encode($scheda), true);
// Niente da aggiornare
if(empty($scheda["anacForm"])) return true;
$schedaAttuale = $this->getCardData();
$schedaAttuale["anacForm"] = $scheda["anacForm"];
$this->updateScheda(["dati" => json_encode($schedaAttuale, JSON_PRETTY_PRINT), "uuid" => $uuid]);
return true;
}
/**
* Associa al form attuale gli XML necessari per la richiesta
*
* @param array $data La richiesta attuale
* @param string|null $reason In caso di fallimento viene popolato con il messaggio d'errore
* @return boolean True se va con successo
*/
function attachXMLDocumentToRequestBody(array &$data, ?string &$reason = null): bool
{
$this->checkIfCardNeedsXMLDocument();
if ($this->check["eform"]["required"]) {
$data["eform"] = null;
if (!empty($this->check["eform"]["ted"])) {
$guue = $this->check["eform"]["ted"];
if (!$guue->isReadyForAnac()) {
$reason = "La scheda n°{$guue->info["codice"]} deve essere in stato 'Pronto per ANAC' prima di essere trasmessa.";
return false;
}
$data["eform"] = base64_encode($guue->toXML());
}
}
if ($this->check["espd"]["required"]) {
$data["espd"] = null;
if (!empty($this->check["espd"]["xml"])) {
$data["espd"] = base64_encode($this->check["espd"]["xml"]);
}
}
return true;
}
/**
* Rretrieves the codes associated with cost centers
*
* @param String $identifier
*
* @return Array
*/
public static function getCodiciCentriDiCostoFromANAC_Cache(String $identifier): ?array
{
global $pdo;
// Nota: su necessità questo metodo può diventare statico
$identifier = trim(strtoupper($identifier));
$centri = $pdo->go(
"SELECT data FROM b_centri_costo_ws_cache WHERE cf = :cf AND created_at > DATE_SUB(NOW(), INTERVAL 1 DAY)",
["cf" => $identifier]
)->fetch(PDO::FETCH_ASSOC);
if (!is_array($centri) || empty($centri)) {
return null;
}
return json_decode($centri["data"], TRUE);
}
/**
* Rretrieves the codes associated with cost centers
*
* @param String $identifier
*
* @return Array
*/
public function getCodiciCentriDiCostoFromAnac(String $identifier): ?array
{
$cachedResponse = $this->getCodiciCentriDiCostoFromANAC_Cache($identifier);
if (!empty($cachedResponse)) return $cachedResponse;
$apiConfig = $this->api_config["ausaController"];
$claims = $this->getAnacJwsCustomClaims(
$apiConfig["endpoint"],
$this->getUUID(),
"",
DelegheRoles::UNAUTHORIZED
);
$response = $this->apiCall("ausaController", "getBy", ["codiceFiscale" => $identifier], $claims);
if (is_json($response)) {
$response = json_decode($response);
if ($response->status == "OK" && !empty($response->items)) {
$centri = [];
foreach ($response->items as $item) {
if (!empty($item->scheda->stazioneAppaltante->centriDiCosto)) {
foreach ($item->scheda->stazioneAppaltante->centriDiCosto as $center) {
if(!empty($center->idCentroDiCosto)) {
$centri[$center->idCentroDiCosto] = $center->denominazioneCentroDiCosto ?? "Centro di costo";
}
}
}
}
if(!empty($centri)) {
global $pdo;
$pdo->go(
"INSERT INTO b_centri_costo_ws_cache (cf, data) VALUES (:cf, :data) ON DUPLICATE KEY UPDATE data = :data, created_at = NOW()",
[
":cf" => $identifier,
":data" => json_encode($centri),
]
);
}
return $centri;
}
}
return [];
}
/**
* Verifica l'esito di una modifica
*
* @param string|null $message Se passato come riferimento, viene popolato in caso di errore con il messaggio d'errore
* @return bool|null
*/
public function checkModify(?string &$message = null): ?bool
{
// Popoliamo message con qualcosa di generico
$message = __("Errore interno sconosciuto ");
// Istanziamo l'NPA rettifica
$npa_originale = self::init(null, $this->scheda["codice_modifica"]);
// Andiamo a verificare che ci sia un'operazione di rettifica in attesa
$modifica = $this->getLastOperation(null, "MODIFICA_AVVISO", NULL);
$salva = new Salva();
$salva->debug = false;
$salva->nome_tabella = "b_npa_operazioni_schede";
date_default_timezone_set('Europe/Rome');
if(empty($modifica)) {
$modifica = [
"codice_scheda" => $this->scheda["codice"],
"operazione" => "MODIFICA_AVVISO",
];
$salva->operazione = "INSERT";
$salva->expect = ["codice_scheda", "operazione", "esito", "risultato"];
} else {
$salva->operazione = "UPDATE";
$salva->expect = ["codice", "esito", "risultato"];
}
$operazione = "AV_MOD";
// Andiamo a chiedere al server lo stato della conferma
$result = $npa_originale->resultNew($operazione);
if (!empty($result)) {
if ($result->status == 200) {
// Esito non trovato
if (empty($result->listaEsiti)) {
$modifica["risultato"] = serialize([]);
$modifica["esito"] = "KO";
$salva->oggetto = $modifica;
if ($salva->save()) {
$message = __("Impossibile verificare l'esito di modifica. Provare ad inviare nuovamente la modifica. ");
$this->updateScheda(["errore" => "SI", "stato" => 0]);
}
return false;
}
$esito = reset($result->listaEsiti); // Ci interessa solo il primo
if ($esito->esito->codice == "KO") {
$modifica["esito"] = "KO";
$modifica["risultato"] = serialize($esito);
$salva->oggetto = $modifica;
$message = __("Vi è stato un problema con la modifica dell'avviso. ");
// Impostiamo lo stato di errore
if ($salva->save()) {
// Appendo al messaggio per distinguere il caso in cui non sono state salvate le info
$message .= " " . __("L'errore sarà disponibile al ricaricamento della pagina.");
$this->updateScheda(["errore" => "SI", "stato" => 0]);
}
// Ha senso ritornare false con un errore parlante
return false;
}
if ($esito->esito->codice == "WT") {
$message = __("INFO - L'avviso è in corso di modifica. Questo processo solitamente richiede diverse ore.<br>Verifica l'esito nuovamente più tardi.");
// Ha senso ritornare false con un errore parlante
return false;
}
if ($esito->esito->codice == "OK") {
$modifica["esito"] = "OK";
$modifica["risultato"] = serialize($esito);
$salva->oggetto = $modifica;
if ($salva->save()) {
// Per via di una svista di ANAC, l'esito OK
// può non contenere i nuovi id, quindi ce li andiamo a cercare...
$esito_con_nuovi_id = null;
foreach ($result->listaEsiti as $_esito) {
if (!empty($_esito->idNuovaScheda)) {
$esito_con_nuovi_id = $_esito;
break;
}
}
if ($esito_con_nuovi_id !== null) {
$info = new stdClass;
if (!empty($npa_originale->scheda["info"])) {
$info = unserialize($npa_originale->scheda["info"]);
}
$info->idNuovaScheda = $esito_con_nuovi_id->idNuovaScheda;
$info->idNuovoAvviso = $esito_con_nuovi_id->idNuovoAvviso;
$npa_originale->updateScheda(["info" => serialize($info)]);
$this->updateScheda(["uuid" => $info->idNuovaScheda , "info" => serialize($info)]);
} else {
$message = __("I server ANAC hanno processato in maniera errata questa modifica.");
$this->updateScheda(["stato" => 100, "errore" => 'SI']);
return false;
}
// Imposta la vecchia scheda su "Modificata"
$npa_originale->updateScheda(["stato" => -10]);
// Cambiamo lo stato della scheda GUUE se presente
$this->publishGUUEOnSuccessfulPublication();
$this->updateScheda(["stato" => 100, "errore" => 'NO']);
$metadata = $this->metadataSchede[$this->scheda["id_scheda"]];
if($metadata["lotto"] == false && $metadata["categoria"] != "pianificazione") {
return $this->getCIG($message);
}
return true;
}
}
}
}
$message = __("Errore durante la verifica dello stato di modifica");
return false;
}
/**
* Invia una modifica avviso o ne verifica lo stato
*
* @param string|null $message Se passato come riferimento, viene popolato in caso di errore con il messaggio d'errore
* @return Bool|null
*/
public function modify(?string &$message = null): ?Bool
{
if (!$this->getServiceStatusBool(true)) {
$message = __("Impossibile completare la richiesta: i server ANAC non sono raggiungibili. Riprova tra qualche minuto.");
return false;
}
// Popoliamo message con qualcosa di generico
$message = __("Errore interno sconosciuto ");
$salva = new Salva();
$salva->debug = false;
$salva->nome_tabella = "b_npa_operazioni_schede";
$salva->operazione = "INSERT";
$salva->expect = ["codice_scheda", "operazione"];
date_default_timezone_set('Europe/Rome');
// Istanziamo l'NPA originale
$npa_originale = self::init(null, $this->scheda["codice_modifica"]);
// Andiamo a verificare che ci sia un'operazione di rettifica in attesa
$modifica = $this->getLastOperation(null, "MODIFICA_AVVISO", NULL);
// Se c'è
if (!empty($modifica)) {
return $this->checkModify($message);
}
$apiEndpoint = "pubblicazioneAvvisi";
$body = $this->getCardData();
$this->organizeRepeteableGroups($body);
$XMLattachedSuccessfully = $this->attachXMLDocumentToRequestBody($body, $xml_message);
if (!$XMLattachedSuccessfully) {
$message = __($xml_message);
return false;
}
$apiMethod = "update";
if (!isset($npa_originale->scheda) || empty($npa_originale->scheda["info"])) {
$message = __("Errore nella scheda originale. Contattare l'Help Desk tecnico.") . " (err: info)";
return false;
}
// Otteniamo le info da cui ottenere gli UUID
$modifica_info = unserialize($npa_originale->scheda["info"]);
if (empty($modifica_info->idAvviso)) {
$message = __("Errore nella scheda originale. Contattare l'Help Desk tecnico.") . " (err: idAvviso)";
return false;
}
if (empty($this->fascicolo["id_appalto"]) && empty($this->fascicolo["id_pianificazione"])) {
$message = __("Errore nella scheda originale. Contattare l'Help Desk tecnico.") . " (err: idAppalto/idPianificazione)";
return false;
}
$request = [
"scheda" => [
"codice" => [
"idTipologica" => "codiceScheda",
"codice" => $this->scheda["id_scheda"]
],
"versione" => str_replace("v", "", $this->version),
"_stato" => [
"idTipologica" => "statoScheda",
"codice" => "CONF"
],
"_dataCreazione" => date("c", strtotime($this->scheda["timestamp_creazione"])),
"body" => $body
]
];
$request["idAvviso"] = $modifica_info->idAvviso;
if (empty($this->fascicolo["id_appalto"])) {
$request["idPianificazione"] = $this->fascicolo["id_pianificazione"];
} else {
$request["idAppalto"] = $this->fascicolo["id_appalto"];
}
// Effettuiamo la richiesta
$result_str = $this->apiCall($apiEndpoint, $apiMethod, $request, null, [], $apiMessage);
$result = self::manageANACResponse($result_str, $message);
if($result === null) {
$message = empty($apiMessage) ? $message : $apiMessage;
return false;
}
$salva->oggetto = [
"codice_scheda" => $this->scheda["codice"],
"operazione" => "MODIFICA_AVVISO"
];
// Verifichiamo la risposta
// Se non ci viene fornita una risposta con successo
if ($result->status !== 200) {
if(isset($result->errori[0]->codice)) {
$error_codes = array_column(json_decode(json_encode($result->errori), true), 'codice');
if (in_array('ERR50', $error_codes) || in_array('ERR44', $error_codes)) {
if($this->checkModify($message)) {
return true;
}
}
}
// Aggiorniamo lo status
$salva->expect = array_merge($salva->expect, ["esito", "risultato"]);
$salva->oggetto = array_merge($salva->oggetto, ["esito" => "KO", "risultato" => serialize($result)]);
// Settiamo errore su SI
if ($salva->save()) {
$this->updateScheda(["errore" => "SI", "stato" => 20]);
}
$message = __("Errore in fase di modifica della scheda. Al ricaricamento della pagina verifica gli Avvisi.");
return false;
}
// Rettifica andata con successo
if ($salva->save()) {
$this->updateScheda(["errore" => "NO", "stato" => 90]);
return true;
}
// Fallback generico
return false;
}
/**
* Verifica l'esito di una rettifica
*
* @param string|null $message Se passato come riferimento, viene popolato in caso di errore con il messaggio d'errore
* @return bool|null
*/
public function checkRectify(?string &$message = null): ?bool
{
// Popoliamo message con qualcosa di generico
$message = __("Errore interno sconosciuto ");
// Istanziamo l'NPA rettifica
$npa_rettifica = self::init(null, $this->scheda["codice_rettifica"]);
// Andiamo a verificare che ci sia un'operazione di rettifica in attesa
$rettifica = $this->getLastOperation(null, "RETTIFICA", NULL);
$salva = new Salva();
$salva->debug = false;
$salva->nome_tabella = "b_npa_operazioni_schede";
date_default_timezone_set('Europe/Rome');
if(empty($rettifica)) {
$rettifica = [
"codice_scheda" => $this->scheda["codice"],
"operazione" => "RETTIFICA",
];
$salva->operazione = "INSERT";
$salva->expect = ["codice_scheda", "operazione", "esito", "risultato"];
} else {
$salva->operazione = "UPDATE";
$salva->expect = ["codice", "esito", "risultato"];
}
$operazione = "AV_RETT";
// Andiamo a chiedere al server lo stato della conferma
$result = $npa_rettifica->resultNew($operazione);
if (!empty($result)) {
if ($result->status == 200) {
// Esito non trovato
if (empty($result->listaEsiti)) {
$rettifica["risultato"] = serialize([]);
$rettifica["esito"] = "KO";
$salva->oggetto = $rettifica;
if ($salva->save()) {
$message = __("Impossibile verificare l'esito di rettifica. Provare ad inviare nuovamente la rettifica. ");
$this->updateScheda(["errore" => "SI", "stato" => 0]);
}
return false;
}
$wtResult = null;
$okResult = null;
$koResult = null;
$lastResult = $result->listaEsiti[0];
foreach($result->listaEsiti as $esito) {
switch($esito->esito->codice) {
case "OK":
if($okResult === null) $okResult = $esito;
break;
case "KO":
if($koResult === null) $koResult = $esito;
break;
case "WT":
if($wtResult === null) $wtResult = $esito;
break;
}
}
// Fallito / Non valido
if(empty($okResult) && empty($wtResult) || $lastResult == $koResult) {
// Se abbiamo un errore 44 e c'è ancora un WT vuol dire che c'è un accavallamento di rettifiche, quindi non falliamo
$errorCode = $koResult->errori[0]->codice ?? null;
if(!($errorCode == "ERR44" && !empty($wtResult))) {
$rettifica["esito"] = "KO";
if(!empty($koResult))
$rettifica["risultato"] = serialize($koResult);
$salva->oggetto = $rettifica;
$message = __("Vi è stato un problema con la rettifica della scheda. ");
// Impostiamo lo stato di errore
if ($salva->save()) {
// Appendo al messaggio per distinguere il caso in cui non sono state salvate le info
$message .= " " . __("L'errore sarà disponibile al ricaricamento della pagina.");
$this->updateScheda(["errore" => "SI", "stato" => 0]);
}
// Ha senso ritornare false con un errore parlante
return false;
}
}
// Successo
if(!empty($okResult)) {
$rettifica["esito"] = "OK";
$rettifica["risultato"] = serialize($okResult);
$salva->oggetto = $rettifica;
if ($salva->save()) {
// Per via di una svista di ANAC, l'esito OK
// può non contenere i nuovi id, quindi ce li andiamo a cercare...
$esito_con_nuovi_id = null;
foreach ($result->listaEsiti as $_esito) {
if (!empty($_esito->idNuovaScheda)) {
$esito_con_nuovi_id = $_esito;
break;
}
}
if ($esito_con_nuovi_id !== null) {
$info = new stdClass;
if (!empty($npa_rettifica->scheda["info"])) {
$info = unserialize($npa_rettifica->scheda["info"]);
}
$info->idNuovaScheda = $esito_con_nuovi_id->idNuovaScheda;
$info->idNuovoAvviso = $esito_con_nuovi_id->idNuovoAvviso;
$npa_rettifica->updateScheda(["info" => serialize($info)]);
$this->updateScheda(["uuid" => $info->idNuovaScheda,"info" => serialize($info)]);
} else {
$message = __("I server ANAC hanno processato in maniera errata questa rettifica.");
$this->updateScheda(["stato" => 100, "errore" => 'SI']);
return false;
}
// Imposta la vecchia scheda su "Rettificata"
$npa_rettifica->updateScheda(["stato" => -20]);
// Cambiamo lo stato della scheda GUUE se presente
$this->publishGUUEOnSuccessfulPublication();
$this->updateScheda(["stato" => 100, "errore" => 'NO']);
return true;
}
}
// In attesa
$this->updateScheda(["errore" => "NO", "stato" => 90]);
$message = __("INFO - L'avviso è in corso di rettifica. Questo processo di solito richiede oltre 24 ore.<br>Verifica l'esito nuovamente più tardi.");
// Ha senso ritornare false con un errore parlante
return false;
}
}
$message = __("Errore durante la verifica dello stato di rettifica");
return false;
}
/**
* Invia una rettifica o ne verifica lo stato
*
* @param string|null $message Se passato come riferimento, viene popolato in caso di errore con il messaggio d'errore
* @return Bool|null
*/
public function rectify(?string &$message = null): ?Bool
{
if (!$this->getServiceStatusBool(true)) {
$message = __("Impossibile completare la richiesta: i server ANAC non sono raggiungibili. Riprova tra qualche minuto.");
return false;
}
// Popoliamo message con qualcosa di generico
$message = __("Errore interno sconosciuto ");
$salva = new Salva();
$salva->debug = false;
$salva->nome_tabella = "b_npa_operazioni_schede";
$salva->operazione = "INSERT";
$salva->expect = ["codice_scheda", "operazione"];
date_default_timezone_set('Europe/Rome');
// Istanziamo l'NPA rettifica
$npa_rettifica = self::init(null, $this->scheda["codice_rettifica"]);
// Andiamo a verificare che ci sia un'operazione di rettifica in attesa
$rettifica = $this->getLastOperation(null, "RETTIFICA", NULL);
// Se c'è
if (!empty($rettifica)) {
return $this->checkRectify($message);
}
$apiEndpoint = "pubblicazioneAvvisi";
$schedaApiEndpoint = $this->getCardEndpoint();
$body = $this->getCardData();
$this->organizeRepeteableGroups($body);
$XMLattachedSuccessfully = $this->attachXMLDocumentToRequestBody($body, $xml_message);
if (!$XMLattachedSuccessfully) {
$message = __($xml_message);
return false;
}
$apiMethod = "amend";
if (!isset($npa_rettifica->scheda) || empty($npa_rettifica->scheda["info"])) {
$message = __("Errore nella scheda originale. Contattare l'Help Desk tecnico.") . " (err: info)";
return false;
}
// Otteniamo le info da cui ottenere gli UUID
$rettifica_info = unserialize($npa_rettifica->scheda["info"]);
if (empty($rettifica_info->idAvviso)) {
$message = __("Errore nella scheda originale. Contattare l'Help Desk tecnico.") . " (err: idAvviso)";
return false;
}
if (empty($this->fascicolo["id_appalto"]) && empty($this->fascicolo["id_pianificazione"])) {
$message = __("Errore nella scheda originale. Contattare l'Help Desk tecnico.") . " (err: idAppalto/idPianificazione)";
return false;
}
$request = [
"scheda" => [
"codice" => [
"idTipologica" => "codiceScheda",
"codice" => $this->scheda["id_scheda"]
],
"versione" => str_replace("v", "", $this->version),
"_stato" => [
"idTipologica" => "statoScheda",
"codice" => "CONF"
],
"_dataCreazione" => date("c", strtotime($this->scheda["timestamp_creazione"])),
"body" => $body
]
];
$request["idAvviso"] = $npa_rettifica->getANACNoticeID();
if (empty($this->fascicolo["id_appalto"])) {
$request["idPianificazione"] = $this->fascicolo["id_pianificazione"];
} else {
$request["idAppalto"] = $this->fascicolo["id_appalto"];
}
// Effettuiamo la richiesta
$result_str = $this->apiCall($apiEndpoint, $apiMethod, $request, null, [], $apiMessage);
$result = self::manageANACResponse($result_str, $message);
if($result === null) {
$message = empty($apiMessage) ? $message : $apiMessage;
return false;
}
$salva->oggetto = [
"codice_scheda" => $this->scheda["codice"],
"operazione" => "RETTIFICA"
];
// Verifichiamo la risposta
// Se non ci viene fornita una risposta con successo
if ($result->status !== 200) {
if(isset($result->errori[0]->codice)) {
$error_codes = array_column(json_decode(json_encode($result->errori), true), 'codice');
if (in_array('ERR50', $error_codes) || in_array('ERR44', $error_codes)) {
if($this->checkRectify($message)) {
return true;
}
if(str_starts_with($message, "INFO -")) {
return false;
}
}
}
// Aggiorniamo lo status
$salva->expect = array_merge($salva->expect, ["esito", "risultato"]);
$salva->oggetto = array_merge($salva->oggetto, ["esito" => "KO", "risultato" => serialize($result)]);
// Settiamo errore su SI
if ($salva->save()) {
$this->updateScheda(["errore" => "SI", "stato" => 20]);
}
$message = __("Errore in fase di rettifica della scheda. Al ricaricamento della pagina verifica gli Avvisi.");
return false;
}
// Rettifica andata con successo
if ($salva->save()) {
$this->updateScheda(["errore" => "NO", "stato" => 90]);
return true;
}
// Fallback generico
return false;
}
/**
* Elimina l'avviso
*
* @param string|null $message Se passato come riferimento, viene popolato in caso di errore con il messaggio d'errore
* @return ?Bool
*/
public function deleteNotice(?string &$message = null): ?Bool
{
if (!$this->getServiceStatusBool(true)) {
$message = __("Impossibile completare la richiesta: i server ANAC non sono raggiungibili. Riprova tra qualche minuto.");
return false;
}
// Popoliamo message con qualcosa di generico
$message = __("Errore interno sconosciuto ");
global $config;
$salva = new Salva();
$salva->debug = false;
$salva->nome_tabella = "b_npa_operazioni_schede";
$salva->operazione = "INSERT";
$salva->oggetto = [
"codice_scheda" => $this->scheda["codice"],
"operazione" => "ELIMINA_AVVISO"
];
$salva->expect = ["codice_scheda", "operazione"];
date_default_timezone_set('Europe/Rome');
// Andiamo a verificare che ci sia un'operazione di pubblicazione in attesa
$elimina = $this->getLastOperation(null, "ELIMINA_AVVISO", NULL);
// Se c'è
if (!empty($elimina)) {
$salva->operazione = "UPDATE";
$salva->expect = ["codice", "esito", "risultato"];
// Andiamo a chiedere al server lo stato della pubblicazione
$result = $this->result("AV_SOSP");
if (!empty($result)) {
if ($result->status == 200) {
if (empty($result->listaEsiti)) {
$message = __("Impossibile verificare l'esito dell'operazione. Contatta l'Help Desk tecnico");
return false;
}
$result = end($result->listaEsiti);
if ($result->esito->codice == "WT") {
$message = __("INFO - La scheda non è ancora stata cancellata. Ricontrolla più tardi.");
return false;
}
// Cancellazione fallita
if ($result->esito->codice == "KO") {
$elimina["esito"] = "KO";
$elimina["risultato"] = serialize($result);
$salva->oggetto = $elimina;
$message = __("Vi è stato un problema con la cancellazione dell'avviso.");
if ($salva->save()) {
$this->updateScheda(["pending_cancellazione" => "N"]);
}
// Ha senso ritornare false con un errore parlante
return false;
}
if ($result->esito->codice == "OK") {
$elimina["esito"] = "OK";
$elimina["risultato"] = serialize($result);
$salva->oggetto = $elimina;
if ($salva->save()) {
$this->resetGUUEonSoftDelete();
if (count($this->getCards(false)) === 1) {
// Creiamo una revisione del fascicolo
$this->createDossierRevision();
}
$this->updateScheda(["soft_delete" => "S"]);
// Verifichiamo che l'appalto ora sia vuoto
return true;
}
}
}
}
$elimina["esito"] = "KO";
$elimina["risultato"] = serialize($result);
$salva->oggetto = $elimina;
if ($salva->save()) {
$this->updateScheda(["pending_cancellazione" => "N"]);
}
$message = __("Errore durante la verifica dello stato di cancellazione");
return false;
}
$pubblicazioneAvvisi = $this->api_config["pubblicazioneAvvisi"];
if (empty($pubblicazioneAvvisi)) {
$message .= __LINE__; // Segnaposto per trovare questo errore
return false;
}
$info = unserialize($this->scheda["info"]);
// Verifichiamo sia popolato l'id avviso
if (empty($info->idAvviso)) {
$message = __("Problema interno con la cancellazione. Contattare l'Help Desk tecnico.");
$today = date("Ymd");
error_log("[" . date('Y-m-d H:i:s') . "] - {$_SESSION["ente"]->getInfo()["dominio"]}" . PHP_EOL .
"\t" . __FUNCTION__ . PHP_EOL .
"\tErrore idAvviso inesistente " . PHP_EOL, 3, "{$config["npaFolder"]}/{$today}.log");
return null;
}
$request = $this->getKeysAndUUIDsForCurrentRequest();
$request["idAvviso"] = $this->getANACNoticeID();
// Effettuiamo la richiesta
$result_str = $this->apiCall("pubblicazioneAvvisi", "delete", $request, null, [], $apiMessage);
$result = self::manageANACResponse($result_str, $message);
if($result === null) {
$message = empty($apiMessage) ? $message : $apiMessage;
return false;
}
// Verifichiamo la risposta
if (!empty($result->status) && ($result->status == 200)) {
if ($salva->save()) {
$this->updateScheda(["pending_cancellazione" => "S"]);
// Workaround per UX
die('window.location.reload()');
return true;
}
$message .= __LINE__;
return false;
}
if (isset($result->errori)) {
$error_codes = array_column(json_decode(json_encode($result->errori), true), 'codice');
$error_codes_str = implode(", ", $error_codes);
if (in_array('ERR16', $error_codes) || in_array('ERR58', $error_codes)) {
$message = __("Lo stato dell'appalto non è compatibile con la cancellazione di questo avviso ({$error_codes_str})");
} elseif (in_array('ERR48', $error_codes) || in_array('ERR51', $error_codes)) {
$message = __("Questa tipologia di avviso non è cancellabile, oppure la pubblicazione potrebbe essere in stadio troppo avanzato per richiederne la cancellazione ({$error_codes_str})");
} else {
$message = __("Impossibile cancellare, il server risponde con {$error_codes_str}");
}
} else {
$message = __("Errore in fase di cancellazione dell'avviso.");
}
$salva->expect = array_merge($salva->expect, ["esito", "risultato"]);
$salva->oggetto = array_merge($salva->oggetto, ["esito" => "KO", "risultato" => serialize($result)]);
if ($salva->save()) {
$this->updateScheda(["pending_cancellazione" => "N"]);
}
return false;
}
/**
* Delete a card
*
* @param string|null $message Se passato come riferimento, viene popolato in caso di errore con il messaggio d'errore
* @return Bool|null
*/
public function delete(?string &$message = null): ?Bool
{
if (!$this->getServiceStatusBool(true)) {
$message = __("Impossibile completare la richiesta: i server ANAC non sono raggiungibili. Riprova tra qualche minuto.");
return false;
}
// Popoliamo message con qualcosa di generico
$message = __("Errore interno sconosciuto ");
global $config;
date_default_timezone_set('Europe/Rome');
$apiEndpoint = $this->scheda["stato"] > NuovaPiattaformaAppalti::TRASMITTED_STATO ? "pubblicazioneAvvisi" : $this->getCardEndpoint();
if (empty($apiEndpoint)) {
$message .= __LINE__; // Segnaposto per trovare questo errore
return false;
}
$body = json_decode($this->scheda["dati"], TRUE);
$this->organizeRepeteableGroups($body);
$apiMethod = "delete";
$request = $uuids = $this->getKeysAndUUIDsForCurrentRequest();
// Effettuiamo la richiesta
$result_str = $this->apiCall($apiEndpoint, $apiMethod, $request, null, [], $apiMessage);
$result = self::manageANACResponse($result_str, $message);
if($result === null) {
$message = empty($apiMessage) ? $message : $apiMessage;
return false;
}
$operazione = new Salva();
$operazione->debug = false;
$operazione->nome_tabella = "b_npa_operazioni_schede";
$operazione->operazione = "INSERT";
$operazione->expect = ["codice_scheda", "operazione", "esito", "risultato"];
$operazione->oggetto = [
"codice_scheda" => $this->scheda["codice"],
"operazione" => "ELIMINA",
"esito" => "KO",
"risultato" => serialize($result)
];
$message = __("Errore sconosciuto in fase di eliminazione della scheda. ");
// Verifichiamo che la richiesta sia andata con successo
if ($result->status == 200) {
$this->scheda["errore"] = "NO";
$operazione->oggetto["esito"] = "OK";
} else {
$operazione->oggetto["esito"] = "KO";
$operazione->save();
if (!empty($result->errori)) {
$error_codes = array_column(json_decode(json_encode($result->errori), true), 'codice');
$error_codes_str = implode(", ", $error_codes);
if (in_array('ERR16', $error_codes) || in_array('ERR58', $error_codes)) {
$message = __("Lo stato dell'appalto non è compatibile con la cancellazione di questa scheda ({$error_codes_str})");
} else {
$message = __("Impossibile cancellare, il server risponde con {$error_codes_str}");
}
/*$bind = [":codice" => $this->scheda["codice"], ":errore" => "SI"];
$sql = "UPDATE b_npa_schede SET errore = :errore WHERE codice = :codice";
$ris = $this->pdo->go($sql, $bind);*/
}
return false;
}
$this->resetGUUEonSoftDelete();
// Cancelliamo la scheda
$bind = [":codice" => $this->scheda["codice"], "soft_delete" => "S"];
$schedeRimanenti = $this->getCards(false);
$resetDossier = count($schedeRimanenti) == 1;
if ($resetDossier)
$this->createDossierRevision();
$sql = "UPDATE b_npa_schede SET soft_delete = :soft_delete WHERE codice = :codice";
$ris = $this->pdo->go($sql, $bind);
// Se abbiamo cancellato l'appalto ricreiamo una nuova revisione del fascicolo
if ($apiEndpoint === "comunicaAppalto") {
$operazione->oggetto["operazione"] = "ELIMINA_APPALTO";
}
if ($apiEndpoint === "pianificazioneAppalto") {
$operazione->oggetto["operazione"] = "ELIMINA_PIANO";
}
$operazione->save();
return true;
}
private function checkAndRetrieveUniqueIdentifierForCard(string $type, array $uuids) : string {
switch($type) {
case 'pianificazione':
if(isset($uuids["idPianificazione"])) {
return $uuids["idPianificazione"];
}
break;
case 'appalto':
if(isset($uuids["idAppalto"])) {
return $uuids["idAppalto"];
}
break;
default:
if(isset($uuids["idScheda"])) {
return $uuids["idScheda"];
}
break;
}
return $this->getUUID("uuid-non-esistente");
}
/**
* Send the card
*
* @param string|null $message Se passato come riferimento, viene popolato in caso di errore con il messaggio d'errore
* @return Bool|null
*/
public function send(?string &$message = null): ?Bool
{
if (!$this->getServiceStatusBool(true)) {
$message = __("Impossibile completare la richiesta: i server ANAC non sono raggiungibili. Riprova tra qualche minuto.");
return false;
}
if (!$this->canBeSent()) {
$message = __("La scheda non è in uno stato valido per questa operazione.");
return false;
}
// Popoliamo message con qualcosa di generico
$message = __("Errore interno sconosciuto ");
$retval = false;
global $config;
date_default_timezone_set('Europe/Rome');
$apiEndpoint = $this->getCardEndpoint();
if (empty($apiEndpoint)) {
$message .= __LINE__; // Segnaposto per trovare questo errore
return false;
}
$apiConfig = $this->api_config[$apiEndpoint];
$body = $this->getCardData();
$this->organizeRepeteableGroups($body);
$XMLattachedSuccessfully = $this->attachXMLDocumentToRequestBody($body, $xml_message);
if (!$XMLattachedSuccessfully) {
$message = __($xml_message);
return false;
}
$stazioniAppaltanti = recursive_array_key_search_all("stazioniAppaltanti", $body)[0] ?? [];
if(!empty($stazioniAppaltanti)) {
$this->updateFascicolo(["stazioni_appaltanti" => json_encode($stazioniAppaltanti)]);
}
$apiMethod = "create";
$request = $uuids = $this->getKeysAndUUIDsForCurrentRequest();
$uuid = $this->checkAndRetrieveUniqueIdentifierForCard($apiConfig["type"], $uuids);
if (!empty($this->scheda["uuid"])) {
$apiMethod = "update";
}
$request = array_merge($request, [
"scheda" => [
"codice" => [
"idTipologica" => "codiceScheda",
"codice" => $this->scheda["id_scheda"]
],
"versione" => str_replace("v", "", $this->version),
"_stato" => [
"idTipologica" => "statoScheda",
"codice" => "CONF"
],
"_dataCreazione" => date("c", strtotime($this->scheda["timestamp_creazione"])),
"body" => $body
]
]);
// Effettuiamo la richiesta
$result_str = $this->apiCall($apiEndpoint, $apiMethod, $request, null, [], $apiMessage);
$result = self::manageANACResponse($result_str, $message);
if($result === null) {
$message = empty($apiMessage) ? $message : $apiMessage;
return false;
}
$this->scheda["timestamp_trasmissione"] = date("d/m/Y H:i:s");
$this->scheda["stato"] = 20;
$this->scheda["errore"] = "SI";
$this->scheda["type"] = $apiConfig["type"];
$salva = new Salva();
$salva->debug = false;
$salva->nome_tabella = "b_npa_schede";
$salva->operazione = "UPDATE";
$salva->expect = ["codice", "uuid", "type", "stato", "timestamp_trasmissione", "errore"];
$operazione = new Salva();
$operazione->debug = false;
$operazione->nome_tabella = "b_npa_operazioni_schede";
$operazione->operazione = "INSERT";
$operazione->expect = ["codice_scheda", "operazione", "esito", "risultato"];
$operazione->oggetto = [
"codice_scheda" => $this->scheda["codice"],
"operazione" => "TRASMETTI",
"esito" => "KO",
"risultato" => serialize($result)
];
$message = __("Errore in fase di invio della scheda. Al ricaricamento della pagina verifica gli Avvisi.");
// Verifichiamo che la richiesta sia andata con successo
if ($result->status == 200) {
$this->scheda["errore"] = "NO";
$operazione->oggetto["esito"] = "OK";
if (!empty($result->idPianificazione) && empty($this->fascicolo["id_pianificazione"])) {
$this->fascicolo["id_pianificazione"] = $result->idPianificazione;
$this->pdo->go("UPDATE b_npa SET id_pianificazione = :id_pianificazione WHERE codice = :codice", [":codice" => $this->fascicolo["codice"], ":id_pianificazione" => $this->fascicolo["id_pianificazione"]]);
}
if (!empty($result->idAppalto) && empty($this->fascicolo["id_appalto"])) {
$this->fascicolo["id_appalto"] = $result->idAppalto;
$this->pdo->go("UPDATE b_npa SET id_appalto = :id_appalto WHERE codice = :codice", [":codice" => $this->fascicolo["codice"], ":id_appalto" => $this->fascicolo["id_appalto"]]);
}
if (!empty($result->idScheda)) {
$this->scheda["uuid"] = $result->idScheda;
} else {
$result = $this->read();
if (!empty($result->appalto->scheda->_idScheda)) {
$this->scheda["uuid"] = $result->appalto->scheda->_idScheda;
}
if (!empty($result->piano->scheda->_idScheda)) {
$this->scheda["uuid"] = $result->piano->scheda->_idScheda;
}
}
// Popoliamo il notice_id inviato
if ($this->check["eform"]["required"]) {
$salva->expect[] = "guue_sent_notice_id";
$this->scheda["guue_sent_notice_id"] = $this->check["eform"]["notice_id"];
}
// Ritorniamo con successo
$retval = true;
}
if (!empty($result->errori)) {
$this->scheda["errore"] = "SI";
$operazione->oggetto["esito"] = "KO";
$retval = false;
$error_codes = array_column(json_decode(json_encode($result->errori), true), 'codice');
// Nel caso questo sia un update e ci viene risposto con un ERR16,
// questa scheda dovrebbe ritornare in "CONFERMATO"
if((in_array('ERR58', $error_codes) || in_array('ERR16', $error_codes)) && $apiMethod == "update" && !empty($this->scheda["uuid"])) {
$checkResult = $this->checkConfirm($message);
// Conferma confermata
if($checkResult !== false) {
if($checkResult === true) {
$message = "INFO - Lo stato della conferma è stato recuperato con successo";
}
// Conferma ancora in attesa
if($checkResult === null) {
$message = "INFO - Lo stato della conferma è stato recuperato con successo ma è ancora in corso.";
}
$this->resyncScheda();
$message .= "<br> <b>Attenzione:</b> Nelle situazioni di recupero, le modifiche effettuate alla scheda potrebbero essere perse. Abbiamo risincronizzato la scheda con i server ANAC.";
return false;
}
// Nel caso in cui si sia verificato un "ERR38 - codiceAppalto già presente" eseguo una ricerca in ANAC e provo a riassociare l'idAppalto al fascicolo
} elseif (in_array('ERR38', $error_codes) && $apiConfig["type"] == "appalto") {
$search = $this->search(["codiceAppalto" => $body["anacForm"]["appalto"]["codiceAppalto"]]);
if (!empty($search) && $search->totRows == 1) {
$result = $search->result[0];
if (!empty($result->idAppalto)) {
if($this->resyncScheda()) {
$this->updateFascicolo(["id_appalto" => $result->idAppalto]);
$this->updateScheda(["stato" => 20, "errore" => "NO"]);
$message = "INFO - Lo stato della scheda è stato recuperato con successo.";
return false;
}
}
$retval = false;
$message = "Attenzione: si è verificato un errore di comunicazione con i server ANAC. Si prega di riprovare";
}
}
}
$salva->oggetto = $this->scheda;
if (!$salva->save() || !$operazione->save()) {
$today = date("Ymd");
error_log("[" . date('Y-m-d H:i:s') . "] - {$_SESSION["ente"]->getInfo()["dominio"]}" . PHP_EOL .
"\tNuovaPiattaformaAppalti::send()" . PHP_EOL .
"\tErrore durante il salvataggio dei dati" . PHP_EOL . PHP_EOL, 3, "{$config["npaFolder"]}/{$today}.log");
$message = DEVELOP_ENV ? "Errore durante il salvataggio dei dati" : __("Errore interno in fase di trasmissione");
return false;
}
if($retval)
$this->closeAllPendingOperations();
return $retval;
}
/**
* Retrieves and processes information from a notice
*
* @return Object
*/
public function readNotice(): ?Object
{
date_default_timezone_set('Europe/Rome');
$pubblicazioneAvvisi = $this->api_config["pubblicazioneAvvisi"];
if (!empty($pubblicazioneAvvisi)) {
$info = unserialize($this->scheda["info"]);
if (!empty($info->idAvviso)) {
$request = ["idAvviso" => $this->getANACNoticeID()];
$result_str = $this->apiCall("pubblicazioneAvvisi", "read", $request);
if (!empty($result_str) && valid_json($result_str, $result)) {
// Normalizzazione dati scheda
foreach (["appalto", "piano", "avviso"] as $entry) {
if (isset($result->{$entry}->scheda)) {
$result->_scheda = $result->{$entry}->scheda->scheda;
}
}
if(isset($result->scheda) && !isset($result->_scheda)) {
$result->_scheda = $result->scheda;
}
return $result;
}
}
}
return null;
}
/**
* Fetch data from ANAC
*
* @param string|null $message Se passato come riferimento, viene popolato in caso di errore con il messaggio d'errore
* @return Object
*/
public function read(?string &$message = null): ?Object
{
$message = "Errore interno sconosciuto.";
global $config;
date_default_timezone_set('Europe/Rome');
$apiEndpoint = $this->getCardEndpoint();
$apiConfig = $this->api_config[$apiEndpoint];
if (empty($apiConfig)) {
$message .= __LINE__; // Segnaposto per trovare questo errore
return NULL;
}
$request = $this->getKeysAndUUIDsForCurrentRequest();
$result_str = $this->apiCall($apiEndpoint, "read", $request, null, [], $apiMessage);
$result = self::manageANACResponse($result_str, $message);
if($result === null) {
$message = empty($apiMessage) ? $message : $apiMessage;
return false;
}
// Normalizzazione dati scheda
foreach (["appalto", "piano", "avviso"] as $entry) {
if (isset($result->{$entry}->scheda)) {
$result->_scheda = $result->{$entry}->scheda;
}
if(isset($result->scheda) && !isset($result->_scheda)) {
$result->_scheda = $result->scheda;
}
}
return $result;
}
/**
* Richiede lo stato della scheda al server ANAC
*
* @return Array|null
*/
public function requestCardStatus(): ?array
{
global $config;
date_default_timezone_set('Europe/Rome');
$apiEndpoint = $this->getCardEndpoint();
$apiConfig = $this->api_config[$apiEndpoint];
if (!empty($apiConfig)) {
$request = $this->getKeysAndUUIDsForCurrentRequest();
$result = $this->apiCall($apiEndpoint, "verify", $request);
if (!empty($result) && is_json($result)) {
$result = json_decode($result);
if ($result->status == 200) {
return $result;
}
}
}
return null;
}
/**
* Verify the card
*
* @return Bool
*/
public function verify(): ?Bool
{
return false;
global $config;
$salva = new Salva();
$salva->debug = false;
$salva->nome_tabella = "b_npa_operazioni_schede";
$verifica = $this->getLastOperation(null, "VERIFICA", NULL);
if (!empty($verifica)) {
$salva->operazione = "UPDATE";
$salva->expect = ["codice", "esito", "risultato"];
$operazione = "AP_VERIF";
if ($this->scheda["type"] == "pianificazione") {
$operazione = "PI_VERIF";
}
if ($this->scheda["type"] == "scheda" || $this->scheda["type"] == "postpubblicazione") {
$operazione = "SC_VERIF";
}
$result = $this->result($operazione);
if (!empty($result)) {
if ($result->status == 200 && !empty($result->listaEsiti)) {
$result = end($result->listaEsiti);
if ($result->esito->codice == "KO") {
$verifica["esito"] = "KO";
$verifica["risultato"] = serialize($result);
$salva->oggetto = $verifica;
if ($salva->save()) {
$this->updateScheda(["errore" => "SI"]);
return true;
}
}
if ($result->esito->codice == "OK") {
$verifica["esito"] = "OK";
$verifica["risultato"] = serialize($result);
$salva->oggetto = $verifica;
if ($salva->save()) {
$this->updateScheda(["errore" => "NO"]);
return true;
}
}
}
}
return false;
}
$salva->operazione = "INSERT";
$salva->expect = ["codice_scheda", "operazione"];
date_default_timezone_set('Europe/Rome');
$apiEndpoint = $this->getCardEndpoint();
$apiConfig = $this->api_config[$apiEndpoint];
if (!empty($apiConfig)) {
$request = $this->getKeysAndUUIDsForCurrentRequest();
$result = $this->apiCall($apiEndpoint, "verify", $request);
if (!empty($result) && is_json($result)) {
$result = json_decode($result);
if ($result->status == 200) {
$salva->oggetto = [
"codice_scheda" => $this->scheda["codice"],
"operazione" => "VERIFICA"
];
if ($salva->save()) {
$this->updateScheda(["stato" => 25]);
return true;
}
}
}
}
return false;
}
/**
* Determina se la scheda può essere trasmessa o modificata
*
* @return Bool
*/
public function canBeSent(): Bool
{
if (!$this->checkIfCardNeedsXMLDocument()) return false;
return
$this->scheda["stato"] == 10 ||
($this->scheda["stato"] > 10 && $this->scheda["stato"] <= 40 && $this->scheda["errore"] == "SI");
}
/**
* Verifica che la scheda possa essere confermata
*
* @param ?boolean $recovery Viene settata su true se la scheda è candidata per un recupero conferma
* @return bool
*/
public function canBeConfirmed(?bool &$recovery = false): bool
{
$recovery = false;
if($this->scheda["stato"] <= 20 && $this->scheda["errore"] == "SI") {
$conferma = $this->getLastOperation(null, "CONFERMA", false);
if(!empty($conferma) && $conferma["esito"] === "KO") {
$risultato = unserialize($conferma["risultato"]);
if(!empty($risultato)) {
$errorCode = $risultato->errori[0]->codice ?? "";
$recovery = $errorCode === "ERR58";
if($recovery) {
return true;
}
}
}
$trasmetti = $this->getLastOperation(null, "TRASMETTI", false);
if(!empty($trasmetti) && $trasmetti["esito"] === "KO") {
$risultato = unserialize($trasmetti["risultato"]);
if(!empty($risultato)) {
$errorCode = $risultato->errori[0]->codice ?? "";
$recovery = $errorCode === "ERR58";
if($recovery) {
return true;
}
}
}
}
return ($this->scheda["stato"] == 20 || $this->scheda["stato"] == 40) && $this->scheda["errore"] == "NO";
}
/**
* Verifica lo stato della conferma
*
* @param string|null $message Viene popolato se necessario con il messaggio d'errore
* @param array|null $conferma Se disponibile, la conferma
* @return Bool|null
*/
public function checkConfirm(?string &$message = null, ?array $conferma = null): ?Bool {
$salva = new Salva();
$salva->debug = false;
$salva->nome_tabella = "b_npa_operazioni_schede";
if(empty($conferma)) {
$salva->operazione = "INSERT";
$salva->oggetto = [
"codice_scheda" => $this->scheda["codice"],
"operazione" => "CONFERMA"
];
} else {
$salva->operazione = "UPDATE";
$salva->oggetto = $conferma;
}
date_default_timezone_set('Europe/Rome');
// Andiamo a chiedere al server lo stato della pubblicazione
$operazione = "AP_CONF";
if ($this->scheda["type"] == "pianificazione") {
$operazione = "PI_CONF";
}
if ($this->scheda["type"] == "scheda" || $this->scheda["type"] == "postpubblicazione") {
$operazione = "SC_CONF";
}
// Andiamo a chiedere al server lo stato della conferma
$result = $this->resultNew($operazione);
if (!empty($result)) {
if ($result->status == 200) {
// Esito non trovato
if (empty($result->listaEsiti)) {
$salva->oggetto["risultato"] = serialize([]);
$salva->oggetto["esito"] = "KO";
if ($salva->save()) {
$message = __("Impossibile verificare l'esito di conferma.");
$this->updateScheda(["errore" => "SI", "stato" => 40]);
}
return false;
}
$wtResult = null;
$okResult = null;
$koResult = null;
$lastResult = $result->listaEsiti[0];
foreach($result->listaEsiti as $esito) {
switch($esito->esito->codice) {
case "OK":
if($okResult === null) $okResult = $esito;
break;
case "KO":
if($koResult === null) $koResult = $esito;
break;
case "WT":
if($wtResult === null) $wtResult = $esito;
break;
}
}
// Fallito / Non valido
if(empty($okResult) && empty($wtResult) || $lastResult == $koResult) {
// Se abbiamo un errore 44 e c'è ancora un WT vuol dire che c'è un accavallamento di rettifiche, quindi non falliamo
$errorCode = $koResult->errori[0]->codice ?? null;
if(in_array($errorCode, ["ERR58", "ERR16"], true) && (!empty($okResult) || !empty($wtResult))) {
// Se l'ultimo errore è ERR58 o ERR16 e abbiamo un esito positivo o di wait, ignoriamo l'errore
} else {
$salva->oggetto["esito"] = "KO";
if(!empty($koResult))
$salva->oggetto["risultato"] = serialize($koResult);
$message = __("Vi è stato un problema con la conferma della scheda. ");
// Impostiamo lo stato di errore
if ($salva->save()) {
// Appendo al messaggio per distinguere il caso in cui non sono state salvate le info
$message .= " " . __("L'errore sarà disponibile al ricaricamento della pagina.");
$this->updateScheda(["errore" => "SI", "stato" => 40]);
}
// Ha senso ritornare false con un errore parlante
return false;
}
}
// Successo
if(!empty($okResult)) {
$salva->oggetto["esito"] = "OK";
$salva->oggetto["risultato"] = serialize($okResult);
if ($salva->save()) {
if ($okResult !== null) {
$info = new stdClass;
if (!empty($this->scheda["info"])) {
$info = unserialize($this->scheda["info"]);
}
foreach(["idAvviso", "idContratto", "idPianificazione", "idAppalto"] as $prop) {
if(!empty($okResult->$prop)) {
$info->$prop = $okResult->$prop;
}
}
$this->updateScheda(["info" => serialize($info)]);
} else {
$message = __("I server ANAC hanno processato in maniera errata questa conferma.");
$this->updateScheda(["stato" => 50, "errore" => 'SI']);
return false;
}
// Successo totale
$this->updateScheda(["stato" => 50, "errore" => 'NO']);
return true;
}
}
// In attesa
if($salva->operazione === "INSERT") {
$this->updateScheda(["errore" => "NO", "stato" => 40]);
$salva->oggetto["esito"] = null;
$salva->save();
}
$message = __("INFO - La scheda è in corso di conferma. Questo processo potrebbe richiedere diverse ore.<br>Verifica l'esito nuovamente più tardi.");
// Ha senso ritornare false con un errore parlante
return null;
}
}
$message = __("Errore durante la verifica dello stato di conferma");
return false;
}
/**
* Confirm the card and start assignment phase for the CIG.
*
* @param string|null $message Se passato come riferimento, viene popolato in caso di errore con il messaggio d'errore
* @return Bool|null
*/
public function confirm(?string &$message = null): ?Bool
{
if (!$this->getServiceStatusBool(true)) {
$message = __("Impossibile completare la richiesta: i server ANAC non sono raggiungibili. Riprova tra qualche minuto.");
return false;
}
if (!$this->canBeConfirmed()) {
$message = __("La scheda non è in uno stato valido per questa operazione.");
return false;
}
// Popoliamo message con qualcosa di generico
$message = __("Errore interno sconosciuto ");
global $config;
$salva = new Salva();
$salva->debug = false;
$salva->nome_tabella = "b_npa_operazioni_schede";
$salva->operazione = "INSERT";
$salva->expect = ["codice_scheda", "operazione"];
date_default_timezone_set('Europe/Rome');
// Andiamo a verificare che ci sia un'operazione di conferma in attesa
$conferma = $this->getLastOperation(null, "CONFERMA", NULL);
// Se c'è
if (!empty($conferma)) {
$checkResult = $this->checkConfirm($message, $conferma);
if($checkResult === null) $checkResult = false;
return $checkResult;
}
$apiEndpoint = $this->getCardEndpoint();
$apiConfig = $this->api_config[$apiEndpoint];
if (empty($apiConfig)) {
$message .= __LINE__; // Segnaposto per trovare questo errore
return false;
}
$request = $this->getKeysAndUUIDsForCurrentRequest();
// Effettuiamo la richiesta
$result_str = $this->apiCall($apiEndpoint, "confirm", $request, null, [], $apiMessage);
$result = self::manageANACResponse($result_str, $message);
if($result === null) {
$message = empty($apiMessage) ? $message : $apiMessage;
return false;
}
$salva->oggetto = [
"codice_scheda" => $this->scheda["codice"],
"operazione" => "CONFERMA"
];
// Verifichiamo la risposta
// Se non ci viene fornita una risposta con successo
if ($result->status !== 200) {
$error_codes = isset($result->errori) ? array_column(json_decode(json_encode($result->errori), true), 'codice') : "Errore sconosciuto";
if(in_array('ERR58', $error_codes) || in_array('ERR16', $error_codes)) {
$checkResult = $this->checkConfirm($message);
// Conferma confermata
if($checkResult !== false) {
if($checkResult === true) {
$message = "INFO - Lo stato della conferma è stato recuperato con successo";
}
// Conferma ancora in attesa
if($checkResult === null) {
$message = "INFO - Lo stato della conferma è stato recuperato con successo ma è ancora in corso.";
}
$this->resyncScheda();
$message .= "<br> <b>Attenzione:</b> Nelle situazioni di recupero, le modifiche effettuate alla scheda potrebbero essere perse. Abbiamo risincronizzato la scheda con i server ANAC.";
return false;
}
}
// Aggiorniamo lo status
$salva->expect = array_merge($salva->expect, ["esito", "risultato"]);
$salva->oggetto = array_merge($salva->oggetto, ["esito" => "KO", "risultato" => serialize($result)]);
// Settiamo errore su SI
if ($salva->save()) {
$this->updateScheda(["errore" => "SI", "stato" => 40]);
}
$message = __("Errore in fase di conferma della scheda. Al ricaricamento della pagina verifica gli Avvisi.");
return false;
}
// Conferma andata con successo
if ($salva->save()) {
$this->updateScheda(["errore" => "NO", "stato" => 40]);
return true;
}
// Fallback generico
return false;
}
/**
* Get the CIG for the lots
*
* @return ?Array
*/
public function getCIG(?string &$message = null, int $page = 1, $perPage = 20): ?Bool
{
$salva = new Salva();
$salva->debug = false;
$salva->nome_tabella = "b_npa_operazioni_schede";
$salva->operazione = "INSERT";
$salva->expect = ["codice_scheda", "operazione", "esito", "risultato"];
date_default_timezone_set('Europe/Rome');
$comunicaAppalto = $this->api_config["comunicaAppalto"];
if (!empty($comunicaAppalto)) {
$request = $this->getKeysAndUUIDsForCurrentRequest("appalto");
$request["page"] = $page;
$request["perPage"] = $perPage;
$result = $this->apiCall("comunicaAppalto", "cig", $request, null, [], $message);
if (!empty($result) && is_json($result)) {
$result = json_decode($result);
if ($result->status == 200) {
$salva->oggetto = [
"codice_scheda" => $this->scheda["codice"],
"operazione" => "OTTIENI-CIG",
"esito" => "OK",
"risultato" => serialize($result)
];
$salva->save();
if (!empty($result->result)) {
foreach ($result->result as $lot) {
if (!empty($lot->cig)) {
$this->updateCig($lot->lotIdentifier, $lot->cig);
}
}
if($result->totPages > $page) {
// I server ANAC sono leeeeeeenti
set_time_limit(0);
ignore_user_abort();
return $this->getCIG($message, $page+1, $perPage);
}
return true;
}
}
}
}
return null;
}
/**
* Determina che la scheda possa essere pubblicata
*
* @return Bool
*/
public function canBePublished(bool $ui = true): Bool
{
if ($ui) {
return ($this->scheda["stato"] == 50 || $this->scheda["stato"] == 90) &&
//$this->scheda["errore"] == "NO" &&
self::hasToBePublished($this->scheda);
}
return ($this->scheda["stato"] == 50 || $this->scheda["stato"] >= 90) &&
//$this->scheda["errore"] == "NO" &&
self::hasToBePublished($this->scheda);
}
public function updatePublicationStatus(?string &$message = null): ?Bool
{
$avviso = $this->readNotice();
if (!empty($avviso) && $avviso->status == 200) {
$this->updateScheda(["pubblicazione" => serialize($avviso->avviso)]);
$this->checkIfCardNeedsXMLDocument();
$hasEUPublication = $this->check["eform"]["required"];
$isFullyPublished = isset($avviso->avviso->datiPubblicazioneIT) && isset($avviso->avviso->datiPubblicazioneIT->stato->codice) && $avviso->avviso->datiPubblicazioneIT->stato->codice === "PUBB";
if ($hasEUPublication && $isFullyPublished) {
$isFullyPublished = isset($avviso->avviso->datiPubblicazioneEU) && isset($avviso->avviso->datiPubblicazioneEU->stato->codice) && $avviso->avviso->datiPubblicazioneEU->stato->codice === "PUBB";
}
if ($isFullyPublished) {
$message = "La pubblicazione della scheda è completa";
$this->updateScheda(["stato" => 110]);
return true;
}
$message = "INFO - La pubblicazione non è ancora stata completata.";
}
return false;
}
public function checkPublish(?string &$message = null, ?array $pubblica): ?Bool {
$salva = new Salva();
$salva->debug = false;
$salva->nome_tabella = "b_npa_operazioni_schede";
if(empty($pubblica)) {
$salva->operazione = "INSERT";
$salva->oggetto = [
"codice_scheda" => $this->scheda["codice"],
"operazione" => "PUBBLICA"
];
} else {
$salva->operazione = "UPDATE";
$salva->oggetto = $pubblica;
}
date_default_timezone_set('Europe/Rome');
// Andiamo a chiedere al server lo stato della pubblicazione
$result = $this->result("AV_PUBB");
if (!empty($result)) {
if ($result->status == 200) {
if (empty($result->listaEsiti)) {
$message = __("Impossibile verificare l'esito dell'operazione. Contatta l'Help Desk tecnico");
return false;
}
$result = end($result->listaEsiti);
if ($result->esito->codice == "WT") {
$message = __("INFO - La scheda non è ancora stata pubblicata. Ricontrolla più tardi.");
// Aggiorniamo comunque lo stato in caso non lo sia
if($salva->operazione === "INSERT") {
$this->updateScheda(["errore" => "NO", "stato" => 90]);
$salva->oggetto["esito"] = NULL;
$salva->save();
}
return null;
}
// Pubblicazione fallita
if ($result->esito->codice == "KO") {
$salva->oggetto["esito"] = "KO";
$salva->oggetto["risultato"] = serialize($result);
$message = __("Vi è stato un problema con la pubblicazione dell'avviso.");
// Impostiamo lo stato di errore
if ($salva->save()) {
// Appendo al messaggio per distinguere il caso in cui non sono state salvate le info
$message .= " " . __("L'errore sarà disponibile al ricaricamento della pagina.");
$this->updateScheda(["errore" => "SI"]);
}
// Ha senso ritornare false con un errore parlante
return false;
}
if ($result->esito->codice == "OK") {
$salva->oggetto["esito"] = "OK";
$salva->oggetto["risultato"] = serialize($result);
if ($salva->save()) {
// Cambiamo lo stato della scheda GUUE se presente
$this->publishGUUEOnSuccessfulPublication();
$this->updateScheda(["errore" => "NO", "stato" => 100]);
return true;
}
}
}
}
$message = __("Errore durante la verifica dello stato di pubblicazione");
return false;
}
/**
* Pubblica l'avviso
*
* @param string|null $message Se passato come riferimento, viene popolato in caso di errore con il messaggio d'errore
* @return ?bool
*/
public function publish(?string &$message = null): ?bool
{
if (!$this->getServiceStatusBool(true)) {
$message = __("Impossibile completare la richiesta: i server ANAC non sono raggiungibili. Riprova tra qualche minuto.");
return false;
}
$metadata = $this->metadataSchede[$this->scheda["id_scheda"]];
if(
$this->scheda["stato"] == 100 || (
$this->scheda["stato"] >= NuovaPiattaformaAppalti::TRASMITTED_STATO &&
$this->scheda["stato"] < NuovaPiattaformaAppalti::PUBLISHED_STATO &&
$metadata["pubblicazione_implicita"]
)
) {
return $this->updatePublicationStatus($message);
}
if (!$this->canBePublished(false)) {
$message = __("La scheda non è in uno stato valido per questa operazione.");
return false;
}
// Popoliamo message con qualcosa di generico
$message = __("Errore interno sconosciuto ");
global $config;
$salva = new Salva();
$salva->debug = false;
$salva->nome_tabella = "b_npa_operazioni_schede";
$salva->operazione = "INSERT";
$salva->oggetto = [
"codice_scheda" => $this->scheda["codice"],
"operazione" => "PUBBLICA"
];
$salva->expect = ["codice_scheda", "operazione"];
date_default_timezone_set('Europe/Rome');
// Andiamo a verificare che ci sia un'operazione di pubblicazione in attesa
$pubblica = $this->getLastOperation(null, "PUBBLICA", NULL);
// Se c'è
if (!empty($pubblica)) {
$checkResult = $this->checkPublish($message, $pubblica);
if($checkResult === null) $checkResult = false;
return $checkResult;
}
$pubblicazioneAvvisi = $this->api_config["pubblicazioneAvvisi"];
if (empty($pubblicazioneAvvisi)) {
$message .= __LINE__; // Segnaposto per trovare questo errore
return false;
}
$info = unserialize($this->scheda["info"]);
// Verifichiamo sia popolato l'id avviso
if (empty($info->idAvviso)) {
$message = __("Problema interno con la pubblicazione. Contattare l'Help Desk tecnico.");
$this->appendLog(
"pubblicazioneAvvisi",
"publish",
[],
"idAvviso non trovato."
);
return null;
}
$request = $this->getKeysAndUUIDsForCurrentRequest();
$request["idAvviso"] = $this->getANACNoticeID();;
// Effettuiamo la richiesta
$result_str = $this->apiCall("pubblicazioneAvvisi", "publish", $request, null, [], $apiMessage);
$result = self::manageANACResponse($result_str, $message);
if($result === null) {
$message = empty($apiMessage) ? $message : $apiMessage;
return false;
}
// Detect errori catturabili
if ($result->status !== 200) {
if(isset($result->errori[0]->codice)) {
$error_codes = array_column(json_decode(json_encode($result->errori), true), 'codice');
if (in_array('ERR45', $error_codes)) {
$checkResult = $this->checkPublish($message, null);
// Pubblicazione confermata
if($checkResult !== false) {
if($checkResult === true) {
$message = "INFO - Lo stato della pubblicazione è stato recuperato con successo";
}
// Conferma ancora in attesa
if($checkResult === null) {
$message = "INFO - Lo stato della pubblicazione è stato recuperato con successo ma è ancora in corso.";
}
$this->resyncScheda();
$message .= "<br> <b>Attenzione:</b> Nelle situazioni di recupero, le modifiche effettuate alla scheda potrebbero essere perse. Abbiamo risincronizzato la scheda con i server ANAC.";
return false;
}
// Altrimenti lasciamo cadere nel resto del codice
}
}
}
// Verifichiamo la risposta
if (!empty($result->status) && $result->status == 200) {
if ($salva->save()) {
$this->updateScheda(["errore" => "NO", "stato" => 90]);
return true;
}
$message .= __LINE__;
return false;
}
$message = __("Errore in fase di pubblicazione della scheda. Al ricaricamento della pagina verifica gli Avvisi.");
$salva->expect = array_merge($salva->expect, ["esito", "risultato"]);
$salva->oggetto = array_merge($salva->oggetto, ["esito" => "KO", "risultato" => serialize($result)]);
if ($salva->save()) {
$this->updateScheda(["errore" => "SI", "stato" => 90]);
}
return false;
}
/**
* Retrieve tender information from the National Anti-Corruption Authority (ANAC)
*
* @return Array
*/
public function getTenderInfoFromANAC(): ?Object
{
global $config;
$salva = new Salva();
$salva->debug = false;
$salva->nome_tabella = "b_npa_operazioni_schede";
$salva->operazione = "INSERT";
$salva->expect = ["codice_scheda", "operazione", "esito"];
date_default_timezone_set('Europe/Rome');
$apiEndpoint = $this->getCardEndpoint();
$apiConfig = $this->api_config[$apiEndpoint];
if (!empty($apiConfig)) {
$key = "idAppalto";
if ($this->scheda["type"] == "pianificazione") {
$key = "idPianificazione";
}
$request = [$key => $this->getUUID()];
$result = $this->apiCall($apiEndpoint, "confirm", $request);
if (!empty($result) && is_json($result)) {
$result = json_decode($result);
$salva->oggetto = [
"codice_scheda" => $this->scheda["codice"],
"operazione" => "CONSULTA",
"esito" => serialize($result)
];
return $result;
}
}
return null;
}
/**
* Uguale a result, ma compie meno operazioni
*
* @param String|null $operazione
* @param String|null $ricerca
* @return Object|null
*/
public function resultNew(?String $operazione = null, ?String $ricerca = "TUTTI_ESITI") : ?Object {
date_default_timezone_set('Europe/Rome');
$serviziComuni = $this->api_config["serviziComuni"];
if (empty($serviziComuni)) return null;
$request = $this->getKeysAndUUIDsForCurrentRequest();
if (!isset($request["idScheda"])) {
$request["idScheda"] = $this->getUUID("scheda");
}
if ($operazione !== null)
$request["tipoOperazione"] = $operazione;
$request["tipoRicerca"] = $ricerca;
$result_str = $this->apiCall("serviziComuni", "result", $request);
if(!valid_json($result_str, $result)) return null;
if(empty($result->listaEsiti)) return null;
if (count($result->listaEsiti) > 0) {
$result->listaEsiti = array_filter($result->listaEsiti, function($esito) use ($request){
if(isset($esito->idScheda)) {
if(strtolower($esito->idScheda) != strtolower($request["idScheda"])) {
return false;
}
}
return true;
}, ARRAY_FILTER_USE_BOTH);
// Workaround per sort
$result->listaEsiti = json_decode(json_encode($result->listaEsiti), true);
$dt = array_column($result->listaEsiti, 'dataControllo');
array_multisort($dt, SORT_DESC, $result->listaEsiti);
$result->listaEsiti = json_decode(json_encode($result->listaEsiti));
}
return $result;
}
/**
* Retrieves the result or outcome of the operation
* @param ?String $operazione
* @param String $ricerca TUTTI_ESITI|ULTIMO_ESITO
*
* @return Object
*/
public function result(?String $operazione = null, ?String $ricerca = "TUTTI_ESITI"): ?Object
{
date_default_timezone_set('Europe/Rome');
$serviziComuni = $this->api_config["serviziComuni"];
if (!empty($serviziComuni)) {
$request = $this->getKeysAndUUIDsForCurrentRequest();
if (!isset($request["idScheda"])) {
$request["idScheda"] = $this->getUUID("scheda");
}
if ($operazione !== null)
$request["tipoOperazione"] = $operazione;
$request["tipoRicerca"] = $ricerca;
$result = $this->apiCall("serviziComuni", "result", $request);
if (!empty($result) && is_json($result)) {
$response = json_decode($result);
if ($response->status == 200 && !empty($response->listaEsiti)) {
$outcomes = $response->listaEsiti;
$response->listaEsiti = [];
foreach ($outcomes as $outcome) {
if (strtolower($outcome->idScheda) == strtolower($request["idScheda"])) {
$response->listaEsiti[] = json_decode(json_encode($outcome), 1);
}
}
if (count($response->listaEsiti) > 0) {
$dt = array_column($response->listaEsiti, 'dataControllo');
array_multisort($dt, SORT_DESC, $response->listaEsiti);
$response->listaEsiti = [$response->listaEsiti[0]];
$response->listaEsiti[0] = json_decode(json_encode($response->listaEsiti[0]));
if (isset($response->listaEsiti[0]->esito->codice) && $response->listaEsiti[0]->esito->codice == "OK") {
try {
$info = new stdClass;
if (!empty($this->scheda["info"])) {
$info = unserialize($this->scheda["info"]);
}
$info->idAppalto = $response->listaEsiti[0]->idAppalto ?? null;
$info->idPianificazione = $response->listaEsiti[0]->idPianificazione ?? null;
$info->idScheda = $response->listaEsiti[0]->idScheda ?? null;
$info->idNuovaScheda = $response->listaEsiti[0]->idNuovaScheda ?? null;
$info->idAvviso = $response->listaEsiti[0]->idAvviso ?? null;
$info->idNuovoAvviso = $response->listaEsiti[0]->idNuovoAvviso ?? null;
$info->idContratto = $response->listaEsiti[0]->idContratto ?? null;
$this->updateScheda(["info" => serialize($info)]);
} catch (\Throwable $th) {
$this->appendLog(
"serviziComuni",
"result",
[],
"Errore formulazione richiesta, tutti i campi sono vuoti."
);
}
}
}
return $response;
}
}
}
return null;
}
/**
* Retrieves the status from a tender or a Lot (required CIG)
*
* @return Object
*/
public function status(String $cig = null): ?Object
{
global $config;
date_default_timezone_set('Europe/Rome');
$serviziComuni = $this->api_config["serviziComuni"];
if (!empty($settings)) {
$request = $this->getKeysAndUUIDsForCurrentRequest();
if (!empty($cig)) {
$request["cig"] = $cig;
}
$result = $this->apiCall("serviziComuni", "status", $request);
if (!empty($result) && is_json($result)) {
return json_decode($result);
}
}
return null;
}
/**
* Retrieves the result or outcome of the operation
* @param String $operazione
* @param String $ricerca TUTTI_ESITI|ULTIMO_ESITO
*
* @return Object
*/
public function search(?array $parameters = []): ?Object
{
global $config;
date_default_timezone_set('Europe/Rome');
// Ottengo la configurazione per contattare il webservice di ANAC
$apiEndpoint = $this->getCardEndpoint();
// Verifico che la richiesta abbia solo i campi compatibili con ANAC
$keys = ["codiceAppalto", "cig", "lotIdentifier", "stato", "tipo", "dataCreazioneDa", "dataCreazioneA", "page", "perPage"];
$parameters = array_filter($parameters, function ($key) use ($keys) {
return in_array($key, $keys);
}, ARRAY_FILTER_USE_KEY);
if (empty($parameters)) {
$this->appendLog(
$apiEndpoint,
"search",
$parameters,
"Errore formulazione richiesta, tutti i campi sono vuoti."
);
return false;
}
// Definisco la paginazione di default
if (empty($parameters["perPage"])) {
$parameters["perPage"] = 20;
}
// Otteniamo il voucher PDND
$result = $this->apiCall($apiEndpoint, "search", $parameters);
if (!empty($result) && is_json($result)) {
return json_decode($result);
}
return false;
}
}