Extend invoices and invoice items to keep more information.
This commit is contained in:
@@ -2,10 +2,12 @@
|
|||||||
|
|
||||||
namespace App\Http\Controllers\Api;
|
namespace App\Http\Controllers\Api;
|
||||||
|
|
||||||
|
use App\Enum\InvoiceTypeCode;
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Models\Invoice;
|
use App\Models\Invoice;
|
||||||
use Illuminate\Http\JsonResponse;
|
use Illuminate\Http\JsonResponse;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Validation\Rule;
|
||||||
|
|
||||||
class InvoiceController extends Controller
|
class InvoiceController extends Controller
|
||||||
{
|
{
|
||||||
@@ -36,16 +38,21 @@ class InvoiceController extends Controller
|
|||||||
'customer_id' => 'required|integer|exists:customers,id',
|
'customer_id' => 'required|integer|exists:customers,id',
|
||||||
'address_id' => 'required|integer|exists:addresses,id',
|
'address_id' => 'required|integer|exists:addresses,id',
|
||||||
'delivery_id' => 'nullable|integer|exists:addresses,id',
|
'delivery_id' => 'nullable|integer|exists:addresses,id',
|
||||||
|
'project_id' => 'nullable|integer|exists:projects,id',
|
||||||
|
'currency_code' => 'required|string',
|
||||||
|
'type' => [Rule::enum(InvoiceTypeCode::class)],
|
||||||
|
'project_count' => 'nullable|integer',
|
||||||
'tax' => 'required|numeric',
|
'tax' => 'required|numeric',
|
||||||
'sum' => 'required|numeric',
|
'sum' => 'required|numeric',
|
||||||
|
'due_date' => 'required|date',
|
||||||
|
'cash_discount' => 'nullable|numeric',
|
||||||
|
'cash_discount_date' => 'nullable|date',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$invoiceData['user_id'] = auth()->id();
|
$invoiceData['user_id'] = auth()->id();
|
||||||
$invoiceData['type'] = '380';
|
|
||||||
$invoiceData['status'] = 'created';
|
$invoiceData['status'] = 'created';
|
||||||
$invoiceData['invoice_number'] = Invoice::whereYear('created_at', now()->year)->count() + 1;
|
$invoiceData['invoice_number'] = Invoice::whereYear('created_at', now()->year)->count() + 1;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
$invoice = new Invoice($invoiceData);
|
$invoice = new Invoice($invoiceData);
|
||||||
$invoice->save();
|
$invoice->save();
|
||||||
|
|
||||||
@@ -63,6 +70,9 @@ class InvoiceController extends Controller
|
|||||||
'delivery_id' => 'nullable|integer|exists:addresses,id',
|
'delivery_id' => 'nullable|integer|exists:addresses,id',
|
||||||
'tax' => 'required|numeric',
|
'tax' => 'required|numeric',
|
||||||
'sum' => 'required|numeric',
|
'sum' => 'required|numeric',
|
||||||
|
'due_date' => 'required|date',
|
||||||
|
'cash_discount' => 'nullable|numeric',
|
||||||
|
'cash_discount_date' => 'nullable|date',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$invoice->update($invoiceData);
|
$invoice->update($invoiceData);
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ class InvoiceitemController extends Controller
|
|||||||
'total' => 'required|numeric|min:0',
|
'total' => 'required|numeric|min:0',
|
||||||
'name' => 'required|string',
|
'name' => 'required|string',
|
||||||
'description' => 'nullable|string',
|
'description' => 'nullable|string',
|
||||||
|
'article_number' => 'nullable|string',
|
||||||
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|||||||
@@ -43,8 +43,8 @@ class EController extends Controller
|
|||||||
if (!isset($taxes[$item->tax])) {
|
if (!isset($taxes[$item->tax])) {
|
||||||
$taxes[$item->tax] = ['tax' => 0, 'taxable' => 0];
|
$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]['tax'] += round($item->price * $item->amount * ($item->tax / 100) * (100 - $item->discount) / 100, 2, PHP_ROUND_HALF_UP);
|
||||||
$taxes[$item->tax]['taxable'] += $item->price * $item->amount;
|
$taxes[$item->tax]['taxable'] += $item->price * $item->amount * (100 - $item->discount) / 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $taxes;
|
return $taxes;
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Http\Option;
|
||||||
use App\Models\Invoice;
|
use App\Models\Invoice;
|
||||||
use Illuminate\Contracts\View\View;
|
use Illuminate\Contracts\View\View;
|
||||||
|
|
||||||
@@ -20,7 +21,7 @@ class InvoiceController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function create(): View
|
public function create(): View
|
||||||
{
|
{
|
||||||
return view('invoice.create');
|
return view('invoice.create', ['options' => Option::optionsAsObject()]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -23,6 +23,13 @@ class Invoice extends Model
|
|||||||
'status',
|
'status',
|
||||||
'sum',
|
'sum',
|
||||||
'tax',
|
'tax',
|
||||||
|
'project_id',
|
||||||
|
'project_count',
|
||||||
|
'is_template',
|
||||||
|
'currency_code',
|
||||||
|
'due_date',
|
||||||
|
'cash_discount',
|
||||||
|
'cash_discount_date',
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -36,6 +43,25 @@ class Invoice extends Model
|
|||||||
'localized_state'
|
'localized_state'
|
||||||
];
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the project_count variable automatically, if a project is related to this invoice.
|
||||||
|
*/
|
||||||
|
public static function boot(): void
|
||||||
|
{
|
||||||
|
parent::boot();
|
||||||
|
self::creating(function (Invoice $invoice) {
|
||||||
|
if (is_null($invoice->project_id)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if ($invoice->type != '326') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
$projectMax = Invoice::where('project_id', '=', $invoice->project_id)->max('project_count') + 1;
|
||||||
|
$invoice->project_count = $projectMax;
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the invoice state as translated string.
|
* Get the invoice state as translated string.
|
||||||
*/
|
*/
|
||||||
@@ -107,4 +133,9 @@ class Invoice extends Model
|
|||||||
{
|
{
|
||||||
return $this->hasMany(Payment::class);
|
return $this->hasMany(Payment::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function project(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Project::class);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ class Invoiceitem extends Model
|
|||||||
*/
|
*/
|
||||||
protected $fillable = [
|
protected $fillable = [
|
||||||
'invoice_id',
|
'invoice_id',
|
||||||
|
'article_number',
|
||||||
'amount',
|
'amount',
|
||||||
'discount',
|
'discount',
|
||||||
'tax',
|
'tax',
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ namespace App\Models;
|
|||||||
use Carbon\Carbon;
|
use Carbon\Carbon;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||||
|
|
||||||
class Project extends Model
|
class Project extends Model
|
||||||
@@ -33,8 +34,20 @@ class Project extends Model
|
|||||||
protected $appends = [
|
protected $appends = [
|
||||||
'start',
|
'start',
|
||||||
'end',
|
'end',
|
||||||
|
'customer_email',
|
||||||
|
'customer_name',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
||||||
|
public function getCustomerNameAttribute(): string
|
||||||
|
{
|
||||||
|
return $this->customer->name;
|
||||||
|
}
|
||||||
|
public function getCustomerEmailAttribute(): string
|
||||||
|
{
|
||||||
|
return $this->customer->email;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the end_date attribute in local format.
|
* Get the end_date attribute in local format.
|
||||||
*/
|
*/
|
||||||
@@ -58,4 +71,9 @@ class Project extends Model
|
|||||||
{
|
{
|
||||||
return $this->belongsTo(Customer::class);
|
return $this->belongsTo(Customer::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function invoices(): HasMany
|
||||||
|
{
|
||||||
|
return $this->hasMany(Invoice::class);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('invoiceitems', function (Blueprint $table) {
|
||||||
|
$table->string('article_number')->after('invoice_id')->nullable();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('invoiceitems', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('article_number');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('invoices', function (Blueprint $table) {
|
||||||
|
$table->date('due_date')->nullable()->after('paid_at');
|
||||||
|
$table->decimal('cash_discount', 8, 2)->default(0)->after('due_date');
|
||||||
|
$table->date('cash_discount_date', 8, 2)->nullable()->after('cash_discount');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('invoices', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('due_date');
|
||||||
|
$table->dropColumn('cash_discount');
|
||||||
|
$table->dropColumn('cash_discount_date');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
25
lang/de/enum.php
Normal file
25
lang/de/enum.php
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
/*
|
||||||
|
|------------------------------------------------- -------------------------
|
||||||
|
| Übersetzungen von Aufzählungsklassen
|
||||||
|
|------------------------------------------------- -------------------------
|
||||||
|
|
|
||||||
|
| Die folgenden Sprachzeilen werden für Enum Klassen zur Lokalisierung
|
||||||
|
| genutzt.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** Invoice Type Codes */
|
||||||
|
'PARTIAL_INVOICE' => 'Abschlagsrechnung',
|
||||||
|
'COMMERCIAL_INVOICE' => 'Rechnung',
|
||||||
|
'CREDIT_NOTE' => 'Gutschrift',
|
||||||
|
'CORRECTED_INVOICE' => 'Rechnungskorrektur',
|
||||||
|
'SELF_BILLED_INVOICE' => 'Selbstfakturierte Rechnung',
|
||||||
|
'PARTIAL_CONSTRUCTION_INVOICE' => 'Abschlagsrechnung $$ 14 und 16 VOB/B',
|
||||||
|
'PARTIAL_FINAL_CONSTRUCTION_INVOICE' => 'Teilschlussrechnung $$ 14 und 16 VOB/B',
|
||||||
|
'FINAL_CONSTRUCTION_INVOICE' => 'Schlussrechnung $$ 14 und 16 VOB/B',
|
||||||
|
|
||||||
|
];
|
||||||
@@ -52,6 +52,7 @@ return [
|
|||||||
'Net long' => 'Netto zuzüglich MwSt.',
|
'Net long' => 'Netto zuzüglich MwSt.',
|
||||||
'Gross long' => 'Gesamtpreis inkl. gesetzlicher MwSt.',
|
'Gross long' => 'Gesamtpreis inkl. gesetzlicher MwSt.',
|
||||||
'Final sentence' => 'Bitte überweisen Sie den fälligen Rechnungsbetrag in Höhe von :sum bis spätestens :date auf das unten genannte Konto.',
|
'Final sentence' => 'Bitte überweisen Sie den fälligen Rechnungsbetrag in Höhe von :sum bis spätestens :date auf das unten genannte Konto.',
|
||||||
|
'Discount sentence' => 'Bei Zahlung bis zum :date gewähren wir einen Skontoabzug von :discount, wodurch der Rechnungsbetrag auf :sum reduziert wird.',
|
||||||
'state_created' => 'Erstellt',
|
'state_created' => 'Erstellt',
|
||||||
'state_sent' => 'Gesendet',
|
'state_sent' => 'Gesendet',
|
||||||
'state_paid' => 'Bezahlt',
|
'state_paid' => 'Bezahlt',
|
||||||
@@ -88,5 +89,19 @@ return [
|
|||||||
'Article number' => 'Artikelnummer',
|
'Article number' => 'Artikelnummer',
|
||||||
'Discount' => 'Rabatt',
|
'Discount' => 'Rabatt',
|
||||||
'Type code' => 'Rechnungstyp',
|
'Type code' => 'Rechnungstyp',
|
||||||
|
'Select invoice basis' => 'Rechnungsgrundlage wählen',
|
||||||
|
'Should this invoice be created for customer or project?' => 'Soll die Rechnung für einen Kunden oder ein Projekt erstellt werden?',
|
||||||
|
'Select project' => 'Projekt auswählen',
|
||||||
|
'Search project' => 'Projekt suchen',
|
||||||
|
'Select your project' => 'Wähle ein Projekt für die Rechnung aus.',
|
||||||
|
'Invoice options' => 'Rechnungsoptionen',
|
||||||
|
'Select the options for this invoice.' => 'Wähle die Optionen für diese Rechnung.',
|
||||||
|
'Partial invoice number' => 'Abschlagsrechnungs-Nr.',
|
||||||
|
'Payment terms' => 'Zahlungsziel',
|
||||||
|
'Payment terms in days' => 'Zahlungsziel in Tagen',
|
||||||
|
'Cash discount' => 'Skonto',
|
||||||
|
'Cash discount sum' => 'Skonto-Summe',
|
||||||
|
'Cash discount until' => 'Skonto bis',
|
||||||
|
'Cash discount terms in days' => 'Skontogültigkeit in Tagen',
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -1,15 +1,16 @@
|
|||||||
<div class="flex flex-row items-end gap-2 w-full">
|
<div class="flex flex-row items-end gap-2 w-full">
|
||||||
<div class="mt-1 block w-1/12">{{ $item->amount }}</div>
|
<div class="mt-1 block w-1/12">{{ $item->amount }}</div>
|
||||||
<div class="mt-1 block w-2/3">{{ $item->name }}</div>
|
<div class="mt-1 block w-7/12">{{ $item->name }}</div>
|
||||||
<div class="mt-1 block w-1/12 text-right">{{ \Illuminate\Support\Number::currency($item->price) }}</div>
|
<div class="mt-1 block w-1/12 text-right">{{ \Illuminate\Support\Number::currency($item->price) }}</div>
|
||||||
|
<div class="mt-1 block w-1/12 text-right">{{ \Illuminate\Support\Number::percentage($item->discount) }}</div>
|
||||||
<div class="mt-1 block w-1/12 text-right">{{ \Illuminate\Support\Number::percentage($item->tax) }}</div>
|
<div class="mt-1 block w-1/12 text-right">{{ \Illuminate\Support\Number::percentage($item->tax) }}</div>
|
||||||
<div class="mt-1 block w-1/12 text-right">{{ \Illuminate\Support\Number::currency($item->total) }}</div>
|
<div class="mt-1 block w-1/12 text-right">{{ \Illuminate\Support\Number::currency($item->total) }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-row items-end gap-2 w-full">
|
<div class="flex flex-row items-end gap-2 w-full">
|
||||||
<div class="w-1/12"></div>
|
<div class="w-1/12"></div>
|
||||||
<div class="w-2/3">{!! nl2br($item->description) !!}</div>
|
<div class="w-7/12 flex flex-col">
|
||||||
<div class="w-1/12"></div>
|
<div class="w-full">{!! nl2br($item->article_number) !!}</div>
|
||||||
<div class="w-1/12"></div>
|
<div class="w-full">{!! nl2br($item->description) !!}</div>
|
||||||
<div class="w-1/12"></div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -8,34 +8,67 @@
|
|||||||
<div class="py-12" x-data="invoiceForm()">
|
<div class="py-12" x-data="invoiceForm()">
|
||||||
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8 space-y-6">
|
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8 space-y-6">
|
||||||
|
|
||||||
<!-- Customer data -->
|
<!-- Create invoice for customer or project -->
|
||||||
<div class="p-4 sm:p-8 bg-white dark:bg-gray-800 shadow sm:rounded-lg">
|
<div class="p-4 sm:p-8 bg-white dark:bg-gray-800 shadow sm:rounded-lg">
|
||||||
<div class="max-w">
|
<div class="max-w">
|
||||||
<section>
|
<section>
|
||||||
<header>
|
<header class="grid grid-cols-2 gap-12">
|
||||||
|
<div>
|
||||||
|
<h2 class="text-lg font-medium text-gray-900 dark:text-gray-100">
|
||||||
|
{{ __('invoice.Select invoice basis') }}
|
||||||
|
</h2>
|
||||||
|
<p class="mt-1 text-sm text-gray-600 dark:text-gray-400">
|
||||||
|
{{ __("invoice.Should this invoice be created for customer or project?") }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<label for="toggleInfo"
|
||||||
|
class="inline-flex min-w-[14rem] cursor-pointer items-center justify-between gap-3 rounded-md border border-neutral-300 bg-neutral-50 px-4 py-1.5 dark:border-neutral-700 dark:bg-neutral-900">
|
||||||
|
<input id="toggleInfo" type="checkbox" class="peer sr-only" role="switch"
|
||||||
|
:checked="from_project" x-model="from_project"
|
||||||
|
x-on:change="invoice.customer_id = 0; selected_customer = {}; invoice.project_id = 0; selected_project = {};"/>
|
||||||
|
<span
|
||||||
|
class="trancking-wide text-sm font-medium text-neutral-600 peer-checked:text-neutral-900 peer-disabled:cursor-not-allowed peer-disabled:opacity-70 dark:text-neutral-300 dark:peer-checked:text-white">{{ __('customer.Customer') }}</span>
|
||||||
|
<div
|
||||||
|
class="relative h-6 w-11 after:h-5 after:w-5 peer-checked:after:translate-x-5 rounded-full border border-neutral-300 bg-neutral-50 after:absolute after:bottom-0 after:left-[0.0625rem] after:top-0 after:my-auto after:rounded-full after:bg-neutral-600 after:transition-all after:content-[''] peer-checked:bg-sky-500 peer-checked:after:bg-white peer-focus:outline peer-focus:outline-2 peer-focus:outline-offset-2 peer-focus:outline-neutral-800 peer-focus:peer-checked:outline-sky-500 peer-active:outline-offset-0 peer-disabled:cursor-not-allowed peer-disabled:opacity-70 dark:border-neutral-700 dark:bg-neutral-900 dark:after:bg-neutral-300 dark:peer-checked:bg-sky-500 dark:peer-checked:after:bg-white dark:peer-focus:outline-neutral-300 dark:peer-focus:peer-checked:outline-sky-500"
|
||||||
|
aria-hidden="true"></div>
|
||||||
|
<span
|
||||||
|
class="trancking-wide text-sm font-medium text-neutral-600 peer-checked:text-neutral-900 peer-disabled:cursor-not-allowed peer-disabled:opacity-70 dark:text-neutral-300 dark:peer-checked:text-white">{{ __('project.Project') }}</span>
|
||||||
|
</label>
|
||||||
|
</header>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Customer data -->
|
||||||
|
<div class="p-4 sm:p-8 bg-white dark:bg-gray-800 shadow sm:rounded-lg" x-show="!from_project">
|
||||||
|
<div class="max-w">
|
||||||
|
<section>
|
||||||
|
<header class="grid grid-cols-2 gap-12 pb-8">
|
||||||
|
<div>
|
||||||
<h2 class="text-lg font-medium text-gray-900 dark:text-gray-100">
|
<h2 class="text-lg font-medium text-gray-900 dark:text-gray-100">
|
||||||
{{ __('invoice.Select customer') }}
|
{{ __('invoice.Select customer') }}
|
||||||
</h2>
|
</h2>
|
||||||
<p class="mt-1 text-sm text-gray-600 dark:text-gray-400">
|
<p class="mt-1 text-sm text-gray-600 dark:text-gray-400">
|
||||||
{{ __("invoice.Select your customer and address") }}
|
{{ __("invoice.Select your customer and address") }}
|
||||||
</p>
|
</p>
|
||||||
</header>
|
</div>
|
||||||
|
<div class="flex flex-row items-center gap-8">
|
||||||
<div class="my-8">
|
<x-input-label for="search_customer" :value="__('common.Search')"/>
|
||||||
<x-input-label for="search" :value="__('common.Search')"/>
|
<x-text-input id="search_customer" name="search_customer" type="text"
|
||||||
<x-text-input id="search" name="search" type="search" class="mt-1 block w-full"
|
class="mt-1 block w-full"
|
||||||
x-ref="searchInput"
|
x-ref="search_customer"
|
||||||
autofocus
|
autofocus
|
||||||
placeholder="{{ __('invoice.Search customer') }}"
|
placeholder="{{ __('invoice.Search customer') }}"
|
||||||
x-on:keydown.window.prevent.slash="$refs.searchInput.focus()"
|
x-on:keydown.window.prevent.slash="$refs.search_customer.focus()"
|
||||||
x-model="search"/>
|
x-model="customer_search"/>
|
||||||
</div>
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<template x-for="(customer, index) in getFilteredCustomer()">
|
<template x-for="(customer, index) in getFilteredCustomer()">
|
||||||
<div class="cursor-pointer grid grid-cols-4 even:bg-gray-100 odd:bg-white"
|
<div class="cursor-pointer grid grid-cols-4 even:bg-gray-100 odd:bg-white"
|
||||||
:class="customer.id == customer_id ? 'font-bold' : ''"
|
:class="customer.id == invoice.customer_id ? 'font-bold' : ''"
|
||||||
x-on:click="getAddress(index);">
|
@click="invoice.customer_id = customer.id; selected_customer = customer; getCustomerAddresses(); if(customer.address != null) { invoice.address_id = customer.address.id; selected_address = customer.address; } else { invoice.address_id = 0; selected_address = {}; } if(customer.delivery != null) { invoice.delivery_id = customer.delivery.id; selected_delivery = customer.delivery; } else { invoice.delivery_id = null; selected_delivery = {}; }">
|
||||||
<div x-text="customer.name"></div>
|
<div x-text="customer.name"></div>
|
||||||
<div x-text="customer.email"></div>
|
<div x-text="customer.email"></div>
|
||||||
<div x-text="(customer.address) ? customer.address.address : ''"></div>
|
<div x-text="(customer.address) ? customer.address.address : ''"></div>
|
||||||
@@ -45,12 +78,54 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
</section>
|
</section>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Project data -->
|
||||||
|
<div class="p-4 sm:p-8 bg-white dark:bg-gray-800 shadow sm:rounded-lg" x-show="from_project">
|
||||||
|
<div class="max-w">
|
||||||
|
<section>
|
||||||
|
<header class="grid grid-cols-2 gap-12 pb-8">
|
||||||
|
<div>
|
||||||
|
<h2 class="text-lg font-medium text-gray-900 dark:text-gray-100">
|
||||||
|
{{ __('invoice.Select project') }}
|
||||||
|
</h2>
|
||||||
|
<p class="mt-1 text-sm text-gray-600 dark:text-gray-400">
|
||||||
|
{{ __("invoice.Select your project") }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-row gap-8 items-center">
|
||||||
|
<x-input-label for="search_project" :value="__('common.Search')"/>
|
||||||
|
<x-text-input id="search_project" name="search_project" type="text"
|
||||||
|
class="mt-1 block w-full"
|
||||||
|
x-ref="search_project"
|
||||||
|
autofocus
|
||||||
|
placeholder="{{ __('invoice.Search project') }}"
|
||||||
|
x-on:keydown.window.prevent.slash="$refs.search_project.focus()"
|
||||||
|
x-model="project_search"/>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<template x-for="(project, index) in getFilteredProject()">
|
||||||
|
<div class="cursor-pointer grid grid-cols-4 even:bg-gray-100 odd:bg-white"
|
||||||
|
:class="project.id == invoice.project_id ? 'font-bold' : ''"
|
||||||
|
@click="invoice.project_id = project.id; selected_project = project; invoice.customer_id = project.customer.id; selected_customer = project.customer; getCustomerAddresses();">
|
||||||
|
<div x-text="project.name"></div>
|
||||||
|
<div x-text="project.project_number"></div>
|
||||||
|
<div x-text="project.customer_name"></div>
|
||||||
|
<div x-text="project.customer_email"></div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Address data -->
|
<!-- Address data -->
|
||||||
<div class="p-4 sm:p-8 bg-white dark:bg-gray-800 shadow sm:rounded-lg">
|
<div class="p-4 sm:p-8 bg-white dark:bg-gray-800 shadow sm:rounded-lg"
|
||||||
|
x-show="Object.keys(selected_customer).length > 0">
|
||||||
<div class="max-w">
|
<div class="max-w">
|
||||||
<section>
|
<section>
|
||||||
<header>
|
<header>
|
||||||
@@ -63,50 +138,62 @@
|
|||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div class="flex flex-row my-8">
|
<div class="flex flex-row my-8">
|
||||||
|
<!-- Invoice address -->
|
||||||
<div class="w-1/2">
|
<div class="w-1/2">
|
||||||
<div class="flex flex-row">
|
<div class="flex flex-row">
|
||||||
<div class="text-lg font-medium text-gray-900 dark:text-gray-100">{{ __('customer.Invoice Address') }}</div>
|
<div
|
||||||
<x-danger-button class="ml-4 px-0 py-0" x-show="address_id != 0">
|
class="text-lg font-medium text-gray-900 dark:text-gray-100">{{ __('customer.Invoice Address') }}</div>
|
||||||
<x-trash-icon class="h-5 p-0" x-on:click="address_id = 0; address = {}"/>
|
<x-danger-button class="ml-4 px-0 py-0" x-show="invoice.address_id != 0">
|
||||||
|
<x-trash-icon class="h-5 p-0"
|
||||||
|
x-on:click="invoice.address_id = 0; selected_address = {}"/>
|
||||||
</x-danger-button>
|
</x-danger-button>
|
||||||
</div>
|
</div>
|
||||||
<select class="absolute z-10 bg-white border-gray-300 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-300 focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 rounded-md shadow-sm mt-1" id="select_address_id" x-show="address_id == 0" x-on:click="setInvoiceAddress()">
|
<select
|
||||||
<template x-for="addr in addresses[customer_id]" >
|
class="absolute z-10 bg-white border-gray-300 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-300 focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 rounded-md shadow-sm mt-1"
|
||||||
<option :value="addr.id" x-text="addr.name + '; ' + addr.address + '; ' + addr.zip + ' ' + addr.city + '; ' + addr.phone + '; ' + addr.email"></option>
|
id="select_address_id" x-show="invoice.address_id == 0"
|
||||||
|
x-on:click="setInvoiceAddress();">
|
||||||
|
<template x-for="addr in addresses[invoice.customer_id]">
|
||||||
|
<option :value="addr.id"
|
||||||
|
x-text="addr.name + '; ' + addr.address + '; ' + addr.zip + ' ' + addr.city + '; ' + addr.phone + '; ' + addr.email"></option>
|
||||||
</template>
|
</template>
|
||||||
</select>
|
</select>
|
||||||
|
<div x-text="selected_address.name"></div>
|
||||||
<div x-text="address.name"></div>
|
<div x-text="selected_address.address"></div>
|
||||||
<div x-text="address.address"></div>
|
|
||||||
<div class="flex flex-row">
|
<div class="flex flex-row">
|
||||||
<div class="mr-2" x-text="address.zip"></div>
|
<div class="mr-2" x-text="selected_address.zip"></div>
|
||||||
<div x-text="address.city"></div>
|
<div x-text="selected_address.city"></div>
|
||||||
</div>
|
</div>
|
||||||
<div x-text="address.phone"></div>
|
<div x-text="selected_address.phone"></div>
|
||||||
<div x-text="address.email"></div>
|
<div x-text="selected_address.email"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Invoice delivery -->
|
||||||
<div class="w-1/2">
|
<div class="w-1/2">
|
||||||
<div class="flex flex-row">
|
<div class="flex flex-row">
|
||||||
<div class="text-lg font-medium text-gray-900 dark:text-gray-100">{{ __('customer.Delivery Address') }}</div>
|
<div
|
||||||
<x-danger-button class="ml-4 px-0 py-0" x-show="delivery_id != null">
|
class="text-lg font-medium text-gray-900 dark:text-gray-100">{{ __('customer.Delivery Address') }}</div>
|
||||||
<x-trash-icon class="h-5 p-0" x-on:click="delivery_id = null; delivery = {}"/>
|
<x-danger-button class="ml-4 px-0 py-0" x-show="invoice.delivery_id != null">
|
||||||
|
<x-trash-icon class="h-5 p-0"
|
||||||
|
x-on:click="invoice.delivery_id = null; selected_delivery = {}"/>
|
||||||
</x-danger-button>
|
</x-danger-button>
|
||||||
</div>
|
</div>
|
||||||
<select class="absolute z-10 bg-white border-gray-300 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-300 focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 rounded-md shadow-sm mt-1" id="select_delivery_id" x-show="delivery_id == null" x-on:click="setIDeliveryAddress()">
|
<select
|
||||||
<template x-for="addr in addresses[customer_id]" >
|
class="absolute z-10 bg-white border-gray-300 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-300 focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 rounded-md shadow-sm mt-1"
|
||||||
<option :value="addr.id" x-text="addr.name + '; ' + addr.address + '; ' + addr.zip + ' ' + addr.city + '; ' + addr.phone + '; ' + addr.email"></option>
|
id="select_delivery_id" x-show="invoice.delivery_id == null"
|
||||||
|
x-on:click="setDeliveryAddress()">
|
||||||
|
<template x-for="addr in addresses[invoice.customer_id]">
|
||||||
|
<option :value="addr.id"
|
||||||
|
x-text="addr.name + '; ' + addr.address + '; ' + addr.zip + ' ' + addr.city + '; ' + addr.phone + '; ' + addr.email"></option>
|
||||||
</template>
|
</template>
|
||||||
</select>
|
</select>
|
||||||
<div x-text="delivery.name"></div>
|
<div x-text="selected_delivery.name"></div>
|
||||||
<div x-text="delivery.address"></div>
|
<div x-text="selected_delivery.address"></div>
|
||||||
<div class="flex flex-row">
|
<div class="flex flex-row">
|
||||||
<div class="mr-2" x-text="delivery.zip"></div>
|
<div class="mr-2" x-text="selected_delivery.zip"></div>
|
||||||
<div x-text="delivery.city"></div>
|
<div x-text="selected_delivery.city"></div>
|
||||||
</div>
|
</div>
|
||||||
<div x-text="delivery.phone"></div>
|
<div x-text="selected_delivery.phone"></div>
|
||||||
<div x-text="delivery.email"></div>
|
<div x-text="selected_delivery.email"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -115,89 +202,161 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Invoice data -->
|
<!-- Invoice options -->
|
||||||
<div class="p-4 sm:p-8 bg-white dark:bg-gray-800 shadow sm:rounded-lg">
|
<div class="p-4 sm:p-8 bg-white dark:bg-gray-800 shadow sm:rounded-lg"
|
||||||
|
x-show="Object.keys(selected_address).length > 0">
|
||||||
<div class="max-w">
|
<div class="max-w">
|
||||||
<section>
|
<section>
|
||||||
<header>
|
<header>
|
||||||
<h2 class="text-lg font-medium text-gray-900 dark:text-gray-100">
|
<h2 class="text-lg font-medium text-gray-900 dark:text-gray-100">
|
||||||
{{ __('invoice.Invoice items') }}
|
{{ __('invoice.Invoice options') }}
|
||||||
</h2>
|
</h2>
|
||||||
<p class="mt-1 text-sm text-gray-600 dark:text-gray-400">
|
<p class="mt-1 text-sm text-gray-600 dark:text-gray-400">
|
||||||
{{ __("invoice.Enter your invoice items. Click add for an additional invoice item.") }}
|
{{ __("invoice.Select the options for this invoice.") }}
|
||||||
</p>
|
</p>
|
||||||
</header>
|
</header>
|
||||||
|
<div class="flex flex-row gap-4">
|
||||||
|
|
||||||
<!-- New invoice item -->
|
<div class="flex flex-col w-1/2">
|
||||||
<div class="flex flex-row items-end gap-2 w-full mt-4">
|
<div class="flex flex-row items-center">
|
||||||
|
<x-input-label class="w-1/3" for="type" :value="__('invoice.Type code')"/>
|
||||||
|
<select
|
||||||
|
class="w-2/3 bg-white border-gray-300 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-300 focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 rounded-md shadow-sm mt-1"
|
||||||
|
id="type" x-model="invoice.type">
|
||||||
|
|
||||||
|
@foreach(App\Enum\InvoiceTypeCode::options() as $key => $value)
|
||||||
|
<option value="{{ $key }}">{{ $value }}</option>
|
||||||
|
@endforeach
|
||||||
|
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-row items-center"
|
||||||
|
x-show="!from_project && (invoice.type === '326' || invoice.type === '875')">
|
||||||
|
<x-input-label class="w-1/3" for="project_count"
|
||||||
|
:value="__('invoice.Partial invoice number')"/>
|
||||||
|
<x-text-input id="project_count" name="project_count" type="text"
|
||||||
|
class="mt-1 w-2/3"
|
||||||
|
autofocus
|
||||||
|
x-model="invoice.project_count"/>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-row items-center">
|
||||||
|
<x-input-label class="w-1/3" for="due_date"
|
||||||
|
:value="__('invoice.Payment terms')"/>
|
||||||
|
<x-text-input id="due_date" name="due_date" type="date"
|
||||||
|
class="mt-1 w-2/3"
|
||||||
|
autofocus
|
||||||
|
x-model="invoice.due_date"/>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-row items-center">
|
||||||
|
<x-input-label class="w-1/3" for="cash_discount"
|
||||||
|
:value="__('invoice.Cash discount')"/>
|
||||||
|
<x-text-input id="cash_discount" name="cash_discount" type="number" steps="0.01"
|
||||||
|
class="mt-1 w-2/3"
|
||||||
|
autofocus x-on:blur="calculateSum();"
|
||||||
|
x-model="invoice.cash_discount"/>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-row items-center" x-show="invoice.cash_discount != 0">
|
||||||
|
<x-input-label class="w-1/3" for="cash_discount_date"
|
||||||
|
:value="__('invoice.Cash discount until')"/>
|
||||||
|
<x-text-input id="cash_discount_date" name="cash_discount_date" type="date"
|
||||||
|
class="mt-1 w-2/3"
|
||||||
|
autofocus
|
||||||
|
x-model="invoice.cash_discount_date"/>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex flex-col w-1/2">
|
||||||
|
<div class="flex flex-row items-center">
|
||||||
|
<x-input-label class="w-1/3" for="net" :value="__('invoice.Net')"/>
|
||||||
|
<x-text-input id="net" name="net" type="number"
|
||||||
|
class="mt-1 w-2/3" step="0.01"
|
||||||
|
autofocus disabled
|
||||||
|
x-model="net"/>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-row items-center">
|
||||||
|
<x-input-label class="w-1/3" for="tax" :value="__('invoice.Tax')"/>
|
||||||
|
<x-text-input id="tax" name="tax" type="number"
|
||||||
|
class="mt-1 w-2/3" step="0.01"
|
||||||
|
autofocus disabled
|
||||||
|
x-model="invoice.tax"/>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-row items-center">
|
||||||
|
<x-input-label class="w-1/3" for="sum" :value="__('invoice.Sum')"/>
|
||||||
|
<x-text-input id="sum" name="sum" type="number"
|
||||||
|
class="mt-1 w-2/3" step="0.01"
|
||||||
|
autofocus disabled
|
||||||
|
x-model="invoice.sum"/>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-row items-center" x-show="invoice.cash_discount != 0">
|
||||||
|
<x-input-label class="w-1/3" for="discount" :value="__('invoice.Cash discount sum')"/>
|
||||||
|
<x-text-input id="discount" name="discount" type="number"
|
||||||
|
class="mt-1 w-2/3" step="0.01"
|
||||||
|
autofocus disabled
|
||||||
|
x-model="discount"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Invoice items -->
|
||||||
|
<div class="p-4 sm:p-8 bg-white dark:bg-gray-800 shadow sm:rounded-lg"
|
||||||
|
x-show="Object.keys(selected_address).length > 0">
|
||||||
|
<div class="max-w">
|
||||||
|
<section>
|
||||||
|
<header>
|
||||||
|
<h2 class="text-lg font-medium text-gray-900 dark:text-gray-100 relative">
|
||||||
|
{{ __('invoice.Invoice items') }}
|
||||||
|
<x-primary-button x-on:click="addItem();" class="absolute right-0">+</x-primary-button>
|
||||||
|
</h2>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="flex flex-row items-end gap-2 w-full">
|
||||||
<x-input-label for="invoice_item.amount" :value="__('invoice.Amount')" class="w-1/12"/>
|
<x-input-label for="invoice_item.amount" :value="__('invoice.Amount')" class="w-1/12"/>
|
||||||
<x-input-label for="invoice_item.name" :value="__('invoice.Name')" class="w-2/3"/>
|
<x-input-label for="invoice_item.name"
|
||||||
|
:value="__('invoice.Name') . ' / ' . __('invoice.Article number') . ' / ' . __('invoice.Description')"
|
||||||
|
class="w-1/2"/>
|
||||||
<x-input-label for="invoice_item.price" :value="__('invoice.Price')" class="w-1/12"/>
|
<x-input-label for="invoice_item.price" :value="__('invoice.Price')" class="w-1/12"/>
|
||||||
|
<x-input-label for="invoice_item.discount" :value="__('invoice.Discount')" class="w-1/12"/>
|
||||||
<x-input-label for="invoice_item.tax" :value="__('invoice.Tax')" class="w-1/12"/>
|
<x-input-label for="invoice_item.tax" :value="__('invoice.Tax')" class="w-1/12"/>
|
||||||
|
<x-input-label for="invoice_item.tax" :value="__('invoice.Net')" class="w-1/12"/>
|
||||||
<div class="w-1/12 relative h-10"></div>
|
<div class="w-1/12 relative h-10"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-row items-end gap-2 w-full">
|
|
||||||
<x-text-input id="invoice_item.amount" name="invoice_item.amount" type="number"
|
|
||||||
class="mt-1 block w-1/12"
|
|
||||||
autofocus
|
|
||||||
x-model="invoice_item.amount"/>
|
|
||||||
|
|
||||||
<x-text-input id="invoice_item.name" name="invoice_item.name" type="text"
|
<div x-sort="sortItems" x-bind="items">
|
||||||
class="mt-1 block w-2/3"
|
|
||||||
autofocus
|
|
||||||
placeholder="{{ __('invoice.Name') }}"
|
|
||||||
x-model="invoice_item.name"/>
|
|
||||||
|
|
||||||
<x-text-input id="invoice_item.price" name="invoice_item.price" type="number"
|
|
||||||
class="mt-1 block w-1/12"
|
|
||||||
autofocus
|
|
||||||
x-model="invoice_item.price"/>
|
|
||||||
|
|
||||||
<select name="invoice_item.tax" x-model="invoice_item.tax" id="invoice_item.tax"
|
|
||||||
class="border-gray-300 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-300 focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 rounded-md shadow-sm w-1/12">
|
|
||||||
<template x-for="tax in tax_rates">
|
|
||||||
<option x-bind:value="tax.rate" x-text="tax.rate_percentage"
|
|
||||||
:selected="(invoice_item.tax == tax.rate) ? true : tax.active"></option>
|
|
||||||
</template>
|
|
||||||
</select>
|
|
||||||
|
|
||||||
<div class="w-1/12 relative h-10">
|
|
||||||
<x-primary-button x-on:click="addItem();" class="absolute right-0">+</x-primary-button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-row items-end gap-2 w-full">
|
|
||||||
<div class="w-1/12"></div>
|
|
||||||
<textarea placeholder="{{ __('invoice.Description') }}" name="invoice_item.description"
|
|
||||||
x-model="invoice_item.description" x-text="invoice_item.description"
|
|
||||||
class="border-gray-300 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-300 focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 rounded-md shadow-sm mt-1 block w-2/3 offset-1"></textarea>
|
|
||||||
<div class="w-1/12 h-10"></div>
|
|
||||||
<div class="w-1/12 h-10"></div>
|
|
||||||
<div class="w-1/12 h-10"></div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Existing invoice items -->
|
|
||||||
<div x-sort="handle" x-bind="items" class="mt-4">
|
|
||||||
<template x-for="(item, index) in items">
|
<template x-for="(item, index) in items">
|
||||||
<div x-sort:item="index">
|
<div>
|
||||||
<div class="flex flex-row items-end gap-2 w-full relative">
|
<div class="flex flex-row items-end gap-2 w-full relative">
|
||||||
<x-text-input id="items[index].amount" name="items[index].amount" type="number"
|
<x-text-input id="items[index].amount" name="items[index].amount" type="number"
|
||||||
class="mt-1 block w-1/12"
|
class="mt-1 block w-1/12"
|
||||||
autofocus
|
autofocus x-on:blur="calculateItem(index);"
|
||||||
x-model="items[index].amount"/>
|
x-model="items[index].amount"/>
|
||||||
|
|
||||||
<x-text-input id="items[index].name" name="items[index].name" type="text"
|
<x-text-input id="items[index].name" name="items[index].name" type="text"
|
||||||
class="mt-1 block w-2/3"
|
class="mt-1 block w-1/2"
|
||||||
autofocus
|
autofocus
|
||||||
placeholder="{{ __('invoice.Name') }}"
|
placeholder="{{ __('invoice.Name') }}"
|
||||||
x-model="items[index].name"/>
|
x-model="items[index].name"/>
|
||||||
|
|
||||||
<x-text-input id="items[index].price" name="items[index].price" type="text"
|
<x-text-input id="items[index].price" name="items[index].price" type="number"
|
||||||
class="mt-1 block w-1/12"
|
class="mt-1 block w-1/12"
|
||||||
autofocus
|
autofocus x-on:blur="calculateItem(index);"
|
||||||
x-model="items[index].price"/>
|
x-model="items[index].price"/>
|
||||||
|
|
||||||
|
<x-text-input id="items[index].discount" name="items[index].discount"
|
||||||
|
type="number"
|
||||||
|
class="mt-1 block w-1/12"
|
||||||
|
autofocus x-on:blur="calculateItem(index);"
|
||||||
|
x-model="items[index].discount"/>
|
||||||
|
|
||||||
<select name="items[index].tax" x-model="items[index].tax"
|
<select name="items[index].tax" x-model="items[index].tax"
|
||||||
|
x-on:change="calculateItem(index);"
|
||||||
class="border-gray-300 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-300 focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 rounded-md shadow-sm w-1/12">
|
class="border-gray-300 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-300 focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 rounded-md shadow-sm w-1/12">
|
||||||
<template x-for="tax in tax_rates">
|
<template x-for="tax in tax_rates">
|
||||||
<option x-bind:value="tax.rate" x-text="tax.rate_percentage"
|
<option x-bind:value="tax.rate" x-text="tax.rate_percentage"
|
||||||
@@ -205,6 +364,11 @@
|
|||||||
</template>
|
</template>
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
|
<x-text-input id="items[index].total" name="items[index].total" type="number"
|
||||||
|
class="mt-1 block w-1/12"
|
||||||
|
autofocus disabled
|
||||||
|
x-model="items[index].total"/>
|
||||||
|
|
||||||
<div class="flex flex-row w-1/12 h-10 relative">
|
<div class="flex flex-row w-1/12 h-10 relative">
|
||||||
<x-sort-icon x-sort:handle class="cursor-move"/>
|
<x-sort-icon x-sort:handle class="cursor-move"/>
|
||||||
<x-danger-button x-on:click="deleteItem(index);" class="absolute right-0">
|
<x-danger-button x-on:click="deleteItem(index);" class="absolute right-0">
|
||||||
@@ -213,31 +377,35 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-row items-end gap-2 w-full">
|
<div class="flex flex-row gap-2">
|
||||||
<div class="w-1/12"></div>
|
<div class="w-1/12"></div>
|
||||||
|
<div class="flex flex-col w-1/2 gap-2">
|
||||||
|
<x-text-input id="items[index].article_number"
|
||||||
|
name="items[index].article_number" type="text"
|
||||||
|
class="mt-1 block"
|
||||||
|
autofocus
|
||||||
|
placeholder="{{ __('invoice.Article number') }}"
|
||||||
|
x-model="items[index].article_number"/>
|
||||||
<textarea placeholder="{{ __('invoice.Description') }}"
|
<textarea placeholder="{{ __('invoice.Description') }}"
|
||||||
name="items[index].description" x-model="items[index].description"
|
name="items[index].description" x-model="items[index].description"
|
||||||
x-text="items[index].description"
|
x-text="items[index].description"
|
||||||
class="border-gray-300 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-300 focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 rounded-md shadow-sm mt-1 block w-2/3"></textarea>
|
class="border-gray-300 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-300 focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 rounded-md shadow-sm mt-1 block w-full"></textarea>
|
||||||
<div class="w-1/12 h-10"></div>
|
|
||||||
<div class="w-1/12 h-10"></div>
|
|
||||||
<div class="w-1/12 h-10"></div>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<x-primary-button x-on:click="submitForm();" class="">{{ __('form.Save') }}</x-primary-button>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
<x-primary-button x-on:click="submit();" class="">{{ __('form.Save') }}</x-primary-button>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</x-app-layout>
|
</x-app-layout>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@@ -245,51 +413,50 @@
|
|||||||
|
|
||||||
function invoiceForm() {
|
function invoiceForm() {
|
||||||
return {
|
return {
|
||||||
card: {},
|
invoice_id: 0,
|
||||||
customers: {},
|
from_project: false,
|
||||||
customer: {},
|
error: false,
|
||||||
customer_id: 0,
|
message: '',
|
||||||
addresses: {},
|
customer_search: '',
|
||||||
address_id: 0,
|
project_search: '',
|
||||||
address: {},
|
|
||||||
delivery_id: null,
|
|
||||||
delivery: {},
|
|
||||||
tax_rates: {},
|
|
||||||
tax_standard: 0,
|
|
||||||
|
|
||||||
invoice_item: {},
|
|
||||||
|
|
||||||
|
projects: [],
|
||||||
|
customers: [],
|
||||||
|
tax_rates: [],
|
||||||
|
addresses: [],
|
||||||
items: [],
|
items: [],
|
||||||
sort: [],
|
sort: [],
|
||||||
|
|
||||||
selection: '',
|
selected_project: {},
|
||||||
|
selected_customer: {},
|
||||||
|
selected_address: {},
|
||||||
|
selected_delivery: {},
|
||||||
|
net: 0,
|
||||||
|
discount: 0,
|
||||||
|
|
||||||
search: '',
|
invoice: {
|
||||||
|
customer_id: 0,
|
||||||
error: false,
|
address_id: 0,
|
||||||
message: '',
|
delivery_id: null,
|
||||||
|
type: '380',
|
||||||
|
currency_code: 'EUR',
|
||||||
|
tax: 0,
|
||||||
|
sum: 0,
|
||||||
|
project_id: null,
|
||||||
|
due_date: '{!! \Illuminate\Support\Carbon::today()->addDays(intval($options->payment_terms))->format('Y-m-d') !!}',
|
||||||
|
cash_discount: 0,
|
||||||
|
cash_discount_date: '{!! \Illuminate\Support\Carbon::today()->addDays(intval($options->cash_discount_date))->format('Y-m-d') !!}',
|
||||||
|
},
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
this.getCustomers();
|
this.getCustomers();
|
||||||
|
this.getProjects();
|
||||||
this.getTaxRates();
|
this.getTaxRates();
|
||||||
self = this;
|
self = this;
|
||||||
},
|
},
|
||||||
|
|
||||||
newItem() {
|
|
||||||
return {
|
|
||||||
name: '',
|
|
||||||
description: '',
|
|
||||||
amount: 1,
|
|
||||||
discount: 0,
|
|
||||||
tax: this.tax_standard,
|
|
||||||
price: 0,
|
|
||||||
total: 0,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
getCustomers() {
|
getCustomers() {
|
||||||
let vm = this;
|
let vm = this;
|
||||||
|
|
||||||
axios.get('/customer')
|
axios.get('/customer')
|
||||||
.then(function (response) {
|
.then(function (response) {
|
||||||
vm.customers = response.data;
|
vm.customers = response.data;
|
||||||
@@ -300,79 +467,123 @@
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
getTaxRates() {
|
|
||||||
let vm = this;
|
|
||||||
|
|
||||||
axios.get('/taxrate')
|
|
||||||
.then(function (response) {
|
|
||||||
vm.tax_rates = response.data;
|
|
||||||
let test = vm.tax_rates.find(function (tax) {
|
|
||||||
return tax.active;
|
|
||||||
});
|
|
||||||
vm.tax_standard = test.rate;
|
|
||||||
vm.invoice_item = vm.newItem();
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
getFilteredCustomer() {
|
getFilteredCustomer() {
|
||||||
if (this.search === '') {
|
if (this.customer_search === '') {
|
||||||
return this.customers;
|
return this.customers;
|
||||||
}
|
}
|
||||||
return this.customers.filter((customer) => {
|
return this.customers.filter((customer) => {
|
||||||
return customer.name
|
return customer.name
|
||||||
.replace(/ /g, '')
|
.replace(/ /g, '')
|
||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
.includes(this.search.replace(/ /g, '').toLowerCase())
|
.includes(this.customer_search.replace(/ /g, '').toLowerCase())
|
||||||
||
|
||
|
||||||
customer.email
|
customer.email
|
||||||
.replace(/ /g, '')
|
.replace(/ /g, '')
|
||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
.includes(this.search.replace(/ /g, '').toLowerCase())
|
.includes(this.customer_search.replace(/ /g, '').toLowerCase())
|
||||||
||
|
||
|
||||||
customer.street
|
customer.street
|
||||||
.replace(/ /g, '')
|
.replace(/ /g, '')
|
||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
.includes(this.search.replace(/ /g, '').toLowerCase())
|
.includes(this.customer_search.replace(/ /g, '').toLowerCase())
|
||||||
||
|
||
|
||||||
customer.city
|
customer.city
|
||||||
.replace(/ /g, '')
|
.replace(/ /g, '')
|
||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
.includes(this.search.replace(/ /g, '').toLowerCase())
|
.includes(this.customer_search.replace(/ /g, '').toLowerCase())
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
getAddress(index) {
|
getProjects() {
|
||||||
this.customer = this.customers[index];
|
let vm = this;
|
||||||
this.customer_id = this.customer.id;
|
axios.get('project')
|
||||||
this.address_id = 0;
|
.then(function (response) {
|
||||||
this.delivery_id = null;
|
vm.projects = response.data;
|
||||||
this.address = {};
|
})
|
||||||
this.delivery = {};
|
.catch(function (error) {
|
||||||
if (this.customer.address) {
|
vm.error = true;
|
||||||
this.address_id = this.customer.address.id;
|
vm.message = error.response.data.message;
|
||||||
this.address = this.customer.address;
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
getFilteredProject() {
|
||||||
|
if (this.project_search === '') {
|
||||||
|
return this.projects;
|
||||||
}
|
}
|
||||||
if (this.customer.delivery) {
|
return this.projects.filter((project) => {
|
||||||
this.delivery_id = this.customer.delivery.id;
|
return project.name
|
||||||
this.delivery = this.customer.delivery;
|
.replace(/ /g, '')
|
||||||
}
|
.toLowerCase()
|
||||||
this.getCustomerAddresses();
|
.includes(this.project_search.replace(/ /g, '').toLowerCase())
|
||||||
|
||
|
||||||
|
project.project_number
|
||||||
|
.replace(/ /g, '')
|
||||||
|
.toLowerCase()
|
||||||
|
.includes(this.project_search.replace(/ /g, '').toLowerCase())
|
||||||
|
||
|
||||||
|
project.customer_name
|
||||||
|
.replace(/ /g, '')
|
||||||
|
.toLowerCase()
|
||||||
|
.includes(this.project_search.replace(/ /g, '').toLowerCase())
|
||||||
|
||
|
||||||
|
project.customer_email
|
||||||
|
.replace(/ /g, '')
|
||||||
|
.toLowerCase()
|
||||||
|
.includes(this.project_search.replace(/ /g, '').toLowerCase())
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
getTaxRates() {
|
||||||
|
let vm = this;
|
||||||
|
axios.get('/taxrate')
|
||||||
|
.then(function (response) {
|
||||||
|
vm.tax_rates = response.data;
|
||||||
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
getCustomerAddresses() {
|
getCustomerAddresses() {
|
||||||
if (typeof (this.addresses[this.customer_id]) == 'undefined') {
|
if (typeof (this.addresses[this.invoice.customer_id]) == 'undefined') {
|
||||||
let vm = this;
|
let vm = this;
|
||||||
axios.get('/customer/' + this.customer_id + '/address')
|
axios.get('/customer/' + this.invoice.customer_id + '/address')
|
||||||
.then(function (response) {
|
.then(function (response) {
|
||||||
vm.addresses[vm.customer_id] = response.data;
|
vm.addresses[vm.invoice.customer_id] = response.data;
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
setInvoiceAddress() {
|
||||||
|
let id = document.querySelector('#select_address_id').value;
|
||||||
|
if (id === '') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.invoice.address_id = id;
|
||||||
|
let address_key = Object.keys(this.addresses[this.invoice.customer_id]).find(key => (this.addresses[this.invoice.customer_id][key].id == id));
|
||||||
|
this.selected_address = this.addresses[this.invoice.customer_id][address_key];
|
||||||
|
},
|
||||||
|
|
||||||
|
setDeliveryAddress() {
|
||||||
|
let id = document.querySelector('#select_delivery_id').value;
|
||||||
|
if (id === '') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.invoice.delivery_id = id;
|
||||||
|
let address_key = Object.keys(this.addresses[this.invoice.customer_id]).find(key => (this.addresses[this.invoice.customer_id][key].id == id));
|
||||||
|
this.selected_delivery = this.addresses[this.invoice.customer_id][address_key];
|
||||||
|
},
|
||||||
|
|
||||||
addItem() {
|
addItem() {
|
||||||
this.items.push(this.invoice_item);
|
let item = {
|
||||||
|
name: '',
|
||||||
|
article_number: '',
|
||||||
|
description: '',
|
||||||
|
amount: 1,
|
||||||
|
discount: 0,
|
||||||
|
tax: 19,
|
||||||
|
price: 0,
|
||||||
|
total: 0
|
||||||
|
};
|
||||||
|
this.items.push(item);
|
||||||
this.sort.push(this.items.length - 1);
|
this.sort.push(this.items.length - 1);
|
||||||
this.invoice_item = this.newItem();
|
|
||||||
},
|
},
|
||||||
|
|
||||||
deleteItem(index) {
|
deleteItem(index) {
|
||||||
@@ -386,65 +597,26 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
submitForm() {
|
calculateItem(index) {
|
||||||
let sum = 0;
|
this.items[index].total = (parseFloat(this.items[index].amount) * parseFloat(this.items[index].price) * (1 + parseFloat(this.items[index].tax) / 100) * (1 - parseFloat(this.items[index].discount) / 100)).toFixed(2);
|
||||||
let tax = 0;
|
this.calculateSum();
|
||||||
let sort_flipped = Object.entries(this.sort)
|
|
||||||
.reduce((obj, [key, value]) => ({...obj, [value]: key}), {});
|
|
||||||
for (let i = 0; i < this.items.length; i++) {
|
|
||||||
this.items[i].total = this.items[i].amount * this.items[i].price * (1 + this.items[i].tax / 100);
|
|
||||||
tax += this.items[i].amount * this.items[i].price * this.items[i].tax / 100;
|
|
||||||
sum += this.items[i].total;
|
|
||||||
}
|
|
||||||
|
|
||||||
axios.post('invoice', {
|
|
||||||
customer_id: this.customer_id,
|
|
||||||
address_id: this.address_id,
|
|
||||||
delivery_id: this.delivery_id,
|
|
||||||
sum: sum,
|
|
||||||
tax: tax
|
|
||||||
})
|
|
||||||
.then(function (response) {
|
|
||||||
let error = false;
|
|
||||||
let invoice_id = response.data.id;
|
|
||||||
for (let i = 0; i < self.items.length; i++) {
|
|
||||||
let pos = sort_flipped[i];
|
|
||||||
let item = self.items[pos];
|
|
||||||
axios.post('invoice/' + invoice_id + '/item', item)
|
|
||||||
.then(function (response) {
|
|
||||||
console.log(response);
|
|
||||||
})
|
|
||||||
.catch(function (error) {
|
|
||||||
error = true;
|
|
||||||
console.log(error);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if (!error) {
|
|
||||||
window.location.href = '/invoice/' + invoice_id;
|
|
||||||
}
|
|
||||||
console.log(response);
|
|
||||||
})
|
|
||||||
.catch(function (error) {
|
|
||||||
console.log(error);
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
|
|
||||||
setInvoiceAddress() {
|
calculateSum() {
|
||||||
let id = document.querySelector('#select_address_id').value;
|
let vm = this;
|
||||||
this.address_id = id;
|
vm.invoice.tax = 0;
|
||||||
let address_key = Object.keys(this.addresses[this.customer_id]).find(key => (this.addresses[this.customer_id][key].id == id ));
|
vm.invoice.sum = 0;
|
||||||
this.address = this.addresses[this.customer_id][address_key];
|
this.items.forEach(function (item) {
|
||||||
|
vm.invoice.tax += item.amount * item.price * (item.tax / 100) * (1 - item.discount / 100);
|
||||||
|
vm.invoice.sum += item.total * 1;
|
||||||
|
});
|
||||||
|
vm.invoice.tax = (vm.invoice.tax).toFixed(2)
|
||||||
|
vm.invoice.sum = (vm.invoice.sum).toFixed(2)
|
||||||
|
vm.net = (vm.invoice.sum - vm.invoice.tax).toFixed(2);
|
||||||
|
vm.discount = (vm.invoice.sum * (100 - vm.invoice.cash_discount) / 100).toFixed(2);
|
||||||
},
|
},
|
||||||
|
|
||||||
setIDeliveryAddress() {
|
sortItems(item, position) {
|
||||||
let id = document.querySelector('#select_delivery_id').value;
|
|
||||||
this.delivery_id = id;
|
|
||||||
let address_key = Object.keys(this.addresses[this.customer_id]).find(key => (this.addresses[this.customer_id][key].id == id ));
|
|
||||||
this.delivery = this.addresses[this.customer_id][address_key];
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
handle(item, position) {
|
|
||||||
if (position > self.sort[item]) {
|
if (position > self.sort[item]) {
|
||||||
for (let i = 0; i < self.sort.length; i++) {
|
for (let i = 0; i < self.sort.length; i++) {
|
||||||
if (self.sort[i] <= position && self.sort[i] > self.sort[item]) {
|
if (self.sort[i] <= position && self.sort[i] > self.sort[item]) {
|
||||||
@@ -464,6 +636,39 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async submit() {
|
||||||
|
let sort_flipped = Object.entries(this.sort)
|
||||||
|
.reduce((obj, [key, value]) => ({...obj, [value]: key}), {});
|
||||||
|
const promises = [];
|
||||||
|
|
||||||
|
const main = axios.post('/invoice', this.invoice)
|
||||||
|
.then(function (response) {
|
||||||
|
self.invoice_id = response.data.id;
|
||||||
|
})
|
||||||
|
.catch(function (error) {
|
||||||
|
self.error = true;
|
||||||
|
self.message = error.response.data.message;
|
||||||
|
})
|
||||||
|
promises.push(main);
|
||||||
|
|
||||||
|
await Promise.all(promises);
|
||||||
|
for (let i = 0; i < self.items.length; i++) {
|
||||||
|
let pos = sort_flipped[i];
|
||||||
|
let item = self.items[pos];
|
||||||
|
const result = axios.post('invoice/' + self.invoice_id + '/item', item)
|
||||||
|
.catch(function (error) {
|
||||||
|
self.error = true;
|
||||||
|
self.message = error.response.data.message;
|
||||||
|
})
|
||||||
|
promises.push(result);
|
||||||
|
|
||||||
|
}
|
||||||
|
await Promise.all(promises);
|
||||||
|
|
||||||
|
if (!self.error) {
|
||||||
|
window.location.href = '/invoice/' + self.invoice_id;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -9,34 +9,67 @@
|
|||||||
<div class="py-12" x-data="invoiceForm()">
|
<div class="py-12" x-data="invoiceForm()">
|
||||||
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8 space-y-6">
|
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8 space-y-6">
|
||||||
|
|
||||||
<!-- Customer data -->
|
<!-- Create invoice for customer or project -->
|
||||||
<div class="p-4 sm:p-8 bg-white dark:bg-gray-800 shadow sm:rounded-lg">
|
<div class="p-4 sm:p-8 bg-white dark:bg-gray-800 shadow sm:rounded-lg">
|
||||||
<div class="max-w">
|
<div class="max-w">
|
||||||
<section>
|
<section>
|
||||||
<header>
|
<header class="grid grid-cols-2 gap-12">
|
||||||
|
<div>
|
||||||
|
<h2 class="text-lg font-medium text-gray-900 dark:text-gray-100">
|
||||||
|
{{ __('invoice.Select invoice basis') }}
|
||||||
|
</h2>
|
||||||
|
<p class="mt-1 text-sm text-gray-600 dark:text-gray-400">
|
||||||
|
{{ __("invoice.Should this invoice be created for customer or project?") }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<label for="toggleInfo"
|
||||||
|
class="inline-flex min-w-[14rem] cursor-pointer items-center justify-between gap-3 rounded-md border border-neutral-300 bg-neutral-50 px-4 py-1.5 dark:border-neutral-700 dark:bg-neutral-900">
|
||||||
|
<input id="toggleInfo" type="checkbox" class="peer sr-only" role="switch"
|
||||||
|
:checked="from_project" x-model="from_project"
|
||||||
|
x-on:change="invoice.customer_id = 0; selected_customer = {}; invoice.project_id = 0; selected_project = {};"/>
|
||||||
|
<span
|
||||||
|
class="trancking-wide text-sm font-medium text-neutral-600 peer-checked:text-neutral-900 peer-disabled:cursor-not-allowed peer-disabled:opacity-70 dark:text-neutral-300 dark:peer-checked:text-white">{{ __('customer.Customer') }}</span>
|
||||||
|
<div
|
||||||
|
class="relative h-6 w-11 after:h-5 after:w-5 peer-checked:after:translate-x-5 rounded-full border border-neutral-300 bg-neutral-50 after:absolute after:bottom-0 after:left-[0.0625rem] after:top-0 after:my-auto after:rounded-full after:bg-neutral-600 after:transition-all after:content-[''] peer-checked:bg-sky-500 peer-checked:after:bg-white peer-focus:outline peer-focus:outline-2 peer-focus:outline-offset-2 peer-focus:outline-neutral-800 peer-focus:peer-checked:outline-sky-500 peer-active:outline-offset-0 peer-disabled:cursor-not-allowed peer-disabled:opacity-70 dark:border-neutral-700 dark:bg-neutral-900 dark:after:bg-neutral-300 dark:peer-checked:bg-sky-500 dark:peer-checked:after:bg-white dark:peer-focus:outline-neutral-300 dark:peer-focus:peer-checked:outline-sky-500"
|
||||||
|
aria-hidden="true"></div>
|
||||||
|
<span
|
||||||
|
class="trancking-wide text-sm font-medium text-neutral-600 peer-checked:text-neutral-900 peer-disabled:cursor-not-allowed peer-disabled:opacity-70 dark:text-neutral-300 dark:peer-checked:text-white">{{ __('project.Project') }}</span>
|
||||||
|
</label>
|
||||||
|
</header>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Customer data -->
|
||||||
|
<div class="p-4 sm:p-8 bg-white dark:bg-gray-800 shadow sm:rounded-lg" x-show="!from_project">
|
||||||
|
<div class="max-w">
|
||||||
|
<section>
|
||||||
|
<header class="grid grid-cols-2 gap-12 pb-8">
|
||||||
|
<div>
|
||||||
<h2 class="text-lg font-medium text-gray-900 dark:text-gray-100">
|
<h2 class="text-lg font-medium text-gray-900 dark:text-gray-100">
|
||||||
{{ __('invoice.Select customer') }}
|
{{ __('invoice.Select customer') }}
|
||||||
</h2>
|
</h2>
|
||||||
<p class="mt-1 text-sm text-gray-600 dark:text-gray-400">
|
<p class="mt-1 text-sm text-gray-600 dark:text-gray-400">
|
||||||
{{ __("invoice.Select your customer and address") }}
|
{{ __("invoice.Select your customer and address") }}
|
||||||
</p>
|
</p>
|
||||||
</header>
|
</div>
|
||||||
|
<div class="flex flex-row items-center gap-8">
|
||||||
<div class="my-8">
|
<x-input-label for="search_customer" :value="__('common.Search')"/>
|
||||||
<x-input-label for="search" :value="__('common.Search')"/>
|
<x-text-input id="search_customer" name="search_customer" type="text"
|
||||||
<x-text-input id="search" name="search" type="search" class="mt-1 block w-full"
|
class="mt-1 block w-full"
|
||||||
x-ref="searchInput"
|
x-ref="search_customer"
|
||||||
autofocus
|
autofocus
|
||||||
placeholder="{{ __('invoice.Search customer') }}"
|
placeholder="{{ __('invoice.Search customer') }}"
|
||||||
x-on:keydown.window.prevent.slash="$refs.searchInput.focus()"
|
x-on:keydown.window.prevent.slash="$refs.search_customer.focus()"
|
||||||
x-model="search"/>
|
x-model="customer_search"/>
|
||||||
</div>
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<template x-for="(customer, index) in getFilteredCustomer()">
|
<template x-for="(customer, index) in getFilteredCustomer()">
|
||||||
<div class="cursor-pointer grid grid-cols-4 even:bg-gray-100 odd:bg-white"
|
<div class="cursor-pointer grid grid-cols-4 even:bg-gray-100 odd:bg-white"
|
||||||
:class="customer.id == customer_id ? 'font-bold' : ''"
|
:class="customer.id == invoice.customer_id ? 'font-bold' : ''"
|
||||||
x-on:click="getAddress(index);">
|
@click="invoice.customer_id = customer.id; selected_customer = customer; getCustomerAddresses(); if(customer.address != null) { invoice.address_id = customer.address.id; selected_address = customer.address; } else { invoice.address_id = 0; selected_address = {}; } if(customer.delivery != null) { invoice.delivery_id = customer.delivery.id; selected_delivery = customer.delivery; } else { invoice.delivery_id = null; selected_delivery = {}; }">
|
||||||
<div x-text="customer.name"></div>
|
<div x-text="customer.name"></div>
|
||||||
<div x-text="customer.email"></div>
|
<div x-text="customer.email"></div>
|
||||||
<div x-text="(customer.address) ? customer.address.address : ''"></div>
|
<div x-text="(customer.address) ? customer.address.address : ''"></div>
|
||||||
@@ -46,12 +79,54 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
</section>
|
</section>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Project data -->
|
||||||
|
<div class="p-4 sm:p-8 bg-white dark:bg-gray-800 shadow sm:rounded-lg" x-show="from_project">
|
||||||
|
<div class="max-w">
|
||||||
|
<section>
|
||||||
|
<header class="grid grid-cols-2 gap-12 pb-8">
|
||||||
|
<div>
|
||||||
|
<h2 class="text-lg font-medium text-gray-900 dark:text-gray-100">
|
||||||
|
{{ __('invoice.Select project') }}
|
||||||
|
</h2>
|
||||||
|
<p class="mt-1 text-sm text-gray-600 dark:text-gray-400">
|
||||||
|
{{ __("invoice.Select your project") }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-row gap-8 items-center">
|
||||||
|
<x-input-label for="search_project" :value="__('common.Search')"/>
|
||||||
|
<x-text-input id="search_project" name="search_project" type="text"
|
||||||
|
class="mt-1 block w-full"
|
||||||
|
x-ref="search_project"
|
||||||
|
autofocus
|
||||||
|
placeholder="{{ __('invoice.Search project') }}"
|
||||||
|
x-on:keydown.window.prevent.slash="$refs.search_project.focus()"
|
||||||
|
x-model="project_search"/>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<template x-for="(project, index) in getFilteredProject()">
|
||||||
|
<div class="cursor-pointer grid grid-cols-4 even:bg-gray-100 odd:bg-white"
|
||||||
|
:class="project.id == invoice.project_id ? 'font-bold' : ''"
|
||||||
|
@click="invoice.project_id = project.id; selected_project = project; invoice.customer_id = project.customer.id; selected_customer = project.customer; getCustomerAddresses();">
|
||||||
|
<div x-text="project.name"></div>
|
||||||
|
<div x-text="project.project_number"></div>
|
||||||
|
<div x-text="project.customer_name"></div>
|
||||||
|
<div x-text="project.customer_email"></div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Address data -->
|
<!-- Address data -->
|
||||||
<div class="p-4 sm:p-8 bg-white dark:bg-gray-800 shadow sm:rounded-lg">
|
<div class="p-4 sm:p-8 bg-white dark:bg-gray-800 shadow sm:rounded-lg"
|
||||||
|
x-show="Object.keys(selected_customer).length > 0">
|
||||||
<div class="max-w">
|
<div class="max-w">
|
||||||
<section>
|
<section>
|
||||||
<header>
|
<header>
|
||||||
@@ -64,50 +139,62 @@
|
|||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div class="flex flex-row my-8">
|
<div class="flex flex-row my-8">
|
||||||
|
<!-- Invoice address -->
|
||||||
<div class="w-1/2">
|
<div class="w-1/2">
|
||||||
<div class="flex flex-row">
|
<div class="flex flex-row">
|
||||||
<div class="text-lg font-medium text-gray-900 dark:text-gray-100">{{ __('customer.Invoice Address') }}</div>
|
<div
|
||||||
<x-danger-button class="ml-4 px-0 py-0" x-show="address_id != 0">
|
class="text-lg font-medium text-gray-900 dark:text-gray-100">{{ __('customer.Invoice Address') }}</div>
|
||||||
<x-trash-icon class="h-5 p-0" x-on:click="address_id = 0; address = {}"/>
|
<x-danger-button class="ml-4 px-0 py-0" x-show="invoice.address_id != 0">
|
||||||
|
<x-trash-icon class="h-5 p-0"
|
||||||
|
x-on:click="invoice.address_id = 0; selected_address = {}"/>
|
||||||
</x-danger-button>
|
</x-danger-button>
|
||||||
</div>
|
</div>
|
||||||
<select class="absolute z-10 bg-white border-gray-300 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-300 focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 rounded-md shadow-sm mt-1" id="select_address_id" x-show="address_id == 0" x-on:click="setInvoiceAddress()">
|
<select
|
||||||
<template x-for="addr in addresses[customer_id]" >
|
class="absolute z-10 bg-white border-gray-300 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-300 focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 rounded-md shadow-sm mt-1"
|
||||||
<option :value="addr.id" x-text="addr.name + '; ' + addr.address + '; ' + addr.zip + ' ' + addr.city + '; ' + addr.phone + '; ' + addr.email"></option>
|
id="select_address_id" x-show="invoice.address_id == 0"
|
||||||
|
x-on:click="setInvoiceAddress();">
|
||||||
|
<template x-for="addr in addresses[invoice.customer_id]">
|
||||||
|
<option :value="addr.id"
|
||||||
|
x-text="addr.name + '; ' + addr.address + '; ' + addr.zip + ' ' + addr.city + '; ' + addr.phone + '; ' + addr.email"></option>
|
||||||
</template>
|
</template>
|
||||||
</select>
|
</select>
|
||||||
|
<div x-text="selected_address.name"></div>
|
||||||
<div x-text="address.name"></div>
|
<div x-text="selected_address.address"></div>
|
||||||
<div x-text="address.address"></div>
|
|
||||||
<div class="flex flex-row">
|
<div class="flex flex-row">
|
||||||
<div class="mr-2" x-text="address.zip"></div>
|
<div class="mr-2" x-text="selected_address.zip"></div>
|
||||||
<div x-text="address.city"></div>
|
<div x-text="selected_address.city"></div>
|
||||||
</div>
|
</div>
|
||||||
<div x-text="address.phone"></div>
|
<div x-text="selected_address.phone"></div>
|
||||||
<div x-text="address.email"></div>
|
<div x-text="selected_address.email"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Invoice delivery -->
|
||||||
<div class="w-1/2">
|
<div class="w-1/2">
|
||||||
<div class="flex flex-row">
|
<div class="flex flex-row">
|
||||||
<div class="text-lg font-medium text-gray-900 dark:text-gray-100">{{ __('customer.Delivery Address') }}</div>
|
<div
|
||||||
<x-danger-button class="ml-4 px-0 py-0" x-show="delivery_id != null">
|
class="text-lg font-medium text-gray-900 dark:text-gray-100">{{ __('customer.Delivery Address') }}</div>
|
||||||
<x-trash-icon class="h-5 p-0" x-on:click="delivery_id = null; delivery = {}"/>
|
<x-danger-button class="ml-4 px-0 py-0" x-show="invoice.delivery_id != null">
|
||||||
|
<x-trash-icon class="h-5 p-0"
|
||||||
|
x-on:click="invoice.delivery_id = null; selected_delivery = {}"/>
|
||||||
</x-danger-button>
|
</x-danger-button>
|
||||||
</div>
|
</div>
|
||||||
<select class="absolute z-10 bg-white border-gray-300 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-300 focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 rounded-md shadow-sm mt-1" id="select_delivery_id" x-show="delivery_id == null" x-on:click="setIDeliveryAddress()">
|
<select
|
||||||
<template x-for="addr in addresses[customer_id]" >
|
class="absolute z-10 bg-white border-gray-300 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-300 focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 rounded-md shadow-sm mt-1"
|
||||||
<option :value="addr.id" x-text="addr.name + '; ' + addr.address + '; ' + addr.zip + ' ' + addr.city + '; ' + addr.phone + '; ' + addr.email"></option>
|
id="select_delivery_id" x-show="invoice.delivery_id == null"
|
||||||
|
x-on:click="setDeliveryAddress()">
|
||||||
|
<template x-for="addr in addresses[invoice.customer_id]">
|
||||||
|
<option :value="addr.id"
|
||||||
|
x-text="addr.name + '; ' + addr.address + '; ' + addr.zip + ' ' + addr.city + '; ' + addr.phone + '; ' + addr.email"></option>
|
||||||
</template>
|
</template>
|
||||||
</select>
|
</select>
|
||||||
<div x-text="delivery.name"></div>
|
<div x-text="selected_delivery.name"></div>
|
||||||
<div x-text="delivery.address"></div>
|
<div x-text="selected_delivery.address"></div>
|
||||||
<div class="flex flex-row">
|
<div class="flex flex-row">
|
||||||
<div class="mr-2" x-text="delivery.zip"></div>
|
<div class="mr-2" x-text="selected_delivery.zip"></div>
|
||||||
<div x-text="delivery.city"></div>
|
<div x-text="selected_delivery.city"></div>
|
||||||
</div>
|
</div>
|
||||||
<div x-text="delivery.phone"></div>
|
<div x-text="selected_delivery.phone"></div>
|
||||||
<div x-text="delivery.email"></div>
|
<div x-text="selected_delivery.email"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -116,89 +203,165 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Invoice data -->
|
<!-- Invoice options -->
|
||||||
<div class="p-4 sm:p-8 bg-white dark:bg-gray-800 shadow sm:rounded-lg">
|
<div class="p-4 sm:p-8 bg-white dark:bg-gray-800 shadow sm:rounded-lg"
|
||||||
|
x-show="Object.keys(selected_address).length > 0">
|
||||||
<div class="max-w">
|
<div class="max-w">
|
||||||
<section>
|
<section>
|
||||||
<header>
|
<header>
|
||||||
<h2 class="text-lg font-medium text-gray-900 dark:text-gray-100">
|
<h2 class="text-lg font-medium text-gray-900 dark:text-gray-100">
|
||||||
{{ __('invoice.Invoice items') }}
|
{{ __('invoice.Invoice options') }}
|
||||||
</h2>
|
</h2>
|
||||||
<p class="mt-1 text-sm text-gray-600 dark:text-gray-400">
|
<p class="mt-1 text-sm text-gray-600 dark:text-gray-400">
|
||||||
{{ __("invoice.Enter your invoice items. Click add for an additional invoice item.") }}
|
{{ __("invoice.Select the options for this invoice.") }}
|
||||||
</p>
|
</p>
|
||||||
</header>
|
</header>
|
||||||
|
<div class="flex flex-row gap-4">
|
||||||
|
|
||||||
<!-- New invoice item -->
|
<div class="flex flex-col w-1/2">
|
||||||
<div class="flex flex-row items-end gap-2 w-full mt-4">
|
<div class="flex flex-row items-center">
|
||||||
|
<x-input-label class="w-1/3" for="type" :value="__('invoice.Type code')"/>
|
||||||
|
<select
|
||||||
|
class="w-2/3 bg-white border-gray-300 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-300 focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 rounded-md shadow-sm mt-1"
|
||||||
|
id="type" x-model="invoice.type">
|
||||||
|
|
||||||
|
@foreach(App\Enum\InvoiceTypeCode::options() as $key => $value)
|
||||||
|
<option value="{{ $key }}">{{ $value }}</option>
|
||||||
|
@endforeach
|
||||||
|
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-row items-center"
|
||||||
|
x-show="!from_project && (invoice.type === '326' || invoice.type === '875')">
|
||||||
|
<x-input-label class="w-1/3" for="project_count"
|
||||||
|
:value="__('invoice.Partial invoice number')"/>
|
||||||
|
<x-text-input id="project_count" name="project_count" type="text"
|
||||||
|
class="mt-1 w-2/3"
|
||||||
|
autofocus
|
||||||
|
x-model="invoice.project_count"/>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-row items-center">
|
||||||
|
<x-input-label class="w-1/3" for="due_date"
|
||||||
|
:value="__('invoice.Payment terms')"/>
|
||||||
|
<x-text-input id="due_date" name="due_date" type="date"
|
||||||
|
class="mt-1 w-2/3"
|
||||||
|
autofocus
|
||||||
|
x-model="invoice.due_date"/>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-row items-center">
|
||||||
|
<x-input-label class="w-1/3" for="cash_discount"
|
||||||
|
:value="__('invoice.Cash discount')"/>
|
||||||
|
<x-text-input id="cash_discount" name="cash_discount" type="number" steps="0.01"
|
||||||
|
class="mt-1 w-2/3"
|
||||||
|
autofocus x-on:blur="calculateSum();"
|
||||||
|
x-model="invoice.cash_discount"/>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-row items-center" x-show="invoice.cash_discount != 0">
|
||||||
|
<x-input-label class="w-1/3" for="cash_discount_date"
|
||||||
|
:value="__('invoice.Cash discount until')"/>
|
||||||
|
<x-text-input id="cash_discount_date" name="cash_discount_date" type="date"
|
||||||
|
class="mt-1 w-2/3"
|
||||||
|
autofocus
|
||||||
|
x-model="invoice.cash_discount_date"/>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex flex-col w-1/2">
|
||||||
|
<div class="flex flex-row items-center">
|
||||||
|
<x-input-label class="w-1/3" for="net" :value="__('invoice.Net')"/>
|
||||||
|
<x-text-input id="net" name="net" type="number"
|
||||||
|
class="mt-1 w-2/3" step="0.01"
|
||||||
|
autofocus disabled
|
||||||
|
x-model="net"/>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-row items-center">
|
||||||
|
<x-input-label class="w-1/3" for="tax" :value="__('invoice.Tax')"/>
|
||||||
|
<x-text-input id="tax" name="tax" type="number"
|
||||||
|
class="mt-1 w-2/3" step="0.01"
|
||||||
|
autofocus disabled
|
||||||
|
x-model="invoice.tax"/>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-row items-center">
|
||||||
|
<x-input-label class="w-1/3" for="sum" :value="__('invoice.Sum')"/>
|
||||||
|
<x-text-input id="sum" name="sum" type="number"
|
||||||
|
class="mt-1 w-2/3" step="0.01"
|
||||||
|
autofocus disabled
|
||||||
|
x-model="invoice.sum"/>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-row items-center" x-show="invoice.cash_discount != 0">
|
||||||
|
<x-input-label class="w-1/3" for="discount" :value="__('invoice.Cash discount sum')"/>
|
||||||
|
<x-text-input id="discount" name="discount" type="number"
|
||||||
|
class="mt-1 w-2/3" step="0.01"
|
||||||
|
autofocus disabled
|
||||||
|
x-model="discount"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Invoice items -->
|
||||||
|
<div class="p-4 sm:p-8 bg-white dark:bg-gray-800 shadow sm:rounded-lg"
|
||||||
|
x-show="Object.keys(selected_address).length > 0">
|
||||||
|
<div class="max-w">
|
||||||
|
<section>
|
||||||
|
<header>
|
||||||
|
<h2 class="text-lg font-medium text-gray-900 dark:text-gray-100 relative">
|
||||||
|
{{ __('invoice.Invoice items') }}
|
||||||
|
<x-primary-button x-on:click="addItem();" class="absolute right-0">+
|
||||||
|
</x-primary-button>
|
||||||
|
</h2>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="flex flex-row items-end gap-2 w-full">
|
||||||
<x-input-label for="invoice_item.amount" :value="__('invoice.Amount')" class="w-1/12"/>
|
<x-input-label for="invoice_item.amount" :value="__('invoice.Amount')" class="w-1/12"/>
|
||||||
<x-input-label for="invoice_item.name" :value="__('invoice.Name')" class="w-2/3"/>
|
<x-input-label for="invoice_item.name"
|
||||||
|
:value="__('invoice.Name') . ' / ' . __('invoice.Article number') . ' / ' . __('invoice.Description')"
|
||||||
|
class="w-1/2"/>
|
||||||
<x-input-label for="invoice_item.price" :value="__('invoice.Price')" class="w-1/12"/>
|
<x-input-label for="invoice_item.price" :value="__('invoice.Price')" class="w-1/12"/>
|
||||||
|
<x-input-label for="invoice_item.discount" :value="__('invoice.Discount')"
|
||||||
|
class="w-1/12"/>
|
||||||
<x-input-label for="invoice_item.tax" :value="__('invoice.Tax')" class="w-1/12"/>
|
<x-input-label for="invoice_item.tax" :value="__('invoice.Tax')" class="w-1/12"/>
|
||||||
|
<x-input-label for="invoice_item.tax" :value="__('invoice.Net')" class="w-1/12"/>
|
||||||
<div class="w-1/12 relative h-10"></div>
|
<div class="w-1/12 relative h-10"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-row items-end gap-2 w-full">
|
|
||||||
<x-text-input id="invoice_item.amount" name="invoice_item.amount" type="number"
|
|
||||||
class="mt-1 block w-1/12"
|
|
||||||
autofocus
|
|
||||||
x-model="invoice_item.amount"/>
|
|
||||||
|
|
||||||
<x-text-input id="invoice_item.name" name="invoice_item.name" type="text"
|
<div x-sort="sortItems">
|
||||||
class="mt-1 block w-2/3"
|
|
||||||
autofocus
|
|
||||||
placeholder="{{ __('invoice.Name') }}"
|
|
||||||
x-model="invoice_item.name"/>
|
|
||||||
|
|
||||||
<x-text-input id="invoice_item.price" name="invoice_item.price" type="number"
|
|
||||||
class="mt-1 block w-1/12"
|
|
||||||
autofocus
|
|
||||||
x-model="invoice_item.price"/>
|
|
||||||
|
|
||||||
<select name="invoice_item.tax" x-model="invoice_item.tax" id="invoice_item.tax"
|
|
||||||
class="border-gray-300 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-300 focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 rounded-md shadow-sm w-1/12">
|
|
||||||
<template x-for="tax in tax_rates">
|
|
||||||
<option x-bind:value="tax.rate" x-text="tax.rate_percentage"
|
|
||||||
:selected="(invoice_item.tax == tax.rate) ? true : tax.active"></option>
|
|
||||||
</template>
|
|
||||||
</select>
|
|
||||||
|
|
||||||
<div class="w-1/12 relative h-10">
|
|
||||||
<x-primary-button x-on:click="addItem();" class="absolute right-0">+</x-primary-button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-row items-end gap-2 w-full">
|
|
||||||
<div class="w-1/12"></div>
|
|
||||||
<textarea placeholder="{{ __('invoice.Description') }}" name="invoice_item.description"
|
|
||||||
x-model="invoice_item.description" x-text="invoice_item.description"
|
|
||||||
class="border-gray-300 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-300 focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 rounded-md shadow-sm mt-1 block w-2/3 offset-1"></textarea>
|
|
||||||
<div class="w-1/12 h-10"></div>
|
|
||||||
<div class="w-1/12 h-10"></div>
|
|
||||||
<div class="w-1/12 h-10"></div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Existing invoice items -->
|
|
||||||
<div x-sort="handle" x-bind="items" class="mt-4">
|
|
||||||
<template x-for="(item, index) in items">
|
<template x-for="(item, index) in items">
|
||||||
<div >
|
<div x-sort:item="index">
|
||||||
<div class="flex flex-row items-end gap-2 w-full relative">
|
<div class="flex flex-row items-end gap-2 w-full relative">
|
||||||
<x-text-input id="items[index].amount" name="items[index].amount" type="number"
|
<x-text-input id="items[index].amount" name="items[index].amount"
|
||||||
|
type="number"
|
||||||
class="mt-1 block w-1/12"
|
class="mt-1 block w-1/12"
|
||||||
autofocus
|
autofocus x-on:blur="calculateItem(index);"
|
||||||
x-model="items[index].amount"/>
|
x-model="items[index].amount"/>
|
||||||
|
|
||||||
<x-text-input id="items[index].name" name="items[index].name" type="text"
|
<x-text-input id="items[index].name" name="items[index].name" type="text"
|
||||||
class="mt-1 block w-2/3"
|
class="mt-1 block w-1/2"
|
||||||
autofocus
|
autofocus
|
||||||
placeholder="{{ __('invoice.Name') }}"
|
placeholder="{{ __('invoice.Name') }}"
|
||||||
x-model="items[index].name"/>
|
x-model="items[index].name"/>
|
||||||
|
|
||||||
<x-text-input id="items[index].price" name="items[index].price" type="text"
|
<x-text-input id="items[index].price" name="items[index].price"
|
||||||
|
type="number"
|
||||||
class="mt-1 block w-1/12"
|
class="mt-1 block w-1/12"
|
||||||
autofocus
|
autofocus x-on:blur="calculateItem(index);"
|
||||||
x-model="items[index].price"/>
|
x-model="items[index].price"/>
|
||||||
|
|
||||||
|
<x-text-input id="items[index].discount" name="items[index].discount"
|
||||||
|
type="number"
|
||||||
|
class="mt-1 block w-1/12"
|
||||||
|
autofocus x-on:blur="calculateItem(index);"
|
||||||
|
x-model="items[index].discount"/>
|
||||||
|
|
||||||
<select name="items[index].tax" x-model="items[index].tax"
|
<select name="items[index].tax" x-model="items[index].tax"
|
||||||
|
x-on:change="calculateItem(index);"
|
||||||
class="border-gray-300 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-300 focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 rounded-md shadow-sm w-1/12">
|
class="border-gray-300 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-300 focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 rounded-md shadow-sm w-1/12">
|
||||||
<template x-for="tax in tax_rates">
|
<template x-for="tax in tax_rates">
|
||||||
<option x-bind:value="tax.rate" x-text="tax.rate_percentage"
|
<option x-bind:value="tax.rate" x-text="tax.rate_percentage"
|
||||||
@@ -206,36 +369,48 @@
|
|||||||
</template>
|
</template>
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
|
<x-text-input id="items[index].total" name="items[index].total"
|
||||||
|
type="number"
|
||||||
|
class="mt-1 block w-1/12"
|
||||||
|
autofocus disabled
|
||||||
|
x-model="items[index].total"/>
|
||||||
|
|
||||||
<div class="flex flex-row w-1/12 h-10 relative">
|
<div class="flex flex-row w-1/12 h-10 relative">
|
||||||
<x-sort-icon x-sort:handle class="cursor-move"/>
|
<x-sort-icon x-sort:handle class="cursor-move"/>
|
||||||
<x-danger-button x-on:click="deleteItem(index);" class="absolute right-0">
|
<x-danger-button x-on:click="deleteItem(index);"
|
||||||
|
class="absolute right-0">
|
||||||
-
|
-
|
||||||
</x-danger-button>
|
</x-danger-button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-row items-end gap-2 w-full">
|
<div class="flex flex-row gap-2">
|
||||||
<div class="w-1/12"></div>
|
<div class="w-1/12"></div>
|
||||||
|
<div class="flex flex-col w-1/2 gap-2">
|
||||||
|
<x-text-input id="items[index].article_number"
|
||||||
|
name="items[index].article_number" type="text"
|
||||||
|
class="mt-1 block"
|
||||||
|
autofocus
|
||||||
|
placeholder="{{ __('invoice.Article number') }}"
|
||||||
|
x-model="items[index].article_number"/>
|
||||||
<textarea placeholder="{{ __('invoice.Description') }}"
|
<textarea placeholder="{{ __('invoice.Description') }}"
|
||||||
name="items[index].description" x-model="items[index].description"
|
name="items[index].description"
|
||||||
|
x-model="items[index].description"
|
||||||
x-text="items[index].description"
|
x-text="items[index].description"
|
||||||
class="border-gray-300 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-300 focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 rounded-md shadow-sm mt-1 block w-2/3"></textarea>
|
class="border-gray-300 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-300 focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 rounded-md shadow-sm mt-1 block w-full"></textarea>
|
||||||
<div class="w-1/12 h-10"></div>
|
|
||||||
<div class="w-1/12 h-10"></div>
|
|
||||||
<div class="w-1/12 h-10"></div>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<x-primary-button x-on:click="submitForm();" class="">{{ __('form.Save') }}</x-primary-button>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<x-primary-button x-on:click="submit();" class="">{{ __('form.Save') }}</x-primary-button>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@else
|
@else
|
||||||
@@ -250,60 +425,48 @@
|
|||||||
|
|
||||||
</x-app-layout>
|
</x-app-layout>
|
||||||
|
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
let self;
|
let self;
|
||||||
|
|
||||||
function invoiceForm() {
|
function invoiceForm() {
|
||||||
return {
|
return {
|
||||||
card: {},
|
from_project: {!! ($invoice->project_id == null) ? 'false' : 'true' !!},
|
||||||
customers: {},
|
|
||||||
customer: {!! $invoice->customer !!},
|
|
||||||
customer_id: {{ $invoice->customer->id }},
|
|
||||||
addresses: {},
|
|
||||||
address_id: {{ $invoice->address_id }},
|
|
||||||
address: {!! $invoice->address !!},
|
|
||||||
delivery_id: {!! (!is_null($invoice->delivery)) ? $invoice->delivery_id : 'null' !!},
|
|
||||||
delivery: {!! (!is_null($invoice->delivery)) ? $invoice->delivery : '{}' !!},
|
|
||||||
tax_rates: {},
|
|
||||||
tax_standard: 0,
|
|
||||||
|
|
||||||
invoice_id: {{ $invoice->id }},
|
|
||||||
|
|
||||||
invoice_item: {},
|
|
||||||
|
|
||||||
items: [],
|
|
||||||
sort: [],
|
|
||||||
|
|
||||||
selection: '',
|
|
||||||
|
|
||||||
search: '',
|
|
||||||
|
|
||||||
error: false,
|
error: false,
|
||||||
message: '',
|
message: '',
|
||||||
|
customer_search: '',
|
||||||
|
project_search: '',
|
||||||
|
|
||||||
|
projects: [],
|
||||||
|
customers: [],
|
||||||
|
tax_rates: [],
|
||||||
|
addresses: [],
|
||||||
|
items: {!! $invoice->items !!},
|
||||||
|
sort: [],
|
||||||
|
|
||||||
|
selected_project: {!! ($invoice->project) ? $invoice->project : '{}' !!},
|
||||||
|
selected_customer: {!! $invoice->customer !!},
|
||||||
|
selected_address: {!! $invoice->address !!},
|
||||||
|
selected_delivery: {!! ($invoice->delivery) ? $invoice->delivery : '{}' !!},
|
||||||
|
net: 0,
|
||||||
|
discount: 0,
|
||||||
|
|
||||||
|
invoice: {!! $invoice !!},
|
||||||
|
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
this.getCustomerAddresses();
|
this.addresses[this.invoice.customer_id] = {!! $invoice->customer->addresses !!};
|
||||||
this.getCustomers();
|
for (let i = 0; i < this.items.length; i++) {
|
||||||
this.getTaxRates();
|
this.sort.push(i);
|
||||||
self = this;
|
|
||||||
},
|
|
||||||
|
|
||||||
newItem() {
|
|
||||||
return {
|
|
||||||
name: '',
|
|
||||||
description: '',
|
|
||||||
amount: 1,
|
|
||||||
discount: 0,
|
|
||||||
tax: this.tax_standard,
|
|
||||||
price: 0,
|
|
||||||
total: 0,
|
|
||||||
}
|
}
|
||||||
|
this.getCustomers();
|
||||||
|
this.getProjects();
|
||||||
|
this.getTaxRates();
|
||||||
|
this.calculateSum();
|
||||||
|
self = this;
|
||||||
},
|
},
|
||||||
|
|
||||||
getCustomers() {
|
getCustomers() {
|
||||||
let vm = this;
|
let vm = this;
|
||||||
|
|
||||||
axios.get('/customer')
|
axios.get('/customer')
|
||||||
.then(function (response) {
|
.then(function (response) {
|
||||||
vm.customers = response.data;
|
vm.customers = response.data;
|
||||||
@@ -314,85 +477,123 @@
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
getTaxRates() {
|
|
||||||
let vm = this;
|
|
||||||
|
|
||||||
axios.get('/taxrate')
|
|
||||||
.then(function (response) {
|
|
||||||
vm.tax_rates = response.data;
|
|
||||||
let test = vm.tax_rates.find(function (tax) {
|
|
||||||
return tax.active;
|
|
||||||
});
|
|
||||||
vm.tax_standard = test.rate;
|
|
||||||
vm.invoice_item = vm.newItem();
|
|
||||||
|
|
||||||
let init_items = {!! $invoice->items !!};
|
|
||||||
init_items.forEach((obj) => {
|
|
||||||
vm.invoice_item = obj;
|
|
||||||
vm.addItem();
|
|
||||||
});
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
getFilteredCustomer() {
|
getFilteredCustomer() {
|
||||||
if (this.search === '') {
|
if (this.customer_search === '') {
|
||||||
return this.customers;
|
return this.customers;
|
||||||
}
|
}
|
||||||
return this.customers.filter((customer) => {
|
return this.customers.filter((customer) => {
|
||||||
return customer.name
|
return customer.name
|
||||||
.replace(/ /g, '')
|
.replace(/ /g, '')
|
||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
.includes(this.search.replace(/ /g, '').toLowerCase())
|
.includes(this.customer_search.replace(/ /g, '').toLowerCase())
|
||||||
||
|
||
|
||||||
customer.email
|
customer.email
|
||||||
.replace(/ /g, '')
|
.replace(/ /g, '')
|
||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
.includes(this.search.replace(/ /g, '').toLowerCase())
|
.includes(this.customer_search.replace(/ /g, '').toLowerCase())
|
||||||
||
|
||
|
||||||
customer.street
|
customer.street
|
||||||
.replace(/ /g, '')
|
.replace(/ /g, '')
|
||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
.includes(this.search.replace(/ /g, '').toLowerCase())
|
.includes(this.customer_search.replace(/ /g, '').toLowerCase())
|
||||||
||
|
||
|
||||||
customer.city
|
customer.city
|
||||||
.replace(/ /g, '')
|
.replace(/ /g, '')
|
||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
.includes(this.search.replace(/ /g, '').toLowerCase())
|
.includes(this.customer_search.replace(/ /g, '').toLowerCase())
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
getAddress(index) {
|
getProjects() {
|
||||||
this.customer = this.customers[index];
|
let vm = this;
|
||||||
this.customer_id = this.customer.id;
|
axios.get('project')
|
||||||
this.address_id = 0;
|
.then(function (response) {
|
||||||
this.delivery_id = null;
|
vm.projects = response.data;
|
||||||
this.address = {};
|
})
|
||||||
this.delivery = {};
|
.catch(function (error) {
|
||||||
if (this.customer.address) {
|
vm.error = true;
|
||||||
this.address_id = this.customer.address.id;
|
vm.message = error.response.data.message;
|
||||||
this.address = this.customer.address;
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
getFilteredProject() {
|
||||||
|
if (this.project_search === '') {
|
||||||
|
return this.projects;
|
||||||
}
|
}
|
||||||
if (this.customer.delivery) {
|
return this.projects.filter((project) => {
|
||||||
this.delivery_id = this.customer.delivery.id;
|
return project.name
|
||||||
this.delivery = this.customer.delivery;
|
.replace(/ /g, '')
|
||||||
}
|
.toLowerCase()
|
||||||
this.getCustomerAddresses();
|
.includes(this.project_search.replace(/ /g, '').toLowerCase())
|
||||||
|
||
|
||||||
|
project.project_number
|
||||||
|
.replace(/ /g, '')
|
||||||
|
.toLowerCase()
|
||||||
|
.includes(this.project_search.replace(/ /g, '').toLowerCase())
|
||||||
|
||
|
||||||
|
project.customer_name
|
||||||
|
.replace(/ /g, '')
|
||||||
|
.toLowerCase()
|
||||||
|
.includes(this.project_search.replace(/ /g, '').toLowerCase())
|
||||||
|
||
|
||||||
|
project.customer_email
|
||||||
|
.replace(/ /g, '')
|
||||||
|
.toLowerCase()
|
||||||
|
.includes(this.project_search.replace(/ /g, '').toLowerCase())
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
getTaxRates() {
|
||||||
|
let vm = this;
|
||||||
|
axios.get('/taxrate')
|
||||||
|
.then(function (response) {
|
||||||
|
vm.tax_rates = response.data;
|
||||||
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
getCustomerAddresses() {
|
getCustomerAddresses() {
|
||||||
if (typeof (this.addresses[this.customer_id]) == 'undefined') {
|
if (typeof (this.addresses[this.invoice.customer_id]) == 'undefined') {
|
||||||
let vm = this;
|
let vm = this;
|
||||||
axios.get('/customer/' + this.customer_id + '/address')
|
axios.get('/customer/' + this.invoice.customer_id + '/address')
|
||||||
.then(function (response) {
|
.then(function (response) {
|
||||||
vm.addresses[vm.customer_id] = response.data;
|
vm.addresses[vm.invoice.customer_id] = response.data;
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
setInvoiceAddress() {
|
||||||
|
let id = document.querySelector('#select_address_id').value;
|
||||||
|
if (id === '') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.invoice.address_id = id;
|
||||||
|
let address_key = Object.keys(this.addresses[this.invoice.customer_id]).find(key => (this.addresses[this.invoice.customer_id][key].id == id));
|
||||||
|
this.selected_address = this.addresses[this.invoice.customer_id][address_key];
|
||||||
|
},
|
||||||
|
|
||||||
|
setDeliveryAddress() {
|
||||||
|
let id = document.querySelector('#select_delivery_id').value;
|
||||||
|
if (id === '') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.invoice.delivery_id = id;
|
||||||
|
let address_key = Object.keys(this.addresses[this.invoice.customer_id]).find(key => (this.addresses[this.invoice.customer_id][key].id == id));
|
||||||
|
this.selected_delivery = this.addresses[this.invoice.customer_id][address_key];
|
||||||
|
},
|
||||||
|
|
||||||
addItem() {
|
addItem() {
|
||||||
this.items.push(this.invoice_item);
|
let item = {
|
||||||
|
name: '',
|
||||||
|
article_number: '',
|
||||||
|
description: '',
|
||||||
|
amount: 1,
|
||||||
|
discount: 0,
|
||||||
|
tax: 19,
|
||||||
|
price: 0,
|
||||||
|
total: 0
|
||||||
|
};
|
||||||
|
this.items.push(item);
|
||||||
this.sort.push(this.items.length - 1);
|
this.sort.push(this.items.length - 1);
|
||||||
this.invoice_item = this.newItem();
|
|
||||||
},
|
},
|
||||||
|
|
||||||
deleteItem(index) {
|
deleteItem(index) {
|
||||||
@@ -406,64 +607,26 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
submitForm() {
|
calculateItem(index) {
|
||||||
let sum = 0;
|
this.items[index].total = (parseFloat(this.items[index].amount) * parseFloat(this.items[index].price) * (1 + parseFloat(this.items[index].tax) / 100) * (1 - parseFloat(this.items[index].discount / 100))).toFixed(2);
|
||||||
let tax = 0;
|
this.calculateSum();
|
||||||
let sort_flipped = Object.entries(this.sort)
|
|
||||||
.reduce((obj, [key, value]) => ({...obj, [value]: key}), {});
|
|
||||||
for (let i = 0; i < this.items.length; i++) {
|
|
||||||
this.items[i].total = this.items[i].amount * this.items[i].price * (1 + this.items[i].tax / 100);
|
|
||||||
tax += this.items[i].amount * this.items[i].price * this.items[i].tax / 100;
|
|
||||||
sum += this.items[i].total;
|
|
||||||
}
|
|
||||||
|
|
||||||
axios.put('invoice/' + this.invoice_id, {
|
|
||||||
customer_id: this.customer_id,
|
|
||||||
address_id: this.address_id,
|
|
||||||
delivery_id: this.delivery_id,
|
|
||||||
sum: sum,
|
|
||||||
tax: tax
|
|
||||||
})
|
|
||||||
.then(function (response) {
|
|
||||||
let error = false;
|
|
||||||
let invoice_id = response.data.id;
|
|
||||||
for (let i = 0; i < self.items.length; i++) {
|
|
||||||
let pos = sort_flipped[i];
|
|
||||||
let item = self.items[pos];
|
|
||||||
axios.post('invoice/' + invoice_id + '/item', item)
|
|
||||||
.then(function (response) {
|
|
||||||
console.log(response);
|
|
||||||
})
|
|
||||||
.catch(function (error) {
|
|
||||||
error = true;
|
|
||||||
console.log(error);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if (!error) {
|
|
||||||
window.location.href = '/invoice/' + invoice_id;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(function (error) {
|
|
||||||
console.log(error);
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
|
|
||||||
setInvoiceAddress() {
|
calculateSum() {
|
||||||
let id = document.querySelector('#select_address_id').value;
|
let vm = this;
|
||||||
this.address_id = id;
|
vm.invoice.tax = 0;
|
||||||
let address_key = Object.keys(this.addresses[this.customer_id]).find(key => (this.addresses[this.customer_id][key].id == id ));
|
vm.invoice.sum = 0;
|
||||||
this.address = this.addresses[this.customer_id][address_key];
|
this.items.forEach(function (item) {
|
||||||
|
vm.invoice.tax += item.amount * item.price * (item.tax / 100) * (1 - item.discount / 100);
|
||||||
|
vm.invoice.sum += item.total * 1;
|
||||||
|
});
|
||||||
|
vm.invoice.tax = (vm.invoice.tax).toFixed(2)
|
||||||
|
vm.invoice.sum = (vm.invoice.sum).toFixed(2)
|
||||||
|
vm.net = (vm.invoice.sum - vm.invoice.tax).toFixed(2);
|
||||||
|
vm.discount = (vm.invoice.sum * (100 - vm.invoice.cash_discount) / 100).toFixed(2);
|
||||||
},
|
},
|
||||||
|
|
||||||
setIDeliveryAddress() {
|
sortItems(item, position) {
|
||||||
let id = document.querySelector('#select_delivery_id').value;
|
|
||||||
this.delivery_id = id;
|
|
||||||
let address_key = Object.keys(this.addresses[this.customer_id]).find(key => (this.addresses[this.customer_id][key].id == id ));
|
|
||||||
this.delivery = this.addresses[this.customer_id][address_key];
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
handle(item, position) {
|
|
||||||
if (position > self.sort[item]) {
|
if (position > self.sort[item]) {
|
||||||
for (let i = 0; i < self.sort.length; i++) {
|
for (let i = 0; i < self.sort.length; i++) {
|
||||||
if (self.sort[i] <= position && self.sort[i] > self.sort[item]) {
|
if (self.sort[i] <= position && self.sort[i] > self.sort[item]) {
|
||||||
@@ -483,6 +646,36 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async submit() {
|
||||||
|
let sort_flipped = Object.entries(this.sort)
|
||||||
|
.reduce((obj, [key, value]) => ({...obj, [value]: key}), {});
|
||||||
|
const promises = [];
|
||||||
|
|
||||||
|
const main = axios.put('/invoice/' + this.invoice.id, this.invoice)
|
||||||
|
.catch(function (error) {
|
||||||
|
self.error = true;
|
||||||
|
self.message = error.response.data.message;
|
||||||
|
})
|
||||||
|
promises.push(main);
|
||||||
|
|
||||||
|
await Promise.all(promises);
|
||||||
|
for (let i = 0; i < self.items.length; i++) {
|
||||||
|
let pos = sort_flipped[i];
|
||||||
|
let item = self.items[pos];
|
||||||
|
const result = axios.post('invoice/' + self.invoice.id + '/item', item)
|
||||||
|
.catch(function (error) {
|
||||||
|
self.error = true;
|
||||||
|
self.message = error.response.data.message;
|
||||||
|
})
|
||||||
|
promises.push(result);
|
||||||
|
|
||||||
|
}
|
||||||
|
await Promise.all(promises);
|
||||||
|
|
||||||
|
if (!self.error) {
|
||||||
|
window.location.href = '/invoice/' + self.invoice.id;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -5,8 +5,10 @@
|
|||||||
{{ __('invoice.Invoice') }} {{ $invoice->number }} ({{ $invoice->localized_state }})
|
{{ __('invoice.Invoice') }} {{ $invoice->number }} ({{ $invoice->localized_state }})
|
||||||
</h2>
|
</h2>
|
||||||
<p class="relative flex flex-row">
|
<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-pdf-icon class="text-gray-800 cursor-pointer"
|
||||||
<x-e-icon class="cursor-pointer" onclick="window.open('/invoice/{{ $invoice->id }}/xml-download', '_blank', 'popup=true')"/>
|
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>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</x-slot>
|
</x-slot>
|
||||||
@@ -20,11 +22,14 @@
|
|||||||
<section>
|
<section>
|
||||||
<header>
|
<header>
|
||||||
<h2 class="text-lg font-medium text-gray-900 dark:text-gray-100">
|
<h2 class="text-lg font-medium text-gray-900 dark:text-gray-100">
|
||||||
{{ __('customer.Customer') }}: {{ $invoice->customer->name }} ({{ $invoice->customer->email }})
|
{{ __('customer.Customer') }}: {{ $invoice->customer->name }}
|
||||||
|
({{ $invoice->customer->email }})
|
||||||
</h2>
|
</h2>
|
||||||
<div class="grid grid-cols-2 mt-1 text-sm text-gray-600 dark:text-gray-400">
|
<div class="grid grid-cols-2 mt-4 text-sm text-gray-600 dark:text-gray-400">
|
||||||
<div>{{ __("customer.Invoice Address") }}</div>
|
<div>{{ __("customer.Invoice Address") }}</div>
|
||||||
|
@if($invoice->delivery)
|
||||||
<div>{{ __("customer.Delivery Address") }}</div>
|
<div>{{ __("customer.Delivery Address") }}</div>
|
||||||
|
@endif
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
@@ -42,6 +47,95 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Invoice options -->
|
||||||
|
<div class="p-4 sm:p-8 bg-white dark:bg-gray-800 shadow sm:rounded-lg">
|
||||||
|
<div class="max-w">
|
||||||
|
<section>
|
||||||
|
<header>
|
||||||
|
<h2 class="text-lg font-medium text-gray-900 dark:text-gray-100">
|
||||||
|
{{ __('invoice.Invoice options') }}
|
||||||
|
</h2>
|
||||||
|
</header>
|
||||||
|
</section>
|
||||||
|
<div class="flex flex-col gap-2 w-1/2 mt-4 text-sm text-gray-600 dark:text-gray-400">
|
||||||
|
<div class="flex flex-row items-center">
|
||||||
|
<x-input-label class="w-1/3" for="type" :value="__('invoice.Type code')"/>
|
||||||
|
<x-text-input id="type" name="type" type="text"
|
||||||
|
value="{{ \App\Enum\InvoiceTypeCode::label($invoice->type) }}"
|
||||||
|
class="mt-1 w-2/3"
|
||||||
|
autofocus disabled
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
@if(in_array($invoice->type, ['326', '875']))
|
||||||
|
<div class="flex flex-row items-center">
|
||||||
|
<x-input-label class="w-1/3" for="project_count"
|
||||||
|
:value="__('invoice.Partial invoice number')"/>
|
||||||
|
<x-text-input id="project_count" name="project_count" type="text"
|
||||||
|
value="{{ $invoice->project_count }}"
|
||||||
|
class="mt-1 w-2/3"
|
||||||
|
autofocus disabled
|
||||||
|
/>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@if($invoice->project)
|
||||||
|
<div class="flex flex-row items-center">
|
||||||
|
<x-input-label class="w-1/3" for="project_count"
|
||||||
|
:value="__('project.Project')"/>
|
||||||
|
<x-text-input id="project_count" name="project_count" type="text"
|
||||||
|
value="{{ $invoice->project->name }}"
|
||||||
|
class="mt-1 w-2/3"
|
||||||
|
autofocus disabled
|
||||||
|
/>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-row items-center">
|
||||||
|
<x-input-label class="w-1/3" for="project_count"
|
||||||
|
:value="__('project.Project Number')"/>
|
||||||
|
<x-text-input id="project_count" name="project_count" type="text"
|
||||||
|
value="{{ $invoice->project->project_number }}"
|
||||||
|
class="mt-1 w-2/3"
|
||||||
|
autofocus disabled
|
||||||
|
/>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
<div class="flex flex-row items-center">
|
||||||
|
<x-input-label class="w-1/3" for="due_date"
|
||||||
|
:value="__('invoice.Payment terms')"/>
|
||||||
|
<x-text-input id="due_date" name="due_date" type="date"
|
||||||
|
value="{{ $invoice->due_date }}"
|
||||||
|
class="mt-1 w-2/3"
|
||||||
|
autofocus disabled
|
||||||
|
/>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-row items-center">
|
||||||
|
<x-input-label class="w-1/3" for="cash_discount"
|
||||||
|
:value="__('invoice.Cash discount')"/>
|
||||||
|
<x-text-input id="cash_discount" name="cash_discount" type="number" steps="0.01"
|
||||||
|
value="{{ $invoice->cash_discount }}"
|
||||||
|
class="mt-1 w-2/3"
|
||||||
|
autofocus disabled
|
||||||
|
/>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-row items-center" x-show="invoice.cash_discount != 0">
|
||||||
|
<x-input-label class="w-1/3" for="cash_discount_date"
|
||||||
|
:value="__('invoice.Cash discount until')"/>
|
||||||
|
<x-text-input id="cash_discount_date" name="cash_discount_date" type="date"
|
||||||
|
value="{{ $invoice->cash_discount_date }}"
|
||||||
|
class="mt-1 w-2/3"
|
||||||
|
autofocus disabled
|
||||||
|
/>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Invoice items -->
|
<!-- Invoice items -->
|
||||||
<div class="p-4 sm:p-8 bg-white dark:bg-gray-800 shadow sm:rounded-lg">
|
<div class="p-4 sm:p-8 bg-white dark:bg-gray-800 shadow sm:rounded-lg">
|
||||||
<div class="max-w">
|
<div class="max-w">
|
||||||
@@ -50,12 +144,18 @@
|
|||||||
<h2 class="text-lg font-medium text-gray-900 dark:text-gray-100">
|
<h2 class="text-lg font-medium text-gray-900 dark:text-gray-100">
|
||||||
{{ __('invoice.Invoice items') }}
|
{{ __('invoice.Invoice items') }}
|
||||||
</h2>
|
</h2>
|
||||||
<div class="flex flex-row items-end gap-2 w-full mt-1 text-sm text-gray-600 dark:text-gray-400">
|
<div
|
||||||
|
class="flex flex-row items-end gap-2 w-full mt-4 text-sm text-gray-600 dark:text-gray-400">
|
||||||
<x-input-label for="invoice_item.amount" :value="__('invoice.Amount')" class="w-1/12"/>
|
<x-input-label for="invoice_item.amount" :value="__('invoice.Amount')" class="w-1/12"/>
|
||||||
<x-input-label for="invoice_item.name" :value="__('invoice.Name')" class="w-2/3"/>
|
<x-input-label for="invoice_item.name" :value="__('invoice.Name')" class="w-7/12"/>
|
||||||
<x-input-label for="invoice_item.price" :value="__('invoice.Price')" class="w-1/12 text-right"/>
|
<x-input-label for="invoice_item.price" :value="__('invoice.Price')"
|
||||||
<x-input-label for="invoice_item.tax" :value="__('invoice.Tax')" class="w-1/12 text-right"/>
|
class="w-1/12 text-right"/>
|
||||||
<x-input-label for="invoice_item.tax" :value="__('invoice.Sum')" class="w-1/12 text-right"/>
|
<x-input-label for="invoice_item.tax" :value="__('invoice.Discount')"
|
||||||
|
class="w-1/12 text-right"/>
|
||||||
|
<x-input-label for="invoice_item.tax" :value="__('invoice.Tax')"
|
||||||
|
class="w-1/12 text-right"/>
|
||||||
|
<x-input-label for="invoice_item.tax" :value="__('invoice.Sum')"
|
||||||
|
class="w-1/12 text-right"/>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
@@ -75,7 +175,8 @@
|
|||||||
<div class="flex flex-row items-end gap-2 w-full">
|
<div class="flex flex-row items-end gap-2 w-full">
|
||||||
<div class="w-1/12"></div>
|
<div class="w-1/12"></div>
|
||||||
<div class="mt-1 block w-2/3">{{ __('invoice.Net') }}</div>
|
<div class="mt-1 block w-2/3">{{ __('invoice.Net') }}</div>
|
||||||
<div class="w-1/4 text-right">{{ \Illuminate\Support\Number::currency($invoice->sum - $invoice->tax) }}</div>
|
<div
|
||||||
|
class="w-1/4 text-right">{{ \Illuminate\Support\Number::currency($invoice->sum - $invoice->tax) }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-row items-end gap-2 w-full">
|
<div class="flex flex-row items-end gap-2 w-full">
|
||||||
<div class="w-1/12"></div>
|
<div class="w-1/12"></div>
|
||||||
@@ -87,6 +188,11 @@
|
|||||||
<div class="mt-1 block w-2/3">{{ __('invoice.Sum') }}</div>
|
<div class="mt-1 block w-2/3">{{ __('invoice.Sum') }}</div>
|
||||||
<div class="w-1/4 text-right">{{ \Illuminate\Support\Number::currency($invoice->sum) }}</div>
|
<div class="w-1/4 text-right">{{ \Illuminate\Support\Number::currency($invoice->sum) }}</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="flex flex-row items-end gap-2 w-full">
|
||||||
|
<div class="w-1/12"></div>
|
||||||
|
<div class="mt-1 block w-2/3">{{ __('invoice.Cash discount sum') }}</div>
|
||||||
|
<div class="w-1/4 text-right">{{ \Illuminate\Support\Number::currency($invoice->sum * (100 - $invoice->cash_discount) / 100) }}</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -94,8 +200,12 @@
|
|||||||
<div class="p-4 sm:p-8 bg-white dark:bg-gray-800 shadow sm:rounded-lg">
|
<div class="p-4 sm:p-8 bg-white dark:bg-gray-800 shadow sm:rounded-lg">
|
||||||
<div class="max-w">
|
<div class="max-w">
|
||||||
<div class="flex flex-row items-end gap-2 w-full">
|
<div class="flex flex-row items-end gap-2 w-full">
|
||||||
<x-primary-button onclick="window.location.href = '{{ route('invoice.mail', $invoice->id) }}'"><x-mail-icon class="mr-4"/>{{ __('form.Send') }}</x-primary-button>
|
<x-primary-button
|
||||||
<x-primary-button onclick="window.location.href = '{{ route('invoice.edit', $invoice->id) }}'"><x-edit-icon class="mr-4"/>{{ __('form.Edit') }}</x-primary-button>
|
onclick="window.location.href = '{{ route('invoice.mail', $invoice->id) }}'">
|
||||||
|
<x-mail-icon class="mr-4"/>{{ __('form.Send') }}</x-primary-button>
|
||||||
|
<x-primary-button
|
||||||
|
onclick="window.location.href = '{{ route('invoice.edit', $invoice->id) }}'">
|
||||||
|
<x-edit-icon class="mr-4"/>{{ __('form.Edit') }}</x-primary-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -205,6 +205,44 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Invoices -->
|
||||||
|
<div class="p-4 sm:p-8 bg-white dark:bg-gray-800 shadow sm:rounded-lg">
|
||||||
|
<div class="max-w">
|
||||||
|
<section>
|
||||||
|
<details>
|
||||||
|
<summary class="text-lg font-medium text-gray-900 dark:text-gray-100 cursor-pointer">
|
||||||
|
{{ __('invoice.Invoices') }}
|
||||||
|
</summary>
|
||||||
|
<form class="mt-6 space-y-2" @submit.prevent="">
|
||||||
|
<div class="flex flex-row items-center">
|
||||||
|
<div class="w-1/4 flex flex-row items-center">
|
||||||
|
<x-input-label for="payment_terms"
|
||||||
|
:value="__('invoice.Payment terms in days')"/>
|
||||||
|
</div>
|
||||||
|
<x-text-input id="payment_terms" name="payment_terms" type="number"
|
||||||
|
class="mt-1 block"
|
||||||
|
:value="old('payment_terms')" required autofocus
|
||||||
|
autocomplete="payment_terms"
|
||||||
|
x-model="options.payment_terms"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex flex-row items-center">
|
||||||
|
<div class="w-1/4 flex flex-row items-center">
|
||||||
|
<x-input-label for="cash_discount_date"
|
||||||
|
:value="__('invoice.Cash discount terms in days')"/>
|
||||||
|
</div>
|
||||||
|
<x-text-input id="cash_discount_date" name="cash_discount_date" type="number"
|
||||||
|
class="mt-1 block"
|
||||||
|
:value="old('cash_discount_date')" required autofocus
|
||||||
|
autocomplete="cash_discount_date"
|
||||||
|
x-model="options.cash_discount_date"/>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</details>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Correspondence -->
|
<!-- Correspondence -->
|
||||||
<div class="p-4 sm:p-8 bg-white dark:bg-gray-800 shadow sm:rounded-lg">
|
<div class="p-4 sm:p-8 bg-white dark:bg-gray-800 shadow sm:rounded-lg">
|
||||||
<div class="max-w">
|
<div class="max-w">
|
||||||
@@ -357,8 +395,12 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="p-4 sm:p-8 bg-white dark:bg-gray-800 shadow sm:rounded-lg">
|
||||||
|
<p class="text-red-600 font-bold mb-8" x-text="message" x-show="error"></p>
|
||||||
|
|
||||||
|
<p x-show="success" x-transition
|
||||||
|
class="text-sm text-green-600 dark:text-green-400 mb-8">{{ __('form.Saved') }}</p>
|
||||||
|
|
||||||
<div class="flex items-center gap-4">
|
|
||||||
<x-primary-button @click="submit">{{ __('form.Save') }}</x-primary-button>
|
<x-primary-button @click="submit">{{ __('form.Save') }}</x-primary-button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -371,6 +413,7 @@
|
|||||||
return {
|
return {
|
||||||
options: {},
|
options: {},
|
||||||
error: false,
|
error: false,
|
||||||
|
success: false,
|
||||||
message: '',
|
message: '',
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
@@ -381,7 +424,6 @@
|
|||||||
vm.options[value.name] = value.value;
|
vm.options[value.name] = value.value;
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
console.log(vm.options);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
updatePreview() {
|
updatePreview() {
|
||||||
@@ -401,11 +443,11 @@
|
|||||||
let vm = this;
|
let vm = this;
|
||||||
axios.post('/option', vm.options)
|
axios.post('/option', vm.options)
|
||||||
.then(function (response) {
|
.then(function (response) {
|
||||||
console.log(response.data);
|
vm.success = true;
|
||||||
|
|
||||||
})
|
})
|
||||||
.catch(function (error) {
|
.catch(function (error) {
|
||||||
console.log(error);
|
vm.error = true;
|
||||||
|
vm.message = error.response.data.message;
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,10 +28,17 @@
|
|||||||
|
|
||||||
<div class="clear"></div>
|
<div class="clear"></div>
|
||||||
|
|
||||||
<div class="left" style="width: 110mm; height:15mm;">
|
@php
|
||||||
{{ __('invoice.Invoice Number short') }}: {{ $invoice->number }}
|
$height = ($invoice->project) ? 25 : 15;
|
||||||
|
@endphp
|
||||||
|
<div class="left" style="width: 110mm; height:{{ $height }}mm;">
|
||||||
|
{{ __('invoice.Invoice Number short') }}: {{ $invoice->number }}<br/>
|
||||||
|
{{ \App\Enum\InvoiceTypeCode::label($invoice->type) }} {{ (in_array($invoice->type, ['326', '875'])) ? $invoice->project_count : '' }}
|
||||||
|
@if($invoice->project)
|
||||||
|
<br/>{{ $invoice->project->name }} - {{ __('project.Project Number') }}: {{ $invoice->project->project_number }}
|
||||||
|
@endif
|
||||||
</div>
|
</div>
|
||||||
<div class="right" style=" width: 60mm; height:15mm;">
|
<div class="right" style=" width: 60mm; height:{{ $height }}mm;">
|
||||||
<div class="left">{{ __('common.Date') }}:</div>
|
<div class="left">{{ __('common.Date') }}:</div>
|
||||||
<div class="right text-right">{{ $invoice->created }}</div>
|
<div class="right text-right">{{ $invoice->created }}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -5,23 +5,93 @@
|
|||||||
<title>{{ __('invoice.Invoice') }}</title>
|
<title>{{ __('invoice.Invoice') }}</title>
|
||||||
<style>
|
<style>
|
||||||
|
|
||||||
@page { margin: 50mm 15mm 35mm 20mm; font-family:Figtree,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";font-feature-settings:normal;font-variation-settings:normal; }
|
@page {
|
||||||
header { position: fixed; top: -30mm; left: 0; right: 0; height: 25mm; }
|
margin: 50mm 15mm 35mm 20mm;
|
||||||
footer { position: fixed; bottom: -25mm; left: 0; right: 0; height: 10mm; font-size: 10px; border-top: 1px solid black; padding-top: 2mm;}
|
font-family: Figtree, ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", Segoe UI Symbol, "Noto Color Emoji";
|
||||||
.invoice { font-size: 13px; }
|
font-feature-settings: normal;
|
||||||
.invoice-header { font-weight: bold; border-bottom: 1px solid black; }
|
font-variation-settings: normal;
|
||||||
.item { border-bottom: 1px solid black; }
|
}
|
||||||
.header-left { width: 100mm; }
|
|
||||||
.header-right { width: 70mm; font-size: 13px; }
|
header {
|
||||||
.header-logo { height: 20mm; }
|
position: fixed;
|
||||||
.footer-left { width: 115mm; }
|
top: -30mm;
|
||||||
.footer-center { width: 25mm; }
|
left: 0;
|
||||||
.footer-right {width: 40mm; }
|
right: 0;
|
||||||
.text-right { text-align: right; }
|
height: 25mm;
|
||||||
.left { float: left; display: inline-block; }
|
}
|
||||||
.right { float: right; display: inline-block; }
|
|
||||||
.clear { clear:both; height: 0; line-height: 0; }
|
footer {
|
||||||
.page-break { page-break-after: always; }
|
position: fixed;
|
||||||
|
bottom: -25mm;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 10mm;
|
||||||
|
font-size: 10px;
|
||||||
|
border-top: 1px solid black;
|
||||||
|
padding-top: 2mm;
|
||||||
|
}
|
||||||
|
|
||||||
|
.invoice {
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.invoice-header {
|
||||||
|
font-weight: bold;
|
||||||
|
border-bottom: 1px solid black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item {
|
||||||
|
border-bottom: 1px solid black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-left {
|
||||||
|
width: 100mm;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-right {
|
||||||
|
width: 70mm;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-logo {
|
||||||
|
height: 20mm;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-left {
|
||||||
|
width: 115mm;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-center {
|
||||||
|
width: 25mm;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-right {
|
||||||
|
width: 40mm;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-right {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.left {
|
||||||
|
float: left;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right {
|
||||||
|
float: right;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.clear {
|
||||||
|
clear: both;
|
||||||
|
height: 0;
|
||||||
|
line-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-break {
|
||||||
|
page-break-after: always;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@@ -36,8 +106,10 @@
|
|||||||
<div class="invoice-header">
|
<div class="invoice-header">
|
||||||
<div style="width: 15mm;" class="left">{{ __('invoice.Item position short') }}</div>
|
<div style="width: 15mm;" class="left">{{ __('invoice.Item position short') }}</div>
|
||||||
<div style="width: 15mm;" class="left">{{ __('invoice.Item amount short') }}</div>
|
<div style="width: 15mm;" class="left">{{ __('invoice.Item amount short') }}</div>
|
||||||
<div style="width: 85mm;" class="left">{{ __('invoice.Item description') }}</div>
|
<div style="width: 65mm;" class="left">{{ __('invoice.Name') }} / {{ __('invoice.Article number') }}
|
||||||
|
/ {{ __('invoice.Description') }}</div>
|
||||||
<div style="width: 20mm;" class="left text-right">{{ __('invoice.Item single price short') }}</div>
|
<div style="width: 20mm;" class="left text-right">{{ __('invoice.Item single price short') }}</div>
|
||||||
|
<div style="width: 20mm;" class="left text-right">{{ __('invoice.Discount') }}</div>
|
||||||
<div style="width: 20mm;" class="left text-right">{{ __('invoice.Vat short') }}</div>
|
<div style="width: 20mm;" class="left text-right">{{ __('invoice.Vat short') }}</div>
|
||||||
<div style="width: 20mm;" class="left text-right">{{ __('invoice.Item total price short') }}</div>
|
<div style="width: 20mm;" class="left text-right">{{ __('invoice.Item total price short') }}</div>
|
||||||
<br class="clear"/>
|
<br class="clear"/>
|
||||||
@@ -47,30 +119,48 @@
|
|||||||
<div class="item">
|
<div class="item">
|
||||||
<div style="width: 15mm;" class="left">{{ $key + 1 }}</div>
|
<div style="width: 15mm;" class="left">{{ $key + 1 }}</div>
|
||||||
<div style="width: 15mm;" class="left">{{ \Illuminate\Support\Number::format($item->amount) }}</div>
|
<div style="width: 15mm;" class="left">{{ \Illuminate\Support\Number::format($item->amount) }}</div>
|
||||||
<div style="width: 85mm;" class="left">{{ $item->name }}</div>
|
<div style="width: 65mm;" class="left">{{ $item->name }}</div>
|
||||||
<div style="width: 20mm;" class="left text-right">{{ \Illuminate\Support\Number::currency($item->price) }}</div>
|
<div style="width: 20mm;"
|
||||||
<div style="width: 20mm;" class="left text-right">{{ \Illuminate\Support\Number::percentage($item->tax) }}<br/></div>
|
class="left text-right">{{ \Illuminate\Support\Number::currency($item->price) }}</div>
|
||||||
<div style="width: 20mm;" class="left text-right">{{ \Illuminate\Support\Number::currency($item->total) }}</div>
|
<div style="width: 20mm;"
|
||||||
|
class="left text-right">{{ \Illuminate\Support\Number::percentage($item->discount) }}<br/></div>
|
||||||
|
<div style="width: 20mm;"
|
||||||
|
class="left text-right">{{ \Illuminate\Support\Number::percentage($item->tax) }}<br/></div>
|
||||||
|
<div style="width: 20mm;"
|
||||||
|
class="left text-right">{{ \Illuminate\Support\Number::currency($item->total) }}</div>
|
||||||
<br class="clear"/>
|
<br class="clear"/>
|
||||||
<div style="width: 30mm;" class="left"></div>
|
<div style="width: 30mm;" class="left"></div>
|
||||||
<div style="width: 105mm;" class="left">{{ $item->description }}</div>
|
<div style="width: 85mm;"
|
||||||
<div style="width: 20mm;" class="left text-right">{{ \Illuminate\Support\Number::currency($item->amount * $item->price * $item->tax / 100) }}</div>
|
class="left">{!! ($item->article_number) ? $item->article_number . '<br/>' : ''; !!}{!! nl2br($item->description) !!}</div>
|
||||||
|
<div style="width: 20mm;"
|
||||||
|
class="left text-right">{{ \Illuminate\Support\Number::currency($item->amount * $item->price * $item->discount / 100) }}</div>
|
||||||
|
<div style="width: 20mm;"
|
||||||
|
class="left text-right">{{ \Illuminate\Support\Number::currency($item->amount * $item->price * ($item->tax / 100) * (1 - $item->discount / 100)) }}</div>
|
||||||
<br class="clear"/>
|
<br class="clear"/>
|
||||||
</div>
|
</div>
|
||||||
@endforeach
|
@endforeach
|
||||||
|
|
||||||
<div class="left" style="padding-left: 30mm; width: 105mm;">{{ __('invoice.Net long') }}</div>
|
<div class="left" style="padding-left: 30mm; width: 105mm;">{{ __('invoice.Net long') }}</div>
|
||||||
<div class="left text-right" style="width: 40mm;">{{ \Illuminate\Support\Number::currency($invoice->sum - $invoice->tax) }}</div>
|
<div class="left text-right"
|
||||||
|
style="width: 40mm;">{{ \Illuminate\Support\Number::currency($invoice->sum - $invoice->tax) }}</div>
|
||||||
<br class="clear"/>
|
<br class="clear"/>
|
||||||
<div class="left" style="padding-left: 30mm; width: 105mm;">+ {{ __('invoice.Vat short') }}</div>
|
<div class="left" style="padding-left: 30mm; width: 105mm;">+ {{ __('invoice.Vat short') }}</div>
|
||||||
<div class="left text-right" style="width: 40mm;">{{ \Illuminate\Support\Number::currency($invoice->tax) }}</div>
|
<div class="left text-right"
|
||||||
|
style="width: 40mm;">{{ \Illuminate\Support\Number::currency($invoice->tax) }}</div>
|
||||||
<br class="clear"/>
|
<br class="clear"/>
|
||||||
<div class="left" style="width: 30mm;"></div>
|
<div class="left" style="width: 30mm;"></div>
|
||||||
<div class="left" style="font-weight: bold; width: 105mm; border-bottom: 1px solid black;">{{ __('invoice.Gross long') }}</div>
|
<div class="left"
|
||||||
<div class="left text-right" style="font-weight: bold; width: 40mm; border-bottom: 1px solid black;">{{ \Illuminate\Support\Number::currency($invoice->sum) }}</div>
|
style="font-weight: bold; width: 105mm; border-bottom: 1px solid black;">{{ __('invoice.Gross long') }}</div>
|
||||||
|
<div class="left text-right"
|
||||||
|
style="font-weight: bold; width: 40mm; border-bottom: 1px solid black;">{{ \Illuminate\Support\Number::currency($invoice->sum) }}</div>
|
||||||
<br class="clear"/>
|
<br class="clear"/>
|
||||||
|
|
||||||
<div style="margin-top: 5mm;">{{ __('invoice.Final sentence', ['sum' => \Illuminate\Support\Number::currency($invoice->sum), 'date' => \Illuminate\Support\Carbon::parse($invoice->created_at)->addDays(14)->format('d.m.Y')]) }}</div>
|
<div
|
||||||
|
style="margin-top: 5mm;">{{ __('invoice.Final sentence', ['sum' => \Illuminate\Support\Number::currency($invoice->sum), 'date' => \Illuminate\Support\Carbon::parse($invoice->due_date)->format('d.m.Y')]) }}</div>
|
||||||
|
@if($invoice->cash_discount != 0)
|
||||||
|
<div
|
||||||
|
style="margin-top: 5mm;">{{ __('invoice.Discount sentence', ['sum' => \Illuminate\Support\Number::currency($invoice->sum * (100 - $invoice->cash_discount) / 100), 'date' => \Illuminate\Support\Carbon::parse($invoice->cash_discount_date)->format('d.m.Y'), 'discount' => \Illuminate\Support\Number::percentage($invoice->cash_discount)]) }}</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|||||||
@@ -2,3 +2,7 @@ resources/views/xml/invoice.blade.php
|
|||||||
BuyerReference = Leitweg-ID (BT-10)
|
BuyerReference = Leitweg-ID (BT-10)
|
||||||
PaymentMeansCode = Bezahlarten
|
PaymentMeansCode = Bezahlarten
|
||||||
InvoicedQuantity unitCode="C62"
|
InvoicedQuantity unitCode="C62"
|
||||||
|
|
||||||
|
<cac:Country>
|
||||||
|
<cbc:IdentificationCode>DE</cbc:IdentificationCode>
|
||||||
|
</cac:Country>
|
||||||
|
|||||||
@@ -8,10 +8,15 @@
|
|||||||
<cbc:ProfileID>urn:fdc:peppol.eu:2017:poacc:billing:01:1.0</cbc:ProfileID>
|
<cbc:ProfileID>urn:fdc:peppol.eu:2017:poacc:billing:01:1.0</cbc:ProfileID>
|
||||||
<cbc:ID>{{ $invoice->number }}</cbc:ID>
|
<cbc:ID>{{ $invoice->number }}</cbc:ID>
|
||||||
<cbc:IssueDate>{{ \Carbon\Carbon::parse($invoice->created_at)->format('Y-m-d') }}</cbc:IssueDate>
|
<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:DueDate>{{ \Carbon\Carbon::parse($invoice->due_date)->format('Y-m-d') }}</cbc:DueDate>
|
||||||
<cbc:InvoiceTypeCode>{{ $invoice->type }}</cbc:InvoiceTypeCode>
|
<cbc:InvoiceTypeCode>{{ $invoice->type }}</cbc:InvoiceTypeCode>
|
||||||
<cbc:DocumentCurrencyCode>EUR</cbc:DocumentCurrencyCode>
|
<cbc:DocumentCurrencyCode>{{ $invoice->currency_code }}</cbc:DocumentCurrencyCode>
|
||||||
<cbc:BuyerReference>0</cbc:BuyerReference>
|
<cbc:BuyerReference>0</cbc:BuyerReference>
|
||||||
|
@if(!is_null($invoice->project_id))
|
||||||
|
<cac:ProjectReference>
|
||||||
|
<cbc:ID>{{ $invoice->project->project_number }}</cbc:ID>
|
||||||
|
</cac:ProjectReference>
|
||||||
|
@endif
|
||||||
<cac:AccountingSupplierParty>
|
<cac:AccountingSupplierParty>
|
||||||
<cac:Party>
|
<cac:Party>
|
||||||
<cbc:EndpointID schemeID="EM">{{ $options->email }}</cbc:EndpointID>
|
<cbc:EndpointID schemeID="EM">{{ $options->email }}</cbc:EndpointID>
|
||||||
@@ -82,15 +87,17 @@
|
|||||||
</cac:FinancialInstitutionBranch>
|
</cac:FinancialInstitutionBranch>
|
||||||
</cac:PayeeFinancialAccount>
|
</cac:PayeeFinancialAccount>
|
||||||
</cac:PaymentMeans>
|
</cac:PaymentMeans>
|
||||||
|
@if($invoice->cash_discount != 0)
|
||||||
<cac:PaymentTerms>
|
<cac:PaymentTerms>
|
||||||
<cbc:Note>Zahlbar bis spätestens {{ \Carbon\Carbon::parse($invoice->created_at)->addDays(14)->format('d.m.Y') }}.</cbc:Note>
|
<cbc:Note>{{ __('invoice.Discount sentence', ['sum' => \Illuminate\Support\Number::currency($invoice->sum * (100 - $invoice->cash_discount) / 100), 'date' => \Illuminate\Support\Carbon::parse($invoice->cash_discount_date)->format('d.m.Y'), 'discount' => \Illuminate\Support\Number::percentage($invoice->cash_discount)]) }}</cbc:Note>
|
||||||
</cac:PaymentTerms>
|
</cac:PaymentTerms>
|
||||||
|
@endif
|
||||||
<cac:TaxTotal>
|
<cac:TaxTotal>
|
||||||
<cbc:TaxAmount currencyID="EUR">{{ $invoice->tax }}</cbc:TaxAmount>
|
<cbc:TaxAmount currencyID="{{ $invoice->currency_code }}">{{ $invoice->tax }}</cbc:TaxAmount>
|
||||||
@foreach($taxes as $tax_rate => $tax)
|
@foreach($taxes as $tax_rate => $tax)
|
||||||
<cac:TaxSubtotal>
|
<cac:TaxSubtotal>
|
||||||
<cbc:TaxableAmount currencyID="EUR">{{ $tax['taxable'] }}</cbc:TaxableAmount>
|
<cbc:TaxableAmount currencyID="{{ $invoice->currency_code }}">{{ $tax['taxable'] }}</cbc:TaxableAmount>
|
||||||
<cbc:TaxAmount currencyID="EUR">{{ $tax['tax'] }}</cbc:TaxAmount>
|
<cbc:TaxAmount currencyID="{{ $invoice->currency_code }}">{{ $tax['tax'] }}</cbc:TaxAmount>
|
||||||
<cac:TaxCategory>
|
<cac:TaxCategory>
|
||||||
<cbc:ID>S</cbc:ID>
|
<cbc:ID>S</cbc:ID>
|
||||||
<cbc:Percent>{{ $tax_rate }}</cbc:Percent>
|
<cbc:Percent>{{ $tax_rate }}</cbc:Percent>
|
||||||
@@ -102,21 +109,35 @@
|
|||||||
@endforeach
|
@endforeach
|
||||||
</cac:TaxTotal>
|
</cac:TaxTotal>
|
||||||
<cac:LegalMonetaryTotal>
|
<cac:LegalMonetaryTotal>
|
||||||
<cbc:LineExtensionAmount currencyID="EUR">{{ $invoice->sum - $invoice->tax }}</cbc:LineExtensionAmount>
|
<cbc:LineExtensionAmount currencyID="{{ $invoice->currency_code }}">{{ $invoice->sum - $invoice->tax }}</cbc:LineExtensionAmount>
|
||||||
<cbc:TaxExclusiveAmount currencyID="EUR">{{ $invoice->sum - $invoice->tax }}</cbc:TaxExclusiveAmount>
|
<cbc:TaxExclusiveAmount currencyID="{{ $invoice->currency_code }}">{{ $invoice->sum - $invoice->tax }}</cbc:TaxExclusiveAmount>
|
||||||
<cbc:TaxInclusiveAmount currencyID="EUR">{{ $invoice->sum }}</cbc:TaxInclusiveAmount>
|
<cbc:TaxInclusiveAmount currencyID="{{ $invoice->currency_code }}">{{ $invoice->sum }}</cbc:TaxInclusiveAmount>
|
||||||
<cbc:AllowanceTotalAmount currencyID="EUR">0.00</cbc:AllowanceTotalAmount>
|
<cbc:AllowanceTotalAmount currencyID="{{ $invoice->currency_code }}">0.00</cbc:AllowanceTotalAmount>
|
||||||
<cbc:PrepaidAmount currencyID="EUR">0</cbc:PrepaidAmount>
|
<cbc:PrepaidAmount currencyID="{{ $invoice->currency_code }}">0</cbc:PrepaidAmount>
|
||||||
<cbc:PayableAmount currencyID="EUR">{{ $invoice->sum }}</cbc:PayableAmount>
|
<cbc:PayableAmount currencyID="{{ $invoice->currency_code }}">{{ $invoice->sum }}</cbc:PayableAmount>
|
||||||
</cac:LegalMonetaryTotal>
|
</cac:LegalMonetaryTotal>
|
||||||
@foreach($invoice->items as $item)
|
@foreach($invoice->items as $item)
|
||||||
<cac:InvoiceLine>
|
<cac:InvoiceLine>+
|
||||||
<cbc:ID>1</cbc:ID>
|
<cbc:ID>1</cbc:ID>
|
||||||
<cbc:InvoicedQuantity unitCode="C62">{{ $item->amount }}</cbc:InvoicedQuantity>
|
<cbc:InvoicedQuantity unitCode="C62">{{ $item->amount }}</cbc:InvoicedQuantity>
|
||||||
<cbc:LineExtensionAmount currencyID="EUR">{{ $item->amount * $item->price }}</cbc:LineExtensionAmount>
|
<cbc:LineExtensionAmount currencyID="{{ $invoice->currency_code }}">{{ $item->amount * $item->price * (100 - $item->discount) / 100 }}</cbc:LineExtensionAmount>
|
||||||
|
@if($item->discount != 0)
|
||||||
|
<cac:AllowanceCharge>
|
||||||
|
<cbc:ChargeIndicator>false</cbc:ChargeIndicator>
|
||||||
|
<cbc:AllowanceChargeReason>Rabatt</cbc:AllowanceChargeReason>
|
||||||
|
<cbc:MultiplierFactorNumeric>{{ $item->discount }}</cbc:MultiplierFactorNumeric>
|
||||||
|
<cbc:Amount currencyID="{{ $invoice->currency_code }}">{{ $item->amount * $item->price * $item->discount / 100 }}</cbc:Amount>
|
||||||
|
<cbc:BaseAmount currencyID="{{ $invoice->currency_code }}">{{ $item->amount * $item->price }}</cbc:BaseAmount>
|
||||||
|
</cac:AllowanceCharge>
|
||||||
|
@endif
|
||||||
<cac:Item>
|
<cac:Item>
|
||||||
<cbc:Description>{{ $item->description }}</cbc:Description>
|
<cbc:Description>{{ $item->description }}</cbc:Description>
|
||||||
<cbc:Name>{{ $item->name }}</cbc:Name>
|
<cbc:Name>{{ $item->name }}</cbc:Name>
|
||||||
|
@if(!is_null($invoice->article_number))
|
||||||
|
<cac:SellersItemIdentification>
|
||||||
|
<cbc:ID>{{ $item->article_number }}</cbc:ID>
|
||||||
|
</cac:SellersItemIdentification>
|
||||||
|
@endif
|
||||||
<cac:ClassifiedTaxCategory>
|
<cac:ClassifiedTaxCategory>
|
||||||
<cbc:ID>S</cbc:ID>
|
<cbc:ID>S</cbc:ID>
|
||||||
<cbc:Percent>{{ $item->tax }}</cbc:Percent>
|
<cbc:Percent>{{ $item->tax }}</cbc:Percent>
|
||||||
@@ -126,7 +147,7 @@
|
|||||||
</cac:ClassifiedTaxCategory>
|
</cac:ClassifiedTaxCategory>
|
||||||
</cac:Item>
|
</cac:Item>
|
||||||
<cac:Price>
|
<cac:Price>
|
||||||
<cbc:PriceAmount currencyID="EUR">{{ $item->price }}</cbc:PriceAmount>
|
<cbc:PriceAmount currencyID="{{ $invoice->currency_code }}">{{ $item->price }}</cbc:PriceAmount>
|
||||||
</cac:Price>
|
</cac:Price>
|
||||||
</cac:InvoiceLine>
|
</cac:InvoiceLine>
|
||||||
@endforeach
|
@endforeach
|
||||||
|
|||||||
Reference in New Issue
Block a user