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.
3187 righe
96 KiB
3187 righe
96 KiB
<?php |
|
|
|
require_once __DIR__ . "/traits/npalogger.trait.php"; |
|
require_once __DIR__ . "/traits/suggestions.trait.php"; |
|
require_once __DIR__ . "/traits/render.trait.php"; |
|
require_once __DIR__ . "/traits/npa.api.trait.php"; |
|
require_once __DIR__ . "/traits/deleghe.trait.php"; |
|
global $beRoot; |
|
require_once $beRoot . "/guue/class/eFormsManager.class.php"; |
|
|
|
class NuovaPiattaformaAppalti |
|
{ |
|
|
|
use NPASuggestionsTrait; |
|
use NuovaPiattaformaAppaltiRenderTrait; |
|
use Deleghe; |
|
use NPALogger; |
|
use NPAAPI; |
|
|
|
public const INTEGRATION_PATHS = [ |
|
"gare" => "/gare/guue/integration.php", |
|
"pcp" => "/pcp/guue/integration.php", |
|
"concorsi" => "/concorsi/guue/integration.php", |
|
"albo_fornitori" => "/albo_fornitori/guue/integration.php", |
|
]; |
|
|
|
/** |
|
* Versione SDK di default |
|
* |
|
* @var string |
|
*/ |
|
const LATEST_SDK_VERSION = "v1.0.2"; |
|
|
|
/** |
|
* Ultima versione interna |
|
* |
|
* @var int |
|
*/ |
|
const LATEST_INTERNAL_VERSION = 4; |
|
/** |
|
* Costante per indicare al costruttore di caricare |
|
* automaticamente la versione SDK del fascicolo |
|
*/ |
|
const AUTOMATICALLY_GET_SDK_VERSION = null; |
|
/** |
|
* Definisce lo stato minimo perchè una scheda sia considerata pubblicata dai vari check |
|
*/ |
|
const TRASMITTED_STATO = 50; |
|
|
|
/** |
|
* Definisce lo stato minimo perchè una scheda sia considerata pubblicata dai vari check |
|
*/ |
|
const PUBLISHED_STATO = 110; |
|
/** |
|
* Connessione al database |
|
* |
|
* @var myPDO |
|
*/ |
|
private $pdo; |
|
/** |
|
* Riferimento a eFormsManager se presente |
|
* |
|
* @var eFormsManager |
|
*/ |
|
private $ted; |
|
/** |
|
* Sdk version path |
|
* |
|
* @var string |
|
*/ |
|
private $path; |
|
|
|
/** |
|
* Form Details |
|
* |
|
* @var array |
|
*/ |
|
private $form; |
|
|
|
/** |
|
* lot |
|
* |
|
* @var mixed |
|
*/ |
|
public $lot; |
|
|
|
/** |
|
* XML Document check |
|
* |
|
* @var array |
|
*/ |
|
public $check; |
|
|
|
/** |
|
* Available cards |
|
* |
|
* @var array |
|
*/ |
|
public $schede; |
|
|
|
/** |
|
* Available cards settings |
|
* |
|
* @var array |
|
*/ |
|
public $metadataSchede; |
|
|
|
/** |
|
* Fascicolo NPA |
|
* |
|
* @var array |
|
*/ |
|
public $fascicolo; |
|
|
|
/** |
|
* I contenuti della scheda attuale. |
|
* Usa getCardData. |
|
*/ |
|
private $card_data; |
|
|
|
/** |
|
* Info |
|
* |
|
* @var array |
|
*/ |
|
public $info; |
|
|
|
/** |
|
* Current Card info |
|
* |
|
* @var array |
|
*/ |
|
public $scheda; |
|
|
|
/** |
|
* version |
|
* |
|
* @var string |
|
*/ |
|
public $version; |
|
|
|
/** |
|
* Object reference |
|
* |
|
* @var mixed |
|
*/ |
|
public $oggetto; |
|
|
|
/** |
|
* Definisce se la scheda corrente necessita di aggiudicatari |
|
* |
|
* @var boolean |
|
*/ |
|
public $has_aggiudicatari = false; |
|
|
|
/** |
|
* Definisce se la scheda corrente necessita di partecipanti |
|
* |
|
* @var boolean |
|
*/ |
|
public $has_partecipanti = false; |
|
|
|
/** |
|
* Contiene la configurazione corrente delle API |
|
* |
|
* @var array |
|
*/ |
|
private $api_config = []; |
|
|
|
|
|
/** |
|
* Se popolata sovrascrive la stazione appaltante usata per generare i JWT di autenticazione |
|
* |
|
* @var ?array |
|
*/ |
|
public $overrideStazioneAppaltante; |
|
|
|
/** |
|
* Class initialization |
|
* |
|
* @param ?int $codice_fascicolo |
|
* @param ?int $codice_scheda |
|
* @param ?string $identificativo_scheda |
|
* @param ?string $version |
|
* @param ?eFormsManager $ted |
|
* @return ?NuovaPiattaformaAppalti |
|
*/ |
|
public static function init(?int $codice_fascicolo = null, ?int $codice_scheda = null, ?string $identificativo_scheda = null, ?string $modulo_riferimento = null, ?int $id_riferimento = null, ?string $version = self::AUTOMATICALLY_GET_SDK_VERSION, ?eFormsManager $ted = null): ?NuovaPiattaformaAppalti |
|
{ |
|
if($version == self::AUTOMATICALLY_GET_SDK_VERSION) { |
|
$version = self::fetchVersion($codice_fascicolo, $codice_scheda); |
|
if($version === null) { |
|
throw new Exception("Impossibile determinare la versione SDK NPA", 999); |
|
} |
|
} |
|
$npa = new self($version); |
|
$npa->fascicolo = get_campi("b_npa"); |
|
$npa->scheda = get_campi("b_npa_schede"); |
|
$npa->scheda["stato"] = 0; |
|
$success = $npa->initCard($codice_scheda, $identificativo_scheda); |
|
if ($success !== true && empty($codice_fascicolo)) { |
|
// Scheda non caricabile |
|
return null; |
|
} |
|
$identificativo_scheda = $npa->scheda["id_scheda"]; |
|
$metadata = $npa->metadataSchede[$identificativo_scheda] ?? []; |
|
if(!empty($metadata)) { |
|
$npa->has_aggiudicatari = $metadata["aggiudicatari"]; |
|
$npa->has_partecipanti = $metadata["partecipanti"]; |
|
} |
|
if (empty($codice_fascicolo) && !empty($npa->scheda)) { |
|
$codice_fascicolo = $npa->scheda["codice_npa"]; |
|
} |
|
|
|
$result = $npa->loadNpaDossier(!empty($codice_fascicolo) ? $codice_fascicolo : null, $modulo_riferimento, $id_riferimento); |
|
if(!$result) { |
|
throw new Exception("Impossibile inizializzare il fascicolo. Potrebbe essere stato cancellato o resettato.", 999); |
|
} |
|
|
|
if($ted !== null) { $npa->ted = $ted; } |
|
|
|
if($npa->scheda["codice"] > 0) { |
|
$npa->checkIfCardNeedsXMLDocument(); |
|
if (!(defined("__CMDS_JOB_EXEC") && !empty(__CMDS_JOB_EXEC))) { |
|
$npa->getSuggestions(); |
|
} |
|
// Workaround per ottenere il noticeID inviato quando mancante |
|
if( |
|
$npa->check["eform"]["required"] && // Se la scheda ha eForms |
|
!empty($npa->scheda["uuid"]) && // Se la scheda ha un idScheda (è già stata inviata) |
|
empty($npa->scheda["guue_sent_notice_id"]) // Se la scheda non ha il record di un notice_id inviato |
|
) { |
|
$sent_notice_id = $npa->retrieveGUUESentNoticeID(); |
|
if(!empty($sent_notice_id)) { |
|
// Verifichiamo che il notice_id sia corretto, altrimenti lo rigeneriamo |
|
while(preg_match("/[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}/", $sent_notice_id) !== 1) { |
|
$sent_notice_id = uuidgen(); |
|
} |
|
// Aggiorniamo la scheda |
|
if($npa->updateScheda(["guue_sent_notice_id" => $sent_notice_id])) { |
|
$npa->checkIfCardNeedsXMLDocument(); |
|
} |
|
} |
|
} |
|
// Primo collegamento automatico della scheda GUUE |
|
if( |
|
$npa->scheda["codice"] > 0 && // Se la scheda esiste |
|
$npa->check["eform"]["required"] |
|
) { |
|
$compatibleCard = []; |
|
if(empty($npa->scheda["codice_scheda_guue"])) { |
|
$compatibleCard = $npa->fetchCompatibleGUUECards(false); |
|
} else { |
|
$compatibleCard["codice"] = $npa->scheda["codice_scheda_guue"]; |
|
} |
|
if(!empty($compatibleCard)) { |
|
// Aggiorniamo la scheda |
|
if($npa->selezionaSchedaGUUE($compatibleCard["codice"])) { |
|
$npa->checkIfCardNeedsXMLDocument(); |
|
} |
|
} |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
return $npa; |
|
} |
|
|
|
/** |
|
* Seleziona la nuova scheda GUUE |
|
* |
|
* @param integer $codiceScheda |
|
* @return boolean |
|
*/ |
|
public function selezionaSchedaGUUE(int $codiceScheda) : bool { |
|
if($this->checkLock()) { |
|
return false; |
|
} |
|
// Se è già settato ci assicuriamo solo che |
|
if($codiceScheda !== $this->scheda["codice_scheda_guue"]) { |
|
// Prima resettiamo il lotto della scheda a noi associata |
|
if($this->scheda["codice_scheda_guue"] > 0) { |
|
$success = $this->pdo->go( |
|
"UPDATE b_guue SET lotto_riferimento = NULL WHERE codice = :codice", |
|
[":codice" => $this->scheda["codice_scheda_guue"]] |
|
); |
|
if(!$success) |
|
return false; |
|
} |
|
// Settiamo il nuovo codice scheda |
|
$success = $this->updateScheda(["codice_scheda_guue" => $codiceScheda]); |
|
if(!$success) return false; |
|
} |
|
|
|
// Dopodichè associamo la nuova al lotto, se presente e se necessario |
|
$codiceLottoNpa = $this->scheda["codice_lotto"]; |
|
if(!empty($codiceLottoNpa)) { |
|
$lot = $this->getLots($codiceLottoNpa) ?? null; |
|
if(!empty($lot)) { |
|
$lot = reset($lot); |
|
$result = $this->pdo->go( |
|
"UPDATE b_guue SET lotto_riferimento = :lotto WHERE codice = :codice", |
|
[ |
|
":lotto" => $lot["codice"], |
|
":codice" => $codiceScheda |
|
] |
|
); |
|
return($result->errorInfo()[0] == "00000"); |
|
} |
|
return false; |
|
|
|
} |
|
return true; |
|
} |
|
|
|
/** |
|
* Class initialization for free requests |
|
* @param ?string $version |
|
* @return ?NuovaPiattaformaAppalti |
|
*/ |
|
public static function initStatic( ?string $version = self::LATEST_SDK_VERSION): ?NuovaPiattaformaAppalti |
|
{ |
|
$npa = new self($version); |
|
$npa->fascicolo = get_campi("b_npa"); |
|
$npa->scheda = get_campi("b_npa_schede"); |
|
$npa->scheda["stato"] = 0; |
|
$npa->fascicolo["codice"] = 0; |
|
return $npa; |
|
} |
|
|
|
public static function initForImport(String $modulo_riferimento, int $id_riferimento, ?string $version = self::LATEST_SDK_VERSION) : ?NuovaPiattaformaAppalti |
|
{ |
|
$npa = new self($version); |
|
$npa->fascicolo = get_campi("b_npa"); |
|
$npa->fascicolo["modulo_riferimento"] = $modulo_riferimento; |
|
$npa->fascicolo["id_riferimento"] = $id_riferimento; |
|
|
|
|
|
$npa->scheda = get_campi("b_npa_schede"); |
|
$npa->scheda["stato"] = 0; |
|
return $npa; |
|
} |
|
|
|
/** |
|
* Check if the version exists. |
|
* |
|
* @param mixed $version |
|
* @return void |
|
*/ |
|
public static function check_version(String $version): void |
|
{ |
|
|
|
$path = dirname(__FILE__, 2); |
|
|
|
// If the folder for the specified version does not exist, we raise an exception. |
|
if (!file_exists("{$path}/versions/{$version}")) { |
|
|
|
throw new Exception("SDK '{$version}' not found!"); |
|
} |
|
} |
|
|
|
/** |
|
* Retrieves a list of cards organized and classified based on their module types |
|
* |
|
* @param mixed $version |
|
* @return array |
|
*/ |
|
public static function getCategorizedCardsList(): ?array |
|
{ |
|
if(valid_json(file_get_contents(__DIR__ . "/match-moduli.json"), $matchModuli, true)) { |
|
return $matchModuli; |
|
} |
|
return null; |
|
} |
|
|
|
private static $cardMetadataCache = null; |
|
|
|
/** |
|
* Ottiene i metadata delle schede |
|
* |
|
* @return array|null |
|
*/ |
|
public static function getCardsMetadata(): ?array |
|
{ |
|
if(self::$cardMetadataCache === null) { |
|
if(valid_json(file_get_contents(__DIR__ . "/schede.json"), $schede, true)) { |
|
self::$cardMetadataCache = $schede; |
|
} |
|
} |
|
return self::$cardMetadataCache; |
|
} |
|
|
|
public static function getInitialCards(?string $modulo = null, ?string $procedura = null, ?string $fase = null, ?string $settore = null, ?string $soglia = null, ?bool $concessione = null) { |
|
|
|
|
|
if (!in_array($settore, ["ordinario", "speciale", "difesa", null])) { |
|
throw new Exception("The '{$settore}' sector specified is invalid. Please choose from valid options: 'ordinario', 'speciale', 'difesa'."); |
|
} |
|
|
|
if (!in_array($soglia, ["soprasoglia", "sottosoglia", null])) { |
|
throw new Exception("The specified '{$soglia}' threshold is invalid. Valid options: 'soprasoglia', 'sottosoglia'."); |
|
} |
|
$schede = self::getCardsMetadata(); |
|
$matches = self::getCategorizedCardsList(); |
|
|
|
$cards = []; |
|
|
|
// If $modulo is specified, we retrieve all available cards for those modules. |
|
if (!is_null($modulo) && !empty($matches[$modulo])) { |
|
//TODO: Ensure array_get function returns desired results |
|
$cards = DotPath::get($matches[$modulo], "procedure.*.*.*", null, false); |
|
// If $procedura is specified, we retrieve all available cards for that procedure. |
|
if (!is_null($procedura)) { |
|
$cards = DotPath::get($matches[$modulo], "procedure.{$procedura}.*.*", null, false); |
|
// If $fase is specified, we retrieve all available cards for that phase. |
|
if (!is_null($fase)) { |
|
$cards = $matches[$modulo]["procedure"][$procedura]["fasi"][$fase] ?? $matches[$modulo]["procedure"][$procedura]["fasi"]["*"]; |
|
} |
|
} |
|
} |
|
|
|
$initialCards = []; |
|
|
|
foreach($cards as $card) { |
|
$metadata = $schede[$card] ?? []; |
|
if(empty($metadata)) continue; |
|
if($settore !== null) { |
|
if(!in_array($settore, $metadata["settori"])) continue; |
|
} |
|
if($settore !== null) { |
|
if(!$metadata[$soglia]) continue; |
|
} |
|
if(!is_null($concessione) && !is_null($metadata["concessione"])) { |
|
if($metadata["concessione"] != $concessione) { |
|
continue; |
|
} |
|
} |
|
|
|
$initialCards[$card] = $metadata; |
|
} |
|
|
|
return $initialCards; |
|
} |
|
|
|
|
|
|
|
/** |
|
* Retrieve the list of all saved cards from the database. |
|
* |
|
* @param ?array $filters |
|
* @param ?array $limits |
|
* @param ?array $order |
|
* @return Generator |
|
*/ |
|
public static function listCards(?array $filters = [], ?array $limits = [], ?array $order = []): ?Generator |
|
{ |
|
global $pdo; |
|
|
|
$count = "SELECT COUNT(codice)"; |
|
$select = "SELECT *"; |
|
|
|
$from = "FROM b_npa_schede"; |
|
|
|
$bind = []; |
|
$conditions = ["SOFT_DELETE != 'S'"]; |
|
|
|
if ($_SESSION["utente"]->gerarchia > 10) { |
|
|
|
$ids = Utente::getPermissionsInModulo($_SESSION["ente"]->codice, $_SESSION["utente"]->codice, "npa"); |
|
$ids = implode(",", $ids); |
|
$conditions[] = "codice IN ({$ids})"; |
|
} |
|
|
|
if (!$_SESSION["utente"]->fromHere()) { |
|
|
|
$bind[":codice_ente"] = $_SESSION["utente"]->codice_ente; |
|
$conditions[] = "codice_ente = :codice_ente"; |
|
} |
|
|
|
$where = ""; |
|
if (!empty($conditions)) { |
|
$where = "WHERE (" . implode(" ) AND (", $conditions) . ")"; |
|
} |
|
|
|
$results = $pdo->go("{$count} {$from} {$where}", $bind)->fetch(PDO::FETCH_COLUMN, 0); |
|
if (!empty($results)) { |
|
|
|
if (!empty($filters)) { |
|
|
|
$clauses = []; |
|
if (!empty($filters["codice"])) { |
|
$clauses[] = "codice = :codice"; |
|
$bind[":codice"] = $filters["codice"]; |
|
} |
|
if (!empty($filters["codice_riferimento"])) { |
|
$clauses[] = "id_riferimento = :id_riferimento"; |
|
$bind[":id_riferimento"] = $filters["codice_riferimento"]; |
|
} |
|
if (!empty($filters["codice_scheda"])) { |
|
$clauses[] = "id_scheda = :id_scheda"; |
|
$bind[":id_scheda"] = $filters["codice_scheda"]; |
|
} |
|
if (!empty($filters["stato"])) { |
|
$clauses[] = "stato = :stato"; |
|
$bind[":stato"] = $filters["stato"]; |
|
} |
|
if (!empty($clauses)) { |
|
$conditions[] = "(" . implode(") OR (", $clauses) . ")"; |
|
} |
|
} |
|
|
|
$where = ""; |
|
if (!empty($conditions)) { |
|
$where = "WHERE (" . implode(" ) AND (", $conditions) . ")"; |
|
} |
|
|
|
$sorting = ""; |
|
if (!empty($order)) { |
|
$sorting = "ORDER BY {$order["column"]} {$order["dir"]}"; |
|
} |
|
|
|
$restrictions = ""; |
|
if (!empty($limits) && $limits["lenght"] > 0) { |
|
$restrictions = "LIMIT {$limits["start"]}, {$limits["lenght"]}"; |
|
} |
|
|
|
$filtered = $pdo->go("{$count} {$from} {$where} {$sorting}", $bind)->fetch(PDO::FETCH_COLUMN, 0); |
|
|
|
yield [ |
|
"recordsTotal" => $results, |
|
"recordsFiltered" => $filtered |
|
]; |
|
$results = $pdo->go("{$select} {$from} {$where} {$sorting} {$restrictions}", $bind); |
|
while ($result = $results->fetch(PDO::FETCH_ASSOC)) { |
|
|
|
yield $result; |
|
} |
|
} |
|
|
|
return null; |
|
} |
|
|
|
/** |
|
* Get the list of available statuses. |
|
* |
|
* @return array |
|
*/ |
|
public static function listAvailableStatusList(): ?array |
|
{ |
|
if(valid_json(file_get_contents(__DIR__ . "/stati.json"), $stati, true)) { |
|
return $stati; |
|
} |
|
return null; |
|
} |
|
|
|
/** |
|
* Retrieves a dictionary of errors within the npa |
|
* @param mixed $version |
|
* |
|
* @return array |
|
*/ |
|
public static function getErrorsDictionary(): ?array |
|
{ |
|
$erroriData = file_get_contents(__DIR__ . "/errori.json"); |
|
$erroriOverrideData = file_get_contents(__DIR__ . "/errori.override.json"); |
|
if(valid_json($erroriData, $errori, true)) { |
|
$errori = array_column($errori, null, "codice"); |
|
if(valid_json($erroriOverrideData, $erroriOverride, true)) { |
|
$erroriOverride = array_column($erroriOverride, null, "codice"); |
|
$errori = array_merge_recursive_distinct($errori, $erroriOverride); |
|
} |
|
return $errori; |
|
} |
|
return null; |
|
} |
|
|
|
/** |
|
* Get connected |
|
* |
|
* @return array |
|
*/ |
|
public const MODULI_COLLEGATI = [ |
|
"gare", "concorsi", "albo_fornitori" |
|
]; |
|
|
|
|
|
/** |
|
* Evaluates and returns a boolean value indicating whether the user is currently logged into the application |
|
* |
|
* @return bool |
|
*/ |
|
public function isUserLoggedIn(string $role = DelegheRoles::RP): Bool |
|
{ |
|
|
|
if (!empty($this->fascicolo["data"])) { |
|
$cf = $this->getUserFromRole($role, RoleSyncBehaviour::NEVER_SYNC); |
|
$timestamp = $this->pdo->go("SELECT `b_check_sessions`.`timestamp` FROM b_check_sessions JOIN b_utenti ON b_utenti.codice = b_check_sessions.codice_utente WHERE b_utenti.cf = :cf", [":cf" => $cf])->fetch(PDO::FETCH_COLUMN, 0); |
|
if (!empty($timestamp) && strtotime($timestamp) > strtotime("-3 hours")) { |
|
|
|
return true; |
|
} |
|
|
|
} |
|
|
|
return false; |
|
} |
|
|
|
|
|
|
|
/** |
|
* Retrieves and loads the data associated with a specific card |
|
* |
|
* @param mixed $codice_scheda |
|
* @param mixed $identificativo_scheda |
|
* @return bool |
|
*/ |
|
public function initCard(?int $codice_scheda = 0, ?string $identificativo_scheda = ""): Bool |
|
{ |
|
|
|
global $pdo; |
|
|
|
$bind = []; |
|
$scheda = null; |
|
|
|
if (!empty($codice_scheda)) { |
|
|
|
$clauses[] = "codice = :codice_scheda"; |
|
$bind[":codice_scheda"] = $codice_scheda; |
|
} |
|
|
|
if (!empty($this->fascicolo["codice"])) { |
|
|
|
$clauses[] = "codice_npa = :codice_fascicolo"; |
|
$bind[":codice_fascicolo"] = $this->fascicolo["codice"]; |
|
|
|
if (!empty($identificativo_scheda)) { |
|
|
|
$clauses[] = "id_scheda = :identificativo_scheda"; |
|
$bind[":identificativo_scheda"] = $identificativo_scheda; |
|
} |
|
} |
|
|
|
if (!empty($clauses)) { |
|
$where = implode(" AND ", $clauses); |
|
$sql = "SELECT * FROM b_npa_schede WHERE {$where} LIMIT 0, 1"; |
|
$scheda = $pdo->go($sql, $bind)->fetch(PDO::FETCH_ASSOC); |
|
if (!empty($scheda)) { |
|
// Verifica soft delete |
|
if (($scheda["soft_delete"] ?? "N") == "S") { |
|
return false; |
|
} |
|
$this->scheda = $scheda; |
|
$this->form = jsonToArray("{$this->path}/schede/{$this->scheda["id_scheda"]}.json"); |
|
if(!$this->loadNpaDossier($scheda["codice_npa"])) |
|
return false; |
|
if(empty($this->scheda["versione_scheda"])) { |
|
$this->scheda["versione_scheda"] = $this->version; |
|
} |
|
return true; |
|
} |
|
} |
|
|
|
if (empty($scheda)) { |
|
$metadata = $this->metadataSchede[$identificativo_scheda] ?? []; |
|
|
|
if (!empty($metadata)) { |
|
$this->scheda["codice"] = null; |
|
$this->scheda["id_scheda"] = $identificativo_scheda; |
|
$this->scheda["type"] = $metadata["tipologia"]; |
|
$this->scheda["stato"] = 0; |
|
if(empty($this->scheda["versione_scheda"])) { |
|
$this->scheda["versione_scheda"] = $this->version; |
|
} |
|
$this->form = jsonToArray("{$this->path}/schede/{$identificativo_scheda}.json"); |
|
return true; |
|
} |
|
} |
|
|
|
|
|
|
|
return false; |
|
} |
|
|
|
/** |
|
* Retrieves and loads information related to an NPA Dossier |
|
* |
|
* @param mixed $codice_fascicolo |
|
* @return void |
|
*/ |
|
public function loadNpaDossier(?int $codice_fascicolo = null, ?string $modulo_riferimento = null, ?int $id_riferimento = null): bool |
|
{ |
|
global $pdo; |
|
|
|
if (!empty($codice_fascicolo)) { |
|
|
|
$bind = [":codice_fascicolo" => $codice_fascicolo]; |
|
$sql = "SELECT * FROM b_npa WHERE codice = :codice_fascicolo ORDER BY revisione_fascicolo DESC LIMIT 0,1"; |
|
$fascicolo = $pdo->go($sql, $bind)->fetch(PDO::FETCH_ASSOC); |
|
|
|
} elseif (!is_null($modulo_riferimento) && !is_null($id_riferimento)) { |
|
|
|
$bind = []; |
|
$bind[":modulo_riferimento"] = $modulo_riferimento; |
|
$bind[":id_riferimento"] = $id_riferimento; |
|
$sql = "SELECT * FROM b_npa WHERE modulo_riferimento = :modulo_riferimento AND id_riferimento = :id_riferimento ORDER BY revisione_fascicolo DESC LIMIT 0,1"; |
|
$fascicolo = $pdo->go($sql, $bind)->fetch(PDO::FETCH_ASSOC); |
|
|
|
} |
|
|
|
if (! empty($fascicolo)) { |
|
$this->fascicolo = $fascicolo; |
|
if (empty($modulo_riferimento)) { |
|
$modulo_riferimento = $this->fascicolo["modulo_riferimento"]; |
|
} |
|
if (empty($id_riferimento)) { |
|
$id_riferimento = $this->fascicolo["id_riferimento"]; |
|
} |
|
} |
|
|
|
|
|
$schedeAppalto = $this->getLastCompletedCards(); |
|
if(!empty($schedeAppalto)) { |
|
foreach($schedeAppalto as $scheda) { |
|
if(valid_json($scheda["dati"], $dati, true)) { |
|
|
|
$stazioniAppaltanti = recursive_array_key_search_all("stazioniAppaltanti", $dati)[0] ?? []; |
|
|
|
if(!empty($stazioniAppaltanti)) { |
|
$stazioniAppaltanti = array_values($stazioniAppaltanti); |
|
$this->updateFascicolo(["stazioni_appaltanti" => json_encode($stazioniAppaltanti)]); |
|
break; |
|
} |
|
} |
|
} |
|
} |
|
|
|
|
|
// Detect fascicolo resettato |
|
if($codice_fascicolo < 0 || $id_riferimento < 0) { |
|
return false; |
|
} |
|
|
|
$this->initOggetto($modulo_riferimento, $id_riferimento); |
|
|
|
if (! empty($this->oggetto) && !is_a($this->oggetto, "AlboFornitori")) { |
|
|
|
$this->syncLots(); |
|
|
|
} |
|
|
|
$this->loadCards(); |
|
|
|
return true; |
|
|
|
} |
|
/** |
|
* Questo metodo inizializza un oggetto falso per le importazioni |
|
* |
|
* @return void |
|
*/ |
|
public function initFakeOggetto(string $modulo_riferimento) { |
|
if(empty($_SESSION["ente"]) || empty($_SESSION["utente"])) |
|
throw new Exception("Nessun ente/utente accessibile"); |
|
$codice_ente = $_SESSION["ente"]->codice; |
|
$ente = $_SESSION["ente"]->getInfo(); |
|
if(empty($ente)) |
|
throw new Exception("Impossibile trovare l'ente {$codice_ente}"); |
|
|
|
$utente = $_SESSION["utente"]; |
|
$userInfo = method_exists($utente, "getInfo") ? $utente->getInfo() : []; |
|
// Prendiamo il CF dell'utente |
|
$cf = $userInfo["cf"] ?? null; |
|
|
|
$this->updateFascicolo([ |
|
"data" => serialize([ |
|
"beneficiario" => ["codice_fiscale" => $ente["cf"], "codice_ausa" => $ente["codice_ausa"]], |
|
"rup" => ["codice_fiscale" => $cf], |
|
]), |
|
"modulo_riferimento" => $modulo_riferimento |
|
]); |
|
|
|
} |
|
public function initOggetto(string $modulo_riferimento, int $id_riferimento) { |
|
switch ($modulo_riferimento) { |
|
case 'albo_fornitori': |
|
$this->oggetto = AlboFornitori::init($id_riferimento); |
|
$this->oggetto->npa = $this; |
|
break; |
|
case 'concorsi': |
|
$this->oggetto = concorso::init($id_riferimento); |
|
$this->oggetto->npa = $this; |
|
break; |
|
case 'gare': |
|
$this->oggetto = gara::init($id_riferimento); |
|
// Per la scheda S1 è necessario riferirsi al primo round |
|
if(($this->scheda["id_scheda"] ?? null) === "S1") { |
|
$codice_primoRound = $this->oggetto->getRounds()[0]["codice"] ?? -1; |
|
if($codice_primoRound > 0) { |
|
$newOggetto = gara::init($this->oggetto->info["codice"], $codice_primoRound); |
|
if(!empty($newOggetto)) { |
|
$this->oggetto = $newOggetto; |
|
} |
|
} |
|
} |
|
$this->oggetto->npa = $this; |
|
break; |
|
default: |
|
$this->oggetto = null; |
|
break; |
|
|
|
} |
|
if(empty($this->fascicolo["modulo_riferimento"])) |
|
$this->fascicolo["modulo_riferimento"] = $modulo_riferimento; |
|
|
|
// Tiene i data aggiornati |
|
if(empty($this->fascicolo["data"]) || (empty($this->fascicolo["id_appalto"]) && empty($this->fascicolo["id_pianificazione"]))) { |
|
$current_data = serialize([ |
|
"benficiario" => $this->oggetto->getEnteBeneficiario(), |
|
"rup" => $this->oggetto->getRup() |
|
]); |
|
$this->updateFascicolo(["data" => $current_data]); |
|
} |
|
if (! empty($this->oggetto) && empty($this->fascicolo["codice"])) { |
|
|
|
$this->fascicolo["id_riferimento"] = $id_riferimento; |
|
$this->fascicolo["data"] = serialize([ |
|
"benficiario" => $this->oggetto->getEnteBeneficiario(), |
|
"rup" => $this->oggetto->getRup() |
|
]); |
|
|
|
} |
|
} |
|
|
|
/** |
|
* Retrieves and loads information related to lots |
|
* |
|
* @return void |
|
*/ |
|
public function syncLots(): void |
|
{ |
|
|
|
$lots = $this->oggetto->getLotti(); |
|
$missing = array_column($lots, "codice"); |
|
$existing = $this->pdo->go("SELECT * FROM b_npa_lotti WHERE codice_npa = :codice_npa", [":codice_npa" => $this->fascicolo["codice"]])->fetchAll(PDO::FETCH_ASSOC); |
|
if (!empty($existing)) { |
|
$missing = array_diff(array_column($lots, "codice"), array_column($existing, "lotto_riferimento")); |
|
} |
|
if (!empty($missing)) { |
|
|
|
$salva = new Salva(); |
|
$salva->debug = false; |
|
$salva->nome_tabella = "b_npa_lotti"; |
|
$salva->operazione = "INSERT"; |
|
$salva->ignore = ["timestamp", "timestamp_trasmissione"]; |
|
$lots = array_column($lots, null, "codice"); |
|
|
|
foreach ($missing as $codice) { |
|
|
|
if (!empty($lots[$codice])) { |
|
|
|
$salva->oggetto = [ |
|
"codice_npa" => $this->fascicolo["codice"], |
|
"lotto_riferimento" => $codice, |
|
"timestamp_creazione" => date('d/m/Y H:i:s') |
|
]; |
|
|
|
$salva->save(); |
|
} |
|
} |
|
} |
|
} |
|
|
|
/** |
|
* Sets information for a specific lot |
|
* |
|
* @return void |
|
*/ |
|
public function setLotto(Int $id): void |
|
{ |
|
$lot = $this->getLots($id); |
|
if (count($lot) == 1) { |
|
|
|
$this->lot = reset($lot); |
|
if(!empty($this->oggetto)) { |
|
$this->oggetto->setLotto($this->lot["codice"]); |
|
} |
|
$this->loadCards(); |
|
} |
|
} |
|
|
|
/** |
|
* Sets information for a specific lot |
|
* |
|
* @return void |
|
*/ |
|
public function unsetLotto(): void |
|
{ |
|
if (!empty($this->lot)) { |
|
$this->lot = null; |
|
$this->schede = null; |
|
if(!empty($this->oggetto->lotto)) { |
|
$this->oggetto->lotto = null; |
|
} |
|
} |
|
} |
|
|
|
/** |
|
* Retrieves information about lots |
|
* |
|
* @return array |
|
*/ |
|
public function getLots(Int $id = null): ?array |
|
{ |
|
|
|
$data = $this->oggetto->getLotti(); |
|
|
|
if (!empty($this->fascicolo["codice"])) { |
|
|
|
$sql = "SELECT lotto_riferimento, codice FROM b_npa_lotti WHERE codice_npa = :codice_npa"; |
|
$bind = [":codice_npa" => $this->fascicolo["codice"]]; |
|
if (!is_null($id)) { |
|
$sql = "{$sql} AND codice = :codice"; |
|
$bind[":codice"] = $id; |
|
} |
|
|
|
$lots = $this->pdo->go($sql, $bind)->fetchAll(PDO::FETCH_ASSOC); |
|
|
|
if (!empty($lots)) { |
|
|
|
$lots = array_column($lots, null, "lotto_riferimento"); |
|
$ids = array_keys($lots); |
|
foreach ($data as $index => $lot) { |
|
|
|
if (!in_array($lot["codice"], $ids)) { |
|
unset($data[$index]); |
|
} else { |
|
$data[$index]["codice_lotto_npa"] = $lots[$lot["codice"]]["codice"]; |
|
} |
|
} |
|
} |
|
} |
|
|
|
return $data; |
|
} |
|
|
|
/** |
|
* Ottiene la versione da utilizzare |
|
* |
|
* @param integer|null $codice_fascicolo |
|
* @param integer|null $codice_scheda |
|
* @return string|null Versione da uasre |
|
*/ |
|
public static function fetchVersion(?int $codice_fascicolo = null, ?int $codice_scheda = null) : string { |
|
|
|
if($codice_scheda !== null) { |
|
$scheda = self::fetchSchede(["versione_scheda", "stato"], [ |
|
"s.codice" => $codice_scheda |
|
], false, false); |
|
if(!empty($scheda)) { |
|
if($scheda["stato"] < 20) { |
|
return self::LATEST_SDK_VERSION; |
|
} |
|
return empty($scheda["versione_scheda"]) ? self::LATEST_SDK_VERSION : $scheda["versione_scheda"]; |
|
} |
|
} |
|
return self::LATEST_SDK_VERSION; |
|
} |
|
|
|
/** |
|
* Fetch and load the set of card for the given dossier. |
|
* |
|
* @return array |
|
*/ |
|
public static function fetchLotti(array $fields = ["*"], array $filters = [], bool $join_npa = false, bool $multiple = true): array |
|
{ |
|
$fields = array_filter($fields, function($field) { |
|
if($field === "*") return true; |
|
if(!myPDO::validFieldName($field)) { |
|
throw new Exception("Invalid field name {$field}"); |
|
} |
|
return true; |
|
}, ARRAY_FILTER_USE_BOTH); |
|
|
|
global $pdo; |
|
|
|
$fields_str = implode(",", $fields); |
|
$bind = []; |
|
$filter_str = []; |
|
|
|
// Per ogni filtro, lo aggiungiamo nei bind e nella query |
|
foreach ($filters as $field => $value) { |
|
if(!myPDO::validFieldName($field)) { |
|
throw new Exception("Invalid field name {$field}"); |
|
} |
|
$operand = "="; |
|
if (is_array($value)) { |
|
$operand = $value[0]; |
|
$value = $value[1]; |
|
} |
|
$bind_name = uniqid(); |
|
$bind[":{$bind_name}"] = $value; |
|
$filter_str[] = "{$field} $operand :{$bind_name}"; |
|
} |
|
// Ricostruiamo la query where |
|
if (!empty($filter_str)) { |
|
$filter_str = "WHERE " . implode(" AND ", $filter_str); |
|
} else { |
|
$filter_str = ""; |
|
} |
|
$join_str = ""; |
|
if ($join_npa) { |
|
if($fields_str == "*") { |
|
$fields_str .= ", l.codice AS codice, n.codice AS codice_npa"; |
|
} |
|
if (empty($filter_str)) { |
|
$filter_str .= " WHERE n.id_riferimento > 0"; |
|
} else { |
|
$filter_str .= " AND n.id_riferimento > 0"; |
|
} |
|
$join_str = "LEFT JOIN b_npa n ON n.codice = l.codice_npa"; |
|
} |
|
|
|
$limit_str = ""; |
|
if (!$multiple) { |
|
$limit_str = "LIMIT 1"; |
|
} |
|
|
|
$sql = "SELECT {$fields_str} FROM b_npa_lotti l {$join_str} {$filter_str} ORDER BY l.timestamp_creazione, l.codice ASC {$limit_str}"; |
|
|
|
$result = $pdo->go($sql, $bind); |
|
$lotti = $multiple ? $result->fetchAll(PDO::FETCH_ASSOC) : $result->fetch(PDO::FETCH_ASSOC); |
|
|
|
if (!is_array($lotti)) { |
|
return []; |
|
} |
|
return $lotti; |
|
} |
|
|
|
/** |
|
* Carica una lista di fascicoli con i filtri indicati |
|
* |
|
* @return array |
|
*/ |
|
public static function fetchDossier(array $fields = ["*"], array $filters = [], bool $multiple = true): array |
|
{ |
|
$fields = array_filter($fields, function($field) { |
|
if($field === "*") return true; |
|
if(!myPDO::validFieldName($field)) { |
|
throw new Exception("Invalid field name {$field}"); |
|
} |
|
return true; |
|
}, ARRAY_FILTER_USE_BOTH); |
|
global $pdo; |
|
// Aggiungo soft delete |
|
$filters["id_riferimento"] = [">", "0"]; |
|
|
|
$fields_str = implode(",", $fields); |
|
$bind = []; |
|
$filter_str = []; |
|
|
|
if(empty($filters["id_riferimento"])) |
|
$filters["id_riferimento"] = [">", "0"]; |
|
|
|
// Per ogni filtro, lo aggiungiamo nei bind e nella query |
|
foreach ($filters as $field => $value) { |
|
if(!myPDO::validFieldName($field)) { |
|
throw new Exception("Invalid field name {$field}"); |
|
} |
|
$operand = "="; |
|
if (is_array($value)) { |
|
$operand = $value[0]; |
|
$value = $value[1]; |
|
} |
|
$bind[":{$field}"] = $value; |
|
$filter_str[] = "`{$field}` $operand :{$field}"; |
|
} |
|
// Ricostruiamo la query where |
|
if (!empty($filter_str)) { |
|
$filter_str = "WHERE " . implode(" AND ", $filter_str); |
|
} else { |
|
$filter_str = ""; |
|
} |
|
|
|
$limit_str = ""; |
|
if (!$multiple) { |
|
$limit_str = "LIMIT 1"; |
|
} |
|
|
|
$sql = "SELECT {$fields_str} FROM b_npa {$filter_str} ORDER BY `codice`, `timestamp` ASC {$limit_str}"; |
|
$result = $pdo->go($sql, $bind); |
|
$fascicoli = $multiple ? $result->fetchAll(PDO::FETCH_ASSOC) : $result->fetch(PDO::FETCH_ASSOC); |
|
if (!is_array($fascicoli)) { |
|
return []; |
|
} |
|
return $fascicoli; |
|
} |
|
|
|
private static function buildFilter(array $filters, array &$bind): array |
|
{ |
|
$filter_str = []; |
|
|
|
foreach ($filters as $field => $value) { |
|
if (strpos($field, "or_") === 0) { |
|
// Supporto ai gruppi OR |
|
$or_filter_str = self::buildFilter($value, $bind); |
|
if (!empty($or_filter_str)) { |
|
$or_filter_str = implode(" OR ", $or_filter_str); |
|
$filter_str[] = "({$or_filter_str})"; |
|
} |
|
} else { |
|
if (!myPDO::validFieldName($field)) { |
|
throw new Exception("Invalid field name {$field}"); |
|
} |
|
$operand = "="; |
|
if (is_array($value)) { |
|
|
|
$operand = $value[0]; |
|
$value = $value[1]; |
|
} |
|
$bind_name = uniqid(); |
|
if ($operand === "IN" && is_array($value)) { |
|
if (!empty($value)) { |
|
$in_str = "{$field} IN ("; |
|
foreach ($value as $idx => $val) { |
|
$in_str .= ":{$bind_name}_{$idx},"; |
|
$bind[":{$bind_name}_{$idx}"] = $val; |
|
} |
|
$in_str = rtrim($in_str, ",") . ")"; |
|
$filter_str[] = $in_str; |
|
} |
|
} elseif (in_array($operand, ["OR", "AND"], true) && is_array($value)) { |
|
$in_str = "("; |
|
foreach ($value as $idx => $val) { |
|
$subOperand = "="; |
|
if (is_array($val)) { |
|
$subOperand = $val[0]; |
|
$val = $val[1]; |
|
} elseif ($val === null) { |
|
$subOperand = "IS"; |
|
} |
|
$in_str .= " {$field} {$subOperand} :{$bind_name}_{$idx} $operand"; |
|
$bind[":{$bind_name}_{$idx}"] = $val; |
|
} |
|
$in_str = rtrim($in_str, " $operand") . ")"; |
|
$filter_str[] = $in_str; |
|
} else { |
|
$bind[":{$bind_name}"] = $value; |
|
$filter_str[] = "{$field} $operand :{$bind_name}"; |
|
} |
|
} |
|
} |
|
return $filter_str; |
|
} |
|
|
|
/** |
|
* Fetch and load the set of card for the given dossier. |
|
* |
|
* @return array |
|
*/ |
|
public static function fetchSchede(array $fields = ["*"], array $filters = [], bool $join_npa = false, bool $multiple = true, $order = "ASC"): array |
|
{ |
|
$fields = array_filter($fields, function($field) { |
|
if($field === "*") return true; |
|
if(!myPDO::validFieldName($field)) { |
|
throw new Exception("Invalid field name {$field}"); |
|
} |
|
return true; |
|
}, ARRAY_FILTER_USE_BOTH); |
|
global $pdo; |
|
|
|
// Disambiguazione codice |
|
$codiceKey = array_search("codice", $fields); |
|
if($codiceKey !== false && $join_npa) { |
|
$fields[$codiceKey] = "s.codice"; |
|
} |
|
|
|
$fields_str = implode(",", $fields); |
|
$bind = []; |
|
$filter_str = []; |
|
// Aggiungo soft delete |
|
$filters["soft_delete"] = ["!=", "S"]; |
|
$join_str = ""; |
|
if ($join_npa) { |
|
if($fields_str == "*") { |
|
$fields_str .= ", s.codice AS codice, n.codice AS codice_npa"; |
|
} |
|
$join_str = "LEFT JOIN b_npa n ON n.codice = s.codice_npa"; |
|
if(empty($filters["id_riferimento"])) |
|
$filters["id_riferimento"] = [">", "0"]; |
|
} |
|
// Per ogni filtro, lo aggiungiamo nei bind e nella query |
|
$filter_str = self::buildFilter($filters, $bind); |
|
|
|
// Ricostruiamo la query where |
|
if (!empty($filter_str)) { |
|
$filter_str = "WHERE " . implode(" AND ", $filter_str); |
|
} else { |
|
$filter_str = ""; |
|
} |
|
|
|
$limit_str = ""; |
|
if (!$multiple) { |
|
$limit_str = "LIMIT 1"; |
|
} |
|
$orderBy = "s.codice"; |
|
$orderDir = $order; |
|
|
|
if(is_array($order)) { |
|
$orderBy = "s.{$order[0]}"; |
|
$orderDir = $order[1]; |
|
} |
|
$orderDir = $orderDir == "ASC" ? "ASC" : "DESC"; |
|
|
|
$sql = "SELECT {$fields_str} FROM b_npa_schede s {$join_str} {$filter_str} ORDER BY {$orderBy} {$orderDir} {$limit_str}"; |
|
|
|
$result = $pdo->go($sql, $bind); |
|
$schede = $multiple ? $result->fetchAll(PDO::FETCH_ASSOC) : $result->fetch(PDO::FETCH_ASSOC); |
|
if (!is_array($schede)) { |
|
return []; |
|
} |
|
return $schede; |
|
} |
|
|
|
/** |
|
* Ottiene le schede categorizzate per id_scheda |
|
* @param bool $multiple Se false viene ritornata solo l'ultima scheda per id_scheda |
|
* @param bool $only_current_lot . Se true, vengono caricate le schede relative al lotto corrente (o alla gara se non c'è un lotto). Se false vengono incluse quelle del lotto corrente E della gara |
|
* |
|
* @return array |
|
*/ |
|
public function getCardsByType($multiple = true, $only_current_lot = true): array |
|
{ |
|
$schede = $this->getCards($only_current_lot); |
|
$retval = []; |
|
foreach ($schede as $scheda) { |
|
$scheda = array_merge($scheda, $this->metadataSchede[$scheda["id_scheda"]]); |
|
|
|
if ($multiple) { |
|
if (empty($retval[$scheda["id_scheda"]])) { |
|
$retval[$scheda["id_scheda"]] = []; |
|
} |
|
$retval[$scheda["id_scheda"]][] = $scheda; |
|
} else { |
|
if (isset($retval[$scheda["id_scheda"]])) { |
|
if ($retval[$scheda["id_scheda"]]["stato"] < $scheda["stato"]) { |
|
$retval[$scheda["id_scheda"]] = $scheda; |
|
} |
|
} else { |
|
$retval[$scheda["id_scheda"]] = $scheda; |
|
} |
|
} |
|
} |
|
return $retval; |
|
} |
|
|
|
/** |
|
* Fetch and load the set of card for the given dossier. |
|
* |
|
* @return void |
|
*/ |
|
public function loadCards(): void |
|
{ |
|
|
|
if (!empty($this->fascicolo)) { |
|
$this->schede = $this->getCards(true); |
|
} |
|
} |
|
|
|
/** |
|
* Ritorna, se presente, l'ultima scheda completata, al fine di stabilire il flusso |
|
* |
|
* @param boolean $only_current_lot Se true ottiene solo dalla scheda generale e dal lotto attuale |
|
* @return array|null |
|
*/ |
|
public function lastCompletedCardForFlusso(bool $only_current_lot = true): ?array |
|
{ |
|
$cards = $this->getCards($only_current_lot, ["timestamp_trasmissione", "DESC"]); |
|
foreach($cards as $scheda) { |
|
$scheda = array_merge($scheda, $this->metadataSchede[$scheda["id_scheda"]]); |
|
// Evitiamo di far contare rettifiche o modifiche |
|
if(!empty($scheda["codice_modifica"]) || !empty($scheda["codice_rettifica"])) { |
|
continue; |
|
} |
|
// Contano invece come complete schede che sono state modificate o rettificate |
|
if(self::checkCardComplete($scheda) || in_array($scheda["stato"], [-10, -20]) ) { |
|
return $scheda; |
|
} |
|
} |
|
return null; |
|
} |
|
|
|
/** |
|
* Ritorna, se disponibili, tutte le schede confermate |
|
* |
|
* @param mixed $only_current_lot |
|
* @return array |
|
*/ |
|
public function getConfirmedCards(bool $only_current_lot = true) : ?array { |
|
$cards = $this->getCards($only_current_lot, true); |
|
if(! empty($cards)) { |
|
foreach($cards as $index => &$scheda) { |
|
$scheda = array_merge($scheda, $this->metadataSchede[$scheda["id_scheda"]]); |
|
if($scheda["stato"] < self::TRASMITTED_STATO) { |
|
unset($cards[$index]); |
|
} |
|
} |
|
return $cards; |
|
} |
|
return null; |
|
} |
|
|
|
/** |
|
* Ritorna, se disponibili, tutte le schede completate |
|
* |
|
* @param mixed $only_current_lot |
|
* @return array |
|
*/ |
|
public function getCompletedCards(bool $only_current_lot = true) : ?array { |
|
$cards = $this->getCards($only_current_lot, true); |
|
if(! empty($cards)) { |
|
foreach($cards as $index => &$scheda) { |
|
$scheda = array_merge($scheda, $this->metadataSchede[$scheda["id_scheda"]]); |
|
if(! self::checkCardComplete($scheda) && ! in_array($scheda["stato"], [-10, -20]) ) { |
|
unset($cards[$index]); |
|
} |
|
} |
|
return $cards; |
|
} |
|
return null; |
|
} |
|
|
|
/** |
|
* Ritorna le ultime schede completate |
|
* |
|
* @param boolean $only_current_lot Se true ottiene solo dalla scheda generale e dal lotto attuale |
|
* @return array |
|
*/ |
|
public function getLastCompletedCards(bool $only_current_lot = true): array |
|
{ |
|
$retval = []; |
|
$cards = $this->getCards($only_current_lot, ["timestamp_trasmissione", "DESC"]); |
|
foreach($cards as $scheda) { |
|
$scheda = array_merge($scheda, $this->metadataSchede[$scheda["id_scheda"]]); |
|
// Contano invece come complete schede che sono state modificate o rettificate |
|
if(self::checkCardComplete($scheda) || in_array($scheda["stato"], [-10, -20]) ) { |
|
$retval[] = $scheda; |
|
} |
|
} |
|
return $retval; |
|
} |
|
|
|
/** |
|
* Ottiene le carte presenti per questo fascicolo |
|
* |
|
* @param boolean $only_current_lot Se true ottiene solo dalla scheda generale e dal lotto attuale |
|
* @param string|array $desc Se stringa, ordina per codice $order - altrimenti ordina per $order[0] $order[¹] |
|
* @return array |
|
*/ |
|
public function getCards(bool $only_current_lot = true, $order = "ASC"): array |
|
{ |
|
|
|
if (!empty($this->fascicolo)) { |
|
|
|
$select = ["codice", "dati", "codice_rettifica", "codice_modifica", "codice_lotto", "id_scheda", "stato", "errore", "timestamp_trasmissione", "versione_scheda", "timestamp_creazione"]; |
|
$filters = ["codice_npa" => $this->fascicolo["codice"]]; |
|
|
|
if (!empty($this->scheda["codice_lotto"]) || !empty($this->lot["codice_lotto_npa"])) { |
|
$codice_lotto = empty($this->scheda["codice_lotto"]) ? $this->lot["codice_lotto_npa"] : $this->scheda["codice_lotto"]; |
|
if ($only_current_lot) { |
|
$filters["codice_lotto"] = $codice_lotto; |
|
} else { |
|
$filters["codice_lotto"] = ["OR", [$codice_lotto, null, '', 0]]; |
|
} |
|
} else { |
|
$filters["codice_lotto"] = ["OR", [null, '', 0]]; |
|
} |
|
return self::fetchSchede($select, $filters, false, true, $order); |
|
} |
|
return []; |
|
} |
|
|
|
/** |
|
* Checking if the card is in an unmodifiable state |
|
* |
|
* @return bool |
|
*/ |
|
public function checkLock(): Bool |
|
{ |
|
if (!empty($this->scheda) && $this->scheda["stato"] >= 40 && ($this->scheda["errore"] ?? "NO") === "NO") { |
|
return true; |
|
} |
|
if (!empty($this->scheda) && $this->scheda["stato"] >= 90) { |
|
return true; |
|
} |
|
if (!empty($this->scheda) && $this->scheda["stato"] < 0) { |
|
return true; |
|
} |
|
return false; |
|
} |
|
|
|
/** |
|
* Ottiene le schede successive |
|
* |
|
* @param String|null $settore Settore corrente, se null viene ignorato |
|
* @param String|null $soglia Soglia corrente, se null viene ignorata |
|
* @param boolean|null $concessione Si tratta di una concessione? Se null viene ignorato |
|
* @param boolean|null $concorso Si tratta di un concorso? Se null viene ignorato |
|
* @return array Ritorna una lista di schede |
|
*/ |
|
public function getCardsAndItsDescendantsList(string $settore = null, ?string $soglia = null, ?bool $concessione = null, ?bool $concorso = null): array |
|
{ |
|
$schede = $this->getCards(false); |
|
if (!empty($schede)) { |
|
|
|
$list = array_filter($schede, fn ($card) => !empty($card["timestamp_trasmissione"])); |
|
if (empty($list)) { |
|
$list = $schede; |
|
} |
|
$card = array_values($list)[0]; |
|
$cards = $this->getNextCards(); |
|
$cards = array_merge(array_column($list, "id_scheda"), $cards); |
|
$cards = array_unique($cards); |
|
|
|
$cards = array_filter($this->metadataSchede, function (&$card, $key) use ($cards, $settore, $soglia, $concessione, $concorso) { |
|
|
|
if (in_array($key, $cards)) { |
|
|
|
if (!is_null($settore)) { |
|
|
|
if (!in_array($settore, $card["settori"])) { |
|
|
|
return false; |
|
} |
|
} |
|
|
|
if (!is_null($soglia)) { |
|
|
|
if ($soglia === "soprasoglia" && !$card[$soglia]) { |
|
|
|
return false; |
|
} |
|
} |
|
|
|
if(!is_null($concessione) && !is_null($card["concessione"])) { |
|
if($card["concessione"] != $concessione) { |
|
return false; |
|
} |
|
} |
|
|
|
if(!is_null($concorso) && !is_null($card["concorso"])) { |
|
if($card["concorso"] != $concorso) { |
|
return false; |
|
} |
|
} |
|
|
|
return true; |
|
} |
|
|
|
return false; |
|
}, ARRAY_FILTER_USE_BOTH); |
|
|
|
foreach ($cards as $key => &$card) { |
|
|
|
if (!empty($list[$key])) { |
|
|
|
$card["codice"] = $list[$key]["codice"]; |
|
} |
|
} |
|
return $cards; |
|
} |
|
|
|
return []; |
|
} |
|
|
|
/** |
|
* Organizes or sorts the cards based on their category |
|
* |
|
* @param mixed $cards |
|
* @return array |
|
*/ |
|
public function arrangeCardsByCategory(array $cards): array |
|
{ |
|
|
|
$arranged = []; |
|
|
|
foreach ($cards as $id => $card) { |
|
|
|
$arranged[$card["categoria"]][$id] = $card; |
|
} |
|
|
|
$arranged = array_replace(["pianificazione" => [], "pubblicazione" => [], "affidamento" => [], "aggiudicazione" => [], "esecuzione" => []], $arranged); |
|
$arranged = array_filter($arranged); |
|
|
|
return $arranged; |
|
} |
|
|
|
/** |
|
* Riordina una lista di schede in base alla gerarchia. |
|
* |
|
* @param array $cards Lista di schede in formato ["id" => scheda] (scheda non è usato, quindi può essere vuoto) |
|
* @return array |
|
*/ |
|
public static function sortCardsByHierarchy(array $cards): array |
|
{ |
|
// Sort in base alla profondità |
|
foreach ($cards as $_id => $_scheda) { |
|
// Salviamo ID all'interno della scheda perchè sarà usato come riferimento |
|
$cards[$_id]["id"] = $_id; |
|
} |
|
// Ordiniamo in base alla profondità (questo lo rende una lista ordinata) |
|
usort($cards, function ($a, $b) { |
|
return $a["order"] > $b["order"]; |
|
}); |
|
|
|
// Settiamo nuovamente id come chiave |
|
return array_column($cards, null, "id"); |
|
} |
|
/** |
|
* Selectively retrieves cards based on their type |
|
* |
|
* @param mixed $cards |
|
* @return array |
|
*/ |
|
public function filterCardsByType(array $cards, Bool $lot_related): array |
|
{ |
|
|
|
return array_filter($cards, function ($info) use ($lot_related) { |
|
|
|
return $info["lotto"] === $lot_related; |
|
}); |
|
} |
|
|
|
/** |
|
* Elimina la scheda corrente |
|
* |
|
* @return boolean |
|
*/ |
|
public function deleteScheda(?string &$message=null): bool |
|
{ |
|
if ($this->isCardDeletable()) { |
|
// Cancellazione via API |
|
if(!empty($this->scheda["uuid"])) { |
|
return $this->scheda["stato"] > self::TRASMITTED_STATO ? $this->deleteNotice($message) : $this->delete($message); |
|
} |
|
|
|
$bind = array(":codice" => $this->scheda["codice"]); |
|
$sql = "UPDATE b_npa_schede SET soft_delete = 'S' WHERE codice = :codice"; |
|
$ris = $this->pdo->go($sql, $bind); |
|
|
|
if ($ris->rowCount() === 1) { |
|
scriviLog("b_npa_schede", "DELETE", $this->pdo->getSQL(), $_POST["codice"]); |
|
return true; |
|
} |
|
|
|
} |
|
$message = __("Impossibile cancellare la scheda"); |
|
return false; |
|
} |
|
/** |
|
* Metodo veloce per cambiare una voce nel fascicolo |
|
* |
|
* @param array $data Dati da cambiare, associativo |
|
* @return boolean True in caso di successo |
|
*/ |
|
public function updateFascicolo(array $data=[]): bool |
|
{ |
|
$result = false; |
|
if($this->fascicolo["codice"] > 0) { |
|
$salva = new Salva(); |
|
$salva->nome_tabella = "b_npa"; |
|
$salva->operazione = "UPDATE"; |
|
$salva->debug = false; |
|
$data["codice"] = intval($this->fascicolo["codice"]); |
|
if(!empty($data["codice"])) { |
|
$salva->expect = array_keys($data); |
|
$salva->oggetto = $data; |
|
$result = $salva->save(); |
|
} |
|
} |
|
foreach($data as $key => $value) |
|
{ |
|
$this->fascicolo[$key] = $value; |
|
} |
|
|
|
return $result == true; |
|
} |
|
public function updateScheda(array $data=[]): bool |
|
{ |
|
$result = false; |
|
if($this->scheda["codice"] > 0) { |
|
$salva = new Salva(); |
|
$salva->nome_tabella = "b_npa_schede"; |
|
$salva->operazione = "UPDATE"; |
|
$data["codice"] = intval($this->scheda["codice"]); |
|
if(!empty($data["codice"])) { |
|
$salva->expect = array_keys($data); |
|
$salva->oggetto = $data; |
|
$result = $salva->save(); |
|
} |
|
foreach($data as $key => $value) |
|
{ |
|
$this->scheda[$key] = $value; |
|
} |
|
} |
|
return $result == true; |
|
} |
|
/** |
|
* Store form data. |
|
* |
|
* @param array $data |
|
* @return bool |
|
*/ |
|
public function save(array $data = [], bool $scheda = true): ?array |
|
{ |
|
|
|
$salva = new Salva(); |
|
$salva->debug = false; |
|
if (empty($this->fascicolo["codice"])) { |
|
|
|
$info = []; |
|
|
|
if (!empty($this->oggetto)) { |
|
|
|
$info["benficiario"] = $this->oggetto->getEnteBeneficiario(); |
|
$info["rup"] = $this->oggetto->getRup(); |
|
} else { |
|
|
|
return null; |
|
} |
|
|
|
$this->fascicolo = [ |
|
"codice_ente" => $data["codice_ente"], |
|
"modulo_riferimento" => in_array($data["modulo_riferimento"], ["gare", "concorsi", "albo_fornitori", "contratti"]) ? $data["modulo_riferimento"] : null, |
|
"id_riferimento" => $data["id_riferimento"], |
|
"data" => serialize($info), |
|
"versione_interna" => self::LATEST_INTERNAL_VERSION |
|
]; |
|
|
|
$salva->ignore = ["timestamp"]; |
|
$salva->nome_tabella = "b_npa"; |
|
$salva->operazione = "INSERT"; |
|
$salva->oggetto = $this->fascicolo; |
|
$this->fascicolo["codice"] = $salva->save(); |
|
if (empty($this->fascicolo["codice"])) { |
|
|
|
throw new Exception("Errore nel salvataggio del fascicolo."); |
|
} |
|
} |
|
|
|
if (!$scheda) { |
|
return null; |
|
} |
|
|
|
$record = $this->scheda; |
|
$record["dati"] = $data["npa"]; |
|
$record["dati"] = json_encode($record["dati"], JSON_PRETTY_PRINT); |
|
$record["codice_npa"] = $this->fascicolo["codice"]; |
|
$record["codice_lotto"] = $data["codice_lotto"] ?? null; |
|
$record["round_riferimento"] = $data["round_riferimento"]; |
|
$record["stato"] = $data["bozza"] == 0 ? 10 : 0; |
|
$record["soft_delete"] = "N"; |
|
$record["versione_scheda"] = $this->version; |
|
|
|
$salva->nome_tabella = "b_npa_schede"; |
|
$operazioneScheda = $salva->operazione = "UPDATE"; |
|
$salva->oggetto = $record; |
|
// Ignoriamo il codice rettifica dopo la prima iterazione |
|
$salva->ignore = ["codice_npa", "timestamp", "timestamp_creazione", "timestamp_trasmissione", "pending_cancellazione", "codice_rettifica", "codice_modifica"]; |
|
if (empty($record["codice"])) { |
|
$operazioneScheda = $salva->operazione = "INSERT"; |
|
$salva->ignore = ["timestamp", "timestamp_trasmissione", "pending_cancellazione"]; |
|
$salva->oggetto["errore"] = "NO"; |
|
$salva->oggetto["timestamp_creazione"] = date("d/m/Y H:i:s"); |
|
$salva->oggetto["errore"] = "NO"; |
|
$salva->oggetto["versione_scheda"] = self::LATEST_SDK_VERSION; |
|
if(isset($data["codice_rettifica"]) && is_numeric($data["codice_rettifica"])) { |
|
$salva->oggetto["codice_rettifica"] = $data["codice_rettifica"]; |
|
} else { |
|
$salva->ignore[] = "codice_rettifica"; |
|
} |
|
if(isset($data["codice_modifica"]) && is_numeric($data["codice_modifica"])) { |
|
$salva->oggetto["codice_modifica"] = $data["codice_modifica"]; |
|
} else { |
|
$salva->ignore[] = "codice_modifica"; |
|
} |
|
|
|
} |
|
if(isset($data["codice_scheda_guue"]) && is_numeric($data["codice_scheda_guue"])) { |
|
$salva->oggetto["codice_scheda_guue"] = $data["codice_scheda_guue"]; |
|
} else { |
|
$salva->ignore[] = "codice_scheda_guue"; |
|
} |
|
$record["codice"] = $salva->save(); |
|
if (!empty($record["codice"])) { |
|
|
|
$this->scheda = $record; |
|
|
|
if ($_SESSION["utente"]->gerarchia > 15 && $salva->operazione == "INSERT") { |
|
|
|
$permesso = [ |
|
"sezione" => "npa", |
|
"codice_elemento" => $this->info["codice"], |
|
"codice_gestore" => $_SESSION["ente"]->codice, |
|
"funzione" => "0", |
|
"codice_utente" => $_SESSION["utente"]->codice, |
|
]; |
|
|
|
$salva_p = new salva(); |
|
$salva_p->debug = false; |
|
$salva_p->nome_tabella = "r_permessi"; |
|
$salva_p->operazione = "INSERT"; |
|
$salva_p->oggetto = $permesso; |
|
$salva_p->save(); |
|
} |
|
if (isset($this->oggetto) && method_exists($this->oggetto,"addToLog")) { |
|
$this->oggetto->addToLog($operazioneScheda,"Scheda ANAC: " . $this->scheda["id_scheda"]); |
|
} |
|
return $record; |
|
} |
|
return null; |
|
} |
|
|
|
/** |
|
* Rretrieves the codes associated with cost centers |
|
* |
|
* @param string $identifier |
|
* @return array |
|
*/ |
|
public function getCodiciCentriDiCosto(String $identifier): ?array |
|
{ |
|
$retval = []; |
|
if(DEVELOP_ENV) { |
|
$source_anac = $this->getCodiciCentriDiCostoFromAnac($identifier); |
|
$source_db = self::getCodiciCentriDiCostoFromDb($identifier); |
|
|
|
foreach($source_anac as $uuid => $centro) { |
|
$retval[$uuid] = ">>> $centro ({$uuid}) [COLL]"; |
|
} |
|
foreach($source_db as $uuid => $centro) { |
|
if(isset($retval[$uuid])) { |
|
$retval[$uuid] = "$centro ({$uuid}) [COLL+PROD]"; |
|
} else { |
|
$retval[$uuid] = "$centro ({$uuid}) [PROD]"; |
|
} |
|
|
|
|
|
} |
|
|
|
} else { |
|
$retval = $this->getCodiciCentriDiCostoFromAnac($identifier); |
|
if(empty($retval)) |
|
$retval = $this->getCodiciCentriDiCostoFromDB($identifier); |
|
|
|
|
|
foreach ($retval as $uuid => $centro) { |
|
$retval[$uuid] = "$centro ({$uuid})"; |
|
} |
|
} |
|
|
|
|
|
|
|
return $retval; |
|
} |
|
|
|
/** |
|
* Rretrieves the codes associated with cost centers |
|
* |
|
* @param string $identifier |
|
* |
|
* @return array |
|
*/ |
|
public static function getCodiciCentriDiCostoFromDB(String $identifier): ?array |
|
{ |
|
global $pdo; |
|
// Nota: su necessità questo metodo può diventare statico |
|
$identifier = trim(strtoupper($identifier)); |
|
$centri = $pdo->go( |
|
"SELECT * FROM b_centri_costo WHERE cf = :cf", |
|
["cf" => $identifier] |
|
)->fetchAll(PDO::FETCH_ASSOC); |
|
|
|
|
|
if (!is_array($centri) || empty($centri)) { |
|
return []; |
|
} |
|
|
|
$retval = []; |
|
foreach ($centri as $centro) { |
|
$retval[$centro["uuid"]] = $centro["denominazione"]; |
|
if($centro["attivo"] !== "S") { |
|
$retval[$centro["uuid"]] .= " (Non Attivo)"; |
|
} |
|
} |
|
return $retval; |
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
* Retrieves keys and UUIDs (Universally Unique Identifiers) associated with the current request |
|
* |
|
* @return array |
|
*/ |
|
private function getKeysAndUUIDsForCurrentRequest(?string $type = null): ?array |
|
{ |
|
|
|
$idAppalto = $this->getUUID("appalto"); |
|
$idPianificazione = $this->getUUID("pianificazione"); |
|
$idScheda = $this->getUUID("scheda"); |
|
if (empty($type)) { $type = $this->scheda["type"]; } |
|
|
|
if ($type == "pianificazione") { |
|
|
|
return [ |
|
"idPianificazione" => $idPianificazione, |
|
"idScheda" => $idScheda |
|
]; |
|
} |
|
|
|
return [ |
|
"idAppalto" => $idAppalto, |
|
"idScheda" => $idScheda |
|
]; |
|
|
|
} |
|
/** |
|
* Chiude tutte le operazioni in corso per questa scheda, |
|
* risolvendo eventuali problemi prima che nascano. |
|
* |
|
* @return void |
|
*/ |
|
public function closeAllPendingOperations() { |
|
$this->pdo->go( |
|
"UPDATE b_npa_operazioni_schede SET esito = 'KO' WHERE (esito IS NULL OR esito = '') AND codice_scheda = :codice_scheda", |
|
[":codice_scheda" => $this->scheda["codice"]] |
|
); |
|
} |
|
|
|
public static function fetchLastOperation(?int $card_id = null, ?string $operation = null, $esito = false, bool $multiple = false, ?int $lot = null, ?string $codice_partecipante = null) : ?array { |
|
global $pdo; |
|
$bind = [":codice_scheda" => $card_id]; |
|
$sql = "SELECT * FROM b_npa_operazioni_schede WHERE ("; |
|
$sql = "{$sql} codice_scheda = :codice_scheda"; |
|
if(empty($bind[":codice_scheda"])) { |
|
$sql = "{$sql} OR codice_scheda IS NULL"; |
|
} |
|
$sql = "{$sql} )"; |
|
if (!empty($lot)) { |
|
$sql = "{$sql} AND codice_lotto = :codice_lotto"; |
|
$bind[":codice_lotto"] = $lot; |
|
} |
|
if (!empty($operation)) { |
|
$sql = "{$sql} AND operazione = :operazione"; |
|
$bind[":operazione"] = $operation; |
|
} |
|
if(!empty($codice_partecipante)) { |
|
$sql = "{$sql} AND codice_partecipante = :codice_partecipante"; |
|
$bind[":codice_partecipante"] = $codice_partecipante; |
|
} |
|
if($esito === null) { |
|
$sql .= " AND (esito IS NULL OR esito = '' OR esito = 0)"; |
|
} elseif($esito !== false) { |
|
$sql .= " AND esito = :esito"; |
|
$bind[":esito"] = $esito; |
|
} |
|
$sql .= " ORDER BY timestamp DESC"; |
|
if(!$multiple) |
|
$sql = "{$sql} LIMIT 0,1"; |
|
$result = $pdo->go($sql, $bind); |
|
if ($result->rowCount() > 0) { |
|
$operations = $result->fetchAll(PDO::FETCH_ASSOC); |
|
return $multiple ? $operations : $operations[0]; |
|
} |
|
return null; |
|
} |
|
|
|
/** |
|
* Ottiene il link alla scheda corrente |
|
* |
|
* @return string|null |
|
*/ |
|
public function getCardLink() : ?string |
|
{ |
|
if(empty($this->scheda["codice"])) return null; |
|
|
|
$query = [ |
|
"scheda" => $this->scheda["id_scheda"], |
|
"codice" => $this->scheda["codice"], |
|
"modulo_riferimento" => $this->fascicolo["modulo_riferimento"] ?? null, |
|
"id_riferimento" => $this->fascicolo["id_riferimento"] ?? null, |
|
]; |
|
if ($this->metadataSchede[$this->scheda["id_scheda"]]["lotto"]) { |
|
$query["codice_lotto"] = $this->scheda["codice_lotto"]; |
|
} |
|
|
|
return "/backend/npa/edit.php?" . http_build_query($query); |
|
} |
|
|
|
/** Retrieves the latest operation associated with a given card |
|
* |
|
* @param ?int $card_id |
|
* @param ?string $operation |
|
* @param ?bool $esito |
|
* @param ?bool $multiple |
|
* @param ?int $lot |
|
* @return array |
|
*/ |
|
public function getLastOperation(?int $card_id = null, ?string $operation = null, $esito = false, bool $multiple = false, ?int $lot = null): ?array |
|
{ |
|
return self::fetchLastOperation($card_id ?? $this->scheda["codice"], $operation, $esito, $multiple, $lot); |
|
} |
|
|
|
|
|
/** |
|
* Recupera il notice_id inviato ad ANAC nei precedenti invii |
|
* |
|
* @return string|null |
|
*/ |
|
private function retrieveGUUESentNoticeID() : ?string |
|
{ |
|
if(!$this->canDoRequest()) return null; |
|
$readData = $this->read(); |
|
if(!empty($readData->_scheda)) { |
|
$scheda = $readData->_scheda; |
|
|
|
if (isset($scheda->body->eform)) { |
|
$eform = base64_decode($scheda->body->eform); |
|
|
|
if($eform) { |
|
libxml_use_internal_errors(true); |
|
$eform_xml = new DOMDocument(); |
|
$eform_xml->loadXML($eform); |
|
if(!empty(libxml_get_errors())) |
|
return null; |
|
$xpath10 = new DOMXPath($eform_xml); |
|
libxml_clear_errors(); |
|
|
|
|
|
return $xpath10->evaluate("string(/*/cbc:ID[@schemeName='notice-id'])"); |
|
} |
|
} |
|
} |
|
return null; |
|
} |
|
/** |
|
* Examines whether cards associated with the given tender require either GUUE or ESPD CML documents |
|
* |
|
* @return void |
|
*/ |
|
public function checkIfCardNeedsXMLDocument(): Bool |
|
{ |
|
if(empty($this->check)) { |
|
|
|
$this->check = [ |
|
"eform" => ["required" => false, "ready" => false], |
|
"espd" => ["required" => false, "ready" => false] |
|
]; |
|
|
|
if (!empty($this->scheda["id_scheda"]) && !empty($this->form["content"])) { |
|
|
|
if (!empty($this->oggetto)) { |
|
|
|
if (!empty(array_column($this->form["content"], null, "id")["espd"])) { |
|
|
|
$this->check["espd"]["required"] = true; |
|
$this->check["espd"]["xml"] = $this->oggetto->getManagerDGUE()->getRequestXML(); |
|
if (!empty($this->check["espd"]["xml"])) { |
|
|
|
$this->check["espd"]["ready"] = true; |
|
} else { |
|
|
|
$this->check["espd"]["ready"] = false; |
|
$check = false; |
|
} |
|
} |
|
} |
|
|
|
if (!empty($this->metadataSchede[$this->scheda["id_scheda"]]["guue"])) { |
|
|
|
$this->check["eform"]["required"] = true; |
|
|
|
if(!empty($this->scheda["codice_scheda_guue"])) { |
|
$idScheda = $this->scheda["codice_scheda_guue"]; |
|
if($this->ted === null) { |
|
try { |
|
$this->ted = new eFormsManager(eFormsManager::ANY_NOTICE_TYPE, $idScheda, $this); |
|
} catch(\Throwable $t) { |
|
$this->ted = null; |
|
} |
|
} |
|
|
|
|
|
if($this->ted) { |
|
// Verifica che la notice_id corrisponda |
|
if(!empty($this->scheda["guue_sent_notice_id"])) { |
|
$eform_notice_id = $this->ted->getNoticeID(); |
|
$sent_notice_id = $this->scheda["guue_sent_notice_id"]; |
|
|
|
|
|
// Se non corrisponde, forziamo |
|
$this->ted->overrideNoticeID($sent_notice_id); |
|
} |
|
$this->check["eform"]["codice"] = $this->scheda["codice_scheda_guue"]; |
|
$this->check["eform"]["ready"] = true; |
|
$this->check["eform"]["notice_id"] = $this->ted->getNoticeID(); |
|
$this->check["eform"]["ted"] = $this->ted; |
|
$this->check["eform"]["ready_for_transmission"] = $this->ted->isReadyForAnacTransmission(); |
|
} else { |
|
$this->check["eform"]["ready"] = false; |
|
$this->updateScheda(["codice_scheda_guue" => 0]); |
|
} |
|
|
|
|
|
} else { |
|
$this->check["eform"]["ready"] = false; |
|
} |
|
} |
|
} |
|
} |
|
return !$this->check["eform"]["required"] || ($this->check["eform"]["required"] && $this->check["eform"]["ready"]) && |
|
!$this->check["espd"]["required"] || ($this->check["espd"]["required"] && $this->check["espd"]["ready"]); |
|
} |
|
|
|
|
|
/** |
|
* Updates the CIG (Contract Identification Code) within the application |
|
* |
|
* @param String $identifier |
|
* @param String $cig |
|
* @return bool |
|
*/ |
|
private function updateCig(String $identifier, String $cig): Bool |
|
{ |
|
|
|
if (!empty($this->fascicolo)) { |
|
|
|
$select = "SELECT b_npa_lotti.codice"; |
|
$select = "{$select}, b_npa_lotti.lotto_riferimento AS id_riferimento"; |
|
$from = "FROM b_npa_lotti"; |
|
$where = "WHERE codice_npa = :codice_npa"; |
|
$bind = [":codice_npa" => $this->fascicolo["codice"]]; |
|
|
|
switch ($this->fascicolo["modulo_riferimento"]) { |
|
case 'concorsi': |
|
$select = "{$select}, b_concorsi_lotti.numero"; |
|
$join = "JOIN b_concorsi_lotti ON b_concorsi_lotti.codice = b_npa_lotti.lotto_riferimento"; |
|
$where = "{$where} AND b_concorsi_lotti.numero = :numero"; |
|
$bind[":numero"] = intval(str_replace("LOT-", "", $identifier)); |
|
break; |
|
case 'gare': |
|
$select = "{$select}, b_gare_lotti.numero"; |
|
$join = "JOIN b_gare_lotti ON b_gare_lotti.codice = b_npa_lotti.lotto_riferimento"; |
|
$where = "{$where} AND b_gare_lotti.numero = :numero"; |
|
$bind[":numero"] = intval(str_replace("LOT-", "", $identifier)); |
|
break; |
|
default: |
|
break; |
|
} |
|
|
|
$lot = $this->pdo->go("{$select} {$from} {$join} {$where}", $bind)->fetch(PDO::FETCH_ASSOC); |
|
if (!empty($lot["codice"])) { |
|
|
|
$this->pdo->go("UPDATE b_npa_lotti SET cig = :cig WHERE codice = :codice", [":codice" => $lot["codice"], ":cig" => $cig]); |
|
if (!empty($this->oggetto)) { |
|
|
|
$this->oggetto->updateCig($lot["id_riferimento"], $cig); |
|
return true; |
|
} |
|
} |
|
} |
|
|
|
return false; |
|
} |
|
|
|
/** |
|
* Verifica che l'istanza di npa corrente abbia un dossier che si può cancellare |
|
* |
|
* @return boolean |
|
*/ |
|
public function canClearDossier() { |
|
|
|
|
|
|
|
// Otteniamo il codice |
|
$codice = $this->fascicolo["codice"] ?? null; |
|
if($codice === null) return false; |
|
|
|
// Verifichiamo che non ci sia alcuna scheda trasmessa |
|
$filterSchede = [ |
|
"stato" => [">=", 20], // Solo schede trasmesse in su |
|
"codice_npa" => $codice, // Solo schede di questo fascicolo |
|
]; |
|
|
|
$schede_trasmesse = self::fetchSchede(["id_scheda"], $filterSchede); |
|
|
|
return empty($schede_trasmesse); |
|
} |
|
|
|
/** |
|
* Elimina il dossier dell'istanza NPA attuale |
|
* |
|
* @return void |
|
*/ |
|
public function clearDossier($full = false) { |
|
|
|
|
|
// Otteniamo il codice corrente |
|
$codice = $this->fascicolo["codice"] ?? null; |
|
$data = $this->fascicolo["data"] ?? null; |
|
|
|
// Controlliamo se possiamo |
|
if($codice === null || $data === null || !$this->canClearDossier()) return; |
|
|
|
// Verifichiamo i permessi |
|
$canFullyClear = true; |
|
|
|
// Se è un cron skippiamo la verifica utente |
|
$isCron = defined("__CMDS_JOB_EXEC") && !empty(__CMDS_JOB_EXEC); |
|
if (!$isCron) { |
|
$utente = $_SESSION["utente"] ?? null; |
|
|
|
if(!$utente) { |
|
return false; |
|
} |
|
// Altrimenti verifichiamo chi può fare un clear completo |
|
$canFullyClear = $utente->isSupportoOrRoot(); |
|
} |
|
if(!$canFullyClear && $full) return; |
|
|
|
if($full) { |
|
// Supporto e root può pulire il fascicolo per intero |
|
$this->pdo->go("UPDATE b_npa SET data = NULL WHERE codice = :codice", [":codice" => $codice]); |
|
} else if(isset($this->oggetto) && method_exists($this->oggetto, "getRup")) { |
|
// Altrimenti viene solo refreshato il RP |
|
$data = unserialize($this->fascicolo["data"]); |
|
$rup_cf = $this->oggetto->getRup()["codice_fiscale"] ?? null; |
|
self::setUserRoleToDB($rup_cf, DelegheRoles::RP, $this->fascicolo["codice"]); |
|
} |
|
} |
|
|
|
/** |
|
* Verifica che la scheda sia nel suo ultimo stadio |
|
* |
|
* @var array $card La scheda da vrificare |
|
* |
|
* @return boolean |
|
*/ |
|
public static function checkCardComplete(Array $card) : bool |
|
{ |
|
return self::hasToBePublished($card) ? $card["stato"] >= self::PUBLISHED_STATO : $card["stato"] >= self::TRASMITTED_STATO; |
|
} |
|
/** |
|
* Verifica che una scheda abbia modifiche |
|
* |
|
* @param array $card |
|
* @return bool|int |
|
*/ |
|
public static function cardHasModifyCard(Array $card) |
|
{ |
|
$modifica = self::fetchSchede( |
|
["*"], |
|
[ |
|
"codice_modifica" => $card["codice"], |
|
], false, false |
|
); |
|
if(empty($modifica)) return false; |
|
return $modifica["codice"]; |
|
} |
|
/** |
|
* Verifica che una scheda abbia rettifiche |
|
* |
|
* @param array $card |
|
* @return bool|int |
|
*/ |
|
public static function cardHasRectifyCard(Array $card) |
|
{ |
|
$rettifica = self::fetchSchede( |
|
["*"], |
|
[ |
|
"codice_rettifica" => $card["codice"], |
|
], false, false |
|
); |
|
if(empty($rettifica)) return false; |
|
return $rettifica["codice"]; |
|
} |
|
|
|
/** |
|
* Verifica che una scheda sia rettificabile |
|
* |
|
* @param array $card Dati della scheda |
|
* @return boolean Se true la scheda è rettificabile |
|
*/ |
|
public static function checkCardRectifiable(Array $card) : bool |
|
{ |
|
if($card["stato"] < self::PUBLISHED_STATO) |
|
return false; |
|
|
|
// Andiamo a cercare eventuali schede di rettifica non pubblicate |
|
$rettifiche = self::fetchSchede( |
|
["*"], |
|
[ |
|
"codice_rettifica" => $card["codice"], |
|
"stato" => ["<", "110"], |
|
] |
|
); |
|
if(!empty($rettifiche)) return false; |
|
return true; |
|
} |
|
/** |
|
* Verifica che una scheda sia modificabile |
|
* |
|
* @param array $card Dati della scheda |
|
* @return boolean Se true la scheda è rettificabile |
|
*/ |
|
public static function checkCardModifiable(Array $card) : bool |
|
{ |
|
if($card["stato"] < 90 || $card["stato"] >= self::PUBLISHED_STATO) |
|
return false; |
|
|
|
if(!empty($card["codice_modifica"])) |
|
return false; |
|
if(!empty($card["codice_rettifica"])) |
|
return false; |
|
|
|
// Rilevazione pubblicazione EU completa |
|
if(!empty($card["pubblicazione"])) { |
|
$pubblicazione = unserialize($card["pubblicazione"]); |
|
if(!empty($pubblicazione) && !empty($pubblicazione->datiPubblicazioneEU) && !empty($pubblicazione->datiPubblicazioneEU->dataPubblicazione)) { |
|
return false; |
|
} |
|
} |
|
// Andiamo a cercare eventuali schede di modifica non pubblicate |
|
$modifiche = self::fetchSchede( |
|
["*"], |
|
[ |
|
"codice_modifica" => $card["codice"], |
|
"stato" => ["<", "110"], |
|
] |
|
); |
|
if(!empty($modifiche)) return false; |
|
return true; |
|
} |
|
/** |
|
* Fetches and provides a list of subordinate cards |
|
* |
|
* @return array |
|
*/ |
|
public function getNextCards(): array |
|
{ |
|
// Otteniamo le schede attuali categorizzate per ID, ci sarà ritornata per ognuna la più aggiornata |
|
$schedeByType = $this->getCardsByType(true, false); |
|
|
|
// Togliamo le pubblicazioni, loro non settano un flusso pare |
|
$schedeByTypeFiltrate = []; |
|
foreach($schedeByType as $id => $schede) { |
|
foreach($schede as $scheda) { |
|
// Evitiamo di far contare rettifiche o modifiche |
|
if(!empty($scheda["codice_modifica"]) || !empty($scheda["codice_rettifica"])) { |
|
continue; |
|
} |
|
// Contano invece come complete schede che sono state modificate o rettificate |
|
if(!(self::checkCardComplete($scheda) || in_array($scheda["stato"], ["-10", "-20"])) ){ |
|
continue; |
|
} |
|
// Se la scheda non definisce un flusso, la ignoriamo |
|
if(empty($scheda["flusso"]) || in_array("*", $scheda["flusso"])) { |
|
continue; |
|
} |
|
$schedeByTypeFiltrate[$id] = $scheda; |
|
break; |
|
} |
|
} |
|
$retval = []; |
|
// Per il momento usiamo il flusso della prima scheda |
|
if(empty($schedeByTypeFiltrate)) { |
|
$flussi = []; |
|
} else { |
|
$flussi = reset($schedeByTypeFiltrate)["flusso"]; |
|
} |
|
// Per l'ultima scheda completata |
|
$ultimaScheda = $this->lastCompletedCardForFlusso(false); |
|
if(empty($ultimaScheda)) { |
|
return []; |
|
} |
|
$retval = []; |
|
foreach ($ultimaScheda["schede_successive"] as $next_id_scheda) { |
|
// Se l'abbiamo già processata continuiamo |
|
if (in_array($next_id_scheda, $retval)) continue; |
|
// Otteniamo i metadata della successiva |
|
$next_metadata = $this->metadataSchede[$next_id_scheda] ?? null; |
|
if (!$next_metadata) continue; |
|
// Verifichiamo che sia compatibile con i flussi attuali ( se ci sono ) |
|
$next_flussi = $next_metadata["flusso"]; |
|
if(!empty($flussi) && !empty($next_flussi) && $next_flussi[0] != ["*"]) { |
|
$compatible = false; |
|
if(in_array("*", $next_flussi)) $compatible = true; |
|
foreach($flussi as $flusso) { |
|
if(in_array("{$flusso}", $next_flussi)) $compatible = true; |
|
if(in_array("!{$flusso}", $next_flussi)) $compatible = false; break; |
|
} |
|
if(!$compatible) continue; |
|
} |
|
|
|
// Aggiungiamo la scheda a quelle trovate |
|
$retval[] = $next_id_scheda; |
|
} |
|
|
|
return $retval; |
|
} |
|
|
|
/** |
|
* Settings default stuff for initialization |
|
* |
|
* @return void |
|
*/ |
|
private function __construct(String $version = self::LATEST_SDK_VERSION) |
|
{ |
|
|
|
self::check_version($version); |
|
|
|
global $pdo, $config; |
|
|
|
if ((DEMO_ENV || DEVELOP_ENV) && !empty($_SESSION["utente"]) && $_SESSION["ente"]->getInfo()["spid"] == "N") { |
|
$_SESSION["utente"]->authenticationLevel = 2; |
|
$_SESSION["utente"]->publicSSO = "SPID"; |
|
} |
|
|
|
|
|
|
|
$this->pdo = $pdo; |
|
|
|
$this->version = $version; |
|
|
|
$this->path = dirname(__FILE__, 2) . "/versions/{$version}"; |
|
|
|
$this->metadataSchede = self::getCardsMetadata(); |
|
|
|
$this->info = get_campi("b_npa"); |
|
$this->info["codice"] = 0; |
|
$this->info["stato"] = 0; |
|
$this->info["codice_gestore"] = $_SESSION["ente"]->codice; |
|
$this->info["codice_ente"] = $_SESSION["utente"]->codice_ente ?? $_SESSION["ente"]->codice; |
|
$this->api_config = require __DIR__ . "/configurazione.php"; |
|
} |
|
|
|
|
|
private static function __processCardDataForRendering($values) { |
|
|
|
if(is_array($values)) { |
|
/*if(array_is_list($values)) { // Conversione da formato ANAC |
|
$newvalues = []; |
|
foreach($values as $value) { |
|
$newvalues[uniqid("rep_")] = $value; |
|
} |
|
$values = $newvalues; |
|
}*/ |
|
// Conversione da vecchio formato |
|
$keys = array_keys($values); |
|
if (isset($keys[0]) && strpos($keys[0], "rep_") !== false) { |
|
$values = array_values($values); |
|
} |
|
foreach($values as $index => $value) { |
|
if(in_array($index, ["espd", "eform"], true)) { |
|
|
|
$value = "TO-BE-FILLED"; |
|
} |
|
$values[$index] = self::__processCardDataForRendering($value); |
|
} |
|
|
|
} elseif(is_bool($values)) { |
|
return $values ? "true" : "false"; |
|
} elseif(is_numeric($values)) { |
|
return "{$values}"; |
|
} elseif(is_string($values)) { |
|
$datetime = DateTime::createFromFormat(DateTime::ATOM, $values); |
|
if (!is_bool($datetime)) { |
|
return $datetime->format("d/m/Y H:i"); |
|
} |
|
} |
|
return $values; |
|
} |
|
/** |
|
* Ottiene i dati della scheda per il rendering. |
|
* Questo metodo è compatibile con il formato interno del json |
|
* e anche con quello usato da ANAC. Utile nei casi in cui la scheda venga |
|
* importata |
|
* |
|
* @return array |
|
*/ |
|
public function getCardData(): Array |
|
{ |
|
if(!empty($this->card_data)) |
|
return $this->card_data; |
|
if(empty($this->scheda["dati"]) || !valid_json($this->scheda["dati"], $data, TRUE)) { |
|
return []; |
|
} |
|
|
|
$this->card_data = self::__processCardDataForRendering($data); |
|
return $this->card_data; |
|
} |
|
/** |
|
* Organize repeteble group from dictionary to array |
|
* |
|
* @param array $data |
|
* @return void |
|
*/ |
|
public function organizeRepeteableGroups(array &$data, ?array $types = [], bool $root = true): void |
|
{ |
|
|
|
date_default_timezone_set('Europe/Rome'); |
|
|
|
$index = ""; |
|
if (empty($types)) { |
|
|
|
$types = $this->getFormInputsTypes($this->form["content"]); |
|
} |
|
|
|
|
|
foreach ($data as $key => &$content) { |
|
|
|
if (is_array($content)) { |
|
|
|
$content = array_filter($content, function ($value) { |
|
|
|
if (is_array($value) && count($value) == 0) { |
|
return false; |
|
} |
|
return ($value !== NULL && $value !== FALSE && $value !== ''); |
|
}); |
|
|
|
if (!array_is_list($content) && count($content) == 1 && isset($content["idTipologica"])) { |
|
|
|
$content = []; |
|
} else { |
|
|
|
$keys = array_keys($content); |
|
if (isset($keys[0]) && strpos($keys[0], "rep_") !== false) { |
|
|
|
$content = array_values($content); |
|
} |
|
} |
|
|
|
$info = $types; |
|
if (!is_numeric($key) && !empty($types[$key])) { |
|
$info = $types[$key]; |
|
} |
|
$this->organizeRepeteableGroups($content, $info, false); |
|
$content = array_filter($content, function (&$value, $key) use($info) { |
|
$required = $info[$key]["_required"] ?? false; |
|
if($required) { |
|
// Workaround per assicurarci che un oggetto vuoto sia effettivamente un oggetto |
|
if(empty($value)) { |
|
$dataType = $info[$key]["_dataType"] ?? null; |
|
$repeatable = $info[$key]["_repeatable"] ?? false; |
|
$contentType = $info[$key]["contentType"] ?? null; |
|
|
|
if($this->version === "v1.0") { |
|
if ($dataType === "object" && !$repeatable) |
|
$value["forceJSONObject"] = true; |
|
} |
|
else { |
|
if (($contentType === "group" || $dataType === "object") && !$repeatable) |
|
$value["forceJSONObject"] = true; |
|
} |
|
// Probabilmente questo funzionerebbe anche, ma non ci giurerei |
|
// $value = new stdClass(); |
|
} |
|
return true; |
|
} |
|
//if($key === "codiceAusa") return TRUE; |
|
if (is_array($value) && count($value) == 0) { |
|
return false; |
|
} |
|
return ($value !== NULL || $value !== ''); |
|
}, ARRAY_FILTER_USE_BOTH); |
|
} else { |
|
if (!empty($types[$key]["type"])) { |
|
switch ($types[$key]["type"]) { |
|
case 'cpv': |
|
$content = str_pad($content, 8, "0", STR_PAD_RIGHT); |
|
break; |
|
case 'codice_istat': |
|
$option = $this->pdo->go("SELECT codice_istat FROM b_istat_anac WHERE codice_istat = :codice_istat", [":codice_istat" => $content])->fetch(PDO::FETCH_ASSOC); |
|
if($option) { |
|
if(self::LATEST_SDK_VERSION === "v1.0") |
|
$content = ltrim($option["codice_istat"], "0"); |
|
else |
|
$content = $option["codice_istat"]; |
|
} |
|
break; |
|
|
|
} |
|
} |
|
if (!empty($types[$key]["format"])) { |
|
|
|
switch ($types[$key]["format"]) { |
|
|
|
case 'double': |
|
$content = filter_var($content, FILTER_VALIDATE_FLOAT); |
|
break; |
|
|
|
case 'datetime': |
|
case 'date-time': |
|
$datetime = DateTime::createFromFormat("Y-m-d H:i", datetime2mysql($content)); |
|
if (!is_bool($datetime)) { |
|
$content = $datetime->format(DateTime::ATOM); |
|
} |
|
break; |
|
|
|
case 'integer': |
|
case 'number': |
|
$content = filter_var($content, FILTER_VALIDATE_INT); |
|
break; |
|
|
|
case 'bool': |
|
$content = filter_var($content, FILTER_VALIDATE_BOOLEAN); |
|
break; |
|
|
|
default: |
|
break; |
|
} |
|
} |
|
} |
|
} |
|
if($root) { |
|
if(($this->scheda["id_scheda"] ?? null) === "P7_3") { |
|
if(empty($data["anacForm"]["lotti"])) { |
|
$data["anacForm"]["lotti"] = []; |
|
} |
|
} |
|
} |
|
} |
|
|
|
|
|
/** |
|
* Get the card uuid |
|
* |
|
* @return string |
|
*/ |
|
private function getUUID(String $type = "appalto"): String |
|
{ |
|
|
|
if ($type == "appalto" && !empty($this->fascicolo["id_appalto"])) { |
|
|
|
return $this->fascicolo["id_appalto"]; |
|
} |
|
|
|
if ($type == "pianificazione" && !empty($this->fascicolo["id_pianificazione"])) { |
|
|
|
return $this->fascicolo["id_pianificazione"]; |
|
} |
|
|
|
if ($type == "scheda" && !empty($this->scheda["uuid"])) { |
|
|
|
return $this->scheda["uuid"]; |
|
} |
|
|
|
return "00000000-0000-0000-0000-000000000000"; |
|
} |
|
|
|
/** |
|
* Verifica che una scheda possa essere risincronizzata con ANAC |
|
* |
|
* @return boolean |
|
*/ |
|
public function canSyncScheda() : bool { |
|
if($this->scheda["stato"] < 20) |
|
return false; |
|
$dataFascicolo = unserialize($this->fascicolo["data"]); |
|
if(!($dataFascicolo["abilita_resync"] ?? false)) { |
|
return false; |
|
} |
|
return true; |
|
} |
|
|
|
|
|
/** |
|
* Verifica che una scheda sia modificabile |
|
* |
|
* @return boolean Se true la scheda è modificabile |
|
*/ |
|
public function isCardModifiable() : bool { |
|
$metadata = $this->metadataSchede[$this->scheda["id_scheda"]]; |
|
$scheda = array_merge($metadata, $this->scheda); |
|
$dataFascicolo = unserialize($this->fascicolo["data"]); |
|
if(!($dataFascicolo["abilita_modifica_appalto"] ?? false)) { |
|
// Workaround per disattivare modifiche su schede d'appalto (provvisorio) |
|
if($scheda["lotto"] === false && $scheda["categoria"] != "pianificazione") { |
|
return false; |
|
} |
|
} |
|
return self::checkCardModifiable($scheda); |
|
} |
|
/** |
|
* Verifica che una scheda sia rettificabile |
|
* |
|
* @return boolean Se true la scheda è rettificabile |
|
*/ |
|
public function isCardRectifiable() : bool { |
|
$metadata = $this->metadataSchede[$this->scheda["id_scheda"]]; |
|
$scheda = array_merge($metadata, $this->scheda); |
|
return self::checkCardRectifiable($scheda); |
|
|
|
} |
|
|
|
/** |
|
* Determina se la scheda i nquestione ha bisogno di essere pubblicata |
|
* |
|
* @param array $scheda |
|
* @return boolean |
|
*/ |
|
public static function hasToBePublished(array $scheda) : bool { |
|
// Retrocompatibilità |
|
if(($scheda["id_scheda"] ?? "") == "AD3" && ($scheda["versione_scheda"] ?? self::LATEST_SDK_VERSION) == "v1.0") { |
|
return false; |
|
} |
|
$metadata = self::getCardsMetadata()[$scheda["id_scheda"]]; |
|
return $metadata["pubblicazione"]; |
|
} |
|
/** |
|
* Determina che una scheda sia cancellabile |
|
* |
|
* @param string|null $text Se passato viene popolato con il testo giusto da mostrare all'utente |
|
* @param boolean|null $server_side Se passato viene popolato con una booleana che determina che la richiesta sia server_side o meno |
|
* @return boolean |
|
*/ |
|
public function isCardDeletable(?string &$text = null, ?bool &$server_side = null) : bool { |
|
|
|
$server_side = true; |
|
// temporaneamente non consentiamo di cancellare rettifiche |
|
if(!empty($card["codice_rettifica"])) { |
|
return false; |
|
} |
|
// Non trasmessa |
|
if(empty($this->scheda["uuid"]) && $this->scheda["stato"] < 40) { |
|
$server_side = false; |
|
$text = "Elimina"; |
|
return true; |
|
} |
|
|
|
// Trasmessa ma non confermata |
|
if($this->scheda["stato"] < 40) { |
|
$text = "Elimina scheda trasmessa"; |
|
return true; |
|
} |
|
// In attesa di conferma |
|
if($this->scheda["stato"] == 40) { |
|
$text = "Elimina scheda in conferma"; |
|
return true; |
|
} |
|
// In attesa di pubblicazione |
|
if($this->scheda["stato"] >= 90 && $this->scheda["stato"] < 110) { |
|
$text = "Richiedi cancellazione della pubblicazione"; |
|
return true; |
|
} |
|
|
|
|
|
|
|
return false; |
|
} |
|
|
|
public function createDossierRevision() : bool { |
|
if(empty($this->fascicolo)) |
|
throw new Exception("Impossibile effettuare l'operazione senza un fascicolo"); |
|
// Crea una copia del dossier attuale |
|
$salva = new salva(); |
|
$new_fascicolo = $this->fascicolo; |
|
unset($new_fascicolo["id_pianificazione"]); |
|
unset($new_fascicolo["id_appalto"]); |
|
if(empty($new_fascicolo["revisione_fascicolo"])) |
|
$new_fascicolo["revisione_fascicolo"] = 1; |
|
$new_fascicolo["revisione_fascicolo"] += 1; |
|
$new_fascicolo["versione_interna"] = self::LATEST_INTERNAL_VERSION; |
|
$salva->nome_tabella = "b_npa"; |
|
$salva->oggetto = $new_fascicolo; |
|
$salva->operazione = "INSERT"; |
|
|
|
$vecchio_codice = $this->fascicolo["codice"]; |
|
$nuovo_codice = $salva->save(); |
|
if($nuovo_codice) { |
|
// Evitiamo di associare quello attuale |
|
$this->fascicolo["id_riferimento"] *= -1; |
|
$deleteOld = new salva(); |
|
$deleteOld->nome_tabella = "b_npa"; |
|
$deleteOld->oggetto = $this->fascicolo; |
|
$deleteOld->operazione = "UPDATE"; |
|
if(!$deleteOld->save()) |
|
return false; |
|
|
|
// Resettiamo i notice_id della GUUE |
|
|
|
$this->pdo->go(<<<SQL |
|
UPDATE b_guue SET notice_id = NULL, stato ='BOZZA' WHERE codice IN |
|
( |
|
select |
|
`codice_scheda_guue` |
|
from b_npa_schede where codice_npa = :vecchio_codice AND (codice_lotto = 0 OR codice_lotto IS NULL OR codice_lotto = '') AND soft_delete != 'S' AND codice_scheda_guue > 0 |
|
) |
|
SQL,[ |
|
":vecchio_codice" => $vecchio_codice, |
|
//":guue_sent_notice_id" => uuidgen_guue(), |
|
]); |
|
|
|
// Riportiamo le schede generali, anche se resettate a bozza |
|
$this->pdo->go(<<<SQL |
|
insert into b_npa_schede ( |
|
`codice_npa`, |
|
`type`, |
|
`id_scheda`, |
|
`dati`, |
|
`utente_modifica`, |
|
`codice_lotto`, |
|
`versione_scheda`, |
|
`codice_scheda_guue`, |
|
`timestamp_creazione` |
|
) |
|
select |
|
:nuovo_codice, |
|
`type`, |
|
`id_scheda`, |
|
`dati`, |
|
`utente_modifica`, |
|
`codice_lotto`, |
|
:versione_scheda, |
|
`codice_scheda_guue`, |
|
NOW() |
|
from b_npa_schede where codice_npa = :vecchio_codice and (codice_lotto = 0 OR codice_lotto IS NULL) and soft_delete != 'S'; |
|
SQL, [ |
|
":vecchio_codice" => $vecchio_codice, |
|
":nuovo_codice" => $nuovo_codice, |
|
":versione_scheda" => self::LATEST_SDK_VERSION, |
|
//":guue_sent_notice_id" => uuidgen_guue(), |
|
]); |
|
|
|
$this->fascicolo = $new_fascicolo; |
|
|
|
// Rimuovo eventuali riferimenti ai fascicoli degli OE |
|
$this->pdo->go("UPDATE b_fvoe SET codice_operatore = -ABS(codice_operatore), stato = -1, uuid_partecipante = NULL WHERE codice_npa = :codice_npa", [":codice_npa" => $vecchio_codice]); |
|
|
|
return true; |
|
} |
|
|
|
return false; |
|
|
|
} |
|
|
|
public function fetchCompatibleGUUECards(bool $multiple=true, bool $draft = false) : Array { |
|
|
|
$filters = [ |
|
"codice_elemento" => $this->fascicolo["id_riferimento"], |
|
"modulo" => $this->fascicolo["modulo_riferimento"], |
|
"stato" => $draft ? ["IN", ["BOZZA", "DA TRASMETTERE"]] : ["IN", ["TRASMESSO", "PUBBLICATO"]], |
|
"form" => ["IN", $this->metadataSchede[$this->scheda["id_scheda"]]["guue"]], |
|
]; |
|
|
|
// Verifichiamo se c'è un lotto collegato nelle multilotto |
|
if(count($this->getLots()) > 1) { |
|
$codice_lotto_npa = $this->scheda["codice_lotto"]; |
|
if(!empty($codice_lotto_npa)) { |
|
$lot = $this->getLots($codice_lotto_npa) ?? null; |
|
|
|
if(!empty($lot)) { |
|
$lot = reset($lot); |
|
$filters["lotto_riferimento"] = ["OR", [$lot["codice"], null, 0]]; |
|
} |
|
} |
|
} |
|
|
|
$allSchede = eFormsManager::fetchSchede( |
|
["codice", "form"], |
|
$filters, |
|
true |
|
); |
|
|
|
|
|
$codice = $this->scheda["codice"]; |
|
$schede = array_filter($allSchede, function($scheda) use($codice) { |
|
// Check che la scheda non sia già usata |
|
$assignedANAC = NuovaPiattaformaAppalti::fetchSchede( |
|
[ |
|
"s.codice", |
|
"id_scheda" |
|
], |
|
[ |
|
"s.codice" => ["!=", $codice], |
|
"codice_scheda_guue" => $scheda["codice"], |
|
"id_riferimento" => $this->fascicolo["id_riferimento"], |
|
"modulo_riferimento" => $this->fascicolo["modulo_riferimento"] |
|
], |
|
true, |
|
false |
|
); |
|
return empty($assignedANAC); |
|
}); |
|
|
|
if($multiple) return $schede; |
|
|
|
return empty($schede) ? [] : reset($schede); |
|
} |
|
|
|
// Override utente per Cron |
|
private $__cronOverrideRole = null; |
|
private $__cronOverrideCF = null; |
|
public function setCronOverrideUser(string $role, string $cf) { |
|
$this->__cronOverrideRole = $role; |
|
$this->__cronOverrideCF = $cf; |
|
} |
|
|
|
private function publishGUUEOnSuccessfulPublication() { |
|
if(isset($this->scheda["codice_scheda_guue"])) { |
|
$codice_guue = $this->scheda["codice_scheda_guue"]; |
|
} else { |
|
$this->checkIfCardNeedsXMLDocument(); |
|
if ($this->check["eform"]["required"]) { |
|
$codice_guue = $this->check["eform"]["codice"] ?? 0; |
|
} |
|
} |
|
|
|
if(!empty($codice_guue)) { |
|
$this->pdo->go("UPDATE b_guue SET stato = :stato WHERE codice = :codice", [":stato" => "PUBBLICATO", ":codice" => $codice_guue]); |
|
} |
|
} |
|
public function resetGUUEonSoftDelete() { |
|
if(isset($this->scheda["codice_scheda_guue"])) { |
|
$codice_guue = $this->scheda["codice_scheda_guue"]; |
|
} else { |
|
$this->checkIfCardNeedsXMLDocument(); |
|
if ($this->check["eform"]["required"]) { |
|
$codice_guue = $this->check["eform"]["codice"] ?? 0; |
|
} |
|
} |
|
|
|
if(!empty($codice_guue)) { |
|
$this->pdo->go("UPDATE b_guue SET stato = :stato, notice_id = NULL WHERE codice = :codice", [":stato" => "BOZZA", ":codice" => $codice_guue]); |
|
} |
|
} |
|
|
|
|
|
private $__givenInfo = []; |
|
/** |
|
* Ottiene la descrizione dei metadati di una scheda. Usato per GPT |
|
* |
|
* @param string $id_scheda |
|
* @return string |
|
*/ |
|
public function getInfoScheda(string $id_scheda) : string { |
|
if(!empty($this->__givenInfo[$id_scheda])) return ""; |
|
$this->__givenInfo[$id_scheda] = True; |
|
$metadata = $this->metadataSchede[$id_scheda] ?? null; |
|
unset($metadata["order"]); |
|
unset($metadata["multipla"]); |
|
unset($metadata["settori"]); |
|
unset($metadata["obbligatoria"]); |
|
$retval = ""; |
|
|
|
foreach ($metadata as $id => $val) { |
|
if(is_array($val)) { |
|
$val = implode(",", $val); |
|
$retval .= "$id => $val, "; |
|
} else if(is_bool($val)) { |
|
if($val) $retval .= "$id => sì, "; |
|
} elseif(is_string($val)) { |
|
$retval .= "$id => \"{$val}\", "; |
|
} else { |
|
$retval .= "$id => $val, "; |
|
} |
|
} |
|
|
|
return "\n- Info sulla scheda {$id_scheda}: $retval" . PHP_EOL; |
|
} |
|
/** |
|
* Verifica che l'utente corrente possa accedere |
|
* alla presente istanza NPA. |
|
* |
|
* @return bool |
|
*/ |
|
public function verifyUtente() |
|
{ |
|
if(empty($_SESSION["utente"])) return false; |
|
if(empty($this->fascicolo)) return false; |
|
|
|
|
|
if(!$_SESSION["utente"]->permission("npa")) { |
|
return false; |
|
} |
|
|
|
if (!empty($this->fascicolo["modulo_riferimento"]) && !empty($this->fascicolo["id_riferimento"])) { |
|
if(!$_SESSION["utente"]->permission($this->fascicolo["modulo_riferimento"], $this->fascicolo["id_riferimento"])) { |
|
return false; |
|
} |
|
} |
|
return true; |
|
} |
|
|
|
/** |
|
* Ritorna l'idAvviso dinamicamente |
|
* |
|
* @return string|null |
|
*/ |
|
public function getANACNoticeID() : ?string { |
|
if(empty($this->scheda["info"])) return NULL; |
|
$info = unserialize($this->scheda["info"]); |
|
return empty($info->idNuovoAvviso) ? $info->idAvviso : $info->idNuovoAvviso; |
|
} |
|
/** |
|
* Crea una revisione (modifica/rettifica) di una scheda |
|
* |
|
* @param boolean $conserva_dati |
|
* @param string $tipo |
|
* @param string|null $message Viene popolato in caso di errore con il messaggio d'errore |
|
* @param array|null $dati Viene popolato in caso di successo con la query per arrivare alla scheda |
|
* @return bool |
|
*/ |
|
public function createRevision(bool $conserva_dati, string $tipo, ?string &$message=null, ?array &$dati = null) : bool{ |
|
$message = "Operazione non valida"; |
|
if(empty($this->scheda)) return false; |
|
if(!in_array($tipo, ["modifica", "rettifica"])) return false; |
|
if($tipo === "modifica") { |
|
if(!$this->isCardModifiable()) { |
|
$message = "Questa scheda non può essere modificata."; |
|
return false; |
|
} |
|
$tipo_codice = "codice_modifica"; |
|
|
|
} else { |
|
if(!$this->isCardRectifiable()) { |
|
$message = "Questa scheda non può essere rettificata."; |
|
return false; |
|
} |
|
$tipo_codice = "codice_rettifica"; |
|
} |
|
|
|
$nuovo_codice_guue = null; |
|
if(!empty($this->scheda["codice_scheda_guue"])) { |
|
$nuovo_codice_guue = eFormsManager::duplicaScheda($this->scheda["codice_scheda_guue"], true, $conserva_dati); |
|
} |
|
$salva = new salva(); |
|
$salva->nome_tabella = "b_npa_schede"; |
|
$salva->oggetto = $this->scheda; |
|
unset($salva->oggetto["codice"]); |
|
$salva->operazione = "INSERT"; |
|
unset($salva->oggetto["dati"]); |
|
unset($salva->oggetto["codice_rettifica"]); |
|
unset($salva->oggetto["codice_modifica"]); |
|
if(isset($_SESSION["utente"])) { |
|
$salva->oggetto["utente_modifica"] = $_SESSION["utente"]->codice; |
|
} |
|
$salva->ignore = ["timestamp_trasmissione", "timestamp_modifica", "uuid", "stato", "info", "pubblicazione"]; |
|
$salva->oggetto[$tipo_codice] = $this->scheda["codice"]; |
|
$salva->oggetto["timestamp_creazione"] = date("d/m/Y H:i:s"); |
|
|
|
if($nuovo_codice_guue !== null) { |
|
$salva->oggetto["codice_scheda_guue"] = $nuovo_codice_guue; |
|
} else { |
|
$salva->ignore[] = "codice_scheda_guue"; |
|
} |
|
|
|
if($conserva_dati) { |
|
$salva->oggetto["dati"] = $this->scheda["dati"]; |
|
} else { |
|
if(valid_json($this->scheda["dati"], $dati, true) && isset($dati["anacForm"]["stazioniAppaltanti"])) { |
|
$salva->oggetto["dati"] = json_encode([ |
|
"anacForm" => [ |
|
"stazioniAppaltanti" => $dati["anacForm"]["stazioniAppaltanti"] |
|
] |
|
]); |
|
} |
|
} |
|
$codice = $salva->save(); |
|
if(!empty($codice)) { |
|
$dati = [ |
|
"codice" => $codice, |
|
"scheda" => $salva->oggetto["id_scheda"], |
|
"id_riferimento" => $this->fascicolo["id_riferimento"], |
|
"modulo_riferimento" => $this->fascicolo["modulo_riferimento"], |
|
]; |
|
return true; |
|
} |
|
$message = "Impossibile creare la {$tipo}"; |
|
return false; |
|
} |
|
|
|
/** |
|
* Verifica che la scheda attuale rispetti ancora i requisiti di soglia dell'oggetto |
|
* |
|
* @return bool |
|
*/ |
|
public function checkSoglia() { |
|
if(empty($this->oggetto) || !method_exists($this->oggetto, "getSogliaGara")) return true; |
|
$sogliaGara = $this->oggetto->getSogliaGara(); |
|
$metadata = $this->metadataSchede[$this->scheda["id_scheda"]]; |
|
if($sogliaGara == "soprasoglia" && !$metadata["soprasoglia"]) return false; |
|
return true; |
|
} |
|
|
|
/** |
|
* Verifica che non ci siano rettifiche o modifiche in corso di compilazione nel fascicolo |
|
* |
|
* @param array|null $filters Se indicato, indica filtri extra per cercare |
|
* @param array $additionalCards Se specificato, indica le schede extra che vanno trattate come rettifiche |
|
* @return boolean |
|
*/ |
|
public function hasPendingRettificheOrModifiche(?array $filters = null, array $additionalCards = []) { |
|
return self::hasPendingRettificheOrModificheStatic($this->fascicolo["codice"], $filters, $additionalCards ); |
|
} |
|
|
|
/** |
|
* Verifica che non ci siano rettifiche o modifiche in corso di compilazione nel fascicolo |
|
* |
|
* @param int $codice_npa |
|
* @param array|null $filters Se indicato, indica filtri extra per cercare |
|
* @param array $additionalCards Se specificato, indica le schede extra che vanno trattate come rettifiche |
|
* @return boolean |
|
*/ |
|
public static function hasPendingRettificheOrModificheStatic(int $codice_npa, ?array $filters = null, array $additionalCards = []) { |
|
|
|
$filters = empty($filters) ? [] : $filters; |
|
$filters["stato"] = ["<", 20]; // Solo pending |
|
$filters["codice_npa"] = $codice_npa; // Solo di questo fascicolo |
|
$orFilters = [ |
|
"codice_rettifica" => [">", 0], |
|
"codice_modifica" => [">", 0], |
|
]; |
|
if(!empty($additionalCards)) { |
|
$orFilters["id_scheda"] = ["IN", $additionalCards]; |
|
} |
|
$filters[uniqid("or_")] = $orFilters; |
|
$rettifiche = self::fetchSchede( |
|
["id_scheda"], |
|
$filters |
|
); |
|
return !empty($rettifiche); |
|
} |
|
/** |
|
* Verifica tra le schede trasmesse se sono in uso partecipanti o aggiudicatari |
|
* |
|
* @param boolean $aggiudicatari Se true ricerca schede con aggiudicatari, altrimenti schede con partecipanti |
|
* @param boolean $unlockOnRettifica Se true e c'è una rettifica in corso non vengono bloccati i partecipanti |
|
* @return boolean |
|
*/ |
|
public function hasLockOnPartecipanti(bool $aggiudicatari = false, bool $unlockOnRettifica = true, ?int $codice_lotto_npa = null) { |
|
return self::hasLockOnPartecipantiStatic($this->fascicolo["codice"], $aggiudicatari, $unlockOnRettifica, $codice_lotto_npa); |
|
} |
|
|
|
|
|
/** |
|
* Ottiene l'ultima versione di un dato all'interno delle schede inviate |
|
* @param string $dotPath |
|
* @param bool $onlyCurrentLot |
|
* @return array|null |
|
*/ |
|
public function getAnacLastSent(string $dotPath, bool $onlyCurrentLot = true) : ?array { |
|
$completedCards = $this->getLastCompletedCards($onlyCurrentLot); |
|
foreach($completedCards as $scheda) { |
|
$dati = json_decode($scheda["dati"], true); |
|
$match = DotPath::get($dati, $dotPath, null); |
|
if($match !== null) { return $match; } |
|
} |
|
return null; |
|
} |
|
|
|
/** |
|
* Verifica tra le schede trasmesse se sono in uso partecipanti o aggiudicatari |
|
* |
|
* @param int $codice_npa Codice fascicolo NPA di riferimento |
|
* @param boolean $aggiudicatari Se true ricerca schede con aggiudicatari, altrimenti schede con partecipanti |
|
* @param boolean $unlockOnRettifica Se true e c'è una rettifica in corso non vengono bloccati i partecipanti |
|
* @return boolean |
|
*/ |
|
public static function hasLockOnPartecipantiStatic(int $codice_npa, bool $aggiudicatari = false, bool $unlockOnRettifica = true, $codice_lotto_npa = null) { |
|
$filterSchede = [ |
|
"stato" => [">=", 20], |
|
"codice_npa" => $codice_npa, |
|
]; |
|
if(! empty($codice_lotto_npa)) { $filters["codice_lotto"] = ["OR", [$codice_lotto_npa, null, '', 0]]; } |
|
$schedeTrasmesse = self::fetchSchede(["id_scheda"], $filterSchede); |
|
if(empty($schedeTrasmesse)) { return false; } |
|
// Ci servono solo gli idScheda |
|
$schedeTrasmesse = array_unique(array_column($schedeTrasmesse, "id_scheda", null)); |
|
$metadataSchede = self::getCardsMetadata(); |
|
foreach($schedeTrasmesse as $scheda) { |
|
if($scheda === "S1") { continue; } // Le S1 vivono nel loro mondo, isolate da tutti, beate loro. |
|
$metadata = $metadataSchede[$scheda]; |
|
$shouldLock = $metadata["aggiudicatari"]; |
|
if(!$shouldLock && !$aggiudicatari) { |
|
$shouldLock = $metadata["partecipanti"]; |
|
} |
|
if($shouldLock) { |
|
// Verifichiamo che non sia in corso una rettifica, in quel caso possiamo sbloccare |
|
if($unlockOnRettifica) { |
|
$hasPendingRettificheOrModifiche = self::hasPendingRettificheOrModificheStatic($codice_npa, ["id_scheda" => $scheda]); |
|
if($hasPendingRettificheOrModifiche) return false; |
|
} |
|
return true; |
|
} |
|
} |
|
return false; |
|
} |
|
|
|
|
|
private $formRequirements = []; |
|
|
|
/** |
|
* Verifica che la scheda corrente richieda partecipanti o aggiudicatari e setta continue su false se non dovessero essere presenti |
|
* |
|
* @return array |
|
*/ |
|
public function verifyFormRequirements() : array{ |
|
|
|
if(!empty($this->formRequirements)) return $this->formRequirements; |
|
|
|
$suggestions = $this->getSuggestions(); |
|
$idScheda = $this->scheda["id_scheda"]; |
|
$metadata = $this->metadataSchede[$idScheda]; |
|
$lottoCorrente = $this->scheda["codice_lotto"]; |
|
|
|
|
|
|
|
$retval = [ |
|
"continue" => true, |
|
"aggiudicazioni" => $metadata["aggiudicatari"], |
|
"partecipanti" => $metadata["partecipanti"] |
|
]; |
|
$toCheck = []; |
|
if($idScheda === "S4") { |
|
$ultimiAggiudicatari = $this->getAnacLastSent("anacForm.aggiudicazioni", false); |
|
if(!empty($ultimiAggiudicatari)) { |
|
foreach($ultimiAggiudicatari as $aggiudicazione) { |
|
$lotIdentifier = "LOT-" . str_pad($this->lot["numero"], 4, "0", STR_PAD_LEFT); |
|
if(!isset($aggiudicazione["lotIdentifier"]) || $aggiudicazione["lotIdentifier"] === $lotIdentifier) { |
|
$ultimiAggiudicatariInviati = array_unique(recursive_array_key_search_all("idPartecipante", $aggiudicazione)); |
|
} |
|
} |
|
|
|
} |
|
$aggiudicatariCorrenti = array_unique(array_column($this->getPartecipanti(true, false), "uuid")); |
|
|
|
|
|
$retval["S4"] = true; |
|
$retval["continue"] = !empty(arrayDiff($aggiudicatariCorrenti, $ultimiAggiudicatariInviati)); |
|
|
|
} elseif($metadata["aggiudicatari"]) { |
|
if($lottoCorrente == 0) { |
|
|
|
foreach($this->getLots() as $lot) { |
|
$this->setLotto($lot["codice_lotto_npa"]); |
|
$agg = $this->getPartecipanti(true); |
|
if(empty($agg)) { |
|
$retval["non_aggiudicati"][] = $lot; |
|
} |
|
$this->unsetLotto(); |
|
} |
|
$retval["continue"] = count($this->getLots()) > count($retval["non_aggiudicati"] ?? []); |
|
} else { |
|
$agg = $this->getPartecipanti(true); |
|
|
|
if(empty($agg)) { |
|
// Le soprasoglia possono partire senza aggiudicazioni |
|
if($metadata['soprasoglia'] ?? false) { |
|
if(!in_array($this->lot['stato'], [99, 95])) { |
|
$retval["non_aggiudicati"][] = $this->lot; |
|
} |
|
// Evitiamo che l'utente possa compilare manualmente questa sezione |
|
$this->locked_sections[] = "anacForm.aggiudicazioni.*.offertePresentate.*."; |
|
$this->locked_repetitions["offertePresentate"] = true; |
|
|
|
} else { |
|
$retval["continue"] = false; |
|
} |
|
} |
|
} |
|
} elseif($metadata["partecipanti"]) { |
|
$toCheck = DotPath::get($suggestions, "anacForm.elencoSoggetti", []); |
|
} |
|
foreach($toCheck as $lotto) { |
|
if(empty($lotto["partecipanti"])) { |
|
$retval["continue"] = false; |
|
break; |
|
} |
|
} |
|
$this->formRequirements = $retval; |
|
return $retval; |
|
} |
|
}
|
|
|