diff --git a/actions.php b/actions.php index d48555100..9483d89f2 100644 --- a/actions.php +++ b/actions.php @@ -28,7 +28,7 @@ $element['edit_file'] = !empty($php) ? $php : $html; $upload_dir = DOCROOT.'/'.Uploads::getDirectory($id_module, $id_plugin); -$dbo->query('START TRANSACTION'); +//$dbo->query('START TRANSACTION'); // GESTIONE UPLOAD if (filter('op') == 'link_file' || filter('op') == 'unlink_file') { @@ -242,4 +242,4 @@ if (Modules::getPermission($id_module) == 'rw') { } } -$dbo->query('COMMIT'); +//$dbo->query('COMMIT'); diff --git a/composer.json b/composer.json index 4eb8b1509..785754fcc 100644 --- a/composer.json +++ b/composer.json @@ -29,6 +29,7 @@ "filp/whoops": "^2.1", "guzzlehttp/guzzle": "^6.3", "ifsnop/mysqldump-php": "^2.3", + "illuminate/database": "~5.4.0", "intervention/image": "^2.3", "league/csv": "^8.2", "maximebf/debugbar": "^1.15", diff --git a/core.php b/core.php index 53d8a5e99..04eb13b15 100644 --- a/core.php +++ b/core.php @@ -156,6 +156,20 @@ if (!API::isAPIRequest()) { // Impostazioni di Content-Type e Charset Header header('Content-Type: text/html; charset=UTF-8'); + // Barra di debug + if (App::debug()) { + $debugbar = new DebugBar\DebugBar(); + + $debugbar->addCollector(new DebugBar\DataCollector\MemoryCollector()); + $debugbar->addCollector(new DebugBar\DataCollector\PhpInfoCollector()); + + $debugbar->addCollector(new DebugBar\DataCollector\RequestDataCollector()); + $debugbar->addCollector(new DebugBar\DataCollector\TimeDataCollector()); + + $debugbar->addCollector(new DebugBar\Bridge\MonologCollector($logger)); + $debugbar->addCollector(new Extension\EloquentCollector($dbo->getCapsule())); + } + // Controllo CSRF csrfProtector::init(); @@ -231,3 +245,12 @@ if (!API::isAPIRequest()) { $post = Filter::getPOST(); $get = Filter::getGET(); } + +$f = Modules\Fatture\Fattura::find(7); + +$result = Modules\Fatture\Fattura::create([ + 'idanagrafica' => 1, + 'data' => '2018-11-11 12:13:21', + 'id_segment' => 1, + 'idtipodocumento' => 1, +]); diff --git a/include/top.php b/include/top.php index c6fe0b616..67b2e5cc5 100644 --- a/include/top.php +++ b/include/top.php @@ -149,17 +149,6 @@ echo ' if (Auth::check()) { // Barra di debug if (App::debug()) { - $debugbar = new DebugBar\DebugBar(); - - $debugbar->addCollector(new DebugBar\DataCollector\MemoryCollector()); - $debugbar->addCollector(new DebugBar\DataCollector\PhpInfoCollector()); - - $debugbar->addCollector(new DebugBar\DataCollector\RequestDataCollector()); - $debugbar->addCollector(new DebugBar\DataCollector\TimeDataCollector()); - - $debugbar->addCollector(new DebugBar\Bridge\MonologCollector($logger)); - $debugbar->addCollector(new DebugBar\DataCollector\PDO\PDOCollector($dbo->getPDO())); - $debugbarRenderer = $debugbar->getJavascriptRenderer(); $debugbarRenderer->setIncludeVendors(false); $debugbarRenderer->setBaseUrl($paths['assets'].'/php-debugbar'); diff --git a/modules/fatture/Fattura.php b/modules/fatture/Fattura.php new file mode 100644 index 000000000..4022e48f8 --- /dev/null +++ b/modules/fatture/Fattura.php @@ -0,0 +1,345 @@ +create($attributes); + + $tipo_documento = Tipo::find($attributes['idtipodocumento']); + $stato_documento = Stato::where('descrizione', 'Bozza')->first(); + + $direzione = $tipo_documento->dir; + + $data = $attributes['data']; + $id_anagrafica = $attributes['idanagrafica']; + $id_segment = $attributes['id_segment']; + + $dbo = database(); + + // Calcolo dei numeri fattura + $numero = static::getNumero($data, $direzione, $id_segment); + $numero_esterno = static::getNumeroSecondario($data, $direzione, $id_segment); + + if ($direzione == 'entrata') { + $id_conto = setting('Conto predefinito fatture di vendita'); + $conto = 'vendite'; + } else { + $id_conto = setting('Conto predefinito fatture di acquisto'); + $conto = 'acquisti'; + } + + // Tipo di pagamento e banca predefinite dall'anagrafica + $pagamento = $dbo->fetchOne('SELECT id, (SELECT idbanca_'.$conto.' FROM an_anagrafiche WHERE idanagrafica = ?) AS idbanca FROM co_pagamenti WHERE id = (SELECT idpagamento_'.$conto.' AS pagamento FROM an_anagrafiche WHERE idanagrafica = ?)', [ + $id_anagrafica, + $id_anagrafica, + ]); + $id_pagamento = $pagamento['id']; + $id_banca = $pagamento['idbanca']; + + // Se la fattura è di vendita e non è stato associato un pagamento predefinito al cliente leggo il pagamento dalle impostazioni + if ($direzione == 'entrata' && empty($id_pagamento)) { + $id_pagamento = setting('Tipo di pagamento predefinito'); + } + + // Se non è impostata la banca dell'anagrafica, uso quella del pagamento. + if (empty($id_banca)) { + $id_banca = $dbo->fetchOne('SELECT id FROM co_banche WHERE id_pianodeiconti3 = (SELECT idconto_'.$conto.' FROM co_pagamenti WHERE id = :id_pagamento)', [ + ':id_pagamento' => $id_pagamento, + ])['id']; + } + + $id_sede = $dbo->selectOne('an_anagrafiche', 'idsede_fatturazione', ['idanagrafica' => $id_anagrafica])['idsede_fatturazione']; + + // Salvataggio delle informazioni + $model->numero = $numero; + $model->numero_esterno = $numero_esterno; + + $model->idconto = $id_conto; + $model->idpagamento = $id_pagamento; + $model->idbanca = $id_banca; + $model->idsede = $id_sede; + + $model->tipo()->associate($tipo_documento); + $model->stato()->associate($stato_documento); + + $model->save(); + + return $model; + } + + /** + * Calcola il nuovo numero di fattura. + * + * @param string $data + * @param string $direzione + * @param int $id_segment + * + * @return string + */ + protected static function getNumero($data, $direzione, $id_segment) + { + $dbo = database(); + + if ($direzione == 'uscita') { + $maschera = static::getMaschera($id_segment); + + $ultima_fattura = $dbo->fetchOne('SELECT numero_esterno FROM co_documenti WHERE YEAR(data) = :year AND id_segment = :id_segment '.static::getMascheraOrder($maschera), [ + ':year' => date('Y', strtotime($data)), + ':id_segment' => $id_segment, + ]); + + $numero = Generator::generate($maschera, $ultima_fattura['numero']); + } else { + $rs = $dbo->fetchOne("SELECT IFNULL(MAX(numero), '0') AS max_numerofattura FROM co_documenti WHERE YEAR(data) = :year AND idtipodocumento IN(SELECT id FROM co_tipidocumento WHERE dir = :direzione) ORDER BY CAST(numero AS UNSIGNED) DESC", [ + ':year' => date('Y', strtotime($data)), + ':direzione' => $direzione, + ]); + + $numero = $rs['max_numerofattura'] + 1; + } + + return $numero; + } + + /** + * Calcola il nuovo numero secondario di fattura. + * + * @param string $data + * @param string $direzione + * @param int $id_segment + * + * @return string + */ + protected static function getNumeroSecondario($data, $direzione, $id_segment) + { + if ($direzione == 'uscita') { + return ''; + } + + $dbo = database(); + + // Recupero maschera per questo segmento + $maschera = static::getMaschera($id_segment); + + $ultima_fattura = $dbo->fetchOne('SELECT numero_esterno FROM co_documenti WHERE YEAR(data) = :year AND id_segment = :id_segment '.static::getMascheraOrder($maschera), [ + ':year' => date('Y', strtotime($data)), + ':id_segment' => $id_segment, + ]); + + $numero_esterno = Generator::generate($maschera, $ultima_fattura['numero_esterno']); + + return $numero_esterno; + } + + /** + * Restituisce la maschera specificata per il segmento indicato. + * + * @param int $id_segment + * + * @return string + */ + protected static function getMaschera($id_segment) + { + $dbo = database(); + + $maschera = $dbo->fetchOne('SELECT pattern FROM zz_segments WHERE id = :id_segment', [ + ':id_segment' => $id_segment, + ])['pattern']; + + return $maschera; + } + + /** + * Metodo per l'individuazione del tipo di ordine da impostare per la corretta interpretazione della maschera. + * Esempi: + * - maschere con testo iniziale (FT-####-YYYY) necessitano l'ordinamento alfabetico + * - maschere di soli numeri (####-YYYY) è necessario l'ordinamento numerico forzato. + * + * @param string $maschera + * + * @return string + */ + protected static function getMascheraOrder($maschera) + { + // Estraggo blocchi di caratteri standard + preg_match('/[#]+/', $maschera, $m1); + //preg_match('/[Y]+/', $maschera, $m2); + + $pos1 = strpos($maschera, $m1[0]); + if ($pos1 == 0) { + $query = 'ORDER BY CAST(numero_esterno AS UNSIGNED) DESC'; + } else { + $query = 'ORDER BY numero_esterno DESC'; + } + + return $query; + } + + /** + * Calcola l'imponibile della fattura (totale delle righe - sconto). + * + * @return float + */ + public function getImponibile() + { + if (isset($this->conti['imponibile'])) { + return $this->conti['imponibile']; + } + + $result = database()->fetchOne('SELECT SUM(co_righe_documenti.subtotale - co_righe_documenti.sconto) AS imponibile FROM co_righe_documenti WHERE iddocumento = :id', [ + ':id' => $this->id, + ]); + + return $result['imponibile']; + } + + /** + * Calcola il totale della fattura (imponibile + iva). + * + * @return float + */ + public function getTotale() + { + if (isset($this->conti['totale'])) { + return $this->conti['totale']; + } + + $dbo = database(); + + // Sommo l'iva di ogni riga al totale + $iva = $dbo->fetchArray('SELECT SUM(iva) AS iva FROM co_righe_documenti WHERE iddocumento = :id', [ + ':id' => $this->id, + ])['iva']; + + $iva_rivalsainps = $dbo->fetchArray('SELECT SUM(rivalsainps / 100 * percentuale) AS iva_rivalsainps FROM co_righe_documenti INNER JOIN co_iva ON co_iva.id = co_righe_documenti.idiva WHERE iddocumento = :id', [ + ':id' => $this->id, + ])['iva_rivalsainps']; + + $totale = sum([ + $this->getImponibile(), + $this->rivalsainps, + $iva, + $iva_rivalsainps, + ]); + + return $totale; + } + + /** + * Calcola il netto a pagare della fattura (totale - ritenute - bolli). + * + * @return float + */ + public function getNetto($iddocumento) + { + if (isset($this->conti['netto'])) { + return $this->conti['netto']; + } + + $netto = sum([ + $this->getTotale(), + $this->bollo, + -$this->ritenutaacconto, + ]); + + return $netto; + } + + /** + * Calcola l'iva detraibile della fattura. + * + * @return float + */ + public function getIvaDetraibile() + { + if (isset($this->conti['iva_detraibile'])) { + return $this->conti['iva_detraibile']; + } + + $result = database()->fetchOne('SELECT SUM(iva) - SUM(iva_indetraibile) AS iva_detraibile FROM co_righe_documenti WHERE iddocumento = :id', [ + ':id' => $this->id, + ]); + + return $result['iva_detraibile']; + } + + /** + * Calcolo l'iva indetraibile della fattura. + * + * @return float + */ + public function getIvaIndetraibile() + { + if (isset($this->conti['iva_indetraibile'])) { + return $this->conti['iva_indetraibile']; + } + + $result = database()->fetchOne('SELECT SUM(iva_indetraibile) AS iva_indetraibile FROM co_righe_documenti WHERE = :id', [ + ':id' => $this->id, + ]); + + return $result['iva_indetraibile']; + } + + /** + * Restituisce l'elenco delle note di accredito collegate. + * + * @return array + */ + public function getNoteDiAccredito() + { + return database()->fetchArray("SELECT co_documenti.id, IF(numero_esterno != '', numero_esterno, numero) AS numero, data FROM co_documenti WHERE idtipodocumento IN (SELECT id FROM co_tipidocumento WHERE reversed = 1) AND ref_documento = :id", [ + ':id' => $this->id, + ]); + } + + /** + * Controlla se la fattura è una nota di accredito. + * + * @return bool + */ + public function isNotaDiAccredito() + { + return $this->getTipo()['reversed'] == 1; + } + + public function tipo() + { + return $this->belongsTo(Tipo::class, 'idtipodocumento'); + } + + public function stato() + { + return $this->belongsTo(Stato::class, 'idstatodocumento'); + } + + public function righe() + { + return $this->hasMany(Riga::class, 'iddocumento'); + } +} diff --git a/modules/fatture/Riga.php b/modules/fatture/Riga.php new file mode 100644 index 000000000..42e8d2070 --- /dev/null +++ b/modules/fatture/Riga.php @@ -0,0 +1,15 @@ +belongsTo(Fattura::class, 'iddocumento'); + } +} diff --git a/modules/fatture/Stato.php b/modules/fatture/Stato.php new file mode 100644 index 000000000..0d048c4fb --- /dev/null +++ b/modules/fatture/Stato.php @@ -0,0 +1,15 @@ +hasMany(Fattura::class, 'idstatodocumento'); + } +} diff --git a/modules/fatture/Tipo.php b/modules/fatture/Tipo.php new file mode 100644 index 000000000..53c0e77e6 --- /dev/null +++ b/modules/fatture/Tipo.php @@ -0,0 +1,15 @@ +hasMany(Fattura::class, 'idtipodocumento'); + } +} diff --git a/src/Auth.php b/src/Auth.php index 8c27e0f39..3387d7936 100644 --- a/src/Auth.php +++ b/src/Auth.php @@ -322,15 +322,17 @@ class Auth extends \Util\Singleton public function getFirstModule() { if (empty($this->first_module)) { + $parameters = []; + $query = 'SELECT id FROM zz_modules WHERE enabled = 1'; if (!$this->isAdmin()) { $query .= " AND id IN (SELECT idmodule FROM zz_permissions WHERE idgruppo = (SELECT id FROM zz_groups WHERE nome = :group) AND permessi IN ('r', 'rw'))"; + + $parameters[':group'] = $this->getUser()['gruppo']; } $database = Database::getConnection(); - $results = $database->fetchArray($query." AND options != '' AND options != 'menu' AND options IS NOT NULL ORDER BY `order` ASC", [ - ':group' => $this->getUser()['gruppo'], - ]); + $results = $database->fetchArray($query." AND options != '' AND options != 'menu' AND options IS NOT NULL ORDER BY `order` ASC", $parameters); if (!empty($results)) { $module = null; diff --git a/src/Database.php b/src/Database.php index d03c9d6ad..b30fdb71c 100644 --- a/src/Database.php +++ b/src/Database.php @@ -1,5 +1,7 @@ \PDO::ERRMODE_EXCEPTION]) + protected function __construct($server, $username, $password, $database_name, $charset = null) { if (is_array($server)) { $host = $server['host']; @@ -60,48 +51,39 @@ class Database extends Util\Singleton // Possibilità di specificare una porta per il servizio MySQL diversa dalla standard 3306 $port = !empty(App::getConfig()['port']) ? App::getConfig()['port'] : $port; - $this->host = $host; - if (!empty($port) && is_int($port * 1)) { - $this->port = $port; - } - - $this->username = $username; - $this->password = $password; $this->database_name = $database_name; - $this->charset = $charset; - $this->option = $option; - - if (!empty($this->host) && !empty($this->database_name)) { + if (!empty($host) && !empty($database_name)) { try { - $pdo = new PDO( - 'mysql:host='.$this->host.(!empty($this->port) ? ';port='.$this->port : '').';dbname='.$this->database_name, - $this->username, - $this->password, - $this->option - ); + // Istanziamento di Eloquent + $this->capsule = new Capsule(); + $this->capsule->addConnection([ + 'driver' => 'mysql', + 'host' => $host, + 'database' => $database_name, + 'username' => $username, + 'password' => $password, + 'charset' => 'utf8', + 'prefix' => '', + 'port' => $port, + ]); - if (App::getConfig()['debug']) { - $pdo = new \DebugBar\DataCollector\PDO\TraceablePDO($pdo); - } + $this->is_connected = !empty($this->getPDO()); - $this->pdo = $pdo; - - if (empty($this->charset) && version_compare($this->getMySQLVersion(), '5.5.3') >= 0) { - $this->charset = 'utf8mb4'; + // Impostazione del charset della comunicazione + if (empty($charset) && version_compare($this->getMySQLVersion(), '5.5.3') >= 0) { + $this->getPDO()->exec("SET NAMES 'utf8mb4'"); } // Fix per problemi di compatibilità delle password MySQL 4.1+ (da versione precedente) - $this->pdo->query('SET SESSION old_passwords = 0'); - //$this->pdo->query('SET PASSWORD = PASSWORD('.$this->prepare($this->password).')'); - - // Impostazione del charset della comunicazione - if (!empty($this->charset)) { - $this->pdo->query("SET NAMES '".$this->charset."'"); - } + $this->getPDO()->exec('SET SESSION old_passwords = 0'); + //$this->getPDO()->exec('SET PASSWORD = PASSWORD('.$this->prepare($this->password).')'); // Reset della modalità di esecuzione MySQL per la sessione corrente - $this->pdo->query("SET sql_mode = ''"); + $this->getPDO()->exec("SET sql_mode = ''"); + + $this->capsule->setAsGlobal(); + $this->capsule->bootEloquent(); } catch (PDOException $e) { if ($e->getCode() == 1049 || $e->getCode() == 1044) { $e = new PDOException(($e->getCode() == 1049) ? tr('Database non esistente!') : tr('Credenziali di accesso invalide!')); @@ -149,7 +131,12 @@ class Database extends Util\Singleton */ public function getPDO() { - return $this->pdo; + return $this->capsule->getConnection()->getPDO(); + } + + public function getCapsule() + { + return $this->capsule; } /** @@ -161,7 +148,7 @@ class Database extends Util\Singleton */ public function isConnected() { - return !empty($this->pdo); + return $this->is_connected; } /** @@ -223,7 +210,7 @@ class Database extends Util\Singleton public function query($query, $parameters = [], $signal = null, $options = []) { try { - $statement = $this->pdo->prepare($query); + $statement = $this->getPDO()->prepare($query); $statement->execute($parameters); $id = $this->lastInsertedID(); @@ -252,7 +239,7 @@ class Database extends Util\Singleton try { $mode = empty($numeric) ? PDO::FETCH_ASSOC : PDO::FETCH_NUM; - $statement = $this->pdo->prepare($query); + $statement = $this->getPDO()->prepare($query); $statement->execute($parameters); $result = $statement->fetchAll($mode); @@ -340,15 +327,11 @@ class Database extends Util\Singleton public function tableExists($table) { - $results = null; - if ($this->isConnected()) { - $results = $this->fetchArray('SHOW TABLES LIKE :table', [ - ':table' => $table, - ]); + return $this->capsule->schema()->hasTable($table); } - return !empty($results); + return null; } /** @@ -374,7 +357,7 @@ class Database extends Util\Singleton public function lastInsertedID() { try { - return $this->pdo->lastInsertId(); + return $this->getPDO()->lastInsertId(); } catch (PDOException $e) { $this->signal($e, tr("Impossibile ottenere l'ultimo identificativo creato")); } @@ -392,7 +375,7 @@ class Database extends Util\Singleton */ public function prepare($parameter) { - return $this->pdo->quote($parameter); + return $this->getPDO()->quote($parameter); } /** @@ -418,11 +401,10 @@ class Database extends Util\Singleton * * @param string $table * @param array $array - * @param bool $return * * @return string|array */ - public function insert($table, $array, $return = false) + public function insert($table, $array) { if (!is_string($table) || !is_array($array)) { throw new UnexpectedValueException(); @@ -432,31 +414,7 @@ class Database extends Util\Singleton $array = [$array]; } - // Chiavi dei valori - $keys = []; - $temp = array_keys($array[0]); - foreach ($temp as $value) { - $keys[] = $this->quote($value); - } - - // Valori da inserire - $inserts = []; - foreach ($array as $values) { - foreach ($values as $key => $value) { - $values[$key] = $this->prepareValue($key, $value); - } - - $inserts[] = '('.implode(array_values($values), ', ').')'; - } - - // Costruzione della query - $query = 'INSERT INTO '.$this->quote($table).' ('.implode(',', $keys).') VALUES '.implode($inserts, ', '); - - if (!empty($return)) { - return $query; - } else { - return $this->query($query); - } + return Capsule::table($table)->insert($array); } /** @@ -807,7 +765,7 @@ class Database extends Util\Singleton for ($i = $start; $i < $end; ++$i) { try { - $this->pdo->query($queries[$i]); + $this->getPDO()->exec($queries[$i]); } catch (PDOException $e) { $this->signal($e, $queries[$i], [ 'throw' => false, diff --git a/src/Extension/EloquentCollector.php b/src/Extension/EloquentCollector.php new file mode 100644 index 000000000..82b5d0648 --- /dev/null +++ b/src/Extension/EloquentCollector.php @@ -0,0 +1,62 @@ +capsule = $capsule; + $this->addConnection($this->getTraceablePdo(), 'Eloquent PDO'); + } + + /** + * @return Illuminate\Database\Capsule\Manager; + */ + protected function getEloquentCapsule() + { + return $this->capsule; + } + + /** + * @return PDO + */ + protected function getEloquentPdo() + { + return $this->getEloquentCapsule()->getConnection()->getPdo(); + } + + /** + * @return \DebugBar\DataCollector\PDO\TraceablePDO + */ + protected function getTraceablePdo() + { + return new \DebugBar\DataCollector\PDO\TraceablePDO($this->getEloquentPdo()); + } + + // Override + public function getName() + { + return 'eloquent_pdo'; + } + + // Override + public function getWidgets() + { + return [ + 'eloquent' => [ + 'icon' => 'inbox', + 'widget' => 'PhpDebugBar.Widgets.SQLQueriesWidget', + 'map' => 'eloquent_pdo', + 'default' => '[]', + ], + 'eloquent:badge' => [ + 'map' => 'eloquent_pdo.nb_statements', + 'default' => 0, + ], + ]; + } +}