add some new features

This commit is contained in:
2026-10-29 15:03:48 +01:00
parent c876405a8f
commit 20012db615
39 changed files with 2908 additions and 97 deletions
+171 -5
View File
@@ -6,6 +6,7 @@ import 'package:get/get.dart';
import 'package:uuid/uuid.dart';
import '../helper/display_formats.dart';
import '../helper/bauteil_preis_berechnung.dart';
import '../helper/filament_bestand.dart';
import '../helper/input_validators.dart';
import '../helper/storage_keys.dart';
@@ -15,12 +16,14 @@ import '../models/kauf_filament_model.dart';
import '../services/bauteil_photo_service.dart';
import '../services/get_storage_services.dart';
import 'home_controller.dart';
import 'abfall_controller.dart';
class BauteilController extends GetxController {
BauteilController({BauteilPhotoService? photoService})
: photoService = photoService ?? BauteilPhotoService();
static const storageKey = StorageKeys.bauteile;
static const double standardAufschlagProzent = 50;
final storage = Get.find<GetStorageService>();
final BauteilPhotoService photoService;
final formKey = GlobalKey<FormState>();
@@ -32,6 +35,12 @@ class BauteilController extends GetxController {
final gewichtController = TextEditingController();
final abfallController = TextEditingController(text: '0');
final gesamtpreisController = TextEditingController();
final aufschlagController = TextEditingController(
text: standardAufschlagProzent.toStringAsFixed(0),
);
final preisDetails = Rxn<BauteilPreis>();
bool _preisPausiert = false;
BauteilModel? _bearbeitetesBauteil;
final _verbrauchControllers = <String, TextEditingController>{};
final filamente = <KaufFilamentModel>[].obs;
final profile = <AnwendungModel>[].obs;
@@ -60,13 +69,20 @@ class BauteilController extends GetxController {
);
bauteile.assignAll(_loadBauteile());
bauteile.sort((a, b) => b.erstelltAm.compareTo(a.erstelltAm));
gewichtController.addListener(berechnePreis);
abfallController.addListener(berechnePreis);
aufschlagController.addListener(berechnePreis);
}
List<BauteilModel> _loadBauteile() =>
storage.getModelList(storageKey).map(BauteilModel.fromJson).toList();
TextEditingController verbrauchController(String id) =>
_verbrauchControllers.putIfAbsent(id, () => TextEditingController());
_verbrauchControllers.putIfAbsent(id, () {
final field = TextEditingController();
field.addListener(berechnePreis);
return field;
});
List<AnwendungModel> profilesFor(String filamentId) =>
profile.where((item) => item.filamentId == filamentId).toList()
@@ -78,6 +94,15 @@ class BauteilController extends GetxController {
return '${item.marke} · ${item.material} · ${item.farbe}';
}
double? restgewichtGramm(String id) {
final startgewicht = filamente
.firstWhereOrNull((item) => item.filamentId == id)
?.gewichtGramm;
if (startgewicht == null) return null;
final verbrauch = FilamentBestand.verbrauch(bauteile);
return startgewicht - (verbrauch[id] ?? 0);
}
double? verfuegbarGramm(String id) {
final start = filamente
.firstWhereOrNull((item) => item.filamentId == id)
@@ -97,6 +122,42 @@ class BauteilController extends GetxController {
} else {
selectedFilamente.remove(id);
}
berechnePreis();
}
String? pruefeFilamentVorrat() {
if (selectedFilamente.isEmpty) return null;
final gewicht = InputValidators.number(gewichtController.text);
final abfall = abfallController.text.trim().isEmpty
? 0.0
: InputValidators.number(abfallController.text);
if (gewicht == null ||
!gewicht.isFinite ||
gewicht <= 0 ||
abfall == null ||
!abfall.isFinite ||
abfall < 0) {
return null;
}
final bedarf = gewicht + abfall;
if (!bedarf.isFinite) return null;
var verfuegbar = 0.0;
for (final id in selectedFilamente.keys) {
final rest = verfuegbarGramm(id);
if (rest == null || !rest.isFinite) {
return 'Die Restmenge einer ausgewählten Rolle ist unbekannt. Bitte das Startgewicht und den bisherigen Verbrauch prüfen.';
}
verfuegbar += rest > 0 ? rest : 0;
}
if (bedarf - verfuegbar <= 0.000001) return null;
final rollen = selectedFilamente.length == 1
? 'der ausgewählten Rolle'
: 'der ausgewählten Rollen zusammen';
return 'Die Restmenge $rollen reicht nicht aus.\n\n'
'Benötigt (inklusive Abfall): ${DisplayFormats.number(bedarf)} g\n'
'Verfügbar: ${DisplayFormats.number(verfuegbar)} g\n'
'Es fehlen: ${DisplayFormats.number(bedarf - verfuegbar)} g\n\n'
'Füge eine weitere Rolle hinzu oder wähle eine andere Rolle aus.';
}
void selectProfile(String filamentId, String? anwendungId) {
@@ -190,8 +251,60 @@ class BauteilController extends GetxController {
if (Get.isRegistered<HomeController>()) {
Get.find<HomeController>().loadVerbrauch();
}
if (Get.isRegistered<AbfallController>()) {
Get.find<AbfallController>().loadAbfaelle();
}
}
void berechnePreis() {
if (_preisPausiert || _closed) return;
final gewicht = InputValidators.number(gewichtController.text);
final abfall = InputValidators.number(abfallController.text);
final aufschlag = InputValidators.number(aufschlagController.text);
BauteilPreis? preis;
if (gewicht != null && abfall != null && aufschlag != null) {
final entry = BauteilModel(
bauteilId: editingId.value ?? '',
erstelltAm: DateTime.now(),
name: '',
verwendung: '',
druckdauerMinuten: 1,
gewichtGramm: gewicht,
abfallGramm: abfall,
filamente: selectedFilamente.keys
.map(
(id) => BauteilFilamentModel(
filamentId: id,
verbrauchGramm:
InputValidators.number(_verbrauchControllers[id]?.text) ??
0,
),
)
.toList(),
);
preis = BauteilPreisBerechnung.berechne(
entry,
filamente,
aufschlag,
previous: _bearbeitetesBauteil,
);
}
preisDetails.value = preis;
gesamtpreisController.text = preis == null
? ''
: preis.gesamtpreis.toStringAsFixed(2).replaceAll('.', ',');
}
BauteilPreis? _kalkulation(
BauteilModel entry,
double aufschlag, {
BauteilModel? previous,
}) => BauteilPreisBerechnung.berechne(
entry,
storage.getModelList(StorageKeys.filamente).map(KaufFilamentModel.fromJson),
aufschlag,
previous: previous,
);
Future<void> saveBauteil() async {
if (isSaving.value || isPickingPhoto.value) return;
message.value = null;
@@ -225,7 +338,7 @@ class BauteilController extends GetxController {
druckdauerMinuten: duration,
gewichtGramm: InputValidators.number(gewichtController.text)!,
abfallGramm: InputValidators.number(abfallController.text)!,
gesamtpreis: InputValidators.number(gesamtpreisController.text),
aufschlagProzent: InputValidators.number(aufschlagController.text),
fotoBase64: foto.value == null ? null : base64Encode(foto.value!),
filamente: selectedFilamente.entries
.map(
@@ -246,13 +359,32 @@ class BauteilController extends GetxController {
_error(error);
return;
}
final preis = _kalkulation(
entry,
entry.aufschlagProzent!,
previous: previous,
);
if (preis == null) {
_error(
'Preis nicht berechenbar. Bitte Verbrauch, Einkaufspreis und Startgewicht der Rollen sowie den Aufschlag prüfen.',
);
return;
}
final gespeichert = entry.copyWith(
bauteilId: entry.bauteilId,
erstelltAm: entry.erstelltAm,
abfallKosten: preis.abfallKosten,
materialKosten: preis.materialKosten,
aufschlagProzent: preis.aufschlagProzent,
gesamtpreis: preis.gesamtpreis,
);
await _commit([
...existing.where((item) => item.bauteilId != entry.bauteilId),
entry,
gespeichert,
]);
resetForm();
hasError.value = false;
message.value = 'Bauteil und Filamentverbrauch gespeichert.';
message.value = 'Bauteil, Filamentverbrauch und Abfall gespeichert.';
} catch (_) {
_error('Speichern fehlgeschlagen. Bitte erneut versuchen.');
} finally {
@@ -263,6 +395,8 @@ class BauteilController extends GetxController {
Future<void> editBauteil(BauteilModel entry) async {
if (isSaving.value || isPickingPhoto.value) return;
resetForm();
_preisPausiert = true;
_bearbeitetesBauteil = entry;
message.value = null;
nameController.text = entry.name;
verwendungController.text = entry.verwendung;
@@ -279,7 +413,13 @@ class BauteilController extends GetxController {
: '';
selectedFilamente[item.filamentId] = item.anwendungId;
}
aufschlagController.text =
(entry.aufschlagProzent ?? standardAufschlagProzent)
.toString()
.replaceAll('.', ',');
editingId.value = entry.bauteilId;
_preisPausiert = false;
berechnePreis();
await scrollToForm();
}
@@ -298,7 +438,27 @@ class BauteilController extends GetxController {
await scrollToForm();
return;
}
await _commit([...existing, copy]);
final preis = _kalkulation(
copy,
copy.aufschlagProzent ?? standardAufschlagProzent,
);
if (preis == null) {
_error(
'Abfallkosten nicht berechenbar. Bitte Einkaufspreis und Startgewicht der verwendeten Rollen prüfen.',
);
return;
}
await _commit([
...existing,
copy.copyWith(
bauteilId: copy.bauteilId,
erstelltAm: copy.erstelltAm,
abfallKosten: preis.abfallKosten,
materialKosten: preis.materialKosten,
aufschlagProzent: preis.aufschlagProzent,
gesamtpreis: preis.gesamtpreis,
),
]);
hasError.value = false;
message.value = 'Bauteil dupliziert und Verbrauch erneut gebucht.';
} catch (_) {
@@ -338,6 +498,8 @@ class BauteilController extends GetxController {
}
void resetForm() {
_preisPausiert = true;
_bearbeitetesBauteil = null;
formKey.currentState?.reset();
editingId.value = null;
nameController.clear();
@@ -352,6 +514,9 @@ class BauteilController extends GetxController {
controller.clear();
}
foto.value = null;
aufschlagController.text = standardAufschlagProzent.toStringAsFixed(0);
preisDetails.value = null;
_preisPausiert = false;
}
@override
@@ -365,6 +530,7 @@ class BauteilController extends GetxController {
gewichtController.dispose();
abfallController.dispose();
gesamtpreisController.dispose();
aufschlagController.dispose();
for (final controller in _verbrauchControllers.values) {
controller.dispose();
}