Browse code

Minor improvements to check-in

Benjamin Roth authored on25/07/2025 13:53:32
Showing6 changed files
... ...
@@ -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
 }