| ... | ... |
@@ -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 |
} |