Problem/Motivation

`AbstractCalendar::getEventsItemized()` contains a loop that is supposed to
produce mock (default-state) itemized events for any unit that has no rows in
the BAT calendar state tables. A single line in that loop was commented out,
which means units with no DB rows are silently dropped from the returned
`$events` array. Downstream callers such as `bat_event_get_matching_units()`
therefore never see those units, and availability checks always return an empty
set for them — regardless of the unit's configured default state.

Steps to reproduce

1. Install BAT + BEE on a fresh Drupal site.
2. Create a "Generally available" daily bookable content type via BEE and create
a node of that type (this creates a BAT unit with a default state of
`bee_daily_available`, but writes no rows to the calendar state table).
3. Navigate to the node's **Add Reservation** form and select any date range.
4. Submit the form.

**Expected:** The reservation succeeds — the unit is available by default.

**Actual:** The form reports no available units and refuses the booking.

The same applies to `UpdateAvailabilityForm`: writing the very first
availability event for a unit fails because `bat_event_get_matching_units()`
returns `[]` (see also drupal.org/project/bee issue #3576346).

## Root Cause

In `AbstractCalendar::getEventsItemized()`, after the main `foreach ($db_events
as $unit => $event)` loop fills in data for units that *do* have DB rows, a
second loop iterates over `$keyed_units` to handle units with *no* DB rows:

foreach ($keyed_units as $id => $unit) {
  if ((isset($events[$id]) && count($events[$id]) == 0) || !isset($events[$id])) {
    $empty_event = new Event($start_date, $end_date, $unit, $unit->getDefaultValue());

    // Mar25: do we need empty events from mock??
    //$events[$id] = $empty_event->itemize(new EventItemizer($empty_event, $granularity));
  }
}

The critical assignment line is commented out. As a result:

- The `$empty_event` object is created but never stored.
- `$events[$id]` is never populated for units without DB rows.
- Those units are absent from the return value of `getEventsItemized()`.
- `bat_event_get_matching_units()` (which calls `getEventsItemized()`) returns
an empty array for any type whose units have no calendar history.

The comment `// Mar25: do we need empty events from mock??` suggests the line
was commented out experimentally in March 2025. The answer to that question is
**yes** — the mock event mechanism is what makes default-state units visible to
all calendar queries.

Note: `itemizeDays()` (called inside the first loop) already handles the
"month exists in DB but a specific day has value 0" case by substituting the
unit's default value. The second loop handles the distinct case where the unit
has **no DB rows at all** for the queried period.

Proposed resolution

Restore the commented-out line:

foreach ($keyed_units as $id => $unit) {
  if ((isset($events[$id]) && count($events[$id]) == 0) || !isset($events[$id])) {
    $empty_event = new Event($start_date, $end_date, $unit, $unit->getDefaultValue());
    $events[$id] = $empty_event->itemize(new EventItemizer($empty_event, $granularity));
  }
}

**Why this is safe:**
- The condition `(isset($events[$id]) && count($events[$id]) == 0) || !isset($events[$id])`
ensures the mock is only applied to units that are missing or empty in
`$events` — i.e., units for which the DB returned no rows.
- Units *with* DB rows are processed by the first loop via `itemizeDays()` /
`itemizeHours()` / `itemizeMinutes()` as before; this loop does not touch
them.
- `$unit->getDefaultValue()` returns the state ID that was configured for the
unit (e.g., the `bee_daily_available` state ID) — exactly what should be
assumed for unset days.
- This restores the originally intended behavior that was in place before the
line was commented out.

## Impact
Any site using BAT with units that have no calendar events written for a given
period (including all fresh installs) will experience:

- `bat_event_get_matching_units()` returning `[]` for those units.
- BEE's `AddReservationForm` always refusing bookings as "no units available."
- BEE's `UpdateAvailabilityForm` failing to write the first availability event
for a unit (see also BEE issue #3576346, which has a partial workaround in
BEE itself, but this is the underlying root cause).
- Any custom code relying on `getEventsItemized()` or `getMatchingUnits()`
receiving incorrect results for default-state units.

Remaining tasks

I have tested this in my own system under ddev, further verification is required prior to merging

User interface changes

none

API changes

none

Data model changes

none

Sponsorship

Comments

owens-d created an issue. See original summary.

owens-d’s picture

Version: 11.1.x-dev » 11.1.0-rc8
afagioli’s picture

Status: Active » Needs work

  • afagioli committed 3cbed1d0 on 11.1.x
    Issue #3576440 : Restore mock events for units with no calendar rows....

  • afagioli committed 3cbed1d0 on 11.0.x
    Issue #3576440 : Restore mock events for units with no calendar rows....
afagioli’s picture

Issue summary: View changes
afagioli’s picture

Suggested fix is available into current dev and 11.1.0-rc11

afagioli’s picture

Status: Needs work » Needs review