Browse code

Extend bottich filter with custom units

Benjamin Roth authored on21/08/2025 16:17:09
Showing2 changed files
... ...
@@ -1,76 +1,78 @@
1 1
 <div hx-get="{{ insert_tag('env::request') }}" hx-headers='{"VR-Ajax": "WaSlotsModule"}' hx-trigger="updateWaList from:body, updateWaBooking from:body" hx-swap="outerHTML" class="{{ class }} content-wrapper block"{{ cssID }}{% if style is defined and style is not empty %} style="{{ style }}"{% endif %}>
2 2
 
3 3
     {% block filter %}
4
-        <form hx-get="{{ pageUrl is defined ? pageUrl : '' }}" hx-push-url="true" hx-headers='{"VR-Ajax": "WaSlotsModule"}' hx-trigger="change, submit" hx-target="closest .content-wrapper" class="filter">
5
-            <div class="row u-justify-center">
6
-                <div class="col-md-2 kapazitaet">
7
-                    <select name="filter_kapazitaet">
8
-                        <option value="">-- Bottiche --</option>
9
-                        {% for option in filter.kapazitaet.options %}
10
-                            <option value="{{ option }}"{% if filter.kapazitaet.selected is defined and filter.kapazitaet.selected == option %} selected{% endif %}>{{ option }}</option>
11
-                        {% endfor %}
12
-                    </select>
13
-                </div>
14
-                <div class="col-md-2 standort">
15
-                    <select name="filter_standort">
16
-                        <option value="">-- Standort --</option>
17
-                        {% for key, option in filter.standort.options %}
18
-                            <option value="{{ key }}"{% if filter.standort.selected is defined and filter.standort.selected == key %} selected{% endif %}>{{ option }}</option>
19
-                        {% endfor %}
20
-                    </select>
21
-                </div>
22
-                <div class="col-md-2 day">
23
-                    <select name="filter_tag">
24
-                        <option value="">-- Datum --</option>
25
-                        {% for key, option in filter.tag.options %}
26
-                            <option value="{{ key }}"{% if filter.tag.selected is defined and filter.tag.selected == key %} selected{% endif %}>{{ option }}</option>
27
-                        {% endfor %}
28
-                    </select>
29
-                </div>
30
-                {#<div class="col-md-2 ernteart">
31
-                    <select name="filter_ernteart">
32
-                        <option value="">-- Ernteart --</option>
33
-                        {% for key, option in filter.ernteart.options %}
34
-                            <option value="{{ key }}"{% if filter.ernteart.selected is defined and filter.ernteart.selected == key %} selected{% endif %}>{{ option }}</option>
35
-                        {% endfor %}
36
-                    </select>
37
-                </div>
38
-                <div class="col-md-3 sorte">
39
-                    <select name="filter_sorte">
40
-                        <option value="">-- Sorte --</option>
41
-                        {% for key, option in filter.sorte.options %}
42
-                            <option value="{{ key }}"{% if filter.sorte.selected is defined and filter.sorte.selected == key %} selected{% endif %}>{{ option }}</option>
43
-                        {% endfor %}
44
-                    </select>
4
+        {% if filter is defined %}
5
+            <form hx-get="{{ pageUrl is defined ? pageUrl : '' }}" hx-push-url="true" hx-headers='{"VR-Ajax": "WaSlotsModule"}' hx-trigger="change, submit" hx-target="closest .content-wrapper" class="filter">
6
+                <div class="row u-justify-center">
7
+                    <div class="col-md-2 kapazitaet">
8
+                        <select name="filter_kapazitaet">
9
+                            <option value="">-- Menge --</option>
10
+                            {% for option in filter.kapazitaet.options %}
11
+                                <option value="{{ option.value }}"{% if filter.kapazitaet.selected is defined and filter.kapazitaet.selected == option.value %} selected{% endif %}>{{ option.label }}</option>
12
+                            {% endfor %}
13
+                        </select>
14
+                    </div>
15
+                    <div class="col-md-2 standort">
16
+                        <select name="filter_standort">
17
+                            <option value="">-- Standort --</option>
18
+                            {% for key, option in filter.standort.options %}
19
+                                <option value="{{ key }}"{% if filter.standort.selected is defined and filter.standort.selected == key %} selected{% endif %}>{{ option }}</option>
20
+                            {% endfor %}
21
+                        </select>
22
+                    </div>
23
+                    <div class="col-md-2 day">
24
+                        <select name="filter_tag">
25
+                            <option value="">-- Datum --</option>
26
+                            {% for key, option in filter.tag.options %}
27
+                                <option value="{{ key }}"{% if filter.tag.selected is defined and filter.tag.selected == key %} selected{% endif %}>{{ option }}</option>
28
+                            {% endfor %}
29
+                        </select>
30
+                    </div>
31
+                    {#<div class="col-md-2 ernteart">
32
+                        <select name="filter_ernteart">
33
+                            <option value="">-- Ernteart --</option>
34
+                            {% for key, option in filter.ernteart.options %}
35
+                                <option value="{{ key }}"{% if filter.ernteart.selected is defined and filter.ernteart.selected == key %} selected{% endif %}>{{ option }}</option>
36
+                            {% endfor %}
37
+                        </select>
38
+                    </div>
39
+                    <div class="col-md-3 sorte">
40
+                        <select name="filter_sorte">
41
+                            <option value="">-- Sorte --</option>
42
+                            {% for key, option in filter.sorte.options %}
43
+                                <option value="{{ key }}"{% if filter.sorte.selected is defined and filter.sorte.selected == key %} selected{% endif %}>{{ option }}</option>
44
+                            {% endfor %}
45
+                        </select>
46
+                    </div>
47
+                    <div class="col-md-3 leseart">
48
+                        <select name="filter_leseart">
49
+                            <option value="">-- Leseart --</option>
50
+                            {% for key, option in filter.leseart.options %}
51
+                                <option value="{{ key }}"{% if filter.leseart.selected is defined and filter.leseart.selected == key %} selected{% endif %}>{{ option }}</option>
52
+                            {% endfor %}
53
+                        </select>
54
+                    </div>#}
55
+    {#                <div class="col submit"><button class="u-block w-100p">Filter übernehmen</button></div>#}
45 56
                 </div>
46
-                <div class="col-md-3 leseart">
47
-                    <select name="filter_leseart">
48
-                        <option value="">-- Leseart --</option>
49
-                        {% for key, option in filter.leseart.options %}
50
-                            <option value="{{ key }}"{% if filter.leseart.selected is defined and filter.leseart.selected == key %} selected{% endif %}>{{ option }}</option>
51
-                        {% endfor %}
52
-                    </select>
53
-                </div>#}
54
-{#                <div class="col submit"><button class="u-block w-100p">Filter übernehmen</button></div>#}
55
-            </div>
56
-        </form>
57
-        {# Todo: Future feature #}
58
-        {#<div class="u-flex u-gap-2 u-justify-center">
59
-            <div class="col">
60
-                <div class="tag-container group-tags group-tags--rounded">
61
-                    <div class="tag tag--dark">Datum:</div>
62
-                    <div class="tag">19.09.1980</div>
63
-                    <div class="tag tag__close-btn"></div>
57
+            </form>
58
+            {# Todo: Future feature #}
59
+            {#<div class="u-flex u-gap-2 u-justify-center">
60
+                <div class="col">
61
+                    <div class="tag-container group-tags group-tags--rounded">
62
+                        <div class="tag tag--dark">Datum:</div>
63
+                        <div class="tag">19.09.1980</div>
64
+                        <div class="tag tag__close-btn"></div>
65
+                    </div>
64 66
                 </div>
65
-            </div>
66
-            <div class="col">
67
-                <div class="tag-container group-tags group-tags--rounded">
68
-                    <div class="tag tag--dark">Standort:</div>
69
-                    <div class="tag">Annahmestelle 1</div>
70
-                    <div class="tag tag__close-btn"></div>
67
+                <div class="col">
68
+                    <div class="tag-container group-tags group-tags--rounded">
69
+                        <div class="tag tag--dark">Standort:</div>
70
+                        <div class="tag">Annahmestelle 1</div>
71
+                        <div class="tag tag__close-btn"></div>
72
+                    </div>
71 73
                 </div>
72
-            </div>
73
-        </div>#}
74
+            </div>#}
75
+        {% endif %}
74 76
     {% endblock %}
75 77
 
76 78
     {% block content %}
... ...
@@ -32,6 +32,7 @@ use vonRotenberg\WeinanlieferungBundle\Model\WeinanlieferungRebsorteModel;
32 32
 use vonRotenberg\WeinanlieferungBundle\Model\WeinanlieferungReservationModel;
33 33
 use vonRotenberg\WeinanlieferungBundle\Model\WeinanlieferungSlotsModel;
34 34
 use vonRotenberg\WeinanlieferungBundle\Model\WeinanlieferungStandortModel;
35
+use vonRotenberg\WeinanlieferungBundle\Model\WeinanlieferungUnitsModel;
35 36
 
36 37
 /**
37 38
  * @FrontendModule(WeinanlieferungSlotsListModuleController::TYPE, category="miscellaneous")
... ...
@@ -63,9 +64,23 @@ class WeinanlieferungSlotsListModuleController extends AbstractFrontendModuleCon
63 64
         // Add filter sql
64 65
         if (!empty($_GET['filter_kapazitaet']))
65 66
         {
66
-            $arrData['filter']['kapazitaet']['selected'] = $_GET['filter_kapazitaet'];
67
+            $filterRaw = (string) $_GET['filter_kapazitaet'];
68
+            $arrData['filter']['kapazitaet']['selected'] = $filterRaw;
69
+
70
+            // Support unit-prefixed values like "u1-16" (unit-specific) and legacy "u-16". Both resolve to numeric capacity 16 for SQL.
71
+            $filterNumeric = null;
72
+            if (preg_match('/^u\d+-(\d+)$/', $filterRaw, $m)) {
73
+                // New format: u<unitId>-<capacity>
74
+                $filterNumeric = (int) $m[1];
75
+            } elseif (preg_match('/^u-(\d+)$/', $filterRaw, $m)) {
76
+                // Legacy format kept for backward compatibility: u-<capacity>
77
+                $filterNumeric = (int) $m[1];
78
+            } else {
79
+                $filterNumeric = (int) $filterRaw;
80
+            }
81
+
67 82
             $arrOptions['column'][] = '(SELECT tl_vr_wa_slot.behaelter - IFNULL(SUM(tl_vr_wa_reservation.behaelter),0) FROM tl_vr_wa_reservation WHERE tl_vr_wa_reservation.pid = tl_vr_wa_slot.id) >= ?';
68
-            $arrOptions['value'][] = $_GET['filter_kapazitaet'];
83
+            $arrOptions['value'][] = $filterNumeric;
69 84
         }
70 85
         if (!empty($_GET['filter_standort']))
71 86
         {
... ...
@@ -159,8 +174,69 @@ class WeinanlieferungSlotsListModuleController extends AbstractFrontendModuleCon
159 174
 
160 175
             // Get filter values
161 176
             $result = $this->db->executeQuery("SELECT MAX(behaelter) as 'kapazitaet' FROM tl_vr_wa_slot WHERE id IN (" . implode(',', $slotIds) . ")");
162
-            $intMaxKapazitaet = $result->fetchOne();
163
-            $arrData['filter']['kapazitaet']['options'] = range(1, max(1, $intMaxKapazitaet));
177
+            $intMaxKapazitaet = (int)$result->fetchOne();
178
+
179
+            // Build capacity filter options as an ordered list where each item has a numeric value and a label.
180
+            // Do not combine unit labels with numeric labels; append unit options at the bottom.
181
+            $options = [];
182
+            $max = max(1, $intMaxKapazitaet);
183
+
184
+            // Plain numeric options first
185
+            for ($i = 1; $i <= $max; $i++) {
186
+                $options[] = [
187
+                    'value' => (string) $i,
188
+                    'label' => (string) $i,
189
+                ];
190
+            }
191
+
192
+            // Determine which units are enabled on any of the shown standorts (locations)
193
+            $allowedUnitIds = [];
194
+            $shownStandortIds = [];
195
+            if (!empty($slotIds)) {
196
+                // Get unique Standort IDs from the shown slots
197
+                $shownStandortIds = array_unique($slots->fetchEach('pid'));
198
+                if (!empty($shownStandortIds)) {
199
+                    if (($ShownStandorte = WeinanlieferungStandortModel::findBy(["id IN (" . implode(',', $shownStandortIds) . ")"],null)) !== null) {
200
+                        foreach ($ShownStandorte as $ShownStandort) {
201
+                            $ids = \Contao\StringUtil::deserialize($ShownStandort->units, true);
202
+                            if (!empty($ids)) {
203
+                                foreach ($ids as $uid) {
204
+                                    $allowedUnitIds[(int)$uid] = true;
205
+                                }
206
+                            }
207
+                        }
208
+                    }
209
+                }
210
+            }
211
+
212
+            // Append unit options at the bottom, without exceeding max capacity and only for allowed units
213
+            if (!empty($allowedUnitIds) && ($Units = WeinanlieferungUnitsModel::findAll()) !== null) {
214
+                foreach ($Units as $unit) {
215
+                    if (empty($allowedUnitIds[(int)$unit->id])) {
216
+                        continue; // this unit is not enabled on any shown standort
217
+                    }
218
+
219
+                    $unitContainers = (int) $unit->containers;
220
+                    if ($unitContainers <= 0) {
221
+                        continue;
222
+                    }
223
+
224
+                    $unitName = $unit->description ?: $unit->title; // visible unit name
225
+                    $multiplierMax = intdiv($max, $unitContainers);
226
+
227
+                    for ($m = 1; $m <= $multiplierMax; $m++) {
228
+                        $value = (string) ($m * $unitContainers);
229
+                        $label = $m . ' ' . $unitName;
230
+
231
+                        $options[] = [
232
+                            'value' => 'u' . $unit->id . '-' . $value,
233
+                            'label' => $label,
234
+                        ];
235
+                    }
236
+                }
237
+            }
238
+
239
+            $arrData['filter']['kapazitaet']['options'] = $options;
164 240
 
165 241
             if (($Standorte = WeinanlieferungStandortModel::findBy(["id IN (" . implode(',', $standortIds) . ")", "id IN (SELECT tl_vr_wa_slot.pid FROM tl_vr_wa_slot WHERE tl_vr_wa_slot.id IN (" . implode(',', $slotIds) . "))"], null, ['order' => 'title ASC'])) !== null)
166 242
             {