... | ... |
@@ -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;{approval_legend},approved' |
|
100 |
+ 'default' => 'pid,uid,behaelter,sorten,lage,ernteart,upload;{approval_legend},approved;{checkin_legend},checked_in,behaelter_numbers' |
|
101 | 101 |
), |
102 | 102 |
|
103 | 103 |
// Subpalettes |
... | ... |
@@ -201,5 +201,26 @@ $GLOBALS['TL_DCA']['tl_vr_wa_reservation'] = array |
201 | 201 |
'eval' => array('rgxp' => 'datim', 'datepicker' => true, 'tl_class' => 'clr w50 wizard'), |
202 | 202 |
'sql' => "int(10) unsigned NOT NULL default '0'" |
203 | 203 |
), |
204 |
+ 'checked_in' => array |
|
205 |
+ ( |
|
206 |
+ 'exclude' => true, |
|
207 |
+ 'inputType' => 'checkbox', |
|
208 |
+ 'eval' => array('tl_class'=>'w50 m12'), |
|
209 |
+ 'sql' => "char(1) NOT NULL default ''" |
|
210 |
+ ), |
|
211 |
+ 'checked_in_on' => array |
|
212 |
+ ( |
|
213 |
+ 'exclude' => true, |
|
214 |
+ 'inputType' => 'text', |
|
215 |
+ 'eval' => array('rgxp' => 'datim', 'datepicker' => true, 'tl_class' => 'clr w50 wizard'), |
|
216 |
+ 'sql' => "int(10) unsigned NOT NULL default '0'" |
|
217 |
+ ), |
|
218 |
+ 'behaelter_numbers' => array |
|
219 |
+ ( |
|
220 |
+ 'exclude' => true, |
|
221 |
+ 'inputType' => 'text', |
|
222 |
+ 'eval' => array('tl_class'=>'clr', 'preserveTags'=>true), |
|
223 |
+ 'sql' => "text NULL" |
|
224 |
+ ), |
|
204 | 225 |
) |
205 | 226 |
); |
... | ... |
@@ -24,8 +24,15 @@ $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 | 25 |
$GLOBALS['TL_LANG']['tl_vr_wa_reservation']['approved'][0] = 'Freigabe'; |
26 | 26 |
$GLOBALS['TL_LANG']['tl_vr_wa_reservation']['approved'][1] = 'Der Freigabe-Status der Buchung.'; |
27 |
+$GLOBALS['TL_LANG']['tl_vr_wa_reservation']['checked_in'][0] = 'Eingecheckt'; |
|
28 |
+$GLOBALS['TL_LANG']['tl_vr_wa_reservation']['checked_in'][1] = 'Markiert die Buchung als eingecheckt.'; |
|
29 |
+$GLOBALS['TL_LANG']['tl_vr_wa_reservation']['checked_in_on'][0] = 'Eingecheckt am'; |
|
30 |
+$GLOBALS['TL_LANG']['tl_vr_wa_reservation']['checked_in_on'][1] = 'Zeitpunkt des Check-ins.'; |
|
31 |
+$GLOBALS['TL_LANG']['tl_vr_wa_reservation']['behaelter_numbers'][0] = 'Behälternummern'; |
|
32 |
+$GLOBALS['TL_LANG']['tl_vr_wa_reservation']['behaelter_numbers'][1] = 'Die zugewiesenen Nummern für jeden Behälter.'; |
|
27 | 33 |
|
28 | 34 |
$GLOBALS['TL_LANG']['tl_vr_wa_reservation']['title_legend'] = 'Leseart'; |
29 | 35 |
$GLOBALS['TL_LANG']['tl_vr_wa_reservation']['approval_legend'] = 'Freigabe-Einstellungen'; |
36 |
+$GLOBALS['TL_LANG']['tl_vr_wa_reservation']['checkin_legend'] = 'Check-in-Informationen'; |
|
30 | 37 |
|
31 | 38 |
$GLOBALS['TL_LANG']['tl_vr_wa_reservation']['back'] = 'Zurück'; |
... | ... |
@@ -102,7 +102,12 @@ |
102 | 102 |
</div> |
103 | 103 |
|
104 | 104 |
<div class="col u-text-right u-text-nowrap action"> |
105 |
- {% if booking.approved != '0' %}<a hx-get="/_ajax/vr_wa/v1/slot?do=booking&id={{ booking.id }}" hx-target="body" hx-swap="beforeend" href="javascript:;" class="btn btn--sm btn-info m-0">Ändern</a>{% endif %} |
|
105 |
+ {% if booking.approved != '0' %} |
|
106 |
+ <a hx-get="/_ajax/vr_wa/v1/slot?do=booking&id={{ booking.id }}" hx-target="body" hx-swap="beforeend" href="javascript:;" class="btn btn--sm btn-info m-0">Ändern</a> |
|
107 |
+ {% if not booking.checked_in %} |
|
108 |
+ <a hx-get="/_ajax/vr_wa/v1/slot?do=checkin&id={{ booking.id }}" hx-target="body" hx-swap="beforeend" href="javascript:;" class="btn btn--sm btn-success m-0">Check-in</a> |
|
109 |
+ {% endif %} |
|
110 |
+ {% endif %} |
|
106 | 111 |
<a hx-get="/_ajax/vr_wa/v1/slot?do=delete&id={{ booking.id }}" hx-target="body" hx-swap="beforeend" hx-confirm="Sind Sie sicher, dass Sie diese Reservierung löschen möchten?" href="javascript:;" class="btn btn--sm btn-danger m-0">Löschen</a> |
107 | 112 |
</div> |
108 | 113 |
</div> |
... | ... |
@@ -93,6 +93,15 @@ class SlotAjaxController extends AbstractController |
93 | 93 |
|
94 | 94 |
case 'delete': |
95 | 95 |
return $this->deleteReservation(); |
96 |
+ |
|
97 |
+ case 'checkin': |
|
98 |
+ return $this->renderCheckin($blnModal); |
|
99 |
+ |
|
100 |
+ case 'updateCheckin': |
|
101 |
+ return $this->updateCheckin(); |
|
102 |
+ |
|
103 |
+ case 'getAvailableNumbers': |
|
104 |
+ return $this->getAvailableNumbers(); |
|
96 | 105 |
} |
97 | 106 |
|
98 | 107 |
return new Response('',500); |
... | ... |
@@ -665,4 +674,210 @@ class SlotAjaxController extends AbstractController |
665 | 674 |
return $this->render('@Contao/modal_unauthorized.html.twig'); |
666 | 675 |
} |
667 | 676 |
|
677 |
+ protected function renderCheckin(bool $blnModal=true, string $error=null) |
|
678 |
+ { |
|
679 |
+ $insertTagService = Controller::getContainer()->get('contao.insert_tag.parser'); |
|
680 |
+ |
|
681 |
+ $arrData = []; |
|
682 |
+ |
|
683 |
+ if (empty($_REQUEST['id'])) |
|
684 |
+ { |
|
685 |
+ return new Response('Required parameter missing', 412); |
|
686 |
+ } |
|
687 |
+ |
|
688 |
+ /** @var WeinanlieferungReservationModel $Booking */ |
|
689 |
+ if (($Booking = WeinanlieferungReservationModel::findById($_REQUEST['id'])) === null || ($Slot = $Booking->getRelated('pid')) === null) |
|
690 |
+ { |
|
691 |
+ return new Response('Could not load booking data', 500); |
|
692 |
+ } |
|
693 |
+ |
|
694 |
+ if ($Booking->approved === '0') |
|
695 |
+ { |
|
696 |
+ return $this->render('@Contao/modal_message.html.twig', ['type'=>'danger', 'content'=>'Diese Buchungsanfrage wurde abgelehnt und kann nicht eingecheckt werden.']); |
|
697 |
+ } |
|
698 |
+ |
|
699 |
+ if ($Booking->checked_in === '1') |
|
700 |
+ { |
|
701 |
+ return $this->render('@Contao/modal_message.html.twig', ['type'=>'info', 'content'=>'Diese Buchung wurde bereits eingecheckt.']); |
|
702 |
+ } |
|
703 |
+ |
|
704 |
+ // Get the standort to access the number_ranges |
|
705 |
+ $Standort = $Slot->getRelated('pid'); |
|
706 |
+ if ($Standort === null) |
|
707 |
+ { |
|
708 |
+ return new Response('Could not load standort data', 500); |
|
709 |
+ } |
|
710 |
+ |
|
711 |
+ // Prepare data for the template |
|
712 |
+ $arrSortenBooked = []; |
|
713 |
+ $SortenLeseart = explode(';', $Booking->sorten); |
|
714 |
+ foreach($SortenLeseart as $sorteLeseart) |
|
715 |
+ { |
|
716 |
+ list($sorte, $leseart) = explode(',', $sorteLeseart); |
|
717 |
+ $objSorte = WeinanlieferungRebsorteModel::findByPk($sorte); |
|
718 |
+ $objLeseart = WeinanlieferungLeseartModel::findByPk($leseart); |
|
719 |
+ $arrSortenBooked[$objSorte->id.','.$objLeseart->id] = ($objSorte !== null ? $objSorte->title : '') . ' ' . ($objLeseart !== null ? $objLeseart->title : ''); |
|
720 |
+ } |
|
721 |
+ |
|
722 |
+ $arrErnteartBooked = []; |
|
723 |
+ if ($Booking->ernteart !== null) |
|
724 |
+ { |
|
725 |
+ foreach (explode(',', $Booking->ernteart) as $ernteart) |
|
726 |
+ { |
|
727 |
+ $arrErnteartBooked[] = $GLOBALS['TL_LANG']['REF']['wa_ernteart'][$ernteart] ?? $ernteart; |
|
728 |
+ } |
|
729 |
+ } |
|
730 |
+ |
|
731 |
+ $arrLagenBooked = []; |
|
732 |
+ if ($Booking->lage !== null) |
|
733 |
+ { |
|
734 |
+ if (($Lagen = $Booking->getRelated('lage')) !== null) |
|
735 |
+ { |
|
736 |
+ $arrLagenBooked = $Lagen->fetchEach('title'); |
|
737 |
+ } |
|
738 |
+ } |
|
739 |
+ |
|
740 |
+ $arrData = array_merge($arrData, [ |
|
741 |
+ 'modal' => $blnModal, |
|
742 |
+ 'id' => $Booking->id, |
|
743 |
+ 'slot' => array_merge($Slot->row(), [ |
|
744 |
+ 'anmerkungen' => $insertTagService->replace($Slot->anmerkungen ?? ''), |
|
745 |
+ ]), |
|
746 |
+ 'buchung' => array_merge($Booking->row(), [ |
|
747 |
+ 'sorten' => $arrSortenBooked, |
|
748 |
+ 'ernteart' => $arrErnteartBooked, |
|
749 |
+ 'lage' => $arrLagenBooked, |
|
750 |
+ ]), |
|
751 |
+ 'standort' => $Standort, |
|
752 |
+ 'checkin' => [ |
|
753 |
+ 'behaelter' => $Booking->behaelter, |
|
754 |
+ ] |
|
755 |
+ ]); |
|
756 |
+ |
|
757 |
+ if (!empty($error)) |
|
758 |
+ { |
|
759 |
+ $arrData['toast'] = $error; |
|
760 |
+ } |
|
761 |
+ |
|
762 |
+ return $this->render('@Contao/modal_checkin.html.twig', $arrData); |
|
763 |
+ } |
|
764 |
+ |
|
765 |
+ protected function updateCheckin() |
|
766 |
+ { |
|
767 |
+ if (empty($_REQUEST['id'])) |
|
768 |
+ { |
|
769 |
+ return new Response('Missing parameter', 412); |
|
770 |
+ } |
|
771 |
+ |
|
772 |
+ /** @var WeinanlieferungReservationModel $Booking */ |
|
773 |
+ if (($Booking = WeinanlieferungReservationModel::findById($_REQUEST['id'])) === null) |
|
774 |
+ { |
|
775 |
+ return new Response('Could not load booking data', 500); |
|
776 |
+ } |
|
777 |
+ |
|
778 |
+ // Validate that we have the correct number of behaelter numbers |
|
779 |
+ $behaelterNumbers = Input::post('behaelter_numbers'); |
|
780 |
+ if (!is_array($behaelterNumbers) || count($behaelterNumbers) != $Booking->behaelter) |
|
781 |
+ { |
|
782 |
+ return $this->renderCheckin(false, '<div class="toast toast--danger mx-0">Bitte wählen Sie für jeden Behälter eine Nummer aus.</div>'); |
|
783 |
+ } |
|
784 |
+ |
|
785 |
+ // Filter out the special value 9999 ("Nummer nicht bekannt") for duplicate check |
|
786 |
+ $numbersForDuplicateCheck = array_filter($behaelterNumbers, function($number) { |
|
787 |
+ return $number !== '9999'; |
|
788 |
+ }); |
|
789 |
+ |
|
790 |
+ // Check for duplicate numbers (excluding the special value 9999) |
|
791 |
+ if (count(array_unique($numbersForDuplicateCheck)) != count($numbersForDuplicateCheck)) |
|
792 |
+ { |
|
793 |
+ return $this->renderCheckin(false, '<div class="toast toast--danger mx-0">Jede Nummer kann nur einmal verwendet werden.</div>'); |
|
794 |
+ } |
|
795 |
+ |
|
796 |
+ // Save the check-in data |
|
797 |
+ $time = time(); |
|
798 |
+ $Booking->checked_in = '1'; |
|
799 |
+ $Booking->checked_in_on = $time; |
|
800 |
+ $Booking->behaelter_numbers = json_encode($behaelterNumbers); |
|
801 |
+ $Booking->tstamp = $time; |
|
802 |
+ $Booking->save(); |
|
803 |
+ |
|
804 |
+ return new Response('<div class="toast toast--success mx-0"><p>Check-in erfolgreich durchgeführt</p></div>', 200, ['HX-Trigger'=> 'updateWaBooking']); |
|
805 |
+ } |
|
806 |
+ |
|
807 |
+ protected function getAvailableNumbers() |
|
808 |
+ { |
|
809 |
+ if (empty($_REQUEST['id'])) |
|
810 |
+ { |
|
811 |
+ return new Response('Required parameter missing', 412); |
|
812 |
+ } |
|
813 |
+ |
|
814 |
+ /** @var WeinanlieferungReservationModel $Booking */ |
|
815 |
+ if (($Booking = WeinanlieferungReservationModel::findById($_REQUEST['id'])) === null || ($Slot = $Booking->getRelated('pid')) === null) |
|
816 |
+ { |
|
817 |
+ return new Response('Could not load booking data', 500); |
|
818 |
+ } |
|
819 |
+ |
|
820 |
+ if ($Booking->approved === '0') |
|
821 |
+ { |
|
822 |
+ return new Response(json_encode(['error' => 'Diese Buchungsanfrage wurde abgelehnt und kann nicht eingecheckt werden.']), 400, ['Content-Type' => 'application/json']); |
|
823 |
+ } |
|
824 |
+ |
|
825 |
+ if ($Booking->checked_in === '1') |
|
826 |
+ { |
|
827 |
+ return new Response(json_encode(['error' => 'Diese Buchung wurde bereits eingecheckt.']), 400, ['Content-Type' => 'application/json']); |
|
828 |
+ } |
|
829 |
+ |
|
830 |
+ // Get the standort to access the number_ranges |
|
831 |
+ $Standort = $Slot->getRelated('pid'); |
|
832 |
+ if ($Standort === null) |
|
833 |
+ { |
|
834 |
+ return new Response(json_encode(['error' => 'Could not load standort data']), 500, ['Content-Type' => 'application/json']); |
|
835 |
+ } |
|
836 |
+ |
|
837 |
+ // Get all used numbers from current bookings (excluding past bookings) |
|
838 |
+ $usedNumbers = []; |
|
839 |
+ $currentTime = time(); |
|
840 |
+ |
|
841 |
+ // Get the database connection |
|
842 |
+ $db = Controller::getContainer()->get('database_connection'); |
|
843 |
+ |
|
844 |
+ // More efficient query to get used numbers from current bookings |
|
845 |
+ $sql = "SELECT r.behaelter_numbers |
|
846 |
+ FROM tl_vr_wa_reservation r |
|
847 |
+ JOIN tl_vr_wa_slot s ON r.pid = s.id |
|
848 |
+ WHERE r.behaelter_numbers != '' |
|
849 |
+ AND s.time >= ? |
|
850 |
+ AND r.id != ?"; |
|
851 |
+ |
|
852 |
+ $stmt = $db->prepare($sql); |
|
853 |
+ $stmt->bindValue(1, $currentTime); |
|
854 |
+ $stmt->bindValue(2, $Booking->id); |
|
855 |
+ $result = $stmt->executeQuery(); |
|
856 |
+ |
|
857 |
+ while ($row = $result->fetchAssociative()) { |
|
858 |
+ $numbers = json_decode($row['behaelter_numbers'], true); |
|
859 |
+ if (is_array($numbers)) { |
|
860 |
+ foreach ($numbers as $number) { |
|
861 |
+ $usedNumbers[] = $number; |
|
862 |
+ } |
|
863 |
+ } |
|
864 |
+ } |
|
865 |
+ |
|
866 |
+ // Get a batch of available numbers |
|
867 |
+ // We'll limit to a reasonable number to improve performance |
|
868 |
+ $limit = 100; // Adjust this based on your needs |
|
869 |
+ if (!empty($_REQUEST['limit']) && is_numeric($_REQUEST['limit'])) { |
|
870 |
+ $limit = (int)$_REQUEST['limit']; |
|
871 |
+ } |
|
872 |
+ |
|
873 |
+ // Get available numbers directly, excluding used ones |
|
874 |
+ $availableNumbers = $Standort->extractNumbersFromRanges($usedNumbers, $limit); |
|
875 |
+ |
|
876 |
+ // Add the special option "Nummer nicht bekannt" with value 9999 |
|
877 |
+ // This option should always be available and can be used multiple times |
|
878 |
+ array_unshift($availableNumbers, '9999'); |
|
879 |
+ |
|
880 |
+ // Return the numbers as JSON |
|
881 |
+ return new Response(json_encode(['numbers' => $availableNumbers]), 200, ['Content-Type' => 'application/json']); |
|
882 |
+ } |
|
668 | 883 |
} |
... | ... |
@@ -21,4 +21,136 @@ class WeinanlieferungStandortModel extends Model |
21 | 21 |
* @var string |
22 | 22 |
*/ |
23 | 23 |
protected static $strTable = 'tl_vr_wa_standort'; |
24 |
+ |
|
25 |
+ /** |
|
26 |
+ * Extracts all possible numbers from the number_ranges field. |
|
27 |
+ * Expands ranges like "0000-0002" into individual numbers: "0000", "0001", "0002". |
|
28 |
+ * |
|
29 |
+ * @param array $excludeNumbers Optional array of numbers to exclude from the result |
|
30 |
+ * @param int $limit Optional limit to return only a specific number of results |
|
31 |
+ * @return array Array of extracted numbers |
|
32 |
+ */ |
|
33 |
+ public function extractNumbersFromRanges(array $excludeNumbers = [], int $limit = 0): array |
|
34 |
+ { |
|
35 |
+ $result = []; |
|
36 |
+ $ranges = $this->number_ranges; |
|
37 |
+ $excludeLookup = array_flip($excludeNumbers); // For faster lookups |
|
38 |
+ |
|
39 |
+ if (empty($ranges)) { |
|
40 |
+ return $result; |
|
41 |
+ } |
|
42 |
+ |
|
43 |
+ // Split by line breaks and other common separators |
|
44 |
+ $lines = preg_split('/[\r\n,;]+/', $ranges); |
|
45 |
+ |
|
46 |
+ foreach ($lines as $line) { |
|
47 |
+ $line = trim($line); |
|
48 |
+ if (empty($line)) { |
|
49 |
+ continue; |
|
50 |
+ } |
|
51 |
+ |
|
52 |
+ // Check if it's a range (contains a hyphen) |
|
53 |
+ if (strpos($line, '-') !== false) { |
|
54 |
+ list($start, $end) = array_map('trim', explode('-', $line, 2)); |
|
55 |
+ |
|
56 |
+ // Ensure both start and end are valid |
|
57 |
+ if (is_numeric($start) && is_numeric($end)) { |
|
58 |
+ // Get the padding length from the start number |
|
59 |
+ $padLength = strlen($start); |
|
60 |
+ |
|
61 |
+ // Convert to integers for the range calculation |
|
62 |
+ $startNum = intval($start); |
|
63 |
+ $endNum = intval($end); |
|
64 |
+ |
|
65 |
+ // Generate numbers in the range, but check limit |
|
66 |
+ for ($i = $startNum; $i <= $endNum; $i++) { |
|
67 |
+ $number = str_pad((string)$i, $padLength, '0', STR_PAD_LEFT); |
|
68 |
+ |
|
69 |
+ // Skip if this number should be excluded |
|
70 |
+ if (isset($excludeLookup[$number])) { |
|
71 |
+ continue; |
|
72 |
+ } |
|
73 |
+ |
|
74 |
+ $result[] = $number; |
|
75 |
+ |
|
76 |
+ // Check if we've reached the limit |
|
77 |
+ if ($limit > 0 && count($result) >= $limit) { |
|
78 |
+ return $result; |
|
79 |
+ } |
|
80 |
+ } |
|
81 |
+ } else { |
|
82 |
+ // If not a valid range, treat as a single number |
|
83 |
+ if (!isset($excludeLookup[$line])) { |
|
84 |
+ $result[] = $line; |
|
85 |
+ |
|
86 |
+ // Check if we've reached the limit |
|
87 |
+ if ($limit > 0 && count($result) >= $limit) { |
|
88 |
+ return $result; |
|
89 |
+ } |
|
90 |
+ } |
|
91 |
+ } |
|
92 |
+ } else { |
|
93 |
+ // It's a single number |
|
94 |
+ if (!isset($excludeLookup[$line])) { |
|
95 |
+ $result[] = $line; |
|
96 |
+ |
|
97 |
+ // Check if we've reached the limit |
|
98 |
+ if ($limit > 0 && count($result) >= $limit) { |
|
99 |
+ return $result; |
|
100 |
+ } |
|
101 |
+ } |
|
102 |
+ } |
|
103 |
+ } |
|
104 |
+ |
|
105 |
+ return $result; |
|
106 |
+ } |
|
107 |
+ |
|
108 |
+ /** |
|
109 |
+ * Returns the count of all possible numbers from the number_ranges field |
|
110 |
+ * without actually generating all the numbers. |
|
111 |
+ * |
|
112 |
+ * @return int Count of all possible numbers |
|
113 |
+ */ |
|
114 |
+ public function countNumbersInRanges(): int |
|
115 |
+ { |
|
116 |
+ $count = 0; |
|
117 |
+ $ranges = $this->number_ranges; |
|
118 |
+ |
|
119 |
+ if (empty($ranges)) { |
|
120 |
+ return $count; |
|
121 |
+ } |
|
122 |
+ |
|
123 |
+ // Split by line breaks and other common separators |
|
124 |
+ $lines = preg_split('/[\r\n,;]+/', $ranges); |
|
125 |
+ |
|
126 |
+ foreach ($lines as $line) { |
|
127 |
+ $line = trim($line); |
|
128 |
+ if (empty($line)) { |
|
129 |
+ continue; |
|
130 |
+ } |
|
131 |
+ |
|
132 |
+ // Check if it's a range (contains a hyphen) |
|
133 |
+ if (strpos($line, '-') !== false) { |
|
134 |
+ list($start, $end) = array_map('trim', explode('-', $line, 2)); |
|
135 |
+ |
|
136 |
+ // Ensure both start and end are valid |
|
137 |
+ if (is_numeric($start) && is_numeric($end)) { |
|
138 |
+ // Convert to integers for the range calculation |
|
139 |
+ $startNum = intval($start); |
|
140 |
+ $endNum = intval($end); |
|
141 |
+ |
|
142 |
+ // Add the count of numbers in this range |
|
143 |
+ $count += ($endNum - $startNum + 1); |
|
144 |
+ } else { |
|
145 |
+ // If not a valid range, count as a single number |
|
146 |
+ $count++; |
|
147 |
+ } |
|
148 |
+ } else { |
|
149 |
+ // It's a single number |
|
150 |
+ $count++; |
|
151 |
+ } |
|
152 |
+ } |
|
153 |
+ |
|
154 |
+ return $count; |
|
155 |
+ } |
|
24 | 156 |
} |