Files
ertl_filament_verwaltung/lib/controller/home_controller.dart
T
2026-10-29 15:03:48 +01:00

308 lines
8.7 KiB
Dart

import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:uuid/uuid.dart';
import '../models/kauf_filament_model.dart';
import '../models/bauteil_model.dart';
import '../models/anwendung_model.dart';
import '../helper/storage_keys.dart';
import '../helper/filament_bestand.dart';
import '../helper/display_formats.dart';
import '../helper/input_validators.dart';
import '../services/get_storage_services.dart';
class HomeController extends GetxController {
static const storageKey = StorageKeys.filamente;
final storage = Get.find<GetStorageService>();
final uuid = const Uuid();
final filamente = <KaufFilamentModel>[].obs;
final verbrauch = <String, double>{}.obs;
final filamentIdsMitProfil = <String>{}.obs;
void loadProfile() {
filamentIdsMitProfil.assignAll(
storage
.getModelList(StorageKeys.anwendungen)
.map(AnwendungModel.fromJson)
.map((profile) => profile.filamentId),
);
}
void loadVerbrauch() {
verbrauch.assignAll(
FilamentBestand.verbrauch(
storage.getModelList(StorageKeys.bauteile).map(BauteilModel.fromJson),
),
);
}
double? restgewicht(KaufFilamentModel filament) {
final gewicht = filament.gewichtGramm;
return gewicht == null
? null
: gewicht - (verbrauch[filament.filamentId] ?? 0);
}
final formKey = GlobalKey<FormState>();
final pageScrollController = ScrollController();
final formScrollController = ScrollController();
final haendlerController = TextEditingController();
final farbeController = TextEditingController();
final preisController = TextEditingController();
final markeController = TextEditingController();
final gewichtController = TextEditingController();
final bruttoGewichtController = TextEditingController();
final materialController = TextEditingController(text: 'PLA');
final kaufdatum = DateTime.now().obs;
final material = 'PLA'.obs;
final editingId = RxnString();
static const defaultMaterialien = [
'PLA',
'PLA+',
'PLA light',
'PLA-CF',
'PETG',
'PETG-CF',
'ABS',
'ASA',
'TPU',
'Sonstiges',
];
final materialien = defaultMaterialien.toList().obs;
final materialMessage = RxnString();
final isSavingMaterial = false.obs;
@override
void onInit() {
super.onInit();
loadFilamente();
loadMaterialien();
selectMaterial(materialien.firstOrNull ?? '');
}
void loadMaterialien() {
final saved = storage.getModelList(StorageKeys.materialien);
if (saved.isNotEmpty) {
materialien.assignAll((saved.single['werte'] as List).cast<String>());
} else {
materialien.assignAll({
...defaultMaterialien,
...filamente.map((filament) => filament.material),
});
}
}
void selectMaterial(String value) {
material.value = value;
materialController.text = value;
}
Future<bool> addMaterial() async {
final value = materialController.text.trim();
if (value.isEmpty) {
materialMessage.value = 'Bitte eine Materialart eingeben.';
return false;
}
final existing = materialien.firstWhereOrNull(
(item) => item.toLowerCase() == value.toLowerCase(),
);
if (existing != null) {
materialMessage.value = null;
selectMaterial(existing);
return true;
}
final saved = await _saveMaterialien([...materialien, value]);
if (saved) selectMaterial(value);
return saved;
}
Future<bool> removeMaterial(String value) =>
_saveMaterialien(materialien.where((item) => item != value).toList());
Future<bool> _saveMaterialien(List<String> values) async {
if (isSavingMaterial.value) return false;
isSavingMaterial.value = true;
materialMessage.value = null;
try {
await storage.saveModelList(StorageKeys.materialien, [
{'werte': values},
]);
materialien.assignAll(values);
return true;
} catch (_) {
materialMessage.value =
'Artenliste konnte nicht gespeichert werden. Bitte erneut versuchen.';
return false;
} finally {
isSavingMaterial.value = false;
}
}
void loadFilamente() {
loadVerbrauch();
loadProfile();
final data = storage.getModelList(storageKey);
filamente.assignAll(data.map(KaufFilamentModel.fromJson));
sortFilamente();
}
void sortFilamente() {
filamente.sort((a, b) => b.kaufdatum.compareTo(a.kaufdatum));
}
Future<void> persist() async {
await storage.saveModelList(
storageKey,
filamente.map((f) => f.toJson()).toList(),
);
}
String? validateBruttogewicht(String? value) {
if ((value ?? '').trim().isEmpty) return null;
final error = InputValidators.positive(value);
if (error != null) return error;
final brutto = InputValidators.number(value)!;
final netto = InputValidators.number(gewichtController.text);
if (netto != null && netto.isFinite && brutto < netto) {
return 'Bruttogewicht muss mindestens dem Nettogewicht entsprechen.';
}
return null;
}
Future<void> saveFilament() async {
if (!formKey.currentState!.validate()) return;
final preis = double.tryParse(
preisController.text.trim().replaceAll(',', '.'),
);
if (preis == null || !preis.isFinite || preis < 0) {
Get.snackbar('Ungültiger Preis', 'Bitte Preis überprüfen.');
return;
}
loadVerbrauch();
final gewicht = InputValidators.number(gewichtController.text);
final bereitsVerbraucht = editingId.value == null
? 0
: (verbrauch[editingId.value!] ?? 0);
if (gewicht == null ||
!gewicht.isFinite ||
gewicht <= 0 ||
gewicht < bereitsVerbraucht) {
Get.snackbar(
'Ungültiges Gewicht',
'Das Nettostartgewicht muss positiv sein und mindestens dem bereits gebuchten Verbrauch entsprechen.',
);
return;
}
if (!await addMaterial()) return;
material.value = materialController.text.trim();
final filament = KaufFilamentModel(
filamentId: editingId.value ?? uuid.v4(),
marke: markeController.text.trim(),
kaufdatum: kaufdatum.value,
haendler: haendlerController.text.trim(),
material: material.value,
preis: preis,
farbe: farbeController.text.trim(),
gewicht: gewicht.toString(),
bruttoGewichtGramm: InputValidators.number(bruttoGewichtController.text),
);
if (editingId.value == null) {
filamente.add(filament);
} else {
final index = filamente.indexWhere(
(f) => f.filamentId == editingId.value,
);
if (index < 0) {
Get.snackbar('Fehler', 'Datensatz nicht gefunden.');
return;
}
filamente[index] = filament;
}
sortFilamente();
await persist();
resetForm();
}
Future<void> editFilament(KaufFilamentModel filament) async {
editingId.value = filament.filamentId;
kaufdatum.value = filament.kaufdatum;
markeController.text = filament.marke;
haendlerController.text = filament.haendler;
selectMaterial(filament.material);
preisController.text = filament.preis.toStringAsFixed(2);
bruttoGewichtController.text =
filament.bruttoGewichtGramm?.toString().replaceAll('.', ',') ?? '';
farbeController.text = filament.farbe;
gewichtController.text = filament.gewichtGramm == null
? filament.gewicht
: DisplayFormats.number(filament.gewichtGramm!);
final scroll = formScrollController.hasClients
? formScrollController
: pageScrollController;
if (scroll.hasClients) {
await scroll.animateTo(
0,
duration: const Duration(milliseconds: 300),
curve: Curves.easeOutCubic,
);
}
}
Future<void> duplicateFilament(KaufFilamentModel filament) async {
filamente.add(filament.copyWith(filamentId: uuid.v4()));
sortFilamente();
await persist();
}
Future<void> deleteFilament(String filamentId) async {
filamente.removeWhere((f) => f.filamentId == filamentId);
if (editingId.value == filamentId) resetForm();
await persist();
}
void resetForm() {
formKey.currentState?.reset();
editingId.value = null;
haendlerController.clear();
markeController.clear();
farbeController.clear();
preisController.clear();
kaufdatum.value = DateTime.now();
selectMaterial(materialien.firstOrNull ?? '');
materialMessage.value = null;
gewichtController.clear();
bruttoGewichtController.clear();
}
@override
void onClose() {
pageScrollController.dispose();
formScrollController.dispose();
haendlerController.dispose();
markeController.dispose();
farbeController.dispose();
preisController.dispose();
gewichtController.dispose();
bruttoGewichtController.dispose();
materialController.dispose();
super.onClose();
}
}