Lager-Tab mit Produkt- und Artikel-Tabellen erweitern

This commit is contained in:
gitea_admin committed 2026-09-06 07:34:18 +02:00
1 parent 8ed5dba3d8
commit 3334913915
5 files changed
+146 -2

No files matched your search

+26
View File
@@ -1,6 +1,32 @@
<?php
declare(strict_types=1);
function get_lager_bundle_overview(PDO $pdo): array
{
$stmt = $pdo->query(
"SELECT
si.id,
si.item_code,
si.display_name
FROM sellable_item si
INNER JOIN sellable_item_component sic
ON sic.sellable_item_id = si.id
WHERE si.status = 'active'
GROUP BY si.id, si.item_code, si.display_name
HAVING COUNT(DISTINCT sic.product_id) > 1
ORDER BY si.display_name ASC, si.id ASC"
);
return array_map(
static fn (array $row): array => [
'id' => (int) $row['id'],
'item_code' => (string) $row['item_code'],
'display_name' => (string) $row['display_name'],
],
$stmt->fetchAll()
);
}
function normalize_title_key(string $value): string
{
$value = trim(mb_strtolower($value, 'UTF-8'));
+32
View File
@@ -50,6 +50,38 @@ function get_otc_order_form_products(PDO $pdo): array
return $products;
}
function get_lager_product_overview(PDO $pdo): array
{
$stmt = $pdo->query(
"SELECT
p.id,
p.name,
p.sku,
p.uom,
COALESCE(SUM(v.qty_net), 0) AS available_qty
FROM product p
LEFT JOIN stock_lot sl
ON sl.product_id = p.id
AND sl.status = 'current'
LEFT JOIN v_stock_lot_balance v
ON v.stock_lot_id = sl.id
WHERE p.status = 'active'
GROUP BY p.id, p.name, p.sku, p.uom
ORDER BY p.name ASC, p.id ASC"
);
return array_map(
static fn (array $row): array => [
'id' => (int) $row['id'],
'name' => (string) $row['name'],
'sku' => (string) $row['sku'],
'uom' => (string) $row['uom'],
'available_qty' => (float) $row['available_qty'],
],
$stmt->fetchAll()
);
}
function get_current_lot_balance_for_update(PDO $pdo, int $productId): array
{
$stmt = $pdo->prepare(
+76 -1
View File
@@ -201,7 +201,58 @@ function auth_render_bestellungen_content(array $bestellungenTable): string
. '</div>';
}
function render_auth_home_page(array $user, array $otcProducts = [], array $bestellungenTable = []): void
function auth_render_lager_table(string $title, array $rows, array $columns, string $tableKey): string
{
$html = [];
$html[] = '<article class="sg-card sg-large-table sg-large-table--lager" data-component="large-table" data-lager-table="true" data-lager-table-key="' . auth_escape_html($tableKey) . '" role="table" aria-label="' . auth_escape_html($title) . '">';
$html[] = '<div class="sg-card-segment sg-card-segment--header sg-card-segment--darkblue sg-large-table__title-segment" data-component-part="large-table-header"><div class="sg-strong">' . auth_escape_html($title) . '</div></div>';
$html[] = '<div class="sg-card-segment sg-card-segment--body sg-card-segment--white sg-large-table__row sg-large-table__row--header" role="row" data-component-part="large-table-header-row">';
foreach ($columns as $label) {
$html[] = '<div class="sg-large-table__cell sg-large-table__cell--header" role="columnheader"><span class="sg-large-table__sort-label">' . auth_escape_html($label) . '</span></div>';
}
$html[] = '</div>';
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 = (string) max(0, (int) round((float) ($row['available_qty'] ?? 0)));
$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') . '"';
$html[] = '<div class="' . $rowClass . '" role="row" data-component-part="large-table-row" data-lager-row="true" data-lager-id="' . $id . '">';
$html[] = '<div class="sg-large-table__cell" role="cell"><a class="sg-hyperlink" href="#" aria-haspopup="dialog" aria-expanded="false" data-lager-drawer-open="true"' . $nameData . $valueData . $typeData . '>' . auth_escape_html($name) . '</a></div>';
if ($tableKey === 'products') {
$html[] = '<div class="sg-large-table__cell" role="cell"><a class="sg-hyperlink" href="#" aria-haspopup="dialog" aria-expanded="false" data-lager-drawer-open="true"' . $nameData . $valueData . $typeData . '>' . auth_escape_html($value) . '</a></div>';
}
$html[] = '</div>';
}
$html[] = '</article>';
return implode('', $html);
}
function auth_render_lager_content(array $lagerProducts, array $lagerBundles): string
{
return '<div data-lager-content="true">'
. '<section class="sg-card-list-page" aria-label="Lager">'
. auth_render_lager_table('Produkte', $lagerProducts, ['Produktname', 'Anzahl im Lager'], 'products')
. auth_render_lager_table('Artikel', $lagerBundles, ['Artikel-Name'], 'bundles')
. '</section>'
. '<div class="sg-card-list-page-drawer-backdrop" aria-hidden="true" data-lager-drawer-backdrop="true" data-open="false"></div>'
. '<aside class="sg-card-list-page-drawer" aria-label="Lager-Details" aria-hidden="true" data-lager-drawer="true" data-open="false">'
. '<div class="sg-card-list-page-drawer__header"><h2 class="sg-heading-h2 sg-card-list-page-drawer__title" data-lager-drawer-title>Lager-Details</h2><button class="sg-interaction-element sg-hyperlink" type="button" data-lager-drawer-close>schliessen</button></div>'
. '<div class="sg-card-list-page-drawer__content"><article class="sg-card sg-vsf-drawer-card" data-component="card" data-pattern="card" aria-label="Lager-Details"><header class="sg-card-segment sg-card-segment--header sg-card-segment--darkblue sg-vsf-drawer-card__header"><div class="sg-strong">Lager-Details</div></header><div class="sg-card-segment sg-card-segment--body sg-vsf-drawer-card__content"><p class="sg-body"><strong>Name:</strong> <span data-lager-drawer-name-value></span></p><p class="sg-body" data-lager-drawer-value-row><strong data-lager-drawer-value-label>Wert:</strong> <span data-lager-drawer-value-value></span></p></div></article></div>'
. '</aside>'
. '</div>';
}
function render_auth_home_page(array $user, array $otcProducts = [], array $bestellungenTable = [], array $lagerProducts = [], array $lagerBundles = []): void
{
$otcProductRows = '';
foreach ($otcProducts as $product) {
@@ -230,9 +281,11 @@ function render_auth_home_page(array $user, array $otcProducts = [], array $best
JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES
);
$bestellungenContent = auth_render_bestellungen_content($bestellungenTable);
$lagerContent = auth_render_lager_content($lagerProducts, $lagerBundles);
$moduleContentCards = json_encode(
[
'Bestellungen' => $bestellungenContent,
'Lager' => $lagerContent,
],
JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES
);
@@ -417,6 +470,7 @@ function render_auth_home_page(array $user, array $otcProducts = [], array $best
echo " if (contentTitle) { contentTitle.textContent = activeEntry; }";
echo " if (contentBody) { contentBody.innerHTML = portalModuleContentCards[activeEntry] || ''; }";
echo " if (activeEntry === 'Bestellungen') { initBestellungenBindings(); }";
echo " if (activeEntry === 'Lager') { initLagerBindings(); }";
echo " menu.innerHTML = '';";
echo " entries.forEach((entry, index) => {";
echo " const button = document.createElement('button');";
@@ -437,6 +491,7 @@ function render_auth_home_page(array $user, array $otcProducts = [], array $best
echo " if (contentTitle) { contentTitle.textContent = entryName; }";
echo " if (contentBody) { contentBody.innerHTML = portalModuleContentCards[entryName] || ''; }";
echo " if (entryName === 'Bestellungen') { initBestellungenBindings(); }";
echo " if (entryName === 'Lager') { initLagerBindings(); }";
echo " });";
echo " });";
echo "};";
@@ -516,6 +571,26 @@ function render_auth_home_page(array $user, array $otcProducts = [], array $best
echo "})();";
echo "let bestellungenTableHandlersInstalled = false;";
echo "let bestellungenRealtimeSource = null;";
echo "let lagerBindingsInstalled = false;";
echo "function initLagerBindings() {";
echo " if (lagerBindingsInstalled) { return; }";
echo " const contentRoot = document.querySelector('[data-left-navigation-content-body]');";
echo " if (!contentRoot || !contentRoot.querySelector('[data-lager-content]')) { return; }";
echo " lagerBindingsInstalled = true;";
echo " const getDrawer = () => contentRoot.querySelector('[data-lager-drawer]');";
echo " const setDrawerState = (open) => {";
echo " const drawer = getDrawer(); const backdrop = contentRoot.querySelector('[data-lager-drawer-backdrop]');";
echo " if (!drawer || !backdrop) { return; }";
echo " drawer.dataset.open = String(open); drawer.setAttribute('aria-hidden', String(!open)); backdrop.dataset.open = String(open); backdrop.setAttribute('aria-hidden', String(!open));";
echo " };";
echo " const closeDrawer = () => { setDrawerState(false); contentRoot.querySelectorAll('[data-lager-drawer-open]').forEach((trigger) => trigger.setAttribute('aria-expanded', 'false')); };";
echo " contentRoot.addEventListener('click', (event) => {";
echo " const trigger = event.target.closest('[data-lager-drawer-open]');";
echo " if (trigger) { event.preventDefault(); const drawer = getDrawer(); if (!drawer) { return; } const type = trigger.dataset.lagerDrawerType || 'Lager'; const name = trigger.dataset.lagerDrawerName || ''; const value = trigger.dataset.lagerDrawerValue || ''; drawer.querySelector('[data-lager-drawer-title]').textContent = type + ': ' + name; drawer.querySelector('[data-lager-drawer-name-value]').textContent = name; const valueRow = drawer.querySelector('[data-lager-drawer-value-row]'); if (valueRow) { valueRow.hidden = type !== 'Produkt'; } drawer.querySelector('[data-lager-drawer-value-label]').textContent = 'Anzahl im Lager:'; drawer.querySelector('[data-lager-drawer-value-value]').textContent = value; setDrawerState(true); contentRoot.querySelectorAll('[data-lager-drawer-open]').forEach((button) => button.setAttribute('aria-expanded', String(button === trigger))); return; }";
echo " if (event.target.closest('[data-lager-drawer-close]') || event.target.closest('[data-lager-drawer-backdrop]')) { closeDrawer(); }";
echo " });";
echo " document.addEventListener('keydown', (event) => { if (event.key === 'Escape') { closeDrawer(); } });";
echo "}";
echo "function initBestellungenBindings() {";
echo " const contentRoot = document.querySelector('[data-left-navigation-content-body]');";
echo " if (!contentRoot) { return; }";
+8
View File
@@ -157,6 +157,14 @@ body.sg-bestellungen-mobile-detail-open > :not([data-bestellungen-mobile-detail-
justify-content: flex-end;
}
.sg-large-table--lager .sg-large-table__row {
grid-template-columns: minmax(0, 2fr) minmax(0, 1fr);
}
.sg-large-table--lager .sg-large-table__cell:last-child {
text-align: right;
}
[data-bestellungen-mobile-detail] .sg-navigation-card-layout {
margin-top: 0;
}
+4 -1
View File
@@ -6,6 +6,7 @@ require_once __DIR__ . '/../modules/shared/auth/ui/login.php';
require_once __DIR__ . '/../modules/shared/auth/ui/home.php';
require_once __DIR__ . '/../modules/erp/bestellungen/service.php';
require_once __DIR__ . '/../modules/erp/lager/service.php';
require_once __DIR__ . '/../modules/erp/artikel-mapping/service.php';
$env = expand_env_values(parse_env_file(__DIR__ . '/../.env'));
$pdo = connect_database($env);
@@ -46,6 +47,8 @@ if (($_SERVER['REQUEST_METHOD'] ?? 'GET') === 'POST') {
$currentUser = auth_current_user($pdo);
if ($currentUser !== null) {
$otcProducts = get_otc_order_form_products($pdo);
$lagerProducts = get_lager_product_overview($pdo);
$lagerBundles = get_lager_bundle_overview($pdo);
$bestellungenTable = get_sales_order_overview($pdo, [
'search' => (string) ($_GET['bestellungen_search'] ?? ''),
'sort_column' => (string) ($_GET['bestellungen_sort'] ?? 'order_date'),
@@ -57,7 +60,7 @@ if ($currentUser !== null) {
echo auth_render_bestellungen_content($bestellungenTable);
exit;
}
render_auth_home_page($currentUser, $otcProducts, $bestellungenTable);
render_auth_home_page($currentUser, $otcProducts, $bestellungenTable, $lagerProducts, $lagerBundles);
exit;
}