Updates for transforming PEPPOL => storecove
This commit is contained in:
parent
f44180ee24
commit
75078de048
|
|
@ -1,67 +1,57 @@
|
||||||
<?php
|
<?php
|
||||||
|
/**
|
||||||
|
* Invoice Ninja (https://invoiceninja.com).
|
||||||
|
*
|
||||||
|
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2024. Invoice Ninja LLC (https://invoiceninja.com)
|
||||||
|
*
|
||||||
|
* @license https://www.elastic.co/licensing/elastic-license
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
namespace App\Services\EDocument\Gateway\Storecove\Models;
|
namespace App\Services\EDocument\Gateway\Storecove\Models;
|
||||||
|
|
||||||
use JsonSerializable;
|
|
||||||
use Symfony\Component\Serializer\Annotation\SerializedName;
|
use Symfony\Component\Serializer\Annotation\SerializedName;
|
||||||
use DateTime;
|
use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer;
|
||||||
|
use Symfony\Component\Serializer\Attribute\Context;
|
||||||
|
|
||||||
class Invoice implements JsonSerializable
|
class Invoice
|
||||||
{
|
{
|
||||||
#[SerializedName('taxSystem')]
|
public string $taxSystem = 'tax_line_percentages';
|
||||||
private string $taxSystem = 'tax_line_percentages';
|
|
||||||
|
|
||||||
#[SerializedName('documentCurrency')]
|
public string $documentCurrency = '';
|
||||||
private string $documentCurrency = '';
|
|
||||||
|
|
||||||
#[SerializedName('invoiceNumber')]
|
public string $invoiceNumber = '';
|
||||||
private string $invoiceNumber = '';
|
|
||||||
|
|
||||||
#[SerializedName('issueDate')]
|
public string $issueDate = '';
|
||||||
private DateTime $issueDate;
|
|
||||||
|
|
||||||
#[SerializedName('taxPointDate')]
|
public string $taxPointDate = '';
|
||||||
private ?DateTime $taxPointDate = null;
|
|
||||||
|
|
||||||
#[SerializedName('dueDate')]
|
public string $dueDate = '';
|
||||||
private DateTime $dueDate;
|
|
||||||
|
|
||||||
#[SerializedName('invoicePeriod')]
|
public string $invoicePeriod = '';
|
||||||
private array $invoicePeriod = [];
|
|
||||||
|
|
||||||
#[SerializedName('references')]
|
public array $references = [];
|
||||||
private array $references = [];
|
|
||||||
|
|
||||||
#[SerializedName('accountingCost')]
|
public ?string $accountingCost = null;
|
||||||
private ?string $accountingCost = null;
|
|
||||||
|
|
||||||
#[SerializedName('note')]
|
public string $note = '';
|
||||||
private string $note = '';
|
|
||||||
|
|
||||||
#[SerializedName('amountIncludingVat')]
|
public float $amountIncludingVat = 0.0;
|
||||||
private float $amountIncludingVat = 0.0;
|
|
||||||
|
|
||||||
#[SerializedName('prepaidAmount')]
|
public ?float $prepaidAmount = null;
|
||||||
private ?float $prepaidAmount = null;
|
|
||||||
|
|
||||||
#[SerializedName('accountingSupplierParty')]
|
public array $accountingSupplierParty = [];
|
||||||
private array $accountingSupplierParty = [];
|
|
||||||
|
|
||||||
#[SerializedName('accountingCustomerParty')]
|
public array $accountingCustomerParty = [];
|
||||||
private array $accountingCustomerParty = [];
|
|
||||||
|
|
||||||
#[SerializedName('paymentMeans')]
|
public array $paymentMeans = [];
|
||||||
private array $paymentMeans = [];
|
|
||||||
|
|
||||||
#[SerializedName('taxTotal')]
|
public array $taxTotal = [];
|
||||||
private array $taxTotal = [];
|
|
||||||
|
|
||||||
/**
|
public array $invoiceLines = [];
|
||||||
* @var InvoiceLines[]
|
|
||||||
*/
|
|
||||||
private array $invoiceLines = [];
|
|
||||||
|
|
||||||
// Getters and setters for all properties
|
|
||||||
|
|
||||||
public function setDocumentCurrency(string $documentCurrency): void
|
public function setDocumentCurrency(string $documentCurrency): void
|
||||||
{
|
{
|
||||||
|
|
@ -73,99 +63,163 @@ class Invoice implements JsonSerializable
|
||||||
$this->invoiceNumber = $invoiceNumber;
|
$this->invoiceNumber = $invoiceNumber;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setIssueDate(DateTime $issueDate): void
|
public function setIssueDate($issueDate): void
|
||||||
{
|
{
|
||||||
$this->issueDate = $issueDate;
|
$this->issueDate = $issueDate;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setTaxPointDate(?DateTime $taxPointDate): void
|
public function setTaxPointDate($taxPointDate): void
|
||||||
{
|
{
|
||||||
$this->taxPointDate = $taxPointDate;
|
$this->taxPointDate = $taxPointDate;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setDueDate(DateTime $dueDate): void
|
public function setDueDate($dueDate): void
|
||||||
{
|
{
|
||||||
$this->dueDate = $dueDate;
|
$this->dueDate = $dueDate;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setInvoicePeriod(array $invoicePeriod): void
|
public function setInvoicePeriod($invoicePeriod): void
|
||||||
{
|
{
|
||||||
$this->invoicePeriod = $invoicePeriod;
|
$this->invoicePeriod = $invoicePeriod;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setReferences(array $references): void
|
public function setReferences( $references): void
|
||||||
{
|
{
|
||||||
$this->references = $references;
|
$this->references = $references;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setAccountingCost(?string $accountingCost): void
|
public function setAccountingCost($accountingCost): void
|
||||||
{
|
{
|
||||||
$this->accountingCost = $accountingCost;
|
$this->accountingCost = $accountingCost;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setNote(string $note): void
|
public function setNote($note): void
|
||||||
{
|
{
|
||||||
$this->note = $note;
|
$this->note = $note;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setAmountIncludingVat(float $amountIncludingVat): void
|
public function setAmountIncludingVat ($amountIncludingVat): void
|
||||||
{
|
{
|
||||||
$this->amountIncludingVat = $amountIncludingVat;
|
$this->amountIncludingVat = $amountIncludingVat;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setPrepaidAmount(?float $prepaidAmount): void
|
public function setPrepaidAmount( $prepaidAmount): void
|
||||||
{
|
{
|
||||||
$this->prepaidAmount = $prepaidAmount;
|
$this->prepaidAmount = $prepaidAmount;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setAccountingSupplierParty(array $accountingSupplierParty): void
|
public function setAccountingSupplierParty( $accountingSupplierParty): void
|
||||||
{
|
{
|
||||||
$this->accountingSupplierParty = $accountingSupplierParty;
|
$this->accountingSupplierParty = $accountingSupplierParty;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setAccountingCustomerParty(array $accountingCustomerParty): void
|
public function setAccountingCustomerParty( $accountingCustomerParty): void
|
||||||
{
|
{
|
||||||
$this->accountingCustomerParty = $accountingCustomerParty;
|
$this->accountingCustomerParty = $accountingCustomerParty;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setPaymentMeans(array $paymentMeans): void
|
public function setPaymentMeans( $paymentMeans): void
|
||||||
{
|
{
|
||||||
$this->paymentMeans = $paymentMeans;
|
$this->paymentMeans = $paymentMeans;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setTaxTotal(array $taxTotal): void
|
public function setTaxTotal( $taxTotal): void
|
||||||
{
|
{
|
||||||
$this->taxTotal = $taxTotal;
|
$this->taxTotal = $taxTotal;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param InvoiceLines[] $invoiceLines
|
|
||||||
*/
|
|
||||||
public function setInvoiceLines(array $invoiceLines): void
|
public function setInvoiceLines(array $invoiceLines): void
|
||||||
{
|
{
|
||||||
$this->invoiceLines = $invoiceLines;
|
$this->invoiceLines = $invoiceLines;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function jsonSerialize(): mixed
|
public function getInvoiceLines()
|
||||||
{
|
{
|
||||||
return [
|
return $this->invoiceLines;
|
||||||
'taxSystem' => $this->taxSystem,
|
}
|
||||||
'documentCurrency' => $this->documentCurrency,
|
|
||||||
'invoiceNumber' => $this->invoiceNumber,
|
public function getTaxSystem(): string
|
||||||
'issueDate' => $this->issueDate->format('Y-m-d'),
|
{
|
||||||
'taxPointDate' => $this->taxPointDate ? $this->taxPointDate->format('Y-m-d') : null,
|
return $this->taxSystem;
|
||||||
'dueDate' => $this->dueDate->format('Y-m-d'),
|
}
|
||||||
'invoicePeriod' => $this->invoicePeriod,
|
|
||||||
'references' => $this->references,
|
public function getDocumentCurrency(): string
|
||||||
'accountingCost' => $this->accountingCost,
|
{
|
||||||
'note' => $this->note,
|
return $this->documentCurrency;
|
||||||
'amountIncludingVat' => $this->amountIncludingVat,
|
}
|
||||||
'prepaidAmount' => $this->prepaidAmount,
|
|
||||||
'accountingSupplierParty' => $this->accountingSupplierParty,
|
public function getInvoiceNumber(): string
|
||||||
'accountingCustomerParty' => $this->accountingCustomerParty,
|
{
|
||||||
'paymentMeans' => $this->paymentMeans,
|
return $this->invoiceNumber;
|
||||||
'taxTotal' => $this->taxTotal,
|
}
|
||||||
'invoiceLines' => $this->invoiceLines,
|
|
||||||
];
|
public function getIssueDate(): string
|
||||||
|
{
|
||||||
|
return $this->issueDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTaxPointDate(): string
|
||||||
|
{
|
||||||
|
return $this->taxPointDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDueDate(): string
|
||||||
|
{
|
||||||
|
return $this->dueDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getInvoicePeriod(): string
|
||||||
|
{
|
||||||
|
return $this->invoicePeriod;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getReferences(): array
|
||||||
|
{
|
||||||
|
return $this->references;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAccountingCost(): ?string
|
||||||
|
{
|
||||||
|
return $this->accountingCost;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getNote(): string
|
||||||
|
{
|
||||||
|
return $this->note;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAmountIncludingVat(): float
|
||||||
|
{
|
||||||
|
return $this->amountIncludingVat;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPrepaidAmount(): ?float
|
||||||
|
{
|
||||||
|
return $this->prepaidAmount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAccountingSupplierParty(): array
|
||||||
|
{
|
||||||
|
return $this->accountingSupplierParty;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAccountingCustomerParty(): array
|
||||||
|
{
|
||||||
|
return $this->accountingCustomerParty;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPaymentMeans(): array
|
||||||
|
{
|
||||||
|
return $this->paymentMeans;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTaxTotal(): array
|
||||||
|
{
|
||||||
|
return $this->taxTotal;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setTaxSystem(string $taxSystem): void
|
||||||
|
{
|
||||||
|
$this->taxSystem = $taxSystem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,25 +7,18 @@ use Symfony\Component\Serializer\Annotation\SerializedName;
|
||||||
|
|
||||||
class InvoiceLines
|
class InvoiceLines
|
||||||
{
|
{
|
||||||
#[SerializedName('ID.value')]
|
|
||||||
public string $lineId = '';
|
public string $lineId = '';
|
||||||
|
|
||||||
#[SerializedName('LineExtensionAmount.amount')]
|
|
||||||
public float $amountExcludingVat = 0.0;
|
public float $amountExcludingVat = 0.0;
|
||||||
|
|
||||||
#[SerializedName('Price.PriceAmount.amount')]
|
|
||||||
public float $itemPrice = 0.0;
|
public float $itemPrice = 0.0;
|
||||||
|
|
||||||
#[SerializedName('InvoicedQuantity')]
|
|
||||||
public int $quantity = 0;
|
public int $quantity = 0;
|
||||||
|
|
||||||
#[SerializedName('InvoicedQuantity.unitCode')]
|
|
||||||
public string $quantityUnitCode = '';
|
public string $quantityUnitCode = '';
|
||||||
|
|
||||||
#[SerializedName('Item.Name')]
|
|
||||||
public string $name = '';
|
public string $name = '';
|
||||||
|
|
||||||
#[SerializedName('Item.Description')]
|
|
||||||
public string $description = '';
|
public string $description = '';
|
||||||
|
|
||||||
public Tax $tax;
|
public Tax $tax;
|
||||||
|
|
|
||||||
|
|
@ -1,244 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Services\EDocument\Gateway\Storecove;
|
|
||||||
|
|
||||||
use App\Services\EDocument\Gateway\Storecove\Models\Tax;
|
|
||||||
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
|
|
||||||
use Symfony\Component\Serializer\Mapping\Loader\AttributeLoader;
|
|
||||||
use App\Services\EDocument\Gateway\Storecove\Models\InvoiceLines;
|
|
||||||
use InvoiceNinja\EInvoice\Models\Peppol\Invoice as PeppolInvoice;
|
|
||||||
use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor;
|
|
||||||
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
|
|
||||||
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory;
|
|
||||||
use Symfony\Component\Serializer\NameConverter\MetadataAwareNameConverter;
|
|
||||||
use App\Services\EDocument\Gateway\Storecove\Models\Invoice as StorecoveInvoice;
|
|
||||||
use Symfony\Component\Serializer\SerializerInterface;
|
|
||||||
|
|
||||||
|
|
||||||
class PeppolToStorecoveNormalizer implements DenormalizerInterface
|
|
||||||
{
|
|
||||||
private SerializerInterface $serializer;
|
|
||||||
private ObjectNormalizer $objectNormalizer;
|
|
||||||
|
|
||||||
|
|
||||||
public function __construct(SerializerInterface $serializer)
|
|
||||||
{
|
|
||||||
|
|
||||||
|
|
||||||
$this->serializer = $serializer;
|
|
||||||
|
|
||||||
$classMetadataFactory = new ClassMetadataFactory(new AttributeLoader());
|
|
||||||
$metadataAwareNameConverter = new MetadataAwareNameConverter($classMetadataFactory);
|
|
||||||
$this->objectNormalizer = new ObjectNormalizer($classMetadataFactory, $metadataAwareNameConverter);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public function denormalize(mixed $data, string $type, string $format = null, array $context = []): mixed
|
|
||||||
{
|
|
||||||
$peppolInvoice = $data;
|
|
||||||
$storecoveInvoice = new StorecoveInvoice();
|
|
||||||
|
|
||||||
|
|
||||||
$storecoveInvoice->setDocumentCurrency($peppolInvoice->DocumentCurrencyCode ?? '');
|
|
||||||
$storecoveInvoice->setInvoiceNumber($peppolInvoice->ID ?? '');
|
|
||||||
$storecoveInvoice->setIssueDate($peppolInvoice->IssueDate);
|
|
||||||
$storecoveInvoice->setDueDate($peppolInvoice->DueDate);
|
|
||||||
$storecoveInvoice->setNote($peppolInvoice->Note ?? '');
|
|
||||||
$storecoveInvoice->setAmountIncludingVat((float)($peppolInvoice->LegalMonetaryTotal->TaxInclusiveAmount->amount ?? 0));
|
|
||||||
|
|
||||||
if (isset($peppolInvoice->InvoicePeriod[0])) {
|
|
||||||
$storecoveInvoice->setInvoicePeriod([
|
|
||||||
'startDate' => $peppolInvoice->InvoicePeriod[0]->StartDate,
|
|
||||||
'endDate' => $peppolInvoice->InvoicePeriod[0]->EndDate,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
$storecoveInvoice->setReferences([
|
|
||||||
'buyerReference' => $peppolInvoice->BuyerReference ?? '',
|
|
||||||
'orderReference' => $peppolInvoice->OrderReference->ID->value ?? '',
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (isset($peppolInvoice->AccountingSupplierParty->Party)) {
|
|
||||||
$supplier = $peppolInvoice->AccountingSupplierParty->Party;
|
|
||||||
$storecoveInvoice->setAccountingSupplierParty([
|
|
||||||
'name' => $supplier->PartyName[0]->Name ?? '',
|
|
||||||
'vatNumber' => $supplier->PartyIdentification[0]->ID->value ?? '',
|
|
||||||
'streetName' => $supplier->PostalAddress->StreetName ?? '',
|
|
||||||
'cityName' => $supplier->PostalAddress->CityName ?? '',
|
|
||||||
'postalZone' => $supplier->PostalAddress->PostalZone ?? '',
|
|
||||||
'countryCode' => $supplier->PostalAddress->Country->IdentificationCode->value ?? '',
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isset($peppolInvoice->AccountingCustomerParty->Party)) {
|
|
||||||
$customer = $peppolInvoice->AccountingCustomerParty->Party;
|
|
||||||
$storecoveInvoice->setAccountingCustomerParty([
|
|
||||||
'name' => $customer->PartyName[0]->Name ?? '',
|
|
||||||
'vatNumber' => $customer->PartyIdentification[0]->ID->value ?? '',
|
|
||||||
'streetName' => $customer->PostalAddress->StreetName ?? '',
|
|
||||||
'cityName' => $customer->PostalAddress->CityName ?? '',
|
|
||||||
'postalZone' => $customer->PostalAddress->PostalZone ?? '',
|
|
||||||
'countryCode' => $customer->PostalAddress->Country->IdentificationCode->value ?? '',
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isset($peppolInvoice->PaymentMeans[0])) {
|
|
||||||
$storecoveInvoice->setPaymentMeans([
|
|
||||||
'paymentID' => $peppolInvoice->PaymentMeans[0]->PayeeFinancialAccount->ID->value ?? '',
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Map tax total at invoice level
|
|
||||||
$taxTotal = [];
|
|
||||||
if (isset($peppolInvoice->InvoiceLine[0]->TaxTotal[0])) {
|
|
||||||
$taxTotal[] = [
|
|
||||||
'taxAmount' => (float)($peppolInvoice->InvoiceLine[0]->TaxTotal[0]->TaxAmount->amount ?? 0),
|
|
||||||
'taxCurrency' => $peppolInvoice->DocumentCurrencyCode ?? '',
|
|
||||||
];
|
|
||||||
}
|
|
||||||
$storecoveInvoice->setTaxTotal($taxTotal);
|
|
||||||
|
|
||||||
if (isset($peppolInvoice->InvoiceLine)) {
|
|
||||||
$invoiceLines = [];
|
|
||||||
foreach ($peppolInvoice->InvoiceLine as $line) {
|
|
||||||
$invoiceLine = new InvoiceLines();
|
|
||||||
$invoiceLine->setLineId($line->ID->value ?? '');
|
|
||||||
$invoiceLine->setAmountExcludingVat((float)($line->LineExtensionAmount->amount ?? 0));
|
|
||||||
$invoiceLine->setQuantity((float)($line->InvoicedQuantity ?? 0));
|
|
||||||
$invoiceLine->setQuantityUnitCode(''); // Not present in the provided JSON
|
|
||||||
$invoiceLine->setItemPrice((float)($line->Price->PriceAmount->amount ?? 0));
|
|
||||||
$invoiceLine->setName($line->Item->Name ?? '');
|
|
||||||
$invoiceLine->setDescription($line->Item->Description ?? '');
|
|
||||||
|
|
||||||
$tax = new Tax();
|
|
||||||
if (isset($line->TaxTotal[0])) {
|
|
||||||
$taxTotal = $line->TaxTotal[0];
|
|
||||||
$tax->setTaxAmount((float)($taxTotal->TaxAmount->amount ?? 0));
|
|
||||||
|
|
||||||
if (isset($line->Item->ClassifiedTaxCategory[0])) {
|
|
||||||
$taxCategory = $line->Item->ClassifiedTaxCategory[0];
|
|
||||||
$tax->setTaxPercentage((float)($taxCategory->Percent ?? 0));
|
|
||||||
$tax->setTaxCategory($taxCategory->ID->value ?? '');
|
|
||||||
}
|
|
||||||
|
|
||||||
$tax->setTaxableAmount((float)($line->LineExtensionAmount->amount ?? 0));
|
|
||||||
}
|
|
||||||
$invoiceLine->setTax($tax);
|
|
||||||
|
|
||||||
$invoiceLines[] = $invoiceLine;
|
|
||||||
}
|
|
||||||
$storecoveInvoice->setInvoiceLines($invoiceLines);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $storecoveInvoice;
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private function validateStorecoveInvoice(StorecoveInvoice $invoice): void
|
|
||||||
{
|
|
||||||
$requiredFields = ['documentCurrency', 'invoiceNumber', 'issueDate', 'dueDate'];
|
|
||||||
foreach ($requiredFields as $field) {
|
|
||||||
if (empty($invoice->$field)) {
|
|
||||||
throw new \InvalidArgumentException("Required field '$field' is missing or empty");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (empty($invoice->invoiceLines)) {
|
|
||||||
throw new \InvalidArgumentException("Invoice must have at least one line item");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add more validations as needed
|
|
||||||
}
|
|
||||||
|
|
||||||
public function supportsDenormalization(mixed $data, string $type, string $format = null, array $context = []): bool
|
|
||||||
{
|
|
||||||
return $type === StorecoveInvoice::class && $data instanceof PeppolInvoice;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getSupportedTypes(?string $format): array
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
StorecoveInvoice::class => true,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
private function mapNestedProperties($object, array $nestedPaths): array
|
|
||||||
{
|
|
||||||
$result = [];
|
|
||||||
foreach ($nestedPaths as $key => $path) {
|
|
||||||
if (is_array($path)) {
|
|
||||||
// Try multiple paths
|
|
||||||
foreach ($path as $possiblePath) {
|
|
||||||
$value = $this->getValueFromPath($object, $possiblePath);
|
|
||||||
if ($value !== null) {
|
|
||||||
$result[$key] = $value;
|
|
||||||
nlog("Mapped nested property: $key", ['path' => $possiblePath, 'value' => $value]);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!isset($result[$key])) {
|
|
||||||
nlog("Failed to map nested property: $key", ['paths' => $path]);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$value = $this->getValueFromPath($object, $path);
|
|
||||||
if ($value !== null) {
|
|
||||||
$result[$key] = $value;
|
|
||||||
nlog("Mapped nested property: $key", ['path' => $path, 'value' => $value]);
|
|
||||||
} else {
|
|
||||||
nlog("Failed to map nested property: $key", ['path' => $path]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return $result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function getValueFromPath($object, string $path)
|
|
||||||
{
|
|
||||||
$parts = explode('.', $path);
|
|
||||||
$value = $object;
|
|
||||||
foreach ($parts as $part) {
|
|
||||||
if (preg_match('/(.+)\[(\d+)\]/', $part, $matches)) {
|
|
||||||
$property = $matches[1];
|
|
||||||
$index = $matches[2];
|
|
||||||
$value = $value->$property[$index] ?? null;
|
|
||||||
} else {
|
|
||||||
$value = $value->$part ?? null;
|
|
||||||
}
|
|
||||||
if ($value === null) {
|
|
||||||
nlog("Null value encountered in path: $path at part: $part");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return $value instanceof \DateTime ? $value->format('Y-m-d') : $value;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function castValue(string $property, $value)
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
$reflectionProperty = new \ReflectionProperty(StorecoveInvoice::class, $property);
|
|
||||||
$type = $reflectionProperty->getType();
|
|
||||||
|
|
||||||
if ($type instanceof \ReflectionNamedType) {
|
|
||||||
switch ($type->getName()) {
|
|
||||||
case 'float':
|
|
||||||
return (float) $value;
|
|
||||||
case 'int':
|
|
||||||
return (int) $value;
|
|
||||||
case 'string':
|
|
||||||
return (string) $value;
|
|
||||||
case 'bool':
|
|
||||||
return (bool) $value;
|
|
||||||
case 'array':
|
|
||||||
return (array) $value;
|
|
||||||
default:
|
|
||||||
return $value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (\ReflectionException $e) {
|
|
||||||
nlog("Error casting value for property: $property", ['error' => $e->getMessage()]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,123 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Services\EDocument\Gateway\Transformers;
|
||||||
|
|
||||||
|
use App\Services\EDocument\Gateway\Storecove\Models\Invoice as StorecoveInvoice;
|
||||||
|
|
||||||
|
class StorecoveTransformer implements TransformerInterface
|
||||||
|
{
|
||||||
|
private StorecoveInvoice $s_invoice;
|
||||||
|
|
||||||
|
public function transform(mixed $peppolInvoice)
|
||||||
|
{
|
||||||
|
|
||||||
|
$this->s_invoice = new StorecoveInvoice();
|
||||||
|
|
||||||
|
$this->s_invoice->setDocumentCurrency($peppolInvoice->DocumentCurrencyCode ?? '');
|
||||||
|
$this->s_invoice->setInvoiceNumber($peppolInvoice->ID ?? '');
|
||||||
|
$this->s_invoice->setIssueDate($peppolInvoice->IssueDate->format('Y-m-d'));
|
||||||
|
$this->s_invoice->setTaxPointDate($peppolInvoice->IssueDate->format('Y-m-d'));
|
||||||
|
$this->s_invoice->setDueDate($peppolInvoice->DueDate->format('Y-m-d') ?? '');
|
||||||
|
$this->s_invoice->setNote($peppolInvoice->Note ?? '');
|
||||||
|
|
||||||
|
// Only use this if we are billing for services between a period.
|
||||||
|
if (isset($peppolInvoice->InvoicePeriod[0]) &&
|
||||||
|
isset($peppolInvoice->InvoicePeriod[0]->StartDate) &&
|
||||||
|
isset($peppolInvoice->InvoicePeriod[0]->EndDate)) {
|
||||||
|
$this->s_invoice->setInvoicePeriod("{$peppolInvoice->InvoicePeriod[0]->StartDate} - {$peppolInvoice->InvoicePeriod[0]->EndDate}");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// $this->s_invoice->setReferences([
|
||||||
|
// 'buyerReference' => $peppolInvoice->BuyerReference ?? '',
|
||||||
|
// 'orderReference' => $peppolInvoice->OrderReference->ID->value ?? '',
|
||||||
|
// ]);
|
||||||
|
|
||||||
|
// $this->s_invoice->setAmountIncludingVat((float)($peppolInvoice->LegalMonetaryTotal->TaxInclusiveAmount->amount ?? 0));
|
||||||
|
// if (isset($peppolInvoice->AccountingSupplierParty->Party)) {
|
||||||
|
// $supplier = $peppolInvoice->AccountingSupplierParty->Party;
|
||||||
|
// $this->s_invoice->setAccountingSupplierParty([
|
||||||
|
// 'name' => $supplier->PartyName[0]->Name ?? '',
|
||||||
|
// 'vatNumber' => $supplier->PartyIdentification[0]->ID->value ?? '',
|
||||||
|
// 'streetName' => $supplier->PostalAddress->StreetName ?? '',
|
||||||
|
// 'cityName' => $supplier->PostalAddress->CityName ?? '',
|
||||||
|
// 'postalZone' => $supplier->PostalAddress->PostalZone ?? '',
|
||||||
|
// 'countryCode' => $supplier->PostalAddress->Country->IdentificationCode->value ?? '',
|
||||||
|
// ]);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if (isset($peppolInvoice->AccountingCustomerParty->Party)) {
|
||||||
|
// $customer = $peppolInvoice->AccountingCustomerParty->Party;
|
||||||
|
// $this->s_invoice->setAccountingCustomerParty([
|
||||||
|
// 'name' => $customer->PartyName[0]->Name ?? '',
|
||||||
|
// 'vatNumber' => $customer->PartyIdentification[0]->ID->value ?? '',
|
||||||
|
// 'streetName' => $customer->PostalAddress->StreetName ?? '',
|
||||||
|
// 'cityName' => $customer->PostalAddress->CityName ?? '',
|
||||||
|
// 'postalZone' => $customer->PostalAddress->PostalZone ?? '',
|
||||||
|
// 'countryCode' => $customer->PostalAddress->Country->IdentificationCode->value ?? '',
|
||||||
|
// ]);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if (isset($peppolInvoice->PaymentMeans[0])) {
|
||||||
|
// $this->s_invoice->setPaymentMeans([
|
||||||
|
// 'paymentID' => $peppolInvoice->PaymentMeans[0]->PayeeFinancialAccount->ID->value ?? '',
|
||||||
|
// ]);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // Map tax total at invoice level
|
||||||
|
// $taxTotal = [];
|
||||||
|
// if (isset($peppolInvoice->InvoiceLine[0]->TaxTotal[0])) {
|
||||||
|
// $taxTotal[] = [
|
||||||
|
// 'taxAmount' => (float)($peppolInvoice->InvoiceLine[0]->TaxTotal[0]->TaxAmount->amount ?? 0),
|
||||||
|
// 'taxCurrency' => $peppolInvoice->DocumentCurrencyCode ?? '',
|
||||||
|
// ];
|
||||||
|
// }
|
||||||
|
// $this->s_invoice->setTaxTotal($taxTotal);
|
||||||
|
|
||||||
|
// if (isset($peppolInvoice->InvoiceLine)) {
|
||||||
|
// $invoiceLines = [];
|
||||||
|
// foreach ($peppolInvoice->InvoiceLine as $line) {
|
||||||
|
// $invoiceLine = new InvoiceLines();
|
||||||
|
// $invoiceLine->setLineId($line->ID->value ?? '');
|
||||||
|
// $invoiceLine->setAmountExcludingVat((float)($line->LineExtensionAmount->amount ?? 0));
|
||||||
|
// $invoiceLine->setQuantity((float)($line->InvoicedQuantity ?? 0));
|
||||||
|
// $invoiceLine->setQuantityUnitCode(''); // Not present in the provided JSON
|
||||||
|
// $invoiceLine->setItemPrice((float)($line->Price->PriceAmount->amount ?? 0));
|
||||||
|
// $invoiceLine->setName($line->Item->Name ?? '');
|
||||||
|
// $invoiceLine->setDescription($line->Item->Description ?? '');
|
||||||
|
|
||||||
|
// $tax = new Tax();
|
||||||
|
// if (isset($line->TaxTotal[0])) {
|
||||||
|
// $taxTotal = $line->TaxTotal[0];
|
||||||
|
// $tax->setTaxAmount((float)($taxTotal->TaxAmount->amount ?? 0));
|
||||||
|
|
||||||
|
// if (isset($line->Item->ClassifiedTaxCategory[0])) {
|
||||||
|
// $taxCategory = $line->Item->ClassifiedTaxCategory[0];
|
||||||
|
// $tax->setTaxPercentage((float)($taxCategory->Percent ?? 0));
|
||||||
|
// $tax->setTaxCategory($taxCategory->ID->value ?? '');
|
||||||
|
// }
|
||||||
|
|
||||||
|
// $tax->setTaxableAmount((float)($line->LineExtensionAmount->amount ?? 0));
|
||||||
|
// }
|
||||||
|
// $invoiceLine->setTax($tax);
|
||||||
|
|
||||||
|
// $invoiceLines[] = $invoiceLine;
|
||||||
|
// }
|
||||||
|
// $this->s_invoice->setInvoiceLines($invoiceLines);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return $this->s_invoice;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getInvoice(): StorecoveInvoice
|
||||||
|
{
|
||||||
|
return $this->s_invoice;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toJson(): string
|
||||||
|
{
|
||||||
|
return json_encode($this->s_invoice, JSON_PRETTY_PRINT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Invoice Ninja (https://invoiceninja.com).
|
||||||
|
*
|
||||||
|
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2024. Invoice Ninja LLC (https://invoiceninja.com)
|
||||||
|
*
|
||||||
|
* @license https://www.elastic.co/licensing/elastic-license
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
namespace App\Services\EDocument\Gateway\Transformers;
|
||||||
|
|
||||||
|
interface TransformerInterface
|
||||||
|
{
|
||||||
|
public function transform(mixed $peppolInvoice);
|
||||||
|
|
||||||
|
public function getInvoice();
|
||||||
|
|
||||||
|
public function toJson();
|
||||||
|
}
|
||||||
|
|
@ -16,6 +16,7 @@ use App\Models\Client;
|
||||||
use App\Models\Company;
|
use App\Models\Company;
|
||||||
use App\Models\Invoice;
|
use App\Models\Invoice;
|
||||||
use Tests\MockAccountData;
|
use Tests\MockAccountData;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
use App\Models\ClientContact;
|
use App\Models\ClientContact;
|
||||||
use App\DataMapper\InvoiceItem;
|
use App\DataMapper\InvoiceItem;
|
||||||
use App\DataMapper\Tax\TaxModel;
|
use App\DataMapper\Tax\TaxModel;
|
||||||
|
|
@ -23,19 +24,28 @@ use App\DataMapper\ClientSettings;
|
||||||
use App\DataMapper\CompanySettings;
|
use App\DataMapper\CompanySettings;
|
||||||
use App\Services\EDocument\Standards\Peppol;
|
use App\Services\EDocument\Standards\Peppol;
|
||||||
use Symfony\Component\Serializer\Serializer;
|
use Symfony\Component\Serializer\Serializer;
|
||||||
|
use Symfony\Component\Serializer\Encoder\XmlEncoder;
|
||||||
use InvoiceNinja\EInvoice\Models\Peppol\PaymentMeans;
|
use InvoiceNinja\EInvoice\Models\Peppol\PaymentMeans;
|
||||||
use Symfony\Component\Serializer\Encoder\JsonEncoder;
|
use Symfony\Component\Serializer\Encoder\JsonEncoder;
|
||||||
|
use App\Services\EDocument\Gateway\Storecove\Storecove;
|
||||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||||
|
use Symfony\Component\PropertyInfo\PropertyInfoExtractor;
|
||||||
|
use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor;
|
||||||
|
|
||||||
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
|
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
|
||||||
|
|
||||||
use Symfony\Component\Serializer\Normalizer\ArrayDenormalizer;
|
use Symfony\Component\Serializer\Normalizer\ArrayDenormalizer;
|
||||||
|
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
|
||||||
|
use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer;
|
||||||
use Symfony\Component\Serializer\Mapping\Loader\AttributeLoader;
|
use Symfony\Component\Serializer\Mapping\Loader\AttributeLoader;
|
||||||
use InvoiceNinja\EInvoice\Models\Peppol\Invoice as PeppolInvoice;
|
use InvoiceNinja\EInvoice\Models\Peppol\Invoice as PeppolInvoice;
|
||||||
|
use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor;
|
||||||
|
use Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer;
|
||||||
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory;
|
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory;
|
||||||
|
|
||||||
use App\Services\EDocument\Gateway\Storecove\PeppolToStorecoveNormalizer;
|
use App\Services\EDocument\Gateway\Storecove\PeppolToStorecoveNormalizer;
|
||||||
|
|
||||||
use Symfony\Component\Serializer\NameConverter\MetadataAwareNameConverter;
|
use Symfony\Component\Serializer\NameConverter\MetadataAwareNameConverter;
|
||||||
use App\Services\EDocument\Gateway\Storecove\Models\Invoice as StorecoveInvoice;
|
use App\Services\EDocument\Gateway\Storecove\Models\Invoice as StorecoveInvoice;
|
||||||
|
use App\Services\EDocument\Gateway\Transformers\StorecoveTransformer;
|
||||||
|
|
||||||
class StorecoveTest extends TestCase
|
class StorecoveTest extends TestCase
|
||||||
{
|
{
|
||||||
|
|
@ -55,6 +65,36 @@ class StorecoveTest extends TestCase
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function testNormalizingToStorecove()
|
||||||
|
{
|
||||||
|
|
||||||
|
$e_invoice = new \InvoiceNinja\EInvoice\Models\Peppol\Invoice();
|
||||||
|
|
||||||
|
$invoice = $this->createATData();
|
||||||
|
|
||||||
|
$stub = json_decode('{"Invoice":{"Note":"Nooo","PaymentMeans":[{"ID":{"value":"afdasfasdfasdfas"},"PayeeFinancialAccount":{"Name":"PFA-NAME","ID":{"value":"DE89370400440532013000"},"AliasName":"PFA-Alias","AccountTypeCode":{"value":"CHECKING"},"AccountFormatCode":{"value":"IBAN"},"CurrencyCode":{"value":"EUR"},"FinancialInstitutionBranch":{"ID":{"value":"DEUTDEMMXXX"},"Name":"Deutsche Bank"}}}]}}');
|
||||||
|
foreach ($stub as $key => $value) {
|
||||||
|
$e_invoice->{$key} = $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
$invoice->e_invoice = $e_invoice;
|
||||||
|
|
||||||
|
$p = new Peppol($invoice);
|
||||||
|
$p->run();
|
||||||
|
$peppolInvoice = $p->getInvoice();
|
||||||
|
|
||||||
|
|
||||||
|
$s_transformer = new StorecoveTransformer();
|
||||||
|
$s_transformer->transform($peppolInvoice);
|
||||||
|
|
||||||
|
$json = $s_transformer->toJson();
|
||||||
|
|
||||||
|
$this->assertJson($json);
|
||||||
|
|
||||||
|
nlog($json);
|
||||||
|
}
|
||||||
|
|
||||||
public function testUnsetOfVatNumers()
|
public function testUnsetOfVatNumers()
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
@ -1332,81 +1372,6 @@ class StorecoveTest extends TestCase
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public function testNormalizingToStorecove()
|
|
||||||
{
|
|
||||||
|
|
||||||
$e_invoice = new \InvoiceNinja\EInvoice\Models\Peppol\Invoice();
|
|
||||||
|
|
||||||
$invoice = $this->createATData();
|
|
||||||
|
|
||||||
$stub = json_decode('{"Invoice":{"Note":"Nooo","PaymentMeans":[{"ID":{"value":"afdasfasdfasdfas"},"PayeeFinancialAccount":{"Name":"PFA-NAME","ID":{"value":"DE89370400440532013000"},"AliasName":"PFA-Alias","AccountTypeCode":{"value":"CHECKING"},"AccountFormatCode":{"value":"IBAN"},"CurrencyCode":{"value":"EUR"},"FinancialInstitutionBranch":{"ID":{"value":"DEUTDEMMXXX"},"Name":"Deutsche Bank"}}}]}}');
|
|
||||||
foreach ($stub as $key => $value) {
|
|
||||||
$e_invoice->{$key} = $value;
|
|
||||||
}
|
|
||||||
|
|
||||||
$invoice->e_invoice = $e_invoice;
|
|
||||||
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Assuming $invoice is already defined or created earlier in your test
|
|
||||||
$p = new Peppol($invoice);
|
|
||||||
$p->run();
|
|
||||||
$peppolInvoice = $p->getInvoice();
|
|
||||||
|
|
||||||
nlog("Peppol Invoice: " . json_encode($peppolInvoice, JSON_PRETTY_PRINT));
|
|
||||||
|
|
||||||
// Create the serializer with all necessary normalizers
|
|
||||||
$classMetadataFactory = new ClassMetadataFactory(new AttributeLoader());
|
|
||||||
$metadataAwareNameConverter = new MetadataAwareNameConverter($classMetadataFactory);
|
|
||||||
$objectNormalizer = new ObjectNormalizer($classMetadataFactory, $metadataAwareNameConverter);
|
|
||||||
|
|
||||||
$encoders = [new JsonEncoder()];
|
|
||||||
$normalizers = [
|
|
||||||
new ArrayDenormalizer(),
|
|
||||||
$objectNormalizer,
|
|
||||||
new ObjectNormalizer(), // This is a fallback normalizer
|
|
||||||
];
|
|
||||||
$serializer = new Serializer($normalizers, $encoders);
|
|
||||||
|
|
||||||
// Create the PeppolToStorecoveNormalizer with the serializer
|
|
||||||
$peppolToStorecoveNormalizer = new PeppolToStorecoveNormalizer($serializer);
|
|
||||||
|
|
||||||
// Denormalize the Peppol invoice to a Storecove invoice
|
|
||||||
$storecoveInvoice = $peppolToStorecoveNormalizer->denormalize($peppolInvoice, StorecoveInvoice::class);
|
|
||||||
|
|
||||||
nlog("Storecove Invoice after denormalization: " . json_encode($storecoveInvoice, JSON_PRETTY_PRINT));
|
|
||||||
|
|
||||||
// Serialize the Storecove invoice to JSON
|
|
||||||
$jsonOutput = $serializer->serialize($storecoveInvoice, 'json', [
|
|
||||||
'json_encode_options' => JSON_PRETTY_PRINT
|
|
||||||
]);
|
|
||||||
|
|
||||||
nlog("Final JSON output: " . $jsonOutput);
|
|
||||||
|
|
||||||
// Add assertions to verify the output
|
|
||||||
$this->assertInstanceOf(StorecoveInvoice::class, $storecoveInvoice);
|
|
||||||
$this->assertJson($jsonOutput);
|
|
||||||
|
|
||||||
// Add more specific assertions based on your expected output
|
|
||||||
$decodedOutput = json_decode($jsonOutput, true);
|
|
||||||
$this->assertArrayHasKey('documentCurrency', $decodedOutput);
|
|
||||||
$this->assertArrayHasKey('invoiceNumber', $decodedOutput);
|
|
||||||
$this->assertArrayHasKey('invoiceLines', $decodedOutput);
|
|
||||||
$this->assertIsArray($decodedOutput['invoiceLines']);
|
|
||||||
// Add more assertions as needed
|
|
||||||
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
nlog("Error occurred: " . $e->getMessage());
|
|
||||||
nlog("Stack trace: " . $e->getTraceAsString());
|
|
||||||
throw $e; // Re-throw the exception to fail the test
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public function PestAtRules()
|
public function PestAtRules()
|
||||||
{
|
{
|
||||||
$this->routing_id = 293801;
|
$this->routing_id = 293801;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue