$value !== null && $value !== ''
);
$path = auth_current_path();
$queryString = http_build_query($query, '', '&', PHP_QUERY_RFC3986);
return $queryString === '' ? $path : $path . '?' . $queryString;
}
function auth_format_bestellung_date(?string $value): string
{
if ($value === null || $value === '') {
return '';
}
try {
return (new DateTimeImmutable($value))->format('d.m.Y H:i');
} catch (Throwable) {
return $value;
}
}
function auth_format_bestellung_total(?float $value): string
{
if ($value === null) {
return '';
}
return 'CHF ' . number_format($value, 2, '.', '\'');
}
function auth_format_market_date(?string $earliest, ?string $latest): string
{
if ($earliest === null || $earliest === '') {
return '';
}
$start = (new DateTimeImmutable($earliest))->format('d.m.Y');
if ($latest === null || $latest === '' || $latest === $earliest) {
return $start;
}
return $start . '–' . (new DateTimeImmutable($latest))->format('d.m.Y');
}
function auth_market_status_label(string $status): string
{
return [
'open' => 'Offen',
'waitlist' => 'Warteliste',
'registered' => 'Angemeldet',
'confirmation_pending' => 'Bestätigung ausstehend',
'confirmed' => 'Bestätigt',
'cancelled' => 'Abgesagt',
'completed' => 'Abgeschlossen',
][$status] ?? $status;
}
function auth_market_status_signal_class(string $status): string
{
return [
'waitlist' => 'market-status--red',
'registered' => 'market-status--yellow',
'confirmation_pending' => 'market-status--yellow',
'confirmed' => 'market-status--green',
][$status] ?? '';
}
function auth_render_markets_large_table(array $marketTable): string
{
$rows = $marketTable['rows'] ?? $marketTable;
$search = (string) ($marketTable['search'] ?? '');
$sortColumn = (string) ($marketTable['sort_column'] ?? 'earliest_date');
$sortDirection = strtoupper((string) ($marketTable['sort_direction'] ?? 'ASC'));
$limit = (int) ($marketTable['limit'] ?? 20);
$pageSize = (int) ($marketTable['page_size'] ?? 20);
$hasMore = (bool) ($marketTable['has_more'] ?? (count($rows) >= $limit));
$sortColumns = [
'market_name' => 'Name der Veranstaltung',
'earliest_date' => 'Durchführung',
'place' => 'Ort',
'status' => 'Status',
];
$html = [];
$html[] = '';
$html[] = '';
$html[] = '';
$html[] = '';
$headerIndex = 0;
foreach ($sortColumns as $column => $label) {
$isActive = $column === $sortColumn;
$headerDirection = $isActive ? strtolower($sortDirection) : 'none';
$iconDirection = $isActive ? strtolower($sortDirection) : 'ascending';
$targetDirection = $isActive ? ($sortDirection === 'ASC' ? 'DESC' : 'ASC') : ($column === 'earliest_date' ? 'ASC' : 'ASC');
$ariaLabel = $isActive ? $label . ' ' . ($sortDirection === 'ASC' ? 'aufsteigend sortiert' : 'absteigend sortiert') : $label . ' sortieren';
$html[] = '';
$headerIndex++;
}
$html[] = '
';
foreach ($rows as $index => $row) {
$rowClass = 'sg-card-segment sg-card-segment--body sg-card-segment--white sg-large-table__row';
if ($index % 2 === 1) {
$rowClass .= ' sg-large-table__row--striped-light';
}
$html[] = '';
$html[] = '
' . auth_escape_html((string) ($row['market_name'] ?? '')) . '
';
$html[] = '
';
$html[] = '
' . auth_escape_html((string) ($row['place'] ?? '')) . '
';
$status = (string) ($row['status'] ?? '');
$statusSignalClass = trim(auth_market_status_signal_class($status));
$statusValue = '
' . auth_escape_html(auth_market_status_label($status)) . '';
$html[] = '
' . $statusValue . '
';
$html[] = '
';
}
if ($hasMore) {
$html[] = '';
}
$html[] = '';
return implode('', $html);
}
function auth_render_markets_content(array $marketTable): string
{
return '
' . auth_render_markets_large_table($marketTable)
. '
'
. '
';
}
function auth_render_bestellungen_large_table(array $bestellungenTable): string
{
$rows = $bestellungenTable['rows'] ?? [];
$search = (string) ($bestellungenTable['search'] ?? '');
$sortColumn = (string) ($bestellungenTable['sort_column'] ?? 'order_date');
$sortDirection = strtoupper((string) ($bestellungenTable['sort_direction'] ?? 'DESC'));
$limit = (int) ($bestellungenTable['limit'] ?? 20);
$pageSize = (int) ($bestellungenTable['page_size'] ?? 20);
$hasMore = (bool) ($bestellungenTable['has_more'] ?? false);
$nextLimit = (int) ($bestellungenTable['next_limit'] ?? $limit);
$sortColumns = [
'order_date' => 'Bestelldatum',
'external_ref' => 'Bestellnummer',
'name' => 'Name',
'total_amount' => 'Gesamtsumme',
];
$html = [];
$html[] = '';
$html[] = '';
$html[] = '';
$html[] = '';
$headerIndex = 0;
foreach ($sortColumns as $column => $label) {
$isActive = $column === $sortColumn;
$headerDirection = $isActive ? strtolower($sortDirection) : 'none';
$iconDirection = $isActive ? strtolower($sortDirection) : 'ascending';
$ariaLabel = $isActive
? $label . ' ' . ($sortDirection === 'ASC' ? 'aufsteigend sortiert' : 'absteigend sortiert')
: $label . ' sortieren';
$targetDirection = $isActive
? ($sortDirection === 'ASC' ? 'DESC' : 'ASC')
: ($column === 'order_date' ? 'DESC' : 'ASC');
$amountHeaderClass = $column === 'total_amount' ? ' sg-large-table__cell--amount' : '';
$html[] = '';
$headerIndex++;
}
$html[] = '
';
foreach ($rows as $index => $row) {
$isStriped = $index % 2 === 1;
$rowClass = 'sg-card-segment sg-card-segment--body sg-card-segment--white sg-large-table__row';
if ($isStriped) {
$rowClass .= ' sg-large-table__row--striped-light';
}
$orderNumber = (string) ($row['external_ref'] ?? '');
$orderDate = auth_format_bestellung_date((string) ($row['order_date'] ?? ''));
$totalAmount = auth_format_bestellung_total(isset($row['total_amount']) ? (float) $row['total_amount'] : null);
$firstName = (string) ($row['first_name'] ?? '');
$lastName = (string) ($row['last_name'] ?? '');
$name = trim($firstName . ' ' . $lastName);
$street = (string) ($row['street'] ?? '');
$houseNumber = (string) ($row['house_number'] ?? '');
$zip = (string) ($row['zip'] ?? '');
$city = (string) ($row['city'] ?? '');
$country = (string) ($row['country_name'] ?? '');
$html[] = '';
$html[] = '
' . auth_escape_html($orderDate) . '
';
$html[] = '
';
$html[] = '
';
$html[] = '
' . auth_escape_html($totalAmount) . '
';
$html[] = '
';
}
if ($hasMore) {
$loadMoreUrl = auth_build_path_with_query([
'bestellungen_search' => $search !== '' ? $search : null,
'bestellungen_sort' => $sortColumn,
'bestellungen_dir' => $sortDirection,
'bestellungen_limit' => $nextLimit,
]);
$html[] = '';
$html[] = '
';
$html[] = '
';
$html[] = '
';
$html[] = '
';
$html[] = '';
$html[] = '
';
$html[] = '
';
$html[] = '';
$html[] = '
';
$html[] = '
';
$html[] = '
';
$html[] = '
';
}
$html[] = '';
$html[] = '';
$html[] = '';
$html[] = '';
return implode('', $html);
}
function auth_render_bestellungen_content(array $bestellungenTable): string
{
return ''
. '
'
. auth_render_bestellungen_large_table($bestellungenTable)
. '
';
}
function auth_render_lager_table(string $title, array $rows, array $columns, string $tableKey): string
{
$renderForecast = static function (?string $date, string $status, ?string $expiryDate, bool $available): array {
if (!$available) {
return ['text' => '-', 'warning' => false, 'mhdWarning' => false];
}
if ($date === null || $date === '') {
return ['text' => 'Keine Prognose', 'warning' => false, 'mhdWarning' => false];
}
$dateObject = new DateTimeImmutable($date);
$text = $dateObject->format('d.m.Y');
if ($status === 'depleted') {
return ['text' => $text . ' (BESTAND ENDE)', 'warning' => true, 'mhdWarning' => false];
}
$warning = false;
$mhdWarning = false;
if ($expiryDate !== null && $expiryDate !== '') {
$expiryObject = new DateTimeImmutable($expiryDate);
$warning = $dateObject >= $expiryObject->modify('-3 months') && $dateObject <= $expiryObject;
$mhdWarning = $dateObject > $expiryObject;
if ($warning || $mhdWarning) {
$text .= ' (MHD: ' . $expiryObject->format('d.m.Y') . ')';
}
}
return ['text' => $text, 'warning' => $warning, 'mhdWarning' => $mhdWarning];
};
$html = [];
$html[] = '';
$html[] = '';
$html[] = '';
foreach ($columns as $label) {
$html[] = '';
}
$html[] = '
';
foreach ($rows as $index => $row) {
$rowClass = 'sg-card-segment sg-card-segment--body sg-card-segment--white sg-large-table__row';
if ($index % 2 === 1) {
$rowClass .= ' sg-large-table__row--striped-light';
}
$id = (int) ($row['id'] ?? 0);
$name = (string) ($row['name'] ?? $row['display_name'] ?? '');
$value = $tableKey === 'products'
? (string) max(0, (int) round((float) ($row['available_qty'] ?? 0)))
: (string) ($row['item_code'] ?? '');
$currentForecast = ['text' => '', 'warning' => false, 'mhdWarning' => false];
$nextForecast = ['text' => '-', 'warning' => false, 'mhdWarning' => false];
if ($tableKey === 'products') {
$currentForecast = $renderForecast(
$row['sellout_date'] ?? null,
(string) ($row['sellout_forecast_status'] ?? 'no_demand'),
$row['expiry_date'] ?? null,
true
);
$nextForecast = $renderForecast(
$row['next_sellout_date'] ?? null,
(string) ($row['next_sellout_forecast_status'] ?? 'no_demand'),
$row['next_expiry_date'] ?? null,
$row['next_lot_id'] !== null
);
}
$nameData = ' data-lager-drawer-name="' . auth_escape_html($name) . '"';
$valueData = ' data-lager-drawer-value="' . auth_escape_html($value) . '"';
$typeData = ' data-lager-drawer-type="' . auth_escape_html($tableKey === 'products' ? 'Produkt' : 'Artikel') . '"';
$detailData = '';
if ($tableKey === 'products') {
$detailData = ' data-lager-drawer-forecast="' . auth_escape_html($currentForecast['text']) . '"'
. ' data-lager-drawer-forecast-status="' . auth_escape_html((string) ($row['sellout_forecast_status'] ?? 'no_demand')) . '"'
. ' data-lager-drawer-forecast-warning="' . ($currentForecast['warning'] ? 'true' : 'false') . '"'
. ' data-lager-drawer-forecast-mhd-warning="' . ($currentForecast['mhdWarning'] ? 'true' : 'false') . '"'
. ' data-lager-drawer-current-qty="' . auth_escape_html(auth_format_lager_quantity((float) ($row['current_qty'] ?? 0))) . '"'
. ' data-lager-drawer-qty-in="' . auth_escape_html(auth_format_lager_quantity((float) ($row['qty_in'] ?? 0))) . '"'
. ' data-lager-drawer-virtual-shortage-qty="' . auth_escape_html(auth_format_lager_quantity((float) ($row['virtual_shortage_qty'] ?? 0))) . '"'
. ' data-lager-drawer-lot-id="' . auth_escape_html((string) ($row['lot_id'] ?? '')) . '"'
. ' data-lager-drawer-lot-number="' . auth_escape_html((string) ($row['lot_number'] ?? '')) . '"'
. ' data-lager-drawer-manufacturer-party-id="' . auth_escape_html((string) ($row['manufacturer_party_id'] ?? '')) . '"'
. ' data-lager-drawer-manufacturer-name="' . auth_escape_html((string) ($row['manufacturer_name'] ?? '')) . '"'
. ' data-lager-drawer-manufacturer-lot-number="' . auth_escape_html((string) ($row['manufacturer_lot_number'] ?? '')) . '"'
. ' data-lager-drawer-mfg-date="' . auth_escape_html((string) ($row['mfg_date'] ?? '')) . '"'
. ' data-lager-drawer-expiry-date="' . auth_escape_html((string) ($row['expiry_date'] ?? '')) . '"'
. ' data-lager-drawer-purchase-date="' . auth_escape_html((string) ($row['purchase_date'] ?? '')) . '"'
. ' data-lager-drawer-virtual-lot-number="' . auth_escape_html((string) ($row['virtual_lot_number'] ?? '')) . '"';
$openLot = $row['open_lots'][0] ?? [];
$detailData .= ' data-lager-open-lot-id="' . auth_escape_html((string) ($openLot['lot_id'] ?? '')) . '"'
. ' data-lager-open-lot-number="' . auth_escape_html((string) ($openLot['lot_number'] ?? '')) . '"'
. ' data-lager-open-manufacturer-party-id="' . auth_escape_html((string) ($openLot['manufacturer_party_id'] ?? '')) . '"'
. ' data-lager-open-manufacturer-lot-number="' . auth_escape_html((string) ($openLot['manufacturer_lot_number'] ?? '')) . '"'
. ' data-lager-open-mfg-date="' . auth_escape_html((string) ($openLot['mfg_date'] ?? '')) . '"'
. ' data-lager-open-expiry-date="' . auth_escape_html((string) ($openLot['expiry_date'] ?? '')) . '"'
. ' data-lager-open-purchase-date="' . auth_escape_html((string) ($openLot['purchase_date'] ?? '')) . '"';
}
$html[] = '';
$html[] = '
';
$valueCell = $tableKey === 'products'
? '
' . auth_escape_html($value) . '
'
: '
';
$html[] = $valueCell;
if ($tableKey === 'products') {
$forecastClass = $currentForecast['warning']
? ' sg-strong lager-forecast-warning'
: ($currentForecast['mhdWarning'] ? ' sg-strong lager-forecast-mhd-warning' : '');
$nextForecastClass = $nextForecast['warning']
? ' sg-strong lager-forecast-warning'
: ($nextForecast['mhdWarning'] ? ' sg-strong lager-forecast-mhd-warning' : '');
$html[] = '
' . auth_escape_html($currentForecast['text']) . '
';
$html[] = '
' . auth_escape_html((string) max(0, (int) round((float) ($row['next_qty'] ?? 0)))) . '
';
$html[] = '
' . auth_escape_html($nextForecast['text']) . '
';
}
$html[] = '
';
}
$html[] = '';
return implode('', $html);
}
function auth_format_lager_date(?string $value): string
{
if ($value === null || $value === '') {
return 'Nicht hinterlegt';
}
return (new DateTimeImmutable($value))->format(str_contains($value, ':') ? 'd.m.Y H:i' : 'd.m.Y');
}
function auth_format_lager_quantity(float $value): string
{
return fmod($value, 1.0) === 0.0
? number_format($value, 0, ',', '.')
: number_format($value, 2, ',', '.');
}
function auth_render_lager_historical_lots(array $lagerProducts): string
{
$html = [];
foreach ($lagerProducts as $product) {
$productId = (int) ($product['id'] ?? 0);
$html[] = '';
foreach (($product['historical_lots'] ?? []) as $lot) {
$qtyNet = (float) ($lot['qty_net'] ?? 0);
$emptyDate = $qtyNet <= 0 ? auth_format_lager_date($lot['last_out_at'] ?? null) : 'Nicht eindeutig';
$html[] = '
'
. ''
. ''
. '
Naurua-Chargennummer: ' . auth_escape_html((string) ($lot['lot_number'] ?? 'Nicht hinterlegt')) . '
'
. 'Status: Abgeschlossen
'
. 'Eingangsmenge: ' . auth_format_lager_quantity((float) ($lot['qty_in'] ?? 0)) . '
'
. 'Verbrauchte Menge: ' . auth_format_lager_quantity((float) ($lot['qty_out'] ?? 0)) . '
'
. 'Verbrauchszeitraum: ' . auth_format_lager_date($lot['first_in_at'] ?? null) . ' – ' . auth_format_lager_date($lot['last_out_at'] ?? null) . '
'
. 'Aufgebraucht am: ' . $emptyDate . '
'
. 'MHD: ' . auth_format_lager_date($lot['expiry_date'] ?? null) . '
'
. 'Hersteller: ' . auth_escape_html((string) ($lot['manufacturer_name'] ?? 'Nicht hinterlegt')) . '
'
. 'Hersteller-Chargennummer: ' . auth_escape_html((string) ($lot['manufacturer_lot_number'] ?? 'Nicht hinterlegt')) . '
'
. '
';
}
$html[] = '
';
}
return implode('', $html);
}
function auth_render_lager_open_lots(array $lagerProducts, string $manufacturerOptions): string
{
$html = [];
foreach ($lagerProducts as $product) {
$productId = (int) ($product['id'] ?? 0);
$html[] = '';
foreach (($product['open_lots'] ?? []) as $lot) {
$lotId = (int) ($lot['lot_id'] ?? 0);
$selectedClass = !empty($lot['next_active']) ? ' sg-lager-open-lot--next-active' : '';
$selectedText = !empty($lot['next_active']) ? 'Als nächste aktive Charge ausgewählt' : 'Nicht ausgewählt';
$isQualified = trim((string) ($lot['lot_number'] ?? '')) !== ''
&& (int) ($lot['manufacturer_party_id'] ?? 0) > 0
&& trim((string) ($lot['manufacturer_lot_number'] ?? '')) !== ''
&& trim((string) ($lot['mfg_date'] ?? '')) !== ''
&& trim((string) ($lot['expiry_date'] ?? '')) !== ''
&& trim((string) ($lot['purchase_date'] ?? '')) !== ''
&& (float) ($lot['qty_net'] ?? 0) > 0;
$selectButton = '
';
$html[] = '
'
. ''
. 'Auswahl: ' . auth_escape_html($selectedText) . '
Naurua-Chargennummer: ' . auth_escape_html((string) ($lot['lot_number'] ?? 'Nicht hinterlegt')) . '
'
. 'Status: Vorbereitet
'
. 'Bestand: ' . auth_format_lager_quantity((float) ($lot['qty_net'] ?? 0)) . '
'
. 'Eingangsmenge: ' . auth_format_lager_quantity((float) ($lot['qty_in'] ?? 0)) . '
'
. 'Hersteller: ' . auth_escape_html((string) ($lot['manufacturer_name'] ?? 'Nicht hinterlegt')) . '
'
. 'Hersteller-Chargennummer: ' . auth_escape_html((string) ($lot['manufacturer_lot_number'] ?? 'Nicht hinterlegt')) . '
'
. 'Herstellungsdatum: ' . auth_format_lager_date($lot['mfg_date'] ?? null) . '
'
. 'MHD: ' . auth_format_lager_date($lot['expiry_date'] ?? null) . '
'
. 'Einkaufsdatum: ' . auth_format_lager_date($lot['purchase_date'] ?? null) . '
'
. ''
. '
Leere Felder speichern?
Die folgenden Felder werden leer gespeichert:
'
. '
Möchtest du diese offene Charge wirklich löschen?
Du kannst das nicht rückgängig machen. Bestätige durch Eingabe von DELETE.
';
}
$html[] = '
';
}
return implode('', $html);
}
function auth_render_lager_content(array $lagerProducts, array $lagerArtikel, array $lagerManufacturers, string $csrfToken, string $sectionTitle = 'Lager'): string
{
$manufacturerOptions = '';
foreach ($lagerManufacturers as $manufacturer) {
$manufacturerOptions .= '';
}
$tables = $sectionTitle === 'Lager'
? auth_render_lager_table('Produkte', $lagerProducts, ['Produktname', 'Lager aktuell', 'Verkauf bis aktuell', 'Lager neu', 'Verkauf bis neu'], 'products')
: auth_render_lager_table('Artikel', $lagerArtikel, ['Artikel-Name', 'Artikelnummer'], 'articles');
$openLotCardTemplate = auth_render_lager_open_lots([[
'id' => 0,
'open_lots' => [[
'lot_id' => 0,
'lot_number' => null,
'manufacturer_party_id' => null,
'manufacturer_name' => null,
'manufacturer_lot_number' => null,
'mfg_date' => null,
'expiry_date' => null,
'purchase_date' => null,
'qty_net' => 0,
'qty_in' => 0,
'next_active' => false,
]],
]], $manufacturerOptions);
return ''
. '
'
. '
'
. '
'
. '
';
}
function render_auth_home_page(array $user, array $otcProducts = [], array $bestellungenTable = [], array $marketOccurrencesTable = [], array $lagerProducts = [], array $lagerArtikel = [], array $lagerManufacturers = []): void
{
$otcProductRows = '';
foreach ($otcProducts as $product) {
$productId = (int) ($product['id'] ?? 0);
$productName = auth_escape_html((string) ($product['name'] ?? ''));
$productSku = auth_escape_html((string) ($product['sku'] ?? ''));
$availableQty = (int) max(0, (int) round((float) ($product['available_qty'] ?? 0)));
$hasSellableMapping = (bool) ($product['has_sellable_mapping'] ?? false);
$inputId = 'otc-product-' . $productId;
$mappingNote = $hasSellableMapping ? '' : ' (Artikel-Mapping fehlt)';
$disabledAttribute = $hasSellableMapping ? '' : ' disabled aria-disabled="true"';
$otcProductRows .= '';
$otcProductRows .= '';
$otcProductRows .= '
';
}
if ($otcProductRows === '') {
$otcProductRows = 'Keine aktiven OTC-Produkte erfasst.
';
}
$moduleNavigation = json_encode(
[
'Übersicht' => [],
'ERP' => ['Bestellungen', 'Lager', 'Produkte', 'Artikel', 'Kontakte'],
'Märkte' => ['Märkte', 'Konfiguration'],
'Buchhaltung' => [],
'Kundenberatung' => [],
],
JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES
);
$bestellungenContent = auth_render_bestellungen_content($bestellungenTable);
$marketsContent = auth_render_markets_content($marketOccurrencesTable);
$lagerContent = auth_render_lager_content($lagerProducts, $lagerArtikel, $lagerManufacturers, auth_csrf_token());
$moduleContentCards = json_encode(
[
'Bestellungen' => $bestellungenContent,
'Märkte' => $marketsContent,
'Lager' => $lagerContent,
'Artikel' => auth_render_lager_content([], $lagerArtikel, $lagerManufacturers, auth_csrf_token(), 'Artikel'),
],
JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES
);
$moduleHeadings = json_encode(
[
'Übersicht' => 'Willkommen bei Naurua Übersicht',
'ERP' => 'Willkommen bei Naurua ERP',
'Märkte' => 'Willkommen bei Naurua Märkte',
'Buchhaltung' => 'Willkommen bei Naurua Buchhaltung',
'Kundenberatung' => 'Willkommen bei Naurua Kundenberatung',
],
JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES
);
echo '';
echo '';
echo '';
echo '';
echo '';
echo 'Naurua ERP';
echo '';
echo '';
echo '';
echo '';
echo '';
echo '';
echo '';
echo '
';
echo '';
echo '';
echo '';
echo '';
echo '
';
echo '';
echo '
Verkauf bestätigen
';
echo '
';
echo '
Gesamtzahl Flaschen: 0
';
echo '
Gesamtbetrag: CHF 0.00
';
echo '
';
echo '';
echo '';
echo '
';
echo '
';
echo '';
echo '
';
echo '';
echo '';
echo '';
echo '';
echo '';
echo '';
}