framework = $framework;
$this->tokenChecker = $tokenChecker;
$this->translator = $translator;
$this->eventDispatcher = $eventDispatcher;
}
public function __invoke(Request $request)
{
System::loadLanguageFile('default');
if (!$this->tokenChecker->hasFrontendUser())
{
return $this->renderUnauthorized();
}
if (empty($_REQUEST['do']))
{
return new Response('Required parameter missing',412);
}
$blnModal = true;
if (!empty($_REQUEST['modal']))
{
$blnModal = !(strtolower($_REQUEST['modal']) == 'false');
}
switch ($_REQUEST['do'])
{
case 'details':
return $this->renderDetails($blnModal);
case 'annotation':
return $this->renderAnnotation($blnModal);
case 'booking':
return $this->renderBooking($blnModal);
case 'reservate':
return $this->reservate();
case 'updateReservation':
return $this->updateReservation();
case 'delete':
return $this->deleteReservation();
case 'checkin':
return $this->renderCheckin($blnModal);
case 'updateCheckin':
return $this->updateCheckin();
case 'getAvailableNumbers':
return $this->getAvailableNumbers();
case 'validateNumber':
return $this->validateNumber();
}
return new Response('',500);
}
protected function renderDetails(bool $blnModal=true,string $error=null)
{
$insertTagService = Controller::getContainer()->get('contao.insert_tag.parser');
if (empty($_REQUEST['id']))
{
return new Response('Required parameter missing',412);
}
if (($Slot = WeinanlieferungSlotsModel::findPublishedById($_REQUEST['id'])) === null)
{
return new Response('Could not load slot data',500);
}
// Get slot reservations from user
$arrReservations = [];
if (($Reservations = WeinanlieferungReservationModel::findBy(["uid = ?","pid = ?"],[FrontendUser::getInstance()->id,$Slot->id])) !== null)
{
foreach ($Reservations as $reservation)
{
$arrSortenBooked = [];
$SortenLeseart = explode(';',$reservation->sorten);
foreach($SortenLeseart as $sorteLeseart)
{
list($sorte,$leseart) = explode(',',$sorteLeseart);
$objSorte = WeinanlieferungRebsorteModel::findByPk($sorte);
$objLeseart = WeinanlieferungLeseartModel::findByPk($leseart);
$arrSortenBooked[$objSorte->id.','.$objLeseart->id] = ($objSorte !== null ? $objSorte->title : '') . ' ' . ($objLeseart !== null ? $objLeseart->title : '');
}
/*if (($Sorten = $reservation->getRelated('sorten')) !== null)
{
$arrSortenBooked = $Sorten->fetchEach('title');
}*/
// Compute unit display fields for this reservation
$unitTitle = $GLOBALS['TL_LANG']['MSC']['wa_unit_base'] ?? 'Behälter';
$unitAmountDisplay = (int) $reservation->behaelter;
if ((int)$reservation->unit_id > 0) {
$unitModel = WeinanlieferungUnitModel::findByPk((int)$reservation->unit_id);
if (null !== $unitModel) {
$unitTitle = (string)$unitModel->title;
}
$unitAmountDisplay = (int) ($reservation->unit_amount ?: 0);
if ($unitAmountDisplay <= 0) {
$mult = (int) ($unitModel ? $unitModel->multiplier : 0);
$unitAmountDisplay = $mult > 0 ? max(1, (int) ($reservation->behaelter / $mult)) : (int) $reservation->behaelter;
}
}
$arrReservations[] = array_merge($reservation->row(),[
'sorten' => $arrSortenBooked,
'unit_title' => $unitTitle,
'unit_amount_display' => $unitAmountDisplay,
]);
}
}
// Build data
$arrSorten = [];
$Sorten = StringUtil::deserialize($Slot->sorten,true);
foreach($Sorten as $sorte)
{
$objSorte = WeinanlieferungRebsorteModel::findByPk($sorte['sorte']);
$objLeseart = WeinanlieferungLeseartModel::findByPk($sorte['leseart']);
$arrSorten[$objSorte->id.','.$objLeseart->id] = ($objSorte !== null ? $objSorte->title : '') . ' ' . ($objLeseart !== null ? $objLeseart->title : '');
}
/*if (($Sorten = $Slot->getRelated('sorte')) !== null)
{
$arrSorten = array_combine($Sorten->fetchEach('id'),$Sorten->fetchEach('title'));
}*/
$arrErnteart = [];
if ($Slot->ernteart !== null)
{
foreach (explode(',', $Slot->ernteart) as $ernteart)
{
$arrErnteart[$ernteart] = $GLOBALS['TL_LANG']['REF']['wa_ernteart'][$ernteart] ?? $ernteart;
}
}
$arrLage = [];
if (($Lage = $Slot->getRelated('lage')) !== null)
{
$arrLage = array_combine($Lage->fetchEach('id'),$Lage->fetchEach('title'));
}
$intReservedBehaelter = $Slot->getReservedBehaelter();
$intAvailableBehaelter = max(0,$Slot->getAvailableBehaelter());
$arrData = [
'modal' => $blnModal,
'id' => $Slot->id,
'slot' => array_merge($Slot->row(),[
'anmerkungen' => $insertTagService->replace($Slot->anmerkungen ?? ''),
'sorte' => $arrSorten,
'behaelterAvailable' => $intAvailableBehaelter,
'behaelterBooked' => $Slot->getReservedBehaelter(),
]),
'standort' => $Slot->getRelated('pid'),
'lage' => $arrLage,
'ernteart' => $arrErnteart,
'buchen' => [
'buchbar' => (boolean) ($Slot->behaelter*1.5 > $intReservedBehaelter),
'behaelter' => range(min($intAvailableBehaelter,1),$Slot->behaelter*1.5-$intReservedBehaelter),
'units' => $this->getAvailableUnitsForCapacity($intAvailableBehaelter),
'sorten' => $arrSorten,
'lage' => $arrLage,
'ernteart' => $arrErnteart,
],
'reservations' => $arrReservations
];
if (!empty($error))
{
$arrData['toast'] = $error;
}
return $this->render('@Contao/modal_slot_details.html.twig',$arrData);
}
protected function renderAnnotation(bool $blnModal=true,string $error=null)
{
$insertTagService = Controller::getContainer()->get('contao.insert_tag.parser');
if (empty($_REQUEST['id']))
{
return new Response('Required parameter missing',412);
}
if (($Slot = WeinanlieferungSlotsModel::findPublishedById($_REQUEST['id'])) === null)
{
return new Response('Could not load slot data',500);
}
// Build data
$arrSorten = [];
$Sorten = StringUtil::deserialize($Slot->sorten,true);
foreach($Sorten as $sorte)
{
$objSorte = WeinanlieferungRebsorteModel::findByPk($sorte['sorte']);
$objLeseart = WeinanlieferungLeseartModel::findByPk($sorte['leseart']);
$arrSorten[$objSorte->id.','.$objLeseart->id] = ($objSorte !== null ? $objSorte->title : '') . ' ' . ($objLeseart !== null ? $objLeseart->title : '');
}
$arrErnteart = [];
if ($Slot->ernteart !== null)
{
foreach (explode(',', $Slot->ernteart) as $ernteart)
{
$arrErnteart[] = $GLOBALS['TL_LANG']['REF']['wa_ernteart'][$ernteart] ?? $ernteart;
}
}
$arrLage = [];
if (($Lage = $Slot->getRelated('lage')) !== null)
{
$arrLage = $Lage->fetchEach('title');
}
$intAvailableBehaelter = max(0,$Slot->getAvailableBehaelter());
$arrData = [
'modal' => $blnModal,
'id' => $Slot->id,
'slot' => array_merge($Slot->row(),[
'anmerkungen' => $insertTagService->replace($Slot->anmerkungen ?? ''),
'sorte' => $arrSorten,
'behaelterAvailable' => $intAvailableBehaelter
]),
'standort' => $Slot->getRelated('pid'),
'lage' => $arrLage,
'ernteart' => $arrErnteart,
'buchen' => [
'buchbar' => (boolean) $intAvailableBehaelter,
'behaelter' => range(min($intAvailableBehaelter,1),$intAvailableBehaelter),
'units' => $this->getAvailableUnitsForCapacity($intAvailableBehaelter),
'sorten' => $arrSorten
],
];
return $this->render('@Contao/modal_slot_annotation.html.twig',$arrData);
}
protected function renderBooking(bool $blnModal=true,string $error=null)
{
$insertTagService = Controller::getContainer()->get('contao.insert_tag.parser');
$arrData = [];
if (empty($_REQUEST['id']))
{
return new Response('Required parameter missing',412);
}
/** @var WeinanlieferungSlotsModel $Slot */
if (($Booking = WeinanlieferungReservationModel::findById($_REQUEST['id'])) === null || ($Slot = $Booking->getRelated('pid')) === null)
{
return new Response('Could not load booking data',500);
}
if ($Booking->approved === '0')
{
return $this->render('@Contao/modal_message.html.twig',['type'=>'danger','content'=>'Diese Buchungsanfrage wurde abgelehnt und kann nicht mehr geändert werden.']);
}
$objFile = FilesModel::findByUuid($Booking->upload);
if (!empty($_REQUEST['deleteFile']) && $_REQUEST['deleteFile'] && $objFile !== null)
{
$File = new File($objFile->path);
if ($File->delete())
{
$objFile->delete();
$objFile = null;
}
}
if (!empty($Booking->upload) && $objFile !== null)
{
$File = new File($objFile->path);
$strHref = Environment::get('request');
// Remove an existing file parameter (see #5683)
if (isset($_GET['file']))
{
$strHref = preg_replace('/(&(amp;)?|\?)file=[^&]+/', '', $strHref);
}
$strHref .= (strpos($strHref, '?') !== false ? '&' : '?') . 'file=' . System::urlEncode($File->value);
$arrData['file'] = [
'link' => $strHref,
'filename' => $File->filename,
'extension' => $File->extension,
'name' => $File->name,
'path' => $File->dirname
];
}
// Send the file to the browser (see #4632 and #8375)
if ($objFile !== null && ($file = Input::get('file', true)))
{
if ($file == $objFile->path)
{
Controller::sendFileToBrowser($file);
}
}
$arrSortenAvailable = [];
$Sorten = StringUtil::deserialize($Slot->sorten,true);
foreach($Sorten as $sorte)
{
$objSorte = WeinanlieferungRebsorteModel::findByPk($sorte['sorte']);
$objLeseart = WeinanlieferungLeseartModel::findByPk($sorte['leseart']);
$arrSortenAvailable[$objSorte->id.','.$objLeseart->id] = ($objSorte !== null ? $objSorte->title : '') . ' ' . ($objLeseart !== null ? $objLeseart->title : '');
}
$arrSortenBooked = [];
$SortenLeseart = explode(';',$Booking->sorten);
foreach($SortenLeseart as $sorteLeseart)
{
list($sorte,$leseart) = explode(',',$sorteLeseart);
$objSorte = WeinanlieferungRebsorteModel::findByPk($sorte);
$objLeseart = WeinanlieferungLeseartModel::findByPk($leseart);
$arrSortenBooked[$objSorte->id.','.$objLeseart->id] = ($objSorte !== null ? $objSorte->title : '') . ' ' . ($objLeseart !== null ? $objLeseart->title : '');
}
$arrErnteartAvailable = [];
if ($Slot->ernteart !== null)
{
foreach (explode(',', $Slot->ernteart) as $ernteart)
{
$arrErnteartAvailable[$ernteart] = $GLOBALS['TL_LANG']['REF']['wa_ernteart'][$ernteart] ?? $ernteart;
}
}
$arrErnteartBooked = [];
if ($Booking->ernteart !== null)
{
$arrErnteartBooked = explode(',', $Booking->ernteart);
}
$arrLagenAvailable = [];
if (($Lagen = $Slot->getRelated('lage')) !== null)
{
foreach ($Lagen as $lage)
{
$arrLagenAvailable[$lage->id] = $lage->title;
}
}
$arrLagenBooked = [];
if ($Booking->lage !== null)
{
$arrLagenBooked = explode(',', $Booking->lage);
}
$intReservedBehaelter = $Slot->getReservedBehaelter();
// While editing, available base units should include the current booking's base units,
// because the edit will override the former amount.
$intAvailableBehaelter = max(0, $Slot->getAvailableBehaelter() + (int) $Booking->behaelter);
$intOcTreshold = $intAvailableBehaelter - $intReservedBehaelter + $Slot->behaelter;
$arrData = array_merge($arrData,[
'modal' => $blnModal,
'id' => $Booking->id,
'slot' => array_merge($Slot->row(),[
'anmerkungen' => $insertTagService->replace($Slot->anmerkungen ?? ''),
'sorte' => $arrSortenAvailable,
'behaelterAvailable' => $intAvailableBehaelter,
'behaelterOcThreshold' => $intOcTreshold,
'behaelterBooked' => $Slot->getReservedBehaelter(),
]),
'buchung' => array_merge($Booking->row(),[
'sorten' => $arrSortenBooked,
'ernteart' => $arrErnteartBooked,
'lage' => $arrLagenBooked,
// display fields
'unit_title' => (function() use ($Booking) {
if ((int)$Booking->unit_id > 0) {
$m = WeinanlieferungUnitModel::findByPk((int)$Booking->unit_id);
return $m ? (string)$m->title : ($GLOBALS['TL_LANG']['MSC']['wa_unit_base'] ?? 'Behälter');
}
return $GLOBALS['TL_LANG']['MSC']['wa_unit_base'] ?? 'Behälter';
})(),
'unit_amount_display' => (function() use ($Booking) {
if ((int)$Booking->unit_id > 0) {
$m = WeinanlieferungUnitModel::findByPk((int)$Booking->unit_id);
$amount = (int) ($Booking->unit_amount ?: 0);
if ($amount > 0) return $amount;
$mult = (int) ($m ? $m->multiplier : 0);
return $mult > 0 ? max(1, (int) ($Booking->behaelter / $mult)) : (int) $Booking->behaelter;
}
return (int) $Booking->behaelter;
})(),
]),
'standort' => $Slot->getRelated('pid'),
'buchen' => [
'buchbar' => (boolean) $intAvailableBehaelter,
'behaelter' => range(min($intAvailableBehaelter,1),$Slot->behaelter*1.5-$Slot->getReservedBehaelter()+$Booking->behaelter),
'units' => $this->getAvailableUnitsForCapacity($intAvailableBehaelter),
'sorten' => $arrSortenAvailable,
'ernteart' => $arrErnteartAvailable,
'lage' => $arrLagenAvailable,
]
]);
if (!empty($error))
{
$arrData['toast'] = $error;
}
return $this->render('@Contao/modal_booking_details.html.twig',$arrData);
}
protected function reservate()
{
Controller::loadDataContainer('tl_vr_wa_reservation');
$arrData = [];
if (($rootPage = Frontend::getRootPageFromUrl()) !== null && !empty($rootPage->vr_wa_uploadFolderSRC))
{
$File = new FormFileUpload(\Contao\Widget::getAttributesFromDca($GLOBALS['TL_DCA']['tl_vr_wa_reservation']['fields']['upload'], 'upload'));
$File->storeFile = true;
$File->doNotOverwrite = true;
$File->uploadFolder = $rootPage->vr_wa_uploadFolderSRC;
$File->validate();
if ($File->hasErrors())
{
return $this->renderDetails(false,'
' . $File->getErrorAsHTML() . '
');
}
if (!empty($_SESSION['FILES'][$File->name]))
{
$arrData['filename'] = $_SESSION['FILES'][$File->name]['name'] ?? '';
$arrData['upload'] = $_SESSION['FILES'][$File->name]['uuid'] ? StringUtil::uuidToBin($_SESSION['FILES'][$File->name]['uuid']) : null;
}
}
if (empty($_REQUEST['id']))
{
return new Response('Missing parameter',412);
}
// recompute the base units
$unitId = (int) Input::post('unit_id');
$unitAmount = (int) Input::post('unit_amount');
$multiplier = 1;
if ($unitId > 0) {
if (($u = WeinanlieferungUnitModel::findByPk($unitId)) !== null) {
$multiplier = max(1, (int) $u->multiplier);
}
}
$intBehaelter = $unitAmount > 0 ? $unitAmount * $multiplier : (int) Input::post('behaelter');
// Form validation and unit capacity
if (($Slot = WeinanlieferungSlotsModel::findByPk($_REQUEST['id'])) !== null)
{
if ($intBehaelter > $Slot->behaelter*1.5 - $Slot->getReservedBehaelter())
{
return $this->renderDetails(false,sprintf('Fehler: Es sind mittlerweile nur noch %s Behälter verfügbar.
',$Slot->getAvailableBehaelter()));
}
}
$arrError = [];
foreach (['sorten','ernteart','lage'] as $field)
{
if (empty(Input::post($field)))
{
$arrError = [$field];
}
}
// Require either (unit selection (allowing 0) with unit_amount, or behaelter fallback)
$postedUnitId = Input::post('unit_id');
$hasUnitId = ($postedUnitId !== null && $postedUnitId !== '' && $postedUnitId !== false);
if (!$hasUnitId || empty(Input::post('unit_amount')))
{
if (empty(Input::post('behaelter')))
{
$arrError[] = 'behaelter';
}
}
if (count($arrError))
{
return $this->renderDetails(false,'Bitte geben Sie alle Pflichtangaben (mit * markierte Felder) an
');
}
$arrSorten = [];
if (!is_array(Input::post('sorten')))
{
$arrSorten[] = Input::post('sorten');
} else {
$arrSorten = implode(';', Input::post('sorten'));
}
$arrErnteart = [];
if (!is_array(Input::post('ernteart')))
{
$arrErnteart[] = Input::post('ernteart');
} else {
$arrErnteart = implode(',', Input::post('ernteart'));
}
$arrLage = [];
if (!is_array(Input::post('lage')))
{
$arrLage[] = Input::post('lage');
} else {
$arrLage = implode(',', Input::post('lage'));
}
$Reservation = new WeinanlieferungReservationModel();
$time = time();
if ($intBehaelter > $Slot->behaelter - $Slot->getReservedBehaelter())
{
$arrData['approved'] = '';
$arrData['approved_on'] = 0;
} else {
$arrData['approved'] = '1';
$arrData['approved_on'] = $time;
}
// Determine fields to store
$arrData = array_merge($arrData,[
'pid' => $_REQUEST['id'],
'tstamp' => $time,
'uid' => FrontendUser::getInstance()->id,
'behaelter' => $intBehaelter,
'unit_id' => $unitId,
'unit_amount' => $unitAmount,
'sorten' => $arrSorten,
'ernteart' => $arrErnteart,
'lage' => $arrLage
]);
$Reservation->setRow($arrData);
$Reservation->save();
if (empty($Reservation->approved))
{
return new Response('Wir haben Ihre Anfrage erhalten. Bitte warten Sie auf eine Freigabe durch uns.
',200,['HX-Trigger'=> 'updateWaBooking']);
}
return new Response('',200,['HX-Trigger'=> 'updateWaList']);
}
protected function updateReservation()
{
Controller::loadDataContainer('tl_vr_wa_reservation');
if (empty($_REQUEST['id']))
{
return new Response('Missing parameter',412);
}
if (($Reservation = WeinanlieferungReservationModel::findById($_REQUEST['id'])) === null)
{
return new Response('Could not load booking data',500);
}
if (FrontendUser::getInstance()->id != $Reservation->uid)
{
return new Response('Member not authorized tu update this reservation',500);
}
if (($rootPage = Frontend::getRootPageFromUrl()) !== null && !empty($rootPage->vr_wa_uploadFolderSRC))
{
$File = new FormFileUpload(\Contao\Widget::getAttributesFromDca($GLOBALS['TL_DCA']['tl_vr_wa_reservation']['fields']['upload'], 'upload'));
$File->storeFile = true;
$File->doNotOverwrite = true;
$File->uploadFolder = $rootPage->vr_wa_uploadFolderSRC;
$File->validate();
if ($File->hasErrors())
{
return $this->renderBooking(false,'' . $File->getErrorAsHTML() . '
');
}
if (!empty($_SESSION['FILES'][$File->name]))
{
$Reservation->filename = $_SESSION['FILES'][$File->name]['name'] ?? '';
$Reservation->upload = $_SESSION['FILES'][$File->name]['uuid'] ? StringUtil::uuidToBin($_SESSION['FILES'][$File->name]['uuid']) : null;
}
}
// recompute base units
$unitId = (int) Input::post('unit_id');
$unitAmount = (int) Input::post('unit_amount');
$multiplier = 1;
if ($unitId > 0) {
if (($u = WeinanlieferungUnitModel::findByPk($unitId)) !== null) {
$multiplier = max(1, (int) $u->multiplier);
}
}
$intBehaelter = $unitAmount > 0 ? $unitAmount * $multiplier : (int) Input::post('behaelter');
// Form validation
/** @var WeinanlieferungSlotsModel $Slot */
if (($Slot = $Reservation->getRelated('pid')) !== null)
{
if ($intBehaelter > $Slot->behaelter*1.5 - $Slot->getReservedBehaelter() + $Reservation->behaelter)
{
return $this->renderBooking(false,sprintf('Fehler: Es sind mittlerweile nur noch %s Behälter verfügbar.
',$Slot->getAvailableBehaelter()+$Reservation->behaelter));
}
}
$arrError = [];
foreach (['sorten','ernteart','lage'] as $field)
{
if (empty(Input::post($field)))
{
$arrError = [$field];
}
}
$postedUnitId = Input::post('unit_id');
$hasUnitId = ($postedUnitId !== null && $postedUnitId !== '' && $postedUnitId !== false);
if (!$hasUnitId || empty(Input::post('unit_amount')))
{
if (empty(Input::post('behaelter')))
{
$arrError[] = 'behaelter';
}
}
if (count($arrError))
{
return $this->renderBooking(false,'Bitte geben Sie alle Pflichtangaben (mit * markierte Felder) an
');
}
$arrSorten = [];
if (!is_array(Input::post('sorten')))
{
$arrSorten[] = Input::post('sorten');
} else {
$arrSorten = implode(';', Input::post('sorten'));
}
$arrErnteart = [];
if (!is_array(Input::post('ernteart')))
{
$arrErnteart[] = Input::post('ernteart');
} else {
$arrErnteart = implode(',', Input::post('ernteart'));
}
$arrLage = [];
if (!is_array(Input::post('lage')))
{
$arrLage[] = Input::post('lage');
} else {
$arrLage = implode(',', Input::post('lage'));
}
$time = time();
if ($intBehaelter > $Slot->behaelter - $Slot->getReservedBehaelter() + $Reservation->behaelter)
{
$Reservation->approved = '';
$Reservation->approved_on = 0;
} else {
$Reservation->approved = '1';
$Reservation->approved_on = $time;
}
$Reservation->tstamp = $time;
$Reservation->behaelter = $intBehaelter;
$Reservation->unit_id = $unitId;
$Reservation->unit_amount = $unitAmount;
$Reservation->sorten = $arrSorten;
$Reservation->ernteart = $arrErnteart;
$Reservation->lage = $arrLage;
$Reservation->save();
if (empty($Reservation->approved))
{
return new Response('Wir haben Ihre Anfrage erhalten. Bitte warten Sie auf eine Freigabe durch uns.
',200,['HX-Trigger'=> 'updateWaBooking']);
}
return new Response('Reservierung erfolgreich geändert
',200,['HX-Trigger'=> 'updateWaBooking']);
}
protected function deleteReservation()
{
if (empty($_REQUEST['id']))
{
return new Response('Missing parameter',412);
}
/** @var Connection $db */
$db = Controller::getContainer()->get('database_connection');
$arrCriteria = [
'uid' => FrontendUser::getInstance()->id,
'id' => $_REQUEST['id']
];
if ($db->delete('tl_vr_wa_reservation',$arrCriteria))
{
return new Response(null,203,['HX-Trigger'=> 'updateWaBooking']);
}
return new Response('Could not delete',500);
}
protected function renderUnauthorized()
{
return $this->render('@Contao/modal_unauthorized.html.twig');
}
protected function renderCheckin(bool $blnModal=true, string $error=null, array $formData=null)
{
$insertTagService = Controller::getContainer()->get('contao.insert_tag.parser');
$arrData = [];
if (empty($_REQUEST['id']))
{
return new Response('Required parameter missing', 412);
}
/** @var WeinanlieferungReservationModel $Booking */
if (($Booking = WeinanlieferungReservationModel::findById($_REQUEST['id'])) === null || ($Slot = $Booking->getRelated('pid')) === null)
{
return new Response('Could not load booking data', 500);
}
if ($Booking->approved === '0')
{
return $this->render('@Contao/modal_message.html.twig', ['type'=>'danger', 'content'=>'Diese Buchungsanfrage wurde abgelehnt und kann nicht eingecheckt werden.']);
}
if ($Booking->checked_in === '1')
{
return $this->render('@Contao/modal_message.html.twig', ['type'=>'info', 'content'=>'Diese Buchung wurde bereits eingecheckt.']);
}
// Get the standort to access the number_ranges
$Standort = $Slot->getRelated('pid');
if ($Standort === null)
{
return new Response('Could not load standort data', 500);
}
// Prepare data for the template
$arrSortenBooked = [];
$SortenLeseart = explode(';', $Booking->sorten);
foreach($SortenLeseart as $sorteLeseart)
{
list($sorte, $leseart) = explode(',', $sorteLeseart);
$objSorte = WeinanlieferungRebsorteModel::findByPk($sorte);
$objLeseart = WeinanlieferungLeseartModel::findByPk($leseart);
$arrSortenBooked[$objSorte->id.','.$objLeseart->id] = ($objSorte !== null ? $objSorte->title : '') . ' ' . ($objLeseart !== null ? $objLeseart->title : '');
}
$arrErnteartBooked = [];
if ($Booking->ernteart !== null)
{
foreach (explode(',', $Booking->ernteart) as $ernteart)
{
$arrErnteartBooked[] = $GLOBALS['TL_LANG']['REF']['wa_ernteart'][$ernteart] ?? $ernteart;
}
}
$arrLagenBooked = [];
if ($Booking->lage !== null)
{
if (($Lagen = $Booking->getRelated('lage')) !== null)
{
$arrLagenBooked = $Lagen->fetchEach('title');
}
}
// Load the member model for the booking's user
$memberModel = MemberModel::findById($Booking->uid);
$currentMemberModel = MemberModel::findById(FrontendUser::getInstance()->id);
$arrData = array_merge($arrData, [
'modal' => $blnModal,
'id' => $Booking->id,
'slot' => array_merge($Slot->row(), [
'anmerkungen' => $insertTagService->replace($Slot->anmerkungen ?? ''),
]),
'buchung' => array_merge($Booking->row(), [
'sorten' => $arrSortenBooked,
'ernteart' => $arrErnteartBooked,
'lage' => $arrLagenBooked,
]),
'standort' => $Standort,
'checkin' => [
'behaelter' => $Booking->behaelter,
'expected' => ($Booking->unit_amount ?? 0) > 0 ? (int)$Booking->unit_amount : (int)$Booking->behaelter,
'unit_title' => (function() use ($Booking) {
if ((int)$Booking->unit_id > 0) {
$m = WeinanlieferungUnitModel::findByPk((int)$Booking->unit_id);
return $m ? (string)$m->title : ($GLOBALS['TL_LANG']['MSC']['wa_unit_base'] ?? 'Behälter');
}
return $GLOBALS['TL_LANG']['MSC']['wa_unit_base'] ?? 'Behälter';
})(),
'unit_amount_display' => (function() use ($Booking) {
if ((int)$Booking->unit_id > 0) {
$m = WeinanlieferungUnitModel::findByPk((int)$Booking->unit_id);
$amount = (int) ($Booking->unit_amount ?: 0);
if ($amount > 0) return $amount;
$mult = (int) ($m ? $m->multiplier : 0);
return $mult > 0 ? max(1, (int) ($Booking->behaelter / $mult)) : (int) $Booking->behaelter;
}
return (int) $Booking->behaelter;
})(),
],
'member' => $memberModel ? $memberModel->row() : null,
'current_member' => $currentMemberModel ? $currentMemberModel->row() : null
]);
// Add form data if provided (to preserve values after validation errors)
if ($formData !== null) {
$arrData['form_data'] = $formData;
}
if (!empty($error))
{
$arrData['toast'] = $error;
}
return $this->render('@Contao/modal_checkin.html.twig', $arrData);
}
protected function updateCheckin()
{
if (empty($_REQUEST['id']))
{
return new Response('Missing parameter', 412);
}
/** @var WeinanlieferungReservationModel $Booking */
if (($Booking = WeinanlieferungReservationModel::findById($_REQUEST['id'])) === null)
{
return new Response('Could not load booking data', 500);
}
// Validate that we have the correct number of behaelter numbers
$behaelterNumbers = Input::post('behaelter_numbers');
$expectedCount = ($Booking->unit_amount ?? 0) > 0 ? (int)$Booking->unit_amount : (int)$Booking->behaelter;
if (!is_array($behaelterNumbers) || count($behaelterNumbers) != $expectedCount)
{
// Prepare form data to preserve input values
$formData = [
'behaelter_numbers' => $behaelterNumbers ?: [],
'member_numbers' => Input::post('member_numbers') ?: []
];
return $this->renderCheckin(false, 'Bitte wählen Sie für jede Einheit eine Nummer aus.
', $formData);
}
// Get member numbers from the form
$memberNumbers = Input::post('member_numbers');
// If member numbers are not provided or not an array, initialize an empty array
if (!is_array($memberNumbers) || count($memberNumbers) != $Booking->behaelter)
{
$memberNumbers = array_fill(0, count($behaelterNumbers), '');
}
// Get the current member's number to use as fallback
$currentMember = MemberModel::findById(FrontendUser::getInstance()->id);
$currentMemberNo = $currentMember ? $currentMember->memberno : '';
// Filter out the special value 9999 ("Nummer nicht bekannt") for duplicate check
$numbersForDuplicateCheck = array_filter($behaelterNumbers, function($number) {
return $number !== '9999';
});
// Check for duplicate numbers (excluding the special value 9999)
if (count(array_unique($numbersForDuplicateCheck)) != count($numbersForDuplicateCheck))
{
// Prepare form data to preserve input values
$formData = [
'behaelter_numbers' => $behaelterNumbers,
'member_numbers' => $memberNumbers
];
return $this->renderCheckin(false, 'Jede Nummer kann nur einmal verwendet werden.
', $formData);
}
// Validate all numbers on the server side as a final check
$invalidNumbers = [];
$currentTime = time();
$Slot = $Booking->getRelated('pid');
$Standort = $Slot->getRelated('pid');
// Get all used numbers from current bookings (excluding past bookings)
$usedNumbers = [];
// Get the database connection
$db = Controller::getContainer()->get('database_connection');
// Query to get used numbers from current bookings
$sql = "SELECT r.behaelter_numbers
FROM tl_vr_wa_reservation r
JOIN tl_vr_wa_slot s ON r.pid = s.id
WHERE r.behaelter_numbers != ''
AND s.time >= ?
AND r.id != ?
AND s.pid = ?"; // Only check for the same standort
$stmt = $db->prepare($sql);
$stmt->bindValue(1, $currentTime);
$stmt->bindValue(2, $Booking->id);
$stmt->bindValue(3, $Standort->id);
$result = $stmt->executeQuery();
while ($row = $result->fetchAssociative()) {
$numbers = json_decode($row['behaelter_numbers'], true);
if (is_array($numbers)) {
foreach ($numbers as $item) {
$usedNumbers[] = isset($item['behaelter']) ? $item['behaelter'] : $item;
}
}
}
// Check each number
foreach ($behaelterNumbers as $index => $number) {
// Skip the special value 9999
if ($number === '9999') {
continue;
}
// Check if the number is numeric
if (!is_numeric($number)) {
$invalidNumbers[] = "Behälter " . ($index + 1) . ": Die eingegebene Nummer ist keine gültige Zahl.";
continue;
}
// Check if the number is already in use
if (in_array($number, $usedNumbers)) {
$invalidNumbers[] = "Behälter " . ($index + 1) . ": Diese Nummer wird bereits in einer anderen aktiven Buchung verwendet.";
continue;
}
// Check if the number is within the valid ranges for this standort
$validRanges = $Standort->extractNumbersFromRanges([], 10000); // Get all possible numbers
if (!in_array($number, $validRanges) && !empty($validRanges)) {
$invalidNumbers[] = "Behälter " . ($index + 1) . ": Die eingegebene Nummer liegt nicht im gültigen Bereich für diesen Standort.";
}
}
// If there are invalid numbers, return an error
if (!empty($invalidNumbers)) {
$errorMessage = 'Folgende Fehler wurden gefunden:
';
foreach ($invalidNumbers as $error) {
$errorMessage .= '- ' . $error . '
';
}
$errorMessage .= '
';
// Prepare form data to preserve input values
$formData = [
'behaelter_numbers' => $behaelterNumbers,
'member_numbers' => $memberNumbers,
'invalid_fields' => array_map(function($error) {
// Extract the behälter number from the error message
if (preg_match('/Behälter (\d+):/', $error, $matches)) {
return (int)$matches[1] - 1; // Convert to zero-based index
}
return null;
}, $invalidNumbers)
];
return $this->renderCheckin(false, $errorMessage, $formData);
}
// Create combined array with behaelter numbers and member numbers
$combinedData = [];
foreach ($behaelterNumbers as $index => $behaelterNumber) {
$memberNumber = !empty($memberNumbers[$index]) ? $memberNumbers[$index] : $currentMemberNo;
$combinedData[] = [
'behaelter' => $behaelterNumber,
'member' => $memberNumber
];
}
// Save the check-in data
$time = time();
$Booking->checked_in = '1';
$Booking->checked_in_on = $time;
$Booking->behaelter_numbers = json_encode($combinedData);
$Booking->tstamp = $time;
$Booking->save();
// Create reservation data array for the event
$reservationData = $Booking->row();
// Dispatch the check-in completed event
$event = new CheckInCompletedEvent($reservationData, $Booking);
$this->eventDispatcher->dispatch($event, CheckInCompletedEvent::NAME);
return new Response('Check-in erfolgreich durchgeführt
', 200, ['HX-Trigger'=> 'updateWaBooking']);
}
protected function getAvailableNumbers()
{
if (empty($_REQUEST['id']))
{
return new Response('Required parameter missing', 412);
}
/** @var WeinanlieferungReservationModel $Booking */
if (($Booking = WeinanlieferungReservationModel::findById($_REQUEST['id'])) === null || ($Slot = $Booking->getRelated('pid')) === null)
{
return new Response('Could not load booking data', 500);
}
if ($Booking->approved === '0')
{
return new Response(json_encode(['error' => 'Diese Buchungsanfrage wurde abgelehnt und kann nicht eingecheckt werden.']), 400, ['Content-Type' => 'application/json']);
}
if ($Booking->checked_in === '1')
{
return new Response(json_encode(['error' => 'Diese Buchung wurde bereits eingecheckt.']), 400, ['Content-Type' => 'application/json']);
}
// Get the standort to access the number_ranges
$Standort = $Slot->getRelated('pid');
if ($Standort === null)
{
return new Response(json_encode(['error' => 'Could not load standort data']), 500, ['Content-Type' => 'application/json']);
}
// Get all used numbers from current bookings (excluding past bookings)
$usedNumbers = [];
$currentTime = time();
// Get the database connection
$db = Controller::getContainer()->get('database_connection');
// More efficient query to get used numbers from current bookings
$sql = "SELECT r.behaelter_numbers
FROM tl_vr_wa_reservation r
JOIN tl_vr_wa_slot s ON r.pid = s.id
WHERE r.behaelter_numbers != ''
AND s.time >= ?
AND r.id != ?";
$stmt = $db->prepare($sql);
$stmt->bindValue(1, $currentTime);
$stmt->bindValue(2, $Booking->id);
$result = $stmt->executeQuery();
while ($row = $result->fetchAssociative()) {
$numbers = json_decode($row['behaelter_numbers'], true);
if (is_array($numbers)) {
foreach ($numbers as $number) {
$usedNumbers[] = isset($number['behaelter']) ? $number['behaelter'] : $number;
}
}
}
// Get a batch of available numbers
// We'll limit to a reasonable number to improve performance
$limit = 100; // Adjust this based on your needs
if (!empty($_REQUEST['limit']) && is_numeric($_REQUEST['limit'])) {
$limit = (int)$_REQUEST['limit'];
}
// Get available numbers directly, excluding used ones
$availableNumbers = $Standort->extractNumbersFromRanges($usedNumbers, $limit);
// Add the special option "Nummer nicht bekannt" with value 9999
// This option should always be available and can be used multiple times
array_unshift($availableNumbers, '9999');
// Return the numbers as JSON
return new Response(json_encode(['numbers' => $availableNumbers]), 200, ['Content-Type' => 'application/json']);
}
protected function validateNumber()
{
if (empty($_REQUEST['id']) || !isset($_REQUEST['number']))
{
return new Response(json_encode(['valid' => false, 'message' => 'Required parameters missing']), 412, ['Content-Type' => 'application/json']);
}
$number = $_REQUEST['number'];
// Special case: "Nummer nicht bekannt" is always valid
if ($number === '9999') {
return new Response(json_encode(['valid' => true]), 200, ['Content-Type' => 'application/json']);
}
// Check if the number is a valid number format
if (!is_numeric($number)) {
return new Response(json_encode([
'valid' => false,
'message' => 'Die eingegebene Nummer ist keine gültige Zahl.'
]), 200, ['Content-Type' => 'application/json']);
}
/** @var WeinanlieferungReservationModel $Booking */
if (($Booking = WeinanlieferungReservationModel::findById($_REQUEST['id'])) === null || ($Slot = $Booking->getRelated('pid')) === null)
{
return new Response(json_encode(['valid' => false, 'message' => 'Could not load booking data']), 500, ['Content-Type' => 'application/json']);
}
// Get the standort to access the number_ranges
$Standort = $Slot->getRelated('pid');
if ($Standort === null)
{
return new Response(json_encode(['valid' => false, 'message' => 'Could not load standort data']), 500, ['Content-Type' => 'application/json']);
}
// Get all used numbers from current bookings (excluding past bookings)
$usedNumbers = [];
$currentTime = time();
// Get the database connection
$db = Controller::getContainer()->get('database_connection');
// Query to get used numbers from current bookings
$sql = "SELECT r.behaelter_numbers
FROM tl_vr_wa_reservation r
JOIN tl_vr_wa_slot s ON r.pid = s.id
WHERE r.behaelter_numbers != ''
AND s.time >= ?
AND r.id != ?
AND s.pid = ?"; // Only check for the same standort
$stmt = $db->prepare($sql);
$stmt->bindValue(1, $currentTime);
$stmt->bindValue(2, $Booking->id);
$stmt->bindValue(3, $Standort->id);
$result = $stmt->executeQuery();
while ($row = $result->fetchAssociative()) {
$numbers = json_decode($row['behaelter_numbers'], true);
if (is_array($numbers)) {
foreach ($numbers as $item) {
$usedNumbers[] = isset($item['behaelter']) ? $item['behaelter'] : $item;
}
}
}
// Check if the number is already in use
if (in_array($number, $usedNumbers)) {
return new Response(json_encode([
'valid' => false,
'message' => 'Diese Nummer wird bereits in einer anderen aktiven Buchung verwendet.'
]), 200, ['Content-Type' => 'application/json']);
}
// Check if the number is within the valid ranges for this standort
$validRanges = $Standort->extractNumbersFromRanges([], 10000); // Get all possible numbers
if (!in_array($number, $validRanges) && !empty($validRanges)) {
return new Response(json_encode([
'valid' => false,
'message' => 'Die eingegebene Nummer liegt nicht im gültigen Bereich für diesen Standort.'
]), 200, ['Content-Type' => 'application/json']);
}
// If we got here, the number is valid
return new Response(json_encode(['valid' => true]), 200, ['Content-Type' => 'application/json']);
}
protected function getAvailableUnitsForCapacity(int $availableBaseUnits): array
{
// Always include the base unit (Behälter) with multiplier 1
$units = [];
$units[] = [
'id' => 0,
'title' => $GLOBALS['TL_LANG']['MSC']['wa_unit_base'] ?? 'Behälter',
'multiplier' => 1,
'max_amount' => $availableBaseUnits, // one check-in per base unit
];
// Load custom units from DB
if (($all = WeinanlieferungUnitModel::findAll()) !== null) {
foreach ($all as $unit) {
$mult = (int)($unit->multiplier ?? 0);
if ($mult < 1) {
continue; // skip invalid
}
// A unit fits if at least one of it can be placed into remaining capacity
$maxAmount = intdiv(max(0, $availableBaseUnits), $mult);
if ($maxAmount < 1) {
continue; // does not fit current capacity
}
$units[] = [
'id' => (int)$unit->id,
'title' => (string)$unit->title,
'multiplier' => $mult,
'max_amount' => $maxAmount,
];
}
}
return $units;
}
}