175 lines
5.4 KiB
Dart
175 lines
5.4 KiB
Dart
import 'package:drift/drift.dart' show Value;
|
|
import 'package:uto_manager/domain/entities/doctrinal_branch.dart';
|
|
import 'package:uto_manager/domain/entities/echelon.dart';
|
|
import 'package:uto_manager/domain/entities/unit_node.dart';
|
|
import 'package:uto_manager/domain/repositories/unit_repository.dart';
|
|
import 'package:uto_manager/infrastructure/database/app_database.dart';
|
|
import 'package:uto_manager/infrastructure/database/daos/unit_dao.dart';
|
|
|
|
/// Drift-backed implementation of [UnitRepository].
|
|
///
|
|
/// Converts between Drift [Unit] data rows and domain [UnitNode] entities.
|
|
/// All persistence is handled via [UnitDao].
|
|
class DriftUnitRepository implements UnitRepository {
|
|
const DriftUnitRepository({required this.dao, required this.db});
|
|
|
|
final UnitDao dao;
|
|
final AppDatabase db;
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Mapping helpers
|
|
// ---------------------------------------------------------------------------
|
|
|
|
UnitNode _toDomain(Unit row) {
|
|
return UnitNode(
|
|
unitId: row.unitId,
|
|
echelon: Echelon.values.firstWhere((e) => e.name == row.echelon),
|
|
branch: DoctrinalBranch.values.firstWhere((b) => b.name == row.branch),
|
|
parentUnitId: row.parentUnitId,
|
|
name: row.name,
|
|
isKeyLeader: row.isKeyLeader,
|
|
);
|
|
}
|
|
|
|
UnitsCompanion _toCompanion(UnitNode unit) {
|
|
return UnitsCompanion.insert(
|
|
unitId: unit.unitId,
|
|
echelon: unit.echelon.name,
|
|
branch: unit.branch.name,
|
|
parentUnitId: Value(unit.parentUnitId),
|
|
name: unit.name,
|
|
isKeyLeader: unit.isKeyLeader,
|
|
);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// UnitRepository implementation
|
|
// ---------------------------------------------------------------------------
|
|
|
|
@override
|
|
Future<List<UnitNode>> getAll() async {
|
|
final rows = await dao.getAllUnits();
|
|
return rows.map(_toDomain).toList();
|
|
}
|
|
|
|
@override
|
|
Future<UnitNode?> getById(String unitId) async {
|
|
final row = await dao.getUnitById(unitId);
|
|
return row == null ? null : _toDomain(row);
|
|
}
|
|
|
|
@override
|
|
Future<List<UnitNode>> getChildren(String parentUnitId) async {
|
|
final rows = await dao.getChildren(parentUnitId);
|
|
return rows.map(_toDomain).toList();
|
|
}
|
|
|
|
@override
|
|
Future<List<UnitNode>> getRoots() async {
|
|
final rows = await dao.getRoots();
|
|
return rows.map(_toDomain).toList();
|
|
}
|
|
|
|
@override
|
|
Future<List<UnitNode>> getByEchelon(Echelon echelon) async {
|
|
final rows = await dao.getByEchelon(echelon.name);
|
|
return rows.map(_toDomain).toList();
|
|
}
|
|
|
|
@override
|
|
Future<List<UnitNode>> getSubtree(String unitId) async {
|
|
final all = await getAll();
|
|
return _collectSubtree(unitId, all);
|
|
}
|
|
|
|
@override
|
|
Future<void> insert(UnitNode unit) async {
|
|
final existing = await dao.getUnitById(unit.unitId);
|
|
if (existing != null) {
|
|
throw StateError('Unit ${unit.unitId} already exists.');
|
|
}
|
|
await dao.insertUnit(_toCompanion(unit));
|
|
}
|
|
|
|
@override
|
|
Future<void> insertAll(List<UnitNode> units) async {
|
|
await dao.insertAllUnits(units.map(_toCompanion).toList());
|
|
}
|
|
|
|
@override
|
|
Future<void> update(UnitNode unit) async {
|
|
final existing = await dao.getUnitById(unit.unitId);
|
|
if (existing == null) {
|
|
throw StateError('Unit ${unit.unitId} not found.');
|
|
}
|
|
await dao.updateUnit(_toCompanion(unit));
|
|
}
|
|
|
|
@override
|
|
Future<void> delete(String unitId) async {
|
|
await dao.deleteUnit(unitId);
|
|
}
|
|
|
|
@override
|
|
Future<void> deleteSubtree(String unitId) async {
|
|
final all = await getAll();
|
|
final subtree = _collectSubtree(unitId, all);
|
|
// Delete leaves first (bottom-up) to respect FK constraints.
|
|
for (final unit in subtree.reversed) {
|
|
await dao.deleteUnit(unit.unitId);
|
|
}
|
|
}
|
|
|
|
@override
|
|
Future<void> reparent(String unitId, String newParentId) async {
|
|
final unit = await getById(unitId);
|
|
if (unit == null) throw StateError('Unit $unitId not found.');
|
|
final newParent = await getById(newParentId);
|
|
if (newParent == null) throw StateError('Parent $newParentId not found.');
|
|
|
|
if (!unit.echelon.isValidParent(newParent.echelon)) {
|
|
throw ArgumentError(
|
|
'${unit.echelon.displayName} cannot be a child of '
|
|
'${newParent.echelon.displayName}.',
|
|
);
|
|
}
|
|
|
|
final updated = unit.copyWith(parentUnitId: newParentId);
|
|
await dao.updateUnit(_toCompanion(updated));
|
|
}
|
|
|
|
@override
|
|
Stream<List<UnitNode>> watchAll() {
|
|
return dao.watchAllUnits().map((rows) => rows.map(_toDomain).toList());
|
|
}
|
|
|
|
@override
|
|
Stream<List<UnitNode>> watchSubtree(String unitId) {
|
|
return watchAll().map((all) => _collectSubtree(unitId, all));
|
|
}
|
|
|
|
@override
|
|
Future<void> deleteAll() async {
|
|
await dao.deleteAllUnits();
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Private helpers
|
|
// ---------------------------------------------------------------------------
|
|
|
|
List<UnitNode> _collectSubtree(String rootId, List<UnitNode> all) {
|
|
final result = <UnitNode>[];
|
|
final queue = <String>[rootId];
|
|
while (queue.isNotEmpty) {
|
|
final current = queue.removeAt(0);
|
|
final node = all.firstWhere(
|
|
(u) => u.unitId == current,
|
|
orElse: () => throw StateError('Unit $current not found'),
|
|
);
|
|
result.add(node);
|
|
queue.addAll(all.where((u) => u.parentUnitId == current).map((u) => u.unitId));
|
|
}
|
|
return result;
|
|
}
|
|
}
|