invoiceninja/app/Services/EDocument/Standards/Verifactu/RegistroAlta.php

285 lines
12 KiB
PHP

<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2025. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Services\EDocument\Standards\Verifactu;
use App\Models\Company;
use App\Models\Invoice;
use App\Models\Product;
use App\Models\VerifactuLog;
use App\Helpers\Invoice\Taxer;
use App\Utils\Traits\MakesHash;
use App\DataMapper\Tax\BaseRule;
use App\Services\AbstractService;
use App\Helpers\Invoice\InvoiceSum;
use App\Utils\Traits\NumberFormatter;
use App\Helpers\Invoice\InvoiceSumInclusive;
use App\Services\EDocument\Standards\Verifactu\Models\IDOtro;
use App\Services\EDocument\Standards\Verifactu\Models\Desglose;
use App\Services\EDocument\Standards\Verifactu\Models\IDFactura;
use App\Services\EDocument\Standards\Verifactu\Models\Encadenamiento;
use App\Services\EDocument\Standards\Verifactu\Models\RegistroAnterior;
use App\Services\EDocument\Standards\Verifactu\Models\SistemaInformatico;
use App\Services\EDocument\Standards\Verifactu\Models\PersonaFisicaJuridica;
use App\Services\EDocument\Standards\Verifactu\Models\Invoice as VerifactuInvoice;
class RegistroAlta
{
use Taxer;
use NumberFormatter;
use MakesHash;
private Company $company;
private InvoiceSum | InvoiceSumInclusive $calc;
private VerifactuInvoice $v_invoice;
private ?VerifactuLog $v_log;
private array $tax_map = [];
private float $allowance_total = 0;
private array $errors = [];
private string $current_timestamp;
private array $impuesto_codes = [
'01' => 'IVA (Impuesto sobre el Valor Añadido)', // Value Added Tax - Standard Spanish VAT
'02' => 'IPSI (Impuesto sobre la Producción, los Servicios y la Importación)', // Production, Services and Import Tax - Ceuta and Melilla
'03' => 'IGIC (Impuesto General Indirecto Canario)', // Canary Islands General Indirect Tax
'05' => 'Otros (Others)' // Other taxes
];
private array $clave_regimen_codes = [
'01' => 'Régimen General', // General Regime - Standard VAT regime for most businesses
'02' => 'Régimen Simplificado', // Simplified Regime - For small businesses with simplified accounting
'03' => 'Régimen Especial de Agrupaciones de Módulos', // Special Module Grouping Regime - For agricultural activities
'04' => 'Régimen Especial del Recargo de Equivalencia', // Special Equivalence Surcharge Regime - For retailers
'05' => 'Régimen Especial de las Agencias de Viajes', // Special Travel Agencies Regime
'06' => 'Régimen Especial de los Bienes Usados', // Special Used Goods Regime
'07' => 'Régimen Especial de los Objetos de Arte', // Special Art Objects Regime
'08' => 'Régimen Especial de las Antigüedades', // Special Antiques Regime
'09' => 'Régimen Especial de los Objetos de Colección', // Special Collectibles Regime
'10' => 'Régimen Especial de los Bienes de Inversión', // Special Investment Goods Regime
'11' => 'Régimen Especial de los Servicios', // Special Services Regime
'12' => 'Régimen Especial de los Bienes de Inversión y Servicios', // Special Investment Goods and Services Regime
'13' => 'Régimen Especial de los Bienes de Inversión y Servicios (Inversión del Sujeto Pasivo)', // Special Investment Goods and Services Regime (Reverse Charge)
'14' => 'Régimen Especial de los Bienes de Inversión y Servicios (Inversión del Sujeto Pasivo - Bienes de Inversión)', // Special Investment Goods and Services Regime (Reverse Charge - Investment Goods)
'15' => 'Régimen Especial de los Bienes de Inversión y Servicios (Inversión del Sujeto Pasivo - Servicios)', // Special Investment Goods and Services Regime (Reverse Charge - Services)
'16' => 'Régimen Especial de los Bienes de Inversión y Servicios (Inversión del Sujeto Pasivo - Bienes de Inversión y Servicios)', // Special Investment Goods and Services Regime (Reverse Charge - Investment Goods and Services)
'17' => 'Régimen Especial de los Bienes de Inversión y Servicios (Inversión del Sujeto Pasivo - Bienes de Inversión y Servicios - Inversión del Sujeto Pasivo)', // Special Investment Goods and Services Regime (Reverse Charge - Investment Goods and Services - Reverse Charge)
'18' => 'Régimen Especial de los Bienes de Inversión y Servicios (Inversión del Sujeto Pasivo - Bienes de Inversión y Servicios - Inversión del Sujeto Pasivo - Bienes de Inversión)', // Special Investment Goods and Services Regime (Reverse Charge - Investment Goods and Services - Reverse Charge - Investment Goods)
'19' => 'Régimen Especial de los Bienes de Inversión y Servicios (Inversión del Sujeto Pasivo - Bienes de Inversión y Servicios - Inversión del Sujeto Pasivo - Servicios)', // Special Investment Goods and Services Regime (Reverse Charge - Investment Goods and Services - Reverse Charge - Services)
'20' => 'Régimen Especial de los Bienes de Inversión y Servicios (Inversión del Sujeto Pasivo - Bienes de Inversión y Servicios - Inversión del Sujeto Pasivo - Bienes de Inversión y Servicios)' // Special Investment Goods and Services Regime (Reverse Charge - Investment Goods and Services - Reverse Charge - Investment Goods and Services)
];
private array $calificacion_operacion_codes = [
'S1' => 'OPERACIÓN SUJETA Y NO EXENTA - SIN INVERSIÓN DEL SUJETO PASIVO', // Subject and Non-Exempt Operation - Without Reverse Charge
'S2' => 'OPERACIÓN SUJETA Y NO EXENTA - CON INVERSIÓN DEL SUJETO PASIVO', // Subject and Non-Exempt Operation - With Reverse Charge
'N1' => 'OPERACIÓN NO SUJETA ARTÍCULO 7, 14, OTROS', // Non-Subject Operation Article 7, 14, Others
'N2' => 'OPERACIÓN NO SUJETA POR REGLAS DE LOCALIZACIÓN' // Non-Subject Operation by Location Rules
];
public function __construct(public Invoice $invoice)
{
$this->company = $invoice->company;
$this->calc = $this->invoice->calc();
$this->v_invoice = new VerifactuInvoice();
}
/**
* Entry point for building document
*
* @return self
*/
public function run(): self
{
// Get the previous invoice log
$this->v_log = $this->company->verifactu_logs()->first();
$this->current_timestamp = now()->format('Y-m-d\TH:i:sP');
$this->v_invoice
->setIdVersion('1.0')
->setIdFactura((new IDFactura())
->setIdEmisorFactura($this->company->settings->vat_number)
->setNumSerieFactura($this->invoice->number)
->setFechaExpedicionFactura(\Carbon\Carbon::parse($this->invoice->date)->format('d-m-Y')))
->setNombreRazonEmisor($this->company->present()->name()) //company name
->setTipoFactura('F1') //invoice type
->setDescripcionOperacion('Alta')// It IS! manadatory - max chars 500
->setCuotaTotal($this->invoice->total_taxes) //total taxes
->setImporteTotal($this->invoice->amount) //total invoice amount
->setFechaHoraHusoGenRegistro($this->current_timestamp) //creation/submission timestamp
->setTipoHuella('01') //sha256
->setHuella('PLACEHOLDER_HUELLA');
/** The business entity that is issuing the invoice */
$emisor = new PersonaFisicaJuridica();
$emisor->setNif($this->company->settings->vat_number)
->setNombreRazon($this->invoice->company->present()->name());
/** The business entity (Client) that is receiving the invoice */
$destinatarios = [];
$destinatario = new PersonaFisicaJuridica();
if($this->invoice->client->country_id == 724) {
$destinatario
->setNif($this->invoice->client->vat_number)
->setNombreRazon($this->invoice->client->present()->name());
}
else {
$locationData = $this->invoice->location();
$destinatario = new IDOtro();
$destinatario->setCodigoPais($locationData['country_code']);
$br = new \App\DataMapper\Tax\BaseRule();
if(in_array($locationData['country_code'], $br->eu_country_codes) && strlen($this->invoice->client->vat_number ?? '') > 0) {
$destinatario->setIdType('03');
$destinatario->setId($this->invoice->client->vat_number);
}
}
$destinatarios[] = $destinatario;
$this->v_invoice->setDestinatarios($destinatarios);
// The tax breakdown
$desglose = new Desglose();
//Combine the line taxes with invoice taxes here to get a total tax amount
$taxes = $this->calc->getTaxMap();
$desglose_iva = [];
foreach ($taxes as $tax) {
$desglose_iva[] = [
'Impuesto' => $this->calculateTaxType($tax['name']), //tax type
'ClaveRegimen' => '01', //tax regime classification code
'CalificacionOperacion' => 'S1', //operation classification code
'BaseImponibleOimporteNoSujeto' => $tax['base_amount'] ?? $this->calc->getNetSubtotal(), // taxable base amount
'TipoImpositivo' => $tax['tax_rate'], // Tax Rate
'CuotaRepercutida' => $tax['total'] // Tax Amount
];
};
$desglose->setDesgloseIVA($desglose_iva);
$this->v_invoice->setDesglose($desglose);
// Encadenamiento
$encadenamiento = new Encadenamiento();
// We chain the previous hash to the current invoice to ensure consistency
if($this->v_log){
$registro_anterior = new RegistroAnterior();
$registro_anterior->setIDEmisorFactura($this->v_log->nif);
$registro_anterior->setNumSerieFactura($this->v_log->invoice_number);
$registro_anterior->setFechaExpedicionFactura($this->v_log->date->format('d-m-Y'));
$registro_anterior->setHuella($this->v_log->hash);
$encadenamiento->setRegistroAnterior($registro_anterior);
}
else {
$encadenamiento->setPrimerRegistro('S');
}
$this->v_invoice->setEncadenamiento($encadenamiento);
//Sending system information - We automatically generate the obligado emision from this later
$sistema = new SistemaInformatico();
$sistema
// ->setNombreRazon('Sistema de Facturación')
->setNombreRazon(config('services.verifactu.sender_name')) //must match the cert name
->setNif(config('services.verifactu.sender_nif'))
->setNombreSistemaInformatico('InvoiceNinja')
->setIdSistemaInformatico('77')
->setVersion('1.0.03')
->setNumeroInstalacion('383')
->setTipoUsoPosibleSoloVerifactu('N')
->setTipoUsoPosibleMultiOT('S')
->setIndicadorMultiplesOT('S');
$this->v_invoice->setSistemaInformatico($sistema);
return $this;
}
public function setRectification(): self
{
$this->v_invoice->setTipoFactura('R2');
$this->v_invoice->setTipoRectificativa('I'); // S for substitutive rectification
//need to harvest the parent invoice!!
$_i = Invoice::withTrashed()->find($this->decodePrimaryKey($this->invoice->backup->parent_invoice_id));
if(!$_i) {
throw new \Exception('Parent invoice not found');
}
// Set up rectified invoice information
$facturasRectificadas = [
[
'IDEmisorFactura' => $this->company->settings->vat_number,
'NumSerieFactura' => $_i->number,
'FechaExpedicionFactura' => \Carbon\Carbon::parse($_i->date)->format('d-m-Y')
]
];
$this->v_invoice->setFacturasRectificadas($facturasRectificadas);
$this->invoice->backup->document_type = 'R2';
$this->invoice->save();
return $this;
}
public function getInvoice(): VerifactuInvoice
{
return $this->v_invoice;
}
private function calculateTaxType(string $tax_name): string
{
if(stripos($tax_name, 'iva') !== false) {
return '01';
}
if(stripos($tax_name, 'igic') !== false) {
return '03';
}
if(stripos($tax_name, 'ipsi') !== false) {
return '02';
}
if(stripos($tax_name, 'otros') !== false) {
return '05';
}
return '01';
}
}