Files
p42.f-w/frontend/src/components/reisen/route.js
T
guyg1474andClaude Opus 5.5 1d7497bead Reisen: Wohnorte mit Datum, Anreise je Besuch, Umzüge in der Kilometer-Rechnung
- Wohnorte als Liste mit "seit"-Datum (Tabelle travel_homes, ersetzt den
  einzelnen Wohnort; Migration Schritt 5 übernimmt ihn): Reisen starten
  beim damals gültigen Wohnort, Wechsel zählen als Umzug, Tage am
  Wohnort nicht als Reisetage
- "In dieser Zeit hier gewohnt" im Besuch: legt Wohnort ab Beginn und den
  vorherigen wieder ab Ende + 1 Tag an
- Anreise je Besuch (automatisch / von zuhause / vom vorherigen Ort):
  Rundreisen auch mit mehreren Tagen zwischen den Orten
- Fix: Besuche bleiben bearbeitbar, auch wenn eine Fotodatei fehlt;
  Speicherfehler erscheinen im Dialog statt verdeckt dahinter

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 20:40:40 +02:00

109 lines
4.3 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Reiseroute für die Kilometer-Statistik, aus Besuchen und Wohnorten (Luftlinie):
// - Reise: Wohnort → Ziel(e) → Wohnort. Welcher Wohnort gilt, entscheidet das "seit"-Datum der Wohnorte.
// - Rundreise: Besuche mit Anreise "vom vorherigen Ort" (oder automatisch: höchstens 1 Tag Abstand)
// gehören zur selben Reise: Wohnort → A → B → Wohnort.
// - Ausflug: Besuch, der während eines längeren Aufenthalts beginnt – Hin- und Rückweg zum Aufenthaltsort.
// - Umzug: Wechsel des Wohnorts (auch ein längerer Auslandsaufenthalt) zählt als eigene Strecke.
// Ohne Wohnort fehlen Hin- und Heimwege; die Reisen werden dann direkt nacheinander verbunden.
import { distanceKm } from './laender.js';
const day = (d) => Math.round(Date.parse(`${d}T12:00:00Z`) / 864e5);
const nextDay = (d) => new Date(Date.parse(`${d}T12:00:00Z`) + 864e5).toISOString().slice(0, 10);
const samePlace = (a, b) => Boolean(a && b && a.lat === b.lat && a.lon === b.lon);
/** Wohnort an einem Tag; vor dem ersten Datum gilt der früheste. homes: sortiert, "von Anfang an" zuerst. */
export function homeAt(homes, date) {
let found = null;
for (const h of homes) if (!h.since || h.since <= date) found = h;
return found || homes[0] || null;
}
// Besuche zu Reisen zusammenfassen (mit Ausflügen je Besuch)
function groupTrips(visits, cities) {
const byId = new Map(cities.map((c) => [c.id, c]));
const items = visits
.map((v) => ({ ...v, city: byId.get(v.cityId), to: v.dateTo || v.dateFrom, excursions: [] }))
.filter((v) => v.city)
.sort((a, b) => a.dateFrom.localeCompare(b.dateFrom) || b.to.localeCompare(a.to));
const trips = [];
for (const v of items) {
const trip = trips.at(-1);
const stay = trip?.visits.findLast((s) => v.dateFrom >= s.dateFrom && v.dateFrom <= s.to);
if (stay) {
stay.excursions.push(v);
continue;
}
const continued = v.continued ?? (trip && day(v.dateFrom) - day(trip.end) <= 1);
if (trip && continued) {
trip.visits.push(v);
if (v.to > trip.end) trip.end = v.to;
} else {
trips.push({ visits: [v], start: v.dateFrom, end: v.to });
}
}
return trips;
}
export function travelRoute(visits, cities, homes = []) {
const trips = groupTrips(visits, cities);
const dated = homes.filter((h) => h.since);
const legs = [];
const leg = (from, to, kind) => {
if (!from || !to || samePlace(from, to)) return;
legs.push({ from: from.name, to: to.name, km: distanceKm(from, to), kind });
};
let home = homes[0] || null; // aktueller Wohnort
let pos = home; // wo man gerade ist
let next = dated.indexOf(home) + 1; // nächster noch nicht verarbeiteter Wohnort-Wechsel
// Wohnort-Wechsel bis zu einem Tag als Umzug verbuchen (nur zwischen den Reisen)
const moveUntil = (date) => {
while (next < dated.length && dated[next].since <= date) {
leg(pos, dated[next], 'Umzug');
pos = home = dated[next];
next += 1;
}
};
let realTrips = 0;
let excursions = 0;
for (const trip of trips) {
moveUntil(trip.start);
if (!trip.visits.every((v) => samePlace(v.city, home))) realTrips += 1;
trip.visits.forEach((v, i) => {
leg(pos, v.city, i === 0 && home ? 'Hinweg' : 'Weiterreise');
pos = v.city;
for (const ex of v.excursions) {
excursions += 1;
leg(pos, ex.city, 'Ausflug');
leg(ex.city, pos, 'Ausflug zurück');
}
});
if (home) {
// Zurück zum Wohnort, der nach der Reise gilt – hat er sich während der Reise geändert, ist es ein Umzug
const after = homeAt(homes, nextDay(trip.end));
leg(pos, after, after === home ? 'Heimweg' : 'Umzug');
while (next < dated.length && dated[next].since <= nextDay(trip.end)) next += 1;
pos = home = after;
}
}
moveUntil('9999-12-31');
return { legs, km: legs.reduce((s, l) => s + l.km, 0), trips: realTrips, excursions };
}
// Reisetage: jeder Kalendertag einmal; Tage am damaligen Wohnort (z. B. Auslandsaufenthalt) zählen nicht
export function travelDays(visits, cities, homes = []) {
const byId = new Map(cities.map((c) => [c.id, c]));
const days = new Set();
for (const v of visits) {
const city = byId.get(v.cityId);
if (!city) continue;
const end = v.dateTo || v.dateFrom;
for (let d = v.dateFrom; d <= end; d = nextDay(d)) {
if (!samePlace(city, homeAt(homes, d))) days.add(d);
}
}
return days.size;
}