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
+38
View File
@@ -0,0 +1,38 @@
class AbfallModel {
const AbfallModel({
required this.abfallId,
required this.datumZeit,
required this.gewichtGramm,
required this.kosten,
this.bauteilId,
this.bauteilName,
this.kostenUnbekannt = false,
this.kostenNachtraeglich = false,
});
final String abfallId;
final DateTime datumZeit;
final double gewichtGramm;
final double kosten;
// Linked records are derived from parts, never stored a second time.
final String? bauteilId;
final String? bauteilName;
final bool kostenUnbekannt;
final bool kostenNachtraeglich;
bool get ausBauteil => bauteilId != null;
factory AbfallModel.fromJson(Map<String, dynamic> json) => AbfallModel(
abfallId: json['abfallId'] as String,
datumZeit: DateTime.parse(json['datumZeit'] as String).toLocal(),
gewichtGramm: (json['gewichtGramm'] as num).toDouble(),
kosten: (json['kosten'] as num).toDouble(),
);
Map<String, dynamic> toJson() => {
'abfallId': abfallId,
'datumZeit': datumZeit.toIso8601String(),
'gewichtGramm': gewichtGramm,
'kosten': kosten,
};
}
+27 -4
View File
@@ -2,6 +2,7 @@ import 'dart:convert';
import '../helper/storage_keys.dart';
import 'anwendung_model.dart';
import 'abfall_model.dart';
import 'bauteil_model.dart';
import 'kauf_filament_model.dart';
@@ -11,6 +12,7 @@ class BackupModel {
StorageKeys.anwendungen,
StorageKeys.bauteile,
StorageKeys.materialien,
StorageKeys.abfaelle,
];
final Map<String, List<Map<String, dynamic>>> data;
@@ -34,9 +36,12 @@ class BackupModel {
final raw = json['daten'] as Map;
final data = <String, List<Map<String, dynamic>>>{
for (final key in keys)
key: (raw[key] as List)
.map((item) => Map<String, dynamic>.from(item as Map))
.toList(),
key:
((key == StorageKeys.abfaelle && !raw.containsKey(key))
? <dynamic>[]
: raw[key] as List)
.map((item) => Map<String, dynamic>.from(item as Map))
.toList(),
};
final filamentIds = <String>{};
for (final json in data[StorageKeys.filamente]!) {
@@ -45,7 +50,10 @@ class BackupModel {
!filamentIds.add(item.filamentId) ||
item.material.trim().isEmpty ||
!_positive(item.gewichtGramm) ||
!_nonNegative(item.preis)) {
!_nonNegative(item.preis) ||
(item.bruttoGewichtGramm != null &&
(!_positive(item.bruttoGewichtGramm) ||
item.bruttoGewichtGramm! < item.gewichtGramm!))) {
throw const FormatException();
}
}
@@ -71,6 +79,11 @@ class BackupModel {
item.druckdauerMinuten <= 0 ||
!_positive(item.gewichtGramm) ||
!_nonNegative(item.abfallGramm) ||
(item.abfallKosten != null && !_nonNegative(item.abfallKosten!)) ||
(item.materialKosten != null &&
!_nonNegative(item.materialKosten!)) ||
(item.aufschlagProzent != null &&
!_nonNegative(item.aufschlagProzent!)) ||
(item.gesamtpreis != null && !_nonNegative(item.gesamtpreis!)) ||
item.filamente.isEmpty ||
item.filamente.any(
@@ -82,6 +95,16 @@ class BackupModel {
}
if (item.fotoBase64 != null) base64Decode(item.fotoBase64!);
}
final wasteIds = <String>{};
for (final json in data[StorageKeys.abfaelle]!) {
final item = AbfallModel.fromJson(json);
if (item.abfallId.isEmpty ||
!wasteIds.add(item.abfallId) ||
!_positive(item.gewichtGramm) ||
!_nonNegative(item.kosten)) {
throw const FormatException();
}
}
final materials = data[StorageKeys.materialien]!;
if (materials.isNotEmpty) {
if (materials.length != 1) throw const FormatException();
+27
View File
@@ -0,0 +1,27 @@
import 'bauteil_model.dart';
enum BauteilZeitraum { jahr, monat, tag }
class BauteilAuswertungsGruppe {
const BauteilAuswertungsGruppe({
required this.beginn,
required this.bauteile,
});
final DateTime beginn;
final List<BauteilModel> bauteile;
static bool preisBekannt(BauteilModel bauteil) =>
bauteil.gesamtpreis != null &&
bauteil.gesamtpreis!.isFinite &&
bauteil.gesamtpreis! >= 0;
static double summeFuer(Iterable<BauteilModel> bauteile) => double.parse(
bauteile
.where(preisBekannt)
.fold<double>(0, (sum, item) => sum + item.gesamtpreis!)
.toStringAsFixed(2),
);
double get summe => summeFuer(bauteile);
int get ohnePreis => bauteile.where((item) => !preisBekannt(item)).length;
}
+20 -1
View File
@@ -35,6 +35,9 @@ class BauteilModel {
final double gewichtGramm;
final double abfallGramm;
final double? gesamtpreis;
final double? abfallKosten;
final double? materialKosten;
final double? aufschlagProzent;
final List<BauteilFilamentModel> filamente;
final String? fotoBase64;
@@ -48,6 +51,9 @@ class BauteilModel {
required this.abfallGramm,
required this.filamente,
this.gesamtpreis,
this.abfallKosten,
this.materialKosten,
this.aufschlagProzent,
this.fotoBase64,
});
@@ -65,6 +71,10 @@ class BauteilModel {
BauteilModel copyWith({
required String bauteilId,
required DateTime erstelltAm,
double? abfallKosten,
double? materialKosten,
double? aufschlagProzent,
double? gesamtpreis,
}) => BauteilModel(
bauteilId: bauteilId,
erstelltAm: erstelltAm,
@@ -73,7 +83,10 @@ class BauteilModel {
druckdauerMinuten: druckdauerMinuten,
gewichtGramm: gewichtGramm,
abfallGramm: abfallGramm,
gesamtpreis: gesamtpreis,
gesamtpreis: gesamtpreis ?? this.gesamtpreis,
abfallKosten: abfallKosten ?? this.abfallKosten,
materialKosten: materialKosten ?? this.materialKosten,
aufschlagProzent: aufschlagProzent ?? this.aufschlagProzent,
filamente: filamente,
fotoBase64: fotoBase64,
);
@@ -87,6 +100,9 @@ class BauteilModel {
'gewichtGramm': gewichtGramm,
'abfallGramm': abfallGramm,
'gesamtpreis': gesamtpreis,
'abfallKosten': abfallKosten,
'materialKosten': materialKosten,
'aufschlagProzent': aufschlagProzent,
'filamente': filamente.map((item) => item.toJson()).toList(),
'fotoBase64': fotoBase64,
};
@@ -100,6 +116,9 @@ class BauteilModel {
gewichtGramm: (json['gewichtGramm'] as num).toDouble(),
abfallGramm: (json['abfallGramm'] as num).toDouble(),
gesamtpreis: (json['gesamtpreis'] as num?)?.toDouble(),
abfallKosten: (json['abfallKosten'] as num?)?.toDouble(),
materialKosten: (json['materialKosten'] as num?)?.toDouble(),
aufschlagProzent: (json['aufschlagProzent'] as num?)?.toDouble(),
fotoBase64: json['fotoBase64'] as String?,
filamente: (json['filamente'] as List)
.map(
+8 -1
View File
@@ -6,7 +6,9 @@ class KaufFilamentModel {
final String material;
final double preis;
final String farbe;
// Original net material weight, without the spool.
final String gewicht;
final double? bruttoGewichtGramm;
const KaufFilamentModel({
required this.filamentId,
@@ -17,9 +19,10 @@ class KaufFilamentModel {
required this.preis,
required this.farbe,
required this.gewicht,
this.bruttoGewichtGramm,
});
/// Supports old purchases saved as "1kg" as well as gram values.
/// Original net material weight. Supports legacy "1kg" and gram values.
double? get gewichtGramm {
final match = RegExp(
r'^\s*(\d+(?:[.,]\d+)?)\s*(kg|g)?\s*$',
@@ -41,6 +44,7 @@ class KaufFilamentModel {
'preis': preis,
'farbe': farbe,
'gewicht': gewicht,
'bruttoGewichtGramm': bruttoGewichtGramm,
};
factory KaufFilamentModel.fromJson(Map<String, dynamic> json) {
@@ -53,6 +57,7 @@ class KaufFilamentModel {
preis: (json['preis'] as num).toDouble(),
farbe: json['farbe'] as String,
gewicht: json['gewicht'] ?? '1000',
bruttoGewichtGramm: (json['bruttoGewichtGramm'] as num?)?.toDouble(),
);
}
@@ -65,6 +70,7 @@ class KaufFilamentModel {
double? preis,
String? farbe,
String? gewicht,
double? bruttoGewichtGramm,
}) {
return KaufFilamentModel(
filamentId: filamentId ?? this.filamentId,
@@ -75,6 +81,7 @@ class KaufFilamentModel {
preis: preis ?? this.preis,
farbe: farbe ?? this.farbe,
gewicht: gewicht ?? this.gewicht,
bruttoGewichtGramm: bruttoGewichtGramm ?? this.bruttoGewichtGramm,
);
}
}