Refactor for einvoicing

This commit is contained in:
David Bomba 2023-08-16 19:55:35 +10:00
parent 33dd437d96
commit 2ba713eeb8
13 changed files with 242 additions and 271 deletions

View File

@ -850,6 +850,7 @@ class InvoiceController extends BaseController
$invoice = $invitation->invoice; $invoice = $invitation->invoice;
$file = $invoice->service()->getEInvoice($contact); $file = $invoice->service()->getEInvoice($contact);
$file_name = $invoice->getFileName("xml");
$headers = ['Content-Type' => 'application/xml']; $headers = ['Content-Type' => 'application/xml'];
@ -858,8 +859,8 @@ class InvoiceController extends BaseController
} }
return response()->streamDownload(function () use ($file) { return response()->streamDownload(function () use ($file) {
echo Storage::get($file); echo $file;
}, basename($file), $headers); }, $file_name, $headers);
} }
/** /**

View File

@ -12,7 +12,6 @@
namespace App\Http\Livewire; namespace App\Http\Livewire;
use App\Services\Invoice\GetInvoiceEInvoice;
use App\Utils\Number; use App\Utils\Number;
use Livewire\Component; use Livewire\Component;
use App\Utils\HtmlEngine; use App\Utils\HtmlEngine;
@ -22,10 +21,12 @@ use App\Models\QuoteInvitation;
use App\Utils\VendorHtmlEngine; use App\Utils\VendorHtmlEngine;
use App\Models\CreditInvitation; use App\Models\CreditInvitation;
use App\Models\InvoiceInvitation; use App\Models\InvoiceInvitation;
use App\Jobs\Invoice\CreateEInvoice;
use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Cache;
use App\Models\PurchaseOrderInvitation; use App\Models\PurchaseOrderInvitation;
use App\Models\RecurringInvoiceInvitation; use App\Models\RecurringInvoiceInvitation;
use App\Jobs\Vendor\CreatePurchaseOrderPdf; use App\Jobs\Vendor\CreatePurchaseOrderPdf;
use App\Services\Invoice\GetInvoiceEInvoice;
use App\Services\PdfMaker\Designs\Utilities\DesignHelpers; use App\Services\PdfMaker\Designs\Utilities\DesignHelpers;
class PdfSlot extends Component class PdfSlot extends Component
@ -102,7 +103,7 @@ class PdfSlot extends Component
$file_name = $this->entity->numberFormatter().'.xml'; $file_name = $this->entity->numberFormatter().'.xml';
$file = (new GetInvoiceEInvoice($this->entity))->run(); $file = (new CreateEInvoice($this->entity))->handle();
$headers = ['Content-Type' => 'application/xml']; $headers = ['Content-Type' => 'application/xml'];

View File

@ -11,38 +11,38 @@
namespace App\Jobs\Entity; namespace App\Jobs\Entity;
use App\Exceptions\FilePermissionsFailure; use App\Utils\Ninja;
use App\Jobs\Invoice\CreateEInvoice; use App\Models\Quote;
use App\Jobs\Invoice\MergeEInvoice;
use App\Libraries\MultiDB;
use App\Models\Credit; use App\Models\Credit;
use App\Models\CreditInvitation;
use App\Models\Design; use App\Models\Design;
use App\Models\Invoice; use App\Models\Invoice;
use App\Models\InvoiceInvitation; use App\Utils\HtmlEngine;
use App\Models\Quote; use App\Libraries\MultiDB;
use Illuminate\Bus\Queueable;
use App\Models\QuoteInvitation; use App\Models\QuoteInvitation;
use App\Utils\Traits\MakesHash;
use App\Models\CreditInvitation;
use App\Models\RecurringInvoice; use App\Models\RecurringInvoice;
use App\Utils\PhantomJS\Phantom;
use App\Models\InvoiceInvitation;
use App\Utils\HostedPDF\NinjaPdf;
use App\Utils\Traits\Pdf\PdfMaker;
use Illuminate\Support\Facades\App;
use App\Jobs\Invoice\CreateEInvoice;
use App\Utils\Traits\NumberFormatter;
use App\Utils\Traits\MakesInvoiceHtml;
use Illuminate\Queue\SerializesModels;
use App\Utils\Traits\Pdf\PageNumbering;
use Illuminate\Support\Facades\Storage;
use Illuminate\Queue\InteractsWithQueue;
use App\Exceptions\FilePermissionsFailure;
use App\Models\RecurringInvoiceInvitation; use App\Models\RecurringInvoiceInvitation;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use horstoeko\zugferd\ZugferdDocumentPdfBuilder;
use App\Services\PdfMaker\Design as PdfDesignModel; use App\Services\PdfMaker\Design as PdfDesignModel;
use App\Services\PdfMaker\Design as PdfMakerDesign; use App\Services\PdfMaker\Design as PdfMakerDesign;
use App\Services\PdfMaker\PdfMaker as PdfMakerService; use App\Services\PdfMaker\PdfMaker as PdfMakerService;
use App\Utils\HostedPDF\NinjaPdf;
use App\Utils\HtmlEngine;
use App\Utils\Ninja;
use App\Utils\PhantomJS\Phantom;
use App\Utils\Traits\MakesHash;
use App\Utils\Traits\MakesInvoiceHtml;
use App\Utils\Traits\NumberFormatter;
use App\Utils\Traits\Pdf\PageNumbering;
use App\Utils\Traits\Pdf\PdfMaker;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Storage;
class CreateEntityPdf implements ShouldQueue class CreateEntityPdf implements ShouldQueue
{ {
@ -117,7 +117,8 @@ class CreateEntityPdf implements ShouldQueue
} }
$entity_design_id = ''; $entity_design_id = '';
$path = '';
if ($this->entity instanceof Invoice) { if ($this->entity instanceof Invoice) {
$path = $this->client->invoice_filepath($this->invitation); $path = $this->client->invoice_filepath($this->invitation);
$entity_design_id = 'invoice_design_id'; $entity_design_id = 'invoice_design_id';
@ -207,6 +208,11 @@ class CreateEntityPdf implements ShouldQueue
info($maker->getCompiledHTML()); info($maker->getCompiledHTML());
} }
if($this->entity_string == "invoice" && $this->client->getSetting('enable_e_invoice'))
{
$pdf = $this->checkEInvoice($pdf);
}
if ($pdf) { if ($pdf) {
try { try {
Storage::disk($this->disk)->put($file_path, $pdf); Storage::disk($this->disk)->put($file_path, $pdf);
@ -214,11 +220,7 @@ class CreateEntityPdf implements ShouldQueue
throw new FilePermissionsFailure($e->getMessage()); throw new FilePermissionsFailure($e->getMessage());
} }
} }
if ($this->entity_string == "invoice" && $this->client->getSetting('enable_e_invoice')){
(new CreateEInvoice($this->entity))->handle();
(new MergeEInvoice($this->entity))->handle();
}
$this->invitation = null; $this->invitation = null;
// $this->entity = null; // $this->entity = null;
$this->company = null; $this->company = null;
@ -227,10 +229,65 @@ class CreateEntityPdf implements ShouldQueue
$maker = null; $maker = null;
$state = null; $state = null;
return $file_path; return $file_path;
} }
/**
* Switch to determine if we need to embed the xml into the PDF itself
*
* @param string $pdf
* @return string
*/
private function checkEInvoice(string $pdf): string
{
if(!$this->entity instanceof Invoice)
return $pdf;
$e_invoice_type = $this->entity->client->getSetting('e_invoice_type');
switch ($e_invoice_type) {
case "EN16931":
case "XInvoice_2_2":
case "XInvoice_2_1":
case "XInvoice_2_0":
case "XInvoice_1_0":
case "XInvoice-Extended":
case "XInvoice-BasicWL":
case "XInvoice-Basic":
return $this->embedEInvoiceZuGFerD($pdf) ?? $pdf;
//case "Facturae_3.2":
//case "Facturae_3.2.1":
//case "Facturae_3.2.2":
//
default:
return $pdf;
}
}
/**
* Embed the .xml file into the PDF
*
* @param string $pdf
* @return string
*/
private function embedEInvoiceZuGFerD(string $pdf): string
{
try {
$e_rechnung = (new CreateEInvoice($this->entity, true))->handle();
$pdfBuilder = new ZugferdDocumentPdfBuilder($e_rechnung, $pdf);
$pdfBuilder->generateDocument();
return $pdfBuilder->downloadString(basename($this->entity->getFileName()));
} catch (\Exception $e) {
nlog("E_Invoice Merge failed - " . $e->getMessage());
}
return $pdf;
}
public function failed($e) public function failed($e)
{ {
} }

View File

@ -11,42 +11,43 @@
namespace App\Jobs\Entity; namespace App\Jobs\Entity;
use App\Exceptions\FilePermissionsFailure; use App\Utils\Ninja;
use App\Jobs\Invoice\MergeEInvoice; use App\Models\Quote;
use App\Libraries\MultiDB;
use App\Models\Credit; use App\Models\Credit;
use App\Models\CreditInvitation;
use App\Models\Design; use App\Models\Design;
use App\Models\Invoice; use App\Models\Invoice;
use App\Models\InvoiceInvitation; use App\Utils\HtmlEngine;
use App\Models\Quote; use App\Libraries\MultiDB;
use Illuminate\Bus\Queueable;
use App\Models\QuoteInvitation; use App\Models\QuoteInvitation;
use App\Utils\Traits\MakesHash;
use App\Models\CreditInvitation;
use App\Models\RecurringInvoice; use App\Models\RecurringInvoice;
use App\Utils\PhantomJS\Phantom;
use App\Models\InvoiceInvitation;
use App\Utils\HostedPDF\NinjaPdf;
use App\Utils\Traits\Pdf\PdfMaker;
use Illuminate\Support\Facades\App;
use App\Jobs\Invoice\CreateEInvoice;
use App\Utils\Traits\NumberFormatter;
use App\Utils\Traits\MakesInvoiceHtml;
use Illuminate\Queue\SerializesModels;
use App\Utils\Traits\Pdf\PageNumbering;
use Illuminate\Queue\InteractsWithQueue;
use App\Exceptions\FilePermissionsFailure;
use App\Models\RecurringInvoiceInvitation; use App\Models\RecurringInvoiceInvitation;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use horstoeko\zugferd\ZugferdDocumentPdfBuilder;
use App\Services\PdfMaker\Design as PdfDesignModel; use App\Services\PdfMaker\Design as PdfDesignModel;
use App\Services\PdfMaker\Design as PdfMakerDesign; use App\Services\PdfMaker\Design as PdfMakerDesign;
use App\Services\PdfMaker\PdfMaker as PdfMakerService; use App\Services\PdfMaker\PdfMaker as PdfMakerService;
use App\Utils\HostedPDF\NinjaPdf;
use App\Utils\HtmlEngine;
use App\Utils\Ninja;
use App\Utils\PhantomJS\Phantom;
use App\Utils\Traits\MakesHash;
use App\Utils\Traits\MakesInvoiceHtml;
use App\Utils\Traits\NumberFormatter;
use App\Utils\Traits\Pdf\PageNumbering;
use App\Utils\Traits\Pdf\PdfMaker;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\App;
class CreateRawPdf implements ShouldQueue class CreateRawPdf implements ShouldQueue
{ {
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, NumberFormatter, MakesInvoiceHtml, PdfMaker, MakesHash, PageNumbering; use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, NumberFormatter, MakesInvoiceHtml, PdfMaker, MakesHash, PageNumbering;
public $entity; public Invoice | Credit | Quote | RecurringInvoice $entity;
public $company; public $company;
@ -104,6 +105,7 @@ class CreateRawPdf implements ShouldQueue
} }
$entity_design_id = ''; $entity_design_id = '';
$path = '';
if ($this->entity instanceof Invoice) { if ($this->entity instanceof Invoice) {
$path = $this->entity->client->invoice_filepath($this->invitation); $path = $this->entity->client->invoice_filepath($this->invitation);
@ -203,17 +205,67 @@ class CreateRawPdf implements ShouldQueue
if ($pdf) { if ($pdf) {
$maker =null; $maker =null;
$state = null; $state = null;
if ($this->invitation->invoice->client->getSetting('enable_e_invoice') && $this->entity_string == "invoice"){
$filename = tempnam(sys_get_temp_dir(), 'InvoiceNinja').".pdf"; return $this->checkEInvoice($pdf);
file_put_contents($filename, $pdf);
(new \App\Services\Invoice\MergeEInvoice($this->invitation->invoice, $filename))->run();
return file_get_contents($filename);
};
return $pdf;
} }
throw new FilePermissionsFailure('Unable to generate the raw PDF'); throw new FilePermissionsFailure('Unable to generate the raw PDF');
} }
/**
* Switch to determine if we need to embed the xml into the PDF itself
*
* @param string $pdf
* @return string
*/
private function checkEInvoice(string $pdf): string
{
if(!$this->entity instanceof Invoice)
return $pdf;
$e_invoice_type = $this->entity->client->getSetting('e_invoice_type');
switch ($e_invoice_type) {
case "EN16931":
case "XInvoice_2_2":
case "XInvoice_2_1":
case "XInvoice_2_0":
case "XInvoice_1_0":
case "XInvoice-Extended":
case "XInvoice-BasicWL":
case "XInvoice-Basic":
return $this->embedEInvoiceZuGFerD($pdf) ?? $pdf;
//case "Facturae_3.2":
//case "Facturae_3.2.1":
//case "Facturae_3.2.2":
//
default:
return $pdf;
}
}
/**
* Embed the .xml file into the PDF
*
* @param string $pdf
* @return string
*/
private function embedEInvoiceZuGFerD(string $pdf): string
{
try {
$e_rechnung = (new CreateEInvoice($this->entity, true))->handle();
$pdfBuilder = new ZugferdDocumentPdfBuilder($e_rechnung, $pdf);
$pdfBuilder->generateDocument();
return $pdfBuilder->downloadString(basename($this->entity->getFileName()));
} catch (\Exception $e) {
nlog("E_Invoice Merge failed - " . $e->getMessage());
}
return $pdf;
}
public function failed($e) public function failed($e)
{ {

View File

@ -37,7 +37,6 @@ class CreateEInvoice implements ShouldQueue
/** /**
* Execute the job. * Execute the job.
* *
*
* @return string|ZugferdDocumentBuilder * @return string|ZugferdDocumentBuilder
*/ */
public function handle(): string|ZugferdDocumentBuilder public function handle(): string|ZugferdDocumentBuilder
@ -64,13 +63,18 @@ class CreateEInvoice implements ShouldQueue
case "XInvoice-Extended": case "XInvoice-Extended":
case "XInvoice-BasicWL": case "XInvoice-BasicWL":
case "XInvoice-Basic": case "XInvoice-Basic":
return (new ZugferdEInvoice($this->invoice, $this->returnObject))->run(); $zugferd = (new ZugferdEInvoice($this->invoice))->run();
return $this->returnObject ? $zugferd->xrechnung : $zugferd->getXml();
case "Facturae_3.2": case "Facturae_3.2":
case "Facturae_3.2.1": case "Facturae_3.2.1":
case "Facturae_3.2.2": case "Facturae_3.2.2":
return (new FacturaEInvoice($this->invoice, str_replace("Facturae_", "", $e_invoice_type)))->run(); return (new FacturaEInvoice($this->invoice, str_replace("Facturae_", "", $e_invoice_type)))->run();
default: default:
return (new ZugferdEInvoice($this->invoice, $this->returnObject))->run();
$zugferd = (new ZugferdEInvoice($this->invoice))->run();
return $this->returnObject ? $zugferd : $zugferd->getXml();
} }

View File

@ -1,70 +0,0 @@
<?php
namespace App\Jobs\Invoice;
use App\Models\ClientContact;
use App\Models\Invoice;
use horstoeko\zugferd\ZugferdDocumentPdfBuilder;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Support\Facades\Storage;
use horstoeko\zugferd\ZugferdDocumentReader;
class MergeEInvoice implements ShouldQueue
{
public function __construct(public Invoice $invoice, private string $pdf_path = "")
{
}
/**
* @throws \Exception
*/
public function handle(): void
{
$e_invoice_type = $this->invoice->client->getSetting('e_invoice_type');
switch ($e_invoice_type) {
case "EN16931":
case "XInvoice_2_2":
case "XInvoice_2_1":
case "XInvoice_2_0":
case "XInvoice_1_0":
case "XInvoice-Extended":
case "XInvoice-BasicWL":
case "XInvoice-Basic":
$this->embedEInvoiceZuGFerD();
//case "Facturae_3.2":
//case "Facturae_3.2.1":
//case "Facturae_3.2.2":
//
default:
$this->embedEInvoiceZuGFerD();
break;
}
}
/**
* @throws \Exception
*/
private function embedEInvoiceZuGFerD(): void
{
try {
$filepath_pdf = !empty($this->pdf_path) ? $this->pdf_path : $this->invoice->service()->getInvoicePdf();
$disk = config('filesystems.default');
$e_rechnung = (new CreateEInvoice($this->invoice, true))->handle();
if (!empty($this->pdf_path)) {
$realpath_pdf = $filepath_pdf;
} else {
$realpath_pdf = Storage::disk($disk)->path($filepath_pdf);
}
if (file_exists($realpath_pdf)) {
$pdfBuilder = new ZugferdDocumentPdfBuilder($e_rechnung, $realpath_pdf);
$pdfBuilder->generateDocument();
$pdfBuilder->saveDocument($realpath_pdf);
} else {
nlog("E_Invoice Merge failed - file to merge not found");
}
} catch (\Exception $e) {
nlog("E_Invoice Merge failed - " . $e->getMessage());
}
}
}

View File

@ -73,28 +73,12 @@ class ZipInvoices implements ShouldQueue
$invitation = $this->invoices->first()->invitations->first(); $invitation = $this->invoices->first()->invitations->first();
$path = $this->invoices->first()->client->invoice_filepath($invitation); $path = $this->invoices->first()->client->invoice_filepath($invitation);
$this->invoices->each(function ($invoice) {
(new CreateEntityPdf($invoice->invitations()->first()))->handle();
if ($invoice->client->getSetting('enable_e_invoice')){
(new CreateEInvoice($invoice))->handle();
(new MergeEInvoice($invoice))->handle();
}
});
try { try {
foreach ($this->invoices as $invoice) { foreach ($this->invoices as $invoice) {
$file = $invoice->service()->getInvoicePdf(); $file = $invoice->service()->getRawInvoicePdf();
$zip_file_name = basename($file); $zip_file_name = $invoice->getFileName();
$zipFile->addFromString($zip_file_name, Storage::get($file)); $zipFile->addFromString($zip_file_name, $file);
if($invoice->client->getSetting('enable_e_invoice')){
$xinvoice = $invoice->service()->getEInvoice();
$xinvoice_zip_file_name = basename($xinvoice);
$zipFile->addFromString($xinvoice_zip_file_name, Storage::get($xinvoice));
}
} }
Storage::put($path.$file_name, $zipFile->outputAsString()); Storage::put($path.$file_name, $zipFile->outputAsString());

View File

@ -276,6 +276,7 @@ class BaseModel extends Model
/** /**
* Returns the base64 encoded PDF string of the entity * Returns the base64 encoded PDF string of the entity
* @deprecated - unused implementation
*/ */
public function fullscreenPdfViewer($invitation = null): string public function fullscreenPdfViewer($invitation = null): string
{ {

View File

@ -322,10 +322,10 @@ class EmailDefaults
} }
/** E-Invoice xml file */ /** E-Invoice xml file */
if ($this->email->email_object->settings->enable_e_invoice && $this->email->email_object->entity instanceof Invoice) { if ($this->email->email_object->settings->enable_e_invoice && $this->email->email_object->entity instanceof Invoice) {
$xinvoice_path = $this->email->email_object->entity->service()->getEInvoice(); $xml_string = $this->email->email_object->entity->service()->getEInvoice();
if(Storage::disk(config('filesystems.default'))->exists($xinvoice_path)) if($xml_string)
$this->email->email_object->attachments = array_merge($this->email->email_object->attachments, [['file' => base64_encode(Storage::get($xinvoice_path)), 'name' => explode(".", $this->email->email_object->entity->getFileName('xml'))[0]."-e_invoice.xml"]]); $this->email->email_object->attachments = array_merge($this->email->email_object->attachments, [['file' => base64_encode($xml_string), 'name' => explode(".", $this->email->email_object->entity->getFileName('xml'))[0]."-e_invoice.xml"]]);
} }
if (!$this->email->email_object->settings->document_email_attachment || !$this->email->company->account->hasFeature(Account::FEATURE_DOCUMENTS)) { if (!$this->email->email_object->settings->document_email_attachment || !$this->email->company->account->hasFeature(Account::FEATURE_DOCUMENTS)) {

View File

@ -22,12 +22,13 @@ use horstoeko\zugferd\codelists\ZugferdDutyTaxFeeCategories;
class ZugferdEInvoice extends AbstractService class ZugferdEInvoice extends AbstractService
{ {
public ZugferdDocumentBuilder $xrechnung;
public function __construct(public Invoice $invoice, private readonly bool $returnObject = false, private array $tax_map = []) public function __construct(public Invoice $invoice, private readonly bool $returnObject = false, private array $tax_map = [])
{ {
} }
public function run(): string|ZugferdDocumentBuilder public function run(): self
{ {
$company = $this->invoice->company; $company = $this->invoice->company;
@ -45,10 +46,9 @@ class ZugferdEInvoice extends AbstractService
default => ZugferdProfiles::PROFILE_EN16931, default => ZugferdProfiles::PROFILE_EN16931,
}; };
$this->xrechnung = ZugferdDocumentBuilder::CreateNew($profile);
$xrechnung = ZugferdDocumentBuilder::CreateNew($profile); $this->xrechnung
$xrechnung
->setDocumentSupplyChainEvent(date_create($this->invoice->date)) ->setDocumentSupplyChainEvent(date_create($this->invoice->date))
->setDocumentSeller($company->getSetting('name')) ->setDocumentSeller($company->getSetting('name'))
->setDocumentSellerAddress($company->getSetting("address1"), $company->getSetting("address2"), "", $company->getSetting("postal_code"), $company->getSetting("city"), $company->country()->iso_3166_2, $company->getSetting("state")) ->setDocumentSellerAddress($company->getSetting("address1"), $company->getSetting("address2"), "", $company->getSetting("postal_code"), $company->getSetting("city"), $company->country()->iso_3166_2, $company->getSetting("state"))
@ -59,32 +59,32 @@ class ZugferdEInvoice extends AbstractService
->addDocumentPaymentTerm(ctrans("texts.xinvoice_payable", ['payeddue' => date_create($this->invoice->date)->diff(date_create($this->invoice->due_date))->format("%d"), 'paydate' => $this->invoice->due_date])); ->addDocumentPaymentTerm(ctrans("texts.xinvoice_payable", ['payeddue' => date_create($this->invoice->date)->diff(date_create($this->invoice->due_date))->format("%d"), 'paydate' => $this->invoice->due_date]));
if (!empty($this->invoice->public_notes)) { if (!empty($this->invoice->public_notes)) {
$xrechnung->addDocumentNote($this->invoice->public_notes); $this->xrechnung->addDocumentNote($this->invoice->public_notes);
} }
if (empty($this->invoice->number)){ if (empty($this->invoice->number)){
$xrechnung->setDocumentInformation("DRAFT", "380", date_create($this->invoice->date), $this->invoice->client->getCurrencyCode()); $this->xrechnung->setDocumentInformation("DRAFT", "380", date_create($this->invoice->date), $this->invoice->client->getCurrencyCode());
} else { } else {
$xrechnung->setDocumentInformation($this->invoice->number, "380", date_create($this->invoice->date), $this->invoice->client->getCurrencyCode()); $this->xrechnung->setDocumentInformation($this->invoice->number, "380", date_create($this->invoice->date), $this->invoice->client->getCurrencyCode());
} }
if (!empty($this->invoice->po_number)) { if (!empty($this->invoice->po_number)) {
$xrechnung->setDocumentBuyerOrderReferencedDocument($this->invoice->po_number); $this->xrechnung->setDocumentBuyerOrderReferencedDocument($this->invoice->po_number);
} }
if (empty($client->routing_id)) { if (empty($client->routing_id)) {
$xrechnung->setDocumentBuyerReference(ctrans("texts.xinvoice_no_buyers_reference")); $this->xrechnung->setDocumentBuyerReference(ctrans("texts.xinvoice_no_buyers_reference"));
} else { } else {
$xrechnung->setDocumentBuyerReference($client->routing_id); $this->xrechnung->setDocumentBuyerReference($client->routing_id);
} }
if (!empty($client->shipping_address1)){ if (!empty($client->shipping_address1)){
$xrechnung->setDocumentShipToAddress($client->shipping_address1, $client->shipping_address2, "", $client->shipping_postal_code, $client->shipping_city, $client->shipping_country->iso_3166_2, $client->shipping_state); $this->xrechnung->setDocumentShipToAddress($client->shipping_address1, $client->shipping_address2, "", $client->shipping_postal_code, $client->shipping_city, $client->shipping_country->iso_3166_2, $client->shipping_state);
} }
$xrechnung->addDocumentPaymentMean(68, ctrans("texts.xinvoice_online_payment")); $this->xrechnung->addDocumentPaymentMean(68, ctrans("texts.xinvoice_online_payment"));
if (str_contains($company->getSetting('vat_number'), "/")) { if (str_contains($company->getSetting('vat_number'), "/")) {
$xrechnung->addDocumentSellerTaxRegistration("FC", $company->getSetting('vat_number')); $this->xrechnung->addDocumentSellerTaxRegistration("FC", $company->getSetting('vat_number'));
} else { } else {
$xrechnung->addDocumentSellerTaxRegistration("VA", $company->getSetting('vat_number')); $this->xrechnung->addDocumentSellerTaxRegistration("VA", $company->getSetting('vat_number'));
} }
$invoicing_data = $this->invoice->calc(); $invoicing_data = $this->invoice->calc();
@ -92,29 +92,29 @@ class ZugferdEInvoice extends AbstractService
//Create line items and calculate taxes //Create line items and calculate taxes
foreach ($this->invoice->line_items as $index => $item) { foreach ($this->invoice->line_items as $index => $item) {
/** @var \App\DataMapper\InvoiceItem $item **/ /** @var \App\DataMapper\InvoiceItem $item **/
$xrechnung->addNewPosition($index) $this->xrechnung->addNewPosition($index)
->setDocumentPositionGrossPrice($item->gross_line_total) ->setDocumentPositionGrossPrice($item->gross_line_total)
->setDocumentPositionNetPrice($item->line_total); ->setDocumentPositionNetPrice($item->line_total);
if (!empty($item->product_key)){ if (!empty($item->product_key)){
if (!empty($item->notes)){ if (!empty($item->notes)){
$xrechnung->setDocumentPositionProductDetails($item->product_key, $item->notes); $this->xrechnung->setDocumentPositionProductDetails($item->product_key, $item->notes);
} }
else { else {
$xrechnung->setDocumentPositionProductDetails($item->product_key); $this->xrechnung->setDocumentPositionProductDetails($item->product_key);
} }
} }
else { else {
if (!empty($item->notes)){ if (!empty($item->notes)){
$xrechnung->setDocumentPositionProductDetails($item->notes); $this->xrechnung->setDocumentPositionProductDetails($item->notes);
} }
else { else {
$xrechnung->setDocumentPositionProductDetails("no product name defined"); $this->xrechnung->setDocumentPositionProductDetails("no product name defined");
} }
} }
if (isset($item->task_id)) { if (isset($item->task_id)) {
$xrechnung->setDocumentPositionQuantity($item->quantity, "HUR"); $this->xrechnung->setDocumentPositionQuantity($item->quantity, "HUR");
} else { } else {
$xrechnung->setDocumentPositionQuantity($item->quantity, "H87"); $this->xrechnung->setDocumentPositionQuantity($item->quantity, "H87");
} }
$linenetamount = $item->line_total; $linenetamount = $item->line_total;
if ($item->discount > 0) { if ($item->discount > 0) {
@ -124,18 +124,18 @@ class ZugferdEInvoice extends AbstractService
$linenetamount -= $linenetamount * ($item->discount / 100); $linenetamount -= $linenetamount * ($item->discount / 100);
} }
} }
$xrechnung->setDocumentPositionLineSummation($linenetamount); $this->xrechnung->setDocumentPositionLineSummation($linenetamount);
// According to european law, each line item can only have one tax rate // According to european law, each line item can only have one tax rate
if (!(empty($item->tax_name1) && empty($item->tax_name2) && empty($item->tax_name3))) { if (!(empty($item->tax_name1) && empty($item->tax_name2) && empty($item->tax_name3))) {
$taxtype = $this->getTaxType($item->tax_id); $taxtype = $this->getTaxType($item->tax_id);
if (!empty($item->tax_name1)) { if (!empty($item->tax_name1)) {
$xrechnung->addDocumentPositionTax($taxtype, 'VAT', $item->tax_rate1); $this->xrechnung->addDocumentPositionTax($taxtype, 'VAT', $item->tax_rate1);
$this->addtoTaxMap($taxtype, $linenetamount, $item->tax_rate1); $this->addtoTaxMap($taxtype, $linenetamount, $item->tax_rate1);
} elseif (!empty($item->tax_name2)) { } elseif (!empty($item->tax_name2)) {
$xrechnung->addDocumentPositionTax($taxtype, 'VAT', $item->tax_rate2); $this->xrechnung->addDocumentPositionTax($taxtype, 'VAT', $item->tax_rate2);
$this->addtoTaxMap($taxtype, $linenetamount, $item->tax_rate2); $this->addtoTaxMap($taxtype, $linenetamount, $item->tax_rate2);
} elseif (!empty($item->tax_name3)) { } elseif (!empty($item->tax_name3)) {
$xrechnung->addDocumentPositionTax($taxtype, 'VAT', $item->tax_rate3); $this->xrechnung->addDocumentPositionTax($taxtype, 'VAT', $item->tax_rate3);
$this->addtoTaxMap($taxtype, $linenetamount, $item->tax_rate3); $this->addtoTaxMap($taxtype, $linenetamount, $item->tax_rate3);
} else { } else {
nlog("Can't add correct tax position"); nlog("Can't add correct tax position");
@ -143,45 +143,45 @@ class ZugferdEInvoice extends AbstractService
} else { } else {
if (!empty($this->invoice->tax_name1)) { if (!empty($this->invoice->tax_name1)) {
$taxtype = $this->getTaxType($this->invoice->tax_name1); $taxtype = $this->getTaxType($this->invoice->tax_name1);
$xrechnung->addDocumentPositionTax($taxtype, 'VAT', $this->invoice->tax_rate1); $this->xrechnung->addDocumentPositionTax($taxtype, 'VAT', $this->invoice->tax_rate1);
$this->addtoTaxMap($taxtype, $linenetamount, $this->invoice->tax_rate1); $this->addtoTaxMap($taxtype, $linenetamount, $this->invoice->tax_rate1);
} elseif (!empty($this->invoice->tax_name2)) { } elseif (!empty($this->invoice->tax_name2)) {
$taxtype = $this->getTaxType($this->invoice->tax_name2); $taxtype = $this->getTaxType($this->invoice->tax_name2);
$xrechnung->addDocumentPositionTax($taxtype, 'VAT', $this->invoice->tax_rate2); $this->xrechnung->addDocumentPositionTax($taxtype, 'VAT', $this->invoice->tax_rate2);
$this->addtoTaxMap($taxtype, $linenetamount, $this->invoice->tax_rate2); $this->addtoTaxMap($taxtype, $linenetamount, $this->invoice->tax_rate2);
} elseif (!empty($this->invoice->tax_name3)) { } elseif (!empty($this->invoice->tax_name3)) {
$taxtype = $this->getTaxType($this->invoice->tax_name3); $taxtype = $this->getTaxType($this->invoice->tax_name3);
$xrechnung->addDocumentPositionTax($taxtype, 'VAT', $this->invoice->tax_rate3); $this->xrechnung->addDocumentPositionTax($taxtype, 'VAT', $this->invoice->tax_rate3);
$this->addtoTaxMap($taxtype, $linenetamount, $this->invoice->tax_rate3); $this->addtoTaxMap($taxtype, $linenetamount, $this->invoice->tax_rate3);
} else { } else {
$taxtype = ZugferdDutyTaxFeeCategories::ZERO_RATED_GOODS; $taxtype = ZugferdDutyTaxFeeCategories::ZERO_RATED_GOODS;
$xrechnung->addDocumentPositionTax($taxtype, 'VAT', 0); $this->xrechnung->addDocumentPositionTax($taxtype, 'VAT', 0);
$this->addtoTaxMap($taxtype, $linenetamount, 0); $this->addtoTaxMap($taxtype, $linenetamount, 0);
nlog("Can't add correct tax position"); nlog("Can't add correct tax position");
} }
} }
} }
$xrechnung->setDocumentSummation($this->invoice->amount, $this->invoice->balance, $invoicing_data->getSubTotal(), $invoicing_data->getTotalSurcharges(), $invoicing_data->getTotalDiscount(), $invoicing_data->getSubTotal(), $invoicing_data->getItemTotalTaxes(), 0.0, $this->invoice->amount-$this->invoice->balance); $this->xrechnung->setDocumentSummation($this->invoice->amount, $this->invoice->balance, $invoicing_data->getSubTotal(), $invoicing_data->getTotalSurcharges(), $invoicing_data->getTotalDiscount(), $invoicing_data->getSubTotal(), $invoicing_data->getItemTotalTaxes(), 0.0, $this->invoice->amount-$this->invoice->balance);
foreach ($this->tax_map as $item){ foreach ($this->tax_map as $item){
$xrechnung->addDocumentTax($item["tax_type"], "VAT", $item["net_amount"], $item["tax_rate"]*$item["net_amount"], $item["tax_rate"]*100); $this->xrechnung->addDocumentTax($item["tax_type"], "VAT", $item["net_amount"], $item["tax_rate"]*$item["net_amount"], $item["tax_rate"]*100);
}
$disk = config('filesystems.default');
if (!Storage::disk($disk)->exists($client->e_invoice_filepath($this->invoice->invitations->first()))) {
Storage::makeDirectory($client->e_invoice_filepath($this->invoice->invitations->first()));
} }
$xrechnung->writeFile(Storage::disk($disk)->path($client->e_invoice_filepath($this->invoice->invitations->first()) . $this->invoice->getFileName("xml")));
// The validity can be checked using https://portal3.gefeg.com/invoice/validation or https://e-rechnung.bayern.de/app/#/upload // The validity can be checked using https://portal3.gefeg.com/invoice/validation or https://e-rechnung.bayern.de/app/#/upload
if ($this->returnObject){ return $this;
return $xrechnung;
} }
throw new \Exception("Invalid e invoice object"); /**
* Returns the XML document
// return $client->e_invoice_filepath($this->invoice->invitations->first()) . $this->invoice->getFileName("xml"); * in string format
*
* @return string
*/
public function getXml(): string
{
return $this->xrechnung->getContent();
} }
private function getTaxType($name): string private function getTaxType($name): string

View File

@ -1,52 +0,0 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Services\Invoice;
use App\Jobs\Invoice\CreateEInvoice;
use App\Models\ClientContact;
use App\Models\Invoice;
use App\Services\AbstractService;
use Illuminate\Support\Facades\Storage;
class GetInvoiceEInvoice extends AbstractService
{
public function __construct(public Invoice $invoice, public ?ClientContact $contact = null)
{
}
public function run()
{
if (! $this->contact) {
$this->contact = $this->invoice->client->primary_contact()->first() ?: $this->invoice->client->contacts()->first();
}
$invitation = $this->invoice->invitations->where('client_contact_id', $this->contact->id)->first();
if (! $invitation) {
$invitation = $this->invoice->invitations->first();
}
$file_path = $this->invoice->client->e_invoice_filepath($this->invoice->invitations->first()). $this->invoice->getFileName("xml");
// $disk = 'public';
$disk = config('filesystems.default');
$file = Storage::disk($disk)->exists($file_path);
if (! $file) {
$file_path = (new CreateEInvoice($this->invoice))->handle();
(new \App\Jobs\Invoice\MergeEInvoice($this->invoice))->handle();
}
return $file_path;
}
}

View File

@ -13,7 +13,6 @@ namespace App\Services\Invoice;
use App\Jobs\Entity\CreateEntityPdf; use App\Jobs\Entity\CreateEntityPdf;
use App\Jobs\Invoice\CreateEInvoice; use App\Jobs\Invoice\CreateEInvoice;
use App\Jobs\Invoice\MergeEInvoice;
use App\Models\ClientContact; use App\Models\ClientContact;
use App\Models\Invoice; use App\Models\Invoice;
use App\Services\AbstractService; use App\Services\AbstractService;
@ -49,10 +48,7 @@ class GetInvoicePdf extends AbstractService
if (! $file) { if (! $file) {
$file_path = (new CreateEntityPdf($invitation))->handle(); $file_path = (new CreateEntityPdf($invitation))->handle();
} }
if ($this->invoice->client->getSetting('enable_e_invoice')){
(new CreateEInvoice($this->invoice))->handle();
(new MergeEInvoice($this->invoice, $file_path))->handle();
}
return $file_path; return $file_path;
} }
} }

View File

@ -13,6 +13,7 @@ namespace App\Services\Invoice;
use App\Events\Invoice\InvoiceWasArchived; use App\Events\Invoice\InvoiceWasArchived;
use App\Jobs\Entity\CreateEntityPdf; use App\Jobs\Entity\CreateEntityPdf;
use App\Jobs\Entity\CreateRawPdf;
use App\Jobs\Inventory\AdjustProductInventory; use App\Jobs\Inventory\AdjustProductInventory;
use App\Jobs\Invoice\CreateEInvoice; use App\Jobs\Invoice\CreateEInvoice;
use App\Libraries\Currency\Conversion\CurrencyApi; use App\Libraries\Currency\Conversion\CurrencyApi;
@ -187,6 +188,13 @@ class InvoiceService
return (new GetInvoicePdf($this->invoice, $contact))->run(); return (new GetInvoicePdf($this->invoice, $contact))->run();
} }
public function getRawInvoicePdf($contact = null)
{
$invitation = $contact ? $this->invoice->invitations()->where('contact_id', $contact->id)->first() : $this->invoice->invitations()->first();
return (new CreateRawPdf($invitation, $invitation->company->db))->handle();
}
public function getInvoiceDeliveryNote(Invoice $invoice, \App\Models\ClientContact $contact = null) public function getInvoiceDeliveryNote(Invoice $invoice, \App\Models\ClientContact $contact = null)
{ {
return (new GenerateDeliveryNote($invoice, $contact))->run(); return (new GenerateDeliveryNote($invoice, $contact))->run();
@ -194,13 +202,9 @@ class InvoiceService
public function getEInvoice($contact = null) public function getEInvoice($contact = null)
{ {
return (new GetInvoiceEInvoice($this->invoice, $contact))->run(); return (new CreateEInvoice($this->invoice))->handle();
} }
public function mergeEInvoice($contact = null): void
{
(new MergeEInvoice($this->invoice, $contact))->run();
}
public function sendEmail($contact = null) public function sendEmail($contact = null)
{ {
$send_email = new SendEmail($this->invoice, null, $contact); $send_email = new SendEmail($this->invoice, null, $contact);
@ -464,13 +468,6 @@ class InvoiceService
if ($force) { if ($force) {
$this->invoice->invitations->each(function ($invitation) { $this->invoice->invitations->each(function ($invitation) {
(new CreateEntityPdf($invitation))->handle(); (new CreateEntityPdf($invitation))->handle();
if ($invitation->invoice->client->getSetting('enable_e_invoice') && $invitation instanceof InvoiceInvitation)
{
(new CreateEInvoice($invitation->invoice))->handle();
(new MergeEInvoice($invitation->invoice))->run();
}
}); });
return $this; return $this;