. */ namespace Plugins\ImportFE; use Common\Components\Component; use Modules\Anagrafiche\Anagrafica; use Modules\Articoli\Articolo as ArticoloOriginale; use Modules\Articoli\Categoria; use Modules\Fatture\Components\Articolo; use Modules\Fatture\Components\Descrizione; use Modules\Fatture\Components\Riga; use Modules\Fatture\Fattura; use Plugins\ListinoClienti\DettaglioPrezzo; use Util\XML; /** * Classe per la gestione della fatturazione elettronica in XML. * * @since 2.4.2 */ class FatturaOrdinaria extends FatturaElettronica { public function __construct($name) { parent::__construct($name); if ($this->getHeader()['DatiTrasmissione']['FormatoTrasmissione'] == 'FSM10') { throw new \UnexpectedValueException(); } } public function getAnagrafe() { $dati = $this->getHeader()['CedentePrestatore']; $anagrafe = $dati['DatiAnagrafici']; $rea = $dati['IscrizioneREA']; $sede = $dati['Sede']; $contatti = $dati['Contatti']; $info = [ 'partita_iva' => $anagrafe['IdFiscaleIVA']['IdCodice'], 'codice_fiscale' => $anagrafe['CodiceFiscale'], 'ragione_sociale' => $anagrafe['Anagrafica']['Denominazione'], 'nome' => $anagrafe['Anagrafica']['Nome'], 'cognome' => $anagrafe['Anagrafica']['Cognome'], 'rea' => [ 'codice' => (!empty($dati['IscrizioneREA']) ? $rea['Ufficio'].'-'.$rea['NumeroREA'] : ''), 'capitale_sociale' => $rea['CapitaleSociale'], ], 'sede' => [ 'indirizzo' => $sede['Indirizzo'].' '.$sede['NumeroCivico'], 'cap' => $sede['CAP'], 'citta' => $sede['Comune'], 'provincia' => $sede['Provincia'], 'nazione' => $sede['Nazione'], ], 'contatti' => [ 'telefono' => $contatti['Telefono'], 'fax' => $contatti['Fax'], 'email' => $contatti['email'], ], ]; return $info; } public function getRighe() { $linee = $this->getBody()['DatiBeniServizi']['DettaglioLinee']; $linee = $this->forceArray($linee); $cassa_previdenziale = $this->getBody()['DatiGenerali']['DatiGeneraliDocumento']['DatiCassaPrevidenziale']; $imponibile = []; $totale_imposta = []; $importo = 0; foreach ($linee as $linea) { $importo = $linea['PrezzoUnitario'] * $linea['Quantita']; if ($linea['ScontoMaggiorazione']) { $linea['ScontoMaggiorazione'] = $this->forceArray($linea['ScontoMaggiorazione']); foreach ($linea['ScontoMaggiorazione'] as $sm) { if (isset($sm['Percentuale'])) { $sconto = ($importo * $sm['Percentuale'] / 100); if ($sm['Tipo'] == 'SC') { $importo -= $sconto; } else { $importo += $sconto; } } elseif (isset($sm['Importo'])) { if ($sm['Tipo'] == 'SC') { $importo -= $sm['Importo']; } else { $importo += $sm['Importo']; } } } } if (!$linea['Quantita']) { $importo = $linea['PrezzoUnitario']; } $imponibile[$linea['AliquotaIVA']] = ($imponibile[$linea['AliquotaIVA']] ?? 0) + round($importo, 2); } // Aggiunta degli arrotondamenti IVA come righe indipendenti $riepiloghi = $this->getBody()['DatiBeniServizi']['DatiRiepilogo']; $riepiloghi = $this->forceArray($riepiloghi); foreach ($riepiloghi as $riepilogo) { $aliquota_iva = $riepilogo['AliquotaIVA']; $imponibile[$aliquota_iva] += (float) $riepilogo['Arrotondamento']; } foreach ($imponibile as $aliquota_iva => $importo) { $totale_imposta[$aliquota_iva] = $imponibile[$aliquota_iva] * $aliquota_iva / 100; if ($cassa_previdenziale['AliquotaIVA'] == $aliquota_iva) { $totale_imposta[$aliquota_iva] += $cassa_previdenziale['ImportoContributoCassa'] * $cassa_previdenziale['AliquotaIVA'] / 100; } } $riepiloghi_raggruppati = []; foreach ($riepiloghi as $riepilogo) { $aliquota_iva = $riepilogo['AliquotaIVA']; if (array_key_exists($aliquota_iva, $riepiloghi_raggruppati)) { $riepiloghi_raggruppati[$aliquota_iva]['ImponibileImporto'] += $riepilogo['ImponibileImporto']; $riepiloghi_raggruppati[$aliquota_iva]['Imposta'] += $riepilogo['Imposta']; } else { $riepiloghi_raggruppati[$aliquota_iva] = $riepilogo; } } foreach ($riepiloghi_raggruppati as $riepilogo) { $valore = 0; $diff_iva = round((float) $riepilogo['Imposta'] - $totale_imposta[$riepilogo['AliquotaIVA']], 2); if ($diff_iva) { $valore = $diff_iva * 100 / $riepilogo['AliquotaIVA']; } if ($valore != 0) { $descrizione = tr('Arrotondamento IVA _VALUE_', [ '_VALUE_' => empty($riepilogo['Natura']) ? numberFormat($riepilogo['AliquotaIVA']).'%' : $riepilogo['Natura'], ]); $linee[] = [ 'Descrizione' => $descrizione, 'PrezzoUnitario' => $valore, 'Quantita' => 1, 'AliquotaIVA' => $riepilogo['AliquotaIVA'], 'Natura' => $riepilogo['Natura'], ]; } } return $this->forceArray($linee); } public function saveRighe($articoli, $iva, $conto, $movimentazione = true, $crea_articoli = [], $tipi_riferimenti = [], $id_riferimenti = [], $tipi_riferimenti_vendita = [], $id_riferimenti_vendita = [], $update_info = [], $serials = []) { $info = $this->getRitenutaRivalsa(); $righe = $this->getRighe(); $fattura = $this->getFattura(); $anagrafica = Anagrafica::find($fattura->idanagrafica); $direzione = 'uscita'; $id_ritenuta_acconto = $info['id_ritenuta_acconto']; $id_rivalsa = $info['id_rivalsa']; $calcolo_ritenuta_acconto = $info['rivalsa_in_ritenuta'] ? 'IMP+RIV' : 'IMP'; $ritenuta_contributi = !empty($fattura->id_ritenuta_contributi); $conto_arrotondamenti = null; $tot_arr = 0; foreach ($righe as $riga) { if (strpos($riga['Descrizione'], 'Arrotondamento') !== false) { $tot_arr += $riga['PrezzoUnitario']; } } // Disattivo temporaneamente l'impostazione per evadere solo le quantità previste $original_setting_evasione = setting('Permetti il superamento della soglia quantità dei documenti di origine'); \Settings::setValue('Permetti il superamento della soglia quantità dei documenti di origine', 1); foreach ($righe as $key => $riga) { $articolo = ArticoloOriginale::find($articoli[$key]); $riga['PrezzoUnitario'] = floatval($riga['PrezzoUnitario']); $riga['Quantita'] = floatval($riga['Quantita']); $is_descrizione = empty($riga['Quantita']) && empty($riga['PrezzoUnitario']); $codici = $riga['CodiceArticolo'] ?: []; $codici = !empty($codici) && !isset($codici[0]) ? [$codici] : $codici; // Creazione articolo relativo if (!empty($codici) && !empty($crea_articoli[$key]) && empty($articolo)) { $codice = $codici[0]['CodiceValore']; $articolo = ArticoloOriginale::where('codice', $codice)->first(); if (empty($articolo)) { $nome_categoria = 'Importazione automatica'; $categoria = Categoria::find((new Categoria())->getByField('name', strtolower($nome_categoria))); if (empty($categoria)) { $categoria = Categoria::build(); $categoria->setTranslation('nome', $nome_categoria); $categoria->save(); } $articolo = ArticoloOriginale::build($codice, $categoria); $articolo->setTranslation('descrizione', $riga['Descrizione']); $articolo->um = $riga['UnitaMisura']; $articolo->idconto_acquisto = $conto[$key]; $articolo->abilita_serial = setting('Serial number abilitato di default'); $articolo->save(); } } if (!empty($articolo)) { $articolo->idconto_acquisto = $conto[$key]; $articolo->save(); $obj = Articolo::build($fattura, $articolo); $obj->movimentazione($movimentazione); $target_type = Articolo::class; } elseif ($is_descrizione) { $obj = Descrizione::build($fattura); $target_type = Descrizione::class; } else { $obj = Riga::build($fattura); $target_type = Riga::class; } $obj->descrizione = $riga['Descrizione']; $obj->save(); // Collegamento al documento di riferimento $has_serial_riferimento = false; if (!empty($tipi_riferimenti[$key]) && is_subclass_of($tipi_riferimenti[$key], Component::class) && !empty($id_riferimenti[$key])) { $riga_origine = ($tipi_riferimenti[$key])::find($id_riferimenti[$key]); list($riferimento_precedente, $nuovo_riferimento) = $obj->impostaOrigine($riga_origine); // Correzione della descrizione $obj->descrizione = str_replace($riferimento_precedente, '', $obj->descrizione); $obj->descrizione .= $nuovo_riferimento; $serials_rif = $riga_origine->serials; if ($serials_rif && $obj->abilita_serial) { $obj->serials = $serials_rif; $has_serial_riferimento = true; } } if (!empty($tipi_riferimenti_vendita[$key])) { database()->insert('co_riferimenti_righe', [ 'source_type' => $tipi_riferimenti_vendita[$key], 'source_id' => $id_riferimenti_vendita[$key], 'target_type' => $target_type, 'target_id' => $obj->id, ]); } if (!$is_descrizione) { $obj->id_iva = $iva[$key]; $obj->idconto = $conto[$key]; if (empty($conto_arrotondamenti) && !empty($conto[$key])) { $conto_arrotondamenti = $conto[$key]; } $obj->ritenuta_contributi = $ritenuta_contributi; // Inserisco la ritenuta se è specificata nella riga o se non è specificata nella riga ma è presente in Dati ritenuta (quindi comprende tutte le righe) if (!empty($riga['Ritenuta']) || $info['ritenuta_norighe'] == true || $info['rivalsa_norighe'] == true) { $obj->id_ritenuta_acconto = $id_ritenuta_acconto; $obj->calcolo_ritenuta_acconto = $calcolo_ritenuta_acconto; $obj->id_rivalsa_inps = $id_rivalsa; } // Totale documento $totale_righe = 0; $totale_arrotondamento = 0; $totale_imp = 0; $dati_riepilogo = $this->getBody()['DatiBeniServizi']['DatiRiepilogo']; if (!empty($dati_riepilogo['ImponibileImporto'])) { $totale_arrotondamento = $dati_riepilogo['Arrotondamento']; $totale_imp = sum($dati_riepilogo['ImponibileImporto'], null, 2); } elseif (is_array($dati_riepilogo)) { foreach ($dati_riepilogo as $dato) { $totale_arrotondamento += $dato['Arrotondamento']; $totale_imp += $dato['ImponibileImporto']; } } $totali_righe = array_column($righe, 'PrezzoTotale'); $tot_righe = sum($totali_righe, null, 2); $totale_righe = round($tot_righe + $tot_arr + $totale_arrotondamento, 2); // Nel caso il prezzo sia negativo viene gestito attraverso l'inversione della quantità (come per le note di credito) // TODO: per migliorare la visualizzazione, sarebbe da lasciare negativo il prezzo e invertire gli sconti. if (!empty($articolo->um) && !empty($articolo->um_secondaria) && !empty((float) $articolo->fattore_um_secondaria) && $riga['UnitaMisura'] == $articolo->um_secondaria) { $qta = (($riga['Quantita'] ?: 1) / $articolo->fattore_um_secondaria); $prezzo = $totale_righe > 0 ? $totale_righe / $qta : -($totale_righe / $qta); } else { $qta = ($riga['Quantita'] ?: 1); $prezzo = $totale_righe > 0 ? $riga['PrezzoUnitario'] : -$riga['PrezzoUnitario']; } // Prezzo e quantità $obj->prezzo_unitario = $prezzo; $obj->qta = $qta; if (!empty($riga['UnitaMisura'])) { if (!empty($articolo->um) && !empty($articolo->um_secondaria) && !empty((float) $articolo->fattore_um_secondaria) && $riga['UnitaMisura'] == $articolo->um_secondaria) { $obj->um = $articolo->um; } else { $obj->um = $riga['UnitaMisura']; } } // Sconti e maggiorazioni $sconti = $riga['ScontoMaggiorazione']; if (!empty($sconti)) { $tot_sconto_calcolato = 0; $sconto_unitario = 0; $sconti = $sconti[0] ? $sconti : [$sconti]; // Determina il tipo di sconto in caso di sconti misti UNT e PRC foreach ($sconti as $sconto) { $tipo_sconto = !empty($sconto['Importo']) ? 'UNT' : 'PRC'; if (!empty($tipo) && $tipo_sconto != $tipo) { $tipo = 'UNT'; } else { $tipo = $tipo_sconto; } } foreach ($sconti as $sconto) { $unitario = $sconto['Importo'] ?: $sconto['Percentuale']; // Sconto o Maggiorazione $sconto_riga = ($sconto['Tipo'] == 'SC') ? $unitario : -$unitario; $tipo_sconto = !empty($sconto['Importo']) ? 'UNT' : 'PRC'; if ($tipo_sconto == 'PRC') { $sconto_calcolato = calcola_sconto([ 'sconto' => $sconto_riga, 'prezzo' => $sconto_unitario ? $obj->prezzo_unitario - ($tot_sconto_calcolato / $obj->qta) : $obj->prezzo_unitario, 'tipo' => 'PRC', 'qta' => $obj->qta, ]); if ($tipo == 'PRC') { $tot_sconto = $sconto_calcolato * 100 / $obj->imponibile; } else { $tot_sconto = $sconto_calcolato; } } else { $tot_sconto = $sconto_riga; } $tot_sconto_calcolato += $sconto_calcolato; $sconto_unitario += $tot_sconto; } $obj->setSconto($sconto_unitario, $tipo); } // Aggiornamento prezzo di acquisto e fornitore predefinito in base alle impostazioni if (!empty($articolo)) { if ($update_info[$key] == 'update_price' || $update_info[$key] == 'update_all') { $dettaglio_predefinito = DettaglioPrezzo::dettaglioPredefinito($articolo->id, $anagrafica->idanagrafica, $direzione) ->first(); // Aggiungo associazione fornitore-articolo se non presente if (empty($dettaglio_predefinito)) { $dettaglio_predefinito = DettaglioPrezzo::build($articolo, $anagrafica, $direzione); } // Imposto lo sconto nel listino solo se è una percentuale, se è un importo lo sottraggo dal prezzo if ($tipo == 'PRC') { $dettaglio_predefinito->sconto_percentuale = $sconto_unitario; $prezzo_unitario = $obj->prezzo_unitario; $prezzo_acquisto = $obj->prezzo_unitario - ($obj->prezzo_unitario * $sconto_unitario / 100); } else { $prezzo_unitario = $obj->prezzo_unitario - $sconto_unitario; $prezzo_acquisto = $prezzo_unitario; } // Aggiornamento listino $dettaglio_predefinito->setPrezzoUnitario($prezzo_unitario); $dettaglio_predefinito->save(); // Aggiornamento fornitore predefinito if ($update_info[$key] == 'update_all') { // Aggiornamento prezzo di acquisto e fornitore predefinito $articolo->prezzo_acquisto = $prezzo_acquisto; $articolo->id_fornitore = $anagrafica->idanagrafica; $articolo->save(); } } // Gestione seriali if ($serials[$key] && !$has_serial_riferimento) { $obj->serials = $serials[$key]; } } $tipo = null; $sconto_unitario = null; } $obj->save(); } // Ripristino l'impostazione iniziale di evasione quantità \Settings::setValue('Permetti il superamento della soglia quantità dei documenti di origine', $original_setting_evasione); // Ricaricamento della fattura $fattura->refresh(); // Arrotondamenti differenti nella fattura XML $riepiloghi = $this->getBody()['DatiBeniServizi']['DatiRiepilogo']; $riepiloghi = $this->forceArray($riepiloghi); $riep_imp = 0; foreach ($riepiloghi as $riepilogo) { $riep_imp += $riepilogo['Imposta']; } $diff_iva = round($riep_imp - $fattura->iva, 2); $diff = round(abs($fattura->totale_imponibile) - abs($totale_righe + $tot_arr), 2); $diff_tot = round($fattura->totale_imponibile + $fattura->rivalsa_inps - $totale_imp + $tot_arr, 2); $iva_arrotondamento = database()->fetchOne('SELECT * FROM `co_iva` WHERE `percentuale`= 0 AND `deleted_at` IS NULL LIMIT 1'); if (($diff != 0 && $diff != $diff_tot) || (($diff_tot != $diff) && !$diff_iva) || $diff_iva) { if ($diff != 0 && $diff != $diff_tot) { $diff *= 100 / (100 + $iva_arrotondamento['percentuale']); } elseif (($diff == $diff_tot) && !$diff_iva) { $diff = $totale_righe - $fattura->totale_imponibile; } elseif (($diff == $diff_tot) && $diff_iva) { $diff = $diff_iva; } else { $diff = -($diff_tot * 100) / (100 + $iva_arrotondamento['percentuale']); } $obj = Riga::build($fattura); $obj->descrizione = tr('Arrotondamento calcolato in automatico'); $obj->id_iva = $iva_arrotondamento['id']; $obj->idconto = $conto_arrotondamenti; $obj->prezzo_unitario = round($diff, 4); $obj->qta = 1; $obj->save(); } } protected function prepareFattura($id_tipo, $data, $data_registrazione, $id_sezionale, $ref_fattura) { $fattura = parent::prepareFattura($id_tipo, $data, $data_registrazione, $id_sezionale, $ref_fattura); $database = database(); $righe = $this->getRighe(); $totali = array_column($righe, 'PrezzoTotale'); $totale = sum($totali); foreach ($righe as $riga) { $dati = $riga['AltriDatiGestionali']; if (!empty($dati)) { $dati = isset($dati[0]) ? $dati : [$dati]; foreach ($dati as $dato) { if ($dato['TipoDato'] == 'CASSA-PREV') { $descrizione = $dato['RiferimentoTesto']; $importo = floatval($dato['RiferimentoNumero']); preg_match('/^(.+?) - (.+?) \((.+?)%\)$/', trim($descrizione), $m); $nome = ucwords(strtolower($m[2])); $percentuale = $m[3]; $totale_previsto = round($importo / $percentuale * 100, 2); $percentuale_importo = round($totale_previsto / $totale * 100, 2); $ritenuta_contributi = $database->fetchOne('SELECT * FROM`co_ritenuta_contributi` WHERE `percentuale` = '.prepare($percentuale).' AND `percentuale_imponibile` = '.prepare($percentuale_importo)); if (empty($ritenuta_contributi)) { $database->query('INSERT INTO `co_ritenuta_contributi` (`descrizione`, `percentuale`, `percentuale_imponibile`) VALUES ('.prepare($nome).', '.prepare($percentuale).', '.prepare($percentuale_importo).')'); } $ritenuta_contributi = $database->fetchOne('SELECT * FROM`co_ritenuta_contributi` WHERE `percentuale` = '.prepare($percentuale).' AND `percentuale_imponibile` = '.prepare($percentuale_importo)); $fattura->id_ritenuta_contributi = $ritenuta_contributi['id']; } } } } return $fattura; } protected function getRitenutaRivalsa() { $database = database(); $dati_generali = $this->getBody()['DatiGenerali']['DatiGeneraliDocumento']; // Righe $righe = $this->getRighe(); $rivalsa_in_ritenuta = false; // Rivalsa $casse = $dati_generali['DatiCassaPrevidenziale']; if (!empty($casse)) { $totale = 0; $rivalsa_norighe = true; $totale_norighe = 0; foreach ($righe as $riga) { if ($riga['Ritenuta']) { $totale += $riga['PrezzoTotale']; $rivalsa_norighe = false; } else { $totale_norighe += $riga['PrezzoTotale']; } } $casse = isset($casse[0]) ? $casse : [$casse]; $importi = []; foreach ($casse as $cassa) { $importi[] = floatval($cassa['ImportoContributoCassa']); if ($cassa['Ritenuta']) { $rivalsa_in_ritenuta = true; } } $importo = sum($importi); $percentuale = round($importo / ($rivalsa_norighe ? $totale_norighe : $totale) * 100, 2); $rivalsa = $database->fetchOne('SELECT * FROM`co_rivalse` WHERE `percentuale` = '.prepare($percentuale)); if (empty($rivalsa)) { $descrizione = tr('Rivalsa _PRC_%', [ '_PRC_' => numberFormat($percentuale), ]); $database->query('INSERT INTO `co_rivalse` (`descrizione`, `percentuale`) VALUES ('.prepare($descrizione).', '.prepare($percentuale).')'); } $rivalsa = $database->fetchOne('SELECT * FROM`co_rivalse` WHERE `percentuale` = '.prepare($percentuale)); $id_rivalsa = $rivalsa['id']; } $percentuale = 0; $importo = 0; // Ritenuta d'Acconto $ritenuta = $dati_generali['DatiRitenuta']; if (!empty($ritenuta)) { $totali = []; $ritenuta_norighe = true; foreach ($righe as $riga) { if (!empty($riga['Ritenuta'])) { $totali[] = $riga['PrezzoTotale']; $ritenuta_norighe = false; } } if (!empty($ritenuta)) { $ritenuta = $this->forceArray($ritenuta); foreach ($ritenuta as $rit) { $percentuale += floatval($rit['AliquotaRitenuta']); $importo += floatval($rit['ImportoRitenuta']); } } // Calcolo la ritenuta su tutte le righe se non è specificata su nessuna riga if (empty($totali)) { $totale = array_sum(array_column($righe, 'PrezzoTotale')); } else { $totale = sum($totali); } $totale_previsto = round($importo * 100 / $percentuale, 2); $percentuale_importo = round($totale_previsto / $totale * 100, 2); $percentuale_importo = min($percentuale_importo, 100); // Nota: Fix per la percentuale che superava il 100% nel caso di importi con Rivalsa compresa $ritenuta_acconto = $database->fetchOne('SELECT * FROM `co_ritenutaacconto` WHERE `percentuale` = '.prepare($percentuale).' AND `percentuale_imponibile` = '.prepare($percentuale_importo)); if (empty($ritenuta_acconto)) { $descrizione = tr('Ritenuta _PRC_% sul _TOT_%', [ '_PRC_' => numberFormat($percentuale), '_TOT_' => numberFormat($percentuale_importo), ]); $database->query('INSERT INTO `co_ritenutaacconto` (`descrizione`, `percentuale`, `percentuale_imponibile`) VALUES ('.prepare($descrizione).', '.prepare($percentuale).', '.prepare($percentuale_importo).')'); $ritenuta_acconto = $database->fetchOne('SELECT * FROM `co_ritenutaacconto` WHERE `percentuale` = '.prepare($percentuale).' AND `percentuale_imponibile` = '.prepare($percentuale_importo)); } $id_ritenuta_acconto = $ritenuta_acconto['id']; } return [ 'id_ritenuta_acconto' => $id_ritenuta_acconto, 'id_rivalsa' => $id_rivalsa, 'rivalsa_norighe' => $rivalsa_norighe, 'rivalsa_in_ritenuta' => $rivalsa_in_ritenuta, 'ritenuta_norighe' => $ritenuta_norighe, ]; } }