| ... | ... |
@@ -28,3 +28,8 @@ $GLOBALS['TL_LANG']['MSC']['wa_approval_status'] = [ |
| 28 | 28 |
'canceled' => 'abgelehnt', |
| 29 | 29 |
'approved' => 'angenommen', |
| 30 | 30 |
]; |
| 31 |
+ |
|
| 32 |
+$GLOBALS['TL_LANG']['MSC']['wa_checkin_status'] = [ |
|
| 33 |
+ 'pending' => 'Check-In ausstehend', |
|
| 34 |
+ 'checked_in' => 'Check-In erfolgt', |
|
| 35 |
+]; |
| ... | ... |
@@ -137,6 +137,14 @@ |
| 137 | 137 |
</div>{% endif %}
|
| 138 | 138 |
{% endif %}
|
| 139 | 139 |
</div> |
| 140 |
+ {% if booking.checked_in %}
|
|
| 141 |
+ <div class="col-1"> |
|
| 142 |
+ </div> |
|
| 143 |
+ <div class="col-10 behaelter_nummern"> |
|
| 144 |
+ <div class="t-label">Eingecheckte Behälternummern</div> |
|
| 145 |
+ {{ booking.behaelter_numbers|join(', ') }}
|
|
| 146 |
+ </div> |
|
| 147 |
+ {% endif %}
|
|
| 140 | 148 |
<div class="col px-1 u-text-right action"> |
| 141 | 149 |
<a |
| 142 | 150 |
href="/contao?do=weinanlieferung&table=tl_vr_wa_reservation&act=edit&id={{ booking.id }}&rt={{ request_token }}&ref={{ ref }}"
|
| ... | ... |
@@ -30,7 +30,12 @@ |
| 30 | 30 |
{% else %}
|
| 31 | 31 |
{% set status = 'pending' %}
|
| 32 | 32 |
{% endif %}
|
| 33 |
- <div class="row py-2 mb-1 u-flex-nowrap-md u-items-center status status--{{ status }}">
|
|
| 33 |
+ {% if booking.checked_in == '1' %}
|
|
| 34 |
+ {% set checkin_state = 'checked_in' %}
|
|
| 35 |
+ {% else %}
|
|
| 36 |
+ {% set checkin_state = 'pending' %}
|
|
| 37 |
+ {% endif %}
|
|
| 38 |
+ <div class="row py-2 mb-1 u-flex-nowrap-md u-items-center status status--{{ status }} checkin--{{ checkin_state }}">
|
|
| 34 | 39 |
{#<div class="col-1">
|
| 35 | 40 |
<div class="u-flex u-items-center u-justify-center"> |
| 36 | 41 |
<i class="status-icon" title="{{ ('MSC.wa_approval_status.'~status)|trans([], 'contao_default') }}"></i>
|
| ... | ... |
@@ -38,13 +43,20 @@ |
| 38 | 43 |
</div>#} |
| 39 | 44 |
<div class="col-11"> |
| 40 | 45 |
<div class="grid-md u-gap-1"> |
| 41 |
- <div class="grid-c-12 bg-white p-1"> |
|
| 46 |
+ <div class="grid-c-6 bg-white p-1"> |
|
| 42 | 47 |
<div class="u-flex u-flex-wrap u-gap-1"> |
| 43 | 48 |
<i class="status-icon"></i> |
| 44 | 49 |
{{ ('MSC.wa_approval_status.'~status)|trans([], 'contao_default') }}
|
| 45 | 50 |
</div> |
| 46 | 51 |
</div> |
| 47 | 52 |
|
| 53 |
+ <div class="grid-c-6 bg-white p-1"> |
|
| 54 |
+ <div class="u-flex u-flex-wrap u-gap-1"> |
|
| 55 |
+ <i class="checkin-icon"></i> |
|
| 56 |
+ {{ ('MSC.wa_checkin_status.'~checkin_state)|trans([], 'contao_default') }}
|
|
| 57 |
+ </div> |
|
| 58 |
+ </div> |
|
| 59 |
+ |
|
| 48 | 60 |
<div class="grid-c-3 time bg-white p-1"> |
| 49 | 61 |
<div class="u-flex u-items-center u-gap-1"> |
| 50 | 62 |
<i class="icon-uhr-outline"></i> |
| ... | ... |
@@ -216,7 +216,8 @@ ul {
|
| 216 | 216 |
|
| 217 | 217 |
.status {
|
| 218 | 218 |
|
| 219 |
- .status-icon {
|
|
| 219 |
+ .status-icon, |
|
| 220 |
+ .checkin-icon {
|
|
| 220 | 221 |
visibility: hidden; |
| 221 | 222 |
position: relative; |
| 222 | 223 |
width: 24px; |
| ... | ... |
@@ -237,7 +238,8 @@ ul {
|
| 237 | 238 |
|
| 238 | 239 |
} |
| 239 | 240 |
} |
| 240 |
-.status-icon {
|
|
| 241 |
+.status-icon, |
|
| 242 |
+.checkin-icon {
|
|
| 241 | 243 |
visibility: hidden; |
| 242 | 244 |
position: relative; |
| 243 | 245 |
display: inline-block; |
| ... | ... |
@@ -308,3 +310,24 @@ ul {
|
| 308 | 310 |
} |
| 309 | 311 |
} |
| 310 | 312 |
} |
| 313 |
+ |
|
| 314 |
+.status, |
|
| 315 |
+.checkin-icon {
|
|
| 316 |
+ &.checkin--checked_in {
|
|
| 317 |
+ &:not(.status), |
|
| 318 |
+ .checkin-icon {
|
|
| 319 |
+ &:after {
|
|
| 320 |
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' class='icon icon-tabler icon-tabler-thumb-up' width='100%25' height='100%25' viewBox='0 0 24 24' stroke-width='1.5' stroke='%2300b341' fill='none' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath stroke='none' d='M0 0h24v24H0z' fill='none'/%3E%3Cpath d='M7 11v8a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1v-7a1 1 0 0 1 1 -1h3a4 4 0 0 0 4 -4v-1a2 2 0 0 1 4 0v5h3a2 2 0 0 1 2 2l-1 5a2 3 0 0 1 -2 2h-7a3 3 0 0 1 -3 -3' /%3E%3C/svg%3E");
|
|
| 321 |
+ } |
|
| 322 |
+ } |
|
| 323 |
+ } |
|
| 324 |
+ |
|
| 325 |
+ &.checkin--pending {
|
|
| 326 |
+ &:not(.status), |
|
| 327 |
+ .checkin-icon {
|
|
| 328 |
+ &:after {
|
|
| 329 |
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' class='icon icon-tabler icon-tabler-hourglass-high' width='100%25' height='100%25' viewBox='0 0 24 24' stroke-width='1.5' stroke='%23ff9300' fill='none' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath stroke='none' d='M0 0h24v24H0z' fill='none'/%3E%3Cpath d='M6.5 7h11' /%3E%3Cpath d='M6 20v-2a6 6 0 1 1 12 0v2a1 1 0 0 1 -1 1h-10a1 1 0 0 1 -1 -1z' /%3E%3Cpath d='M6 4v2a6 6 0 1 0 12 0v-2a1 1 0 0 0 -1 -1h-10a1 1 0 0 0 -1 1z' /%3E%3C/svg%3E");
|
|
| 330 |
+ } |
|
| 331 |
+ } |
|
| 332 |
+ } |
|
| 333 |
+} |
| ... | ... |
@@ -279,7 +279,8 @@ class WeinanlieferungBookingsController extends AbstractController |
| 279 | 279 |
'lage' => $arrLagen, |
| 280 | 280 |
'slot' => $Slot->row(), |
| 281 | 281 |
'standort' => $strStandort, |
| 282 |
- 'member' => $booking->getRelated('uid') !== null ? $booking->getRelated('uid')->row() : null
|
|
| 282 |
+ 'member' => $booking->getRelated('uid') !== null ? $booking->getRelated('uid')->row() : null,
|
|
| 283 |
+ 'behaelter_numbers' => \json_decode($booking->behaelter_numbers) |
|
| 283 | 284 |
]); |
| 284 | 285 |
} |
| 285 | 286 |
} |
| ... | ... |
@@ -167,4 +167,108 @@ class WeinanlieferungReservationContainerListener |
| 167 | 167 |
|
| 168 | 168 |
return $varValue; |
| 169 | 169 |
} |
| 170 |
+ |
|
| 171 |
+ /** |
|
| 172 |
+ * @Callback(table="tl_vr_wa_reservation", target="fields.behaelter_numbers.load") |
|
| 173 |
+ */ |
|
| 174 |
+ public function onBehaelterNumbersLoadCallback($varValue, DataContainer $dc) |
|
| 175 |
+ {
|
|
| 176 |
+ if (!empty($varValue)) |
|
| 177 |
+ {
|
|
| 178 |
+ $varValue = implode(', ',\json_decode($varValue));
|
|
| 179 |
+ } |
|
| 180 |
+ return $varValue; |
|
| 181 |
+ } |
|
| 182 |
+ |
|
| 183 |
+ /** |
|
| 184 |
+ * @Callback(table="tl_vr_wa_reservation", target="fields.behaelter_numbers.save") |
|
| 185 |
+ */ |
|
| 186 |
+ public function onBehaelterNumbersSaveCallback($varValue, DataContainer $dc) |
|
| 187 |
+ {
|
|
| 188 |
+ if (!empty($varValue)) |
|
| 189 |
+ {
|
|
| 190 |
+ $values = array_map('trim', explode(',', $varValue));
|
|
| 191 |
+ |
|
| 192 |
+ // Get the reservation model |
|
| 193 |
+ $reservation = \vonRotenberg\WeinanlieferungBundle\Model\WeinanlieferungReservationModel::findByPk($dc->id); |
|
| 194 |
+ if ($reservation === null) {
|
|
| 195 |
+ throw new \Exception('Reservation not found');
|
|
| 196 |
+ } |
|
| 197 |
+ |
|
| 198 |
+ // Get the slot model |
|
| 199 |
+ $slot = $reservation->getRelated('pid');
|
|
| 200 |
+ if ($slot === null) {
|
|
| 201 |
+ throw new \Exception('Slot not found');
|
|
| 202 |
+ } |
|
| 203 |
+ |
|
| 204 |
+ // Check if the booking is in the past |
|
| 205 |
+ $isInPast = $slot->time < time(); |
|
| 206 |
+ |
|
| 207 |
+ // Only perform validation if the booking is not in the past |
|
| 208 |
+ if (!$isInPast) {
|
|
| 209 |
+ // Check if the number of container numbers matches the number of booked containers |
|
| 210 |
+ if (count($values) != $reservation->behaelter) {
|
|
| 211 |
+ throw new \Exception('Die Anzahl der Behälternummern muss mit der Anzahl der gebuchten Behälter übereinstimmen.');
|
|
| 212 |
+ } |
|
| 213 |
+ |
|
| 214 |
+ // Filter out the special value 9999 ("Nummer nicht bekannt") for duplicate check
|
|
| 215 |
+ $numbersForDuplicateCheck = array_filter($values, function($number) {
|
|
| 216 |
+ return $number !== '9999'; |
|
| 217 |
+ }); |
|
| 218 |
+ |
|
| 219 |
+ // Check for duplicate numbers (excluding the special value 9999) |
|
| 220 |
+ if (count(array_unique($numbersForDuplicateCheck)) != count($numbersForDuplicateCheck)) {
|
|
| 221 |
+ throw new \Exception('Jede Nummer kann nur einmal verwendet werden.');
|
|
| 222 |
+ } |
|
| 223 |
+ |
|
| 224 |
+ // Get the standort to access the number_ranges |
|
| 225 |
+ $standort = $slot->getRelated('pid');
|
|
| 226 |
+ if ($standort === null) {
|
|
| 227 |
+ throw new \Exception('Standort not found');
|
|
| 228 |
+ } |
|
| 229 |
+ |
|
| 230 |
+ // Get all used numbers from current bookings (excluding past bookings) |
|
| 231 |
+ $usedNumbers = []; |
|
| 232 |
+ $currentTime = time(); |
|
| 233 |
+ |
|
| 234 |
+ // Get the database connection |
|
| 235 |
+ $sql = "SELECT r.behaelter_numbers |
|
| 236 |
+ FROM tl_vr_wa_reservation r |
|
| 237 |
+ JOIN tl_vr_wa_slot s ON r.pid = s.id |
|
| 238 |
+ WHERE r.behaelter_numbers != '' |
|
| 239 |
+ AND s.time >= ? |
|
| 240 |
+ AND r.id != ?"; |
|
| 241 |
+ |
|
| 242 |
+ $stmt = $this->db->prepare($sql); |
|
| 243 |
+ $stmt->bindValue(1, $currentTime); |
|
| 244 |
+ $stmt->bindValue(2, $dc->id); |
|
| 245 |
+ $result = $stmt->executeQuery(); |
|
| 246 |
+ |
|
| 247 |
+ while ($row = $result->fetchAssociative()) {
|
|
| 248 |
+ $numbers = json_decode($row['behaelter_numbers'], true); |
|
| 249 |
+ if (is_array($numbers)) {
|
|
| 250 |
+ foreach ($numbers as $number) {
|
|
| 251 |
+ $usedNumbers[] = $number; |
|
| 252 |
+ } |
|
| 253 |
+ } |
|
| 254 |
+ } |
|
| 255 |
+ |
|
| 256 |
+ // Get available numbers directly, excluding used ones |
|
| 257 |
+ $availableNumbers = $standort->extractNumbersFromRanges($usedNumbers); |
|
| 258 |
+ |
|
| 259 |
+ // Add the special option "9999" which is always valid |
|
| 260 |
+ $availableNumbers[] = '9999'; |
|
| 261 |
+ |
|
| 262 |
+ // Check if all numbers are valid |
|
| 263 |
+ foreach ($numbersForDuplicateCheck as $number) {
|
|
| 264 |
+ if (!in_array($number, $availableNumbers)) {
|
|
| 265 |
+ throw new \Exception('Die Behälternummer "' . $number . '" ist nicht gültig oder wird bereits verwendet.');
|
|
| 266 |
+ } |
|
| 267 |
+ } |
|
| 268 |
+ } |
|
| 269 |
+ |
|
| 270 |
+ $varValue = json_encode($values); |
|
| 271 |
+ } |
|
| 272 |
+ return $varValue; |
|
| 273 |
+ } |
|
| 170 | 274 |
} |