gruene-seele/app/helpers.php
Kevin Adametz 6bd867cbe7 Warenwirtschaft: AP-53 — Nacharbeit zur Prüfung 10.09.2026 (Toleranzregel zentral, Sockelrand, Chargenzeile, Ausschuss-Meldung, Auflösung/Obergrenze)
Prüfbericht pruefung-ap53-mengenpraezision-2026-09-10.md, alle Punkte umgesetzt:
- AP53-01: Toleranz nur noch in ProductionService (TOLERANCE_RATIO 0,05, TOLERANCE_FLOOR 0,005, COMPARE_EPSILON); das Produktions-JS rendert die Konstanten aus dem Service statt eigener Zahlen (Ampel hatte noch Sockel 0,05 g).
- AP53-02: withinTolerance() vergleicht mit Float-Ausgleich 1e-9 — 0,025 g auf 0,020 g Soll ist wieder zulässig, 0,026 g nicht; Sollwerte werden nicht gerundet.
- AP53-03: Chargen-Hauptzeile und Standortsumme im Rohstoff-Detail mit formatWawiQuantity(…, 0) (0,125 g statt 0 g).
- AP53-04: Ausschuss-Fehlermeldungen (Request ohne/mit Charge, Controller-Wiederholung) mit formatWawiQuantity().
- 4.1: Obergrenze decimal(12,3) bewusst akzeptiert (Live max. 49.000 g) und als InventoryService::QUANTITY_MAX validiert; Bereichsprüfung + Rollback-Hinweis in der Deploy-Checkliste.
- 4.2: normalizeWawiQuantity() (parsen + drei Stellen) in Bestell-/Wareneingangs-/Ausschuss-Requests, parseQuantity() rundet kanonisch für batch_key- und stock_entry_id-Zeilen; Untergrenze 0,001 g; ml-Eingaben, die umgerechnet 0,000 g ergeben, werden abgelehnt (Store/Update/Receive).
- 4.3: Einkaufs-JS trennt formatQuantity (3), formatPrice (2, wie vor AP-53) und formatDensity (4).
- 4.4: Hinweise präzisiert (Übersichten ganzzahlig, Rundungs-/Untergrenzregel), Leitfaden §26 mit Sockelrand-, Hauptzeilen- und Meldungs-Prüfschritten, Plan V7 und README aktualisiert, Prüfbericht Abschnitt 6 mit Nacharbeitsstand.

Tests: QuantityPrecisionTest +6 (AP53-01…04, 4.2 Rundung/Untergrenze/Obergrenze, ml→0 g). Suite 384 grün / 1.592 Assertions.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-10 07:25:11 +00:00

161 lines
4.6 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
use App\Services\InventoryService;
if (! function_exists('make_old_url')) {
function make_old_url($path)
{
return config('app.old_url').$path;
}
}
if (! function_exists('make_old_url')) {
function make_old_url($path)
{
return config('app.old_url').$path;
}
}
if (! function_exists('get_file_last_time')) {
function get_file_last_time($value)
{
if (file_exists($value)) {
return filemtime($value);
}
return date('Ymd-is', time());
}
}
if (! function_exists('get_user_attr')) {
function get_user_attr($key)
{
if ($user = Auth::user()) {
return $user->getSetting($key);
}
return null;
}
}
if (! function_exists('set_user_attr')) {
function set_user_attr($key, $value)
{
if ($user = Auth::user()) {
return $user->setSetting([$key => $value]);
}
return null;
}
}
if (! function_exists('get_active_badge')) {
function get_active_badge($active, $tooltip = false, $pos = 'top')
{
if ($tooltip) {
$tooltip = 'data-toggle="tooltip" data-placement="top" data-original-title="'.$tooltip.'"';
}
return $active ? '<span class="badge badge-pill badge-success" '.$tooltip.'><i class="fa fa-check"></i></span>' : '<span class="badge badge-pill badge-danger" '.$tooltip.'><i class="fa fa-times"></i></span>';
}
}
if (! function_exists('formatDate')) {
function formatDate($date)
{
return Carbon::parse($date)->format(Util::formatDateDB());
}
}
if (! function_exists('formatNumber')) {
function formatNumber($number, $dec = 2)
{
return ! $number ? $number : Util::formatNumber($number, $dec);
}
}
if (! function_exists('reFormatNumber')) {
function reFormatNumber($number)
{
return ! $number ? $number : Util::reFormatNumber($number);
}
}
if (! function_exists('reFormatWawiNumber')) {
/**
* Eingabe-Parsing der Warenwirtschaft (Audit N-6, Kundenentscheid 15.07.2026):
* Ein einzelner Punkt mit genau 1–2 Nachkommastellen gilt als Dezimaltrenner
* („5.1" = 5,1 · „5.12" = 5,12 — kommt beim Kopieren von Daten vor);
* alles andere („1.500", „1.234,56") folgt der deutschen Konvention.
*/
function reFormatWawiNumber($number)
{
// AP-53 (09.09.2026): zusätzlich „0.xxx" mit drei Stellen als Dezimalzahl —
// ein Tausenderpunkt hinter einer 0 ergibt keinen Sinn. „1.500" bleibt 1500.
if (is_string($number) && preg_match('/^-?(\d+\.\d{1,2}|0\.\d{3})$/', trim($number))) {
return (float) trim($number);
}
return reFormatNumber($number);
}
}
if (! function_exists('roundWawiQuantity')) {
/**
* AP-53: Mengenrundung der Warenwirtschaft (Gramm) — einheitlich drei
* Nachkommastellen (Milligramm), siehe InventoryService::QUANTITY_DECIMALS.
*/
function roundWawiQuantity($value): float
{
return round((float) $value, InventoryService::QUANTITY_DECIMALS);
}
}
if (! function_exists('normalizeWawiQuantity')) {
/**
* Prüfung AP-53 (4.2): Eingabe → kanonische Gramm-Menge (deutsch geparst und auf
* drei Stellen gerundet), damit Validierung, Toleranz und Speicherung denselben
* Wert sehen. Nicht-numerische Eingaben bleiben unverändert (Validator meldet sie).
*/
function normalizeWawiQuantity($value)
{
$n = reFormatWawiNumber($value);
return is_numeric($n) ? roundWawiQuantity($n) : $n;
}
}
if (! function_exists('formatWawiQuantity')) {
/**
* AP-53: Mengenanzeige (deutsch) mit bis zu drei Nachkommastellen; mindestens
* $minDecimals werden gezeigt (Standard 2: „12,50", „0,125", „1.500,00").
*/
function formatWawiQuantity($value, int $minDecimals = 2): string
{
$max = InventoryService::QUANTITY_DECIMALS;
$rounded = round((float) $value, $max);
$decimals = $max;
$asString = number_format($rounded, $max, '.', '');
while ($decimals > $minDecimals && str_ends_with($asString, '0')) {
$asString = substr($asString, 0, -1);
$decimals--;
}
return number_format($rounded, $decimals, ',', '.');
}
}
if (! function_exists('formatPlural')) {
function formatPlural($count, $singular, $plural)
{
return Util::formatPlural($count, $singular, $plural);
}
}
if (! function_exists('substr_ellipsis')) {
function substr_ellipsis($str, $len = 100, $clean = false, $ell = '...')
{
return Util::substr_ellipsis($str, $len, $clean, $ell);
}
}