diff --git a/VERSION.txt b/VERSION.txt index 02a68e85db..b3ec1cfe93 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -5.10.42 \ No newline at end of file +5.10.43 \ No newline at end of file diff --git a/app/Http/Controllers/SelfUpdateController.php b/app/Http/Controllers/SelfUpdateController.php index f48230d361..0ae16be627 100644 --- a/app/Http/Controllers/SelfUpdateController.php +++ b/app/Http/Controllers/SelfUpdateController.php @@ -63,6 +63,9 @@ class SelfUpdateController extends BaseController $file_headers = @get_headers($this->getDownloadUrl()); + nlog("Download URL"); + nlog($this->getDownloadUrl()); + if(!is_array($file_headers)) { nlog($file_headers); return response()->json(['message' => 'There was a problem reaching the update server, please try again in a little while.'], 410); diff --git a/app/Services/EDocument/Gateway/Storecove/Models/Invoice.php b/app/Services/EDocument/Gateway/Storecove/Models/Invoice.php index e130b63756..b0485b69f4 100644 --- a/app/Services/EDocument/Gateway/Storecove/Models/Invoice.php +++ b/app/Services/EDocument/Gateway/Storecove/Models/Invoice.php @@ -1,92 +1,171 @@ taxSystem = $taxSystem; + private array $invoiceLines = []; + + // Getters and setters for all properties + + public function setDocumentCurrency(string $documentCurrency): void + { $this->documentCurrency = $documentCurrency; + } + + public function setInvoiceNumber(string $invoiceNumber): void + { $this->invoiceNumber = $invoiceNumber; + } + + public function setIssueDate(DateTime $issueDate): void + { $this->issueDate = $issueDate; + } + + public function setTaxPointDate(?DateTime $taxPointDate): void + { $this->taxPointDate = $taxPointDate; + } + + public function setDueDate(DateTime $dueDate): void + { $this->dueDate = $dueDate; + } + + public function setInvoicePeriod(array $invoicePeriod): void + { $this->invoicePeriod = $invoicePeriod; + } + + public function setReferences(array $references): void + { $this->references = $references; + } + + public function setAccountingCost(?string $accountingCost): void + { $this->accountingCost = $accountingCost; + } + + public function setNote(string $note): void + { $this->note = $note; - $this->accountingSupplierParty = $accountingSupplierParty; - $this->accountingCustomerParty = $accountingCustomerParty; - $this->delivery = $delivery; - $this->paymentTerms = $paymentTerms; - $this->paymentMeansArray = $paymentMeansArray; - $this->invoiceLines = $invoiceLines; - $this->allowanceCharges = $allowanceCharges; - $this->taxSubtotals = $taxSubtotals; + } + + public function setAmountIncludingVat(float $amountIncludingVat): void + { $this->amountIncludingVat = $amountIncludingVat; + } + + public function setPrepaidAmount(?float $prepaidAmount): void + { $this->prepaidAmount = $prepaidAmount; } + + public function setAccountingSupplierParty(array $accountingSupplierParty): void + { + $this->accountingSupplierParty = $accountingSupplierParty; + } + + public function setAccountingCustomerParty(array $accountingCustomerParty): void + { + $this->accountingCustomerParty = $accountingCustomerParty; + } + + public function setPaymentMeans(array $paymentMeans): void + { + $this->paymentMeans = $paymentMeans; + } + + public function setTaxTotal(array $taxTotal): void + { + $this->taxTotal = $taxTotal; + } + + /** + * @param InvoiceLines[] $invoiceLines + */ + public function setInvoiceLines(array $invoiceLines): void + { + $this->invoiceLines = $invoiceLines; + } + + public function jsonSerialize(): mixed + { + return [ + 'taxSystem' => $this->taxSystem, + 'documentCurrency' => $this->documentCurrency, + 'invoiceNumber' => $this->invoiceNumber, + 'issueDate' => $this->issueDate->format('Y-m-d'), + 'taxPointDate' => $this->taxPointDate ? $this->taxPointDate->format('Y-m-d') : null, + 'dueDate' => $this->dueDate->format('Y-m-d'), + 'invoicePeriod' => $this->invoicePeriod, + 'references' => $this->references, + 'accountingCost' => $this->accountingCost, + 'note' => $this->note, + 'amountIncludingVat' => $this->amountIncludingVat, + 'prepaidAmount' => $this->prepaidAmount, + 'accountingSupplierParty' => $this->accountingSupplierParty, + 'accountingCustomerParty' => $this->accountingCustomerParty, + 'paymentMeans' => $this->paymentMeans, + 'taxTotal' => $this->taxTotal, + 'invoiceLines' => $this->invoiceLines, + ]; + } } diff --git a/app/Services/EDocument/Gateway/Storecove/Models/InvoiceLines.php b/app/Services/EDocument/Gateway/Storecove/Models/InvoiceLines.php index 4e97adff74..34dd1e1e06 100644 --- a/app/Services/EDocument/Gateway/Storecove/Models/InvoiceLines.php +++ b/app/Services/EDocument/Gateway/Storecove/Models/InvoiceLines.php @@ -1,77 +1,119 @@ tax = new Tax(); + } + + // Getters and setters + public function getLineId(): string + { + return $this->lineId; + } + + public function setLineId(string $lineId): void + { $this->lineId = $lineId; + } + + public function getAmountExcludingVat(): float + { + return $this->amountExcludingVat; + } + + public function setAmountExcludingVat(float $amountExcludingVat): void + { $this->amountExcludingVat = $amountExcludingVat; + } + + public function getItemPrice(): float + { + return $this->itemPrice; + } + + public function setItemPrice(float $itemPrice): void + { $this->itemPrice = $itemPrice; - $this->baseQuantity = $baseQuantity; + } + + public function getQuantity(): int + { + return $this->quantity; + } + + public function setQuantity(int $quantity): void + { $this->quantity = $quantity; + } + + public function getQuantityUnitCode(): string + { + return $this->quantityUnitCode; + } + + public function setQuantityUnitCode(string $quantityUnitCode): void + { $this->quantityUnitCode = $quantityUnitCode; - $this->allowanceCharges = $allowanceCharges; - $this->tax = $tax; - $this->orderLineReferenceLineId = $orderLineReferenceLineId; - $this->accountingCost = $accountingCost; + } + + public function getName(): string + { + return $this->name; + } + + public function setName(string $name): void + { $this->name = $name; + } + + public function getDescription(): string + { + return $this->description; + } + + public function setDescription(string $description): void + { $this->description = $description; - $this->invoicePeriod = $invoicePeriod; - $this->note = $note; - $this->references = $references; - $this->additionalItemProperties = $additionalItemProperties; + } + + public function getTax(): Tax + { + return $this->tax; + } + + public function setTax(Tax $tax): void + { + $this->tax = $tax; } } + diff --git a/app/Services/EDocument/Gateway/Storecove/Models/Tax.php b/app/Services/EDocument/Gateway/Storecove/Models/Tax.php index 4908b9619e..b8a504e8cc 100644 --- a/app/Services/EDocument/Gateway/Storecove/Models/Tax.php +++ b/app/Services/EDocument/Gateway/Storecove/Models/Tax.php @@ -11,19 +11,61 @@ namespace App\Services\EDocument\Gateway\Storecove\Models; +use Symfony\Component\Serializer\Annotation\SerializedName; + + class Tax { - public int $percentage; - public string $country; - public string $category; + #[SerializedName('Item.ClassifiedTaxCategory.0.Percent')] + public float $taxPercentage = 0.0; - public function __construct( - int $percentage, - string $country, - string $category - ) { - $this->percentage = $percentage; - $this->country = $country; - $this->category = $category; + #[SerializedName('LineExtensionAmount.amount')] + public float $taxableAmount = 0.0; + + #[SerializedName('TaxTotal.0.TaxAmount.amount')] + public float $taxAmount = 0.0; + + #[SerializedName('Item.ClassifiedTaxCategory.0.ID.value')] + public string $taxCategory = ''; + + // Getters and setters + public function getTaxPercentage(): float + { + return $this->taxPercentage; + } + + public function setTaxPercentage(float $taxPercentage): void + { + $this->taxPercentage = $taxPercentage; + } + + public function getTaxableAmount(): float + { + return $this->taxableAmount; + } + + public function setTaxableAmount(float $taxableAmount): void + { + $this->taxableAmount = $taxableAmount; + } + + public function getTaxAmount(): float + { + return $this->taxAmount; + } + + public function setTaxAmount(float $taxAmount): void + { + $this->taxAmount = $taxAmount; + } + + public function getTaxCategory(): string + { + return $this->taxCategory; + } + + public function setTaxCategory(string $taxCategory): void + { + $this->taxCategory = $taxCategory; } } diff --git a/app/Services/EDocument/Standards/Peppol.php b/app/Services/EDocument/Standards/Peppol.php index 4c80c603f1..baafbc257e 100644 --- a/app/Services/EDocument/Standards/Peppol.php +++ b/app/Services/EDocument/Standards/Peppol.php @@ -896,6 +896,26 @@ class Peppol extends AbstractService $party_name->Name = $this->invoice->company->present()->name(); $party->PartyName[] = $party_name; + + + if (strlen($this->company->settings->vat_number ?? '') > 1) { + + $pi = new PartyIdentification(); + + $vatID = new ID(); + + if ($scheme = $this->resolveTaxScheme()) { + $vatID->schemeID = $scheme; + } + + $vatID->value = $this->company->settings->vat_number; //todo if we are cross border - switch to the supplier local vat number + $pi->ID = $vatID; + + $party->PartyIdentification[] = $pi; + + } + + $address = new Address(); $address->CityName = $this->invoice->company->settings->city; $address->StreetName = $this->invoice->company->settings->address1; diff --git a/app/Services/Pdf/PdfService.php b/app/Services/Pdf/PdfService.php index bdb94a78d8..85d115e8a4 100644 --- a/app/Services/Pdf/PdfService.php +++ b/app/Services/Pdf/PdfService.php @@ -88,7 +88,6 @@ class PdfService */ public function getPdf() { - try { $pdf = $this->resolvePdfEngine($this->getHtml()); diff --git a/app/Services/Template/TemplateService.php b/app/Services/Template/TemplateService.php index cb02859bb2..aa5ed530d0 100644 --- a/app/Services/Template/TemplateService.php +++ b/app/Services/Template/TemplateService.php @@ -283,7 +283,6 @@ class TemplateService */ public function processData($data): self { - $this->data = $this->preProcessDataBlocks($data); return $this; @@ -494,6 +493,7 @@ class TemplateService */ private function preProcessDataBlocks($data): array { + return collect($data)->map(function ($value, $key) { $processed = []; @@ -962,7 +962,8 @@ class TemplateService 'created_at' => $this->translateDate($task->created_at, $task->client ? $task->client->date_format() : $task->company->date_format(), $task->client ? $task->client->locale() : $task->company->locale()), 'updated_at' => $this->translateDate($task->updated_at, $task->client ? $task->client->date_format() : $task->company->date_format(), $task->client ? $task->client->locale() : $task->company->locale()), 'date' => $task->calculated_start_date ? $this->translateDate($task->calculated_start_date, $task->client ? $task->client->date_format() : $task->company->date_format(), $task->client ? $task->client->locale() : $task->company->locale()) : '', - 'project' => $task->project ? $this->transformProject($task->project, true) : [], + // 'project' => $task->project ? $this->transformProject($task->project, true) : [], + 'project' => $task->project ? $task->project->name : '', 'time_log' => $task->processLogsExpandedNotation(), 'custom_value1' => $task->custom_value1 ?: '', 'custom_value2' => $task->custom_value2 ?: '', @@ -1021,7 +1022,7 @@ class TemplateService */ public function processProjects($projects): array { - + return collect($projects)->map(function ($project) { @@ -1042,7 +1043,7 @@ class TemplateService private function transformProject(Project $project, bool $nested = false): array { - + return [ 'name' => $project->name ?: '', 'number' => $project->number ?: '', diff --git a/config/ninja.php b/config/ninja.php index e6bc59a3ff..cf2113df5a 100644 --- a/config/ninja.php +++ b/config/ninja.php @@ -17,8 +17,8 @@ return [ 'require_https' => env('REQUIRE_HTTPS', true), 'app_url' => rtrim(env('APP_URL', ''), '/'), 'app_domain' => env('APP_DOMAIN', 'invoicing.co'), - 'app_version' => env('APP_VERSION', '5.10.42'), - 'app_tag' => env('APP_TAG', '5.10.42'), + 'app_version' => env('APP_VERSION', '5.10.43'), + 'app_tag' => env('APP_TAG', '5.10.43'), 'minimum_client_version' => '5.0.16', 'terms_version' => '1.0.1', 'api_secret' => env('API_SECRET', false), diff --git a/tests/Feature/EInvoice/PeppolTest.php b/tests/Feature/EInvoice/PeppolTest.php index efb362e266..09d73f2095 100644 --- a/tests/Feature/EInvoice/PeppolTest.php +++ b/tests/Feature/EInvoice/PeppolTest.php @@ -172,7 +172,6 @@ class PeppolTest extends TestCase $this->assertCount(0, $errors); - } diff --git a/tests/Integration/Einvoice/Storecove/StorecoveTest.php b/tests/Integration/Einvoice/Storecove/StorecoveTest.php index ea4ca4b14b..f761f2fc73 100644 --- a/tests/Integration/Einvoice/Storecove/StorecoveTest.php +++ b/tests/Integration/Einvoice/Storecove/StorecoveTest.php @@ -22,8 +22,20 @@ use App\DataMapper\Tax\TaxModel; use App\DataMapper\ClientSettings; use App\DataMapper\CompanySettings; use App\Services\EDocument\Standards\Peppol; +use Symfony\Component\Serializer\Serializer; use InvoiceNinja\EInvoice\Models\Peppol\PaymentMeans; +use Symfony\Component\Serializer\Encoder\JsonEncoder; use Illuminate\Foundation\Testing\DatabaseTransactions; +use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; +use Symfony\Component\Serializer\Normalizer\ArrayDenormalizer; +use Symfony\Component\Serializer\Mapping\Loader\AttributeLoader; +use InvoiceNinja\EInvoice\Models\Peppol\Invoice as PeppolInvoice; +use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory; + +use App\Services\EDocument\Gateway\Storecove\PeppolToStorecoveNormalizer; + +use Symfony\Component\Serializer\NameConverter\MetadataAwareNameConverter; +use App\Services\EDocument\Gateway\Storecove\Models\Invoice as StorecoveInvoice; class StorecoveTest extends TestCase { @@ -1320,6 +1332,81 @@ 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() { $this->routing_id = 293801;