Tracks: un-merge a merged session back into its original rides (UI button)

A merged session keeps orig_session_id on every point, so split it back:
unmergeGpsSession recreates the original session rows by that id, retags points,
and sets each piece's date/times from its own points. A 'Разделить склейку (N)'
button shows in track detail only when the session is a merge; it un-merges and
recomputes each piece.
This commit is contained in:
4aiman 2026-10-20 23:49:17 +03:00
commit 435a0dd27c

View file

@ -1001,6 +1001,65 @@ async function splitGpsSession(sessionId, part2Ids, part1EndTime, part2StartTime
return String(newId);
}
// Undo a merge: split a merged session back into its original sessions by orig_session_id
// (merge preserved it on every point). The current session keeps its own original group; each
// other orig_session_id gets its session row recreated (metadata copied). Points are retagged
// (orig_session_id cleared, seq cleared → timestamp order). Returns the list of resulting
// session id_strs (incl. the current one), or null if this session isn't a merge. The caller
// recomputes stats + times for each (computeStats + finishGpsSession).
async function unmergeGpsSession(sessionId) {
const s = await getGpsSession(sessionId);
if (!s) return null;
const rows = await select(
"SELECT DISTINCT CAST(orig_session_id AS TEXT) AS o FROM gps_points WHERE session_id = CAST(?1 AS INTEGER) AND orig_session_id IS NOT NULL",
[s.id_str]
);
const origs = rows.map(r => r.o).filter(Boolean);
if (origs.length < 2) return null; // not a merge — nothing to split
const resultIds = [];
for (const oid of origs) {
if (oid !== s.id_str) {
await execute(
`INSERT INTO gps_sessions (id, date, start_time, end_time, activity_type, source, bike_id)
VALUES (CAST(?1 AS INTEGER), ?2, ?3, ?4, ?5, ?6, ?7)`,
[oid, s.date, s.start_time, s.end_time, s.activity_type, s.source ?? "recorded", s.bike_id ?? null]
);
await execute(
"UPDATE gps_points SET session_id = CAST(?1 AS INTEGER), orig_session_id = NULL, seq = NULL WHERE session_id = CAST(?2 AS INTEGER) AND orig_session_id = CAST(?1 AS TEXT)",
[oid, s.id_str]
);
} else {
await execute(
"UPDATE gps_points SET orig_session_id = NULL, seq = NULL WHERE session_id = CAST(?1 AS INTEGER) AND orig_session_id = CAST(?1 AS TEXT)",
[s.id_str]
);
}
resultIds.push(oid);
}
// Fix each piece's date/times from its own points (merge had stretched them to the whole span).
for (const id of resultIds) {
const ts = await select("SELECT MIN(ts) AS a, MAX(ts) AS b FROM gps_points WHERE session_id = CAST(?1 AS INTEGER)", [id]);
const a = ts[0] && ts[0].a;
if (a) {
const hm = (t) => new Date(t).toTimeString().slice(0, 8);
await execute("UPDATE gps_sessions SET date = ?1, start_time = ?2, end_time = ?3 WHERE id = CAST(?4 AS INTEGER)",
[new Date(a).toISOString().slice(0, 10), hm(a), hm(ts[0].b), id]);
}
}
return resultIds;
}
// How many distinct original sessions are glued into this one (>1 ⇒ it's a merge that can be split).
async function countMergedOrigins(sessionId) {
const s = await getGpsSession(sessionId);
if (!s) return 0;
const r = await select(
"SELECT COUNT(DISTINCT orig_session_id) AS c FROM gps_points WHERE session_id = CAST(?1 AS INTEGER) AND orig_session_id IS NOT NULL",
[s.id_str]
);
return (r[0] && r[0].c) || 0;
}
async function importGpxSession(session, points) {
await execute(
`INSERT INTO gps_sessions (date, start_time, end_time, activity_type,
@ -1389,6 +1448,8 @@ window.DB.getGpsSession = getGpsSession;
window.DB.getGpsPoints = getGpsPoints;
window.DB.deleteGpsSession = deleteGpsSession;
window.DB.mergeGpsSessions = mergeGpsSessions;
window.DB.unmergeGpsSession = unmergeGpsSession;
window.DB.countMergedOrigins = countMergedOrigins;
window.DB.splitGpsSession = splitGpsSession;
window.DB.importGpxSession = importGpxSession;
window.DB.saveGapFill = saveGapFill;