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

1071 righe
38 KiB

<?php
/**
* Enum che definisce i tipi di suggerimento
*/
class NPASuggestionType {
/**
* Il suggeerimento viene applicato e l'utente non può modificarlo
*/
const FORCED = "FORCE";
/**
* Il suggerimento viene applicato se il campo è vuoto e l'utente può modificarlo
*/
const AUTOFILL = "AUTO";
/**
* Il suggerimento viene suggerito ma è a scelta dell'utente applicarlo
*/
const OPTIONAL = "OPT";
}
trait NPASuggestionsTrait
{
/**
* La cache dei suggerimenti già processati
*
* @var array
*/
public $suggestionsCache = [];
/**
* I suggerimenti non processati così come ottenuti dalle integrazioni
*
* @var array
*/
public $integration = [];
/**
* Traccia interna dei lotti per la consistenza delle ripetizioni
*
* @var array
*/
private $indiciLotto = [];
/**
* Traccia interna dei partecipanti per la consistenza delle ripetizioni
*
* @var array
*/
private $indiciPartecipanti = [];
/**
* Traccia interna degli aggiudicatari per la consistenza delle ripetizioni
*
* @var array
*/
private $indiciAggiudicatari = [];
/**
* Return a cleaned versione of the suggestion to be compliant with the JSON
*
* @param String $string The raw suggestion to be cleaned
* @return String
*/
public static function cleanSuggestion(String $string): String
{
return strip_tags(str_replace('"', '\"', $string));
}
/**
* Ottiene la modalità con cui suggerire l'URL dell'appalto impostato nei settings di piattaforma (Forzato o Modificabile)
*
* @return String
*/
public static function urlSuggetionMode() : String {
$option = settingsManager::getValue("consentiModificaURLANAC");
return ($option == "S") ? NPASuggestionType::AUTOFILL : NPASuggestionType::FORCED;
}
/**
* Ottiene i suggerimenti relativi alla procedura in corso
*
* @return array
*/
public function getSuggestions(): array
{
//if (empty($this->scheda["codice"])) return [];
if (!empty($this->suggestionsCache)) {
return $this->suggestionsCache;
}
$integrationPath = NuovaPiattaformaAppalti::INTEGRATION_PATHS[$this->fascicolo["modulo_riferimento"]];
if (empty($this->fascicolo["id_riferimento"])) {
return [];
}
global $root;
$this->info["modulo"] = $this->fascicolo["modulo_riferimento"];
$this->info["codice_elemento"] = $this->fascicolo["id_riferimento"];
$integration = [];
if (!file_exists("{$root}/backend/{$integrationPath}")) {
return [];
}
$callIntegration = function ($path) use (&$integration, $root) {
$contents = require "{$root}/backend/{$path}";
if (is_array($contents)) {
$integration = array_merge($integration, $contents);
}
};
$callIntegration($integrationPath);
$this->integration = $integration;
$stazioneAppaltante = $this->integration["STAZIONEAPPALTANTE"] ?? [];
$this->suggestionsCache = [];
#region Suggerimenti Generali
$this->addSuggestion(
"appalto.linkDocumenti",
$this->integration["PUBLIC_URL"] ?? null,
self::urlSuggetionMode()
);
$_oggetto = isset($this->integration["GENERAL"]["oggetto"]) ? self::cleanSuggestion($this->integration["GENERAL"]["oggetto"], "appalto.datiBase.oggetto") : "";
if (!empty($_oggetto)) {
$this->addSuggestion("appalto.datiBase.oggetto", $_oggetto);
}
if ($this->fascicolo["modulo_riferimento"] == "gare") {
$this->addSuggestionsGara();
}
// Prezzo di riferimento
$prezzoRiferimento = $this->integration["GENERAL"]["prezzoRiferimento"] ?? null;
if ($prezzoRiferimento !== null) {
$this->addSuggestion("appalto.datiBase.importo", currency_format($prezzoRiferimento));
}
// Codice Appalto e stazione appaltante principale
if (!empty($stazioneAppaltante)) {
$letter = "P";
if (DEVELOP_ENV) {
global $config;
if (isset($config["npaLetter"])) {
$letter = $config["npaLetter"];
}
}
// Generiamo il codice appalto
$versione_interna = $this->fascicolo["versione_interna"] ?? 0;
if($versione_interna >= 4) {
$codiceAppalto = $this->integration["GENERAL"]["codiceAppalto"];
if (!empty($this->fascicolo["revisione_fascicolo"])) {
$codiceAppalto .= "_v{$this->fascicolo['revisione_fascicolo']}";
}
} else {
$codiceAppalto = strtoupper("{$letter}{$_SESSION["ente"]->codice}-E{$stazioneAppaltante["codice"]}-{$this->integration["MODULO"]}-ID-{$this->oggetto->info["id"]}");
if (!empty($this->fascicolo["revisione_fascicolo"])) {
$codiceAppalto .= "_rev{$this->fascicolo['revisione_fascicolo']}";
}
}
// Se siamo una modifica od una rettifica, allora prendiamo il codice appalto della scheda che stiamo andando a rettificare
if(!empty($this->scheda["codice_modifica"]) || !empty($this->scheda["codice_rettifica"])) {
$codiceSchedaOriginale = empty($this->scheda["codice_modifica"]) ? $this->scheda["codice_rettifica"] : $this->scheda["codice_modifica"];
$schedaOriginale = NuovaPiattaformaAppalti::fetchSchede(["dati"], ["codice" => $codiceSchedaOriginale], false, false);
if(!empty($schedaOriginale) && valid_json($schedaOriginale["dati"], $schedaOriginale, true)) {
$codiceAppaltoOriginale = DotPath::Get($schedaOriginale, "anacForm.appalto.codiceAppalto");
if(!empty($codiceAppaltoOriginale)) {
$codiceAppalto = $codiceAppaltoOriginale;
}
}
}
if($this->fascicolo["codice_appalto"] !== $codiceAppalto) {
$this->updateFascicolo(["codice_appalto" => $codiceAppalto]);
}
$this->addSuggestion("appalto.codiceAppalto", $codiceAppalto);
$this->addSuggestion("stazioniAppaltanti.0.codiceFiscale", $stazioneAppaltante["cf"]);
$this->addSuggestion("stazioniAppaltanti.0.codiceAusa", $stazioneAppaltante["codice_ausa"]);
$this->addSuggestion("datiContratto.codiceAusa.0", $stazioneAppaltante["codice_ausa"]);
}
$idPianificazione = $this->fascicolo["id_pianificazione"] ?? "";
$this->addSuggestion(
"appalto.idPianificazione",
$idPianificazione,
empty($idPianificazione) ? NPASuggestionType::AUTOFILL : NPASuggestionType::FORCED
);
#endregion
// Per una scheda di lotto usiamo solo il lotto corrente, altrimenti tutti
$codice_lotto_npa = $this->scheda["codice_lotto"] ?? 0;
if (!empty($codice_lotto_npa)) {
$this->addSuggestionsLotto($codice_lotto_npa);
} else {
foreach ($this->getLots() as $lot) {
$this->setLotto($lot["codice_lotto_npa"]);
$ignoreDeserta = false;
if(isset($this->scheda["id_scheda"])) {
$idScheda = $this->scheda["id_scheda"];
$metadata = $this->metadataSchede[$idScheda];
$ignoreDeserta = $metadata['categoria'] === 'affidamento';
}
$agg = $this->getPartecipanti(true);
if($ignoreDeserta && ($lot["stato"] == 95 || empty($agg))) {
continue;
}
$this->unsetLotto();
$this->addSuggestionsLotto($lot["codice_lotto_npa"]);
}
}
return $this->suggestionsCache;
}
/**
* Aggiunge i suggerimenti relativi alle scadenze della procedura
*
* @return void
*/
private function addSuggestionsScadenze(int $indiceLotto)
{
if (empty($this->integration["ROUNDS"])) return;
$isLotto = false;
if(!empty($this->scheda["codice"])) {
$idScheda = $this->scheda["id_scheda"];
$metadata = $this->metadataSchede[$idScheda];
$isLotto = $metadata["lotto"];
}
$scadenzaOfferte = null;
$scadenzaInviti = null;
$rounds = $isLotto ? [end($this->integration["ROUNDS"])] : $this->integration["ROUNDS"];
foreach ($rounds as $round) {
if (empty($round["scadenza"])) continue;
if ($round["infoFase"]["graduatoria"]) {
$scadenzaOfferte = mysql2datetime($round["scadenza"]);
} else {
$scadenzaInviti = mysql2datetime($round["scadenza"]);
}
}
if($scadenzaOfferte !== null) {
$this->addSuggestion(
"lotti.$indiceLotto.datiBaseTerminiInvio.oraScadenzaPresentazioneOfferte",
$scadenzaOfferte,
NPASuggestionType::FORCED
);
$this->addSuggestion(
"elencoSoggetti.$indiceLotto.dataScadenzaPresentazioneOfferta",
$scadenzaOfferte,
NPASuggestionType::FORCED
);
} elseif($scadenzaInviti !== null) {
$this->addSuggestion(
"lotti.$indiceLotto.datiBaseTerminiInvio.scadenzaPresentazioneInvito",
$scadenzaInviti,
NPASuggestionType::FORCED
);
}
}
/**
* Aggiunge un suggerimento alla cache dei suggerimenti
*
* @param string $tag Dotpath della chiave del suggerimento (senza anacForm.)
* @param mixed $value Valore del suggerimento
* @param string $forced
* @return void
*/
private function addSuggestion(string $tag, $value, string $forced = NPASuggestionType::FORCED)
{
DotPath::set($this->suggestionsCache, "anacForm.$tag", [
"forced" => $forced,
"value" => $value
]);
}
/**
* Aggiunge i suggerimenti puramente relativi alle gare
*
* @return void
*/
private function addSuggestionsGara()
{
/**
* @var gara
*/
$gara = $this->oggetto;
// Strumenti svolgimento
$procedura = $gara->getProcedura();
$accordoQuadro = false;
switch ($procedura["key"]) {
case "aq":
case "aqe":
$strumentiSvolgimento = 6;
$accordoQuadro = true;
break;
case "mercato":
case "meex":
$strumentiSvolgimento = 3;
break;
case "sda":
$strumentiSvolgimento = 7;
break;
default:
$elaborazione = $gara->getRisposteElaborazione();
if (($elaborazione["asta"] ?? "N") == "S") {
$strumentiSvolgimento = 2;
} else {
$strumentiSvolgimento = 5;
}
}
$this->addSuggestion(
"appalto.strumentiSvolgimentoProcedure.codice",
$strumentiSvolgimento,
NPASuggestionType::AUTOFILL
);
// Asta elettronica
$this->addSuggestion(
"appalto.datiBaseStrumentiProcedura.astaElettronica",
$strumentiSvolgimento == 2 ? "true" : "false", // True se strumenti svolgimento è asta elettronica,
NPASuggestionType::AUTOFILL
);
// Tipo procedura
$tipoProcedura = $procedura["bdncp"][0] ?? null;
if ($tipoProcedura !== null) {
// Tipo procedura
$this->addSuggestion(
"appalto.datiBaseProcedura.tipoProcedura.codice",
$tipoProcedura,
NPASuggestionType::AUTOFILL
);
}
/*$this->addSuggestion(
"appalto.datiBaseStrumentiProcedura.accordoQuadro.codice",
"none",
);*/
if(!empty($this->oggetto->info["derivazione"]) && $procedura["key"] == "aq") {
$accordo = accordiQuadro::init($this->oggetto->info["derivazione"]);
if(!empty($accordo->info["cig"])) {
$this->addSuggestion(
"appalto.cigAccordoQuadro",
$accordo->info["cig"]
);
}
}
}
/**
* Shorthand per aggiungere un suggerimento ai partecipanti
*
* @param integer $indiceLotto Indice del lotto
* @param integer $indicePartecipante Indice del partecipante
* @param string $key Chiave del suggerimento
* @param mixed $value Valore del suggerimento
* @param string $forced Forzato?
* @return void
*/
private function addSuggestionPartecipante(int $indiceLotto, int $indicePartecipante, string $key, $value, string $forced = NPASuggestionType::FORCED) {
$this->addSuggestion("elencoSoggetti.$indiceLotto.partecipanti.$indicePartecipante.$key", $value, $forced);
$this->addSuggestion("elencoSoggettiRichiedenti.$indiceLotto.soggettiInteressati.$indicePartecipante.$key", $value, $forced);
}
/**
* Aggiunge i suggerimenti relativi ai partecipanti
*
* @param integer $indiceLotto Indice del lotto
* @param array $partecipanti Array contenente i partecipanti
* @param array $invitati Array contenente gli invitati alla procedura
* @return void
*/
private function addSuggestionsPartecipanti(int $indiceLotto, array $partecipanti, array $invitati = [])
{
if (!empty($invitati)) {
$invitatiNonPartecipanti = array_filter($invitati, function ($invitato, $key) use ($partecipanti) {
foreach ($partecipanti as $partecipante) {
if (trim(strtoupper($partecipante["codice_fiscale"])) == trim(strtoupper($invitato["codice_fiscale"]))) {
return false;
}
}
return true;
}, ARRAY_FILTER_USE_BOTH);
$indiceInvitato = 0;
if(!empty($invitatiNonPartecipanti)) {
$this->locked_repetitions["invitatiCheNonHannoPresentatoOfferta"] = true;
}
foreach ($invitatiNonPartecipanti as $invitato) {
$this->addSuggestion("elencoSoggetti.$indiceLotto.invitatiCheNonHannoPresentatoOfferta.$indiceInvitato.codiceFiscale", $invitato["codice_fiscale"]);
$this->addSuggestion("elencoSoggetti.$indiceLotto.invitatiCheNonHannoPresentatoOfferta.$indiceInvitato.denominazione", $invitato["ragione_sociale"]);
$indiceInvitato++;
}
}
$this->locked_repetitions["partecipanti"] = true;
$this->locked_repetitions["soggettiInteressati"] = true;
foreach ($partecipanti as $partecipante) {
$codice_partecipante = $partecipante["codice"]; // Preveniamo edge case in cui due entry di tabelle diverse
// hanno lo stesso codice
if(isset($partecipante["codice_padre"])) {
$codice_partecipante += 1000000000;
}
$indicePartecipante = $this->indiciPartecipanti[$indiceLotto][$codice_partecipante] ?? count($this->indiciPartecipanti[$indiceLotto] ?? []);
$this->indiciPartecipanti[$indiceLotto][$codice_partecipante] = $indicePartecipante;
$idPartecipante = $partecipante["identificativo_partecipante"];
$codiceFiscale = $partecipante["codice_fiscale"];
$ragioneSociale = $partecipante["ragione_sociale"];
$paese = $partecipante["stato"] ?? null;
$this->addSuggestionPartecipante($indiceLotto, $indicePartecipante, "idPartecipante", $idPartecipante);
$this->addSuggestionPartecipante($indiceLotto, $indicePartecipante, "codiceFiscale", $codiceFiscale);
$this->addSuggestionPartecipante($indiceLotto, $indicePartecipante, "denominazione", $ragioneSociale);
if (!empty($paese)) {
$this->addSuggestionPartecipante($indiceLotto, $indicePartecipante, "paeseOperatoreEconomico", $paese, NPASuggestionType::AUTOFILL);
}
if (!empty($partecipante["ruoloOE"])) {
$this->addSuggestionPartecipante($indiceLotto, $indicePartecipante, "ruoloOE.codice", $partecipante["ruoloOE"], NPASuggestionType::AUTOFILL);
}
if(!empty($partecipante["avvalimento"])) {
$this->addSuggestionPartecipante($indiceLotto, $indicePartecipante, "avvalimento", "true", NPASuggestionType::AUTOFILL);
$this->addSuggestionPartecipante($indiceLotto, $indicePartecipante, "tipologiaAvvalimento.codice", $partecipante["avvalimento"], NPASuggestionType::AUTOFILL);
} else {
$this->addSuggestionPartecipante($indiceLotto, $indicePartecipante, "avvalimento", "false", NPASuggestionType::AUTOFILL);
}
}
}
/**
* Shortand per aggiungere un suggerimento per gli aggiudicatari
*
* @param integer $indiceLotto Indice del lotto
* @param integer $indiceAggiudicatario Indice dell'aggiudicatario
* @param string $key Chiave del suggerimento
* @param mixed $value Valore del suggerimento
* @param boolean $forced Forzato?
* @return void
*/
private function addSuggestionAggiudicatario(int $indiceLotto, int $indiceAggiudicatario, string $key, $value, string $forced = NPASuggestionType::FORCED) {
$this->addSuggestion("aggiudicazioni.$indiceLotto.partecipanti.$indiceAggiudicatario.$key", $value, $forced);
$this->addSuggestion("aggiudicazioni.$indiceLotto.offertePresentate.$indiceAggiudicatario.$key", $value, $forced);
$this->addSuggestion("partecipante.$key", $value, $forced);
}
/**
* Aggiunge i suggerimenti relativi agli aggiudicatari
*
* @param integer $indiceLotto Indice del lotto
* @param array $partecipanti Array contenente gli aggiudicatari
* @return void
*/
private function addSuggestionsAggiudicatari(int $indiceLotto, array $partecipanti)
{
$affidamento = false;
if(!empty($this->scheda["codice"])) {
$metadata = $this->metadataSchede[$this->scheda["id_scheda"]];
$affidamento = str_contains(strtolower("affidamento diretto"), $metadata["evento"]);
}
foreach ($partecipanti as $partecipante) {
// Per i non affidamenti prendiamo senza raggruppamenti o avvalimenti perchè sono già stati definiti
if(!$affidamento && $partecipante["isChild"]) {
continue;
}
$isAggiudicatario = ($partecipante["aggiudicatario"] ?? "N") == "S";
// negli affidamenti prendiamo solo gli aggiudicatari
if($affidamento && !$isAggiudicatario) {
continue;
}
// TODO Inserire anche i non aggiudicatari se necessario
if(!$isAggiudicatario) continue;
$this->locked_repetitions["aggiudicazioni"] = true;
$codice_partecipante = $partecipante["codice"];
// Preveniamo edge case in cui due entry di tabelle diverse
// hanno lo stesso codice
if(isset($partecipante["codice_padre"])) {
$codice_partecipante += 1000000000;
}
$idPartecipante = $partecipante["identificativo_partecipante"];
$codiceFiscale = $partecipante["codice_fiscale"];
$ragioneSociale = $partecipante["ragione_sociale"];
$paeseOE = $partecipante["stato"] ?? null;
$indiceAggiudicatario = $this->indiciAggiudicatari[$indiceLotto][$codice_partecipante] ?? count($this->indiciAggiudicatari[$indiceLotto] ?? []);
$this->indiciAggiudicatari[$indiceLotto][$codice_partecipante] = $indiceAggiudicatario;
$this->addSuggestionAggiudicatario($indiceLotto, $indiceAggiudicatario, "idPartecipante", $idPartecipante);
$this->addSuggestionAggiudicatario($indiceLotto, $indiceAggiudicatario, "codiceFiscale", $codiceFiscale);
$this->addSuggestionAggiudicatario($indiceLotto, $indiceAggiudicatario, "denominazione", $ragioneSociale);
$this->addSuggestionAggiudicatario($indiceLotto, $indiceAggiudicatario, "aggiudicatario", $isAggiudicatario ? "true" : "false");
$importoAggiudicazione = 0;
if($isAggiudicatario) {
$importoAggiudicazione = $this->lot["importoAggiudicazione"];
}
if($importoAggiudicazione > 0) {
$this->addSuggestion("comunicazione.importo", currency_format($importoAggiudicazione), NPASuggestionType::FORCED);
} else {
$this->addSuggestion("comunicazione.importo", "", NPASuggestionType::AUTOFILL);
}
$this->addSuggestionAggiudicatario($indiceLotto, $indiceAggiudicatario, "importo", currency_format($importoAggiudicazione), NPASuggestionType::AUTOFILL);
if (!empty($paeseOE)) {
$this->addSuggestionAggiudicatario($indiceLotto, $indiceAggiudicatario, "paeseOperatoreEconomico", $paeseOE, NPASuggestionType::OPTIONAL);
$this->addSuggestionAggiudicatario($indiceLotto, $indiceAggiudicatario, "paeseOperatoreEconomico", $paeseOE, NPASuggestionType::OPTIONAL);
}
if (!empty($partecipante["ruoloOE"])) {
$this->addSuggestionAggiudicatario($indiceLotto, $indiceAggiudicatario, "ruoloOE.codice", $partecipante["ruoloOE"], NPASuggestionType::AUTOFILL);
$this->addSuggestionAggiudicatario($indiceLotto, $indiceAggiudicatario, "ruoloOE.codice", $partecipante["ruoloOE"], NPASuggestionType::AUTOFILL);
}
if(!empty($partecipante["avvalimento"])) {
$this->addSuggestionAggiudicatario($indiceLotto, $indiceAggiudicatario, "avvalimento", "true", NPASuggestionType::AUTOFILL);
$this->addSuggestionAggiudicatario($indiceLotto, $indiceAggiudicatario, "tipologiaAvvalimento.codice", $partecipante["avvalimento"], NPASuggestionType::AUTOFILL);
} else {
$this->addSuggestionAggiudicatario($indiceLotto, $indiceAggiudicatario, "avvalimento", "false", NPASuggestionType::AUTOFILL);
}
}
}
/**
* Ottiene il lotto delle integrazioni a partire dal codice lotto NPA
*
* @param integer $codiceLottoNpa
* @return bool
*/
private function getIntegrationLot(int $codiceLottoNpa): bool
{
// Otteniamo il lotto NPA
$this->setLotto($codiceLottoNpa);
$npaLot = $this->lot;
// Cerchiamo la corrispondenza con i lotti passati dalle integrazioni
foreach ($this->integration["LOT"] as $integrationLot) {
if ($integrationLot["codice"] == $npaLot["codice"]) {
$this->lot = array_merge($this->lot, $integrationLot);
return true;
}
}
return false;
}
/**
* Cache di getPartecipanti per questioni di performance
*
* @var array
*/
private $cachePartecipanti = [];
/**
* Ritorna gli invitati alla procedura corrente
*
* @return array
*/
public function getInvitati(): array
{
if(empty($this->lot)) {
return [];
}
$retval = [];
if (!method_exists($this->oggetto, "getInvitati")) {
return $retval;
}
// Otteniamo gli invitati
$invitati = $this->oggetto->getInvitati();
if (!empty($invitati)) {
$invitati = array_column($invitati, "codice_operatore");
$ooee = oeManager::getOE($invitati);
if (!empty($ooee)) {
foreach ($ooee as $oe) {
$retval[] = [
"codice" => $oe["codice"],
"codice_fiscale" => $oe["codice_fiscale_impresa"],
"ragione_sociale" => $oe["ragione_sociale"],
];
}
}
}
if (!method_exists($this->oggetto, "getInvitatiManuali")) {
return $retval;
}
// Otteniamo gli invitati manuali
$invitatiManuali = $this->oggetto->getInvitatiManuali();
if (!empty($invitatiManuali)) {
foreach ($invitatiManuali as $oe) {
$retval[] = [
"codice" => $oe["codice"],
"codice_fiscale" => $oe["codice_fiscale_operatore"],
"ragione_sociale" => $oe["denominazione"],
];
}
}
return $retval;
}
/**
* Ottiene i partecipanti relativi a questo fascicolo
*
* @param boolean $only_aggiudicatari Se true vengono ritornati solo i partecipanti aggiudicatari
* @param boolean $include_raggruppamenti Se true vengono inclusi anche i partecipanti dei raggruppamenti, condividendo lo stesso idPartecipante
* @param boolean $noCache Se true non viene usata la cache
* @return array|null
*/
public function getPartecipanti(bool $only_aggiudicatari, bool $include_raggruppamenti = true, bool $noCache = false, bool $subappalto = false): ?array
{
if(empty($this->lot)) {
return [];
}
$codice_lotto_npa = $this->lot["codice_lotto_npa"];
$id_scheda = ($this->scheda["id_scheda"] ?? null);
// Cache per velocizzare chiamate successive
if (!$noCache && isset($this->cachePartecipanti[$codice_lotto_npa][$only_aggiudicatari ? 1 : 0][$include_raggruppamenti ? 1 : 0][$subappalto ? 1 : 0])) {
return $this->cachePartecipanti[$codice_lotto_npa][$only_aggiudicatari ? 1 : 0][$include_raggruppamenti ? 1 : 0][$subappalto ? 1 : 0];
}
if (! empty($this->oggetto) && method_exists($this->oggetto, "getPartecipanti")) {
$shouldUseInvitati = $id_scheda === "S1" && ($this->oggetto->round["infoFase"]["graduatoria"] ?? false);
$isExpired = $this->oggetto->info["stato"] >= 21 || !empty($this->oggetto->quickPanel);
if (! $isExpired && is_a($this->oggetto, "gara")) {
if (! ($shouldUseInvitati || ($id_scheda === "S1" && count($this->oggetto->getRounds()) > 1))) {
return [];
}
}
if ($shouldUseInvitati) {
$participantsData = $this->getInvitati();
} else {
$participantsData = $this->oggetto->getPartecipanti();
}
$finalParticipantsData = [];
if (!empty($participantsData)) {
$participantsData = array_column($participantsData, null, "codice");
$versione_interna = $this->fascicolo["versione_interna"] ?? 0;
foreach ($participantsData as &$participant) {
if ($only_aggiudicatari && $participant["aggiudicatario"] !== "S") {
continue;
}
// Generiamo comunque la versione v1 dell'idPartecipante perchè viene usata come fallback
$idPartecipanteV1 = uuidgen_v4("{$participant["codice"]}-{$participant["codice_fiscale"]}-{$participant["ragione_sociale"]}" . "tuttogare_npa" . $this->fascicolo["codice"]);
switch ($versione_interna) {
case 3:
case 4:
// La v3 cerca di usare quello preso dalla tabella e fa fallback alla v1
$participant["identificativo_partecipante"] = !empty($participant["uuid"]) ? $participant["uuid"] : $idPartecipanteV1;
break;
case 2:
case 1:
$participant["identificativo_partecipante"] = $idPartecipanteV1;
break;
default:
$md5 = md5("{$participant["codice"]}-{$participant["codice_fiscale"]}-{$participant["ragione_sociale"]}");
$participant["identificativo_partecipante"] = substr($md5, 0, 8) . '-' . substr($md5, 8, 4) . '-' . substr($md5, 12, 4) . '-' . substr($md5, 16, 4) . '-' . substr($md5, 20);
break;
}
$participant["codice_lotto_npa"] = $codice_lotto_npa;
$participant["ruoloOE"] = 3; // Ente unico
$participant["isChild"] = false; // Ente unico
// Dalla v2 andiamo pure a pescarci i raggruppamenti
if ($include_raggruppamenti && $versione_interna >= 2 && isset($participant["uuid"])) {
$raggruppamento = $this->oggetto->getRaggruppamento($participant["codice"]);
if (!empty($raggruppamento)) {
$participant["ruoloOE"] = 1; // Mandatario
}
if(method_exists($this->oggetto, "getAvvalimento")) {
$avvalimento = $this->oggetto->getAvvalimento($participant["codice"]);
$participant["has_avvalimento"] = !empty($avvalimento);
}
}
if($shouldUseInvitati) {
$participant["fromInvitati"] = true;
unset($participant["ruoloOE"]);
}
$finalParticipantsData[] = $participant;
// Dati di avvalimento e raggruppamento
$subData = [];
if (!empty($avvalimento)) {
foreach ($avvalimento as $partecipanteAvvalimento) {
$cf = trim(strtoupper($partecipanteAvvalimento["codice_fiscale_impresa"]));
// Usiamo il codice fiscale come differenziante
$target = &$subData[$cf];
$target = $partecipanteAvvalimento;
$target["identificativo_partecipante"] = $participant["identificativo_partecipante"];
$target["codice_lotto_npa"] = $codice_lotto_npa;
$target["codice_fiscale"] = $partecipanteAvvalimento["codice_fiscale_impresa"];
$target["ruoloOE"] = 4; // Ausiliaria
$target["isChild"] = true; // Ausiliaria
$target["avvalimento"] = $partecipanteAvvalimento["ruolo"];
$target["aggiudicatario"] = $participant["aggiudicatario"] ?? "N";
$target["uuid"] = uuidgen_v4("{$target["codice"]}-{$target["codice_fiscale"]}-{$partecipanteAvvalimento["ragione_sociale"]}" . "tuttogare_npa" . $this->fascicolo["codice"]);
}
}
if (!empty($raggruppamento)) {
foreach ($raggruppamento as $partecipanteGruppo) {
$cf = trim(strtoupper($partecipanteGruppo["codice_fiscale_impresa"]));
// Usiamo il codice fiscale come differenziante
$target = &$subData[$cf];
// Se esiste già sovrascriviamo solo alcuni dati
if(empty($target))
$target = $partecipanteGruppo;
$target["identificativo_partecipante"] = $participant["identificativo_partecipante"];
$target["codice_lotto_npa"] = $codice_lotto_npa;
$target["codice_fiscale"] = $partecipanteGruppo["codice_fiscale_impresa"];
$target["aggiudicatario"] = $participant["aggiudicatario"] ?? "N";
$target["isChild"] = true;
// ruoloOE del raggruppamento prende priorità
$target["ruoloOE"] = 2; // Mandante
$target["uuid"] = uuidgen_v4("{$target["codice"]}-{$target["codice_fiscale"]}-{$partecipanteGruppo["ragione_sociale"]}" . "tuttogare_npa" . $this->fascicolo["codice"]);
}
}
foreach($subData as $subPartecipante) {
$finalParticipantsData[] = $subPartecipante;
}
}
}
if($subappalto) {
$subappaltatori = $this->getPartecipantiFromSubappalto();
$subappaltatori = array_column($subappaltatori, null, "codice_fiscale");
foreach (array_column($subappaltatori, null, "codice_fiscale") as $codice_fiscale_subappaltatore => $subappaltatore) {
if(array_search($codice_fiscale_subappaltatore, array_column($finalParticipantsData, "codice_fiscale")) === false) {
$finalParticipantsData[] = $subappaltatore;
}
}
}
if (!$noCache) {
$this->cachePartecipanti[$codice_lotto_npa][$only_aggiudicatari ? 1 : 0][$include_raggruppamenti ? 1 : 0][$subappalto ? 1 : 0] = $finalParticipantsData;
}
return $finalParticipantsData;
}
return null;
}
/**
* Recupera i partecipenti trasmessi nelle schede di subappalto ANAC (RSU1)
*
* @return Array
*/
public function getPartecipantiFromSubappalto() : Array {
$retval = [];
$select = ["codice", "dati"];
$filters = [
"codice_npa" => $this->fascicolo["codice"],
"codice_lotto" => $this->lot["codice_lotto_npa"],
"stato" => [">=", 50],
"errore" => "NO",
"id_scheda" => "RSU1"
];
$schede = self::fetchSchede($select, $filters, false, true);
if(! empty($schede)) {
foreach($schede as $scheda) {
$info = json_decode($scheda["dati"], TRUE);
$retval[] = [
'codice' => null,
'codice_operatore' => null,
'codice_round' => null,
'codice_lotto' => $this->lot['codice'],
'stato' => 'IT',
'codice_fiscale' => $info['anacForm']['subappalto']['codiceFiscaleDittaACascata'],
'ragione_sociale' => $info['anacForm']['subappalto']['denominazioneDittaACascata'] ?? 'OPERATORE ECONOMICO',
'ragione_sociale' => 'OE ' . $info['anacForm']['subappalto']['codiceFiscaleDittaACascata'],
'aggiudicatario' => 'N',
'posizione' => '0',
'utente_modifica' => '4',
'timestamp' => '',
'uuid' => generateUUIDFromString(md5('OE-SUBAPPALTO-SCHEDA-RSU1-'.$scheda['codice'].'-'.$info['anacForm']['subappalto']['codiceFiscaleDittaACascata'])),
'identificativo_partecipante' => generateUUIDFromString(md5('OE-SUBAPPALTO-SCHEDA-RSU1-'.$scheda['codice'].'-'.$info['anacForm']['subappalto']['codiceFiscaleDittaACascata'])),
'codice_lotto_npa' => $this->lot["codice_lotto_npa"],
'ruoloOE' => 3,
"isSubAppaltatore" => true,
'isChild' => false,
'has_avvalimento' => false,
];
// if(isset($info["anacForm"]["subappalto"]["codiciFiscaliOE"]) && is_array($info["anacForm"]["subappalto"]["codiciFiscaliOE"])) {
// foreach ($info["anacForm"]["subappalto"]["codiciFiscaliOE"] as $codice_fiscale) {
// if(! empty($codice_fiscale)) {
// $retval[] = [
// 'codice' => null,
// 'codice_operatore' => null,
// 'codice_round' => null,
// 'codice_lotto' => $this->lot['codice'],
// 'stato' => 'IT',
// 'codice_fiscale' => $codice_fiscale,
// 'ragione_sociale' => 'OPERATORE ECONOMICO ' . $codice_fiscale,
// 'aggiudicatario' => 'N',
// 'posizione' => '0',
// 'utente_modifica' => '4',
// 'timestamp' => '',
// 'uuid' => generateUUIDFromString(md5('OE-SUBAPPALTO-SCHEDA-RSU1-'.$scheda['codice'].'-'.$codice_fiscale)),
// 'identificativo_partecipante' => generateUUIDFromString(md5('OE-SUBAPPALTO-SCHEDA-RSU1-'.$scheda['codice'].'-'.$codice_fiscale)),
// 'codice_lotto_npa' => $this->lot["codice_lotto_npa"],
// 'ruoloOE' => 3,
// "isSubAppaltatore" => true,
// 'isChild' => false,
// 'has_avvalimento' => false,
// ];
// }
// }
// }
}
return $retval;
}
return [];
}
/**
* Shorthand per aggiungere un suggerimento al quadroEconomicoStandard
*
* @param integer $indiceLotto Indice del lotto
* @param string $key Chiave del suggerimento
* @param mixed $value Valore del suggerimento
* @param string $forced Forzato?
* @return void
*/
private function addSuggestionLotto(int $indiceLotto, string $key, $value, string $forced = NPASuggestionType::FORCED) {
$this->addSuggestion("lotti.$indiceLotto.$key", $value, $forced);
$this->addSuggestion("aggiudicazioni.$indiceLotto.$key", $value, $forced);
}
/**
* Aggiunge i suggerimenti relativi ad un lotto
*
* @param integer $codiceLottoNpa Codice lotto NPA
* @return void
*/
private function addSuggestionsLotto(int $codiceLottoNpa)
{
$oldLot = $this->lot;
try {
if(!$this->getIntegrationLot($codiceLottoNpa)) {
return;
}
$lot = $this->lot;
$this->locked_repetitions["lotti"] = true;
$this->locked_repetitions["elencoSoggetti"] = true;
$this->locked_repetitions["elencoSoggettiRichiedenti"] = true;
$this->locked_repetitions["elencoIncarichi"] = true;
$indiceLotto = $this->indiciLotto[$codiceLottoNpa] ?? count($this->indiciLotto);
$this->indiciLotto[$codiceLottoNpa] = $indiceLotto;
// Popoliamo la select dei partecipanti
$aggiudicatariNoRaggruppamenti = $this->getPartecipanti(true, false);
$aggiudicatari_options = [];
foreach ($aggiudicatariNoRaggruppamenti as $partecipante) {
$aggiudicatari_options[$partecipante["identificativo_partecipante"]] = "{$partecipante["ragione_sociale"]} ({$partecipante["codice_fiscale"]})";
}
if (!empty($aggiudicatari_options)) {
$this->addSuggestion(
"datiContratto.idPartecipante",
["options" => $aggiudicatari_options],
NPASuggestionType::OPTIONAL
);
}
$partecipanti = $this->getPartecipanti(false, true);
$invitati = [];
if ($this->scheda["id_scheda"] !== "S1") {
$invitati = $this->getInvitati($codiceLottoNpa);
}
// Partecipanti
if ($this->has_partecipanti || $this->scheda["id_scheda"] === "S1") {
$this->addSuggestionsPartecipanti($indiceLotto, $partecipanti, $invitati);
}
// Aggiudicatari
if ($this->has_aggiudicatari || in_array($this->scheda["id_scheda"], ["CM1", "CM2", "S4"], true)) {
$this->addSuggestionsAggiudicatari($indiceLotto, $partecipanti);
}
/* TODO trovare una corrispondenza tra codici NUTS e codici ISTAT se possibile
$this->addSuggestion(
"lotti.$lotIndex.codIstat.codice",
$codiceIstat,
false
);
*/
// Dati base lotto
$this->locked_repetitions["lotti"] = true;
$this->addSuggestion("elencoIncarichi.$indiceLotto.cig", $lot["cig"]);
$this->addSuggestion("elencoSoggettiRichiedenti.$indiceLotto.cig", $lot["cig"]);
$this->addSuggestion("elencoSoggetti.$indiceLotto.cig", $lot["cig"]);
$this->addSuggestion("aggiudicazioni.$indiceLotto.cig", $lot["cig"]);
$this->addSuggestion("datiContratto.cig.$indiceLotto", $lot["cig"]);
$this->addSuggestion("cig", $lot["cig"]);
$this->addSuggestion("lotti.$indiceLotto.cig", $lot["cig"]);
$tipologia = $this->integration["MODULO"] == "concorsi" ? "services" : $this->integration["GENERAL"]["tipologia"] ?? null;
$this->addSuggestionLotto($indiceLotto, "datiBaseDocumenti.url", $this->integration["PUBLIC_URL"], self::urlSuggetionMode());
$this->addSuggestionLotto($indiceLotto, "lotIdentifier", "LOT-" . str_pad($lot["numero"], 4, "0", STR_PAD_LEFT));
$this->addSuggestionLotto($indiceLotto, "datiBase.oggettoContratto.codice", $tipologia);
$this->addSuggestionLotto($indiceLotto, "datiBase.oggetto", self::cleanSuggestion($lot["oggetto"], "aggiudicazioni.$indiceLotto.datiBase.oggetto"));
// scadenzaPresentazioneInvito e oraScadenzaPresentazioneOfferte, iterare tutti i round ed in base a
// alla tipologia, impostare (non forzatamente) la scadenza
// La CPV
$this->addSuggestionLotto($indiceLotto, "datiBaseCPV.tipoClassificazione.codice", "cpv");
$this->addSuggestionLotto($indiceLotto, "datiBaseCPV.cpvPrevalente.codice", $lot["cpv"]);
// lotti.0.datiBaseDocumenti.lingue.codice => it
$nuts = DotPath::get($this->integration, "GENERAL.extraInfo.gara.nuts");
if ($nuts) {
$this->addSuggestionLotto($indiceLotto, "datiBaseContratto.codNUTS.codice", $nuts, NPASuggestionType::OPTIONAL);
}
$this->addSuggestionLotto($indiceLotto, "cupLotto.0", $lot["cup"]);
$this->addSuggestionLotto($indiceLotto, "cup", $lot["cup"]);
$this->addSuggestion("comunicazione.cup", $lot["cup"], NPASuggestionType::FORCED);
$importoTotale = $lot["importoTotale"] ?? null;
if ($importoTotale !== null) {
$this->addSuggestionLotto(
$indiceLotto,
"finanziamenti.0.importo",
currency_format($importoTotale),
NPASuggestionType::AUTOFILL
);
$this->addSuggestion("lotti.$indiceLotto.datiBase.importo", currency_format($importoTotale), NPASuggestionType::AUTOFILL);
}
//
if (isset($lot["criterio"])) {
$this->addSuggestion("lotti.$indiceLotto.datiBaseAggiudicazione.criteriAggiudicazione.codice", $lot["criterio"] == "vantaggiosa" ? "quality" : "price");
}
// $this->addSuggestion("lotti.$lotIndex.datiBaseTerminiInvio.oraScadenzaPresentazioneOfferte"];
$this->addSuggestionLotto($indiceLotto, "quadroEconomicoStandard.impForniture", "0.00", NPASuggestionType::AUTOFILL);
$this->addSuggestionLotto($indiceLotto, "quadroEconomicoStandard.impServizi", "0.00", NPASuggestionType::AUTOFILL);
$this->addSuggestionLotto($indiceLotto, "quadroEconomicoStandard.impLavori", "0.00", NPASuggestionType::AUTOFILL);
$this->addSuggestionLotto($indiceLotto, "quadroEconomicoStandard.impTotaleSicurezza", currency_format($lot["oneriNoRibasso"] ?? 0), NPASuggestionType::AUTOFILL);
// Importo del lotto
$importoRibassabile = currency_format($lot["importoRibassabile"] ?? ($lot["importoBase"] ?? 0));
$chiaveImportoLotto = [
"services" => "impServizi",
"supplies" => "impForniture",
"works" => "impLavori",
][$tipologia] ?? null;
if ($chiaveImportoLotto !== null) {
$this->addSuggestionLotto(
$indiceLotto,
"quadroEconomicoStandard." . $chiaveImportoLotto,
$importoRibassabile,
NPASuggestionType::AUTOFILL
);
}
$sommeOpzioniRinnovi = $lot["importoOpzioni"] ?? 0;
$this->addSuggestionQuadroEconomicoStandard($indiceLotto, "quadroEconomicoStandard.sommeOpzioniRinnovi", currency_format($sommeOpzioniRinnovi));
$this->addSuggestion("lotti.$indiceLotto.opzioniRinnovi", ($sommeOpzioniRinnovi > 0) ? "true" : "false", NPASuggestionType::AUTOFILL);
if ($tipologia === "works") {
if(!empty($lot["CAT_SOA"])) {
foreach ($lot["CAT_SOA"] as $cs) {
$indiceRipetibile = 0;
$categoria = $this->pdo->go("SELECT id FROM b_categorie_soa WHERE codice = :codice AND id <> 'ALTRO'", [":codice" => $cs["codice_categoria"]])->fetch(PDO::FETCH_COLUMN, 0);
if (!empty($categoria)) {
if ($cs["prevalente"] == "S") {
$this->addSuggestionLotto($indiceLotto, "categoria.codice", $categoria);
} else {
$this->addSuggestionLotto($indiceLotto, "categoriaScorporabile.$indiceRipetibile.codice", $categoria);
$indiceRipetibile++;
}
}
}
}
} else {
$categoriaLotto = ["services" => "FS", "supplies" => "FB"][$tipologia] ?? "999";
$this->addSuggestionLotto($indiceLotto, "categoria.codice", $categoriaLotto, NPASuggestionType::AUTOFILL);
}
$this->addSuggestionsScadenze($indiceLotto);
} finally {
// Resettiamo tutto com'era
if(empty($oldLot)) {
$this->lot = null;
if(isset($this->oggetto)) $this->oggetto->lotto = null;
} else {
$this->setLot($oldLot["codice_lotto_npa"]);
}
}
}
}