Browse code

Update

Benjamin Roth authored on09/08/2023 01:02:13
Showing8 changed files
... ...
@@ -9,6 +9,7 @@
9 9
  */
10 10
 
11 11
 use vonRotenberg\WeinanlieferungBundle\Model\WeinanlieferungRebsorteModel;
12
+use vonRotenberg\WeinanlieferungBundle\Model\WeinanlieferungReservationModel;
12 13
 use vonRotenberg\WeinanlieferungBundle\Model\WeinanlieferungSlotsModel;
13 14
 use vonRotenberg\WeinanlieferungBundle\Model\WeinanlieferungStandortModel;
14 15
 
... ...
@@ -21,3 +22,4 @@ $GLOBALS['BE_MOD']['content']['weinanlieferung'] = array
21 22
 $GLOBALS['TL_MODELS']['tl_vr_wa_slot'] = WeinanlieferungSlotsModel::class;
22 23
 $GLOBALS['TL_MODELS']['tl_vr_wa_standort'] = WeinanlieferungStandortModel::class;
23 24
 $GLOBALS['TL_MODELS']['tl_vr_wa_rebsorte'] = WeinanlieferungRebsorteModel::class;
25
+$GLOBALS['TL_MODELS']['tl_vr_wa_reservation'] = WeinanlieferungReservationModel::class;
24 26
new file mode 100644
... ...
@@ -0,0 +1,63 @@
1
+<?php
2
+
3
+/**
4
+ * This file is part of contao-weinanlieferung-bundle.
5
+ *
6
+ * (c) vonRotenberg
7
+ *
8
+ * @license commercial
9
+ */
10
+
11
+use Contao\DC_Table;
12
+use Contao\DataContainer;
13
+
14
+$GLOBALS['TL_DCA']['tl_vr_wa_reservation'] = array
15
+(
16
+
17
+    // Config
18
+    'config' => array
19
+    (
20
+        'sql'              => array
21
+        (
22
+            'keys' => array
23
+            (
24
+                'id' => 'primary',
25
+                'pid' => 'index'
26
+            )
27
+        )
28
+    ),
29
+
30
+    'fields' => array
31
+    (
32
+        'id'          => array
33
+        (
34
+            'sql' => "int(10) unsigned NOT NULL auto_increment"
35
+        ),
36
+        'pid'         => array
37
+        (
38
+            'foreignKey' => 'tl_vr_wa_slot.time',
39
+            'sql'        => "int(10) unsigned NOT NULL default '0'",
40
+            'relation'   => array('type' => 'belongsTo', 'load' => 'lazy')
41
+        ),
42
+        'tstamp'      => array
43
+        (
44
+            'sql' => "int(10) unsigned NOT NULL default '0'"
45
+        ),
46
+        'uid'         => array
47
+        (
48
+            'foreignKey' => 'tl_member.username',
49
+            'sql'        => "int(10) unsigned NOT NULL default '0'",
50
+            'relation'   => array('type' => 'belongsTo', 'load' => 'lazy')
51
+        ),
52
+        'behaelter'         => array
53
+        (
54
+            'sql'        => "smallint(3) unsigned NOT NULL default 0",
55
+        ),
56
+        'sorten'       => array
57
+        (
58
+            'foreignKey'   => 'tl_vr_wa_rebsorte.title',
59
+            'sql'       => "blob NULL",
60
+            'relation'   => array('type' => 'hasMany', 'load' => 'lazy')
61
+        ),
62
+    )
63
+);
... ...
@@ -5,16 +5,42 @@
5 5
             <dd>{{ slot.time|date('d.m.Y H:i') }}</dd>
6 6
 
7 7
             <dt>Verfügbare Behälterkapazität</dt>
8
-            <dd>{{ slot.behaelter }}</dd>
8
+            <dd>{{ slot.behaelterAvailable }}</dd>
9 9
 
10 10
             <dt>Verarbeitete Sorten</dt>
11 11
             <dd>{{ slot.sorte|join(', ') }}</dd>
12 12
         </dl>
13
-
13
+        <h3>Reservieren</h3>
14
+        {% if buchen.buchbar %}
15
+            <form hx-post="/_ajax/vr_wa/v1/slot?do=reservate">
16
+                <input type="hidden" name="id" value="{{ id }}">
17
+                <div class="dflex">
18
+                    <div>
19
+                        <label for="res-behaelter">Liefernde Behältermenge</label>
20
+                        <select id="res-behaelter" name="behaelter" required>
21
+                            <option value="">-</option>
22
+                            {% for option in buchen.behaelter %}
23
+                                <option value="{{ option }}">{{ option }}</option>
24
+                            {% endfor %}
25
+                        </select>
26
+                    </div>
27
+                    <fieldset>
28
+                      <legend>Anliefernde Rebsorte(n)</legend>
29
+                        {% for value,label in buchen.sorten %}
30
+                            <label><input type="checkbox" name="sorten[]" value="{{ value }}"> <span class="checkable">{{ label }}</span></label><br>
31
+                        {% endfor %}
32
+                    </fieldset>
33
+                    <div>
34
+                        <button type="submit">Reservieren</button>
35
+                    </div>
36
+                </div>
37
+            </form>
38
+        {% else %}
39
+          <p class="warning">Dieser Zeit-Slot ist bereits ausgebucht</p>
40
+        {% endif %}
14 41
     </div>
15 42
 </div>
16 43
 <script>
17
-
18 44
     (function ($) {
19 45
 
20 46
         window.modals = window.modals || []
... ...
@@ -1,26 +1,57 @@
1
-<div hx-get="{{ insert_tag('env::request') }}" hx-headers='{"SR-Ajax": "CommunityFriendsModule"}' hx-trigger="updateWaList from:body" class="{{ class }} content-wrapper block"{{ cssID }}{% if style is defined and style is not empty %} style="{{ style }}"{% endif %}>
1
+<div hx-get="{{ insert_tag('env::request') }}" hx-headers='{"VR-Ajax": "WaSlotsModule"}' hx-trigger="updateWaList from:body" class="{{ class }} content-wrapper block"{{ cssID }}{% if style is defined and style is not empty %} style="{{ style }}"{% endif %}>
2
+
3
+    {% block filter %}
4
+        <form hx-get="{{ insert_tag('env::request') }}" hx-headers='{"VR-Ajax": "WaSlotsModule"}' hx-trigger="change, submit" hx-target="closest .content-wrapper" class="filter">
5
+            <div class="flex">
6
+                <div class="full two-fifth-500 kapazitaet">
7
+                    <select name="filter_kapazitaet">
8
+                        <option value="">-</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="full two-fifth-500 sorte">
15
+                    <select name="filter_sorte">
16
+                        <option value="">-</option>
17
+                        {% for key, option in filter.sorte.options %}
18
+                            <option value="{{ key }}"{% if filter.sorte.selected is defined and filter.sorte.selected == key %} selected{% endif %}>{{ option }}</option>
19
+                        {% endfor %}
20
+                    </select>
21
+                </div>
22
+                <div class="full fifth-500 submit"><button>Filter übernehmen</button></div>
23
+            </div>
24
+        </form>
25
+    {% endblock %}
2 26
 
3 27
     {% block content %}
4
-        {% if slots is defined and slots|length %}
28
+        {% if days is defined and days|length %}
5 29
             <div class="list">
6
-                {% for slot in slots %}
7
-                    <div class="row">
8
-                        <div class="flex align-items-center">
9
-                            <div class="full half-500 third-900 time">
10
-                                <span class="title">Tag/Uhrzeit</span>
11
-                                {{ slot.time|date('d.m.Y H:i') }}
12
-                            </div>
13
-                            <div class="full half-500 fourth-900 behaelter">
14
-                                <span class="title">Verfügbare Behälterkapazität</span>
15
-                                {{ slot.behaelter }}
16
-                            </div>
17
-                            <div class="full two-third-500 fourth-900 rebsorten">
18
-                                <span class="title">Verarbeitete Sorten</span>
19
-                                {{ slot.sorte }}
20
-                            </div>
21
-                            <div class="full third-500 sixth-900 align-right action">
22
-                                <a href="javascript:;" class="button">Anzeigen</a>
23
-                            </div>
30
+                {% for day,slots in days %}
31
+                    <h3 class="toggler-dis">{{ day|date('d.m.Y') }}<span class="counter">{{ slots|length }}</span></h3>
32
+                    <div class="accordion-dis">
33
+                        <div class="slots">
34
+                            {% for slot in slots %}
35
+                                <div class="row{{ not slot.buchbar ? ' error' : '' }}">
36
+                                    <div class="flex align-items-center">
37
+                                        <div class="full half-500 third-900 time">
38
+                                            <span class="title">Uhrzeit</span>
39
+                                            {{ slot.time|date('H:i') }}
40
+                                        </div>
41
+                                        <div class="full half-500 fourth-900 behaelter">
42
+                                            <span class="title">Verfügbare Behälterkapazität</span>
43
+                                            {{ slot.behaelterAvailable }}
44
+                                        </div>
45
+                                        <div class="full two-third-500 fourth-900 rebsorten">
46
+                                            <span class="title">Verarbeitete Sorten</span>
47
+                                            {{ slot.sorte|join(', ') }}
48
+                                        </div>
49
+                                        <div class="full third-500 sixth-900 align-right action">
50
+                                            <a hx-get="/_ajax/vr_wa/v1/slot?do=details&id={{ slot.id }}" hx-target="body" hx-swap="beforeend" href="javascript:;" class="button{{ not slot.buchbar ? ' pseudo' : '' }}">Anzeigen</a>
51
+                                        </div>
52
+                                    </div>
53
+                                </div>
54
+                            {% endfor %}
24 55
                         </div>
25 56
                     </div>
26 57
                 {% endfor %}
... ...
@@ -28,4 +59,26 @@
28 59
         {% endif %}
29 60
     {% endblock %}
30 61
 
62
+    {% block script %}
63
+        {#<script>
64
+            jQuery(function($) {
65
+                $(".mod_wa_slots_list").accordion({
66
+                    // Put custom options here
67
+                    heightStyle: 'content',
68
+                    header: '.toggler',
69
+                    collapsible: true,
70
+                    create: function(event, ui) {
71
+                        ui.header.addClass('active');
72
+                        $('.toggler').attr('tabindex', 0);
73
+                    },
74
+                    activate: function(event, ui) {
75
+                        ui.newHeader.addClass('active');
76
+                        ui.oldHeader.removeClass('active');
77
+                        $('.toggler').attr('tabindex', 0);
78
+                    }
79
+                });
80
+            });
81
+        </script>#}
82
+    {% endblock %}
83
+
31 84
 </div>
... ...
@@ -15,11 +15,14 @@ namespace vonRotenberg\WeinanlieferungBundle\Controller\Frontend\Ajax;
15 15
 use Contao\CoreBundle\Controller\AbstractController;
16 16
 use Contao\CoreBundle\Framework\ContaoFramework;
17 17
 use Contao\CoreBundle\Security\Authentication\Token\TokenChecker;
18
+use Contao\FrontendUser;
19
+use Contao\Input;
18 20
 use Contao\System;
19 21
 use Symfony\Component\HttpFoundation\Request;
20 22
 use Symfony\Component\HttpFoundation\Response;
21 23
 use Symfony\Component\Routing\Annotation\Route;
22 24
 use Symfony\Contracts\Translation\TranslatorInterface;
25
+use vonRotenberg\WeinanlieferungBundle\Model\WeinanlieferungReservationModel;
23 26
 use vonRotenberg\WeinanlieferungBundle\Model\WeinanlieferungSlotsModel;
24 27
 
25 28
 /**
... ...
@@ -57,6 +60,9 @@ class SlotAjaxController extends AbstractController
57 60
             case 'details':
58 61
                 return $this->renderDetails();
59 62
                 break;
63
+
64
+            case 'reservate':
65
+                return $this->reservate();
60 66
         }
61 67
 
62 68
         return new Response('',500);
... ...
@@ -78,20 +84,59 @@ class SlotAjaxController extends AbstractController
78 84
         $arrSorten = [];
79 85
         if (($Sorten = $Slot->getRelated('sorte')) !== null)
80 86
         {
81
-            $arrSorten = $Sorten->fetchEach('title');
87
+            $arrSorten = array_combine($Sorten->fetchEach('id'),$Sorten->fetchEach('title'));
82 88
         }
83 89
 
90
+        $intAvailableBehaelter = $Slot->getAvailableBehaelter();
91
+
84 92
         $arrData = [
85 93
             'id'       => $Slot->id,
86 94
             'slot'     => array_merge($Slot->row(),[
87
-                'sorte' => $arrSorten
95
+                'sorte' => $arrSorten,
96
+                'behaelterAvailable' => $intAvailableBehaelter
88 97
             ]),
89
-            'standort' => $Slot->getRelated('pid')
98
+            'standort' => $Slot->getRelated('pid'),
99
+            'buchen' => [
100
+                'buchbar' => (boolean) $intAvailableBehaelter,
101
+                'behaelter' => range(min($intAvailableBehaelter,1),$intAvailableBehaelter),
102
+                'sorten' => $arrSorten
103
+            ]
90 104
         ];
91 105
 
92 106
         return $this->render('@Contao/modal_slot_details.html.twig',$arrData);
93 107
     }
94 108
 
109
+    protected function reservate()
110
+    {
111
+        if (empty($_REQUEST['id']) || empty(Input::post('behaelter')) || empty(Input::post('sorten')))
112
+        {
113
+            return new Response('Missing parameter',500);
114
+        }
115
+
116
+        $arrSorten = [];
117
+        if (!is_array(Input::post('sorten')))
118
+        {
119
+            $arrSorten[] = Input::post('sorten');
120
+        } else {
121
+            $arrSorten = implode(',', Input::post('sorten'));
122
+        }
123
+
124
+        $Reservation = new WeinanlieferungReservationModel();
125
+
126
+        $Reservation->setRow([
127
+            'pid' => $_REQUEST['id'],
128
+            'tstamp' => time(),
129
+            'uid' => FrontendUser::getInstance()->id,
130
+            'behaelter' => Input::post('behaelter'),
131
+            'sorten' => $arrSorten
132
+        ]);
133
+
134
+
135
+        $Reservation->save();
136
+
137
+        return new Response('<p>Reservierung erfolgreich</p>');
138
+    }
139
+
95 140
     protected function renderUnauthorized()
96 141
     {
97 142
         return $this->render('@Contao/modal_unauthorized.html.twig');
... ...
@@ -12,13 +12,18 @@ declare(strict_types=1);
12 12
 
13 13
 namespace vonRotenberg\WeinanlieferungBundle\Controller\Frontend\Module;
14 14
 
15
+use Contao\Controller;
15 16
 use Contao\CoreBundle\Controller\FrontendModule\AbstractFrontendModuleController;
17
+use Contao\CoreBundle\Exception\ResponseException;
18
+use Contao\CoreBundle\InsertTag\InsertTagParser;
16 19
 use Contao\CoreBundle\ServiceAnnotation\FrontendModule;
20
+use Contao\Date;
17 21
 use Contao\ModuleModel;
18 22
 use Contao\StringUtil;
19 23
 use Contao\Template;
20 24
 use Symfony\Component\HttpFoundation\Request;
21 25
 use Symfony\Component\HttpFoundation\Response;
26
+use vonRotenberg\WeinanlieferungBundle\Model\WeinanlieferungRebsorteModel;
22 27
 use vonRotenberg\WeinanlieferungBundle\Model\WeinanlieferungSlotsModel;
23 28
 
24 29
 /**
... ...
@@ -28,22 +33,71 @@ class WeinanlieferungSlotsListModuleController extends AbstractFrontendModuleCon
28 33
 {
29 34
     public const TYPE = 'wa_slots_list';
30 35
 
36
+    private $insertTagParser;
37
+
38
+    public function __construct(InsertTagParser $insertTagParser)
39
+    {
40
+        $this->insertTagParser = $insertTagParser;
41
+    }
42
+
43
+
31 44
     protected function getResponse(Template $template, ModuleModel $model, Request $request): ?Response
32 45
     {
33 46
         $standortIds = StringUtil::deserialize($model->vr_wa_standortId);
34
-
35 47
         $arrData = $template->getData();
48
+        $arrOptions = [];
36 49
 
37
-        if (($slots = WeinanlieferungSlotsModel::findMultiplePublishedByPids($standortIds)) !== null)
50
+        // Add filter values
51
+        if (!empty($_GET['filter_kapazitaet']))
52
+        {
53
+            $arrData['filter']['kapazitaet']['selected'] = $_GET['filter_kapazitaet'];
54
+            $arrOptions['column'][] = 'behaelter = ?';
55
+            $arrOptions['value'][] = $_GET['filter_kapazitaet'];
56
+        }
57
+        if (!empty($_GET['filter_sorte']))
38 58
         {
59
+            $arrData['filter']['sorte']['selected'] = $_GET['filter_sorte'];
60
+            $arrOptions['column'][] = 'FIND_IN_SET(?,sorte)';
61
+            $arrOptions['value'][] = $_GET['filter_sorte'];
62
+        }
63
+        $arrData['filter']['kapazitaet']['options'] = range(1,30);
64
+
65
+        if (($Sorten = WeinanlieferungRebsorteModel::findAll(['order'=>'title ASC'])) !== null)
66
+        {
67
+            $arrData['filter']['sorte']['options'] = array_combine($Sorten->fetchEach('id'),$Sorten->fetchEach('title'));
68
+        }
69
+
70
+        // Get available slots
71
+        if (($slots = WeinanlieferungSlotsModel::findMultiplePublishedByPids($standortIds,$arrOptions)) !== null)
72
+        {
73
+            /** @var WeinanlieferungSlotsModel $slot */
39 74
             foreach ($slots as $slot)
40 75
             {
41
-                $arrData['slots'][] = $slot->row();
76
+                $day = new Date($slot->date);
77
+                $arrSorten = [];
78
+                $intAvailableBehaelter = $slot->getAvailableBehaelter();
79
+
80
+                if (($Sorten = $slot->getRelated('sorte')) !== null)
81
+                {
82
+                    $arrSorten = $Sorten->fetchEach('title');
83
+                }
84
+
85
+                $arrData['days'][$day->dayBegin][] = array_merge($slot->row(),[
86
+                    'sorte' => $arrSorten,
87
+                    'behaelterAvailable' => $intAvailableBehaelter,
88
+                    'buchbar' => (boolean) $intAvailableBehaelter
89
+                ]);
42 90
             }
43 91
         }
44 92
 
45 93
         $template->setData($arrData);
46 94
 
95
+        // Handle ajax
96
+        if ($request->headers->get('VR-Ajax') == 'WaSlotsModule')
97
+        {
98
+            throw new ResponseException(new Response($this->insertTagParser->replace($template->parse())));
99
+        }
100
+
47 101
         return $template->getResponse();
48 102
     }
49 103
 
50 104
new file mode 100644
... ...
@@ -0,0 +1,24 @@
1
+<?php
2
+
3
+declare(strict_types=1);
4
+
5
+/*
6
+ * This file is part of contao-weinanlieferung-bundle.
7
+ *
8
+ * (c) vonRotenberg
9
+ *
10
+ * @license commercial
11
+ */
12
+
13
+namespace vonRotenberg\WeinanlieferungBundle\Model;
14
+
15
+use Contao\Model;
16
+
17
+class WeinanlieferungReservationModel extends Model
18
+{
19
+    /**
20
+     * Table name
21
+     * @var string
22
+     */
23
+    protected static $strTable = 'tl_vr_wa_reservation';
24
+}
... ...
@@ -12,9 +12,11 @@ declare(strict_types=1);
12 12
 
13 13
 namespace vonRotenberg\WeinanlieferungBundle\Model;
14 14
 
15
+use Contao\Controller;
15 16
 use Contao\Database;
16 17
 use Contao\Model;
17 18
 use Contao\Model\Registry;
19
+use Doctrine\DBAL\Connection;
18 20
 
19 21
 class WeinanlieferungSlotsModel extends Model
20 22
 {
... ...
@@ -40,7 +42,7 @@ class WeinanlieferungSlotsModel extends Model
40 42
             $arrOptions['order'] = "$t.time ASC";
41 43
         }
42 44
 
43
-        return static::findBy($arrColumns, $intId, $arrOptions);
45
+        return static::findOneBy($arrColumns, $intId, $arrOptions);
44 46
     }
45 47
 
46 48
     public static function findPublishedByPid($intPid, array $arrOptions=array())
... ...
@@ -94,4 +96,22 @@ class WeinanlieferungSlotsModel extends Model
94 96
 
95 97
         return static::findBy($arrColumns,null,$arrOptions);
96 98
     }
99
+
100
+    public function getAvailableBehaelter()
101
+    {
102
+        /** @var Connection $db */
103
+        $db = Controller::getContainer()->get('database_connection');
104
+
105
+        $ReservedBehaelter = $db->prepare("SELECT SUM(behaelter) FROM tl_vr_wa_reservation WHERE pid = ?")
106
+            ->executeQuery([$this->id]);
107
+
108
+        $intReserved = $ReservedBehaelter->fetchOne();
109
+
110
+        if ($intReserved === null)
111
+        {
112
+            $intReserved = 0;
113
+        }
114
+
115
+        return (int) $this->behaelter - $intReserved;
116
+    }
97 117
 }