This commit is contained in:
Beppe 2021-09-27 10:27:19 +02:00
commit 5999abf86e
50 changed files with 926 additions and 402 deletions

View File

@ -4,6 +4,7 @@ Tutti i maggiori cambiamenti di questo progetto saranno documentati in questo fi
Il formato utilizzato è basato sulle linee guida di [Keep a Changelog](http://keepachangelog.com/), e il progetto segue il [Semantic Versioning](http://semver.org/) per definire le versioni delle release.
- [2.4.26 (2021-09-24)](#2426-2021-09-24)
- [2.4.25 (2021-08-25)](#2425-2021-08-25)
- [2.4.24 (2021-07-28)](#2424-2021-07-28)
- [2.4.23 (2021-05-18)](#2423-2021-05-18)
@ -36,15 +37,21 @@ Il formato utilizzato è basato sulle linee guida di [Keep a Changelog](http://k
- [2.2 (2016-11-10)](#22-2016-11-10)
- [2.1 (2015-04-02)](#21-2015-04-02)
## 2.4.26 (2021-00-00)
## 2.4.26 (2021-09-24)
### Aggiunto (Added)
- Aggiunto modal in fase di **Stampa Bilancio** per visualizzare o meno l'elenco analitico dei clienti e fornitori
- Aggiunta scelta del tipo documento in fase di creazione fattura da un altro documento
- Aggiunta possibilità di creare delle ricorrenze per gli **Interventi** in fase di aggiunta
- Aggiunta scelta del tipo documento in fase di creazione fattura da un azione di gruppo di un altro documento
- Aggiunta sistema di gestione Combinazioni Articoli
### Modificato (Changed)
- Modificata query per generare liste in **Newsletter**
### Fixed
- Fix orario della modifica del listino di riferimento dell'articolo
- Fix movimentazione articoli tra due sedi tramite **DDT**
- Fix duplicazione **Pagamenti**
## 2.4.25 (2021-08-25)
### Aggiunto (Added)

View File

@ -293,6 +293,14 @@ Input.prototype.set = function (value) {
CKEDITOR.instances[name].setData(value);
} else {
this.element.val(value).trigger("change");
// Impostazione valore per checkbox
let group = this.element.closest(".form-group");
if (group.find("input[type=checkbox]").length) {
value = value === true || parseInt(value) !== 1;
group.find("[type=hidden]").val(+value).trigger('change')
group.find("[type=checkbox]").prop("checked", value);
}
}
return this;

View File

@ -399,6 +399,7 @@ function release(done) {
'!include/custom/**',
'!backup/**',
'!files/**',
'files/temp/.gitkeep',
'!logs/**',
'!config.inc.php',
'!update/structure.php',

View File

@ -202,7 +202,7 @@ if (filter('action') == 'do_update') {
foreach ($updates as $update) {
if ($update['sql'] && (!empty($update['done']) || is_null($update['done']))) {
$queries = readSQLFile(base_dir().$update['directory'].$update['filename'].'.sql', ';');
$queries = readSQLFile(base_dir().'/'.$update['directory'].$update['filename'].'.sql', ';');
$total += count($queries);
if (intval($update['done']) > 1) {
@ -214,7 +214,7 @@ if (filter('action') == 'do_update') {
$total += $scriptValue;
}
}
echo '
<script>
$(document).ready(function(){

View File

@ -35,6 +35,10 @@ if (!isset($_SESSION['module_'.$id_fatture]['id_segment'])) {
}
$id_segment = $_SESSION['module_'.$id_fatture]['id_segment'];
$idconto = setting('Conto predefinito fatture di vendita');
$idtipodocumento = $dbo->selectOne('co_tipidocumento', ['id'], [
'predefined' => 1,
'dir' => 'entrata',
])['id'];
switch (post('op')) {
case 'crea_fattura':
@ -42,8 +46,7 @@ switch (post('op')) {
$numero_totale = 0;
// Informazioni della fattura
$descrizione_tipo = 'Fattura immediata di vendita';
$tipo_documento = Tipo::where('descrizione', $descrizione_tipo)->first();
$tipo_documento = Tipo::where('id', post('idtipodocumento'))->first();
$stato_documenti_accodabili = Stato::where('descrizione', 'Bozza')->first();
$accodare = post('accodare');
@ -125,7 +128,9 @@ $operations['crea_fattura'] = [
'text' => '<span><i class="fa fa-file-code-o"></i> '.tr('Fattura _TYPE_', ['_TYPE_' => strtolower($module['name'])]),
'data' => [
'title' => tr('Fatturare i _TYPE_ selezionati?', ['_TYPE_' => strtolower($module['name'])]),
'msg' => '{[ "type": "checkbox", "label": "<small>'.tr('Aggiungere alle fatture di vendita non ancora emesse?').'</small>", "placeholder": "'.tr('Aggiungere alle fatture esistenti non ancora emesse?').'", "name": "accodare" ]}<br>{[ "type": "select", "label": "'.tr('Sezionale').'", "name": "id_segment", "required": 1, "values": "query=SELECT id, name AS descrizione FROM zz_segments WHERE id_module=\''.$id_fatture.'\' AND is_fiscale = 1 ORDER BY name", "value": "'.$id_segment.'" ]}',
'msg' => '{[ "type": "checkbox", "label": "<small>'.tr('Aggiungere alle fatture di vendita non ancora emesse?').'</small>", "placeholder": "'.tr('Aggiungere alle fatture esistenti non ancora emesse?').'", "name": "accodare" ]}<br>
{[ "type": "select", "label": "'.tr('Sezionale').'", "name": "id_segment", "required": 1, "values": "query=SELECT id, name AS descrizione FROM zz_segments WHERE id_module=\''.$id_fatture.'\' AND is_fiscale = 1 ORDER BY name", "value": "'.$id_segment.'" ]}<br>
{[ "type": "select", "label": "'.tr('Tipo documento').'", "name": "idtipodocumento", "required": 1, "values": "query=SELECT id, CONCAT(codice_tipo_documento_fe, \' - \', descrizione) AS descrizione FROM co_tipidocumento WHERE enabled = 1 AND dir =\'entrata\' ORDER BY codice_tipo_documento_fe", "value": "'.$idtipodocumento.'" ]}',
'button' => tr('Procedi'),
'class' => 'btn btn-lg btn-warning',
'blank' => false,

View File

@ -50,8 +50,10 @@ class Contratto extends Document
* @var array
*/
protected $dates = [
'data_bozza',
'data_conclusione',
'data_accettazione',
'data_rifiuto',
];
/**

View File

@ -41,6 +41,10 @@ if (!isset($_SESSION['module_'.$id_fatture]['id_segment'])) {
}
$id_segment = $_SESSION['module_'.$id_fatture]['id_segment'];
$idconto = setting('Conto predefinito fatture di vendita');
$idtipodocumento = $dbo->selectOne('co_tipidocumento', ['id'], [
'predefined' => 1,
'dir' => $dir,
])['id'];
switch (post('op')) {
case 'crea_fattura':
@ -48,13 +52,7 @@ switch (post('op')) {
$numero_totale = 0;
// Informazioni della fattura
if ($dir == 'entrata') {
$descrizione_tipo = 'Fattura immediata di vendita';
} else {
$descrizione_tipo = 'Fattura immediata di acquisto';
}
$tipo_documento = Tipo::where('descrizione', $descrizione_tipo)->first();
$tipo_documento = Tipo::where('id', post('idtipodocumento'))->first();
$stato_documenti_accodabili = Stato::where('descrizione', 'Bozza')->first();
$accodare = post('accodare');
@ -180,8 +178,9 @@ $operations['crea_fattura'] = [
'text' => '<span><i class="fa fa-file-code-o"></i> '.tr('Fattura _TYPE_', ['_TYPE_' => strtolower($module['name'])]),
'data' => [
'title' => tr('Fatturare i _TYPE_ selezionati?', ['_TYPE_' => strtolower($module['name'])]),
'msg' => '{[ "type": "checkbox", "label": "<small>'.tr('Aggiungere alle _TYPE_ non ancora emesse?', ['_TYPE_' => strtolower($module_fatture)]).'", "placeholder": "'.tr('Aggiungere alle _TYPE_ nello stato bozza?', ['_TYPE_' => strtolower($module_fatture)]).'</small>", "name": "accodare" ]}
<br>{[ "type": "select", "label": "'.tr('Sezionale').'", "name": "id_segment", "required": 1, "values": "query=SELECT id, name AS descrizione FROM zz_segments WHERE id_module=\''.$id_fatture.'\' AND is_fiscale = 1 ORDER BY name", "value": "'.$id_segment.'" ]}',
'msg' => '{[ "type": "checkbox", "label": "<small>'.tr('Aggiungere alle _TYPE_ non ancora emesse?', ['_TYPE_' => strtolower($module_fatture)]).'", "placeholder": "'.tr('Aggiungere alle _TYPE_ nello stato bozza?', ['_TYPE_' => strtolower($module_fatture)]).'</small>", "name": "accodare" ]}<br>
{[ "type": "select", "label": "'.tr('Sezionale').'", "name": "id_segment", "required": 1, "values": "query=SELECT id, name AS descrizione FROM zz_segments WHERE id_module=\''.$id_fatture.'\' AND is_fiscale = 1 ORDER BY name", "value": "'.$id_segment.'" ]}<br>
{[ "type": "select", "label": "'.tr('Tipo documento').'", "name": "idtipodocumento", "required": 1, "values": "query=SELECT id, CONCAT(codice_tipo_documento_fe, \' - \', descrizione) AS descrizione FROM co_tipidocumento WHERE enabled = 1 AND dir ='.prepare($dir).' ORDER BY codice_tipo_documento_fe", "value": "'.$idtipodocumento.'" ]}',
'button' => tr('Procedi'),
'class' => 'btn btn-lg btn-warning',
'blank' => false,

View File

@ -23,6 +23,8 @@ use Carbon\Carbon;
use Common\SimpleModelTrait;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Modules\Emails\OAuth2\Google;
use Modules\Emails\OAuth2\Microsoft;
use Notifications\EmailNotification;
use Traits\LocalPoolTrait;
@ -32,14 +34,20 @@ class Account extends Model
use LocalPoolTrait;
use SoftDeletes;
protected $table = 'em_accounts';
protected $casts = [
'oauth2_config' => 'array',
public static $providers = [
'microsoft' => [
'name' => 'Microsoft',
'class' => Microsoft::class,
'help' => 'https://docs.openstamanager.com/faq/configurazione-oauth2#microsoft',
],
'google' => [
'name' => 'Google',
'class' => Google::class,
'help' => 'https://docs.openstamanager.com/faq/configurazione-oauth2#google',
],
];
/** @var OAuth2 */
protected $gestoreOAuth2;
protected $table = 'em_accounts';
public function testConnection()
{
@ -61,17 +69,6 @@ class Account extends Model
return $result;
}
public function getGestoreOAuth2()
{
if (isset($this->gestoreOAuth2)) {
return $this->gestoreOAuth2;
}
$this->gestoreOAuth2 = new OAuth2($this);
return $this->gestoreOAuth2;
}
/* Relazioni Eloquent */
public function templates()
@ -79,6 +76,11 @@ class Account extends Model
return $this->hasMany(Template::class, 'id_account');
}
public function oauth2()
{
return $this->belongsTo(\Models\OAuth2::class, 'id_oauth2');
}
public function emails()
{
return $this->hasMany(Mail::class, 'id_account');

View File

@ -95,9 +95,9 @@ class EmailHook extends Manager
}
// Invio effettivo
foreach ($lista as $lista_account) {
foreach ($lista as $mail) {
try {
$email = EmailNotification::build($lista_account);
$email = EmailNotification::build($mail);
$email->send();
} catch (Exception $e) {
}

View File

@ -1,170 +0,0 @@
<?php
namespace Modules\Emails;
use InvalidArgumentException;
use League\OAuth2\Client\Provider\Exception\IdentityProviderException;
use League\OAuth2\Client\Token\AccessToken;
use Modules\Emails\OAuth2\Google;
use Modules\Emails\OAuth2\Microsoft;
class OAuth2
{
public static $providers = [
'microsoft' => [
'name' => 'Microsoft',
'class' => Microsoft::class,
'help' => 'https://docs.openstamanager.com/faq/configurazione-oauth2#microsoft',
],
'google' => [
'name' => 'Google',
'class' => Google::class,
'help' => 'https://docs.openstamanager.com/faq/configurazione-oauth2#google',
],
];
protected $provider;
protected $account;
public function __construct(Account $account)
{
$this->account = $account;
// Inizializza il provider per l'autenticazione OAuth2.
$redirect_uri = base_url().'/oauth2.php';
$class = $this->getProviderConfiguration()['class'];
$this->provider = new $class($this->account, $redirect_uri);
}
public function getProvider()
{
return $this->provider;
}
public function getProviderConfiguration()
{
return self::$providers[$this->account->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 = $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->account->oauth2_state = $provider->getState();
$this->account->save();
// Redirect the user to the authorization URL.
return $authorization_url;
} elseif (!empty($this->account->oauth2_state) && $this->account->oauth2_state !== $state) {
$this->account->oauth2_state = null;
$this->account->save();
throw new InvalidArgumentException();
} else {
$this->account->oauth2_state = null;
$this->account->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;
}
public function getRefreshToken()
{
$this->checkTokens();
return $this->account->refresh_token;
}
/**
* Restituisce l'access token per l'autenticazione OAuth2.
*
* @return AccessToken|null
*/
public function getAccessToken()
{
$this->checkTokens();
return unserialize($this->account->access_token);
}
/**
* Imposta l'access token per l'autenticazione OAuth2.
*
* @param AccessToken|null
*/
public function updateTokens($access_token, $refresh_token)
{
$this->account->access_token = serialize($access_token);
$previous_refresh_token = $this->account->refresh_token;
$this->account->refresh_token = $refresh_token ?: $previous_refresh_token;
$this->account->save();
}
protected function checkTokens()
{
$access_token = unserialize($this->account->access_token);
if (!empty($access_token) && $access_token->hasExpired()) {
// Tentativo di refresh del token di accesso
$refresh_token = $this->account->refresh_token;
if (!empty($refresh_token)) {
$access_token = $this->getProvider()->getAccessToken('refresh_token', [
'refresh_token' => $this->account->refresh_token,
]);
$refresh_token = $access_token->getRefreshToken();
} else {
$access_token = null;
$refresh_token = null;
}
$this->updateTokens($access_token, $refresh_token);
}
}
}

View File

@ -3,7 +3,6 @@
namespace Modules\Emails\OAuth2;
use League\OAuth2\Client\Provider\Google as OriginalProvider;
use Modules\Emails\Account;
class Google extends OriginalProvider implements ProviderInterface
{
@ -12,16 +11,6 @@ class Google extends OriginalProvider implements ProviderInterface
'accessType' => 'offline',
];
public function __construct(Account $account, $redirect_uri)
{
parent::__construct([
'clientId' => $account->client_id,
'clientSecret' => $account->client_secret,
'redirectUri' => $redirect_uri,
'accessType' => 'offline',
]);
}
public function getOptions()
{
return self::$options;

View File

@ -2,7 +2,6 @@
namespace Modules\Emails\OAuth2;
use Modules\Emails\Account;
use TheNetworg\OAuth2\Client\Provider\Azure;
class Microsoft extends Azure implements ProviderInterface
@ -24,18 +23,15 @@ class Microsoft extends Azure implements ProviderInterface
],
];
public function __construct(Account $account, $redirect_uri)
public function __construct(array $options = [], array $collaborators = [])
{
parent::__construct([
'clientId' => $account->client_id,
'clientSecret' => $account->client_secret,
'redirectUri' => $redirect_uri,
'accessType' => 'offline',
// Configurazioni specifiche per il provider di Microsoft Azure
$config = array_merge($options, [
'defaultEndPointVersion' => parent::ENDPOINT_VERSION_2_0,
'tenant' => $options['tenant_id'],
]);
// Configurazioni specifiche per il provider di Microsoft Azure
$this->defaultEndPointVersion = parent::ENDPOINT_VERSION_2_0;
$this->tenant = $account->oauth2_config['tenant_id'];
parent::__construct($config, $collaborators);
}
public function getOptions()

View File

@ -2,12 +2,8 @@
namespace Modules\Emails\OAuth2;
use Modules\Emails\Account;
interface ProviderInterface
{
public function __construct(Account $account, $redirect_uri);
/**
* Restituisce l'array di configurazione per la connessione remota al servizio del provider.
*

View File

@ -19,6 +19,8 @@
include_once __DIR__.'/../../core.php';
use Modules\Fatture\Fattura;
$module = Modules::get($id_module);
if ($module['name'] == 'Fatture di vendita') {
@ -29,9 +31,9 @@ if ($module['name'] == 'Fatture di vendita') {
$conti = 'conti-acquisti';
}
$info = $dbo->fetchOne('SELECT * FROM co_documenti WHERE id='.prepare($id_record));
$numero = ($info['numero_esterno'] != '') ? $info['numero_esterno'] : $info['numero'];
$idanagrafica = $info['idanagrafica'];
$fattura = Fattura::find($id_record);
$numero = ($fattura->numero_esterno != '') ? $fattura->numero_esterno : $fattura->numero;
$idanagrafica = $fattura->idanagrafica;
$idconto = ($dir == 'entrata') ? setting('Conto predefinito fatture di vendita') : setting('Conto predefinito fatture di acquisto');
@ -108,7 +110,7 @@ $options['id_ritenuta_acconto_predefined'] = $ritenuta_acconto['id_ritenuta_acco
echo App::internalLoad('conti.php', [], $options);
// Leggo l'iva predefinita dall'articolo e se non c'è leggo quella predefinita generica
$idiva = $idiva ?: setting('Iva predefinita');
$idiva = $fattura->anagrafica->idiva_vendite ?: setting('Iva predefinita');
// Iva
echo '

View File

@ -23,6 +23,7 @@ use Modules\Anagrafiche\Nazione;
use Modules\Fatture\Gestori\Bollo;
use Modules\Interventi\Intervento;
use Modules\Iva\Aliquota;
use Plugins\ExportFE\Interaction;
include_once __DIR__.'/../../core.php';
@ -228,7 +229,7 @@ elseif ($record['stato'] == 'Bozza') {
?>
<div class="col-md-2" <?php echo ($is_fiscale) ? '' : 'hidden'; ?> >
{[ "type": "select", "label": "<?php echo tr('Stato FE'); ?>", "name": "codice_stato_fe", "values": "query=SELECT codice as id, CONCAT_WS(' - ',codice,descrizione) as text FROM fe_stati_documento", "value": "$codice_stato_fe$", "disabled": <?php echo intval(API\Services::isEnabled() || ($record['stato'] == 'Bozza' && $abilita_genera)); ?>, "class": "unblockable", "help": "<?php echo (!empty($record['data_stato_fe'])) ? Translator::timestampToLocale($record['data_stato_fe']) : ''; ?>" ]}
{[ "type": "select", "label": "<?php echo tr('Stato FE'); ?>", "name": "codice_stato_fe", "values": "query=SELECT codice as id, CONCAT_WS(' - ',codice,descrizione) as text FROM fe_stati_documento", "value": "$codice_stato_fe$", "disabled": <?php echo intval(Interaction::isEnabled() || ($record['stato'] == 'Bozza' && $abilita_genera)); ?>, "class": "unblockable", "help": "<?php echo (!empty($record['data_stato_fe'])) ? Translator::timestampToLocale($record['data_stato_fe']) : ''; ?>" ]}
</div>
<?php

View File

@ -214,6 +214,97 @@ switch (post('op')) {
'id_tecnico' => $tecnici_assegnati,
]);
if (!empty(post('ricorsiva'))) {
$periodicita = post('periodicita');
$data = post('data_inizio_ricorrenza');
$interval = post('tipo_periodicita') != 'manual' ? post('tipo_periodicita') : 'days';
$stato = Stato::find(post('idstatoricorrenze'));
// Estraggo le date delle ricorrenze
if (post('metodo_ricorrenza') == 'data') {
$data_fine = post('data_fine_ricorrenza');
while (strtotime($data) <= strtotime($data_fine)) {
$data = date('Y-m-d', strtotime('+'.$periodicita.' '.$interval.'', strtotime($data)));
$w = date('w', strtotime($data));
//Escludo sabato e domenica
if ($w == '6') {
$data = date('Y-m-d', strtotime('+2 day', strtotime($data)));
} elseif ($w == '0') {
$data = date('Y-m-d', strtotime('+1 day', strtotime($data)));
}
if ($data <= $data_fine) {
$date_ricorrenze[] = $data;
}
}
} else {
$ricorrenze = post('numero_ricorrenze');
for ($i = 0; $i < $ricorrenze; ++$i) {
$data = date('Y-m-d', strtotime('+'.$periodicita.' '.$interval.'', strtotime($data)));
$w = date('w', strtotime($data));
//Escludo sabato e domenica
if ($w == '6') {
$data = date('Y-m-d', strtotime('+2 day', strtotime($data)));
} elseif ($w == '0') {
$data = date('Y-m-d', strtotime('+1 day', strtotime($data)));
}
$date_ricorrenze[] = $data;
}
}
foreach ($date_ricorrenze as $data_ricorrenza) {
$intervento = Intervento::find($id_record);
$new = $intervento->replicate();
// Calcolo il nuovo codice
$new->codice = Intervento::getNextCodice($data_ricorrenza);
$new->data_richiesta = $data_ricorrenza;
$new->idstatointervento = $stato->idstatointervento;
$new->save();
$idintervento = $new->id;
// Inserimento sessioni
if (!empty(post('riporta_sessioni'))) {
$numero_sessione = 0;
$sessioni = $intervento->sessioni;
foreach ($sessioni as $sessione) {
// Se è la prima sessione che copio importo la data con quella della richiesta
if ($numero_sessione == 0) {
$orario_inizio = date('Y-m-d', strtotime($data_ricorrenza)).' '.date('H:i:s', strtotime($sessione->orario_inizio));
} else {
$diff = strtotime($sessione->orario_inizio) - strtotime($inizio_old);
$orario_inizio = date('Y-m-d H:i:s', (strtotime($sessione->orario_inizio) + $diff));
}
$diff_fine = strtotime($sessione->orario_fine) - strtotime($sessione->orario_inizio);
$orario_fine = date('Y-m-d H:i:s', (strtotime($orario_inizio) + $diff_fine));
$new_sessione = $sessione->replicate();
$new_sessione->idintervento = $new->id;
$new_sessione->orario_inizio = $orario_inizio;
$new_sessione->orario_fine = $orario_fine;
$new_sessione->save();
++$numero_sessione;
$inizio_old = $sessione->orario_inizio;
}
}
// Assegnazione dei tecnici all'intervento
$tecnici_assegnati = (array) post('tecnici_assegnati');
$dbo->sync('in_interventi_tecnici_assegnati', [
'id_intervento' => $new->id,
], [
'id_tecnico' => $tecnici_assegnati,
]);
++$n_ricorrenze;
}
flash()->info(tr('Aggiunte _NUM_ nuove ricorrenze!', [
'_NUM_' => $n_ricorrenze,
]));
}
if (post('ref') == 'dashboard') {
flash()->clearMessage('info');
flash()->clearMessage('warning');

View File

@ -47,7 +47,7 @@ if ($user['gruppo'] == 'Tecnici' && !empty($user['idanagrafica'])) {
}
// Stato di default associato all'attivitò
$stato = $dbo->fetchArray("SELECT * FROM in_statiintervento WHERE descrizione = 'In programmazione'");
$stato = $dbo->fetchArray("SELECT * FROM in_statiintervento WHERE codice = 'WIP'");
$id_stato = $stato['idstatointervento'];
// Se è indicata un'anagrafica relativa, si carica il tipo di intervento di default impostato
@ -314,6 +314,58 @@ echo '
</div>
</div>
<!-- RICORRENZA -->
<div class="box box-warning collapsable collapsed-box">
<div class="box-header with-border">
<h3 class="box-title">'.tr('Ricorrenza').'</h3>
<div class="box-tools pull-right">
<button type="button" class="btn btn-box-tool" data-widget="collapse">
<i class="fa fa-plus"></i>
</button>
</div>
</div>
<div class="box-body">
<div class="row">
<div class="col-md-4">
{[ "type": "checkbox", "label": "'.tr('Ricorsiva').'", "name": "ricorsiva", "value": "" ]}
</div>
<div class="col-md-4 ricorrenza">
{[ "type": "timestamp", "label": "'.tr('Data/ora inizio').'", "name": "data_inizio_ricorrenza", "value": "'.($data_richiesta ?: '-now-').'" ]}
</div>
<div class="col-md-4 ricorrenza">
{[ "type": "number", "label": "'.tr('Periodicità').'", "name": "periodicita", "decimals": "0", "icon-after": "choice|period|months", "value": "1" ]}
</div>
</div>
<div class="row ricorrenza">
<div class="col-md-4">
{[ "type": "select", "label": "'.tr('Metodo fine ricorrenza').'", "name": "metodo_ricorrenza", "values": "list=\"data\":\"Data fine\",\"numero\":\"Numero ricorrenze\"" ]}
</div>
<div class="col-md-4">
{[ "type": "timestamp", "label": "'.tr('Data/ora fine').'", "name": "data_fine_ricorrenza" ]}
</div>
<div class="col-md-4">
{[ "type": "number", "label": "'.tr('Numero ricorrenze').'", "name": "numero_ricorrenze", "decimals": "0" ]}
</div>
</div>
<div class="row ricorrenza">
<div class="col-md-4">
{[ "type": "select", "label": "'.tr('Stato ricorrenze').'", "name": "idstatoricorrenze", "values": "query=SELECT idstatointervento AS id, descrizione, colore AS _bgcolor_ FROM in_statiintervento WHERE deleted_at IS NULL AND is_completato=0" ]}
</div>
<div class="col-md-4">
{[ "type": "checkbox", "label": "'.tr('Riporta sessioni di lavoro').'", "name": "riporta_sessioni", "value": "" ]}
</div>
</div>
</div>
</div>
<!-- DETTAGLI CLIENTE -->
<div class="box box-success collapsable collapsed-box">
<div class="box-header with-border">
@ -407,6 +459,9 @@ echo '
location.reload();
});
}
// Ricorrenza
$(".ricorrenza").addClass("hidden");
});
input("idtecnico").change(function() {
@ -597,4 +652,32 @@ if (filter('orario_fine') !== null) {
function deassegnaTuttiTecnici() {
input("tecnici_assegnati").getElement().selectReset();
}
$("#ricorsiva").on("change", function(){
if ($(this).is(":checked")) {
$(".ricorrenza").removeClass("hidden");
$("#data_inizio_ricorrenza").attr("required", true);
$("#metodo_ricorrenza").attr("required", true);
$("#idstatoricorrenze").attr("required", true);
} else {
$(".ricorrenza").addClass("hidden");
$("#data_inizio_ricorrenza").attr("required", false);
$("#metodo_ricorrenza").attr("required", false);
$("#idstatoricorrenze").attr("required", false);
}
});
$("#metodo_ricorrenza").on("change", function(){
if ($(this).val()=="data") {
input("data_fine_ricorrenza").enable();
$("#data_fine_ricorrenza").attr("required", true);
input("numero_ricorrenze").disable();
input("numero_ricorrenze").set("");
} else {
input("numero_ricorrenze").enable();
input("data_fine_ricorrenza").disable();
input("data_fine_ricorrenza").set("");
$("#data_fine_ricorrenza").attr("required", false);
}
});
</script>';

View File

@ -33,7 +33,6 @@ if (!isset($_SESSION['module_'.$id_fatture]['id_segment'])) {
$_SESSION['module_'.$id_fatture]['id_segment'] = isset($segments[0]['id']) ? $segments[0]['id'] : null;
}
$id_segment = $_SESSION['module_'.$id_fatture]['id_segment'];
$idtipodocumento = $dbo->selectOne('co_tipidocumento', ['id'], [
'predefined' => 1,
'dir' => 'entrata',
@ -85,7 +84,6 @@ switch (post('op')) {
$dir = 'entrata';
$tipo_documento = Tipo::where('id', post('idtipodocumento'))->first();
$id_iva = setting('Iva predefinita');
$id_conto = setting('Conto predefinito fatture di vendita');
$accodare = post('accodare');
@ -96,9 +94,11 @@ switch (post('op')) {
// Lettura righe selezionate
foreach ($interventi as $intervento) {
$id_anagrafica = $intervento['idanagrafica'];
$id_documento = $id_documento_cliente[$id_anagrafica];
$anagrafica = Anagrafica::find($id_anagrafica);
$id_iva = $anagrafica->idiva_vendite ?: setting('Iva predefinita');
// Se non c'è già una fattura appena creata per questo cliente, creo una fattura nuova
if (empty($id_documento)) {
if (!empty($accodare)) {
@ -109,7 +109,6 @@ switch (post('op')) {
}
if (empty($id_documento)) {
$anagrafica = Anagrafica::find($id_anagrafica);
$fattura = Fattura::build($anagrafica, $tipo_documento, $data, $id_segment);
$id_documento = $fattura->id;

View File

@ -143,10 +143,6 @@ function aggiungi_intervento_in_fattura($id_intervento, $id_fattura, $descrizion
$fattura = Fattura::find($id_fattura);
$intervento = Intervento::find($id_intervento);
if (!empty($fattura->anagrafica->idiva_vendite)) {
$id_iva = $fattura->anagrafica->idiva_vendite;
}
$data = $intervento->fine;
$codice = $intervento->codice;

View File

@ -24,7 +24,8 @@ use Modules\Emails\Mail;
use Modules\Emails\Template;
use Modules\ListeNewsletter\Lista;
use Modules\Newsletter\Newsletter;
use Respect\Validation\Validator as v;
use Notifications\EmailNotification;
use PHPMailer\PHPMailer\Exception;
include_once __DIR__.'/../../core.php';
@ -61,42 +62,19 @@ switch (filter('op')) {
break;
case 'send':
$template = $newsletter->template;
$uploads = $newsletter->uploads()->pluck('id');
$newsletter = Newsletter::find($id_record);
$destinatari = $newsletter->destinatari();
$count = $destinatari->count();
for ($i = 0; $i < $count; ++$i) {
$destinatario = $destinatari->skip($i)->first();
$origine = $destinatario->getOrigine();
$anagrafica = $origine instanceof Anagrafica ? $origine : $origine->anagrafica;
$abilita_newsletter = $anagrafica->enable_newsletter;
$email = $destinatario->email;
if (empty($email) || empty($abilita_newsletter) || !v::email()->validate($email)) {
continue;
}
// Inizializzazione email
$mail = Mail::build($user, $template, $anagrafica->id);
// Completamento informazioni
$mail->addReceiver($email);
$mail->subject = $newsletter->subject;
$mail->content = $newsletter->content;
$mail->id_newsletter = $newsletter->id;
// Registrazione allegati
foreach ($uploads as $upload) {
$mail->addUpload($upload);
}
$mail->save();
$mail = $newsletter->inviaDestinatario($destinatario);
// Aggiornamento riferimento per la newsletter
$destinatario->id_email = $mail->id;
$destinatario->save();
if (!empty($mail)) {
$destinatario->id_email = $mail->id;
$destinatario->save();
}
}
// Aggiornamento stato newsletter
@ -107,6 +85,38 @@ switch (filter('op')) {
break;
case 'test':
$receiver_id = post('id');
$receiver_type = post('type');
// Individuazione destinatario interessato
$newsletter = Newsletter::find($id_record);
$destinatario = $newsletter->destinatari()
->where('record_type', '=', $receiver_type)
->where('record_id', '=', $receiver_id)
->first();
// Generazione email e tentativo di invio
$inviata = false;
if (!empty($destinatario)) {
$mail = $newsletter->inviaDestinatario($destinatario, true);
try {
$email = EmailNotification::build($mail, true);
$email->send();
$inviata = true;
} catch (Exception $e) {
// $mail->delete();
}
}
echo json_encode([
'result' => $inviata,
]);
break;
case 'block':
$mails = $newsletter->emails;

View File

@ -114,12 +114,15 @@ foreach ($destinatari_filtrati as $destinatario) {
$riga = array_merge($riga, [
'<div class="text-center">'.
(!empty($anagrafica->enable_newsletter) ?
(!empty($origine->enable_newsletter) ?
'<span class="text-success"><i class="fa fa-check"></i> '.tr('Abilitato').'</span>' :
'<span class="text-warning"><i class="fa fa-exclamation-triangle"></i> '.tr('Disabilitato').'</span>'
).'
</div>',
'<div class="text-center">
'<div class="text-center">'.(empty($lista) && !empty($origine->email) && !empty($origine->enable_newsletter) ? '
<a class="btn btn-warning btn-xs" data-type="'.get_class($origine).'" data-id="'.$origine->id.'" data-email="'.$origine->email.'" onclick="testInvio(this)">
<i class="fa fa-paper-plane "></i>
</a>' : '').'
<a class="btn btn-danger ask btn-xs" data-backto="record-edit" data-op="remove_receiver" data-type="'.get_class($origine).'" data-id="'.$origine->id.'">
<i class="fa fa-trash"></i>
</a>

View File

@ -240,4 +240,47 @@ $(document).ready(function() {
}
});
});
function testInvio(button) {
const destinatario_id = $(button).data("id");
const destinatario_type = $(button).data("type");
const email = $(button).data("email");
swal({
title: "'.tr('Invio di test?').'",
html: `'.tr("Vuoi effettuare un invio di test all'indirizzo _EMAIL_?", ['_EMAIL_' => '${email}']).' '.tr("L'email non sarà registrata come inviata, e l'invio della newsletter non escluderà questo indirizzo").'.`,
type: "warning",
showCancelButton: true,
confirmButtonText: "'.tr('Invia').'",
confirmButtonClass: "btn btn-lg btn-success",
}).then(function() {
const restore = buttonLoading(button);
$.ajax({
url: globals.rootdir + "/actions.php",
type: "POST",
dataType: "JSON",
data: {
id_module: globals.id_module,
id_record: globals.id_record,
op: "test",
id: destinatario_id,
type: destinatario_type,
},
success: function (response) {
buttonRestore(button, restore);
if (response.result) {
swal("'.tr('Invio completato').'", "", "success");
} else {
swal("'.tr('Invio fallito').'", "", "error");
}
},
error: function() {
buttonRestore(button, restore);
swal("'.tr('Errore').'", "'.tr("Errore durante l'invio dell'email").'", "error");
}
});
});
}
</script>';

View File

@ -29,6 +29,7 @@ use Modules\Anagrafiche\Sede;
use Modules\Emails\Account;
use Modules\Emails\Mail;
use Modules\Emails\Template;
use Respect\Validation\Validator as v;
use Traits\RecordTrait;
class Newsletter extends Model
@ -132,6 +133,45 @@ class Newsletter extends Model
->where('record_type', '=', $tipo);
}
/**
* Metodo per inviare l'email della newsletter a uno specifico destinatario.
*
* @return Mail|null
*/
public function inviaDestinatario(Destinatario $destinatario, $test = false)
{
$template = $this->template;
$uploads = $this->uploads()->pluck('id');
$origine = $destinatario->getOrigine();
$anagrafica = $origine instanceof Anagrafica ? $origine : $origine->anagrafica;
$abilita_newsletter = $origine->enable_newsletter;
$email = $destinatario->email;
if (empty($email) || empty($abilita_newsletter) || !v::email()->validate($email)) {
return null;
}
// Inizializzazione email
$mail = Mail::build(auth()->getUser(), $template, $anagrafica->id);
// Completamento informazioni
$mail->addReceiver($email);
$mail->subject = ($test ? '[Test] ' : '').$this->subject;
$mail->content = $this->content;
$mail->id_newsletter = $this->id;
// Registrazione allegati
foreach ($uploads as $upload) {
$mail->addUpload($upload);
}
$mail->save();
return $mail;
}
// Relazione Eloquent
public function destinatari()

View File

@ -35,14 +35,17 @@ if (!isset($_SESSION['module_'.$id_fatture]['id_segment'])) {
}
$id_segment = $_SESSION['module_'.$id_fatture]['id_segment'];
$idconto = setting('Conto predefinito fatture di vendita');
$idtipodocumento = $dbo->selectOne('co_tipidocumento', ['id'], [
'predefined' => 1,
'dir' => 'entrata',
])['id'];
switch (post('op')) {
case 'crea_fattura':
$documenti = collect();
$numero_totale = 0;
$descrizione_tipo = 'Fattura immediata di vendita';
$tipo_documento = Tipo::where('descrizione', $descrizione_tipo)->first();
$tipo_documento = Tipo::where('id', post('idtipodocumento'))->first();
$stato_documenti_accodabili = Stato::where('descrizione', 'Bozza')->first();
$accodare = post('accodare');
@ -145,7 +148,8 @@ if ($module['name'] == 'Ordini cliente') {
'data' => [
'title' => tr('Fatturare i _TYPE_ selezionati?', ['_TYPE_' => strtolower($module['name'])]),
'msg' => '{[ "type": "checkbox", "label": "<small>'.tr('Aggiungere alle _TYPE_ non ancora emesse?', ['_TYPE_' => strtolower($module_fatture)]).'", "placeholder": "'.tr('Aggiungere alle _TYPE_ nello stato bozza?', ['_TYPE_' => strtolower($module_fatture)]).'</small>", "name": "accodare" ]}
<br>{[ "type": "select", "label": "'.tr('Sezionale').'", "name": "id_segment", "required": 1, "values": "query=SELECT id, name AS descrizione FROM zz_segments WHERE id_module=\''.$id_fatture.'\' AND is_fiscale = 1 ORDER BY name", "value": "'.$id_segment.'" ]}',
<br>{[ "type": "select", "label": "'.tr('Sezionale').'", "name": "id_segment", "required": 1, "values": "query=SELECT id, name AS descrizione FROM zz_segments WHERE id_module=\''.$id_fatture.'\' AND is_fiscale = 1 ORDER BY name", "value": "'.$id_segment.'" ]}<br>
{[ "type": "select", "label": "'.tr('Tipo documento').'", "name": "idtipodocumento", "required": 1, "values": "query=SELECT id, CONCAT(codice_tipo_documento_fe, \' - \', descrizione) AS descrizione FROM co_tipidocumento WHERE enabled = 1 AND dir =\'entrata\' ORDER BY codice_tipo_documento_fe", "value": "'.$idtipodocumento.'" ]}',
'button' => tr('Procedi'),
'class' => 'btn btn-lg btn-warning',
'blank' => false,

View File

@ -34,6 +34,10 @@ if (!isset($_SESSION['module_'.$id_fatture]['id_segment'])) {
$_SESSION['module_'.$id_fatture]['id_segment'] = isset($segments[0]['id']) ? $segments[0]['id'] : null;
}
$id_segment = $_SESSION['module_'.$id_fatture]['id_segment'];
$idtipodocumento = $dbo->selectOne('co_tipidocumento', ['id'], [
'predefined' => 1,
'dir' => 'entrata',
])['id'];
switch (post('op')) {
case 'crea_fattura':
@ -41,8 +45,7 @@ switch (post('op')) {
$numero_totale = 0;
// Informazioni della fattura
$descrizione_tipo = 'Fattura immediata di vendita';
$tipo_documento = Tipo::where('descrizione', $descrizione_tipo)->first();
$tipo_documento = Tipo::where('id', post('idtipodocumento'))->first();
$stato_documenti_accodabili = Stato::where('descrizione', 'Bozza')->first();
$accodare = post('accodare');
@ -123,7 +126,8 @@ $operations['crea_fattura'] = [
'text' => '<span><i class="fa fa-file-code-o"></i> '.tr('Fattura _TYPE_', ['_TYPE_' => strtolower($module['name'])]),
'data' => [
'title' => tr('Fatturare i _TYPE_ selezionati?', ['_TYPE_' => strtolower($module['name'])]),
'msg' => '{[ "type": "checkbox", "label": "<small>'.tr('Aggiungere alle fatture di vendita non ancora emesse?').'</small>", "placeholder": "'.tr('Aggiungere alle fatture di vendita nello stato bozza?').'", "name": "accodare" ]}<br>{[ "type": "select", "label": "'.tr('Sezionale').'", "name": "id_segment", "required": 1, "values": "query=SELECT id, name AS descrizione FROM zz_segments WHERE id_module=\''.$id_fatture.'\' AND is_fiscale = 1 ORDER BY name", "value": "'.$id_segment.'" ]}',
'msg' => '{[ "type": "checkbox", "label": "<small>'.tr('Aggiungere alle fatture di vendita non ancora emesse?').'</small>", "placeholder": "'.tr('Aggiungere alle fatture di vendita nello stato bozza?').'", "name": "accodare" ]}<br>{[ "type": "select", "label": "'.tr('Sezionale').'", "name": "id_segment", "required": 1, "values": "query=SELECT id, name AS descrizione FROM zz_segments WHERE id_module=\''.$id_fatture.'\' AND is_fiscale = 1 ORDER BY name", "value": "'.$id_segment.'" ]}<br>
{[ "type": "select", "label": "'.tr('Tipo documento').'", "name": "idtipodocumento", "required": 1, "values": "query=SELECT id, CONCAT(codice_tipo_documento_fe, \' - \', descrizione) AS descrizione FROM co_tipidocumento WHERE enabled = 1 AND dir =\'entrata\' ORDER BY codice_tipo_documento_fe", "value": "'.$idtipodocumento.'" ]}',
'button' => tr('Procedi'),
'class' => 'btn btn-lg btn-warning',
'blank' => false,

View File

@ -43,6 +43,18 @@ class Preventivo extends Document
protected $table = 'co_preventivi';
/**
* The attributes that should be mutated to dates.
*
* @var array
*/
protected $dates = [
'data_bozza',
'data_conclusione',
'data_accettazione',
'data_rifiuto',
];
/**
* Crea un nuovo preventivo.
*

View File

@ -17,6 +17,10 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
use Models\Module;
use Models\OAuth2;
use Modules\Emails\Account;
include_once __DIR__.'/../../core.php';
switch (filter('op')) {
@ -34,6 +38,8 @@ switch (filter('op')) {
break;
case 'update':
$account = Account::find($id_record);
$predefined = post('predefined');
if (!empty($predefined)) {
$dbo->query('UPDATE em_accounts SET predefined = 0');
@ -55,26 +61,38 @@ switch (filter('op')) {
'timeout' => post('timeout'),
'ssl_no_verify' => post('ssl_no_verify'),
'predefined' => $predefined,
// OAuth2
'provider' => post('provider'),
'client_id' => post('client_id'),
'client_secret' => post('client_secret'),
'oauth2_config' => json_encode(post('config')),
], ['id' => $id_record]);
flash()->info(tr('Informazioni salvate correttamente!'));
// Rimozione informazioni OAuth2 in caso di disabilitazione
if (!$abilita_oauth2) {
$dbo->update('em_accounts', [
'provider' => null,
'client_id' => null,
'client_secret' => null,
'access_token' => null,
'refresh_token' => null,
'oauth2_config' => null,
], ['id' => $id_record]);
$oauth2 = $account->oauth2;
if (!empty($oauth2)) {
$account->oauth2()->dissociate();
$account->save();
$oauth2->delete();
}
}
// Aggiornamento delle informazioni per OAuth2
else {
$oauth2 = $account->oauth2 ?: OAuth2::build();
$oauth2->class = post('provider');
$oauth2->client_id = post('client_id');
$oauth2->client_secret = post('client_secret');
$oauth2->config = post('config');
// Link di redirect dopo la configurazione
$modulo_account_email = Module::pool('Account email');
$oauth2->after_configuration = base_path().'/editor.php?id_module='.$modulo_account_email->id.'&id_record='.$id_record;
$oauth2->save();
// Associazione Account-OAuth2
$account->oauth2()->associate($oauth2);
$account->save();
}
// Validazione indirizzo email mittente
@ -119,7 +137,9 @@ switch (filter('op')) {
break;
case 'oauth2':
$redirect = base_path().'/oauth2.php?id_account='.$account->id;
$oauth2 = $account->oauth2;
$redirect = base_path().'/oauth2.php?id='.$oauth2->id;
redirect($redirect);
break;

View File

@ -17,7 +17,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
use Modules\Emails\OAuth2;
use Modules\Emails\Account;
include_once __DIR__.'/../../core.php';
@ -101,16 +101,18 @@ echo '
</div>';
// Elenco provider disponibili
$providers = OAuth2::$providers;
$providers = Account::$providers;
$elenco_provider = [];
foreach ($providers as $key => $provider) {
$elenco_provider[] = [
'id' => $key,
'id' => $provider['class'],
'short' => $key,
'text' => $provider['name'],
'help' => $provider['help'],
];
}
$oauth2 = $account->oauth2;
echo '
<!-- OAuth2 -->
<div class="box box-info">
@ -122,32 +124,32 @@ echo '
<div class="row">
<div class="col-md-6">
<span class="label label-warning pull-right hidden" id="guida-configurazione"></span>
{[ "type": "select", "label": "'.tr('Provider account').'", "name": "provider", "value": "$provider$", "values": '.json_encode($elenco_provider).', "disabled": "'.intval(empty($account->provider)).'" ]}
{[ "type": "select", "label": "'.tr('Provider account').'", "name": "provider", "value": '.json_encode($oauth2->class).', "values": '.json_encode($elenco_provider).', "disabled": "'.intval(empty($oauth2)).'" ]}
</div>
<div class="col-md-3">
{[ "type": "checkbox", "label": "'.tr('Abilita OAuth2').'", "name": "abilita_oauth2", "value": "'.intval(!empty($account->provider)).'" ]}
{[ "type": "checkbox", "label": "'.tr('Abilita OAuth2').'", "name": "abilita_oauth2", "value": "'.intval(!empty($oauth2)).'" ]}
</div>
<div id="oauth2-config">
<div class="col-md-3">
<a type="button" class="btn btn-success btn-block '.(empty($account->provider) || empty($account->client_id) || empty($account->client_secret) ? 'disabled' : '').'" style="margin-top: 25px" href="'.base_url().'/editor.php?id_module='.$id_module.'&id_record='.$id_record.'&op=oauth2">
<i class="fa fa-refresh"></i> '.(empty($account->access_token) ? tr('Completa configurazione') : tr('Ripeti configurazione')).'
</a>
</div>
<div class="col-md-6">
{[ "type": "text", "label": "'.tr('Client ID').'", "name": "client_id", "value": "$client_id$", "disabled": "'.intval(empty($account->provider)).'" ]}
</div>
<div class="col-md-6">
{[ "type": "text", "label": "'.tr('Client Secret').'", "name": "client_secret", "value": "$client_secret$", "disabled": "'.intval(empty($account->provider)).'" ]}
</div>
<div id="provider-config"></div>
<div class="col-md-3 oauth2-config">
<a type="button" class="btn btn-success btn-block '.(empty($oauth2->class) || empty($oauth2->client_id) || empty($oauth2->client_secret) ? 'disabled' : '').'" style="margin-top: 25px" href="'.base_url().'/editor.php?id_module='.$id_module.'&id_record='.$id_record.'&op=oauth2">
<i class="fa fa-refresh"></i> '.(empty($oauth2->access_token) ? tr('Completa configurazione') : tr('Ripeti configurazione')).'
</a>
</div>
</div>
<div class="row oauth2-config">
<div class="col-md-6">
{[ "type": "text", "label": "'.tr('Client ID').'", "name": "client_id", "value": "'.$oauth2->client_id.'", "disabled": "'.intval(empty($oauth2)).'" ]}
</div>
<div class="col-md-6">
{[ "type": "text", "label": "'.tr('Client Secret').'", "name": "client_secret", "value": "'.$oauth2->client_secret.'", "disabled": "'.intval(empty($oauth2)).'" ]}
</div>
<div id="provider-config"></div>
</div>
<div class="alert alert-info">
<i class="fa fa-info-circle"></i> '.tr('Durante la procedura di configurazione verrà effettuato il logout dal gestionale').'.
</div>
@ -163,7 +165,7 @@ foreach ($providers as $key => $provider) {
$config = $provider['class']::getConfigInputs();
foreach ($config as $name => $field) {
$field['name'] = 'config['.$name.']';
$field['value'] = $account->oauth2_config[$name];
$field['value'] = $oauth2 ? $oauth2->config[$name] : null;
echo '
<div class="col-md-6">'.input($field).'</div>';
@ -186,7 +188,7 @@ abilita_oauth2.change(function() {
const disable = !abilita_oauth2.get();
provider.setDisabled(disable);
const inputs = $("#oauth2-config .openstamanager-input");
const inputs = $(".oauth2-config .openstamanager-input");
for (i of inputs) {
input(i).setDisabled(disable);
}
@ -204,7 +206,7 @@ provider.change(function() {
// Impostazione dei dati aggiuntivi da configurare
config.html("")
aggiungiContenuto(config, "#provider-" + data.id);
aggiungiContenuto(config, "#provider-" + data.short);
})
$(document).ready(function() {

View File

@ -19,8 +19,6 @@
use API\Services;
use Carbon\Carbon;
use Models\Cache;
use Modules\StatoServizi\ServicesHook;
include_once __DIR__.'/../../core.php';
@ -28,12 +26,6 @@ include_once __DIR__.'/../../core.php';
echo '
<div class="row">';
/**
* Contenuto aggiornato e gestito dall'Hook ServicesHook.
*
* @var array
*/
$response = Cache::pool('Informazioni su Services')->content;
$limite_scadenze = (new Carbon())->addDays(60);
if (Services::isEnabled()) {
echo '
@ -48,7 +40,7 @@ if (Services::isEnabled()) {
<div class="box-body">';
$servizi = collect($response['servizi'])->flatten(1);
$servizi = Services::getServiziAttivi()->flatten(1);
if (!$servizi->isEmpty()) {
echo '
<table class="table table-striped table-hover">
@ -99,8 +91,9 @@ if (Services::isEnabled()) {
<div class="box-body">';
// Elaborazione delle risorse API in scadenza
if (!empty($response['risorse-api'])) {
$risorse_in_scadenza = ServicesHook::getRisorseInScadenza($response['risorse-api'], $limite_scadenze);
$risorse_attive = Services::getRisorseAttive();
if (!$risorse_attive->isEmpty()) {
$risorse_in_scadenza = Services::getRisorseInScadenza($limite_scadenze);
if (!$risorse_in_scadenza->isEmpty()) {
echo '
<p>'.tr('Le seguenti risorse sono in scadenza:').'</p>
@ -115,11 +108,12 @@ if (Services::isEnabled()) {
<tbody>';
foreach ($risorse_in_scadenza as $servizio) {
$scadenza = Carbon::parse($servizio['data_scadenza']);
$scadenza = Carbon::parse($servizio['expiration_at']);
echo '
<tr>
<td>'.$servizio['nome'].'</td>
<td>'.$servizio['crediti'].'</td>
<td>'.$servizio['name'].'</td>
<td>'.$servizio['credits'].'</td>
<td>'.dateFormat($scadenza).' ('.$scadenza->diffForHumans().')</td>
</tr>';
}

View File

@ -21,29 +21,15 @@ namespace Modules\StatoServizi;
use API\Services;
use Carbon\Carbon;
use Hooks\CachedManager;
use Hooks\Manager;
class ServicesHook extends CachedManager
class ServicesHook extends Manager
{
public function getCacheName()
{
return 'Informazioni su Services';
}
public function cacheData()
{
$response = Services::request('GET', 'info');
return Services::responseBody($response);
}
public function response()
{
$servizi = $this->getCache()->content;
$limite_scadenze = (new Carbon())->addDays(60);
// Elaborazione dei servizi in scadenza
$risorse_in_scadenza = self::getRisorseInScadenza($servizi['risorse-api'], $limite_scadenze);
$limite_scadenze = (new Carbon())->addDays(60);
$risorse_in_scadenza = Services::getRisorseInScadenza($limite_scadenze);
$message = tr('I seguenti servizi sono in scadenza: _LIST_', [
'_LIST_' => implode(', ', $risorse_in_scadenza->pluck('nome')->all()),
@ -56,26 +42,13 @@ class ServicesHook extends CachedManager
];
}
/**
* Restituisce l'elenco delle risorse API in scadenza, causa data oppure crediti.
*
* @param $servizi
*/
public static function getRisorseInScadenza($risorse, $limite_scadenze)
public function execute()
{
// Elaborazione dei servizi in scadenza
$risorse_in_scadenza = collect($risorse)
->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);
});
return false;
}
return $risorse_in_scadenza->transform(function ($item, $key) {
return [
'nome' => $item['name'],
'data_scadenza' => $item['expiration_at'],
'crediti' => $item['credits'],
];
});
public function needsExecution()
{
return false;
}
}

View File

@ -1,8 +1,23 @@
<?php
/*
* OpenSTAManager: il software gestionale open source per l'assistenza tecnica e la fatturazione
* Copyright (C) DevCode s.r.l.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
use Models\Module;
use Modules\Emails\Account;
use Modules\Emails\OAuth2;
use Models\OAuth2;
$skip_permissions = true;
include_once __DIR__.'/core.php';
@ -12,12 +27,12 @@ session_write_close();
$state = $_GET['state'];
$code = $_GET['code'];
// Account individuato via oauth2_state
// Account individuato via state
if (!empty($state)) {
$account = Account::where('oauth2_state', '=', $state)
$account = OAuth2::where('state', '=', $state)
->first();
} else {
$account = Account::find(get('id_account'));
$account = OAuth2::find(get('id'));
// Impostazione access token a null per reimpostare la configurazione
$account->access_token = null;
@ -31,16 +46,12 @@ if (empty($account)) {
return;
}
// Inizializzazione
$oauth2 = new OAuth2($account);
// Redirect all'URL di autorizzazione del servizio esterno
$redirect = $oauth2->configure($code, $state);
$redirect = $account->configure($code, $state);
// Redirect automatico al record
if (empty($redirect)) {
$modulo_account_email = Module::pool('Account email');
$redirect = base_path().'/editor.php?id_module='.$modulo_account_email->id.'&id_record='.$account->id;
$redirect = $account->after_configuration;
}
if (empty($_GET['error'])) {

View File

@ -30,6 +30,11 @@ use UnexpectedValueException;
*/
class Interaction extends Services
{
public static function isEnabled()
{
return parent::isEnabled() && self::verificaRisorsaAttiva('Fatturazione Elettronica');
}
public static function sendInvoice($id_record)
{
try {

View File

@ -160,6 +160,9 @@ class FatturaOrdinaria extends FatturaElettronica
}
if (!empty($articolo)) {
$articolo->idconto_acquisto = $conto[$key];
$articolo->save();
$obj = Articolo::build($fattura, $articolo);
$obj->movimentazione($movimentazione);

View File

@ -29,6 +29,11 @@ use Models\Cache;
*/
class Interaction extends Services
{
public static function isEnabled()
{
return parent::isEnabled() && self::verificaRisorsaAttiva('Fatturazione Elettronica');
}
public static function getInvoiceList()
{
$list = self::getRemoteList();

View File

@ -29,6 +29,11 @@ use Models\Cache;
*/
class Interaction extends Services
{
public static function isEnabled()
{
return parent::isEnabled() && self::verificaRisorsaAttiva('Fatturazione Elettronica');
}
public static function getReceiptList()
{
$list = self::getRemoteList();

View File

@ -46,6 +46,8 @@ switch ($operazione) {
break;
case 'updatereferente':
$opt_out_newsletter = post('disable_newsletter');
$dbo->update('an_referenti', [
'idanagrafica' => $id_parent,
'nome' => post('nome'),
@ -53,6 +55,8 @@ switch ($operazione) {
'telefono' => post('telefono'),
'email' => post('email'),
'idsede' => post('idsede'),
'enable_newsletter' => empty($opt_out_newsletter),
], ['id' => $id_record]);
flash()->info(tr('Salvataggio completato!'));

View File

@ -49,9 +49,13 @@ echo '
</div>
<div class="row">
<div class="col-md-12">
<div class="col-md-6">
{[ "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": "0", "required": 1 ]}
</div>
<div class="col-md-6">
{[ "type": "checkbox", "label": "'.tr('Opt-out per newsletter').'", "name": "disable_newsletter", "id": "disable_newsletter_m", "value": "0" ]}
</div>
</div>
<!-- PULSANTI -->

View File

@ -48,9 +48,13 @@ echo '
</div>
<div class="row">
<div class="col-md-12">
<div class="col-md-6">
{[ "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 ]}
</div>
<div class="col-md-6">
{[ "type": "checkbox", "label": "'.tr('Opt-out per newsletter').'", "name": "disable_newsletter", "id": "disable_newsletter_m", "value": "'.empty($record['enable_newsletter']).'" ]}
</div>
</div>
<!-- PULSANTI -->

View File

@ -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!'));

View File

@ -95,10 +95,15 @@ echo '
{[ "type": "text", "label": "'.tr('Indirizzo email').'", "name": "email", "value": "$email$" ]}
</div>
<div class="col-md-6">
<div class="col-md-3">
{[ "type": "checkbox", "label": "'.tr('Opt-out per newsletter').'", "name": "disable_newsletter", "id": "disable_newsletter_m", "value": "'.empty($record['enable_newsletter']).'" ]}
</div>
<div class="col-md-3">
{[ "type": "select", "label": "'.tr('Zona').'", "name": "idzona", "ajax-source": "zone", "value": "$idzona$", "placeholder": "'.tr('Nessuna zona').'", "icon-after": "add|'.Modules::get('Zone')['id'].'" ]}
</div>
</div>
<div class="row">
<div class="col-md-12">
{[ "type": "textarea", "label": "'.tr('Note').'", "name": "note", "value": "$note$" ]}

View File

@ -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'])) {

View File

@ -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();

212
src/Models/OAuth2.php Normal file
View File

@ -0,0 +1,212 @@
<?php
/*
* OpenSTAManager: il software gestionale open source per l'assistenza tecnica e la fatturazione
* Copyright (C) DevCode s.r.l.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
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);
}
}
}

View File

@ -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,
])
);

View File

@ -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,
]);

View File

@ -134,7 +134,7 @@ echo '
<tr>
<td>'.$liv3_p['numero'].'</td>
<td>'.$liv3_p['descrizione'].'</td>
<td class="text-right">'.numberFormat(abs($liv3_p['totale'])).'</td>
<td class="text-right">'.numberFormat(-$liv3_p['totale']).'</td>
</tr>';
}
}

View File

@ -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

12
update/2_4_26.php Normal file
View File

@ -0,0 +1,12 @@
<?php
use Models\Module;
$modulo_account_email = Module::pool('Account email');
$redirect = base_path().'/editor.php?id_module='.$modulo_account_email->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`)');

View File

@ -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;

View File

@ -99,6 +99,7 @@ return [
'mg_valori_attributi',
'my_impianto_componenti',
'my_componenti',
'zz_oauth2',
'or_ordini',
'or_righe_ordini',
'or_statiordine',