feat: Implement order details retrieval for pre-filling new order forms for reordering.

This commit is contained in:
Achintha Isuru
2026-02-22 02:18:33 -05:00
parent 3aab5bfc26
commit 214e0d1237
10 changed files with 594 additions and 197 deletions

View File

@@ -42,6 +42,7 @@ export 'src/entities/orders/permanent_order.dart';
export 'src/entities/orders/permanent_order_position.dart';
export 'src/entities/orders/order_type.dart';
export 'src/entities/orders/order_item.dart';
export 'src/entities/orders/reorder_data.dart';
// Skills & Certs
export 'src/entities/skills/skill.dart';

View File

@@ -0,0 +1,76 @@
import 'package:equatable/equatable.dart';
import 'one_time_order.dart';
import 'order_type.dart';
/// Represents the full details of an order retrieved for reordering.
class ReorderData extends Equatable {
const ReorderData({
required this.orderId,
required this.orderType,
required this.eventName,
required this.vendorId,
required this.hub,
required this.positions,
this.date,
this.startDate,
this.endDate,
this.recurringDays = const <String>[],
this.permanentDays = const <String>[],
});
final String orderId;
final OrderType orderType;
final String eventName;
final String? vendorId;
final OneTimeOrderHubDetails hub;
final List<ReorderPosition> positions;
// One-time specific
final DateTime? date;
// Recurring/Permanent specific
final DateTime? startDate;
final DateTime? endDate;
final List<String> recurringDays;
final List<String> permanentDays;
@override
List<Object?> get props => <Object?>[
orderId,
orderType,
eventName,
vendorId,
hub,
positions,
date,
startDate,
endDate,
recurringDays,
permanentDays,
];
}
class ReorderPosition extends Equatable {
const ReorderPosition({
required this.roleId,
required this.count,
required this.startTime,
required this.endTime,
this.lunchBreak = 'NO_BREAK',
});
final String roleId;
final int count;
final String startTime;
final String endTime;
final String lunchBreak;
@override
List<Object?> get props => <Object?>[
roleId,
count,
startTime,
endTime,
lunchBreak,
];
}

View File

@@ -8,6 +8,7 @@ import 'domain/usecases/create_one_time_order_usecase.dart';
import 'domain/usecases/create_permanent_order_usecase.dart';
import 'domain/usecases/create_recurring_order_usecase.dart';
import 'domain/usecases/create_rapid_order_usecase.dart';
import 'domain/usecases/get_order_details_for_reorder_usecase.dart';
import 'presentation/blocs/index.dart';
import 'presentation/pages/create_order_page.dart';
import 'presentation/pages/one_time_order_page.dart';
@@ -27,13 +28,16 @@ class ClientCreateOrderModule extends Module {
@override
void binds(Injector i) {
// Repositories
i.addLazySingleton<ClientCreateOrderRepositoryInterface>(ClientCreateOrderRepositoryImpl.new);
i.addLazySingleton<ClientCreateOrderRepositoryInterface>(
ClientCreateOrderRepositoryImpl.new,
);
// UseCases
i.addLazySingleton(CreateOneTimeOrderUseCase.new);
i.addLazySingleton(CreatePermanentOrderUseCase.new);
i.addLazySingleton(CreateRecurringOrderUseCase.new);
i.addLazySingleton(CreateRapidOrderUseCase.new);
i.addLazySingleton(GetOrderDetailsForReorderUseCase.new);
// BLoCs
i.add<RapidOrderBloc>(RapidOrderBloc.new);
@@ -49,19 +53,31 @@ class ClientCreateOrderModule extends Module {
child: (BuildContext context) => const ClientCreateOrderPage(),
);
r.child(
ClientPaths.childRoute(ClientPaths.createOrder, ClientPaths.createOrderRapid),
ClientPaths.childRoute(
ClientPaths.createOrder,
ClientPaths.createOrderRapid,
),
child: (BuildContext context) => const RapidOrderPage(),
);
r.child(
ClientPaths.childRoute(ClientPaths.createOrder, ClientPaths.createOrderOneTime),
ClientPaths.childRoute(
ClientPaths.createOrder,
ClientPaths.createOrderOneTime,
),
child: (BuildContext context) => const OneTimeOrderPage(),
);
r.child(
ClientPaths.childRoute(ClientPaths.createOrder, ClientPaths.createOrderRecurring),
ClientPaths.childRoute(
ClientPaths.createOrder,
ClientPaths.createOrderRecurring,
),
child: (BuildContext context) => const RecurringOrderPage(),
);
r.child(
ClientPaths.childRoute(ClientPaths.createOrder, ClientPaths.createOrderPermanent),
ClientPaths.childRoute(
ClientPaths.createOrder,
ClientPaths.createOrderPermanent,
),
child: (BuildContext context) => const PermanentOrderPage(),
);
}

View File

@@ -1,4 +1,4 @@
import 'package:firebase_data_connect/firebase_data_connect.dart' as fdc;
import 'package:firebase_data_connect/firebase_data_connect.dart';
import 'package:intl/intl.dart';
import 'package:krow_data_connect/krow_data_connect.dart' as dc;
import 'package:krow_domain/krow_domain.dart' as domain;
@@ -11,10 +11,10 @@ import '../../domain/repositories/client_create_order_repository_interface.dart'
///
/// It follows the KROW Clean Architecture by keeping the data layer focused
/// on delegation and data mapping, without business logic.
class ClientCreateOrderRepositoryImpl implements ClientCreateOrderRepositoryInterface {
ClientCreateOrderRepositoryImpl({
required dc.DataConnectService service,
}) : _service = service;
class ClientCreateOrderRepositoryImpl
implements ClientCreateOrderRepositoryInterface {
ClientCreateOrderRepositoryImpl({required dc.DataConnectService service})
: _service = service;
final dc.DataConnectService _service;
@@ -36,19 +36,19 @@ class ClientCreateOrderRepositoryImpl implements ClientCreateOrderRepositoryInte
order.date.month,
order.date.day,
);
final fdc.Timestamp orderTimestamp = _service.toTimestamp(orderDateOnly);
final fdc.OperationResult<dc.CreateOrderData, dc.CreateOrderVariables> orderResult =
await _service.connector
.createOrder(
businessId: businessId,
orderType: dc.OrderType.ONE_TIME,
teamHubId: hub.id,
)
.vendorId(vendorId)
.eventName(order.eventName)
.status(dc.OrderStatus.POSTED)
.date(orderTimestamp)
.execute();
final Timestamp orderTimestamp = _service.toTimestamp(orderDateOnly);
final OperationResult<dc.CreateOrderData, dc.CreateOrderVariables>
orderResult = await _service.connector
.createOrder(
businessId: businessId,
orderType: dc.OrderType.ONE_TIME,
teamHubId: hub.id,
)
.vendorId(vendorId)
.eventName(order.eventName)
.status(dc.OrderStatus.POSTED)
.date(orderTimestamp)
.execute();
final String orderId = orderResult.data.order_insert.id;
@@ -59,32 +59,34 @@ class ClientCreateOrderRepositoryImpl implements ClientCreateOrderRepositoryInte
final String shiftTitle = 'Shift 1 ${_formatDate(order.date)}';
final double shiftCost = _calculateShiftCost(order);
final fdc.OperationResult<dc.CreateShiftData, dc.CreateShiftVariables> shiftResult =
await _service.connector
.createShift(title: shiftTitle, orderId: orderId)
.date(orderTimestamp)
.location(hub.name)
.locationAddress(hub.address)
.latitude(hub.latitude)
.longitude(hub.longitude)
.placeId(hub.placeId)
.city(hub.city)
.state(hub.state)
.street(hub.street)
.country(hub.country)
.status(dc.ShiftStatus.OPEN)
.workersNeeded(workersNeeded)
.filled(0)
.durationDays(1)
.cost(shiftCost)
.execute();
final OperationResult<dc.CreateShiftData, dc.CreateShiftVariables>
shiftResult = await _service.connector
.createShift(title: shiftTitle, orderId: orderId)
.date(orderTimestamp)
.location(hub.name)
.locationAddress(hub.address)
.latitude(hub.latitude)
.longitude(hub.longitude)
.placeId(hub.placeId)
.city(hub.city)
.state(hub.state)
.street(hub.street)
.country(hub.country)
.status(dc.ShiftStatus.OPEN)
.workersNeeded(workersNeeded)
.filled(0)
.durationDays(1)
.cost(shiftCost)
.execute();
final String shiftId = shiftResult.data.shift_insert.id;
for (final domain.OneTimeOrderPosition position in order.positions) {
final DateTime start = _parseTime(order.date, position.startTime);
final DateTime end = _parseTime(order.date, position.endTime);
final DateTime normalizedEnd = end.isBefore(start) ? end.add(const Duration(days: 1)) : end;
final DateTime normalizedEnd = end.isBefore(start)
? end.add(const Duration(days: 1))
: end;
final double hours = normalizedEnd.difference(start).inMinutes / 60.0;
final double rate = order.roleRates[position.role] ?? 0;
final double totalValue = rate * hours * position.count;
@@ -106,7 +108,7 @@ class ClientCreateOrderRepositoryImpl implements ClientCreateOrderRepositoryInte
await _service.connector
.updateOrder(id: orderId, teamHubId: hub.id)
.shifts(fdc.AnyValue(<String>[shiftId]))
.shifts(AnyValue(<String>[shiftId]))
.execute();
});
}
@@ -129,74 +131,78 @@ class ClientCreateOrderRepositoryImpl implements ClientCreateOrderRepositoryInte
order.startDate.month,
order.startDate.day,
);
final fdc.Timestamp orderTimestamp = _service.toTimestamp(orderDateOnly);
final fdc.Timestamp startTimestamp = orderTimestamp;
final fdc.Timestamp endTimestamp = _service.toTimestamp(order.endDate);
final Timestamp orderTimestamp = _service.toTimestamp(orderDateOnly);
final Timestamp startTimestamp = orderTimestamp;
final Timestamp endTimestamp = _service.toTimestamp(order.endDate);
final fdc.OperationResult<dc.CreateOrderData, dc.CreateOrderVariables> orderResult =
await _service.connector
.createOrder(
businessId: businessId,
orderType: dc.OrderType.RECURRING,
teamHubId: hub.id,
)
.vendorId(vendorId)
.eventName(order.eventName)
.status(dc.OrderStatus.POSTED)
.date(orderTimestamp)
.startDate(startTimestamp)
.endDate(endTimestamp)
.recurringDays(order.recurringDays)
.execute();
final OperationResult<dc.CreateOrderData, dc.CreateOrderVariables>
orderResult = await _service.connector
.createOrder(
businessId: businessId,
orderType: dc.OrderType.RECURRING,
teamHubId: hub.id,
)
.vendorId(vendorId)
.eventName(order.eventName)
.status(dc.OrderStatus.POSTED)
.date(orderTimestamp)
.startDate(startTimestamp)
.endDate(endTimestamp)
.recurringDays(order.recurringDays)
.execute();
final String orderId = orderResult.data.order_insert.id;
// NOTE: Recurring orders are limited to 30 days of generated shifts.
// Future shifts beyond 30 days should be created by a scheduled job.
final DateTime maxEndDate = orderDateOnly.add(const Duration(days: 29));
final DateTime effectiveEndDate =
order.endDate.isAfter(maxEndDate) ? maxEndDate : order.endDate;
final DateTime effectiveEndDate = order.endDate.isAfter(maxEndDate)
? maxEndDate
: order.endDate;
final Set<String> selectedDays = Set<String>.from(order.recurringDays);
final int workersNeeded = order.positions.fold<int>(
0,
(int sum, domain.RecurringOrderPosition position) => sum + position.count,
(int sum, domain.RecurringOrderPosition position) =>
sum + position.count,
);
final double shiftCost = _calculateRecurringShiftCost(order);
final List<String> shiftIds = <String>[];
for (DateTime day = orderDateOnly;
!day.isAfter(effectiveEndDate);
day = day.add(const Duration(days: 1))) {
for (
DateTime day = orderDateOnly;
!day.isAfter(effectiveEndDate);
day = day.add(const Duration(days: 1))
) {
final String dayLabel = _weekdayLabel(day);
if (!selectedDays.contains(dayLabel)) {
continue;
}
final String shiftTitle = 'Shift ${_formatDate(day)}';
final fdc.Timestamp dayTimestamp = _service.toTimestamp(
final Timestamp dayTimestamp = _service.toTimestamp(
DateTime(day.year, day.month, day.day),
);
final fdc.OperationResult<dc.CreateShiftData, dc.CreateShiftVariables> shiftResult =
await _service.connector
.createShift(title: shiftTitle, orderId: orderId)
.date(dayTimestamp)
.location(hub.name)
.locationAddress(hub.address)
.latitude(hub.latitude)
.longitude(hub.longitude)
.placeId(hub.placeId)
.city(hub.city)
.state(hub.state)
.street(hub.street)
.country(hub.country)
.status(dc.ShiftStatus.OPEN)
.workersNeeded(workersNeeded)
.filled(0)
.durationDays(1)
.cost(shiftCost)
.execute();
final OperationResult<dc.CreateShiftData, dc.CreateShiftVariables>
shiftResult = await _service.connector
.createShift(title: shiftTitle, orderId: orderId)
.date(dayTimestamp)
.location(hub.name)
.locationAddress(hub.address)
.latitude(hub.latitude)
.longitude(hub.longitude)
.placeId(hub.placeId)
.city(hub.city)
.state(hub.state)
.street(hub.street)
.country(hub.country)
.status(dc.ShiftStatus.OPEN)
.workersNeeded(workersNeeded)
.filled(0)
.durationDays(1)
.cost(shiftCost)
.execute();
final String shiftId = shiftResult.data.shift_insert.id;
shiftIds.add(shiftId);
@@ -204,8 +210,9 @@ class ClientCreateOrderRepositoryImpl implements ClientCreateOrderRepositoryInte
for (final domain.RecurringOrderPosition position in order.positions) {
final DateTime start = _parseTime(day, position.startTime);
final DateTime end = _parseTime(day, position.endTime);
final DateTime normalizedEnd =
end.isBefore(start) ? end.add(const Duration(days: 1)) : end;
final DateTime normalizedEnd = end.isBefore(start)
? end.add(const Duration(days: 1))
: end;
final double hours = normalizedEnd.difference(start).inMinutes / 60.0;
final double rate = order.roleRates[position.role] ?? 0;
final double totalValue = rate * hours * position.count;
@@ -228,7 +235,7 @@ class ClientCreateOrderRepositoryImpl implements ClientCreateOrderRepositoryInte
await _service.connector
.updateOrder(id: orderId, teamHubId: hub.id)
.shifts(fdc.AnyValue(shiftIds))
.shifts(AnyValue(shiftIds))
.execute();
});
}
@@ -251,23 +258,23 @@ class ClientCreateOrderRepositoryImpl implements ClientCreateOrderRepositoryInte
order.startDate.month,
order.startDate.day,
);
final fdc.Timestamp orderTimestamp = _service.toTimestamp(orderDateOnly);
final fdc.Timestamp startTimestamp = orderTimestamp;
final Timestamp orderTimestamp = _service.toTimestamp(orderDateOnly);
final Timestamp startTimestamp = orderTimestamp;
final fdc.OperationResult<dc.CreateOrderData, dc.CreateOrderVariables> orderResult =
await _service.connector
.createOrder(
businessId: businessId,
orderType: dc.OrderType.PERMANENT,
teamHubId: hub.id,
)
.vendorId(vendorId)
.eventName(order.eventName)
.status(dc.OrderStatus.POSTED)
.date(orderTimestamp)
.startDate(startTimestamp)
.permanentDays(order.permanentDays)
.execute();
final OperationResult<dc.CreateOrderData, dc.CreateOrderVariables>
orderResult = await _service.connector
.createOrder(
businessId: businessId,
orderType: dc.OrderType.PERMANENT,
teamHubId: hub.id,
)
.vendorId(vendorId)
.eventName(order.eventName)
.status(dc.OrderStatus.POSTED)
.date(orderTimestamp)
.startDate(startTimestamp)
.permanentDays(order.permanentDays)
.execute();
final String orderId = orderResult.data.order_insert.id;
@@ -283,38 +290,40 @@ class ClientCreateOrderRepositoryImpl implements ClientCreateOrderRepositoryInte
final double shiftCost = _calculatePermanentShiftCost(order);
final List<String> shiftIds = <String>[];
for (DateTime day = orderDateOnly;
!day.isAfter(maxEndDate);
day = day.add(const Duration(days: 1))) {
for (
DateTime day = orderDateOnly;
!day.isAfter(maxEndDate);
day = day.add(const Duration(days: 1))
) {
final String dayLabel = _weekdayLabel(day);
if (!selectedDays.contains(dayLabel)) {
continue;
}
final String shiftTitle = 'Shift ${_formatDate(day)}';
final fdc.Timestamp dayTimestamp = _service.toTimestamp(
final Timestamp dayTimestamp = _service.toTimestamp(
DateTime(day.year, day.month, day.day),
);
final fdc.OperationResult<dc.CreateShiftData, dc.CreateShiftVariables> shiftResult =
await _service.connector
.createShift(title: shiftTitle, orderId: orderId)
.date(dayTimestamp)
.location(hub.name)
.locationAddress(hub.address)
.latitude(hub.latitude)
.longitude(hub.longitude)
.placeId(hub.placeId)
.city(hub.city)
.state(hub.state)
.street(hub.street)
.country(hub.country)
.status(dc.ShiftStatus.OPEN)
.workersNeeded(workersNeeded)
.filled(0)
.durationDays(1)
.cost(shiftCost)
.execute();
final OperationResult<dc.CreateShiftData, dc.CreateShiftVariables>
shiftResult = await _service.connector
.createShift(title: shiftTitle, orderId: orderId)
.date(dayTimestamp)
.location(hub.name)
.locationAddress(hub.address)
.latitude(hub.latitude)
.longitude(hub.longitude)
.placeId(hub.placeId)
.city(hub.city)
.state(hub.state)
.street(hub.street)
.country(hub.country)
.status(dc.ShiftStatus.OPEN)
.workersNeeded(workersNeeded)
.filled(0)
.durationDays(1)
.cost(shiftCost)
.execute();
final String shiftId = shiftResult.data.shift_insert.id;
shiftIds.add(shiftId);
@@ -322,8 +331,9 @@ class ClientCreateOrderRepositoryImpl implements ClientCreateOrderRepositoryInte
for (final domain.OneTimeOrderPosition position in order.positions) {
final DateTime start = _parseTime(day, position.startTime);
final DateTime end = _parseTime(day, position.endTime);
final DateTime normalizedEnd =
end.isBefore(start) ? end.add(const Duration(days: 1)) : end;
final DateTime normalizedEnd = end.isBefore(start)
? end.add(const Duration(days: 1))
: end;
final double hours = normalizedEnd.difference(start).inMinutes / 60.0;
final double rate = order.roleRates[position.role] ?? 0;
final double totalValue = rate * hours * position.count;
@@ -346,7 +356,7 @@ class ClientCreateOrderRepositoryImpl implements ClientCreateOrderRepositoryInte
await _service.connector
.updateOrder(id: orderId, teamHubId: hub.id)
.shifts(fdc.AnyValue(shiftIds))
.shifts(AnyValue(shiftIds))
.execute();
});
}
@@ -363,13 +373,76 @@ class ClientCreateOrderRepositoryImpl implements ClientCreateOrderRepositoryInte
throw UnimplementedError('Reorder functionality is not yet implemented.');
}
@override
Future<domain.ReorderData> getOrderDetailsForReorder(String orderId) async {
return _service.run(() async {
final String businessId = await _service.getBusinessId();
final QueryResult<
dc.ListShiftRolesByBusinessAndOrderData,
dc.ListShiftRolesByBusinessAndOrderVariables
>
result = await _service.connector
.listShiftRolesByBusinessAndOrder(
businessId: businessId,
orderId: orderId,
)
.execute();
final List<dc.ListShiftRolesByBusinessAndOrderShiftRoles> shiftRoles =
result.data.shiftRoles;
if (shiftRoles.isEmpty) {
throw Exception('Order not found or has no roles.');
}
final dc.ListShiftRolesByBusinessAndOrderShiftRolesShiftOrder order =
shiftRoles.first.shift.order;
final domain.OrderType orderType = _mapOrderType(order.orderType);
final dc.ListShiftRolesByBusinessAndOrderShiftRolesShiftOrderTeamHub
teamHub = order.teamHub;
return domain.ReorderData(
orderId: orderId,
eventName: order.eventName ?? '',
vendorId: order.vendorId ?? '',
orderType: orderType,
hub: domain.OneTimeOrderHubDetails(
id: teamHub.id,
name: teamHub.hubName,
address: teamHub.address,
placeId: teamHub.placeId,
latitude: 0, // Not available in this query
longitude: 0,
),
positions: shiftRoles.map((
dc.ListShiftRolesByBusinessAndOrderShiftRoles role,
) {
return domain.ReorderPosition(
roleId: role.roleId,
count: role.count,
startTime: _formatTimestamp(role.startTime),
endTime: _formatTimestamp(role.endTime),
lunchBreak: _formatBreakDuration(role.breakType),
);
}).toList(),
startDate: order.startDate?.toDateTime(),
endDate: order.endDate?.toDateTime(),
recurringDays: order.recurringDays ?? const <String>[],
permanentDays: order.permanentDays ?? const <String>[],
);
});
}
double _calculateShiftCost(domain.OneTimeOrder order) {
double total = 0;
for (final domain.OneTimeOrderPosition position in order.positions) {
final DateTime start = _parseTime(order.date, position.startTime);
final DateTime end = _parseTime(order.date, position.endTime);
final DateTime normalizedEnd =
end.isBefore(start) ? end.add(const Duration(days: 1)) : end;
final DateTime normalizedEnd = end.isBefore(start)
? end.add(const Duration(days: 1))
: end;
final double hours = normalizedEnd.difference(start).inMinutes / 60.0;
final double rate = order.roleRates[position.role] ?? 0;
total += rate * hours * position.count;
@@ -382,8 +455,9 @@ class ClientCreateOrderRepositoryImpl implements ClientCreateOrderRepositoryInte
for (final domain.RecurringOrderPosition position in order.positions) {
final DateTime start = _parseTime(order.startDate, position.startTime);
final DateTime end = _parseTime(order.startDate, position.endTime);
final DateTime normalizedEnd =
end.isBefore(start) ? end.add(const Duration(days: 1)) : end;
final DateTime normalizedEnd = end.isBefore(start)
? end.add(const Duration(days: 1))
: end;
final double hours = normalizedEnd.difference(start).inMinutes / 60.0;
final double rate = order.roleRates[position.role] ?? 0;
total += rate * hours * position.count;
@@ -396,8 +470,9 @@ class ClientCreateOrderRepositoryImpl implements ClientCreateOrderRepositoryInte
for (final domain.OneTimeOrderPosition position in order.positions) {
final DateTime start = _parseTime(order.startDate, position.startTime);
final DateTime end = _parseTime(order.startDate, position.endTime);
final DateTime normalizedEnd =
end.isBefore(start) ? end.add(const Duration(days: 1)) : end;
final DateTime normalizedEnd = end.isBefore(start)
? end.add(const Duration(days: 1))
: end;
final double hours = normalizedEnd.difference(start).inMinutes / 60.0;
final double rate = order.roleRates[position.role] ?? 0;
total += rate * hours * position.count;
@@ -473,4 +548,49 @@ class ClientCreateOrderRepositoryImpl implements ClientCreateOrderRepositoryInte
final String day = dateTime.day.toString().padLeft(2, '0');
return '$year-$month-$day';
}
String _formatTimestamp(Timestamp? value) {
if (value == null) return '';
try {
return DateFormat('HH:mm').format(value.toDateTime());
} catch (_) {
return '';
}
}
String _formatBreakDuration(dc.EnumValue<dc.BreakDuration>? breakType) {
if (breakType is dc.Known<dc.BreakDuration>) {
switch (breakType.value) {
case dc.BreakDuration.MIN_10:
return 'MIN_10';
case dc.BreakDuration.MIN_15:
return 'MIN_15';
case dc.BreakDuration.MIN_30:
return 'MIN_30';
case dc.BreakDuration.MIN_45:
return 'MIN_45';
case dc.BreakDuration.MIN_60:
return 'MIN_60';
case dc.BreakDuration.NO_BREAK:
return 'NO_BREAK';
}
}
return 'NO_BREAK';
}
domain.OrderType _mapOrderType(dc.EnumValue<dc.OrderType>? orderType) {
if (orderType is dc.Known<dc.OrderType>) {
switch (orderType.value) {
case dc.OrderType.ONE_TIME:
return domain.OrderType.oneTime;
case dc.OrderType.RECURRING:
return domain.OrderType.recurring;
case dc.OrderType.PERMANENT:
return domain.OrderType.permanent;
case dc.OrderType.RAPID:
return domain.OrderType.oneTime;
}
}
return domain.OrderType.oneTime;
}
}

View File

@@ -29,4 +29,9 @@ abstract interface class ClientCreateOrderRepositoryInterface {
/// [previousOrderId] is the ID of the order to reorder.
/// [newDate] is the new date for the order.
Future<void> reorder(String previousOrderId, DateTime newDate);
/// Fetches the details of an existing order to be used as a template for a new order.
///
/// returns [ReorderData] containing the order details and positions.
Future<ReorderData> getOrderDetailsForReorder(String orderId);
}

View File

@@ -0,0 +1,14 @@
import 'package:krow_core/core.dart';
import 'package:krow_domain/krow_domain.dart';
import '../repositories/client_create_order_repository_interface.dart';
/// Use case for fetching order details for reordering.
class GetOrderDetailsForReorderUseCase implements UseCase<String, ReorderData> {
const GetOrderDetailsForReorderUseCase(this._repository);
final ClientCreateOrderRepositoryInterface _repository;
@override
Future<ReorderData> call(String orderId) {
return _repository.getOrderDetailsForReorder(orderId);
}
}

View File

@@ -1,6 +1,7 @@
import 'package:client_create_order/src/domain/arguments/one_time_order_arguments.dart';
import 'package:client_create_order/src/domain/usecases/create_one_time_order_usecase.dart';
import 'package:firebase_data_connect/firebase_data_connect.dart';
import 'package:client_create_order/src/domain/usecases/get_order_details_for_reorder_usecase.dart';
import 'package:firebase_data_connect/firebase_data_connect.dart' as fdc;
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:krow_core/core.dart';
import 'package:krow_data_connect/krow_data_connect.dart' as dc;
@@ -14,8 +15,11 @@ class OneTimeOrderBloc extends Bloc<OneTimeOrderEvent, OneTimeOrderState>
with
BlocErrorHandler<OneTimeOrderState>,
SafeBloc<OneTimeOrderEvent, OneTimeOrderState> {
OneTimeOrderBloc(this._createOneTimeOrderUseCase, this._service)
: super(OneTimeOrderState.initial()) {
OneTimeOrderBloc(
this._createOneTimeOrderUseCase,
this._getOrderDetailsForReorderUseCase,
this._service,
) : super(OneTimeOrderState.initial()) {
on<OneTimeOrderVendorsLoaded>(_onVendorsLoaded);
on<OneTimeOrderVendorChanged>(_onVendorChanged);
on<OneTimeOrderHubsLoaded>(_onHubsLoaded);
@@ -32,12 +36,13 @@ class OneTimeOrderBloc extends Bloc<OneTimeOrderEvent, OneTimeOrderState>
_loadHubs();
}
final CreateOneTimeOrderUseCase _createOneTimeOrderUseCase;
final GetOrderDetailsForReorderUseCase _getOrderDetailsForReorderUseCase;
final dc.DataConnectService _service;
Future<void> _loadVendors() async {
final List<Vendor>? vendors = await handleErrorWithResult(
action: () async {
final QueryResult<dc.ListVendorsData, void> result = await _service
final fdc.QueryResult<dc.ListVendorsData, void> result = await _service
.connector
.listVendors()
.execute();
@@ -65,7 +70,7 @@ class OneTimeOrderBloc extends Bloc<OneTimeOrderEvent, OneTimeOrderState>
) async {
final List<OneTimeOrderRoleOption>? roles = await handleErrorWithResult(
action: () async {
final QueryResult<
final fdc.QueryResult<
dc.ListRolesByVendorIdData,
dc.ListRolesByVendorIdVariables
>
@@ -95,7 +100,7 @@ class OneTimeOrderBloc extends Bloc<OneTimeOrderEvent, OneTimeOrderState>
final List<OneTimeOrderHubOption>? hubs = await handleErrorWithResult(
action: () async {
final String businessId = await _service.getBusinessId();
final QueryResult<
final fdc.QueryResult<
dc.ListTeamHubsByOwnerIdData,
dc.ListTeamHubsByOwnerIdVariables
>
@@ -274,27 +279,72 @@ class OneTimeOrderBloc extends Bloc<OneTimeOrderEvent, OneTimeOrderState>
);
}
void _onInitialized(
Future<void> _onInitialized(
OneTimeOrderInitialized event,
Emitter<OneTimeOrderState> emit,
) {
) async {
final Map<String, dynamic> data = event.data;
final String title = data['title']?.toString() ?? '';
final int workers = (data['workers'] as int?) ?? 1;
final DateTime? startDate = data['startDate'] as DateTime?;
final String? orderId = data['orderId']?.toString();
emit(
state.copyWith(
eventName: title,
date: startDate ?? DateTime.now(),
positions: <OneTimeOrderPosition>[
OneTimeOrderPosition(
role: data['roleName']?.toString() ?? '',
count: workers,
startTime: data['startTime']?.toString() ?? '09:00',
endTime: data['endTime']?.toString() ?? '17:00',
emit(state.copyWith(eventName: title, date: startDate ?? DateTime.now()));
if (orderId == null || orderId.isEmpty) return;
emit(state.copyWith(status: OneTimeOrderStatus.loading));
await handleError(
emit: emit.call,
action: () async {
final ReorderData orderDetails =
await _getOrderDetailsForReorderUseCase(orderId);
// Map positions
final List<OneTimeOrderPosition> positions = orderDetails.positions.map(
(ReorderPosition role) {
return OneTimeOrderPosition(
role: role.roleId,
count: role.count,
startTime: role.startTime,
endTime: role.endTime,
lunchBreak: role.lunchBreak,
);
},
).toList();
// Update state with order details
final Vendor? selectedVendor = state.vendors
.where((Vendor v) => v.id == orderDetails.vendorId)
.firstOrNull;
final OneTimeOrderHubOption? selectedHub = state.hubs
.where(
(OneTimeOrderHubOption h) =>
h.placeId == orderDetails.hub.placeId,
)
.firstOrNull;
emit(
state.copyWith(
eventName: orderDetails.eventName.isNotEmpty
? orderDetails.eventName
: title,
positions: positions,
selectedVendor: selectedVendor,
selectedHub: selectedHub,
location: selectedHub?.name ?? '',
status: OneTimeOrderStatus.initial,
),
],
);
if (selectedVendor != null) {
await _loadRolesForVendor(selectedVendor.id, emit);
}
},
onError: (String errorKey) => state.copyWith(
status: OneTimeOrderStatus.failure,
errorMessage: errorKey,
),
);
}

View File

@@ -1,5 +1,6 @@
import 'package:client_create_order/src/domain/usecases/create_permanent_order_usecase.dart';
import 'package:firebase_data_connect/firebase_data_connect.dart';
import 'package:client_create_order/src/domain/usecases/get_order_details_for_reorder_usecase.dart';
import 'package:firebase_data_connect/firebase_data_connect.dart' as fdc;
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:krow_core/core.dart';
import 'package:krow_data_connect/krow_data_connect.dart' as dc;
@@ -13,8 +14,11 @@ class PermanentOrderBloc extends Bloc<PermanentOrderEvent, PermanentOrderState>
with
BlocErrorHandler<PermanentOrderState>,
SafeBloc<PermanentOrderEvent, PermanentOrderState> {
PermanentOrderBloc(this._createPermanentOrderUseCase, this._service)
: super(PermanentOrderState.initial()) {
PermanentOrderBloc(
this._createPermanentOrderUseCase,
this._getOrderDetailsForReorderUseCase,
this._service,
) : super(PermanentOrderState.initial()) {
on<PermanentOrderVendorsLoaded>(_onVendorsLoaded);
on<PermanentOrderVendorChanged>(_onVendorChanged);
on<PermanentOrderHubsLoaded>(_onHubsLoaded);
@@ -33,6 +37,7 @@ class PermanentOrderBloc extends Bloc<PermanentOrderEvent, PermanentOrderState>
}
final CreatePermanentOrderUseCase _createPermanentOrderUseCase;
final GetOrderDetailsForReorderUseCase _getOrderDetailsForReorderUseCase;
final dc.DataConnectService _service;
static const List<String> _dayLabels = <String>[
@@ -48,7 +53,7 @@ class PermanentOrderBloc extends Bloc<PermanentOrderEvent, PermanentOrderState>
Future<void> _loadVendors() async {
final List<domain.Vendor>? vendors = await handleErrorWithResult(
action: () async {
final QueryResult<dc.ListVendorsData, void> result = await _service
final fdc.QueryResult<dc.ListVendorsData, void> result = await _service
.connector
.listVendors()
.execute();
@@ -76,7 +81,7 @@ class PermanentOrderBloc extends Bloc<PermanentOrderEvent, PermanentOrderState>
) async {
final List<PermanentOrderRoleOption>? roles = await handleErrorWithResult(
action: () async {
final QueryResult<
final fdc.QueryResult<
dc.ListRolesByVendorIdData,
dc.ListRolesByVendorIdVariables
>
@@ -106,7 +111,7 @@ class PermanentOrderBloc extends Bloc<PermanentOrderEvent, PermanentOrderState>
final List<PermanentOrderHubOption>? hubs = await handleErrorWithResult(
action: () async {
final String businessId = await _service.getBusinessId();
final QueryResult<
final fdc.QueryResult<
dc.ListTeamHubsByOwnerIdData,
dc.ListTeamHubsByOwnerIdVariables
>
@@ -337,27 +342,76 @@ class PermanentOrderBloc extends Bloc<PermanentOrderEvent, PermanentOrderState>
);
}
void _onInitialized(
Future<void> _onInitialized(
PermanentOrderInitialized event,
Emitter<PermanentOrderState> emit,
) {
) async {
final Map<String, dynamic> data = event.data;
final String title = data['title']?.toString() ?? '';
final int workers = (data['workers'] as int?) ?? 1;
final DateTime? startDate = data['startDate'] as DateTime?;
final String? orderId = data['orderId']?.toString();
emit(
state.copyWith(
eventName: title,
startDate: startDate ?? DateTime.now(),
positions: <PermanentOrderPosition>[
PermanentOrderPosition(
role: data['roleName']?.toString() ?? '',
count: workers,
startTime: data['startTime']?.toString() ?? '09:00',
endTime: data['endTime']?.toString() ?? '17:00',
state.copyWith(eventName: title, startDate: startDate ?? DateTime.now()),
);
if (orderId == null || orderId.isEmpty) return;
emit(state.copyWith(status: PermanentOrderStatus.loading));
await handleError(
emit: emit.call,
action: () async {
final domain.ReorderData orderDetails =
await _getOrderDetailsForReorderUseCase(orderId);
// Map positions
final List<PermanentOrderPosition> positions = orderDetails.positions
.map((domain.ReorderPosition role) {
return PermanentOrderPosition(
role: role.roleId,
count: role.count,
startTime: role.startTime,
endTime: role.endTime,
lunchBreak: role.lunchBreak,
);
})
.toList();
// Update state with order details
final domain.Vendor? selectedVendor = state.vendors
.where((domain.Vendor v) => v.id == orderDetails.vendorId)
.firstOrNull;
final PermanentOrderHubOption? selectedHub = state.hubs
.where(
(PermanentOrderHubOption h) =>
h.placeId == orderDetails.hub.placeId,
)
.firstOrNull;
emit(
state.copyWith(
eventName: orderDetails.eventName.isNotEmpty
? orderDetails.eventName
: title,
positions: positions,
selectedVendor: selectedVendor,
selectedHub: selectedHub,
location: selectedHub?.name ?? '',
status: PermanentOrderStatus.initial,
startDate: startDate ?? orderDetails.startDate ?? DateTime.now(),
permanentDays: orderDetails.permanentDays,
),
],
);
if (selectedVendor != null) {
await _loadRolesForVendor(selectedVendor.id, emit);
}
},
onError: (String errorKey) => state.copyWith(
status: PermanentOrderStatus.failure,
errorMessage: errorKey,
),
);
}

View File

@@ -1,5 +1,6 @@
import 'package:client_create_order/src/domain/usecases/create_recurring_order_usecase.dart';
import 'package:firebase_data_connect/firebase_data_connect.dart';
import 'package:client_create_order/src/domain/usecases/get_order_details_for_reorder_usecase.dart';
import 'package:firebase_data_connect/firebase_data_connect.dart' as fdc;
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:krow_core/core.dart';
import 'package:krow_data_connect/krow_data_connect.dart' as dc;
@@ -13,8 +14,11 @@ class RecurringOrderBloc extends Bloc<RecurringOrderEvent, RecurringOrderState>
with
BlocErrorHandler<RecurringOrderState>,
SafeBloc<RecurringOrderEvent, RecurringOrderState> {
RecurringOrderBloc(this._createRecurringOrderUseCase, this._service)
: super(RecurringOrderState.initial()) {
RecurringOrderBloc(
this._createRecurringOrderUseCase,
this._getOrderDetailsForReorderUseCase,
this._service,
) : super(RecurringOrderState.initial()) {
on<RecurringOrderVendorsLoaded>(_onVendorsLoaded);
on<RecurringOrderVendorChanged>(_onVendorChanged);
on<RecurringOrderHubsLoaded>(_onHubsLoaded);
@@ -34,6 +38,7 @@ class RecurringOrderBloc extends Bloc<RecurringOrderEvent, RecurringOrderState>
}
final CreateRecurringOrderUseCase _createRecurringOrderUseCase;
final GetOrderDetailsForReorderUseCase _getOrderDetailsForReorderUseCase;
final dc.DataConnectService _service;
static const List<String> _dayLabels = <String>[
@@ -49,7 +54,7 @@ class RecurringOrderBloc extends Bloc<RecurringOrderEvent, RecurringOrderState>
Future<void> _loadVendors() async {
final List<domain.Vendor>? vendors = await handleErrorWithResult(
action: () async {
final QueryResult<dc.ListVendorsData, void> result = await _service
final fdc.QueryResult<dc.ListVendorsData, void> result = await _service
.connector
.listVendors()
.execute();
@@ -77,7 +82,7 @@ class RecurringOrderBloc extends Bloc<RecurringOrderEvent, RecurringOrderState>
) async {
final List<RecurringOrderRoleOption>? roles = await handleErrorWithResult(
action: () async {
final QueryResult<
final fdc.QueryResult<
dc.ListRolesByVendorIdData,
dc.ListRolesByVendorIdVariables
>
@@ -107,7 +112,7 @@ class RecurringOrderBloc extends Bloc<RecurringOrderEvent, RecurringOrderState>
final List<RecurringOrderHubOption>? hubs = await handleErrorWithResult(
action: () async {
final String businessId = await _service.getBusinessId();
final QueryResult<
final fdc.QueryResult<
dc.ListTeamHubsByOwnerIdData,
dc.ListTeamHubsByOwnerIdVariables
>
@@ -356,27 +361,77 @@ class RecurringOrderBloc extends Bloc<RecurringOrderEvent, RecurringOrderState>
);
}
void _onInitialized(
Future<void> _onInitialized(
RecurringOrderInitialized event,
Emitter<RecurringOrderState> emit,
) {
) async {
final Map<String, dynamic> data = event.data;
final String title = data['title']?.toString() ?? '';
final int workers = (data['workers'] as int?) ?? 1;
final DateTime? startDate = data['startDate'] as DateTime?;
final String? orderId = data['orderId']?.toString();
emit(
state.copyWith(
eventName: title,
startDate: startDate ?? DateTime.now(),
positions: <RecurringOrderPosition>[
RecurringOrderPosition(
role: data['roleName']?.toString() ?? '',
count: workers,
startTime: data['startTime']?.toString() ?? '09:00',
endTime: data['endTime']?.toString() ?? '17:00',
state.copyWith(eventName: title, startDate: startDate ?? DateTime.now()),
);
if (orderId == null || orderId.isEmpty) return;
emit(state.copyWith(status: RecurringOrderStatus.loading));
await handleError(
emit: emit.call,
action: () async {
final domain.ReorderData orderDetails =
await _getOrderDetailsForReorderUseCase(orderId);
// Map positions
final List<RecurringOrderPosition> positions = orderDetails.positions
.map((domain.ReorderPosition role) {
return RecurringOrderPosition(
role: role.roleId,
count: role.count,
startTime: role.startTime,
endTime: role.endTime,
lunchBreak: role.lunchBreak,
);
})
.toList();
// Update state with order details
final domain.Vendor? selectedVendor = state.vendors
.where((domain.Vendor v) => v.id == orderDetails.vendorId)
.firstOrNull;
final RecurringOrderHubOption? selectedHub = state.hubs
.where(
(RecurringOrderHubOption h) =>
h.placeId == orderDetails.hub.placeId,
)
.firstOrNull;
emit(
state.copyWith(
eventName: orderDetails.eventName.isNotEmpty
? orderDetails.eventName
: title,
positions: positions,
selectedVendor: selectedVendor,
selectedHub: selectedHub,
location: selectedHub?.name ?? '',
status: RecurringOrderStatus.initial,
startDate: startDate ?? orderDetails.startDate ?? DateTime.now(),
endDate: orderDetails.endDate ?? DateTime.now(),
recurringDays: orderDetails.recurringDays,
),
],
);
if (selectedVendor != null) {
await _loadRolesForVendor(selectedVendor.id, emit);
}
},
onError: (String errorKey) => state.copyWith(
status: RecurringOrderStatus.failure,
errorMessage: errorKey,
),
);
}