Kitűzés: kényelmi funkciók, pontok listája, export
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user