Merge branch 'origin/dev' into feature/session-persistence-new
This commit is contained in:
@@ -10,6 +10,7 @@ class OneTimeOrderPositionArgument extends UseCaseArgument {
|
||||
required this.endTime,
|
||||
this.roleName,
|
||||
this.lunchBreak,
|
||||
this.hourlyRateCents,
|
||||
});
|
||||
|
||||
/// The role ID for this position.
|
||||
@@ -30,9 +31,19 @@ class OneTimeOrderPositionArgument extends UseCaseArgument {
|
||||
/// Break duration label (e.g. `'MIN_30'`, `'NO_BREAK'`), if set.
|
||||
final String? lunchBreak;
|
||||
|
||||
/// Hourly rate in cents for this position, if set.
|
||||
final int? hourlyRateCents;
|
||||
|
||||
@override
|
||||
List<Object?> get props =>
|
||||
<Object?>[roleId, roleName, workerCount, startTime, endTime, lunchBreak];
|
||||
List<Object?> get props => <Object?>[
|
||||
roleId,
|
||||
roleName,
|
||||
workerCount,
|
||||
startTime,
|
||||
endTime,
|
||||
lunchBreak,
|
||||
hourlyRateCents,
|
||||
];
|
||||
}
|
||||
|
||||
/// Typed arguments for [CreateOneTimeOrderUseCase].
|
||||
@@ -63,6 +74,40 @@ class OneTimeOrderArguments extends UseCaseArgument {
|
||||
/// The selected vendor ID, if applicable.
|
||||
final String? vendorId;
|
||||
|
||||
/// Serialises these arguments into the V2 API payload shape.
|
||||
///
|
||||
/// Times and dates are converted to UTC so the backend's
|
||||
/// `combineDateAndTime` helper receives the correct values.
|
||||
Map<String, dynamic> toJson() {
|
||||
final String firstStartTime =
|
||||
positions.isNotEmpty ? positions.first.startTime : '00:00';
|
||||
final String utcOrderDate = toUtcDateIso(orderDate, firstStartTime);
|
||||
|
||||
final List<Map<String, dynamic>> positionsList =
|
||||
positions.map((OneTimeOrderPositionArgument p) {
|
||||
return <String, dynamic>{
|
||||
if (p.roleName != null) 'roleName': p.roleName,
|
||||
if (p.roleId.isNotEmpty) 'roleId': p.roleId,
|
||||
'workerCount': p.workerCount,
|
||||
'startTime': toUtcTimeHHmm(orderDate, p.startTime),
|
||||
'endTime': toUtcTimeHHmm(orderDate, p.endTime),
|
||||
if (p.lunchBreak != null &&
|
||||
p.lunchBreak != 'NO_BREAK' &&
|
||||
p.lunchBreak!.isNotEmpty)
|
||||
'lunchBreakMinutes': breakMinutesFromLabel(p.lunchBreak!),
|
||||
if (p.hourlyRateCents != null) 'hourlyRateCents': p.hourlyRateCents,
|
||||
};
|
||||
}).toList();
|
||||
|
||||
return <String, dynamic>{
|
||||
'hubId': hubId,
|
||||
'eventName': eventName,
|
||||
'orderDate': utcOrderDate,
|
||||
'positions': positionsList,
|
||||
if (vendorId != null) 'vendorId': vendorId,
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
List<Object?> get props =>
|
||||
<Object?>[hubId, eventName, orderDate, positions, vendorId];
|
||||
|
||||
@@ -9,6 +9,7 @@ class PermanentOrderPositionArgument extends UseCaseArgument {
|
||||
required this.startTime,
|
||||
required this.endTime,
|
||||
this.roleName,
|
||||
this.hourlyRateCents,
|
||||
});
|
||||
|
||||
/// The role ID for this position.
|
||||
@@ -26,9 +27,18 @@ class PermanentOrderPositionArgument extends UseCaseArgument {
|
||||
/// Shift end time in HH:mm format.
|
||||
final String endTime;
|
||||
|
||||
/// Hourly rate in cents for this position, if set.
|
||||
final int? hourlyRateCents;
|
||||
|
||||
@override
|
||||
List<Object?> get props =>
|
||||
<Object?>[roleId, roleName, workerCount, startTime, endTime];
|
||||
List<Object?> get props => <Object?>[
|
||||
roleId,
|
||||
roleName,
|
||||
workerCount,
|
||||
startTime,
|
||||
endTime,
|
||||
hourlyRateCents,
|
||||
];
|
||||
}
|
||||
|
||||
/// Typed arguments for [CreatePermanentOrderUseCase].
|
||||
@@ -63,6 +73,52 @@ class PermanentOrderArguments extends UseCaseArgument {
|
||||
/// The selected vendor ID, if applicable.
|
||||
final String? vendorId;
|
||||
|
||||
/// Day-of-week labels in Sunday-first order, matching the V2 API convention.
|
||||
static const List<String> _dayLabels = <String>[
|
||||
'SUN',
|
||||
'MON',
|
||||
'TUE',
|
||||
'WED',
|
||||
'THU',
|
||||
'FRI',
|
||||
'SAT',
|
||||
];
|
||||
|
||||
/// Serialises these arguments into the V2 API payload shape.
|
||||
///
|
||||
/// Times and dates are converted to UTC so the backend's
|
||||
/// `combineDateAndTime` helper receives the correct values.
|
||||
Map<String, dynamic> toJson() {
|
||||
final String firstStartTime =
|
||||
positions.isNotEmpty ? positions.first.startTime : '00:00';
|
||||
final String utcStartDate = toUtcDateIso(startDate, firstStartTime);
|
||||
|
||||
final List<int> daysOfWeekList = daysOfWeek
|
||||
.map((String day) => _dayLabels.indexOf(day) % 7)
|
||||
.toList();
|
||||
|
||||
final List<Map<String, dynamic>> positionsList =
|
||||
positions.map((PermanentOrderPositionArgument p) {
|
||||
return <String, dynamic>{
|
||||
if (p.roleName != null) 'roleName': p.roleName,
|
||||
if (p.roleId.isNotEmpty) 'roleId': p.roleId,
|
||||
'workerCount': p.workerCount,
|
||||
'startTime': toUtcTimeHHmm(startDate, p.startTime),
|
||||
'endTime': toUtcTimeHHmm(startDate, p.endTime),
|
||||
if (p.hourlyRateCents != null) 'hourlyRateCents': p.hourlyRateCents,
|
||||
};
|
||||
}).toList();
|
||||
|
||||
return <String, dynamic>{
|
||||
'hubId': hubId,
|
||||
'eventName': eventName,
|
||||
'startDate': utcStartDate,
|
||||
'daysOfWeek': daysOfWeekList,
|
||||
'positions': positionsList,
|
||||
if (vendorId != null) 'vendorId': vendorId,
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
List<Object?> get props => <Object?>[
|
||||
hubId,
|
||||
|
||||
@@ -9,6 +9,7 @@ class RecurringOrderPositionArgument extends UseCaseArgument {
|
||||
required this.startTime,
|
||||
required this.endTime,
|
||||
this.roleName,
|
||||
this.hourlyRateCents,
|
||||
});
|
||||
|
||||
/// The role ID for this position.
|
||||
@@ -26,9 +27,18 @@ class RecurringOrderPositionArgument extends UseCaseArgument {
|
||||
/// Shift end time in HH:mm format.
|
||||
final String endTime;
|
||||
|
||||
/// Hourly rate in cents for this position, if set.
|
||||
final int? hourlyRateCents;
|
||||
|
||||
@override
|
||||
List<Object?> get props =>
|
||||
<Object?>[roleId, roleName, workerCount, startTime, endTime];
|
||||
List<Object?> get props => <Object?>[
|
||||
roleId,
|
||||
roleName,
|
||||
workerCount,
|
||||
startTime,
|
||||
endTime,
|
||||
hourlyRateCents,
|
||||
];
|
||||
}
|
||||
|
||||
/// Typed arguments for [CreateRecurringOrderUseCase].
|
||||
@@ -67,6 +77,54 @@ class RecurringOrderArguments extends UseCaseArgument {
|
||||
/// The selected vendor ID, if applicable.
|
||||
final String? vendorId;
|
||||
|
||||
/// Day-of-week labels in Sunday-first order, matching the V2 API convention.
|
||||
static const List<String> _dayLabels = <String>[
|
||||
'SUN',
|
||||
'MON',
|
||||
'TUE',
|
||||
'WED',
|
||||
'THU',
|
||||
'FRI',
|
||||
'SAT',
|
||||
];
|
||||
|
||||
/// Serialises these arguments into the V2 API payload shape.
|
||||
///
|
||||
/// Times and dates are converted to UTC so the backend's
|
||||
/// `combineDateAndTime` helper receives the correct values.
|
||||
Map<String, dynamic> toJson() {
|
||||
final String firstStartTime =
|
||||
positions.isNotEmpty ? positions.first.startTime : '00:00';
|
||||
final String utcStartDate = toUtcDateIso(startDate, firstStartTime);
|
||||
final String utcEndDate = toUtcDateIso(endDate, firstStartTime);
|
||||
|
||||
final List<int> recurrenceDaysList = recurringDays
|
||||
.map((String day) => _dayLabels.indexOf(day) % 7)
|
||||
.toList();
|
||||
|
||||
final List<Map<String, dynamic>> positionsList =
|
||||
positions.map((RecurringOrderPositionArgument p) {
|
||||
return <String, dynamic>{
|
||||
if (p.roleName != null) 'roleName': p.roleName,
|
||||
if (p.roleId.isNotEmpty) 'roleId': p.roleId,
|
||||
'workerCount': p.workerCount,
|
||||
'startTime': toUtcTimeHHmm(startDate, p.startTime),
|
||||
'endTime': toUtcTimeHHmm(startDate, p.endTime),
|
||||
if (p.hourlyRateCents != null) 'hourlyRateCents': p.hourlyRateCents,
|
||||
};
|
||||
}).toList();
|
||||
|
||||
return <String, dynamic>{
|
||||
'hubId': hubId,
|
||||
'eventName': eventName,
|
||||
'startDate': utcStartDate,
|
||||
'endDate': utcEndDate,
|
||||
'recurrenceDays': recurrenceDaysList,
|
||||
'positions': positionsList,
|
||||
if (vendorId != null) 'vendorId': vendorId,
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
List<Object?> get props => <Object?>[
|
||||
hubId,
|
||||
|
||||
@@ -1,49 +1,19 @@
|
||||
import 'package:krow_core/core.dart';
|
||||
|
||||
import '../arguments/one_time_order_arguments.dart';
|
||||
import '../repositories/client_create_order_repository_interface.dart';
|
||||
|
||||
/// Use case for creating a one-time staffing order.
|
||||
///
|
||||
/// Builds the V2 API payload from typed [OneTimeOrderArguments] and
|
||||
/// delegates submission to the repository. Payload construction (date
|
||||
/// formatting, position mapping, break-minutes conversion) is business
|
||||
/// logic that belongs here, not in the BLoC.
|
||||
class CreateOneTimeOrderUseCase
|
||||
implements UseCase<OneTimeOrderArguments, void> {
|
||||
/// Delegates payload construction to [OneTimeOrderArguments.toJson] and
|
||||
/// submission to the repository.
|
||||
class CreateOneTimeOrderUseCase {
|
||||
/// Creates a [CreateOneTimeOrderUseCase].
|
||||
const CreateOneTimeOrderUseCase(this._repository);
|
||||
|
||||
/// The create-order repository.
|
||||
final ClientCreateOrderRepositoryInterface _repository;
|
||||
|
||||
@override
|
||||
/// Creates a one-time order from the given arguments.
|
||||
Future<void> call(OneTimeOrderArguments input) {
|
||||
final String orderDate = formatDateToIso(input.orderDate);
|
||||
|
||||
final List<Map<String, dynamic>> positions =
|
||||
input.positions.map((OneTimeOrderPositionArgument p) {
|
||||
return <String, dynamic>{
|
||||
if (p.roleName != null) 'roleName': p.roleName,
|
||||
if (p.roleId.isNotEmpty) 'roleId': p.roleId,
|
||||
'workerCount': p.workerCount,
|
||||
'startTime': p.startTime,
|
||||
'endTime': p.endTime,
|
||||
if (p.lunchBreak != null &&
|
||||
p.lunchBreak != 'NO_BREAK' &&
|
||||
p.lunchBreak!.isNotEmpty)
|
||||
'lunchBreakMinutes': breakMinutesFromLabel(p.lunchBreak!),
|
||||
};
|
||||
}).toList();
|
||||
|
||||
final Map<String, dynamic> payload = <String, dynamic>{
|
||||
'hubId': input.hubId,
|
||||
'eventName': input.eventName,
|
||||
'orderDate': orderDate,
|
||||
'positions': positions,
|
||||
if (input.vendorId != null) 'vendorId': input.vendorId,
|
||||
};
|
||||
|
||||
return _repository.createOneTimeOrder(payload);
|
||||
return _repository.createOneTimeOrder(input.toJson());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,61 +1,19 @@
|
||||
import 'package:krow_core/core.dart';
|
||||
|
||||
import '../arguments/permanent_order_arguments.dart';
|
||||
import '../repositories/client_create_order_repository_interface.dart';
|
||||
|
||||
/// Day-of-week labels in Sunday-first order, matching the V2 API convention.
|
||||
const List<String> _dayLabels = <String>[
|
||||
'SUN',
|
||||
'MON',
|
||||
'TUE',
|
||||
'WED',
|
||||
'THU',
|
||||
'FRI',
|
||||
'SAT',
|
||||
];
|
||||
|
||||
/// Use case for creating a permanent staffing order.
|
||||
///
|
||||
/// Builds the V2 API payload from typed [PermanentOrderArguments] and
|
||||
/// delegates submission to the repository. Payload construction (date
|
||||
/// formatting, day-of-week mapping, position mapping) is business
|
||||
/// logic that belongs here, not in the BLoC.
|
||||
class CreatePermanentOrderUseCase
|
||||
implements UseCase<PermanentOrderArguments, void> {
|
||||
/// Delegates payload construction to [PermanentOrderArguments.toJson] and
|
||||
/// submission to the repository.
|
||||
class CreatePermanentOrderUseCase {
|
||||
/// Creates a [CreatePermanentOrderUseCase].
|
||||
const CreatePermanentOrderUseCase(this._repository);
|
||||
|
||||
/// The create-order repository.
|
||||
final ClientCreateOrderRepositoryInterface _repository;
|
||||
|
||||
@override
|
||||
/// Creates a permanent order from the given arguments.
|
||||
Future<void> call(PermanentOrderArguments input) {
|
||||
final String startDate = formatDateToIso(input.startDate);
|
||||
|
||||
final List<int> daysOfWeek = input.daysOfWeek
|
||||
.map((String day) => _dayLabels.indexOf(day) % 7)
|
||||
.toList();
|
||||
|
||||
final List<Map<String, dynamic>> positions =
|
||||
input.positions.map((PermanentOrderPositionArgument p) {
|
||||
return <String, dynamic>{
|
||||
if (p.roleName != null) 'roleName': p.roleName,
|
||||
if (p.roleId.isNotEmpty) 'roleId': p.roleId,
|
||||
'workerCount': p.workerCount,
|
||||
'startTime': p.startTime,
|
||||
'endTime': p.endTime,
|
||||
};
|
||||
}).toList();
|
||||
|
||||
final Map<String, dynamic> payload = <String, dynamic>{
|
||||
'hubId': input.hubId,
|
||||
'eventName': input.eventName,
|
||||
'startDate': startDate,
|
||||
'daysOfWeek': daysOfWeek,
|
||||
'positions': positions,
|
||||
if (input.vendorId != null) 'vendorId': input.vendorId,
|
||||
};
|
||||
|
||||
return _repository.createPermanentOrder(payload);
|
||||
return _repository.createPermanentOrder(input.toJson());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,63 +1,19 @@
|
||||
import 'package:krow_core/core.dart';
|
||||
|
||||
import '../arguments/recurring_order_arguments.dart';
|
||||
import '../repositories/client_create_order_repository_interface.dart';
|
||||
|
||||
/// Day-of-week labels in Sunday-first order, matching the V2 API convention.
|
||||
const List<String> _dayLabels = <String>[
|
||||
'SUN',
|
||||
'MON',
|
||||
'TUE',
|
||||
'WED',
|
||||
'THU',
|
||||
'FRI',
|
||||
'SAT',
|
||||
];
|
||||
|
||||
/// Use case for creating a recurring staffing order.
|
||||
///
|
||||
/// Builds the V2 API payload from typed [RecurringOrderArguments] and
|
||||
/// delegates submission to the repository. Payload construction (date
|
||||
/// formatting, recurrence-day mapping, position mapping) is business
|
||||
/// logic that belongs here, not in the BLoC.
|
||||
class CreateRecurringOrderUseCase
|
||||
implements UseCase<RecurringOrderArguments, void> {
|
||||
/// Delegates payload construction to [RecurringOrderArguments.toJson] and
|
||||
/// submission to the repository.
|
||||
class CreateRecurringOrderUseCase {
|
||||
/// Creates a [CreateRecurringOrderUseCase].
|
||||
const CreateRecurringOrderUseCase(this._repository);
|
||||
|
||||
/// The create-order repository.
|
||||
final ClientCreateOrderRepositoryInterface _repository;
|
||||
|
||||
@override
|
||||
/// Creates a recurring order from the given arguments.
|
||||
Future<void> call(RecurringOrderArguments input) {
|
||||
final String startDate = formatDateToIso(input.startDate);
|
||||
final String endDate = formatDateToIso(input.endDate);
|
||||
|
||||
final List<int> recurrenceDays = input.recurringDays
|
||||
.map((String day) => _dayLabels.indexOf(day) % 7)
|
||||
.toList();
|
||||
|
||||
final List<Map<String, dynamic>> positions =
|
||||
input.positions.map((RecurringOrderPositionArgument p) {
|
||||
return <String, dynamic>{
|
||||
if (p.roleName != null) 'roleName': p.roleName,
|
||||
if (p.roleId.isNotEmpty) 'roleId': p.roleId,
|
||||
'workerCount': p.workerCount,
|
||||
'startTime': p.startTime,
|
||||
'endTime': p.endTime,
|
||||
};
|
||||
}).toList();
|
||||
|
||||
final Map<String, dynamic> payload = <String, dynamic>{
|
||||
'hubId': input.hubId,
|
||||
'eventName': input.eventName,
|
||||
'startDate': startDate,
|
||||
'endDate': endDate,
|
||||
'recurrenceDays': recurrenceDays,
|
||||
'positions': positions,
|
||||
if (input.vendorId != null) 'vendorId': input.vendorId,
|
||||
};
|
||||
|
||||
return _repository.createRecurringOrder(payload);
|
||||
return _repository.createRecurringOrder(input.toJson());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -265,6 +265,8 @@ class OneTimeOrderBloc extends Bloc<OneTimeOrderEvent, OneTimeOrderState>
|
||||
startTime: p.startTime,
|
||||
endTime: p.endTime,
|
||||
lunchBreak: p.lunchBreak,
|
||||
hourlyRateCents:
|
||||
role != null ? (role.costPerHour * 100).round() : null,
|
||||
);
|
||||
}).toList();
|
||||
|
||||
|
||||
@@ -360,6 +360,8 @@ class PermanentOrderBloc extends Bloc<PermanentOrderEvent, PermanentOrderState>
|
||||
workerCount: p.count,
|
||||
startTime: p.startTime,
|
||||
endTime: p.endTime,
|
||||
hourlyRateCents:
|
||||
role != null ? (role.costPerHour * 100).round() : null,
|
||||
);
|
||||
}).toList();
|
||||
|
||||
|
||||
@@ -380,6 +380,8 @@ class RecurringOrderBloc extends Bloc<RecurringOrderEvent, RecurringOrderState>
|
||||
workerCount: p.count,
|
||||
startTime: p.startTime,
|
||||
endTime: p.endTime,
|
||||
hourlyRateCents:
|
||||
role != null ? (role.costPerHour * 100).round() : null,
|
||||
);
|
||||
}).toList();
|
||||
|
||||
|
||||
@@ -22,8 +22,8 @@ class ViewOrdersRepositoryImpl implements ViewOrdersRepositoryInterface {
|
||||
final ApiResponse response = await _api.get(
|
||||
ClientEndpoints.ordersView,
|
||||
params: <String, dynamic>{
|
||||
'startDate': start.toIso8601String(),
|
||||
'endDate': end.toIso8601String(),
|
||||
'startDate': start.toUtc().toIso8601String(),
|
||||
'endDate': end.toUtc().toIso8601String(),
|
||||
},
|
||||
);
|
||||
final Map<String, dynamic> data = response.data as Map<String, dynamic>;
|
||||
|
||||
@@ -48,13 +48,13 @@ class OrderEditSheetState extends State<OrderEditSheet> {
|
||||
_orderNameController = TextEditingController(text: widget.order.roleName);
|
||||
|
||||
final String startHH =
|
||||
widget.order.startsAt.toLocal().hour.toString().padLeft(2, '0');
|
||||
widget.order.startsAt.hour.toString().padLeft(2, '0');
|
||||
final String startMM =
|
||||
widget.order.startsAt.toLocal().minute.toString().padLeft(2, '0');
|
||||
widget.order.startsAt.minute.toString().padLeft(2, '0');
|
||||
final String endHH =
|
||||
widget.order.endsAt.toLocal().hour.toString().padLeft(2, '0');
|
||||
widget.order.endsAt.hour.toString().padLeft(2, '0');
|
||||
final String endMM =
|
||||
widget.order.endsAt.toLocal().minute.toString().padLeft(2, '0');
|
||||
widget.order.endsAt.minute.toString().padLeft(2, '0');
|
||||
|
||||
_positions = <Map<String, dynamic>>[
|
||||
<String, dynamic>{
|
||||
|
||||
@@ -77,9 +77,8 @@ class _ViewOrderCardState extends State<ViewOrderCard> {
|
||||
|
||||
/// Formats a [DateTime] to a display time string (e.g. "9:00 AM").
|
||||
String _formatTime({required DateTime dateTime}) {
|
||||
final DateTime local = dateTime.toLocal();
|
||||
final int hour24 = local.hour;
|
||||
final int minute = local.minute;
|
||||
final int hour24 = dateTime.hour;
|
||||
final int minute = dateTime.minute;
|
||||
final String ampm = hour24 >= 12 ? 'PM' : 'AM';
|
||||
int hour = hour24 % 12;
|
||||
if (hour == 0) hour = 12;
|
||||
@@ -124,7 +123,9 @@ class _ViewOrderCardState extends State<ViewOrderCard> {
|
||||
: 0;
|
||||
|
||||
final double hours = _computeHours(order);
|
||||
final double cost = order.totalCostCents / 100.0;
|
||||
final double cost = order.totalValue > 0
|
||||
? order.totalValue
|
||||
: order.totalCostCents / 100.0;
|
||||
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
|
||||
Reference in New Issue
Block a user