-
+
{[ "type": "select", "label": "'.tr('Sede').'", "name": "idsede", "values": "query=SELECT 0 AS id, \'Sede legale\' AS descrizione UNION SELECT id, CONCAT_WS(\' - \', nomesede, citta) AS descrizione FROM an_sedi WHERE idanagrafica='.$id_parent.'", "value" : "$idsede$", "required": 1 ]}
+
+
+ {[ "type": "checkbox", "label": "'.tr('Opt-out per newsletter').'", "name": "disable_newsletter", "id": "disable_newsletter_m", "value": "'.empty($record['enable_newsletter']).'" ]}
+
diff --git a/plugins/sedi/actions.php b/plugins/sedi/actions.php
index ff5f30723..08a8a7358 100755
--- a/plugins/sedi/actions.php
+++ b/plugins/sedi/actions.php
@@ -53,6 +53,8 @@ switch ($operazione) {
break;
case 'updatesede':
+ $opt_out_newsletter = post('disable_newsletter');
+
$dbo->update('an_sedi', [
'nomesede' => post('nomesede'),
'indirizzo' => post('indirizzo'),
@@ -73,6 +75,8 @@ switch ($operazione) {
'gaddress' => post('gaddress'),
'lat' => post('lat'),
'lng' => post('lng'),
+
+ 'enable_newsletter' => empty($opt_out_newsletter),
], ['id' => $id_record]);
flash()->info(tr('Salvataggio completato!'));
diff --git a/plugins/sedi/edit.php b/plugins/sedi/edit.php
index 9c25607de..8b1e555f4 100755
--- a/plugins/sedi/edit.php
+++ b/plugins/sedi/edit.php
@@ -95,10 +95,15 @@ echo '
{[ "type": "text", "label": "'.tr('Indirizzo email').'", "name": "email", "value": "$email$" ]}
-
{[ "type": "textarea", "label": "'.tr('Note').'", "name": "note", "value": "$note$" ]}
diff --git a/src/API/App/v1/Interventi.php b/src/API/App/v1/Interventi.php
index a6c6999ce..7204f5adb 100644
--- a/src/API/App/v1/Interventi.php
+++ b/src/API/App/v1/Interventi.php
@@ -216,6 +216,7 @@ class Interventi extends AppResource
$record->richiesta = $data['richiesta'];
$record->descrizione = $data['descrizione'];
$record->informazioniaggiuntive = $data['informazioni_aggiuntive'];
+ $record->idsede_destinazione = $data['id_sede'] ?: 0;
// Salvataggio firma eventuale
if (empty($record->firma_nome) && !empty($data['firma_nome'])) {
diff --git a/src/API/Services.php b/src/API/Services.php
index b81cc3239..e5c7cdd01 100755
--- a/src/API/Services.php
+++ b/src/API/Services.php
@@ -19,7 +19,9 @@
namespace API;
+use Carbon\Carbon;
use GuzzleHttp\Client;
+use Models\Cache;
/**
* Classe per l'interazione con API esterne.
@@ -30,11 +32,98 @@ class Services
{
protected static $client = null;
+ /**
+ * Controlla se il gestionale ha accesso a Services.
+ *
+ * @return bool
+ */
public static function isEnabled()
{
return !empty(setting('OSMCloud Services API Token'));
}
+ /**
+ * Restituisce le informazioni disponibili su Services.
+ *
+ * @return array
+ */
+ public static function getInformazioni($force = false)
+ {
+ $cache = Cache::pool('Informazioni su Services');
+
+ // Aggiornamento dei contenuti della cache
+ if (!$cache->isValid() || $force) {
+ $response = self::request('GET', 'info');
+ $content = self::responseBody($response);
+
+ $cache->set($content);
+
+ return $content;
+ }
+
+ return $cache->content;
+ }
+
+ /**
+ * Restituisce i servizi attivi attraverso Services.
+ *
+ * @return \Illuminate\Support\Collection
+ */
+ public static function getServiziAttivi()
+ {
+ return collect(self::getInformazioni()['servizi']);
+ }
+
+ /**
+ * Restituisce le risorse attive in Services.
+ *
+ * @return \Illuminate\Support\Collection
+ */
+ public static function getRisorseAttive()
+ {
+ return collect(self::getInformazioni()['risorse-api']);
+ }
+
+ /**
+ * Controlla se il gestionale ha accesso a una specifica risorsa di Services.
+ *
+ * @return bool
+ */
+ public static function verificaRisorsaAttiva($servizio)
+ {
+ return self::isEnabled() && self::getRisorseAttive()->search(function ($item) use ($servizio) {
+ return $item['name'] == $servizio;
+ }) !== false;
+ }
+
+ /**
+ * Restituisce le risorse in scadenza per assenza di crediti oppure per data di fine prossima.
+ *
+ * @param Carbon $limite_scadenze
+ *
+ * @return \Illuminate\Support\Collection
+ */
+ public static function getRisorseInScadenza($limite_scadenze)
+ {
+ return self::getRisorseAttive()
+ ->filter(function ($item) use ($limite_scadenze) {
+ return (isset($item['expiration_at']) && Carbon::parse($item['expiration_at'])->lessThan($limite_scadenze))
+ || (isset($item['credits']) && $item['credits'] < 100);
+ });
+ }
+
+ /**
+ * Effettua una richiesta a Services.
+ *
+ * @param $type
+ * @param $resource
+ * @param array $data
+ * @param array $options
+ *
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ *
+ * @return \Psr\Http\Message\ResponseInterface
+ */
public static function request($type, $resource, $data = [], $options = [])
{
$client = static::getClient();
@@ -53,6 +142,13 @@ class Services
return $client->request($type, '', $options);
}
+ /**
+ * Restituisce il corpo JSON della risposta in array.
+ *
+ * @param $response
+ *
+ * @return array
+ */
public static function responseBody($response)
{
$body = $response->getBody();
diff --git a/src/Models/OAuth2.php b/src/Models/OAuth2.php
new file mode 100644
index 000000000..f5affef56
--- /dev/null
+++ b/src/Models/OAuth2.php
@@ -0,0 +1,212 @@
+.
+ */
+
+namespace Models;
+
+use Common\SimpleModelTrait;
+use Illuminate\Database\Eloquent\Model;
+use InvalidArgumentException;
+use League\OAuth2\Client\Provider\AbstractProvider;
+use League\OAuth2\Client\Provider\Exception\IdentityProviderException;
+use League\OAuth2\Client\Token\AccessToken;
+
+class OAuth2 extends Model
+{
+ use SimpleModelTrait;
+
+ protected $provider;
+
+ protected $table = 'zz_oauth2';
+
+ protected $casts = [
+ 'config' => 'array',
+ ];
+
+ /**
+ * @return AbstractProvider
+ */
+ public function getProvider()
+ {
+ // Inizializza il provider per l'autenticazione OAuth2.
+ if (!isset($this->provider)) {
+ $config = $this->config ?? [];
+ $config = array_merge($config, [
+ 'clientId' => $this->client_id,
+ 'clientSecret' => $this->client_secret,
+ 'redirectUri' => base_url().'/oauth2.php',
+ 'accessType' => 'offline',
+ ]);
+
+ $class = $this->class;
+ if (!class_exists($class)) {
+ throw new InvalidArgumentException('Classe non esistente');
+ }
+
+ $this->provider = new $class($config);
+ }
+
+ return $this->provider;
+ }
+
+ public function needsConfiguration()
+ {
+ $access_token = $this->getAccessToken();
+
+ return empty($access_token);
+ }
+
+ /**
+ * Gestisce le operazioni di configurazione per l'autenticazione OAuth2.
+ * Restituisce l'URL di redirect per le operazioni di aggiornamento dei dati, lancia un eccezione in caso di errori e restituisce null in caso di completamento della configurazione.
+ *
+ * Nota: l'autenticazione OAuth2 richiede una serie di richieste su una singola pagina
+ * - Richiesta di autenticazione al server remoto (code, state vuoti)
+ * - Conferma di autenticazione alla pagina di redirect (code, state impostati)
+ * - Richiesta del token di accesso dalla pagina di redirect al server remoto
+ *
+ * @param string|null $code
+ * @param string|null $state
+ *
+ * @throws IdentityProviderException
+ * @throws InvalidArgumentException
+ *
+ * @return string|null
+ */
+ public function configure($code, $state)
+ {
+ if (!$this->needsConfiguration()) {
+ return null;
+ }
+
+ $provider = $this->getProvider();
+ $options = method_exists($provider, 'getOptions') ? $provider->getOptions() : [];
+ if (empty($code)) {
+ // Fetch the authorization URL from the provider; this returns the
+ // urlAuthorize option and generates and applies any necessary parameters
+ // (e.g. state).
+ $authorization_url = $provider->getAuthorizationUrl($options);
+
+ // Get the state generated for you and store it to the session.
+ $this->state = $provider->getState();
+ $this->save();
+
+ // Redirect the user to the authorization URL.
+ return $authorization_url;
+ } elseif (!empty($this->state) && $this->state !== $state) {
+ $this->state = null;
+ $this->save();
+
+ throw new InvalidArgumentException();
+ } else {
+ $this->state = null;
+ $this->save();
+
+ // Try to get an access token using the authorization code grant
+ $access_token = $provider->getAccessToken('authorization_code', [
+ 'code' => $code,
+ ]);
+ $refresh_token = $access_token->getRefreshToken();
+
+ $this->updateTokens($access_token, $refresh_token);
+ }
+
+ return null;
+ }
+
+ /**
+ * @return string|null
+ */
+ public function getRefreshToken()
+ {
+ $this->checkTokens();
+
+ return $this->attributes['refresh_token'];
+ }
+
+ /**
+ * Restituisce l'access token per l'autenticazione OAuth2.
+ *
+ * @return AccessToken|null
+ */
+ public function getAccessToken()
+ {
+ $this->checkTokens();
+
+ return unserialize($this->attributes['access_token']);
+ }
+
+ /**
+ * Effettua una richiesta utilizzando il token di accesso prestabilito.
+ *
+ * @param string $method
+ * @param string $url
+ * @param array $options
+ *
+ * @return array
+ */
+ public function request($method, $url, $options = [])
+ {
+ $provider = $this->getProvider();
+ $accessToken = $this->getAccessToken();
+
+ $request = $provider->getAuthenticatedRequest($method, $url, $accessToken, $options);
+
+ return $provider->getParsedResponse($request);
+ }
+
+ /**
+ * Imposta Access Token e Refresh Token per l'autenticazione OAuth2.
+ *
+ * @param AccessToken|null
+ */
+ protected function updateTokens($access_token, $refresh_token)
+ {
+ $this->access_token = serialize($access_token);
+
+ $previous_refresh_token = $this->refresh_token;
+ $this->refresh_token = $refresh_token ?: $previous_refresh_token;
+
+ $this->save();
+ }
+
+ /**
+ * Controlla la validità dei token correnti e ne effettua il refresh se necessario.
+ */
+ protected function checkTokens()
+ {
+ $access_token = unserialize($this->access_token);
+
+ if (!empty($access_token) && $access_token->hasExpired()) {
+ // Tentativo di refresh del token di accesso
+ $refresh_token = $this->refresh_token;
+ if (!empty($refresh_token)) {
+ $access_token = $this->getProvider()->getAccessToken('refresh_token', [
+ 'refresh_token' => $this->refresh_token,
+ ]);
+
+ $refresh_token = $access_token->getRefreshToken();
+ } else {
+ $access_token = null;
+ $refresh_token = null;
+ }
+
+ $this->updateTokens($access_token, $refresh_token);
+ }
+ }
+}
diff --git a/src/Notifications/EmailNotification.php b/src/Notifications/EmailNotification.php
index 831c7175b..bcfb07ff1 100755
--- a/src/Notifications/EmailNotification.php
+++ b/src/Notifications/EmailNotification.php
@@ -69,16 +69,15 @@ class EmailNotification extends PHPMailer implements NotificationInterface
$this->Username = $account['username'];
// Configurazione OAuth2
- if (!empty($account['access_token'])) {
- $oauth2 = $account->getGestoreOAuth2();
-
+ $oauth2 = $account->oauth2;
+ if (!empty($oauth2)) {
$this->AuthType = 'XOAUTH2';
$this->setOAuth(
new OAuth([
'provider' => $oauth2->getProvider(),
'refreshToken' => $oauth2->getRefreshToken(),
- 'clientId' => $account->client_id,
- 'clientSecret' => $account->client_secret,
+ 'clientId' => $oauth2->client_id,
+ 'clientSecret' => $oauth2->client_secret,
'userName' => $account->username,
])
);
diff --git a/src/Validate.php b/src/Validate.php
index 8b0b718e6..f79a6ecf0 100755
--- a/src/Validate.php
+++ b/src/Validate.php
@@ -98,7 +98,7 @@ class Validate
} */
// Controllo attraverso apilayer
- if (Services::isEnabled()) {
+ if (Services::verificaRisorsaAttiva('Verifica Partita IVA')) {
$response = Services::request('post', 'check_iva', [
'partita_iva' => $vat_number,
]);
@@ -151,7 +151,7 @@ class Validate
}
// Controllo attraverso apilayer
- if (Services::isEnabled()) {
+ if (Services::verificaRisorsaAttiva('Verifica Email')) {
$response = Services::request('post', 'check_email', [
'email' => $email,
]);
diff --git a/templates/bilancio/body.php b/templates/bilancio/body.php
index 820ef8d74..f5aca9adb 100644
--- a/templates/bilancio/body.php
+++ b/templates/bilancio/body.php
@@ -134,7 +134,7 @@ echo '
'.$liv3_p['numero'].' |
'.$liv3_p['descrizione'].' |
- '.numberFormat(abs($liv3_p['totale'])).' |
+ '.numberFormat(-$liv3_p['totale']).' |
';
}
}
diff --git a/templates/preventivi/body.php b/templates/preventivi/body.php
index 45105f16d..8c9828726 100755
--- a/templates/preventivi/body.php
+++ b/templates/preventivi/body.php
@@ -18,19 +18,36 @@
*/
use Carbon\CarbonInterval;
+use Illuminate\Database\Eloquent\ModelNotFoundException;
use Modules\Anagrafiche\Anagrafica;
+use Modules\Banche\Banca;
+use Modules\Pagamenti\Pagamento;
include_once __DIR__.'/../../core.php';
$anagrafica = Anagrafica::find($documento['idanagrafica']);
-$pagamento = $dbo->fetchOne('SELECT * FROM co_pagamenti WHERE id = '.prepare($documento['idpagamento']));
+$anagrafica_azienda = Anagrafica::find(setting('Azienda predefinita'));
-// Verifico se c'è una banca predefinita per il mio cliente
-if (!empty($anagrafica->idbanca_vendite)) {
- $banca = $dbo->fetchOne('SELECT co_banche.nome, co_banche.iban, co_banche.bic FROM co_banche WHERE co_banche.id_pianodeiconti3 = '.prepare($pagamento['idconto_vendite']).' AND co_banche.id_anagrafica = '.prepare($anagrafica->id).' AND co_banche.id ='.prepare($anagrafica->idbanca_vendite));
-} elseif (!empty($pagamento['idconto_vendite'])) {
- // Altrimenti prendo quella associata la metodo di pagamento selezionato
- $banca = $dbo->fetchOne('SELECT co_banche.nome, co_banche.iban, co_banche.bic FROM co_banche WHERE co_banche.id_pianodeiconti3 = '.prepare($pagamento['idconto_vendite']).' AND co_banche.id_anagrafica = '.prepare($anagrafica->id).' AND co_banche.deleted_at IS NULL');
+$pagamento = Pagamento::find($documento['idpagamento']);
+
+// Banca dell'Azienda corrente impostata come predefinita per il Cliente
+$banca_azienda = Banca::where('id_anagrafica', '=', $anagrafica_azienda->id)
+ ->where('id_pianodeiconti3', '=', $pagamento['idconto_vendite'] ?: 0);
+try {
+ $banca = (clone $banca_azienda)
+ ->findOrFail($anagrafica->idbanca_vendite);
+} catch (ModelNotFoundException $e) {
+ // Ricerca prima banca dell'Azienda con Conto corrispondente
+ $banca = (clone $banca_azienda)
+ ->orderBy('predefined', 'DESC')
+ ->first();
+}
+
+// Ri.Ba: Banca predefinita *del Cliente* piuttosto che dell'Azienda
+if ($pagamento->isRiBa()) {
+ $banca = Banca::where('id_anagrafica', $anagrafica->id)
+ ->where('predefined', 1)
+ ->first();
}
// Righe documento
diff --git a/update/2_4_26.php b/update/2_4_26.php
new file mode 100644
index 000000000..f5c08f0c7
--- /dev/null
+++ b/update/2_4_26.php
@@ -0,0 +1,12 @@
+id.'&id_record=';
+
+$database->query("INSERT INTO `zz_oauth2` (`id`, `class`, `client_id`, `client_secret`, `config`, `state`, `access_token`, `refresh_token`, `after_configuration`) SELECT `id`, 'Modules\\\\Emails\\\\OAuth2\\\\Microsoft', `client_id`, `client_secret`, `oauth2_config`, `oauth2_state`, `access_token`, `refresh_token`, CONCAT('".$redirect."', `id`) FROM `em_accounts` WHERE `provider` = 'microsoft' AND `client_id` IS NOT NULL");
+
+$database->query("INSERT INTO `zz_oauth2` (`id`, `class`, `client_id`, `client_secret`, `config`, `state`, `access_token`, `refresh_token`, `after_configuration`) SELECT `id`, 'Modules\\\\Emails\\\\OAuth2\\\\Google', `client_id`, `client_secret`, `oauth2_config`, `oauth2_state`, `access_token`, `refresh_token`, CONCAT('".$redirect."', `id`) FROM `em_accounts` WHERE `provider` = 'google' AND `client_id` IS NOT NULL");
+
+$database->query('UPDATE `em_accounts` SET `id_oauth2` = (SELECT `id` FROM `zz_oauth2` WHERE `zz_oauth2`.`id` = `em_accounts`.`id`)');
diff --git a/update/2_4_26.sql b/update/2_4_26.sql
index fc4a4fa92..4c1945ccf 100644
--- a/update/2_4_26.sql
+++ b/update/2_4_26.sql
@@ -107,3 +107,27 @@ INSERT INTO `em_lists` (`id`, `name`, `description`, `query`, `deleted_at`) VALU
-- Fix riferimento documento per righe create da Interventi
UPDATE `co_righe_documenti` SET `original_document_id` = `idintervento`, `original_document_type` = 'Modules\\Interventi\\Intervento' WHERE `idintervento` IS NOT NULL;
+
+-- Generalizzazione della configurazione OAuth2
+CREATE TABLE IF NOT EXISTS `zz_oauth2` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `class` varchar(255) DEFAULT NULL,
+ `client_id` text DEFAULT NULL,
+ `client_secret` text DEFAULT NULL,
+ `config` text DEFAULT NULL,
+ `state` text DEFAULT NULL,
+ `access_token` text DEFAULT NULL,
+ `refresh_token` text DEFAULT NULL,
+ `after_configuration` text DEFAULT NULL,
+ `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
+ `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB;
+
+ALTER TABLE `em_accounts` ADD `id_oauth2` INT(11) DEFAULT NULL,
+ ADD FOREIGN KEY (`id_oauth2`) REFERENCES `zz_oauth2`(`id`);
+
+-- Aggiunta opt-out Newsletter per Referenti e Sedi
+ALTER TABLE `an_referenti` ADD `enable_newsletter` BOOLEAN DEFAULT TRUE;
+ALTER TABLE `an_sedi` ADD `enable_newsletter` BOOLEAN DEFAULT TRUE;
+
diff --git a/update/tables.php b/update/tables.php
index 76c355e97..c305d13de 100755
--- a/update/tables.php
+++ b/update/tables.php
@@ -99,6 +99,7 @@ return [
'mg_valori_attributi',
'my_impianto_componenti',
'my_componenti',
+ 'zz_oauth2',
'or_ordini',
'or_righe_ordini',
'or_statiordine',