Merge branch 'dev' into feature/session-persistence-424
This commit is contained in:
61
.github/workflows/mobile-ci.yml
vendored
61
.github/workflows/mobile-ci.yml
vendored
@@ -29,10 +29,10 @@ jobs:
|
|||||||
id: detect
|
id: detect
|
||||||
run: |
|
run: |
|
||||||
if [[ "${{ github.event_name }}" == "pull_request" ]]; then
|
if [[ "${{ github.event_name }}" == "pull_request" ]]; then
|
||||||
# For PR, compare with base branch
|
# For PR, compare all changes against base branch (not just latest commit)
|
||||||
|
# Using three-dot syntax (...) shows all files changed in the PR branch
|
||||||
BASE_REF="${{ github.event.pull_request.base.ref }}"
|
BASE_REF="${{ github.event.pull_request.base.ref }}"
|
||||||
HEAD_REF="${{ github.event.pull_request.head.ref }}"
|
CHANGED_FILES=$(git diff --name-only origin/$BASE_REF...HEAD 2>/dev/null || echo "")
|
||||||
CHANGED_FILES=$(git diff --name-only origin/$BASE_REF..origin/$HEAD_REF 2>/dev/null || echo "")
|
|
||||||
else
|
else
|
||||||
# For push, compare with previous commit
|
# For push, compare with previous commit
|
||||||
if [[ "${{ github.event.before }}" == "0000000000000000000000000000000000000000" ]]; then
|
if [[ "${{ github.event.before }}" == "0000000000000000000000000000000000000000" ]]; then
|
||||||
@@ -74,39 +74,47 @@ jobs:
|
|||||||
- name: 🦋 Set up Flutter
|
- name: 🦋 Set up Flutter
|
||||||
uses: subosito/flutter-action@v2
|
uses: subosito/flutter-action@v2
|
||||||
with:
|
with:
|
||||||
flutter-version: '3.19.x'
|
flutter-version: '3.38.x'
|
||||||
channel: 'stable'
|
channel: 'stable'
|
||||||
cache: true
|
cache: true
|
||||||
|
|
||||||
|
- name: 🔧 Install Firebase CLI
|
||||||
|
run: |
|
||||||
|
npm install -g firebase-tools
|
||||||
|
|
||||||
- name: 📦 Get Flutter dependencies
|
- name: 📦 Get Flutter dependencies
|
||||||
run: |
|
run: |
|
||||||
cd apps/mobile
|
make mobile-install
|
||||||
flutter pub get
|
|
||||||
|
|
||||||
- name: 🔨 Run compilation check
|
- name: 🔨 Run compilation check
|
||||||
run: |
|
run: |
|
||||||
cd apps/mobile
|
set -o pipefail
|
||||||
echo "⚙️ Running build_runner..."
|
|
||||||
flutter pub run build_runner build --delete-conflicting-outputs 2>&1 || true
|
echo "🏗️ Building client app for Android (dev mode)..."
|
||||||
|
if ! make mobile-client-build PLATFORM=apk MODE=debug 2>&1 | tee client_build.txt; then
|
||||||
echo ""
|
echo ""
|
||||||
echo "🔬 Running flutter analyze on all files..."
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
flutter analyze lib/ --no-fatal-infos 2>&1 | tee analyze_output.txt || true
|
echo "❌ CLIENT APP BUILD FAILED"
|
||||||
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
|
|
||||||
# Check for actual errors (not just warnings)
|
echo "🏗️ Building staff app for Android (dev mode)..."
|
||||||
if grep -E "^\s*(error|SEVERE):" analyze_output.txt > /dev/null; then
|
if ! make mobile-staff-build PLATFORM=apk MODE=debug 2>&1 | tee staff_build.txt; then
|
||||||
echo "❌ COMPILATION ERRORS FOUND:"
|
echo ""
|
||||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
grep -B 2 -A 1 -E "^\s*(error|SEVERE):" analyze_output.txt | sed 's/^/ /'
|
echo "❌ STAFF APP BUILD FAILED"
|
||||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
exit 1
|
exit 1
|
||||||
else
|
|
||||||
echo "✅ Compilation check PASSED - No errors found"
|
|
||||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
|
echo "✅ Build check PASSED - Both apps built successfully"
|
||||||
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
|
|
||||||
lint:
|
lint:
|
||||||
name: 🧹 Lint Changed Files
|
name: 🧹 Lint Changed Files
|
||||||
@@ -120,18 +128,21 @@ jobs:
|
|||||||
- name: 🦋 Set up Flutter
|
- name: 🦋 Set up Flutter
|
||||||
uses: subosito/flutter-action@v2
|
uses: subosito/flutter-action@v2
|
||||||
with:
|
with:
|
||||||
flutter-version: '3.19.x'
|
flutter-version: '3.38.x'
|
||||||
channel: 'stable'
|
channel: 'stable'
|
||||||
cache: true
|
cache: true
|
||||||
|
|
||||||
|
- name: 🔧 Install Firebase CLI
|
||||||
|
run: |
|
||||||
|
npm install -g firebase-tools
|
||||||
|
|
||||||
- name: 📦 Get Flutter dependencies
|
- name: 📦 Get Flutter dependencies
|
||||||
run: |
|
run: |
|
||||||
cd apps/mobile
|
make mobile-install
|
||||||
flutter pub get
|
|
||||||
|
|
||||||
- name: 🔍 Lint changed Dart files
|
- name: 🔍 Lint changed Dart files
|
||||||
run: |
|
run: |
|
||||||
cd apps/mobile
|
set -o pipefail
|
||||||
|
|
||||||
# Get the list of changed files
|
# Get the list of changed files
|
||||||
CHANGED_FILES="${{ needs.detect-changes.outputs.changed-files }}"
|
CHANGED_FILES="${{ needs.detect-changes.outputs.changed-files }}"
|
||||||
@@ -153,7 +164,7 @@ jobs:
|
|||||||
if [[ -n "$file" && "$file" == *.dart && -f "$file" ]]; then
|
if [[ -n "$file" && "$file" == *.dart && -f "$file" ]]; then
|
||||||
echo "📝 Analyzing: $file"
|
echo "📝 Analyzing: $file"
|
||||||
|
|
||||||
if ! flutter analyze "$file" --no-fatal-infos 2>&1 | tee -a lint_output.txt; then
|
if ! dart analyze "$file" 2>&1 | tee -a lint_output.txt; then
|
||||||
HAS_ERRORS=true
|
HAS_ERRORS=true
|
||||||
FAILED_FILES+=("$file")
|
FAILED_FILES+=("$file")
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -98,7 +98,11 @@ extension StaffNavigator on IModularNavigator {
|
|||||||
/// Parameters:
|
/// Parameters:
|
||||||
/// * [selectedDate] - Optional date to pre-select in the shifts view
|
/// * [selectedDate] - Optional date to pre-select in the shifts view
|
||||||
/// * [initialTab] - Optional initial tab (via query parameter)
|
/// * [initialTab] - Optional initial tab (via query parameter)
|
||||||
void toShifts({DateTime? selectedDate, String? initialTab}) {
|
void toShifts({
|
||||||
|
DateTime? selectedDate,
|
||||||
|
String? initialTab,
|
||||||
|
bool? refreshAvailable,
|
||||||
|
}) {
|
||||||
final Map<String, dynamic> args = <String, dynamic>{};
|
final Map<String, dynamic> args = <String, dynamic>{};
|
||||||
if (selectedDate != null) {
|
if (selectedDate != null) {
|
||||||
args['selectedDate'] = selectedDate;
|
args['selectedDate'] = selectedDate;
|
||||||
@@ -106,6 +110,9 @@ extension StaffNavigator on IModularNavigator {
|
|||||||
if (initialTab != null) {
|
if (initialTab != null) {
|
||||||
args['initialTab'] = initialTab;
|
args['initialTab'] = initialTab;
|
||||||
}
|
}
|
||||||
|
if (refreshAvailable == true) {
|
||||||
|
args['refreshAvailable'] = true;
|
||||||
|
}
|
||||||
navigate(
|
navigate(
|
||||||
StaffPaths.shifts,
|
StaffPaths.shifts,
|
||||||
arguments: args.isEmpty ? null : args,
|
arguments: args.isEmpty ? null : args,
|
||||||
|
|||||||
@@ -2,6 +2,38 @@ import 'package:equatable/equatable.dart';
|
|||||||
import 'package:krow_domain/src/entities/shifts/break/break.dart';
|
import 'package:krow_domain/src/entities/shifts/break/break.dart';
|
||||||
|
|
||||||
class Shift extends Equatable {
|
class Shift extends Equatable {
|
||||||
|
final String id;
|
||||||
|
final String title;
|
||||||
|
final String clientName;
|
||||||
|
final String? logoUrl;
|
||||||
|
final double hourlyRate;
|
||||||
|
final String location;
|
||||||
|
final String locationAddress;
|
||||||
|
final String date;
|
||||||
|
final String startTime;
|
||||||
|
final String endTime;
|
||||||
|
final String createdDate;
|
||||||
|
final bool? tipsAvailable;
|
||||||
|
final bool? travelTime;
|
||||||
|
final bool? mealProvided;
|
||||||
|
final bool? parkingAvailable;
|
||||||
|
final bool? gasCompensation;
|
||||||
|
final String? description;
|
||||||
|
final String? instructions;
|
||||||
|
final List<ShiftManager>? managers;
|
||||||
|
final double? latitude;
|
||||||
|
final double? longitude;
|
||||||
|
final String? status;
|
||||||
|
final int? durationDays; // For multi-day shifts
|
||||||
|
final int? requiredSlots;
|
||||||
|
final int? filledSlots;
|
||||||
|
final String? roleId;
|
||||||
|
final bool? hasApplied;
|
||||||
|
final double? totalValue;
|
||||||
|
final Break? breakInfo;
|
||||||
|
final String? orderId;
|
||||||
|
final String? orderType;
|
||||||
|
final List<ShiftSchedule>? schedules;
|
||||||
|
|
||||||
const Shift({
|
const Shift({
|
||||||
required this.id,
|
required this.id,
|
||||||
@@ -33,6 +65,9 @@ class Shift extends Equatable {
|
|||||||
this.hasApplied,
|
this.hasApplied,
|
||||||
this.totalValue,
|
this.totalValue,
|
||||||
this.breakInfo,
|
this.breakInfo,
|
||||||
|
this.orderId,
|
||||||
|
this.orderType,
|
||||||
|
this.schedules,
|
||||||
});
|
});
|
||||||
final String id;
|
final String id;
|
||||||
final String title;
|
final String title;
|
||||||
@@ -95,9 +130,27 @@ class Shift extends Equatable {
|
|||||||
hasApplied,
|
hasApplied,
|
||||||
totalValue,
|
totalValue,
|
||||||
breakInfo,
|
breakInfo,
|
||||||
|
orderId,
|
||||||
|
orderType,
|
||||||
|
schedules,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class ShiftSchedule extends Equatable {
|
||||||
|
const ShiftSchedule({
|
||||||
|
required this.date,
|
||||||
|
required this.startTime,
|
||||||
|
required this.endTime,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String date;
|
||||||
|
final String startTime;
|
||||||
|
final String endTime;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => <Object?>[date, startTime, endTime];
|
||||||
|
}
|
||||||
|
|
||||||
class ShiftManager extends Equatable {
|
class ShiftManager extends Equatable {
|
||||||
const ShiftManager({required this.name, required this.phone, this.avatar});
|
const ShiftManager({required this.name, required this.phone, this.avatar});
|
||||||
|
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ class ClientCreateOrderRepositoryImpl implements ClientCreateOrderRepositoryInte
|
|||||||
.state(hub.state)
|
.state(hub.state)
|
||||||
.street(hub.street)
|
.street(hub.street)
|
||||||
.country(hub.country)
|
.country(hub.country)
|
||||||
.status(dc.ShiftStatus.CONFIRMED)
|
.status(dc.ShiftStatus.OPEN)
|
||||||
.workersNeeded(workersNeeded)
|
.workersNeeded(workersNeeded)
|
||||||
.filled(0)
|
.filled(0)
|
||||||
.durationDays(1)
|
.durationDays(1)
|
||||||
@@ -224,7 +224,7 @@ class ClientCreateOrderRepositoryImpl implements ClientCreateOrderRepositoryInte
|
|||||||
.state(hub.state)
|
.state(hub.state)
|
||||||
.street(hub.street)
|
.street(hub.street)
|
||||||
.country(hub.country)
|
.country(hub.country)
|
||||||
.status(dc.ShiftStatus.CONFIRMED)
|
.status(dc.ShiftStatus.OPEN)
|
||||||
.workersNeeded(workersNeeded)
|
.workersNeeded(workersNeeded)
|
||||||
.filled(0)
|
.filled(0)
|
||||||
.durationDays(1)
|
.durationDays(1)
|
||||||
@@ -342,7 +342,7 @@ class ClientCreateOrderRepositoryImpl implements ClientCreateOrderRepositoryInte
|
|||||||
.state(hub.state)
|
.state(hub.state)
|
||||||
.street(hub.street)
|
.street(hub.street)
|
||||||
.country(hub.country)
|
.country(hub.country)
|
||||||
.status(dc.ShiftStatus.CONFIRMED)
|
.status(dc.ShiftStatus.OPEN)
|
||||||
.workersNeeded(workersNeeded)
|
.workersNeeded(workersNeeded)
|
||||||
.filled(0)
|
.filled(0)
|
||||||
.durationDays(1)
|
.durationDays(1)
|
||||||
|
|||||||
@@ -20,6 +20,42 @@ class PermanentOrderView extends StatelessWidget {
|
|||||||
/// Creates a [PermanentOrderView].
|
/// Creates a [PermanentOrderView].
|
||||||
const PermanentOrderView({super.key});
|
const PermanentOrderView({super.key});
|
||||||
|
|
||||||
|
DateTime _firstPermanentShiftDate(
|
||||||
|
DateTime startDate,
|
||||||
|
List<String> permanentDays,
|
||||||
|
) {
|
||||||
|
final DateTime start = DateTime(startDate.year, startDate.month, startDate.day);
|
||||||
|
final DateTime end = start.add(const Duration(days: 29));
|
||||||
|
final Set<String> selected = permanentDays.toSet();
|
||||||
|
for (DateTime day = start; !day.isAfter(end); day = day.add(const Duration(days: 1))) {
|
||||||
|
if (selected.contains(_weekdayLabel(day))) {
|
||||||
|
return day;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return start;
|
||||||
|
}
|
||||||
|
|
||||||
|
String _weekdayLabel(DateTime date) {
|
||||||
|
switch (date.weekday) {
|
||||||
|
case DateTime.monday:
|
||||||
|
return 'MON';
|
||||||
|
case DateTime.tuesday:
|
||||||
|
return 'TUE';
|
||||||
|
case DateTime.wednesday:
|
||||||
|
return 'WED';
|
||||||
|
case DateTime.thursday:
|
||||||
|
return 'THU';
|
||||||
|
case DateTime.friday:
|
||||||
|
return 'FRI';
|
||||||
|
case DateTime.saturday:
|
||||||
|
return 'SAT';
|
||||||
|
case DateTime.sunday:
|
||||||
|
return 'SUN';
|
||||||
|
default:
|
||||||
|
return 'SUN';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final TranslationsClientCreateOrderPermanentEn labels =
|
final TranslationsClientCreateOrderPermanentEn labels =
|
||||||
@@ -42,6 +78,10 @@ class PermanentOrderView extends StatelessWidget {
|
|||||||
},
|
},
|
||||||
builder: (BuildContext context, PermanentOrderState state) {
|
builder: (BuildContext context, PermanentOrderState state) {
|
||||||
if (state.status == PermanentOrderStatus.success) {
|
if (state.status == PermanentOrderStatus.success) {
|
||||||
|
final DateTime initialDate = _firstPermanentShiftDate(
|
||||||
|
state.startDate,
|
||||||
|
state.permanentDays,
|
||||||
|
);
|
||||||
return PermanentOrderSuccessView(
|
return PermanentOrderSuccessView(
|
||||||
title: labels.title,
|
title: labels.title,
|
||||||
message: labels.subtitle,
|
message: labels.subtitle,
|
||||||
@@ -50,7 +90,7 @@ class PermanentOrderView extends StatelessWidget {
|
|||||||
ClientPaths.orders,
|
ClientPaths.orders,
|
||||||
(_) => false,
|
(_) => false,
|
||||||
arguments: <String, dynamic>{
|
arguments: <String, dynamic>{
|
||||||
'initialDate': state.startDate.toIso8601String(),
|
'initialDate': initialDate.toIso8601String(),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -20,6 +20,43 @@ class RecurringOrderView extends StatelessWidget {
|
|||||||
/// Creates a [RecurringOrderView].
|
/// Creates a [RecurringOrderView].
|
||||||
const RecurringOrderView({super.key});
|
const RecurringOrderView({super.key});
|
||||||
|
|
||||||
|
DateTime _firstRecurringShiftDate(
|
||||||
|
DateTime startDate,
|
||||||
|
DateTime endDate,
|
||||||
|
List<String> recurringDays,
|
||||||
|
) {
|
||||||
|
final DateTime start = DateTime(startDate.year, startDate.month, startDate.day);
|
||||||
|
final DateTime end = DateTime(endDate.year, endDate.month, endDate.day);
|
||||||
|
final Set<String> selected = recurringDays.toSet();
|
||||||
|
for (DateTime day = start; !day.isAfter(end); day = day.add(const Duration(days: 1))) {
|
||||||
|
if (selected.contains(_weekdayLabel(day))) {
|
||||||
|
return day;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return start;
|
||||||
|
}
|
||||||
|
|
||||||
|
String _weekdayLabel(DateTime date) {
|
||||||
|
switch (date.weekday) {
|
||||||
|
case DateTime.monday:
|
||||||
|
return 'MON';
|
||||||
|
case DateTime.tuesday:
|
||||||
|
return 'TUE';
|
||||||
|
case DateTime.wednesday:
|
||||||
|
return 'WED';
|
||||||
|
case DateTime.thursday:
|
||||||
|
return 'THU';
|
||||||
|
case DateTime.friday:
|
||||||
|
return 'FRI';
|
||||||
|
case DateTime.saturday:
|
||||||
|
return 'SAT';
|
||||||
|
case DateTime.sunday:
|
||||||
|
return 'SUN';
|
||||||
|
default:
|
||||||
|
return 'SUN';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final TranslationsClientCreateOrderRecurringEn labels =
|
final TranslationsClientCreateOrderRecurringEn labels =
|
||||||
@@ -44,6 +81,15 @@ class RecurringOrderView extends StatelessWidget {
|
|||||||
},
|
},
|
||||||
builder: (BuildContext context, RecurringOrderState state) {
|
builder: (BuildContext context, RecurringOrderState state) {
|
||||||
if (state.status == RecurringOrderStatus.success) {
|
if (state.status == RecurringOrderStatus.success) {
|
||||||
|
final DateTime maxEndDate =
|
||||||
|
state.startDate.add(const Duration(days: 29));
|
||||||
|
final DateTime effectiveEndDate =
|
||||||
|
state.endDate.isAfter(maxEndDate) ? maxEndDate : state.endDate;
|
||||||
|
final DateTime initialDate = _firstRecurringShiftDate(
|
||||||
|
state.startDate,
|
||||||
|
effectiveEndDate,
|
||||||
|
state.recurringDays,
|
||||||
|
);
|
||||||
return RecurringOrderSuccessView(
|
return RecurringOrderSuccessView(
|
||||||
title: labels.title,
|
title: labels.title,
|
||||||
message: labels.subtitle,
|
message: labels.subtitle,
|
||||||
@@ -52,7 +98,7 @@ class RecurringOrderView extends StatelessWidget {
|
|||||||
ClientPaths.orders,
|
ClientPaths.orders,
|
||||||
(_) => false,
|
(_) => false,
|
||||||
arguments: <String, dynamic>{
|
arguments: <String, dynamic>{
|
||||||
'initialDate': state.startDate.toIso8601String(),
|
'initialDate': initialDate.toIso8601String(),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -265,7 +265,7 @@ class _ShiftOrderFormSheetState extends State<ShiftOrderFormSheet> {
|
|||||||
.state(selectedHub.state)
|
.state(selectedHub.state)
|
||||||
.street(selectedHub.street)
|
.street(selectedHub.street)
|
||||||
.country(selectedHub.country)
|
.country(selectedHub.country)
|
||||||
.status(dc.ShiftStatus.PENDING)
|
.status(dc.ShiftStatus.OPEN)
|
||||||
.workersNeeded(workersNeeded)
|
.workersNeeded(workersNeeded)
|
||||||
.filled(0)
|
.filled(0)
|
||||||
.durationDays(1)
|
.durationDays(1)
|
||||||
|
|||||||
@@ -46,6 +46,145 @@ class ShiftsRepositoryImpl implements ShiftsRepositoryInterface {
|
|||||||
Future<List<Shift>> getHistoryShifts() async {
|
Future<List<Shift>> getHistoryShifts() async {
|
||||||
final staffId = await _service.getStaffId();
|
final staffId = await _service.getStaffId();
|
||||||
return _connectorRepository.getHistoryShifts(staffId: staffId);
|
return _connectorRepository.getHistoryShifts(staffId: staffId);
|
||||||
|
final fdc.QueryResult<dc.ListCompletedApplicationsByStaffIdData, dc.ListCompletedApplicationsByStaffIdVariables> response = await _service.executeProtected(() => _service.connector
|
||||||
|
.listCompletedApplicationsByStaffId(staffId: staffId)
|
||||||
|
.execute());
|
||||||
|
final List<Shift> shifts = [];
|
||||||
|
|
||||||
|
for (final app in response.data.applications) {
|
||||||
|
_shiftToAppIdMap[app.shift.id] = app.id;
|
||||||
|
_appToRoleIdMap[app.id] = app.shiftRole.id;
|
||||||
|
|
||||||
|
final String roleName = app.shiftRole.role.name;
|
||||||
|
final String orderName =
|
||||||
|
(app.shift.order.eventName ?? '').trim().isNotEmpty
|
||||||
|
? app.shift.order.eventName!
|
||||||
|
: app.shift.order.business.businessName;
|
||||||
|
final String title = '$roleName - $orderName';
|
||||||
|
final DateTime? shiftDate = _service.toDateTime(app.shift.date);
|
||||||
|
final DateTime? startDt = _service.toDateTime(app.shiftRole.startTime);
|
||||||
|
final DateTime? endDt = _service.toDateTime(app.shiftRole.endTime);
|
||||||
|
final DateTime? createdDt = _service.toDateTime(app.createdAt);
|
||||||
|
|
||||||
|
shifts.add(
|
||||||
|
Shift(
|
||||||
|
id: app.shift.id,
|
||||||
|
roleId: app.shiftRole.roleId,
|
||||||
|
title: title,
|
||||||
|
clientName: app.shift.order.business.businessName,
|
||||||
|
logoUrl: app.shift.order.business.companyLogoUrl,
|
||||||
|
hourlyRate: app.shiftRole.role.costPerHour,
|
||||||
|
location: app.shift.location ?? '',
|
||||||
|
locationAddress: app.shift.order.teamHub.hubName,
|
||||||
|
date: shiftDate?.toIso8601String() ?? '',
|
||||||
|
startTime: startDt != null ? DateFormat('HH:mm').format(startDt) : '',
|
||||||
|
endTime: endDt != null ? DateFormat('HH:mm').format(endDt) : '',
|
||||||
|
createdDate: createdDt?.toIso8601String() ?? '',
|
||||||
|
status: _mapStatus(dc.ApplicationStatus.CHECKED_OUT),
|
||||||
|
description: app.shift.description,
|
||||||
|
durationDays: app.shift.durationDays,
|
||||||
|
requiredSlots: app.shiftRole.count,
|
||||||
|
filledSlots: app.shiftRole.assigned ?? 0,
|
||||||
|
hasApplied: true,
|
||||||
|
latitude: app.shift.latitude,
|
||||||
|
longitude: app.shift.longitude,
|
||||||
|
breakInfo: BreakAdapter.fromData(
|
||||||
|
isPaid: app.shiftRole.isBreakPaid ?? false,
|
||||||
|
breakTime: app.shiftRole.breakType?.stringValue,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return shifts;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<Shift>> _fetchApplications({
|
||||||
|
DateTime? start,
|
||||||
|
DateTime? end,
|
||||||
|
}) async {
|
||||||
|
final staffId = await _service.getStaffId();
|
||||||
|
var query = _service.connector.getMyApplicationsByStaffId(staffId: staffId);
|
||||||
|
if (start != null && end != null) {
|
||||||
|
query = query.dayStart(_service.toTimestamp(start)).dayEnd(_service.toTimestamp(end));
|
||||||
|
}
|
||||||
|
final fdc.QueryResult<dc.GetMyApplicationsByStaffIdData, dc.GetMyApplicationsByStaffIdVariables> response =
|
||||||
|
await _service.executeProtected(() => query.execute());
|
||||||
|
|
||||||
|
final apps = response.data.applications;
|
||||||
|
final List<Shift> shifts = [];
|
||||||
|
|
||||||
|
for (final app in apps) {
|
||||||
|
_shiftToAppIdMap[app.shift.id] = app.id;
|
||||||
|
_appToRoleIdMap[app.id] = app.shiftRole.id;
|
||||||
|
|
||||||
|
final String roleName = app.shiftRole.role.name;
|
||||||
|
final String orderName =
|
||||||
|
(app.shift.order.eventName ?? '').trim().isNotEmpty
|
||||||
|
? app.shift.order.eventName!
|
||||||
|
: app.shift.order.business.businessName;
|
||||||
|
final String title = '$roleName - $orderName';
|
||||||
|
final DateTime? shiftDate = _service.toDateTime(app.shift.date);
|
||||||
|
final DateTime? startDt = _service.toDateTime(app.shiftRole.startTime);
|
||||||
|
final DateTime? endDt = _service.toDateTime(app.shiftRole.endTime);
|
||||||
|
final DateTime? createdDt = _service.toDateTime(app.createdAt);
|
||||||
|
|
||||||
|
// Override status to reflect the application state (e.g., CHECKED_OUT, CONFIRMED)
|
||||||
|
final bool hasCheckIn = app.checkInTime != null;
|
||||||
|
final bool hasCheckOut = app.checkOutTime != null;
|
||||||
|
dc.ApplicationStatus? appStatus;
|
||||||
|
if (app.status is dc.Known<dc.ApplicationStatus>) {
|
||||||
|
appStatus = (app.status as dc.Known<dc.ApplicationStatus>).value;
|
||||||
|
}
|
||||||
|
final String mappedStatus = hasCheckOut
|
||||||
|
? 'completed'
|
||||||
|
: hasCheckIn
|
||||||
|
? 'checked_in'
|
||||||
|
: _mapStatus(appStatus ?? dc.ApplicationStatus.CONFIRMED);
|
||||||
|
shifts.add(
|
||||||
|
Shift(
|
||||||
|
id: app.shift.id,
|
||||||
|
roleId: app.shiftRole.roleId,
|
||||||
|
title: title,
|
||||||
|
clientName: app.shift.order.business.businessName,
|
||||||
|
logoUrl: app.shift.order.business.companyLogoUrl,
|
||||||
|
hourlyRate: app.shiftRole.role.costPerHour,
|
||||||
|
location: app.shift.location ?? '',
|
||||||
|
locationAddress: app.shift.order.teamHub.hubName,
|
||||||
|
date: shiftDate?.toIso8601String() ?? '',
|
||||||
|
startTime: startDt != null ? DateFormat('HH:mm').format(startDt) : '',
|
||||||
|
endTime: endDt != null ? DateFormat('HH:mm').format(endDt) : '',
|
||||||
|
createdDate: createdDt?.toIso8601String() ?? '',
|
||||||
|
status: mappedStatus,
|
||||||
|
description: app.shift.description,
|
||||||
|
durationDays: app.shift.durationDays,
|
||||||
|
requiredSlots: app.shiftRole.count,
|
||||||
|
filledSlots: app.shiftRole.assigned ?? 0,
|
||||||
|
hasApplied: true,
|
||||||
|
latitude: app.shift.latitude,
|
||||||
|
longitude: app.shift.longitude,
|
||||||
|
breakInfo: BreakAdapter.fromData(
|
||||||
|
isPaid: app.shiftRole.isBreakPaid ?? false,
|
||||||
|
breakTime: app.shiftRole.breakType?.stringValue,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return shifts;
|
||||||
|
}
|
||||||
|
|
||||||
|
String _mapStatus(dc.ApplicationStatus status) {
|
||||||
|
switch (status) {
|
||||||
|
case dc.ApplicationStatus.CONFIRMED:
|
||||||
|
return 'confirmed';
|
||||||
|
case dc.ApplicationStatus.PENDING:
|
||||||
|
return 'pending';
|
||||||
|
case dc.ApplicationStatus.CHECKED_OUT:
|
||||||
|
return 'completed';
|
||||||
|
case dc.ApplicationStatus.REJECTED:
|
||||||
|
return 'cancelled';
|
||||||
|
default:
|
||||||
|
return 'open';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -56,6 +195,76 @@ class ShiftsRepositoryImpl implements ShiftsRepositoryInterface {
|
|||||||
query: query,
|
query: query,
|
||||||
type: type,
|
type: type,
|
||||||
);
|
);
|
||||||
|
final String? vendorId = dc.StaffSessionStore.instance.session?.ownerId;
|
||||||
|
if (vendorId == null || vendorId.isEmpty) {
|
||||||
|
return <Shift>[];
|
||||||
|
}
|
||||||
|
|
||||||
|
final fdc.QueryResult<dc.ListShiftRolesByVendorIdData, dc.ListShiftRolesByVendorIdVariables> result = await _service.executeProtected(() => _service.connector
|
||||||
|
.listShiftRolesByVendorId(vendorId: vendorId)
|
||||||
|
.execute());
|
||||||
|
|
||||||
|
final allShiftRoles = result.data.shiftRoles;
|
||||||
|
|
||||||
|
// Fetch my applications to filter out already booked shifts
|
||||||
|
final List<Shift> myShifts = await _fetchApplications();
|
||||||
|
final Set<String> myShiftIds = myShifts.map((s) => s.id).toSet();
|
||||||
|
|
||||||
|
final List<Shift> mappedShifts = [];
|
||||||
|
for (final sr in allShiftRoles) {
|
||||||
|
// Skip if I have already applied/booked this shift
|
||||||
|
if (myShiftIds.contains(sr.shiftId)) continue;
|
||||||
|
|
||||||
|
|
||||||
|
final DateTime? shiftDate = _service.toDateTime(sr.shift.date);
|
||||||
|
final startDt = _service.toDateTime(sr.startTime);
|
||||||
|
final endDt = _service.toDateTime(sr.endTime);
|
||||||
|
final createdDt = _service.toDateTime(sr.createdAt);
|
||||||
|
|
||||||
|
mappedShifts.add(
|
||||||
|
Shift(
|
||||||
|
id: sr.shiftId,
|
||||||
|
roleId: sr.roleId,
|
||||||
|
title: sr.role.name,
|
||||||
|
clientName: sr.shift.order.business.businessName,
|
||||||
|
logoUrl: null,
|
||||||
|
hourlyRate: sr.role.costPerHour,
|
||||||
|
location: sr.shift.location ?? '',
|
||||||
|
locationAddress: sr.shift.locationAddress ?? '',
|
||||||
|
date: shiftDate?.toIso8601String() ?? '',
|
||||||
|
startTime: startDt != null
|
||||||
|
? DateFormat('HH:mm').format(startDt)
|
||||||
|
: '',
|
||||||
|
endTime: endDt != null ? DateFormat('HH:mm').format(endDt) : '',
|
||||||
|
createdDate: createdDt?.toIso8601String() ?? '',
|
||||||
|
status: sr.shift.status?.stringValue.toLowerCase() ?? 'open',
|
||||||
|
description: sr.shift.description,
|
||||||
|
durationDays: sr.shift.durationDays,
|
||||||
|
requiredSlots: sr.count,
|
||||||
|
filledSlots: sr.assigned ?? 0,
|
||||||
|
latitude: sr.shift.latitude,
|
||||||
|
longitude: sr.shift.longitude,
|
||||||
|
orderId: sr.shift.order.id,
|
||||||
|
orderType: sr.shift.order.orderType?.stringValue,
|
||||||
|
breakInfo: BreakAdapter.fromData(
|
||||||
|
isPaid: sr.isBreakPaid ?? false,
|
||||||
|
breakTime: sr.breakType?.stringValue,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (query.isNotEmpty) {
|
||||||
|
return mappedShifts
|
||||||
|
.where(
|
||||||
|
(s) =>
|
||||||
|
s.title.toLowerCase().contains(query.toLowerCase()) ||
|
||||||
|
s.clientName.toLowerCase().contains(query.toLowerCase()),
|
||||||
|
)
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
return mappedShifts;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -112,9 +112,15 @@ class ShiftsBloc extends Bloc<ShiftsEvent, ShiftsState>
|
|||||||
) async {
|
) async {
|
||||||
final currentState = state;
|
final currentState = state;
|
||||||
if (currentState is! ShiftsLoaded) return;
|
if (currentState is! ShiftsLoaded) return;
|
||||||
if (currentState.availableLoading || currentState.availableLoaded) return;
|
if (!event.force &&
|
||||||
|
(currentState.availableLoading || currentState.availableLoaded)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
emit(currentState.copyWith(availableLoading: true));
|
emit(currentState.copyWith(
|
||||||
|
availableLoading: true,
|
||||||
|
availableLoaded: false,
|
||||||
|
));
|
||||||
await handleError(
|
await handleError(
|
||||||
emit: emit.call,
|
emit: emit.call,
|
||||||
action: () async {
|
action: () async {
|
||||||
|
|||||||
@@ -12,7 +12,13 @@ class LoadShiftsEvent extends ShiftsEvent {}
|
|||||||
|
|
||||||
class LoadHistoryShiftsEvent extends ShiftsEvent {}
|
class LoadHistoryShiftsEvent extends ShiftsEvent {}
|
||||||
|
|
||||||
class LoadAvailableShiftsEvent extends ShiftsEvent {}
|
class LoadAvailableShiftsEvent extends ShiftsEvent {
|
||||||
|
final bool force;
|
||||||
|
const LoadAvailableShiftsEvent({this.force = false});
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [force];
|
||||||
|
}
|
||||||
|
|
||||||
class LoadFindFirstEvent extends ShiftsEvent {}
|
class LoadFindFirstEvent extends ShiftsEvent {}
|
||||||
|
|
||||||
|
|||||||
@@ -93,7 +93,11 @@ class _ShiftDetailsPageState extends State<ShiftDetailsPage> {
|
|||||||
message: state.message,
|
message: state.message,
|
||||||
type: UiSnackbarType.success,
|
type: UiSnackbarType.success,
|
||||||
);
|
);
|
||||||
Modular.to.toShifts(selectedDate: state.shiftDate);
|
Modular.to.toShifts(
|
||||||
|
selectedDate: state.shiftDate,
|
||||||
|
initialTab: 'find',
|
||||||
|
refreshAvailable: true,
|
||||||
|
);
|
||||||
} else if (state is ShiftDetailsError) {
|
} else if (state is ShiftDetailsError) {
|
||||||
if (_isApplying) {
|
if (_isApplying) {
|
||||||
UiSnackbar.show(
|
UiSnackbar.show(
|
||||||
@@ -112,7 +116,8 @@ class _ShiftDetailsPageState extends State<ShiftDetailsPage> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Shift displayShift = widget.shift;
|
final Shift displayShift =
|
||||||
|
state is ShiftDetailsLoaded ? state.shift : widget.shift;
|
||||||
final i18n = Translations.of(context).staff_shifts.shift_details;
|
final i18n = Translations.of(context).staff_shifts.shift_details;
|
||||||
|
|
||||||
final duration = _calculateDuration(displayShift);
|
final duration = _calculateDuration(displayShift);
|
||||||
|
|||||||
@@ -12,7 +12,13 @@ import '../widgets/tabs/history_shifts_tab.dart';
|
|||||||
class ShiftsPage extends StatefulWidget {
|
class ShiftsPage extends StatefulWidget {
|
||||||
final String? initialTab;
|
final String? initialTab;
|
||||||
final DateTime? selectedDate;
|
final DateTime? selectedDate;
|
||||||
const ShiftsPage({super.key, this.initialTab, this.selectedDate});
|
final bool refreshAvailable;
|
||||||
|
const ShiftsPage({
|
||||||
|
super.key,
|
||||||
|
this.initialTab,
|
||||||
|
this.selectedDate,
|
||||||
|
this.refreshAvailable = false,
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<ShiftsPage> createState() => _ShiftsPageState();
|
State<ShiftsPage> createState() => _ShiftsPageState();
|
||||||
@@ -22,6 +28,8 @@ class _ShiftsPageState extends State<ShiftsPage> {
|
|||||||
late String _activeTab;
|
late String _activeTab;
|
||||||
DateTime? _selectedDate;
|
DateTime? _selectedDate;
|
||||||
bool _prioritizeFind = false;
|
bool _prioritizeFind = false;
|
||||||
|
bool _refreshAvailable = false;
|
||||||
|
bool _pendingAvailableRefresh = false;
|
||||||
final ShiftsBloc _bloc = Modular.get<ShiftsBloc>();
|
final ShiftsBloc _bloc = Modular.get<ShiftsBloc>();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -30,6 +38,8 @@ class _ShiftsPageState extends State<ShiftsPage> {
|
|||||||
_activeTab = widget.initialTab ?? 'myshifts';
|
_activeTab = widget.initialTab ?? 'myshifts';
|
||||||
_selectedDate = widget.selectedDate;
|
_selectedDate = widget.selectedDate;
|
||||||
_prioritizeFind = widget.initialTab == 'find';
|
_prioritizeFind = widget.initialTab == 'find';
|
||||||
|
_refreshAvailable = widget.refreshAvailable;
|
||||||
|
_pendingAvailableRefresh = widget.refreshAvailable;
|
||||||
if (_prioritizeFind) {
|
if (_prioritizeFind) {
|
||||||
_bloc.add(LoadFindFirstEvent());
|
_bloc.add(LoadFindFirstEvent());
|
||||||
} else {
|
} else {
|
||||||
@@ -40,7 +50,9 @@ class _ShiftsPageState extends State<ShiftsPage> {
|
|||||||
}
|
}
|
||||||
if (_activeTab == 'find') {
|
if (_activeTab == 'find') {
|
||||||
if (!_prioritizeFind) {
|
if (!_prioritizeFind) {
|
||||||
_bloc.add(LoadAvailableShiftsEvent());
|
_bloc.add(
|
||||||
|
LoadAvailableShiftsEvent(force: _refreshAvailable),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Check profile completion
|
// Check profile completion
|
||||||
@@ -61,6 +73,10 @@ class _ShiftsPageState extends State<ShiftsPage> {
|
|||||||
_selectedDate = widget.selectedDate;
|
_selectedDate = widget.selectedDate;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
if (widget.refreshAvailable) {
|
||||||
|
_refreshAvailable = true;
|
||||||
|
_pendingAvailableRefresh = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -79,6 +95,10 @@ class _ShiftsPageState extends State<ShiftsPage> {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
|
if (_pendingAvailableRefresh && state is ShiftsLoaded) {
|
||||||
|
_pendingAvailableRefresh = false;
|
||||||
|
_bloc.add(const LoadAvailableShiftsEvent(force: true));
|
||||||
|
}
|
||||||
final bool baseLoaded = state is ShiftsLoaded;
|
final bool baseLoaded = state is ShiftsLoaded;
|
||||||
final List<Shift> myShifts = (state is ShiftsLoaded)
|
final List<Shift> myShifts = (state is ShiftsLoaded)
|
||||||
? state.myShifts
|
? state.myShifts
|
||||||
|
|||||||
@@ -77,6 +77,13 @@ class _MyShiftCardState extends State<MyShiftCard> {
|
|||||||
String _getShiftType() {
|
String _getShiftType() {
|
||||||
// Handling potential localization key availability
|
// Handling potential localization key availability
|
||||||
try {
|
try {
|
||||||
|
final String orderType = (widget.shift.orderType ?? '').toUpperCase();
|
||||||
|
if (orderType == 'PERMANENT') {
|
||||||
|
return t.staff_shifts.filter.long_term;
|
||||||
|
}
|
||||||
|
if (orderType == 'RECURRING') {
|
||||||
|
return t.staff_shifts.filter.multi_day;
|
||||||
|
}
|
||||||
if (widget.shift.durationDays != null && widget.shift.durationDays! > 30) {
|
if (widget.shift.durationDays != null && widget.shift.durationDays! > 30) {
|
||||||
return t.staff_shifts.filter.long_term;
|
return t.staff_shifts.filter.long_term;
|
||||||
}
|
}
|
||||||
@@ -133,6 +140,24 @@ class _MyShiftCardState extends State<MyShiftCard> {
|
|||||||
statusText = status?.toUpperCase() ?? "";
|
statusText = status?.toUpperCase() ?? "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final schedules = widget.shift.schedules ?? <ShiftSchedule>[];
|
||||||
|
final hasSchedules = schedules.isNotEmpty;
|
||||||
|
final List<ShiftSchedule> visibleSchedules = schedules.length <= 5
|
||||||
|
? schedules
|
||||||
|
: schedules.take(3).toList();
|
||||||
|
final int remainingSchedules =
|
||||||
|
schedules.length <= 5 ? 0 : schedules.length - 3;
|
||||||
|
final String scheduleRange = hasSchedules
|
||||||
|
? () {
|
||||||
|
final first = schedules.first.date;
|
||||||
|
final last = schedules.last.date;
|
||||||
|
if (first == last) {
|
||||||
|
return _formatDate(first);
|
||||||
|
}
|
||||||
|
return '${_formatDate(first)} – ${_formatDate(last)}';
|
||||||
|
}()
|
||||||
|
: '';
|
||||||
|
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Modular.to.pushNamed(
|
Modular.to.pushNamed(
|
||||||
@@ -191,8 +216,8 @@ class _MyShiftCardState extends State<MyShiftCard> {
|
|||||||
letterSpacing: 0.5,
|
letterSpacing: 0.5,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
// Shift Type Badge
|
// Shift Type Badge (Order type)
|
||||||
if (status == 'open' || status == 'pending') ...[
|
if ((widget.shift.orderType ?? '').isNotEmpty) ...[
|
||||||
const SizedBox(width: UiConstants.space2),
|
const SizedBox(width: UiConstants.space2),
|
||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(
|
||||||
@@ -200,13 +225,14 @@ class _MyShiftCardState extends State<MyShiftCard> {
|
|||||||
vertical: 2,
|
vertical: 2,
|
||||||
),
|
),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: UiColors.primary.withValues(alpha: 0.1),
|
color: UiColors.background,
|
||||||
borderRadius: UiConstants.radiusSm,
|
borderRadius: UiConstants.radiusSm,
|
||||||
|
border: Border.all(color: UiColors.border),
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
_getShiftType(),
|
_getShiftType(),
|
||||||
style: UiTypography.footnote2m.copyWith(
|
style: UiTypography.footnote2m.copyWith(
|
||||||
color: UiColors.primary,
|
color: UiColors.textSecondary,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -299,7 +325,55 @@ class _MyShiftCardState extends State<MyShiftCard> {
|
|||||||
const SizedBox(height: UiConstants.space2),
|
const SizedBox(height: UiConstants.space2),
|
||||||
|
|
||||||
// Date & Time
|
// Date & Time
|
||||||
if (widget.shift.durationDays != null &&
|
if (hasSchedules) ...[
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
const Icon(
|
||||||
|
UiIcons.clock,
|
||||||
|
size: UiConstants.iconXs,
|
||||||
|
color: UiColors.primary,
|
||||||
|
),
|
||||||
|
const SizedBox(width: UiConstants.space1),
|
||||||
|
Text(
|
||||||
|
'${schedules.length} schedules',
|
||||||
|
style: UiTypography.footnote2m.copyWith(
|
||||||
|
color: UiColors.primary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: UiConstants.space1),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(bottom: 2),
|
||||||
|
child: Text(
|
||||||
|
scheduleRange,
|
||||||
|
style: UiTypography.footnote2r.copyWith(color: UiColors.primary),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
...visibleSchedules.map(
|
||||||
|
(schedule) => Padding(
|
||||||
|
padding: const EdgeInsets.only(bottom: 2),
|
||||||
|
child: Text(
|
||||||
|
'${_formatDate(schedule.date)}, ${_formatTime(schedule.startTime)} – ${_formatTime(schedule.endTime)}',
|
||||||
|
style: UiTypography.footnote2r.copyWith(
|
||||||
|
color: UiColors.primary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (remainingSchedules > 0)
|
||||||
|
Text(
|
||||||
|
'+$remainingSchedules more schedules',
|
||||||
|
style: UiTypography.footnote2r.copyWith(
|
||||||
|
color: UiColors.primary.withOpacity(0.7),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
] else if (widget.shift.durationDays != null &&
|
||||||
widget.shift.durationDays! > 1) ...[
|
widget.shift.durationDays! > 1) ...[
|
||||||
Column(
|
Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
@@ -324,17 +398,22 @@ class _MyShiftCardState extends State<MyShiftCard> {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: UiConstants.space1),
|
const SizedBox(height: UiConstants.space1),
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(bottom: 2),
|
padding: const EdgeInsets.only(bottom: 2),
|
||||||
child: Text(
|
child: Text(
|
||||||
'${_formatDate(widget.shift.date)}, ${_formatTime(widget.shift.startTime)} – ${_formatTime(widget.shift.endTime)}',
|
'${_formatDate(widget.shift.date)}, ${_formatTime(widget.shift.startTime)} – ${_formatTime(widget.shift.endTime)}',
|
||||||
style: UiTypography.footnote2r.copyWith(color: UiColors.primary),
|
style: UiTypography.footnote2r.copyWith(
|
||||||
|
color: UiColors.primary,
|
||||||
),
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
if (widget.shift.durationDays! > 1)
|
if (widget.shift.durationDays! > 1)
|
||||||
Text(
|
Text(
|
||||||
'... +${widget.shift.durationDays! - 1} more days',
|
'... +${widget.shift.durationDays! - 1} more days',
|
||||||
style: UiTypography.footnote2r.copyWith(color: UiColors.primary.withOpacity(0.7)),
|
style: UiTypography.footnote2r.copyWith(
|
||||||
)
|
color:
|
||||||
|
UiColors.primary.withOpacity(0.7),
|
||||||
|
),
|
||||||
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
] else ...[
|
] else ...[
|
||||||
|
|||||||
@@ -20,6 +20,119 @@ class _FindShiftsTabState extends State<FindShiftsTab> {
|
|||||||
String _searchQuery = '';
|
String _searchQuery = '';
|
||||||
String _jobType = 'all';
|
String _jobType = 'all';
|
||||||
|
|
||||||
|
bool _isRecurring(Shift shift) =>
|
||||||
|
(shift.orderType ?? '').toUpperCase() == 'RECURRING';
|
||||||
|
|
||||||
|
bool _isPermanent(Shift shift) =>
|
||||||
|
(shift.orderType ?? '').toUpperCase() == 'PERMANENT';
|
||||||
|
|
||||||
|
DateTime? _parseShiftDate(String date) {
|
||||||
|
if (date.isEmpty) return null;
|
||||||
|
try {
|
||||||
|
return DateTime.parse(date);
|
||||||
|
} catch (_) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Shift> _groupMultiDayShifts(List<Shift> shifts) {
|
||||||
|
final Map<String, List<Shift>> grouped = <String, List<Shift>>{};
|
||||||
|
for (final shift in shifts) {
|
||||||
|
if (!_isRecurring(shift) && !_isPermanent(shift)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
final orderId = shift.orderId;
|
||||||
|
final roleId = shift.roleId;
|
||||||
|
if (orderId == null || roleId == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
final key = '$orderId::$roleId';
|
||||||
|
grouped.putIfAbsent(key, () => <Shift>[]).add(shift);
|
||||||
|
}
|
||||||
|
|
||||||
|
final Set<String> addedGroups = <String>{};
|
||||||
|
final List<Shift> result = <Shift>[];
|
||||||
|
|
||||||
|
for (final shift in shifts) {
|
||||||
|
if (!_isRecurring(shift) && !_isPermanent(shift)) {
|
||||||
|
result.add(shift);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
final orderId = shift.orderId;
|
||||||
|
final roleId = shift.roleId;
|
||||||
|
if (orderId == null || roleId == null) {
|
||||||
|
result.add(shift);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
final key = '$orderId::$roleId';
|
||||||
|
if (addedGroups.contains(key)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
addedGroups.add(key);
|
||||||
|
final List<Shift> group = grouped[key] ?? <Shift>[];
|
||||||
|
if (group.isEmpty) {
|
||||||
|
result.add(shift);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
group.sort((a, b) {
|
||||||
|
final ad = _parseShiftDate(a.date);
|
||||||
|
final bd = _parseShiftDate(b.date);
|
||||||
|
if (ad == null && bd == null) return 0;
|
||||||
|
if (ad == null) return 1;
|
||||||
|
if (bd == null) return -1;
|
||||||
|
return ad.compareTo(bd);
|
||||||
|
});
|
||||||
|
|
||||||
|
final Shift first = group.first;
|
||||||
|
final List<ShiftSchedule> schedules = group
|
||||||
|
.map((s) => ShiftSchedule(
|
||||||
|
date: s.date,
|
||||||
|
startTime: s.startTime,
|
||||||
|
endTime: s.endTime,
|
||||||
|
))
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
result.add(
|
||||||
|
Shift(
|
||||||
|
id: first.id,
|
||||||
|
roleId: first.roleId,
|
||||||
|
title: first.title,
|
||||||
|
clientName: first.clientName,
|
||||||
|
logoUrl: first.logoUrl,
|
||||||
|
hourlyRate: first.hourlyRate,
|
||||||
|
location: first.location,
|
||||||
|
locationAddress: first.locationAddress,
|
||||||
|
date: first.date,
|
||||||
|
startTime: first.startTime,
|
||||||
|
endTime: first.endTime,
|
||||||
|
createdDate: first.createdDate,
|
||||||
|
tipsAvailable: first.tipsAvailable,
|
||||||
|
travelTime: first.travelTime,
|
||||||
|
mealProvided: first.mealProvided,
|
||||||
|
parkingAvailable: first.parkingAvailable,
|
||||||
|
gasCompensation: first.gasCompensation,
|
||||||
|
description: first.description,
|
||||||
|
instructions: first.instructions,
|
||||||
|
managers: first.managers,
|
||||||
|
latitude: first.latitude,
|
||||||
|
longitude: first.longitude,
|
||||||
|
status: first.status,
|
||||||
|
durationDays: schedules.length,
|
||||||
|
requiredSlots: first.requiredSlots,
|
||||||
|
filledSlots: first.filledSlots,
|
||||||
|
hasApplied: first.hasApplied,
|
||||||
|
totalValue: first.totalValue,
|
||||||
|
breakInfo: first.breakInfo,
|
||||||
|
orderId: first.orderId,
|
||||||
|
orderType: first.orderType,
|
||||||
|
schedules: schedules,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
Widget _buildFilterTab(String id, String label) {
|
Widget _buildFilterTab(String id, String label) {
|
||||||
final isSelected = _jobType == id;
|
final isSelected = _jobType == id;
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
@@ -49,8 +162,10 @@ class _FindShiftsTabState extends State<FindShiftsTab> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final groupedJobs = _groupMultiDayShifts(widget.availableJobs);
|
||||||
|
|
||||||
// Filter logic
|
// Filter logic
|
||||||
final filteredJobs = widget.availableJobs.where((s) {
|
final filteredJobs = groupedJobs.where((s) {
|
||||||
final matchesSearch =
|
final matchesSearch =
|
||||||
s.title.toLowerCase().contains(_searchQuery.toLowerCase()) ||
|
s.title.toLowerCase().contains(_searchQuery.toLowerCase()) ||
|
||||||
s.location.toLowerCase().contains(_searchQuery.toLowerCase()) ||
|
s.location.toLowerCase().contains(_searchQuery.toLowerCase()) ||
|
||||||
@@ -60,10 +175,15 @@ class _FindShiftsTabState extends State<FindShiftsTab> {
|
|||||||
|
|
||||||
if (_jobType == 'all') return true;
|
if (_jobType == 'all') return true;
|
||||||
if (_jobType == 'one-day') {
|
if (_jobType == 'one-day') {
|
||||||
|
if (_isRecurring(s) || _isPermanent(s)) return false;
|
||||||
return s.durationDays == null || s.durationDays! <= 1;
|
return s.durationDays == null || s.durationDays! <= 1;
|
||||||
}
|
}
|
||||||
if (_jobType == 'multi-day') {
|
if (_jobType == 'multi-day') {
|
||||||
return s.durationDays != null && s.durationDays! > 1;
|
return _isRecurring(s) ||
|
||||||
|
(s.durationDays != null && s.durationDays! > 1);
|
||||||
|
}
|
||||||
|
if (_jobType == 'long-term') {
|
||||||
|
return _isPermanent(s);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}).toList();
|
}).toList();
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ class StaffShiftsModule extends Module {
|
|||||||
return ShiftsPage(
|
return ShiftsPage(
|
||||||
initialTab: queryParams['tab'] ?? args?['initialTab'],
|
initialTab: queryParams['tab'] ?? args?['initialTab'],
|
||||||
selectedDate: args?['selectedDate'],
|
selectedDate: args?['selectedDate'],
|
||||||
|
refreshAvailable: args?['refreshAvailable'] == true,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1530,4 +1530,4 @@ packages:
|
|||||||
version: "2.2.3"
|
version: "2.2.3"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=3.10.3 <4.0.0"
|
dart: ">=3.10.3 <4.0.0"
|
||||||
flutter: ">=3.38.4"
|
flutter: ">=3.38.4 <4.0.0"
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ publish_to: 'none'
|
|||||||
description: "A sample project using melos and modular scaffold."
|
description: "A sample project using melos and modular scaffold."
|
||||||
environment:
|
environment:
|
||||||
sdk: '>=3.10.0 <4.0.0'
|
sdk: '>=3.10.0 <4.0.0'
|
||||||
|
flutter: '>=3.38.0 <4.0.0'
|
||||||
workspace:
|
workspace:
|
||||||
- packages/design_system
|
- packages/design_system
|
||||||
- packages/core
|
- packages/core
|
||||||
|
|||||||
@@ -356,6 +356,95 @@ query getApplicationsByStaffId(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
query getMyApplicationsByStaffId(
|
||||||
|
$staffId: UUID!
|
||||||
|
$offset: Int
|
||||||
|
$limit: Int
|
||||||
|
$dayStart: Timestamp
|
||||||
|
$dayEnd: Timestamp
|
||||||
|
) @auth(level: USER) {
|
||||||
|
applications(
|
||||||
|
where: {
|
||||||
|
staffId: { eq: $staffId }
|
||||||
|
status: { in: [ CONFIRMED, CHECKED_IN, CHECKED_OUT, LATE, PENDING] }
|
||||||
|
shift: {
|
||||||
|
date: { ge: $dayStart, le: $dayEnd }
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
offset: $offset
|
||||||
|
limit: $limit
|
||||||
|
) {
|
||||||
|
id
|
||||||
|
shiftId
|
||||||
|
staffId
|
||||||
|
status
|
||||||
|
appliedAt
|
||||||
|
checkInTime
|
||||||
|
checkOutTime
|
||||||
|
origin
|
||||||
|
createdAt
|
||||||
|
|
||||||
|
shift {
|
||||||
|
id
|
||||||
|
title
|
||||||
|
date
|
||||||
|
startTime
|
||||||
|
endTime
|
||||||
|
location
|
||||||
|
status
|
||||||
|
durationDays
|
||||||
|
description
|
||||||
|
latitude
|
||||||
|
longitude
|
||||||
|
|
||||||
|
order {
|
||||||
|
id
|
||||||
|
eventName
|
||||||
|
#location
|
||||||
|
|
||||||
|
teamHub {
|
||||||
|
address
|
||||||
|
placeId
|
||||||
|
hubName
|
||||||
|
}
|
||||||
|
|
||||||
|
business {
|
||||||
|
id
|
||||||
|
businessName
|
||||||
|
email
|
||||||
|
contactName
|
||||||
|
companyLogoUrl
|
||||||
|
}
|
||||||
|
vendor {
|
||||||
|
id
|
||||||
|
companyName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
shiftRole {
|
||||||
|
id
|
||||||
|
roleId
|
||||||
|
count
|
||||||
|
assigned
|
||||||
|
startTime
|
||||||
|
endTime
|
||||||
|
hours
|
||||||
|
breakType
|
||||||
|
isBreakPaid
|
||||||
|
totalValue
|
||||||
|
role {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
costPerHour
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
query vaidateDayStaffApplication(
|
query vaidateDayStaffApplication(
|
||||||
$staffId: UUID!
|
$staffId: UUID!
|
||||||
$offset: Int
|
$offset: Int
|
||||||
@@ -695,10 +784,14 @@ query listCompletedApplicationsByStaffId(
|
|||||||
durationDays
|
durationDays
|
||||||
latitude
|
latitude
|
||||||
longitude
|
longitude
|
||||||
|
orderId
|
||||||
|
|
||||||
order {
|
order {
|
||||||
id
|
id
|
||||||
eventName
|
eventName
|
||||||
|
orderType
|
||||||
|
startDate
|
||||||
|
endDate
|
||||||
|
|
||||||
teamHub {
|
teamHub {
|
||||||
address
|
address
|
||||||
|
|||||||
@@ -254,7 +254,7 @@ query listShiftRolesByVendorId(
|
|||||||
shiftRoles(
|
shiftRoles(
|
||||||
where: {
|
where: {
|
||||||
shift: {
|
shift: {
|
||||||
status: {in: [IN_PROGRESS, CONFIRMED, ASSIGNED, OPEN, PENDING]} #IN_PROGRESS? PENDING?
|
status: {in: [IN_PROGRESS, ASSIGNED, OPEN]} #IN_PROGRESS?
|
||||||
order: {
|
order: {
|
||||||
vendorId: { eq: $vendorId }
|
vendorId: { eq: $vendorId }
|
||||||
}
|
}
|
||||||
@@ -511,7 +511,7 @@ query getCompletedShiftsByBusinessId(
|
|||||||
shifts(
|
shifts(
|
||||||
where: {
|
where: {
|
||||||
order: { businessId: { eq: $businessId } }
|
order: { businessId: { eq: $businessId } }
|
||||||
status: {in: [IN_PROGRESS, CONFIRMED, COMPLETED, OPEN]}
|
status: {in: [IN_PROGRESS, COMPLETED, OPEN]}
|
||||||
date: { ge: $dateFrom, le: $dateTo }
|
date: { ge: $dateFrom, le: $dateTo }
|
||||||
}
|
}
|
||||||
offset: $offset
|
offset: $offset
|
||||||
|
|||||||
@@ -927,7 +927,7 @@ mutation seedAll @transaction {
|
|||||||
placeId: "Eiw0MDAwIFNhbiBKb3NlIFN0cmVldCwgR3JhbmFkYSBIaWxscywgQ0EsIFVTQSIuKiwKFAoSCYNJZBTdmsKAEddGOfBj8LvTEhQKEglnNXI0zZrCgBEjR6om62lcVw"
|
placeId: "Eiw0MDAwIFNhbiBKb3NlIFN0cmVldCwgR3JhbmFkYSBIaWxscywgQ0EsIFVTQSIuKiwKFAoSCYNJZBTdmsKAEddGOfBj8LvTEhQKEglnNXI0zZrCgBEjR6om62lcVw"
|
||||||
latitude: 34.2611486
|
latitude: 34.2611486
|
||||||
longitude: -118.5010287
|
longitude: -118.5010287
|
||||||
status: ASSIGNED
|
status: OPEN
|
||||||
workersNeeded: 2
|
workersNeeded: 2
|
||||||
filled: 1
|
filled: 1
|
||||||
}
|
}
|
||||||
@@ -950,7 +950,7 @@ mutation seedAll @transaction {
|
|||||||
placeId: "Eiw2ODAwIFNhbiBKb3NlIFN0cmVldCwgR3JhbmFkYSBIaWxscywgQ0EsIFVTQSIuKiwKFAoSCYNJZBTdmsKAEddGOfBj8LvTEhQKEglnNXI0zZrCgBEjR6om62lcVw"
|
placeId: "Eiw2ODAwIFNhbiBKb3NlIFN0cmVldCwgR3JhbmFkYSBIaWxscywgQ0EsIFVTQSIuKiwKFAoSCYNJZBTdmsKAEddGOfBj8LvTEhQKEglnNXI0zZrCgBEjR6om62lcVw"
|
||||||
latitude: 34.2611486
|
latitude: 34.2611486
|
||||||
longitude: -118.5010287
|
longitude: -118.5010287
|
||||||
status: ASSIGNED
|
status: OPEN
|
||||||
workersNeeded: 2
|
workersNeeded: 2
|
||||||
filled: 1
|
filled: 1
|
||||||
}
|
}
|
||||||
@@ -996,7 +996,7 @@ mutation seedAll @transaction {
|
|||||||
placeId: "Eiw0MDAwIFNhbiBKb3NlIFN0cmVldCwgR3JhbmFkYSBIaWxscywgQ0EsIFVTQSIuKiwKFAoSCYNJZBTdmsKAEddGOfBj8LvTEhQKEglnNXI0zZrCgBEjR6om62lcVw"
|
placeId: "Eiw0MDAwIFNhbiBKb3NlIFN0cmVldCwgR3JhbmFkYSBIaWxscywgQ0EsIFVTQSIuKiwKFAoSCYNJZBTdmsKAEddGOfBj8LvTEhQKEglnNXI0zZrCgBEjR6om62lcVw"
|
||||||
latitude: 34.2611486
|
latitude: 34.2611486
|
||||||
longitude: -118.5010287
|
longitude: -118.5010287
|
||||||
status: ASSIGNED
|
status: OPEN
|
||||||
workersNeeded: 2
|
workersNeeded: 2
|
||||||
filled: 1
|
filled: 1
|
||||||
}
|
}
|
||||||
@@ -1042,7 +1042,7 @@ mutation seedAll @transaction {
|
|||||||
placeId: "Eiw1MDAwIFNhbiBKb3NlIFN0cmVldCwgR3JhbmFkYSBIaWxscywgQ0EsIFVTQSIuKiwKFAoSCYNJZBTdmsKAEddGOfBj8LvTEhQKEglnNXI0zZrCgBEjR6om62lcVw"
|
placeId: "Eiw1MDAwIFNhbiBKb3NlIFN0cmVldCwgR3JhbmFkYSBIaWxscywgQ0EsIFVTQSIuKiwKFAoSCYNJZBTdmsKAEddGOfBj8LvTEhQKEglnNXI0zZrCgBEjR6om62lcVw"
|
||||||
latitude: 34.2611486
|
latitude: 34.2611486
|
||||||
longitude: -118.5010287
|
longitude: -118.5010287
|
||||||
status: ASSIGNED
|
status: OPEN
|
||||||
workersNeeded: 2
|
workersNeeded: 2
|
||||||
filled: 1
|
filled: 1
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
enum ShiftStatus {
|
enum ShiftStatus {
|
||||||
DRAFT
|
DRAFT
|
||||||
FILLED
|
FILLED
|
||||||
PENDING
|
|
||||||
ASSIGNED
|
ASSIGNED
|
||||||
CONFIRMED
|
|
||||||
OPEN
|
OPEN
|
||||||
IN_PROGRESS
|
IN_PROGRESS
|
||||||
COMPLETED
|
COMPLETED
|
||||||
|
|||||||
@@ -11,9 +11,10 @@ This document details the primary business actions and user flows within the **K
|
|||||||
* **Description:** Secure entry into the management console.
|
* **Description:** Secure entry into the management console.
|
||||||
* **Main Flow:**
|
* **Main Flow:**
|
||||||
1. User enters email and password on the login screen.
|
1. User enters email and password on the login screen.
|
||||||
2. System verifies credentials.
|
2. System verifies credentials against authentication service.
|
||||||
3. System determines user role (Admin, Client, or Vendor).
|
3. System determines user role (Admin, Client, or Vendor).
|
||||||
4. User is directed to their specific role-based dashboard.
|
4. User is directed to their specific role-based dashboard with customizable widgets.
|
||||||
|
5. System loads user-specific dashboard layout preferences.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -21,67 +22,257 @@ This document details the primary business actions and user flows within the **K
|
|||||||
|
|
||||||
### 2.1 Global Operational Oversight
|
### 2.1 Global Operational Oversight
|
||||||
* **Actor:** Admin
|
* **Actor:** Admin
|
||||||
* **Description:** Monitoring the pulse of the entire platform.
|
* **Description:** Monitoring the pulse of the entire platform through a customizable dashboard.
|
||||||
* **Main Flow:** User accesses Admin Dashboard -> Views all active orders across all clients -> Monitors user registration trends.
|
|
||||||
|
|
||||||
### 2.2 Marketplace & Vendor Management
|
|
||||||
* **Actor:** Admin
|
|
||||||
* **Description:** Expanding the platform's supply network.
|
|
||||||
* **Main Flow:**
|
* **Main Flow:**
|
||||||
1. User navigates to Marketplace.
|
1. User accesses Admin Dashboard with global metrics.
|
||||||
2. User invites a new Vendor via email.
|
2. Views fill rate, total spend, performance score, and active events.
|
||||||
3. User sets global default rates for roles.
|
3. Monitors today's orders with status indicators (RAPID, Fully Staffed, Partial Staffed).
|
||||||
4. User audits vendor performance scores.
|
4. Reviews action items prioritized by urgency (critical, high, medium).
|
||||||
|
5. Accesses ecosystem visualization showing connections between Buyers, Enterprises, Sectors, Partners, and Vendors.
|
||||||
|
6. Customizes dashboard widget layout via drag-and-drop.
|
||||||
|
|
||||||
### 2.3 System Administration
|
### 2.2 Vendor & Partner Management
|
||||||
* **Actor:** Admin
|
* **Actor:** Admin
|
||||||
* **Description:** Configuring platform-wide settings and security.
|
* **Description:** Managing the vendor network and partnerships.
|
||||||
* **Main Flow:** User updates system configurations -> Reviews security audit logs -> Manages internal support tickets.
|
* **Main Flow:**
|
||||||
|
1. User navigates to Vendor Marketplace.
|
||||||
|
2. Reviews vendor approval status and performance metrics.
|
||||||
|
3. Sets vendor tier levels (Approved Vendor, Gold Vendor).
|
||||||
|
4. Monitors vendor CSAT scores and compliance rates.
|
||||||
|
5. Views vendor rate books and service rates.
|
||||||
|
|
||||||
|
### 2.3 Order & Schedule Management
|
||||||
|
* **Actor:** Admin
|
||||||
|
* **Description:** Overseeing all orders across the platform.
|
||||||
|
* **Main Flow:**
|
||||||
|
1. User views all orders with filtering by status (All, Upcoming, Active, Past, Conflicts).
|
||||||
|
2. Reviews order details including business, hub, date/time, assigned staff.
|
||||||
|
3. Monitors assignment status (Requested vs Assigned counts).
|
||||||
|
4. Detects and resolves scheduling conflicts.
|
||||||
|
5. Accesses schedule view for visual timeline.
|
||||||
|
|
||||||
|
### 2.4 Workforce Management
|
||||||
|
* **Actor:** Admin
|
||||||
|
* **Description:** Managing platform-wide workforce.
|
||||||
|
* **Main Flow:**
|
||||||
|
1. User navigates to Staff Directory.
|
||||||
|
2. Views staff with filters (position, department, hub, profile type).
|
||||||
|
3. Monitors compliance status (background checks, certifications).
|
||||||
|
4. Reviews staff performance metrics (rating, reliability score, shift coverage).
|
||||||
|
5. Manages onboarding workflows for new staff.
|
||||||
|
|
||||||
|
### 2.5 Analytics & Reporting
|
||||||
|
* **Actor:** Admin
|
||||||
|
* **Description:** Generating insights through reports and activity logs.
|
||||||
|
* **Main Flow:**
|
||||||
|
1. User accesses Reports Dashboard.
|
||||||
|
2. Selects report type (Staffing Cost, Staff Performance, Operational Efficiency, Client Trends).
|
||||||
|
3. Configures report parameters and filters.
|
||||||
|
4. Views report insights with AI-generated recommendations.
|
||||||
|
5. Exports reports in multiple formats (PDF, Excel, CSV).
|
||||||
|
6. Reviews Activity Log for audit trail.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 3. Client Executive Workflows
|
## 3. Client Executive Workflows
|
||||||
|
|
||||||
### 3.1 Strategic Insights (Savings Engine)
|
### 3.1 Dashboard Overview
|
||||||
* **Actor:** Client Executive
|
* **Actor:** Client Executive
|
||||||
* **Description:** Using AI to optimize labor spend.
|
* **Description:** Personalized dashboard for order and labor management.
|
||||||
* **Main Flow:**
|
* **Main Flow:**
|
||||||
1. User opens the Savings Engine.
|
1. User opens Client Dashboard with customizable widgets.
|
||||||
2. User reviews identified cost-saving opportunities.
|
2. Views action items (overdue invoices, unfilled orders, rapid requests).
|
||||||
3. User clicks "Approve Strategy" to implement recommendations (e.g., vendor consolidation).
|
3. Monitors key metrics (Today's Count, In Progress, Needs Attention).
|
||||||
|
4. Reviews labor summary with cost breakdown by position.
|
||||||
|
5. Analyzes sales analytics via pie charts.
|
||||||
|
|
||||||
### 3.2 Finance & Billing Management
|
### 3.2 Order Management
|
||||||
* **Actor:** Client Executive / Finance Admin
|
* **Actor:** Client Executive / Operations Manager
|
||||||
* **Description:** Managing corporate financial obligations.
|
* **Description:** Creating and managing staffing requests.
|
||||||
* **Main Flow:** User views all pending invoices -> Downloads detailed line-item reports -> Processes payments to Krow.
|
* **Main Flow:**
|
||||||
|
1. User clicks "Order Now" or "RAPID Order" for urgent requests.
|
||||||
|
2. Selects business, hub, and event details.
|
||||||
|
3. Defines shifts with roles, counts, start/end times, and rates.
|
||||||
|
4. Chooses order type (one-time, rapid, recurring, permanent).
|
||||||
|
5. Enables conflict detection to prevent scheduling issues.
|
||||||
|
6. Reviews detected conflicts before submission.
|
||||||
|
7. Submits order to preferred vendor or marketplace.
|
||||||
|
|
||||||
### 3.3 Operations Overview
|
### 3.3 Vendor Discovery & Selection
|
||||||
* **Actor:** Client Executive
|
* **Actor:** Client Executive
|
||||||
* **Description:** High-level monitoring of venue operations.
|
* **Description:** Finding and managing vendor relationships.
|
||||||
* **Main Flow:** User views a summary of their venue orders -> Reviews ratings of assigned staff -> Monitors fulfillment rates.
|
* **Main Flow:**
|
||||||
|
1. User navigates to Vendor Marketplace.
|
||||||
|
2. Searches and filters vendors by region, category, rating, price.
|
||||||
|
3. Views vendor profiles with metrics (staff count, rating, fill rate, response time).
|
||||||
|
4. Expands vendor cards to view rate books by category.
|
||||||
|
5. Sets preferred vendor for automatic order routing.
|
||||||
|
6. Configures vendor preferences (locked vendors for optimization).
|
||||||
|
7. Contacts vendors via integrated messaging.
|
||||||
|
|
||||||
|
### 3.4 Savings Engine (Strategic Insights)
|
||||||
|
* **Actor:** Client Executive
|
||||||
|
* **Description:** Using AI to optimize labor spend and vendor mix.
|
||||||
|
* **Main Flow:**
|
||||||
|
1. User opens Savings Engine.
|
||||||
|
2. Reviews overview cards showing total spend, potential savings, fill rate.
|
||||||
|
3. Selects analysis timeframe (7 days, 30 days, Quarter, Year).
|
||||||
|
4. Navigates tabs for different insights:
|
||||||
|
- **Overview**: Dynamic dashboard with savings opportunities
|
||||||
|
- **Budget**: Budget utilization tracker
|
||||||
|
- **Strategies**: Smart operation strategies with AI recommendations
|
||||||
|
- **Predictions**: Cost forecasts and trend analysis
|
||||||
|
- **Vendors**: Vendor performance comparison
|
||||||
|
5. Views actionable strategies (vendor consolidation, rate optimization).
|
||||||
|
6. Exports analysis report.
|
||||||
|
|
||||||
|
### 3.5 Finance & Invoicing
|
||||||
|
* **Actor:** Client Executive / Finance Admin
|
||||||
|
* **Description:** Managing invoices and payments.
|
||||||
|
* **Main Flow:**
|
||||||
|
1. User views invoice list filtered by status (Open, Overdue, Paid, Disputed).
|
||||||
|
2. Opens invoice detail to review line items by role and staff.
|
||||||
|
3. Views from/to company information and payment terms.
|
||||||
|
4. Downloads invoice in PDF or Excel format.
|
||||||
|
5. Processes payment or disputes invoice with reason.
|
||||||
|
6. Tracks payment history.
|
||||||
|
|
||||||
|
### 3.6 Communication & Support
|
||||||
|
* **Actor:** Client Executive
|
||||||
|
* **Description:** Engaging with vendors and getting help.
|
||||||
|
* **Main Flow:**
|
||||||
|
1. User accesses Message Center for conversations.
|
||||||
|
2. Initiates conversation with vendors or admins.
|
||||||
|
3. Views conversation threads grouped by type (client-vendor, client-admin).
|
||||||
|
4. Accesses Tutorials for platform guidance.
|
||||||
|
5. Submits support tickets via Support Center.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 4. Vendor Workflows (Staffing Agency)
|
## 4. Vendor Workflows (Staffing Agency)
|
||||||
|
|
||||||
### 4.1 Vendor Operations (Order Fulfillment)
|
### 4.1 Vendor Dashboard
|
||||||
|
* **Actor:** Vendor Manager
|
||||||
|
* **Description:** Comprehensive view of operations and performance.
|
||||||
|
* **Main Flow:**
|
||||||
|
1. User accesses Vendor Dashboard with customizable widgets.
|
||||||
|
2. Views KPI cards (Orders Today, In Progress, RAPID, Staff Assigned).
|
||||||
|
3. Monitors action items (urgent unfilled orders, expiring certifications, invoices to submit).
|
||||||
|
4. Reviews recent orders table with assignment status.
|
||||||
|
5. Accesses revenue carousel showing monthly revenue, total revenue, active orders.
|
||||||
|
6. Views top clients by revenue and order count.
|
||||||
|
7. Reviews client loyalty status (Champion, Loyal, At Risk).
|
||||||
|
8. Monitors top performer staff by rating.
|
||||||
|
|
||||||
|
### 4.2 Order Fulfillment
|
||||||
* **Actor:** Vendor Manager
|
* **Actor:** Vendor Manager
|
||||||
* **Description:** Fulfilling client staffing requests.
|
* **Description:** Fulfilling client staffing requests.
|
||||||
* **Main Flow:**
|
* **Main Flow:**
|
||||||
1. User views incoming shift requests.
|
1. User views incoming orders via "Orders" section.
|
||||||
2. User selects a shift.
|
2. Filters orders by tab (All, Conflicts, Upcoming, Active, Past).
|
||||||
3. User uses the **Worker Selection Tool** to assign the best-fit staff.
|
3. Reviews order details (business, hub, event, date/time, roles).
|
||||||
4. User confirms assignment.
|
4. Identifies RAPID orders (< 24 hours) needing immediate attention.
|
||||||
|
5. Clicks "Assign Staff" to open Smart Assign Modal.
|
||||||
|
6. Selects optimal staff based on skills, availability, and proximity.
|
||||||
|
7. Confirms assignments and updates order status.
|
||||||
|
8. Reviews conflict alerts for staff/venue overlaps.
|
||||||
|
|
||||||
### 4.2 Workforce Roster Management
|
### 4.3 Workforce Roster Management
|
||||||
* **Actor:** Vendor Manager
|
* **Actor:** Vendor Manager
|
||||||
* **Description:** Maintaining their agency's supply of workers.
|
* **Description:** Managing agency's worker pool.
|
||||||
* **Main Flow:** User navigates to Roster -> Adds new workers -> Updates compliance documents and certifications -> Edits worker profiles.
|
* **Main Flow:**
|
||||||
|
1. User navigates to Staff Directory.
|
||||||
|
2. Views staff with filtering options (profile type, position, department, hub).
|
||||||
|
3. Toggles between grid and list view.
|
||||||
|
4. Adds new staff via "Add Staff" button.
|
||||||
|
5. Fills staff profile form (personal info, position, department, hub, contact).
|
||||||
|
6. Edits existing staff profiles.
|
||||||
|
7. Monitors staff metrics (rating, reliability score, shift coverage, cancellations).
|
||||||
|
8. Reviews compliance status (background checks, certifications).
|
||||||
|
|
||||||
### 4.3 Vendor Finance
|
### 4.4 Staff Onboarding
|
||||||
* **Actor:** Vendor Manager
|
* **Actor:** Vendor Manager
|
||||||
* **Description:** Managing agency revenue and worker payouts.
|
* **Description:** Streamlined multi-step onboarding for new workers.
|
||||||
* **Main Flow:** User views payout history -> Submits invoices for completed shifts -> Tracks pending payments from Krow.
|
* **Main Flow:**
|
||||||
|
1. User navigates to "Onboard Staff" section.
|
||||||
|
2. Completes profile setup step (name, email, position, department).
|
||||||
|
3. Uploads required documents (ID, certifications, licenses).
|
||||||
|
4. Assigns training modules.
|
||||||
|
5. Reviews completion status.
|
||||||
|
6. Activates staff member upon completion.
|
||||||
|
|
||||||
|
### 4.5 Compliance Management
|
||||||
|
* **Actor:** Vendor Manager
|
||||||
|
* **Description:** Maintaining workforce compliance standards.
|
||||||
|
* **Main Flow:**
|
||||||
|
1. User accesses Compliance Dashboard.
|
||||||
|
2. Views compliance metrics (background check status, certification expiry).
|
||||||
|
3. Filters staff needing attention.
|
||||||
|
4. Updates compliance documents in Document Vault.
|
||||||
|
5. Tracks certification renewal deadlines.
|
||||||
|
|
||||||
|
### 4.6 Schedule & Availability
|
||||||
|
* **Actor:** Vendor Manager
|
||||||
|
* **Description:** Managing staff availability and schedules.
|
||||||
|
* **Main Flow:**
|
||||||
|
1. User navigates to Staff Availability.
|
||||||
|
2. Views calendar-based availability grid.
|
||||||
|
3. Updates staff availability preferences.
|
||||||
|
4. Accesses Schedule view for visual timeline of assignments.
|
||||||
|
5. Identifies gaps and conflicts.
|
||||||
|
|
||||||
|
### 4.7 Client Relationship Management
|
||||||
|
* **Actor:** Vendor Manager
|
||||||
|
* **Description:** Managing client accounts and preferences.
|
||||||
|
* **Main Flow:**
|
||||||
|
1. User navigates to Clients section.
|
||||||
|
2. Views client list with business details.
|
||||||
|
3. Adds new client accounts.
|
||||||
|
4. Edits client information (contact, address, hubs, departments).
|
||||||
|
5. Configures client preferences (favorite staff, blocked staff).
|
||||||
|
6. Sets ERP integration details (vendor ID, cost center, EDI format).
|
||||||
|
|
||||||
|
### 4.8 Rate Management
|
||||||
|
* **Actor:** Vendor Manager
|
||||||
|
* **Description:** Managing service rates and pricing.
|
||||||
|
* **Main Flow:**
|
||||||
|
1. User accesses Service Rates section.
|
||||||
|
2. Views rate cards by client and role.
|
||||||
|
3. Creates new rate entries (role, client rate, employee wage).
|
||||||
|
4. Configures markup percentage and vendor fee.
|
||||||
|
5. Sets approved cap rates.
|
||||||
|
6. Activates/deactivates rates.
|
||||||
|
|
||||||
|
### 4.9 Vendor Finance & Invoicing
|
||||||
|
* **Actor:** Vendor Manager
|
||||||
|
* **Description:** Managing revenue and submitting invoices.
|
||||||
|
* **Main Flow:**
|
||||||
|
1. User views invoice list for completed orders.
|
||||||
|
2. Auto-generates invoices from completed events.
|
||||||
|
3. Reviews invoice details with staff entries and line items.
|
||||||
|
4. Edits invoice before submission if needed.
|
||||||
|
5. Submits invoice to client.
|
||||||
|
6. Tracks invoice status (Draft, Open, Confirmed, Paid).
|
||||||
|
7. Downloads invoice for records.
|
||||||
|
|
||||||
|
### 4.10 Performance Analytics
|
||||||
|
* **Actor:** Vendor Manager
|
||||||
|
* **Description:** Monitoring vendor performance metrics.
|
||||||
|
* **Main Flow:**
|
||||||
|
1. User accesses Performance section.
|
||||||
|
2. Reviews fill rate, on-time performance, client satisfaction.
|
||||||
|
3. Views staff performance leaderboard.
|
||||||
|
4. Analyzes revenue trends by client and timeframe.
|
||||||
|
|
||||||
|
### 4.11 Savings Engine (Growth Opportunities)
|
||||||
|
* **Actor:** Vendor Manager
|
||||||
|
* **Description:** Identifying growth and optimization opportunities.
|
||||||
|
* **Main Flow:**
|
||||||
|
1. User opens Savings Engine with vendor-specific tabs.
|
||||||
|
2. Reviews performance metrics and benchmarks.
|
||||||
|
3. Identifies opportunities to improve ratings and win more business.
|
||||||
|
4. Views workforce utilization statistics.
|
||||||
|
5. Analyzes growth forecasts.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -90,16 +281,181 @@ This document details the primary business actions and user flows within the **K
|
|||||||
### 5.1 Order Details & History
|
### 5.1 Order Details & History
|
||||||
* **Actor:** All Roles
|
* **Actor:** All Roles
|
||||||
* **Description:** Accessing granular data for any specific staffing request.
|
* **Description:** Accessing granular data for any specific staffing request.
|
||||||
* **Main Flow:** User clicks any order ID -> System displays shift times, roles, assigned staff, and audit history.
|
* **Main Flow:**
|
||||||
|
1. User clicks any order ID from lists or dashboards.
|
||||||
|
2. System displays comprehensive order information:
|
||||||
|
- Event details (name, business, hub, date, time)
|
||||||
|
- Shift configuration with roles, counts, and rates
|
||||||
|
- Assigned staff with profiles
|
||||||
|
- Status history and audit trail
|
||||||
|
- Detected conflicts (if any)
|
||||||
|
- Invoice linkage (if completed)
|
||||||
|
3. User can edit order (if permissions allow).
|
||||||
|
4. User can assign/reassign staff.
|
||||||
|
5. User can view related invoices.
|
||||||
|
|
||||||
### 5.2 Invoice Detail View
|
### 5.2 Invoice Detail View
|
||||||
* **Actor:** Admin, Client, Vendor
|
* **Actor:** Admin, Client, Vendor
|
||||||
* **Description:** Reviewing the breakdown of costs for a billing period.
|
* **Description:** Reviewing the breakdown of costs for a billing period.
|
||||||
* **Main Flow:** User opens an invoice -> System displays worker names, hours worked, bill rates, and total totals per role.
|
* **Main Flow:**
|
||||||
|
1. User opens an invoice from the invoice list.
|
||||||
|
2. System displays invoice header (invoice number, dates, status, parties).
|
||||||
|
3. Views detailed breakdown:
|
||||||
|
- Roles section with staff entries per role
|
||||||
|
- Hours worked (regular, overtime, double-time)
|
||||||
|
- Bill rates and totals per role
|
||||||
|
- Additional charges
|
||||||
|
- Subtotal and grand total
|
||||||
|
4. Reviews payment terms and PO reference.
|
||||||
|
5. Downloads invoice in PDF or Excel.
|
||||||
|
6. Copies invoice data to clipboard.
|
||||||
|
7. Sends invoice via email (vendor role).
|
||||||
|
8. Approves or disputes invoice (client role).
|
||||||
|
|
||||||
|
### 5.3 Task Board
|
||||||
|
* **Actor:** All Roles
|
||||||
|
* **Description:** Collaborative task management across teams.
|
||||||
|
* **Main Flow:**
|
||||||
|
1. User accesses Task Board.
|
||||||
|
2. Views tasks in columns by status (Pending, In Progress, On Hold, Completed).
|
||||||
|
3. Drags tasks between columns to update status.
|
||||||
|
4. Creates new tasks with details (name, description, priority, due date).
|
||||||
|
5. Assigns tasks to team members.
|
||||||
|
6. Adds comments and attachments to tasks.
|
||||||
|
7. Filters tasks by department, priority, or assignee.
|
||||||
|
|
||||||
|
### 5.4 Message Center
|
||||||
|
* **Actor:** All Roles
|
||||||
|
* **Description:** Cross-platform communication hub.
|
||||||
|
* **Main Flow:**
|
||||||
|
1. User accesses Message Center.
|
||||||
|
2. Views conversation list with unread counts.
|
||||||
|
3. Filters by conversation type (client-vendor, client-admin, internal).
|
||||||
|
4. Opens conversation thread.
|
||||||
|
5. Sends messages with attachments.
|
||||||
|
6. Views system-generated messages for automated events.
|
||||||
|
7. Archives completed conversations.
|
||||||
|
|
||||||
|
### 5.5 Reports & Analytics
|
||||||
|
* **Actor:** All Roles (with role-specific access)
|
||||||
|
* **Description:** Data-driven insights and custom reporting.
|
||||||
|
* **Main Flow:**
|
||||||
|
1. User accesses Reports Dashboard.
|
||||||
|
2. Selects from report types:
|
||||||
|
- Staffing Cost Report
|
||||||
|
- Staff Performance Report
|
||||||
|
- Operational Efficiency Report
|
||||||
|
- Client Trends Report
|
||||||
|
- Custom Report Builder
|
||||||
|
3. Configures report parameters (date range, filters, grouping).
|
||||||
|
4. Views AI-generated insights banner with key findings.
|
||||||
|
5. Exports report in preferred format.
|
||||||
|
6. Schedules recurring reports for automated delivery.
|
||||||
|
7. Saves report templates for reuse.
|
||||||
|
|
||||||
|
### 5.6 Teams Management
|
||||||
|
* **Actor:** Admin, Client, Vendor
|
||||||
|
* **Description:** Creating and managing staff teams.
|
||||||
|
* **Main Flow:**
|
||||||
|
1. User navigates to Teams section.
|
||||||
|
2. Views team list with member counts.
|
||||||
|
3. Creates new team with name and description.
|
||||||
|
4. Adds team members from staff directory.
|
||||||
|
5. Views team detail page with member profiles.
|
||||||
|
6. Assigns teams to orders as groups.
|
||||||
|
|
||||||
|
### 5.7 Staff Conflict Detection
|
||||||
|
* **Actor:** Admin, Vendor
|
||||||
|
* **Description:** Automated detection of scheduling conflicts.
|
||||||
|
* **Main Flow:**
|
||||||
|
1. System automatically detects conflicts when creating/editing orders:
|
||||||
|
- **Staff Overlap**: Same staff assigned to overlapping shifts
|
||||||
|
- **Venue Overlap**: Same venue booked for overlapping times
|
||||||
|
- **Time Buffer**: Insufficient travel time between assignments
|
||||||
|
2. System assigns severity level (Critical, High, Medium, Low).
|
||||||
|
3. Displays conflict alerts with details (conflicting event, staff, location).
|
||||||
|
4. User resolves conflicts before finalizing order.
|
||||||
|
5. System tracks conflict resolution in audit log.
|
||||||
|
|
||||||
|
### 5.8 Dashboard Customization
|
||||||
|
* **Actor:** All Roles
|
||||||
|
* **Description:** Personalizing dashboard layouts.
|
||||||
|
* **Main Flow:**
|
||||||
|
1. User clicks "Customize Dashboard" button.
|
||||||
|
2. Enters customization mode with drag-and-drop interface.
|
||||||
|
3. Reorders widgets by dragging.
|
||||||
|
4. Hides/shows widgets using visibility controls.
|
||||||
|
5. Previews changes in real-time.
|
||||||
|
6. Saves layout preferences to user profile.
|
||||||
|
7. Resets to default layout if desired.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Advanced Features
|
||||||
|
|
||||||
|
### 6.1 Smart Assignment Engine (Vendor)
|
||||||
|
* **Actor:** Vendor Manager
|
||||||
|
* **Description:** AI-powered staff assignment optimization.
|
||||||
|
* **Main Flow:**
|
||||||
|
1. User clicks "Smart Assign" on an order.
|
||||||
|
2. System analyzes requirements (skills, location, time, availability).
|
||||||
|
3. Engine scores available staff based on:
|
||||||
|
- Skill match
|
||||||
|
- Proximity to venue
|
||||||
|
- Past performance
|
||||||
|
- Availability
|
||||||
|
- Client preferences
|
||||||
|
4. Presents ranked staff recommendations.
|
||||||
|
5. User reviews suggestions and confirms assignments.
|
||||||
|
|
||||||
|
### 6.2 Auto-Invoice Generation
|
||||||
|
* **Actor:** Vendor Manager
|
||||||
|
* **Description:** Automated invoice creation from completed orders.
|
||||||
|
* **Main Flow:**
|
||||||
|
1. When order status changes to "Completed", system triggers auto-invoice.
|
||||||
|
2. System aggregates staff entries, hours, and rates.
|
||||||
|
3. Generates invoice line items by role.
|
||||||
|
4. Calculates totals (regular, overtime, double-time).
|
||||||
|
5. Applies additional charges if configured.
|
||||||
|
6. Creates draft invoice for vendor review.
|
||||||
|
7. Vendor reviews and submits to client.
|
||||||
|
|
||||||
|
### 6.3 Vendor Preferences & Optimization (Client)
|
||||||
|
* **Actor:** Client Executive
|
||||||
|
* **Description:** Configuring vendor routing and procurement strategies.
|
||||||
|
* **Main Flow:**
|
||||||
|
1. User accesses Client Vendor Preferences panel.
|
||||||
|
2. Sets preferred vendor for automatic order routing.
|
||||||
|
3. Configures locked vendors (never used for optimization).
|
||||||
|
4. Enables/disables procurement optimization.
|
||||||
|
5. System respects preferences when suggesting vendors in Savings Engine.
|
||||||
|
|
||||||
|
### 6.4 Contract Conversion & Tier Optimization
|
||||||
|
* **Actor:** Admin, Client (via Savings Engine)
|
||||||
|
* **Description:** Analyzing opportunities to move spend to preferred vendors.
|
||||||
|
* **Main Flow:**
|
||||||
|
1. User accesses "Conversion Map" tab in Savings Engine.
|
||||||
|
2. Views non-contracted spend by vendor.
|
||||||
|
3. System identifies conversion opportunities to approved/gold vendors.
|
||||||
|
4. Reviews potential savings from rate arbitrage.
|
||||||
|
5. Approves conversion strategy.
|
||||||
|
6. System routes future orders accordingly.
|
||||||
|
|
||||||
|
### 6.5 Predictive Savings Model
|
||||||
|
* **Actor:** Admin, Client
|
||||||
|
* **Description:** Forecasting cost savings through AI analysis.
|
||||||
|
* **Main Flow:**
|
||||||
|
1. User accesses "Predictions" tab in Savings Engine.
|
||||||
|
2. System analyzes historical spend, rates, and vendor performance.
|
||||||
|
3. Generates forecasts for 7 days, 30 days, quarter, year.
|
||||||
|
4. Identifies rate optimization opportunities.
|
||||||
|
5. Recommends vendor consolidation strategies.
|
||||||
|
6. Shows projected ROI for each strategy.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
# Use Case Diagram
|
# Use Case Diagram
|
||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
flowchart TD
|
flowchart TD
|
||||||
subgraph AccessControl [Access & Authentication]
|
subgraph AccessControl [Access & Authentication]
|
||||||
@@ -118,53 +474,166 @@ flowchart TD
|
|||||||
|
|
||||||
subgraph AdminWorkflows [Admin Workflows]
|
subgraph AdminWorkflows [Admin Workflows]
|
||||||
AdminDash --> GlobalOversight[Global Oversight]
|
AdminDash --> GlobalOversight[Global Oversight]
|
||||||
|
GlobalOversight --> EcosystemWheel[Ecosystem Wheel]
|
||||||
GlobalOversight --> ViewAllOrders[View All Orders]
|
GlobalOversight --> ViewAllOrders[View All Orders]
|
||||||
GlobalOversight --> ViewAllUsers[View All Users]
|
GlobalOversight --> ActionItems[Action Items]
|
||||||
|
|
||||||
AdminDash --> MarketplaceMgmt[Marketplace Management]
|
AdminDash --> VendorMgmt[Vendor Management]
|
||||||
MarketplaceMgmt --> OnboardVendor[Onboard Vendor]
|
VendorMgmt --> ApproveVendors[Approve Vendors]
|
||||||
MarketplaceMgmt --> ManageRates[Manage Global Rates]
|
VendorMgmt --> SetTiers[Set Vendor Tiers]
|
||||||
|
|
||||||
AdminDash --> SystemAdmin[System Administration]
|
AdminDash --> WorkforceMgmt[Workforce Management]
|
||||||
SystemAdmin --> ConfigSettings[Configure Settings]
|
WorkforceMgmt --> StaffDirectory[Staff Directory]
|
||||||
SystemAdmin --> AuditLogs[View Audit Logs]
|
WorkforceMgmt --> Compliance[Compliance Dashboard]
|
||||||
|
|
||||||
|
AdminDash --> AnalyticsReports[Analytics & Reports]
|
||||||
|
AnalyticsReports --> ReportsDashboard[Reports Dashboard]
|
||||||
|
AnalyticsReports --> ActivityLog[Activity Log]
|
||||||
end
|
end
|
||||||
|
|
||||||
subgraph ClientWorkflows [Client Executive Workflows]
|
subgraph ClientWorkflows [Client Executive Workflows]
|
||||||
ClientDash --> ClientInsights[Strategic Insights]
|
ClientDash --> ClientActionItems[Action Items]
|
||||||
ClientInsights --> SavingsEngine[Savings Engine]
|
ClientActionItems --> ReviewAlerts[Review Alerts]
|
||||||
SavingsEngine --> ViewOpp[View Opportunity]
|
|
||||||
ViewOpp --> ApproveStrategy[Approve Strategy]
|
|
||||||
|
|
||||||
ClientDash --> ClientFinance[Finance & Billing]
|
ClientDash --> OrderMgmt[Order Management]
|
||||||
|
OrderMgmt --> CreateOrder[Create Order]
|
||||||
|
CreateOrder --> DefineShifts[Define Shifts & Roles]
|
||||||
|
DefineShifts --> ConflictDetection[Conflict Detection]
|
||||||
|
ConflictDetection --> SubmitOrder[Submit Order]
|
||||||
|
|
||||||
|
OrderMgmt --> ViewMyOrders[View My Orders]
|
||||||
|
ViewMyOrders --> OrderDetail[Order Detail]
|
||||||
|
|
||||||
|
ClientDash --> VendorDiscovery[Vendor Discovery]
|
||||||
|
VendorDiscovery --> BrowseMarketplace[Browse Marketplace]
|
||||||
|
BrowseMarketplace --> SetPreferred[Set Preferred Vendor]
|
||||||
|
BrowseMarketplace --> ContactVendor[Contact Vendor]
|
||||||
|
|
||||||
|
ClientDash --> SavingsEngine[Savings Engine]
|
||||||
|
SavingsEngine --> AnalyzeSpend[Analyze Spend]
|
||||||
|
AnalyzeSpend --> ViewStrategies[View Strategies]
|
||||||
|
ViewStrategies --> ApproveStrategy[Approve Strategy]
|
||||||
|
SavingsEngine --> PredictiveSavings[Predictive Savings]
|
||||||
|
SavingsEngine --> ConversionMap[Conversion Map]
|
||||||
|
|
||||||
|
ClientDash --> ClientFinance[Finance & Invoicing]
|
||||||
ClientFinance --> ViewInvoices[View Invoices]
|
ClientFinance --> ViewInvoices[View Invoices]
|
||||||
ClientFinance --> PayInvoice[Pay Invoice]
|
ViewInvoices --> InvoiceDetail[Invoice Detail]
|
||||||
|
InvoiceDetail --> PayInvoice[Pay Invoice]
|
||||||
|
InvoiceDetail --> DisputeInvoice[Dispute Invoice]
|
||||||
|
|
||||||
ClientDash --> ClientOps[Operations Overview]
|
ClientDash --> Communication[Communication]
|
||||||
ClientOps --> ViewMyOrders[View My Orders]
|
Communication --> MessageCenter[Message Center]
|
||||||
ClientOps --> ViewMyStaff[View Assigned Staff]
|
Communication --> SupportCenter[Support Center]
|
||||||
end
|
end
|
||||||
|
|
||||||
subgraph VendorWorkflows [Vendor Workflows]
|
subgraph VendorWorkflows [Vendor Workflows]
|
||||||
VendorDash --> VendorOps[Vendor Operations]
|
VendorDash --> VendorKPIs[KPI Dashboard]
|
||||||
VendorOps --> ViewRequests[View Shift Requests]
|
VendorKPIs --> RevenueStats[Revenue Stats]
|
||||||
ViewRequests --> AssignWorker[Assign Worker]
|
VendorKPIs --> TopClients[Top Clients]
|
||||||
VendorOps --> ManageRoster[Manage Worker Roster]
|
VendorKPIs --> TopPerformers[Top Performers]
|
||||||
ManageRoster --> UpdateWorkerProfile[Update Worker Profile]
|
|
||||||
|
|
||||||
VendorDash --> VendorFinance[Vendor Finance]
|
VendorDash --> OrderFulfillment[Order Fulfillment]
|
||||||
VendorFinance --> ViewPayouts[View Payouts]
|
OrderFulfillment --> ViewOrders[View Orders]
|
||||||
|
ViewOrders --> FilterOrders[Filter Orders]
|
||||||
|
FilterOrders --> AssignStaff[Smart Assign Staff]
|
||||||
|
AssignStaff --> ResolveConflicts[Resolve Conflicts]
|
||||||
|
|
||||||
|
VendorDash --> RosterMgmt[Roster Management]
|
||||||
|
RosterMgmt --> StaffDir[Staff Directory]
|
||||||
|
StaffDir --> AddStaff[Add Staff]
|
||||||
|
StaffDir --> EditStaff[Edit Staff]
|
||||||
|
StaffDir --> ViewMetrics[View Staff Metrics]
|
||||||
|
|
||||||
|
RosterMgmt --> OnboardStaff[Onboard Staff]
|
||||||
|
OnboardStaff --> ProfileSetup[Profile Setup]
|
||||||
|
ProfileSetup --> UploadDocs[Upload Documents]
|
||||||
|
UploadDocs --> AssignTraining[Assign Training]
|
||||||
|
AssignTraining --> ActivateStaff[Activate Staff]
|
||||||
|
|
||||||
|
VendorDash --> ComplianceMgmt[Compliance Management]
|
||||||
|
ComplianceMgmt --> ComplianceDash[Compliance Dashboard]
|
||||||
|
ComplianceDash --> DocumentVault[Document Vault]
|
||||||
|
ComplianceDash --> CertTracking[Certification Tracking]
|
||||||
|
|
||||||
|
VendorDash --> ScheduleAvail[Schedule & Availability]
|
||||||
|
ScheduleAvail --> StaffAvailability[Staff Availability]
|
||||||
|
ScheduleAvail --> ScheduleView[Schedule View]
|
||||||
|
|
||||||
|
VendorDash --> ClientMgmt[Client Management]
|
||||||
|
ClientMgmt --> ManageClients[Manage Clients]
|
||||||
|
ManageClients --> ClientPrefs[Client Preferences]
|
||||||
|
|
||||||
|
VendorDash --> RateMgmt[Rate Management]
|
||||||
|
RateMgmt --> ServiceRates[Service Rates]
|
||||||
|
ServiceRates --> RateCards[Rate Cards]
|
||||||
|
|
||||||
|
VendorDash --> VendorFinance[Finance]
|
||||||
|
VendorFinance --> AutoInvoice[Auto-Generate Invoice]
|
||||||
VendorFinance --> SubmitInvoice[Submit Invoice]
|
VendorFinance --> SubmitInvoice[Submit Invoice]
|
||||||
|
VendorFinance --> TrackPayments[Track Payments]
|
||||||
|
|
||||||
|
VendorDash --> VendorPerformance[Performance Analytics]
|
||||||
|
VendorPerformance --> FillRate[Fill Rate]
|
||||||
|
VendorPerformance --> CSAT[Client Satisfaction]
|
||||||
|
VendorPerformance --> RevenueAnalysis[Revenue Analysis]
|
||||||
end
|
end
|
||||||
|
|
||||||
subgraph SharedModules [Shared Functional Modules]
|
subgraph SharedModules [Shared Functional Modules]
|
||||||
ViewAllOrders -.-> OrderDetail[Order Details]
|
TaskBoard[Task Board] -.-> Tasks[Manage Tasks]
|
||||||
ViewMyOrders -.-> OrderDetail
|
Tasks -.-> DragDrop[Drag & Drop Status]
|
||||||
ViewRequests -.-> OrderDetail
|
|
||||||
|
|
||||||
AssignWorker -.-> WorkerSelection[Worker Selection Tool]
|
MessageCenter -.-> Conversations[Conversations]
|
||||||
|
Conversations -.-> SendMessage[Send Message]
|
||||||
|
|
||||||
ViewInvoices -.-> InvoiceDetail[Invoice Detail View]
|
ReportsDashboard -.-> ReportTypes[Report Types]
|
||||||
SubmitInvoice -.-> InvoiceDetail
|
ReportTypes -.-> CustomBuilder[Custom Report Builder]
|
||||||
|
ReportTypes -.-> ScheduledReports[Scheduled Reports]
|
||||||
|
ReportTypes -.-> ExportReport[Export Report]
|
||||||
|
|
||||||
|
TeamsModule[Teams] -.-> CreateTeam[Create Team]
|
||||||
|
CreateTeam -.-> AddMembers[Add Members]
|
||||||
|
|
||||||
|
DashboardCustom[Dashboard Customization] -.-> DragWidgets[Drag Widgets]
|
||||||
|
DragWidgets -.-> HideShow[Hide/Show Widgets]
|
||||||
|
HideShow -.-> SaveLayout[Save Layout]
|
||||||
end
|
end
|
||||||
```
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Summary of Key Enhancements
|
||||||
|
|
||||||
|
**Compared to the original document, this updated version includes:**
|
||||||
|
|
||||||
|
1. **Detailed Dashboard Workflows**: Comprehensive descriptions of customizable dashboards for each role with specific widgets and metrics.
|
||||||
|
|
||||||
|
2. **Advanced Order Management**: Multi-step order creation with shift configuration, conflict detection, and order type options (one-time, rapid, recurring, permanent).
|
||||||
|
|
||||||
|
3. **Smart Assignment**: AI-powered staff assignment engine for vendors to optimize worker selection.
|
||||||
|
|
||||||
|
4. **Savings Engine**: Detailed AI-driven cost optimization workflows with predictive modeling, vendor conversion strategies, and budget tracking.
|
||||||
|
|
||||||
|
5. **Vendor Marketplace**: Complete vendor discovery and selection process with filtering, rate comparison, and preference settings.
|
||||||
|
|
||||||
|
6. **Enhanced Finance**: Auto-invoice generation, detailed invoice views, export capabilities, and dispute resolution.
|
||||||
|
|
||||||
|
7. **Onboarding Workflow**: Multi-step staff onboarding process for vendors.
|
||||||
|
|
||||||
|
8. **Compliance Management**: Dedicated compliance dashboard and document vault.
|
||||||
|
|
||||||
|
9. **Conflict Detection**: Automated scheduling conflict detection with severity levels.
|
||||||
|
|
||||||
|
10. **Communication Hub**: Integrated message center for cross-platform communication.
|
||||||
|
|
||||||
|
11. **Teams Management**: Team creation and assignment workflows.
|
||||||
|
|
||||||
|
12. **Advanced Analytics**: Multiple report types, custom report builder, scheduled reports, and AI-generated insights.
|
||||||
|
|
||||||
|
13. **Dashboard Customization**: Drag-and-drop widget management with layout persistence.
|
||||||
|
|
||||||
|
14. **Schedule & Availability**: Calendar-based staff availability management with visual schedule view.
|
||||||
|
|
||||||
|
15. **Client & Rate Management**: Vendor-side client relationship and service rate management.
|
||||||
|
|
||||||
|
This document now accurately reflects the robust feature set implemented in the krow_web_application.
|
||||||
|
|||||||
Reference in New Issue
Block a user