Build everything to deal with tax rates.

This commit is contained in:
2024-12-11 11:52:14 +01:00
parent 64e11864d2
commit 13c23a076a
11 changed files with 468 additions and 13 deletions

View File

@@ -0,0 +1,61 @@
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\Taxrate;
use Illuminate\Http\Request;
class TaxrateController extends Controller
{
/**
* Display a listing of the resource.
*/
public function index()
{
return response()->json(Taxrate::all());
}
/**
* Store a newly created resource in storage.
*/
public function store(Request $request)
{
$taxData = $request->validate([
'name' => 'required|string',
'rate' => 'required|numeric',
'active' => 'required|boolean'
]);
$tax = new Taxrate($taxData);
$tax->save();
return response()->json($tax);
}
/**
* Update the specified resource in storage.
*/
public function update(Request $request, Taxrate $taxrate)
{
$taxData = $request->validate([
'name' => 'required|string',
'rate' => 'required|numeric',
'active' => 'required|boolean'
]);
$taxrate->update($taxData);
return response()->json($taxrate);
}
/**
* Remove the specified resource from storage.
*/
public function destroy(Taxrate $taxrate)
{
$taxrate->delete();
return response()->json();
}
}

View File

@@ -0,0 +1,32 @@
<?php
namespace App\Http\Controllers;
use App\Models\Taxrate;
class TaxrateController extends Controller
{
/**
* Display a listing of the resource.
*/
public function index()
{
return view('taxrate.index');
}
/**
* Show the form for creating a new resource.
*/
public function create()
{
return view('taxrate.create');
}
/**
* Show the form for editing the specified resource.
*/
public function edit(Taxrate $taxrate)
{
return view('taxrate.edit', ['taxrate' => $taxrate]);
}
}

View File

@@ -4,8 +4,28 @@ namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Number;
class Taxrate extends Model
{
use SoftDeletes;
protected $fillable = [
'name',
'rate',
'active',
];
/**
* The attributes that are appended with attribute getters.
*
* @var string[]
*/
protected $appends = [
'rate_percentage',
];
public function getRatePercentageAttribute()
{
return Number::percentage($this->rate, 2, 8, 'de');
}
}

26
lang/de/configuration.php Normal file
View File

@@ -0,0 +1,26 @@
<?php
return [
/*
|------------------------------------------------- -------------------------
| Übersetzungen der Einstellungen
|------------------------------------------------- -------------------------
|
| Die folgenden Sprachzeilen werden für die Einstellungen verwendet.
|
*/
'Taxrate' => 'Steuersatz',
'Taxrates' => 'Steuersätze',
'Add new taxrate' => 'Neuen Steuersatz anlegen',
'Add new taxrate by clicking add' => 'Durch Klick auf "Anlegen" neuen Steuersatz erstellen',
'Create new taxrate' => 'Neuen Steuersatz erstellen',
'Existing taxrates' => 'Bestehende Steuersätze',
'Edit taxrate' => 'Steuersatz bearbeiten',
'New taxrate' => 'Neuer Steuersatz',
'Delete Taxrate' => 'Steuersatz löschen',
'Once the taxrate is deleted, all of its resources and data will be permanently deleted.' => 'Sobald der Steuersatz gelöscht wird, werden alle Ressourcen und Daten dauerhaft gelöscht.',
'Enter your taxrate\'s information.' => 'Gib die Informationen des Steuersatzes ein.',
];

View File

@@ -6,7 +6,7 @@
<!-- Logo -->
<div class="shrink-0 flex items-center">
<a href="{{ route('dashboard') }}">
<x-application-logo class="block h-9 w-auto fill-current text-gray-800 dark:text-gray-200" />
<x-application-logo class="block h-9 w-auto fill-current text-gray-800 dark:text-gray-200"/>
</a>
</div>
@@ -14,12 +14,15 @@
<div class="hidden space-x-8 sm:-my-px sm:ms-10 sm:flex">
<x-nav-link :href="route('dashboard')" :active="request()->routeIs('dashboard')">
{{ __('common.Dashboard') }}
</x-nav-link>
</div>
<div class="hidden space-x-8 sm:-my-px sm:ms-10 sm:flex">
<x-nav-link :href="route('customer.index')" :active="\Illuminate\Support\Str::startsWith(request()->route()->getName(), 'customer.')">
</x-nav-link>
<x-nav-link :href="route('customer.index')"
:active="\Illuminate\Support\Str::startsWith(request()->route()->getName(), 'customer.')">
{{ __('customer.Customers') }}
</x-nav-link>
<x-nav-link :href="route('taxrate.index')"
:active="\Illuminate\Support\Str::startsWith(request()->route()->getName(), 'taxrate.')">
{{ __('configuration.Taxrates') }}
</x-nav-link>
</div>
</div>
@@ -28,12 +31,16 @@
<div class="hidden sm:flex sm:items-center sm:ms-6">
<x-dropdown align="right" width="48">
<x-slot name="trigger">
<button class="inline-flex items-center px-3 py-2 border border-transparent text-sm leading-4 font-medium rounded-md text-gray-500 dark:text-gray-400 bg-white dark:bg-gray-800 hover:text-gray-700 dark:hover:text-gray-300 focus:outline-none transition ease-in-out duration-150">
<button
class="inline-flex items-center px-3 py-2 border border-transparent text-sm leading-4 font-medium rounded-md text-gray-500 dark:text-gray-400 bg-white dark:bg-gray-800 hover:text-gray-700 dark:hover:text-gray-300 focus:outline-none transition ease-in-out duration-150">
<div>{{ Auth::user()->name }}</div>
<div class="ms-1">
<svg class="fill-current h-4 w-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clip-rule="evenodd" />
<svg class="fill-current h-4 w-4" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20">
<path fill-rule="evenodd"
d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z"
clip-rule="evenodd"/>
</svg>
</div>
</button>
@@ -49,7 +56,7 @@
@csrf
<x-dropdown-link :href="route('logout')"
onclick="event.preventDefault();
onclick="event.preventDefault();
this.closest('form').submit();">
{{ __('user.Log Out') }}
</x-dropdown-link>
@@ -60,10 +67,14 @@
<!-- Hamburger -->
<div class="-me-2 flex items-center sm:hidden">
<button @click="open = ! open" class="inline-flex items-center justify-center p-2 rounded-md text-gray-400 dark:text-gray-500 hover:text-gray-500 dark:hover:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-900 focus:outline-none focus:bg-gray-100 dark:focus:bg-gray-900 focus:text-gray-500 dark:focus:text-gray-400 transition duration-150 ease-in-out">
<button @click="open = ! open"
class="inline-flex items-center justify-center p-2 rounded-md text-gray-400 dark:text-gray-500 hover:text-gray-500 dark:hover:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-900 focus:outline-none focus:bg-gray-100 dark:focus:bg-gray-900 focus:text-gray-500 dark:focus:text-gray-400 transition duration-150 ease-in-out">
<svg class="h-6 w-6" stroke="currentColor" fill="none" viewBox="0 0 24 24">
<path :class="{'hidden': open, 'inline-flex': ! open }" class="inline-flex" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
<path :class="{'hidden': ! open, 'inline-flex': open }" class="hidden" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
<path :class="{'hidden': open, 'inline-flex': ! open }" class="inline-flex"
stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M4 6h16M4 12h16M4 18h16"/>
<path :class="{'hidden': ! open, 'inline-flex': open }" class="hidden" stroke-linecap="round"
stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
</svg>
</button>
</div>
@@ -95,7 +106,7 @@
@csrf
<x-responsive-nav-link :href="route('logout')"
onclick="event.preventDefault();
onclick="event.preventDefault();
this.closest('form').submit();">
{{ __('user.Log Out') }}
</x-responsive-nav-link>

View File

@@ -0,0 +1,86 @@
<x-app-layout>
<x-slot name="header">
<h2 class="font-semibold text-xl text-gray-800 dark:text-gray-200 leading-tight">
{{ __('configuration.Create new taxrate') }}
</h2>
</x-slot>
<div class="py-12" x-data="taxForm()">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8 space-y-6">
<!-- Customer data -->
<div class="p-4 sm:p-8 bg-white dark:bg-gray-800 shadow sm:rounded-lg">
<div class="max-w-xl">
<section>
<header>
<h2 class="text-lg font-medium text-gray-900 dark:text-gray-100">
{{ __('configuration.New taxrate') }}
</h2>
<p class="mt-1 text-sm text-gray-600 dark:text-gray-400">
{{ __("configuration.Enter your taxrate's information.") }}
</p>
</header>
<form class="mt-6 space-y-6" @submit.prevent="">
<p class="text-red-600 font-bold" x-text="message" x-show="error"></p>
<div>
<x-input-label for="name" :value="__('common.Name')"/>
<x-text-input id="name" name="name" type="text" class="mt-1 block w-full"
:value="old('name')" required autofocus autocomplete="name"
x-model="taxrate.name"/>
</div>
<div>
<x-input-label for="email" :value="__('configuration.Taxrate')"/>
<x-text-input id="email" name="email" type="number" class="mt-1 block w-full"
:value="old('email')" required min="0" max="100" step=".01"
x-model="taxrate.rate"/>
</div>
<div>
<x-input-label for="active" :value="__('common.Is active')"/>
<x-text-input id="active" name="active" type="checkbox" class="mt-1"
:value="old('active')" autofocus
x-model="taxrate.active"/>
</div>
<div class="flex items-center gap-4">
<x-primary-button @click="submit">{{ __('form.Save') }}</x-primary-button>
</div>
</form>
</section>
</div>
</div>
</div>
</div>
</x-app-layout>
<script>
function taxForm() {
return {
taxrate: {
name: '',
rate: 0,
is_active: false,
},
error: false,
message: '',
submit() {
let vm = this;
axios.post('/taxrate', this.taxrate)
.then(function (response) {
window.location.href = '/taxrate';
})
.catch(function(error) {
vm.error = true;
vm.message = error.response.data.message;
})
}
}
}
</script>

View File

@@ -0,0 +1,140 @@
<x-app-layout>
<x-slot name="header">
<h2 class="font-semibold text-xl text-gray-800 dark:text-gray-200 leading-tight">
{{ __('configuration.Create new taxrate') }}
</h2>
</x-slot>
<div class="py-12" x-data="taxForm()">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8 space-y-6">
<!-- Tax data -->
<div class="p-4 sm:p-8 bg-white dark:bg-gray-800 shadow sm:rounded-lg">
<div class="max-w-xl">
<section>
<header>
<h2 class="text-lg font-medium text-gray-900 dark:text-gray-100">
{{ __('configuration.Edit taxrate') }}
</h2>
<p class="mt-1 text-sm text-gray-600 dark:text-gray-400">
{{ __("configuration.Enter your taxrate's information.") }}
</p>
</header>
<form class="mt-6 space-y-6" @submit.prevent="">
<p class="text-red-600 font-bold" x-text="message" x-show="error"></p>
<div>
<x-input-label for="name" :value="__('common.Name')"/>
<x-text-input id="name" name="name" type="text" class="mt-1 block w-full"
:value="old('name')" required autofocus autocomplete="name"
x-model="taxrate.name"/>
</div>
<div>
<x-input-label for="rate" :value="__('configuration.Taxrate')"/>
<x-text-input id="rate" name="rate" type="number" class="mt-1 block w-full"
:value="old('rate')" required min="0" max="100" step=".01"
x-model="taxrate.rate"/>
</div>
<div>
<x-input-label for="active" :value="__('common.Is active')"/>
<x-text-input id="active" name="active" type="checkbox" class="mt-1"
:value="old('active')" autofocus
x-model="taxrate.active"/>
</div>
<div class="flex items-center gap-4">
<x-primary-button @click="submit">{{ __('form.Save') }}</x-primary-button>
</div>
</form>
</section>
</div>
</div>
</div>
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8 space-y-6 mt-8">
<!-- Delete tax -->
<div class="p-4 sm:p-8 bg-white dark:bg-gray-800 shadow sm:rounded-lg">
<section class="space-y-6">
<header>
<h2 class="text-lg font-medium text-gray-900 dark:text-gray-100">
{{ __('configuration.Delete Taxrate') }}
</h2>
<p class="mt-1 text-sm text-gray-600 dark:text-gray-400">
{{ __('configuration.Once the taxrate is deleted, all of its resources and data will be permanently deleted.') }}
</p>
</header>
<x-danger-button
x-data=""
x-on:click.prevent="$dispatch('open-modal', 'confirm-taxrate-deletion')"
>{{ __('configuration.Delete Taxrate') }}</x-danger-button>
<x-modal name="confirm-taxrate-deletion" :show="$errors->userDeletion->isNotEmpty()" focusable>
<form class="p-6" @submit.prevent="">
<h2 class="text-lg font-medium text-gray-900 dark:text-gray-100">
{{ __('configuration.Are you sure you want to delete this taxrate?') }}
</h2>
<p class="mt-1 text-sm text-gray-600 dark:text-gray-400">
{{ __('configuration.Once the taxrate is deleted, all of its resources and data will be permanently deleted.') }}
</p>
<div class="mt-6 flex justify-end">
<x-secondary-button x-on:click="$dispatch('close')">
{{ __('form.Cancel') }}
</x-secondary-button>
<x-danger-button class="ms-3" x-on:click="deleteTax();$dispatch('close');">
{{ __('configuration.Delete taxrate') }}
</x-danger-button>
</div>
</form>
</x-modal>
</section>
</div>
</div>
</div>
</x-app-layout>
<script>
function taxForm() {
return {
taxrate: {!! $taxrate !!},
error: false,
message: '',
submit() {
let vm;
axios.put('/taxrate/' + this.taxrate.id, this.taxrate)
.then(function (response) {
window.location.href = '/taxrate';
})
.catch(function (error) {
vm.error = true;
vm.message = error.response.data.message;
})
},
deleteTax() {
let vm;
axios.delete('/taxrate/' + this.taxrate.id)
.then(function (response) {
window.location.href = '/taxrate';
})
.catch(function (error) {
vm.error = true;
vm.message = error.response.data.message;
})
}
}
}
</script>

View File

@@ -0,0 +1,75 @@
<x-app-layout>
<x-slot name="header">
<h2 class="font-semibold text-xl text-gray-800 dark:text-gray-200 leading-tight">
{{ __('configuration.Taxrates') }}
</h2>
</x-slot>
<div class="py-12">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8 space-y-6">
<div class="p-4 sm:p-8 bg-white dark:bg-gray-800 shadow sm:rounded-lg">
<div class="max-w-xl">
<section>
<header>
<h2 class="text-lg font-medium text-gray-900 dark:text-gray-100">
{{ __('configuration.Add new taxrate') }}
</h2>
<p class="mt-1 text-sm text-gray-600 dark:text-gray-400">
{{ __("configuration.Add new taxrate by clicking add") }}
</p>
</header>
<a class="mt-6 inline-block" href="{{ route('taxrate.create') }}"><x-primary-button>{{ __('form.Add') }}</x-primary-button></a>
</section>
</div>
</div>
<div class="p-4 sm:p-8 bg-white dark:bg-gray-800 shadow sm:rounded-lg">
<div class="max-w" x-data="tax">
<section>
<header>
<h2 class="text-lg font-medium text-gray-900 dark:text-gray-100">
{{ __('configuration.Existing taxrates') }}
</h2>
</header>
<div class="grid grid-cols-3 mt-4">
<div class="font-bold border-b-2">{{ __('common.Name') }}</div>
<div class="font-bold border-b-2">{{ __('configuration.Taxrate') }}</div>
<div class="font-bold border-b-2">{{ __('common.Is active') }}</div>
</div>
<template x-for="tax in taxrates">
<a class="cursor-pointer grid grid-cols-3 even:bg-gray-100 odd:bg-white" x-data="{url: '/taxrate/' + tax.id + '/edit'}" x-bind:href="url">
<div x-text="tax.name"></div>
<div x-text="tax.rate_percentage"></div>
<div x-text="tax.active ? '{{ __('common.Yes') }}' : '{{ __('common.No') }}'"></div>
</a>
</template>
</section>
</div>
</div>
</div>
</div>
</x-app-layout>
<script>
function tax() {
return {
taxrates: {},
init() {
let vm = this;
axios.get('/taxrate')
.then(function (response) {
console.log(response.data);
vm.taxrates = response.data;
})
}
}
}
</script>

View File

@@ -3,6 +3,7 @@
use App\Http\Controllers\Api\AddressController;
use App\Http\Controllers\Api\AuthController;
use App\Http\Controllers\Api\CustomerController;
use App\Http\Controllers\Api\TaxrateController;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
@@ -20,6 +21,7 @@ Route::group(['as' => 'api.'], function () {
Route::apiResource('/customer', CustomerController::class);
Route::apiResource('/customer.address', AddressController::class)->shallow()->except(['update']);
Route::apiResource('/taxrate', TaxRateController::class)->except(['show']);
});
});

View File

@@ -2,6 +2,7 @@
use App\Http\Controllers\CustomerController;
use App\Http\Controllers\ProfileController;
use App\Http\Controllers\TaxrateController;
use Illuminate\Support\Facades\Route;
Route::get('/', function () {
@@ -18,6 +19,7 @@ Route::middleware('auth')->group(function () {
Route::delete('/profile', [ProfileController::class, 'destroy'])->name('profile.destroy');
Route::resource('/customer', CustomerController::class)->only(['index', 'create', 'edit']);
Route::resource('/taxrate', TaxrateController::class)->only(['index', 'create', 'edit']);
});
require __DIR__.'/auth.php';