recurring v2
This commit is contained in:
@@ -179,61 +179,84 @@ class ClientCreateOrderRepositoryImpl implements ClientCreateOrderRepositoryInte
|
||||
|
||||
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 Set<String> selectedDays = Set<String>.from(order.recurringDays);
|
||||
final int workersNeeded = order.positions.fold<int>(
|
||||
0,
|
||||
(int sum, domain.RecurringOrderPosition position) => sum + position.count,
|
||||
);
|
||||
final String shiftTitle = 'Shift 1 ${_formatDate(order.startDate)}';
|
||||
final double shiftCost = _calculateRecurringShiftCost(order);
|
||||
|
||||
final fdc.OperationResult<dc.CreateShiftData, dc.CreateShiftVariables> shiftResult =
|
||||
final List<String> shiftIds = <String>[];
|
||||
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(
|
||||
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.PENDING)
|
||||
.workersNeeded(workersNeeded)
|
||||
.filled(0)
|
||||
.durationDays(1)
|
||||
.cost(shiftCost)
|
||||
.execute();
|
||||
|
||||
final String shiftId = shiftResult.data.shift_insert.id;
|
||||
shiftIds.add(shiftId);
|
||||
|
||||
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 double hours = normalizedEnd.difference(start).inMinutes / 60.0;
|
||||
final double rate = order.roleRates[position.role] ?? 0;
|
||||
final double totalValue = rate * hours * position.count;
|
||||
|
||||
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.PENDING)
|
||||
.workersNeeded(workersNeeded)
|
||||
.filled(0)
|
||||
.durationDays(1)
|
||||
.cost(shiftCost)
|
||||
.createShiftRole(
|
||||
shiftId: shiftId,
|
||||
roleId: position.role,
|
||||
count: position.count,
|
||||
)
|
||||
.startTime(_service.toTimestamp(start))
|
||||
.endTime(_service.toTimestamp(normalizedEnd))
|
||||
.hours(hours)
|
||||
.breakType(_breakDurationFromValue(position.lunchBreak))
|
||||
.isBreakPaid(_isBreakPaid(position.lunchBreak))
|
||||
.totalValue(totalValue)
|
||||
.execute();
|
||||
|
||||
final String shiftId = shiftResult.data.shift_insert.id;
|
||||
|
||||
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 double hours = normalizedEnd.difference(start).inMinutes / 60.0;
|
||||
final double rate = order.roleRates[position.role] ?? 0;
|
||||
final double totalValue = rate * hours * position.count;
|
||||
|
||||
await _service.connector
|
||||
.createShiftRole(
|
||||
shiftId: shiftId,
|
||||
roleId: position.role,
|
||||
count: position.count,
|
||||
)
|
||||
.startTime(_service.toTimestamp(start))
|
||||
.endTime(_service.toTimestamp(normalizedEnd))
|
||||
.hours(hours)
|
||||
.breakType(_breakDurationFromValue(position.lunchBreak))
|
||||
.isBreakPaid(_isBreakPaid(position.lunchBreak))
|
||||
.totalValue(totalValue)
|
||||
.execute();
|
||||
}
|
||||
}
|
||||
|
||||
await _service.connector
|
||||
.updateOrder(id: orderId, teamHubId: hub.id)
|
||||
.shifts(fdc.AnyValue(<String>[shiftId]))
|
||||
.shifts(fdc.AnyValue(shiftIds))
|
||||
.execute();
|
||||
});
|
||||
}
|
||||
@@ -272,6 +295,26 @@ class ClientCreateOrderRepositoryImpl implements ClientCreateOrderRepositoryInte
|
||||
return total;
|
||||
}
|
||||
|
||||
String _weekdayLabel(DateTime date) {
|
||||
switch (date.weekday) {
|
||||
case DateTime.monday:
|
||||
return 'MON';
|
||||
case DateTime.tuesday:
|
||||
return 'TUE';
|
||||
case DateTime.wednesday:
|
||||
return 'WED';
|
||||
case DateTime.thursday:
|
||||
return 'THU';
|
||||
case DateTime.friday:
|
||||
return 'FRI';
|
||||
case DateTime.saturday:
|
||||
return 'SAT';
|
||||
case DateTime.sunday:
|
||||
default:
|
||||
return 'SUN';
|
||||
}
|
||||
}
|
||||
|
||||
dc.BreakDuration _breakDurationFromValue(String value) {
|
||||
switch (value) {
|
||||
case 'MIN_10':
|
||||
|
||||
@@ -34,13 +34,13 @@ class RecurringOrderBloc extends Bloc<RecurringOrderEvent, RecurringOrderState>
|
||||
final dc.DataConnectService _service;
|
||||
|
||||
static const List<String> _dayLabels = <String>[
|
||||
'S',
|
||||
'M',
|
||||
'T',
|
||||
'W',
|
||||
'T',
|
||||
'F',
|
||||
'S',
|
||||
'SUN',
|
||||
'MON',
|
||||
'TUE',
|
||||
'WED',
|
||||
'THU',
|
||||
'FRI',
|
||||
'SAT',
|
||||
];
|
||||
|
||||
Future<void> _loadVendors() async {
|
||||
@@ -195,7 +195,26 @@ class RecurringOrderBloc extends Bloc<RecurringOrderEvent, RecurringOrderState>
|
||||
if (endDate.isBefore(event.date)) {
|
||||
endDate = event.date;
|
||||
}
|
||||
emit(state.copyWith(startDate: event.date, endDate: endDate));
|
||||
final int newDayIndex = event.date.weekday % 7;
|
||||
final int? autoIndex = state.autoSelectedDayIndex;
|
||||
List<String> days = List<String>.from(state.recurringDays);
|
||||
if (autoIndex != null) {
|
||||
final String oldDay = _dayLabels[autoIndex];
|
||||
days.remove(oldDay);
|
||||
final String newDay = _dayLabels[newDayIndex];
|
||||
if (!days.contains(newDay)) {
|
||||
days.add(newDay);
|
||||
}
|
||||
days = _sortDays(days);
|
||||
}
|
||||
emit(
|
||||
state.copyWith(
|
||||
startDate: event.date,
|
||||
endDate: endDate,
|
||||
recurringDays: days,
|
||||
autoSelectedDayIndex: autoIndex == null ? null : newDayIndex,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _onEndDateChanged(
|
||||
@@ -213,14 +232,18 @@ class RecurringOrderBloc extends Bloc<RecurringOrderEvent, RecurringOrderState>
|
||||
RecurringOrderDayToggled event,
|
||||
Emitter<RecurringOrderState> emit,
|
||||
) {
|
||||
final List<int> days = List<int>.from(state.recurringDays);
|
||||
if (days.contains(event.dayIndex)) {
|
||||
days.remove(event.dayIndex);
|
||||
final List<String> days = List<String>.from(state.recurringDays);
|
||||
final String label = _dayLabels[event.dayIndex];
|
||||
int? autoIndex = state.autoSelectedDayIndex;
|
||||
if (days.contains(label)) {
|
||||
days.remove(label);
|
||||
if (autoIndex == event.dayIndex) {
|
||||
autoIndex = null;
|
||||
}
|
||||
} else {
|
||||
days.add(event.dayIndex);
|
||||
days.sort();
|
||||
days.add(label);
|
||||
}
|
||||
emit(state.copyWith(recurringDays: days));
|
||||
emit(state.copyWith(recurringDays: _sortDays(days), autoSelectedDayIndex: autoIndex));
|
||||
}
|
||||
|
||||
void _onPositionAdded(
|
||||
@@ -277,13 +300,10 @@ class RecurringOrderBloc extends Bloc<RecurringOrderEvent, RecurringOrderState>
|
||||
if (selectedHub == null) {
|
||||
throw domain.OrderMissingHubException();
|
||||
}
|
||||
final List<String> recurringDays = state.recurringDays
|
||||
.map((int index) => _dayLabels[index])
|
||||
.toList();
|
||||
final domain.RecurringOrder order = domain.RecurringOrder(
|
||||
startDate: state.startDate,
|
||||
endDate: state.endDate,
|
||||
recurringDays: recurringDays,
|
||||
recurringDays: state.recurringDays,
|
||||
location: selectedHub.name,
|
||||
positions: state.positions
|
||||
.map(
|
||||
@@ -325,4 +345,12 @@ class RecurringOrderBloc extends Bloc<RecurringOrderEvent, RecurringOrderState>
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
static List<String> _sortDays(List<String> days) {
|
||||
days.sort(
|
||||
(String a, String b) =>
|
||||
_dayLabels.indexOf(a).compareTo(_dayLabels.indexOf(b)),
|
||||
);
|
||||
return days;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ class RecurringOrderState extends Equatable {
|
||||
required this.location,
|
||||
required this.eventName,
|
||||
required this.positions,
|
||||
required this.autoSelectedDayIndex,
|
||||
this.status = RecurringOrderStatus.initial,
|
||||
this.errorMessage,
|
||||
this.vendors = const <Vendor>[],
|
||||
@@ -23,15 +24,26 @@ class RecurringOrderState extends Equatable {
|
||||
factory RecurringOrderState.initial() {
|
||||
final DateTime now = DateTime.now();
|
||||
final DateTime start = DateTime(now.year, now.month, now.day);
|
||||
final List<String> dayLabels = <String>[
|
||||
'SUN',
|
||||
'MON',
|
||||
'TUE',
|
||||
'WED',
|
||||
'THU',
|
||||
'FRI',
|
||||
'SAT',
|
||||
];
|
||||
final int weekdayIndex = now.weekday % 7;
|
||||
return RecurringOrderState(
|
||||
startDate: start,
|
||||
endDate: start.add(const Duration(days: 7)),
|
||||
recurringDays: const <int>[],
|
||||
recurringDays: <String>[dayLabels[weekdayIndex]],
|
||||
location: '',
|
||||
eventName: '',
|
||||
positions: const <RecurringOrderPosition>[
|
||||
RecurringOrderPosition(role: '', count: 1, startTime: '', endTime: ''),
|
||||
],
|
||||
autoSelectedDayIndex: weekdayIndex,
|
||||
vendors: const <Vendor>[],
|
||||
hubs: const <RecurringOrderHubOption>[],
|
||||
roles: const <RecurringOrderRoleOption>[],
|
||||
@@ -40,10 +52,11 @@ class RecurringOrderState extends Equatable {
|
||||
|
||||
final DateTime startDate;
|
||||
final DateTime endDate;
|
||||
final List<int> recurringDays;
|
||||
final List<String> recurringDays;
|
||||
final String location;
|
||||
final String eventName;
|
||||
final List<RecurringOrderPosition> positions;
|
||||
final int? autoSelectedDayIndex;
|
||||
final RecurringOrderStatus status;
|
||||
final String? errorMessage;
|
||||
final List<Vendor> vendors;
|
||||
@@ -55,10 +68,11 @@ class RecurringOrderState extends Equatable {
|
||||
RecurringOrderState copyWith({
|
||||
DateTime? startDate,
|
||||
DateTime? endDate,
|
||||
List<int>? recurringDays,
|
||||
List<String>? recurringDays,
|
||||
String? location,
|
||||
String? eventName,
|
||||
List<RecurringOrderPosition>? positions,
|
||||
int? autoSelectedDayIndex,
|
||||
RecurringOrderStatus? status,
|
||||
String? errorMessage,
|
||||
List<Vendor>? vendors,
|
||||
@@ -74,6 +88,7 @@ class RecurringOrderState extends Equatable {
|
||||
location: location ?? this.location,
|
||||
eventName: eventName ?? this.eventName,
|
||||
positions: positions ?? this.positions,
|
||||
autoSelectedDayIndex: autoSelectedDayIndex ?? this.autoSelectedDayIndex,
|
||||
status: status ?? this.status,
|
||||
errorMessage: errorMessage ?? this.errorMessage,
|
||||
vendors: vendors ?? this.vendors,
|
||||
@@ -109,6 +124,7 @@ class RecurringOrderState extends Equatable {
|
||||
location,
|
||||
eventName,
|
||||
positions,
|
||||
autoSelectedDayIndex,
|
||||
status,
|
||||
errorMessage,
|
||||
vendors,
|
||||
|
||||
@@ -324,16 +324,33 @@ class _RecurringDaysSelector extends StatelessWidget {
|
||||
required this.onToggle,
|
||||
});
|
||||
|
||||
final List<int> selectedDays;
|
||||
final List<String> selectedDays;
|
||||
final ValueChanged<int> onToggle;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
const List<String> labels = <String>['S', 'M', 'T', 'W', 'T', 'F', 'S'];
|
||||
const List<String> labelsShort = <String>[
|
||||
'S',
|
||||
'M',
|
||||
'T',
|
||||
'W',
|
||||
'T',
|
||||
'F',
|
||||
'S',
|
||||
];
|
||||
const List<String> labelsLong = <String>[
|
||||
'SUN',
|
||||
'MON',
|
||||
'TUE',
|
||||
'WED',
|
||||
'THU',
|
||||
'FRI',
|
||||
'SAT',
|
||||
];
|
||||
return Wrap(
|
||||
spacing: UiConstants.space2,
|
||||
children: List<Widget>.generate(labels.length, (int index) {
|
||||
final bool isSelected = selectedDays.contains(index);
|
||||
children: List<Widget>.generate(labelsShort.length, (int index) {
|
||||
final bool isSelected = selectedDays.contains(labelsLong[index]);
|
||||
return GestureDetector(
|
||||
onTap: () => onToggle(index),
|
||||
child: Container(
|
||||
@@ -346,7 +363,7 @@ class _RecurringDaysSelector extends StatelessWidget {
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
child: Text(
|
||||
labels[index],
|
||||
labelsShort[index],
|
||||
style: UiTypography.body2m.copyWith(
|
||||
color: isSelected ? UiColors.white : UiColors.textSecondary,
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user