1 | 1 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,187 @@ |
1 |
+# Behaelter and Member Number Widget |
|
2 |
+ |
|
3 |
+This document describes the implementation of a widget for editing both behaelter (container) numbers and their corresponding member numbers in the backend. |
|
4 |
+ |
|
5 |
+## Overview |
|
6 |
+ |
|
7 |
+The implementation allows backend users to: |
|
8 |
+- Edit both behaelter numbers and their corresponding member numbers in a paired interface |
|
9 |
+- See which member number is associated with each behaelter |
|
10 |
+- Change the member number for individual behaelters |
|
11 |
+- Leave the member number field empty to use the booking member's number as a fallback |
|
12 |
+ |
|
13 |
+## Implementation Details |
|
14 |
+ |
|
15 |
+### 1. Widget Configuration |
|
16 |
+ |
|
17 |
+The behaelter_numbers field in `tl_vr_wa_reservation.php` has been updated to use the multiColumnWizard input type with two columns: |
|
18 |
+- **Behälternummer**: A text input for the behaelter number with validation for natural numbers |
|
19 |
+- **Mitgliedsnummer**: A text input for the member number with validation for natural numbers |
|
20 |
+ |
|
21 |
+```php |
|
22 |
+'behaelter_numbers' => array |
|
23 |
+( |
|
24 |
+ 'exclude' => true, |
|
25 |
+ 'inputType' => 'multiColumnWizard', |
|
26 |
+ 'eval' => array( |
|
27 |
+ 'tl_class' => 'clr', |
|
28 |
+ 'columnFields' => array |
|
29 |
+ ( |
|
30 |
+ 'behaelter' => array |
|
31 |
+ ( |
|
32 |
+ 'label' => $GLOBALS['TL_LANG']['MSC']['wa_behaelter_number'], |
|
33 |
+ 'inputType' => 'text', |
|
34 |
+ 'eval' => array('style' => 'width:200px', 'mandatory' => true, 'rgxp' => 'natural') |
|
35 |
+ ), |
|
36 |
+ 'member' => array |
|
37 |
+ ( |
|
38 |
+ 'label' => $GLOBALS['TL_LANG']['MSC']['wa_member_number'], |
|
39 |
+ 'inputType' => 'text', |
|
40 |
+ 'eval' => array('style' => 'width:200px', 'rgxp' => 'natural') |
|
41 |
+ ) |
|
42 |
+ ) |
|
43 |
+ ), |
|
44 |
+ 'sql' => "text NULL" |
|
45 |
+), |
|
46 |
+``` |
|
47 |
+ |
|
48 |
+### 2. Callback Modifications |
|
49 |
+ |
|
50 |
+#### Load Callback |
|
51 |
+ |
|
52 |
+The `onBehaelterNumbersLoadCallback` method has been updated to: |
|
53 |
+- Handle the new data format (array of objects with behaelter and member) |
|
54 |
+- Convert old format data (simple array of behaelter numbers) to the new format |
|
55 |
+- Return the data in the format expected by the multiColumnWizard |
|
56 |
+ |
|
57 |
+```php |
|
58 |
+public function onBehaelterNumbersLoadCallback($varValue, DataContainer $dc) |
|
59 |
+{ |
|
60 |
+ if (!empty($varValue)) |
|
61 |
+ { |
|
62 |
+ $decodedValue = \json_decode($varValue, true); |
|
63 |
+ |
|
64 |
+ // Check if we have the new format (array of objects with behaelter and member) |
|
65 |
+ // or the old format (simple array of behaelter numbers) |
|
66 |
+ $isNewFormat = isset($decodedValue[0]) && is_array($decodedValue[0]) && isset($decodedValue[0]['behaelter']); |
|
67 |
+ |
|
68 |
+ if ($isNewFormat) { |
|
69 |
+ // The data is already in the correct format for the multiColumnWizard |
|
70 |
+ return $decodedValue; |
|
71 |
+ } else { |
|
72 |
+ // Convert old format to new format |
|
73 |
+ $result = []; |
|
74 |
+ |
|
75 |
+ // Get the member associated with this reservation as fallback |
|
76 |
+ $reservation = \vonRotenberg\WeinanlieferungBundle\Model\WeinanlieferungReservationModel::findByPk($dc->id); |
|
77 |
+ $memberId = $reservation ? $reservation->uid : 0; |
|
78 |
+ $memberModel = \Contao\MemberModel::findById($memberId); |
|
79 |
+ $defaultMemberNo = $memberModel ? $memberModel->memberno : ''; |
|
80 |
+ |
|
81 |
+ foreach ($decodedValue as $behaelterNumber) { |
|
82 |
+ $result[] = [ |
|
83 |
+ 'behaelter' => $behaelterNumber, |
|
84 |
+ 'member' => $defaultMemberNo |
|
85 |
+ ]; |
|
86 |
+ } |
|
87 |
+ |
|
88 |
+ return $result; |
|
89 |
+ } |
|
90 |
+ } |
|
91 |
+ return $varValue; |
|
92 |
+} |
|
93 |
+``` |
|
94 |
+ |
|
95 |
+#### Save Callback |
|
96 |
+ |
|
97 |
+The `onBehaelterNumbersSaveCallback` method has been updated to: |
|
98 |
+- Handle the data format from the multiColumnWizard (array of rows with behaelter and member) |
|
99 |
+- Validate the behaelter numbers |
|
100 |
+- Process the member numbers, using the default member number as a fallback if a member number is empty |
|
101 |
+- Return the processed data as JSON |
|
102 |
+ |
|
103 |
+```php |
|
104 |
+public function onBehaelterNumbersSaveCallback($varValue, DataContainer $dc) |
|
105 |
+{ |
|
106 |
+ if (!empty($varValue) && is_array($varValue)) |
|
107 |
+ { |
|
108 |
+ // ... validation code ... |
|
109 |
+ |
|
110 |
+ // Get the member associated with this reservation as fallback |
|
111 |
+ $memberId = $reservation->uid; |
|
112 |
+ $memberModel = \Contao\MemberModel::findById($memberId); |
|
113 |
+ $defaultMemberNo = $memberModel ? $memberModel->memberno : ''; |
|
114 |
+ |
|
115 |
+ // Process the data from the multiColumnWizard |
|
116 |
+ $processedData = []; |
|
117 |
+ foreach ($varValue as $item) { |
|
118 |
+ $behaelterNumber = $item['behaelter']; |
|
119 |
+ |
|
120 |
+ // If member number is empty, use the default member number |
|
121 |
+ $memberNumber = !empty($item['member']) ? $item['member'] : $defaultMemberNo; |
|
122 |
+ |
|
123 |
+ $processedData[] = [ |
|
124 |
+ 'behaelter' => $behaelterNumber, |
|
125 |
+ 'member' => $memberNumber |
|
126 |
+ ]; |
|
127 |
+ } |
|
128 |
+ |
|
129 |
+ // Return the processed data as JSON |
|
130 |
+ return json_encode($processedData); |
|
131 |
+ } |
|
132 |
+ |
|
133 |
+ return $varValue; |
|
134 |
+} |
|
135 |
+``` |
|
136 |
+ |
|
137 |
+### 3. Language Labels |
|
138 |
+ |
|
139 |
+Language labels have been added to the default.php language file for the column headers: |
|
140 |
+ |
|
141 |
+```php |
|
142 |
+$GLOBALS['TL_LANG']['MSC']['wa_behaelter_number'] = 'Behälternummer'; |
|
143 |
+$GLOBALS['TL_LANG']['MSC']['wa_member_number'] = 'Mitgliedsnummer'; |
|
144 |
+``` |
|
145 |
+ |
|
146 |
+## Data Structure |
|
147 |
+ |
|
148 |
+The behaelter_numbers field stores a JSON-encoded array of objects, each with: |
|
149 |
+- `behaelter`: The behaelter number |
|
150 |
+- `member`: The associated member number |
|
151 |
+ |
|
152 |
+Example: |
|
153 |
+```json |
|
154 |
+[ |
|
155 |
+ { |
|
156 |
+ "behaelter": "123", |
|
157 |
+ "member": "456" |
|
158 |
+ }, |
|
159 |
+ { |
|
160 |
+ "behaelter": "789", |
|
161 |
+ "member": "456" |
|
162 |
+ } |
|
163 |
+] |
|
164 |
+``` |
|
165 |
+ |
|
166 |
+## Backward Compatibility |
|
167 |
+ |
|
168 |
+The implementation maintains backward compatibility with existing data: |
|
169 |
+- If the behaelter_numbers field contains the old format (simple array of behaelter numbers), it's automatically converted to the new format |
|
170 |
+- For old data, all behaelters are associated with the booking member's number |
|
171 |
+- The existing CSV export functionality continues to work with both old and new data formats |
|
172 |
+ |
|
173 |
+## Usage |
|
174 |
+ |
|
175 |
+To use the widget: |
|
176 |
+1. Edit a reservation in the backend |
|
177 |
+2. Scroll to the "Check-in" section |
|
178 |
+3. Enter behaelter numbers and optionally member numbers for each behaelter |
|
179 |
+4. If a member number is left empty, the booking member's number will be used as a fallback |
|
180 |
+5. Save the reservation |
|
181 |
+ |
|
182 |
+## Integration with Existing Features |
|
183 |
+ |
|
184 |
+This widget integrates with the existing features: |
|
185 |
+- The CSV export functionality uses the member numbers associated with each behaelter |
|
186 |
+- The backend booking list displays the behaelter numbers with their associated member numbers |
|
187 |
+- The check-in process in the frontend continues to work with the new data structure |
0 | 188 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,66 @@ |
1 |
+# Behaelter Numbers Backend Edit Fix |
|
2 |
+ |
|
3 |
+This document describes the changes made to fix the backend edit functionality for the `behaelter_numbers` field in the `tl_vr_wa_reservation` table. |
|
4 |
+ |
|
5 |
+## Issue Description |
|
6 |
+ |
|
7 |
+The backend edit functionality for the `behaelter_numbers` field was not handling the new data format correctly, which was causing errors. The new data format stores both the behaelter number and the associated member number, while the old format only stored the behaelter numbers. |
|
8 |
+ |
|
9 |
+## Changes Made |
|
10 |
+ |
|
11 |
+### 1. WeinanlieferungReservationContainerListener.php |
|
12 |
+ |
|
13 |
+#### onBehaelterNumbersLoadCallback |
|
14 |
+ |
|
15 |
+The `onBehaelterNumbersLoadCallback` method was updated to handle the new data format: |
|
16 |
+ |
|
17 |
+- It now checks if the data is in the new format (array of objects with behaelter and member) |
|
18 |
+- If it's the new format, it extracts just the behaelter numbers for display in the backend |
|
19 |
+- If it's the old format, it continues to work as before |
|
20 |
+- This ensures that the backend edit form displays just the behaelter numbers as a comma-separated list, regardless of the data format |
|
21 |
+ |
|
22 |
+#### onBehaelterNumbersSaveCallback |
|
23 |
+ |
|
24 |
+The `onBehaelterNumbersSaveCallback` method was updated to handle the new data format: |
|
25 |
+ |
|
26 |
+- It now properly handles both the old and new formats when checking used numbers from existing reservations |
|
27 |
+- It preserves member information when saving by: |
|
28 |
+ - Checking if the original value is in the new format |
|
29 |
+ - Creating a map of behaelter number to member number from the original data |
|
30 |
+ - Getting the member associated with the reservation as a fallback |
|
31 |
+ - Creating a new data structure that preserves the member information for existing behaelter numbers and uses the default member number for new ones |
|
32 |
+- This ensures that member information is not lost when editing behaelter numbers in the backend |
|
33 |
+ |
|
34 |
+## Data Structure |
|
35 |
+ |
|
36 |
+The `behaelter_numbers` field now stores a JSON-encoded array of objects, each with: |
|
37 |
+- `behaelter`: The behaelter number |
|
38 |
+- `member`: The associated member number |
|
39 |
+ |
|
40 |
+Example: |
|
41 |
+```json |
|
42 |
+[ |
|
43 |
+ { |
|
44 |
+ "behaelter": "123", |
|
45 |
+ "member": "456" |
|
46 |
+ }, |
|
47 |
+ { |
|
48 |
+ "behaelter": "789", |
|
49 |
+ "member": "456" |
|
50 |
+ } |
|
51 |
+] |
|
52 |
+``` |
|
53 |
+ |
|
54 |
+## Backward Compatibility |
|
55 |
+ |
|
56 |
+The implementation maintains backward compatibility with existing data: |
|
57 |
+- If the `behaelter_numbers` field contains the old format (simple array of behaelter numbers), it's automatically handled correctly |
|
58 |
+- When saving, the old format is converted to the new format, with all behaelters associated with the booking member's number |
|
59 |
+ |
|
60 |
+## Testing |
|
61 |
+ |
|
62 |
+The changes were tested with both the old and new data formats to ensure: |
|
63 |
+- The backend edit form displays just the behaelter numbers as a comma-separated list |
|
64 |
+- Member information is preserved when saving |
|
65 |
+- Validation of behaelter numbers works correctly |
|
66 |
+- The implementation handles both formats correctly when checking used numbers |
0 | 67 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,73 @@ |
1 |
+# Member Number for Behaelter Feature |
|
2 |
+ |
|
3 |
+This document describes the changes made to implement the feature that allows specifying a member number for each booked behaelter during check-in. |
|
4 |
+ |
|
5 |
+## Overview |
|
6 |
+ |
|
7 |
+The feature allows users to: |
|
8 |
+- Specify a different member number for each behaelter during check-in |
|
9 |
+- If no member number is provided, the system uses the logged-in member's number as a fallback |
|
10 |
+- The behaelter number and member number are stored together in the existing `behaelter_numbers` database column as a serialized array |
|
11 |
+ |
|
12 |
+## Changes Made |
|
13 |
+ |
|
14 |
+### 1. SlotAjaxController.php |
|
15 |
+ |
|
16 |
+- Added import for `MemberModel` class |
|
17 |
+- Updated `renderCheckin` method to: |
|
18 |
+ - Load the member model for the booking's user |
|
19 |
+ - Load the current logged-in member's model |
|
20 |
+ - Pass both to the template as `member` and `current_member` |
|
21 |
+- Updated `updateCheckin` method to: |
|
22 |
+ - Process member numbers from the form input |
|
23 |
+ - Implement fallback to use the logged-in member's number if none is provided |
|
24 |
+ - Store a combined data structure with both behaelter numbers and member numbers |
|
25 |
+ |
|
26 |
+### 2. modal_checkin.html.twig |
|
27 |
+ |
|
28 |
+- Added input fields for member numbers alongside each behaelter number select |
|
29 |
+- Set the current member's number as the default value |
|
30 |
+- Added helpful text indicating that leaving the field empty will use the current member's number |
|
31 |
+- Updated the layout to accommodate the new fields |
|
32 |
+ |
|
33 |
+### 3. CheckInCompletedListener.php |
|
34 |
+ |
|
35 |
+- Added backward compatibility to handle both the new format (array of objects with behaelter and member) and the old format (simple array of behaelter numbers) |
|
36 |
+- Updated the CSV export generation to use the member number associated with each behaelter |
|
37 |
+- Added logic to look up member information based on the member number |
|
38 |
+ |
|
39 |
+## Data Structure |
|
40 |
+ |
|
41 |
+The `behaelter_numbers` column now stores a JSON-encoded array of objects, each with: |
|
42 |
+- `behaelter`: The behaelter number |
|
43 |
+- `member`: The associated member number |
|
44 |
+ |
|
45 |
+Example: |
|
46 |
+```json |
|
47 |
+[ |
|
48 |
+ { |
|
49 |
+ "behaelter": "123", |
|
50 |
+ "member": "456" |
|
51 |
+ }, |
|
52 |
+ { |
|
53 |
+ "behaelter": "789", |
|
54 |
+ "member": "456" |
|
55 |
+ } |
|
56 |
+] |
|
57 |
+``` |
|
58 |
+ |
|
59 |
+## Backward Compatibility |
|
60 |
+ |
|
61 |
+The implementation maintains backward compatibility with existing data: |
|
62 |
+- If the `behaelter_numbers` column contains the old format (simple array of behaelter numbers), it's automatically converted to the new format |
|
63 |
+- For old data, all behaelters are associated with the booking member's number |
|
64 |
+ |
|
65 |
+## Testing |
|
66 |
+ |
|
67 |
+To test this feature: |
|
68 |
+1. Log in as a member |
|
69 |
+2. Navigate to a booking that can be checked in |
|
70 |
+3. Click the check-in button |
|
71 |
+4. Enter behaelter numbers and optionally member numbers |
|
72 |
+5. Submit the form |
|
73 |
+6. Verify that the CSV export contains the correct member numbers for each behaelter |
0 | 74 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,89 @@ |
1 |
+# Check-In Completed Event |
|
2 |
+ |
|
3 |
+This document describes the implementation of the check-in completed event in the Weinanlieferung Bundle. |
|
4 |
+ |
|
5 |
+## Overview |
|
6 |
+ |
|
7 |
+The check-in completed event is triggered when a reservation check-in is completed. This event allows other parts of the application to react to check-ins, such as sending notifications, updating statistics, or performing other actions. |
|
8 |
+ |
|
9 |
+## Implementation Details |
|
10 |
+ |
|
11 |
+### Files Created/Modified |
|
12 |
+ |
|
13 |
+1. **Created: `src/Event/CheckInCompletedEvent.php`** |
|
14 |
+ - Defines the event class that is dispatched when a check-in is completed |
|
15 |
+ - Contains the reservation data and model |
|
16 |
+ - Provides getter methods to access the data |
|
17 |
+ |
|
18 |
+2. **Modified: `src/Controller/Frontend/Ajax/SlotAjaxController.php`** |
|
19 |
+ - Added the event dispatcher as a dependency |
|
20 |
+ - Updated the constructor to inject the event dispatcher |
|
21 |
+ - Modified the `updateCheckin()` method to dispatch the event after a successful check-in |
|
22 |
+ |
|
23 |
+3. **Created: `src/EventListener/CheckInCompletedListener.php`** |
|
24 |
+ - Example listener that demonstrates how to subscribe to the check-in completed event |
|
25 |
+ - Logs when a check-in is completed |
|
26 |
+ |
|
27 |
+## How to Use the Event |
|
28 |
+ |
|
29 |
+### Listening to the Event |
|
30 |
+ |
|
31 |
+To listen to the check-in completed event, create a class that implements `EventSubscriberInterface`: |
|
32 |
+ |
|
33 |
+```php |
|
34 |
+use Symfony\Component\EventDispatcher\EventSubscriberInterface; |
|
35 |
+use vonRotenberg\WeinanlieferungBundle\Event\CheckInCompletedEvent; |
|
36 |
+ |
|
37 |
+class YourListener implements EventSubscriberInterface |
|
38 |
+{ |
|
39 |
+ public static function getSubscribedEvents() |
|
40 |
+ { |
|
41 |
+ return [ |
|
42 |
+ CheckInCompletedEvent::NAME => 'onCheckInCompleted', |
|
43 |
+ ]; |
|
44 |
+ } |
|
45 |
+ |
|
46 |
+ public function onCheckInCompleted(CheckInCompletedEvent $event) |
|
47 |
+ { |
|
48 |
+ // Get the reservation data |
|
49 |
+ $reservationData = $event->getReservationData(); |
|
50 |
+ $reservationModel = $event->getReservationModel(); |
|
51 |
+ |
|
52 |
+ // Perform your custom actions here |
|
53 |
+ } |
|
54 |
+} |
|
55 |
+``` |
|
56 |
+ |
|
57 |
+### Available Data |
|
58 |
+ |
|
59 |
+The event provides access to: |
|
60 |
+ |
|
61 |
+1. **Reservation Data Array**: Contains all fields from the reservation record |
|
62 |
+ ```php |
|
63 |
+ $reservationData = $event->getReservationData(); |
|
64 |
+ $id = $reservationData['id']; |
|
65 |
+ $checkedIn = $reservationData['checked_in']; |
|
66 |
+ $checkedInOn = $reservationData['checked_in_on']; |
|
67 |
+ // etc. |
|
68 |
+ ``` |
|
69 |
+ |
|
70 |
+2. **Reservation Model**: Provides the Contao model object for the reservation |
|
71 |
+ ```php |
|
72 |
+ $reservationModel = $event->getReservationModel(); |
|
73 |
+ $id = $reservationModel->id; |
|
74 |
+ $slot = $reservationModel->getRelated('pid'); |
|
75 |
+ // etc. |
|
76 |
+ ``` |
|
77 |
+ |
|
78 |
+## Testing |
|
79 |
+ |
|
80 |
+The example listener `CheckInCompletedListener` logs when a check-in is completed. You can check the Contao system log to verify that the event is being dispatched correctly. |
|
81 |
+ |
|
82 |
+When a check-in is completed, you should see a log entry like: |
|
83 |
+``` |
|
84 |
+Check-in completed for reservation ID: 123 |
|
85 |
+``` |
|
86 |
+ |
|
87 |
+## Conclusion |
|
88 |
+ |
|
89 |
+This implementation provides a flexible way to react to check-in events in the Weinanlieferung Bundle. By using Symfony's event system, it allows for loose coupling between the check-in process and any additional functionality that needs to be triggered when a check-in occurs. |
0 | 90 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,72 @@ |
1 |
+# Check-In CSV Export |
|
2 |
+ |
|
3 |
+This document describes the implementation of the CSV export functionality for checked-in bookings in the Weinanlieferung Bundle. |
|
4 |
+ |
|
5 |
+## Overview |
|
6 |
+ |
|
7 |
+When a reservation is checked in, a CSV export file is automatically generated. The file contains one line for each container (Behälter) in the booking, with detailed information about the member, grape variety, location, harvest type, and more. |
|
8 |
+ |
|
9 |
+## Implementation Details |
|
10 |
+ |
|
11 |
+The CSV export functionality is implemented in the `CheckInCompletedListener` class, which listens to the `CheckInCompletedEvent` that is dispatched when a reservation check-in is completed. |
|
12 |
+ |
|
13 |
+### Files Modified |
|
14 |
+ |
|
15 |
+1. **Modified: `src/EventListener/CheckInCompletedListener.php`** |
|
16 |
+ - Updated to generate a CSV export file for each checked-in booking |
|
17 |
+ - Added functionality to create the export directory if it doesn't exist |
|
18 |
+ - Implemented the CSV generation logic according to the requirements |
|
19 |
+ |
|
20 |
+2. **Modified: `config/services.yml`** |
|
21 |
+ - Added a specific service definition for the CheckInCompletedListener |
|
22 |
+ - Configured it to inject the project directory parameter |
|
23 |
+ |
|
24 |
+### CSV File Format |
|
25 |
+ |
|
26 |
+The CSV file contains one line for each container (Behälter) in the booking, with the following columns: |
|
27 |
+ |
|
28 |
+1. Member ID |
|
29 |
+2. Member first and last name (concatenated with space) |
|
30 |
+3. Rebsorte (grape variety) identifier |
|
31 |
+4. Rebsorte (grape variety) title |
|
32 |
+5. Lage (location) identifier |
|
33 |
+6. Lage (location) title |
|
34 |
+7. Leseart (harvest type) identifier |
|
35 |
+8. Leseart (harvest type) title |
|
36 |
+9. Ernteart (harvest method) - "H" for "handlese" or "V" for "vollernter" |
|
37 |
+10. Behälter number |
|
38 |
+11. Check-in date (format d.m.Y) |
|
39 |
+12. Behälter amount for the whole checked-in booking |
|
40 |
+ |
|
41 |
+### File Location and Naming |
|
42 |
+ |
|
43 |
+The CSV files are saved in the `/export/check_in` directory inside the project directory. If these directories don't exist, they are created automatically. |
|
44 |
+ |
|
45 |
+The filename is constructed using the booking ID and the member ID, separated by an underscore, with a `.csv` extension. For example: `123_456.csv`. |
|
46 |
+ |
|
47 |
+## How It Works |
|
48 |
+ |
|
49 |
+1. When a reservation is checked in, the `CheckInCompletedEvent` is dispatched in the `SlotAjaxController`. |
|
50 |
+2. The `CheckInCompletedListener` receives the event and extracts the reservation data. |
|
51 |
+3. The listener creates the export directory if it doesn't exist. |
|
52 |
+4. It retrieves the member data, container numbers, and other required information. |
|
53 |
+5. For each container, it creates a line in the CSV file with the required data. |
|
54 |
+6. It saves the CSV file in the export directory with the appropriate filename. |
|
55 |
+7. The listener logs the successful creation of the CSV file. |
|
56 |
+ |
|
57 |
+## Error Handling |
|
58 |
+ |
|
59 |
+The listener includes error handling for the following cases: |
|
60 |
+ |
|
61 |
+1. If the member data cannot be found, an error is logged and the CSV generation is aborted. |
|
62 |
+2. If no container numbers are found, an error is logged and the CSV generation is aborted. |
|
63 |
+3. If a container number is "9999" (special value for "Nummer nicht bekannt"), it is skipped. |
|
64 |
+ |
|
65 |
+## Testing |
|
66 |
+ |
|
67 |
+To test the CSV export functionality: |
|
68 |
+ |
|
69 |
+1. Check in a reservation through the frontend interface. |
|
70 |
+2. Verify that a CSV file is created in the `/export/check_in` directory. |
|
71 |
+3. Check the content of the CSV file to ensure it contains the correct data. |
|
72 |
+4. Check the Contao system log for any errors or success messages related to the CSV export. |
... | ... |
@@ -16,6 +16,8 @@ use vonRotenberg\WeinanlieferungBundle\Model\WeinanlieferungLeseartModel; |
16 | 16 |
use vonRotenberg\WeinanlieferungBundle\Model\WeinanlieferungReservationModel; |
17 | 17 |
use vonRotenberg\WeinanlieferungBundle\Model\WeinanlieferungSlotsModel; |
18 | 18 |
use vonRotenberg\WeinanlieferungBundle\Model\WeinanlieferungStandortModel; |
19 |
+use vonRotenberg\WeinanlieferungBundle\Model\WeinanlieferungAttributeGroupModel; |
|
20 |
+use vonRotenberg\WeinanlieferungBundle\Model\WeinanlieferungAttributeModel; |
|
19 | 21 |
use Contao\ArrayUtil; |
20 | 22 |
|
21 | 23 |
ArrayUtil::arrayInsert($GLOBALS['BE_MOD'],1,[ |
... | ... |
@@ -27,6 +29,10 @@ ArrayUtil::arrayInsert($GLOBALS['BE_MOD'],1,[ |
27 | 29 |
'wa_units' => [ |
28 | 30 |
'tables' => array('tl_vr_wa_units'), |
29 | 31 |
'stylesheet' => array('bundles/vonrotenbergweinanlieferung/css/backend.css') |
32 |
+ ], |
|
33 |
+ 'wa_attributes' => [ |
|
34 |
+ 'tables' => array('tl_vr_wa_attribute_group', 'tl_vr_wa_attribute'), |
|
35 |
+ 'stylesheet' => array('bundles/vonrotenbergweinanlieferung/css/backend.css') |
|
30 | 36 |
] |
31 | 37 |
] |
32 | 38 |
]); |
... | ... |
@@ -40,6 +46,8 @@ $GLOBALS['TL_MODELS']['tl_vr_wa_rebsorte'] = WeinanlieferungRebsorteModel::class |
40 | 46 |
$GLOBALS['TL_MODELS']['tl_vr_wa_leseart'] = WeinanlieferungLeseartModel::class; |
41 | 47 |
$GLOBALS['TL_MODELS']['tl_vr_wa_lage'] = WeinanlieferungLageModel::class; |
42 | 48 |
$GLOBALS['TL_MODELS']['tl_vr_wa_reservation'] = WeinanlieferungReservationModel::class; |
49 |
+$GLOBALS['TL_MODELS']['tl_vr_wa_attribute_group'] = WeinanlieferungAttributeGroupModel::class; |
|
50 |
+$GLOBALS['TL_MODELS']['tl_vr_wa_attribute'] = WeinanlieferungAttributeModel::class; |
|
43 | 51 |
|
44 | 52 |
|
45 | 53 |
// Notification |
46 | 54 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,150 @@ |
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 |
+\Contao\System::loadLanguageFile('default'); |
|
15 |
+\Contao\System::loadLanguageFile('tl_vr_wa_attribute'); |
|
16 |
+ |
|
17 |
+$GLOBALS['TL_DCA']['tl_vr_wa_attribute'] = array |
|
18 |
+( |
|
19 |
+ // Config |
|
20 |
+ 'config' => array |
|
21 |
+ ( |
|
22 |
+ 'dataContainer' => DC_Table::class, |
|
23 |
+ 'ptable' => 'tl_vr_wa_attribute_group', |
|
24 |
+ 'enableVersioning' => true, |
|
25 |
+ 'sql' => array |
|
26 |
+ ( |
|
27 |
+ 'keys' => array |
|
28 |
+ ( |
|
29 |
+ 'id' => 'primary', |
|
30 |
+ 'pid' => 'index' |
|
31 |
+ ) |
|
32 |
+ ) |
|
33 |
+ ), |
|
34 |
+ |
|
35 |
+ // List |
|
36 |
+ 'list' => array |
|
37 |
+ ( |
|
38 |
+ 'sorting' => array |
|
39 |
+ ( |
|
40 |
+ 'mode' => DataContainer::MODE_PARENT, |
|
41 |
+ 'fields' => array('title'), |
|
42 |
+ 'headerFields' => array('title'), |
|
43 |
+ 'flag' => DataContainer::SORT_INITIAL_LETTER_ASC, |
|
44 |
+ 'panelLayout' => 'filter;search,limit' |
|
45 |
+ ), |
|
46 |
+ 'label' => array |
|
47 |
+ ( |
|
48 |
+ 'fields' => array('title', 'type'), |
|
49 |
+ 'format' => '%s <span style="color:#999;padding-left:3px">[%s]</span>' |
|
50 |
+ ), |
|
51 |
+ 'global_operations' => array |
|
52 |
+ ( |
|
53 |
+ 'all' => array |
|
54 |
+ ( |
|
55 |
+ 'href' => 'act=select', |
|
56 |
+ 'class' => 'header_edit_all', |
|
57 |
+ 'attributes' => 'onclick="Backend.getScrollOffset()" accesskey="e"' |
|
58 |
+ ) |
|
59 |
+ ), |
|
60 |
+ 'operations' => array |
|
61 |
+ ( |
|
62 |
+ 'edit' => array |
|
63 |
+ ( |
|
64 |
+ 'href' => 'act=edit', |
|
65 |
+ 'icon' => 'edit.gif' |
|
66 |
+ ), |
|
67 |
+ 'copy' => array |
|
68 |
+ ( |
|
69 |
+ 'href' => 'act=paste&mode=copy', |
|
70 |
+ 'icon' => 'copy.svg' |
|
71 |
+ ), |
|
72 |
+ 'cut' => array |
|
73 |
+ ( |
|
74 |
+ 'href' => 'act=paste&mode=cut', |
|
75 |
+ 'icon' => 'cut.svg', |
|
76 |
+ 'attributes' => 'onclick="Backend.getScrollOffset()"' |
|
77 |
+ ), |
|
78 |
+ 'delete' => array |
|
79 |
+ ( |
|
80 |
+ 'href' => 'act=delete', |
|
81 |
+ 'icon' => 'delete.gif', |
|
82 |
+ 'attributes' => 'onclick="if(!confirm(\'' . $GLOBALS['TL_LANG']['MSC']['deleteConfirm'] . '\'))return false;Backend.getScrollOffset()"' |
|
83 |
+ ), |
|
84 |
+ 'show' => array |
|
85 |
+ ( |
|
86 |
+ 'href' => 'act=show', |
|
87 |
+ 'icon' => 'show.gif' |
|
88 |
+ ) |
|
89 |
+ ) |
|
90 |
+ ), |
|
91 |
+ |
|
92 |
+ // Palettes |
|
93 |
+ 'palettes' => array |
|
94 |
+ ( |
|
95 |
+ '__selector__' => array('type'), |
|
96 |
+ 'default' => '{title_legend},title,description;{type_legend},type' |
|
97 |
+ ), |
|
98 |
+ |
|
99 |
+ // Subpalettes |
|
100 |
+ 'subpalettes' => array |
|
101 |
+ ( |
|
102 |
+ 'type_text' => '', |
|
103 |
+ 'type_option' => '' |
|
104 |
+ ), |
|
105 |
+ |
|
106 |
+ // Fields |
|
107 |
+ 'fields' => array |
|
108 |
+ ( |
|
109 |
+ 'id' => array |
|
110 |
+ ( |
|
111 |
+ 'sql' => "int(10) unsigned NOT NULL auto_increment" |
|
112 |
+ ), |
|
113 |
+ 'pid' => array |
|
114 |
+ ( |
|
115 |
+ 'foreignKey' => 'tl_vr_wa_attribute_group.title', |
|
116 |
+ 'sql' => "int(10) unsigned NOT NULL default '0'", |
|
117 |
+ 'relation' => array('type'=>'belongsTo', 'load'=>'eager') |
|
118 |
+ ), |
|
119 |
+ 'tstamp' => array |
|
120 |
+ ( |
|
121 |
+ 'sql' => "int(10) unsigned NOT NULL default '0'" |
|
122 |
+ ), |
|
123 |
+ 'title' => array |
|
124 |
+ ( |
|
125 |
+ 'exclude' => true, |
|
126 |
+ 'search' => true, |
|
127 |
+ 'inputType' => 'text', |
|
128 |
+ 'eval' => array('mandatory'=>true, 'maxlength'=>255, 'tl_class'=>'w50'), |
|
129 |
+ 'sql' => "varchar(255) NOT NULL default ''" |
|
130 |
+ ), |
|
131 |
+ 'description' => array |
|
132 |
+ ( |
|
133 |
+ 'exclude' => true, |
|
134 |
+ 'search' => true, |
|
135 |
+ 'inputType' => 'textarea', |
|
136 |
+ 'eval' => array('tl_class'=>'clr'), |
|
137 |
+ 'sql' => "text NULL" |
|
138 |
+ ), |
|
139 |
+ 'type' => array |
|
140 |
+ ( |
|
141 |
+ 'exclude' => true, |
|
142 |
+ 'filter' => true, |
|
143 |
+ 'inputType' => 'select', |
|
144 |
+ 'options' => array('text', 'option'), |
|
145 |
+ 'reference' => &$GLOBALS['TL_LANG']['tl_vr_wa_attribute']['type_options'], |
|
146 |
+ 'eval' => array('mandatory'=>true, 'submitOnChange'=>true, 'tl_class'=>'w50'), |
|
147 |
+ 'sql' => "varchar(32) NOT NULL default 'text'" |
|
148 |
+ ), |
|
149 |
+ ) |
|
150 |
+); |
0 | 151 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,135 @@ |
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 |
+\Contao\System::loadLanguageFile('default'); |
|
15 |
+\Contao\System::loadLanguageFile('tl_vr_wa_attribute_group'); |
|
16 |
+ |
|
17 |
+$GLOBALS['TL_DCA']['tl_vr_wa_attribute_group'] = array |
|
18 |
+( |
|
19 |
+ // Config |
|
20 |
+ 'config' => array |
|
21 |
+ ( |
|
22 |
+ 'dataContainer' => DC_Table::class, |
|
23 |
+ 'ctable' => array('tl_vr_wa_attribute'), |
|
24 |
+ 'enableVersioning' => true, |
|
25 |
+ 'sql' => array |
|
26 |
+ ( |
|
27 |
+ 'keys' => array |
|
28 |
+ ( |
|
29 |
+ 'id' => 'primary' |
|
30 |
+ ) |
|
31 |
+ ) |
|
32 |
+ ), |
|
33 |
+ |
|
34 |
+ // List |
|
35 |
+ 'list' => array |
|
36 |
+ ( |
|
37 |
+ 'sorting' => array |
|
38 |
+ ( |
|
39 |
+ 'mode' => DataContainer::MODE_SORTED, |
|
40 |
+ 'fields' => array('title'), |
|
41 |
+ 'flag' => DataContainer::SORT_INITIAL_LETTER_ASC, |
|
42 |
+ 'panelLayout' => 'filter;search,limit' |
|
43 |
+ ), |
|
44 |
+ 'label' => array |
|
45 |
+ ( |
|
46 |
+ 'fields' => array('title'), |
|
47 |
+ 'format' => '%s' |
|
48 |
+ ), |
|
49 |
+ 'global_operations' => array |
|
50 |
+ ( |
|
51 |
+ 'all' => array |
|
52 |
+ ( |
|
53 |
+ 'href' => 'act=select', |
|
54 |
+ 'class' => 'header_edit_all', |
|
55 |
+ 'attributes' => 'onclick="Backend.getScrollOffset()" accesskey="e"' |
|
56 |
+ ) |
|
57 |
+ ), |
|
58 |
+ 'operations' => array |
|
59 |
+ ( |
|
60 |
+ 'edit' => array |
|
61 |
+ ( |
|
62 |
+ 'href' => 'act=edit', |
|
63 |
+ 'icon' => 'edit.gif' |
|
64 |
+ ), |
|
65 |
+ 'copy' => array |
|
66 |
+ ( |
|
67 |
+ 'href' => 'act=copy', |
|
68 |
+ 'icon' => 'copy.svg' |
|
69 |
+ ), |
|
70 |
+ 'delete' => array |
|
71 |
+ ( |
|
72 |
+ 'href' => 'act=delete', |
|
73 |
+ 'icon' => 'delete.gif', |
|
74 |
+ 'attributes' => 'onclick="if(!confirm(\'' . $GLOBALS['TL_LANG']['MSC']['deleteConfirm'] . '\'))return false;Backend.getScrollOffset()"' |
|
75 |
+ ), |
|
76 |
+ 'show' => array |
|
77 |
+ ( |
|
78 |
+ 'href' => 'act=show', |
|
79 |
+ 'icon' => 'show.gif' |
|
80 |
+ ), |
|
81 |
+ 'attributes' => array |
|
82 |
+ ( |
|
83 |
+ 'href' => 'table=tl_vr_wa_attribute', |
|
84 |
+ 'icon' => '/bundles/vonrotenbergweinanlieferung/images/icons/layers.svg' |
|
85 |
+ ) |
|
86 |
+ ) |
|
87 |
+ ), |
|
88 |
+ |
|
89 |
+ // Palettes |
|
90 |
+ 'palettes' => array |
|
91 |
+ ( |
|
92 |
+ 'default' => '{title_legend},title,description;{config_legend},multiple,required' |
|
93 |
+ ), |
|
94 |
+ |
|
95 |
+ // Fields |
|
96 |
+ 'fields' => array |
|
97 |
+ ( |
|
98 |
+ 'id' => array |
|
99 |
+ ( |
|
100 |
+ 'sql' => "int(10) unsigned NOT NULL auto_increment" |
|
101 |
+ ), |
|
102 |
+ 'tstamp' => array |
|
103 |
+ ( |
|
104 |
+ 'sql' => "int(10) unsigned NOT NULL default '0'" |
|
105 |
+ ), |
|
106 |
+ 'title' => array |
|
107 |
+ ( |
|
108 |
+ 'exclude' => true, |
|
109 |
+ 'inputType' => 'text', |
|
110 |
+ 'eval' => array('mandatory'=>true, 'maxlength'=>255, 'tl_class'=>'w50'), |
|
111 |
+ 'sql' => "varchar(255) NOT NULL default ''" |
|
112 |
+ ), |
|
113 |
+ 'description' => array |
|
114 |
+ ( |
|
115 |
+ 'exclude' => true, |
|
116 |
+ 'inputType' => 'textarea', |
|
117 |
+ 'eval' => array('tl_class'=>'clr'), |
|
118 |
+ 'sql' => "text NULL" |
|
119 |
+ ), |
|
120 |
+ 'multiple' => array |
|
121 |
+ ( |
|
122 |
+ 'exclude' => true, |
|
123 |
+ 'inputType' => 'checkbox', |
|
124 |
+ 'eval' => array('tl_class'=>'w50 m12'), |
|
125 |
+ 'sql' => "char(1) NOT NULL default ''" |
|
126 |
+ ), |
|
127 |
+ 'required' => array |
|
128 |
+ ( |
|
129 |
+ 'exclude' => true, |
|
130 |
+ 'inputType' => 'checkbox', |
|
131 |
+ 'eval' => array('tl_class'=>'w50 m12'), |
|
132 |
+ 'sql' => "char(1) NOT NULL default ''" |
|
133 |
+ ) |
|
134 |
+ ) |
|
135 |
+); |
... | ... |
@@ -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,unit,amount,behaelter,annotation,upload' |
|
100 |
+ 'default' => 'pid,uid,unit,amount,behaelter,attribute_values,annotation,upload' |
|
101 | 101 |
), |
102 | 102 |
|
103 | 103 |
// Subpalettes |
... | ... |
@@ -216,6 +216,16 @@ $GLOBALS['TL_DCA']['tl_vr_wa_reservation'] = array |
216 | 216 |
), |
217 | 217 |
'sql' => "varchar(255) BINARY NOT NULL default ''" |
218 | 218 |
), |
219 |
+ 'attribute_values' => array |
|
220 |
+ ( |
|
221 |
+ 'exclude' => true, |
|
222 |
+ 'inputType' => 'textarea', |
|
223 |
+ 'eval' => array('tl_class'=>'clr', 'readonly'=>"true", 'allowHtml'=>false, 'preserveTags'=>false), |
|
224 |
+ 'sql' => "text NULL", |
|
225 |
+ 'load_callback' => array( |
|
226 |
+ array('tl_vr_wa_reservation', 'formatAttributeValues') |
|
227 |
+ ) |
|
228 |
+ ), |
|
219 | 229 |
'annotation' => array |
220 | 230 |
( |
221 | 231 |
'exclude' => true, |
... | ... |
@@ -93,7 +93,7 @@ $GLOBALS['TL_DCA']['tl_vr_wa_slot'] = array |
93 | 93 |
( |
94 | 94 |
'__selector__' => array('addEnclosure'), |
95 | 95 |
// 'default' => '{time_legend},date,time,duration;{type_legend},behaelter,sorten,lage,ernteart;{info_legend},anmerkungen,addEnclosure;{booking_legend},published,buchbar_ab,buchbar_bis' |
96 |
- 'default' => '{time_legend},date,time,duration;{type_legend},behaelter,overcapacity;{info_legend},anmerkungen,addEnclosure;{booking_legend},published,buchbar_ab,buchbar_bis' |
|
96 |
+ 'default' => '{time_legend},date,time,duration;{type_legend},behaelter,overcapacity;{attributes_legend},attributes;{info_legend},anmerkungen,addEnclosure;{booking_legend},published,buchbar_ab,buchbar_bis' |
|
97 | 97 |
), |
98 | 98 |
|
99 | 99 |
// Subpalettes |
... | ... |
@@ -278,5 +278,15 @@ $GLOBALS['TL_DCA']['tl_vr_wa_slot'] = array |
278 | 278 |
'eval' => array('rgxp' => 'datim', 'mandatory' => true, 'datepicker' => true, 'tl_class' => 'w50 wizard'), |
279 | 279 |
'sql' => "int(10) unsigned NULL" |
280 | 280 |
), |
281 |
+ 'attributes' => array |
|
282 |
+ ( |
|
283 |
+ 'exclude' => true, |
|
284 |
+ 'inputType' => 'checkboxWizard', |
|
285 |
+ 'eval' => array('multiple'=>true, 'csv'=>',', 'tl_class'=>'clr'), |
|
286 |
+ 'load_callback' => array(array('vonRotenberg\WeinanlieferungBundle\EventListener\DataContainer\WeinanlieferungSlotContainerListener', 'loadAttributes')), |
|
287 |
+ 'save_callback' => array(array('vonRotenberg\WeinanlieferungBundle\EventListener\DataContainer\WeinanlieferungSlotContainerListener', 'saveAttributes')), |
|
288 |
+ 'options_callback' => array('vonRotenberg\WeinanlieferungBundle\EventListener\DataContainer\WeinanlieferungSlotContainerListener', 'getAttributeOptions'), |
|
289 |
+ 'sql' => "blob NULL" |
|
290 |
+ ), |
|
281 | 291 |
) |
282 | 292 |
); |
... | ... |
@@ -76,5 +76,14 @@ $GLOBALS['TL_DCA']['tl_vr_wa_slotassistant'] = |
76 | 76 |
'eval' => ['multiple' =>true, 'fieldType' =>'checkbox', 'filesOnly' =>true, 'isDownloads' =>true, 'extensions' =>Config::get('allowedDownload'), 'mandatory' =>true, 'isSortable' =>true], |
77 | 77 |
'sql' => "blob NULL" |
78 | 78 |
], |
79 |
+ 'attributes' => [ |
|
80 |
+ 'exclude' => true, |
|
81 |
+ 'inputType' => 'checkboxWizard', |
|
82 |
+ 'eval' => ['multiple'=>true, 'csv'=>',', 'tl_class'=>'clr'], |
|
83 |
+ 'load_callback' => [['vonRotenberg\WeinanlieferungBundle\EventListener\DataContainer\WeinanlieferungSlotContainerListener', 'loadAttributes']], |
|
84 |
+ 'save_callback' => [['vonRotenberg\WeinanlieferungBundle\EventListener\DataContainer\WeinanlieferungSlotContainerListener', 'saveAttributes']], |
|
85 |
+ 'options_callback' => ['vonRotenberg\WeinanlieferungBundle\EventListener\DataContainer\WeinanlieferungSlotContainerListener', 'getAttributeOptions'], |
|
86 |
+ 'sql' => "blob NULL" |
|
87 |
+ ], |
|
79 | 88 |
] |
80 | 89 |
]; |
81 | 90 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,28 @@ |
1 |
+<?php |
|
2 |
+ |
|
3 |
+/** |
|
4 |
+ * This file is part of contao-weinanlieferung-bundle. |
|
5 |
+ * |
|
6 |
+ * (c) vonRotenberg |
|
7 |
+ * |
|
8 |
+ * @license commercial |
|
9 |
+ */ |
|
10 |
+ |
|
11 |
+$GLOBALS['TL_LANG']['tl_vr_wa_attribute']['title_legend'] = 'Titel und Beschreibung'; |
|
12 |
+$GLOBALS['TL_LANG']['tl_vr_wa_attribute']['type_legend'] = 'Attributtyp'; |
|
13 |
+ |
|
14 |
+$GLOBALS['TL_LANG']['tl_vr_wa_attribute']['title'] = ['Titel', 'Bitte geben Sie den Titel des Attributs ein.']; |
|
15 |
+$GLOBALS['TL_LANG']['tl_vr_wa_attribute']['description'] = ['Beschreibung', 'Bitte geben Sie eine Beschreibung des Attributs ein.']; |
|
16 |
+$GLOBALS['TL_LANG']['tl_vr_wa_attribute']['type'] = ['Typ', 'Bitte wählen Sie den Typ des Attributs.']; |
|
17 |
+ |
|
18 |
+$GLOBALS['TL_LANG']['tl_vr_wa_attribute']['type_options'] = [ |
|
19 |
+ 'text' => 'Freitext', |
|
20 |
+ 'option' => 'Option (Auswählbar)' |
|
21 |
+]; |
|
22 |
+ |
|
23 |
+$GLOBALS['TL_LANG']['tl_vr_wa_attribute']['new'] = ['Neues Attribut', 'Ein neues Attribut erstellen']; |
|
24 |
+$GLOBALS['TL_LANG']['tl_vr_wa_attribute']['edit'] = ['Attribut bearbeiten', 'Attribut ID %s bearbeiten']; |
|
25 |
+$GLOBALS['TL_LANG']['tl_vr_wa_attribute']['copy'] = ['Attribut duplizieren', 'Attribut ID %s duplizieren']; |
|
26 |
+$GLOBALS['TL_LANG']['tl_vr_wa_attribute']['cut'] = ['Attribut verschieben', 'Attribut ID %s verschieben']; |
|
27 |
+$GLOBALS['TL_LANG']['tl_vr_wa_attribute']['delete'] = ['Attribut löschen', 'Attribut ID %s löschen']; |
|
28 |
+$GLOBALS['TL_LANG']['tl_vr_wa_attribute']['show'] = ['Attribut anzeigen', 'Details des Attributs ID %s anzeigen']; |
0 | 29 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,24 @@ |
1 |
+<?php |
|
2 |
+ |
|
3 |
+/** |
|
4 |
+ * This file is part of contao-weinanlieferung-bundle. |
|
5 |
+ * |
|
6 |
+ * (c) vonRotenberg |
|
7 |
+ * |
|
8 |
+ * @license commercial |
|
9 |
+ */ |
|
10 |
+ |
|
11 |
+$GLOBALS['TL_LANG']['tl_vr_wa_attribute_group']['title_legend'] = 'Titel und Beschreibung'; |
|
12 |
+$GLOBALS['TL_LANG']['tl_vr_wa_attribute_group']['config_legend'] = 'Konfiguration'; |
|
13 |
+ |
|
14 |
+$GLOBALS['TL_LANG']['tl_vr_wa_attribute_group']['title'] = ['Titel', 'Bitte geben Sie den Titel der Attributgruppe ein.']; |
|
15 |
+$GLOBALS['TL_LANG']['tl_vr_wa_attribute_group']['description'] = ['Beschreibung', 'Bitte geben Sie eine Beschreibung der Attributgruppe ein.']; |
|
16 |
+$GLOBALS['TL_LANG']['tl_vr_wa_attribute_group']['multiple'] = ['Mehrfachauswahl', 'Erlaubt die Auswahl mehrerer Attribute aus dieser Gruppe.']; |
|
17 |
+$GLOBALS['TL_LANG']['tl_vr_wa_attribute_group']['required'] = ['Pflichtfeld', 'Die Auswahl eines Attributs aus dieser Gruppe ist erforderlich.']; |
|
18 |
+ |
|
19 |
+$GLOBALS['TL_LANG']['tl_vr_wa_attribute_group']['new'] = ['Neue Attributgruppe', 'Eine neue Attributgruppe erstellen']; |
|
20 |
+$GLOBALS['TL_LANG']['tl_vr_wa_attribute_group']['edit'] = ['Attributgruppe bearbeiten', 'Attributgruppe ID %s bearbeiten']; |
|
21 |
+$GLOBALS['TL_LANG']['tl_vr_wa_attribute_group']['copy'] = ['Attributgruppe duplizieren', 'Attributgruppe ID %s duplizieren']; |
|
22 |
+$GLOBALS['TL_LANG']['tl_vr_wa_attribute_group']['delete'] = ['Attributgruppe löschen', 'Attributgruppe ID %s löschen']; |
|
23 |
+$GLOBALS['TL_LANG']['tl_vr_wa_attribute_group']['show'] = ['Attributgruppe anzeigen', 'Details der Attributgruppe ID %s anzeigen']; |
|
24 |
+$GLOBALS['TL_LANG']['tl_vr_wa_attribute_group']['attributes'] = ['Attribute verwalten', 'Attribute der Gruppe ID %s verwalten']; |
... | ... |
@@ -26,6 +26,8 @@ $GLOBALS['TL_LANG']['tl_vr_wa_reservation']['uid'][0] = 'Winzer'; |
26 | 26 |
$GLOBALS['TL_LANG']['tl_vr_wa_reservation']['uid'][1] = 'Zuordnung des anliefernden Winzers.'; |
27 | 27 |
$GLOBALS['TL_LANG']['tl_vr_wa_reservation']['upload'][0] = 'Datei'; |
28 | 28 |
$GLOBALS['TL_LANG']['tl_vr_wa_reservation']['upload'][1] = 'Die hochgeladene Datei des Winzers.'; |
29 |
+$GLOBALS['TL_LANG']['tl_vr_wa_reservation']['attribute_values'][0] = 'Attributwerte'; |
|
30 |
+$GLOBALS['TL_LANG']['tl_vr_wa_reservation']['attribute_values'][1] = 'Die vom Buchenden ausgewählten Attributwerte.'; |
|
29 | 31 |
$GLOBALS['TL_LANG']['tl_vr_wa_reservation']['annotation'][0] = 'Anmerkungen'; |
30 | 32 |
$GLOBALS['TL_LANG']['tl_vr_wa_reservation']['annotation'][1] = 'Ergänzende Hinweise zu diesem Zeitslot.'; |
31 | 33 |
|
... | ... |
@@ -40,9 +40,12 @@ $GLOBALS['TL_LANG']['tl_vr_wa_slot']['buchbar_ab'][0] = 'Buchbar ab'; |
40 | 40 |
$GLOBALS['TL_LANG']['tl_vr_wa_slot']['buchbar_ab'][1] = 'Zeitpunkt, ab wann der Slot gebucht werden kann.'; |
41 | 41 |
$GLOBALS['TL_LANG']['tl_vr_wa_slot']['buchbar_bis'][0] = 'Buchbar bis'; |
42 | 42 |
$GLOBALS['TL_LANG']['tl_vr_wa_slot']['buchbar_bis'][1] = 'Zeitpunkt, bis wann der Slot spätestens gebucht werden kann.'; |
43 |
+$GLOBALS['TL_LANG']['tl_vr_wa_slot']['attribute_groups'][0] = 'Attributgruppen'; |
|
44 |
+$GLOBALS['TL_LANG']['tl_vr_wa_slot']['attribute_groups'][1] = 'Wählen Sie die Attributgruppen aus, die für diesen Slot verfügbar sein sollen.'; |
|
43 | 45 |
|
44 | 46 |
$GLOBALS['TL_LANG']['tl_vr_wa_slot']['time_legend'] = 'Zeit'; |
45 | 47 |
$GLOBALS['TL_LANG']['tl_vr_wa_slot']['type_legend'] = 'Verarbeitungs-Einstellungen'; |
48 |
+$GLOBALS['TL_LANG']['tl_vr_wa_slot']['attributes_legend'] = 'Attribute'; |
|
46 | 49 |
$GLOBALS['TL_LANG']['tl_vr_wa_slot']['info_legend'] = 'Zusätzliche Informationen'; |
47 | 50 |
$GLOBALS['TL_LANG']['tl_vr_wa_slot']['booking_legend'] = 'Buchbarkeits-Einstellungen'; |
48 | 51 |
|
... | ... |
@@ -22,6 +22,14 @@ |
22 | 22 |
<source>Slot settings</source> |
23 | 23 |
<target>Slot-Einstellungen</target> |
24 | 24 |
</trans-unit> |
25 |
+ <trans-unit id="tl_vr_wa_slotassistant.attribute_groups"> |
|
26 |
+ <source>Attribute groups</source> |
|
27 |
+ <target>Attributgruppen</target> |
|
28 |
+ </trans-unit> |
|
29 |
+ <trans-unit id="tl_vr_wa_slotassistant.attributes_legend"> |
|
30 |
+ <source>Attributes</source> |
|
31 |
+ <target>Attribute</target> |
|
32 |
+ </trans-unit> |
|
25 | 33 |
</body> |
26 | 34 |
</file> |
27 | 35 |
</xliff> |
... | ... |
@@ -77,6 +77,39 @@ |
77 | 77 |
</fieldset> |
78 | 78 |
</div> |
79 | 79 |
</div> |
80 |
+ {% if attribute_groups is defined and attribute_groups|length > 0 %} |
|
81 |
+ {% for group in attribute_groups %} |
|
82 |
+ <fieldset> |
|
83 |
+ <legend><strong>{{ group.title }}{% if group.required %}<sup class="text-danger">*</sup>{% endif %}</strong></legend> |
|
84 |
+ {% if group.description %} |
|
85 |
+ <div class="description">{{ group.description }}</div> |
|
86 |
+ {% endif %} |
|
87 |
+ |
|
88 |
+ {% for attribute in group.attributes %} |
|
89 |
+ <div class="attribute-item"> |
|
90 |
+ {% if attribute.type == 'text' %} |
|
91 |
+ <label for="attribute_{{ attribute.id }}">{{ attribute.title }}</label> |
|
92 |
+ <input type="text" id="attribute_{{ attribute.id }}" name="attribute_{{ attribute.id }}" class="form-control" value="{{ attribute.value }}"> |
|
93 |
+ {% if attribute.description %} |
|
94 |
+ <div class="description">{{ attribute.description }}</div> |
|
95 |
+ {% endif %} |
|
96 |
+ {% elseif attribute.type == 'option' %} |
|
97 |
+ <div class="checkbox"> |
|
98 |
+ <label> |
|
99 |
+ <input type="checkbox" id="attribute_{{ attribute.id }}" name="attribute_{{ attribute.id }}" value="1" {% if attribute.value %}checked{% endif %}> |
|
100 |
+ {{ attribute.title }} |
|
101 |
+ </label> |
|
102 |
+ {% if attribute.description %} |
|
103 |
+ <div class="description">{{ attribute.description }}</div> |
|
104 |
+ {% endif %} |
|
105 |
+ </div> |
|
106 |
+ {% endif %} |
|
107 |
+ </div> |
|
108 |
+ {% endfor %} |
|
109 |
+ </fieldset> |
|
110 |
+ {% endfor %} |
|
111 |
+ {% endif %} |
|
112 |
+ |
|
80 | 113 |
<fieldset> |
81 | 114 |
<label for="res-annotation"><strong>Anmerkung</strong></label> |
82 | 115 |
<textarea id="res-annotation" name="annotation">{{ buchung.annotation }}</textarea> |
... | ... |
@@ -88,12 +121,14 @@ |
88 | 121 |
</fieldset> |
89 | 122 |
<fieldset> |
90 | 123 |
<label for="res-upload"><strong>Datei überschreiben</strong></label> |
124 |
+ <input type="file" id="res-upload" name="upload"> |
|
125 |
+ </fieldset> |
|
91 | 126 |
{% else %} |
92 | 127 |
<fieldset> |
93 | 128 |
<label for="res-upload"><strong>Datei hochladen</strong></label> |
129 |
+ <input type="file" id="res-upload" name="upload"> |
|
130 |
+ </fieldset> |
|
94 | 131 |
{% endif %} |
95 |
- <input type="file" id="res-upload" name="upload"> |
|
96 |
- </fieldset> |
|
97 | 132 |
<fieldset> |
98 | 133 |
<button type="submit">Speichern</button> |
99 | 134 |
</fieldset> |
... | ... |
@@ -101,6 +136,7 @@ |
101 | 136 |
</div> |
102 | 137 |
|
103 | 138 |
<div class="loader animated loading"></div> |
139 |
+ </div> |
|
104 | 140 |
{% if modal %}</div>{% endif %} |
105 | 141 |
{% endblock %} |
106 | 142 |
|
... | ... |
@@ -111,6 +111,39 @@ |
111 | 111 |
</fieldset> |
112 | 112 |
</div> |
113 | 113 |
</div> |
114 |
+ {% if attribute_groups is defined and attribute_groups|length > 0 %} |
|
115 |
+ {% for group in attribute_groups %} |
|
116 |
+ <fieldset> |
|
117 |
+ <legend><strong>{{ group.title }}{% if group.required %}<sup class="text-danger">*</sup>{% endif %}</strong></legend> |
|
118 |
+ {% if group.description %} |
|
119 |
+ <div class="description">{{ group.description }}</div> |
|
120 |
+ {% endif %} |
|
121 |
+ |
|
122 |
+ {% for attribute in group.attributes %} |
|
123 |
+ <div class="attribute-item"> |
|
124 |
+ {% if attribute.type == 'text' %} |
|
125 |
+ <label for="attribute_{{ attribute.id }}">{{ attribute.title }}</label> |
|
126 |
+ <input type="text" id="attribute_{{ attribute.id }}" name="attribute_{{ attribute.id }}" class="form-control"> |
|
127 |
+ {% if attribute.description %} |
|
128 |
+ <div class="description">{{ attribute.description }}</div> |
|
129 |
+ {% endif %} |
|
130 |
+ {% elseif attribute.type == 'option' %} |
|
131 |
+ <div class="checkbox"> |
|
132 |
+ <label> |
|
133 |
+ <input type="checkbox" id="attribute_{{ attribute.id }}" name="attribute_{{ attribute.id }}" value="1"> |
|
134 |
+ {{ attribute.title }} |
|
135 |
+ </label> |
|
136 |
+ {% if attribute.description %} |
|
137 |
+ <div class="description">{{ attribute.description }}</div> |
|
138 |
+ {% endif %} |
|
139 |
+ </div> |
|
140 |
+ {% endif %} |
|
141 |
+ </div> |
|
142 |
+ {% endfor %} |
|
143 |
+ </fieldset> |
|
144 |
+ {% endfor %} |
|
145 |
+ {% endif %} |
|
146 |
+ |
|
114 | 147 |
<fieldset> |
115 | 148 |
<label for="res-annotation"><strong>Anmerkung</strong></label> |
116 | 149 |
<textarea id="res-annotation" name="annotation"></textarea> |
... | ... |
@@ -37,6 +37,8 @@ use vonRotenberg\WeinanlieferungBundle\Model\WeinanlieferungRebsorteModel; |
37 | 37 |
use vonRotenberg\WeinanlieferungBundle\Model\WeinanlieferungReservationModel; |
38 | 38 |
use vonRotenberg\WeinanlieferungBundle\Model\WeinanlieferungSlotsModel; |
39 | 39 |
use vonRotenberg\WeinanlieferungBundle\Model\WeinanlieferungUnitsModel; |
40 |
+use vonRotenberg\WeinanlieferungBundle\Model\WeinanlieferungAttributeGroupModel; |
|
41 |
+use vonRotenberg\WeinanlieferungBundle\Model\WeinanlieferungAttributeModel; |
|
40 | 42 |
|
41 | 43 |
/** |
42 | 44 |
* @Route("/_ajax/vr_wa/v1/slot", name="vr_wa_slot_ajax", defaults={"_scope" = "frontend", "_token_check" = false}) |
... | ... |
@@ -147,7 +149,69 @@ class SlotAjaxController extends AbstractController |
147 | 149 |
$arrUnits[$unit->id] = $unit->title . ' (' . $unit->containers . ' '.$this->translator->trans('tl_vr_wa_units.containers.0', [], 'contao_tl_vr_wa_units').')'; |
148 | 150 |
} |
149 | 151 |
} |
152 |
+ } |
|
153 |
+ |
|
154 |
+ // Load attribute groups and attributes |
|
155 |
+ $arrAttributeGroups = []; |
|
156 |
+ |
|
157 |
+ // Get selected attributes |
|
158 |
+ $selectedAttributeIds = []; |
|
159 |
+ if ($Slot->attributes) { |
|
160 |
+ $selectedAttributeIds = StringUtil::deserialize($Slot->attributes, true); |
|
161 |
+ } |
|
162 |
+ |
|
163 |
+ // If no attributes are selected, don't show any |
|
164 |
+ if (empty($selectedAttributeIds)) { |
|
165 |
+ // Keep the attribute_groups array empty |
|
166 |
+ } else { |
|
167 |
+ // Fetch the selected attributes |
|
168 |
+ $selectedAttributes = WeinanlieferungAttributeModel::findMultipleByIds($selectedAttributeIds); |
|
169 |
+ if ($selectedAttributes !== null) { |
|
170 |
+ // Group the attributes by their parent group |
|
171 |
+ $attributesByGroup = []; |
|
172 |
+ $groupIds = []; |
|
173 |
+ |
|
174 |
+ // First pass: collect all group IDs and organize attributes by group |
|
175 |
+ foreach ($selectedAttributes as $attribute) { |
|
176 |
+ $groupId = $attribute->pid; |
|
177 |
+ $groupIds[] = $groupId; |
|
178 |
+ |
|
179 |
+ if (!isset($attributesByGroup[$groupId])) { |
|
180 |
+ $attributesByGroup[$groupId] = []; |
|
181 |
+ } |
|
182 |
+ |
|
183 |
+ $attributesByGroup[$groupId][] = [ |
|
184 |
+ 'id' => $attribute->id, |
|
185 |
+ 'title' => $attribute->title, |
|
186 |
+ 'description' => $attribute->description, |
|
187 |
+ 'type' => $attribute->type |
|
188 |
+ ]; |
|
189 |
+ } |
|
150 | 190 |
|
191 |
+ // Get unique group IDs |
|
192 |
+ $groupIds = array_unique($groupIds); |
|
193 |
+ |
|
194 |
+ // Fetch the groups |
|
195 |
+ if (!empty($groupIds)) { |
|
196 |
+ $groups = WeinanlieferungAttributeGroupModel::findMultipleByIds($groupIds); |
|
197 |
+ |
|
198 |
+ if ($groups !== null) { |
|
199 |
+ // Create the attribute groups array |
|
200 |
+ foreach ($groups as $group) { |
|
201 |
+ if (isset($attributesByGroup[$group->id])) { |
|
202 |
+ $arrAttributeGroups[] = [ |
|
203 |
+ 'id' => $group->id, |
|
204 |
+ 'title' => $group->title, |
|
205 |
+ 'description' => $group->description, |
|
206 |
+ 'multiple' => (bool)$group->multiple, |
|
207 |
+ 'required' => (bool)$group->required, |
|
208 |
+ 'attributes' => $attributesByGroup[$group->id] |
|
209 |
+ ]; |
|
210 |
+ } |
|
211 |
+ } |
|
212 |
+ } |
|
213 |
+ } |
|
214 |
+ } |
|
151 | 215 |
} |
152 | 216 |
|
153 | 217 |
$arrData = [ |
... | ... |
@@ -163,7 +227,8 @@ class SlotAjaxController extends AbstractController |
163 | 227 |
'behaelter' => range(min($intBookableBehaelter,1),$intBookableBehaelter), |
164 | 228 |
'units' => $arrUnits, |
165 | 229 |
], |
166 |
- 'reservations' => $arrReservations |
|
230 |
+ 'reservations' => $arrReservations, |
|
231 |
+ 'attribute_groups' => $arrAttributeGroups |
|
167 | 232 |
]; |
168 | 233 |
|
169 | 234 |
if (!empty($error)) |
... | ... |
@@ -322,7 +387,83 @@ class SlotAjaxController extends AbstractController |
322 | 387 |
$intDefaultAmount = floor($intDefaultAmount / max(1, $Unit->containers)); |
323 | 388 |
$intUnitAmount = floor($intUnitAmount / max(1, $Unit->containers)); |
324 | 389 |
} |
390 |
+ } |
|
391 |
+ |
|
392 |
+ // Load attribute groups and attributes |
|
393 |
+ $arrAttributeGroups = []; |
|
394 |
+ $attributeValues = []; |
|
395 |
+ |
|
396 |
+ // Parse existing attribute values if available |
|
397 |
+ if (!empty($Booking->attribute_values)) { |
|
398 |
+ try { |
|
399 |
+ $attributeValues = json_decode($Booking->attribute_values, true); |
|
400 |
+ if (!is_array($attributeValues)) { |
|
401 |
+ $attributeValues = []; |
|
402 |
+ } |
|
403 |
+ } catch (\Exception $e) { |
|
404 |
+ $attributeValues = []; |
|
405 |
+ } |
|
406 |
+ } |
|
325 | 407 |
|
408 |
+ // Get selected attributes |
|
409 |
+ $selectedAttributeIds = []; |
|
410 |
+ if ($Slot->attributes) { |
|
411 |
+ $selectedAttributeIds = StringUtil::deserialize($Slot->attributes, true); |
|
412 |
+ } |
|
413 |
+ |
|
414 |
+ // If no attributes are selected, don't show any |
|
415 |
+ if (empty($selectedAttributeIds)) { |
|
416 |
+ // Keep the attribute_groups array empty |
|
417 |
+ } else { |
|
418 |
+ // Fetch the selected attributes |
|
419 |
+ $selectedAttributes = WeinanlieferungAttributeModel::findMultipleByIds($selectedAttributeIds); |
|
420 |
+ if ($selectedAttributes !== null) { |
|
421 |
+ // Group the attributes by their parent group |
|
422 |
+ $attributesByGroup = []; |
|
423 |
+ $groupIds = []; |
|
424 |
+ |
|
425 |
+ // First pass: collect all group IDs and organize attributes by group |
|
426 |
+ foreach ($selectedAttributes as $attribute) { |
|
427 |
+ $groupId = $attribute->pid; |
|
428 |
+ $groupIds[] = $groupId; |
|
429 |
+ |
|
430 |
+ if (!isset($attributesByGroup[$groupId])) { |
|
431 |
+ $attributesByGroup[$groupId] = []; |
|
432 |
+ } |
|
433 |
+ |
|
434 |
+ $attributesByGroup[$groupId][] = [ |
|
435 |
+ 'id' => $attribute->id, |
|
436 |
+ 'title' => $attribute->title, |
|
437 |
+ 'description' => $attribute->description, |
|
438 |
+ 'type' => $attribute->type, |
|
439 |
+ 'value' => $attributeValues[$attribute->id] ?? null |
|
440 |
+ ]; |
|
441 |
+ } |
|
442 |
+ |
|
443 |
+ // Get unique group IDs |
|
444 |
+ $groupIds = array_unique($groupIds); |
|
445 |
+ |
|
446 |
+ // Fetch the groups |
|
447 |
+ if (!empty($groupIds)) { |
|
448 |
+ $groups = WeinanlieferungAttributeGroupModel::findMultipleByIds($groupIds); |
|
449 |
+ |
|
450 |
+ if ($groups !== null) { |
|
451 |
+ // Create the attribute groups array |
|
452 |
+ foreach ($groups as $group) { |
|
453 |
+ if (isset($attributesByGroup[$group->id])) { |
|
454 |
+ $arrAttributeGroups[] = [ |
|
455 |
+ 'id' => $group->id, |
|
456 |
+ 'title' => $group->title, |
|
457 |
+ 'description' => $group->description, |
|
458 |
+ 'multiple' => (bool)$group->multiple, |
|
459 |
+ 'required' => (bool)$group->required, |
|
460 |
+ 'attributes' => $attributesByGroup[$group->id] |
|
461 |
+ ]; |
|
462 |
+ } |
|
463 |
+ } |
|
464 |
+ } |
|
465 |
+ } |
|
466 |
+ } |
|
326 | 467 |
} |
327 | 468 |
|
328 | 469 |
$arrData = array_merge($arrData,[ |
... | ... |
@@ -339,7 +480,8 @@ class SlotAjaxController extends AbstractController |
339 | 480 |
'default' => $intDefaultAmount, |
340 | 481 |
'behaelter' => $intUnitAmount ? range(1,$intUnitAmount) : [], |
341 | 482 |
'units' => $arrUnits, |
342 |
- ] |
|
483 |
+ ], |
|
484 |
+ 'attribute_groups' => $arrAttributeGroups |
|
343 | 485 |
]); |
344 | 486 |
|
345 | 487 |
if (!empty($error)) |
... | ... |
@@ -425,6 +567,83 @@ class SlotAjaxController extends AbstractController |
425 | 567 |
$intBehaelter = intval($intBehaelter) * intval($SelectedUnit->containers); |
426 | 568 |
} |
427 | 569 |
|
570 |
+ // Process attribute values |
|
571 |
+ $attributeValues = []; |
|
572 |
+ |
|
573 |
+ // Get selected attributes |
|
574 |
+ $selectedAttributeIds = []; |
|
575 |
+ if ($Slot->attributes) { |
|
576 |
+ $selectedAttributeIds = StringUtil::deserialize($Slot->attributes, true); |
|
577 |
+ } |
|
578 |
+ |
|
579 |
+ // If no attributes are selected, skip attribute processing |
|
580 |
+ if (!empty($selectedAttributeIds)) { |
|
581 |
+ // Fetch the selected attributes |
|
582 |
+ $selectedAttributes = WeinanlieferungAttributeModel::findMultipleByIds($selectedAttributeIds); |
|
583 |
+ if ($selectedAttributes !== null) { |
|
584 |
+ // Group the attributes by their parent group |
|
585 |
+ $attributesByGroup = []; |
|
586 |
+ $groupIds = []; |
|
587 |
+ |
|
588 |
+ // First pass: collect all group IDs |
|
589 |
+ foreach ($selectedAttributes as $attribute) { |
|
590 |
+ $groupId = $attribute->pid; |
|
591 |
+ $groupIds[] = $groupId; |
|
592 |
+ } |
|
593 |
+ |
|
594 |
+ // Get unique group IDs |
|
595 |
+ $groupIds = array_unique($groupIds); |
|
596 |
+ |
|
597 |
+ // Fetch the groups |
|
598 |
+ if (!empty($groupIds)) { |
|
599 |
+ $groups = WeinanlieferungAttributeGroupModel::findMultipleByIds($groupIds); |
|
600 |
+ |
|
601 |
+ if ($groups !== null) { |
|
602 |
+ // Create maps for attribute-to-group and group-required status |
|
603 |
+ $attributeToGroupMap = []; |
|
604 |
+ $groupRequiredMap = []; |
|
605 |
+ |
|
606 |
+ foreach ($groups as $group) { |
|
607 |
+ $groupRequiredMap[$group->id] = (bool)$group->required; |
|
608 |
+ } |
|
609 |
+ |
|
610 |
+ foreach ($selectedAttributes as $attribute) { |
|
611 |
+ $attributeToGroupMap[$attribute->id] = $attribute->pid; |
|
612 |
+ } |
|
613 |
+ |
|
614 |
+ // Track which groups have values |
|
615 |
+ $groupHasValue = []; |
|
616 |
+ |
|
617 |
+ // Process the selected attributes |
|
618 |
+ foreach ($selectedAttributes as $attribute) { |
|
619 |
+ $attributeKey = 'attribute_' . $attribute->id; |
|
620 |
+ $groupId = $attributeToGroupMap[$attribute->id]; |
|
621 |
+ |
|
622 |
+ if ($attribute->type === 'text') { |
|
623 |
+ // For text attributes, store the text value |
|
624 |
+ if (!empty(Input::post($attributeKey))) { |
|
625 |
+ $attributeValues[$attribute->id] = Input::post($attributeKey); |
|
626 |
+ $groupHasValue[$groupId] = true; |
|
627 |
+ } |
|
628 |
+ } else if ($attribute->type === 'option') { |
|
629 |
+ // For option attributes, store true if selected |
|
630 |
+ if (Input::post($attributeKey)) { |
|
631 |
+ $attributeValues[$attribute->id] = true; |
|
632 |
+ $groupHasValue[$groupId] = true; |
|
633 |
+ } |
|
634 |
+ } |
|
635 |
+ } |
|
636 |
+ |
|
637 |
+ // Check if required groups have values |
|
638 |
+ foreach ($groupRequiredMap as $groupId => $isRequired) { |
|
639 |
+ if ($isRequired && empty($groupHasValue[$groupId])) { |
|
640 |
+ $arrError[] = 'attribute_group_' . $groupId; |
|
641 |
+ } |
|
642 |
+ } |
|
643 |
+ } |
|
644 |
+ } |
|
645 |
+ } |
|
646 |
+ } |
|
428 | 647 |
|
429 | 648 |
if (count($arrError)) |
430 | 649 |
{ |
... | ... |
@@ -440,6 +659,7 @@ class SlotAjaxController extends AbstractController |
440 | 659 |
'behaelter' => $intBehaelter, |
441 | 660 |
'amount' => Input::post('behaelter'), |
442 | 661 |
'annotation' => Input::post('annotation'), |
662 |
+ 'attribute_values' => !empty($attributeValues) ? json_encode($attributeValues) : null, |
|
443 | 663 |
]); |
444 | 664 |
$Reservation->setRow($arrData); |
445 | 665 |
$Reservation->save(); |
... | ... |
@@ -526,6 +746,84 @@ class SlotAjaxController extends AbstractController |
526 | 746 |
$intBehaelter = intval($intBehaelter) * intval($SelectedUnit->containers); |
527 | 747 |
} |
528 | 748 |
|
749 |
+ // Process attribute values |
|
750 |
+ $attributeValues = []; |
|
751 |
+ |
|
752 |
+ // Get selected attributes |
|
753 |
+ $selectedAttributeIds = []; |
|
754 |
+ if ($Slot->attributes) { |
|
755 |
+ $selectedAttributeIds = StringUtil::deserialize($Slot->attributes, true); |
|
756 |
+ } |
|
757 |
+ |
|
758 |
+ // If no attributes are selected, skip attribute processing |
|
759 |
+ if (!empty($selectedAttributeIds)) { |
|
760 |
+ // Fetch the selected attributes |
|
761 |
+ $selectedAttributes = WeinanlieferungAttributeModel::findMultipleByIds($selectedAttributeIds); |
|
762 |
+ if ($selectedAttributes !== null) { |
|
763 |
+ // Group the attributes by their parent group |
|
764 |
+ $attributesByGroup = []; |
|
765 |
+ $groupIds = []; |
|
766 |
+ |
|
767 |
+ // First pass: collect all group IDs |
|
768 |
+ foreach ($selectedAttributes as $attribute) { |
|
769 |
+ $groupId = $attribute->pid; |
|
770 |
+ $groupIds[] = $groupId; |
|
771 |
+ } |
|
772 |
+ |
|
773 |
+ // Get unique group IDs |
|
774 |
+ $groupIds = array_unique($groupIds); |
|
775 |
+ |
|
776 |
+ // Fetch the groups |
|
777 |
+ if (!empty($groupIds)) { |
|
778 |
+ $groups = WeinanlieferungAttributeGroupModel::findMultipleByIds($groupIds); |
|
779 |
+ |
|
780 |
+ if ($groups !== null) { |
|
781 |
+ // Create maps for attribute-to-group and group-required status |
|
782 |
+ $attributeToGroupMap = []; |
|
783 |
+ $groupRequiredMap = []; |
|
784 |
+ |
|
785 |
+ foreach ($groups as $group) { |
|
786 |
+ $groupRequiredMap[$group->id] = (bool)$group->required; |
|
787 |
+ } |
|
788 |
+ |
|
789 |
+ foreach ($selectedAttributes as $attribute) { |
|
790 |
+ $attributeToGroupMap[$attribute->id] = $attribute->pid; |
|
791 |
+ } |
|
792 |
+ |
|
793 |
+ // Track which groups have values |
|
794 |
+ $groupHasValue = []; |
|
795 |
+ |
|
796 |
+ // Process the selected attributes |
|
797 |
+ foreach ($selectedAttributes as $attribute) { |
|
798 |
+ $attributeKey = 'attribute_' . $attribute->id; |
|
799 |
+ $groupId = $attributeToGroupMap[$attribute->id]; |
|
800 |
+ |
|
801 |
+ if ($attribute->type === 'text') { |
|
802 |
+ // For text attributes, store the text value |
|
803 |
+ if (!empty(Input::post($attributeKey))) { |
|
804 |
+ $attributeValues[$attribute->id] = Input::post($attributeKey); |
|
805 |
+ $groupHasValue[$groupId] = true; |
|
806 |
+ } |
|
807 |
+ } else if ($attribute->type === 'option') { |
|
808 |
+ // For option attributes, store true if selected |
|
809 |
+ if (Input::post($attributeKey)) { |
|
810 |
+ $attributeValues[$attribute->id] = true; |
|
811 |
+ $groupHasValue[$groupId] = true; |
|
812 |
+ } |
|
813 |
+ } |
|
814 |
+ } |
|
815 |
+ |
|
816 |
+ // Check if required groups have values |
|
817 |
+ foreach ($groupRequiredMap as $groupId => $isRequired) { |
|
818 |
+ if ($isRequired && empty($groupHasValue[$groupId])) { |
|
819 |
+ $arrError[] = 'attribute_group_' . $groupId; |
|
820 |
+ } |
|
821 |
+ } |
|
822 |
+ } |
|
823 |
+ } |
|
824 |
+ } |
|
825 |
+ } |
|
826 |
+ |
|
529 | 827 |
if (count($arrError)) |
530 | 828 |
{ |
531 | 829 |
return $this->renderBooking(false,'<div class="toast toast--danger mx-0">Bitte geben Sie alle Pflichtangaben (mit * markierte Felder) an</div>'); |
... | ... |
@@ -536,6 +834,7 @@ class SlotAjaxController extends AbstractController |
536 | 834 |
$Reservation->behaelter = $intBehaelter; |
537 | 835 |
$Reservation->amount = Input::post('behaelter'); |
538 | 836 |
$Reservation->annotation = Input::post('annotation'); |
837 |
+ $Reservation->attribute_values = !empty($attributeValues) ? json_encode($attributeValues) : null; |
|
539 | 838 |
|
540 | 839 |
$Reservation->save(); |
541 | 840 |
|
... | ... |
@@ -19,6 +19,7 @@ use Contao\Input; |
19 | 19 |
use Contao\StringUtil; |
20 | 20 |
use Doctrine\DBAL\Connection; |
21 | 21 |
use Symfony\Contracts\Translation\TranslatorInterface; |
22 |
+use vonRotenberg\WeinanlieferungBundle\Model\WeinanlieferungAttributeModel; |
|
22 | 23 |
use vonRotenberg\WeinanlieferungBundle\Model\WeinanlieferungLeseartModel; |
23 | 24 |
use vonRotenberg\WeinanlieferungBundle\Model\WeinanlieferungRebsorteModel; |
24 | 25 |
use vonRotenberg\WeinanlieferungBundle\Model\WeinanlieferungSlotsModel; |
... | ... |
@@ -200,4 +201,53 @@ class WeinanlieferungReservationContainerListener |
200 | 201 |
|
201 | 202 |
return $varValue; |
202 | 203 |
} |
204 |
+ |
|
205 |
+ /** |
|
206 |
+ * Format attribute values for display in the backend |
|
207 |
+ * |
|
208 |
+ * @Callback(table="tl_vr_wa_reservation", target="fields.attribute_values.load") |
|
209 |
+ */ |
|
210 |
+ public function formatAttributeValues($varValue, DataContainer $dc) |
|
211 |
+ { |
|
212 |
+ if (empty($varValue)) { |
|
213 |
+ return ''; |
|
214 |
+ } |
|
215 |
+ |
|
216 |
+ try { |
|
217 |
+ $attributeValues = json_decode($varValue, true); |
|
218 |
+ |
|
219 |
+ if (!is_array($attributeValues) || empty($attributeValues)) { |
|
220 |
+ return ''; |
|
221 |
+ } |
|
222 |
+ |
|
223 |
+ $formattedValues = []; |
|
224 |
+ |
|
225 |
+ foreach ($attributeValues as $attributeId => $value) { |
|
226 |
+ // Get attribute information |
|
227 |
+ $attribute = WeinanlieferungAttributeModel::findByPk($attributeId); |
|
228 |
+ |
|
229 |
+ if ($attribute === null) { |
|
230 |
+ $formattedValues[] = $attributeId . ': ' . (is_array($value) ? implode(', ', $value) : $value); |
|
231 |
+ continue; |
|
232 |
+ } |
|
233 |
+ |
|
234 |
+ // Format based on attribute type |
|
235 |
+ $attributeTitle = $attribute->title; |
|
236 |
+ |
|
237 |
+ if ($attribute->type === 'option') { |
|
238 |
+ // For option type, the value is either true (selected) or false (not selected) |
|
239 |
+ if ($value === true || $value === 'true' || $value === '1' || $value === 1) { |
|
240 |
+ $formattedValues[] = $attributeTitle . ': Ausgewählt'; |
|
241 |
+ } |
|
242 |
+ } else { |
|
243 |
+ // Text type or fallback |
|
244 |
+ $formattedValues[] = $attributeTitle . ': ' . $value; |
|
245 |
+ } |
|
246 |
+ } |
|
247 |
+ |
|
248 |
+ return implode("\n", $formattedValues); |
|
249 |
+ } catch (\Exception $e) { |
|
250 |
+ return 'Error parsing attribute values: ' . $e->getMessage(); |
|
251 |
+ } |
|
252 |
+ } |
|
203 | 253 |
} |
... | ... |
@@ -24,6 +24,8 @@ use vonRotenberg\WeinanlieferungBundle\Model\WeinanlieferungLeseartModel; |
24 | 24 |
use vonRotenberg\WeinanlieferungBundle\Model\WeinanlieferungRebsorteModel; |
25 | 25 |
use vonRotenberg\WeinanlieferungBundle\Model\WeinanlieferungSlotsModel; |
26 | 26 |
use vonRotenberg\WeinanlieferungBundle\Model\WeinanlieferungSlottypesModel; |
27 |
+use vonRotenberg\WeinanlieferungBundle\Model\WeinanlieferungAttributeGroupModel; |
|
28 |
+use vonRotenberg\WeinanlieferungBundle\Model\WeinanlieferungAttributeModel; |
|
27 | 29 |
use vonRotenberg\WeinanlieferungBundle\SlotChecker; |
28 | 30 |
|
29 | 31 |
class WeinanlieferungSlotContainerListener |
... | ... |
@@ -130,4 +132,72 @@ class WeinanlieferungSlotContainerListener |
130 | 132 |
|
131 | 133 |
return $varValue; |
132 | 134 |
} |
135 |
+ |
|
136 |
+ /** |
|
137 |
+ * Load callback for the attributes field |
|
138 |
+ * |
|
139 |
+ * @param mixed $varValue The current value |
|
140 |
+ * @param DataContainer $dc The data container |
|
141 |
+ * @return mixed The processed value |
|
142 |
+ */ |
|
143 |
+ public function loadAttributes($varValue, DataContainer $dc) |
|
144 |
+ { |
|
145 |
+ // Just return the value as is, it's already in the correct format (comma-separated list of attribute IDs) |
|
146 |
+ return $varValue; |
|
147 |
+ } |
|
148 |
+ |
|
149 |
+ /** |
|
150 |
+ * Save callback for the attributes field |
|
151 |
+ * |
|
152 |
+ * @param mixed $varValue The value to save |
|
153 |
+ * @param DataContainer $dc The data container |
|
154 |
+ * @return mixed The processed value |
|
155 |
+ */ |
|
156 |
+ public function saveAttributes($varValue, DataContainer $dc) |
|
157 |
+ { |
|
158 |
+ // Just return the value as is, it's already in the correct format (comma-separated list of attribute IDs) |
|
159 |
+ return $varValue; |
|
160 |
+ } |
|
161 |
+ |
|
162 |
+ /** |
|
163 |
+ * Options callback for the attributes field |
|
164 |
+ * Returns a multi-dimensional array of attribute options grouped by their parent groups |
|
165 |
+ * |
|
166 |
+ * @param DataContainer $dc The data container |
|
167 |
+ * @return array The options array |
|
168 |
+ */ |
|
169 |
+ public function getAttributeOptions(DataContainer $dc) |
|
170 |
+ { |
|
171 |
+ System::loadLanguageFile('tl_vr_wa_attribute'); |
|
172 |
+ |
|
173 |
+ // Fetch all attribute groups |
|
174 |
+ $groups = WeinanlieferungAttributeGroupModel::findAll(['order' => 'title ASC']); |
|
175 |
+ if ($groups === null) { |
|
176 |
+ return []; |
|
177 |
+ } |
|
178 |
+ |
|
179 |
+ // Build the multi-dimensional array of options |
|
180 |
+ $options = []; |
|
181 |
+ |
|
182 |
+ foreach ($groups as $group) { |
|
183 |
+ // Get the attributes for this group |
|
184 |
+ $attributes = WeinanlieferungAttributeModel::findBy(['pid=?'], [$group->id], ['order' => 'title ASC']); |
|
185 |
+ if ($attributes === null) { |
|
186 |
+ continue; |
|
187 |
+ } |
|
188 |
+ |
|
189 |
+ // Add attributes to the options array |
|
190 |
+ $groupAttributes = []; |
|
191 |
+ foreach ($attributes as $attribute) { |
|
192 |
+ $groupAttributes[$attribute->id] = $attribute->title . ' <span style="color:#999;padding-left:3px">[' . $GLOBALS['TL_LANG']['tl_vr_wa_attribute']['type_options'][$attribute->type] . ']</span>'; |
|
193 |
+ } |
|
194 |
+ |
|
195 |
+ // Add the group to the options array if it has attributes |
|
196 |
+ if (!empty($groupAttributes)) { |
|
197 |
+ $options[$group->title] = $groupAttributes; |
|
198 |
+ } |
|
199 |
+ } |
|
200 |
+ |
|
201 |
+ return $options; |
|
202 |
+ } |
|
133 | 203 |
} |
134 | 204 |
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 WeinanlieferungAttributeGroupModel extends Model |
|
18 |
+{ |
|
19 |
+ /** |
|
20 |
+ * Table name |
|
21 |
+ * @var string |
|
22 |
+ */ |
|
23 |
+ protected static $strTable = 'tl_vr_wa_attribute_group'; |
|
24 |
+} |
0 | 25 |
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 WeinanlieferungAttributeModel extends Model |
|
18 |
+{ |
|
19 |
+ /** |
|
20 |
+ * Table name |
|
21 |
+ * @var string |
|
22 |
+ */ |
|
23 |
+ protected static $strTable = 'tl_vr_wa_attribute'; |
|
24 |
+} |