Skip to content

Commit

Permalink
Merge pull request #114 from josemmo/develop
Browse files Browse the repository at this point in the history
v1.7.5
  • Loading branch information
josemmo authored Jan 21, 2023
2 parents 82a85dd + cc6dfa6 commit b864968
Show file tree
Hide file tree
Showing 11 changed files with 340 additions and 15 deletions.
8 changes: 4 additions & 4 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,17 @@ jobs:
continue-on-error: ${{ matrix.experimental || false }}
strategy:
matrix:
php-version: ['5.6', '7.0', '7.1', '7.2', '7.3', '7.4', '8.0']
php-version: ['5.6', '7.0', '7.1', '7.2', '7.3', '7.4', '8.0', '8.1']
include:
- php-version: '8.1'
- php-version: '8.2'
test-ws: true
send-coverage: true
- php-version: '8.2'
- php-version: '8.3'
experimental: true
steps:
# Download code from repository
- name: Checkout code
uses: actions/checkout@v2
uses: actions/checkout@v3

# Setup PHP and Composer
- name: Setup PHP
Expand Down
20 changes: 20 additions & 0 deletions doc/anexos/constantes.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,15 @@ permalink: /anexos/constantes.html

---

## Tipos de documento

|Constante|Descripción|
|--------:|:----------|
|`Facturae::INVOICE_FULL`|Factura completa|
|`Facturae::INVOICE_SIMPLIFIED`|Factura simplificada|

---

## Modos de precisión

|Constante|Descripción|
Expand All @@ -26,6 +35,17 @@ permalink: /anexos/constantes.html

---

## Modos de rectificación

|Constante|Descripción|
|--------:|:----------|
|`CorrectiveDetails::METHOD_FULL`|Rectificación íntegra|
|`CorrectiveDetails::METHOD_DIFFERENCES`|Rectificación por diferencias|
|`CorrectiveDetails::METHOD_VOLUME_DISCOUNT`|Rectificación por descuento por volumen de operaciones durante un periodo|
|`CorrectiveDetails::METHOD_AEAT_AUTHORIZED`|Autorizadas por la Agencia Tributaria|

---

## Formas de pago

|Constante|Descripción|
Expand Down
56 changes: 56 additions & 0 deletions doc/propiedades/rectificativas.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
---
title: Rectificativas
parent: Propiedades de una factura
nav_order: 9
permalink: /propiedades/rectificativas.html
---

# Facturas rectificativas
Por defecto, todos los documentos generados con la librería son facturas originales. Para generar una factura original
**rectificativa** se deben añadir una serie de propiedades adicionales a través del método `$fac->setCorrective()`:
```php
$fac->setCorrective(new CorrectiveDetails([
// Serie y número de la factura a rectificar
"invoiceSeriesCode" => "EMP201712",
"invoiceNumber" => "0002",

// Código del motivo de la rectificación según:
// - RD 1496/2003 (del "01" al 16")
// - Art. 80 Ley 37/92 (del "80" al "85")
"reason" => "01",

// Periodo de tributación de la factura a rectificar
"taxPeriodStart" => "2017-10-01",
"taxPeriodEnd" => "2017-10-31",

// Modo del criterio empleado para la rectificación
"correctionMethod" => CorrectiveDetails::METHOD_FULL
]));
```

Las razones (valores de `reason`) admitidas en la especificación de FacturaE son:

- `01`: Número de la factura
- `02`: Serie de la factura
- `03`: Fecha expedición
- `04`: Nombre y apellidos/Razón Social-Emisor
- `05`: Nombre y apellidos/Razón Social-Receptor
- `06`: Identificación fiscal Emisor/obligado
- `07`: Identificación fiscal Receptor
- `08`: Domicilio Emisor/Obligado
- `09`: Domicilio Receptor
- `10`: Detalle Operación
- `11`: Porcentaje impositivo a aplicar
- `12`: Cuota tributaria a aplicar
- `13`: Fecha/Periodo a aplicar
- `14`: Clase de factura
- `15`: Literales legales
- `16`: Base imponible
- `80`: Cálculo de cuotas repercutidas
- `81`: Cálculo de cuotas retenidas
- `82`: Base imponible modificada por devolución de envases / embalajes
- `83`: Base imponible modificada por descuentos y bonificaciones
- `84`: Base imponible modificada por resolución firme, judicial o administrativa
- `85`: Base imponible modificada cuotas repercutidas no satisfechas. Auto de declaración de concurso

Los distintos modos de rectificación (valores de `correctionMethod`) se definen en las [constantes del anexo](../anexos/constantes.html#modos-de-rectificación).
157 changes: 157 additions & 0 deletions src/CorrectiveDetails.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
<?php
namespace josemmo\Facturae;

class CorrectiveDetails {
const METHOD_FULL = "01";
const METHOD_DIFFERENCES = "02";
const METHOD_VOLUME_DISCOUNT = "03";
const METHOD_AEAT_AUTHORIZED = "04";

/**
* Invoice number
* @var string|null
*/
public $invoiceNumber = null;

/**
* Invoice series code
* @var string|null
*/
public $invoiceSeriesCode = null;

/**
* Reason
* @var string
*/
public $reason = "01";

/**
* Reason description
*
* NOTE: Using a custom value might yield a non-compliant invoice.
*
* @var string|null
*/
public $reasonDescription = null;

/**
* Start of tax period (as UNIX timestamp or parseable date string)
* @var string|int|null
*/
public $taxPeriodStart = null;

/**
* End of tax period (as UNIX timestamp or parseable date string)
* @var string|int|null
*/
public $taxPeriodEnd = null;

/**
* Correction method
* @var string
*/
public $correctionMethod = self::METHOD_FULL;

/**
* Correction method description
*
* NOTE: Using a custom value might yield a non-compliant invoice.
*
* @var string|null
*/
public $correctionMethodDescription = null;

/**
* Class constructor
* @param array $properties Corrective details properties as an array
*/
public function __construct($properties=array()) {
foreach ($properties as $key=>$value) {
$this->{$key} = $value;
}
}

/**
* Get reason description
* @return string Reason description
*/
public function getReasonDescription() {
// Use custom value if available
if ($this->reasonDescription !== null) {
return $this->reasonDescription;
}

// Fallback to default value per specification
switch ($this->reason) {
case "01":
return "Número de la factura";
case "02":
return "Serie de la factura";
case "03":
return "Fecha expedición";
case "04":
return "Nombre y apellidos/Razón Social-Emisor";
case "05":
return "Nombre y apellidos/Razón Social-Receptor";
case "06":
return "Identificación fiscal Emisor/obligado";
case "07":
return "Identificación fiscal Receptor";
case "08":
return "Domicilio Emisor/Obligado";
case "09":
return "Domicilio Receptor";
case "10":
return "Detalle Operación";
case "11":
return "Porcentaje impositivo a aplicar";
case "12":
return "Cuota tributaria a aplicar";
case "13":
return "Fecha/Periodo a aplicar";
case "14":
return "Clase de factura";
case "15":
return "Literales legales";
case "16":
return "Base imponible";
case "80":
return "Cálculo de cuotas repercutidas";
case "81":
return "Cálculo de cuotas retenidas";
case "82":
return "Base imponible modificada por devolución de envases / embalajes";
case "83":
return "Base imponible modificada por descuentos y bonificaciones";
case "84":
return "Base imponible modificada por resolución firme, judicial o administrativa";
case "85":
return "Base imponible modificada cuotas repercutidas no satisfechas. Auto de declaración de concurso";
}
return "";
}

/**
* Get correction method description
* @return string Correction method description
*/
public function getCorrectionMethodDescription() {
// Use custom value if available
if ($this->correctionMethodDescription !== null) {
return $this->correctionMethodDescription;
}

// Fallback to default value per specification
switch ($this->correctionMethod) {
case self::METHOD_FULL:
return "Rectificación íntegra";
case self::METHOD_DIFFERENCES:
return "Rectificación por diferencias";
case self::METHOD_VOLUME_DISCOUNT:
return "Rectificación por descuento por volumen de operaciones durante un periodo";
case self::METHOD_AEAT_AUTHORIZED:
return "Autorizadas por la Agencia Tributaria";
}
return "";
}
}
5 changes: 4 additions & 1 deletion src/Facturae.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
* Class for creating electronic invoices that comply with the Spanish FacturaE format.
*/
class Facturae {
const VERSION = "1.7.4";
const VERSION = "1.7.5";
const USER_AGENT = "FacturaePHP/" . self::VERSION;

const SCHEMA_3_2 = "3.2";
Expand All @@ -23,6 +23,9 @@ class Facturae {
"digest" => "Ohixl6upD6av8N7pEvDABhEL6hM="
);

const INVOICE_FULL = "FC";
const INVOICE_SIMPLIFIED = "FA";

const PRECISION_LINE = 1;
const PRECISION_INVOICE = 2;

Expand Down
39 changes: 33 additions & 6 deletions src/FacturaeTraits/ExportableTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
namespace josemmo\Facturae\FacturaeTraits;

use josemmo\Facturae\Common\XmlTools;
use josemmo\Facturae\CorrectiveDetails;
use josemmo\Facturae\FacturaePayment;
use josemmo\Facturae\ReimbursableExpense;

Expand Down Expand Up @@ -42,6 +43,8 @@ public function export($filePath=null) {
// Prepare document
$xml = '<fe:Facturae xmlns:fe="' . self::$SCHEMA_NS[$this->version] . '">';
$totals = $this->getTotals();
/** @var CorrectiveDetails|null */
$corrective = $this->getCorrective();
$paymentDetailsXML = $this->getPaymentDetailsXML($totals);

// Add header
Expand Down Expand Up @@ -89,12 +92,36 @@ public function export($filePath=null) {

// Add invoice data
$xml .= '<Invoices><Invoice>';
$xml .= '<InvoiceHeader>' .
'<InvoiceNumber>' . $this->header['number'] . '</InvoiceNumber>' .
'<InvoiceSeriesCode>' . $this->header['serie'] . '</InvoiceSeriesCode>' .
'<InvoiceDocumentType>FC</InvoiceDocumentType>' .
'<InvoiceClass>OO</InvoiceClass>' .
'</InvoiceHeader>';
$xml .= '<InvoiceHeader>';
$xml .= '<InvoiceNumber>' . XmlTools::escape($this->header['number']) . '</InvoiceNumber>';
$xml .= '<InvoiceSeriesCode>' . XmlTools::escape($this->header['serie']) . '</InvoiceSeriesCode>';
$xml .= '<InvoiceDocumentType>' . $this->header['type'] . '</InvoiceDocumentType>';
$xml .= '<InvoiceClass>' . ($corrective === null ? 'OO' : 'OR') . '</InvoiceClass>';
if ($corrective !== null) {
$xml .= '<Corrective>';
if ($corrective->invoiceNumber !== null) {
$xml .= '<InvoiceNumber>' . XmlTools::escape($corrective->invoiceNumber) . '</InvoiceNumber>';
}
if ($corrective->invoiceSeriesCode !== null) {
$xml .= '<InvoiceSeriesCode>' . XmlTools::escape($corrective->invoiceSeriesCode) . '</InvoiceSeriesCode>';
}
$xml .= '<ReasonCode>' . $corrective->reason . '</ReasonCode>';
$xml .= '<ReasonDescription>' . XmlTools::escape($corrective->getReasonDescription()) . '</ReasonDescription>';
if ($corrective->taxPeriodStart !== null && $corrective->taxPeriodEnd !== null) {
$start = is_string($corrective->taxPeriodStart) ? strtotime($corrective->taxPeriodStart) : $corrective->taxPeriodStart;
$end = is_string($corrective->taxPeriodEnd) ? strtotime($corrective->taxPeriodEnd) : $corrective->taxPeriodEnd;
$xml .= '<TaxPeriod>' .
'<StartDate>' . date('Y-m-d', $start) . '</StartDate>' .
'<EndDate>' . date('Y-m-d', $end) . '</EndDate>' .
'</TaxPeriod>';
}
$xml .= '<CorrectionMethod>' . $corrective->correctionMethod . '</CorrectionMethod>';
$xml .= '<CorrectionMethodDescription>' .
XmlTools::escape($corrective->getCorrectionMethodDescription()) .
'</CorrectionMethodDescription>';
$xml .= '</Corrective>';
}
$xml .= '</InvoiceHeader>';
$xml .= '<InvoiceIssueData>';
$xml .= '<IssueDate>' . date('Y-m-d', $this->header['issueDate']) . '</IssueDate>';
if (!is_null($this->header['startDate'])) {
Expand Down
Loading

0 comments on commit b864968

Please sign in to comment.