This commit is contained in:
2026-05-08 00:12:37 +02:00
parent 811069a25c
commit 7278a422af
18 changed files with 1356 additions and 43 deletions

View File

@@ -142,6 +142,17 @@ class Rest_Controller extends \WP_REST_Controller {
)
);
// GET /yacht-booking/v1/availability/bounds
register_rest_route(
self::NAMESPACE,
'/availability/bounds',
array(
'methods' => \WP_REST_Server::READABLE,
'callback' => array( $this, 'get_availability_bounds' ),
'permission_callback' => '__return_true',
)
);
// POST /yacht-booking/v1/bookings
register_rest_route(
self::NAMESPACE,
@@ -407,6 +418,11 @@ class Rest_Controller extends \WP_REST_Controller {
)
);
$ical_sources = array(
\YachtBooking\Integrations\ICal\ICal_Import::GLOBAL_CALENDAR_SOURCE,
\YachtBooking\Integrations\ICal\ICal_Import::GLOBAL_IMPORT_SOURCE,
);
$events = array();
foreach ( $bookings as $booking ) {
$booking_id = $booking->ID;
@@ -421,33 +437,113 @@ class Rest_Controller extends \WP_REST_Controller {
$source = (string) get_post_meta( $booking_id, '_booking_source', true );
$is_global_event = ( 0 === $yacht_id || \YachtBooking\Integrations\ICal\ICal_Import::GLOBAL_CALENDAR_SOURCE === $source );
// W trybie global wszystko traktujemy jak wspólne wydarzenia: szary kolor,
// brak yacht_id, generyczny tytuł "Rezerwacja" — bez wycieku danych klientów.
// Color: zachowane z poprzedniej logiki (per-yacht paleta lub kolor global).
if ( $is_global_mode || $is_global_event ) {
$color = self::GLOBAL_EVENT_COLOR;
$title = __( 'Rezerwacja', 'yacht-booking' );
$y_id = 0;
} else {
$color = isset( $color_map[ $yacht_id ] ) ? $color_map[ $yacht_id ] : self::GLOBAL_EVENT_COLOR;
$yacht = get_post( $yacht_id );
// Tryb per_yacht: pokazujemy tylko nazwę jachtu (bez nazwiska klienta — privacy).
$title = $yacht ? $yacht->post_title : __( 'Rezerwacja', 'yacht-booking' );
$y_id = $yacht_id;
}
$events[] = array(
'id' => $booking_id,
'title' => $title,
'start' => $start_date . 'T12:00:00',
'end' => $end_date . 'T12:00:00',
'color' => $color,
'yacht_id' => $y_id,
);
// Title: raw SUMMARY z _booking_notes (iCal) lub customer_name (frontend).
// Klient świadomie cofa privacy z 09-04 — tytuły rezerwacji widoczne publicznie.
if ( in_array( $source, $ical_sources, true ) ) {
$notes = (string) get_post_meta( $booking_id, '_booking_notes', true );
$title = '' !== trim( $notes ) ? $notes : __( 'Rezerwacja', 'yacht-booking' );
} else {
$customer = (string) Booking::get_customer_name( $booking_id );
$title = '' !== trim( $customer ) ? $customer : __( 'Rezerwacja', 'yacht-booking' );
}
$title = sanitize_text_field( $title );
// Split na N eventów per dzień (allDay = każdy event mieści się w jednej komórce).
// Iteracja od start_date do end_date INCLUSIVE — pierwszy i ostatni dzień
// mają half-day visual (yacht odbierany / zwracany w południe).
try {
$cursor = new \DateTimeImmutable( $start_date );
$end_dt = new \DateTimeImmutable( $end_date );
} catch ( \Exception $e ) {
continue;
}
while ( $cursor <= $end_dt ) {
$day = $cursor->format( 'Y-m-d' );
$is_first = ( $day === $start_date );
$is_last = ( $day === $end_date );
$events[] = array(
'id' => $booking_id . '-' . $day,
'title' => $title,
'start' => $day,
'allDay' => true,
'color' => $color,
'yacht_id' => $y_id,
'extendedProps' => array(
'is_first' => $is_first,
'is_last_night' => $is_last,
'booking_id' => (int) $booking_id,
),
);
$cursor = $cursor->modify( '+1 day' );
}
}
return rest_ensure_response( $events );
}
/**
* Zwraca granice nawigacji kalendarza zbiorczego: datę ostatniej rezerwacji
* (confirmed/pending) z `_booking_end_date >= dziś`.
*
* Frontend używa tej wartości do ustawienia `validRange.end` w FullCalendar,
* blokując nawigację w przód poza miesiąc ostatniej rezerwacji.
*
* @return \WP_REST_Response { max_booking_date: 'YYYY-MM-DD' | null }
*/
public function get_availability_bounds() {
$today = gmdate( 'Y-m-d' );
$bookings = get_posts(
array(
'post_type' => 'yacht_booking',
'post_status' => 'publish',
'posts_per_page' => 1,
'fields' => 'ids',
'meta_key' => '_booking_end_date',
'orderby' => 'meta_value',
'meta_type' => 'DATE',
'order' => 'DESC',
'meta_query' => array(
'relation' => 'AND',
array(
'key' => '_booking_status',
'value' => array( 'confirmed', 'pending' ),
'compare' => 'IN',
),
array(
'key' => '_booking_end_date',
'value' => $today,
'compare' => '>=',
'type' => 'DATE',
),
),
)
);
$max_date = null;
if ( ! empty( $bookings ) ) {
$end = (string) get_post_meta( (int) $bookings[0], '_booking_end_date', true );
if ( preg_match( '/^\d{4}-\d{2}-\d{2}$/', $end ) ) {
$max_date = $end;
}
}
return rest_ensure_response( array( 'max_booking_date' => $max_date ) );
}
/**
* Buduje deterministyczną mapę yacht_id → kolor z palety.
*