@@ -240,8 +240,12 @@ function auth_render_lager_table(string $title, array $rows, array $columns, str
$detailData = '' ;
if ( $tableKey === 'products' ) {
$detailData = ' data-lager-drawer-forecast="' . auth_escape_html ( $forecastText ) . '"'
. ' data-lager-drawer-forecast-status="' . auth_escape_html ( $forecastStatus ) . '"'
. ' data-lager-drawer-forecast-warning="' . ( $forecastWarning ? 'true' : 'false' ) . '"'
. ' data-lager-drawer-forecast-mhd-warning="' . ( $forecastMhdWarning ? 'true' : 'false' ) . '"'
. ' data-lager-drawer-current-qty="' . auth_escape_html ( auth_format_lager_quantity (( float ) ( $row [ 'available_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' ] ? ? '' )) . '"'
@@ -251,6 +255,14 @@ function auth_render_lager_table(string $title, array $rows, array $columns, str
. ' 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 [] = '<div class="' . $rowClass . '" role="row" data-component-part="large-table-row" data-lager-row="true" data-lager-id="' . $id . '">' ;
@@ -269,17 +281,101 @@ function auth_render_lager_table(string $title, array $rows, array $columns, str
return implode ( '' , $html );
}
function auth_render_lager_content ( array $lagerProducts , array $lagerArtikel , array $lagerManufacturers , string $csrfToken ) : string
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 [] = '<div data-lager-historical-product="' . $productId . '" hidden>' ;
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 [] = '<article class="sg-card sg-card--content-card" data-component="content-card" aria-label="Vergangene Charge">'
. '<div class="sg-card-segment sg-card-segment--header sg-card-segment--darkgreen" data-component-part="card-header"><div class="sg-strong sg-text-on-dark">Vergangene Charge</div></div>'
. '<div class="sg-card-segment sg-card-segment--body" data-component-part="card-body">'
. '<p class="sg-body"><strong>Naurua-Chargennummer:</strong> ' . auth_escape_html (( string ) ( $lot [ 'lot_number' ] ? ? 'Nicht hinterlegt' )) . '<br>'
. '<strong>Status:</strong> Abgeschlossen<br>'
. '<strong>Eingangsmenge:</strong> ' . auth_format_lager_quantity (( float ) ( $lot [ 'qty_in' ] ? ? 0 )) . '<br>'
. '<strong>Verbrauchte Menge:</strong> ' . auth_format_lager_quantity (( float ) ( $lot [ 'qty_out' ] ? ? 0 )) . '<br>'
. '<strong>Verbrauchszeitraum:</strong> ' . auth_format_lager_date ( $lot [ 'first_in_at' ] ? ? null ) . ' – ' . auth_format_lager_date ( $lot [ 'last_out_at' ] ? ? null ) . '<br>'
. '<strong>Aufgebraucht am:</strong> ' . $emptyDate . '<br>'
. '<strong>MHD:</strong> ' . auth_format_lager_date ( $lot [ 'expiry_date' ] ? ? null ) . '<br>'
. '<strong>Hersteller:</strong> ' . auth_escape_html (( string ) ( $lot [ 'manufacturer_name' ] ? ? 'Nicht hinterlegt' )) . '<br>'
. '<strong>Hersteller-Chargennummer:</strong> ' . auth_escape_html (( string ) ( $lot [ 'manufacturer_lot_number' ] ? ? 'Nicht hinterlegt' )) . '</p>'
. '</div></article>' ;
}
$html [] = '</div>' ;
}
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 [] = '<div data-lager-open-product="' . $productId . '" hidden>' ;
foreach (( $product [ 'open_lots' ] ? ? []) as $lot ) {
$lotId = ( int ) ( $lot [ 'lot_id' ] ? ? 0 );
$html [] = '<article class="sg-card sg-card--content-card" data-component="content-card" data-lager-open-card-wrapper="' . $lotId . '" aria-label="Offene Charge">'
. '<div class="sg-card-segment sg-card-segment--header sg-card-segment--darkgreen sg-object-card__header" data-component-part="card-header"><div class="sg-strong sg-text-on-dark">Offene Charge</div><button class="sg-interaction-element sg-button sg-button--active" type="button" data-lager-open-edit-open>Bearbeiten</button></div>'
. '<div class="sg-card-segment sg-card-segment--body" data-component-part="card-body" data-lager-open-card="' . ( int ) ( $lot [ 'lot_id' ] ? ? 0 ) . '"><p class="sg-body"><strong>Naurua-Chargennummer:</strong> <span data-lager-open-card-lot-number>' . auth_escape_html (( string ) ( $lot [ 'lot_number' ] ? ? 'Nicht hinterlegt' )) . '</span><br>'
. '<strong>Status:</strong> Vorbereitet<br>'
. '<strong>Bestand:</strong> <span data-lager-open-card-qty-net>' . auth_format_lager_quantity (( float ) ( $lot [ 'qty_net' ] ? ? 0 )) . '</span><br>'
. '<strong>Eingangsmenge:</strong> <span data-lager-open-card-qty-in>' . auth_format_lager_quantity (( float ) ( $lot [ 'qty_in' ] ? ? 0 )) . '</span><br>'
. '<strong>Hersteller:</strong> <span data-lager-open-card-manufacturer>' . auth_escape_html (( string ) ( $lot [ 'manufacturer_name' ] ? ? 'Nicht hinterlegt' )) . '</span><br>'
. '<strong>Hersteller-Chargennummer:</strong> <span data-lager-open-card-manufacturer-lot>' . auth_escape_html (( string ) ( $lot [ 'manufacturer_lot_number' ] ? ? 'Nicht hinterlegt' )) . '</span><br>'
. '<strong>Herstellungsdatum:</strong> <span data-lager-open-card-mfg-date>' . auth_format_lager_date ( $lot [ 'mfg_date' ] ? ? null ) . '</span><br>'
. '<strong>MHD:</strong> <span data-lager-open-card-expiry-date>' . auth_format_lager_date ( $lot [ 'expiry_date' ] ? ? null ) . '</span><br>'
. '<strong>Einkaufsdatum:</strong> <span data-lager-open-card-purchase-date>' . auth_format_lager_date ( $lot [ 'purchase_date' ] ? ? null ) . '</span></p></div>'
. '<div class="sg-card-segment sg-card-segment--body sg-card-segment--gray" data-lager-open-edit-form hidden><div class="sg-form-sections-card-wrapper" data-pattern="form-sections" aria-label="Offene Charge bearbeiten"><form class="sg-form-sections-card" data-lager-open-edit-form-element><div class="sg-form-sections-card__body" data-pattern-part="form-body"><h2 class="sg-strong sg-form-sections-card__title">Offene Charge bearbeiten</h2>'
. '<section class="sg-form-sections-card__chapter" aria-labelledby="lager-open-charge-data-' . $lotId . '"><h2 id="lager-open-charge-data-' . $lotId . '" class="sg-strong sg-form-sections-card__chapter-title">Chargendaten</h2>'
. '<div class="sg-form-sections-card__field-group"><label class="sg-labeled-input-row"><span class="sg-label">Naurua-Chargennummer</span><input class="sg-interaction-element sg-input-single-line" type="text" data-lager-open-lot-number required value="' . auth_escape_html (( string ) ( $lot [ 'lot_number' ] ? ? '' )) . '"></label></div>'
. '<div class="sg-form-sections-card__field-group"><label class="sg-labeled-input-row"><span class="sg-label">Hersteller</span><select class="sg-interaction-element sg-input-single-line" data-lager-open-manufacturer>' . str_replace ( 'value="' . ( int ) ( $lot [ 'manufacturer_party_id' ] ? ? 0 ) . '"' , 'value="' . ( int ) ( $lot [ 'manufacturer_party_id' ] ? ? 0 ) . '" selected' , $manufacturerOptions ) . '</select></label></div>'
. '<div class="sg-form-sections-card__field-group"><label class="sg-labeled-input-row"><span class="sg-label">Hersteller-Chargennummer</span><input class="sg-interaction-element sg-input-single-line" type="text" data-lager-open-manufacturer-lot value="' . auth_escape_html (( string ) ( $lot [ 'manufacturer_lot_number' ] ? ? '' )) . '"></label></div>'
. '<div class="sg-form-sections-card__field-group"><label class="sg-labeled-input-row"><span class="sg-label">Herstellungsdatum</span><input class="sg-interaction-element sg-input-single-line" type="date" data-lager-open-mfg-date value="' . auth_escape_html (( string ) ( $lot [ 'mfg_date' ] ? ? '' )) . '"></label></div>'
. '<div class="sg-form-sections-card__field-group"><label class="sg-labeled-input-row"><span class="sg-label">MHD</span><input class="sg-interaction-element sg-input-single-line" type="date" data-lager-open-expiry-date value="' . auth_escape_html (( string ) ( $lot [ 'expiry_date' ] ? ? '' )) . '"></label></div>'
. '<div class="sg-form-sections-card__field-group"><label class="sg-labeled-input-row"><span class="sg-label">Einkaufsdatum</span><input class="sg-interaction-element sg-input-single-line" type="date" data-lager-open-purchase-date value="' . auth_escape_html (( string ) ( $lot [ 'purchase_date' ] ? ? '' )) . '"></label></div>'
. '<div class="sg-form-sections-card__field-group"><label class="sg-labeled-input-row"><span class="sg-label">Zugang buchen</span><input class="sg-interaction-element sg-input-single-line" type="number" min="0" step="1" inputmode="numeric" data-lager-open-receipt-qty placeholder="Menge"></label></div>'
. '</section><footer class="sg-form-sections-card__actions-segment" data-pattern-part="form-actions-segment"><div class="sg-form-sections-card__actions" data-pattern-part="form-actions"><button class="sg-interaction-element sg-button sg-button--active sg-form-sections-card__action" type="button" data-lager-open-edit-close>Abbrechen</button><button class="sg-interaction-element sg-button sg-button--process sg-form-sections-card__action" type="submit">Speichern</button></div></footer><p class="sg-body" data-lager-open-edit-status aria-live="polite"></p></div></form></div></div></article>' ;
}
$html [] = '</div>' ;
}
return implode ( '' , $html );
}
function auth_render_lager_content ( array $lagerProducts , array $lagerArtikel , array $lagerManufacturers , string $csrfToken , string $sectionTitle = 'Lager' ) : string
{
$manufacturerOptions = '<option value="">Hersteller auswählen</option>' ;
foreach ( $lagerManufacturers as $manufacturer ) {
$manufacturerOptions .= '<option value="' . ( int ) ( $manufacturer [ 'id' ] ? ? 0 ) . '">' . auth_escape_html (( string ) ( $manufacturer [ 'name' ] ? ? '' )) . '</option>' ;
}
$tables = $sectionTitle === 'Lager'
? auth_render_lager_table ( 'Produkte' , $lagerProducts , [ 'Produktname' , 'Anzahl im Lager' , 'Verbraucht bis' ], 'products' )
: auth_render_lager_table ( 'Artikel' , $lagerArtikel , [ 'Artikel-Name' , 'Artikelnummer' ], 'articles' );
return '<div data-lager-content="true" data-lager-csrf-token="' . auth_escape_html ( $csrfToken ) . '">'
. '<section class="sg-card-list-page" aria-label="Lager ">'
. auth_render_lager_table ( 'Produkte' , $lagerProducts , [ 'Produktname' , 'Anzahl im Lager' , 'Verbraucht bis' ], 'products' )
. auth_render_lager_table ( 'Artikel' , $lagerArtikel , [ 'Artikel-Name' , 'Artikelnummer' ], 'articles' )
. '<section class="sg-card-list-page" aria-label="' . auth_escape_html ( $sectionTitle ) . ' ">'
. $tables
. '</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">'
@@ -290,28 +386,45 @@ function auth_render_lager_content(array $lagerProducts, array $lagerArtikel, ar
. '<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>'
. '<article class="sg-card sg-card--content-card" data-component="content-card" data-lager-drawer-topic="warning" aria-label="Warnungen">'
. '<div class="sg-card-segment sg-card-segment--header sg-card-segment--darkgreen" data-component-part="card-header"><div class="sg-strong sg-text-on-dark"> Warnungen </div></div>'
. '<div class="sg-card-segment sg-card-segment--header sg-card-segment--darkgreen" data-component-part="card-header" data-lager-drawer-warning-header ><div class="sg-strong sg-text-on-dark" data-lager-drawer-warning-title>Keine Warnung</div></div>'
. '<div class="sg-card-segment sg-card-segment--body" data-component-part="card-body"><p class="sg-body" data-lager-drawer-warning-value></p></div>'
. '</article>'
. '<article class="sg-card sg-card--content-card" data-component="content-card" data-lager-drawer-topic="lot" aria-label="Lagercharge" >'
. '<div class="sg-card-segment sg-card-segment--header sg-card-segment--darkgreen" data-component-part="card-header"><div class="sg-strong sg-text-on-dark">Lagercharge</div></div >'
. '<div class="sg-card-segment sg-card-segment--body" data-component-part="card-bod y"><p class="sg-body"><strong>Naurua-Chargennummer:</strong> <span data-lager-drawer-lot-number></span><br><strong>Hersteller:</strong> <span data-lager-drawer-manufacturer-name></span><br><strong>Hersteller-Chargennummer:</strong> <span data-lager-drawer-manufacturer-lot-number></span><br><strong>Herstellungsdatum:</strong> <span data-lager-drawer-mfg-date></span><br><strong>MHD:</strong> <span data-lager-drawer-expiry-date></span><br><strong>Einkaufsdatum:</strong> <span data-lager-drawer-purchase-date></span></p><button class="sg-interaction-element sg-button sg-button--active" type="button" data-lager-edit-open>Charge bearbeiten</button></div >'
. '<div class="sg-card-segment sg-card-segment--body sg-card-segment--gray" data-lager-edit-form hidden >'
. '<form class="sg-form-sections-card" data-lager-edit-form-element><div class="sg-form-sections-card__body" >'
. '<div class="sg-form-sections-card__field-group"><label class="sg-label" for="lager-manufacturer">Hersteller</label><select class="sg-interaction-element sg-input-single-line" id="lager-manufacturer" data-lager-manufacturer>' . $manufacturerOptions . '</select ></div>'
. '<div class="sg-form-sections-card__field-group"><label class="sg-label" for="lager-manufacturer-lot">Hersteller-Chargennummer</label><inpu t class="sg-interaction-element sg-input-single-line" type="text" id=" lager-manufacturer-lot" data-lager-manufacturer-lot ></div>'
. '<div class="sg-form-sections-card__field-group"><label class="sg-label" for="lager-mfg-date">Herstellungsdatum</label ><input class="sg-interaction-element sg-input-single-line" type="date" id="lager-mfg-date" data-lager-mfg-date ></div>'
. '<div class="sg-form-sections-card__field-group"><label class="sg-label" for="lager-expiry-date">MHD</label ><input class="sg-interaction-element sg-input-single-line" type="date" id="lager-expiry-date" data-lager-expiry-date ></div>'
. '<div class="sg-form-sections-card__field-group"><label class="sg-label" for="lager-purchase-date">Einkaufsdatum</label ><input class="sg-interaction-element sg-input-single-line" type="date" id="lager-purchase-date" data-lager-purchase-date ></div>'
. '<div class="sg-component-row sg-basic-card__actions"><button class="sg-interaction-element sg-button sg-button--active" type="submit">Speichern</button><button class="sg-interaction-element sg-button " type="button " data-lager-edit-close>Abbrechen</button ></div>'
. '<p class="sg-body" data-lager-edit-status aria-live="polite"></p></div></form >'
. '<form class="sg-form-sections-card" data-lager-manufacturer-form><div class="sg-form-sections-card__body"><h3 class="sg-strong">Neuen Hersteller anlegen</h3 >'
. '<div class="sg-form-sections-card__field-group"><label class="sg-label" for="lager-new-manufacturer-name">Name</label><input class="sg-interaction-element sg-input-single-line" type="text" id="lager-new-manufacturer-name" data-lager-new-manufacturer-name required></div>'
. '<div class="sg-form-sections-card__field-group"><label class="sg-label" for="lager-new-manufacturer-email">E-Mail</label><input class="sg-interaction-element sg-input-single-line" type="email" id="lager-new-manufacturer-email" data-lager-new-manufacturer-email></div>'
. '<div class="sg-form-sections-card__field-group"><label class="sg-label" for="lager-new-manufacturer-phone">Telefon</label><input class="sg-interaction-element sg-input-single-line" type="text" id="lager-new-manufacturer-phone" data-lager-new-manufacturer-phone></div>'
. '<div class="sg-component-row sg-basic-card__actions"><button class="sg-interaction-element sg-button sg-button--active" type="submit">Hersteller anlegen</button></div></div></form>'
. '</div></article>'
. '<div class="sg-inline-controls-row" data-lager-drawer-topic="new-lot-action"><button class="sg-interaction-element sg-button sg-button--active" type="button" data-lager-new-lot-open>Neue Charge anlegen</button></div >'
. '<article class="sg-card sg-card--content-card" data-lager-drawer-topic="new-lot-form" aria-label="Neue Charge anlegen" hidden >'
. '<div class="sg-card-segment sg-card-segment--body sg-card-segment--gra y"><div class="sg-form-sections-card-wrapper" data-pattern="form-sections" aria-label="Neue Charge anlegen"><form class="sg-form-sections-card" data-lager-new-lot-form-element><div class="sg-form-sections-card__body" data-pattern-part="form-body"><h2 class="sg-strong sg-form-sections-card__title">Neue Charge anlegen</h2 >'
. '<div class="sg-notifications-pattern" data-lager-new-lot-notification-wrapper hidden><article class="sg-card sg-notifications-pattern__card" data-pattern="notification-small" data-component="notification-card"><div class="sg-card-segment sg-card-segment--header sg-card-segment--signal-green sg-notifications-pattern__text-segment sg-notifications-pattern__text-segment--small" data-component-part="card-header"><p class="sg-body" data-lager-new-lot-notification></p></div></article></div >'
. '<section class="sg-form-sections-card__chapter" aria-labelledby="lager-new-charge-data-title"><h2 id="lager-new-charge-data-title" class="sg-strong sg-form-sections-card__chapter-title">Chargendaten</h2 >'
. '<div class="sg-form-sections-card__field-group"><label class="sg-labeled-input-row"><span class="sg-label">Naurua-Chargennummer</span><input class="sg-interaction-element sg-input-single-line" type="text" data-lager-new-lot-number required></label ></div>'
. '<div class="sg-form-sections-card__field-group"><label class="sg-labeled-input-row"><span class="sg-label">Hersteller</span><selec t class="sg-interaction-element sg-input-single-line" data- lager-new- manufacturer>' . $manufacturerOptions . '</select></label ></div>'
. '<div class="sg-form-sections-card__field-group"><label class="sg-labeled-input-row"><span class="sg-label">Hersteller-Chargennummer</span ><input class="sg-interaction-element sg-input-single-line" type="text" data-lager-new-manufacturer-lot></label ></div>'
. '<div class="sg-form-sections-card__field-group"><label class="sg-labeled-input-row"><span class="sg-label">Herstellungsdatum</span ><input class="sg-interaction-element sg-input-single-line" type="date" data-lager-new-mfg-date></label ></div>'
. '<div class="sg-form-sections-card__field-group"><label class="sg-labeled-input-row"><span class="sg-label">MHD</span ><input class="sg-interaction-element sg-input-single-line" type="date" data-lager-new-expiry-date></label ></div>'
. '<div class="sg-form-sections-card__field-group"><label class="sg-labeled-input-row"><span class="sg-label">Einkaufsdatum</span><input class="sg-interaction-element sg-input-single-line " type="date " data-lager-new-purchase-date></label ></div>'
. '<div class="sg-form-sections-card__field-group"><label class="sg-labeled-input-row"><span class="sg-label">Eingangsmenge</span><input class="sg-interaction-element sg-input-single-line" type="number" min="0" step="1" inputmode="numeric" data-lager-new-receipt-qty placeholder="Menge"></label></div >'
. '</section><footer class="sg-form-sections-card__actions-segment" data-pattern-part="form-actions-segment"><div class="sg-form-sections-card__actions" data-pattern-part="form-actions"><button class="sg-interaction-element sg-button sg-button--active sg-form-sections-card__action" type="button" data-lager-new-lot-close>Abbrechen</button><button class="sg-interaction-element sg-button sg-button--process sg-form-sections-card__action" type="submit">Speichern</button></div></footer><p class="sg-body" data-lager-new-lot-status aria-live="polite"></p></div></form></div></div >'
. '</article>'
. '<div data-lager-drawer-topic="open-lots" aria-label="Offene Chargen">' . auth_render_lager_open_lots ( $lagerProducts , $manufacturerOptions ) . '</div>'
. '<article class="sg-card sg-card--content-card" data-component="content-card" data-lager-drawer-topic="lot" aria-label="Aktive Charge">'
. '<div class="sg-card-segment sg-card-segment--header sg-card-segment--darkgreen sg-object-card__header" data-component-part="card-header"><div class="sg-strong sg-text-on-dark">Aktive Charge</div><button class="sg-interaction-element sg-button sg-button--active" type="button" data-lager-edit-open>Bearbeiten</button></div>'
. '<div class="sg-card-segment sg-card-segment--body" data-component-part="card-body" data-lager-read-view><p class="sg-body"><strong>Naurua-Chargennummer:</strong> <span data-lager-drawer-lot-number></span><br><strong>Aktueller Bestand:</strong> <span data-lager-drawer-current-qty></span><br><strong>Eingangsmenge:</strong> <span data-lager-drawer-qty-in></span><br><strong>Hersteller:</strong> <span data-lager-drawer-manufacturer-name></span><br><strong>Hersteller-Chargennummer:</strong> <span data-lager-drawer-manufacturer-lot-number></span><br><strong>Herstellungsdatum:</strong> <span data-lager-drawer-mfg-date></span><br><strong>MHD:</strong> <span data-lager-drawer-expiry-date></span><br><strong>Einkaufsdatum:</strong> <span data-lager-drawer-purchase-date></span></p></div>'
. '<div class="sg-card-segment sg-card-segment--body sg-card-segment--gray" data-lager-edit-form hidden><div class="sg-form-sections-card-wrapper" data-pattern="form-sections" aria-label="Aktive Charge bearbeiten">'
. '<form class="sg-form-sections-card" data-lager-edit-form-element><div class="sg-form-sections-card__body" data-pattern-part="form-body"><h2 class="sg-strong sg-form-sections-card__title">Aktive Charge bearbeiten</h2>'
. '<section class="sg-form-sections-card__chapter" aria-labelledby="lager-charge-traceability-title"><h2 id="lager-charge-traceability-title" class="sg-strong sg-form-sections-card__chapter-title">Rückverfolgbarkeit</h2>'
. '<div class="sg-form-sections-card__field-group"><label class="sg-labeled-input-row"><span class="sg-label">Naurua-Chargennummer</span><input class="sg-interaction-element sg-input-single-line sg-form-inactive-selectable" type="text" data-lager-lot-number readonly></label></div>'
. '<div class="sg-form-sections-card__field-group"><label class="sg-labeled-input-row"><span class="sg-label">Hersteller</span><select class="sg-interaction-element sg-input-single-line" id="lager-manufacturer" data-lager-manufacturer>' . $manufacturerOptions . '</select></label></div>'
. '<div class="sg-form-sections-card__field-group"><label class="sg-labeled-input-row"><span class="sg-label">Hersteller-Chargennummer</span><input class="sg-interaction-element sg-input-single-line" type="text" id="lager-manufacturer-lot" data-lager-manufacturer-lot></label></div>'
. '<div class="sg-form-sections-card__field-group"><label class="sg-labeled-input-row"><span class="sg-label">Herstellungsdatum</span><input class="sg-interaction-element sg-input-single-line" type="date" id="lager-mfg-date" data-lager-mfg-date></label></div>'
. '<div class="sg-form-sections-card__field-group"><label class="sg-labeled-input-row"><span class="sg-label">MHD</span><input class="sg-interaction-element sg-input-single-line" type="date" id="lager-expiry-date" data-lager-expiry-date></label></div>'
. '<div class="sg-form-sections-card__field-group"><label class="sg-labeled-input-row"><span class="sg-label">Einkaufsdatum</span><input class="sg-interaction-element sg-input-single-line" type="date" id="lager-purchase-date" data-lager-purchase-date></label></div>'
. '</section><section class="sg-form-sections-card__chapter" aria-labelledby="lager-charge-movements-title"><h2 id="lager-charge-movements-title" class="sg-strong sg-form-sections-card__chapter-title">Lagerbewegungen</h2>'
. '<p class="sg-form-sections-card__sentence">Buchungen werden zusätzlich zur Rückverfolgbarkeit als Lagerbewegung gespeichert. Positive Werte erhöhen den Bestand, negative Werte reduzieren ihn.</p>'
. '<div class="sg-form-sections-card__field-group"><label class="sg-labeled-input-row"><span class="sg-label">Zugang buchen</span><input class="sg-interaction-element sg-input-single-line" type="number" min="0" step="0.01" inputmode="decimal" data-lager-receipt-qty placeholder="Menge"></label></div>'
. '<div class="sg-form-sections-card__field-group"><label class="sg-labeled-input-row"><span class="sg-label">Bestandskorrektur (+ / − )</span><input class="sg-interaction-element sg-input-single-line" type="number" step="1" inputmode="numeric" data-lager-adjustment-qty placeholder="+ / − Menge"></label></div>'
. '<div class="sg-form-sections-card__field-group"><label class="sg-labeled-input-row"><span class="sg-label">Abgang buchen</span><input class="sg-interaction-element sg-input-single-line" type="number" min="0" step="0.01" inputmode="decimal" data-lager-out-qty placeholder="Menge"></label></div>'
. '</section><footer class="sg-form-sections-card__actions-segment" data-pattern-part="form-actions-segment"><div class="sg-form-sections-card__actions" data-pattern-part="form-actions"><button class="sg-interaction-element sg-button sg-button--active sg-form-sections-card__action" type="button" data-lager-edit-close>Abbrechen</button><button class="sg-interaction-element sg-button sg-button--process sg-form-sections-card__action" type="submit">Speichern</button></div></footer><p class="sg-body" data-lager-edit-status aria-live="polite"></p></div></form>'
. '</div></div>'
. '</article>'
. '<div data-lager-drawer-topic="historical-lots" aria-label="Vergangene Chargen">' . auth_render_lager_historical_lots ( $lagerProducts ) . '</div>'
. '<article class="sg-card sg-card--content-card" data-component="content-card" data-lager-drawer-topic="virtual-lot" aria-label="Virtuelle Charge">'
. '<div class="sg-card-segment sg-card-segment--header sg-card-segment--darkgreen" data-component-part="card-header"><div class="sg-strong sg-text-on-dark">Virtuelle Charge</div></div>'
. '<div class="sg-card-segment sg-card-segment--body" data-component-part="card-body"><p class="sg-body" data-lager-drawer-virtual-lot-value></p></div>'
@@ -360,6 +473,7 @@ function render_auth_home_page(array $user, array $otcProducts = [], array $best
[
'Bestellungen' => $bestellungenContent ,
'Lager' => $lagerContent ,
'Artikel' => auth_render_lager_content ([], $lagerArtikel , $lagerManufacturers , auth_csrf_token (), 'Artikel' ),
],
JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES
);
@@ -440,7 +554,8 @@ function render_auth_home_page(array $user, array $otcProducts = [], array $best
echo '</section>' ;
echo '</section>' ;
echo '<section class="sg-otc-order-overlay" data-otc-order-overlay data-open="false" aria-hidden="true">' ;
echo '<article class="sg-card sg-object-card sg-object-card--variable-height sg-otc-order-overlay__panel " data-pattern="object -card" aria-labelledby="otc-order-modal-title" role="dialog" aria-modal="true" >' ;
echo '<div class="sg-delete-confirmation-pattern__stage sg-delete-confirmation-pattern__host sg-object-card " data-pattern="overlay -card" data-dialog-open="false" data-otc-confirmation-stage >' ;
echo '<article class="sg-card sg-object-card sg-object-card--variable-height sg-otc-order-overlay__panel sg-delete-confirmation-pattern__target" data-pattern="object-card" aria-labelledby="otc-order-modal-title" role="dialog" aria-modal="true">' ;
echo '<header class="sg-card-segment sg-card-segment--header sg-card-segment--darkblue sg-object-card__header">' ;
echo '<h2 id="otc-order-modal-title" class="sg-strong sg-otc-order-overlay__title">Neuen OTC-Verkauf erfassen</h2>' ;
echo '<button class="sg-interaction-element sg-button sg-button--active sg-otc-order-overlay__close" type="button" data-otc-order-close>Schliessen</button>' ;
@@ -453,14 +568,6 @@ function render_auth_home_page(array $user, array $otcProducts = [], array $best
echo '<h2 id="otc-products-title" class="sg-strong sg-form-sections-card__chapter-title">PRODUKTE</h2>' ;
echo $otcProductRows ;
echo '</section>' ;
echo '<section class="sg-form-sections-card__chapter" aria-labelledby="otc-total-price-title">' ;
echo '<h2 id="otc-total-price-title" class="sg-strong sg-form-sections-card__chapter-title">Preis</h2>' ;
echo '<div class="sg-form-sections-card__field-group">' ;
echo '<label class="sg-label" for="otc-total-price">Preis alle Flaschen brutto (CHF)</label>' ;
echo '<input class="sg-interaction-element sg-input-single-line" type="number" id="otc-total-price" min="0" step="0.01" data-otc-order-total-price>' ;
echo '<p class="sg-body sg-form-sections-card__sentence">Der Preis wird durch die Anzahl aller Flaschen geteilt und das Ergebnis ist der Preis jeder einzelnen Flasche.</p>' ;
echo '</div>' ;
echo '</section>' ;
echo '<section class="sg-form-sections-card__chapter" aria-labelledby="otc-order-date-title">' ;
echo '<h2 id="otc-order-date-title" class="sg-strong sg-form-sections-card__chapter-title">Datum</h2>' ;
echo '<div class="sg-form-sections-card__field-group">' ;
@@ -518,13 +625,26 @@ function render_auth_home_page(array $user, array $otcProducts = [], array $best
echo '<footer class="sg-form-sections-card__actions-segment" data-pattern-part="form-actions-segment">' ;
echo '<div class="sg-form-sections-card__actions" data-pattern-part="form-actions">' ;
echo '<button class="sg-interaction-element sg-button sg-button--active sg-form-sections-card__action" type="button" data-otc-order-close>Abbrechen</button>' ;
echo '<button class="sg-interaction-element sg-button sg-button--process sg-button--process-inactive sg-form-sections-card__action" type="submit" disabled aria-disabled="true" data-otc-order-submit>Verkaufen </button>' ;
echo '<button class="sg-interaction-element sg-button sg-button--process sg-button--process-inactive sg-form-sections-card__action" type="submit" disabled aria-disabled="true" data-otc-order-submit>Weiter </button>' ;
echo '</div>' ;
echo '</footer>' ;
echo '</form>' ;
echo '</div>' ;
echo '</div>' ;
echo '</article>' ;
echo '<article class="sg-card sg-card--overlay-host sg-delete-confirmation-pattern__floating-card" aria-label="Verkaufsbestätigung" role="dialog" aria-modal="true" aria-labelledby="otc-confirmation-title" data-otc-confirmation-card hidden>' ;
echo '<div class="sg-card-segment sg-card-segment--body sg-delete-confirmation-pattern__body">' ;
echo '<p class="sg-body sg-delete-confirmation-pattern__text" id="otc-confirmation-title"><strong>Verkauf bestätigen</strong></p>' ;
echo '<div class="sg-delete-confirmation-pattern__list" data-otc-confirmation-products aria-label="Produkte"></div>' ;
echo '<p class="sg-body sg-delete-confirmation-pattern__text"><strong>Gesamtzahl Flaschen: <span data-otc-confirmation-quantity>0</span></strong></p>' ;
echo '<p class="sg-body sg-delete-confirmation-pattern__text"><strong>Gesamtbetrag: CHF <span data-otc-confirmation-total>0.00</span></strong></p>' ;
echo '<div class="sg-delete-confirmation-pattern__actions">' ;
echo '<button class="sg-interaction-element sg-button sg-button--active" type="button" data-otc-confirmation-cancel>Abbrechen</button>' ;
echo '<button class="sg-interaction-element sg-button sg-button--process" type="button" data-otc-confirmation-submit>Verkaufen</button>' ;
echo '</div>' ;
echo '</div>' ;
echo '</article>' ;
echo '</div>' ;
echo '</section>' ;
echo '<script src="/scripts/help-icon-overlays.js"></script>' ;
echo '<script>' ;
@@ -547,7 +667,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 " if (activeEntry === 'Lager' || activeEntry === 'Artikel' ) { initLagerBindings(); } " ;
echo " menu.innerHTML = ''; " ;
echo " entries.forEach((entry, index) => { " ;
echo " const button = document.createElement('button'); " ;
@@ -568,7 +688,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 " if (entryName === 'Lager' || entryName === 'Artikel' ) { initLagerBindings(); } " ;
echo " }); " ;
echo " }); " ;
echo " }; " ;
@@ -647,7 +767,6 @@ function render_auth_home_page(array $user, array $otcProducts = [], array $best
echo " mediaQuery.addEventListener('change', syncMode); " ;
echo " })(); " ;
echo " let bestellungenTableHandlersInstalled = false; " ;
echo " let bestellungenRealtimeSource = null; " ;
echo " let lagerBindingsInstalled = false; " ;
echo " function initLagerBindings() { " ;
echo " if (lagerBindingsInstalled) { return; } " ;
@@ -694,15 +813,29 @@ function render_auth_home_page(array $user, array $otcProducts = [], array $best
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)); document.body.classList.toggle('sg-card-list-page-drawer-open', open); " ;
echo " }; " ;
echo " const getLagerWarningTitle = (trigger) => trigger.dataset.lagerDrawerForecastStatus === 'depleted' ? 'Warnung: Charge ist aufgebraucht' : (trigger.dataset.lagerDrawerForecastMhdWarning === 'true' ? 'Warnung: MHD wird vor Chargenende erreicht' : (trigger.dataset.lagerDrawerForecastWarning === 'true' ? 'Warnung: Charge wird innerhalb von drei Monaten aufgebraucht sein' : 'Keine Warnung')); " ;
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(); if (window.matchMedia('(max-width: 767px)').matches) { openLagerMobileDetail(trigger); return; } 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 = false; } drawer.querySelector('[data-lager-drawer-value-label]').textContent = type === 'Produkt' ? 'Anzahl im Lager:' : 'Artikelnummer:'; drawer.querySelector('[data-lager-drawer-value-value]').textContent = value; drawer.querySelectorAll('[data-lager-drawer-topic]').forEach((card) => { card.hidden = type !== 'Produkt'; }); if (type === 'Produkt') { const forecast = trigger.dataset.lagerDrawerForecast || 'Keine Prognose'; const warning = trigger.dataset.lagerDrawerForecastWarning === 'true' || trigger.dataset.lagerDrawerForecastMhdWarning === 'true'; const warningValue = drawer.querySelector('[data-lager-drawer-warning-value]'); if (warningValue) { warningValue.textContent = warning ? forecast : 'Keine akute Warnung.'; warningValue.classList.toggle('sg-strong', warning); warningValue.classList.toggle('lager-forecast-warning', trigger.dataset.lagerDrawerForecastWarning === 'true'); warningValue.classList.toggle('lager-forecast-mhd-warning', trigger.dataset.lagerDrawerForecastMhdWarning === 'true'); } drawer.querySelector('[data-lager-drawer-lot-number]').textContent = trigger.dataset.lagerDrawerLotNumber || 'Nicht hinterlegt'; drawer.querySelector('[data-lager-drawer-manufacturer-name]').textContent = trigger.dataset.lagerDrawerManufacturerName || 'Nicht hinterlegt'; drawer.querySelector('[data-lager-drawer-manufacturer-lot-number]').textContent = trigger.dataset.lagerDrawerManufacturerLotNumber || 'Nicht hinterlegt'; drawer.querySelector('[data-lager-drawer-mfg-date]').textContent = trigger.dataset.lagerDrawerMfgDate || 'Nicht hinterlegt'; drawer.querySelector('[data-lager-drawer-expiry-date]').textContent = trigger.dataset.lagerDrawerExpiryDate || 'Nicht hinterlegt'; drawer.querySelector('[data-lager-drawer-purchase-date]').textContent = trigger.dataset.lagerDrawerPurchaseDate || 'Nicht hinterlegt'; const virtualLot = trigger.dataset.lagerDrawerVirtualLotNumber || ''; drawer.querySelector('[data-lager-drawer-virtual-lot-value]').textContent = virtualLot ? 'Virtuelle Charge: ' + virtualLot : 'Keine virtuelle Fehlbestandscharge vorhanden.'; const manufacturer = drawer.querySelector('[data-lager-manufacturer]'); if (manufacturer) { manufacturer.value = trigger.dataset.lagerDrawerManufacturerPartyId || ''; } const manufacturerLot = drawer.querySelector('[data-lager-manufacturer-lot]'); if (manufacturerLot) { manufacturerLot.value = trigger.dataset.lagerDrawerManufacturerLotNumber || ''; } const mfgDate = drawer.querySelector('[data-lager-mfg-date]'); if (mfgDate) { mfgDate.value = trigger.dataset.lagerDrawerMfgDate || ''; } const expiryDate = drawer.querySelector('[data-lager-expiry-date]'); if (expiryDate) { expiryDate.value = trigger.dataset.lagerDrawerExpiryDate || ''; } const purchaseDate = drawer.querySelector('[data-lager-purchase-date]'); if (purchaseDate) { purchaseDate.value = trigger.dataset.lagerDrawerPurchaseDate || ''; } const editForm = drawer.querySelector('[data-lager-edit-form]'); if (editForm) { editForm.hidden = true; } drawer.dataset.lagerActiveLotId = trigger.dataset.lagerDrawerLotId || ''; } 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 " contentRoot.addEventListener('click', (event) => { const trigger = event.target.closest('[data-lager-drawer-open]'); if (!trigger || trigger.dataset.lagerDrawerType !== 'Produkt' || window.matchMedia('(max-width: 767px)').matches) { return; } const header = contentRoot.querySelector('[data-lager-drawer-warning-header]'); const title = contentRoot.querySelector('[data-lager-drawer-warning-title]'); if (!header || !title) { return; } const forecastWarning = trigger.dataset.lagerDrawerForecastWarning === 'true'; const mhdWarning = trigger.dataset.lagerDrawerForecastMhdWarning === 'true'; title.textContent = getLagerWarningTitle(trigger); header.classList.toggle('sg-card-segment--darkgreen', !forecastWarning && !mhdWarning); header.classList.toggle('sg-card-segment--signal-red', forecastWarning); header.classList.toggle('sg-card-segment--signal-yellow', mhdWarning); }); " ;
echo " contentRoot.addEventListener('click', (event) => { const trigger = event.target.closest('[data-lager-drawer-open]'); if (!trigger || trigger.dataset.lagerDrawerType !== 'Produkt' || window.matchMedia('(max-width: 767px)').matches) { return; } setLagerEditMode(false); const drawer = getDrawer(); const currentQty = drawer?.querySelector('[data-lager-drawer-current-qty]'); const qtyIn = drawer?.querySelector('[data-lager-drawer-qty-in]'); const lotNumber = drawer?.querySelector('[data-lager-lot-number]'); if (currentQty) { currentQty.textContent = trigger.dataset.lagerDrawerCurrentQty || '0'; } if (qtyIn) { qtyIn.textContent = trigger.dataset.lagerDrawerQtyIn || '0'; } if (lotNumber) { lotNumber.value = trigger.dataset.lagerDrawerLotNumber || ''; } const productId = trigger.closest('[data-lager-row]')?.dataset.lagerId || ''; contentRoot.querySelectorAll('[data-lager-historical-product]').forEach((product) => { product.hidden = product.dataset.lagerHistoricalProduct !== productId; }); }); " ;
echo " const postLagerChange = async (payload) => { const response = await fetch('/api/lager-charge.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify( { ...payload, csrfToken: contentRoot.querySelector('[data-lager-csrf-token]')?.dataset.lagerCsrfToken || '' }) }); const result = await response.json(); if (!response.ok || result.ok !== true) { throw new Error(result.error || 'Speichern fehlgeschlagen'); } return result; }; " ;
echo " contentRoot.addEventListener('click', (event) => { const editButton = event.target.closest('[data-lager-edit-open]'); if (editButton) { const drawer = getDrawer(); const editForm = drawer?.querySelector('[data-lager-edit-form]'); if (editForm) { editForm.hidden = false; } return; } const closeButton = event.target.closest('[data-lager-edit-close]'); if (closeButton) { const drawer = getDrawer(); const editForm = drawer?.querySelector('[data-lager-edit-form]'); if (editForm) { editForm.hidden = true; } } }); " ;
echo " contentRoot.addEventListener('submit', async (event) => { const editForm = event.target.closest('[data-l ager-e dit-form-element]'); const manufacturerForm = event.target.closest('[data-lager-manufacturer-form]'); if (!editForm && !manufacturerForm) { return; } event.preventDefault(); const drawer = getDrawer(); const status = drawer?.querySelector('[data-lager-edit-status]'); if (status) { status.textContent = 'Speichern läuft …'; } try { if (manufacturerForm) { const result = await postLagerChange( { action: 'create_manufacturer', name: manufacturerForm.querySelector('[data-lager-new-manufacturer-name]')?.value || '', email: manufacturerForm.querySelector('[data-lager-new-manufacturer-email]')?.value || '', phone: manufacturerForm.querySelector('[data-lager-new-manufacturer-phone]')?.value || '' }); const select = drawer?.querySelector('[data-lager-manufacturer]'); if (select && result.manufacturer) { const option = document.createElement('option'); option.value = String(result.manufacturer.id); option.textCont ent = result.manufacturer.name; select.appendChild(option); select.value = String(result.manufacturer.id); } manufacturerForm.reset(); if (status) { status.textContent = 'Hersteller angelegt. Bitte Charge speichern.'; } return; } await postLagerChange( { action: 'update_lot', lotId: drawer?.dataset.lagerActiveLotId || '', manufacturerPartyId: drawer?.querySelector('[data-lager-manufacturer]')?.value || null, manufacturerLotNumber: drawer?.querySelector('[data-lager-manufacturer-lot]')?.value || '', mfgDate: drawer?.querySelector('[data-lager-mfg-date]')?.value || '', expiryDate: drawer?.querySelector('[data-lager-expiry-date]')?.value || '', purchaseDate: drawer?.querySelector('[data-lager-purchase-date]')?.value || '' }); window.location.reload(); } catch (error) { if (status) { status.textCont ent = error instanceof Error ? error.message : 'Speichern fehlgeschlagen' ; } } }) ;" ;
echo " const setL agerE ditMode = (editing) => { const drawer = getDrawer(); const readView = drawer?.querySelector('[data-lager-read-view]'); const editForm = drawer?.querySelector('[data-lager-edit-form]'); if (readView) { readView.hidd en = editing; } if (editForm) { editForm.hidd en = !editing ; } };" ;
echo " contentRoot.addEventListener('click', (event) => { if (event.target.closest('[data-lager-edit-open]')) { setLagerEditMode(true); } if (event.target.closest('[data-lager-edit-close]')) { clearLagerMovementInputs(); setLagerEditMode(false); } }); " ;
echo " const syncLagerReadView = () => { const drawer = getDrawer(); if (!drawer) { return; } const manufacturer = drawer.querySelector('[data-lager-manufacturer]'); const values = { '[data-lager-drawer-manufacturer-name]': manufacturer?.selectedOptions?.[0]?.textContent || 'Nicht hinterlegt', '[data-lager-drawer-manufacturer-lot-number]': drawer.querySelector('[data-lager-manufacturer-lot]')?.value || 'Nicht hinterlegt', '[data-lager-drawer-mfg-date]': drawer.querySelector('[data-lager-mfg-date]')?.value || 'Nicht hinterlegt', '[data-lager-drawer-expiry-date]': drawer.querySelector('[data-lager-expiry-date]')?.value || 'Nicht hinterlegt', '[data-lager-drawer-purchase-date]': drawer.querySelector('[data-lager-purchase-date]')?.value || 'Nicht hinterlegt' }; Object.entries(values).forEach(([selector, value]) => { const target = drawer.querySelector(selector); if (target) { target.textContent = value; } }); const trigger = contentRoot.querySelector('[data-lager-drawer-open][aria-expanded= \" true \" ]'); if (trigger) { trigger.dataset.lagerDrawerManufacturerName = values['[data-lager-drawer-manufacturer-name]']; trigger.dataset.lagerDrawerManufacturerLotNumber = values['[data-lager-drawer-manufacturer-lot-number]']; trigger.dataset.lagerDrawerMfgDate = values['[data-lager-drawer-mfg-date]']; trigger.dataset.lagerDrawerExpiryDate = values['[data-lager-drawer-expiry-date]']; trigger.dataset.lagerDrawerPurchaseDate = values['[data-lager-drawer-purchase-date]']; } }; " ;
echo " const syncLagerBalanceView = (balance) => { const drawer = getDrawer(); if (!drawer || !balance) { return; } const currentQty = drawer.querySelector('[data-lager-drawer-current-qty]'); const qtyIn = drawer.querySelector('[data-lager-drawer-qty-in]'); if (currentQty) { currentQty.textContent = String(balance.qty_net ?? 0); } if (qtyIn) { qtyIn.textContent = String(balance.qty_in ?? 0); } const trigger = contentRoot.querySelector('[data-lager-drawer-open][aria-expanded= \" true \" ]'); if (trigger) { trigger.dataset.lagerDrawerCurrentQty = String(balance.qty_net ?? 0); trigger.dataset.lagerDrawerQtyIn = String(balance.qty_in ?? 0); } }; " ;
echo " const clearLagerMovementInputs = () => { const drawer = getDrawer(); if (!drawer) { return; } drawer.querySelectorAll('[data-lager-receipt-qty], [data-lager-adjustment-qty], [data-lager-out-qty]').forEach((input) => { input.value = ''; }); }; " ;
echo " const syncLagerOpenLotCard = (lot, balance) => { const drawer = getDrawer(); const card = drawer?.querySelector('[data-lager-open-card= \" ' + (lot?.id || '') + ' \" ]'); if (!card) { return; } const values = { '[data-lager-open-card-lot-number]': lot?.lot_number || 'Nicht hinterlegt', '[data-lager-open-card-qty-net]': String(balance?.qty_net ?? 0), '[data-lager-open-card-qty-in]': String(balance?.qty_in ?? 0), '[data-lager-open-card-manufacturer]': drawer.querySelector('[data-lager-new-manufacturer]')?.selectedOptions?.[0]?.textContent || 'Nicht hinterlegt', '[data-lager-open-card-manufacturer-lot]': lot?.manufacturer_lot_number || 'Nicht hinterlegt', '[data-lager-open-card-mfg-date]': lot?.mfg_date || 'Nicht hinterlegt', '[data-lager-open-card-expiry-date]': lot?.expiry_date || 'Nicht hinterlegt', '[data-lager-open-card-purchase-date]': lot?.purchase_date || 'Nicht hinterlegt' }; Object.entries(values).forEach(([selector, value]) => { const target = card.querySelector(selector); if (target) { target.textContent = value; } }); }; " ;
echo " contentRoot.addEventListener('click', (event) => { const trigger = event.target.closest('[data-lager-drawer-open]'); if (!trigger || trigger.dataset.lagerDrawerType !== 'Produkt' || window.matchMedia('(max-width: 767px)').matches) { return; } const drawer = getDrawer(); const productId = trigger.closest('[data-lager-row]')?.dataset.lagerId || ''; const openProduct = drawer?.querySelector('[data-lager-open-product= \" ' + productId + ' \" ]'); drawer?.querySelectorAll('[data-lager-open-product]').forEach((product) => { product.hidden = product !== openProduct; product.querySelectorAll('[data-lager-open-edit-form]').forEach((form) => { form.hidden = true; }); product.querySelectorAll('[data-lager-open-card]').forEach((readView) => { readView.hidden = false; }); }); const newForm = drawer?.querySelector('[data-lager-drawer-topic= \" new-lot-form \" ]'); const newAction = drawer?.querySelector('[data-lager-drawer-topic= \" new-lot-action \" ]'); if (newForm) { newForm.hidden = true; } if (newAction) { newAction.hidden = false; } const notificationWrapper = drawer?.querySelector('[data-lager-new-lot-notification-wrapper]'); const notification = drawer?.querySelector('[data-lager-new-lot-notification]'); const shortageQty = trigger.dataset.lagerDrawerVirtualShortageQty || '0'; const hasShortage = shortageQty !== '0' && shortageQty !== '0,00'; if (notificationWrapper) { notificationWrapper.hidden = !hasShortage; } if (notification && hasShortage) { notification.textContent = 'Für dieses Produkt bestehen virtuelle Fehlbestände in Höhe von ' + shortageQty + '. Die Eingangsmenge wird nach Aktivierung der Charge zur Nachallokation verwendet.'; } const setValue = (selector, value) => { const field = drawer?.querySelector(selector); if (field) { field.value = value || ''; } }; setValue('[data-lager-new-lot-number]', trigger.dataset.lagerOpenLotNumber); setValue('[data-lager-new-manufacturer]', trigger.dataset.lagerOpenManufacturerPartyId); setValue('[data-lager-new-manufacturer-lot]', trigger.dataset.lagerOpenManufacturerLotNumber); setValue('[data-lager-new-mfg-date]', trigger.dataset.lagerOpenMfgDate); setValue('[data-lager-new-expiry-date]', trigger.dataset.lagerOpenExpiryDate); setValue('[data-lager-new-purchase-date]', trigger.dataset.lagerOpenPurchaseDate); if (drawer) { drawer.dataset.lagerOpenLotId = trigger.dataset.lagerOpenLotId || ''; drawer.dataset.lagerProductId = productId; } }); " ;
echo " contentRoot.addEventListener('click', (event) => { const openButton = event.target.closest('[data-lager-new-lot-open]'); if (openButton) { const drawer = getDrawer(); const form = drawer?.querySelector('[data-lager-drawer-topic= \" new-lot-form \" ]'); const action = drawer?.querySelector('[data-lager-drawer-topic= \" new-lot-action \" ]'); if (form) { form.hidden = false; } if (action) { action.hidden = true; } return; } if (event.target.closest('[data-lager-new-lot-close]')) { const drawer = getDrawer(); const form = drawer?.querySelector('[data-lager-drawer-topic= \" new-lot-form \" ]'); const action = drawer?.querySelector('[data-lager-drawer-topic= \" new-lot-action \" ]'); if (form) { form.hidden = true; } if (action) { action.hidden = false; } } }); " ;
echo " contentRoot.addEventListener('click', (event) => { const editButton = event.target.closest('[data-lager-open-edit-open]'); if (editButton) { const card = editButton.closest('[data-lager-open-card-wrapper]'); const form = card?.querySelector('[data-lager-open-edit-form]'); const readView = card?.querySelector('[data-lager-open-card]'); if (form) { form.hidden = false; } if (readView) { readView.hidden = true; } return; } const closeButton = event.target.closest('[data-lager-open-edit-close]'); if (closeButton) { const card = closeButton.closest('[data-lager-open-card-wrapper]'); const form = card?.querySelector('[data-lager-open-edit-form]'); const readView = card?.querySelector('[data-lager-open-card]'); if (form) { form.hidden = true; } if (readView) { readView.hidden = false; } } }); " ;
echo " contentRoot.addEventListener('submit', async (event) => { const openLotForm = event.target.closest('[data-lager-open-edit-form-element]'); if (!openLotForm) { return; } event.preventDefault(); const drawer = getDrawer(); const card = openLotForm.closest('[data-lager-open-card-wrapper]'); const status = openLotForm.querySelector('[data-lager-open-edit-status]'); if (status) { status.textContent = 'Speichern läuft …'; } try { const result = await postLagerChange( { action: 'prepare_lot', productId: drawer?.dataset.lagerProductId || '', lotId: card?.dataset.lagerOpenCardWrapper || '', lotNumber: openLotForm.querySelector('[data-lager-open-lot-number]')?.value || '', manufacturerPartyId: openLotForm.querySelector('[data-lager-open-manufacturer]')?.value || null, manufacturerLotNumber: openLotForm.querySelector('[data-lager-open-manufacturer-lot]')?.value || '', mfgDate: openLotForm.querySelector('[data-lager-open-mfg-date]')?.value || '', expiryDate: openLotForm.querySelector('[data-lager-open-expiry-date]')?.value || '', purchaseDate: openLotForm.querySelector('[data-lager-open-purchase-date]')?.value || '', receiptQty: openLotForm.querySelector('[data-lager-open-receipt-qty]')?.value || '' }); syncLagerOpenLotCard(result.lot, result.balance); openLotForm.querySelector('[data-lager-open-receipt-qty]').value = ''; openLotForm.setAttribute('hidden', 'hidden'); card?.querySelector('[data-lager-open-card]')?.removeAttribute('hidden'); if (status) { status.textContent = ''; } } catch (error) { if (status) { status.textContent = error instanceof Error ? error.message : 'Speichern fehlgeschlagen'; } } }); " ;
echo " contentRoot.addEventListener('submit', async (event) => { const newLotForm = event.target.closest('[data-lager-new-lot-form-element]'); if (!newLotForm) { return; } event.preventDefault(); const drawer = getDrawer(); const status = drawer?.querySelector('[data-lager-new-lot-status]'); if (status) { status.textContent = 'Speichern läuft …'; } try { const result = await postLagerChange( { action: 'prepare_lot', productId: drawer?.dataset.lagerProductId || '', lotId: drawer?.dataset.lagerOpenLotId || '', lotNumber: drawer?.querySelector('[data-lager-new-lot-number]')?.value || '', manufacturerPartyId: drawer?.querySelector('[data-lager-new-manufacturer]')?.value || null, manufacturerLotNumber: drawer?.querySelector('[data-lager-new-manufacturer-lot]')?.value || '', mfgDate: drawer?.querySelector('[data-lager-new-mfg-date]')?.value || '', expiryDate: drawer?.querySelector('[data-lager-new-expiry-date]')?.value || '', purchaseDate: drawer?.querySelector('[data-lager-new-purchase-date]')?.value || '', receiptQty: drawer?.querySelector('[data-lager-new-receipt-qty]')?.value || '' }); syncLagerOpenLotCard(result.lot, result.balance); const form = drawer?.querySelector('[data-lager-drawer-topic= \" new-lot-form \" ]'); const action = drawer?.querySelector('[data-lager-drawer-topic= \" new-lot-action \" ]'); if (form) { form.hidden = true; } if (action) { action.hidden = false; } if (status) { status.textContent = ''; } } catch (error) { if (status) { status.textContent = error instanceof Error ? error.message : 'Speichern fehlgeschlagen'; } } }); " ;
echo " contentRoot.addEventListener('submit', async (event) => { const editForm = event.target.closest('[data-lager-edit-form-element]'); if (!editForm) { return; } event.preventDefault(); const drawer = getDrawer(); const status = drawer?.querySelector('[data-lager-edit-status]'); if (status) { status.textContent = 'Speichern läuft …'; } try { const result = await postLagerChange( { action: 'update_lot', lotId: drawer?.dataset.lagerActiveLotId || '', manufacturerPartyId: drawer?.querySelector('[data-lager-manufacturer]')?.value || null, manufacturerLotNumber: drawer?.querySelector('[data-lager-manufacturer-lot]')?.value || '', mfgDate: drawer?.querySelector('[data-lager-mfg-date]')?.value || '', expiryDate: drawer?.querySelector('[data-lager-expiry-date]')?.value || '', purchaseDate: drawer?.querySelector('[data-lager-purchase-date]')?.value || '', receiptQty: drawer?.querySelector('[data-lager-receipt-qty]')?.value || '', adjustmentQty: drawer?.querySelector('[data-lager-adjustment-qty]')?.value || '', outQty: drawer?.querySelector('[data-lager-out-qty]')?.value || '' }); syncLagerReadView(); syncLagerBalanceView(result.balance); clearLagerMovementInputs(); setLagerEditMode(false); if (status) { status.textContent = ''; } } catch (error) { if (status) { status.textContent = error instanceof Error ? error.message : 'Speichern fehlgeschlagen'; } } }); " ;
echo " document.addEventListener('click', (event) => { if (event.target.closest('[data-lager-detail-back]')) { event.preventDefault(); closeLagerMobileDetail(); } }); " ;
echo " document.addEventListener('keydown', (event) => { if (event.key === 'Escape') { closeDrawer(); closeLagerMobileDetail(); } }); " ;
echo " window.addEventListener('popstate', (event) => { if (lagerMobileDetailOpen && (!event.state || event.state.lagerView !== 'detail')) { restoreLagerMobileDetail(); } }); " ;
@@ -779,7 +912,7 @@ function render_auth_home_page(array $user, array $otcProducts = [], array $best
echo " '</article>', " ;
echo " '</div>', " ;
echo " '</div>', " ;
echo " '<div data-bestellungen-mobile-cards><article class= \" sg-card sg-vsf-drawer -card \" data-component= \" card \" data-pattern= \" card \" aria-label= \" Bestell-Details \" >', " ;
echo " '<div data-bestellungen-mobile-cards><article class= \" sg-card sg-card--content -card \" data-component= \" content-card \" aria-label= \" Bestell-Details \" >', " ;
echo " '<div class= \" sg-card-segment sg-card-segment--body sg-vsf-drawer-card__content \" >', " ;
echo " '<p class= \" sg-body \" ><strong>Bestellnummer:</strong> <span>' + escapeHtml(orderNumber) + '</span></p>', " ;
echo " '<p class= \" sg-body \" ><strong>Bestelldatum:</strong> <span>' + escapeHtml(orderDate) + '</span></p>', " ;
@@ -827,9 +960,9 @@ function render_auth_home_page(array $user, array $otcProducts = [], array $best
echo " const value = (label, content) => '<p class= \" sg-body \" ><strong>' + escapeHtml(label) + ':</strong> ' + escapeHtml(content || 'Nicht vorhanden') + '</p>'; " ;
echo " const renderCards = (order) => { " ;
echo " const c = order.customer || { }; const a = order.amounts || { }; const address = [[c.street, c.house_number].filter(Boolean).join(' '), [c.zip, c.city].filter(Boolean).join(' '), c.country_name].filter(Boolean).join('<br>'); " ;
echo " const lines = (order.lines || []).map((line) => '<div class= \" sg-card-segment sg-card-segment--body sg-vsf-drawer-card__content \" ><p class= \" sg-body \" ><strong>' + escapeHtml(line.item_name) + '</strong> · ' + escapeHtml(line.item_code) + ' · Menge: ' + escapeHtml(String(line.qty)) + '</p>' + value('Positionsstatus', line.line_status) + value('Einzelpreis', formatAmount(line.unit_price, a.currency || 'CHF')) + value('Positionssumme', formatAmount(line.line_total, a.currency || 'CHF')) + '</div>').join(''); " ;
echo " const lines = (order.lines || []).map((line) => '<div class= \" sg-card-segment sg-card-segment--body \" ><p class= \" sg-body \" ><strong>' + escapeHtml(line.item_name) + '</strong> · ' + escapeHtml(line.item_code) + ' · Menge: ' + escapeHtml(String(line.qty)) + '</p>' + value('Positionsstatus', line.line_status) + value('Einzelpreis', formatAmount(line.unit_price, a.currency || 'CHF')) + value('Positionssumme', formatAmount(line.line_total, a.currency || 'CHF')) + '</div>').join(''); " ;
echo " const allocations = (order.lines || []).flatMap((line) => (line.product_allocations || []).map((item) => '<div class= \" sg-body \" ><strong>' + escapeHtml(item.product_name) + ':</strong> ' + escapeHtml(item.lot_number || 'Nicht zugeordnet') + ' · ' + escapeHtml(item.allocated_qty === null ? 'Nicht zugeordnet' : String(item.allocated_qty)) + ' · ' + escapeHtml(item.allocation_status || 'Nicht vorhanden') + (item.lot_status === 'virtual_shortage' ? ' · Virtuelle Fehlbestandscharge' : '') + '</div>')).join(''); " ;
echo " return '<article class= \" sg-card sg-vsf-drawer -card \" data-component= \" card \" data-pattern= \" card \" aria-label= \" Bestellübersicht \" ><header class= \" sg-card-segment sg-card-segment--header sg-card-segment--darkblue sg-vsf-drawer-card__header \" ><div class= \" sg-strong \" >Bestellübersicht</div></header><div class= \" sg-card-segment sg-card-segment--body sg-vsf-drawer-card__content \" >' + value('Bestellnummer', order.external_ref) + value('Bestelldatum', formatDate(order.order_date)) + value('Verkaufskanal', order.order_source) + value('Bestellstatus', order.order_status) + value('Zahlungsstatus', order.payment_status) + value('Zahlungsart', order.payment_method) + value('Versandart', order.shipping_method) + '<p class= \" sg-body \" ><strong>Kunde:</strong> ' + escapeHtml([c.first_name, c.last_name].filter(Boolean).join(' ') || 'Nicht vorhanden') + '</p><p class= \" sg-body \" ><strong>Lieferadresse:</strong><br>' + address + '</p>' + value('Netto', formatAmount(a.net, a.currency || 'CHF')) + value('Versand', formatAmount(a.shipping, a.currency || 'CHF')) + value('Rabatt', formatAmount(a.discount, a.currency || 'CHF')) + value('Steuer', formatAmount(a.tax, a.currency || 'CHF')) + value('Gesamtsumme', formatAmount(a.total, a.currency || 'CHF')) + '</div></article><article class= \" sg-card sg-vsf-drawer -card \" data-component= \" card \" data-pattern= \" card \" aria-label= \" Bestellte Artikel \" ><header class= \" sg-card-segment sg-card-segment--header sg-card-segment--darkblue sg-vsf-drawer-card__header \" ><div class= \" sg-strong \" >Bestellte Artikel</div></header>' + (lines || '<div class= \" sg-card-segment sg-card-segment--body sg-vsf-drawer-card__c ont ent \" ><p class= \" sg-body \" >Keine Artikel vorhanden.</p></div>') + '</article><article class= \" sg-card sg-vsf-drawer -card \" data-component= \" card \" data-pattern= \" card \" aria-label= \" Chargenzuordnung \" ><header class= \" sg-card-segment sg-card-segment--header sg-card-segment--darkblue sg-vsf-drawer-card__header \" ><div class= \" sg-strong \" >Chargenzuordnung</div></header><div class= \" sg-card-segment sg-card-segment--body sg-vsf-drawer-card__content \" >' + (allocations || '<p class= \" sg-body \" >Keine Chargenzuordnung vorhanden.</p>') + '</div></article>'; " ;
echo " return '<article class= \" sg-card sg-card--content -card \" data-component= \" content-card \" aria-label= \" Bestellübersicht \" ><header class= \" sg-card-segment sg-card-segment--header sg-card-segment--darkgreen \" data-component-part= \" card-header \" ><div class= \" sg-strong sg-text-on-dark \" >Bestellübersicht</div></header><div class= \" sg-card-segment sg-card-segment--body \" data-component-part= \" card-body \" >' + value('Bestellnummer', order.external_ref) + value('Bestelldatum', formatDate(order.order_date)) + value('Verkaufskanal', order.order_source) + value('Bestellstatus', order.order_status) + value('Zahlungsstatus', order.payment_status) + value('Zahlungsart', order.payment_method) + value('Versandart', order.shipping_method) + '<p class= \" sg-body \" ><strong>Kunde:</strong> ' + escapeHtml([c.first_name, c.last_name].filter(Boolean).join(' ') || 'Nicht vorhanden') + '</p><p class= \" sg-body \" ><strong>Lieferadresse:</strong><br>' + address + '</p>' + value('Netto', formatAmount(a.net, a.currency || 'CHF')) + value('Versand', formatAmount(a.shipping, a.currency || 'CHF')) + value('Rabatt', formatAmount(a.discount, a.currency || 'CHF')) + value('Steuer', formatAmount(a.tax, a.currency || 'CHF')) + value('Gesamtsumme', formatAmount(a.total, a.currency || 'CHF')) + '</div></article><article class= \" sg-card sg-card--content -card \" data-component= \" content-card \" aria-label= \" Bestellte Artikel \" ><header class= \" sg-card-segment sg-card-segment--header sg-card-segment--darkgreen \" data-component-part= \" card-header \" ><div class= \" sg-strong sg-text-on-dark \" >Bestellte Artikel</div></header>' + (lines || '<div class= \" sg-card-segment sg-card-segment--body \" data-comp onent-part= \" card-body \" ><p class= \" sg-body \" >Keine Artikel vorhanden.</p></div>') + '</article><article class= \" sg-card sg-card--content -card \" data-component= \" content-card \" aria-label= \" Chargenzuordnung \" ><header class= \" sg-card-segment sg-card-segment--header sg-card-segment--darkgreen \" data-component-part= \" card-header \" ><div class= \" sg-strong sg-text-on-dark \" >Chargenzuordnung</div></header><div class= \" sg-card-segment sg-card-segment--body \" data-component-part= \" card-body \" >' + (allocations || '<p class= \" sg-body \" >Keine Chargenzuordnung vorhanden.</p>') + '</div></article>'; " ;
echo " }; " ;
echo " setDrawerState(true); " ;
echo " if (drawerTitle) { drawerTitle.textContent = trigger.dataset.orderNumber ? 'Bestellung ' + trigger.dataset.orderNumber : 'Bestellung'; } " ;
@@ -857,7 +990,7 @@ function render_auth_home_page(array $user, array $otcProducts = [], array $best
echo " document.body.appendChild(bestellungenMobileDetailHost); " ;
echo " document.body.classList.add('sg-bestellungen-mobile-detail-open'); " ;
echo " } " ;
echo " fetch('/api/bestellungen/detail.php?id=' + encodeURIComponent(trigger.dataset.orderId || ''), { credentials: 'same-origin' }).then((response) => response.ok ? response.json() : Promise.reject(new Error('Bestelldetails konnten nicht geladen werden.'))).then((payload) => { if (!payload.ok || !bestellungenMobileDetailHost) { throw new Error('Bestelldetails konnten nicht geladen werden.'); } const order = payload.order; const cards = bestellungenMobileDetailHost.querySelector('[data-bestellungen-mobile-cards]'); const lines = (order.lines || []).map((line) => '<p class= \" sg-body \" ><strong>' + escapeHtml(line.item_name) + '</strong> · ' + escapeHtml(line.item_code) + ' · Menge: ' + escapeHtml(String(line.qty)) + ' · Status: ' + escapeHtml(line.line_status) + '</p>').join(''); const allocations = (order.lines || []).flatMap((line) => (line.product_allocations || []).map((item) => '<p class= \" sg-body \" ><strong>' + escapeHtml(item.product_name) + ':</strong> ' + escapeHtml(item.lot_number || 'Nicht zugeordnet') + ' · ' + escapeHtml(item.allocation_status || 'Nicht vorhanden') + '</p>')).join(''); if (cards) { cards.innerHTML = '<article class= \" sg-card sg-vsf-drawer -card \" ><header class= \" sg-card-segment sg-card-segment--header sg-card-segment--darkblue sg-vsf-drawer-card__header \" ><div class= \" sg-strong \" >Bestellübersicht</div></header><div class= \" sg-card-segment sg-card-segment--body sg-vsf-drawer-card__content \" ><p class= \" sg-body \" ><strong>Bestellnummer:</strong> ' + escapeHtml(order.external_ref) + '</p><p class= \" sg-body \" ><strong>Bestelldatum:</strong> ' + escapeHtml(formatDate(order.order_date)) + '</p><p class= \" sg-body \" ><strong>Verkaufskanal:</strong> ' + escapeHtml(order.order_source) + '</p><p class= \" sg-body \" ><strong>Status:</strong> ' + escapeHtml(order.order_status) + '</p></div></article><article class= \" sg-card sg-vsf-drawer -card \" ><header class= \" sg-card-segment sg-card-segment--header sg-card-segment--darkblue sg-vsf-drawer-card__header \" ><div class= \" sg-strong \" >Bestellte Artikel</div></header><div class= \" sg-card-segment sg-card-segment--body sg-vsf-drawer-card__content \" >' + (lines || '<p class= \" sg-body \" >Keine Artikel vorhanden.</p>') + '</div></article><article class= \" sg-card sg-vsf-drawer -card \" ><header class= \" sg-card-segment sg-card-segment--header sg-card-segment--darkblue sg-vsf-drawer-card__header \" ><div class= \" sg-strong \" >Chargenzuordnung</div></header><div class= \" sg-card-segment sg-card-segment--body sg-vsf-drawer-card__content \" >' + (allocations || '<p class= \" sg-body \" >Keine Chargenzuordnung vorhanden.</p>') + '</div></article>'; } }).catch((error) => { const cards = bestellungenMobileDetailHost?.querySelector('[data-bestellungen-mobile-cards]'); if (cards) { cards.innerHTML = '<p class= \" sg-body \" >' + escapeHtml(error.message) + '</p>'; } }); " ;
echo " fetch('/api/bestellungen/detail.php?id=' + encodeURIComponent(trigger.dataset.orderId || ''), { credentials: 'same-origin' }).then((response) => response.ok ? response.json() : Promise.reject(new Error('Bestelldetails konnten nicht geladen werden.'))).then((payload) => { if (!payload.ok || !bestellungenMobileDetailHost) { throw new Error('Bestelldetails konnten nicht geladen werden.'); } const order = payload.order; const cards = bestellungenMobileDetailHost.querySelector('[data-bestellungen-mobile-cards]'); const lines = (order.lines || []).map((line) => '<p class= \" sg-body \" ><strong>' + escapeHtml(line.item_name) + '</strong> · ' + escapeHtml(line.item_code) + ' · Menge: ' + escapeHtml(String(line.qty)) + ' · Status: ' + escapeHtml(line.line_status) + '</p>').join(''); const allocations = (order.lines || []).flatMap((line) => (line.product_allocations || []).map((item) => '<p class= \" sg-body \" ><strong>' + escapeHtml(item.product_name) + ':</strong> ' + escapeHtml(item.lot_number || 'Nicht zugeordnet') + ' · ' + escapeHtml(item.allocation_status || 'Nicht vorhanden') + '</p>')).join(''); if (cards) { cards.innerHTML = '<article class= \" sg-card sg-card--content -card \" ><header class= \" sg-card-segment sg-card-segment--header sg-card-segment--darkgreen \" ><div class= \" sg-strong sg-text-on-dark \" >Bestellübersicht</div></header><div class= \" sg-card-segment sg-card-segment--body \" ><p class= \" sg-body \" ><strong>Bestellnummer:</strong> ' + escapeHtml(order.external_ref) + '</p><p class= \" sg-body \" ><strong>Bestelldatum:</strong> ' + escapeHtml(formatDate(order.order_date)) + '</p><p class= \" sg-body \" ><strong>Verkaufskanal:</strong> ' + escapeHtml(order.order_source) + '</p><p class= \" sg-body \" ><strong>Status:</strong> ' + escapeHtml(order.order_status) + '</p></div></article><article class= \" sg-card sg-card--content -card \" ><header class= \" sg-card-segment sg-card-segment--header sg-card-segment--darkgreen \" ><div class= \" sg-strong sg-text-on-dark \" >Bestellte Artikel</div></header><div class= \" sg-card-segment sg-card-segment--body \" >' + (lines || '<p class= \" sg-body \" >Keine Artikel vorhanden.</p>') + '</div></article><article class= \" sg-card sg-card--content -card \" ><header class= \" sg-card-segment sg-card-segment--header sg-card-segment--darkgreen \" ><div class= \" sg-strong sg-text-on-dark \" >Chargenzuordnung</div></header><div class= \" sg-card-segment sg-card-segment--body \" >' + (allocations || '<p class= \" sg-body \" >Keine Chargenzuordnung vorhanden.</p>') + '</div></article>'; } }).catch((error) => { const cards = bestellungenMobileDetailHost?.querySelector('[data-bestellungen-mobile-cards]'); if (cards) { cards.innerHTML = '<p class= \" sg-body \" >' + escapeHtml(error.message) + '</p>'; } }); " ;
echo " bestellungenDetailOpen = true; " ;
echo " window.history.pushState( { bestellungenView: 'detail', orderId: trigger.dataset.orderId || '' }, '', window.location.href); " ;
echo " }; " ;
@@ -1002,35 +1135,6 @@ function render_auth_home_page(array $user, array $otcProducts = [], array $best
echo " } " ;
echo " } " ;
echo " }; " ;
echo " const connectBestellungenRealtime = () => { " ;
echo " if (!window.EventSource) { return null; } " ;
echo " if (bestellungenRealtimeSource) { " ;
echo " bestellungenRealtimeSource.close(); " ;
echo " } " ;
echo " const source = new EventSource('/api/realtime/bestellungen.php', { withCredentials: true }); " ;
echo " source.addEventListener('bestellungen.changed', (event) => { " ;
echo " const payload = parseJsonRealtimeEvent(event.data); " ;
echo " if (!payload) { return; } " ;
echo " const kind = String(payload.kind || ''); " ;
echo " const orderId = payload.orderId !== undefined && payload.orderId !== null ? String(payload.orderId) : ''; " ;
echo " if (kind === 'updated' && orderId !== '') { " ;
echo " void reloadBestellungenRow(orderId); " ;
echo " return; " ;
echo " } " ;
echo " void reloadBestellungenSection(); " ;
echo " }); " ;
echo " bestellungenRealtimeSource = source; " ;
echo " return source; " ;
echo " }; " ;
echo " const parseJsonRealtimeEvent = (raw) => { " ;
echo " if (typeof raw !== 'string' || raw.trim() === '') { return null; } " ;
echo " try { " ;
echo " const parsed = JSON.parse(raw); " ;
echo " return parsed && typeof parsed === 'object' ? parsed : null; " ;
echo " } catch (error) { " ;
echo " return null; " ;
echo " } " ;
echo " }; " ;
echo " if (!bestellungenTableHandlersInstalled) { " ;
echo " bestellungenTableHandlersInstalled = true; " ;
echo " document.addEventListener('click', (event) => { " ;
@@ -1096,14 +1200,12 @@ function render_auth_home_page(array $user, array $otcProducts = [], array $best
echo " } " ;
echo " } " ;
echo " bindTable(); " ;
echo " connectBestellungenRealtime(); " ;
echo " } " ;
echo " (() => { " ;
echo " const overlay = document.querySelector('[data-otc-order-overlay]'); " ;
echo " if (!overlay) { return; } " ;
echo " const form = overlay.querySelector('[data-otc-order-form]'); " ;
echo " const productInputs = Array.from(form.querySelectorAll('[data-otc-order-product]')); " ;
echo " const totalPriceInput = form.querySelector('[data-otc-order-total-price]'); " ;
echo " const orderDateInput = form.querySelector('[data-otc-order-date]'); " ;
echo " const orderDateDefaultValue = orderDateInput ? orderDateInput.value : ''; " ;
echo " const paymentRows = form.querySelector('[data-otc-payment-rows]'); " ;
@@ -1120,6 +1222,13 @@ function render_auth_home_page(array $user, array $otcProducts = [], array $best
echo " const errorEl = form.querySelector('[data-otc-order-error]'); " ;
echo " const successEl = form.querySelector('[data-otc-order-success]'); " ;
echo " const successDefaultText = successEl ? successEl.textContent : ''; " ;
echo " const confirmationStage = overlay.querySelector('[data-otc-confirmation-stage]'); " ;
echo " const confirmationCard = overlay.querySelector('[data-otc-confirmation-card]'); " ;
echo " const confirmationProducts = overlay.querySelector('[data-otc-confirmation-products]'); " ;
echo " const confirmationQuantity = overlay.querySelector('[data-otc-confirmation-quantity]'); " ;
echo " const confirmationTotal = overlay.querySelector('[data-otc-confirmation-total]'); " ;
echo " const confirmationSubmit = overlay.querySelector('[data-otc-confirmation-submit]'); " ;
echo " let pendingOrderData = null; " ;
echo " const openTriggers = () => document.querySelectorAll('[data-otc-order-open]'); " ;
echo " const setTriggerState = (expanded) => { " ;
echo " openTriggers().forEach((trigger) => { trigger.setAttribute('aria-expanded', String(expanded)); }); " ;
@@ -1154,7 +1263,7 @@ function render_auth_home_page(array $user, array $otcProducts = [], array $best
echo " clearSuccess(); " ;
echo " ensureCurrentOrderDate(); " ;
echo " requestAnimationFrame(() => { " ;
echo " (productInputs[0] || totalPriceInput || submitBtn)?.focus(); " ;
echo " (productInputs[0] || submitBtn)?.focus(); " ;
echo " }); " ;
echo " return; " ;
echo " } " ;
@@ -1166,9 +1275,7 @@ function render_auth_home_page(array $user, array $otcProducts = [], array $best
echo " const updatePaymentAddButton = () => { addPaymentButton.disabled = paymentRows.querySelectorAll('[data-otc-payment-row]').length >= paymentOptionValues.length; }; " ;
echo " const updateFormState = () => { " ;
echo " const totalQty = productInputs.reduce((sum, input) => sum + getInputValue(input), 0); " ;
echo " const totalPrice = parseFloat(totalPriceInput.value || '0') || 0; " ;
echo " const isValid = totalQty > 0 " ;
echo " && totalPrice > 0 " ;
echo " && !!getFieldValue(orderDateInput) " ;
echo " && paymentRows.querySelectorAll('[data-otc-payment-row]').length > 0 " ;
echo " && !!getFieldValue(firstNameInput) " ;
@@ -1180,12 +1287,8 @@ function render_auth_home_page(array $user, array $otcProducts = [], array $best
echo " let errorMsg = ''; " ;
echo " if (totalQty === 0) { " ;
echo " errorMsg = 'Mindestens ein Produkt mit Menge > 0 erforderlich.'; " ;
echo " } else if (totalPrice <= 0) { " ;
echo " errorMsg = 'Preis muss größer als 0 sein.'; " ;
echo " } else if (!getFieldValue(orderDateInput)) { " ;
echo " errorMsg = 'Bestelldatum auswählen.'; " ;
echo " } else if (getPaymentTotal() !== totalPrice) { " ;
echo " errorMsg = 'Die Zahlungsbeträge müssen dem Gesamtbetrag entsprechen.'; " ;
echo " } else if (!getFieldValue(firstNameInput) || !getFieldValue(lastNameInput) || !getFieldValue(streetInput) || !getFieldValue(houseNumberInput) || !getFieldValue(zipInput) || !getFieldValue(cityInput)) { " ;
echo " errorMsg = 'Alle Rechnungsadress-Felder ausfüllen.'; " ;
echo " } " ;
@@ -1206,6 +1309,27 @@ function render_auth_home_page(array $user, array $otcProducts = [], array $best
echo " if (!preserveSuccess) { clearSuccess(); } " ;
echo " if (!preserveSuccess) { updateFormState(); } " ;
echo " }; " ;
echo " const closeConfirmation = () => { " ;
echo " pendingOrderData = null; " ;
echo " confirmationStage.dataset.dialogOpen = 'false'; " ;
echo " confirmationCard.hidden = true; " ;
echo " }; " ;
echo " const openConfirmation = (orderData) => { " ;
echo " pendingOrderData = orderData; " ;
echo " confirmationProducts.replaceChildren(); " ;
echo " orderData.products.forEach((product) => { " ;
echo " const input = productInputs.find((candidate) => parseInt(candidate.dataset.productId || '0', 10) === product.productId); " ;
echo " const item = document.createElement('div'); " ;
echo " item.className = 'sg-body'; " ;
echo " item.textContent = (input?.dataset.title || 'Produkt') + ': ' + product.qty; " ;
echo " confirmationProducts.appendChild(item); " ;
echo " }); " ;
echo " confirmationQuantity.textContent = String(orderData.products.reduce((sum, product) => sum + product.qty, 0)); " ;
echo " confirmationTotal.textContent = orderData.payments.reduce((sum, payment) => sum + payment.amount, 0).toFixed(2); " ;
echo " confirmationStage.dataset.dialogOpen = 'true'; " ;
echo " confirmationCard.hidden = false; " ;
echo " requestAnimationFrame(() => confirmationSubmit.focus()); " ;
echo " }; " ;
echo " overlay.addEventListener('pointerdown', (event) => { " ;
echo " backdropPointerDown = event.target === overlay; " ;
echo " }); " ;
@@ -1233,7 +1357,7 @@ function render_auth_home_page(array $user, array $otcProducts = [], array $best
echo " toggleOverlay(false); " ;
echo " } " ;
echo " }); " ;
echo " [totalPriceInput, orderDateInput, firstNameInput, lastNameInput, streetInput, houseNumberInput, zipInput, cityInput].forEach((input) => { " ;
echo " [orderDateInput, firstNameInput, lastNameInput, streetInput, houseNumberInput, zipInput, cityInput].forEach((input) => { " ;
echo " if (!input) { return; } " ;
echo " input.addEventListener('input', updateFormState); " ;
echo " input.addEventListener('change', updateFormState); " ;
@@ -1242,7 +1366,6 @@ function render_auth_home_page(array $user, array $otcProducts = [], array $best
echo " input.addEventListener('input', updateFormState); " ;
echo " input.addEventListener('change', updateFormState); " ;
echo " }); " ;
echo " const getPaymentTotal = () => Math.round(Array.from(paymentRows.querySelectorAll('[data-otc-payment-amount]')).reduce((sum, input) => sum + (parseFloat(input.value || '0') || 0), 0) * 100) / 100; " ;
echo " const bindPaymentRow = (row) => { " ;
echo " row.querySelectorAll('select, input').forEach((input) => { input.addEventListener('input', updateFormState); input.addEventListener('change', updateFormState); }); " ;
echo " }; " ;
@@ -1264,31 +1387,11 @@ function render_auth_home_page(array $user, array $otcProducts = [], array $best
echo " addPaymentButton.addEventListener('click', addPaymentRow); " ;
echo " bindPaymentRow(paymentRows.querySelector('[data-otc-payment-row]')); " ;
echo " updatePaymentAddButton(); " ;
echo " form.addEventListener('submit', async (event ) => { " ;
echo " event.preventDefault(); " ;
echo " const submitOrder = async (orderData ) => { " ;
echo " let submissionSucceeded = false; " ;
echo " if (!updateFormState()) { return; } " ;
echo " const products = productInputs " ;
echo " .filter((input) => getInputValue(input) > 0) " ;
echo " .map((input) => ( { productId: parseInt(input.dataset.productId || '0', 10), qty: getInputValue(input) })); " ;
echo " const orderData = { " ;
echo " products: products, " ;
echo " totalPrice: parseFloat(totalPriceInput.value || '0') || 0, " ;
echo " orderDate: orderDateInput ? orderDateInput.value : '', " ;
echo " payments: Array.from(paymentRows.querySelectorAll('[data-otc-payment-row]')).map((row) => ( { paymentMethod: row.querySelector('[data-otc-payment-method]').value, amount: parseFloat(row.querySelector('[data-otc-payment-amount]').value || '0') || 0 })), " ;
echo " billing: { " ;
echo " firstName: getFieldValue(firstNameInput), " ;
echo " lastName: getFieldValue(lastNameInput), " ;
echo " street: getFieldValue(streetInput), " ;
echo " houseNumber: getFieldValue(houseNumberInput), " ;
echo " zip: getFieldValue(zipInput), " ;
echo " city: getFieldValue(cityInput) " ;
echo " } " ;
echo " }; " ;
echo " const originalBtnText = submitBtn.innerHTML; " ;
echo " submitBtn.innerHTML = '<span class= \" loading \" ></span> Wird verarbeitet...'; " ;
echo " submitBtn.disabled = true; " ;
echo " submitBtn.classList.add('sg-button--process-inactive'); " ;
echo " const originalBtnText = confirmationSubmit.innerHTML; " ;
echo " confirmationSubmit.innerHTML = '<span class= \" loading \" ></span> Wird verarbeitet...'; " ;
echo " confirmationSubmit.disabled = true; " ;
echo " clearError(); " ;
echo " try { " ;
echo " const response = await fetch('/api/otc-order.php', { " ;
@@ -1303,20 +1406,50 @@ function render_auth_home_page(array $user, array $otcProducts = [], array $best
echo " successEl.textContent = result.externalRef ? 'Bestellung erfolgreich erfasst! Bestellnummer: ' + result.externalRef : successDefaultText; " ;
echo " successEl.classList.remove('hidden'); " ;
echo " } " ;
echo " resetForm(true ); " ;
echo " closeConfirmation( ); " ;
echo " resetForm(); " ;
echo " toggleOverlay(false); " ;
echo " } else if (errorEl) { " ;
echo " errorEl.textContent = result.error || 'Unbekannter Fehler'; " ;
echo " errorEl.classList.remove('hidden'); " ;
echo " closeConfirmation(); " ;
echo " } " ;
echo " } catch (error) { " ;
echo " if (errorEl) { " ;
echo " errorEl.textContent = 'Netzwerkfehler: ' + error.message; " ;
echo " errorEl.classList.remove('hidden'); " ;
echo " } " ;
echo " closeConfirmation(); " ;
echo " } finally { " ;
echo " s ubmitBtn .innerHTML = originalBtnText; " ;
echo " confirmationS ubmit.innerHTML = originalBtnText; " ;
echo " confirmationSubmit.disabled = false; " ;
echo " if (!submissionSucceeded) { updateFormState(); } " ;
echo " } " ;
echo " }; " ;
echo " form.addEventListener('submit', (event) => { " ;
echo " event.preventDefault(); " ;
echo " if (!updateFormState()) { return; } " ;
echo " const products = productInputs " ;
echo " .filter((input) => getInputValue(input) > 0) " ;
echo " .map((input) => ( { productId: parseInt(input.dataset.productId || '0', 10), qty: getInputValue(input) })); " ;
echo " const orderData = { " ;
echo " products: products, " ;
echo " orderDate: orderDateInput ? orderDateInput.value : '', " ;
echo " payments: Array.from(paymentRows.querySelectorAll('[data-otc-payment-row]')).map((row) => ( { paymentMethod: row.querySelector('[data-otc-payment-method]').value, amount: parseFloat(row.querySelector('[data-otc-payment-amount]').value || '0') || 0 })), " ;
echo " billing: { " ;
echo " firstName: getFieldValue(firstNameInput), " ;
echo " lastName: getFieldValue(lastNameInput), " ;
echo " street: getFieldValue(streetInput), " ;
echo " houseNumber: getFieldValue(houseNumberInput), " ;
echo " zip: getFieldValue(zipInput), " ;
echo " city: getFieldValue(cityInput) " ;
echo " } " ;
echo " }; " ;
echo " openConfirmation(orderData); " ;
echo " }); " ;
echo " confirmationStage.addEventListener('click', (event) => { " ;
echo " if (event.target.closest('[data-otc-confirmation-cancel]')) { closeConfirmation(); return; } " ;
echo " if (event.target.closest('[data-otc-confirmation-submit]') && pendingOrderData) { submitOrder(pendingOrderData); } " ;
echo " }); " ;
echo " updateFormState(); " ;
echo " })(); " ;