feat: Refactor context reading in emergency contact and FAQs widgets
- Updated the context reading method in `EmergencyContactAddButton` and `EmergencyContactFormItem` to use `ReadContext`. - Modified the `FaqsWidget` to utilize `ReadContext` for fetching FAQs. - Adjusted the `PrivacySectionWidget` to read from `PrivacySecurityBloc` using `ReadContext`. feat: Implement Firebase Auth isolation pattern - Introduced `FirebaseAuthService` and `FirebaseAuthServiceImpl` to abstract Firebase Auth operations. - Ensured features do not directly import `firebase_auth`, adhering to architecture rules. feat: Create repository interfaces for billing and coverage - Added `BillingRepositoryInterface` for billing-related operations. - Created `CoverageRepositoryInterface` for coverage data access. feat: Add use cases for order management - Implemented use cases for fetching hubs, managers, and roles related to orders. - Created `GetHubsUseCase`, `GetManagersByHubUseCase`, and `GetRolesByVendorUseCase`. feat: Develop report use cases for client reports - Added use cases for fetching various reports including coverage, daily operations, forecast, no-show, performance, and spend reports. - Implemented `GetCoverageReportUseCase`, `GetDailyOpsReportUseCase`, `GetForecastReportUseCase`, `GetNoShowReportUseCase`, `GetPerformanceReportUseCase`, and `GetSpendReportUseCase`. feat: Establish profile repository and use cases - Created `ProfileRepositoryInterface` for staff profile data access. - Implemented use cases for retrieving staff profile and section statuses: `GetStaffProfileUseCase` and `GetProfileSectionsUseCase`. - Added `SignOutUseCase` for signing out the current user.
This commit is contained in:
@@ -11,7 +11,11 @@ import 'domain/usecases/create_one_time_order_usecase.dart';
|
||||
import 'domain/usecases/create_permanent_order_usecase.dart';
|
||||
import 'domain/usecases/create_rapid_order_usecase.dart';
|
||||
import 'domain/usecases/create_recurring_order_usecase.dart';
|
||||
import 'domain/usecases/get_hubs_usecase.dart';
|
||||
import 'domain/usecases/get_managers_by_hub_usecase.dart';
|
||||
import 'domain/usecases/get_order_details_for_reorder_usecase.dart';
|
||||
import 'domain/usecases/get_roles_by_vendor_usecase.dart';
|
||||
import 'domain/usecases/get_vendors_usecase.dart';
|
||||
import 'domain/usecases/parse_rapid_order_usecase.dart';
|
||||
import 'domain/usecases/transcribe_rapid_order_usecase.dart';
|
||||
import 'presentation/blocs/index.dart';
|
||||
@@ -46,7 +50,7 @@ class ClientCreateOrderModule extends Module {
|
||||
),
|
||||
);
|
||||
|
||||
// UseCases
|
||||
// Command UseCases (order creation)
|
||||
i.addLazySingleton(CreateOneTimeOrderUseCase.new);
|
||||
i.addLazySingleton(CreatePermanentOrderUseCase.new);
|
||||
i.addLazySingleton(CreateRecurringOrderUseCase.new);
|
||||
@@ -55,6 +59,12 @@ class ClientCreateOrderModule extends Module {
|
||||
i.addLazySingleton(ParseRapidOrderTextToOrderUseCase.new);
|
||||
i.addLazySingleton(GetOrderDetailsForReorderUseCase.new);
|
||||
|
||||
// Query UseCases (reference data loading)
|
||||
i.addLazySingleton(GetVendorsUseCase.new);
|
||||
i.addLazySingleton(GetRolesByVendorUseCase.new);
|
||||
i.addLazySingleton(GetHubsUseCase.new);
|
||||
i.addLazySingleton(GetManagersByHubUseCase.new);
|
||||
|
||||
// BLoCs
|
||||
i.add<RapidOrderBloc>(
|
||||
() => RapidOrderBloc(
|
||||
@@ -63,15 +73,36 @@ class ClientCreateOrderModule extends Module {
|
||||
i.get<AudioRecorderService>(),
|
||||
),
|
||||
);
|
||||
i.add<OneTimeOrderBloc>(OneTimeOrderBloc.new);
|
||||
i.add<OneTimeOrderBloc>(
|
||||
() => OneTimeOrderBloc(
|
||||
i.get<CreateOneTimeOrderUseCase>(),
|
||||
i.get<GetOrderDetailsForReorderUseCase>(),
|
||||
i.get<GetVendorsUseCase>(),
|
||||
i.get<GetRolesByVendorUseCase>(),
|
||||
i.get<GetHubsUseCase>(),
|
||||
i.get<GetManagersByHubUseCase>(),
|
||||
),
|
||||
);
|
||||
i.add<PermanentOrderBloc>(
|
||||
() => PermanentOrderBloc(
|
||||
i.get<CreatePermanentOrderUseCase>(),
|
||||
i.get<GetOrderDetailsForReorderUseCase>(),
|
||||
i.get<ClientOrderQueryRepositoryInterface>(),
|
||||
i.get<GetVendorsUseCase>(),
|
||||
i.get<GetRolesByVendorUseCase>(),
|
||||
i.get<GetHubsUseCase>(),
|
||||
i.get<GetManagersByHubUseCase>(),
|
||||
),
|
||||
);
|
||||
i.add<RecurringOrderBloc>(
|
||||
() => RecurringOrderBloc(
|
||||
i.get<CreateRecurringOrderUseCase>(),
|
||||
i.get<GetOrderDetailsForReorderUseCase>(),
|
||||
i.get<GetVendorsUseCase>(),
|
||||
i.get<GetRolesByVendorUseCase>(),
|
||||
i.get<GetHubsUseCase>(),
|
||||
i.get<GetManagersByHubUseCase>(),
|
||||
),
|
||||
);
|
||||
i.add<RecurringOrderBloc>(RecurringOrderBloc.new);
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@@ -1,15 +1,69 @@
|
||||
import 'package:krow_core/core.dart';
|
||||
|
||||
/// Arguments for the [CreateOneTimeOrderUseCase].
|
||||
///
|
||||
/// Wraps the V2 API payload map for a one-time order.
|
||||
class OneTimeOrderArguments extends UseCaseArgument {
|
||||
/// Creates a [OneTimeOrderArguments] with the given [payload].
|
||||
const OneTimeOrderArguments({required this.payload});
|
||||
/// A single position entry for a one-time order submission.
|
||||
class OneTimeOrderPositionArgument extends UseCaseArgument {
|
||||
/// Creates a [OneTimeOrderPositionArgument].
|
||||
const OneTimeOrderPositionArgument({
|
||||
required this.roleId,
|
||||
required this.workerCount,
|
||||
required this.startTime,
|
||||
required this.endTime,
|
||||
this.roleName,
|
||||
this.lunchBreak,
|
||||
});
|
||||
|
||||
/// The V2 API payload map.
|
||||
final Map<String, dynamic> payload;
|
||||
/// The role ID for this position.
|
||||
final String roleId;
|
||||
|
||||
/// Human-readable role name, if available.
|
||||
final String? roleName;
|
||||
|
||||
/// Number of workers needed for this position.
|
||||
final int workerCount;
|
||||
|
||||
/// Shift start time in HH:mm format.
|
||||
final String startTime;
|
||||
|
||||
/// Shift end time in HH:mm format.
|
||||
final String endTime;
|
||||
|
||||
/// Break duration label (e.g. `'MIN_30'`, `'NO_BREAK'`), if set.
|
||||
final String? lunchBreak;
|
||||
|
||||
@override
|
||||
List<Object?> get props => <Object?>[payload];
|
||||
List<Object?> get props =>
|
||||
<Object?>[roleId, roleName, workerCount, startTime, endTime, lunchBreak];
|
||||
}
|
||||
|
||||
/// Typed arguments for [CreateOneTimeOrderUseCase].
|
||||
///
|
||||
/// Carries structured form data so the use case can build the V2 API payload.
|
||||
class OneTimeOrderArguments extends UseCaseArgument {
|
||||
/// Creates a [OneTimeOrderArguments] with the given structured fields.
|
||||
const OneTimeOrderArguments({
|
||||
required this.hubId,
|
||||
required this.eventName,
|
||||
required this.orderDate,
|
||||
required this.positions,
|
||||
this.vendorId,
|
||||
});
|
||||
|
||||
/// The selected hub ID.
|
||||
final String hubId;
|
||||
|
||||
/// The order event name / title.
|
||||
final String eventName;
|
||||
|
||||
/// The order date.
|
||||
final DateTime orderDate;
|
||||
|
||||
/// The list of position entries.
|
||||
final List<OneTimeOrderPositionArgument> positions;
|
||||
|
||||
/// The selected vendor ID, if applicable.
|
||||
final String? vendorId;
|
||||
|
||||
@override
|
||||
List<Object?> get props =>
|
||||
<Object?>[hubId, eventName, orderDate, positions, vendorId];
|
||||
}
|
||||
|
||||
@@ -1,10 +1,75 @@
|
||||
/// Arguments for the [CreatePermanentOrderUseCase].
|
||||
///
|
||||
/// Wraps the V2 API payload map for a permanent order.
|
||||
class PermanentOrderArguments {
|
||||
/// Creates a [PermanentOrderArguments] with the given [payload].
|
||||
const PermanentOrderArguments({required this.payload});
|
||||
import 'package:krow_core/core.dart';
|
||||
|
||||
/// The V2 API payload map.
|
||||
final Map<String, dynamic> payload;
|
||||
/// A single position entry for a permanent order submission.
|
||||
class PermanentOrderPositionArgument extends UseCaseArgument {
|
||||
/// Creates a [PermanentOrderPositionArgument].
|
||||
const PermanentOrderPositionArgument({
|
||||
required this.roleId,
|
||||
required this.workerCount,
|
||||
required this.startTime,
|
||||
required this.endTime,
|
||||
this.roleName,
|
||||
});
|
||||
|
||||
/// The role ID for this position.
|
||||
final String roleId;
|
||||
|
||||
/// Human-readable role name, if available.
|
||||
final String? roleName;
|
||||
|
||||
/// Number of workers needed for this position.
|
||||
final int workerCount;
|
||||
|
||||
/// Shift start time in HH:mm format.
|
||||
final String startTime;
|
||||
|
||||
/// Shift end time in HH:mm format.
|
||||
final String endTime;
|
||||
|
||||
@override
|
||||
List<Object?> get props =>
|
||||
<Object?>[roleId, roleName, workerCount, startTime, endTime];
|
||||
}
|
||||
|
||||
/// Typed arguments for [CreatePermanentOrderUseCase].
|
||||
///
|
||||
/// Carries structured form data so the use case can build the V2 API payload.
|
||||
class PermanentOrderArguments extends UseCaseArgument {
|
||||
/// Creates a [PermanentOrderArguments] with the given structured fields.
|
||||
const PermanentOrderArguments({
|
||||
required this.hubId,
|
||||
required this.eventName,
|
||||
required this.startDate,
|
||||
required this.daysOfWeek,
|
||||
required this.positions,
|
||||
this.vendorId,
|
||||
});
|
||||
|
||||
/// The selected hub ID.
|
||||
final String hubId;
|
||||
|
||||
/// The order event name / title.
|
||||
final String eventName;
|
||||
|
||||
/// The start date of the permanent order.
|
||||
final DateTime startDate;
|
||||
|
||||
/// Day-of-week labels (e.g. `['MON', 'WED', 'FRI']`).
|
||||
final List<String> daysOfWeek;
|
||||
|
||||
/// The list of position entries.
|
||||
final List<PermanentOrderPositionArgument> positions;
|
||||
|
||||
/// The selected vendor ID, if applicable.
|
||||
final String? vendorId;
|
||||
|
||||
@override
|
||||
List<Object?> get props => <Object?>[
|
||||
hubId,
|
||||
eventName,
|
||||
startDate,
|
||||
daysOfWeek,
|
||||
positions,
|
||||
vendorId,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -1,10 +1,80 @@
|
||||
/// Arguments for the [CreateRecurringOrderUseCase].
|
||||
///
|
||||
/// Wraps the V2 API payload map for a recurring order.
|
||||
class RecurringOrderArguments {
|
||||
/// Creates a [RecurringOrderArguments] with the given [payload].
|
||||
const RecurringOrderArguments({required this.payload});
|
||||
import 'package:krow_core/core.dart';
|
||||
|
||||
/// The V2 API payload map.
|
||||
final Map<String, dynamic> payload;
|
||||
/// A single position entry for a recurring order submission.
|
||||
class RecurringOrderPositionArgument extends UseCaseArgument {
|
||||
/// Creates a [RecurringOrderPositionArgument].
|
||||
const RecurringOrderPositionArgument({
|
||||
required this.roleId,
|
||||
required this.workerCount,
|
||||
required this.startTime,
|
||||
required this.endTime,
|
||||
this.roleName,
|
||||
});
|
||||
|
||||
/// The role ID for this position.
|
||||
final String roleId;
|
||||
|
||||
/// Human-readable role name, if available.
|
||||
final String? roleName;
|
||||
|
||||
/// Number of workers needed for this position.
|
||||
final int workerCount;
|
||||
|
||||
/// Shift start time in HH:mm format.
|
||||
final String startTime;
|
||||
|
||||
/// Shift end time in HH:mm format.
|
||||
final String endTime;
|
||||
|
||||
@override
|
||||
List<Object?> get props =>
|
||||
<Object?>[roleId, roleName, workerCount, startTime, endTime];
|
||||
}
|
||||
|
||||
/// Typed arguments for [CreateRecurringOrderUseCase].
|
||||
///
|
||||
/// Carries structured form data so the use case can build the V2 API payload.
|
||||
class RecurringOrderArguments extends UseCaseArgument {
|
||||
/// Creates a [RecurringOrderArguments] with the given structured fields.
|
||||
const RecurringOrderArguments({
|
||||
required this.hubId,
|
||||
required this.eventName,
|
||||
required this.startDate,
|
||||
required this.endDate,
|
||||
required this.recurringDays,
|
||||
required this.positions,
|
||||
this.vendorId,
|
||||
});
|
||||
|
||||
/// The selected hub ID.
|
||||
final String hubId;
|
||||
|
||||
/// The order event name / title.
|
||||
final String eventName;
|
||||
|
||||
/// The start date of the recurring order period.
|
||||
final DateTime startDate;
|
||||
|
||||
/// The end date of the recurring order period.
|
||||
final DateTime endDate;
|
||||
|
||||
/// Day-of-week labels (e.g. `['MON', 'WED', 'FRI']`).
|
||||
final List<String> recurringDays;
|
||||
|
||||
/// The list of position entries.
|
||||
final List<RecurringOrderPositionArgument> positions;
|
||||
|
||||
/// The selected vendor ID, if applicable.
|
||||
final String? vendorId;
|
||||
|
||||
@override
|
||||
List<Object?> get props => <Object?>[
|
||||
hubId,
|
||||
eventName,
|
||||
startDate,
|
||||
endDate,
|
||||
recurringDays,
|
||||
positions,
|
||||
vendorId,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -5,16 +5,45 @@ import '../repositories/client_create_order_repository_interface.dart';
|
||||
|
||||
/// Use case for creating a one-time staffing order.
|
||||
///
|
||||
/// Delegates the V2 API payload to the repository.
|
||||
/// 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> {
|
||||
/// Creates a [CreateOneTimeOrderUseCase].
|
||||
const CreateOneTimeOrderUseCase(this._repository);
|
||||
|
||||
/// The create-order repository.
|
||||
final ClientCreateOrderRepositoryInterface _repository;
|
||||
|
||||
@override
|
||||
Future<void> call(OneTimeOrderArguments input) {
|
||||
return _repository.createOneTimeOrder(input.payload);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,61 @@
|
||||
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.
|
||||
///
|
||||
/// Delegates the V2 API payload to the repository.
|
||||
class CreatePermanentOrderUseCase {
|
||||
/// 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> {
|
||||
/// Creates a [CreatePermanentOrderUseCase].
|
||||
const CreatePermanentOrderUseCase(this._repository);
|
||||
|
||||
/// The create-order repository.
|
||||
final ClientCreateOrderRepositoryInterface _repository;
|
||||
|
||||
/// Executes the use case with the given [args].
|
||||
Future<void> call(PermanentOrderArguments args) {
|
||||
return _repository.createPermanentOrder(args.payload);
|
||||
@override
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,63 @@
|
||||
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.
|
||||
///
|
||||
/// Delegates the V2 API payload to the repository.
|
||||
class CreateRecurringOrderUseCase {
|
||||
/// 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> {
|
||||
/// Creates a [CreateRecurringOrderUseCase].
|
||||
const CreateRecurringOrderUseCase(this._repository);
|
||||
|
||||
/// The create-order repository.
|
||||
final ClientCreateOrderRepositoryInterface _repository;
|
||||
|
||||
/// Executes the use case with the given [args].
|
||||
Future<void> call(RecurringOrderArguments args) {
|
||||
return _repository.createRecurringOrder(args.payload);
|
||||
@override
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import 'package:krow_core/core.dart';
|
||||
|
||||
import '../models/order_hub.dart';
|
||||
import '../repositories/client_order_query_repository_interface.dart';
|
||||
|
||||
/// Use case for fetching team hubs for the current business.
|
||||
///
|
||||
/// Returns the list of [OrderHub] instances available for order assignment.
|
||||
class GetHubsUseCase implements NoInputUseCase<List<OrderHub>> {
|
||||
/// Creates a [GetHubsUseCase].
|
||||
const GetHubsUseCase(this._repository);
|
||||
|
||||
/// The query repository for order reference data.
|
||||
final ClientOrderQueryRepositoryInterface _repository;
|
||||
|
||||
@override
|
||||
Future<List<OrderHub>> call() {
|
||||
return _repository.getHubs();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import 'package:krow_core/core.dart';
|
||||
|
||||
import '../models/order_manager.dart';
|
||||
import '../repositories/client_order_query_repository_interface.dart';
|
||||
|
||||
/// Use case for fetching managers assigned to a specific hub.
|
||||
///
|
||||
/// Takes a hub ID and returns the list of [OrderManager] instances
|
||||
/// for that hub.
|
||||
class GetManagersByHubUseCase implements UseCase<String, List<OrderManager>> {
|
||||
/// Creates a [GetManagersByHubUseCase].
|
||||
const GetManagersByHubUseCase(this._repository);
|
||||
|
||||
/// The query repository for order reference data.
|
||||
final ClientOrderQueryRepositoryInterface _repository;
|
||||
|
||||
@override
|
||||
Future<List<OrderManager>> call(String hubId) {
|
||||
return _repository.getManagersByHub(hubId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import 'package:krow_core/core.dart';
|
||||
|
||||
import '../models/order_role.dart';
|
||||
import '../repositories/client_order_query_repository_interface.dart';
|
||||
|
||||
/// Use case for fetching roles offered by a specific vendor.
|
||||
///
|
||||
/// Takes a vendor ID and returns the list of [OrderRole] instances
|
||||
/// available from that vendor.
|
||||
class GetRolesByVendorUseCase implements UseCase<String, List<OrderRole>> {
|
||||
/// Creates a [GetRolesByVendorUseCase].
|
||||
const GetRolesByVendorUseCase(this._repository);
|
||||
|
||||
/// The query repository for order reference data.
|
||||
final ClientOrderQueryRepositoryInterface _repository;
|
||||
|
||||
@override
|
||||
Future<List<OrderRole>> call(String vendorId) {
|
||||
return _repository.getRolesByVendor(vendorId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import 'package:krow_core/core.dart';
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
|
||||
import '../repositories/client_order_query_repository_interface.dart';
|
||||
|
||||
/// Use case for fetching the list of available vendors.
|
||||
///
|
||||
/// Wraps the query repository to enforce the use-case boundary between
|
||||
/// presentation and data layers.
|
||||
class GetVendorsUseCase implements NoInputUseCase<List<Vendor>> {
|
||||
/// Creates a [GetVendorsUseCase].
|
||||
const GetVendorsUseCase(this._repository);
|
||||
|
||||
/// The query repository for order reference data.
|
||||
final ClientOrderQueryRepositoryInterface _repository;
|
||||
|
||||
@override
|
||||
Future<List<Vendor>> call() {
|
||||
return _repository.getVendors();
|
||||
}
|
||||
}
|
||||
@@ -2,9 +2,12 @@ import 'package:client_create_order/src/domain/arguments/one_time_order_argument
|
||||
import 'package:client_create_order/src/domain/models/order_hub.dart';
|
||||
import 'package:client_create_order/src/domain/models/order_manager.dart';
|
||||
import 'package:client_create_order/src/domain/models/order_role.dart';
|
||||
import 'package:client_create_order/src/domain/repositories/client_order_query_repository_interface.dart';
|
||||
import 'package:client_create_order/src/domain/usecases/create_one_time_order_usecase.dart';
|
||||
import 'package:client_create_order/src/domain/usecases/get_hubs_usecase.dart';
|
||||
import 'package:client_create_order/src/domain/usecases/get_managers_by_hub_usecase.dart';
|
||||
import 'package:client_create_order/src/domain/usecases/get_order_details_for_reorder_usecase.dart';
|
||||
import 'package:client_create_order/src/domain/usecases/get_roles_by_vendor_usecase.dart';
|
||||
import 'package:client_create_order/src/domain/usecases/get_vendors_usecase.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:krow_core/core.dart';
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
@@ -14,16 +17,20 @@ import 'one_time_order_state.dart';
|
||||
|
||||
/// BLoC for managing the multi-step one-time order creation form.
|
||||
///
|
||||
/// Builds V2 API payloads and uses [OrderPreview] for reorder.
|
||||
/// Delegates all data fetching to query use cases and order submission
|
||||
/// to [CreateOneTimeOrderUseCase]. Uses [OrderPreview] for reorder.
|
||||
class OneTimeOrderBloc extends Bloc<OneTimeOrderEvent, OneTimeOrderState>
|
||||
with
|
||||
BlocErrorHandler<OneTimeOrderState>,
|
||||
SafeBloc<OneTimeOrderEvent, OneTimeOrderState> {
|
||||
/// Creates the BLoC with required dependencies.
|
||||
/// Creates the BLoC with required use case dependencies.
|
||||
OneTimeOrderBloc(
|
||||
this._createOneTimeOrderUseCase,
|
||||
this._getOrderDetailsForReorderUseCase,
|
||||
this._queryRepository,
|
||||
this._getVendorsUseCase,
|
||||
this._getRolesByVendorUseCase,
|
||||
this._getHubsUseCase,
|
||||
this._getManagersByHubUseCase,
|
||||
) : super(OneTimeOrderState.initial()) {
|
||||
on<OneTimeOrderVendorsLoaded>(_onVendorsLoaded);
|
||||
on<OneTimeOrderVendorChanged>(_onVendorChanged);
|
||||
@@ -45,16 +52,21 @@ class OneTimeOrderBloc extends Bloc<OneTimeOrderEvent, OneTimeOrderState>
|
||||
|
||||
final CreateOneTimeOrderUseCase _createOneTimeOrderUseCase;
|
||||
final GetOrderDetailsForReorderUseCase _getOrderDetailsForReorderUseCase;
|
||||
final ClientOrderQueryRepositoryInterface _queryRepository;
|
||||
final GetVendorsUseCase _getVendorsUseCase;
|
||||
final GetRolesByVendorUseCase _getRolesByVendorUseCase;
|
||||
final GetHubsUseCase _getHubsUseCase;
|
||||
final GetManagersByHubUseCase _getManagersByHubUseCase;
|
||||
|
||||
/// Loads available vendors via the use case.
|
||||
Future<void> _loadVendors() async {
|
||||
final List<Vendor>? vendors = await handleErrorWithResult(
|
||||
action: () => _queryRepository.getVendors(),
|
||||
action: () => _getVendorsUseCase(),
|
||||
onError: (_) => add(const OneTimeOrderVendorsLoaded(<Vendor>[])),
|
||||
);
|
||||
if (vendors != null) add(OneTimeOrderVendorsLoaded(vendors));
|
||||
}
|
||||
|
||||
/// Loads roles for [vendorId] and maps them to presentation option models.
|
||||
Future<void> _loadRolesForVendor(
|
||||
String vendorId,
|
||||
Emitter<OneTimeOrderState> emit,
|
||||
@@ -62,7 +74,7 @@ class OneTimeOrderBloc extends Bloc<OneTimeOrderEvent, OneTimeOrderState>
|
||||
final List<OneTimeOrderRoleOption>? roles = await handleErrorWithResult(
|
||||
action: () async {
|
||||
final List<OrderRole> result =
|
||||
await _queryRepository.getRolesByVendor(vendorId);
|
||||
await _getRolesByVendorUseCase(vendorId);
|
||||
return result
|
||||
.map((OrderRole r) => OneTimeOrderRoleOption(
|
||||
id: r.id, name: r.name, costPerHour: r.costPerHour))
|
||||
@@ -74,10 +86,11 @@ class OneTimeOrderBloc extends Bloc<OneTimeOrderEvent, OneTimeOrderState>
|
||||
if (roles != null) emit(state.copyWith(roles: roles));
|
||||
}
|
||||
|
||||
/// Loads hubs via the use case and maps to presentation option models.
|
||||
Future<void> _loadHubs() async {
|
||||
final List<OneTimeOrderHubOption>? hubs = await handleErrorWithResult(
|
||||
action: () async {
|
||||
final List<OrderHub> result = await _queryRepository.getHubs();
|
||||
final List<OrderHub> result = await _getHubsUseCase();
|
||||
return result
|
||||
.map((OrderHub h) => OneTimeOrderHubOption(
|
||||
id: h.id,
|
||||
@@ -100,12 +113,13 @@ class OneTimeOrderBloc extends Bloc<OneTimeOrderEvent, OneTimeOrderState>
|
||||
if (hubs != null) add(OneTimeOrderHubsLoaded(hubs));
|
||||
}
|
||||
|
||||
/// Loads managers for [hubId] via the use case.
|
||||
Future<void> _loadManagersForHub(String hubId) async {
|
||||
final List<OneTimeOrderManagerOption>? managers =
|
||||
await handleErrorWithResult(
|
||||
action: () async {
|
||||
final List<OrderManager> result =
|
||||
await _queryRepository.getManagersByHub(hubId);
|
||||
await _getManagersByHubUseCase(hubId);
|
||||
return result
|
||||
.map((OrderManager m) =>
|
||||
OneTimeOrderManagerOption(id: m.id, name: m.name))
|
||||
@@ -224,7 +238,7 @@ class OneTimeOrderBloc extends Bloc<OneTimeOrderEvent, OneTimeOrderState>
|
||||
emit(state.copyWith(positions: newPositions));
|
||||
}
|
||||
|
||||
/// Builds a V2 API payload and submits the one-time order.
|
||||
/// Builds typed arguments from form state and submits via the use case.
|
||||
Future<void> _onSubmitted(
|
||||
OneTimeOrderSubmitted event,
|
||||
Emitter<OneTimeOrderState> emit,
|
||||
@@ -236,12 +250,7 @@ class OneTimeOrderBloc extends Bloc<OneTimeOrderEvent, OneTimeOrderState>
|
||||
final OneTimeOrderHubOption? selectedHub = state.selectedHub;
|
||||
if (selectedHub == null) throw const OrderMissingHubException();
|
||||
|
||||
final String orderDate =
|
||||
'${state.date.year.toString().padLeft(4, '0')}-'
|
||||
'${state.date.month.toString().padLeft(2, '0')}-'
|
||||
'${state.date.day.toString().padLeft(2, '0')}';
|
||||
|
||||
final List<Map<String, dynamic>> positions =
|
||||
final List<OneTimeOrderPositionArgument> positionArgs =
|
||||
state.positions.map((OneTimeOrderPosition p) {
|
||||
final OneTimeOrderRoleOption? role = state.roles
|
||||
.cast<OneTimeOrderRoleOption?>()
|
||||
@@ -249,28 +258,24 @@ class OneTimeOrderBloc extends Bloc<OneTimeOrderEvent, OneTimeOrderState>
|
||||
(OneTimeOrderRoleOption? r) => r != null && r.id == p.role,
|
||||
orElse: () => null,
|
||||
);
|
||||
return <String, dynamic>{
|
||||
if (role != null) 'roleName': role.name,
|
||||
if (p.role.isNotEmpty) 'roleId': p.role,
|
||||
'workerCount': p.count,
|
||||
'startTime': p.startTime,
|
||||
'endTime': p.endTime,
|
||||
if (p.lunchBreak != 'NO_BREAK' && p.lunchBreak.isNotEmpty)
|
||||
'lunchBreakMinutes': _breakMinutes(p.lunchBreak),
|
||||
};
|
||||
return OneTimeOrderPositionArgument(
|
||||
roleId: p.role,
|
||||
roleName: role?.name,
|
||||
workerCount: p.count,
|
||||
startTime: p.startTime,
|
||||
endTime: p.endTime,
|
||||
lunchBreak: p.lunchBreak,
|
||||
);
|
||||
}).toList();
|
||||
|
||||
final Map<String, dynamic> payload = <String, dynamic>{
|
||||
'hubId': selectedHub.id,
|
||||
'eventName': state.eventName,
|
||||
'orderDate': orderDate,
|
||||
'positions': positions,
|
||||
if (state.selectedVendor != null)
|
||||
'vendorId': state.selectedVendor!.id,
|
||||
};
|
||||
|
||||
await _createOneTimeOrderUseCase(
|
||||
OneTimeOrderArguments(payload: payload),
|
||||
OneTimeOrderArguments(
|
||||
hubId: selectedHub.id,
|
||||
eventName: state.eventName,
|
||||
orderDate: state.date,
|
||||
positions: positionArgs,
|
||||
vendorId: state.selectedVendor?.id,
|
||||
),
|
||||
);
|
||||
emit(state.copyWith(status: OneTimeOrderStatus.success));
|
||||
},
|
||||
@@ -339,8 +344,8 @@ class OneTimeOrderBloc extends Bloc<OneTimeOrderEvent, OneTimeOrderState>
|
||||
positions.add(OneTimeOrderPosition(
|
||||
role: role.roleId,
|
||||
count: role.workersNeeded,
|
||||
startTime: _formatTime(shift.startsAt),
|
||||
endTime: _formatTime(shift.endsAt),
|
||||
startTime: formatTimeHHmm(shift.startsAt),
|
||||
endTime: formatTimeHHmm(shift.endsAt),
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -357,29 +362,4 @@ class OneTimeOrderBloc extends Bloc<OneTimeOrderEvent, OneTimeOrderState>
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Formats a [DateTime] to HH:mm string.
|
||||
String _formatTime(DateTime dt) {
|
||||
final DateTime local = dt.toLocal();
|
||||
return '${local.hour.toString().padLeft(2, '0')}:'
|
||||
'${local.minute.toString().padLeft(2, '0')}';
|
||||
}
|
||||
|
||||
/// Converts a break duration string to minutes.
|
||||
int _breakMinutes(String value) {
|
||||
switch (value) {
|
||||
case 'MIN_10':
|
||||
return 10;
|
||||
case 'MIN_15':
|
||||
return 15;
|
||||
case 'MIN_30':
|
||||
return 30;
|
||||
case 'MIN_45':
|
||||
return 45;
|
||||
case 'MIN_60':
|
||||
return 60;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,26 +1,36 @@
|
||||
import 'package:client_create_order/src/domain/arguments/permanent_order_arguments.dart';
|
||||
import 'package:client_create_order/src/domain/models/order_hub.dart';
|
||||
import 'package:client_create_order/src/domain/models/order_manager.dart';
|
||||
import 'package:client_create_order/src/domain/models/order_role.dart';
|
||||
import 'package:client_create_order/src/domain/repositories/client_order_query_repository_interface.dart';
|
||||
import 'package:client_create_order/src/domain/usecases/create_permanent_order_usecase.dart';
|
||||
import 'package:client_create_order/src/domain/usecases/get_hubs_usecase.dart';
|
||||
import 'package:client_create_order/src/domain/usecases/get_managers_by_hub_usecase.dart';
|
||||
import 'package:client_create_order/src/domain/usecases/get_order_details_for_reorder_usecase.dart';
|
||||
import 'package:client_create_order/src/domain/usecases/get_roles_by_vendor_usecase.dart';
|
||||
import 'package:client_create_order/src/domain/usecases/get_vendors_usecase.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:krow_core/core.dart';
|
||||
import 'package:client_create_order/src/domain/arguments/permanent_order_arguments.dart';
|
||||
import 'package:krow_domain/krow_domain.dart' as domain;
|
||||
|
||||
import 'permanent_order_event.dart';
|
||||
import 'permanent_order_state.dart';
|
||||
|
||||
/// BLoC for managing the permanent order creation form.
|
||||
///
|
||||
/// Delegates all data fetching to query use cases and order submission
|
||||
/// to [CreatePermanentOrderUseCase].
|
||||
class PermanentOrderBloc extends Bloc<PermanentOrderEvent, PermanentOrderState>
|
||||
with
|
||||
BlocErrorHandler<PermanentOrderState>,
|
||||
SafeBloc<PermanentOrderEvent, PermanentOrderState> {
|
||||
/// Creates a BLoC with required use case dependencies.
|
||||
PermanentOrderBloc(
|
||||
this._createPermanentOrderUseCase,
|
||||
this._getOrderDetailsForReorderUseCase,
|
||||
this._queryRepository,
|
||||
this._getVendorsUseCase,
|
||||
this._getRolesByVendorUseCase,
|
||||
this._getHubsUseCase,
|
||||
this._getManagersByHubUseCase,
|
||||
) : super(PermanentOrderState.initial()) {
|
||||
on<PermanentOrderVendorsLoaded>(_onVendorsLoaded);
|
||||
on<PermanentOrderVendorChanged>(_onVendorChanged);
|
||||
@@ -43,7 +53,10 @@ class PermanentOrderBloc extends Bloc<PermanentOrderEvent, PermanentOrderState>
|
||||
|
||||
final CreatePermanentOrderUseCase _createPermanentOrderUseCase;
|
||||
final GetOrderDetailsForReorderUseCase _getOrderDetailsForReorderUseCase;
|
||||
final ClientOrderQueryRepositoryInterface _queryRepository;
|
||||
final GetVendorsUseCase _getVendorsUseCase;
|
||||
final GetRolesByVendorUseCase _getRolesByVendorUseCase;
|
||||
final GetHubsUseCase _getHubsUseCase;
|
||||
final GetManagersByHubUseCase _getManagersByHubUseCase;
|
||||
|
||||
static const List<String> _dayLabels = <String>[
|
||||
'SUN',
|
||||
@@ -55,9 +68,10 @@ class PermanentOrderBloc extends Bloc<PermanentOrderEvent, PermanentOrderState>
|
||||
'SAT',
|
||||
];
|
||||
|
||||
/// Loads available vendors via the use case.
|
||||
Future<void> _loadVendors() async {
|
||||
final List<domain.Vendor>? vendors = await handleErrorWithResult(
|
||||
action: () => _queryRepository.getVendors(),
|
||||
action: () => _getVendorsUseCase(),
|
||||
onError: (_) => add(const PermanentOrderVendorsLoaded(<domain.Vendor>[])),
|
||||
);
|
||||
|
||||
@@ -66,6 +80,8 @@ class PermanentOrderBloc extends Bloc<PermanentOrderEvent, PermanentOrderState>
|
||||
}
|
||||
}
|
||||
|
||||
/// Loads roles for [vendorId] via the use case and maps them to
|
||||
/// presentation option models.
|
||||
Future<void> _loadRolesForVendor(
|
||||
String vendorId,
|
||||
Emitter<PermanentOrderState> emit,
|
||||
@@ -73,7 +89,7 @@ class PermanentOrderBloc extends Bloc<PermanentOrderEvent, PermanentOrderState>
|
||||
final List<PermanentOrderRoleOption>? roles = await handleErrorWithResult(
|
||||
action: () async {
|
||||
final List<OrderRole> orderRoles =
|
||||
await _queryRepository.getRolesByVendor(vendorId);
|
||||
await _getRolesByVendorUseCase(vendorId);
|
||||
return orderRoles
|
||||
.map(
|
||||
(OrderRole r) => PermanentOrderRoleOption(
|
||||
@@ -93,10 +109,11 @@ class PermanentOrderBloc extends Bloc<PermanentOrderEvent, PermanentOrderState>
|
||||
}
|
||||
}
|
||||
|
||||
/// Loads hubs via the use case and maps them to presentation option models.
|
||||
Future<void> _loadHubs() async {
|
||||
final List<PermanentOrderHubOption>? hubs = await handleErrorWithResult(
|
||||
action: () async {
|
||||
final List<OrderHub> orderHubs = await _queryRepository.getHubs();
|
||||
final List<OrderHub> orderHubs = await _getHubsUseCase();
|
||||
return orderHubs
|
||||
.map(
|
||||
(OrderHub hub) => PermanentOrderHubOption(
|
||||
@@ -193,6 +210,7 @@ class PermanentOrderBloc extends Bloc<PermanentOrderEvent, PermanentOrderState>
|
||||
emit(state.copyWith(managers: event.managers));
|
||||
}
|
||||
|
||||
/// Loads managers for [hubId] via the use case.
|
||||
Future<void> _loadManagersForHub(
|
||||
String hubId,
|
||||
Emitter<PermanentOrderState> emit,
|
||||
@@ -201,7 +219,7 @@ class PermanentOrderBloc extends Bloc<PermanentOrderEvent, PermanentOrderState>
|
||||
await handleErrorWithResult(
|
||||
action: () async {
|
||||
final List<OrderManager> orderManagers =
|
||||
await _queryRepository.getManagersByHub(hubId);
|
||||
await _getManagersByHubUseCase(hubId);
|
||||
return orderManagers
|
||||
.map(
|
||||
(OrderManager m) => PermanentOrderManagerOption(
|
||||
@@ -221,7 +239,6 @@ class PermanentOrderBloc extends Bloc<PermanentOrderEvent, PermanentOrderState>
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void _onEventNameChanged(
|
||||
PermanentOrderEventNameChanged event,
|
||||
Emitter<PermanentOrderState> emit,
|
||||
@@ -315,6 +332,7 @@ class PermanentOrderBloc extends Bloc<PermanentOrderEvent, PermanentOrderState>
|
||||
emit(state.copyWith(positions: newPositions));
|
||||
}
|
||||
|
||||
/// Builds typed arguments from form state and submits via the use case.
|
||||
Future<void> _onSubmitted(
|
||||
PermanentOrderSubmitted event,
|
||||
Emitter<PermanentOrderState> emit,
|
||||
@@ -328,16 +346,7 @@ class PermanentOrderBloc extends Bloc<PermanentOrderEvent, PermanentOrderState>
|
||||
throw const domain.OrderMissingHubException();
|
||||
}
|
||||
|
||||
final String startDate =
|
||||
'${state.startDate.year.toString().padLeft(4, '0')}-'
|
||||
'${state.startDate.month.toString().padLeft(2, '0')}-'
|
||||
'${state.startDate.day.toString().padLeft(2, '0')}';
|
||||
|
||||
final List<int> daysOfWeek = state.permanentDays
|
||||
.map((String day) => _dayLabels.indexOf(day) % 7)
|
||||
.toList();
|
||||
|
||||
final List<Map<String, dynamic>> positions =
|
||||
final List<PermanentOrderPositionArgument> positionArgs =
|
||||
state.positions.map((PermanentOrderPosition p) {
|
||||
final PermanentOrderRoleOption? role = state.roles
|
||||
.cast<PermanentOrderRoleOption?>()
|
||||
@@ -345,27 +354,24 @@ class PermanentOrderBloc extends Bloc<PermanentOrderEvent, PermanentOrderState>
|
||||
(PermanentOrderRoleOption? r) => r != null && r.id == p.role,
|
||||
orElse: () => null,
|
||||
);
|
||||
return <String, dynamic>{
|
||||
if (role != null) 'roleName': role.name,
|
||||
if (p.role.isNotEmpty) 'roleId': p.role,
|
||||
'workerCount': p.count,
|
||||
'startTime': p.startTime,
|
||||
'endTime': p.endTime,
|
||||
};
|
||||
return PermanentOrderPositionArgument(
|
||||
roleId: p.role,
|
||||
roleName: role?.name,
|
||||
workerCount: p.count,
|
||||
startTime: p.startTime,
|
||||
endTime: p.endTime,
|
||||
);
|
||||
}).toList();
|
||||
|
||||
final Map<String, dynamic> payload = <String, dynamic>{
|
||||
'hubId': selectedHub.id,
|
||||
'eventName': state.eventName,
|
||||
'startDate': startDate,
|
||||
'daysOfWeek': daysOfWeek,
|
||||
'positions': positions,
|
||||
if (state.selectedVendor != null)
|
||||
'vendorId': state.selectedVendor!.id,
|
||||
};
|
||||
|
||||
await _createPermanentOrderUseCase(
|
||||
PermanentOrderArguments(payload: payload),
|
||||
PermanentOrderArguments(
|
||||
hubId: selectedHub.id,
|
||||
eventName: state.eventName,
|
||||
startDate: state.startDate,
|
||||
daysOfWeek: state.permanentDays,
|
||||
positions: positionArgs,
|
||||
vendorId: state.selectedVendor?.id,
|
||||
),
|
||||
);
|
||||
emit(state.copyWith(status: PermanentOrderStatus.success));
|
||||
},
|
||||
@@ -376,6 +382,7 @@ class PermanentOrderBloc extends Bloc<PermanentOrderEvent, PermanentOrderState>
|
||||
);
|
||||
}
|
||||
|
||||
/// Initializes the form from route arguments or reorder preview data.
|
||||
Future<void> _onInitialized(
|
||||
PermanentOrderInitialized event,
|
||||
Emitter<PermanentOrderState> emit,
|
||||
@@ -406,8 +413,8 @@ class PermanentOrderBloc extends Bloc<PermanentOrderEvent, PermanentOrderState>
|
||||
positions.add(PermanentOrderPosition(
|
||||
role: role.roleId,
|
||||
count: role.workersNeeded,
|
||||
startTime: _formatTime(shift.startsAt),
|
||||
endTime: _formatTime(shift.endsAt),
|
||||
startTime: formatTimeHHmm(shift.startsAt),
|
||||
endTime: formatTimeHHmm(shift.endsAt),
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -430,13 +437,6 @@ class PermanentOrderBloc extends Bloc<PermanentOrderEvent, PermanentOrderState>
|
||||
);
|
||||
}
|
||||
|
||||
/// Formats a [DateTime] to HH:mm string.
|
||||
String _formatTime(DateTime dt) {
|
||||
final DateTime local = dt.toLocal();
|
||||
return '${local.hour.toString().padLeft(2, '0')}:'
|
||||
'${local.minute.toString().padLeft(2, '0')}';
|
||||
}
|
||||
|
||||
static List<String> _sortDays(List<String> days) {
|
||||
days.sort(
|
||||
(String a, String b) =>
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
import 'package:client_create_order/src/domain/arguments/recurring_order_arguments.dart';
|
||||
import 'package:client_create_order/src/domain/models/order_hub.dart';
|
||||
import 'package:client_create_order/src/domain/models/order_manager.dart';
|
||||
import 'package:client_create_order/src/domain/models/order_role.dart';
|
||||
import 'package:client_create_order/src/domain/repositories/client_order_query_repository_interface.dart';
|
||||
import 'package:client_create_order/src/domain/usecases/create_recurring_order_usecase.dart';
|
||||
import 'package:client_create_order/src/domain/usecases/get_hubs_usecase.dart';
|
||||
import 'package:client_create_order/src/domain/usecases/get_managers_by_hub_usecase.dart';
|
||||
import 'package:client_create_order/src/domain/usecases/get_order_details_for_reorder_usecase.dart';
|
||||
import 'package:client_create_order/src/domain/usecases/get_roles_by_vendor_usecase.dart';
|
||||
import 'package:client_create_order/src/domain/usecases/get_vendors_usecase.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:krow_core/core.dart';
|
||||
import 'package:client_create_order/src/domain/arguments/recurring_order_arguments.dart';
|
||||
import 'package:krow_domain/krow_domain.dart' as domain;
|
||||
|
||||
import 'recurring_order_event.dart';
|
||||
@@ -14,19 +17,20 @@ import 'recurring_order_state.dart';
|
||||
|
||||
/// BLoC for managing the recurring order creation form.
|
||||
///
|
||||
/// Delegates all backend queries to [ClientOrderQueryRepositoryInterface]
|
||||
/// and order submission to [CreateRecurringOrderUseCase].
|
||||
/// Builds V2 API payloads from form state.
|
||||
/// Delegates all data fetching to query use cases and order submission
|
||||
/// to [CreateRecurringOrderUseCase]. Builds V2 API payloads from form state.
|
||||
class RecurringOrderBloc extends Bloc<RecurringOrderEvent, RecurringOrderState>
|
||||
with
|
||||
BlocErrorHandler<RecurringOrderState>,
|
||||
SafeBloc<RecurringOrderEvent, RecurringOrderState> {
|
||||
/// Creates a [RecurringOrderBloc] with the required use cases and
|
||||
/// query repository.
|
||||
/// Creates a [RecurringOrderBloc] with the required use case dependencies.
|
||||
RecurringOrderBloc(
|
||||
this._createRecurringOrderUseCase,
|
||||
this._getOrderDetailsForReorderUseCase,
|
||||
this._queryRepository,
|
||||
this._getVendorsUseCase,
|
||||
this._getRolesByVendorUseCase,
|
||||
this._getHubsUseCase,
|
||||
this._getManagersByHubUseCase,
|
||||
) : super(RecurringOrderState.initial()) {
|
||||
on<RecurringOrderVendorsLoaded>(_onVendorsLoaded);
|
||||
on<RecurringOrderVendorChanged>(_onVendorChanged);
|
||||
@@ -50,7 +54,10 @@ class RecurringOrderBloc extends Bloc<RecurringOrderEvent, RecurringOrderState>
|
||||
|
||||
final CreateRecurringOrderUseCase _createRecurringOrderUseCase;
|
||||
final GetOrderDetailsForReorderUseCase _getOrderDetailsForReorderUseCase;
|
||||
final ClientOrderQueryRepositoryInterface _queryRepository;
|
||||
final GetVendorsUseCase _getVendorsUseCase;
|
||||
final GetRolesByVendorUseCase _getRolesByVendorUseCase;
|
||||
final GetHubsUseCase _getHubsUseCase;
|
||||
final GetManagersByHubUseCase _getManagersByHubUseCase;
|
||||
|
||||
static const List<String> _dayLabels = <String>[
|
||||
'SUN',
|
||||
@@ -62,12 +69,10 @@ class RecurringOrderBloc extends Bloc<RecurringOrderEvent, RecurringOrderState>
|
||||
'SAT',
|
||||
];
|
||||
|
||||
/// Loads the list of available vendors from the query repository.
|
||||
/// Loads the list of available vendors via the use case.
|
||||
Future<void> _loadVendors() async {
|
||||
final List<domain.Vendor>? vendors = await handleErrorWithResult(
|
||||
action: () async {
|
||||
return _queryRepository.getVendors();
|
||||
},
|
||||
action: () => _getVendorsUseCase(),
|
||||
onError: (_) =>
|
||||
add(const RecurringOrderVendorsLoaded(<domain.Vendor>[])),
|
||||
);
|
||||
@@ -77,8 +82,8 @@ class RecurringOrderBloc extends Bloc<RecurringOrderEvent, RecurringOrderState>
|
||||
}
|
||||
}
|
||||
|
||||
/// Loads roles for the given [vendorId] and maps them to presentation
|
||||
/// option models.
|
||||
/// Loads roles for [vendorId] via the use case and maps them to
|
||||
/// presentation option models.
|
||||
Future<void> _loadRolesForVendor(
|
||||
String vendorId,
|
||||
Emitter<RecurringOrderState> emit,
|
||||
@@ -86,7 +91,7 @@ class RecurringOrderBloc extends Bloc<RecurringOrderEvent, RecurringOrderState>
|
||||
final List<RecurringOrderRoleOption>? roles = await handleErrorWithResult(
|
||||
action: () async {
|
||||
final List<OrderRole> orderRoles =
|
||||
await _queryRepository.getRolesByVendor(vendorId);
|
||||
await _getRolesByVendorUseCase(vendorId);
|
||||
return orderRoles
|
||||
.map(
|
||||
(OrderRole r) => RecurringOrderRoleOption(
|
||||
@@ -106,12 +111,12 @@ class RecurringOrderBloc extends Bloc<RecurringOrderEvent, RecurringOrderState>
|
||||
}
|
||||
}
|
||||
|
||||
/// Loads team hubs for the current business owner and maps them to
|
||||
/// presentation option models.
|
||||
/// Loads team hubs via the use case and maps them to presentation
|
||||
/// option models.
|
||||
Future<void> _loadHubs() async {
|
||||
final List<RecurringOrderHubOption>? hubs = await handleErrorWithResult(
|
||||
action: () async {
|
||||
final List<OrderHub> orderHubs = await _queryRepository.getHubs();
|
||||
final List<OrderHub> orderHubs = await _getHubsUseCase();
|
||||
return orderHubs
|
||||
.map(
|
||||
(OrderHub hub) => RecurringOrderHubOption(
|
||||
@@ -208,8 +213,8 @@ class RecurringOrderBloc extends Bloc<RecurringOrderEvent, RecurringOrderState>
|
||||
emit(state.copyWith(managers: event.managers));
|
||||
}
|
||||
|
||||
/// Loads managers for the given [hubId] and maps them to presentation
|
||||
/// option models.
|
||||
/// Loads managers for [hubId] via the use case and maps them to
|
||||
/// presentation option models.
|
||||
Future<void> _loadManagersForHub(
|
||||
String hubId,
|
||||
Emitter<RecurringOrderState> emit,
|
||||
@@ -218,7 +223,7 @@ class RecurringOrderBloc extends Bloc<RecurringOrderEvent, RecurringOrderState>
|
||||
await handleErrorWithResult(
|
||||
action: () async {
|
||||
final List<OrderManager> orderManagers =
|
||||
await _queryRepository.getManagersByHub(hubId);
|
||||
await _getManagersByHubUseCase(hubId);
|
||||
return orderManagers
|
||||
.map(
|
||||
(OrderManager m) => RecurringOrderManagerOption(
|
||||
@@ -347,6 +352,7 @@ class RecurringOrderBloc extends Bloc<RecurringOrderEvent, RecurringOrderState>
|
||||
emit(state.copyWith(positions: newPositions));
|
||||
}
|
||||
|
||||
/// Builds typed arguments from form state and submits via the use case.
|
||||
Future<void> _onSubmitted(
|
||||
RecurringOrderSubmitted event,
|
||||
Emitter<RecurringOrderState> emit,
|
||||
@@ -360,21 +366,7 @@ class RecurringOrderBloc extends Bloc<RecurringOrderEvent, RecurringOrderState>
|
||||
throw const domain.OrderMissingHubException();
|
||||
}
|
||||
|
||||
final String startDate =
|
||||
'${state.startDate.year.toString().padLeft(4, '0')}-'
|
||||
'${state.startDate.month.toString().padLeft(2, '0')}-'
|
||||
'${state.startDate.day.toString().padLeft(2, '0')}';
|
||||
final String endDate =
|
||||
'${state.endDate.year.toString().padLeft(4, '0')}-'
|
||||
'${state.endDate.month.toString().padLeft(2, '0')}-'
|
||||
'${state.endDate.day.toString().padLeft(2, '0')}';
|
||||
|
||||
// Map day labels (MON=1, TUE=2, ..., SUN=0) to V2 int format
|
||||
final List<int> recurrenceDays = state.recurringDays
|
||||
.map((String day) => _dayLabels.indexOf(day) % 7)
|
||||
.toList();
|
||||
|
||||
final List<Map<String, dynamic>> positions =
|
||||
final List<RecurringOrderPositionArgument> positionArgs =
|
||||
state.positions.map((RecurringOrderPosition p) {
|
||||
final RecurringOrderRoleOption? role = state.roles
|
||||
.cast<RecurringOrderRoleOption?>()
|
||||
@@ -382,28 +374,25 @@ class RecurringOrderBloc extends Bloc<RecurringOrderEvent, RecurringOrderState>
|
||||
(RecurringOrderRoleOption? r) => r != null && r.id == p.role,
|
||||
orElse: () => null,
|
||||
);
|
||||
return <String, dynamic>{
|
||||
if (role != null) 'roleName': role.name,
|
||||
if (p.role.isNotEmpty) 'roleId': p.role,
|
||||
'workerCount': p.count,
|
||||
'startTime': p.startTime,
|
||||
'endTime': p.endTime,
|
||||
};
|
||||
return RecurringOrderPositionArgument(
|
||||
roleId: p.role,
|
||||
roleName: role?.name,
|
||||
workerCount: p.count,
|
||||
startTime: p.startTime,
|
||||
endTime: p.endTime,
|
||||
);
|
||||
}).toList();
|
||||
|
||||
final Map<String, dynamic> payload = <String, dynamic>{
|
||||
'hubId': selectedHub.id,
|
||||
'eventName': state.eventName,
|
||||
'startDate': startDate,
|
||||
'endDate': endDate,
|
||||
'recurrenceDays': recurrenceDays,
|
||||
'positions': positions,
|
||||
if (state.selectedVendor != null)
|
||||
'vendorId': state.selectedVendor!.id,
|
||||
};
|
||||
|
||||
await _createRecurringOrderUseCase(
|
||||
RecurringOrderArguments(payload: payload),
|
||||
RecurringOrderArguments(
|
||||
hubId: selectedHub.id,
|
||||
eventName: state.eventName,
|
||||
startDate: state.startDate,
|
||||
endDate: state.endDate,
|
||||
recurringDays: state.recurringDays,
|
||||
positions: positionArgs,
|
||||
vendorId: state.selectedVendor?.id,
|
||||
),
|
||||
);
|
||||
emit(state.copyWith(status: RecurringOrderStatus.success));
|
||||
},
|
||||
@@ -414,6 +403,7 @@ class RecurringOrderBloc extends Bloc<RecurringOrderEvent, RecurringOrderState>
|
||||
);
|
||||
}
|
||||
|
||||
/// Initializes the form from route arguments or reorder preview data.
|
||||
Future<void> _onInitialized(
|
||||
RecurringOrderInitialized event,
|
||||
Emitter<RecurringOrderState> emit,
|
||||
@@ -445,8 +435,8 @@ class RecurringOrderBloc extends Bloc<RecurringOrderEvent, RecurringOrderState>
|
||||
positions.add(RecurringOrderPosition(
|
||||
role: role.roleId,
|
||||
count: role.workersNeeded,
|
||||
startTime: _formatTime(shift.startsAt),
|
||||
endTime: _formatTime(shift.endsAt),
|
||||
startTime: formatTimeHHmm(shift.startsAt),
|
||||
endTime: formatTimeHHmm(shift.endsAt),
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -470,13 +460,6 @@ class RecurringOrderBloc extends Bloc<RecurringOrderEvent, RecurringOrderState>
|
||||
);
|
||||
}
|
||||
|
||||
/// Formats a [DateTime] to HH:mm string.
|
||||
String _formatTime(DateTime dt) {
|
||||
final DateTime local = dt.toLocal();
|
||||
return '${local.hour.toString().padLeft(2, '0')}:'
|
||||
'${local.minute.toString().padLeft(2, '0')}';
|
||||
}
|
||||
|
||||
static List<String> _sortDays(List<String> days) {
|
||||
days.sort(
|
||||
(String a, String b) =>
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import 'package:krow_core/core.dart';
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
|
||||
import '../../domain/repositories/i_view_orders_repository.dart';
|
||||
import '../../domain/repositories/view_orders_repository_interface.dart';
|
||||
|
||||
/// V2 API implementation of [IViewOrdersRepository].
|
||||
/// V2 API implementation of [ViewOrdersRepositoryInterface].
|
||||
///
|
||||
/// Replaces the old Data Connect implementation with [BaseApiService] calls
|
||||
/// to the V2 query and command API endpoints.
|
||||
class ViewOrdersRepositoryImpl implements IViewOrdersRepository {
|
||||
class ViewOrdersRepositoryImpl implements ViewOrdersRepositoryInterface {
|
||||
/// Creates an instance backed by the given [apiService].
|
||||
ViewOrdersRepositoryImpl({required BaseApiService apiService})
|
||||
: _api = apiService;
|
||||
|
||||
@@ -4,7 +4,7 @@ import 'package:krow_domain/krow_domain.dart';
|
||||
///
|
||||
/// V2 API returns workers inline with order items, so the separate
|
||||
/// accepted-applications method is no longer needed.
|
||||
abstract class IViewOrdersRepository {
|
||||
abstract class ViewOrdersRepositoryInterface {
|
||||
/// Fetches [OrderItem] list for the given date range via the V2 API.
|
||||
Future<List<OrderItem>> getOrdersForRange({
|
||||
required DateTime start,
|
||||
@@ -1,18 +1,18 @@
|
||||
import 'package:krow_core/core.dart';
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
import '../repositories/i_view_orders_repository.dart';
|
||||
import '../repositories/view_orders_repository_interface.dart';
|
||||
import '../arguments/orders_range_arguments.dart';
|
||||
|
||||
/// Use case for retrieving the list of client orders.
|
||||
///
|
||||
/// This use case encapsulates the business rule of fetching orders
|
||||
/// and delegates the data retrieval to the [IViewOrdersRepository].
|
||||
/// and delegates the data retrieval to the [ViewOrdersRepositoryInterface].
|
||||
class GetOrdersUseCase
|
||||
implements UseCase<OrdersRangeArguments, List<OrderItem>> {
|
||||
|
||||
/// Creates a [GetOrdersUseCase] with the required [IViewOrdersRepository].
|
||||
/// Creates a [GetOrdersUseCase] with the required [ViewOrdersRepositoryInterface].
|
||||
GetOrdersUseCase(this._repository);
|
||||
final IViewOrdersRepository _repository;
|
||||
final ViewOrdersRepositoryInterface _repository;
|
||||
|
||||
@override
|
||||
Future<List<OrderItem>> call(OrdersRangeArguments input) {
|
||||
|
||||
@@ -4,11 +4,11 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_modular/flutter_modular.dart';
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
|
||||
import '../../domain/repositories/i_view_orders_repository.dart';
|
||||
import '../../domain/repositories/view_orders_repository_interface.dart';
|
||||
|
||||
/// Bottom sheet for editing an existing order via the V2 API.
|
||||
///
|
||||
/// Delegates all backend calls through [IViewOrdersRepository].
|
||||
/// Delegates all backend calls through [ViewOrdersRepositoryInterface].
|
||||
/// The V2 `clientOrderEdit` endpoint creates an edited copy.
|
||||
class OrderEditSheet extends StatefulWidget {
|
||||
/// Creates an [OrderEditSheet] for the given [order].
|
||||
@@ -39,12 +39,12 @@ class OrderEditSheetState extends State<OrderEditSheet> {
|
||||
List<Map<String, dynamic>> _hubs = const <Map<String, dynamic>>[];
|
||||
Map<String, dynamic>? _selectedHub;
|
||||
|
||||
late IViewOrdersRepository _repository;
|
||||
late ViewOrdersRepositoryInterface _repository;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_repository = Modular.get<IViewOrdersRepository>();
|
||||
_repository = Modular.get<ViewOrdersRepositoryInterface>();
|
||||
_orderNameController = TextEditingController(text: widget.order.roleName);
|
||||
|
||||
final String startHH =
|
||||
@@ -441,9 +441,9 @@ class OrderEditSheetState extends State<OrderEditSheet> {
|
||||
const SizedBox(height: UiConstants.space3),
|
||||
|
||||
// Role selector
|
||||
_buildSectionHeader('ROLE'),
|
||||
_buildSectionHeader('ROLE'), // TODO: localize
|
||||
_buildDropdown(
|
||||
hint: 'Select role',
|
||||
hint: 'Select role', // TODO: localize
|
||||
value: roleName.isNotEmpty ? roleName : null,
|
||||
items: _roles
|
||||
.map((Map<String, dynamic> r) => r['roleName'] as String? ?? r['name'] as String? ?? '')
|
||||
@@ -495,7 +495,7 @@ class OrderEditSheetState extends State<OrderEditSheet> {
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: _buildInlineTimeInput(
|
||||
label: 'Start Time',
|
||||
label: 'Start Time', // TODO: localize
|
||||
value: pos['startTime'] as String? ?? '09:00',
|
||||
onTap: () async {
|
||||
final TimeOfDay? picked = await showTimePicker(
|
||||
@@ -513,7 +513,7 @@ class OrderEditSheetState extends State<OrderEditSheet> {
|
||||
const SizedBox(width: UiConstants.space3),
|
||||
Expanded(
|
||||
child: _buildInlineTimeInput(
|
||||
label: 'End Time',
|
||||
label: 'End Time', // TODO: localize
|
||||
value: pos['endTime'] as String? ?? '17:00',
|
||||
onTap: () async {
|
||||
final TimeOfDay? picked = await showTimePicker(
|
||||
@@ -825,6 +825,7 @@ class OrderEditSheetState extends State<OrderEditSheet> {
|
||||
style: UiTypography.body2b.textPrimary,
|
||||
),
|
||||
Text(
|
||||
// TODO: localize
|
||||
'${pos['workerCount']} worker${(pos['workerCount'] as int? ?? 1) > 1 ? 's' : ''}',
|
||||
style: UiTypography.footnote2r.textSecondary,
|
||||
),
|
||||
|
||||
@@ -4,7 +4,7 @@ import 'package:krow_core/core.dart';
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
|
||||
import 'data/repositories/view_orders_repository_impl.dart';
|
||||
import 'domain/repositories/i_view_orders_repository.dart';
|
||||
import 'domain/repositories/view_orders_repository_interface.dart';
|
||||
import 'domain/usecases/get_orders_use_case.dart';
|
||||
import 'presentation/blocs/view_orders_cubit.dart';
|
||||
import 'presentation/pages/view_orders_page.dart';
|
||||
@@ -20,7 +20,7 @@ class ViewOrdersModule extends Module {
|
||||
@override
|
||||
void binds(Injector i) {
|
||||
// Repositories
|
||||
i.add<IViewOrdersRepository>(
|
||||
i.add<ViewOrdersRepositoryInterface>(
|
||||
() => ViewOrdersRepositoryImpl(
|
||||
apiService: i.get<BaseApiService>(),
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user