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.
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.
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 .= "
Attenzione: 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.
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 .= "
Attenzione: 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 .= "
Attenzione: 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; } }