From b2359a48f0f9ee3f6f8653e85c09fce3a8c71979 Mon Sep 17 00:00:00 2001 From: Thomas Zilio Date: Fri, 13 Sep 2019 11:29:45 +0200 Subject: [PATCH] Gestione automatica dichiarazione --- modules/anagrafiche/src/Anagrafica.php | 72 ++++++++---- modules/fatture/actions.php | 42 +++---- modules/fatture/edit.php | 73 ++++++++++-- modules/fatture/row-add.php | 4 + modules/fatture/src/Fattura.php | 107 +++++++++++++----- .../src/Dichiarazione.php | 31 +++++ update/2_4_11.sql | 2 + 7 files changed, 254 insertions(+), 77 deletions(-) diff --git a/modules/anagrafiche/src/Anagrafica.php b/modules/anagrafiche/src/Anagrafica.php index 7361823e0..28b719a26 100644 --- a/modules/anagrafiche/src/Anagrafica.php +++ b/modules/anagrafiche/src/Anagrafica.php @@ -4,8 +4,13 @@ namespace Modules\Anagrafiche; use Common\Model; use Illuminate\Database\Eloquent\SoftDeletes; +use Modules\Contratti\Contratto; +use Modules\DDT\DDT; use Modules\Fatture\Fattura; +use Modules\Ordini\Ordine; +use Modules\Preventivi\Preventivo; use Modules\TipiIntervento\Tipo as TipoSessione; +use Plugins\DichiarazioniIntento\Dichiarazione; use Settings; use Traits\RecordTrait; use Util\Generator; @@ -186,6 +191,15 @@ class Anagrafica extends Model }) !== false; } + public function delete() + { + if (!$this->isAzienda()) { + return parent::delete(); + } + } + + // Attributi Eloquent + /** * Restituisce l'identificativo. * @@ -236,21 +250,6 @@ class Anagrafica extends Model $this->attributes['codice_destinatario'] = trim(strtoupper($codice_destinatario)); } - public function tipi() - { - return $this->belongsToMany(Tipo::class, 'an_tipianagrafiche_anagrafiche', 'idanagrafica', 'idtipoanagrafica'); - } - - public function fatture() - { - return $this->hasMany(Fattura::class, 'idanagrafica'); - } - - public function nazione() - { - return $this->belongsTo(Nazione::class, 'id_nazione'); - } - /** * Restituisce la sede legale collegata. * @@ -261,11 +260,46 @@ class Anagrafica extends Model return $this; } - public function delete() + // Relazioni Eloquent + + public function tipi() { - if (!$this->isAzienda()) { - return parent::delete(); - } + return $this->belongsToMany(Tipo::class, 'an_tipianagrafiche_anagrafiche', 'idanagrafica', 'idtipoanagrafica'); + } + + public function nazione() + { + return $this->belongsTo(Nazione::class, 'id_nazione'); + } + + public function fatture() + { + return $this->hasMany(Fattura::class, 'idanagrafica'); + } + + public function ordini() + { + return $this->hasMany(Ordine::class, 'idanagrafica'); + } + + public function ddt() + { + return $this->hasMany(DDT::class, 'idanagrafica'); + } + + public function contratti() + { + return $this->hasMany(Contratto::class, 'idanagrafica'); + } + + public function preventivi() + { + return $this->hasMany(Preventivo::class, 'idanagrafica'); + } + + public function dichiarazioni() + { + return $this->hasMany(Dichiarazione::class, 'id_anagrafica'); } // Metodi statici diff --git a/modules/fatture/actions.php b/modules/fatture/actions.php index 78bb1c6c7..8f652a597 100644 --- a/modules/fatture/actions.php +++ b/modules/fatture/actions.php @@ -78,10 +78,11 @@ switch (post('op')) { $fattura->rivalsainps = 0; $fattura->ritenutaacconto = 0; $fattura->iva_rivalsainps = 0; - - $fattura->codice_stato_fe = post('codice_stato_fe') ?: null; $fattura->id_ritenuta_contributi = post('id_ritenuta_contributi') ?: null; + $fattura->codice_stato_fe = post('codice_stato_fe') ?: null; + + // Informazioni per le fatture di acquisto if ($dir == 'uscita') { $fattura->numero = post('numero'); $fattura->numero_esterno = post('numero_esterno'); @@ -89,6 +90,7 @@ switch (post('op')) { $fattura->idritenutaacconto = post('id_ritenuta_acconto'); } + // Operazioni sul bollo $fattura->addebita_bollo = post('addebita_bollo'); $bollo_automatico = post('bollo_automatico'); if (empty($bollo_automatico)) { @@ -97,8 +99,26 @@ switch (post('op')) { $fattura->bollo = null; } + // Operazioni sulla dichiarazione d'intento + $dichiarazione_precedente = $fattura->dichiarazione; + $fattura->id_dichiarazione_intento = post('id_dichiarazione_intento') ?: null; + $fattura->save(); + // Operazioni sulla dichiarazione d'intento + if (!empty($dichiarazione_precedente) && $dichiarazione_precedente->id != $fattura->id_dichiarazione_intento) { + // Correzione dichiarazione precedente + $dichiarazione_precedente->fixTotale(); + $dichiarazione_precedente->save(); + + // Correzione nuova dichiarazione + $dichiarazione = $fattura->dichiarazione; + if (!empty($dichiarazione)) { + $dichiarazione->fixTotale(); + $dichiarazione->save(); + } + } + // Ricalcolo inps, ritenuta e bollo (se la fattura non è stata pagata) ricalcola_costiagg_fattura($id_record); @@ -722,21 +742,3 @@ if (get('op') == 'nota_addebito') { $id_record = $nota->id; aggiorna_sedi_movimenti('documenti', $id_record); } - -// Aggiornamento stato dei ddt presenti in questa fattura in base alle quantità totali evase -if (!empty($id_record) && setting('Cambia automaticamente stato ddt fatturati')) { - $rs = $dbo->fetchArray('SELECT DISTINCT idddt FROM co_righe_documenti WHERE iddocumento='.prepare($id_record)); - - for ($i = 0; $i < sizeof($rs); ++$i) { - $dbo->query('UPDATE dt_ddt SET idstatoddt=(SELECT id FROM dt_statiddt WHERE descrizione="'.get_stato_ddt($rs[$i]['idddt']).'") WHERE id = '.prepare($rs[$i]['idddt'])); - } -} - -// Aggiornamento stato degli ordini presenti in questa fattura in base alle quantità totali evase -if (!empty($id_record) && setting('Cambia automaticamente stato ordini fatturati')) { - $rs = $dbo->fetchArray('SELECT DISTINCT idordine FROM co_righe_documenti WHERE iddocumento='.prepare($id_record)); - - for ($i = 0; $i < sizeof($rs); ++$i) { - $dbo->query('UPDATE or_ordini SET idstatoordine=(SELECT id FROM or_statiordine WHERE descrizione="'.get_stato_ordine($rs[$i]['idordine']).'") WHERE id = '.prepare($rs[$i]['idordine'])); - } -} diff --git a/modules/fatture/edit.php b/modules/fatture/edit.php index ba2b3d103..5fca37c2b 100644 --- a/modules/fatture/edit.php +++ b/modules/fatture/edit.php @@ -1,5 +1,7 @@ dichiarazione) && $fattura->stato->descrizione == 'Bozza') { + $diff = $fattura->dichiarazione->massimale - $fattura->dichiarazione->totale; + + $id_iva = setting("Iva per lettere d'intento"); + $iva = Aliquota::find($id_iva); + + if ($diff > 0) { + echo ' +
+ '.tr("La fattura è collegata a una dichiarazione d'intento con diponibilità di _MONEY_: per collegare una riga alla dichiarazione è sufficiente inserire come IVA _IVA_", [ + '_MONEY_' => moneyFormat(abs($diff)), + '_IVA_' => '"'.$iva->descrizione.'"', + ]).' +
'; + } elseif ($diff == 0) { + echo ' +
+ '.tr("La dichiarazione d'intento ha raggiunto il massimale previsto di _MONEY_: le nuove righe della fattura devono presentare IVA diversa da _IVA_", [ + '_MONEY_' => moneyFormat(abs($fattura->dichiarazione->massimale)), + '_IVA_' => '"'.$iva->descrizione.'"', + ]).' +
'; + } else { + echo ' +
+ '.tr("La dichiarazione d'intento ha superato il massimale previsto di _MONEY_: per rimuovere righe della fattura dalla dichiarazione è sufficiente modificare l'IVA in qualcosa di diverso da _IVA_", [ + '_MONEY_' => moneyFormat(abs($diff)), + '_IVA_' => '"'.$iva->descrizione.'"', + ]).' +
'; + } +} + ?>
@@ -78,9 +114,9 @@ if ($dir == 'entrata') { - {[ "type": "text", "label": "'.tr('Numero fattura/protocollo').'", "required": 1, "name": "numero","class": "text-center alphanumeric-mask", "value": "$numero$" ]} - '; +
+ {[ "type": "text", "label": "'.tr('Numero fattura/protocollo').'", "required": 1, "name": "numero","class": "text-center alphanumeric-mask", "value": "$numero$" ]} +
'; $label = tr('Numero fattura del fornitore'); } else { $label = tr('Numero fattura'); @@ -136,15 +172,22 @@ if (empty($record['is_fiscale'])) { -
- {[ "type": "select", "label": "", "name": "codice_stato_fe", "required": 0, "values": "query=SELECT codice as id, CONCAT_WS(' - ',codice,descrizione) as text FROM fe_stati_documento", "value": "$codice_stato_fe$", "disabled": , "class": "unblockable", "help": "", "disabled": "" ]} - + {[ "type": "select", "label": "", "name": "codice_stato_fe", "required": 0, "values": "query=SELECT codice as id, CONCAT_WS(' - ',codice,descrizione) as text FROM fe_stati_documento", "value": "$codice_stato_fe$", "disabled": , "class": "unblockable", "help": "" ]} +
+ + - + +
+ + {[ "type": "select", "label": "", "name": "idstatodocumento", "required": 1, "values": "query=", "value": "$idstatodocumento$", "class": "unblockable", "extra": " onchange = \"if ($('#idstatodocumento option:selected').text()=='Pagato' || $('#idstatodocumento option:selected').text()=='Parzialmente pagato' ){if( confirm('') ){ return true; }else{ $('#idstatodocumento').selectSet(); }}\" " ]} +
@@ -191,11 +234,6 @@ if (empty($record['is_fiscale'])) { } ?> -
- - {[ "type": "select", "label": "", "name": "idstatodocumento", "required": 1, "values": "query=", "value": "$idstatodocumento$", "class": "unblockable", "extra": " onchange = \"if ($('#idstatodocumento option:selected').text()=='Pagato' || $('#idstatodocumento option:selected').text()=='Parzialmente pagato' ){if( confirm('') ){ return true; }else{ $('#idstatodocumento').selectSet(); }}\" " ]} -
-
@@ -286,6 +324,17 @@ if (empty($record['is_fiscale'])) {
{[ "type": "select", "label": "", "name": "id_ritenuta_contributi", "value": "$id_ritenuta_contributi$", "values": "query=SELECT * FROM co_ritenuta_contributi" ]}
+ + +
+ {[ "type": "select", "label": "", "name": "id_dichiarazione_intento", "values": "query=SELECT id, CONCAT_WS(' - ', numero_protocollo, numero_progressivo) as text FROM co_dichiarazioni_intento WHERE deleted_at IS NULL AND id_anagrafica = $idanagrafica$", "value": "$id_dichiarazione_intento$" ]} +
+ +
diff --git a/modules/fatture/row-add.php b/modules/fatture/row-add.php index cfeb2d0a6..a1535e4cf 100644 --- a/modules/fatture/row-add.php +++ b/modules/fatture/row-add.php @@ -40,6 +40,10 @@ $result = [ $iva = $dbo->fetchArray('SELECT idiva_'.($dir == 'uscita' ? 'acquisti' : 'vendite').' AS idiva FROM an_anagrafiche WHERE idanagrafica='.prepare($documento['idanagrafica'])); $result['idiva'] = $iva[0]['idiva'] ?: setting('Iva predefinita'); +if (!empty($documento->dichiarazione)) { + $result['idiva'] = setting("Iva per lettere d'intento"); +} + // Leggo la ritenuta d'acconto predefinita per l'anagrafica e se non c'è leggo quella predefinita generica // id_ritenuta_acconto_vendite oppure id_ritenuta_acconto_acquisti $ritenuta_acconto = $dbo->fetchOne('SELECT id_ritenuta_acconto_'.($dir == 'uscita' ? 'acquisti' : 'vendite').' AS id_ritenuta_acconto FROM an_anagrafiche WHERE idanagrafica='.prepare($documento['idanagrafica'])); diff --git a/modules/fatture/src/Fattura.php b/modules/fatture/src/Fattura.php index b40530eae..1499a15fc 100644 --- a/modules/fatture/src/Fattura.php +++ b/modules/fatture/src/Fattura.php @@ -3,6 +3,8 @@ namespace Modules\Fatture; use Auth; +use Carbon\Carbon; +use Common\Components\Description; use Common\Document; use Modules\Anagrafiche\Anagrafica; use Modules\Fatture\Components\Riga; @@ -10,6 +12,7 @@ use Modules\Pagamenti\Pagamento; use Modules\PrimaNota\Movimento; use Modules\RitenuteContributi\RitenutaContributi; use Modules\Scadenzario\Scadenza; +use Plugins\DichiarazioniIntento\Dichiarazione; use Plugins\ExportFE\FatturaElettronica; use Traits\RecordTrait; use Util\Generator; @@ -41,8 +44,6 @@ class Fattura extends Document $user = Auth::user(); $stato_documento = Stato::where('descrizione', 'Bozza')->first(); - - $id_anagrafica = $anagrafica->id; $direzione = $tipo_documento->dir; $database = database(); @@ -55,28 +56,6 @@ class Fattura extends Document $conto = 'acquisti'; } - // Tipo di pagamento e banca predefinite dall'anagrafica - $id_pagamento = $database->fetchOne('SELECT id FROM co_pagamenti WHERE id = :id_pagamento', [ - ':id_pagamento' => $anagrafica['idpagamento_'.$conto], - ])['id']; - $id_banca = $anagrafica['idbanca_'.$conto]; - - $split_payment = $anagrafica->split_payment; - - // 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 = $database->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 = $anagrafica->idsede_fatturazione; - $model->anagrafica()->associate($anagrafica); $model->tipo()->associate($tipo_documento); $model->stato()->associate($stato_documento); @@ -102,6 +81,24 @@ class Fattura extends Document $id_ritenuta_contributi = ($tipo_documento->dir == 'entrata') ? setting('Ritenuta contributi') : null; $model->id_ritenuta_contributi = $id_ritenuta_contributi ?: null; + // Tipo di pagamento e banca predefinite dall'anagrafica + $id_pagamento = $database->fetchOne('SELECT id FROM co_pagamenti WHERE id = :id_pagamento', [ + ':id_pagamento' => $anagrafica['idpagamento_'.$conto], + ])['id']; + $id_banca = $anagrafica['idbanca_'.$conto]; + + // 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 = $database->fetchOne('SELECT id FROM co_banche WHERE id_pianodeiconti3 = (SELECT idconto_'.$conto.' FROM co_pagamenti WHERE id = :id_pagamento)', [ + ':id_pagamento' => $id_pagamento, + ])['id']; + } + if (!empty($id_pagamento)) { $model->idpagamento = $id_pagamento; } @@ -109,15 +106,38 @@ class Fattura extends Document $model->idbanca = $id_banca; } + // Split Payment + $split_payment = $anagrafica->split_payment; if (!empty($split_payment)) { $model->split_payment = $split_payment; } + // Dichiarazione d'Intento + $now = new Carbon(); + $dichiarazione = $anagrafica->dichiarazioni() + ->where('massimale', '>', 'totale') + ->where('data_inizio', '<', $now) + ->where('data_fine', '>', $now) + ->first(); + if (!empty($dichiarazione)) { + $model->dichiarazione()->associate($dichiarazione); + + $model->note = tr("Operazione non imponibile come da vostra dichiarazione d'intento nr _PROT_ del _PROT_DATE_ emessa in data _RELEASE_DATE_, da noi registrata al nr _ID_ del _DATE_", [ + '_PROT_' => $dichiarazione->numero_protocollo, + '_PROT_DATE_' => $dichiarazione->data_protocollo, + '_RELEASE_DATE_' => $dichiarazione->data_emissione, + '_ID_' => $dichiarazione->id, + '_DATE_' => $dichiarazione->data, + ]).'.'; + } + $model->save(); return $model; } + // Attributi Eloquent + /** * Imposta il sezionale relativo alla fattura e calcola il relativo numero. * **Attenzione**: la data deve inserita prima! @@ -250,14 +270,19 @@ class Fattura extends Document return $this->belongsTo(Tipo::class, 'idtipodocumento'); } + public function stato() + { + return $this->belongsTo(Stato::class, 'idstatodocumento'); + } + public function pagamento() { return $this->belongsTo(Pagamento::class, 'idpagamento'); } - public function stato() + public function dichiarazione() { - return $this->belongsTo(Stato::class, 'idstatodocumento'); + return $this->belongsTo(Dichiarazione::class, 'id_dichiarazione_intento'); } public function statoFE() @@ -307,6 +332,23 @@ class Fattura extends Document // Metodi generali + public function triggerComponent(Description $trigger) + { + parent::triggerComponent($trigger); + + // Correzione del totale della dichiarazione d'intento + $dichiarazione = $this->dichiarazione; + if (!empty($dichiarazione)) { + $dichiarazione->fixTotale(); + $dichiarazione->save(); + } + } + + /** + * Restituisce i contenuti della fattura elettronica relativa al documento. + * + * @return false|string + */ public function getXML() { if (empty($this->progressivo_invio) && $this->module == 'Fatture di acquisto') { @@ -320,6 +362,11 @@ class Fattura extends Document return file_get_contents($file->filepath); } + /** + * Controlla se la fattura di acquisto è elettronica. + * + * @return bool + */ public function isFE() { $file = $this->uploads()->where('name', 'Fattura Elettronica')->first(); @@ -511,6 +558,11 @@ class Fattura extends Document return $result; } + /** + * Metodo per calcolare automaticamente il bollo da applicare al documento. + * + * @return float + */ public function getBollo() { if (isset($this->bollo)) { @@ -536,6 +588,9 @@ class Fattura extends Document return $marca_da_bollo; } + /** + * Metodo per aggiornare ed eventualemente aggiungere la marca da bollo al documento. + */ public function manageRigaMarcaDaBollo() { $riga = $this->rigaBollo; diff --git a/plugins/dichiarazioni_intento/src/Dichiarazione.php b/plugins/dichiarazioni_intento/src/Dichiarazione.php index 20f99ca4c..916cd434c 100644 --- a/plugins/dichiarazioni_intento/src/Dichiarazione.php +++ b/plugins/dichiarazioni_intento/src/Dichiarazione.php @@ -5,6 +5,7 @@ namespace Plugins\DichiarazioniIntento; use Common\Model; use Illuminate\Database\Eloquent\SoftDeletes; use Modules\Anagrafiche\Anagrafica; +use Modules\Fatture\Fattura; /** * Classe per la gestione delle dichiarazione d'intento. @@ -46,8 +47,38 @@ class Dichiarazione extends Model return $model; } + /** + * Metodo per ricalcolare il totale utlizzato della dichiarazione. + */ + public function fixTotale() + { + $this->setRelations([]); + + $righe = collect(); + $fatture = $this->fatture; + foreach ($fatture as $fattura) { + $righe = $righe->merge($fattura->getRighe()); + } + + // Filtro delle righe per IVA + $id_iva = setting("Iva per lettere d'intento"); + $righe_dichiarazione = $righe->filter(function ($item, $key) use ($id_iva) { + return $item->aliquota != null && $item->aliquota->id == $id_iva; + }); + + $totale = $righe_dichiarazione->sum('totale_imponibile') ?: 0; + $this->totale = $totale; + } + + // Relazioni Eloquent + public function anagrafica() { return $this->belongsTo(Anagrafica::class, 'id_anagrafica'); } + + public function fatture() + { + return $this->hasMany(Fattura::class, 'id_dichiarazione_intento'); + } } diff --git a/update/2_4_11.sql b/update/2_4_11.sql index adc6ff370..e6bee20cf 100644 --- a/update/2_4_11.sql +++ b/update/2_4_11.sql @@ -643,3 +643,5 @@ ALTER TABLE `co_documenti` ADD `id_dichiarazione_intento` int(11), ADD FOREIGN K INSERT INTO `zz_plugins` (`id`, `name`, `title`, `idmodule_from`, `idmodule_to`, `position`, `script`, `enabled`, `default`, `order`, `compatibility`, `version`, `options2`, `options`, `directory`, `help`) VALUES (NULL, 'Dichiarazioni d''Intento', 'Dichiarazioni d''Intento', (SELECT id FROM zz_modules WHERE name = 'Fatture di vendita'), (SELECT id FROM zz_modules WHERE name='Anagrafiche'), 'tab', '', '1', '1', '0', '', '', NULL, '{ "main_query": [ { "type": "table", "fields": "Protocollo, Progressivo, Massimale, Totale, Data inizio, Data fine", "query": "SELECT id, numero_protocollo AS Protocollo, numero_progressivo AS Progressivo, DATE_FORMAT(data_inizio,''%d/%m/%Y'') AS ''Data inizio'', DATE_FORMAT(data_inizio,''%d/%m/%Y'') AS ''Data fine'', ROUND(massimale, 2) AS Massimale, ROUND(totale, 2) AS Totale FROM co_dichiarazioni_intento WHERE 1=1 AND deleted_at IS NULL AND id_anagrafica = |id_parent| HAVING 2=2 ORDER BY co_dichiarazioni_intento.id DESC"} ]}', 'dichiarazioni_intento', ''); +INSERT INTO `zz_settings` (`id`, `nome`, `valore`, `tipo`, `editable`, `sezione`, `order`) VALUES +(NULL, 'Iva per lettere d''intento', '', 'query=SELECT id, descrizione FROM `co_iva` WHERE codice_natura_fe = ''N3'' AND deleted_at IS NULL ORDER BY descrizione ASC', 1, 'Fatturazione', 11);