Kitűzés: kényelmi funkciók, pontok listája, export

This commit is contained in:
2026-07-06 14:26:16 +02:00
parent 6706b2b1ba
commit 2a2ce4bb92
9 changed files with 1238 additions and 33 deletions
+253
View File
@@ -277,6 +277,14 @@ class AppDatabase {
return rows.isEmpty ? null : Project.fromMap(rows.first);
}
/// Projekt keresése a globális uuid alapján — közös projekthez
/// csatlakozáskor ezzel derül ki, megvan-e már lokálisan.
Future<Project?> getProjectByUuid(String uuid) async {
final rows = await (await database)
.query('projects', where: 'uuid = ?', whereArgs: [uuid], limit: 1);
return rows.isEmpty ? null : Project.fromMap(rows.first);
}
// Projekt statisztikák — a listázáshoz
Future<Map<String, int>> getProjectStats(int projectId) async {
final db = await database;
@@ -787,4 +795,249 @@ class AppDatabase {
orderBy: 'line_id ASC, station ASC');
return rows.map(StakeoutPoint.fromMap).toList();
}
/// Feltöltésre váró kitűzési pontok — a projekt uuid-jával együtt.
/// A csak lokális projektek SQL-szinten kizárva.
Future<List<Map<String, dynamic>>> pendingStakeoutPoints() async {
final db = await database;
return db.rawQuery('''
SELECT sp.*, pr.uuid AS project_uuid
FROM stakeout_points sp
JOIN projects pr ON pr.id = sp.project_id
WHERE sp.sync_status = 'pending' AND pr.is_local_only = 0
ORDER BY sp.id ASC
''');
}
// ═══════════════════════════════════════════════════════════════
// SZINKRON-SEGÉDEK — a TsSyncService használja őket
// ═══════════════════════════════════════════════════════════════
//
// FONTOS: minden lekérdezés kihagyja az is_local_only = 1 projekteket
// és azok adatait — a "csak lokális" projekt SOHA nem hagyja el az
// eszközt, semmilyen szinkron-útvonalon.
/// Feltöltésre váró (nem lokális) projektek.
Future<List<Map<String, dynamic>>> pendingProjects() async {
final db = await database;
return db.query('projects',
where: "is_local_only = 0 AND sync_status = 'pending'");
}
/// Feltöltésre váró bemért pontok — a projekt uuid-jával együtt.
Future<List<Map<String, dynamic>>> pendingMeasuredPoints() async {
final db = await database;
return db.rawQuery('''
SELECT mp.*, pr.uuid AS project_uuid
FROM measured_points mp
JOIN projects pr ON pr.id = mp.project_id
WHERE mp.sync_status = 'pending' AND pr.is_local_only = 0
ORDER BY mp.id ASC
''');
}
/// Feltöltésre váró trackek — a projekt uuid-jával együtt.
Future<List<Map<String, dynamic>>> pendingTracks() async {
final db = await database;
return db.rawQuery('''
SELECT t.*, pr.uuid AS project_uuid
FROM tracks t
JOIN projects pr ON pr.id = t.project_id
WHERE t.sync_status = 'pending' AND pr.is_local_only = 0
ORDER BY t.id ASC
''');
}
/// Feltöltésre váró track-pontok, batch-ben — track- és projekt-uuid-val.
/// Csak lezárt vagy futó, NEM törölt, NEM lokális trackek pontjai.
Future<List<Map<String, dynamic>>> pendingTrackPoints(
{int limit = 500}) async {
final db = await database;
return db.rawQuery('''
SELECT tp.*, t.uuid AS track_uuid, pr.uuid AS project_uuid
FROM track_points tp
JOIN tracks t ON t.id = tp.track_id
JOIN projects pr ON pr.id = t.project_id
WHERE tp.sync_status = 'pending'
AND t.deleted_at IS NULL
AND t.sync_status != 'pending'
AND pr.is_local_only = 0
ORDER BY tp.id ASC
LIMIT ?
''', [limit]);
}
/// Feltöltésre váró terepbejárás-elemek (csak projekthez rendeltek).
Future<List<Map<String, dynamic>>> pendingNoteItems() async {
final db = await database;
return db.rawQuery('''
SELECT n.*, pr.uuid AS project_uuid
FROM note_items n
JOIN projects pr ON pr.id = n.project_id
WHERE n.sync_status = 'pending' AND pr.is_local_only = 0
ORDER BY n.id ASC
''');
}
/// Összes feltöltésre váró sor száma (UI-jelvényhez).
Future<int> pendingSyncCount() async {
final db = await database;
final res = await db.rawQuery('''
SELECT
(SELECT COUNT(*) FROM projects p
WHERE p.is_local_only = 0 AND p.sync_status = 'pending') +
(SELECT COUNT(*) FROM measured_points mp
JOIN projects pr ON pr.id = mp.project_id
WHERE mp.sync_status = 'pending' AND pr.is_local_only = 0) +
(SELECT COUNT(*) FROM tracks t
JOIN projects pr ON pr.id = t.project_id
WHERE t.sync_status = 'pending' AND pr.is_local_only = 0) +
(SELECT COUNT(*) FROM track_points tp
JOIN tracks t ON t.id = tp.track_id
JOIN projects pr ON pr.id = t.project_id
WHERE tp.sync_status = 'pending' AND pr.is_local_only = 0)
AS cnt
''');
return (res.first['cnt'] as int?) ?? 0;
}
/// Sorok megjelölése szinkronizáltként, uuid alapján (500-as darabokban,
/// az SQLite paraméter-limit miatt).
Future<void> markSyncedByUuid(String table, List<String> uuids) async {
if (uuids.isEmpty) return;
final db = await database;
for (var i = 0; i < uuids.length; i += 500) {
final chunk =
uuids.sublist(i, i + 500 > uuids.length ? uuids.length : i + 500);
final placeholders = List.filled(chunk.length, '?').join(',');
await db.rawUpdate(
"UPDATE $table SET sync_status = 'synced' WHERE uuid IN ($placeholders)",
chunk,
);
}
}
// ── Szinkron-kurzor (projektenként) ──────────────────────────────
Future<String?> getProjectCursor(int localProjectId) async {
final db = await database;
final rows = await db.query('projects',
columns: ['last_pulled_at'],
where: 'id = ?',
whereArgs: [localProjectId],
limit: 1);
return rows.isEmpty ? null : rows.first['last_pulled_at'] as String?;
}
Future<void> setProjectCursor(int localProjectId, String cursor) async {
final db = await database;
await db.update('projects', {'last_pulled_at': cursor},
where: 'id = ?', whereArgs: [localProjectId]);
}
// ── Távoli sorok lokális alkalmazása ─────────────────────────────
/// Távoli projekt-sor lokális upsertje uuid alapján.
/// Visszaadja a lokális id-t. Csatlakozáskor és letöltéskor is ez fut.
Future<int> upsertProjectFromRemote(Map<String, dynamic> remote) async {
final db = await database;
final existing = await getProjectByUuid(remote['id'] as String);
final map = <String, dynamic>{
'uuid': remote['id'],
'name': remote['name'] ?? '',
'client': remote['client'] ?? '',
'description': remote['description'] ?? '',
'crs': remote['crs'] ?? 'eov',
'color': remote['color'] ?? '#185FA5',
'status': remote['status'] ?? 'active',
'is_local_only': 0,
'updated_at': remote['updated_at'] ?? DateTime.now().toIso8601String(),
'deleted_at': remote['deleted_at'],
'sync_status': 'synced',
'last_synced_at': DateTime.now().toIso8601String(),
};
if (existing == null) {
map['created_at'] =
remote['created_at'] ?? DateTime.now().toIso8601String();
return db.insert('projects', map);
}
// Lokális, még fel nem töltött módosítás nyer (push fut előbb).
final localPending = await db.query('projects',
columns: ['sync_status'],
where: 'id = ?',
whereArgs: [existing.id],
limit: 1);
if (localPending.first['sync_status'] == 'pending') return existing.id!;
await db.update('projects', map, where: 'id = ?', whereArgs: [existing.id]);
return existing.id!;
}
/// Általános távoli-sor alkalmazás uuid-kulcsú táblára
/// (measured_points / tracks / note_items).
///
/// LWW-szabály: ha a lokális sor 'pending' (van el nem küldött helyi
/// módosítás), a távolit kihagyjuk — a push úgyis előbb fut, mint a pull.
Future<void> applyRemoteRow(
String table, Map<String, dynamic> localMap) async {
final db = await database;
final uuid = localMap['uuid'] as String;
final rows = await db.query(table,
columns: ['id', 'sync_status'],
where: 'uuid = ?',
whereArgs: [uuid],
limit: 1);
if (rows.isEmpty) {
// Ismeretlen sor tombstone-ja: nincs mit törölni, kihagyjuk.
if (localMap['deleted_at'] != null) return;
await db.insert(table, localMap);
return;
}
if (rows.first['sync_status'] == 'pending') return;
await db.update(table, localMap,
where: 'id = ?', whereArgs: [rows.first['id']]);
}
/// Track lokális id-ja uuid alapján (a track-pontok letöltéséhez).
Future<int?> getTrackIdByUuid(String uuid) async {
final db = await database;
final rows = await db.query('tracks',
columns: ['id'], where: 'uuid = ?', whereArgs: [uuid], limit: 1);
return rows.isEmpty ? null : rows.first['id'] as int;
}
/// Távoli track-pontok beszúrása — a uuid unique index miatt az
/// ismétlődések csendben kimaradnak (idempotens).
Future<void> insertRemoteTrackPoints(List<Map<String, dynamic>> maps) async {
if (maps.isEmpty) return;
final db = await database;
final batch = db.batch();
for (final m in maps) {
batch.insert('track_points', m,
conflictAlgorithm: ConflictAlgorithm.ignore);
}
await batch.commit(noResult: true);
// A point_count/distance a saját eszközön mérésnél frissül; letöltött
// pontoknál a track sora már a helyes összesítőkkel érkezik a szerverről.
}
// ── Réteg-szinkron segédek ───────────────────────────────────────
Future<int> getImportedLayerVersion(String id) async {
final db = await database;
final rows = await db.query('imported_layers',
columns: ['version'], where: 'id = ?', whereArgs: [id], limit: 1);
return rows.isEmpty ? -1 : (rows.first['version'] as int? ?? 0);
}
Future<void> upsertImportedLayerRaw(Map<String, dynamic> map) async {
final db = await database;
await db.insert('imported_layers', map,
conflictAlgorithm: ConflictAlgorithm.replace);
}
}