Skip to content

Commit

Permalink
Added option to add classification line numbers to squashed invoice r…
Browse files Browse the repository at this point in the history
…ows.

Removed xsi:schemaLocation from the generated xml.
Updated docs.
Added more tests.
  • Loading branch information
firebed committed Jun 12, 2024
1 parent 588694c commit 395a30c
Show file tree
Hide file tree
Showing 9 changed files with 292 additions and 20 deletions.
1 change: 1 addition & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
| Εισαγωγή | ΑΑΔΕ myDATA REST API | getting-started |
| Εισαγωγή | Εγκατάσταση | installation |
| Εισαγωγή | Αναβάθμιση | upgrade-guide |
| Εισαγωγή | Σύνοψη Γραμμών | squashing-invoice-rows |
| Εισαγωγή | Σφάλματα | exceptions |
| Εισαγωγή | Contributing | contributing |
| Περιγραφή λειτουργιών | Αναζήτηση ΑΦΜ | http/search-vat |
Expand Down
2 changes: 1 addition & 1 deletion docs/invoices/complete-example.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

[👉 Προβολή στο GitHub][1]

[1]: https://github.com/firebed/aade-mydata/blob/4.x/docs/samples/complete-example.php
[1]: https://github.com/firebed/aade-mydata/blob/5.x/docs/samples/complete-example.php

```php
use Firebed\AadeMyData\Models\Issuer;
Expand Down
119 changes: 119 additions & 0 deletions docs/squashing-invoice-rows.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
# Σύνοψη Γραμμών Παραστατικού

Ο Πάροχος ηλεκτρονικής τιμολόγησης και τα ERP διαβιβάζουν υποχρεωτικά μόνο τη σύνοψη
γραμμών και χαρακτηρισμών των παραστατικών. Οι λεπτομέρειες των γραμμών παραστατικού
θα πρέπει να ομαδοποιηθούν και να αθροιστούν κατάλληλα πριν την αποστολή στο myDATA.

Η σύνοψη γραμμών παραστατικού είναι μια σύνθεση διαδικασία και αναλύεται βάση της:
- κατηγορίας ΦΠΑ γραμμής
- κατηγορίας εξαίρεσης ΦΠΑ γραμμής
- κατηγορίας παρακρατούμενων φόρων γραμμής
- κατηγορίας λοιπών φόρων γραμμής
- κατηγορίας χαρτοσήμου γραμμής
- κατηγορίας τελών γραμμής
- τύπου (recType) γραμμής

Μετά τη σύνοψη γραμμών παραστατικού, το API υπολογίζει αυτόματα τους χαρακτηρισμών ανά γραμμή.

### Σύνοψη Γραμμών

Για τη σύνοψη των γραμμών του παραστατικού αρκεί να καλέσετε τη μέθοδο `squashInvoiceRows` της κλάσης `Invoice`.
Η διαδικασία σύνοψης θα αντικαταστήσει τις λεπτομέρειες των γραμμών με τις συνολικές τους τιμές.

```php
use Firebed\AadeMyData\Models\Invoice;
use Firebed\AadeMyData\Models\InvoiceDetails;

$invoice = new Invoice();
$invoice->addInvoiceDetails(new InvoiceDetails(...));
$invoice->addInvoiceDetails(new InvoiceDetails(...));
$invoice->addInvoiceDetails(new InvoiceDetails(...));
$invoice->addInvoiceDetails(new InvoiceDetails(...));
// ... set more details

$invoice->squashInvoiceRows();
$invoice->summarizeInvoice();

print_r($invoice->toXml());
```

> [!CAUTION]
> Η σύνοψη γραμμών δεν πρέπει να εφαρμοστεί για όλα τα είδη των παραστατικών.
> Για παράδειγμα, τα δελτία αποστολής δε χρειάζονται σύνοψη γραμμών.
## Παράδειγμα

### XML παραστατικού πριν τη σύνοψη

```xml
<invoice>
...
<invoiceDetails>
<netValue>4.03</netValue>
<vatCategory>1</vatCategory>
<vatAmount>0.97</vatAmount>
<incomeClassification>
<icls:classificationType>E3_561_001</icls:classificationType>
<icls:classificationCategory>category1_1</icls:classificationCategory>
<icls:amount>4.03</icls:amount>
</incomeClassification>
</invoiceDetails>
<invoiceDetails>
<netValue>4.03</netValue>
<vatCategory>1</vatCategory>
<vatAmount>0.97</vatAmount>
<incomeClassification>
<icls:classificationType>E3_561_001</icls:classificationType>
<icls:classificationCategory>category1_1</icls:classificationCategory>
<icls:amount>2.02</icls:amount>
</incomeClassification>
<incomeClassification>
<icls:classificationType>E3_561_007</icls:classificationType>
<icls:classificationCategory>category1_1</icls:classificationCategory>
<icls:amount>2.01</icls:amount>
</incomeClassification>
</invoiceDetails>
<invoiceDetails>
<netValue>4.03</netValue>
<vatCategory>1</vatCategory>
<vatAmount>0.97</vatAmount>
<incomeClassification>
<icls:classificationType>E3_561_001</icls:classificationType>
<icls:classificationCategory>category1_1</icls:classificationCategory>
<icls:amount>2.02</icls:amount>
</incomeClassification>
<incomeClassification>
<icls:classificationType>E3_561_007</icls:classificationType>
<icls:classificationCategory>category1_1</icls:classificationCategory>
<icls:amount>2.01</icls:amount>
</incomeClassification>
</invoiceDetails>
...
</invoice>
```

### XML παραστατικού μετά τη σύνοψη

```xml
<invoice>
...
<invoiceDetails>
<netValue>12.09</netValue>
<vatCategory>1</vatCategory>
<vatAmount>2.91</vatAmount>
<incomeClassification>
<icls:classificationType>E3_561_001</icls:classificationType>
<icls:classificationCategory>category1_1</icls:classificationCategory>
<icls:amount>8.07</icls:amount>
<icls:id>1</icls:id>
</incomeClassification>
<incomeClassification>
<icls:classificationType>E3_561_007</icls:classificationType>
<icls:classificationCategory>category1_1</icls:classificationCategory>
<icls:amount>4.02</icls:amount>
<icls:id>2</icls:id>
</incomeClassification>
</invoiceDetails>
...
</invoice>
```
49 changes: 49 additions & 0 deletions docs/upgrade-guide.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,54 @@
# Οδηγός Αναβάθμισης

## Upgrade guide from 4.x to 5.x

### Breaking changes
- Removed vat-registry dependency from composer. You can include vat-registry by running `composer require firebed/vat-registry`.
- [See vat-registry](https://github.com/firebed/vat-registry) documentation for more information.
- If you were not using vat search, you can safely ignore this change.

### Features

- Ability to "squash" invoice rows `$invoice->squashInvoiceRows()`.
> Ο Πάροχος ηλεκτρονικής τιμολόγησης και τα ERP διαβιβάζουν υποχρεωτικά μόνο τη σύνοψη
γραμμών και χαρακτηρισμών των παραστατικών και όχι αναλυτικά τις γραμμές. [Δείτε Σύνοψη Γραμμών Παραστατικού](../docs/squashing-invoice-rows) για περισσότερες λεπτομέρειες.
- Ability to validate invoices against xsd files before sending them to myDATA.
- `$invoice->validate()`.
- Ability to preview invoice xml before sending it to myDATA.
- `$invoice->toXml()`.
- Ability to populate model attributes within constructor by using **<u>mixed</u>** array values as parameter.
```php
use Firebed\AadeMyData\Models\InvoiceDetails;
use Firebed\AadeMyData\Enums\RecType;
use Firebed\AadeMyData\Enums\IncomeClassificationType;
use Firebed\AadeMyData\Enums\IncomeClassificationCategory;

new InvoiceDetails([
'lineNumber' => 1,
'netValue' => 5,
'recType' => RecType::TYPE_2,
'incomeClassification' => [
[
'classificationType' => IncomeClassificationType::E3_561_001,
'classificationCategory' => IncomeClassificationCategory::CATEGORY_1_1,
'amount' => '5'
]
]
])
```
- Model setters are now fluent (chainable).
- `$invoice->setIssuer(...)->setCounterpart(...)`.
- New methods: Invoice::setTaxesTotals, Invoice::setOtherTransportDetails.
- Implemented `add_` methods to add an amount to InvoiceDetails and Classifications attributes (e.g. `$row->addNetValue(5)`, `$row->addVatAmount(1.2)` etc).
- Implemented endpoints for electronic invoice providers.

### Fixes

- Fixed tax calculation when summarizing invoice.
- Removed ext-soap dependency from composer.
- Fixed InvoiceDetails::setOtherMeasurementUnitQuantity
- Fixed InvoiceDetails::setOtherMeasurementUnitTitle

## Αναβάθμιση από 3.x σε 4.x

Η έκδοση 4.x περιέχει αρκετές αλλαγές, μεταξύ των οποίων αρκετές
Expand Down
47 changes: 36 additions & 11 deletions src/Actions/SquashInvoiceRows.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,18 +28,23 @@ class SquashInvoiceRows
*/
private array $squashedEcls = [];

private array $options = [];

/**
* Groups similar rows and returns a new array with the rows summed.
*
* @param InvoiceDetails[]|null $invoiceRows An array of invoice rows.
* @param array $options Additional options.
* @return array|null An array of squashed invoice rows.
*/
public function handle(?array $invoiceRows): ?array
public function handle(?array $invoiceRows, array $options = []): ?array
{
if ($invoiceRows === null) {
return null;
}

$this->options = $options;

return $this->squashInvoiceRows($invoiceRows);
}

Expand Down Expand Up @@ -192,12 +197,14 @@ private function aggregateExpenseClassifications(string $rowKey, ?array $classif
private function mergeAndRoundResults(): array
{
foreach ($this->squashedRows as $key => $row) {
$clsLineNumber = 1;

if (isset($this->squashedIcls[$key])) {
$row->setIncomeClassification(array_values($this->squashedIcls[$key]));
$row->setIncomeClassification($this->mapClassifications($this->squashedIcls[$key], $clsLineNumber));
}

if (isset($this->squashedEcls[$key])) {
$row->setExpensesClassification(array_values($this->squashedEcls[$key]));
$row->setExpensesClassification($this->mapClassifications($this->squashedEcls[$key], $clsLineNumber));
}

$this->roundRow($row);
Expand All @@ -208,9 +215,27 @@ private function mergeAndRoundResults(): array
return array_merge(array_values($this->squashedRows), $this->rowsWithRecType);
}

/**
* @param ExpensesClassification[]|IncomeClassification[] $classifications
* @param int $lineNumber
* @return array
*/
private function mapClassifications(array $classifications, int &$lineNumber = 1): array
{
// If clsLineNumber option is set to true, we will add a line number to each classification.
if (isset($this->options['clsLineNumber']) && $this->options['clsLineNumber'] === true) {
return array_map(function ($cls) use (&$lineNumber) {
$cls->setId($lineNumber++);
return $cls;
}, array_values($classifications));
}

return array_values($classifications);
}

/**
* Rounds the values of a row.
*
*
* @param InvoiceDetails $row
* @return void
*/
Expand All @@ -219,27 +244,27 @@ private function roundRow(InvoiceDetails $row): void
if ($row->getNetValue() !== null) {
$row->setNetValue(round($row->getNetValue(), 2));
}

if ($row->getVatAmount() !== null) {
$row->setVatAmount(round($row->getVatAmount(), 2));
}

if ($row->getWithheldAmount() !== null) {
$row->setWithheldAmount(round($row->getWithheldAmount(), 2));
}

if ($row->getFeesAmount() !== null) {
$row->setFeesAmount(round($row->getFeesAmount(), 2));
}

if ($row->getOtherTaxesAmount() !== null) {
$row->setOtherTaxesAmount(round($row->getOtherTaxesAmount(), 2));
}

if ($row->getStampDutyAmount() !== null) {
$row->setStampDutyAmount(round($row->getStampDutyAmount(), 2));
}

if ($row->getDeductionsAmount() !== null) {
$row->setDeductionsAmount(round($row->getDeductionsAmount(), 2));
}
Expand Down Expand Up @@ -277,7 +302,7 @@ private function adjustClassificationAmount(InvoiceDetails $row): void

$classificationSum = array_sum(array_map(fn($item) => $item->getAmount(), $classifications));
$diff = round($row->getNetValue() - $classificationSum, 2);

if ($diff != 0) {
end($classifications);
$lastKey = key($classifications);
Expand Down
12 changes: 10 additions & 2 deletions src/Models/Invoice.php
Original file line number Diff line number Diff line change
Expand Up @@ -220,10 +220,18 @@ public function setInvoiceSummary(InvoiceSummary $invoiceSummary): static
return $this->set('invoiceSummary', $invoiceSummary);
}

public function squashInvoiceRows(): static
/**
* "Squashes" similar invoice lines and sums up their values.
*
* @param array{clsLineNumber: bool} $options Squashing options.
* If 'clsLineNumber' == true the process will add line numbers to classifications.
*
* @return $this
*/
public function squashInvoiceRows(array $options = []): static
{
$squash = new SquashInvoiceRows();
$this->setInvoiceDetails($squash->handle($this->getInvoiceDetails()));
$this->setInvoiceDetails($squash->handle($this->getInvoiceDetails(), $options));
return $this;
}

Expand Down
2 changes: 1 addition & 1 deletion src/Xml/InvoicesDocWriter.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public function asXML(InvoicesDoc $invoicesDoc): string
$rootNode->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:xsi', self::XSI);
$rootNode->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:icls', self::ICLS);
$rootNode->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:ecls', self::ECLS);
$rootNode->setAttributeNS('http://www.w3.org/2001/XMLSchema-instance', 'xsi:schemaLocation', self::SCHEMA_LOCATION);
// $rootNode->setAttributeNS('http://www.w3.org/2001/XMLSchema-instance', 'xsi:schemaLocation', self::SCHEMA_LOCATION);

$this->buildArray($rootNode, 'invoice', iterator_to_array($invoicesDoc));

Expand Down
17 changes: 17 additions & 0 deletions tests/InvoiceTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@

namespace Tests;

use Firebed\AadeMyData\Enums\IncomeClassificationCategory;
use Firebed\AadeMyData\Enums\IncomeClassificationType;
use Firebed\AadeMyData\Enums\RecType;
use Firebed\AadeMyData\Models\Invoice;
use Firebed\AadeMyData\Models\InvoiceDetails;
use PHPUnit\Framework\TestCase;
use Tests\Traits\HandlesInvoiceXml;

Expand All @@ -12,6 +16,19 @@ class InvoiceTest extends TestCase

public function test_invoice_xml()
{
new InvoiceDetails([
'lineNumber' => 1,
'netValue' => 5,
'recType' => RecType::TYPE_2,
'incomeClassification' => [
[
'classificationType' => IncomeClassificationType::E3_561_001,
'classificationCategory' => IncomeClassificationCategory::CATEGORY_1_1,
'amount' => '5'
]
]
]);

$invoice = Invoice::factory()->make();
$this->assertNotEmpty($invoice->toXml());
}
Expand Down
Loading

0 comments on commit 395a30c

Please sign in to comment.