... | ... |
@@ -8,6 +8,11 @@ services: |
8 | 8 |
resource: ../src |
9 | 9 |
exclude: ../src/{VonrotenbergWeinanlieferungBundle.php,ContaoManager,Entity,Migrations,Model,Resources,Tests,Widget} |
10 | 10 |
|
11 |
+ vonRotenberg\WeinanlieferungBundle\EventListener\CheckInCompletedListener: |
|
12 |
+ arguments: |
|
13 |
+ $logger: '@logger' |
|
14 |
+ $projectDir: '%kernel.project_dir%' |
|
15 |
+ |
|
11 | 16 |
vonRotenberg\WeinanlieferungBundle\Migration\RebsortenMigration: |
12 | 17 |
tags: |
13 | 18 |
- { name: contao.migration, priority: 0 } |
... | ... |
@@ -27,10 +27,12 @@ use Contao\Input; |
27 | 27 |
use Contao\StringUtil; |
28 | 28 |
use Contao\System; |
29 | 29 |
use Doctrine\DBAL\Connection; |
30 |
+use Symfony\Component\EventDispatcher\EventDispatcherInterface; |
|
30 | 31 |
use Symfony\Component\HttpFoundation\Request; |
31 | 32 |
use Symfony\Component\HttpFoundation\Response; |
32 | 33 |
use Symfony\Component\Routing\Annotation\Route; |
33 | 34 |
use Symfony\Contracts\Translation\TranslatorInterface; |
35 |
+use vonRotenberg\WeinanlieferungBundle\Event\CheckInCompletedEvent; |
|
34 | 36 |
use vonRotenberg\WeinanlieferungBundle\Model\WeinanlieferungLageModel; |
35 | 37 |
use vonRotenberg\WeinanlieferungBundle\Model\WeinanlieferungLeseartModel; |
36 | 38 |
use vonRotenberg\WeinanlieferungBundle\Model\WeinanlieferungRebsorteModel; |
... | ... |
@@ -45,12 +47,19 @@ class SlotAjaxController extends AbstractController |
45 | 47 |
private $tokenChecker; |
46 | 48 |
private $translator; |
47 | 49 |
private $framework; |
48 |
- |
|
49 |
- public function __construct(ContaoFramework $framework, TokenChecker $tokenChecker, TranslatorInterface $translator) |
|
50 |
+ private $eventDispatcher; |
|
51 |
+ |
|
52 |
+ public function __construct( |
|
53 |
+ ContaoFramework $framework, |
|
54 |
+ TokenChecker $tokenChecker, |
|
55 |
+ TranslatorInterface $translator, |
|
56 |
+ EventDispatcherInterface $eventDispatcher |
|
57 |
+ ) |
|
50 | 58 |
{ |
51 | 59 |
$this->framework = $framework; |
52 | 60 |
$this->tokenChecker = $tokenChecker; |
53 | 61 |
$this->translator = $translator; |
62 |
+ $this->eventDispatcher = $eventDispatcher; |
|
54 | 63 |
} |
55 | 64 |
|
56 | 65 |
public function __invoke(Request $request) |
... | ... |
@@ -801,6 +810,13 @@ class SlotAjaxController extends AbstractController |
801 | 810 |
$Booking->tstamp = $time; |
802 | 811 |
$Booking->save(); |
803 | 812 |
|
813 |
+ // Create reservation data array for the event |
|
814 |
+ $reservationData = $Booking->row(); |
|
815 |
+ |
|
816 |
+ // Dispatch the check-in completed event |
|
817 |
+ $event = new CheckInCompletedEvent($reservationData, $Booking); |
|
818 |
+ $this->eventDispatcher->dispatch($event, CheckInCompletedEvent::NAME); |
|
819 |
+ |
|
804 | 820 |
return new Response('<div class="toast toast--success mx-0"><p>Check-in erfolgreich durchgeführt</p></div>', 200, ['HX-Trigger'=> 'updateWaBooking']); |
805 | 821 |
} |
806 | 822 |
|
807 | 823 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,59 @@ |
1 |
+<?php |
|
2 |
+ |
|
3 |
+namespace vonRotenberg\WeinanlieferungBundle\Event; |
|
4 |
+ |
|
5 |
+use Symfony\Contracts\EventDispatcher\Event; |
|
6 |
+use vonRotenberg\WeinanlieferungBundle\Model\WeinanlieferungReservationModel; |
|
7 |
+ |
|
8 |
+/** |
|
9 |
+ * Event that is triggered when a check-in is completed. |
|
10 |
+ */ |
|
11 |
+class CheckInCompletedEvent extends Event |
|
12 |
+{ |
|
13 |
+ /** |
|
14 |
+ * Event name |
|
15 |
+ */ |
|
16 |
+ public const NAME = 'vonrotenberg.weinanlieferung.checkin_completed'; |
|
17 |
+ |
|
18 |
+ /** |
|
19 |
+ * @var array The reservation data |
|
20 |
+ */ |
|
21 |
+ private $reservationData; |
|
22 |
+ |
|
23 |
+ /** |
|
24 |
+ * @var WeinanlieferungReservationModel The reservation model |
|
25 |
+ */ |
|
26 |
+ private $reservationModel; |
|
27 |
+ |
|
28 |
+ /** |
|
29 |
+ * CheckInCompletedEvent constructor. |
|
30 |
+ * |
|
31 |
+ * @param array $reservationData The reservation data as array |
|
32 |
+ * @param WeinanlieferungReservationModel $reservationModel The reservation model |
|
33 |
+ */ |
|
34 |
+ public function __construct(array $reservationData, WeinanlieferungReservationModel $reservationModel) |
|
35 |
+ { |
|
36 |
+ $this->reservationData = $reservationData; |
|
37 |
+ $this->reservationModel = $reservationModel; |
|
38 |
+ } |
|
39 |
+ |
|
40 |
+ /** |
|
41 |
+ * Get the reservation data. |
|
42 |
+ * |
|
43 |
+ * @return array |
|
44 |
+ */ |
|
45 |
+ public function getReservationData(): array |
|
46 |
+ { |
|
47 |
+ return $this->reservationData; |
|
48 |
+ } |
|
49 |
+ |
|
50 |
+ /** |
|
51 |
+ * Get the reservation model. |
|
52 |
+ * |
|
53 |
+ * @return WeinanlieferungReservationModel |
|
54 |
+ */ |
|
55 |
+ public function getReservationModel(): WeinanlieferungReservationModel |
|
56 |
+ { |
|
57 |
+ return $this->reservationModel; |
|
58 |
+ } |
|
59 |
+} |
0 | 60 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,231 @@ |
1 |
+<?php |
|
2 |
+ |
|
3 |
+namespace vonRotenberg\WeinanlieferungBundle\EventListener; |
|
4 |
+ |
|
5 |
+use Psr\Log\LoggerInterface; |
|
6 |
+use Psr\Log\LogLevel; |
|
7 |
+use Contao\CoreBundle\Monolog\ContaoContext; |
|
8 |
+use Contao\MemberModel; |
|
9 |
+use Symfony\Component\EventDispatcher\EventSubscriberInterface; |
|
10 |
+use Symfony\Component\Filesystem\Filesystem; |
|
11 |
+use vonRotenberg\WeinanlieferungBundle\Event\CheckInCompletedEvent; |
|
12 |
+use vonRotenberg\WeinanlieferungBundle\Model\WeinanlieferungLageModel; |
|
13 |
+use vonRotenberg\WeinanlieferungBundle\Model\WeinanlieferungLeseartModel; |
|
14 |
+use vonRotenberg\WeinanlieferungBundle\Model\WeinanlieferungRebsorteModel; |
|
15 |
+ |
|
16 |
+/** |
|
17 |
+ * Listener for the CheckInCompletedEvent. |
|
18 |
+ * Generates a CSV export file for each checked-in booking. |
|
19 |
+ */ |
|
20 |
+class CheckInCompletedListener implements EventSubscriberInterface |
|
21 |
+{ |
|
22 |
+ /** |
|
23 |
+ * @var LoggerInterface |
|
24 |
+ */ |
|
25 |
+ private $logger; |
|
26 |
+ |
|
27 |
+ /** |
|
28 |
+ * @var string |
|
29 |
+ */ |
|
30 |
+ private $projectDir; |
|
31 |
+ |
|
32 |
+ /** |
|
33 |
+ * Constructor. |
|
34 |
+ * |
|
35 |
+ * @param LoggerInterface $logger |
|
36 |
+ * @param string $projectDir |
|
37 |
+ */ |
|
38 |
+ public function __construct(LoggerInterface $logger, string $projectDir) |
|
39 |
+ { |
|
40 |
+ $this->logger = $logger; |
|
41 |
+ $this->projectDir = $projectDir; |
|
42 |
+ } |
|
43 |
+ |
|
44 |
+ /** |
|
45 |
+ * Returns an array of event names this subscriber wants to listen to. |
|
46 |
+ * |
|
47 |
+ * @return array The event names to listen to |
|
48 |
+ */ |
|
49 |
+ public static function getSubscribedEvents() |
|
50 |
+ { |
|
51 |
+ return [ |
|
52 |
+ CheckInCompletedEvent::NAME => 'onCheckInCompleted', |
|
53 |
+ ]; |
|
54 |
+ } |
|
55 |
+ |
|
56 |
+ /** |
|
57 |
+ * Handle the check-in completed event. |
|
58 |
+ * |
|
59 |
+ * @param CheckInCompletedEvent $event |
|
60 |
+ */ |
|
61 |
+ public function onCheckInCompleted(CheckInCompletedEvent $event) |
|
62 |
+ { |
|
63 |
+ // Get the reservation data from the event |
|
64 |
+ $reservationData = $event->getReservationData(); |
|
65 |
+ $reservationModel = $event->getReservationModel(); |
|
66 |
+ |
|
67 |
+ // Log the event for testing purposes |
|
68 |
+ $this->logger->log( |
|
69 |
+ LogLevel::INFO, |
|
70 |
+ sprintf('Check-in completed for reservation ID: %s', $reservationData['id']), |
|
71 |
+ ['contao' => new ContaoContext(__METHOD__, 'CHECK_IN')] |
|
72 |
+ ); |
|
73 |
+ |
|
74 |
+ // Generate and save the CSV export |
|
75 |
+ $this->generateCsvExport($reservationData, $reservationModel); |
|
76 |
+ } |
|
77 |
+ |
|
78 |
+ /** |
|
79 |
+ * Generate and save the CSV export file. |
|
80 |
+ * |
|
81 |
+ * @param array $reservationData |
|
82 |
+ * @param object $reservationModel |
|
83 |
+ */ |
|
84 |
+ private function generateCsvExport(array $reservationData, $reservationModel) |
|
85 |
+ { |
|
86 |
+ // Create export directory if it doesn't exist |
|
87 |
+ $exportDir = $this->projectDir . '/export/check_in'; |
|
88 |
+ $filesystem = new Filesystem(); |
|
89 |
+ |
|
90 |
+ if (!$filesystem->exists($exportDir)) { |
|
91 |
+ $filesystem->mkdir($exportDir, 0755); |
|
92 |
+ } |
|
93 |
+ |
|
94 |
+ // Get member data |
|
95 |
+ $memberId = $reservationData['uid']; |
|
96 |
+ $memberModel = MemberModel::findById($memberId); |
|
97 |
+ |
|
98 |
+ if (null === $memberModel) { |
|
99 |
+ $this->logger->log( |
|
100 |
+ LogLevel::ERROR, |
|
101 |
+ sprintf('Could not find member with ID: %s', $memberId), |
|
102 |
+ ['contao' => new ContaoContext(__METHOD__, 'CHECK_IN_CSV_EXPORT')] |
|
103 |
+ ); |
|
104 |
+ return; |
|
105 |
+ } |
|
106 |
+ |
|
107 |
+ // Get behaelter numbers |
|
108 |
+ $behaelterNumbers = json_decode($reservationData['behaelter_numbers'], true); |
|
109 |
+ if (!is_array($behaelterNumbers) || empty($behaelterNumbers)) { |
|
110 |
+ $this->logger->log( |
|
111 |
+ LogLevel::ERROR, |
|
112 |
+ sprintf('No behaelter numbers found for reservation ID: %s', $reservationData['id']), |
|
113 |
+ ['contao' => new ContaoContext(__METHOD__, 'CHECK_IN_CSV_EXPORT')] |
|
114 |
+ ); |
|
115 |
+ return; |
|
116 |
+ } |
|
117 |
+ |
|
118 |
+ // Format check-in date |
|
119 |
+ $checkInDate = date('d.m.Y', $reservationData['checked_in_on']); |
|
120 |
+ |
|
121 |
+ // Get sorten (grape varieties and harvest types) |
|
122 |
+ $sortenData = []; |
|
123 |
+ $sortenLeseartPairs = explode(';', $reservationData['sorten']); |
|
124 |
+ foreach ($sortenLeseartPairs as $pair) { |
|
125 |
+ if (empty($pair)) { |
|
126 |
+ continue; |
|
127 |
+ } |
|
128 |
+ |
|
129 |
+ list($sorteId, $leseartId) = explode(',', $pair); |
|
130 |
+ |
|
131 |
+ $rebsorteModel = WeinanlieferungRebsorteModel::findById($sorteId); |
|
132 |
+ $leseartModel = WeinanlieferungLeseartModel::findById($leseartId); |
|
133 |
+ |
|
134 |
+ if (null !== $rebsorteModel && null !== $leseartModel) { |
|
135 |
+ $sortenData[] = [ |
|
136 |
+ 'rebsorte_id' => $rebsorteModel->ident_string, |
|
137 |
+ 'rebsorte_title' => $rebsorteModel->title, |
|
138 |
+ 'leseart_id' => $leseartModel->ident_string, |
|
139 |
+ 'leseart_title' => $leseartModel->title |
|
140 |
+ ]; |
|
141 |
+ } |
|
142 |
+ } |
|
143 |
+ |
|
144 |
+ // Get lagen (locations) |
|
145 |
+ $lagenData = []; |
|
146 |
+ $lagenIds = explode(',', $reservationData['lage']); |
|
147 |
+ foreach ($lagenIds as $lageId) { |
|
148 |
+ if (empty($lageId)) { |
|
149 |
+ continue; |
|
150 |
+ } |
|
151 |
+ |
|
152 |
+ $lageModel = WeinanlieferungLageModel::findById($lageId); |
|
153 |
+ |
|
154 |
+ if (null !== $lageModel) { |
|
155 |
+ $lagenData[] = [ |
|
156 |
+ 'lage_id' => $lageModel->ident_string, |
|
157 |
+ 'lage_title' => $lageModel->title |
|
158 |
+ ]; |
|
159 |
+ } |
|
160 |
+ } |
|
161 |
+ |
|
162 |
+ // Get ernteart (harvest method) |
|
163 |
+ $ernteartValues = explode(',', $reservationData['ernteart']); |
|
164 |
+ $ernteartMapping = [ |
|
165 |
+ 'handlese' => 'H', |
|
166 |
+ 'vollernter' => 'V' |
|
167 |
+ ]; |
|
168 |
+ |
|
169 |
+ // Prepare CSV data |
|
170 |
+ $csvData = []; |
|
171 |
+ |
|
172 |
+ // For each behaelter, create a line in the CSV |
|
173 |
+ foreach ($behaelterNumbers as $behaelterNumber) { |
|
174 |
+ // Skip special value 9999 if needed |
|
175 |
+ if ($behaelterNumber === '9999') { |
|
176 |
+ continue; |
|
177 |
+ } |
|
178 |
+ |
|
179 |
+ // Use the first sorte, leseart, and lage if available |
|
180 |
+ $rebsorteId = !empty($sortenData) ? $sortenData[0]['rebsorte_id'] : ''; |
|
181 |
+ $rebsorteTitle = !empty($sortenData) ? $sortenData[0]['rebsorte_title'] : ''; |
|
182 |
+ $leseartId = !empty($sortenData) ? $sortenData[0]['leseart_id'] : ''; |
|
183 |
+ $leseartTitle = !empty($sortenData) ? $sortenData[0]['leseart_title'] : ''; |
|
184 |
+ $lageId = !empty($lagenData) ? $lagenData[0]['lage_id'] : ''; |
|
185 |
+ $lageTitle = !empty($lagenData) ? $lagenData[0]['lage_title'] : ''; |
|
186 |
+ |
|
187 |
+ // Map ernteart values to H/V |
|
188 |
+ $ernteart = ''; |
|
189 |
+ foreach ($ernteartValues as $value) { |
|
190 |
+ if (isset($ernteartMapping[$value])) { |
|
191 |
+ $ernteart = $ernteartMapping[$value]; |
|
192 |
+ break; |
|
193 |
+ } |
|
194 |
+ } |
|
195 |
+ |
|
196 |
+ // Create CSV line |
|
197 |
+ $csvData[] = [ |
|
198 |
+ $memberModel->memberno, // member id |
|
199 |
+ $memberModel->firstname . ' ' . $memberModel->lastname, // member first and lastname |
|
200 |
+ $rebsorteId, // rebsorte identifikator |
|
201 |
+ $rebsorteTitle, // rebsorte title |
|
202 |
+ $lageId, // lage identifikator |
|
203 |
+ $lageTitle, // lage title |
|
204 |
+ $leseartId, // leseart identifikator |
|
205 |
+ $leseartTitle, // leseart title |
|
206 |
+ $ernteart, // ernteart (H for handlese, V for vollernter) |
|
207 |
+ $behaelterNumber, // behaelter number |
|
208 |
+ $checkInDate, // check-in date |
|
209 |
+ $reservationData['behaelter'] // behaelter amount for the whole checked-in booking |
|
210 |
+ ]; |
|
211 |
+ } |
|
212 |
+ |
|
213 |
+ // Generate CSV file |
|
214 |
+ $filename = $reservationData['id'] . '_' . $memberModel->memberno . '.csv'; |
|
215 |
+ $filepath = $exportDir . '/' . $filename; |
|
216 |
+ |
|
217 |
+ $file = fopen($filepath, 'w'); |
|
218 |
+ |
|
219 |
+ foreach ($csvData as $line) { |
|
220 |
+ fputcsv($file, $line, ';'); |
|
221 |
+ } |
|
222 |
+ |
|
223 |
+ fclose($file); |
|
224 |
+ |
|
225 |
+ $this->logger->log( |
|
226 |
+ LogLevel::INFO, |
|
227 |
+ sprintf('CSV export created for reservation ID: %s at %s', $reservationData['id'], $filepath), |
|
228 |
+ ['contao' => new ContaoContext(__METHOD__, 'CHECK_IN_CSV_EXPORT')] |
|
229 |
+ ); |
|
230 |
+ } |
|
231 |
+} |