Warenwirtschaft: AP-09 bis AP-13 (Produktbestand, Set-Produkte, Ausschuss, Konzepte)

- AP-09 Produktbestand inkl. Bewegungshistorie (product_stock_movements, ProductStockService)
- AP-10 Rohstoffbestand-Ansicht je Lager (RawMaterialStockController)
- AP-11 Bestandsschwellen / Out-of-Stock-Handling fuer Produkte und Shop
- AP-12 Ausgang/Ausschuss (stock_disposals, StockDisposalController, InventoryService)
- Set-Produkte (product_set_items) inkl. Aufloesung
- Produktentwicklung & Hinweise-Verwaltung (Notices)
- AP-13 Entwicklungskonzept Shop-Bestandsabzug im Plan dokumentiert
- Feature-Tests fuer neue Module + aktualisierter Entwicklungsplan

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Kevin Adametz 2026-06-03 11:04:22 +00:00
parent 78679e0c55
commit 3ee2d756e9
63 changed files with 5968 additions and 901 deletions

View file

@ -1,72 +1,90 @@
@extends('layouts.layout-2')
@section('content')
<style>
.btn-md-extra {
padding: 0.3rem 0.6rem;
font-size: 0.8rem;
line-height: 1.5;
border-radius: 0.25rem;
}
.md-btn-extra {
width: calc(1.7rem + 2px) !important;
line-height: 1.5rem;
}
.form-control.input-extra {
padding: 0.28rem 0.6rem;
font-size: 0.8rem;
font-weight: 500;
min-height: calc(1.8rem + 2px);
height: calc(1.8rem + 2px);
width: 44px;
}
.input-group-min-w {
min-width: 102px;
}
.img-extra {
min-width:55px;
max-height: 160px;
}
@media (max-width: 767px) {
.default-style:not([dir=rtl]) div.card-datatable table.dataTable thead th:first-child,
.default-style:not([dir=rtl]) div.card-datatable table.dataTable tbody td:first-child,
.default-style:not([dir=rtl]) div.card-datatable table.dataTable tfoot th:first-child {
padding-left: 0.6rem !important;
<style>
.btn-md-extra {
padding: 0.3rem 0.6rem;
font-size: 0.8rem;
line-height: 1.5;
border-radius: 0.25rem;
}
.md-btn-extra {
width: calc(1.7rem + 2px) !important;
line-height: 1.5rem;
}
.form-control.input-extra {
padding: 0.28rem 0.6rem;
font-size: 0.8rem;
font-weight: 500;
min-height: calc(1.8rem + 2px);
height: calc(1.8rem + 2px);
width: 44px;
}
.input-group-min-w {
min-width: 102px;
}
.img-extra {
min-width:35px;
min-width: 55px;
max-height: 160px;
}
}
.product-stock-hint {
display: inline-block;
background-color: #c81031;
color: #fff;
font-weight: 700;
padding: 0.5rem 0.85rem;
border-radius: 0.25rem;
white-space: normal;
line-height: 1.3;
}
@media (max-width: 767px) {
.default-style:not([dir=rtl]) div.card-datatable table.dataTable thead th:first-child,
.default-style:not([dir=rtl]) div.card-datatable table.dataTable tbody td:first-child,
.default-style:not([dir=rtl]) div.card-datatable table.dataTable tfoot th:first-child {
padding-left: 0.6rem !important;
}
.img-extra {
min-width: 35px;
max-height: 160px;
}
}
</style>
</style>
@if($for === 'cr')
<h4 class="font-weight-bold py-2 mb-2">
{{ __('navigation.my_orders') }} / Mein Guthaben aufladen
<a href="{{ route('user_order_my_delivery', [$for, $delivery_id]) }}" class="btn btn-sm btn-default float-right">zurück</a>
<div class="clearfix"></div>
</h4>
@if ($for === 'cr')
<h4 class="font-weight-bold py-2 mb-2">
{{ __('navigation.my_orders') }} / Mein Guthaben aufladen
<a href="{{ route('user_order_my_delivery', [$for, $delivery_id]) }}"
class="btn btn-sm btn-default float-right">zurück</a>
<div class="clearfix"></div>
</h4>
@else
<h4 class="font-weight-bold py-2 mb-2">
{{ __('navigation.my_orders') }} / {{ __('navigation.do_order') }}
<a href="{{ route('user_order_my_delivery', [$for, $delivery_id]) }}" class="btn btn-sm btn-default float-right">zurück</a>
<div class="clearfix"></div>
</h4>
@if($user->user_level)
<p>Die Produktpreise werden entsprechend Deiner Rolle: <strong>{{$user->user_level->name}}</strong> angezeigt.<br>
<h4 class="font-weight-bold py-2 mb-2">
{{ __('navigation.my_orders') }} / {{ __('navigation.do_order') }}
<a href="{{ route('user_order_my_delivery', [$for, $delivery_id]) }}"
class="btn btn-sm btn-default float-right">zurück</a>
<div class="clearfix"></div>
</h4>
@if ($user->user_level)
<p>Die Produktpreise werden entsprechend Deiner Rolle: <strong>{{ $user->user_level->name }}</strong>
angezeigt.<br>
Hinweis: Wenn Du den Warenkorb verlässt, gehen alle Einstellungen verloren.</p>
@else
<p>Hinweis: Dir wurde noch keine Rolle zugewisen. Bitte wende dich an serivce@gruene-seele.bio</p>
@endif
@endif
@if($errors->has('switchers-comp-product'))
@if ($errors->has('switchers-comp-product'))
<div class="row">
<div class="col-sm-12">
<div class="alert alert-danger" id="gotocomp">
@ -86,46 +104,50 @@
<div class="row">
<div class="col-md-3 mb-3">
<div class="text-muted small">{{ __('payment.ordering_country') }}</div>
{{ App\Services\UserService::getOrderInfo('billing_state') }}
{{ App\Services\UserService::getOrderInfo('billing_state') }}
</div>
<div class="col-md-3 mb-3">
<div class="text-muted small">{{ __('payment.country_of_delivery') }}</div>
{{ App\Services\UserService::getOrderInfo('shipping_state') }}
{{ App\Services\UserService::getOrderInfo('shipping_state') }}
</div>
<div class="col-md-3 mb-3">
<div class="text-muted small">{{ __('payment.VAT') }}</div>
{{ App\Services\UserService::getOrderInfo('tax_free') }}
{{ App\Services\UserService::getOrderInfo('tax_free') }}
</div>
<div class="col-md-3 mb-3">
<div class="text-muted small">{{ __('payment.reverse_charge_procedure') }}</div>
{{ App\Services\UserService::getOrderInfo('user_reverse_charge') }}
{{ App\Services\UserService::getOrderInfo('user_reverse_charge') }}
</div>
</div>
<i>{!! __('order.delivery_country_changed_info', ['link'=> route('user_edit')]) !!}</i>
<hr>
@if($user->user_level)
<p>{!! __('order.product_prices_career_level_info', ['user_level_name'=>$user->user_level->getLang('name'), 'user_level_margin'=>$user->user_level->getFormattedMargin()]) !!}</p>
@else
<p>{{ __('order.no_career_level_info') }}</p>
@endif
<i>{!! __('order.delivery_country_changed_info', ['link' => route('user_edit')]) !!}</i>
<hr>
@if ($user->user_level)
<p>{!! __('order.product_prices_career_level_info', [
'user_level_name' => $user->user_level->getLang('name'),
'user_level_margin' => $user->user_level->getFormattedMargin(),
]) !!}</p>
@else
<p>{{ __('order.no_career_level_info') }}</p>
@endif
</div>
</div>
<div class="card">
<div class="card-datatable table-responsive">
<table class="datatables-order-list table table-striped table-bordered" id="datatables-order-list" data-url="{{route('user_order_my_perform_request')}}">
<table class="datatables-order-list table table-striped table-bordered" id="datatables-order-list"
data-url="{{ route('user_order_my_perform_request') }}">
<thead>
<tr>
<th>{{__('Bild')}}</th>
<th>{{__('Produkt')}}</th>
<th>{{__('Kategorie')}}</th>
<th>{{__('Preis netto')}}</th>
<th>{{__('Preis brutto')}}</th>
<th>{{__('Provision')}}</th>
<th>{{__('Gewicht')}}</th>
<th><span class="no-line-break">{{__('Inhalt (ml)')}}</span></th>
<th>{{__('Artikelnummer')}}</th>
</tr>
<tr>
<th>{{ __('Bild') }}</th>
<th>{{ __('Produkt') }}</th>
<th>{{ __('Kategorie') }}</th>
<th>{{ __('Preis netto') }}</th>
<th>{{ __('Preis brutto') }}</th>
<th>{{ __('Provision') }}</th>
<th>{{ __('Gewicht') }}</th>
<th><span class="no-line-break">{{ __('Inhalt (ml)') }}</span></th>
<th>{{ __('Artikelnummer') }}</th>
</tr>
</thead>
<tbody>
</tbody>
@ -134,30 +156,29 @@
</div>
{!! Form::open(['action' => route('user_order_my_payment', [$for, $delivery_id]), 'class' => 'form-horizontal']) !!}
<input type="hidden" name="shipping_is_for" value="{{$for}}">
<input type="hidden" name="shipping_is_for" value="{{ $for }}">
@if($for === 'cr')
@if ($for === 'cr')
@include('user.order.shipping_credit')
@else
<div class="card mt-4">
<div class="card-body">
@if($for === 'ot')
<h4>Lieferland des Kunden</h4>
@include('user.order.shipping_ot')
@endif
@if($for === 'me' || $for === 'mp')
<h4>Mein Lieferland</h4>
@include('user.order.shipping_me')
@endif
@if ($for === 'ot')
<h4>Lieferland des Kunden</h4>
@include('user.order.shipping_ot')
@endif
@if ($for === 'me' || $for === 'mp')
<h4>Mein Lieferland</h4>
@include('user.order.shipping_me')
@endif
</div>
</div>
@endif
@if($comp_products)
@if ($comp_products)
<div id="holder_html_view_comp_product">
@include('user.order.comp_product')
</div>
@endif
<div class="card mt-4">
@ -169,13 +190,14 @@
</div>
</div>
<div class="mt-2">
<a href="{{ route('user_order_my_delivery', [$for, $delivery_id]) }}" class="btn btn-sm btn-default float-right">zurück</a>
<a href="{{ route('user_order_my_delivery', [$for, $delivery_id]) }}"
class="btn btn-sm btn-default float-right">zurück</a>
</div>
{!! Form::close() !!}
<script>
$( document ).ready(function() {
$(document).ready(function() {
var iqShoppingCart = IqShoppingCart.init();
@ -183,23 +205,61 @@
"processing": true,
"serverSide": true,
ajax: {
url: '{!! route( 'user_order_my_datatable') !!}',
url: '{!! route('user_order_my_datatable') !!}',
data: function(d) {
d.shipping_is_for = $('input[name=shipping_is_for]').val();
// d.filter_customer_member = $('select[name=filter_customer_member]').val();
// d.filter_customer_member = $('select[name=filter_customer_member]').val();
}
},
"order": [[8, "asc" ]],
"columns": [
{ data: 'picture', name: 'picture', searchable: false, width: 35 },
{ data: 'product', name: 'product' },
{ data: 'category', name: 'category', orderable: true },
{ data: 'price_net', name: 'price_net', searchable: false, orderable: false },
{ data: 'price_gross', name: 'price_gross', searchable: false, orderable: false },
{ data: 'single_commission', name: 'single_commission', searchable: false },
{ data: 'weight', name: 'weight', searchable: false },
{ data: 'contents_total', name: 'contents_total', searchable: false },
{ data: 'number', name: 'number' },
"order": [
[8, "asc"]
],
"columns": [{
data: 'picture',
name: 'picture',
searchable: false,
width: 35
},
{
data: 'product',
name: 'product'
},
{
data: 'category',
name: 'category',
orderable: true
},
{
data: 'price_net',
name: 'price_net',
searchable: false,
orderable: false
},
{
data: 'price_gross',
name: 'price_gross',
searchable: false,
orderable: false
},
{
data: 'single_commission',
name: 'single_commission',
searchable: false
},
{
data: 'weight',
name: 'weight',
searchable: false
},
{
data: 'contents_total',
name: 'contents_total',
searchable: false
},
{
data: 'number',
name: 'number'
},
],
"bLengthChange": false,
"iDisplayLength": 1000,
@ -207,7 +267,7 @@
"language": {
"url": "/js/German.json"
},
drawCallback: function (settings) {
drawCallback: function(settings) {
iqShoppingCart.reInit();
}
});