Create invoices in XML Format.

This commit is contained in:
2025-01-07 12:52:25 +01:00
parent 8e0696d438
commit c20a3bcdcf
9 changed files with 217 additions and 14 deletions

View File

@@ -0,0 +1,29 @@
<?php
namespace App\Http\Controllers;
use App\Http\Option;
use App\Models\Invoice;
use Symfony\Component\HttpFoundation\StreamedResponse;
class EController extends Controller
{
public function downloadInvoice(int $id): StreamedResponse
{
$invoice = Invoice::find($id);
$taxes = [];
foreach ($invoice->items as $item) {
if (!isset($taxes[$item->tax])) {
$taxes[$item->tax] = ['tax' => 0, 'taxable' => 0];
}
$taxes[$item->tax]['tax'] += round($item->price * $item->amount * $item->tax / 100, 2, PHP_ROUND_HALF_UP);
$taxes[$item->tax]['taxable'] += $item->price * $item->amount;
}
return response()->streamDownload(function () use ($invoice, $taxes) {
echo view('xml.invoice', ['invoice' => $invoice, 'options' => Option::optionsAsObject(), 'taxes' => $taxes]);
}, 'test.xml', ['Content-Type' => 'application/xml']);
}
}

View File

@@ -2,8 +2,8 @@
namespace App\Http\Controllers;
use App\Http\Option;
use App\Models\Invoice;
use App\Models\Option;
use Barryvdh\DomPDF\Facade\Pdf;
use Illuminate\Http\Response;
@@ -12,13 +12,7 @@ class PdfController extends Controller
public function downloadInvoice(int $invoice_id): Response
{
$invoice = Invoice::find($invoice_id);
$all_options = Option::all(['name', 'value']);
$options = new \stdClass();
foreach ($all_options as $option) {
$key = $option->name;
$options->$key = $option->value;
}
return Pdf::loadView('pdfs.invoice', ['invoice' => $invoice->load(['address', 'delivery']), 'options' => $options])->stream();
return Pdf::loadView('pdfs.invoice', ['invoice' => $invoice->load(['address', 'delivery']), 'options' => Option::optionsAsObject()])->stream();
}
}

17
app/Http/Option.php Normal file
View File

@@ -0,0 +1,17 @@
<?php
namespace App\Http;
class Option
{
public static function optionsAsObject()
{
$all_options = \App\Models\Option::all(['name', 'value']);
$options = new \stdClass();
foreach ($all_options as $option) {
$key = $option->name;
$options->$key = $option->value;
}
return $options;
}
}

View File

@@ -0,0 +1,23 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512" {{ $attributes->merge(['class' => 'size-8 p-1']) }}>
<path
d="M320 464c8.8 0 16-7.2 16-16l0-288-80 0c-17.7 0-32-14.3-32-32l0-80L64 48c-8.8 0-16 7.2-16 16l0 384c0 8.8 7.2 16 16 16l256 0zM0 64C0 28.7 28.7 0 64 0L229.5 0c17 0 33.3 6.7 45.3 18.7l90.5 90.5c12 12 18.7 28.3 18.7 45.3L384 448c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 64z"
id="path2" />
<rect
style="fill:#ffffff;stroke-width:1.71413"
id="rect482"
width="172.96552"
height="190.99074"
x="211.70058"
y="322.9201" />
<text
xml:space="preserve"
style="font-weight:bold;font-size:311.933px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif Bold';stroke-width:1.62465"
x="213.09235"
y="509.25986"
id="text486"><tspan
sodipodi:role="line"
id="tspan484"
x="213.09235"
y="509.25986"
style="stroke-width:1.62465">e</tspan></text>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -1,3 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" {{ $attributes->merge(['class' => 'size-8 p-1']) }}>>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" {{ $attributes->merge(['class' => 'size-8 p-1']) }}>
<path d="M64 464l48 0 0 48-48 0c-35.3 0-64-28.7-64-64L0 64C0 28.7 28.7 0 64 0L229.5 0c17 0 33.3 6.7 45.3 18.7l90.5 90.5c12 12 18.7 28.3 18.7 45.3L384 304l-48 0 0-144-80 0c-17.7 0-32-14.3-32-32l0-80L64 48c-8.8 0-16 7.2-16 16l0 384c0 8.8 7.2 16 16 16zM176 352l32 0c30.9 0 56 25.1 56 56s-25.1 56-56 56l-16 0 0 32c0 8.8-7.2 16-16 16s-16-7.2-16-16l0-48 0-80c0-8.8 7.2-16 16-16zm32 80c13.3 0 24-10.7 24-24s-10.7-24-24-24l-16 0 0 48 16 0zm96-80l32 0c26.5 0 48 21.5 48 48l0 64c0 26.5-21.5 48-48 48l-32 0c-8.8 0-16-7.2-16-16l0-128c0-8.8 7.2-16 16-16zm32 128c8.8 0 16-7.2 16-16l0-64c0-8.8-7.2-16-16-16l-16 0 0 96 16 0zm80-112c0-8.8 7.2-16 16-16l48 0c8.8 0 16 7.2 16 16s-7.2 16-16 16l-32 0 0 32 32 0c8.8 0 16 7.2 16 16s-7.2 16-16 16l-32 0 0 48c0 8.8-7.2 16-16 16s-16-7.2-16-16l0-64 0-64z"/>
</svg>

Before

Width:  |  Height:  |  Size: 907 B

After

Width:  |  Height:  |  Size: 906 B

View File

@@ -1,11 +1,12 @@
<x-app-layout>
<x-slot name="header">
<div class="flex flex-row">
<h2 class="w-1/2 font-semibold text-xl text-gray-800 dark:text-gray-200 leading-tight">
<div class="flex flex-row w-full">
<h2 class="grow font-semibold text-xl text-gray-800 dark:text-gray-200 leading-tight">
{{ __('invoice.Invoice') }} {{ $invoice->number }}
</h2>
<p class="w-1/2 relative">
<x-pdf-icon class="text-gray-800 absolute right-0 cursor-pointer" onclick="window.open('/invoice/{{ $invoice->id }}/download', '_blank', 'popup=true')"/>
<p class="relative flex flex-row">
<x-pdf-icon class="text-gray-800 cursor-pointer" onclick="window.open('/invoice/{{ $invoice->id }}/pdf-download', '_blank', 'popup=true')"/>
<x-e-icon class="cursor-pointer" onclick="window.open('/invoice/{{ $invoice->id }}/xml-download', '_blank', 'popup=true')"/>
</p>
</div>
</x-slot>

View File

@@ -0,0 +1,4 @@
resources/views/xml/invoice.blade.php
BuyerReference = Leitweg-ID (BT-10)
PaymentMeansCode = Bezahlarten
InvoicedQuantity unitCode="C62"

View File

@@ -0,0 +1,133 @@
<?xml version="1.0" encoding="UTF-8"?>
<Invoice
xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2"
xmlns:cac="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2"
xmlns:cec="urn:oasis:names:specification:ubl:schema:xsd:CommonExtensionComponents-2"
xmlns:cbc="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2">
<cbc:CustomizationID>urn:cen.eu:en16931:2017#compliant#urn:xeinkauf.de:kosit:xrechnung_3.0</cbc:CustomizationID>
<cbc:ProfileID>urn:fdc:peppol.eu:2017:poacc:billing:01:1.0</cbc:ProfileID>
<cbc:ID>{{ $invoice->number }}</cbc:ID>
<cbc:IssueDate>{{ \Carbon\Carbon::parse($invoice->created_at)->format('Y-m-d') }}</cbc:IssueDate>
<cbc:DueDate>{{ \Carbon\Carbon::parse($invoice->created_at)->addDays(14)->format('Y-m-d') }}</cbc:DueDate>
<cbc:InvoiceTypeCode>{{ $invoice->type }}</cbc:InvoiceTypeCode>
<cbc:DocumentCurrencyCode>EUR</cbc:DocumentCurrencyCode>
<cbc:BuyerReference>0</cbc:BuyerReference>
<cac:AccountingSupplierParty>
<cac:Party>
<cbc:EndpointID schemeID="EM">{{ $options->email }}</cbc:EndpointID>
<cac:PartyIdentification>
<cbc:ID>0</cbc:ID>
</cac:PartyIdentification>
<cac:PartyName>
<cbc:Name>{{ $options->company_name }}</cbc:Name>
</cac:PartyName>
<cac:PostalAddress>
<cbc:StreetName>{{ $options->address }}</cbc:StreetName>
<cbc:CityName>{{ $options->city }}</cbc:CityName>
<cbc:PostalZone>{{ $options->zip }}</cbc:PostalZone>
<cac:Country>
<cbc:IdentificationCode>DE</cbc:IdentificationCode>
</cac:Country>
</cac:PostalAddress>
@if (isset ($options->UStID))
<cac:PartyTaxScheme>
<cbc:CompanyID>{{ $options->UStID }}</cbc:CompanyID>
<cac:TaxScheme>
<cbc:ID>VAT</cbc:ID>
</cac:TaxScheme>
</cac:PartyTaxScheme>
@endif
<cac:PartyTaxScheme>
<cbc:CompanyID>{{ $options->tax_number }}</cbc:CompanyID>
<cac:TaxScheme>
<cbc:ID>FC</cbc:ID>
</cac:TaxScheme>
</cac:PartyTaxScheme>
<cac:PartyLegalEntity>
<cbc:RegistrationName>{{ $options->company_name }}</cbc:RegistrationName>
</cac:PartyLegalEntity>
<cac:Contact>
<cbc:Name>{{ $options->representative }}</cbc:Name>
<cbc:Telephone>{{ $options->phone }}</cbc:Telephone>
<cbc:ElectronicMail>{{ $options->email }}</cbc:ElectronicMail>
</cac:Contact>
</cac:Party>
</cac:AccountingSupplierParty>
<cac:AccountingCustomerParty>
<cac:Party>
<cbc:EndpointID schemeID="EM">{{ $invoice->address->email }}</cbc:EndpointID>
<cac:PartyName>
<cbc:Name>{{ $invoice->address->name }}</cbc:Name>
</cac:PartyName>
<cac:PostalAddress>
<cbc:StreetName>{{ $invoice->address->address }}</cbc:StreetName>
<cbc:CityName>{{ $invoice->address->city }}</cbc:CityName>
<cbc:PostalZone>{{ $invoice->address->zip }}</cbc:PostalZone>
<cac:Country>
<cbc:IdentificationCode>DE</cbc:IdentificationCode>
</cac:Country>
</cac:PostalAddress>
<cac:PartyLegalEntity>
<cbc:RegistrationName>{{ $invoice->address->name }}</cbc:RegistrationName>
</cac:PartyLegalEntity>
</cac:Party>
</cac:AccountingCustomerParty>
<cac:PaymentMeans>
<cbc:PaymentMeansCode>58</cbc:PaymentMeansCode>
<cac:PayeeFinancialAccount>
<cbc:ID>{{ str_replace(' ', '', $options->iban_1) }}</cbc:ID>
<cbc:Name>{{ $options->representative }}</cbc:Name>
<cac:FinancialInstitutionBranch>
<cbc:ID>{{ $options->bic_1 }}</cbc:ID>
</cac:FinancialInstitutionBranch>
</cac:PayeeFinancialAccount>
</cac:PaymentMeans>
<cac:PaymentTerms>
<cbc:Note>Zahlbar bis spätestens {{ \Carbon\Carbon::parse($invoice->created_at)->addDays(14)->format('d.m.Y') }}.</cbc:Note>
</cac:PaymentTerms>
<cac:TaxTotal>
<cbc:TaxAmount currencyID="EUR">{{ $invoice->tax }}</cbc:TaxAmount>
@foreach($taxes as $tax_rate => $tax)
<cac:TaxSubtotal>
<cbc:TaxableAmount currencyID="EUR">{{ $tax['taxable'] }}</cbc:TaxableAmount>
<cbc:TaxAmount currencyID="EUR">{{ $tax['tax'] }}</cbc:TaxAmount>
<cac:TaxCategory>
<cbc:ID>S</cbc:ID>
<cbc:Percent>{{ $tax_rate }}</cbc:Percent>
<cac:TaxScheme>
<cbc:ID>VAT</cbc:ID>
</cac:TaxScheme>
</cac:TaxCategory>
</cac:TaxSubtotal>
@endforeach
</cac:TaxTotal>
<cac:LegalMonetaryTotal>
<cbc:LineExtensionAmount currencyID="EUR">{{ $invoice->sum - $invoice->tax }}</cbc:LineExtensionAmount>
<cbc:TaxExclusiveAmount currencyID="EUR">{{ $invoice->sum - $invoice->tax }}</cbc:TaxExclusiveAmount>
<cbc:TaxInclusiveAmount currencyID="EUR">{{ $invoice->sum }}</cbc:TaxInclusiveAmount>
<cbc:AllowanceTotalAmount currencyID="EUR">0.00</cbc:AllowanceTotalAmount>
<cbc:PrepaidAmount currencyID="EUR">0</cbc:PrepaidAmount>
<cbc:PayableAmount currencyID="EUR">{{ $invoice->sum }}</cbc:PayableAmount>
</cac:LegalMonetaryTotal>
@foreach($invoice->items as $item)
<cac:InvoiceLine>
<cbc:ID>1</cbc:ID>
<cbc:InvoicedQuantity unitCode="C62">{{ $item->amount }}</cbc:InvoicedQuantity>
<cbc:LineExtensionAmount currencyID="EUR">{{ $item->amount * $item->price }}</cbc:LineExtensionAmount>
<cac:Item>
<cbc:Description>{{ $item->description }}</cbc:Description>
<cbc:Name>{{ $item->name }}</cbc:Name>
<cac:ClassifiedTaxCategory>
<cbc:ID>S</cbc:ID>
<cbc:Percent>{{ $item->tax }}</cbc:Percent>
<cac:TaxScheme>
<cbc:ID>VAT</cbc:ID>
</cac:TaxScheme>
</cac:ClassifiedTaxCategory>
</cac:Item>
<cac:Price>
<cbc:PriceAmount currencyID="EUR">{{ $item->price }}</cbc:PriceAmount>
</cac:Price>
</cac:InvoiceLine>
@endforeach
</Invoice>

View File

@@ -1,6 +1,7 @@
<?php
use App\Http\Controllers\CustomerController;
use App\Http\Controllers\EController;
use App\Http\Controllers\InvoiceController;
use App\Http\Controllers\OptionController;
use App\Http\Controllers\PdfController;
@@ -25,7 +26,8 @@ Route::middleware('auth')->group(function () {
Route::resource('/taxrate', TaxrateController::class)->only(['index', 'create', 'edit']);
Route::resource('/invoice', InvoiceController::class)->only(['index', 'create', 'show', 'edit']);
Route::get('/option', [OptionController::class, 'index'])->name('option.index');
Route::get('/invoice/{id}/download', [PdfController::class, 'downloadInvoice'])->name('invoice.download');
Route::get('/invoice/{id}/pdf-download', [PdfController::class, 'downloadInvoice'])->name('invoice.pdfDownload');
Route::get('/invoice/{id}/xml-download', [EController::class, 'downloadInvoice'])->name('invoice.eDownload');
});
require __DIR__.'/auth.php';