fix(trips): copy todo_items and budget_category_order when duplicating a trip

Both tables were added after the original copy logic in #270 and were
silently omitted on copy. todo_items are copied with checked reset to 0
and assigned_user_id nulled; budget_category_order rows are copied verbatim.
Adds TRIP-027 regression test.

Closes #786
This commit is contained in:
jubnl
2026-04-21 20:38:53 +02:00
parent e25b513d0b
commit 13162c0920
2 changed files with 64 additions and 0 deletions
+18
View File
@@ -681,6 +681,24 @@ export function copyTripById(sourceTripId: string | number, newOwnerId: number,
if (newDayId) insertNote.run(newDayId, newTripId, n.text, n.time, n.icon, n.sort_order);
}
const oldTodos = db.prepare('SELECT * FROM todo_items WHERE trip_id = ?').all(sourceTripId) as any[];
const insertTodo = db.prepare(`
INSERT INTO todo_items (trip_id, name, checked, category, sort_order, due_date, description, assigned_user_id, priority)
VALUES (?, ?, 0, ?, ?, ?, ?, NULL, ?)
`);
for (const t of oldTodos) {
insertTodo.run(newTripId, t.name, t.category, t.sort_order, t.due_date, t.description, t.priority);
}
const oldCategoryOrder = db.prepare('SELECT category, sort_order FROM budget_category_order WHERE trip_id = ?').all(sourceTripId) as any[];
const insertCategoryOrder = db.prepare(`
INSERT INTO budget_category_order (trip_id, category, sort_order)
VALUES (?, ?, ?)
`);
for (const o of oldCategoryOrder) {
insertCategoryOrder.run(newTripId, o.category, o.sort_order);
}
return Number(newTripId);
});
+46
View File
@@ -950,6 +950,52 @@ describe('Copy trip with data', () => {
expect(newNotes).toHaveLength(1);
expect(newNotes[0].text).toBe('Pack early!');
});
it('TRIP-027 — copy preserves todos (unchecked, unassigned) and budget category order', async () => {
const { user } = createUser(testDb);
const trip = createTrip(testDb, user.id, { title: 'Todo Trip' });
// Two todos: one checked and assigned — both should arrive unchecked and unassigned
testDb.prepare(
'INSERT INTO todo_items (trip_id, name, checked, category, sort_order, due_date, description, priority) VALUES (?, ?, ?, ?, ?, ?, ?, ?)'
).run(trip.id, 'Buy tickets', 0, 'Transport', 0, '2026-06-01', 'Check Ryanair', 1);
testDb.prepare(
'INSERT INTO todo_items (trip_id, name, checked, category, sort_order, assigned_user_id, priority) VALUES (?, ?, ?, ?, ?, ?, ?)'
).run(trip.id, 'Book hotel', 1, 'Accommodation', 1, user.id, 0);
// Two budget category order rows
const insOrder = testDb.prepare('INSERT INTO budget_category_order (trip_id, category, sort_order) VALUES (?, ?, ?)');
insOrder.run(trip.id, 'Transport', 0);
insOrder.run(trip.id, 'Accommodation', 1);
const res = await request(app)
.post(`/api/trips/${trip.id}/copy`)
.set('Cookie', authCookie(user.id))
.send({ title: 'Todo Trip (Copy)' });
expect(res.status).toBe(201);
const newId = res.body.trip.id;
// Todos copied with checked reset and assigned_user_id nulled
const newTodos = testDb.prepare('SELECT * FROM todo_items WHERE trip_id = ? ORDER BY sort_order').all(newId) as any[];
expect(newTodos).toHaveLength(2);
expect(newTodos[0].name).toBe('Buy tickets');
expect(newTodos[0].category).toBe('Transport');
expect(newTodos[0].checked).toBe(0);
expect(newTodos[0].assigned_user_id).toBeNull();
expect(newTodos[0].due_date).toBe('2026-06-01');
expect(newTodos[0].description).toBe('Check Ryanair');
expect(newTodos[0].priority).toBe(1);
expect(newTodos[1].name).toBe('Book hotel');
expect(newTodos[1].checked).toBe(0);
expect(newTodos[1].assigned_user_id).toBeNull();
// Budget category order copied
const newOrder = testDb.prepare('SELECT category, sort_order FROM budget_category_order WHERE trip_id = ? ORDER BY sort_order').all(newId) as any[];
expect(newOrder).toHaveLength(2);
expect(newOrder[0]).toMatchObject({ category: 'Transport', sort_order: 0 });
expect(newOrder[1]).toMatchObject({ category: 'Accommodation', sort_order: 1 });
});
});
// ─────────────────────────────────────────────────────────────────────────────