Add multi-day entries and area stay blocks
Entries gain an optional inclusive end_date (backfilled via migration): flights and travel longer than 24h show a continuation marker on following days, and a new stay entry type marks a time frame in one area (e.g. 3 days Venice) rendered as continuous bands across the calendar weeks. Stays feed the map route as stops; the route summary gains stays count and a chronological areas list with day spans. 49 API tests.
This commit is contained in:
+18
-4
@@ -34,7 +34,9 @@ trip_members (trip_id INTEGER REFERENCES trips(id), user_id INTEGER REFERENCES u
|
||||
role TEXT NOT NULL DEFAULT 'editor', -- 'owner' | 'editor'
|
||||
PRIMARY KEY (trip_id, user_id))
|
||||
entries (id INTEGER PK, trip_id INTEGER NOT NULL REFERENCES trips(id),
|
||||
date TEXT NOT NULL, type TEXT NOT NULL,
|
||||
date TEXT NOT NULL, -- start date
|
||||
end_date TEXT, -- optional inclusive end date (multi-day entries; null = single day)
|
||||
type TEXT NOT NULL,
|
||||
title TEXT NOT NULL, details TEXT DEFAULT '',
|
||||
start_time TEXT, end_time TEXT,
|
||||
location_name TEXT, lat REAL, lng REAL,
|
||||
@@ -49,7 +51,18 @@ entry_participants (entry_id INTEGER REFERENCES entries(id), user_id INTEGER REF
|
||||
-- no rows for an entry = "all trip members participate" (dynamic default)
|
||||
```
|
||||
|
||||
Entry `type` ∈ `flight | immigration | travel | hotel | activity | rental | note`.
|
||||
Entry `type` ∈ `flight | immigration | travel | hotel | activity | rental | stay | note`.
|
||||
|
||||
### Multi-day entries & stays
|
||||
|
||||
Any entry may carry an optional `end_date` (inclusive, `YYYY-MM-DD`, must be ≥ `date`; PATCH `end_date: null` clears it). Use cases:
|
||||
|
||||
- **Flights/travel longer than 24 h** — dep on `date` at `start_time`, arr on `end_date` at `end_time`.
|
||||
- **`stay` entries ("area blocks")** — "3 days Venice", "7 days Berlin": a location plus a date range. The frontend renders stays as continuous bands across the calendar days (all-day-event style), not as chips; other multi-day entries show a chip on the start day and lightweight "…continues" markers on the days up to `end_date`.
|
||||
|
||||
Stays behave like normal entries otherwise: optional geocoded location (feeds the route as one stop, positioned at the start date), optional price/split (e.g. an apartment for the whole block).
|
||||
|
||||
Summary additions: `stays` (count) and `areas` — stay entries in chronological order as `[{ "name": "Venice", "days": 3 }]` where `name` is the stay's `location_name` (fallback: title) and `days` is the inclusive span (`end_date` null → 1). Example: `"areas": [{"name":"Venice","days":3},{"name":"Berlin","days":7}]`.
|
||||
|
||||
### Rental car details
|
||||
|
||||
@@ -142,11 +155,11 @@ User JSON shape everywhere: `{id, display_name}`.
|
||||
|
||||
| Method & path | Body | Response |
|
||||
|---|---|---|
|
||||
| `POST /api/trips/:id/entries` | `{date, type, title, details?, start_time?, end_time?, location_name?, lat?, lng?, sort_order?, price?, paid_by?, split_mode?, participants?, segments?}` | `201 {entry}`; validates type enum, date format, title non-empty ≤200 chars; lat/lng must both be present or both absent, lat ∈ [-90,90], lng ∈ [-180,180]; price null or number ≥ 0; paid_by null or a trip member's user id; split_mode ∈ `equal\|own\|payer` (`payer` requires paid_by); participants null/[] (= all members) or array of trip-member user ids; segments per "Flight segments" above |
|
||||
| `POST /api/trips/:id/entries` | `{date, end_date?, type, title, details?, start_time?, end_time?, location_name?, lat?, lng?, sort_order?, price?, paid_by?, split_mode?, participants?, segments?}` | `201 {entry}`; validates type enum, date format, end_date null or valid date ≥ date, title non-empty ≤200 chars; lat/lng must both be present or both absent, lat ∈ [-90,90], lng ∈ [-180,180]; price null or number ≥ 0; paid_by null or a trip member's user id; split_mode ∈ `equal\|own\|payer` (`payer` requires paid_by); participants null/[] (= all members) or array of trip-member user ids; segments per "Flight segments" above |
|
||||
| `PATCH /api/entries/:id` | any subset of the above | `200 {entry}` (member of the entry's trip required; `participants` replaces the whole set) |
|
||||
| `DELETE /api/entries/:id` | — | `204` (also deletes its entry_participants rows) |
|
||||
|
||||
Entry JSON shape (always full row): `{id, trip_id, date, type, title, details, start_time, end_time, location_name, lat, lng, sort_order, price, paid_by, split_mode, participants, segments}` where `participants` is an array of user ids (`[]` = all members) and `segments` is the parsed array or `null`.
|
||||
Entry JSON shape (always full row): `{id, trip_id, date, end_date, type, title, details, start_time, end_time, location_name, lat, lng, sort_order, price, paid_by, split_mode, participants, segments, rental}` where `participants` is an array of user ids (`[]` = all members) and `segments` is the parsed array or `null`.
|
||||
|
||||
### Route & summary (computed)
|
||||
|
||||
@@ -162,6 +175,7 @@ Entry JSON shape (always full row): `{id, trip_id, date, type, title, details, s
|
||||
"days": 10, "nights": 9,
|
||||
"flights": 2, "flightSegments": 4, "hotels": 3, "travelLegs": 1, "activities": 4,
|
||||
"rentals": 1, "includedKm": 1500,
|
||||
"stays": 2, "areas": [{"name": "Venice", "days": 3}, {"name": "Berlin", "days": 7}],
|
||||
"kmAir": 0, "kmDriven": 587.3,
|
||||
"locations": ["Bangkok", "Chiang Mai"]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user