<?php
declare(strict_types=1);
/*
* This file is part of contao-weinanlieferung-bundle.
*
* (c) vonRotenberg
*
* @license commercial
*/
namespace vonRotenberg\WeinanlieferungBundle\EventListener\DataContainer;
use Contao\CoreBundle\ServiceAnnotation\Callback;
use Contao\DataContainer;
use Contao\Date;
use Contao\Input;
use Contao\StringUtil;
use Doctrine\DBAL\Connection;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use vonRotenberg\WeinanlieferungBundle\Event\CheckInCompletedEvent;
use vonRotenberg\WeinanlieferungBundle\Model\WeinanlieferungLeseartModel;
use vonRotenberg\WeinanlieferungBundle\Model\WeinanlieferungRebsorteModel;
use vonRotenberg\WeinanlieferungBundle\Model\WeinanlieferungReservationModel;
use vonRotenberg\WeinanlieferungBundle\Model\WeinanlieferungSlotsModel;
use vonRotenberg\WeinanlieferungBundle\Model\WeinanlieferungUnitModel;
class WeinanlieferungReservationContainerListener
{
/** @var Connection */
protected $db;
private $eventDispatcher;
public function __construct(Connection $db, EventDispatcherInterface $eventDispatcher)
{
$this->db = $db;
$this->eventDispatcher = $eventDispatcher;
}
/**
* @Callback(table="tl_vr_wa_reservation", target="fields.sorten.options")
*/
public function onSortenOptionsCallback(DataContainer $dc)
{
$arrSorten = [];
if (($Slot = WeinanlieferungSlotsModel::findByPk($dc->activeRecord->pid)) === null)
{
return $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 : '');
}
return $arrSorten;
}
/**
* @Callback(table="tl_vr_wa_reservation", target="fields.lage.options")
*/
public function onLageOptionsCallback(DataContainer $dc)
{
$arrLagen = [];
if (($Slot = WeinanlieferungSlotsModel::findByPk($dc->activeRecord->pid)) === null || $Slot->lage === null)
{
return $arrLagen;
}
if (($Lagen = $Slot->getRelated('lage')) !== null)
{
$arrLagen = array_combine($Lagen->fetchEach('id'),$Lagen->fetchEach('title'));
}
return $arrLagen;
}
/**
* @Callback(table="tl_vr_wa_reservation", target="fields.ernteart.options")
*/
public function onErnteartOptionsCallback(DataContainer $dc)
{
$arrErnteart = [];
if (($Slot = WeinanlieferungSlotsModel::findByPk($dc->activeRecord->pid)) === null || $Slot->ernteart === null)
{
return $arrErnteart;
}
$Ernteart = explode(',',$Slot->ernteart);
foreach($Ernteart as $art)
{
$arrErnteart[$art] = $GLOBALS['TL_LANG']['REF']['wa_ernteart'][$art] ?? $art;
}
return $arrErnteart;
}
/**
* @Callback(table="tl_vr_wa_reservation", target="fields.pid.options")
*/
public function onPidOptionsCallback(DataContainer $dc)
{
$arrData = [];
if (($CurrentSlot = WeinanlieferungSlotsModel::findByPk($dc->activeRecord->pid)) !== null)
{
$arrSorten = [];
$intAvailableBehaelter = $CurrentSlot->getAvailableBehaelter();
$Standort = $CurrentSlot->getRelated('pid');
$Sorten = StringUtil::deserialize($CurrentSlot->sorten,true);
foreach($Sorten as $sorte)
{
$objSorte = WeinanlieferungRebsorteModel::findByPk($sorte['sorte']);
$objLeseart = WeinanlieferungLeseartModel::findByPk($sorte['leseart']);
$arrSorten[] = ($objSorte !== null ? $objSorte->title : '') . ' ' . ($objLeseart !== null ? $objLeseart->title : '');
}
$arrData[$CurrentSlot->id] = Date::parse(Date::getNumericDatimFormat(),$CurrentSlot->time) . ' - ' . ($Standort !== null ? $Standort->title : $CurrentSlot->pid) . ' - (' . implode(', ',$arrSorten) . ') [Verfügbare Behälter: ' . $intAvailableBehaelter . '/' . $CurrentSlot->behaelter . ']';
}
if (($Slots = WeinanlieferungSlotsModel::findAllFuturePublished()) === null)
{
return $arrData;
}
foreach ($Slots as $slot)
{
$arrSorten = [];
$intAvailableBehaelter = $slot->getAvailableBehaelter();
$Standort = $slot->getRelated('pid');
$Sorten = StringUtil::deserialize($slot->sorten,true);
foreach($Sorten as $sorte)
{
$objSorte = WeinanlieferungRebsorteModel::findByPk($sorte['sorte']);
$objLeseart = WeinanlieferungLeseartModel::findByPk($sorte['leseart']);
$arrSorten[] = ($objSorte !== null ? $objSorte->title : '') . ' ' . ($objLeseart !== null ? $objLeseart->title : '');
}
$arrData[$slot->id] = Date::parse(Date::getNumericDatimFormat(),$slot->time) . ' - ' . ($Standort !== null ? $Standort->title : $slot->pid) . ' - (' . implode(', ',$arrSorten) . ') [Verfügbare Behälter: ' . $intAvailableBehaelter . '/' . $slot->behaelter . ']';
}
return $arrData;
}
/**
* @Callback(table="tl_vr_wa_reservation", target="fields.uid.options")
*/
public function onUidOptionsCallback(DataContainer $dc)
{
if (($Members = \MemberModel::findAll()) === null)
{
return [];
}
$arrData = [];
foreach ($Members as $member)
{
$arrData[$member->id] = ($member->memberno !== null && $member->memberno ? $member->memberno.' ' : '') . $member->firstname . ' ' . $member->lastname . ' [' . $member->email . ']';
}
return $arrData;
}
/**
* @Callback(table="tl_vr_wa_reservation", target="fields.approved.save")
*/
public function onApprovedSaveCallback($varValue, DataContainer $dc)
{
if (($Current = WeinanlieferungReservationModel::findByPk($dc->activeRecord->id)) !== null)
{
if ($Current->approved === '1' && $varValue !== '1')
{
$Current->approved_on = 0;
$Current->save();
} elseif ($Current->approved !== '1' && $varValue === '1')
{
$Current->approved_on = time();
$Current->save();
}
}
return $varValue;
}
/**
* @Callback(table="tl_vr_wa_reservation", target="fields.behaelter_numbers.load")
*/
public function onBehaelterNumbersLoadCallback($varValue, DataContainer $dc)
{
if (!empty($varValue))
{
$decodedValue = \json_decode($varValue, true);
// Check if we have the new format (array of objects with behaelter and member)
// or the old format (simple array of behaelter numbers)
$isNewFormat = isset($decodedValue[0]) && is_array($decodedValue[0]) && isset($decodedValue[0]['behaelter']);
if ($isNewFormat) {
// The data is already in the correct format for the multiColumnWizard
return $decodedValue;
} else {
// Convert old format to new format
$result = [];
// Get the member associated with this reservation as fallback
$reservation = \vonRotenberg\WeinanlieferungBundle\Model\WeinanlieferungReservationModel::findByPk($dc->id);
$memberId = $reservation ? $reservation->uid : 0;
$memberModel = \Contao\MemberModel::findById($memberId);
$defaultMemberNo = $memberModel ? $memberModel->memberno : '';
foreach ($decodedValue as $behaelterNumber) {
$result[] = [
'behaelter' => $behaelterNumber,
'member' => $defaultMemberNo
];
}
return $result;
}
}
return $varValue;
}
/**
* @Callback(table="tl_vr_wa_reservation", target="fields.behaelter_numbers.save")
*/
public function onBehaelterNumbersSaveCallback($varValue, DataContainer $dc)
{
$varValue = StringUtil::deserialize($varValue, true);
if (!empty($varValue) && is_array($varValue))
{
// Get the reservation model
$reservation = \vonRotenberg\WeinanlieferungBundle\Model\WeinanlieferungReservationModel::findByPk($dc->id);
if ($reservation === null) {
throw new \Exception('Reservation not found');
}
// Get the slot model
$slot = $reservation->getRelated('pid');
if ($slot === null) {
throw new \Exception('Slot not found');
}
// Check if the booking is in the past
$isInPast = $slot->time < time();
// Extract behaelter numbers from the multiColumnWizard data
$behaelterNumbers = array_map(function($item) {
return $item['behaelter'];
}, $varValue);
// Only perform validation if the booking is not in the past
if (!$isInPast) {
// Check if the number of container numbers matches the number of required check-ins (unit amount or base behaelter)
$expectedCount = ($reservation->unit_amount ?? 0) > 0 ? (int)$reservation->unit_amount : (int)$reservation->behaelter;
if (count($behaelterNumbers) != $expectedCount) {
throw new \Exception('Die Anzahl der Behälternummern muss mit der Anzahl der gebuchten Einheiten übereinstimmen.');
}
// 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)) {
throw new \Exception('Jede Nummer kann nur einmal verwendet werden.');
}
// Get the standort to access the number_ranges
$standort = $slot->getRelated('pid');
if ($standort === null) {
throw new \Exception('Standort not found');
}
// Get all used numbers from current bookings (excluding past bookings)
$usedNumbers = [];
$currentTime = time();
// Get the database connection
$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 = $this->db->prepare($sql);
$stmt->bindValue(1, $currentTime);
$stmt->bindValue(2, $dc->id);
$result = $stmt->executeQuery();
while ($row = $result->fetchAssociative()) {
$numbers = json_decode($row['behaelter_numbers'], true);
if (is_array($numbers)) {
// Check if we have the new format (array of objects with behaelter and member)
// or the old format (simple array of behaelter numbers)
$isNewFormat = isset($numbers[0]) && is_array($numbers[0]) && isset($numbers[0]['behaelter']);
if ($isNewFormat) {
// Extract just the behaelter numbers from the new format
foreach ($numbers as $item) {
$usedNumbers[] = $item['behaelter'];
}
} else {
// Old format - simple array of behaelter numbers
foreach ($numbers as $number) {
$usedNumbers[] = $number;
}
}
}
}
// Get available numbers directly, excluding used ones
$availableNumbers = $standort->extractNumbersFromRanges($usedNumbers);
// Add the special option "9999" which is always valid
$availableNumbers[] = '9999';
// Check if all numbers are valid
foreach ($numbersForDuplicateCheck as $number) {
if (!in_array($number, $availableNumbers)) {
throw new \Exception('Die Behälternummer "' . $number . '" ist nicht gültig oder wird bereits verwendet.');
}
}
}
// Get the member associated with this reservation as fallback
$memberId = $reservation->uid;
$memberModel = \Contao\MemberModel::findById($memberId);
$defaultMemberNo = $memberModel ? $memberModel->memberno : '';
// Process the data from the multiColumnWizard
$processedData = [];
foreach ($varValue as $item) {
$behaelterNumber = $item['behaelter'];
// If member number is empty, use the default member number
$memberNumber = !empty($item['member']) ? $item['member'] : $defaultMemberNo;
$processedData[] = [
'behaelter' => $behaelterNumber,
'member' => $memberNumber
];
}
// Return the processed data as JSON
return json_encode($processedData);
}
return $varValue;
}
/**
* @Callback(table="tl_vr_wa_reservation", target="config.onsubmit")
*/
public function onSubmitCallback(DataContainer $dc)
{
if (($reservation = WeinanlieferungReservationModel::findByPk($dc->id)) !== null)
{
// Handle check-in
if (Input::post('checked_in') == '1')
{
$time = time();
$reservation->checked_in = '1';
$reservation->checked_in_on = $time;
$reservation->save();
$event = new CheckInCompletedEvent($reservation->row(), $reservation);
$this->eventDispatcher->dispatch($event, CheckInCompletedEvent::NAME);
}
// Handle units
if (Input::post('unit_amount') !== null)
{
// 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 * $multiplier;
$reservation->behaelter = $intBehaelter;
$reservation->save();
}
}
}
/**
* @Callback (table="tl_vr_wa_reservation", target="config.onload")
*/
public function onLoadCallback(DataContainer $dc)
{
if ($_GET['act'] == 'edit' && ($reservation = WeinanlieferungReservationModel::findByPk($dc->id)) !== null && $reservation->checked_in_export_generated)
{
foreach ($GLOBALS['TL_DCA'][$dc->table]['fields'] as &$field)
{
$field['eval']['disabled'] = true;
$field['eval']['readonly'] = true;
if (!empty($field['eval']['chosen']))
{
$field['eval']['chosen'] = false;
}
}
}
}
}