Browse code

Extend bookingnotification and implement backend reservation approval

Benjamin Roth authored on10/09/2024 13:02:50
Showing8 changed files
... ...
@@ -61,6 +61,8 @@ $GLOBALS['NOTIFICATION_CENTER']['NOTIFICATION_TYPE']['weinanlieferung']['wa_book
61 61
         'slot_time',
62 62
         'slot_standort',
63 63
         'booking_ncsent',
64
+        'booking_approved',
65
+        'booking_approved_on',
64 66
     ),
65 67
     'email_text'    => array
66 68
     (
... ...
@@ -80,6 +82,8 @@ $GLOBALS['NOTIFICATION_CENTER']['NOTIFICATION_TYPE']['weinanlieferung']['wa_book
80 82
         'booking_sorten',
81 83
         'booking_ernteart',
82 84
         'booking_lage',
85
+        'booking_approved',
86
+        'booking_approved_on',
83 87
     ),
84 88
     'email_html'    => array
85 89
     (
... ...
@@ -99,6 +103,8 @@ $GLOBALS['NOTIFICATION_CENTER']['NOTIFICATION_TYPE']['weinanlieferung']['wa_book
99 103
         'booking_sorten',
100 104
         'booking_ernteart',
101 105
         'booking_lage',
106
+        'booking_approved',
107
+        'booking_approved_on',
102 108
     )
103 109
 );
104 110
 
... ...
@@ -97,7 +97,7 @@ $GLOBALS['TL_DCA']['tl_vr_wa_reservation'] = array
97 97
     'palettes' => array
98 98
     (
99 99
         '__selector__' => array(),
100
-        'default' => 'pid,uid,behaelter,sorten,lage,ernteart,upload,approved'
100
+        'default' => 'pid,uid,behaelter,sorten,lage,ernteart,upload;{approval_legend},approved'
101 101
     ),
102 102
 
103 103
     // Subpalettes
... ...
@@ -22,7 +22,10 @@ $GLOBALS['TL_LANG']['tl_vr_wa_reservation']['uid'][0] = 'Winzer';
22 22
 $GLOBALS['TL_LANG']['tl_vr_wa_reservation']['uid'][1] = 'Zuordnung des anliefernden Winzers.';
23 23
 $GLOBALS['TL_LANG']['tl_vr_wa_reservation']['upload'][0] = 'Datei';
24 24
 $GLOBALS['TL_LANG']['tl_vr_wa_reservation']['upload'][1] = 'Die hochgeladene Datei des Winzers.';
25
+$GLOBALS['TL_LANG']['tl_vr_wa_reservation']['approved'][0] = 'Freigabe';
26
+$GLOBALS['TL_LANG']['tl_vr_wa_reservation']['approved'][1] = 'Der Freigabe-Status der Buchung.';
25 27
 
26 28
 $GLOBALS['TL_LANG']['tl_vr_wa_reservation']['title_legend'] = 'Leseart';
29
+$GLOBALS['TL_LANG']['tl_vr_wa_reservation']['approval_legend'] = 'Freigabe-Einstellungen';
27 30
 
28 31
 $GLOBALS['TL_LANG']['tl_vr_wa_reservation']['back'] = 'Zurück';
... ...
@@ -47,6 +47,21 @@
47 47
         <a href="/contao?do=weinanlieferung&table=tl_vr_wa_reservation&act=create&rt={{ request_token }}&ref={{ ref }}" class="header_new" title="" accesskey="n" onclick="Backend.getScrollOffset()">Neu</a>
48 48
     </div>
49 49
     <div class="tl_listing_container">
50
+        <div class="u-flex u-items-center u-gap-2 px-1 text-sm">
51
+            <div><strong>Legende:</strong></div>
52
+            <div class="u-flex u-items-center u-gap-1">
53
+                <i class="status-icon status--approved status--small"></i>
54
+                angenommen
55
+            </div>
56
+            <div class="u-flex u-items-center u-gap-1">
57
+                <i class="status-icon status--pending status--small"></i>
58
+                Freigabe ausstehend
59
+            </div>
60
+            <div class="u-flex u-items-center u-gap-1">
61
+                <i class="status-icon status--canceled status--small"></i>
62
+                abgelehnt
63
+            </div>
64
+        </div>
50 65
         <div>
51 66
 
52 67
             {% block content %}
... ...
@@ -56,37 +71,47 @@
56 71
                             <h3 class="tl_folder_list px-1 text-lg text-primary mt-2">{{ day|date('d.m.Y') }}</h3>
57 72
                             {% for standort in standorte %}
58 73
                                 {% for time,bookings in standort.times %}
59
-                                    <h3 class="tl_folder_list px-1 row u-items-center">
60
-                                        <div class="col-2 pl-0">{{ time|date('H:i') }}</div>
61
-                                        <div class="col-3">
74
+                                    <h3 class="tl_folder_list row u-items-center">
75
+                                        <div class="col-2 offset-1">{{ time|date('H:i') }}</div>
76
+                                        <div class="col-2">
62 77
                                             <div class="t-label">Standort</div>
63 78
                                             {{ standort.standort }}
64 79
                                         </div>
65
-                                        <div class="col-3">
80
+                                        <div class="col-2">
66 81
                                             <div class="t-label">Verfügbare Behälterkapazität</div>
67 82
                                             {{ bookings.behaelterAvailable }}
68 83
                                         </div>
69
-                                        <div class="col">
84
+                                        <div class="col px-1">
70 85
                                             <div class="t-label">Verarbeitete Sorten</div>
71 86
                                             {{ bookings.sorten|join(', ') }}
72 87
                                         </div>
73 88
                                     </h3>
74 89
                                     <div class="bookings u-row-striped">
75 90
                                         {% for booking in bookings.items %}
76
-                                            <div class="row u-items-center">
77
-                                                <div class="col-1 time icon-uhr-outline">
91
+                                            {% if booking.approved == '1' %}
92
+                                                {% set status = 'approved' %}
93
+                                            {% elseif booking.approved == '0' %}
94
+                                                {% set status = 'canceled' %}
95
+                                            {% else %}
96
+                                                {% set status = 'pending' %}
97
+                                            {% endif %}
98
+                                            <div class="row u-items-flex-start my-1 status status--{{ status }}">
99
+                                                <div class="col-1">
100
+                                                    <i class="status-icon" title="{{ ('MSC.wa_approval_status.'~status)|trans([], 'contao_default') }}">{{ ('MSC.wa_approval_status.'~status)|trans([], 'contao_default') }}</i>
101
+                                                </div>
102
+                                                <div class="col-2 time icon-uhr-outline">
78 103
                                                     <div class="t-label">Uhrzeit</div>
79 104
                                                     {{ booking.slot.time|date('H:i') }}
80 105
                                                     <div class="t-label">Standort</div>
81 106
                                                     {{ booking.standort }}
82 107
                                                 </div>
83
-                                                <div class="col-3 behaelter icon-behaelter-outline">
108
+                                                <div class="col-2 behaelter icon-behaelter-outline">
84 109
                                                     <div class="t-label">Gebuchte Behälterkapazität</div>
85 110
                                                     {{ booking.behaelter }}
86 111
                                                     <div class="t-label">Ernteart</div>
87 112
                                                     {{ booking.ernteart|join(', ') }}
88 113
                                                 </div>
89
-                                                <div class="col-3 behaelter icon-behaelter-outline">
114
+                                                <div class="col-2 behaelter icon-behaelter-outline">
90 115
                                                     <div class="t-label">Anliefernde Sorten</div>
91 116
                                                     {{ booking.sorte|join(', ') }}
92 117
                                                     <div class="t-label">Lage</div>
... ...
@@ -106,7 +131,7 @@
106 131
                                                             </div>{% endif %}
107 132
                                                     {% endif %}
108 133
                                                 </div>
109
-                                                <div class="col-1 u-text-right action">
134
+                                                <div class="col px-1 u-text-right action">
110 135
                                                     <a
111 136
                                                         href="/contao?do=weinanlieferung&table=tl_vr_wa_reservation&act=edit&id={{ booking.id }}&rt={{ request_token }}&ref={{ ref }}"
112 137
                                                         {#                        onclick="Backend.openModalIframe({'title':'Quellelement ID {{ booking.id }} bearbeiten','url':this.href});return false" #}
... ...
@@ -6307,3 +6307,69 @@
6307 6307
 .u-border-opacity-100 {
6308 6308
   --border-opacity: 1;
6309 6309
 }
6310
+
6311
+
6312
+/** Status icons */
6313
+.status .status-icon {
6314
+  visibility: hidden;
6315
+  position: relative;
6316
+  width: 24px;
6317
+  height: 24px;
6318
+}
6319
+.status .status-icon:after {
6320
+  width: 24px;
6321
+  height: 24px;
6322
+  visibility: visible;
6323
+  position: absolute;
6324
+  top: 50%;
6325
+  left: 50%;
6326
+  margin: -12px 0 0 -12px;
6327
+  content: '';
6328
+  background-position: 50% 50%;
6329
+  background-repeat: no-repeat;
6330
+}
6331
+.status-icon {
6332
+  visibility: hidden;
6333
+  position: relative;
6334
+  display: inline-block;
6335
+  width: 24px;
6336
+  height: 24px;
6337
+}
6338
+.status-icon:after {
6339
+  width: 24px;
6340
+  height: 24px;
6341
+  visibility: visible;
6342
+  position: absolute;
6343
+  top: 50%;
6344
+  left: 50%;
6345
+  margin: -12px 0 0 -12px;
6346
+  content: '';
6347
+  background-position: 50% 50%;
6348
+  background-repeat: no-repeat;
6349
+}
6350
+.status-icon.status--small, .status.status--small .status-icon, .status-icon.status--small:after, .status.status--small .status-icon:after {
6351
+  width: 16px;
6352
+  height: 16px;
6353
+}
6354
+.status-icon.status--small:after, .status.status--small .status-icon:after {
6355
+  margin: -8px 0 0 -8px;
6356
+}
6357
+.status.status--approved:not(.status-icon):not(.status-no-rowcolor), .status-icon.status--approved:not(.status-icon):not(.status-no-rowcolor) {
6358
+  background-color: #f3ffe2;
6359
+}
6360
+.status.status--approved:not(.status):after, .status-icon.status--approved:not(.status):after, .status.status--approved .status-icon:after, .status-icon.status--approved .status-icon:after {
6361
+  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");
6362
+}
6363
+.status.status--pending:not(.status-icon):not(.status-no-rowcolor), .status-icon.status--pending:not(.status-icon):not(.status-no-rowcolor) {
6364
+  background-color: #fff6e4;
6365
+}
6366
+.status.status--pending:not(.status):after, .status-icon.status--pending:not(.status):after, .status.status--pending .status-icon:after, .status-icon.status--pending .status-icon:after {
6367
+  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");
6368
+}
6369
+.status.status--canceled:not(.status-icon):not(.status-no-rowcolor), .status-icon.status--canceled:not(.status-icon):not(.status-no-rowcolor) {
6370
+  background-color: #ffe7e7;
6371
+}
6372
+.status.status--canceled:not(.status):after, .status-icon.status--canceled:not(.status):after, .status.status--canceled .status-icon:after, .status-icon.status--canceled .status-icon:after {
6373
+  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' class='icon icon-tabler icon-tabler-thumb-down' width='100%25' height='100%25' viewBox='0 0 24 24' stroke-width='1.5' stroke='%23ff2825' fill='none' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath stroke='none' d='M0 0h24v24H0z' fill='none'/%3E%3Cpath d='M7 13v-8a1 1 0 0 0 -1 -1h-2a1 1 0 0 0 -1 1v7a1 1 0 0 0 1 1h3a4 4 0 0 1 4 4v1a2 2 0 0 0 4 0v-5h3a2 2 0 0 0 2 -2l-1 -5a2 3 0 0 0 -2 -2h-7a3 3 0 0 0 -3 3' /%3E%3C/svg%3E");
6374
+}
6375
+
... ...
@@ -62,7 +62,7 @@ class SendBookingChangeNotificationJob
62 62
                 $time = Date::floorToMinute();
63 63
 
64 64
                 // Do we have updateable items
65
-                $Bookings = $this->db->executeQuery("SELECT r.id, r.pid, r.uid, r.behaelter, r.sorten, r.lage, r.ernteart, r.upload, r.nc_sent, s.date as 'slot_date', s.time as 'slot_time', s.behaelter as 'slot_behaelter', s.sorten as 'slot_sorten', s.ernteart as 'slot_ernteart', s.lage as 'slot_lage', s.anmerkungen as 'slot_anmerkungen' FROM tl_vr_wa_reservation r INNER JOIN tl_vr_wa_slot s ON s.id = r.pid WHERE s.pid = ? AND r.nc_sent < r.tstamp AND s.published='1'",[$Location->id]);
65
+                $Bookings = $this->db->executeQuery("SELECT r.id, r.pid, r.uid, r.behaelter, r.sorten, r.lage, r.ernteart, r.upload, r.nc_sent, s.date as 'slot_date', s.time as 'slot_time', s.behaelter as 'slot_behaelter', s.sorten as 'slot_sorten', s.ernteart as 'slot_ernteart', s.lage as 'slot_lage', s.anmerkungen as 'slot_anmerkungen', r.approved, r.approved_on FROM tl_vr_wa_reservation r INNER JOIN tl_vr_wa_slot s ON s.id = r.pid WHERE s.pid = ? AND r.nc_sent < r.tstamp AND s.published='1'",[$Location->id]);
66 66
 
67 67
                 // Load groups and notification models if we have news to share
68 68
                 if ($Bookings->rowCount() && ($Notification = Notification::findByPk($Location->nc_notification)) !== null)
... ...
@@ -139,6 +139,8 @@ class SendBookingChangeNotificationJob
139 139
                                 'booking_sorten'     => implode(', ',$arrSortenBooked),
140 140
                                 'booking_ernteart'   => implode(', ',$arrErnteartBooked),
141 141
                                 'booking_lage'       => implode(', ',$arrLageBooked),
142
+                                'booking_approved'   => $Booking['approved'],
143
+                                'booking_approved_on'=> $Booking['approved_on'],
142 144
                                 'admin_email'        => $admin_email,
143 145
                             ),
144 146
                                 $GLOBALS['TL_LANGUAGE']);
... ...
@@ -19,6 +19,7 @@ use Contao\StringUtil;
19 19
 use Doctrine\DBAL\Connection;
20 20
 use vonRotenberg\WeinanlieferungBundle\Model\WeinanlieferungLeseartModel;
21 21
 use vonRotenberg\WeinanlieferungBundle\Model\WeinanlieferungRebsorteModel;
22
+use vonRotenberg\WeinanlieferungBundle\Model\WeinanlieferungReservationModel;
22 23
 use vonRotenberg\WeinanlieferungBundle\Model\WeinanlieferungSlotsModel;
23 24
 
24 25
 class WeinanlieferungReservationContainerListener
... ...
@@ -145,4 +146,25 @@ class WeinanlieferungReservationContainerListener
145 146
 
146 147
         return $arrData;
147 148
     }
149
+
150
+    /**
151
+     * @Callback(table="tl_vr_wa_reservation", target="fields.approved.save")
152
+     */
153
+    public function onApprovedSaveCallback($varValue, DataContainer $dc)
154
+    {
155
+        if (($Current = WeinanlieferungReservationModel::findByPk($dc->activeRecord->id)) !== null)
156
+        {
157
+            if ($Current->approved === '1' && $varValue !== '1')
158
+            {
159
+                $Current->approved_on = 0;
160
+                $Current->save();
161
+            } elseif ($Current->approved !== '1' && $varValue === '1')
162
+            {
163
+                $Current->approved_on = time();
164
+                $Current->save();
165
+            }
166
+        }
167
+
168
+        return $varValue;
169
+    }
148 170
 }
... ...
@@ -29,6 +29,12 @@ class WeinanlieferungReservationModel extends Model
29 29
         $Date = new Date();
30 30
         $time = $Date->dayBegin;
31 31
 
32
-        return static::findBy(array("pid IN (SELECT tl_vr_wa_slot.id FROM tl_vr_wa_slot WHERE tl_vr_wa_slot.id=tl_vr_wa_reservation.pid AND tl_vr_wa_slot.time >= ?)"), $time, $arrOptions);
32
+        $arrColumn = ["pid IN (SELECT tl_vr_wa_slot.id FROM tl_vr_wa_slot WHERE tl_vr_wa_slot.id=tl_vr_wa_reservation.pid AND tl_vr_wa_slot.time >= ?)"];
33
+
34
+        /*if (!isset($arrOptions['showCanceled']) || !$arrOptions['showCanceled']) {
35
+            $arrColumn[] = "approved != '0'";
36
+        }*/
37
+
38
+        return static::findBy($arrColumn, $time, $arrOptions);
33 39
     }
34 40
 }