diff --git a/apps/mobile/packages/domain/lib/krow_domain.dart b/apps/mobile/packages/domain/lib/krow_domain.dart index fc763e64..3dc41679 100644 --- a/apps/mobile/packages/domain/lib/krow_domain.dart +++ b/apps/mobile/packages/domain/lib/krow_domain.dart @@ -87,6 +87,11 @@ export 'src/adapters/clock_in/clock_in_adapter.dart'; export 'src/entities/availability/availability_slot.dart'; export 'src/entities/availability/day_availability.dart'; +// Coverage +export 'src/entities/coverage_domain/coverage_shift.dart'; +export 'src/entities/coverage_domain/coverage_worker.dart'; +export 'src/entities/coverage_domain/coverage_stats.dart'; + // Adapters export 'src/adapters/profile/emergency_contact_adapter.dart'; export 'src/adapters/profile/experience_adapter.dart'; diff --git a/apps/mobile/packages/domain/lib/src/entities/coverage_domain/coverage_shift.dart b/apps/mobile/packages/domain/lib/src/entities/coverage_domain/coverage_shift.dart new file mode 100644 index 00000000..afc10d60 --- /dev/null +++ b/apps/mobile/packages/domain/lib/src/entities/coverage_domain/coverage_shift.dart @@ -0,0 +1,57 @@ +import 'package:equatable/equatable.dart'; +import 'coverage_worker.dart'; + +/// Domain entity representing a shift in the coverage view. +/// +/// This is a feature-specific domain entity that encapsulates shift information +/// including scheduling details and assigned workers. +class CoverageShift extends Equatable { + /// Creates a [CoverageShift]. + const CoverageShift({ + required this.id, + required this.title, + required this.location, + required this.startTime, + required this.workersNeeded, + required this.date, + required this.workers, + }); + + /// The unique identifier for the shift. + final String id; + + /// The title or role of the shift. + final String title; + + /// The location where the shift takes place. + final String location; + + /// The start time of the shift (e.g., "16:00"). + final String startTime; + + /// The number of workers needed for this shift. + final int workersNeeded; + + /// The date of the shift. + final DateTime date; + + /// The list of workers assigned to this shift. + final List workers; + + /// Calculates the coverage percentage for this shift. + int get coveragePercent { + if (workersNeeded == 0) return 0; + return ((workers.length / workersNeeded) * 100).round(); + } + + @override + List get props => [ + id, + title, + location, + startTime, + workersNeeded, + date, + workers, + ]; +} diff --git a/apps/mobile/packages/domain/lib/src/entities/coverage_domain/coverage_stats.dart b/apps/mobile/packages/domain/lib/src/entities/coverage_domain/coverage_stats.dart new file mode 100644 index 00000000..580116a9 --- /dev/null +++ b/apps/mobile/packages/domain/lib/src/entities/coverage_domain/coverage_stats.dart @@ -0,0 +1,45 @@ +import 'package:equatable/equatable.dart'; + +/// Domain entity representing coverage statistics. +/// +/// Aggregates coverage metrics for a specific date. +class CoverageStats extends Equatable { + /// Creates a [CoverageStats]. + const CoverageStats({ + required this.totalNeeded, + required this.totalConfirmed, + required this.checkedIn, + required this.enRoute, + required this.late, + }); + + /// The total number of workers needed. + final int totalNeeded; + + /// The total number of confirmed workers. + final int totalConfirmed; + + /// The number of workers who have checked in. + final int checkedIn; + + /// The number of workers en route. + final int enRoute; + + /// The number of late workers. + final int late; + + /// Calculates the overall coverage percentage. + int get coveragePercent { + if (totalNeeded == 0) return 0; + return ((totalConfirmed / totalNeeded) * 100).round(); + } + + @override + List get props => [ + totalNeeded, + totalConfirmed, + checkedIn, + enRoute, + late, + ]; +} diff --git a/apps/mobile/packages/domain/lib/src/entities/coverage_domain/coverage_worker.dart b/apps/mobile/packages/domain/lib/src/entities/coverage_domain/coverage_worker.dart new file mode 100644 index 00000000..3ade4d9d --- /dev/null +++ b/apps/mobile/packages/domain/lib/src/entities/coverage_domain/coverage_worker.dart @@ -0,0 +1,55 @@ +import 'package:equatable/equatable.dart'; + +/// Worker status enum matching ApplicationStatus from Data Connect. +enum CoverageWorkerStatus { + /// Application is pending approval. + pending, + + /// Application has been accepted. + accepted, + + /// Application has been rejected. + rejected, + + /// Worker has confirmed attendance. + confirmed, + + /// Worker has checked in. + checkedIn, + + /// Worker has checked out. + checkedOut, + + /// Worker is late. + late, + + /// Worker did not show up. + noShow, + + /// Shift is completed. + completed, +} + +/// Domain entity representing a worker in the coverage view. +/// +/// This entity tracks worker status including check-in information. +class CoverageWorker extends Equatable { + /// Creates a [CoverageWorker]. + const CoverageWorker({ + required this.name, + required this.status, + this.checkInTime, + }); + + /// The name of the worker. + final String name; + + /// The status of the worker. + final CoverageWorkerStatus status; + + /// The time the worker checked in, if applicable. + final String? checkInTime; + + @override + List get props => [name, status, checkInTime]; +} diff --git a/apps/mobile/packages/features/client/client_coverage/lib/src/data/repositories_impl/coverage_repository_impl.dart b/apps/mobile/packages/features/client/client_coverage/lib/src/data/repositories_impl/coverage_repository_impl.dart index 305f65e9..9ee38782 100644 --- a/apps/mobile/packages/features/client/client_coverage/lib/src/data/repositories_impl/coverage_repository_impl.dart +++ b/apps/mobile/packages/features/client/client_coverage/lib/src/data/repositories_impl/coverage_repository_impl.dart @@ -1,7 +1,7 @@ import 'package:firebase_data_connect/firebase_data_connect.dart' as fdc; import 'package:krow_data_connect/krow_data_connect.dart' as dc; +import 'package:krow_domain/krow_domain.dart'; import '../../domain/repositories/coverage_repository.dart'; -import '../../domain/ui_entities/coverage_entities.dart'; /// Implementation of [CoverageRepository] in the Data layer. /// @@ -25,18 +25,14 @@ class CoverageRepositoryImpl implements CoverageRepository { Future> getShiftsForDate({required DateTime date}) async { final String? businessId = dc.ClientSessionStore.instance.session?.business?.id; - print('Coverage: now=${DateTime.now().toIso8601String()}'); if (businessId == null || businessId.isEmpty) { - print('Coverage: missing businessId for date=${date.toIso8601String()}'); return []; } final DateTime start = DateTime(date.year, date.month, date.day); final DateTime end = DateTime(date.year, date.month, date.day, 23, 59, 59, 999); - print( - 'Coverage: request businessId=$businessId dayStart=${start.toIso8601String()} dayEnd=${end.toIso8601String()}', - ); + final fdc.QueryResult< dc.ListShiftRolesByBusinessAndDateRangeData, dc.ListShiftRolesByBusinessAndDateRangeVariables> shiftRolesResult = @@ -58,9 +54,6 @@ class CoverageRepositoryImpl implements CoverageRepository { dayEnd: _toTimestamp(end), ) .execute(); - print( - 'Coverage: ${date.toIso8601String()} staffsApplications=${applicationsResult.data.applications.length}', - ); return _mapCoverageShifts( shiftRolesResult.data.shiftRoles, @@ -84,11 +77,16 @@ class CoverageRepositoryImpl implements CoverageRepository { final List allWorkers = shifts.expand((CoverageShift shift) => shift.workers).toList(); final int totalConfirmed = allWorkers.length; - final int checkedIn = - allWorkers.where((CoverageWorker w) => w.isCheckedIn).length; - final int enRoute = - allWorkers.where((CoverageWorker w) => w.isEnRoute).length; - final int late = allWorkers.where((CoverageWorker w) => w.isLate).length; + final int checkedIn = allWorkers + .where((CoverageWorker w) => w.status == CoverageWorkerStatus.checkedIn) + .length; + final int enRoute = allWorkers + .where((CoverageWorker w) => + w.status == CoverageWorkerStatus.confirmed && w.checkInTime == null) + .length; + final int late = allWorkers + .where((CoverageWorker w) => w.status == CoverageWorkerStatus.late) + .length; return CoverageStats( totalNeeded: totalNeeded, @@ -172,25 +170,32 @@ class CoverageRepositoryImpl implements CoverageRepository { .toList(); } - String _mapWorkerStatus( + CoverageWorkerStatus _mapWorkerStatus( dc.EnumValue status, ) { if (status is dc.Known) { switch (status.value) { - case dc.ApplicationStatus.LATE: - return 'late'; - case dc.ApplicationStatus.CHECKED_IN: - case dc.ApplicationStatus.CHECKED_OUT: - case dc.ApplicationStatus.ACCEPTED: - case dc.ApplicationStatus.CONFIRMED: case dc.ApplicationStatus.PENDING: + return CoverageWorkerStatus.pending; + case dc.ApplicationStatus.ACCEPTED: + return CoverageWorkerStatus.accepted; case dc.ApplicationStatus.REJECTED: + return CoverageWorkerStatus.rejected; + case dc.ApplicationStatus.CONFIRMED: + return CoverageWorkerStatus.confirmed; + case dc.ApplicationStatus.CHECKED_IN: + return CoverageWorkerStatus.checkedIn; + case dc.ApplicationStatus.CHECKED_OUT: + return CoverageWorkerStatus.checkedOut; + case dc.ApplicationStatus.LATE: + return CoverageWorkerStatus.late; case dc.ApplicationStatus.NO_SHOW: + return CoverageWorkerStatus.noShow; case dc.ApplicationStatus.COMPLETED: - return 'confirmed'; + return CoverageWorkerStatus.completed; } } - return 'confirmed'; + return CoverageWorkerStatus.pending; } String? _formatTime(fdc.Timestamp? timestamp) { diff --git a/apps/mobile/packages/features/client/client_coverage/lib/src/domain/repositories/coverage_repository.dart b/apps/mobile/packages/features/client/client_coverage/lib/src/domain/repositories/coverage_repository.dart index 6d7de8ba..f5c340b3 100644 --- a/apps/mobile/packages/features/client/client_coverage/lib/src/domain/repositories/coverage_repository.dart +++ b/apps/mobile/packages/features/client/client_coverage/lib/src/domain/repositories/coverage_repository.dart @@ -1,4 +1,4 @@ -import '../ui_entities/coverage_entities.dart'; +import 'package:krow_domain/krow_domain.dart'; /// Repository interface for coverage-related operations. /// diff --git a/apps/mobile/packages/features/client/client_coverage/lib/src/domain/ui_entities/coverage_entities.dart b/apps/mobile/packages/features/client/client_coverage/lib/src/domain/ui_entities/coverage_entities.dart deleted file mode 100644 index bb9249c9..00000000 --- a/apps/mobile/packages/features/client/client_coverage/lib/src/domain/ui_entities/coverage_entities.dart +++ /dev/null @@ -1,133 +0,0 @@ -import 'package:equatable/equatable.dart'; - -/// Domain entity representing a shift in the coverage view. -/// -/// This is a feature-specific domain entity that encapsulates shift information -/// including scheduling details and assigned workers. -class CoverageShift extends Equatable { - /// Creates a [CoverageShift]. - const CoverageShift({ - required this.id, - required this.title, - required this.location, - required this.startTime, - required this.workersNeeded, - required this.date, - required this.workers, - }); - - /// The unique identifier for the shift. - final String id; - - /// The title or role of the shift. - final String title; - - /// The location where the shift takes place. - final String location; - - /// The start time of the shift (e.g., "16:00"). - final String startTime; - - /// The number of workers needed for this shift. - final int workersNeeded; - - /// The date of the shift. - final DateTime date; - - /// The list of workers assigned to this shift. - final List workers; - - /// Calculates the coverage percentage for this shift. - int get coveragePercent { - if (workersNeeded == 0) return 0; - return ((workers.length / workersNeeded) * 100).round(); - } - - @override - List get props => [ - id, - title, - location, - startTime, - workersNeeded, - date, - workers, - ]; -} - -/// Domain entity representing a worker in the coverage view. -/// -/// This entity tracks worker status including check-in information. -class CoverageWorker extends Equatable { - /// Creates a [CoverageWorker]. - const CoverageWorker({ - required this.name, - required this.status, - this.checkInTime, - }); - - /// The name of the worker. - final String name; - - /// The status of the worker ('confirmed', 'late', etc.). - final String status; - - /// The time the worker checked in, if applicable. - final String? checkInTime; - - /// Returns true if the worker is checked in. - bool get isCheckedIn => status == 'confirmed' && checkInTime != null; - - /// Returns true if the worker is en route. - bool get isEnRoute => status == 'confirmed' && checkInTime == null; - - /// Returns true if the worker is late. - bool get isLate => status == 'late'; - - @override - List get props => [name, status, checkInTime]; -} - -/// Domain entity representing coverage statistics. -/// -/// Aggregates coverage metrics for a specific date. -class CoverageStats extends Equatable { - /// Creates a [CoverageStats]. - const CoverageStats({ - required this.totalNeeded, - required this.totalConfirmed, - required this.checkedIn, - required this.enRoute, - required this.late, - }); - - /// The total number of workers needed. - final int totalNeeded; - - /// The total number of confirmed workers. - final int totalConfirmed; - - /// The number of workers who have checked in. - final int checkedIn; - - /// The number of workers en route. - final int enRoute; - - /// The number of late workers. - final int late; - - /// Calculates the overall coverage percentage. - int get coveragePercent { - if (totalNeeded == 0) return 0; - return ((totalConfirmed / totalNeeded) * 100).round(); - } - - @override - List get props => [ - totalNeeded, - totalConfirmed, - checkedIn, - enRoute, - late, - ]; -} diff --git a/apps/mobile/packages/features/client/client_coverage/lib/src/domain/usecases/get_coverage_stats_usecase.dart b/apps/mobile/packages/features/client/client_coverage/lib/src/domain/usecases/get_coverage_stats_usecase.dart index 00cb7c1d..a2fa4a50 100644 --- a/apps/mobile/packages/features/client/client_coverage/lib/src/domain/usecases/get_coverage_stats_usecase.dart +++ b/apps/mobile/packages/features/client/client_coverage/lib/src/domain/usecases/get_coverage_stats_usecase.dart @@ -1,7 +1,8 @@ import 'package:krow_core/core.dart'; +import 'package:krow_domain/krow_domain.dart'; + import '../arguments/get_coverage_stats_arguments.dart'; import '../repositories/coverage_repository.dart'; -import '../ui_entities/coverage_entities.dart'; /// Use case for fetching coverage statistics for a specific date. /// diff --git a/apps/mobile/packages/features/client/client_coverage/lib/src/domain/usecases/get_shifts_for_date_usecase.dart b/apps/mobile/packages/features/client/client_coverage/lib/src/domain/usecases/get_shifts_for_date_usecase.dart index da84506b..1b17c969 100644 --- a/apps/mobile/packages/features/client/client_coverage/lib/src/domain/usecases/get_shifts_for_date_usecase.dart +++ b/apps/mobile/packages/features/client/client_coverage/lib/src/domain/usecases/get_shifts_for_date_usecase.dart @@ -1,7 +1,7 @@ import 'package:krow_core/core.dart'; import '../arguments/get_shifts_for_date_arguments.dart'; import '../repositories/coverage_repository.dart'; -import '../ui_entities/coverage_entities.dart'; +import 'package:krow_domain/krow_domain.dart'; /// Use case for fetching shifts for a specific date. /// diff --git a/apps/mobile/packages/features/client/client_coverage/lib/src/presentation/blocs/coverage_bloc.dart b/apps/mobile/packages/features/client/client_coverage/lib/src/presentation/blocs/coverage_bloc.dart index d8a0a8c3..c218e9a5 100644 --- a/apps/mobile/packages/features/client/client_coverage/lib/src/presentation/blocs/coverage_bloc.dart +++ b/apps/mobile/packages/features/client/client_coverage/lib/src/presentation/blocs/coverage_bloc.dart @@ -1,7 +1,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import '../../domain/arguments/get_coverage_stats_arguments.dart'; import '../../domain/arguments/get_shifts_for_date_arguments.dart'; -import '../../domain/ui_entities/coverage_entities.dart'; +import 'package:krow_domain/krow_domain.dart'; import '../../domain/usecases/get_coverage_stats_usecase.dart'; import '../../domain/usecases/get_shifts_for_date_usecase.dart'; import 'coverage_event.dart'; diff --git a/apps/mobile/packages/features/client/client_coverage/lib/src/presentation/blocs/coverage_state.dart b/apps/mobile/packages/features/client/client_coverage/lib/src/presentation/blocs/coverage_state.dart index 9ca35dad..e6b99656 100644 --- a/apps/mobile/packages/features/client/client_coverage/lib/src/presentation/blocs/coverage_state.dart +++ b/apps/mobile/packages/features/client/client_coverage/lib/src/presentation/blocs/coverage_state.dart @@ -1,5 +1,5 @@ import 'package:equatable/equatable.dart'; -import '../../domain/ui_entities/coverage_entities.dart'; +import 'package:krow_domain/krow_domain.dart'; /// Enum representing the status of coverage data loading. enum CoverageStatus { diff --git a/apps/mobile/packages/features/client/client_coverage/lib/src/presentation/widgets/coverage_quick_stats.dart b/apps/mobile/packages/features/client/client_coverage/lib/src/presentation/widgets/coverage_quick_stats.dart index 56f87c69..31e3fd42 100644 --- a/apps/mobile/packages/features/client/client_coverage/lib/src/presentation/widgets/coverage_quick_stats.dart +++ b/apps/mobile/packages/features/client/client_coverage/lib/src/presentation/widgets/coverage_quick_stats.dart @@ -1,6 +1,6 @@ import 'package:design_system/design_system.dart'; import 'package:flutter/material.dart'; -import '../../domain/ui_entities/coverage_entities.dart'; +import 'package:krow_domain/krow_domain.dart'; /// Quick statistics cards showing coverage metrics. /// diff --git a/apps/mobile/packages/features/client/client_coverage/lib/src/presentation/widgets/coverage_shift_list.dart b/apps/mobile/packages/features/client/client_coverage/lib/src/presentation/widgets/coverage_shift_list.dart index 7ec0c0c5..504828dd 100644 --- a/apps/mobile/packages/features/client/client_coverage/lib/src/presentation/widgets/coverage_shift_list.dart +++ b/apps/mobile/packages/features/client/client_coverage/lib/src/presentation/widgets/coverage_shift_list.dart @@ -1,7 +1,7 @@ import 'package:design_system/design_system.dart'; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; -import '../../domain/ui_entities/coverage_entities.dart'; +import 'package:krow_domain/krow_domain.dart'; /// List of shifts with their workers. /// @@ -194,7 +194,8 @@ class _ShiftHeader extends StatelessWidget { size: UiConstants.space3, color: UiColors.iconSecondary, ), - Expanded(child: Text( + Expanded( + child: Text( location, style: UiTypography.body3r.textSecondary, overflow: TextOverflow.ellipsis, @@ -314,36 +315,92 @@ class _WorkerRow extends StatelessWidget { Color badgeText; String badgeLabel; - if (worker.isCheckedIn) { - bg = UiColors.textSuccess.withOpacity(0.1); - border = UiColors.textSuccess; - textBg = UiColors.textSuccess.withOpacity(0.2); - textColor = UiColors.textSuccess; - icon = UiIcons.success; - statusText = '✓ Checked In at ${formatTime(worker.checkInTime)}'; - badgeBg = UiColors.textSuccess; - badgeText = UiColors.primaryForeground; - badgeLabel = 'On Site'; - } else if (worker.isEnRoute) { - bg = UiColors.textWarning.withOpacity(0.1); - border = UiColors.textWarning; - textBg = UiColors.textWarning.withOpacity(0.2); - textColor = UiColors.textWarning; - icon = UiIcons.clock; - statusText = 'En Route - Expected $shiftStartTime'; - badgeBg = UiColors.textWarning; - badgeText = UiColors.primaryForeground; - badgeLabel = 'En Route'; - } else { - bg = UiColors.destructive.withOpacity(0.1); - border = UiColors.destructive; - textBg = UiColors.destructive.withOpacity(0.2); - textColor = UiColors.destructive; - icon = UiIcons.warning; - statusText = '⚠ Running Late'; - badgeBg = UiColors.destructive; - badgeText = UiColors.destructiveForeground; - badgeLabel = 'Late'; + switch (worker.status) { + case CoverageWorkerStatus.checkedIn: + bg = UiColors.textSuccess.withOpacity(0.1); + border = UiColors.textSuccess; + textBg = UiColors.textSuccess.withOpacity(0.2); + textColor = UiColors.textSuccess; + icon = UiIcons.success; + statusText = '✓ Checked In at ${formatTime(worker.checkInTime)}'; + badgeBg = UiColors.textSuccess; + badgeText = UiColors.primaryForeground; + badgeLabel = 'On Site'; + case CoverageWorkerStatus.confirmed: + if (worker.checkInTime == null) { + bg = UiColors.textWarning.withOpacity(0.1); + border = UiColors.textWarning; + textBg = UiColors.textWarning.withOpacity(0.2); + textColor = UiColors.textWarning; + icon = UiIcons.clock; + statusText = 'En Route - Expected $shiftStartTime'; + badgeBg = UiColors.textWarning; + badgeText = UiColors.primaryForeground; + badgeLabel = 'En Route'; + } else { + bg = UiColors.muted.withOpacity(0.1); + border = UiColors.border; + textBg = UiColors.muted.withOpacity(0.2); + textColor = UiColors.textSecondary; + icon = UiIcons.success; + statusText = 'Confirmed'; + badgeBg = UiColors.muted; + badgeText = UiColors.textPrimary; + badgeLabel = 'Confirmed'; + } + case CoverageWorkerStatus.late: + bg = UiColors.destructive.withOpacity(0.1); + border = UiColors.destructive; + textBg = UiColors.destructive.withOpacity(0.2); + textColor = UiColors.destructive; + icon = UiIcons.warning; + statusText = '⚠ Running Late'; + badgeBg = UiColors.destructive; + badgeText = UiColors.destructiveForeground; + badgeLabel = 'Late'; + case CoverageWorkerStatus.checkedOut: + bg = UiColors.muted.withOpacity(0.1); + border = UiColors.border; + textBg = UiColors.muted.withOpacity(0.2); + textColor = UiColors.textSecondary; + icon = UiIcons.success; + statusText = 'Checked Out'; + badgeBg = UiColors.muted; + badgeText = UiColors.textPrimary; + badgeLabel = 'Done'; + case CoverageWorkerStatus.noShow: + bg = UiColors.destructive.withOpacity(0.1); + border = UiColors.destructive; + textBg = UiColors.destructive.withOpacity(0.2); + textColor = UiColors.destructive; + icon = UiIcons.warning; + statusText = 'No Show'; + badgeBg = UiColors.destructive; + badgeText = UiColors.destructiveForeground; + badgeLabel = 'No Show'; + case CoverageWorkerStatus.completed: + bg = UiColors.textSuccess.withOpacity(0.1); + border = UiColors.textSuccess; + textBg = UiColors.textSuccess.withOpacity(0.2); + textColor = UiColors.textSuccess; + icon = UiIcons.success; + statusText = 'Completed'; + badgeBg = UiColors.textSuccess; + badgeText = UiColors.primaryForeground; + badgeLabel = 'Completed'; + case CoverageWorkerStatus.pending: + case CoverageWorkerStatus.accepted: + case CoverageWorkerStatus.rejected: + bg = UiColors.muted.withOpacity(0.1); + border = UiColors.border; + textBg = UiColors.muted.withOpacity(0.2); + textColor = UiColors.textSecondary; + icon = UiIcons.clock; + statusText = worker.status.name.toUpperCase(); + badgeBg = UiColors.muted; + badgeText = UiColors.textPrimary; + badgeLabel = worker.status.name[0].toUpperCase() + + worker.status.name.substring(1); } return Container( diff --git a/apps/mobile/packages/features/staff/shifts/lib/src/data/repositories_impl/shifts_repository_impl.dart b/apps/mobile/packages/features/staff/shifts/lib/src/data/repositories_impl/shifts_repository_impl.dart index d4df32c0..347f389f 100644 --- a/apps/mobile/packages/features/staff/shifts/lib/src/data/repositories_impl/shifts_repository_impl.dart +++ b/apps/mobile/packages/features/staff/shifts/lib/src/data/repositories_impl/shifts_repository_impl.dart @@ -525,7 +525,7 @@ class ShiftsRepositoryImpl implements ShiftsRepositoryInterface { @override Future acceptShift(String shiftId) async { - await _updateApplicationStatus(shiftId, dc.ApplicationStatus.ACCEPTED); + await _updateApplicationStatus(shiftId, dc.ApplicationStatus.CONFIRMED); } @override diff --git a/docs/QA_TESTING_CHECKLIST.md b/docs/QA_TESTING_CHECKLIST.md index ff272efa..49fef7e4 100644 --- a/docs/QA_TESTING_CHECKLIST.md +++ b/docs/QA_TESTING_CHECKLIST.md @@ -155,7 +155,7 @@ - [ ] Missing worker photo shows default avatar **Loading & Empty States:** -- [ ] Skeleton loaders display while fetching data + - [ ] Empty coverage shows "No shifts today" - [ ] No workers show "No staff assigned" @@ -192,20 +192,10 @@ - [ ] Missing invoice data shows placeholder **Loading & Empty States:** -- [ ] Skeleton loaders display while fetching data - [ ] Empty pending invoices shows "No pending invoices" - [ ] Empty history shows "No invoice history" - [ ] Empty spending breakdown shows "No spending data" -**State Persistence:** -- [ ] Selected period persists after navigating away -- [ ] Billing data refreshes after returning from background - -**Backend Dependency Validation:** -- [ ] `listInvoicesByBusinessId` returns invoice records -- [ ] `listShiftRolesByBusinessAndDatesSummary` returns spending aggregates -- [ ] Period date range correctly calculated -- [ ] Business ID correctly filtered --- @@ -234,27 +224,10 @@ - [ ] Duplicate hub name shows warning - [ ] Hub with active orders prevents deletion (validation error) - **Loading & Empty States:** -- [ ] Skeleton loaders display while fetching hubs - [ ] Empty hubs list shows "No hubs configured" -- [ ] Address autocomplete shows loading during search - [ ] Hub creation shows loading spinner -**State Persistence:** -- [ ] Hub list refreshes after creation/deletion -- [ ] Hub data persists across app sessions - -**Backend Dependency Validation:** -- [ ] `getBusinessesByUserId` retrieves business ID -- [ ] `getTeamsByOwnerId` checks for existing team -- [ ] `createTeam` creates team if missing -- [ ] `getTeamHubsByTeamId` fetches hub list -- [ ] `createTeamHub` creates hub with geocoded data -- [ ] `deleteTeamHub` removes hub entity -- [ ] `listOrdersByBusinessAndTeamHub` validates no active orders -- [ ] Google Places API returns valid address components - --- #### 📱 CLIENT-008: Settings @@ -276,13 +249,6 @@ **Loading & Empty States:** - [ ] Profile data loads on page mount -**State Persistence:** -- [ ] User data refreshes on page focus - -**Backend Dependency Validation:** -- [ ] Firebase Auth signOut called -- [ ] Session data cleared - --- #### 📱 CLIENT-009: Client Main Navigation @@ -307,13 +273,6 @@ - [ ] Navigation bar displays immediately - [ ] Initial tab loads first -**State Persistence:** -- [ ] Active tab persists after app background → foreground -- [ ] Tab state resets to home on app restart - -**Backend Dependency Validation:** -- [ ] No direct backend calls (navigation only) - --- ### STAFF APP FEATURES @@ -332,7 +291,6 @@ - [ ] OTP verification succeeds with valid code - [ ] Profile setup wizard displays for new users - [ ] Authenticated users bypass auth and show home -- [ ] Session persists after app restart **Validation & Error States:** - [ ] Invalid phone format shows validation error @@ -346,16 +304,6 @@ - [ ] OTP input shows countdown timer - [ ] Profile setup shows progress indicator -**State Persistence:** -- [ ] Authenticated session persists after app background → foreground -- [ ] Session expires appropriately after logout - -**Backend Dependency Validation:** -- [ ] Firebase Auth phone verification flow completes -- [ ] `getUserById` returns user data -- [ ] `getStaffByUserId` retrieves staff profile -- [ ] Staff profile created if missing - --- #### 📱 STAFF-002: Home Dashboard @@ -375,21 +323,10 @@ **Validation & Error States:** - [ ] Missing shift data shows placeholder - **Loading & Empty States:** -- [ ] Skeleton loaders display while fetching data - [ ] Empty today's shifts shows "No shifts today" - [ ] Empty recommended shows "No available shifts" -**State Persistence:** -- [ ] Dashboard data refreshes after returning from background -- [ ] Shift status updates reflected immediately - -**Backend Dependency Validation:** -- [ ] `getApplicationsByStaffId` fetches staff assignments -- [ ] `listShifts` returns available shifts -- [ ] Date filtering correctly applied - --- #### 📱 STAFF-003: Profile @@ -412,16 +349,6 @@ **Loading & Empty States:** - [ ] Profile data loads on page mount -- [ ] Statistics display placeholders while loading - -**State Persistence:** -- [ ] Profile data refreshes on page focus -- [ ] Profile updates reflect immediately - -**Backend Dependency Validation:** -- [ ] `getStaffByUserId` retrieves complete staff profile -- [ ] Firebase Auth signOut called -- [ ] Session data cleared --- @@ -446,31 +373,10 @@ **Validation & Error States:** - [ ] Empty tabs show appropriate empty state messages - - [ ] Already applied shift prevents duplicate application - [ ] Past shifts cannot be applied to - [ ] Cancelled shifts show cancellation reason -**Loading & Empty States:** -- [ ] Skeleton loaders display while fetching shifts -- [ ] Empty My Shifts shows "No assigned shifts" -- [ ] Empty Available shows "No open shifts" -- [ ] Empty Pending shows "No pending applications" -- [ ] Empty History shows "No past shifts" - -**State Persistence:** -- [ ] Active tab persists after navigating away -- [ ] Shift list refreshes after status changes -- [ ] Shift data refreshes after returning from background - -**Backend Dependency Validation:** -- [ ] `getApplicationsByStaffId` fetches applications by status -- [ ] `getShiftById` retrieves shift details -- [ ] `updateApplicationStatus` changes application state -- [ ] `createApplication` creates new application -- [ ] `deleteApplication` removes application -- [ ] `updateShift` updates filled count - --- #### 📱 STAFF-005: Availability Management @@ -497,17 +403,6 @@ - [ ] Loading spinner displays while fetching availability - [ ] Default state shows all unavailable -**State Persistence:** -- [ ] Availability persists across app sessions -- [ ] Changes reflect immediately in shift matching - -**Backend Dependency Validation:** -- [ ] `getStaffByUserId` retrieves staff ID -- [ ] `listStaffAvailabilitiesByStaffId` fetches availability records -- [ ] `getStaffAvailabilityByKey` checks existing record -- [ ] `updateStaffAvailability` updates existing slot -- [ ] `createStaffAvailability` creates new slot - --- #### 📱 STAFF-006: Clock In/Out @@ -535,17 +430,6 @@ - [ ] Loading spinner displays while fetching shift - [ ] Empty state shows "No shifts scheduled" -**State Persistence:** -- [ ] Attendance status persists across app sessions -- [ ] Clock in/out times display correctly - -**Backend Dependency Validation:** -- [ ] `getApplicationsByStaffId` fetches today's shifts -- [ ] `createAttendance` records clock in -- [ ] `updateAttendance` records clock out -- [ ] `listAttendancesByApplicationId` gets attendance status -- [ ] `updateApplicationStatus` updates application state - --- #### 📱 STAFF-007: Payments @@ -565,20 +449,9 @@ - [ ] Zero earnings show $0.00 (not error) - [ ] Missing payment data shows placeholder - **Loading & Empty States:** -- [ ] Skeleton loaders display while fetching payments - [ ] Empty history shows "No payment history" -**State Persistence:** -- [ ] Payment data refreshes after returning from background -- [ ] Filter state persists after navigating away - -**Backend Dependency Validation:** -- [ ] `getStaffByUserId` retrieves staff ID -- [ ] `getPaymentsByStaffId` fetches payment records -- [ ] Mock summary data calculated correctly - --- #### 📱 STAFF-008: Personal Info (Onboarding) @@ -609,14 +482,6 @@ - [ ] Photo upload shows progress indicator - [ ] Save button shows loading spinner -**State Persistence:** -- [ ] Changes persist after save -- [ ] Unsaved changes show confirmation dialog on exit - -**Backend Dependency Validation:** -- [ ] `getStaffByUserId` fetches profile -- [ ] `updateStaff` saves profile changes - --- #### 📱 STAFF-009: Emergency Contact (Onboarding) @@ -640,22 +505,11 @@ - [ ] Invalid phone format shows error - [ ] At least one contact required (if applicable) - **Loading & Empty States:** - [ ] Loading spinner displays while fetching contacts - [ ] Empty state shows "No emergency contacts" - [ ] Save button shows loading spinner -**State Persistence:** -- [ ] Contacts persist after save -- [ ] Unsaved changes show confirmation dialog on exit - -**Backend Dependency Validation:** -- [ ] `getStaffByUserId` retrieves staff ID -- [ ] `getEmergencyContactsByStaffId` fetches contacts -- [ ] `deleteEmergencyContact` removes contacts (replace-all pattern) -- [ ] `createEmergencyContact` creates new contacts - --- #### 📱 STAFF-010: Experience & Skills (Onboarding) @@ -681,48 +535,6 @@ - [ ] Loading spinner displays while fetching data - [ ] Save button shows loading spinner -**State Persistence:** -- [ ] Selections persist after save -- [ ] Unsaved changes show confirmation dialog on exit - -**Backend Dependency Validation:** -- [ ] `getStaffByUserId` fetches profile with industries and skills -- [ ] `updateStaff` updates industries and skills arrays - ---- - -#### 📱 STAFF-011: Attire Selection (Onboarding) - -**Applications:** Staff -**Entry Points:** -- Profile → Attire -- Onboarding wizard - -**Happy Path Test Cases:** -- [ ] Attire options list displays all items -- [ ] Item selection toggles checkmark -- [ ] Photo upload button opens camera/gallery -- [ ] Photos display in grid -- [ ] Save updates selections and photos - -**Validation & Error States:** -- [ ] At least one attire item required (if applicable) -- [ ] Photo upload failure shows error - - -**Loading & Empty States:** -- [ ] Loading spinner displays while fetching options -- [ ] Photo upload shows progress indicator -- [ ] Save button shows loading spinner - -**State Persistence:** -- [ ] Selections and photos persist after save -- [ ] Unsaved changes show confirmation dialog on exit - -**Backend Dependency Validation:** -- [ ] `listAttireOptions` fetches available items -- [ ] Photo upload and save mutations (pending implementation) - --- #### 📱 STAFF-012: Bank Account (Finances) @@ -780,19 +592,9 @@ **Validation & Error States:** - [ ] Missing attendance data shows "Not recorded" - **Loading & Empty States:** -- [ ] Skeleton loaders display while fetching records - [ ] Empty state shows "No time card history" -**State Persistence:** -- [ ] Time card data refreshes after returning from background - -**Backend Dependency Validation:** -- [ ] `getStaffByUserId` retrieves staff ID -- [ ] `getApplicationsByStaffId` fetches applications with attendance -- [ ] Attendance records mapped to time card format - --- #### 📱 STAFF-014: Tax Forms (Compliance) @@ -817,82 +619,11 @@ - [ ] Invalid date format shows error - [ ] Signature required validation - **Loading & Empty States:** - [ ] Loading spinner displays while fetching forms - [ ] Form editor loads with skeleton placeholders - [ ] Save button shows loading spinner -**State Persistence:** -- [ ] Form data persists after save -- [ ] Unsaved changes show confirmation dialog on exit -- [ ] Form status updates immediately - -**Backend Dependency Validation:** -- [ ] `getTaxFormsByStaffId` fetches forms -- [ ] `createTaxForm` initializes missing forms -- [ ] `updateTaxForm` saves form data and status - ---- - -#### 📱 STAFF-015: Documents (Compliance) - -**Applications:** Staff -**Entry Points:** -- Profile → Documents - -**Happy Path Test Cases:** -- [ ] Documents list displays required documents -- [ ] Document status shows verified/pending/expired -- [ ] Document detail view shows requirements -- [ ] Expiry dates display correctly -- [ ] Expired documents highlight in red - -**Validation & Error States:** -- [ ] Missing documents show incomplete status - - -**Loading & Empty States:** -- [ ] Skeleton loaders display while fetching documents -- [ ] Empty state shows "No documents required" - -**State Persistence:** -- [ ] Document data refreshes after returning from background - -**Backend Dependency Validation:** -- [ ] Mock implementation currently -- [ ] ⚠️ Requires clarification: Real Data Connect integration pending - ---- - -#### 📱 STAFF-016: Certificates (Compliance) - -**Applications:** Staff -**Entry Points:** -- Profile → Certificates - -**Happy Path Test Cases:** -- [ ] Certificates list displays all certificates -- [ ] Certificate cards show name, status, and expiry -- [ ] Certificate detail view shows full information -- [ ] Expired certificates highlight in red -- [ ] Certificate verification status displays - -**Validation & Error States:** -- [ ] Missing certificates show placeholder - - -**Loading & Empty States:** -- [ ] Skeleton loaders display while fetching certificates -- [ ] Empty state shows "No certificates" - -**State Persistence:** -- [ ] Certificate data refreshes after returning from background - -**Backend Dependency Validation:** -- [ ] `listStaffDocumentsByStaffId` fetches certificate documents -- [ ] Document data mapped to certificate entities - --- #### 📱 STAFF-017: Staff Main Navigation @@ -1175,435 +906,3 @@ - ✅ No feature overlap or unauthorized access --- - -### Scenario 9: Race Condition - Concurrent Shift Application - -**Preconditions:** -- One available shift with 1 position -- Two staff users authenticated on separate devices - -**Steps:** -1. **STAFF APP (Device 1):** - - [ ] Navigate to Shifts → Available - - [ ] View shift details - -2. **STAFF APP (Device 2):** - - [ ] Navigate to Shifts → Available - - [ ] View same shift details - -3. **STAFF APP (Device 1):** - - [ ] Apply for shift - - [ ] Verify application created - -4. **STAFF APP (Device 2):** - - [ ] Attempt to apply for same shift - - [ ] Verify appropriate behavior (position filled message or pending status) - -5. **CLIENT APP:** - - [ ] Navigate to View Orders - - [ ] Verify only 1 application shows (not 2) - - [ ] Accept Device 1 application - -6. **STAFF APP (Device 2):** - - [ ] Refresh Available shifts - - [ ] Verify shift removed or shows as filled - -**Expected Results:** -- ✅ Only first application succeeds (or both go to pending) -- ✅ No double-booking occurs -- ✅ Race condition handled gracefully -- ⚠️ **Requires clarification:** Backend concurrency control behavior - ---- - -### Scenario 10: Network Failure During Critical Operation - -**Preconditions:** -- Staff has pending shift application - -**Steps:** -1. **STAFF APP:** - - [ ] Navigate to Shifts → Pending - - [ ] Disable network connection - - [ ] Attempt to accept shift - - [ ] Verify offline error message displays - - [ ] Re-enable network - - [ ] Retry accept shift - - [ ] Verify acceptance succeeds - -2. **CLIENT APP:** - - [ ] Verify shift shows as filled after network restored - -**Expected Results:** -- ✅ Offline state handled gracefully with clear messaging -- ✅ Retry succeeds after network restored -- ✅ Data consistency maintained - ---- - -## 3️⃣ SHARED INFRASTRUCTURE VALIDATION - -### Domain Entity Consistency - -#### Test: Entity Field Validation - -- [ ] **Staff Entity:** - - [ ] Verify all required fields populate (id, userId, firstName, lastName, email, phone) - - [ ] Verify optional fields handle null correctly (photoUrl, preferredLocations) - - [ ] Verify enum fields map correctly (UserStatus) - -- [ ] **Order Entity:** - - [ ] Verify all required fields populate - - [ ] Verify OrderStatus enum maps correctly - - [ ] Verify OrderType enum maps correctly - -- [ ] **Shift Entity:** - - [ ] Verify date/time fields parse correctly - - [ ] Verify ShiftStatus enum maps correctly - - [ ] Verify location data (hub) links correctly - -- [ ] **Application Entity:** - - [ ] Verify ApplicationStatus enum maps correctly - - [ ] Verify relationships (staff, shift, role) link correctly - -- [ ] **Invoice Entity:** - - [ ] Verify amount calculations correct - - [ ] Verify date fields parse correctly - - [ ] Verify InvoiceStatus enum maps correctly - -- [ ] **Hub Entity:** - - [ ] Verify address components parse correctly - - [ ] Verify geocoding (lat/lng) present and valid - - [ ] Verify placeId populated - ---- - -### Data Connect Schema Alignment - -#### Test: Backend Operation Contracts - -- [ ] **User Operations:** - - [ ] `getUserById(userId)` returns expected fields - - [ ] `createUser(...)` accepts all required parameters - - [ ] `updateUser(...)` updates only specified fields - -- [ ] **Staff Operations:** - - [ ] `getStaffByUserId(userId)` returns staff profile - - [ ] `updateStaff(...)` updates specified fields - - [ ] `listStaffs()` returns paginated results - -- [ ] **Order Operations:** - - [ ] `createOrder(...)` creates order with shifts - - [ ] `listOrdersByBusinessId(...)` filters by business correctly - - [ ] `updateOrder(...)` updates order fields - -- [ ] **Shift Operations:** - - [ ] `createShift(...)` creates shift with location - - [ ] `getShiftById(id)` returns full shift details - - [ ] `listShiftRolesByBusinessAndDateRange(...)` returns correct date range - -- [ ] **Application Operations:** - - [ ] `createApplication(...)` creates pending application - - [ ] `updateApplicationStatus(...)` changes status correctly - - [ ] `getApplicationsByStaffId(...)` filters by staff and date - -- [ ] **Attendance Operations:** - - [ ] `createAttendance(...)` records clock in - - [ ] `updateAttendance(...)` records clock out - - [ ] `listAttendancesByApplicationId(...)` returns attendance records - -- [ ] **Hub Operations:** - - [ ] `createTeamHub(...)` creates hub with location data - - [ ] `getTeamHubsByTeamId(...)` returns hubs for team - - [ ] `deleteTeamHub(id)` removes hub entity - ---- - -### Error Handling Consistency - -#### Test: Standard Error Patterns - -- [ ] **Network Errors:** - - [ ] All features show "Network error" message - - [ ] All features show "Retry" button - - [ ] Retry button re-attempts operation - -- [ ] **Authentication Errors:** - - [ ] Expired token redirects to login - - [ ] Invalid credentials show appropriate message - - [ ] Auth failures log out user - -- [ ] **Validation Errors:** - - [ ] Field-level validation shows inline errors - - [ ] Form-level validation prevents submission - - [ ] Error messages are user-friendly - -- [ ] **Backend Errors:** - - [ ] 400 errors show validation details - - [ ] 404 errors show "Not found" message - - [ ] 500 errors show "Server error, try again" message - -- [ ] **Data Not Found:** - - [ ] Empty lists show appropriate empty state - - [ ] Missing entities show "Not found" message - - [ ] Deleted entities handle gracefully - ---- - -### Version Mismatch Tolerance - -#### Test: App Version Compatibility - -- [ ] **Client App Updated, Staff App Not:** - - [ ] Backend operations remain compatible - - [ ] Shared domain entities parse correctly - - [ ] New fields in Client don't break Staff - -- [ ] **Staff App Updated, Client App Not:** - - [ ] Backend operations remain compatible - - [ ] Shared domain entities parse correctly - - [ ] New fields in Staff don't break Client - -- [ ] **Backend Schema Updated:** - - [ ] Apps handle new optional fields gracefully - - [ ] Apps ignore unknown fields - - [ ] Required fields validated correctly - ---- - -## 4️⃣ REGRESSION & RELEASE CHECKLIST - -### Smoke Testing (Critical Path) - -#### Authentication Flow (5 minutes) - -- [ ] **Client App:** - - [ ] Launch app shows Get Started screen - - [ ] Sign in with valid credentials succeeds - - [ ] Home dashboard displays - -- [ ] **Staff App:** - - [ ] Launch app shows Get Started screen - - [ ] Phone verification sends OTP - - [ ] OTP verification succeeds - - [ ] Home dashboard displays - -#### Order Creation & Application (10 minutes) - -- [ ] **Client App:** - - [ ] Create one-time order succeeds - - [ ] Order appears in View Orders list - - [ ] Order details display correctly - -- [ ] **Staff App:** - - [ ] Available shift appears in Shifts tab - - [ ] Apply for shift succeeds - - [ ] Application appears in Pending tab - -- [ ] **Client App:** - - [ ] Pending application displays in View Orders - - [ ] Coverage shows staff as pending - -#### Clock In/Out Flow (5 minutes) - -- [ ] **Staff App:** - - [ ] Accept shift from Pending tab - - [ ] Clock in on Clock In tab - - [ ] Clock in time recorded - -- [ ] **Client App:** - - [ ] Coverage shows staff as checked in - - [ ] Staff status updates in real-time - -- [ ] **Staff App:** - - [ ] Clock out succeeds - - [ ] Time card displays attendance record - ---- - -### Critical Path Validation (Must Pass Before Release) - -#### Client App Critical Features - -- [ ] **Authentication:** - - [ ] Sign in with email/password works - - [ ] Session persists after restart - -- [ ] **Order Management:** - - [ ] Create order succeeds - - [ ] View orders displays correctly - - [ ] Order details accurate - -- [ ] **Coverage Monitoring:** - - [ ] Coverage stats display correctly - - [ ] Staff status updates reflect backend - -- [ ] **Billing:** - - [ ] Invoice list displays - - [ ] Spending breakdown calculates correctly - -#### Staff App Critical Features - -- [ ] **Authentication:** - - [ ] Phone verification works - - [ ] Session persists after restart - -- [ ] **Shift Management:** - - [ ] Available shifts display - - [ ] Apply for shift succeeds - - [ ] Accept shift succeeds - - [ ] My Shifts displays assigned shifts - -- [ ] **Clock In/Out:** - - [ ] Clock in records attendance - - [ ] Clock out completes record - -- [ ] **Profile:** - - [ ] View profile displays data - - [ ] Update personal info succeeds - ---- - -### High-Risk Features (Require Extra Scrutiny) - -#### Payment Processing - -- [ ] **Staff App:** - - [ ] Payment history displays correctly - - [ ] Payment amounts accurate - - [ ] No double-payment scenarios - -- [ ] **Client App:** - - [ ] Invoice amounts correct - - [ ] Billing calculations accurate - - [ ] No overcharging scenarios - -#### Data Integrity - -- [ ] **Order → Shift → Application Chain:** - - [ ] Order creation creates shifts - - [ ] Shift deletion cascades correctly - - [ ] Application deletion updates shift counts - -- [ ] **Attendance Records:** - - [ ] Clock in/out times accurate - - [ ] Hours calculation correct - - [ ] No duplicate attendance records - -#### Concurrency Issues - -- [ ] **Multiple Staff Applying:** - - [ ] Race condition handled correctly - - [ ] No double-booking - - [ ] First-come-first-served logic works - -- [ ] **Shift Cancellation:** - - [ ] Staff notified appropriately - - [ ] Applications updated correctly - - [ ] No orphaned assignments - ---- - -### Release-Blocking Failures - -**The following issues MUST be fixed before release:** - -- [ ] **Authentication fails completely** (users cannot log in) -- [ ] **Order creation fails completely** (clients cannot create orders) -- [ ] **Shift application fails completely** (staff cannot apply for shifts) -- [ ] **Clock in/out fails completely** (staff cannot track attendance) -- [ ] **Payment data displays incorrectly** (financial inaccuracies) -- [ ] **Data loss occurs** (orders, shifts, or applications deleted unintentionally) -- [ ] **App crashes on launch** (unrecoverable error) -- [ ] **Backend connection fails** (cannot communicate with Data Connect) -- [ ] **Critical security vulnerability** (unauthorized access, data exposure) - ---- - -## 📊 TESTING METRICS & REPORTING - -### Test Execution Summary - -**Date:** __________ -**Tester:** __________ -**Build Version:** __________ - -| Category | Total Tests | Passed | Failed | Blocked | Pass Rate | -|----------|-------------|--------|--------|---------|-----------| -| Client Features | __ | __ | __ | __ | __% | -| Staff Features | __ | __ | __ | __ | __% | -| Cross-App Scenarios | __ | __ | __ | __ | __% | -| Infrastructure | __ | __ | __ | __ | __% | -| Smoke Tests | __ | __ | __ | __ | __% | -| **TOTAL** | **__** | **__** | **__** | **__** | **__%** | - ---- - -### Defect Severity Classification - -**Critical (P0):** Release-blocking, affects core functionality -**High (P1):** Major functionality broken, workaround exists -**Medium (P2):** Minor functionality affected, low impact -**Low (P3):** Cosmetic issue, no functional impact - ---- - -### Sign-Off Criteria - -**Release can proceed when:** -- [ ] All P0 defects resolved -- [ ] 95%+ pass rate on Critical Path tests -- [ ] 85%+ pass rate on all Feature tests -- [ ] No unresolved P1 defects in core features -- [ ] Cross-app scenarios pass 90%+ -- [ ] Backend integration stable (no frequent failures) -- [ ] QA lead approval obtained -- [ ] Product owner approval obtained - ---- - -## 📝 NOTES & CLARIFICATIONS NEEDED - -The following items require clarification before full QA execution: - -1. ⚠️ **Documents Feature (STAFF-015):** Real Data Connect integration status unclear. Currently using mock implementation. - -2. ⚠️ **Shift Cancellation:** Feature existence and behavior not confirmed in current implementation. - -3. ⚠️ **Race Condition Handling (Scenario 9):** Backend concurrency control mechanism needs documentation. - -4. ⚠️ **Payment Processing:** End-to-end payment flow from shift completion to payment disbursement not fully implemented. - -5. ⚠️ **NFC Tag Assignment:** Hub NFC functionality interface exists but implementation status unclear. - -6. ⚠️ **Recurring & Permanent Orders:** Placeholder screens exist but full workflow not implemented. - -7. ⚠️ **Reports Feature (Client):** Currently shows placeholder, implementation status unknown. - -8. ⚠️ **Notification System:** Push notifications for shift assignments, cancellations, and status updates not covered in current analysis. - ---- - -## 🎯 CONCLUSION - -This QA checklist provides comprehensive coverage of all implemented features across both Client and Staff applications. It is designed for manual testing by QA engineers and supports release sign-off decisions based on structured test execution and clear pass/fail criteria. - -**Key Strengths:** -- ✅ Feature-by-feature detailed test cases -- ✅ Cross-application integration scenarios -- ✅ Infrastructure and data consistency validation -- ✅ Clear release-blocking criteria -- ✅ Based on actual implemented code (not speculative) - -**Recommended Usage:** -1. Execute smoke tests before each build -2. Run full feature regression weekly -3. Execute cross-app scenarios before major releases -4. Validate infrastructure after backend schema updates -5. Use sign-off checklist for release go/no-go decisions - ---- - -**Document Maintainer:** KROW QA Team -**Last Updated:** February 1, 2026 -**Next Review:** Upon next major feature release