# Analysis: Why R1 was NOT scheduled for colab_id 207

**Analysis only. No code changes.**

---

## Context

- **colab_id:** 207
- **PENDING_COMPANY at:** 2026-03-17 11:04:19
- **Reservation date (expected):** 2026-03-17 20:29
- **Expected:** eventDeadline = 18:29, lead time ≈ 7h25 (> 4h) → R1 should be scheduled
- **Observed:** `sendNewColabCompanyNotification` ran; **no** reminder scheduling; **no** `sendNotificationAt`

---

## Step 1 — `getDate()` and what can make `$ts` null

**Code path:** `HandlesHistoryTransitions::handlePendingCompanyState` → `$ts = $colab->getDate();` then `if (!$ts) return;`.

**Colab::getDate()** (`app/Models/Colab.php` lines 72–78):

- Returns `$this->collabable->getDateTime()` for: `COLLABABLE_DELIVERY`, `COLLABABLE_LODGING`, `COLLABABLE_ACTIVITY`, `COLLABABLE_RESTAURANT`.
- Returns `null` for any other type (e.g. **Brand** → `default => null`).

**Collabable getDateTime()** (`app/Traits/HasColabDateTime.php`):

- `getDateTime()` calls `getDay()` and `getTime()`.
- **If `getDay()` is null** → returns `null`.
- `getDay()` returns `null` if neither `from_day` nor `day` is set (or parse fails).
- Any exception in `getDateTime()` is caught and it returns `null` (and logs).

So **`$ts` can be null** if:

1. **`$colab->collabable_type` is not one of the four datable classes** (e.g. Brand) → `Colab::getDate()` returns `null` without calling `getDateTime()`.
2. **Datable type but collabable has no date:** `getDay()` returns null (no `day` / `from_day` or unparseable) → `getDateTime()` returns null.
3. **Datable type but exception** in `getDay()` / `getTime()` / `Carbon::parse()` → `getDateTime()` returns null.

To know what happened for colab 207 you need to **log for that colab**:

- `$colab->collabable_type`
- `$ts` (value and whether it’s null or a Carbon instance)
- If you have access to the collabable: `day`, `from_day`, `time`, `from_time`.

---

## Step 2 — `collabable_type` and the scheduling block

Reminder scheduling runs **only inside**:

```php
if (in_array($colab->collabable_type, CollabableConstants::DATABLE_COLLABABLE_TYPES)) {
```

**DATABLE_COLLABABLE_TYPES** = `[CollabableActivity::class, CollabableLodging::class, CollabableRestaurant::class, CollabableDelivery::class]` (i.e. FQCN strings). **Brand is not included.**

- If **`collabable_type` is Brand** (or any non-datable value): the whole block is skipped; only `scheduleBrandReminders` can run later. So no R1/R2 for datable logic.
- If **`collabable_type` is one of the four datable classes**: the block is entered; then `$ts = $colab->getDate();` and the rest of the logic run.

So you must **confirm for colab 207**:

- Actual value of `$colab->collabable_type` (string).
- Whether it is **strictly equal** to one of `CollabableConstants::DATABLE_COLLABABLE_TYPES` (e.g. `App\Models\CollabableRestaurant`). Any typo or different format (e.g. short class name) and `in_array` fails and the scheduling block is never executed.

---

## Step 3 — `cancelAt` and `leadRealMinutes`

Inside the datable block:

```php
$t0 = now();                                    // e.g. 2026-03-17 11:04:19
$ts = $colab->getDate();                        // must be non-null
$eventDeadline = $ts->copy()->subHours(2);      // e.g. 2026-03-17 18:29
$maxPendingDeadline = $t0->copy()->addHours(24);
$cancelAt = $eventDeadline->lessThan($maxPendingDeadline) ? $eventDeadline : $maxPendingDeadline;
$leadRealMinutes = $t0->diffInMinutes($cancelAt, false);
```

- With **ts = 2026-03-17 20:29** and **t0 = 2026-03-17 11:04:19**:  
  - eventDeadline = 18:29, maxPendingDeadline = 11:04 next day → cancelAt = 18:29.  
  - `$t0->diffInMinutes($cancelAt, false)` = minutes from t0 to cancelAt → **positive** (~445 min ≈ 7h25). So **leadRealMinutes ≈ 445**, and we do **not** hit `<= 0` or `<= 4*60`.

So with the **expected** reservation date and server time, the condition that prevents scheduling would **not** be the lead-time checks. That implies the failure happens **before** we get a valid `$ts` or **before** we enter the datable block.

To confirm behaviour for 207, log for that colab:

- `$t0` (e.g. `$t0->toDateTimeString()`)
- `$ts` (or `$ts?->toDateTimeString()`)
- `$eventDeadline`, `$cancelAt` (e.g. `->toDateTimeString()`)
- `$leadRealMinutes`

---

## Step 4 — Which early return can prevent scheduling

In order:

| # | Condition | Location | Effect |
|---|------------|----------|--------|
| 1 | `!in_array($colab->collabable_type, CollabableConstants::DATABLE_COLLABABLE_TYPES)` | Line 129 | **Scheduling block never runs.** No R1/R2; only Brand branch can run. |
| 2 | `if (!$ts) return;` | Lines 133–135 | **First return inside the block.** getDate() returned null. |
| 3 | `if ($leadRealMinutes <= 0) return;` | Lines 145–147 | cancelAt in the past or same as t0. |
| 4 | `if ($leadRealMinutes <= 4 * 60) return;` | Lines 149–151 | ≤ 4 hours until cancel. |

For colab 207, with **expected** data (reservation 20:29, t0 = 11:04:19):

- **Not** (3) or (4): lead time would be ~7h25.
- So the blocker is either **(1) collabable_type not in DATABLE** or **(2) $ts null**.

---

## Step 5 — Conclusion and what to log

### Which condition prevented scheduling

One of:

- **Condition 1:** `collabable_type` is **not** in `DATABLE_COLLABABLE_TYPES` → entire reminder block is skipped.  
  **Or**
- **Condition 2:** `$ts = $colab->getDate()` is **null** → `if (!$ts) return;` runs and no reminders are scheduled.

The lead-time checks (3) and (4) do **not** explain the failure if the reservation is really 20:29 and t0 is 11:04:19.

### Actual values you need to confirm (add temporary logging for colab 207)

- **`$colab->collabable_type`** – exact string (e.g. `App\Models\CollabableRestaurant` vs `App\Models\CollabableBrand` or something else).
- **`$ts`** – value and type: `null` or Carbon instance; if Carbon, e.g. `$ts->toDateTimeString()`.
- **If datable and you load collabable:** `$colab->collabable->day`, `$colab->collabable->from_day`, `$colab->collabable->time`, `$colab->collabable->from_time`.
- **If you reach the date math:** `$cancelAt->toDateTimeString()`, `$leadRealMinutes`.

### Root cause: data vs logic

- **Most likely: data / model state**
  - **A)** Colab 207 is **Brand** (or collabable_type not in DATABLE) → scheduling block never runs. “Reservation date 20:29” might come from another source (e.g. UI) but the colab in DB is not datable.
  - **B)** Colab 207 is **datable** but **getDate() returns null**: collabable has no `day`/`from_day`, or they’re null/empty, or parse fails (wrong format/exception in HasColabDateTime). Then `if (!$ts) return;` runs.
- **Less likely: logic bug**
  - e.g. `collabable_type` stored in a format that doesn’t match `DATABLE_COLLABABLE_TYPES` (string mismatch), or a rare timezone/parse issue making `$ts` in the past so that cancelAt is before t0 (leadRealMinutes ≤ 0). Only worth considering after ruling out (A) and (B).

**Recommended next step:** Add temporary logging in `handlePendingCompanyState` for colab_id 207 (or when `$colab->id === 207`): log `collabable_type`, `getDate()` result (and collabable day/from_day if datable), and, if you get past `if (!$ts)`, `cancelAt` and `leadRealMinutes`. That will show exactly which of the conditions above is triggered and whether the cause is data (type or missing date) or a logic/calculation edge case.
