diff --git a/api/index.php b/api/index.php index 815e7d4b3..5bf3dc75a 100644 --- a/api/index.php +++ b/api/index.php @@ -5,7 +5,7 @@ function serverError() $error = error_get_last(); if ($error['type'] == E_ERROR) { ob_end_clean(); - echo API::error('serverError'); + echo Response::error('serverError'); } } @@ -22,61 +22,21 @@ session_write_close(); header('Access-Control-Allow-Origin: *'); header('Access-Control-Allow-Methods: POST, GET, PUT, DELETE, OPTIONS'); -// Attenzione: al momento l'API permette la lettura di tutte le tabelle presenti nel database (non limitate a quelle del progetto) +use API\Response; try { - // Controlli sulla chiave di accesso - $api = new API(); - - // Lettura delle informazioni - $request = API::getRequest(); - - // Gestione della richiesta - $method = $_SERVER['REQUEST_METHOD']; - switch ($method) { - // Richiesta PUT (modifica elementi) - case 'PUT': - $response = $api->update($request); - break; - - // Richiesta POST (creazione elementi) - case 'POST': - $response = $api->create($request); - break; - - // Richiesta GET (ottenimento elementi) - case 'GET': - // Risorsa specificata - if (count($request) > 1) { - $response = $api->retrieve($request); - } - - // Risorsa non specificata (lista delle risorse disponibili) - else { - $response = API::response([ - 'resources' => array_keys(API::getResources()['retrieve']), - ]); - } - break; - - // Richiesta DELETE (eliminazione elementi) - case 'DELETE': - $response = $api->delete($request); - break; - } -} catch (InvalidArgumentException $e) { - $response = API::error('unauthorized'); + $response = Response::manage(); } catch (Exception $e) { // Log dell'errore $logger = logger(); $logger->addRecord(\Monolog\Logger::ERROR, $e); - $response = API::error('serverError'); + $response = Response::error('serverError'); } // Richiesta OPTIONS (controllo da parte del dispositivo) if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS') { - $response = API::error('ok'); + $response = Response::error('ok'); } json_decode($response); diff --git a/config/namespaces.php b/config/namespaces.php index 974ca80a2..e00ca261e 100644 --- a/config/namespaces.php +++ b/config/namespaces.php @@ -17,6 +17,12 @@ return [ 'modules/interventi' => 'Modules\Interventi', 'modules/pagamenti' => 'Modules\Pagamenti', 'modules/statistiche' => 'Modules\Statistiche', + 'modules/utenti' => 'Modules\Utenti', + 'modules/stato_servizi' => 'Modules\StatoServizi', + 'modules/stati_intervento' => 'Modules\StatiIntervento', + 'modules/stati_preventivo' => 'Modules\StatiPreventivo', + 'modules/stati_contratto' => 'Modules\StatiContratto', + 'modules/stato_servizi' => 'Modules\StatoServizi', 'modules/tipi_intervento' => 'Modules\TipiIntervento', 'plugins/exportFE' => 'Plugins\ExportFE', 'plugins/importFE' => 'Plugins\ImportFE', diff --git a/core.php b/core.php index cab7c3788..19d36896b 100644 --- a/core.php +++ b/core.php @@ -28,6 +28,9 @@ $namespaces = require_once __DIR__.'/config/namespaces.php'; foreach ($namespaces as $path => $namespace) { $loader->addPsr4($namespace.'\\', __DIR__.'/'.$path.'/custom/src'); $loader->addPsr4($namespace.'\\', __DIR__.'/'.$path.'/src'); + + $loader->addPsr4($namespace.'\\API\\', __DIR__.'/'.$path.'/custom/api'); + $loader->addPsr4($namespace.'\\API\\', __DIR__.'/'.$path.'/api'); } // Individuazione dei percorsi di base @@ -60,7 +63,7 @@ use Monolog\Handler\RotatingFileHandler; use Monolog\Handler\StreamHandler; $handlers = []; -if (!API::isAPIRequest()) { +if (!API\Response::isAPIRequest()) { // File di log di base (logs/error.log, logs/setup.log) $handlers[] = new StreamHandler($docroot.'/logs/error.log', Monolog\Logger::ERROR); $handlers[] = new StreamHandler($docroot.'/logs/setup.log', Monolog\Logger::EMERGENCY); @@ -124,7 +127,7 @@ Monolog\ErrorHandler::register($logger, [], Monolog\Logger::ERROR, Monolog\Logge $dbo = $database = database(); /* SESSIONE */ -if (!API::isAPIRequest()) { +if (!API\Response::isAPIRequest()) { // Sicurezza della sessioni ini_set('session.use_trans_sid', '0'); ini_set('session.use_only_cookies', '1'); @@ -162,7 +165,7 @@ $revision = Update::getRevision(); /* ACCESSO E INSTALLAZIONE */ // Controllo sulla presenza dei permessi di accesso basilari -$continue = $dbo->isInstalled() && !Update::isUpdateAvailable() && (Auth::check() || API::isAPIRequest()); +$continue = $dbo->isInstalled() && !Update::isUpdateAvailable() && (Auth::check() || API\Response::isAPIRequest()); if (!empty($skip_permissions)) { Permissions::skip(); @@ -179,7 +182,7 @@ if (!$continue && getURLPath() != slashes(ROOTDIR.'/index.php') && !Permissions: /* INIZIALIZZAZIONE GENERALE */ // Operazione aggiuntive (richieste non API) -if (!API::isAPIRequest()) { +if (!API\Response::isAPIRequest()) { // Impostazioni di Content-Type e Charset Header header('Content-Type: text/html; charset=UTF-8'); @@ -278,3 +281,18 @@ $list = array_merge($files, $custom_files); foreach ($list as $file) { include_once $file; } + +// Inclusione dei file vendor/autoload.php di Composer +$files = glob(__DIR__.'/{modules,plugins}/*/vendor/autoload.php', GLOB_BRACE); +$custom_files = glob(__DIR__.'/{modules,plugins}/*/custom/vendor/autoload.php', GLOB_BRACE); +foreach ($custom_files as $key => $value) { + $index = array_search(str_replace('custom/', '', $value), $files); + if ($index !== false) { + unset($files[$index]); + } +} + +$list = array_merge($files, $custom_files); +foreach ($list as $file) { + include_once $file; +} diff --git a/editor.php b/editor.php index 728d19f01..e9ab6eeba 100755 --- a/editor.php +++ b/editor.php @@ -13,7 +13,7 @@ if (empty($id_record) && !empty($id_module)) { include_once App::filepath('include|custom|', 'top.php'); Util\Query::setSegments(false); -$query = Util\Query::getQuery($module, [ +$query = Util\Query::getQuery($structure, [ 'id' => $id_record, ]); Util\Query::setSegments(true); diff --git a/gulpfile.js b/gulpfile.js index 93d343d8b..9e0b33b72 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -198,6 +198,15 @@ gulp.task('colorpicker', function () { .pipe(gulp.dest(config.production + '/' + config.paths.images + '/bootstrap-colorpicker')); }); +gulp.task('password-strength', function () { + gulp.src([ + config.main.bowerDirectory + '/pwstrength-bootstrap/dist/*.js', + ]) + .pipe(concat('password.min.js')) + .pipe(minifyJS()) + .pipe(gulp.dest(config.production + '/password-strength')); +}); + gulp.task('chartjs', function () { gulp.src([ config.main.bowerDirectory + '/chart.js/dist/Chart.min.js', @@ -368,6 +377,7 @@ gulp.task('bower', ['clean'], function () { gulp.task('other', ['clean'], function () { gulp.start('ckeditor'); gulp.start('colorpicker'); + gulp.start('password-strength'); gulp.start('i18n'); gulp.start('pdfjs'); diff --git a/include/common/form.php b/include/common/form.php index 51c9f27b3..5e54e1458 100644 --- a/include/common/form.php +++ b/include/common/form.php @@ -30,4 +30,4 @@ echo ' '; echo ' - '; +'; diff --git a/include/common/importa.php b/include/common/importa.php index 00dbc3f30..042e02a7c 100644 --- a/include/common/importa.php +++ b/include/common/importa.php @@ -1,197 +1,270 @@ fetchOne('SELECT * FROM '.$table.' WHERE id = '.prepare($id_record)); -$numero = !empty($documento['numero_esterno']) ? $documento['numero_esterno'] : $documento['numero']; -$id_anagrafica = $documento['idanagrafica']; -$id_pagamento = $documento['idpagamento']; -$id_conto = $documento['idconto']; - -if (empty($documento)) { +// Inizializzazione +$documento = $options['documento']; +$documento_finale = $options['documento_finale']; +if (empty($documento) || (!empty($documento_finale) && $documento_finale->direzione != $documento->direzione)) { return; } +// Informazioi utili +$dir = $documento->direzione; +$original_module = Modules::get($documento->module); + +$name = !empty($documento_finale) ? $documento_finale->module : $options['module']; +$final_module = Modules::get($name); + +// IVA predefinta $id_iva = $id_iva ?: setting('Iva predefinita'); -if (empty($id_conto)) { - $id_conto = ($dir == 'entrata') ? setting('Conto predefinito fatture di vendita') : setting('Conto predefinito fatture di acquisto'); + +$righe = $documento->getRighe()->where('qta_rimanente', '>', 0); +if (empty($righe)) { + echo ' +

'.tr('Non ci sono elementi da evadere').'...

'; + + return; } -// Selezione articoli dell'ordine da portare nel ddt -$righe = $dbo->fetchArray('SELECT *, IFNULL((SELECT codice FROM mg_articoli WHERE id=idarticolo),"") AS codice, (qta - qta_evasa) AS qta_rimanente FROM '.$table.' INNER JOIN '.$rows.' ON '.$table.'.id='.$rows.'.'.$id_rows.' WHERE '.$table.'.id='.prepare($id_record).' HAVING qta_rimanente > 0 OR is_descrizione = 1 ORDER BY `order`'); +$link = !empty($documento_finale) ? ROOTDIR.'/editor.php?id_module='.$final_module['id'].'&id_record='.$documento_finale->id : ROOTDIR.'/controller.php?id_module='.$final_module['id']; -if (!empty($righe)) { - echo ' +echo ' -
- - - - - - - - + + - '; + + + '; - // Creazione fattura dal documento - if (!empty($options['create_document'])) { - echo ' -
- +// Creazione fattura dal documento +if (!empty($options['create_document'])) { + echo ' +
+
+

'.tr('Nuovo documento').'

+
+
+ +
+ + +
+ {[ "type": "date", "label": "'.tr('Data del documento').'", "name": "data", "required": 1, "value": "-now-" ]} +
'; -
- {[ "type": "date", "label": "'.tr('Data del documento').'", "name": "data", "required": 1, "value": "-now-" ]} -
'; + if (in_array($final_module['name'], ['Fatture di vendita', 'Fatture di acquisto'])) { + if ($op == 'nota_accredito' && !empty($segmenti)) { + $segmento = $dbo->fetchOne("SELECT * FROM zz_segments WHERE predefined_accredito='1'"); - if ($final_module['name'] == 'Fatture di vendita' || $final_module['name'] == 'Fatture di acquisto') { - if ($op == 'nota_accredito' && !empty($segmenti)) { - $segmento = $dbo->fetchOne("SELECT * FROM zz_segments WHERE predefined_accredito='1'"); - - $id_segment = $segmento['id']; - } else { - $id_segment = $_SESSION['module_'.$final_module['id']]['id_segment']; - } - - echo ' -
- {[ "type": "select", "label": "'.tr('Sezionale').'", "name": "id_segment", "required": 1, "values": "query=SELECT id, name AS descrizione FROM zz_segments WHERE id_module='.prepare($final_module['id']).' ORDER BY name", "value": "'.$id_segment.'" ]} -
'; + $id_segment = $segmento['id']; + } else { + $id_segment = $_SESSION['module_'.$final_module['id']]['id_segment']; } echo ' +
+ {[ "type": "select", "label": "'.tr('Ritenuta contributi').'", "name": "id_ritenuta_contributi", "value": "$id_ritenuta_contributi$", "values": "query=SELECT * FROM co_ritenuta_contributi" ]} +
+ +
+ {[ "type": "select", "label": "'.tr('Sezionale').'", "name": "id_segment", "required": 1, "values": "query=SELECT id, name AS descrizione FROM zz_segments WHERE id_module='.prepare($final_module['id']).' ORDER BY name", "value": "'.$id_segment.'" ]} +
'; + } + + echo ' +
+
'; +} + + // Conto, rivalsa INPS, ritenuta d'acconto e ritenuta contributi +if (in_array($final_module['name'], ['Fatture di vendita', 'Fatture di acquisto']) && !in_array($original_module['name'], ['Fatture di vendita', 'Fatture di acquisto'])) { + $id_rivalsa_inps = setting('Percentuale rivalsa'); + if ($dir == 'uscita') { + $id_ritenuta_acconto = $documento->anagrafica->id_ritenuta_acconto_acquisti; + } else { + $id_ritenuta_acconto = $documento->anagrafica->id_ritenuta_acconto_vendite ?: setting("Percentuale ritenuta d'acconto"); + } + $calcolo_ritenuta_acconto = setting("Metodologia calcolo ritenuta d'acconto predefinito"); + + $show_rivalsa = !empty($id_rivalsa_inps); + $show_ritenuta_acconto = setting("Percentuale ritenuta d'acconto") != '' || !empty($id_ritenuta_acconto); + $show_ritenuta_contributi = !empty($documento_finale['id_ritenuta_contributi']); + + $id_conto = $documento_finale['idconto']; + if (empty($id_conto)) { + $id_conto = ($dir == 'entrata') ? setting('Conto predefinito fatture di vendita') : setting('Conto predefinito fatture di acquisto'); + } + + echo ' +
+
+

'.tr('Opzioni generali delle righe').'

+
+
'; + + if ($show_rivalsa || $show_ritenuta_acconto) { + echo ' +
'; + + // Rivalsa INPS + if ($show_rivalsa) { + echo ' +
+ {[ "type": "select", "label": "'.tr('Rivalsa').'", "name": "id_rivalsa_inps", "value": "'.$id_rivalsa_inps.'", "values": "query=SELECT * FROM co_rivalse", "help": "'.(($options['dir'] == 'entrata') ? setting('Tipo Cassa Previdenziale') : null).'" ]} +
'; + } + + // Ritenuta d'acconto + if ($show_ritenuta_acconto) { + echo ' +
+ {[ "type": "select", "label": "'.tr("Ritenuta d'acconto").'", "name": "id_ritenuta_acconto", "value": "'.$id_ritenuta_acconto.'", "values": "query=SELECT * FROM co_ritenutaacconto" ]} +
'; + + // Calcola ritenuta d'acconto su + echo ' +
+ {[ "type": "select", "label": "'.tr("Calcola ritenuta d'acconto su").'", "name": "calcolo_ritenuta_acconto", "value": "'.$calcolo_ritenuta_acconto.'", "values": "list=\"IMP\":\"Imponibile\", \"IMP+RIV\":\"Imponibile + rivalsa\"", "required": "1" ]} +
'; + } + + echo ' +
'; + } + + $width = $show_ritenuta_contributi ? 6 : 12; + + echo ' +
'; + + // Ritenuta contributi + if ($show_ritenuta_contributi) { + echo ' +
+ {[ "type": "checkbox", "label": "'.tr('Ritenuta contributi').'", "name": "ritenuta_contributi", "value": "1" ]} +
'; } // Conto - if (($final_module['name'] == 'Fatture di vendita' || $final_module['name'] == 'Fatture di acquisto') && !($original_module['name'] == 'Fatture di vendita' || $original_module['name'] == 'Fatture di acquisto')) { - echo ' -
-
- {[ "type": "select", "label": "'.tr('Conto').'", "name": "id_conto", "required": 1, "value": "'.$id_conto.'", "ajax-source": "'.($dir == 'entrata' ? 'conti-vendite' : 'conti-acquisti').'" ]} + echo ' +
+ {[ "type": "select", "label": "'.tr('Conto').'", "name": "id_conto", "required": 1, "value": "'.$id_conto.'", "ajax-source": "'.($dir == 'entrata' ? 'conti-vendite' : 'conti-acquisti').'" ]} +
+
'; - } +} echo ' -
-
+
+
+

'.tr('Righe da importare').'

+
-

'.tr('Seleziona le righe e le relative quantità da inserire nel documento').'.

+ + + + + + '; -
'.tr('Descrizione').''.tr('Q.tà').''.tr('Q.tà da evadere').''.tr('Subtot.').'
- - - - - '; +if (!empty($options['serials'])) { + echo ' + '; +} +echo ' + '; + +foreach ($righe as $i => $r) { + // Descrizione + echo ' + + '; + + // Q.tà rimanente + echo ' + '; + + // Q.tà da evadere + echo ' + '; + + echo ' + '; + + // Seriali if (!empty($options['serials'])) { echo ' - '; - } + '; + if (!empty($r['abilita_serial'])) { + $serials = $r->serials; - foreach ($righe as $i => $r) { - // Descrizione - echo ' - - '; - - // Q.tà rimanente - echo ' - '; - - // Q.tà da evadere - echo ' - '; - - // Subtotale - $subtotale = $r['subtotale'] / $r['qta'] * ($r['qta'] - $r['qta_evasa']); - $sconto = $r['sconto'] / $r['qta'] * ($r['qta'] - $r['qta_evasa']); - $iva = $r['iva'] / $r['qta'] * ($r['qta'] - $r['qta_evasa']); - - echo ' - '; - - // Seriali - if (!empty($options['serials'])) { - echo ' - '; + if (empty($r['abilita_serial']) || empty($serials)) { + echo '-'; } echo ' - '; + '; } - // Totale echo ' - - - - -
'.tr('Descrizione').''.tr('Q.tà').''.tr('Q.tà da evadere').''.tr('Subtot.').''.tr('Seriali').'
+ + + + '; + + // Checkbox - da evadere? + echo ' + '; + + $descrizione = ($r->isArticolo() ? $r->articolo->codice.' - ' : '').$r['descrizione']; + + echo ' '.nl2br($descrizione); + + echo ' + + '.Translator::numberToLocale($r['qta_rimanente']).' + + {[ "type": "number", "name": "qta_da_evadere['.$r['id'].']", "id": "qta_'.$i.'", "required": 1, "value": "'.$r['qta_rimanente'].'", "decimals": "qta", "min-value": "0", "extra": "'.(($r['is_descrizione']) ? 'readonly' : '').' onkeyup=\"ricalcola_subtotale_riga('.$i.');\"" ]} + + '.moneyFormat($r->totale).'
+ + '.Translator::numberToLocale($r->totale_imponibile).' + '.Translator::numberToLocale($r->iva).' +
'.tr('Seriali').''; - echo ' -
'; - - // Checkbox - da evadere? - echo ' - '; - - $descrizione = (!empty($r['codice']) ? $r['codice'].' - ' : '').$r['descrizione']; - - echo ' '.nl2br($descrizione); - - echo ' - - -

'.Translator::numberToLocale($r['qta_rimanente']).'

-
- {[ "type": "number", "name": "qta_da_evadere['.$r['id'].']", "id": "qta_'.$i.'", "required": 1, "value": "'.$r['qta_rimanente'].'", "decimals": "qta", "min-value": "0", "extra": "'.(($r['is_descrizione']) ? 'readonly' : '').' onkeyup=\"ricalcola_subtotale_riga('.$i.');\"" ]} - - - - - - '.moneyFormat($subtotale - $sconto + $iva).'
- - '.Translator::numberToLocale($subtotale - $sconto).' + '.Translator::numberToLocale($iva).' -
'; - - if (!empty($r['abilita_serial'])) { - $query = 'SELECT DISTINCT serial AS id, serial AS descrizione FROM mg_prodotti WHERE dir='.prepare($dir).' AND '.$options['serials']['id_riga'].' = '.prepare($r['id']).' AND serial IS NOT NULL AND serial NOT IN (SELECT serial FROM mg_prodotti AS t WHERE serial IS NOT NULL AND dir='.prepare($dir).' AND '.$options['serials']['condition'].')'; - - $values = $dbo->fetchArray($query); - if (!empty($values)) { - echo ' - {[ "type": "select", "name": "serial['.$r['id'].'][]", "id": "serial_'.$i.'", "multiple": 1, "values": "query='.$query.'", "value": "'.implode(',', array_column($values, 'id')).'", "extra": "data-maximum=\"'.intval($r['qta_rimanente']).'\"" ]}'; - } + $list = []; + foreach ($serials as $serial) { + $list[] = [ + 'id' => $serial, + 'text' => $serial, + ]; } - if (empty($r['abilita_serial']) || empty($values)) { - echo '-'; + if (!empty($serials)) { + echo ' + {[ "type": "select", "name": "serial['.$r['id'].'][]", "id": "serial_'.$i.'", "multiple": 1, "values": '.json_encode($list).', "value": "'.implode(',', $serials).'", "extra": "data-maximum=\"'.intval($r['qta_rimanente']).'\"" ]}'; } + } - echo ' -
- '.tr('Totale').': - - -
'; + '; +} - echo ' +// Totale +echo ' + + + '.tr('Totale').': + + + + + + +
'; + +echo '
@@ -202,10 +275,6 @@ if (!empty($righe)) {
'; -} else { - echo ' -

'.tr('Non ci sono elementi da evadere').'...

'; -} echo ' '; @@ -255,13 +324,13 @@ echo ' } function ricalcola_totale() { - tot_qta = 0; - r = 0; totale = 0.00; + $('input[id*=qta_]').each(function() { qta = $(this).val().toEnglish(); + r = $(this).attr("id").replace("qta_", ""); - if (!$('#checked_' + r).is(':checked') || isNaN(qta)) { + if (!$("#checked_" + r).is(":checked") || isNaN(qta)) { qta = 0; } @@ -278,17 +347,13 @@ echo ' if(subtot) { totale += subtot * qta + iva * qta; } - - r++; - - tot_qta += qta; }); $('#totale').html((totale.toLocale()) + " " + globals.currency); 0) $("#submit_btn").show(); diff --git a/include/common/riga.php b/include/common/riga.php index a30a1fddf..77e8016bf 100644 --- a/include/common/riga.php +++ b/include/common/riga.php @@ -16,7 +16,7 @@ echo ' // Quantità echo '
- {[ "type": "number", "label": "'.tr('Q.tà').'", "name": "qta", "required": 1, "value": "'.$result['qta'].'", "decimals": "qta" ]} + {[ "type": "number", "label": "'.tr('Q.tà').'", "name": "qta", "required": 1, "value": "'.$result['qta'].'", "decimals": "qta"'.(isset($result['max_qta']) ? ', "icon-after": "/ '.numberFormat($result['max_qta'], 'qta').'", "help": "'.tr("Quantità dell'elemento / quantità totale massima").'"' : '').' ]}
'; // Unità di misura @@ -92,70 +92,3 @@ echo ' {[ "type": "number", "label": "'.tr('Sconto unitario').'", "name": "sconto", "value": "'.$result['sconto_unitario'].'", "icon-after": "choice|untprc|'.$result['tipo_sconto'].'", "help": "'.tr('Il valore positivo indica uno sconto. Per applicare un rincaro inserire un valore negativo.').'" ]}
'; - -if ($module['name'] == 'Fatture di vendita') { - $collapsed = empty($result['data_inizio_periodo']) && empty($result['data_fine_periodo']) && empty($result['riferimento_amministrazione']); - - echo ' -
-
-

'.tr('Dati Fatturazione Elettronica').'

-
- -
-
-
'; - - $tipi_cessione_prestazione = [ - [ - 'id' => 'SC', - 'text' => 'SC - '.tr('Sconto'), - ], - [ - 'id' => 'PR', - 'text' => 'PR - '.tr('Premio'), - ], - [ - 'id' => 'AB', - 'text' => 'AB - '.tr('Abbuono'), - ], - [ - 'id' => 'AC', - 'text' => 'AC - '.tr('Spesa accessoria'), - ], - ]; - - // Data inizio periodo - echo ' -
-
- {[ "type": "select", "label": "'.tr('Tipo Cessione Prestazione').'", "name": "tipo_cessione_prestazione", "value": "'.$result['tipo_cessione_prestazione'].'", "values": '.json_encode($tipi_cessione_prestazione).' ]} -
'; - - // Riferimento amministrazione - echo ' -
- {[ "type": "text", "label": "'.tr('Riferimento Amministrazione').'", "name": "riferimento_amministrazione", "value": "'.$result['riferimento_amministrazione'].'", "maxlength": 20 ]} -
-
'; - - // Data inizio periodo - echo ' -
-
- {[ "type": "date", "label": "'.tr('Data Inizio Periodo').'", "name": "data_inizio_periodo", "value": "'.$result['data_inizio_periodo'].'" ]} -
'; - - // Data fine periodo - echo ' -
- {[ "type": "date", "label": "'.tr('Data Fine Periodo').'", "name": "data_fine_periodo", "value": "'.$result['data_fine_periodo'].'" ]} -
-
'; - - echo ' -
-
'; -} diff --git a/include/common/sconto.php b/include/common/sconto.php index fe4db5bc5..4428552ed 100644 --- a/include/common/sconto.php +++ b/include/common/sconto.php @@ -39,7 +39,7 @@ echo ' var sconto_percentuale = form.find("#sconto_percentuale"); var sconto_unitario = form.find("#sconto_unitario"); - var totale = '.($options['imponibile_scontato'] ?: 0).'; + var totale = '.($options['totale_imponibile'] ?: 0).'; function aggiorna_sconto_percentuale() { var sconto = sconto_percentuale.val().toEnglish(); @@ -48,13 +48,20 @@ echo ' msg = sconto >= 0 ? "'.tr('Sconto percentuale').'" : "'.tr('Maggiorazione percentuale').'"; sconto_unitario.val(unitario.toLocale()); - descrizione.val(msg + " " + Math.abs(sconto).toLocale() + "%"); + + if (!descrizione.val()) { + descrizione.val(msg + " " + Math.abs(sconto).toLocale() + "%"); + } } function aggiorna_sconto_unitario(){ msg = sconto_unitario.val().toEnglish() >= 0 ? "'.tr('Sconto unitario').'" : "'.tr('Maggiorazione unitaria').'"; - descrizione.val(msg); + sconto_percentuale.val(0); + + if (!descrizione.val()) { + descrizione.val(msg); + } } sconto_percentuale.keyup(aggiorna_sconto_percentuale); diff --git a/include/init/init.php b/include/init/init.php index a500e397a..98f399bf6 100644 --- a/include/init/init.php +++ b/include/init/init.php @@ -128,11 +128,11 @@ if (!$has_user) {
- {[ "type": "password", "label": "'.tr('Password').'", "id": "password", "name": "admin_password", "value": "'.$osm_password.'", "placeholder": "'.tr("Digita la password dell'amministratore").'", "required": 1, "icon-after": "" ]} + {[ "type": "password", "label": "'.tr('Password').'", "id": "password", "name": "admin_password", "value": "'.$osm_password.'", "placeholder": "'.tr("Digita la password dell'amministratore").'", "required": 1, "strength": "#config" ]}
- {[ "type": "email", "label": "'.tr('Email').'", "name": "admin_email", "value": "'.$osm_email.'", "placeholder": "'.tr("Digita l'indirizzo email dell'amministratore").'" ]} + {[ "type": "email", "label": "'.tr('Email').'", "name": "admin_email", "value": "'.$osm_email.'", "placeholder": "'.tr("Digita l'indirizzo email dell'amministratore").'", "required": 1 ]}
diff --git a/include/src/Components/Article.php b/include/src/Components/Article.php index e0e63f72f..b6dde2d2c 100644 --- a/include/src/Components/Article.php +++ b/include/src/Components/Article.php @@ -9,6 +9,7 @@ use UnexpectedValueException; abstract class Article extends Row { + public $movimenta_magazzino = true; protected $serialRowID = null; protected $abilita_movimentazione = true; protected $serialsList = null; @@ -28,7 +29,24 @@ abstract class Article extends Row return $model; } - abstract public function movimenta($qta); + public function movimenta($qta) + { + if (!$this->movimenta_magazzino) { + return; + } + + $movimenta = true; + + // Movimenta il magazzino solo se l'articolo non è già stato movimentato da un documento precedente + if ($this->hasOriginal()) { + $original = $this->getOriginal(); + $movimenta = !$original->movimenta_magazzino; + } + + if ($movimenta) { + $this->movimentaMagazzino($qta); + } + } abstract public function getDirection(); @@ -96,9 +114,15 @@ abstract class Article extends Row * * @return float */ - public function getMissingSerialsAttribute() + public function getMissingSerialsNumberAttribute() { - return $this->qta - count($this->serials); + if (!$this->abilita_serial) { + return 0; + } + + $missing = $this->qta - count($this->serials); + + return $missing; } /** @@ -145,12 +169,39 @@ abstract class Article extends Row return parent::save($options); } + public function canDelete() + { + $serials = $this->usedSerials(); + + return empty($serials); + } + + public function delete() + { + if (!$this->canDelete()) { + throw new \InvalidArgumentException(); + } + + $this->serials = []; + + $this->qta = 0; // Fix movimentazione automatica + if (!empty($this->qta_movimentazione)) { + $this->movimenta($this->qta_movimentazione); + } + + return parent::delete(); + } + + abstract protected function movimentaMagazzino($qta); + protected static function boot() { parent::boot(true); - static::addGlobalScope('articles', function (Builder $builder) { - $builder->whereNotNull('idarticolo')->where('idarticolo', '<>', 0); + $table = parent::getTableName(); + + static::addGlobalScope('articles', function (Builder $builder) use ($table) { + $builder->whereNotNull($table.'.idarticolo')->where($table.'.idarticolo', '<>', 0); }); } diff --git a/include/src/Components/Description.php b/include/src/Components/Description.php index 5d6c48673..911aa5694 100644 --- a/include/src/Components/Description.php +++ b/include/src/Components/Description.php @@ -8,8 +8,14 @@ use Illuminate\Database\Eloquent\Builder; abstract class Description extends Model { + use MorphTrait; + protected $guarded = []; + protected $appends = [ + 'max_qta', + ]; + public static function build(Document $document, $bypass = false) { $model = parent::build(); @@ -24,6 +30,17 @@ abstract class Description extends Model return $model; } + public function getMaxQtaAttribute() + { + if (!$this->hasOriginal()) { + return null; + } + + $original = $this->getOriginal(); + + return $original->qta_rimanente + $this->qta; + } + /** * Modifica la quantità dell'elemento. * @@ -36,9 +53,23 @@ abstract class Description extends Model $previous = $this->qta; $diff = $value - $previous; + if ($this->hasOriginal()) { + $original = $this->getOriginal(); + + if ($original->qta_rimanente < $diff) { + $diff = $original->qta_rimanente; + $value = $previous + $diff; + } + } + $this->attributes['qta'] = $value; - $this->evasione($diff); + if ($this->hasOriginal()) { + $original = $this->getOriginal(); + + $original->qta_evasa += $diff; + $original->save(); + } return $diff; } @@ -53,11 +84,23 @@ abstract class Description extends Model return $this->qta - $this->qta_evasa; } + public function canDelete() + { + return true; + } + public function delete() { - $this->evasione(-$this->qta); + if (!$this->canDelete()) { + throw new \InvalidArgumentException(); + } - return parent::delete(); + $this->qta = 0; + $result = parent::delete(); + + $this->parent->fixStato($this); + + return $result; } /** @@ -98,6 +141,9 @@ abstract class Description extends Model // Attributi dell'oggetto da copiare $attributes = $this->getAttributes(); unset($attributes['id']); + unset($attributes['original_id']); + unset($attributes['original_type']); + unset($attributes['order']); if ($qta !== null) { $attributes['qta'] = $qta; @@ -108,6 +154,15 @@ abstract class Description extends Model // Creazione del nuovo oggetto $model = new $object(); + // Rimozione attributo in conflitto + unset($attributes[$model->getParentID()]); + + $model->original_id = $this->id; + $model->original_type = $current; + + // Impostazione del genitore + $model->setParent($document); + // Azioni specifiche di inizalizzazione $model->customInitCopiaIn($this); @@ -123,18 +178,11 @@ abstract class Description extends Model $attributes = array_intersect_key($attributes, $accepted); $model->fill($attributes); - // Impostazione del genitore - $model->setParent($document); - // Azioni specifiche successive $model->customAfterDataCopiaIn($this); $model->save(); - // Rimozione quantità evasa - $this->qta_evasa = $this->qta_evasa + abs($attributes['qta']); - $this->save(); - return $model; } @@ -162,8 +210,13 @@ abstract class Description extends Model return $this instanceof Article; } - protected function evasione($diff) + public function save(array $options = []) { + $result = parent::save($options); + + $this->parent->fixStato($this); + + return $result; } /** @@ -199,13 +252,15 @@ abstract class Description extends Model { parent::boot(); + $table = parent::getTableName(); + if (!$bypass) { - static::addGlobalScope('descriptions', function (Builder $builder) { - $builder->where('is_descrizione', '=', 1); + static::addGlobalScope('descriptions', function (Builder $builder) use ($table) { + $builder->where($table.'.is_descrizione', '=', 1); }); } else { - static::addGlobalScope('not_descriptions', function (Builder $builder) { - $builder->where('is_descrizione', '=', 0); + static::addGlobalScope('not_descriptions', function (Builder $builder) use ($table) { + $builder->where($table.'.is_descrizione', '=', 0); }); } } diff --git a/include/src/Components/Discount.php b/include/src/Components/Discount.php index 9faf30fdd..ce887d10d 100644 --- a/include/src/Components/Discount.php +++ b/include/src/Components/Discount.php @@ -24,6 +24,11 @@ abstract class Discount extends Row return $this->attributes['iva']; } + public function isMaggiorazione() + { + return $this->totale_imponibile < 0; + } + /** * Effettua i conti per l'IVA. */ @@ -43,8 +48,10 @@ abstract class Discount extends Row { parent::boot(true); - static::addGlobalScope('discounts', function (Builder $builder) { - $builder->where('is_sconto', '=', 1); + $table = parent::getTableName(); + + static::addGlobalScope('discounts', function (Builder $builder) use ($table) { + $builder->where($table.'.is_sconto', '=', 1); }); } } diff --git a/include/src/Components/MorphTrait.php b/include/src/Components/MorphTrait.php new file mode 100644 index 000000000..7d7df8a70 --- /dev/null +++ b/include/src/Components/MorphTrait.php @@ -0,0 +1,21 @@ +original_type) && !empty($this->original); + } + + public function original() + { + return $this->morphedByMany($this->original_type, 'original', $this->table, 'id'); + } + + public function getOriginal() + { + return $this->original()->first(); + } +} diff --git a/include/src/Components/Row.php b/include/src/Components/Row.php index 32a8af6b2..dadf170d5 100644 --- a/include/src/Components/Row.php +++ b/include/src/Components/Row.php @@ -33,11 +33,11 @@ abstract class Row extends Description } /** - * Restituisce l'imponibile scontato dell'elemento. + * Restituisce il totale imponibile dell'elemento. * * @return float */ - public function getImponibileScontatoAttribute() + public function getTotaleImponibileAttribute() { $result = $this->prezzo_unitario_vendita >= 0 ? $this->imponibile : -$this->imponibile; @@ -53,7 +53,7 @@ abstract class Row extends Description */ public function getTotaleAttribute() { - return $this->imponibile_scontato + $this->iva; + return $this->totale_imponibile + $this->iva; } /** @@ -67,13 +67,13 @@ abstract class Row extends Description } /** - * Restituisce il gaudagno totale (imponibile_scontato - spesa) relativo all'elemento. + * Restituisce il gaudagno totale (totale_imponibile - spesa) relativo all'elemento. * * @return float */ public function getGuadagnoAttribute() { - return $this->imponibile_scontato - $this->spesa; + return $this->totale_imponibile - $this->spesa; } // Attributi della componente @@ -85,7 +85,7 @@ abstract class Row extends Description public function getIvaAttribute() { - return ($this->imponibile_scontato) * $this->aliquota->percentuale / 100; + return ($this->totale_imponibile) * $this->aliquota->percentuale / 100; } public function getIvaDetraibileAttribute() @@ -125,7 +125,7 @@ abstract class Row extends Description } /** - * Imposta il costo unitario della riga. + * Imposta il prezzo unitario della riga. * * @param float $value */ @@ -135,7 +135,7 @@ abstract class Row extends Description } /** - * Restituisce il costo unitario della riga. + * Restituisce il prezzo unitario della riga. */ public function getPrezzoUnitarioVenditaAttribute() { @@ -173,13 +173,15 @@ abstract class Row extends Description { parent::boot(true); + $table = parent::getTableName(); + if (!$bypass) { - static::addGlobalScope('rows', function (Builder $builder) { - $builder->whereNull('idarticolo')->orWhere('idarticolo', '=', 0); + static::addGlobalScope('rows', function (Builder $builder) use ($table) { + $builder->whereNull($table.'.idarticolo')->orWhere($table.'.idarticolo', '=', 0); }); - static::addGlobalScope('not_discounts', function (Builder $builder) { - $builder->where('is_sconto', '=', 0); + static::addGlobalScope('not_discounts', function (Builder $builder) use ($table) { + $builder->where($table.'.is_sconto', '=', 0); }); } } diff --git a/include/src/Document.php b/include/src/Document.php index cc2a08924..16d957d41 100644 --- a/include/src/Document.php +++ b/include/src/Document.php @@ -2,6 +2,8 @@ namespace Common; +use Common\Components\Description; + abstract class Document extends Model { /** @@ -19,6 +21,29 @@ abstract class Document extends Model return $descrizioni->merge($righe)->merge($articoli)->merge($sconti)->sortBy('order'); } + /** + * Restituisce la collezione di righe e articoli con valori rilevanti per i conti, raggruppate sulla base dei documenti di provenienza. + * La chiave è la serializzazione del documento di origine, oppure null in caso non esista. + * + * @return iterable + */ + public function getRigheRaggruppate() + { + $righe = $this->getRighe(); + + $groups = $righe->groupBy(function ($item, $key) { + if (!$item->hasOriginal()) { + return null; + } + + $parent = $item->getOriginal()->parent; + + return serialize($parent); + }); + + return $groups; + } + abstract public function righe(); abstract public function articoli(); @@ -48,13 +73,13 @@ abstract class Document extends Model } /** - * Calcola l'imponibile scontato del documento. + * Calcola il totale imponibile del documento. * * @return float */ - public function getImponibileScontatoAttribute() + public function getTotaleImponibileAttribute() { - return $this->calcola('imponibile_scontato'); + return $this->calcola('totale_imponibile'); } /** @@ -97,6 +122,40 @@ abstract class Document extends Model return $this->calcola('guadagno'); } + public function delete() + { + $righe = $this->getRighe(); + + $can_delete = true; + foreach ($righe as $riga) { + $can_delete &= $riga->canDelete(); + } + + if (!$can_delete) { + throw new \InvalidArgumentException(); + } + + foreach ($righe as $riga) { + $riga->delete(); + } + + return parent::delete(); + } + + /** + * Effettua un controllo sui campi del documento. + * Viene richiamatp dalle modifiche alle righe del documento. + * + * @param Description $trigger + */ + public function fixStato(Description $trigger) + { + $this->load('righe'); + $this->load('articoli'); + $this->load('descrizioni'); + $this->load('sconti'); + } + /** * Calcola la somma degli attributi indicati come parametri. * Il metodo **non** deve essere adattato per ulteriori funzionalità: deve esclusivamente calcolare la somma richiesta in modo esplicito dagli argomenti. @@ -122,9 +181,7 @@ abstract class Document extends Model */ protected function getRigheContabili() { - $sconto = $this->scontoGlobale ? [$this->scontoGlobale] : []; - - return $this->getRighe()->merge(collect($sconto)); + return $this->getRighe(); } /** diff --git a/include/src/Model.php b/include/src/Model.php index 36869e2e0..c9d8c6ba8 100644 --- a/include/src/Model.php +++ b/include/src/Model.php @@ -7,7 +7,10 @@ use Illuminate\Database\Eloquent\Model as Original; abstract class Model extends Original { // Retrocompatibilità MySQL - const UPDATED_AT = null; + public function setUpdatedAtAttribute($value) + { + // to Disable updated_at + } /** * Crea una nuova istanza del modello. @@ -18,4 +21,9 @@ abstract class Model extends Original { return new static(); } + + public static function getTableName() + { + return with(new static())->getTable(); + } } diff --git a/include/top.php b/include/top.php index 64ba56655..9e5d176a3 100644 --- a/include/top.php +++ b/include/top.php @@ -96,6 +96,23 @@ if (Auth::check()) { '.$key.': "'.addslashes($value).'",'; } echo ' + password: { + "wordMinLength": "'.tr('La tua password è troppo corta').'", + "wordMaxLength": "'.tr('La tua password è troppo lunga').'", + "wordInvalidChar": "'.tr('La tua password contiene un carattere non valido').'", + "wordNotEmail": "'.tr('Non usare la tua e-mail come password').'", + "wordSimilarToUsername": "'.tr('La tua password non può contenere il tuo nome').'", + "wordTwoCharacterClasses": "'.tr('Usa classi di caratteri diversi').'", + "wordRepetitions": "'.tr('Troppe ripetizioni').'", + "wordSequences": "'.tr('La tua password contiene sequenze').'", + "errorList": "'.tr('Errori').':", + "veryWeak": "'.tr('Molto debole').'", + "weak": "'.tr('Debole').'", + "normal": "'.tr('Normale').'", + "medium": "'.tr('Media').'", + "strong": "'.tr('Forte').'", + "veryStrong": "'.tr('Molto forte').'", + }, }; globals = { rootdir: "'.$rootdir.'", @@ -138,7 +155,25 @@ if (Auth::check()) { rootdir: "'.$rootdir.'", search: {}, - translations: {}, + translations: { + password: { + "wordMinLength": "'.tr('La tua password è troppo corta').'", + "wordMaxLength": "'.tr('La tua password è troppo lunga').'", + "wordInvalidChar": "'.tr('La tua password contiene un carattere non valido').'", + "wordNotEmail": "'.tr('Non usare la tua e-mail come password').'", + "wordSimilarToUsername": "'.tr('La tua password non può contenere il tuo nome').'", + "wordTwoCharacterClasses": "'.tr('Usa classi di caratteri diversi').'", + "wordRepetitions": "'.tr('Troppe ripetizioni').'", + "wordSequences": "'.tr('La tua password contiene sequenze').'", + "errorList": "'.tr('Errori').':", + "veryWeak": "'.tr('Molto debole').'", + "weak": "'.tr('Debole').'", + "normal": "'.tr('Normale').'", + "medium": "'.tr('Media').'", + "strong": "'.tr('Forte').'", + "veryStrong": "'.tr('Molto forte').'", + }, + }, locale: "'.$lang.'", full_locale: "'.$lang.'_'.strtoupper($lang).'", @@ -275,7 +310,7 @@ if (Auth::check()) { -
  • +
  • @@ -289,18 +324,27 @@ if (Auth::check()) {