Initial draft

This commit is contained in:
Dennis Nemec
2025-09-20 16:14:06 +02:00
commit b19a6e1cd4
219 changed files with 10317 additions and 0 deletions

37
lib/bloc/app_bloc.dart Normal file
View File

@ -0,0 +1,37 @@
import 'dart:convert';
import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:hl_lieferservice/bloc/app_events.dart';
import 'package:hl_lieferservice/bloc/app_states.dart';
import 'package:hl_lieferservice/repository/config.dart';
import '../services/erpframe.dart';
class AppBloc extends Bloc<AppEvents, AppState> {
AppBloc() : super(AppInitial()) {
on<AppLoadConfig>(_loadConfig);
}
Future<void> _loadConfig(AppLoadConfig event, Emitter<AppState> emit) async {
emit(AppConfigLoading());
try {
final repository = ConfigurationRepository(path: event.path);
final configuration = LocalDocuFrameConfiguration.fromJson(
json.decode(await rootBundle.loadString("assets/${event.path}")),
);
repository.setDocuFrameConfiguration(configuration);
emit(
AppConfigLoaded(config: await repository.getDocuFrameConfiguration()),
);
} catch (e) {
emit(
AppConfigLoadingFailed(
message: "Fehler beim Laden der Konfigurationsdatei.",
),
);
}
}
}

7
lib/bloc/app_events.dart Normal file
View File

@ -0,0 +1,7 @@
abstract class AppEvents {}
class AppLoadConfig extends AppEvents {
String path;
AppLoadConfig({required this.path});
}

16
lib/bloc/app_states.dart Normal file
View File

@ -0,0 +1,16 @@
import '../services/erpframe.dart';
abstract class AppState {}
class AppInitial extends AppState {}
class AppConfigLoading extends AppState {}
class AppConfigLoaded extends AppState {
LocalDocuFrameConfiguration config;
AppConfigLoaded({required this.config});
}
class AppConfigLoadingFailed extends AppState {
String message;
AppConfigLoadingFailed({required this.message});
}

20
lib/dto/address.dart Normal file
View File

@ -0,0 +1,20 @@
import 'package:json_annotation/json_annotation.dart';
part 'address.g.dart';
@JsonSerializable(fieldRename: FieldRename.snake)
class AddressDTO {
AddressDTO(
{required this.streetName,
required this.postalCode,
required this.city});
String streetName;
String postalCode;
String city;
factory AddressDTO.fromJson(Map<String, dynamic> json) =>
_$AddressDTOFromJson(json);
Map<dynamic, dynamic> toJson() => _$AddressDTOToJson(this);
}

20
lib/dto/address.g.dart Normal file
View File

@ -0,0 +1,20 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'address.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
AddressDTO _$AddressDTOFromJson(Map<String, dynamic> json) => AddressDTO(
streetName: json['street_name'] as String,
postalCode: json['postal_code'] as String,
city: json['city'] as String,
);
Map<String, dynamic> _$AddressDTOToJson(AddressDTO instance) =>
<String, dynamic>{
'street_name': instance.streetName,
'postal_code': instance.postalCode,
'city': instance.city,
};

35
lib/dto/article.dart Normal file
View File

@ -0,0 +1,35 @@
import 'package:json_annotation/json_annotation.dart';
part 'article.g.dart';
@JsonSerializable(fieldRename: FieldRename.snake)
class ArticleDTO {
ArticleDTO({
required this.name,
required this.articleNr,
required this.quantity,
required this.price,
required this.scannable,
required this.internalId,
required this.scannedRemovedAmount,
required this.scannedAmount,
required this.removeNoteId,
required this.taxRate,
});
String name;
String articleNr;
String quantity;
String price;
String taxRate;
String internalId;
String scannedAmount;
String scannedRemovedAmount;
String? removeNoteId;
bool scannable;
factory ArticleDTO.fromJson(Map<String, dynamic> json) =>
_$ArticleDTOFromJson(json);
Map<dynamic, dynamic> toJson() => _$ArticleDTOToJson(this);
}

34
lib/dto/article.g.dart Normal file
View File

@ -0,0 +1,34 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'article.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
ArticleDTO _$ArticleDTOFromJson(Map<String, dynamic> json) => ArticleDTO(
name: json['name'] as String,
articleNr: json['article_nr'] as String,
quantity: json['quantity'] as String,
price: json['price'] as String,
scannable: json['scannable'] as bool,
internalId: json['internal_id'] as String,
scannedRemovedAmount: json['scanned_removed_amount'] as String,
scannedAmount: json['scanned_amount'] as String,
removeNoteId: json['remove_note_id'] as String?,
taxRate: json['tax_rate'] as String,
);
Map<String, dynamic> _$ArticleDTOToJson(ArticleDTO instance) =>
<String, dynamic>{
'name': instance.name,
'article_nr': instance.articleNr,
'quantity': instance.quantity,
'price': instance.price,
'tax_rate': instance.taxRate,
'internal_id': instance.internalId,
'scanned_amount': instance.scannedAmount,
'scanned_removed_amount': instance.scannedRemovedAmount,
'remove_note_id': instance.removeNoteId,
'scannable': instance.scannable,
};

View File

@ -0,0 +1,16 @@
import 'package:json_annotation/json_annotation.dart';
part 'basic_response.g.dart';
@JsonSerializable(fieldRename: FieldRename.snake)
class BasicResponseDTO {
BasicResponseDTO(
{required this.succeeded,
required this.message});
final bool succeeded;
final String message;
factory BasicResponseDTO.fromJson(Map<String, dynamic> json) => _$BasicResponseDTOFromJson(json);
Map<dynamic, dynamic> toJson() => _$BasicResponseDTOToJson(this);
}

View File

@ -0,0 +1,19 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'basic_response.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
BasicResponseDTO _$BasicResponseDTOFromJson(Map<String, dynamic> json) =>
BasicResponseDTO(
succeeded: json['succeeded'] as bool,
message: json['message'] as String,
);
Map<String, dynamic> _$BasicResponseDTOToJson(BasicResponseDTO instance) =>
<String, dynamic>{
'succeeded': instance.succeeded,
'message': instance.message,
};

16
lib/dto/car.dart Normal file
View File

@ -0,0 +1,16 @@
import 'package:json_annotation/json_annotation.dart';
part 'car.g.dart';
@JsonSerializable(fieldRename: FieldRename.snake)
class CarDTO {
CarDTO(
{required this.id,
required this.plate});
final String id;
final String plate;
factory CarDTO.fromJson(Map<String, dynamic> json) => _$CarDTOFromJson(json);
Map<dynamic, dynamic> toJson() => _$CarDTOToJson(this);
}

15
lib/dto/car.g.dart Normal file
View File

@ -0,0 +1,15 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'car.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
CarDTO _$CarDTOFromJson(Map<String, dynamic> json) =>
CarDTO(id: json['id'] as String, plate: json['plate'] as String);
Map<String, dynamic> _$CarDTOToJson(CarDTO instance) => <String, dynamic>{
'id': instance.id,
'plate': instance.plate,
};

20
lib/dto/car_add.dart Normal file
View File

@ -0,0 +1,20 @@
import 'package:json_annotation/json_annotation.dart';
part 'car_add.g.dart';
@JsonSerializable(fieldRename: FieldRename.snake)
class CarAddDTO {
CarAddDTO(
{required this.teamId,
required this.plate});
final int teamId;
final String plate;
factory CarAddDTO.fromJson(Map<String, dynamic> json) => _$CarAddDTOFromJson(json);
factory CarAddDTO.make(int teamID, String plate) {
Map<String, dynamic> data = {"team_id": teamID, "plate": plate};
return CarAddDTO.fromJson(data);
}
Map<dynamic, dynamic> toJson() => _$CarAddDTOToJson(this);
}

17
lib/dto/car_add.g.dart Normal file
View File

@ -0,0 +1,17 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'car_add.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
CarAddDTO _$CarAddDTOFromJson(Map<String, dynamic> json) => CarAddDTO(
teamId: (json['team_id'] as num).toInt(),
plate: json['plate'] as String,
);
Map<String, dynamic> _$CarAddDTOToJson(CarAddDTO instance) => <String, dynamic>{
'team_id': instance.teamId,
'plate': instance.plate,
};

View File

@ -0,0 +1,19 @@
import 'car.dart';
import 'package:json_annotation/json_annotation.dart';
part 'car_add_response.g.dart';
@JsonSerializable(fieldRename: FieldRename.snake)
class CarAddResponseDTO {
CarAddResponseDTO(
{required this.succeeded,
required this.message,
required this.car});
final bool succeeded;
final String message;
final CarDTO car;
factory CarAddResponseDTO.fromJson(Map<String, dynamic> json) => _$CarAddResponseDTOFromJson(json);
Map<dynamic, dynamic> toJson() => _$CarAddResponseDTOToJson(this);
}

View File

@ -0,0 +1,21 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'car_add_response.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
CarAddResponseDTO _$CarAddResponseDTOFromJson(Map<String, dynamic> json) =>
CarAddResponseDTO(
succeeded: json['succeeded'] as bool,
message: json['message'] as String,
car: CarDTO.fromJson(json['car'] as Map<String, dynamic>),
);
Map<String, dynamic> _$CarAddResponseDTOToJson(CarAddResponseDTO instance) =>
<String, dynamic>{
'succeeded': instance.succeeded,
'message': instance.message,
'car': instance.car,
};

View File

@ -0,0 +1,19 @@
import 'car.dart';
import 'package:json_annotation/json_annotation.dart';
part 'car_get_response.g.dart';
@JsonSerializable(fieldRename: FieldRename.snake)
class CarGetResponseDTO {
CarGetResponseDTO(
{required this.succeeded,
required this.message,
required this.cars});
final bool succeeded;
final String message;
final List<CarDTO>? cars;
factory CarGetResponseDTO.fromJson(Map<String, dynamic> json) => _$CarGetResponseDTOFromJson(json);
Map<dynamic, dynamic> toJson() => _$CarGetResponseDTOToJson(this);
}

View File

@ -0,0 +1,24 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'car_get_response.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
CarGetResponseDTO _$CarGetResponseDTOFromJson(Map<String, dynamic> json) =>
CarGetResponseDTO(
succeeded: json['succeeded'] as bool,
message: json['message'] as String,
cars:
(json['cars'] as List<dynamic>?)
?.map((e) => CarDTO.fromJson(e as Map<String, dynamic>))
.toList(),
);
Map<String, dynamic> _$CarGetResponseDTOToJson(CarGetResponseDTO instance) =>
<String, dynamic>{
'succeeded': instance.succeeded,
'message': instance.message,
'cars': instance.cars,
};

View File

@ -0,0 +1,20 @@
import 'package:json_annotation/json_annotation.dart';
part 'contact_person.g.dart';
@JsonSerializable(fieldRename: FieldRename.snake)
class ContactPersonDTO {
ContactPersonDTO(
{required this.name,
required this.salutation,
required this.phoneNo,
required this.mobileNo});
String name;
String salutation;
String phoneNo;
String mobileNo;
factory ContactPersonDTO.fromJson(Map<String, dynamic> json) => _$ContactPersonDTOFromJson(json);
Map<dynamic, dynamic> toJson() => _$ContactPersonDTOToJson(this);
}

View File

@ -0,0 +1,23 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'contact_person.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
ContactPersonDTO _$ContactPersonDTOFromJson(Map<String, dynamic> json) =>
ContactPersonDTO(
name: json['name'] as String,
salutation: json['salutation'] as String,
phoneNo: json['phone_no'] as String,
mobileNo: json['mobile_no'] as String,
);
Map<String, dynamic> _$ContactPersonDTOToJson(ContactPersonDTO instance) =>
<String, dynamic>{
'name': instance.name,
'salutation': instance.salutation,
'phone_no': instance.phoneNo,
'mobile_no': instance.mobileNo,
};

15
lib/dto/customer.dart Normal file
View File

@ -0,0 +1,15 @@
import 'address.dart';
import 'package:json_annotation/json_annotation.dart';
part 'customer.g.dart';
@JsonSerializable(fieldRename: FieldRename.snake)
class CustomerDTO {
CustomerDTO({required this.name, required this.address});
String name;
AddressDTO address;
factory CustomerDTO.fromJson(Map<String, dynamic> json) => _$CustomerDTOFromJson(json);
Map<dynamic, dynamic> toJson() => _$CustomerDTOToJson(this);
}

15
lib/dto/customer.g.dart Normal file
View File

@ -0,0 +1,15 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'customer.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
CustomerDTO _$CustomerDTOFromJson(Map<String, dynamic> json) => CustomerDTO(
name: json['name'] as String,
address: AddressDTO.fromJson(json['address'] as Map<String, dynamic>),
);
Map<String, dynamic> _$CustomerDTOToJson(CustomerDTO instance) =>
<String, dynamic>{'name': instance.name, 'address': instance.address};

83
lib/dto/delivery.dart Normal file
View File

@ -0,0 +1,83 @@
import 'article.dart';
import 'contact_person.dart';
import 'customer.dart';
import 'discount.dart';
import 'image.dart';
import 'note.dart';
import 'payment.dart';
import 'package:json_annotation/json_annotation.dart';
part 'delivery.g.dart';
@JsonSerializable(fieldRename: FieldRename.snake)
class DeliveryOptionDTO {
DeliveryOptionDTO({
required this.numerical,
required this.value,
required this.display,
required this.key,
});
bool numerical;
String value;
String display;
String key;
factory DeliveryOptionDTO.fromJson(Map<String, dynamic> json) =>
_$DeliveryOptionDTOFromJson(json);
Map<dynamic, dynamic> toJson() => _$DeliveryOptionDTOToJson(this);
}
@JsonSerializable(fieldRename: FieldRename.snake)
class DeliveryDTO {
DeliveryDTO({
required this.internalReceiptNo,
required this.specialAggreements,
required this.currency,
required this.notes,
required this.totalPrice,
required this.prepayment,
required this.paymentAtDelivery,
required this.desiredTime,
required this.contactPerson,
required this.articles,
required this.totalNetValue,
required this.totalGrossValue,
required this.images,
required this.customer,
required this.finishedTime,
required this.note,
required this.state,
required this.payment,
required this.carId,
required this.options,
});
String internalReceiptNo;
String? specialAggreements;
CustomerDTO customer;
String totalPrice;
String desiredTime;
String totalGrossValue;
String totalNetValue;
ContactPersonDTO contactPerson;
String? currency;
List<ArticleDTO> articles;
String note;
String finishedTime;
String carId;
String state;
String prepayment;
String paymentAtDelivery;
DiscountDTO? discount;
PaymentMethodDTO payment;
List<NoteDTO> notes;
List<ImageDTO> images;
List<DeliveryOptionDTO> options;
factory DeliveryDTO.fromJson(Map<String, dynamic> json) =>
_$DeliveryDTOFromJson(json);
Map<dynamic, dynamic> toJson() => _$DeliveryDTOToJson(this);
}

89
lib/dto/delivery.g.dart Normal file
View File

@ -0,0 +1,89 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'delivery.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
DeliveryOptionDTO _$DeliveryOptionDTOFromJson(Map<String, dynamic> json) =>
DeliveryOptionDTO(
numerical: json['numerical'] as bool,
value: json['value'] as String,
display: json['display'] as String,
key: json['key'] as String,
);
Map<String, dynamic> _$DeliveryOptionDTOToJson(DeliveryOptionDTO instance) =>
<String, dynamic>{
'numerical': instance.numerical,
'value': instance.value,
'display': instance.display,
'key': instance.key,
};
DeliveryDTO _$DeliveryDTOFromJson(Map<String, dynamic> json) => DeliveryDTO(
internalReceiptNo: json['internal_receipt_no'] as String,
specialAggreements: json['special_aggreements'] as String?,
currency: json['currency'] as String?,
notes:
(json['notes'] as List<dynamic>)
.map((e) => NoteDTO.fromJson(e as Map<String, dynamic>))
.toList(),
totalPrice: json['total_price'] as String,
prepayment: json['prepayment'] as String,
paymentAtDelivery: json['payment_at_delivery'] as String,
desiredTime: json['desired_time'] as String,
contactPerson: ContactPersonDTO.fromJson(
json['contact_person'] as Map<String, dynamic>,
),
articles:
(json['articles'] as List<dynamic>)
.map((e) => ArticleDTO.fromJson(e as Map<String, dynamic>))
.toList(),
totalNetValue: json['total_net_value'] as String,
totalGrossValue: json['total_gross_value'] as String,
images:
(json['images'] as List<dynamic>)
.map((e) => ImageDTO.fromJson(e as Map<String, dynamic>))
.toList(),
customer: CustomerDTO.fromJson(json['customer'] as Map<String, dynamic>),
finishedTime: json['finished_time'] as String,
note: json['note'] as String,
state: json['state'] as String,
payment: PaymentMethodDTO.fromJson(json['payment'] as Map<String, dynamic>),
carId: json['car_id'] as String,
options:
(json['options'] as List<dynamic>)
.map((e) => DeliveryOptionDTO.fromJson(e as Map<String, dynamic>))
.toList(),
)
..discount =
json['discount'] == null
? null
: DiscountDTO.fromJson(json['discount'] as Map<String, dynamic>);
Map<String, dynamic> _$DeliveryDTOToJson(DeliveryDTO instance) =>
<String, dynamic>{
'internal_receipt_no': instance.internalReceiptNo,
'special_aggreements': instance.specialAggreements,
'customer': instance.customer,
'total_price': instance.totalPrice,
'desired_time': instance.desiredTime,
'total_gross_value': instance.totalGrossValue,
'total_net_value': instance.totalNetValue,
'contact_person': instance.contactPerson,
'currency': instance.currency,
'articles': instance.articles,
'note': instance.note,
'finished_time': instance.finishedTime,
'car_id': instance.carId,
'state': instance.state,
'prepayment': instance.prepayment,
'payment_at_delivery': instance.paymentAtDelivery,
'discount': instance.discount,
'payment': instance.payment,
'notes': instance.notes,
'images': instance.images,
'options': instance.options,
};

View File

@ -0,0 +1,22 @@
import 'delivery.dart';
import 'driver.dart';
import 'package:json_annotation/json_annotation.dart';
part 'delivery_response.g.dart';
@JsonSerializable(fieldRename: FieldRename.snake)
class DeliveryResponseDTO {
DeliveryResponseDTO(
{required this.deliveries,
required this.driver,
required this.discountArticleNumber});
List<DeliveryDTO> deliveries;
DriverDTO driver;
String discountArticleNumber;
factory DeliveryResponseDTO.fromJson(Map<String, dynamic> json) =>
_$DeliveryResponseDTOFromJson(json);
Map<dynamic, dynamic> toJson() => _$DeliveryResponseDTOToJson(this);
}

View File

@ -0,0 +1,25 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'delivery_response.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
DeliveryResponseDTO _$DeliveryResponseDTOFromJson(Map<String, dynamic> json) =>
DeliveryResponseDTO(
deliveries:
(json['deliveries'] as List<dynamic>)
.map((e) => DeliveryDTO.fromJson(e as Map<String, dynamic>))
.toList(),
driver: DriverDTO.fromJson(json['driver'] as Map<String, dynamic>),
discountArticleNumber: json['discount_article_number'] as String,
);
Map<String, dynamic> _$DeliveryResponseDTOToJson(
DeliveryResponseDTO instance,
) => <String, dynamic>{
'deliveries': instance.deliveries,
'driver': instance.driver,
'discount_article_number': instance.discountArticleNumber,
};

View File

@ -0,0 +1,55 @@
import 'package:hl_lieferservice/model/delivery.dart';
import 'package:json_annotation/json_annotation.dart';
part 'delivery_update.g.dart';
@JsonSerializable(fieldRename: FieldRename.snake)
class DeliveryUpdateDTO {
DeliveryUpdateDTO({
required this.deliveryId,
this.note,
this.finishedDate,
this.discount,
this.selectedPaymentMethodId,
this.state,
this.carId,
});
String deliveryId;
String? note;
String? finishedDate;
String? state;
int? carId;
String? selectedPaymentMethodId;
double? discount;
factory DeliveryUpdateDTO.fromJson(Map<String, dynamic> json) =>
_$DeliveryUpdateDTOFromJson(json);
factory DeliveryUpdateDTO.fromEntity(Delivery delivery) {
String state = "";
switch (delivery.state) {
case DeliveryState.finished:
state = "geliefert";
break;
case DeliveryState.ongoing:
state = "laufend";
break;
case DeliveryState.onhold:
state = "vertagt";
break;
case DeliveryState.canceled:
state = "abgebrochen";
break;
}
return DeliveryUpdateDTO(
deliveryId: delivery.id,
state: state,
carId: delivery.carId,
);
}
Map<dynamic, dynamic> toJson() => _$DeliveryUpdateDTOToJson(this);
}

View File

@ -0,0 +1,29 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'delivery_update.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
DeliveryUpdateDTO _$DeliveryUpdateDTOFromJson(Map<String, dynamic> json) =>
DeliveryUpdateDTO(
deliveryId: json['delivery_id'] as String,
note: json['note'] as String?,
finishedDate: json['finished_date'] as String?,
discount: (json['discount'] as num?)?.toDouble(),
selectedPaymentMethodId: json['selected_payment_method_id'] as String?,
state: json['state'] as String?,
carId: (json['car_id'] as num?)?.toInt(),
);
Map<String, dynamic> _$DeliveryUpdateDTOToJson(DeliveryUpdateDTO instance) =>
<String, dynamic>{
'delivery_id': instance.deliveryId,
'note': instance.note,
'finished_date': instance.finishedDate,
'state': instance.state,
'car_id': instance.carId,
'selected_payment_method_id': instance.selectedPaymentMethodId,
'discount': instance.discount,
};

View File

@ -0,0 +1,17 @@
import 'package:json_annotation/json_annotation.dart';
part 'delivery_update_response.g.dart';
@JsonSerializable(fieldRename: FieldRename.snake)
class DeliveryUpdateResponseDTO {
DeliveryUpdateResponseDTO(
{required this.message, required this.code});
final String code;
final String message;
factory DeliveryUpdateResponseDTO.fromJson(Map<String, dynamic> json) =>
_$DeliveryUpdateResponseDTOFromJson(json);
Map<dynamic, dynamic> toJson() => _$DeliveryUpdateResponseDTOToJson(this);
}

View File

@ -0,0 +1,18 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'delivery_update_response.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
DeliveryUpdateResponseDTO _$DeliveryUpdateResponseDTOFromJson(
Map<String, dynamic> json,
) => DeliveryUpdateResponseDTO(
message: json['message'] as String,
code: json['code'] as String,
);
Map<String, dynamic> _$DeliveryUpdateResponseDTOToJson(
DeliveryUpdateResponseDTO instance,
) => <String, dynamic>{'code': instance.code, 'message': instance.message};

15
lib/dto/discount.dart Normal file
View File

@ -0,0 +1,15 @@
import 'article.dart';
import 'package:json_annotation/json_annotation.dart';
part 'discount.g.dart';
@JsonSerializable(fieldRename: FieldRename.snake)
class DiscountDTO {
DiscountDTO({required this.note, required this.noteId, required this.article});
String? note;
String? noteId;
ArticleDTO article;
factory DiscountDTO.fromJson(Map<String, dynamic> json) => _$DiscountDTOFromJson(json);
Map<dynamic, dynamic> toJson() => _$DiscountDTOToJson(this);
}

20
lib/dto/discount.g.dart Normal file
View File

@ -0,0 +1,20 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'discount.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
DiscountDTO _$DiscountDTOFromJson(Map<String, dynamic> json) => DiscountDTO(
note: json['note'] as String?,
noteId: json['note_id'] as String?,
article: ArticleDTO.fromJson(json['article'] as Map<String, dynamic>),
);
Map<String, dynamic> _$DiscountDTOToJson(DiscountDTO instance) =>
<String, dynamic>{
'note': instance.note,
'note_id': instance.noteId,
'article': instance.article,
};

18
lib/dto/discount_add.dart Normal file
View File

@ -0,0 +1,18 @@
import 'package:json_annotation/json_annotation.dart';
part 'discount_add.g.dart';
@JsonSerializable(fieldRename: FieldRename.snake)
class DiscountAddDTO {
DiscountAddDTO(
{required this.note, required this.deliveryId, required this.discount});
String note;
String deliveryId;
int discount;
factory DiscountAddDTO.fromJson(Map<String, dynamic> json) =>
_$DiscountAddDTOFromJson(json);
Map<dynamic, dynamic> toJson() => _$DiscountAddDTOToJson(this);
}

View File

@ -0,0 +1,21 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'discount_add.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
DiscountAddDTO _$DiscountAddDTOFromJson(Map<String, dynamic> json) =>
DiscountAddDTO(
note: json['note'] as String,
deliveryId: json['delivery_id'] as String,
discount: (json['discount'] as num).toInt(),
);
Map<String, dynamic> _$DiscountAddDTOToJson(DiscountAddDTO instance) =>
<String, dynamic>{
'note': instance.note,
'delivery_id': instance.deliveryId,
'discount': instance.discount,
};

View File

@ -0,0 +1,63 @@
import 'article.dart';
import 'basic_response.dart';
import 'package:json_annotation/json_annotation.dart';
part 'discount_add_response.g.dart';
@JsonSerializable(fieldRename: FieldRename.snake)
class PriceInformation {
PriceInformation({required this.net, required this.gross});
double net;
double gross;
factory PriceInformation.fromJson(Map<String, dynamic> json) =>
_$PriceInformationFromJson(json);
Map<dynamic, dynamic> toJson() => _$PriceInformationToJson(this);
}
@JsonSerializable(fieldRename: FieldRename.snake)
class NoteInformation {
NoteInformation({required this.rowId, required this.noteDescription});
String rowId;
String noteDescription;
factory NoteInformation.fromJson(Map<String, dynamic> json) =>
_$NoteInformationFromJson(json);
Map<dynamic, dynamic> toJson() => _$NoteInformationToJson(this);
}
@JsonSerializable(fieldRename: FieldRename.snake)
class UpdatedValues {
UpdatedValues(
{required this.discount,
required this.receipt,
required this.article,
required this.note});
PriceInformation discount;
PriceInformation receipt;
NoteInformation note;
ArticleDTO article;
factory UpdatedValues.fromJson(Map<String, dynamic> json) =>
_$UpdatedValuesFromJson(json);
Map<dynamic, dynamic> toJson() => _$UpdatedValuesToJson(this);
}
@JsonSerializable(fieldRename: FieldRename.snake)
class DiscountAddResponseDTO extends BasicResponseDTO {
DiscountAddResponseDTO(
{required this.values, required super.succeeded, required super.message});
UpdatedValues values;
factory DiscountAddResponseDTO.fromJson(Map<String, dynamic> json) =>
_$DiscountAddResponseDTOFromJson(json);
Map<dynamic, dynamic> toJson() => _$DiscountAddResponseDTOToJson(this);
}

View File

@ -0,0 +1,61 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'discount_add_response.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
PriceInformation _$PriceInformationFromJson(Map<String, dynamic> json) =>
PriceInformation(
net: (json['net'] as num).toDouble(),
gross: (json['gross'] as num).toDouble(),
);
Map<String, dynamic> _$PriceInformationToJson(PriceInformation instance) =>
<String, dynamic>{'net': instance.net, 'gross': instance.gross};
NoteInformation _$NoteInformationFromJson(Map<String, dynamic> json) =>
NoteInformation(
rowId: json['row_id'] as String,
noteDescription: json['note_description'] as String,
);
Map<String, dynamic> _$NoteInformationToJson(NoteInformation instance) =>
<String, dynamic>{
'row_id': instance.rowId,
'note_description': instance.noteDescription,
};
UpdatedValues _$UpdatedValuesFromJson(
Map<String, dynamic> json,
) => UpdatedValues(
discount: PriceInformation.fromJson(json['discount'] as Map<String, dynamic>),
receipt: PriceInformation.fromJson(json['receipt'] as Map<String, dynamic>),
article: ArticleDTO.fromJson(json['article'] as Map<String, dynamic>),
note: NoteInformation.fromJson(json['note'] as Map<String, dynamic>),
);
Map<String, dynamic> _$UpdatedValuesToJson(UpdatedValues instance) =>
<String, dynamic>{
'discount': instance.discount,
'receipt': instance.receipt,
'note': instance.note,
'article': instance.article,
};
DiscountAddResponseDTO _$DiscountAddResponseDTOFromJson(
Map<String, dynamic> json,
) => DiscountAddResponseDTO(
values: UpdatedValues.fromJson(json['values'] as Map<String, dynamic>),
succeeded: json['succeeded'] as bool,
message: json['message'] as String,
);
Map<String, dynamic> _$DiscountAddResponseDTOToJson(
DiscountAddResponseDTO instance,
) => <String, dynamic>{
'succeeded': instance.succeeded,
'message': instance.message,
'values': instance.values,
};

View File

@ -0,0 +1,15 @@
import 'package:json_annotation/json_annotation.dart';
part 'discount_remove.g.dart';
@JsonSerializable(fieldRename: FieldRename.snake)
class DiscountRemoveDTO {
DiscountRemoveDTO(
{required this.deliveryId});
String deliveryId;
factory DiscountRemoveDTO.fromJson(Map<String, dynamic> json) =>
_$DiscountRemoveDTOFromJson(json);
Map<dynamic, dynamic> toJson() => _$DiscountRemoveDTOToJson(this);
}

View File

@ -0,0 +1,13 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'discount_remove.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
DiscountRemoveDTO _$DiscountRemoveDTOFromJson(Map<String, dynamic> json) =>
DiscountRemoveDTO(deliveryId: json['delivery_id'] as String);
Map<String, dynamic> _$DiscountRemoveDTOToJson(DiscountRemoveDTO instance) =>
<String, dynamic>{'delivery_id': instance.deliveryId};

View File

@ -0,0 +1,22 @@
import 'basic_response.dart';
import 'package:json_annotation/json_annotation.dart';
import 'discount_add_response.dart';
part 'discount_remove_response.g.dart';
@JsonSerializable(fieldRename: FieldRename.snake)
class DiscountRemoveResponseDTO extends BasicResponseDTO {
DiscountRemoveResponseDTO(
{
required this.receipt,
required super.succeeded,
required super.message});
PriceInformation receipt;
factory DiscountRemoveResponseDTO.fromJson(Map<String, dynamic> json) =>
_$DiscountRemoveResponseDTOFromJson(json);
Map<dynamic, dynamic> toJson() => _$DiscountRemoveResponseDTOToJson(this);
}

View File

@ -0,0 +1,23 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'discount_remove_response.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
DiscountRemoveResponseDTO _$DiscountRemoveResponseDTOFromJson(
Map<String, dynamic> json,
) => DiscountRemoveResponseDTO(
receipt: PriceInformation.fromJson(json['receipt'] as Map<String, dynamic>),
succeeded: json['succeeded'] as bool,
message: json['message'] as String,
);
Map<String, dynamic> _$DiscountRemoveResponseDTOToJson(
DiscountRemoveResponseDTO instance,
) => <String, dynamic>{
'succeeded': instance.succeeded,
'message': instance.message,
'receipt': instance.receipt,
};

View File

@ -0,0 +1,18 @@
import 'package:json_annotation/json_annotation.dart';
part 'discount_update.g.dart';
@JsonSerializable(fieldRename: FieldRename.snake)
class DiscountUpdateDTO {
DiscountUpdateDTO(
{required this.note, required this.deliveryId, required this.discount});
String? note;
String deliveryId;
int? discount;
factory DiscountUpdateDTO.fromJson(Map<String, dynamic> json) =>
_$DiscountUpdateDTOFromJson(json);
Map<dynamic, dynamic> toJson() => _$DiscountUpdateDTOToJson(this);
}

View File

@ -0,0 +1,21 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'discount_update.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
DiscountUpdateDTO _$DiscountUpdateDTOFromJson(Map<String, dynamic> json) =>
DiscountUpdateDTO(
note: json['note'] as String?,
deliveryId: json['delivery_id'] as String,
discount: (json['discount'] as num?)?.toInt(),
);
Map<String, dynamic> _$DiscountUpdateDTOToJson(DiscountUpdateDTO instance) =>
<String, dynamic>{
'note': instance.note,
'delivery_id': instance.deliveryId,
'discount': instance.discount,
};

View File

@ -0,0 +1,22 @@
import 'package:hl_lieferservice/dto/article.dart';
import 'basic_response.dart';
import 'discount_add_response.dart';
import 'package:json_annotation/json_annotation.dart';
part 'discount_update_response.g.dart';
@JsonSerializable(fieldRename: FieldRename.snake)
class DiscountUpdateResponseDTO extends BasicResponseDTO {
DiscountUpdateResponseDTO(
{required this.values,
required super.succeeded,
required super.message});
UpdatedValues? values;
factory DiscountUpdateResponseDTO.fromJson(Map<String, dynamic> json) =>
_$DiscountUpdateResponseDTOFromJson(json);
Map<dynamic, dynamic> toJson() => _$DiscountUpdateResponseDTOToJson(this);
}

View File

@ -0,0 +1,26 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'discount_update_response.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
DiscountUpdateResponseDTO _$DiscountUpdateResponseDTOFromJson(
Map<String, dynamic> json,
) => DiscountUpdateResponseDTO(
values:
json['values'] == null
? null
: UpdatedValues.fromJson(json['values'] as Map<String, dynamic>),
succeeded: json['succeeded'] as bool,
message: json['message'] as String,
);
Map<String, dynamic> _$DiscountUpdateResponseDTOToJson(
DiscountUpdateResponseDTO instance,
) => <String, dynamic>{
'succeeded': instance.succeeded,
'message': instance.message,
'values': instance.values,
};

16
lib/dto/driver.dart Normal file
View File

@ -0,0 +1,16 @@
import 'car.dart';
import 'package:json_annotation/json_annotation.dart';
part 'driver.g.dart';
@JsonSerializable(fieldRename: FieldRename.snake)
class DriverDTO {
DriverDTO({required this.id, required this.name, required this.salutation, required this.cars});
String id;
String name;
String salutation;
List<CarDTO> cars;
factory DriverDTO.fromJson(Map<String, dynamic> json) => _$DriverDTOFromJson(json);
Map<dynamic, dynamic> toJson() => _$DriverDTOToJson(this);
}

24
lib/dto/driver.g.dart Normal file
View File

@ -0,0 +1,24 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'driver.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
DriverDTO _$DriverDTOFromJson(Map<String, dynamic> json) => DriverDTO(
id: json['id'] as String,
name: json['name'] as String,
salutation: json['salutation'] as String,
cars:
(json['cars'] as List<dynamic>)
.map((e) => CarDTO.fromJson(e as Map<String, dynamic>))
.toList(),
);
Map<String, dynamic> _$DriverDTOToJson(DriverDTO instance) => <String, dynamic>{
'id': instance.id,
'name': instance.name,
'salutation': instance.salutation,
'cars': instance.cars,
};

18
lib/dto/image.dart Normal file
View File

@ -0,0 +1,18 @@
import 'package:json_annotation/json_annotation.dart';
part 'image.g.dart';
@JsonSerializable(fieldRename: FieldRename.snake)
class ImageDTO {
ImageDTO(
{required this.url, required this.name, required this.oid});
String url;
String name;
String oid;
factory ImageDTO.fromJson(Map<String, dynamic> json) =>
_$ImageDTOFromJson(json);
Map<dynamic, dynamic> toJson() => _$ImageDTOToJson(this);
}

19
lib/dto/image.g.dart Normal file
View File

@ -0,0 +1,19 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'image.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
ImageDTO _$ImageDTOFromJson(Map<String, dynamic> json) => ImageDTO(
url: json['url'] as String,
name: json['name'] as String,
oid: json['oid'] as String,
);
Map<String, dynamic> _$ImageDTOToJson(ImageDTO instance) => <String, dynamic>{
'url': instance.url,
'name': instance.name,
'oid': instance.oid,
};

16
lib/dto/note.dart Normal file
View File

@ -0,0 +1,16 @@
import 'package:json_annotation/json_annotation.dart';
part 'note.g.dart';
@JsonSerializable(fieldRename: FieldRename.snake)
class NoteDTO {
NoteDTO(
{required this.id,
required this.note});
final String id;
final String note;
factory NoteDTO.fromJson(Map<String, dynamic> json) => _$NoteDTOFromJson(json);
Map<dynamic, dynamic> toJson() => _$NoteDTOToJson(this);
}

15
lib/dto/note.g.dart Normal file
View File

@ -0,0 +1,15 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'note.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
NoteDTO _$NoteDTOFromJson(Map<String, dynamic> json) =>
NoteDTO(id: json['id'] as String, note: json['note'] as String);
Map<String, dynamic> _$NoteDTOToJson(NoteDTO instance) => <String, dynamic>{
'id': instance.id,
'note': instance.note,
};

View File

@ -0,0 +1,16 @@
import 'package:hl_lieferservice/dto/basic_response.dart';
import 'package:hl_lieferservice/dto/note.dart';
import 'package:json_annotation/json_annotation.dart';
part 'note_add_response.g.dart';
@JsonSerializable(fieldRename: FieldRename.snake)
class NoteAddResponseDTO extends BasicResponseDTO {
NoteAddResponseDTO(
{required this.note, required super.succeeded, required super.message});
final NoteDTO? note;
factory NoteAddResponseDTO.fromJson(Map<String, dynamic> json) => _$NoteAddResponseDTOFromJson(json);
Map<dynamic, dynamic> toJson() => _$NoteAddResponseDTOToJson(this);
}

View File

@ -0,0 +1,24 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'note_add_response.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
NoteAddResponseDTO _$NoteAddResponseDTOFromJson(Map<String, dynamic> json) =>
NoteAddResponseDTO(
note:
json['note'] == null
? null
: NoteDTO.fromJson(json['note'] as Map<String, dynamic>),
succeeded: json['succeeded'] as bool,
message: json['message'] as String,
);
Map<String, dynamic> _$NoteAddResponseDTOToJson(NoteAddResponseDTO instance) =>
<String, dynamic>{
'succeeded': instance.succeeded,
'message': instance.message,
'note': instance.note,
};

View File

@ -0,0 +1,19 @@
import 'note.dart';
import 'package:json_annotation/json_annotation.dart';
part 'note_get_response.g.dart';
@JsonSerializable(fieldRename: FieldRename.snake)
class NoteGetResponseDTO {
NoteGetResponseDTO(
{required this.notes, required this.succeeded, required this.message});
final List<NoteDTO> notes;
final bool succeeded;
final String message;
factory NoteGetResponseDTO.fromJson(Map<String, dynamic> json) =>
_$NoteGetResponseDTOFromJson(json);
Map<dynamic, dynamic> toJson() => _$NoteGetResponseDTOToJson(this);
}

View File

@ -0,0 +1,24 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'note_get_response.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
NoteGetResponseDTO _$NoteGetResponseDTOFromJson(Map<String, dynamic> json) =>
NoteGetResponseDTO(
notes:
(json['notes'] as List<dynamic>)
.map((e) => NoteDTO.fromJson(e as Map<String, dynamic>))
.toList(),
succeeded: json['succeeded'] as bool,
message: json['message'] as String,
);
Map<String, dynamic> _$NoteGetResponseDTOToJson(NoteGetResponseDTO instance) =>
<String, dynamic>{
'notes': instance.notes,
'succeeded': instance.succeeded,
'message': instance.message,
};

View File

@ -0,0 +1,18 @@
import 'package:json_annotation/json_annotation.dart';
part 'note_template.g.dart';
@JsonSerializable(fieldRename: FieldRename.snake)
class NoteTemplateDTO {
NoteTemplateDTO(
{required this.language,
required this.title,
required this.note});
final String note;
final String language;
final String title;
factory NoteTemplateDTO.fromJson(Map<String, dynamic> json) => _$NoteTemplateDTOFromJson(json);
Map<dynamic, dynamic> toJson() => _$NoteTemplateDTOToJson(this);
}

View File

@ -0,0 +1,21 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'note_template.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
NoteTemplateDTO _$NoteTemplateDTOFromJson(Map<String, dynamic> json) =>
NoteTemplateDTO(
language: json['language'] as String,
title: json['title'] as String,
note: json['note'] as String,
);
Map<String, dynamic> _$NoteTemplateDTOToJson(NoteTemplateDTO instance) =>
<String, dynamic>{
'note': instance.note,
'language': instance.language,
'title': instance.title,
};

View File

@ -0,0 +1,19 @@
import 'note_template.dart';
import 'package:json_annotation/json_annotation.dart';
part 'note_template_response.g.dart';
@JsonSerializable(fieldRename: FieldRename.snake)
class NoteTemplateResponseDTO {
NoteTemplateResponseDTO(
{required this.notes, required this.succeeded, required this.message});
final List<NoteTemplateDTO> notes;
final bool succeeded;
final String message;
factory NoteTemplateResponseDTO.fromJson(Map<String, dynamic> json) =>
_$NoteTemplateResponseDTOFromJson(json);
Map<dynamic, dynamic> toJson() => _$NoteTemplateResponseDTOToJson(this);
}

View File

@ -0,0 +1,26 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'note_template_response.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
NoteTemplateResponseDTO _$NoteTemplateResponseDTOFromJson(
Map<String, dynamic> json,
) => NoteTemplateResponseDTO(
notes:
(json['notes'] as List<dynamic>)
.map((e) => NoteTemplateDTO.fromJson(e as Map<String, dynamic>))
.toList(),
succeeded: json['succeeded'] as bool,
message: json['message'] as String,
);
Map<String, dynamic> _$NoteTemplateResponseDTOToJson(
NoteTemplateResponseDTO instance,
) => <String, dynamic>{
'notes': instance.notes,
'succeeded': instance.succeeded,
'message': instance.message,
};

17
lib/dto/payment.dart Normal file
View File

@ -0,0 +1,17 @@
import 'package:json_annotation/json_annotation.dart';
part 'payment.g.dart';
@JsonSerializable()
class PaymentMethodDTO {
PaymentMethodDTO(this.id,
{required this.description,
required this.shortCode});
final String id;
final String description;
final String shortCode;
factory PaymentMethodDTO.fromJson(Map<String, dynamic> json) => _$PaymentMethodDTOFromJson(json);
Map<dynamic, dynamic> toJson() => _$PaymentMethodDTOToJson(this);
}

21
lib/dto/payment.g.dart Normal file
View File

@ -0,0 +1,21 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'payment.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
PaymentMethodDTO _$PaymentMethodDTOFromJson(Map<String, dynamic> json) =>
PaymentMethodDTO(
json['id'] as String,
description: json['description'] as String,
shortCode: json['shortCode'] as String,
);
Map<String, dynamic> _$PaymentMethodDTOToJson(PaymentMethodDTO instance) =>
<String, dynamic>{
'id': instance.id,
'description': instance.description,
'shortCode': instance.shortCode,
};

14
lib/dto/payments.dart Normal file
View File

@ -0,0 +1,14 @@
import 'payment.dart';
import 'package:json_annotation/json_annotation.dart';
part 'payments.g.dart';
@JsonSerializable()
class PaymentMethodListDTO {
PaymentMethodListDTO({required this.paymentMethods});
final List<PaymentMethodDTO> paymentMethods;
factory PaymentMethodListDTO.fromJson(Map<String, dynamic> json) => _$PaymentMethodListDTOFromJson(json);
Map<dynamic, dynamic> toJson() => _$PaymentMethodListDTOToJson(this);
}

20
lib/dto/payments.g.dart Normal file
View File

@ -0,0 +1,20 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'payments.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
PaymentMethodListDTO _$PaymentMethodListDTOFromJson(
Map<String, dynamic> json,
) => PaymentMethodListDTO(
paymentMethods:
(json['paymentMethods'] as List<dynamic>)
.map((e) => PaymentMethodDTO.fromJson(e as Map<String, dynamic>))
.toList(),
);
Map<String, dynamic> _$PaymentMethodListDTOToJson(
PaymentMethodListDTO instance,
) => <String, dynamic>{'paymentMethods': instance.paymentMethods};

15
lib/dto/scan.dart Normal file
View File

@ -0,0 +1,15 @@
import 'package:json_annotation/json_annotation.dart';
part 'scan.g.dart';
@JsonSerializable(fieldRename: FieldRename.snake)
class ScanDTO {
ScanDTO({required this.internalId});
String internalId;
factory ScanDTO.fromJson(Map<String, dynamic> json) =>
_$ScanDTOFromJson(json);
Map<dynamic, dynamic> toJson() => _$ScanDTOToJson(this);
}

14
lib/dto/scan.g.dart Normal file
View File

@ -0,0 +1,14 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'scan.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
ScanDTO _$ScanDTOFromJson(Map<String, dynamic> json) =>
ScanDTO(internalId: json['internal_id'] as String);
Map<String, dynamic> _$ScanDTOToJson(ScanDTO instance) => <String, dynamic>{
'internal_id': instance.internalId,
};

View File

@ -0,0 +1,18 @@
import 'package:json_annotation/json_annotation.dart';
part 'scan_response.g.dart';
@JsonSerializable(fieldRename: FieldRename.snake)
class ScanResponseDTO {
ScanResponseDTO(
{required this.message, required this.succeeded, required this.noteId});
final bool succeeded;
final String message;
final String? noteId;
factory ScanResponseDTO.fromJson(Map<String, dynamic> json) =>
_$ScanResponseDTOFromJson(json);
Map<dynamic, dynamic> toJson() => _$ScanResponseDTOToJson(this);
}

View File

@ -0,0 +1,21 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'scan_response.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
ScanResponseDTO _$ScanResponseDTOFromJson(Map<String, dynamic> json) =>
ScanResponseDTO(
message: json['message'] as String,
succeeded: json['succeeded'] as bool,
noteId: json['note_id'] as String?,
);
Map<String, dynamic> _$ScanResponseDTOToJson(ScanResponseDTO instance) =>
<String, dynamic>{
'succeeded': instance.succeeded,
'message': instance.message,
'note_id': instance.noteId,
};

View File

@ -0,0 +1,28 @@
import 'package:hl_lieferservice/feature/authentication/bloc/auth_event.dart';
import 'package:hl_lieferservice/feature/authentication/bloc/auth_state.dart';
import 'package:hl_lieferservice/repository/user_repository.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:hl_lieferservice/widget/operations/bloc/operation_bloc.dart';
import 'package:hl_lieferservice/widget/operations/bloc/operation_event.dart';
class AuthBloc extends Bloc<AuthEvent, AuthState> {
UserRepository repository;
OperationBloc operationBloc;
AuthBloc({required this.repository, required this.operationBloc})
: super(Unauthenticated()) {
on<Authenticate>(_auth);
on<Logout>(_logout);
}
Future<void> _auth(Authenticate event, Emitter<AuthState> emit) async {
operationBloc.add(LoadOperation());
await Future.delayed(Duration(seconds: 5));
emit(Authenticated(teamId: event.username));
operationBloc.add(FinishOperation());
}
Future<void> _logout(Logout event, Emitter<AuthState> emit) async {
emit(Unauthenticated());
}
}

View File

@ -0,0 +1,14 @@
abstract class AuthEvent {}
class Authenticate extends AuthEvent {
String username;
String password;
Authenticate({required this.username, required this.password});
}
class Logout extends AuthEvent {
String username;
Logout({required this.username});
}

View File

@ -0,0 +1,9 @@
abstract class AuthState {}
class Unauthenticated extends AuthState {}
class Authenticated extends AuthState {
String teamId;
Authenticated({required this.teamId});
}

View File

@ -0,0 +1,25 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:hl_lieferservice/feature/authentication/presentation/login_page.dart';
import '../bloc/auth_bloc.dart';
import '../bloc/auth_state.dart';
class LoginEnforcer extends StatelessWidget {
final Widget child;
const LoginEnforcer({super.key, required this.child});
@override
Widget build(BuildContext context) {
return BlocBuilder<AuthBloc, AuthState>(
builder: (context, state) {
if (state is Authenticated) {
return child;
}
return LoginPage();
},
);
}
}

View File

@ -0,0 +1,121 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:hl_lieferservice/feature/authentication/bloc/auth_event.dart';
import 'package:hl_lieferservice/widget/operations/bloc/operation_bloc.dart';
import 'package:hl_lieferservice/widget/operations/bloc/operation_event.dart';
import 'package:hl_lieferservice/widget/operations/presentation/operation_view_enforcer.dart';
import '../bloc/auth_bloc.dart';
class LoginPage extends StatefulWidget {
const LoginPage({super.key});
@override
State<StatefulWidget> createState() => _LoginPageState();
}
class _LoginPageState extends State<LoginPage> {
final _loginFormKey = GlobalKey<FormState>();
final TextEditingController _passwordEditingController =
TextEditingController();
final TextEditingController _userIdEditingController =
TextEditingController();
bool _isEmpty = false;
void onChanged(String value) {
setState(() {
_isEmpty = value.isEmpty;
});
}
void _onPressLogin(BuildContext context) async {
if (context.mounted) {
context.read<AuthBloc>().add(
Authenticate(
username: _userIdEditingController.text,
password: _passwordEditingController.text,
),
);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.all(50),
child: Column(
children: [
Image.asset(
"assets/holzleitner_Logo_2017_RZ_transparent.png",
),
const Padding(
padding: EdgeInsets.only(top: 20),
child: Text(
"Auslieferservice",
style: TextStyle(
fontWeight: FontWeight.w400,
fontSize: 20,
),
),
),
],
),
),
Form(
key: _loginFormKey,
child: FractionallySizedBox(
widthFactor: 0.8,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Padding(
padding: const EdgeInsets.only(bottom: 20),
child: TextFormField(
decoration: const InputDecoration(
labelText: "Personalnummer",
border: OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(10.0),
),
),
),
controller: _userIdEditingController,
onChanged: onChanged,
),
),
TextFormField(
decoration: const InputDecoration(
labelText: "Passwort",
border: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(10.0)),
),
),
controller: _passwordEditingController,
obscureText: true,
onChanged: onChanged,
),
Padding(
padding: const EdgeInsets.only(top: 15, bottom: 15),
child: OutlinedButton(
onPressed:
!_isEmpty ? () => _onPressLogin(context) : null,
child: const Text("Anmelden"),
),
),
],
),
),
),
],
),
),
);
}
}

View File

@ -0,0 +1,106 @@
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:hl_lieferservice/feature/cars/repository/cars_repository.dart';
import 'package:hl_lieferservice/widget/operations/bloc/operation_bloc.dart';
import 'package:hl_lieferservice/widget/operations/bloc/operation_event.dart';
import '../../../model/car.dart';
import 'cars_event.dart';
import 'cars_state.dart';
class CarsBloc extends Bloc<CarEvents, CarsState> {
CarsRepository repository;
OperationBloc opBloc;
CarsBloc({required this.repository, required this.opBloc})
: super(CarsInitial()) {
on<CarAdd>(_carAdd);
on<CarEdit>(_carEdit);
on<CarDelete>(_carDelete);
on<CarLoad>(_carLoad);
}
Future<void> _carLoad(CarLoad event, Emitter<CarsState> emit) async {
try {
emit(CarsLoading());
List<Car> cars = await repository.getAll(event.teamId);
emit(CarsLoaded(cars: cars, teamId: event.teamId));
} catch (e) {
emit(CarsLoadingFailed());
}
}
Future<void> _carAdd(CarAdd event, Emitter<CarsState> emit) async {
final currentState = state;
try {
opBloc.add(LoadOperation());
Car newCar = await repository.add(event.teamId, event.plate);
if (currentState is CarsLoaded) {
emit(
currentState.copyWith(
cars: List<Car>.from(currentState.cars)..add(newCar),
),
);
}
opBloc.add(FinishOperation(message: "Auto erfolgreich hinzugefügt"));
} catch (e) {
opBloc.add(FailOperation(message: "Fehler beim Hinzufügen eines Autos"));
}
}
Future<void> _carEdit(CarEdit event, Emitter<CarsState> emit) async {
final currentState = state;
try {
opBloc.add(LoadOperation());
await repository.edit(event.teamId, event.newCar);
if (currentState is CarsLoaded) {
emit(
currentState.copyWith(
cars:
List<Car>.from(currentState.cars).map((car) {
if (car.id == event.newCar.id) {
return event.newCar;
}
return car;
}).toList(),
),
);
}
opBloc.add(FinishOperation(message: "Auto erfolgreich editiert"));
} catch (e) {
opBloc.add(FailOperation(message: "Fehler beim Editieren des Autos"));
}
}
Future<void> _carDelete(CarDelete event, Emitter<CarsState> emit) async {
final currentState = state;
try {
opBloc.add(LoadOperation());
await repository.delete(event.carId, event.teamId);
if (currentState is CarsLoaded) {
emit(
CarsLoaded(
cars: [
...currentState.cars.where(
(car) => car.id != int.parse(event.carId),
),
],
teamId: currentState.teamId,
),
);
}
opBloc.add(FinishOperation(message: "Auto erfolgreich gelöscht"));
} catch (e) {
opBloc.add(FailOperation(message: "Fehler beim Löschen des Autos"));
}
}
}

View File

@ -0,0 +1,30 @@
import '../../../model/car.dart';
abstract class CarEvents {}
class CarLoad extends CarEvents {
String teamId;
CarLoad({required this.teamId});
}
class CarEdit extends CarEvents {
Car newCar;
String teamId;
CarEdit({required this.newCar, required this.teamId});
}
class CarAdd extends CarEvents {
String teamId;
String plate;
CarAdd({required this.teamId, required this.plate});
}
class CarDelete extends CarEvents {
String carId;
String teamId;
CarDelete({required this.carId, required this.teamId});
}

View File

@ -0,0 +1,23 @@
import 'package:hl_lieferservice/model/car.dart';
abstract class CarsState {}
class CarsInitial extends CarsState {}
class CarsLoading extends CarsState {}
class CarsLoadingFailed extends CarsState {}
class CarsLoaded extends CarsState {
List<Car> cars;
String teamId;
CarsLoaded({required this.cars, required this.teamId});
CarsLoaded copyWith({List<Car>? cars, String? teamId}) {
return CarsLoaded(
cars: cars ?? this.cars,
teamId: teamId ?? this.teamId,
);
}
}

View File

@ -0,0 +1,75 @@
import 'package:flutter/material.dart';
import '../../../model/car.dart';
import 'car_dialog.dart';
class CarCard extends StatelessWidget {
final Car car;
final Function(Car car) onDelete;
final Function(Car car, String newName) onEdit;
const CarCard({
super.key,
required this.car,
required this.onEdit,
required this.onDelete,
});
@override
Widget build(BuildContext context) {
return Card(
child: Padding(
padding: const EdgeInsets.all(10),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
Padding(
padding: const EdgeInsets.only(left: 10),
child: Icon(
Icons.local_shipping,
size: 32,
color: Theme.of(context).primaryColor,
),
),
Padding(
padding: const EdgeInsets.only(left: 10),
child: Text(car.plate),
),
],
),
Row(
children: [
IconButton(
onPressed: () {
showDialog(
context: context,
builder: (context) {
return CarDialog(
onAction: (plate) {
onEdit(car, plate);
},
action: CarAction.edit,
initialPlateValue: car.plate,
);
},
);
},
icon: Icon(Icons.edit, color: Theme.of(context).primaryColor),
),
IconButton(
onPressed: () {
onDelete(car);
},
icon: const Icon(Icons.delete, color: Colors.redAccent),
),
],
),
],
),
),
);
}
}

View File

@ -0,0 +1,92 @@
import 'package:flutter/material.dart';
enum CarAction { edit, add }
class CarDialog extends StatefulWidget {
const CarDialog({
super.key,
required this.onAction,
this.action = CarAction.add,
this.initialPlateValue = "",
});
final CarAction action;
final String initialPlateValue;
final Function(String) onAction;
@override
State<StatefulWidget> createState() => _CarDialogState();
}
class _CarDialogState extends State<CarDialog> {
bool _isLoading = false;
final _formKey = GlobalKey<FormState>();
late final TextEditingController _plateController;
@override
void initState() {
super.initState();
_plateController = TextEditingController.fromValue(
TextEditingValue(text: widget.initialPlateValue),
);
}
@override
Widget build(BuildContext context) {
return AlertDialog(
content: SizedBox(
height: 120,
child: Form(
key: _formKey,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.only(bottom: 10.0),
child: Text(
"Fahrzeug ${widget.action == CarAction.add ? "hinzufügen" : "bearbeiten"}",
style: const TextStyle(
fontSize: 18.0,
fontWeight: FontWeight.bold,
),
),
),
TextFormField(
validator: (value) {
if (value == "") {
return "Kennzeichen darf nicht leer sein";
}
return null;
},
decoration: const InputDecoration(labelText: "Kennzeichen"),
controller: _plateController,
),
],
),
),
),
actions: [
OutlinedButton(
onPressed: () {
Navigator.of(context).pop();
},
child: const Text("Abbrechen"),
),
FilledButton(
onPressed: () {
if (_formKey.currentState!.validate()) {
widget.onAction(_plateController.text);
Navigator.of(context).pop();
}
},
child: Text(
widget.action == CarAction.add ? "Hinzufügen" : "Bearbeiten",
),
),
],
);
}
}

View File

@ -0,0 +1,78 @@
import 'package:hl_lieferservice/feature/cars/presentation/car_card.dart';
import '../../../model/car.dart';
import 'car_dialog.dart';
import 'package:flutter/material.dart';
class CarManagementOverview extends StatefulWidget {
final List<Car> cars;
final Function(String plate) onAdd;
final Function(String id) onDelete;
final Function(String id, String plate) onEdit;
const CarManagementOverview({
super.key,
required this.cars,
required this.onDelete,
required this.onEdit,
required this.onAdd,
});
@override
State<StatefulWidget> createState() => _CarManagementOverviewState();
}
class _CarManagementOverviewState extends State<CarManagementOverview> {
void _addCar() async {
showDialog(
context: context,
builder: (context) {
return CarDialog(onAction: widget.onAdd);
},
);
}
void _removeCar(Car car) async {
widget.onDelete(car.id.toString());
}
void _editCar(Car car, String newName) async {
widget.onEdit(car.id.toString(), newName);
}
Widget _buildCarOverview() {
return Padding(
padding: const EdgeInsets.all(10),
child: widget.cars.isEmpty ? const Center(child: Text("keine Fahrzeuge vorhanden")) : ListView.builder(
itemBuilder:
(context, index) => CarCard(
car: widget.cars[index],
onEdit: _editCar,
onDelete: _removeCar,
),
itemCount: widget.cars.length,
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(
"Fahrzeugverwaltung",
style: Theme.of(context).textTheme.headlineMedium,
),
),
floatingActionButton: FloatingActionButton(
onPressed: _addCar,
backgroundColor: Theme.of(context).primaryColor,
child: Icon(
Icons.add,
color: Theme.of(context).colorScheme.onSecondary,
),
),
body: _buildCarOverview(),
);
}
}

View File

@ -0,0 +1,82 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:hl_lieferservice/feature/authentication/bloc/auth_bloc.dart';
import 'package:hl_lieferservice/feature/authentication/bloc/auth_state.dart';
import 'package:hl_lieferservice/feature/cars/bloc/cars_bloc.dart';
import 'package:hl_lieferservice/feature/cars/bloc/cars_event.dart';
import 'package:hl_lieferservice/feature/cars/bloc/cars_state.dart';
import 'package:hl_lieferservice/feature/cars/presentation/car_management.dart';
import 'package:hl_lieferservice/model/car.dart';
class CarManagementPage extends StatefulWidget {
const CarManagementPage({super.key});
@override
State<StatefulWidget> createState() => _CarManagementPageState();
}
class _CarManagementPageState extends State<CarManagementPage> {
late Authenticated _authState;
@override
void initState() {
super.initState();
// Load cars
_authState = context.read<AuthBloc>().state as Authenticated;
context.read<CarsBloc>().add(CarLoad(teamId: _authState.teamId));
}
void _add(String plate) {
context.read<CarsBloc>().add(
CarAdd(teamId: _authState.teamId, plate: plate),
);
}
void _remove(String id) {
context.read<CarsBloc>().add(
CarDelete(carId: id, teamId: _authState.teamId),
);
}
void _edit(String id, String plate) {
context.read<CarsBloc>().add(
CarEdit(
newCar: Car(id: int.parse(id), plate: plate),
teamId: _authState.teamId,
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: BlocBuilder<CarsBloc, CarsState>(
builder: (context, state) {
debugPrint('BlocBuilder rebuilding with state: $state');
if (state is CarsLoading) {
return Center(child: const CircularProgressIndicator());
}
if (state is CarsLoaded) {
return CarManagementOverview(
cars: state.cars,
onEdit: _edit,
onAdd: _add,
onDelete: _remove,
);
}
if (state is CarsLoadingFailed) {
return Center(
child: const Text("Fahrzeuge konnten nicht geladen werden"),
);
}
return Container();
},
),
);
}
}

View File

@ -0,0 +1,25 @@
import 'package:hl_lieferservice/feature/cars/service/cars_service.dart';
import '../../../model/car.dart';
class CarsRepository {
CarService service;
CarsRepository({required this.service});
Future<List<Car>> getAll(String teamId) async {
return service.getCars(int.parse(teamId));
}
Future<void> delete(String carId, String teamId) async {
return service.removeCar(int.parse(carId), int.parse(teamId));
}
Future<void> edit(String teamId, Car newCar) async {
return service.editCar(newCar);
}
Future<Car> add(String teamId, String plate) async {
return service.addCar(plate, int.parse(teamId));
}
}

View File

@ -0,0 +1,146 @@
import 'dart:convert';
import 'package:docuframe/docuframe.dart' as df;
import 'package:flutter/cupertino.dart';
import 'package:hl_lieferservice/services/erpframe.dart';
import '../../../dto/basic_response.dart';
import '../../../dto/car_add.dart';
import '../../../dto/car_add_response.dart';
import '../../../dto/car_get_response.dart';
import '../../../model/car.dart';
class CarService extends ErpFrameService {
CarService({required super.config});
Future<Car> addCar(String plate, int teamId) async {
df.LoginSession? session;
try {
session = await getSession();
df.DocuFrameMacroResponse response =
await df.Macro(config: dfConfig, session: session).execute(
"_web_addCar",
parameter: CarAddDTO.make(teamId, plate).toJson()
as Map<String, dynamic>);
Map<String, dynamic> responseJson = jsonDecode(response.body!);
debugPrint(responseJson.toString());
CarAddResponseDTO responseDto = CarAddResponseDTO.fromJson(responseJson);
if (responseDto.succeeded == true) {
return Car(
id: int.parse(responseDto.car.id), plate: responseDto.car.plate);
} else {
throw responseDto.message;
}
} catch (e, st) {
debugPrint("ERROR WHILE ADDING CAR");
debugPrint(e.toString());
debugPrint(st.toString());
rethrow;
} finally {
await logout(session);
}
}
Future<void> editCar(Car car) async {
df.LoginSession? session;
try {
session = await getSession();
debugPrint(car.plate);
debugPrint(car.id.toString());
df.DocuFrameMacroResponse response =
await df.Macro(config: dfConfig, session: session).execute(
"_web_editCar",
parameter: {"id": car.id, "plate": car.plate});
Map<String, dynamic> responseJson = jsonDecode(response.body!);
debugPrint(responseJson.toString());
BasicResponseDTO responseDto = BasicResponseDTO.fromJson(responseJson);
if (responseDto.succeeded == true) {
return;
} else {
throw responseDto.message;
}
} catch (e, st) {
debugPrint("ERROR WHILE EDITING CAR ${car.id}");
debugPrint("$e");
debugPrint(st.toString());
rethrow;
} finally {
await logout(session);
}
}
Future<void> removeCar(int carId, int teamId) async {
df.LoginSession? session;
try {
session = await getSession();
df.DocuFrameMacroResponse response =
await df.Macro(config: dfConfig, session: session).execute(
"_web_removeCar",
parameter: {"team_id": teamId, "id": carId});
Map<String, dynamic> responseJson = jsonDecode(response.body!);
debugPrint(responseJson.toString());
BasicResponseDTO responseDto = BasicResponseDTO.fromJson(responseJson);
if (responseDto.succeeded == true) {
return;
} else {
throw responseDto.message;
}
} catch (e, st) {
debugPrint("ERROR WHILE REMOVING CAR");
debugPrint(e.toString());
debugPrint(st.toString());
rethrow;
} finally {
await logout(session);
}
}
Future<List<Car>> getCars(int teamId) async {
df.LoginSession? session;
try {
session = await getSession();
df.DocuFrameMacroResponse response =
await df.Macro(config: dfConfig, session: session)
.execute("_web_getCars", parameter: {"team_id": teamId});
debugPrint(teamId.toString());
Map<String, dynamic> responseJson = jsonDecode(response.body!);
debugPrint("RESPONSE");
debugPrint(responseJson.toString());
CarGetResponseDTO responseDto = CarGetResponseDTO.fromJson(responseJson);
if (responseDto.succeeded == true) {
return responseDto.cars!
.map((carDto) => Car(id: int.parse(carDto.id), plate: carDto.plate))
.toList();
} else {
throw responseDto.message;
}
} catch (e, st) {
debugPrint("ERROR WHILE FETCHING CARS");
debugPrint(e.toString());
debugPrint(st.toString());
rethrow;
} finally {
await logout(session);
}
}
}

View File

@ -0,0 +1,309 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:hl_lieferservice/dto/discount_add_response.dart';
import 'package:hl_lieferservice/dto/discount_update_response.dart';
import 'package:hl_lieferservice/feature/delivery/detail/bloc/delivery_event.dart';
import 'package:hl_lieferservice/feature/delivery/detail/bloc/delivery_state.dart';
import 'package:hl_lieferservice/feature/delivery/detail/repository/delivery_repository.dart';
import 'package:hl_lieferservice/widget/operations/bloc/operation_bloc.dart';
import 'package:hl_lieferservice/widget/operations/bloc/operation_event.dart';
import '../../../../dto/discount_remove_response.dart';
import '../../../../model/article.dart';
import '../../../../model/delivery.dart' as model;
class DeliveryBloc extends Bloc<DeliveryEvent, DeliveryState> {
OperationBloc opBloc;
DeliveryRepository repository;
DeliveryBloc({required this.opBloc, required this.repository})
: super(DeliveryInitial()) {
on<UnscanArticleEvent>(_unscan);
on<ResetScanAmountEvent>(_resetAmount);
on<LoadDeliveryEvent>(_load);
on<AddDiscountEvent>(_addDiscount);
on<RemoveDiscountEvent>(_removeDiscount);
on<UpdateDiscountEvent>(_updateDiscount);
on<UpdateDeliveryOption>(_updateDeliveryOptions);
on<UpdateSelectedPaymentMethod>(_updatePayment);
}
void _updatePayment(
UpdateSelectedPaymentMethod event,
Emitter<DeliveryState> emit,
) {
final currentState = state;
if (currentState is DeliveryLoaded) {
emit(
DeliveryLoaded(
delivery: currentState.delivery.copyWith(payment: event.payment),
),
);
}
}
void _updateDeliveryOptions(
UpdateDeliveryOption event,
Emitter<DeliveryState> emit,
) {
final currentState = state;
if (currentState is DeliveryLoaded) {
List<model.DeliveryOption> options =
currentState.delivery.options.map((option) {
if (option.key == event.key) {
return option.copyWith(value: event.value.toString());
}
return option;
}).toList();
emit(
DeliveryLoaded(
delivery: currentState.delivery.copyWith(options: options),
),
);
}
}
void _updateDiscount(
UpdateDiscountEvent event,
Emitter<DeliveryState> emit,
) async {
opBloc.add(LoadOperation());
try {
final currentState = state;
if (currentState is DeliveryLoaded) {
DiscountUpdateResponseDTO response = await repository.updateDiscount(
event.deliveryId,
event.reason,
event.value,
);
model.Delivery delivery = currentState.delivery;
if (response.values?.receipt != null) {
delivery.totalNetValue = response.values!.receipt.net;
delivery.totalGrossValue = response.values!.receipt.gross;
}
String discountArticleNumber = delivery.discount!.article.articleNumber;
delivery.discount = model.Discount(
article:
response.values?.article != null
? Article.fromDTO(response.values!.article)
: delivery.discount!.article,
note:
response.values?.note != null
? response.values!.note.noteDescription
: delivery.discount!.note,
noteId:
response.values?.note != null
? response.values!.note.rowId
: delivery.discount!.noteId,
);
delivery.articles = [
...delivery.articles.where(
(article) => article.articleNumber != discountArticleNumber,
),
delivery.discount!.article,
];
emit(currentState.copyWith(delivery));
opBloc.add(FinishOperation());
}
} catch (e, st) {
debugPrint(
"Fehler beim Hinzufügen eins Discounts zur Lieferung: ${event.deliveryId}:",
);
debugPrint("$e");
debugPrint("$st");
opBloc.add(
FailOperation(message: "Fehler beim Hinzufügen des Discounts: $e"),
);
}
}
void _removeDiscount(
RemoveDiscountEvent event,
Emitter<DeliveryState> emit,
) async {
opBloc.add(LoadOperation());
try {
final currentState = state;
if (currentState is DeliveryLoaded) {
model.Delivery delivery = currentState.delivery;
DiscountRemoveResponseDTO response = await repository.removeDiscount(
event.deliveryId,
);
delivery.articles =
delivery.articles
.where(
(article) =>
article.internalId !=
delivery.discount?.article.internalId,
)
.toList();
delivery.discount = null;
delivery.totalGrossValue = response.receipt.gross;
delivery.totalNetValue = response.receipt.net;
emit(currentState.copyWith(delivery));
opBloc.add(FinishOperation());
}
} catch (e, st) {
debugPrint(
"Fehler beim Löschen des Discounts der Lieferung: ${event.deliveryId}:",
);
debugPrint("$e");
debugPrint("$st");
opBloc.add(
FailOperation(message: "Fehler beim Löschen des Discounts: $e"),
);
}
}
void _addDiscount(AddDiscountEvent event, Emitter<DeliveryState> emit) async {
opBloc.add(LoadOperation());
try {
final currentState = state;
if (currentState is DeliveryLoaded) {
DiscountAddResponseDTO response = await repository.addDiscount(
event.deliveryId,
event.reason,
event.value,
);
model.Delivery delivery = currentState.delivery;
delivery.totalNetValue = response.values.receipt.net;
delivery.totalGrossValue = response.values.receipt.gross;
delivery.discount = model.Discount(
article: Article.fromDTO(response.values.article),
note: response.values.note.noteDescription,
noteId: response.values.note.rowId,
);
delivery.articles = [...delivery.articles, delivery.discount!.article];
emit(currentState.copyWith(delivery));
opBloc.add(FinishOperation());
}
} catch (e, st) {
debugPrint(
"Fehler beim Hinzufügen eins Discounts zur Lieferung: ${event.deliveryId}:",
);
debugPrint("$e");
debugPrint("$st");
opBloc.add(
FailOperation(message: "Fehler beim Hinzufügen des Discounts: $e"),
);
}
}
void _load(LoadDeliveryEvent event, Emitter<DeliveryState> emit) async {
debugPrint("Discount; ${event.delivery.discount?.note}");
emit(DeliveryLoaded(delivery: event.delivery));
}
void _unscan(UnscanArticleEvent event, Emitter<DeliveryState> emit) async {
opBloc.add(LoadOperation());
try {
String? noteId = await repository.unscan(
event.articleId,
event.newAmount,
event.reason,
);
if (noteId != null) {
final currentState = state;
if (currentState is DeliveryLoaded) {
Article article = currentState.delivery.articles.firstWhere(
(article) => article.internalId == int.parse(event.articleId),
);
article.removeNoteId = noteId;
article.scannedRemovedAmount += event.newAmount;
article.scannedAmount -= event.newAmount;
List<Article> articles = [
...currentState.delivery.articles.where(
(article) => article.internalId != int.parse(event.articleId),
),
article,
];
currentState.delivery.articles = articles;
emit.call(currentState.copyWith(currentState.delivery));
}
}
opBloc.add(FinishOperation());
} catch (e, st) {
debugPrint("Fehler beim Unscan des Artikels: ${event.articleId}:");
debugPrint("$e");
debugPrint("$st");
opBloc.add(FailOperation(message: "Fehler beim Unscan des Artikels: $e"));
}
}
void _resetAmount(
ResetScanAmountEvent event,
Emitter<DeliveryState> emit,
) async {
opBloc.add(LoadOperation());
try {
final currentState = state;
await repository.resetScan(event.articleId);
if (currentState is DeliveryLoaded) {
Article article = currentState.delivery.articles.firstWhere(
(article) => article.internalId == int.parse(event.articleId),
);
article.removeNoteId = null;
article.scannedRemovedAmount = 0;
article.scannedAmount = article.amount;
List<Article> articles = [
...currentState.delivery.articles.where(
(article) => article.internalId != int.parse(event.articleId),
),
article,
];
currentState.delivery.articles = articles;
emit.call(currentState.copyWith(currentState.delivery));
}
opBloc.add(FinishOperation());
} catch (e, st) {
debugPrint("Fehler beim Unscan des Artikels: ${event.articleId}:");
debugPrint("$e");
debugPrint("$st");
opBloc.add(FailOperation(message: "Fehler beim Zurücksetzen: $e"));
}
}
}

View File

@ -0,0 +1,71 @@
import 'package:hl_lieferservice/model/delivery.dart';
import 'package:hl_lieferservice/model/tour.dart';
abstract class DeliveryEvent {}
class LoadDeliveryEvent extends DeliveryEvent {
LoadDeliveryEvent({required this.delivery});
Delivery delivery;
}
class UnscanArticleEvent extends DeliveryEvent {
UnscanArticleEvent({
required this.articleId,
required this.newAmount,
required this.reason,
});
String articleId;
String reason;
int newAmount;
}
class ResetScanAmountEvent extends DeliveryEvent {
ResetScanAmountEvent({required this.articleId});
String articleId;
}
class AddDiscountEvent extends DeliveryEvent {
AddDiscountEvent({
required this.deliveryId,
required this.value,
required this.reason,
});
String deliveryId;
String reason;
int value;
}
class RemoveDiscountEvent extends DeliveryEvent {
RemoveDiscountEvent({required this.deliveryId});
String deliveryId;
}
class UpdateDiscountEvent extends DeliveryEvent {
UpdateDiscountEvent({
required this.deliveryId,
required this.value,
required this.reason,
});
String deliveryId;
String? reason;
int? value;
}
class UpdateDeliveryOption extends DeliveryEvent {
UpdateDeliveryOption({required this.key, required this.value});
String key;
dynamic value;
}
class UpdateSelectedPaymentMethod extends DeliveryEvent {
UpdateSelectedPaymentMethod({required this.payment});
Payment payment;
}

View File

@ -0,0 +1,15 @@
import 'package:hl_lieferservice/model/delivery.dart';
abstract class DeliveryState {}
class DeliveryInitial extends DeliveryState {}
class DeliveryLoaded extends DeliveryState {
DeliveryLoaded({required this.delivery});
Delivery delivery;
DeliveryLoaded copyWith(Delivery? delivery) {
return DeliveryLoaded(delivery: delivery ?? this.delivery);
}
}

View File

@ -0,0 +1,188 @@
import 'dart:typed_data';
import 'package:flutter/cupertino.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:hl_lieferservice/model/delivery.dart';
import 'package:hl_lieferservice/widget/operations/bloc/operation_bloc.dart';
import 'package:hl_lieferservice/widget/operations/bloc/operation_event.dart';
import 'note_event.dart';
import 'note_state.dart';
import 'package:hl_lieferservice/feature/delivery/detail/repository/note_repository.dart';
class NoteBloc extends Bloc<NoteEvent, NoteState> {
final NoteRepository repository;
final OperationBloc opBloc;
NoteBloc({required this.repository, required this.opBloc})
: super(NoteInitial()) {
on<LoadNote>(_load);
on<AddNote>(_add);
on<EditNote>(_edit);
on<RemoveNote>(_remove);
on<AddImageNote>(_upload);
on<RemoveImageNote>(_removeImage);
}
Future<void> _removeImage(
RemoveImageNote event,
Emitter<NoteState> emit,
) async {
opBloc.add(LoadOperation());
try {
final currentState = state;
await repository.deleteImage(event.deliveryId, event.objectId);
if (currentState is NoteLoaded) {
emit.call(
currentState.copyWith(
images:
currentState.images
.where((image) => image.$1.objectId != event.objectId)
.toList(),
),
);
}
opBloc.add(FinishOperation());
} catch (e, st) {
debugPrint("Fehler beim Löschen des Bildes: $e");
debugPrint(st.toString());
opBloc.add(FailOperation(message: e.toString()));
}
}
Future<void> _upload(AddImageNote event, Emitter<NoteState> emit) async {
opBloc.add(LoadOperation());
try {
final currentState = state;
Uint8List imageBytes = await event.file.readAsBytes();
ImageNote note = await repository.addImage(event.deliveryId, imageBytes);
if (currentState is NoteLoaded) {
emit.call(
currentState.copyWith(
images: [...currentState.images, (note, imageBytes)],
),
);
}
opBloc.add(FinishOperation());
} catch (e, st) {
debugPrint("Fehler beim Hinzufügen des Bildes: $e");
debugPrint(st.toString());
opBloc.add(FailOperation(message: e.toString()));
}
}
Future<void> _load(LoadNote event, Emitter<NoteState> emit) async {
emit.call(NoteLoading());
try {
List<String> urls =
event.delivery.images.map((image) => image.url).toList();
List<Note> notes = await repository.loadNotes(event.delivery.id);
List<NoteTemplate> templates = await repository.loadTemplates();
List<Uint8List> images = await repository.loadImages(urls);
emit.call(
NoteLoaded(
notes: notes,
templates: templates,
images: List.generate(
images.length,
(index) => (event.delivery.images[index], images[index]),
),
),
);
opBloc.add(FinishOperation());
} catch (e, st) {
debugPrint("Fehler beim Herunterladen der Notizen: $e");
debugPrint(st.toString());
opBloc.add(
FailOperation(message: "Notizen konnten nicht heruntergeladen werden."),
);
emit.call(NoteLoadingFailed());
}
}
Future<void> _add(AddNote event, Emitter<NoteState> emit) async {
opBloc.add(LoadOperation());
try {
final currentState = state;
Note note = await repository.addNote(event.deliveryId, event.note);
if (currentState is NoteLoaded) {
List<Note> refreshedNotes = [...currentState.notes, note];
emit.call(currentState.copyWith(notes: refreshedNotes));
}
opBloc.add(FinishOperation());
} catch (e, st) {
debugPrint("Fehler beim Hinzufügen der Notiz: $e");
debugPrint(st.toString());
opBloc.add(FailOperation(message: e.toString()));
}
}
Future<void> _edit(EditNote event, Emitter<NoteState> emit) async {
opBloc.add(LoadOperation());
try {
final currentState = state;
await repository.editNote(event.noteId, event.content);
if (currentState is NoteLoaded) {
List<Note> refreshedNotes = [
...currentState.notes.where(
(note) => note.id != int.parse(event.noteId),
),
Note(content: event.content, id: int.parse(event.noteId)),
];
emit.call(currentState.copyWith(notes: refreshedNotes));
}
opBloc.add(FinishOperation());
} catch (e, st) {
debugPrint("Fehler beim Hinzufügen der Notiz: $e");
debugPrint(st.toString());
opBloc.add(FailOperation(message: e.toString()));
}
}
Future<void> _remove(RemoveNote event, Emitter<NoteState> emit) async {
opBloc.add(LoadOperation());
try {
final currentState = state;
await repository.deleteNote(event.noteId);
if (currentState is NoteLoaded) {
List<Note> refreshedNotes =
currentState.notes
.where((note) => note.id != int.parse(event.noteId))
.toList();
emit.call(currentState.copyWith(notes: refreshedNotes));
}
opBloc.add(FinishOperation());
} catch (e, st) {
debugPrint("Fehler beim Hinzufügen der Notiz: $e");
debugPrint(st.toString());
opBloc.add(
FailOperation(message: "Notizen konnte nicht gelöscht werden."),
);
}
}
}

View File

@ -0,0 +1,44 @@
import 'package:hl_lieferservice/model/delivery.dart';
import 'package:image_picker/image_picker.dart';
abstract class NoteEvent {}
class LoadNote extends NoteEvent {
LoadNote({required this.delivery});
final Delivery delivery;
}
class AddNote extends NoteEvent {
AddNote({required this.note, required this.deliveryId});
final String note;
final String deliveryId;
}
class RemoveNote extends NoteEvent {
RemoveNote({required this.noteId});
final String noteId;
}
class EditNote extends NoteEvent {
EditNote({required this.content, required this.noteId});
final String noteId;
final String content;
}
class AddImageNote extends NoteEvent {
AddImageNote({required this.file, required this.deliveryId});
final XFile file;
final String deliveryId;
}
class RemoveImageNote extends NoteEvent {
RemoveImageNote({required this.objectId, required this.deliveryId});
final String objectId;
final String deliveryId;
}

View File

@ -0,0 +1,35 @@
import 'dart:typed_data';
import 'package:hl_lieferservice/model/delivery.dart';
abstract class NoteState {}
class NoteInitial extends NoteState {}
class NoteLoading extends NoteState {}
class NoteLoadingFailed extends NoteState {}
class NoteLoaded extends NoteState {
NoteLoaded({
required this.notes,
required this.templates,
required this.images,
});
List<Note> notes;
List<NoteTemplate> templates;
List<(ImageNote, Uint8List)> images;
NoteLoaded copyWith({
List<Note>? notes,
List<NoteTemplate>? templates,
List<(ImageNote, Uint8List)>? images,
}) {
return NoteLoaded(
notes: notes ?? this.notes,
templates: templates ?? this.templates,
images: images ?? this.images,
);
}
}

View File

@ -0,0 +1,9 @@
import 'package:hl_lieferservice/model/article.dart';
import 'package:hl_lieferservice/model/delivery.dart';
class NoteInformation {
NoteInformation({required this.note, this.article});
Note note;
Article? article;
}

View File

@ -0,0 +1,34 @@
import 'package:flutter/material.dart';
import 'package:hl_lieferservice/feature/delivery/detail/presentation/article/article_list_item.dart';
import 'package:hl_lieferservice/model/article.dart';
class ArticleList extends StatefulWidget {
const ArticleList({
super.key,
required this.articles,
required this.deliveryId,
});
final List<Article> articles;
final String deliveryId;
@override
State<StatefulWidget> createState() => _ArticleListState();
}
class _ArticleListState extends State<ArticleList> {
@override
Widget build(BuildContext context) {
return ListView.separated(
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemBuilder:
(context, index) => ArticleListItem(
article: widget.articles[index],
deliveryId: widget.deliveryId,
),
separatorBuilder: (context, index) => const Divider(height: 0),
itemCount: widget.articles.length,
);
}
}

View File

@ -0,0 +1,85 @@
import 'package:flutter/material.dart';
import 'package:hl_lieferservice/feature/delivery/detail/presentation/article/article_reset_scan_dialog.dart';
import 'package:hl_lieferservice/feature/delivery/detail/presentation/article/article_unscan_dialog.dart';
import 'package:hl_lieferservice/model/article.dart';
class ArticleListItem extends StatefulWidget {
const ArticleListItem({
super.key,
required this.article,
required this.deliveryId,
});
final Article article;
final String deliveryId;
@override
State<StatefulWidget> createState() => _ArticleListItem();
}
class _ArticleListItem extends State<ArticleListItem> {
Widget _leading() {
int amount = widget.article.getScannedAmount();
Color? color;
Color? textColor;
if (amount == 0) {
color = Colors.redAccent;
textColor = Theme.of(context).colorScheme.onSecondary;
}
return CircleAvatar(
backgroundColor: color,
child: Text("${amount}x", style: TextStyle(color: textColor)),
);
}
@override
Widget build(BuildContext context) {
Widget actionButton = IconButton.outlined(
style: ButtonStyle(
backgroundColor: WidgetStatePropertyAll(Colors.redAccent),
),
onPressed: () {
showDialog(
context: context,
builder: (context) => ArticleUnscanDialog(article: widget.article),
);
},
icon: Icon(
Icons.delete,
color: Theme.of(context).colorScheme.onSecondary,
),
);
if (widget.article.unscanned()) {
actionButton = IconButton.outlined(
style: ButtonStyle(
backgroundColor: WidgetStatePropertyAll(Colors.blueAccent),
),
onPressed: () {
showDialog(
context: context,
builder:
(context) => ResetArticleAmountDialog(article: widget.article),
);
},
icon: Icon(
Icons.refresh,
color: Theme.of(context).colorScheme.onSecondary,
),
);
}
return Padding(
padding: const EdgeInsets.all(0),
child: ListTile(
tileColor: Theme.of(context).colorScheme.surfaceContainerLowest,
title: Text(widget.article.name),
leading: _leading(),
subtitle: Text("Artikelnr. ${widget.article.articleNumber}"),
trailing: actionButton,
),
);
}
}

View File

@ -0,0 +1,56 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:hl_lieferservice/feature/delivery/detail/bloc/delivery_bloc.dart';
import 'package:hl_lieferservice/feature/delivery/detail/bloc/delivery_event.dart';
import '../../../../../model/article.dart';
class ResetArticleAmountDialog extends StatefulWidget {
const ResetArticleAmountDialog({super.key, required this.article});
final Article article;
@override
State<StatefulWidget> createState() => _ResetArticleAmountDialogState();
}
class _ResetArticleAmountDialogState extends State<ResetArticleAmountDialog> {
void _reset() {
context.read<DeliveryBloc>().add(
ResetScanAmountEvent(articleId: widget.article.internalId.toString()),
);
Navigator.pop(context);
}
@override
Widget build(BuildContext context) {
return AlertDialog(
title: const Text("Anzahl Artikel zurücksetzen?"),
content: SizedBox(
height: 120,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text("Wollen Sie die entfernten Artikel wieder hinzufügen?"),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
FilledButton(
onPressed: _reset,
child: const Text("Zurücksetzen"),
),
OutlinedButton(
onPressed: () {
Navigator.of(context).pop();
},
child: const Text("Abbrechen"),
),
],
),
],
),
),
);
}
}

View File

@ -0,0 +1,153 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:hl_lieferservice/feature/delivery/detail/bloc/delivery_bloc.dart';
import 'package:hl_lieferservice/feature/delivery/detail/bloc/delivery_event.dart';
import '../../../../../model/article.dart';
class ArticleUnscanDialog extends StatefulWidget {
const ArticleUnscanDialog({super.key, required this.article});
final Article article;
@override
State<StatefulWidget> createState() => _ArticleUnscanDialogState();
}
class _ArticleUnscanDialogState extends State<ArticleUnscanDialog> {
late TextEditingController unscanAmountController;
late TextEditingController unscanNoteController;
bool isValidText = false;
final _formKey = GlobalKey<FormState>();
void _unscan() {
context.read<DeliveryBloc>().add(
UnscanArticleEvent(
articleId: widget.article.internalId.toString(),
newAmount: int.parse(unscanAmountController.text),
reason: unscanNoteController.text,
),
);
Navigator.pop(context);
}
@override
void initState() {
super.initState();
unscanAmountController = TextEditingController(text: "1");
unscanNoteController = TextEditingController(text: "");
unscanNoteController.addListener(() {
setState(() {
isValidText = _isValid();
});
});
unscanAmountController.addListener(() {
setState(() {
isValidText = _isValid();
});
});
}
@override
void dispose() {
unscanAmountController.dispose();
unscanNoteController.dispose();
super.dispose();
}
bool _isValid() {
return _isAmountValid() && unscanNoteController.text.isNotEmpty;
}
bool _isAmountValid() {
final amount = int.tryParse(unscanAmountController.text);
return amount != null && amount > 0 && amount <= widget.article.amount;
}
@override
Widget build(BuildContext context) {
return AlertDialog(
title: const Text("Scan rückgängig machen"),
content: SizedBox(
width: double.infinity,
height: 350,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Text(
"Wollen Sie den Scanvorgang des Artikel '${widget.article.name}' rückgängig machen und den Artikel aus der Bestellung entfernen?",
),
Form(
key: _formKey,
autovalidateMode: AutovalidateMode.always,
child: Column(
children: [
TextFormField(
keyboardType: TextInputType.number,
inputFormatters: <TextInputFormatter>[
FilteringTextInputFormatter.digitsOnly,
],
validator: (text) {
if (text == null || text.isEmpty) {
return "Geben Sie eine Zahl ein";
}
final amount = int.tryParse(text);
if (amount == null || amount <= 0) {
return "Geben Sie eine gültige Zahl ein";
}
if (amount > widget.article.amount) {
return "Maximal ${widget.article.amount} möglich.";
}
return null;
},
controller: unscanAmountController,
decoration: const InputDecoration(
labelText: "Menge zu löschender Artikel",
),
),
TextFormField(
controller: unscanNoteController,
keyboardType: TextInputType.text,
decoration: const InputDecoration(
labelText: "Grund für die Entfernung",
),
validator: (text) {
if (text == null || text.isEmpty) {
return "Geben Sie einen Grund an.";
}
return null;
},
),
],
),
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
FilledButton(
onPressed: isValidText ? _unscan : null,
child: const Text("Entfernen"),
),
OutlinedButton(
onPressed: () {
Navigator.pop(context);
},
child: const Text("Abbrechen"),
),
],
),
],
),
),
);
}
}

View File

@ -0,0 +1,186 @@
import 'dart:typed_data';
import 'package:easy_stepper/easy_stepper.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:hl_lieferservice/feature/delivery/detail/bloc/delivery_bloc.dart';
import 'package:hl_lieferservice/feature/delivery/detail/bloc/delivery_event.dart';
import 'package:hl_lieferservice/feature/delivery/detail/bloc/delivery_state.dart';
import 'package:hl_lieferservice/feature/delivery/detail/presentation/delivery_sign.dart';
import 'package:hl_lieferservice/feature/delivery/detail/presentation/steps/step.dart';
import 'package:hl_lieferservice/model/delivery.dart' as model;
class DeliveryDetail extends StatefulWidget {
final model.Delivery delivery;
const DeliveryDetail({super.key, required this.delivery});
@override
State<StatefulWidget> createState() => _DeliveryDetailState();
}
class _DeliveryDetailState extends State<DeliveryDetail> {
late int _step;
late List<EasyStep> _steps;
@override
void initState() {
super.initState();
// Initialize BLOC
context.read<DeliveryBloc>().add(
LoadDeliveryEvent(delivery: widget.delivery),
);
// Initialize steps
_step = 0;
_steps = [
EasyStep(
icon: const Icon(Icons.info),
customTitle: Text("Info", textAlign: TextAlign.center),
),
EasyStep(
icon: const Icon(Icons.book),
customTitle: Text("Notizen", textAlign: TextAlign.center),
),
EasyStep(
icon: const Icon(Icons.shopping_cart),
customTitle: Text("Artikel/Gutschriften", textAlign: TextAlign.center),
),
EasyStep(
icon: const Icon(Icons.settings),
customTitle: Text("Optionen", textAlign: TextAlign.center),
),
EasyStep(
icon: const Icon(Icons.check_box),
customTitle: Text(
"Überprüfen",
textAlign: TextAlign.center,
overflow: TextOverflow.clip,
),
),
];
}
Widget _stepInfo() {
return DecoratedBox(
decoration: const BoxDecoration(),
child: SizedBox(
height: 115,
child: EasyStepper(
activeStep: _step,
showLoadingAnimation: false,
activeStepTextColor: Theme.of(context).primaryColor,
activeStepBorderType: BorderType.dotted,
finishedStepBorderType: BorderType.normal,
unreachedStepBorderType: BorderType.normal,
activeStepBackgroundColor: Colors.white,
borderThickness: 2,
internalPadding: 0.0,
enableStepTapping: true,
stepRadius: 25.0,
onStepReached:
(index) => {
setState(() {
_step = index;
}),
},
steps: _steps,
),
),
);
}
Widget _stepMissingWarning() {
return Center(
child: Text("Kein Inhalt für den aktuellen Step $_step gefunden."),
);
}
void _clickForward() {
if (_step < _steps.length) {
setState(() {
_step += 1;
});
}
}
void _clickBack() {
if (_step > 0) {
setState(() {
_step -= 1;
});
}
}
void _openSignatureView() {
Navigator.of(context).push(
MaterialPageRoute(
builder:
(context) => SignatureView(
onSigned: _onSign,
customer: widget.delivery.customer,
),
),
);
}
void _onSign(Uint8List customer, Uint8List driver) async {
}
Widget _stepsNavigation() {
return SizedBox(
width: double.infinity,
height: 90,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
OutlinedButton(
onPressed: _step == 0 ? null : _clickBack,
child: const Text("zurück"),
),
Padding(
padding: const EdgeInsets.only(left: 20),
child: FilledButton(
onPressed: _step == _steps.length - 1 ? _openSignatureView : _clickForward,
child:
_step == _steps.length - 1
? const Text("Unterschreiben")
: const Text("weiter"),
),
),
],
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text("Auslieferungsdetails")),
body: BlocBuilder<DeliveryBloc, DeliveryState>(
builder: (context, state) {
final currentState = state;
if (currentState is DeliveryLoaded) {
return Column(
children: [
_stepInfo(),
const Divider(),
Expanded(
child:
StepFactory().make(_step, currentState.delivery) ??
_stepMissingWarning(),
),
_stepsNavigation(),
],
);
}
return Container();
},
),
);
}
}

View File

@ -0,0 +1,221 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:hl_lieferservice/feature/delivery/detail/bloc/delivery_bloc.dart';
import 'package:hl_lieferservice/feature/delivery/detail/bloc/delivery_event.dart';
import 'package:hl_lieferservice/model/delivery.dart';
class DeliveryDiscount extends StatefulWidget {
const DeliveryDiscount({
super.key,
this.discount,
required this.disabled,
required this.deliveryId,
});
final bool disabled;
final Discount? discount;
final String deliveryId;
@override
State<StatefulWidget> createState() => _DeliveryDiscountState();
}
class _DeliveryDiscountState extends State<DeliveryDiscount> {
final int stepSize = 10;
late TextEditingController _reasonController;
late bool _isReasonEmpty;
late bool _isUpdated;
late int _discountValue;
@override
void initState() {
super.initState();
_reasonController = TextEditingController(text: widget.discount?.note);
_isReasonEmpty = _reasonController.text.isEmpty;
_reasonController.addListener(() {
setState(() {
_isReasonEmpty = _reasonController.text.isEmpty;
});
});
_discountValue =
widget.discount?.article.getGrossPrice().floor().abs() ?? 0;
_isUpdated = _discountValue > 0 && _reasonController.text.isNotEmpty;
}
@override
void dispose() {
super.dispose();
_reasonController.dispose();
}
bool _maximumReached() {
return _discountValue >= 150;
}
bool _minimumReached() {
return _discountValue <= 0;
}
Widget _incrementDiscount() {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
IconButton.filled(
onPressed:
_minimumReached() || widget.disabled
? null
: () {
setState(() {
if (_discountValue - stepSize >= 0) {
_discountValue -= stepSize;
}
});
},
icon: const Icon(Icons.remove),
style: ButtonStyle(
backgroundColor:
_minimumReached() || widget.disabled
? WidgetStateProperty.all(Colors.grey)
: WidgetStateProperty.all(Colors.red),
),
),
Padding(
padding: const EdgeInsets.all(5),
child: Column(
children: [
Text(
"${_discountValue.abs()}",
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 18.0,
),
),
const Text("max. 150€", style: TextStyle(fontSize: 10.0)),
],
),
),
IconButton.filled(
onPressed:
_maximumReached() || widget.disabled
? null
: () {
setState(() {
_discountValue += stepSize;
});
},
icon: const Icon(Icons.add),
style: ButtonStyle(
backgroundColor:
_maximumReached() || widget.disabled
? WidgetStateProperty.all(Colors.grey)
: WidgetStateProperty.all(Colors.green),
),
),
],
);
}
void _resetValues() async {
setState(() {
_discountValue = 0;
_reasonController.clear();
_isUpdated = false;
});
context.read<DeliveryBloc>().add(
RemoveDiscountEvent(deliveryId: widget.deliveryId),
);
}
void _updateValues() async {
if (_isUpdated) {
context.read<DeliveryBloc>().add(
UpdateDiscountEvent(
deliveryId: widget.deliveryId,
value: _discountValue,
reason: _reasonController.text,
),
);
} else {
context.read<DeliveryBloc>().add(
AddDiscountEvent(
deliveryId: widget.deliveryId,
value: _discountValue,
reason: _reasonController.text,
),
);
setState(() {
setState(() {
_isUpdated = true;
});
});
}
}
@override
Widget build(BuildContext context) {
return DecoratedBox(
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.onSecondary,
),
child: Padding(
padding: const EdgeInsets.all(20),
child: Form(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
"Betrag:",
style: TextStyle(fontWeight: FontWeight.bold),
),
_incrementDiscount(),
const Padding(
padding: const EdgeInsets.only(top: 10),
child: const Text(
"Begründung:",
style: TextStyle(fontWeight: FontWeight.bold),
),
),
Padding(
padding: const EdgeInsets.only(bottom: 20),
child: TextFormField(
controller: _reasonController,
validator: (text) {
if (text == null || text.isEmpty) {
return "Begründung für Gutschrift notwendig.";
}
return null;
},
),
),
Row(
children: [
Padding(
padding: const EdgeInsets.only(right: 10),
child: FilledButton(
onPressed:
!_isReasonEmpty && _discountValue > 0
? _updateValues
: null,
child: const Text("Speichern"),
),
),
OutlinedButton(
onPressed: _discountValue > 0 ? _resetValues : null,
child: const Text("Gutschrift entfernen"),
),
],
),
],
),
),
),
);
}
}

View File

@ -0,0 +1,84 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:hl_lieferservice/feature/delivery/detail/bloc/delivery_bloc.dart';
import 'package:hl_lieferservice/feature/delivery/detail/bloc/delivery_event.dart';
import 'package:hl_lieferservice/model/delivery.dart' as model;
class DeliveryOptionsView extends StatefulWidget {
const DeliveryOptionsView({super.key, required this.options});
final List<model.DeliveryOption> options;
@override
State<StatefulWidget> createState() => _DeliveryOptionsViewState();
}
class _DeliveryOptionsViewState extends State<DeliveryOptionsView> {
@override
void initState() {
super.initState();
}
@override
void didUpdateWidget(covariant DeliveryOptionsView oldWidget) {
super.didUpdateWidget(oldWidget);
}
void _update(model.DeliveryOption option, dynamic value) {
context.read<DeliveryBloc>().add(
UpdateDeliveryOption(key: option.key, value: value),
);
}
List<Widget> _options() {
List<Widget> boolOptions =
widget.options.where((option) => !option.numerical).map((option) {
return CheckboxListTile(
value: option.getValue() as bool,
onChanged: (value) => _update(option, value),
title: Text(option.display),
);
}).toList();
List<Widget> numericalOptions =
widget.options.where((option) => option.numerical).map((option) {
return Padding(
padding: const EdgeInsets.all(15),
child: TextFormField(
decoration: InputDecoration(labelText: option.display),
initialValue: option.getValue().toString(),
keyboardType: TextInputType.number,
onTapOutside: (event) => FocusScope.of(context).unfocus(),
onChanged: (value) => _update(option, value),
),
);
}).toList();
return [
Padding(
padding: const EdgeInsets.only(bottom: 5),
child: Text(
"Auswählbare Optionen",
style: Theme.of(context).textTheme.headlineSmall,
),
),
...boolOptions,
Padding(
padding: const EdgeInsets.only(top: 10),
child: Text(
"Zahlenwerte",
style: Theme.of(context).textTheme.headlineSmall,
),
),
...numericalOptions,
];
}
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(10),
child: ListView(children: _options()),
);
}
}

View File

@ -0,0 +1,164 @@
import 'dart:typed_data';
import 'package:hl_lieferservice/model/customer.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:signature/signature.dart';
class SignatureView extends StatefulWidget {
const SignatureView({
super.key,
required this.onSigned,
required this.customer,
});
final Customer customer;
/// Callback that is called when the user has signed.
/// The parameter stores the path to the image file of the signature.
final void Function(Uint8List customerSignaturePng, Uint8List driverSignaturePng) onSigned;
@override
State<StatefulWidget> createState() => _SignatureViewState();
}
class _SignatureViewState extends State<SignatureView> {
final SignatureController _customerController = SignatureController(
penStrokeWidth: 5,
penColor: Colors.black,
exportBackgroundColor: Colors.white,
);
final SignatureController _driverController = SignatureController(
penStrokeWidth: 5,
penColor: Colors.black,
exportBackgroundColor: Colors.white,
);
bool _isDriverSigning = false;
bool _customerAccepted = false;
@override
void initState() {
super.initState();
}
@override
void dispose() {
_customerController.dispose();
super.dispose();
}
Widget _signatureField() {
return Signature(
controller: _isDriverSigning ? _driverController : _customerController,
backgroundColor: Colors.white,
);
}
@override
Widget build(BuildContext context) {
String formattedDate = DateFormat("dd.MM.yyyy").format(DateTime.now());
return Scaffold(
appBar: AppBar(
title:
!_isDriverSigning
? const Text("Unterschrift des Kunden")
: const Text("Unterschrift des Fahrers"),
),
body: Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: SizedBox(
width: double.infinity,
child: DecoratedBox(
decoration: const BoxDecoration(color: Colors.white),
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
"Lieferung an: ${widget.customer.name}",
style: const TextStyle(
fontWeight: FontWeight.bold,
),
),
Expanded(child: _signatureField()),
],
),
),
const Divider(),
Text(
"${widget.customer.address.city}, den $formattedDate",
),
],
),
),
),
),
),
!_isDriverSigning
? Padding(
padding: const EdgeInsets.only(top: 25.0, bottom: 25.0),
child: Row(
children: [
Checkbox(
value: _customerAccepted,
onChanged: (value) {
setState(() {
_customerAccepted = value!;
});
},
),
const Flexible(
child: Text(
"Ich bestätige, dass ich die Ware im ordnungsgemäßen Zustand erhalten habe und, dass die Aufstell- und Einbauarbeiten korrekt durchgeführt wurden.",
overflow: TextOverflow.fade,
),
),
],
),
)
: Container(),
Padding(
padding: const EdgeInsets.only(top: 25.0, bottom: 25.0),
child: Center(
child: FilledButton(
onPressed:
!_customerAccepted
? null
: () async {
if (!_isDriverSigning) {
setState(() {
_isDriverSigning = true;
});
} else {
widget.onSigned(
(await _customerController.toPngBytes())!,
(await _driverController.toPngBytes())!,
);
}
},
child:
!_isDriverSigning
? const Text("Weiter")
: const Text("Absenden"),
),
),
),
],
),
),
);
}
}

View File

@ -0,0 +1,182 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:hl_lieferservice/feature/delivery/detail/bloc/delivery_bloc.dart';
import 'package:hl_lieferservice/feature/delivery/detail/bloc/delivery_event.dart';
import 'package:hl_lieferservice/feature/delivery/overview/bloc/tour_bloc.dart';
import 'package:hl_lieferservice/feature/delivery/overview/bloc/tour_state.dart';
import 'package:hl_lieferservice/model/delivery.dart';
import '../../../../model/tour.dart';
class DeliverySummary extends StatefulWidget {
const DeliverySummary({super.key, required this.delivery});
final Delivery delivery;
@override
State<StatefulWidget> createState() => _DeliverySummaryState();
}
class _DeliverySummaryState extends State<DeliverySummary> {
late List<Payment> _paymentMethods;
@override
void initState() {
super.initState();
final tourState = context.read<TourBloc>().state as TourLoaded;
_paymentMethods = [
widget.delivery.payment,
...tourState.tour.paymentMethods,
];
}
Widget _deliveredArticles() {
List<Widget> items =
widget.delivery
.getDeliveredArticles()
.map(
(article) => DecoratedBox(
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.onSecondary,
),
child: ListTile(
title: Text(article.name),
subtitle: Text("Artikelnr. ${article.articleNumber}"),
trailing: Text(
"${article.scannable ? article.getGrossPriceScanned().toStringAsFixed(2) : article.getGrossPrice().toStringAsFixed(2)}",
),
leading: CircleAvatar(
child: Text(
"${article.scannable ? article.scannedAmount : article.amount}x",
),
),
),
),
)
.toList();
items.add(
DecoratedBox(
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.onSecondary,
),
child: ListTile(
title: const Text(
"Gesamtsumme:",
style: TextStyle(fontWeight: FontWeight.bold),
),
trailing: Text(
"${widget.delivery.getGrossPrice().toStringAsFixed(2)}",
style: TextStyle(fontWeight: FontWeight.bold),
),
),
),
);
return ListView(
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
children: items,
);
}
Widget _paymentOptions() {
List<DropdownMenuEntry> entries =
_paymentMethods
.map(
(payment) => DropdownMenuEntry(
value: payment.id,
label: "${payment.description} (${payment.shortcode})",
),
)
.toList();
debugPrint(widget.delivery.payment.id);
return DropdownMenu(
dropdownMenuEntries: entries,
initialSelection: widget.delivery.payment.id,
onSelected: (id) {
context.read<DeliveryBloc>().add(
UpdateSelectedPaymentMethod(
payment: _paymentMethods.firstWhere(
(payment) => payment.id == id,
),
),
);
},
);
}
Widget _payment() {
return _paymentOptions();
}
Widget _paymentDone() {
return DecoratedBox(
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.onSecondary,
),
child: Column(
children: [
ListTile(
title: const Text(
"Bei Bestellung bezahlt:",
style: TextStyle(fontWeight: FontWeight.bold),
),
trailing: Text("${widget.delivery.prepayment.toStringAsFixed(2)}"),
),
ListTile(
title: const Text(
"Offener Betrag:",
style: TextStyle(fontWeight: FontWeight.bold),
),
trailing: Text(
"${widget.delivery.getOpenPrice().toStringAsFixed(2)}",
style: TextStyle(fontWeight: FontWeight.w900, color: Colors.red),
),
),
],
),
);
}
@override
Widget build(BuildContext context) {
final insets = EdgeInsets.all(10);
return Padding(
padding: const EdgeInsets.all(10),
child: ListView(
children: [
Text(
"Ausgelieferte Artikel",
style: Theme.of(context).textTheme.headlineSmall,
),
Padding(padding: insets, child: _deliveredArticles()),
Padding(
padding: const EdgeInsets.only(top: 10),
child: Text(
"Geleistete Zahlung",
style: Theme.of(context).textTheme.headlineSmall,
),
),
Padding(padding: insets, child: _paymentDone()),
Padding(
padding: const EdgeInsets.only(top: 10),
child: Text(
"Zahlungsmethode",
style: Theme.of(context).textTheme.headlineSmall,
),
),
Padding(padding: insets, child: _payment()),
],
),
);
}
}

View File

@ -0,0 +1,151 @@
import 'package:flutter/material.dart';
import 'package:collection/collection.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:hl_lieferservice/feature/delivery/detail/bloc/note_bloc.dart';
import 'package:hl_lieferservice/feature/delivery/detail/bloc/note_event.dart';
import 'package:hl_lieferservice/model/delivery.dart';
class NoteAddDialog extends StatefulWidget {
final String delivery;
final List<NoteTemplate> templates;
const NoteAddDialog({
super.key,
required this.delivery,
required this.templates,
});
@override
State<StatefulWidget> createState() => _NoteAddDialogState();
}
class _NoteAddDialogState extends State<NoteAddDialog> {
final _noteController = TextEditingController();
final _noteSelectionController = TextEditingController();
late FocusNode _noteFieldFocusNode;
bool _isCustomNotesEmpty = true;
@override
void initState() {
super.initState();
_noteFieldFocusNode = FocusNode();
_noteController.addListener(() {
setState(() {
_isCustomNotesEmpty = _noteController.text.isEmpty;
});
});
}
void _onSave() {
String content = _noteController.text;
if (_noteSelectionController.text.isNotEmpty) {
NoteTemplate template = widget.templates.firstWhere(
(note) => note.title == _noteSelectionController.text,
);
content = template.content;
}
context.read<NoteBloc>().add(
AddNote(note: content, deliveryId: widget.delivery),
);
Navigator.pop(context);
}
@override
Widget build(BuildContext context) {
return Dialog(
child: SizedBox(
width: MediaQuery.of(context).size.width * 0.75,
height: MediaQuery.of(context).size.height * 0.45,
child: Padding(
padding: const EdgeInsets.all(20),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"Notiz hinzufügen",
style: Theme.of(context).textTheme.headlineSmall,
),
IconButton(
onPressed: () {
Navigator.of(context).pop();
},
icon: Icon(Icons.close),
),
],
),
Padding(
padding: const EdgeInsets.only(bottom: 10.0, top: 20),
child: DropdownMenu(
controller: _noteSelectionController,
onSelected: (int? value) {
setState(() {
_noteSelectionController.text =
widget.templates[value!].title;
});
},
enabled: _isCustomNotesEmpty,
width: double.infinity,
label: const Text("Notiz auswählen"),
dropdownMenuEntries:
widget.templates
.mapIndexed(
(i, note) =>
DropdownMenuEntry(value: i, label: note.title),
)
.toList(),
),
),
const Padding(
padding: EdgeInsets.only(top: 0.0, bottom: 0.0),
child: Text("oder"),
),
Padding(
padding: const EdgeInsets.only(top: 10.0, bottom: 20.0),
child: TextFormField(
controller: _noteController,
enabled: _noteSelectionController.text.isEmpty,
focusNode: _noteFieldFocusNode,
decoration: const InputDecoration(
labelText: "Eigene Notiz",
border: OutlineInputBorder(),
),
minLines: 3,
maxLines: 5,
),
),
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
FilledButton(
onPressed:
_noteSelectionController.text.isNotEmpty ||
_noteController.text.isNotEmpty
? _onSave
: null,
child: const Text("Hinzufügen"),
),
Padding(
padding: const EdgeInsets.only(left: 10.0),
child: OutlinedButton(
onPressed: null,
child: const Text("Zurücksetzen"),
),
),
],
),
],
),
),
),
);
}
}

View File

@ -0,0 +1,104 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:hl_lieferservice/feature/delivery/detail/bloc/note_bloc.dart';
import 'package:hl_lieferservice/feature/delivery/detail/bloc/note_event.dart';
import 'package:hl_lieferservice/model/delivery.dart';
class NoteEditDialog extends StatefulWidget {
final Note note;
const NoteEditDialog({super.key, required this.note});
@override
State<StatefulWidget> createState() => _NoteEditDialogState();
}
class _NoteEditDialogState extends State<NoteEditDialog> {
final _formKey = GlobalKey<FormState>();
late TextEditingController _editController;
@override
void initState() {
super.initState();
_editController = TextEditingController(text: widget.note.content);
}
void _onEdit(BuildContext context) {
context.read<NoteBloc>().add(
EditNote(
content: _editController.text,
noteId: widget.note.id.toString(),
),
);
Navigator.of(context).pop();
}
@override
Widget build(BuildContext context) {
return Dialog(
child: SizedBox(
width: MediaQuery.of(context).size.width * 0.75,
height: MediaQuery.of(context).size.height * 0.32,
child: Padding(
padding: const EdgeInsets.all(20),
child: Column(
children: [
Text(
"Notiz bearbeiten",
style: Theme.of(context).textTheme.headlineMedium,
),
Padding(
padding: const EdgeInsets.only(top: 15),
child: Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
TextFormField(
onTapOutside: (event) {
FocusScope.of(context).unfocus();
},
decoration: InputDecoration(label: const Text("Notiz")),
controller: _editController,
minLines: 4,
maxLines: 8,
),
Padding(
padding: const EdgeInsets.only(top: 20),
child: Row(
children: [
FilledButton(
onPressed: () {
_onEdit(context);
},
child: const Text("Bearbeiten"),
),
Padding(
padding: const EdgeInsets.only(left: 10),
child: OutlinedButton(
onPressed: () {
Navigator.of(context).pop();
},
child: const Text("Abbrechen"),
),
),
],
),
),
],
),
),
),
],
),
),
),
);
}
}

View File

@ -0,0 +1,109 @@
import 'dart:typed_data';
import 'package:carousel_slider/carousel_slider.dart';
import 'package:flutter/material.dart';
import 'package:collection/collection.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:hl_lieferservice/feature/delivery/detail/bloc/note_bloc.dart';
import 'package:hl_lieferservice/feature/delivery/detail/bloc/note_event.dart';
import 'package:hl_lieferservice/model/delivery.dart';
class NoteImageOverview extends StatefulWidget {
final List<(ImageNote, Uint8List)> images;
final String deliveryId;
const NoteImageOverview({
super.key,
required this.images,
required this.deliveryId,
});
@override
State<StatefulWidget> createState() => _NoteImageOverviewState();
}
class _NoteImageOverviewState extends State<NoteImageOverview> {
int? _imageDeleting;
void _onRemoveImage(int index) {
ImageNote note = widget.images[index].$1;
context.read<NoteBloc>().add(
RemoveImageNote(objectId: note.objectId, deliveryId: widget.deliveryId),
);
}
Widget _buildImageCarousel() {
return CarouselSlider(
options: CarouselOptions(
height: 300.0,
aspectRatio: 2.0,
enableInfiniteScroll: false,
),
items:
widget.images.mapIndexed((index, data) {
ImageNote note = data.$1;
Uint8List bytes = data.$2;
return Builder(
builder: (BuildContext context) {
return Stack(
children: [
Padding(
padding: const EdgeInsets.all(15.0),
child: Image.memory(
bytes,
fit: BoxFit.fill,
width: 1920.0,
height: 1090.0,
),
),
_imageDeleting == index
? Stack(
children: [
Padding(
padding: const EdgeInsets.all(15.0),
child: Container(
color: Colors.black.withValues(alpha: 0.5),
),
),
Center(
child: CircularProgressIndicator(
backgroundColor:
Theme.of(context).colorScheme.onSecondary,
),
),
],
)
: Container(),
Positioned(
right: 0.0,
top: 0.0,
child: CircleAvatar(
radius: 20,
child: IconButton.filled(
onPressed:
!(_imageDeleting == index)
? () {
_onRemoveImage(index);
}
: null,
icon: const Icon(Icons.delete, color: Colors.white),
),
),
),
],
);
},
);
}).toList(),
);
}
@override
Widget build(BuildContext context) {
return widget.images.isEmpty
? const Center(child: Text("Noch keine Bilder hochgeladen"))
: _buildImageCarousel();
}
}

View File

@ -0,0 +1,30 @@
import 'package:flutter/material.dart';
import 'package:hl_lieferservice/feature/delivery/detail/model/note.dart';
import 'package:hl_lieferservice/feature/delivery/detail/presentation/note/note_list_item.dart';
class NoteList extends StatelessWidget {
final List<NoteInformation> notes;
final String deliveryId;
const NoteList({super.key, required this.notes, required this.deliveryId});
@override
Widget build(BuildContext context) {
if (notes.isEmpty) {
return const Center(child: Text("keine Notizen vorhanden"));
}
return ListView.separated(
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemBuilder:
(context, index) => NoteListItem(
note: notes[index],
deliveryId: deliveryId,
index: index,
),
separatorBuilder: (context, index) => const Divider(height: 0),
itemCount: notes.length,
);
}
}

View File

@ -0,0 +1,90 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:hl_lieferservice/feature/delivery/detail/bloc/note_bloc.dart';
import 'package:hl_lieferservice/feature/delivery/detail/bloc/note_event.dart';
import 'package:hl_lieferservice/feature/delivery/detail/model/note.dart';
import 'package:hl_lieferservice/feature/delivery/detail/presentation/note/note_edit_dialog.dart';
import 'package:hl_lieferservice/feature/delivery/overview/bloc/tour_bloc.dart';
import 'package:hl_lieferservice/feature/delivery/overview/bloc/tour_state.dart';
import '../../../../../model/delivery.dart';
class NoteListItem extends StatelessWidget {
final NoteInformation note;
final String deliveryId;
final int index;
const NoteListItem({
super.key,
required this.note,
required this.deliveryId,
required this.index,
});
void _onDelete(BuildContext context) {
context.read<NoteBloc>().add(RemoveNote(noteId: note.note.id.toString()));
}
Widget? _subtitle(BuildContext context) {
String discountArticleId =
(context.read<TourBloc>().state as TourLoaded)
.tour
.discountArticleNumber;
if (note.article != null && note.article?.articleNumber == discountArticleId) {
return const Text("Begründung der Gutschrift");
}
return note.article != null ? Text(note.article!.name) : null;
}
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(0),
child: ListTile(
title: Text(note.note.content),
subtitle: _subtitle(context),
tileColor: Theme.of(context).colorScheme.surfaceContainerLowest,
leading: CircleAvatar(child: Text("${index + 1}")),
trailing: PopupMenuButton(
itemBuilder: (context) {
return [
PopupMenuItem(
onTap: () {
showDialog(
context: context,
builder: (context) => NoteEditDialog(note: note.note),
);
},
child: Row(
children: [
Icon(Icons.edit, color: Colors.blueAccent),
Padding(
padding: const EdgeInsets.only(left: 5),
child: const Text("Editieren"),
),
],
),
),
PopupMenuItem(
onTap: () {
_onDelete(context);
},
child: Row(
children: [
Icon(Icons.delete, color: Colors.redAccent),
Padding(
padding: const EdgeInsets.only(left: 5),
child: const Text("Löschen"),
),
],
),
),
];
},
),
),
);
}
}

Some files were not shown because too many files have changed in this diff Show More