feat: add entities for staff personal info, reports, shifts, and user sessions

- Implemented StaffPersonalInfo entity for staff profile data.
- Created ReportSummary entity for summarizing report metrics.
- Added SpendReport and SpendDataPoint entities for spend reporting.
- Introduced AssignedShift, CancelledShift, CompletedShift, OpenShift, PendingAssignment, ShiftDetail, TodayShift entities for shift management.
- Developed ClientSession and StaffSession entities for user session management.
This commit is contained in:
Achintha Isuru
2026-03-16 15:59:22 -04:00
parent 641dfac73d
commit 4834266986
159 changed files with 6857 additions and 3937 deletions

View File

@@ -1,33 +0,0 @@
import '../../entities/availability/availability_slot.dart';
/// Adapter for [AvailabilitySlot] domain entity.
class AvailabilityAdapter {
static const Map<String, Map<String, String>> _slotDefinitions = <String, Map<String, String>>{
'MORNING': <String, String>{
'id': 'morning',
'label': 'Morning',
'timeRange': '4:00 AM - 12:00 PM',
},
'AFTERNOON': <String, String>{
'id': 'afternoon',
'label': 'Afternoon',
'timeRange': '12:00 PM - 6:00 PM',
},
'EVENING': <String, String>{
'id': 'evening',
'label': 'Evening',
'timeRange': '6:00 PM - 12:00 AM',
},
};
/// Converts a backend slot name (e.g. 'MORNING') to a Domain [AvailabilitySlot].
static AvailabilitySlot fromPrimitive(String slotName, {bool isAvailable = false}) {
final Map<String, String> def = _slotDefinitions[slotName.toUpperCase()] ?? _slotDefinitions['MORNING']!;
return AvailabilitySlot(
id: def['id']!,
label: def['label']!,
timeRange: def['timeRange']!,
isAvailable: isAvailable,
);
}
}

View File

@@ -1,27 +0,0 @@
import '../../entities/clock_in/attendance_status.dart';
/// Adapter for Clock In related data.
class ClockInAdapter {
/// Converts primitive attendance data to [AttendanceStatus].
static AttendanceStatus toAttendanceStatus({
required String status,
DateTime? checkInTime,
DateTime? checkOutTime,
String? activeShiftId,
String? activeApplicationId,
}) {
final bool isCheckedIn = status == 'CHECKED_IN' || status == 'LATE'; // Assuming LATE is also checked in?
// Statuses that imply active attendance: CHECKED_IN, LATE.
// Statuses that imply completed: CHECKED_OUT.
return AttendanceStatus(
isCheckedIn: isCheckedIn,
checkInTime: checkInTime,
checkOutTime: checkOutTime,
activeShiftId: activeShiftId,
activeApplicationId: activeApplicationId,
);
}
}

View File

@@ -1,21 +0,0 @@
import '../../../entities/financial/bank_account/business_bank_account.dart';
/// Adapter for [BusinessBankAccount] to map data layer values to domain entity.
class BusinessBankAccountAdapter {
/// Maps primitive values to [BusinessBankAccount].
static BusinessBankAccount fromPrimitives({
required String id,
required String bank,
required String last4,
required bool isPrimary,
DateTime? expiryTime,
}) {
return BusinessBankAccount(
id: id,
bankName: bank,
last4: last4,
isPrimary: isPrimary,
expiryTime: expiryTime,
);
}
}

View File

@@ -1,19 +0,0 @@
import '../../entities/financial/staff_payment.dart';
/// Adapter for Payment related data.
class PaymentAdapter {
/// Converts string status to [PaymentStatus].
static PaymentStatus toPaymentStatus(String status) {
switch (status) {
case 'PAID':
return PaymentStatus.paid;
case 'PENDING':
return PaymentStatus.pending;
case 'FAILED':
return PaymentStatus.failed;
default:
return PaymentStatus.unknown;
}
}
}

View File

@@ -1,49 +0,0 @@
import '../../entities/financial/time_card.dart';
/// Adapter for [TimeCard] to map data layer values to domain entity.
class TimeCardAdapter {
/// Maps primitive values to [TimeCard].
static TimeCard fromPrimitives({
required String id,
required String shiftTitle,
required String clientName,
required DateTime date,
required String startTime,
required String endTime,
required double totalHours,
required double hourlyRate,
required double totalPay,
required String status,
String? location,
}) {
return TimeCard(
id: id,
shiftTitle: shiftTitle,
clientName: clientName,
date: date,
startTime: startTime,
endTime: endTime,
totalHours: totalHours,
hourlyRate: hourlyRate,
totalPay: totalPay,
status: _stringToStatus(status),
location: location,
);
}
static TimeCardStatus _stringToStatus(String status) {
switch (status.toUpperCase()) {
case 'CHECKED_OUT':
case 'COMPLETED':
return TimeCardStatus.approved; // Assuming completed = approved for now
case 'PAID':
return TimeCardStatus.paid; // If this status exists
case 'DISPUTED':
return TimeCardStatus.disputed;
case 'CHECKED_IN':
case 'CONFIRMED':
default:
return TimeCardStatus.pending;
}
}
}

View File

@@ -1,53 +0,0 @@
import '../../entities/financial/bank_account/staff_bank_account.dart';
/// Adapter for [StaffBankAccount] to map data layer values to domain entity.
class BankAccountAdapter {
/// Maps primitive values to [StaffBankAccount].
static StaffBankAccount fromPrimitives({
required String id,
required String userId,
required String bankName,
required String? type,
String? accountNumber,
String? last4,
String? sortCode,
bool? isPrimary,
}) {
return StaffBankAccount(
id: id,
userId: userId,
bankName: bankName,
accountNumber: accountNumber ?? '',
accountName: '', // Not provided by backend
last4: last4,
sortCode: sortCode,
type: _stringToType(type),
isPrimary: isPrimary ?? false,
);
}
static StaffBankAccountType _stringToType(String? value) {
if (value == null) return StaffBankAccountType.checking;
try {
// Assuming backend enum names match or are uppercase
return StaffBankAccountType.values.firstWhere(
(StaffBankAccountType e) => e.name.toLowerCase() == value.toLowerCase(),
orElse: () => StaffBankAccountType.other,
);
} catch (_) {
return StaffBankAccountType.other;
}
}
/// Converts domain type to string for backend.
static String typeToString(StaffBankAccountType type) {
switch (type) {
case StaffBankAccountType.checking:
return 'CHECKING';
case StaffBankAccountType.savings:
return 'SAVINGS';
default:
return 'CHECKING';
}
}
}

View File

@@ -1,19 +0,0 @@
import '../../entities/profile/emergency_contact.dart';
/// Adapter for [EmergencyContact] to map data layer values to domain entity.
class EmergencyContactAdapter {
/// Maps primitive values to [EmergencyContact].
static EmergencyContact fromPrimitives({
required String id,
required String name,
required String phone,
String? relationship,
}) {
return EmergencyContact(
id: id,
name: name,
phone: phone,
relationship: EmergencyContact.stringToRelationshipType(relationship),
);
}
}

View File

@@ -1,18 +0,0 @@
/// Adapter for Experience data (skills/industries) to map data layer values to domain models.
class ExperienceAdapter {
/// Converts a dynamic list (from backend AnyValue) to List<String>.
///
/// Handles nulls and converts elements to Strings.
static List<String> fromDynamicList(dynamic data) {
if (data == null) return <String>[];
if (data is List) {
return data
.where((dynamic e) => e != null)
.map((dynamic e) => e.toString())
.toList();
}
return <String>[];
}
}

View File

@@ -1,104 +0,0 @@
import '../../entities/profile/tax_form.dart';
/// Adapter for [TaxForm] to map data layer values to domain entity.
class TaxFormAdapter {
/// Maps primitive values to [TaxForm].
static TaxForm fromPrimitives({
required String id,
required String type,
required String title,
String? subtitle,
String? description,
required String status,
String? staffId,
dynamic formData,
DateTime? createdAt,
DateTime? updatedAt,
}) {
final TaxFormType formType = _stringToType(type);
final TaxFormStatus formStatus = _stringToStatus(status);
final Map<String, dynamic> formDetails =
formData is Map ? Map<String, dynamic>.from(formData) : <String, dynamic>{};
if (formType == TaxFormType.i9) {
return I9TaxForm(
id: id,
title: title,
subtitle: subtitle,
description: description,
status: formStatus,
staffId: staffId,
formData: formDetails,
createdAt: createdAt,
updatedAt: updatedAt,
);
} else {
return W4TaxForm(
id: id,
title: title,
subtitle: subtitle,
description: description,
status: formStatus,
staffId: staffId,
formData: formDetails,
createdAt: createdAt,
updatedAt: updatedAt,
);
}
}
static TaxFormType _stringToType(String? value) {
if (value == null) return TaxFormType.i9;
try {
return TaxFormType.values.firstWhere(
(TaxFormType e) => e.name.toLowerCase() == value.toLowerCase(),
orElse: () => TaxFormType.i9,
);
} catch (_) {
return TaxFormType.i9;
}
}
static TaxFormStatus _stringToStatus(String? value) {
if (value == null) return TaxFormStatus.notStarted;
try {
final String normalizedValue = value.replaceAll('_', '').toLowerCase();
// map DRAFT to inProgress
if (normalizedValue == 'draft') return TaxFormStatus.inProgress;
return TaxFormStatus.values.firstWhere(
(TaxFormStatus e) {
// Handle differences like not_started vs notStarted if any,
// but standardizing to lowercase is a good start.
// The enum names are camelCase in Dart, but might be SNAKE_CASE from backend.
final String normalizedEnum = e.name.toLowerCase();
return normalizedValue == normalizedEnum;
},
orElse: () => TaxFormStatus.notStarted,
);
} catch (_) {
return TaxFormStatus.notStarted;
}
}
/// Converts domain [TaxFormType] to string for backend.
static String typeToString(TaxFormType type) {
return type.name.toUpperCase();
}
/// Converts domain [TaxFormStatus] to string for backend.
static String statusToString(TaxFormStatus status) {
switch (status) {
case TaxFormStatus.notStarted:
return 'NOT_STARTED';
case TaxFormStatus.inProgress:
return 'DRAFT';
case TaxFormStatus.submitted:
return 'SUBMITTED';
case TaxFormStatus.approved:
return 'APPROVED';
case TaxFormStatus.rejected:
return 'REJECTED';
}
}
}

View File

@@ -1,39 +0,0 @@
import '../../../entities/shifts/break/break.dart';
/// Adapter for Break related data.
class BreakAdapter {
/// Maps break data to a Break entity.
///
/// [isPaid] whether the break is paid.
/// [breakTime] the string representation of the break duration (e.g., 'MIN_10', 'MIN_30').
static Break fromData({
required bool isPaid,
required String? breakTime,
}) {
return Break(
isBreakPaid: isPaid,
duration: _parseDuration(breakTime),
);
}
static BreakDuration _parseDuration(String? breakTime) {
if (breakTime == null) return BreakDuration.none;
switch (breakTime.toUpperCase()) {
case 'MIN_10':
return BreakDuration.ten;
case 'MIN_15':
return BreakDuration.fifteen;
case 'MIN_20':
return BreakDuration.twenty;
case 'MIN_30':
return BreakDuration.thirty;
case 'MIN_45':
return BreakDuration.fortyFive;
case 'MIN_60':
return BreakDuration.sixty;
default:
return BreakDuration.none;
}
}
}

View File

@@ -1,59 +0,0 @@
import 'package:intl/intl.dart';
import '../../entities/shifts/shift.dart';
/// Adapter for Shift related data.
class ShiftAdapter {
/// Maps application data to a Shift entity.
///
/// This method handles the common mapping logic used across different
/// repositories when converting application data from Data Connect to
/// domain Shift entities.
static Shift fromApplicationData({
required String shiftId,
required String roleId,
required String roleName,
required String businessName,
String? companyLogoUrl,
required double costPerHour,
String? shiftLocation,
required String teamHubName,
DateTime? shiftDate,
DateTime? startTime,
DateTime? endTime,
DateTime? createdAt,
required String status,
String? description,
int? durationDays,
required int count,
int? assigned,
String? eventName,
bool hasApplied = false,
}) {
final String orderName = (eventName ?? '').trim().isNotEmpty
? eventName!
: businessName;
final String title = '$roleName - $orderName';
return Shift(
id: shiftId,
roleId: roleId,
title: title,
clientName: businessName,
logoUrl: companyLogoUrl,
hourlyRate: costPerHour,
location: shiftLocation ?? '',
locationAddress: teamHubName,
date: shiftDate?.toIso8601String() ?? '',
startTime: startTime != null ? DateFormat('HH:mm').format(startTime) : '',
endTime: endTime != null ? DateFormat('HH:mm').format(endTime) : '',
createdDate: createdAt?.toIso8601String() ?? '',
status: status,
description: description,
durationDays: durationDays,
requiredSlots: count,
filledSlots: assigned ?? 0,
hasApplied: hasApplied,
);
}
}

View File

@@ -27,4 +27,11 @@ abstract class BaseApiService {
dynamic data,
Map<String, dynamic>? params,
});
/// Performs a DELETE request to the specified [endpoint].
Future<ApiResponse> delete(
String endpoint, {
dynamic data,
Map<String, dynamic>? params,
});
}

View File

@@ -0,0 +1,86 @@
import 'package:equatable/equatable.dart';
import 'package:krow_domain/src/entities/availability/time_slot.dart';
import 'package:krow_domain/src/entities/enums/availability_status.dart';
/// Availability for a single calendar date.
///
/// Returned by `GET /staff/availability`. The backend generates one entry
/// per date in the requested range by projecting the staff member's
/// recurring weekly availability pattern.
class AvailabilityDay extends Equatable {
/// Creates an [AvailabilityDay].
const AvailabilityDay({
required this.date,
required this.dayOfWeek,
required this.availabilityStatus,
this.slots = const <TimeSlot>[],
});
/// Deserialises from the V2 API JSON response.
factory AvailabilityDay.fromJson(Map<String, dynamic> json) {
final dynamic rawSlots = json['slots'];
final List<TimeSlot> parsedSlots = rawSlots is List<dynamic>
? rawSlots
.map((dynamic e) =>
TimeSlot.fromJson(e as Map<String, dynamic>))
.toList()
: <TimeSlot>[];
return AvailabilityDay(
date: json['date'] as String,
dayOfWeek: json['dayOfWeek'] as int,
availabilityStatus:
AvailabilityStatus.fromJson(json['availabilityStatus'] as String?),
slots: parsedSlots,
);
}
/// ISO date string (`YYYY-MM-DD`).
final String date;
/// Day of week (0 = Sunday, 6 = Saturday).
final int dayOfWeek;
/// Availability status for this day.
final AvailabilityStatus availabilityStatus;
/// Time slots when the worker is available (relevant for `PARTIAL`).
final List<TimeSlot> slots;
/// Whether the worker has any availability on this day.
bool get isAvailable => availabilityStatus != AvailabilityStatus.unavailable;
/// Creates a copy with the given fields replaced.
AvailabilityDay copyWith({
String? date,
int? dayOfWeek,
AvailabilityStatus? availabilityStatus,
List<TimeSlot>? slots,
}) {
return AvailabilityDay(
date: date ?? this.date,
dayOfWeek: dayOfWeek ?? this.dayOfWeek,
availabilityStatus: availabilityStatus ?? this.availabilityStatus,
slots: slots ?? this.slots,
);
}
/// Serialises to JSON.
Map<String, dynamic> toJson() {
return <String, dynamic>{
'date': date,
'dayOfWeek': dayOfWeek,
'availabilityStatus': availabilityStatus.toJson(),
'slots': slots.map((TimeSlot s) => s.toJson()).toList(),
};
}
@override
List<Object?> get props => <Object?>[
date,
dayOfWeek,
availabilityStatus,
slots,
];
}

View File

@@ -1,33 +0,0 @@
import 'package:equatable/equatable.dart';
/// Represents a specific time slot within a day (e.g., Morning, Afternoon, Evening).
class AvailabilitySlot extends Equatable {
const AvailabilitySlot({
required this.id,
required this.label,
required this.timeRange,
this.isAvailable = true,
});
final String id;
final String label;
final String timeRange;
final bool isAvailable;
AvailabilitySlot copyWith({
String? id,
String? label,
String? timeRange,
bool? isAvailable,
}) {
return AvailabilitySlot(
id: id ?? this.id,
label: label ?? this.label,
timeRange: timeRange ?? this.timeRange,
isAvailable: isAvailable ?? this.isAvailable,
);
}
@override
List<Object?> get props => <Object?>[id, label, timeRange, isAvailable];
}

View File

@@ -1,31 +0,0 @@
import 'package:equatable/equatable.dart';
import 'availability_slot.dart';
/// Represents availability configuration for a specific date.
class DayAvailability extends Equatable {
const DayAvailability({
required this.date,
this.isAvailable = false,
this.slots = const <AvailabilitySlot>[],
});
final DateTime date;
final bool isAvailable;
final List<AvailabilitySlot> slots;
DayAvailability copyWith({
DateTime? date,
bool? isAvailable,
List<AvailabilitySlot>? slots,
}) {
return DayAvailability(
date: date ?? this.date,
isAvailable: isAvailable ?? this.isAvailable,
slots: slots ?? this.slots,
);
}
@override
List<Object?> get props => <Object?>[date, isAvailable, slots];
}

View File

@@ -0,0 +1,39 @@
import 'package:equatable/equatable.dart';
/// A time range within a day of availability.
///
/// Embedded inside [AvailabilityDay.slots]. Times are stored as `HH:MM`
/// strings because the backend stores them in a JSONB array and they
/// are timezone-agnostic display values.
class TimeSlot extends Equatable {
/// Creates a [TimeSlot].
const TimeSlot({
required this.startTime,
required this.endTime,
});
/// Deserialises from a JSON map inside the availability slots array.
factory TimeSlot.fromJson(Map<String, dynamic> json) {
return TimeSlot(
startTime: json['startTime'] as String? ?? '00:00',
endTime: json['endTime'] as String? ?? '00:00',
);
}
/// Start time in `HH:MM` format.
final String startTime;
/// End time in `HH:MM` format.
final String endTime;
/// Serialises to JSON.
Map<String, dynamic> toJson() {
return <String, dynamic>{
'startTime': startTime,
'endTime': endTime,
};
}
@override
List<Object?> get props => <Object?>[startTime, endTime];
}

View File

@@ -1,26 +1,73 @@
import 'package:equatable/equatable.dart';
/// Represents a staff member's benefit balance.
import 'package:krow_domain/src/entities/enums/benefit_status.dart';
/// A benefit accrued by a staff member (e.g. sick leave, vacation).
///
/// Returned by `GET /staff/profile/benefits`.
class Benefit extends Equatable {
/// Creates a [Benefit].
/// Creates a [Benefit] instance.
const Benefit({
required this.benefitId,
required this.benefitType,
required this.title,
required this.entitlementHours,
required this.usedHours,
required this.status,
required this.trackedHours,
required this.targetHours,
});
/// The title of the benefit (e.g., Sick Leave, Holiday, Vacation).
/// Deserialises a [Benefit] from a V2 API JSON map.
factory Benefit.fromJson(Map<String, dynamic> json) {
return Benefit(
benefitId: json['benefitId'] as String,
benefitType: json['benefitType'] as String,
title: json['title'] as String,
status: BenefitStatus.fromJson(json['status'] as String?),
trackedHours: (json['trackedHours'] as num).toInt(),
targetHours: (json['targetHours'] as num).toInt(),
);
}
/// Unique identifier.
final String benefitId;
/// Type code (e.g. SICK_LEAVE, VACATION).
final String benefitType;
/// Human-readable title.
final String title;
/// The total entitlement in hours.
final double entitlementHours;
/// Current benefit status.
final BenefitStatus status;
/// The hours used so far.
final double usedHours;
/// Hours tracked so far.
final int trackedHours;
/// The hours remaining.
double get remainingHours => entitlementHours - usedHours;
/// Target hours to accrue.
final int targetHours;
/// Remaining hours to reach the target.
int get remainingHours => targetHours - trackedHours;
/// Serialises this [Benefit] to a JSON map.
Map<String, dynamic> toJson() {
return <String, dynamic>{
'benefitId': benefitId,
'benefitType': benefitType,
'title': title,
'status': status.toJson(),
'trackedHours': trackedHours,
'targetHours': targetHours,
};
}
@override
List<Object?> get props => [title, entitlementHours, usedHours];
List<Object?> get props => <Object?>[
benefitId,
benefitType,
title,
status,
trackedHours,
targetHours,
];
}

View File

@@ -1,36 +0,0 @@
import 'package:equatable/equatable.dart';
/// Represents a legal or service contract.
///
/// Can be between a business and the platform, or a business and staff.
class BizContract extends Equatable {
const BizContract({
required this.id,
required this.businessId,
required this.name,
required this.startDate,
this.endDate,
required this.contentUrl,
});
/// Unique identifier.
final String id;
/// The [Business] party to the contract.
final String businessId;
/// Descriptive name of the contract.
final String name;
/// Valid from date.
final DateTime startDate;
/// Valid until date (null if indefinite).
final DateTime? endDate;
/// URL to the document content (PDF/HTML).
final String contentUrl;
@override
List<Object?> get props => <Object?>[id, businessId, name, startDate, endDate, contentUrl];
}

View File

@@ -1,47 +1,111 @@
import 'package:equatable/equatable.dart';
/// The operating status of a [Business].
enum BusinessStatus {
/// Business created but not yet approved.
pending,
import 'package:krow_domain/src/entities/enums/business_status.dart';
/// Fully active and operational.
active,
/// Temporarily suspended (e.g. for non-payment).
suspended,
/// Permanently inactive.
inactive,
}
/// Represents a Client Company / Business.
/// A client company registered on the platform.
///
/// This is the top-level organizational entity in the system.
/// Maps to the V2 `businesses` table.
class Business extends Equatable {
/// Creates a [Business] instance.
const Business({
required this.id,
required this.name,
required this.registrationNumber,
required this.tenantId,
required this.slug,
required this.businessName,
required this.status,
this.avatar,
this.contactName,
this.contactEmail,
this.contactPhone,
this.metadata = const <String, dynamic>{},
this.createdAt,
this.updatedAt,
});
/// Unique identifier for the business.
/// Deserialises a [Business] from a V2 API JSON map.
factory Business.fromJson(Map<String, dynamic> json) {
return Business(
id: json['id'] as String,
tenantId: json['tenantId'] as String,
slug: json['slug'] as String,
businessName: json['businessName'] as String,
status: BusinessStatus.fromJson(json['status'] as String?),
contactName: json['contactName'] as String?,
contactEmail: json['contactEmail'] as String?,
contactPhone: json['contactPhone'] as String?,
metadata: json['metadata'] is Map
? Map<String, dynamic>.from(json['metadata'] as Map<dynamic, dynamic>)
: const <String, dynamic>{},
createdAt: json['createdAt'] != null
? DateTime.parse(json['createdAt'] as String)
: null,
updatedAt: json['updatedAt'] != null
? DateTime.parse(json['updatedAt'] as String)
: null,
);
}
/// Unique identifier.
final String id;
/// Tenant this business belongs to.
final String tenantId;
/// URL-safe slug.
final String slug;
/// Display name of the business.
final String name;
final String businessName;
/// Legal registration or tax number.
final String registrationNumber;
/// Current operating status.
/// Current account status.
final BusinessStatus status;
/// URL to the business logo.
final String? avatar;
/// Primary contact name.
final String? contactName;
/// Primary contact email.
final String? contactEmail;
/// Primary contact phone.
final String? contactPhone;
/// Flexible metadata bag.
final Map<String, dynamic> metadata;
/// When the record was created.
final DateTime? createdAt;
/// When the record was last updated.
final DateTime? updatedAt;
/// Serialises this [Business] to a JSON map.
Map<String, dynamic> toJson() {
return <String, dynamic>{
'id': id,
'tenantId': tenantId,
'slug': slug,
'businessName': businessName,
'status': status.toJson(),
'contactName': contactName,
'contactEmail': contactEmail,
'contactPhone': contactPhone,
'metadata': metadata,
'createdAt': createdAt?.toIso8601String(),
'updatedAt': updatedAt?.toIso8601String(),
};
}
@override
List<Object?> get props => <Object?>[id, name, registrationNumber, status, avatar];
}
List<Object?> get props => <Object?>[
id,
tenantId,
slug,
businessName,
status,
contactName,
contactEmail,
contactPhone,
metadata,
createdAt,
updatedAt,
];
}

View File

@@ -1,41 +0,0 @@
import 'package:equatable/equatable.dart';
/// Represents payroll and operational configuration for a [Business].
class BusinessSetting extends Equatable {
const BusinessSetting({
required this.id,
required this.businessId,
required this.prefix,
required this.overtimeEnabled,
this.clockInRequirement,
this.clockOutRequirement,
});
/// Unique identifier for the settings record.
final String id;
/// The [Business] these settings apply to.
final String businessId;
/// Prefix for generated invoices (e.g., "INV-").
final String prefix;
/// Whether overtime calculations are applied.
final bool overtimeEnabled;
/// Requirement method for clocking in (e.g. "qr_code", "geo_fence").
final String? clockInRequirement;
/// Requirement method for clocking out.
final String? clockOutRequirement;
@override
List<Object?> get props => <Object?>[
id,
businessId,
prefix,
overtimeEnabled,
clockInRequirement,
clockOutRequirement,
];
}

View File

@@ -1,22 +1,37 @@
import 'package:equatable/equatable.dart';
/// Represents a financial cost center used for billing and tracking.
/// A financial cost center used for billing and tracking.
///
/// Returned by `GET /client/cost-centers`.
class CostCenter extends Equatable {
/// Creates a [CostCenter] instance.
const CostCenter({
required this.id,
required this.costCenterId,
required this.name,
this.code,
});
/// Unique identifier.
final String id;
/// Deserialises a [CostCenter] from a V2 API JSON map.
factory CostCenter.fromJson(Map<String, dynamic> json) {
return CostCenter(
costCenterId: json['costCenterId'] as String,
name: json['name'] as String,
);
}
/// Display name of the cost center.
/// Unique identifier.
final String costCenterId;
/// Display name.
final String name;
/// Optional alphanumeric code associated with this cost center.
final String? code;
/// Serialises this [CostCenter] to a JSON map.
Map<String, dynamic> toJson() {
return <String, dynamic>{
'costCenterId': costCenterId,
'name': name,
};
}
@override
List<Object?> get props => <Object?>[id, name, code];
List<Object?> get props => <Object?>[costCenterId, name];
}

View File

@@ -1,51 +1,107 @@
import 'package:equatable/equatable.dart';
import 'cost_center.dart';
/// The status of a [Hub].
enum HubStatus {
/// Fully operational.
active,
/// Closed or inactive.
inactive,
/// Not yet ready for operations.
underConstruction,
}
/// Represents a branch location or operational unit within a [Business].
/// A physical clock-point location (hub) belonging to a business.
///
/// Maps to the V2 `clock_points` table; returned by `GET /client/hubs`.
class Hub extends Equatable {
/// Creates a [Hub] instance.
const Hub({
required this.id,
required this.businessId,
required this.hubId,
required this.name,
required this.address,
this.fullAddress,
this.latitude,
this.longitude,
this.nfcTagId,
required this.status,
this.costCenter,
this.city,
this.state,
this.zipCode,
this.costCenterId,
this.costCenterName,
});
/// Unique identifier.
final String id;
/// The parent [Business].
final String businessId;
/// Deserialises a [Hub] from a V2 API JSON map.
factory Hub.fromJson(Map<String, dynamic> json) {
return Hub(
hubId: json['hubId'] as String,
name: json['name'] as String,
fullAddress: json['fullAddress'] as String?,
latitude: json['latitude'] != null
? double.parse(json['latitude'].toString())
: null,
longitude: json['longitude'] != null
? double.parse(json['longitude'].toString())
: null,
nfcTagId: json['nfcTagId'] as String?,
city: json['city'] as String?,
state: json['state'] as String?,
zipCode: json['zipCode'] as String?,
costCenterId: json['costCenterId'] as String?,
costCenterName: json['costCenterName'] as String?,
);
}
/// Display name of the hub (e.g. "Downtown Branch").
/// Unique identifier (clock_point id).
final String hubId;
/// Display label for the hub.
final String name;
/// Physical address of this hub.
final String address;
/// Full street address.
final String? fullAddress;
/// Unique identifier of the NFC tag assigned to this hub.
/// GPS latitude.
final double? latitude;
/// GPS longitude.
final double? longitude;
/// NFC tag UID assigned to this hub.
final String? nfcTagId;
/// Operational status.
final HubStatus status;
/// City from metadata.
final String? city;
/// Assigned cost center for this hub.
final CostCenter? costCenter;
/// State from metadata.
final String? state;
/// Zip code from metadata.
final String? zipCode;
/// Associated cost center ID.
final String? costCenterId;
/// Associated cost center name.
final String? costCenterName;
/// Serialises this [Hub] to a JSON map.
Map<String, dynamic> toJson() {
return <String, dynamic>{
'hubId': hubId,
'name': name,
'fullAddress': fullAddress,
'latitude': latitude,
'longitude': longitude,
'nfcTagId': nfcTagId,
'city': city,
'state': state,
'zipCode': zipCode,
'costCenterId': costCenterId,
'costCenterName': costCenterName,
};
}
@override
List<Object?> get props => <Object?>[id, businessId, name, address, nfcTagId, status, costCenter];
List<Object?> get props => <Object?>[
hubId,
name,
fullAddress,
latitude,
longitude,
nfcTagId,
city,
state,
zipCode,
costCenterId,
costCenterName,
];
}

View File

@@ -1,24 +0,0 @@
import 'package:equatable/equatable.dart';
/// Represents a department within a [Hub].
///
/// Used for more granular organization of staff and events (e.g. "Kitchen", "Service").
class HubDepartment extends Equatable {
const HubDepartment({
required this.id,
required this.hubId,
required this.name,
});
/// Unique identifier.
final String id;
/// The [Hub] this department belongs to.
final String hubId;
/// Name of the department.
final String name;
@override
List<Object?> get props => <Object?>[id, hubId, name];
}

View File

@@ -0,0 +1,54 @@
import 'package:equatable/equatable.dart';
/// A manager assigned to a hub (clock point).
///
/// Returned by `GET /client/hubs/:id/managers`.
class HubManager extends Equatable {
/// Creates a [HubManager] instance.
const HubManager({
required this.managerAssignmentId,
required this.businessMembershipId,
required this.managerId,
required this.name,
});
/// Deserialises a [HubManager] from a V2 API JSON map.
factory HubManager.fromJson(Map<String, dynamic> json) {
return HubManager(
managerAssignmentId: json['managerAssignmentId'] as String,
businessMembershipId: json['businessMembershipId'] as String,
managerId: json['managerId'] as String,
name: json['name'] as String,
);
}
/// Primary key of the hub_managers row.
final String managerAssignmentId;
/// Business membership ID of the manager.
final String businessMembershipId;
/// User ID of the manager.
final String managerId;
/// Display name of the manager.
final String name;
/// Serialises this [HubManager] to a JSON map.
Map<String, dynamic> toJson() {
return <String, dynamic>{
'managerAssignmentId': managerAssignmentId,
'businessMembershipId': businessMembershipId,
'managerId': managerId,
'name': name,
};
}
@override
List<Object?> get props => <Object?>[
managerAssignmentId,
businessMembershipId,
managerId,
name,
];
}

View File

@@ -0,0 +1,61 @@
import 'package:equatable/equatable.dart';
/// A member of a business team (business membership + user).
///
/// Returned by `GET /client/team-members`.
class TeamMember extends Equatable {
/// Creates a [TeamMember] instance.
const TeamMember({
required this.businessMembershipId,
required this.userId,
required this.name,
this.email,
this.role,
});
/// Deserialises a [TeamMember] from a V2 API JSON map.
factory TeamMember.fromJson(Map<String, dynamic> json) {
return TeamMember(
businessMembershipId: json['businessMembershipId'] as String,
userId: json['userId'] as String,
name: json['name'] as String,
email: json['email'] as String?,
role: json['role'] as String?,
);
}
/// Business membership primary key.
final String businessMembershipId;
/// User ID.
final String userId;
/// Display name.
final String name;
/// Email address.
final String? email;
/// Business role (owner, manager, member, viewer).
final String? role;
/// Serialises this [TeamMember] to a JSON map.
Map<String, dynamic> toJson() {
return <String, dynamic>{
'businessMembershipId': businessMembershipId,
'userId': userId,
'name': name,
'email': email,
'role': role,
};
}
@override
List<Object?> get props => <Object?>[
businessMembershipId,
userId,
name,
email,
role,
];
}

View File

@@ -1,15 +1,86 @@
import 'package:equatable/equatable.dart';
/// Represents a staffing vendor.
import 'package:krow_domain/src/entities/enums/business_status.dart';
/// A staffing vendor that supplies workers to businesses.
///
/// Maps to the V2 `vendors` table.
class Vendor extends Equatable {
const Vendor({required this.id, required this.name, required this.rates});
/// Creates a [Vendor] instance.
const Vendor({
required this.id,
required this.tenantId,
required this.slug,
required this.companyName,
required this.status,
this.contactName,
this.contactEmail,
this.contactPhone,
});
/// Deserialises a [Vendor] from a V2 API JSON map.
factory Vendor.fromJson(Map<String, dynamic> json) {
return Vendor(
id: json['id'] as String? ?? json['vendorId'] as String,
tenantId: json['tenantId'] as String? ?? '',
slug: json['slug'] as String? ?? '',
companyName: json['companyName'] as String? ??
json['vendorName'] as String? ??
'',
status: BusinessStatus.fromJson(json['status'] as String?),
contactName: json['contactName'] as String?,
contactEmail: json['contactEmail'] as String?,
contactPhone: json['contactPhone'] as String?,
);
}
/// Unique identifier.
final String id;
final String name;
/// A map of role names to hourly rates.
final Map<String, double> rates;
/// Tenant this vendor belongs to.
final String tenantId;
/// URL-safe slug.
final String slug;
/// Display name of the vendor company.
final String companyName;
/// Current account status.
final BusinessStatus status;
/// Primary contact name.
final String? contactName;
/// Primary contact email.
final String? contactEmail;
/// Primary contact phone.
final String? contactPhone;
/// Serialises this [Vendor] to a JSON map.
Map<String, dynamic> toJson() {
return <String, dynamic>{
'id': id,
'tenantId': tenantId,
'slug': slug,
'companyName': companyName,
'status': status.toJson(),
'contactName': contactName,
'contactEmail': contactEmail,
'contactPhone': contactPhone,
};
}
@override
List<Object?> get props => <Object?>[id, name, rates];
List<Object?> get props => <Object?>[
id,
tenantId,
slug,
companyName,
status,
contactName,
contactEmail,
contactPhone,
];
}

View File

@@ -0,0 +1,49 @@
import 'package:equatable/equatable.dart';
/// A role available through a vendor with its billing rate.
///
/// Returned by `GET /client/vendors/:id/roles`.
class VendorRole extends Equatable {
/// Creates a [VendorRole] instance.
const VendorRole({
required this.roleId,
required this.roleCode,
required this.roleName,
required this.hourlyRateCents,
});
/// Deserialises a [VendorRole] from a V2 API JSON map.
factory VendorRole.fromJson(Map<String, dynamic> json) {
return VendorRole(
roleId: json['roleId'] as String,
roleCode: json['roleCode'] as String,
roleName: json['roleName'] as String,
hourlyRateCents: (json['hourlyRateCents'] as num).toInt(),
);
}
/// Unique identifier from the roles catalog.
final String roleId;
/// Short code for the role (e.g. BARISTA).
final String roleCode;
/// Human-readable role name.
final String roleName;
/// Billing rate in cents per hour.
final int hourlyRateCents;
/// Serialises this [VendorRole] to a JSON map.
Map<String, dynamic> toJson() {
return <String, dynamic>{
'roleId': roleId,
'roleCode': roleCode,
'roleName': roleName,
'hourlyRateCents': hourlyRateCents,
};
}
@override
List<Object?> get props => <Object?>[roleId, roleCode, roleName, hourlyRateCents];
}

View File

@@ -1,27 +1,56 @@
import 'package:equatable/equatable.dart';
/// Simple entity to hold attendance state
class AttendanceStatus extends Equatable {
import 'package:krow_domain/src/entities/enums/attendance_status_type.dart';
/// Current clock-in / attendance status of the staff member.
///
/// Returned by `GET /staff/clock-in/status`. When no open session exists
/// the API returns `attendanceStatus: 'NOT_CLOCKED_IN'` with null IDs.
class AttendanceStatus extends Equatable {
/// Creates an [AttendanceStatus].
const AttendanceStatus({
this.isCheckedIn = false,
this.checkInTime,
this.checkOutTime,
this.activeShiftId,
this.activeApplicationId,
required this.attendanceStatus,
this.clockInAt,
});
final bool isCheckedIn;
final DateTime? checkInTime;
final DateTime? checkOutTime;
/// Deserialises from the V2 API JSON response.
factory AttendanceStatus.fromJson(Map<String, dynamic> json) {
return AttendanceStatus(
activeShiftId: json['activeShiftId'] as String?,
attendanceStatus:
AttendanceStatusType.fromJson(json['attendanceStatus'] as String?),
clockInAt: json['clockInAt'] != null
? DateTime.parse(json['clockInAt'] as String)
: null,
);
}
/// The shift id of the currently active attendance session, if any.
final String? activeShiftId;
final String? activeApplicationId;
/// Attendance session status.
final AttendanceStatusType attendanceStatus;
/// Timestamp of clock-in, if currently clocked in.
final DateTime? clockInAt;
/// Whether the worker is currently clocked in.
bool get isClockedIn => attendanceStatus == AttendanceStatusType.open;
/// Serialises to JSON.
Map<String, dynamic> toJson() {
return <String, dynamic>{
'activeShiftId': activeShiftId,
'attendanceStatus': attendanceStatus.toJson(),
'clockInAt': clockInAt?.toIso8601String(),
};
}
@override
List<Object?> get props => <Object?>[
isCheckedIn,
checkInTime,
checkOutTime,
activeShiftId,
activeApplicationId,
attendanceStatus,
clockInAt,
];
}

View File

@@ -0,0 +1,65 @@
import 'package:equatable/equatable.dart';
import 'package:krow_domain/src/entities/enums/assignment_status.dart';
/// A worker assigned to a coverage shift.
///
/// Nested within [ShiftWithWorkers].
class AssignedWorker extends Equatable {
/// Creates an [AssignedWorker] instance.
const AssignedWorker({
required this.assignmentId,
required this.staffId,
required this.fullName,
required this.status,
this.checkInAt,
});
/// Deserialises an [AssignedWorker] from a V2 API JSON map.
factory AssignedWorker.fromJson(Map<String, dynamic> json) {
return AssignedWorker(
assignmentId: json['assignmentId'] as String,
staffId: json['staffId'] as String,
fullName: json['fullName'] as String,
status: AssignmentStatus.fromJson(json['status'] as String?),
checkInAt: json['checkInAt'] != null
? DateTime.parse(json['checkInAt'] as String)
: null,
);
}
/// Assignment ID.
final String assignmentId;
/// Staff member ID.
final String staffId;
/// Worker display name.
final String fullName;
/// Assignment status.
final AssignmentStatus status;
/// When the worker clocked in (null if not yet).
final DateTime? checkInAt;
/// Serialises this [AssignedWorker] to a JSON map.
Map<String, dynamic> toJson() {
return <String, dynamic>{
'assignmentId': assignmentId,
'staffId': staffId,
'fullName': fullName,
'status': status.toJson(),
'checkInAt': checkInAt?.toIso8601String(),
};
}
@override
List<Object?> get props => <Object?>[
assignmentId,
staffId,
fullName,
status,
checkInAt,
];
}

View File

@@ -0,0 +1,68 @@
import 'package:equatable/equatable.dart';
/// A staff member on the business's core team (favorites).
///
/// Returned by `GET /client/coverage/core-team`.
class CoreTeamMember extends Equatable {
/// Creates a [CoreTeamMember] instance.
const CoreTeamMember({
required this.staffId,
required this.fullName,
this.primaryRole,
required this.averageRating,
required this.ratingCount,
required this.favorite,
});
/// Deserialises a [CoreTeamMember] from a V2 API JSON map.
factory CoreTeamMember.fromJson(Map<String, dynamic> json) {
return CoreTeamMember(
staffId: json['staffId'] as String,
fullName: json['fullName'] as String,
primaryRole: json['primaryRole'] as String?,
averageRating: (json['averageRating'] as num).toDouble(),
ratingCount: (json['ratingCount'] as num).toInt(),
favorite: json['favorite'] as bool? ?? true,
);
}
/// Staff member ID.
final String staffId;
/// Display name.
final String fullName;
/// Primary role code.
final String? primaryRole;
/// Average review rating (0-5).
final double averageRating;
/// Total number of reviews.
final int ratingCount;
/// Whether this staff is favorited by the business.
final bool favorite;
/// Serialises this [CoreTeamMember] to a JSON map.
Map<String, dynamic> toJson() {
return <String, dynamic>{
'staffId': staffId,
'fullName': fullName,
'primaryRole': primaryRole,
'averageRating': averageRating,
'ratingCount': ratingCount,
'favorite': favorite,
};
}
@override
List<Object?> get props => <Object?>[
staffId,
fullName,
primaryRole,
averageRating,
ratingCount,
favorite,
];
}

View File

@@ -1,57 +0,0 @@
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<CoverageWorker> workers;
/// Calculates the coverage percentage for this shift.
int get coveragePercent {
if (workersNeeded == 0) return 0;
return ((workers.length / workersNeeded) * 100).round();
}
@override
List<Object?> get props => <Object?>[
id,
title,
location,
startTime,
workersNeeded,
date,
workers,
];
}

View File

@@ -1,45 +1,69 @@
import 'package:equatable/equatable.dart';
/// Domain entity representing coverage statistics.
/// Aggregated coverage statistics for a specific date.
///
/// Aggregates coverage metrics for a specific date.
/// Returned by `GET /client/coverage/stats`.
class CoverageStats extends Equatable {
/// Creates a [CoverageStats].
/// Creates a [CoverageStats] instance.
const CoverageStats({
required this.totalNeeded,
required this.totalConfirmed,
required this.checkedIn,
required this.enRoute,
required this.late,
required this.totalPositionsNeeded,
required this.totalPositionsConfirmed,
required this.totalWorkersCheckedIn,
required this.totalWorkersEnRoute,
required this.totalWorkersLate,
required this.totalCoveragePercentage,
});
/// The total number of workers needed.
final int totalNeeded;
/// Deserialises a [CoverageStats] from a V2 API JSON map.
factory CoverageStats.fromJson(Map<String, dynamic> json) {
return CoverageStats(
totalPositionsNeeded: (json['totalPositionsNeeded'] as num).toInt(),
totalPositionsConfirmed: (json['totalPositionsConfirmed'] as num).toInt(),
totalWorkersCheckedIn: (json['totalWorkersCheckedIn'] as num).toInt(),
totalWorkersEnRoute: (json['totalWorkersEnRoute'] as num).toInt(),
totalWorkersLate: (json['totalWorkersLate'] as num).toInt(),
totalCoveragePercentage:
(json['totalCoveragePercentage'] as num).toInt(),
);
}
/// The total number of confirmed workers.
final int totalConfirmed;
/// Total positions that need to be filled.
final int totalPositionsNeeded;
/// The number of workers who have checked in.
final int checkedIn;
/// Total positions that have been confirmed.
final int totalPositionsConfirmed;
/// The number of workers en route.
final int enRoute;
/// Workers who have checked in.
final int totalWorkersCheckedIn;
/// The number of late workers.
final int late;
/// Workers en route (accepted but not checked in).
final int totalWorkersEnRoute;
/// Calculates the overall coverage percentage.
int get coveragePercent {
if (totalNeeded == 0) return 0;
return ((totalConfirmed / totalNeeded) * 100).round();
/// Workers marked as late / no-show.
final int totalWorkersLate;
/// Overall coverage percentage (0-100).
final int totalCoveragePercentage;
/// Serialises this [CoverageStats] to a JSON map.
Map<String, dynamic> toJson() {
return <String, dynamic>{
'totalPositionsNeeded': totalPositionsNeeded,
'totalPositionsConfirmed': totalPositionsConfirmed,
'totalWorkersCheckedIn': totalWorkersCheckedIn,
'totalWorkersEnRoute': totalWorkersEnRoute,
'totalWorkersLate': totalWorkersLate,
'totalCoveragePercentage': totalCoveragePercentage,
};
}
@override
List<Object?> get props => <Object?>[
totalNeeded,
totalConfirmed,
checkedIn,
enRoute,
late,
totalPositionsNeeded,
totalPositionsConfirmed,
totalWorkersCheckedIn,
totalWorkersEnRoute,
totalWorkersLate,
totalCoveragePercentage,
];
}

View File

@@ -1,55 +0,0 @@
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<Object?> get props => <Object?>[name, status, checkInTime];
}

View File

@@ -0,0 +1,80 @@
import 'package:equatable/equatable.dart';
import 'assigned_worker.dart';
import 'time_range.dart';
/// A shift in the coverage view with its assigned workers.
///
/// Returned by `GET /client/coverage`.
class ShiftWithWorkers extends Equatable {
/// Creates a [ShiftWithWorkers] instance.
const ShiftWithWorkers({
required this.shiftId,
required this.roleName,
required this.timeRange,
required this.requiredWorkerCount,
required this.assignedWorkerCount,
this.assignedWorkers = const <AssignedWorker>[],
});
/// Deserialises a [ShiftWithWorkers] from a V2 API JSON map.
factory ShiftWithWorkers.fromJson(Map<String, dynamic> json) {
final dynamic workersRaw = json['assignedWorkers'];
final List<AssignedWorker> workersList = workersRaw is List
? workersRaw
.map((dynamic e) =>
AssignedWorker.fromJson(e as Map<String, dynamic>))
.toList()
: const <AssignedWorker>[];
return ShiftWithWorkers(
shiftId: json['shiftId'] as String,
roleName: json['roleName'] as String? ?? '',
timeRange: TimeRange.fromJson(json['timeRange'] as Map<String, dynamic>),
requiredWorkerCount: (json['requiredWorkerCount'] as num).toInt(),
assignedWorkerCount: (json['assignedWorkerCount'] as num).toInt(),
assignedWorkers: workersList,
);
}
/// Shift ID.
final String shiftId;
/// Role name for this shift.
final String roleName;
/// Start and end time range.
final TimeRange timeRange;
/// Total workers required.
final int requiredWorkerCount;
/// Workers currently assigned.
final int assignedWorkerCount;
/// List of assigned workers with their statuses.
final List<AssignedWorker> assignedWorkers;
/// Serialises this [ShiftWithWorkers] to a JSON map.
Map<String, dynamic> toJson() {
return <String, dynamic>{
'shiftId': shiftId,
'roleName': roleName,
'timeRange': timeRange.toJson(),
'requiredWorkerCount': requiredWorkerCount,
'assignedWorkerCount': assignedWorkerCount,
'assignedWorkers':
assignedWorkers.map((AssignedWorker w) => w.toJson()).toList(),
};
}
@override
List<Object?> get props => <Object?>[
shiftId,
roleName,
timeRange,
requiredWorkerCount,
assignedWorkerCount,
assignedWorkers,
];
}

View File

@@ -0,0 +1,37 @@
import 'package:equatable/equatable.dart';
/// A time range with start and end timestamps.
///
/// Used within [ShiftWithWorkers] for shift time windows.
class TimeRange extends Equatable {
/// Creates a [TimeRange] instance.
const TimeRange({
required this.startsAt,
required this.endsAt,
});
/// Deserialises a [TimeRange] from a V2 API JSON map.
factory TimeRange.fromJson(Map<String, dynamic> json) {
return TimeRange(
startsAt: DateTime.parse(json['startsAt'] as String),
endsAt: DateTime.parse(json['endsAt'] as String),
);
}
/// Start timestamp.
final DateTime startsAt;
/// End timestamp.
final DateTime endsAt;
/// Serialises this [TimeRange] to a JSON map.
Map<String, dynamic> toJson() {
return <String, dynamic>{
'startsAt': startsAt.toIso8601String(),
'endsAt': endsAt.toIso8601String(),
};
}
@override
List<Object?> get props => <Object?>[startsAt, endsAt];
}

View File

@@ -0,0 +1,30 @@
/// Type of bank account.
///
/// Used by both staff bank accounts and client billing accounts.
enum AccountType {
/// Checking account.
checking('CHECKING'),
/// Savings account.
savings('SAVINGS'),
/// Fallback for unrecognised API values.
unknown('UNKNOWN');
const AccountType(this.value);
/// The V2 API string representation.
final String value;
/// Deserialises from a V2 API string with safe fallback.
static AccountType fromJson(String? value) {
if (value == null) return AccountType.checking;
for (final AccountType type in AccountType.values) {
if (type.value == value) return type;
}
return AccountType.unknown;
}
/// Serialises to the V2 API string.
String toJson() => value;
}

View File

@@ -0,0 +1,48 @@
/// Status of a worker's application to a shift.
///
/// Maps to the `status` CHECK constraint in the V2 `applications` table.
enum ApplicationStatus {
/// Application submitted, awaiting review.
pending('PENDING'),
/// Application confirmed / approved.
confirmed('CONFIRMED'),
/// Worker has checked in for the shift.
checkedIn('CHECKED_IN'),
/// Worker is late for check-in.
late_('LATE'),
/// Worker did not show up.
noShow('NO_SHOW'),
/// Application / attendance completed.
completed('COMPLETED'),
/// Application rejected.
rejected('REJECTED'),
/// Application cancelled.
cancelled('CANCELLED'),
/// Fallback for unrecognised API values.
unknown('UNKNOWN');
const ApplicationStatus(this.value);
/// The V2 API string representation.
final String value;
/// Deserialises from a V2 API string with safe fallback.
static ApplicationStatus fromJson(String? value) {
if (value == null) return ApplicationStatus.unknown;
for (final ApplicationStatus status in ApplicationStatus.values) {
if (status.value == value) return status;
}
return ApplicationStatus.unknown;
}
/// Serialises to the V2 API string.
String toJson() => value;
}

View File

@@ -0,0 +1,48 @@
/// Status of a worker's assignment to a shift.
///
/// Maps to the `status` CHECK constraint in the V2 `assignments` table.
enum AssignmentStatus {
/// Worker has been assigned but not yet accepted.
assigned('ASSIGNED'),
/// Worker accepted the assignment.
accepted('ACCEPTED'),
/// Worker requested to swap this assignment.
swapRequested('SWAP_REQUESTED'),
/// Worker has checked in.
checkedIn('CHECKED_IN'),
/// Worker has checked out.
checkedOut('CHECKED_OUT'),
/// Assignment completed.
completed('COMPLETED'),
/// Assignment cancelled.
cancelled('CANCELLED'),
/// Worker did not show up.
noShow('NO_SHOW'),
/// Fallback for unrecognised API values.
unknown('UNKNOWN');
const AssignmentStatus(this.value);
/// The V2 API string representation.
final String value;
/// Deserialises from a V2 API string with safe fallback.
static AssignmentStatus fromJson(String? value) {
if (value == null) return AssignmentStatus.unknown;
for (final AssignmentStatus status in AssignmentStatus.values) {
if (status.value == value) return status;
}
return AssignmentStatus.unknown;
}
/// Serialises to the V2 API string.
String toJson() => value;
}

View File

@@ -0,0 +1,36 @@
/// Attendance session status for clock-in tracking.
///
/// Maps to the `status` CHECK constraint in the V2 `attendance_events` table.
enum AttendanceStatusType {
/// Worker has not clocked in yet.
notClockedIn('NOT_CLOCKED_IN'),
/// Attendance session is open (worker is clocked in).
open('OPEN'),
/// Attendance session is closed (worker clocked out).
closed('CLOSED'),
/// Attendance record is disputed.
disputed('DISPUTED'),
/// Fallback for unrecognised API values.
unknown('UNKNOWN');
const AttendanceStatusType(this.value);
/// The V2 API string representation.
final String value;
/// Deserialises from a V2 API string with safe fallback.
static AttendanceStatusType fromJson(String? value) {
if (value == null) return AttendanceStatusType.notClockedIn;
for (final AttendanceStatusType status in AttendanceStatusType.values) {
if (status.value == value) return status;
}
return AttendanceStatusType.unknown;
}
/// Serialises to the V2 API string.
String toJson() => value;
}

View File

@@ -0,0 +1,34 @@
/// Availability status for a calendar day.
///
/// Used by the staff availability feature to indicate whether a worker
/// is available on a given date.
enum AvailabilityStatus {
/// Worker is available for the full day.
available('AVAILABLE'),
/// Worker is not available.
unavailable('UNAVAILABLE'),
/// Worker is available for partial time slots.
partial('PARTIAL'),
/// Fallback for unrecognised API values.
unknown('UNKNOWN');
const AvailabilityStatus(this.value);
/// The V2 API string representation.
final String value;
/// Deserialises from a V2 API string with safe fallback.
static AvailabilityStatus fromJson(String? value) {
if (value == null) return AvailabilityStatus.unavailable;
for (final AvailabilityStatus status in AvailabilityStatus.values) {
if (status.value == value) return status;
}
return AvailabilityStatus.unknown;
}
/// Serialises to the V2 API string.
String toJson() => value;
}

View File

@@ -0,0 +1,34 @@
/// Status of a staff benefit accrual.
///
/// Used by the benefits feature to track whether a benefit is currently
/// active, paused, or pending activation.
enum BenefitStatus {
/// Benefit is active and accruing.
active('ACTIVE'),
/// Benefit is inactive / paused.
inactive('INACTIVE'),
/// Benefit is pending activation.
pending('PENDING'),
/// Fallback for unrecognised API values.
unknown('UNKNOWN');
const BenefitStatus(this.value);
/// The V2 API string representation.
final String value;
/// Deserialises from a V2 API string with safe fallback.
static BenefitStatus fromJson(String? value) {
if (value == null) return BenefitStatus.unknown;
for (final BenefitStatus status in BenefitStatus.values) {
if (status.value == value) return status;
}
return BenefitStatus.unknown;
}
/// Serialises to the V2 API string.
String toJson() => value;
}

View File

@@ -0,0 +1,34 @@
/// Account status of a business or vendor.
///
/// Maps to the `status` CHECK constraint in the V2 `businesses` and
/// `vendors` tables.
enum BusinessStatus {
/// Account is active.
active('ACTIVE'),
/// Account is inactive / suspended.
inactive('INACTIVE'),
/// Account has been archived.
archived('ARCHIVED'),
/// Fallback for unrecognised API values.
unknown('UNKNOWN');
const BusinessStatus(this.value);
/// The V2 API string representation.
final String value;
/// Deserialises from a V2 API string with safe fallback.
static BusinessStatus fromJson(String? value) {
if (value == null) return BusinessStatus.unknown;
for (final BusinessStatus status in BusinessStatus.values) {
if (status.value == value) return status;
}
return BusinessStatus.unknown;
}
/// Serialises to the V2 API string.
String toJson() => value;
}

View File

@@ -0,0 +1,48 @@
/// Lifecycle status of an invoice.
///
/// Maps to the `status` CHECK constraint in the V2 `invoices` table.
enum InvoiceStatus {
/// Invoice created but not yet sent.
draft('DRAFT'),
/// Invoice sent, awaiting payment.
pending('PENDING'),
/// Invoice under review.
pendingReview('PENDING_REVIEW'),
/// Invoice approved for payment.
approved('APPROVED'),
/// Invoice paid.
paid('PAID'),
/// Invoice overdue.
overdue('OVERDUE'),
/// Invoice disputed by the client.
disputed('DISPUTED'),
/// Invoice voided / cancelled.
void_('VOID'),
/// Fallback for unrecognised API values.
unknown('UNKNOWN');
const InvoiceStatus(this.value);
/// The V2 API string representation.
final String value;
/// Deserialises from a V2 API string with safe fallback.
static InvoiceStatus fromJson(String? value) {
if (value == null) return InvoiceStatus.unknown;
for (final InvoiceStatus status in InvoiceStatus.values) {
if (status.value == value) return status;
}
return InvoiceStatus.unknown;
}
/// Serialises to the V2 API string.
String toJson() => value;
}

View File

@@ -0,0 +1,33 @@
/// Onboarding progress status for a staff member.
///
/// Maps to the `onboarding_status` CHECK constraint in the V2 `staffs` table.
enum OnboardingStatus {
/// Onboarding not yet started.
pending('PENDING'),
/// Onboarding in progress.
inProgress('IN_PROGRESS'),
/// Onboarding completed.
completed('COMPLETED'),
/// Fallback for unrecognised API values.
unknown('UNKNOWN');
const OnboardingStatus(this.value);
/// The V2 API string representation.
final String value;
/// Deserialises from a V2 API string with safe fallback.
static OnboardingStatus fromJson(String? value) {
if (value == null) return OnboardingStatus.unknown;
for (final OnboardingStatus status in OnboardingStatus.values) {
if (status.value == value) return status;
}
return OnboardingStatus.unknown;
}
/// Serialises to the V2 API string.
String toJson() => value;
}

View File

@@ -0,0 +1,36 @@
/// Type of order placed by a business client.
///
/// Maps to the `order_type` CHECK constraint in the V2 `orders` table.
enum OrderType {
/// A single occurrence order.
oneTime('ONE_TIME'),
/// A recurring/repeating order.
recurring('RECURRING'),
/// A permanent/ongoing order.
permanent('PERMANENT'),
/// A rapid-fill order.
rapid('RAPID'),
/// Fallback for unrecognised API values.
unknown('UNKNOWN');
const OrderType(this.value);
/// The V2 API string representation.
final String value;
/// Deserialises from a V2 API string with safe fallback.
static OrderType fromJson(String? value) {
if (value == null) return OrderType.unknown;
for (final OrderType type in OrderType.values) {
if (type.value == value) return type;
}
return OrderType.unknown;
}
/// Serialises to the V2 API string.
String toJson() => value;
}

View File

@@ -0,0 +1,36 @@
/// Payment processing status.
///
/// Maps to the `payment_status` CHECK constraint in the V2 schema.
enum PaymentStatus {
/// Payment not yet processed.
pending('PENDING'),
/// Payment is being processed.
processing('PROCESSING'),
/// Payment completed successfully.
paid('PAID'),
/// Payment processing failed.
failed('FAILED'),
/// Fallback for unrecognised API values.
unknown('UNKNOWN');
const PaymentStatus(this.value);
/// The V2 API string representation.
final String value;
/// Deserialises from a V2 API string with safe fallback.
static PaymentStatus fromJson(String? value) {
if (value == null) return PaymentStatus.unknown;
for (final PaymentStatus status in PaymentStatus.values) {
if (status.value == value) return status;
}
return PaymentStatus.unknown;
}
/// Serialises to the V2 API string.
String toJson() => value;
}

View File

@@ -0,0 +1,45 @@
/// Lifecycle status of a shift.
///
/// Maps to the `status` CHECK constraint in the V2 `shifts` table.
enum ShiftStatus {
/// Shift created but not yet published.
draft('DRAFT'),
/// Open for applications.
open('OPEN'),
/// Awaiting worker confirmation.
pendingConfirmation('PENDING_CONFIRMATION'),
/// All roles filled and confirmed.
assigned('ASSIGNED'),
/// Currently in progress.
active('ACTIVE'),
/// Shift finished.
completed('COMPLETED'),
/// Shift cancelled.
cancelled('CANCELLED'),
/// Fallback for unrecognised API values.
unknown('UNKNOWN');
const ShiftStatus(this.value);
/// The V2 API string representation.
final String value;
/// Deserialises from a V2 API string with safe fallback.
static ShiftStatus fromJson(String? value) {
if (value == null) return ShiftStatus.unknown;
for (final ShiftStatus status in ShiftStatus.values) {
if (status.value == value) return status;
}
return ShiftStatus.unknown;
}
/// Serialises to the V2 API string.
String toJson() => value;
}

View File

@@ -0,0 +1,36 @@
/// Account status of a staff member.
///
/// Maps to the `status` CHECK constraint in the V2 `staffs` table.
enum StaffStatus {
/// Staff account is active.
active('ACTIVE'),
/// Staff has been invited but not yet onboarded.
invited('INVITED'),
/// Staff account is inactive / suspended.
inactive('INACTIVE'),
/// Staff account has been blocked.
blocked('BLOCKED'),
/// Fallback for unrecognised API values.
unknown('UNKNOWN');
const StaffStatus(this.value);
/// The V2 API string representation.
final String value;
/// Deserialises from a V2 API string with safe fallback.
static StaffStatus fromJson(String? value) {
if (value == null) return StaffStatus.unknown;
for (final StaffStatus status in StaffStatus.values) {
if (status.value == value) return status;
}
return StaffStatus.unknown;
}
/// Serialises to the V2 API string.
String toJson() => value;
}

View File

@@ -1,58 +0,0 @@
import 'package:equatable/equatable.dart';
/// The status of a staff [Assignment].
enum AssignmentStatus {
/// Staff member has been assigned but hasn't confirmed.
assigned,
/// Staff member has accepted the assignment.
confirmed,
/// Work is currently in progress (Clocked In).
ongoing,
/// Work completed successfully (Clocked Out).
completed,
/// Staff rejected the assignment offer.
declinedByStaff,
/// Staff canceled after accepting.
canceledByStaff,
/// Staff did not show up.
noShowed,
}
/// Represents the link between a [Staff] member and an [EventShiftPosition].
class Assignment extends Equatable {
const Assignment({
required this.id,
required this.positionId,
required this.staffId,
required this.status,
this.clockIn,
this.clockOut,
});
/// Unique identifier.
final String id;
/// The job position being filled.
final String positionId;
/// The staff member filling the position.
final String staffId;
/// Current status of the assignment.
final AssignmentStatus status;
/// Actual timestamp when staff clocked in.
final DateTime? clockIn;
/// Actual timestamp when staff clocked out.
final DateTime? clockOut;
@override
List<Object?> get props => <Object?>[id, positionId, staffId, status, clockIn, clockOut];
}

View File

@@ -1,70 +0,0 @@
import 'package:equatable/equatable.dart';
/// The workflow status of an [Event].
enum EventStatus {
/// Created but incomplete.
draft,
/// Waiting for approval or publication.
pending,
/// Published and staff have been assigned.
assigned,
/// Fully confirmed and ready to start.
confirmed,
/// Currently in progress.
active,
/// Work has finished.
finished,
/// All post-event processes (invoicing) complete.
completed,
/// Archived.
closed,
/// Flagged for administrative review.
underReview,
}
/// Represents a Job Posting or Event.
///
/// This is the central entity for scheduling work. An Event contains [EventShift]s.
class Event extends Equatable {
const Event({
required this.id,
required this.businessId,
required this.hubId,
required this.name,
required this.date,
required this.status,
required this.contractType,
});
/// Unique identifier.
final String id;
/// The [Business] hosting the event.
final String businessId;
/// The [Hub] location.
final String hubId;
/// Title of the event.
final String name;
/// Date of the event.
final DateTime date;
/// Current workflow status.
final EventStatus status;
/// Type of employment contract (e.g., 'freelance', 'permanent').
final String contractType;
@override
List<Object?> get props => <Object?>[id, businessId, hubId, name, date, status, contractType];
}

View File

@@ -1,28 +0,0 @@
import 'package:equatable/equatable.dart';
/// Represents a specific time block or "shift" within an [Event].
///
/// An Event can have multiple shifts (e.g. "Morning Shift", "Evening Shift").
class EventShift extends Equatable {
const EventShift({
required this.id,
required this.eventId,
required this.name,
required this.address,
});
/// Unique identifier.
final String id;
/// The [Event] this shift belongs to.
final String eventId;
/// Descriptive name (e.g. "Setup Crew").
final String name;
/// Specific address for this shift (if different from Hub).
final String address;
@override
List<Object?> get props => <Object?>[id, eventId, name, address];
}

View File

@@ -1,53 +0,0 @@
import 'package:equatable/equatable.dart';
/// Represents a specific job opening within a [EventShift].
///
/// Defines the requirement for a specific [Skill], the quantity needed, and the pay.
class EventShiftPosition extends Equatable {
const EventShiftPosition({
required this.id,
required this.shiftId,
required this.skillId,
required this.count,
required this.rate,
required this.startTime,
required this.endTime,
required this.breakDurationMinutes,
});
/// Unique identifier.
final String id;
/// The [EventShift] this position is part of.
final String shiftId;
/// The [Skill] required for this position.
final String skillId;
/// Number of staff needed.
final int count;
/// Hourly pay rate.
final double rate;
/// Start time of this specific position.
final DateTime startTime;
/// End time of this specific position.
final DateTime endTime;
/// Deducted break duration in minutes.
final int breakDurationMinutes;
@override
List<Object?> get props => <Object?>[
id,
shiftId,
skillId,
count,
rate,
startTime,
endTime,
breakDurationMinutes,
];
}

View File

@@ -1,32 +0,0 @@
import 'package:equatable/equatable.dart';
/// Represents a verified record of time worked.
///
/// Derived from [Assignment] clock-in/out times, used for payroll.
class WorkSession extends Equatable {
const WorkSession({
required this.id,
required this.assignmentId,
required this.startTime,
this.endTime,
required this.breakDurationMinutes,
});
/// Unique identifier.
final String id;
/// The [Assignment] this session belongs to.
final String assignmentId;
/// Verified start time.
final DateTime startTime;
/// Verified end time.
final DateTime? endTime;
/// Verified break duration.
final int breakDurationMinutes;
@override
List<Object?> get props => <Object?>[id, assignmentId, startTime, endTime, breakDurationMinutes];
}

View File

@@ -0,0 +1,70 @@
import 'package:equatable/equatable.dart';
import 'package:krow_domain/src/entities/enums/account_type.dart';
/// A bank account belonging to a staff member.
///
/// Returned by `GET /staff/profile/bank-accounts`.
class BankAccount extends Equatable {
/// Creates a [BankAccount] instance.
const BankAccount({
required this.accountId,
required this.bankName,
required this.providerReference,
this.last4,
required this.isPrimary,
required this.accountType,
});
/// Deserialises a [BankAccount] from a V2 API JSON map.
factory BankAccount.fromJson(Map<String, dynamic> json) {
return BankAccount(
accountId: json['accountId'] as String,
bankName: json['bankName'] as String,
providerReference: json['providerReference'] as String,
last4: json['last4'] as String?,
isPrimary: json['isPrimary'] as bool,
accountType: AccountType.fromJson(json['accountType'] as String?),
);
}
/// Unique identifier.
final String accountId;
/// Name of the bank / payment provider.
final String bankName;
/// External provider reference.
final String providerReference;
/// Last 4 digits of the account number.
final String? last4;
/// Whether this is the primary payout account.
final bool isPrimary;
/// Account type.
final AccountType accountType;
/// Serialises this [BankAccount] to a JSON map.
Map<String, dynamic> toJson() {
return <String, dynamic>{
'accountId': accountId,
'bankName': bankName,
'providerReference': providerReference,
'last4': last4,
'isPrimary': isPrimary,
'accountType': accountType.toJson(),
};
}
@override
List<Object?> get props => <Object?>[
accountId,
bankName,
providerReference,
last4,
isPrimary,
accountType,
];
}

View File

@@ -1,27 +0,0 @@
import 'package:equatable/equatable.dart';
/// Abstract base class for all types of bank accounts.
abstract class BankAccount extends Equatable {
/// Creates a [BankAccount].
const BankAccount({
required this.id,
required this.bankName,
required this.isPrimary,
this.last4,
});
/// Unique identifier.
final String id;
/// Name of the bank or provider.
final String bankName;
/// Whether this is the primary payment method.
final bool isPrimary;
/// Last 4 digits of the account/card.
final String? last4;
@override
List<Object?> get props => <Object?>[id, bankName, isPrimary, last4];
}

View File

@@ -1,26 +0,0 @@
import 'bank_account.dart';
/// Domain model representing a business bank account or payment method.
class BusinessBankAccount extends BankAccount {
/// Creates a [BusinessBankAccount].
const BusinessBankAccount({
required super.id,
required super.bankName,
required String last4,
required super.isPrimary,
this.expiryTime,
}) : super(last4: last4);
/// Expiration date if applicable.
final DateTime? expiryTime;
@override
List<Object?> get props => <Object?>[
...super.props,
expiryTime,
];
/// Getter for non-nullable last4 in Business context.
@override
String get last4 => super.last4!;
}

View File

@@ -1,48 +0,0 @@
import 'bank_account.dart';
/// Type of staff bank account.
enum StaffBankAccountType {
/// Checking account.
checking,
/// Savings account.
savings,
/// Other type.
other,
}
/// Domain entity representing a staff's bank account.
class StaffBankAccount extends BankAccount {
/// Creates a [StaffBankAccount].
const StaffBankAccount({
required super.id,
required this.userId,
required super.bankName,
required this.accountNumber,
required this.accountName,
required super.isPrimary,
super.last4,
this.sortCode,
this.type = StaffBankAccountType.checking,
});
/// User identifier.
final String userId;
/// Full account number.
final String accountNumber;
/// Name of the account holder.
final String accountName;
/// Sort code (optional).
final String? sortCode;
/// Account type.
final StaffBankAccountType type;
@override
List<Object?> get props =>
<Object?>[...super.props, userId, accountNumber, accountName, sortCode, type];
}

View File

@@ -0,0 +1,77 @@
import 'package:equatable/equatable.dart';
import 'package:krow_domain/src/entities/enums/account_type.dart';
/// A billing/bank account belonging to a business.
///
/// Returned by `GET /client/billing/accounts`.
class BillingAccount extends Equatable {
/// Creates a [BillingAccount] instance.
const BillingAccount({
required this.accountId,
required this.bankName,
required this.providerReference,
this.last4,
required this.isPrimary,
required this.accountType,
this.routingNumberMasked,
});
/// Deserialises a [BillingAccount] from a V2 API JSON map.
factory BillingAccount.fromJson(Map<String, dynamic> json) {
return BillingAccount(
accountId: json['accountId'] as String,
bankName: json['bankName'] as String,
providerReference: json['providerReference'] as String,
last4: json['last4'] as String?,
isPrimary: json['isPrimary'] as bool,
accountType: AccountType.fromJson(json['accountType'] as String?),
routingNumberMasked: json['routingNumberMasked'] as String?,
);
}
/// Unique identifier.
final String accountId;
/// Name of the bank / payment provider.
final String bankName;
/// External provider reference (e.g. Stripe account ID).
final String providerReference;
/// Last 4 digits of the account number.
final String? last4;
/// Whether this is the primary billing account.
final bool isPrimary;
/// Account type.
final AccountType accountType;
/// Masked routing number.
final String? routingNumberMasked;
/// Serialises this [BillingAccount] to a JSON map.
Map<String, dynamic> toJson() {
return <String, dynamic>{
'accountId': accountId,
'bankName': bankName,
'providerReference': providerReference,
'last4': last4,
'isPrimary': isPrimary,
'accountType': accountType.toJson(),
'routingNumberMasked': routingNumberMasked,
};
}
@override
List<Object?> get props => <Object?>[
accountId,
bankName,
providerReference,
last4,
isPrimary,
accountType,
routingNumberMasked,
];
}

View File

@@ -1,8 +0,0 @@
/// Defines the period for billing calculations.
enum BillingPeriod {
/// Weekly billing period.
week,
/// Monthly billing period.
month,
}

View File

@@ -0,0 +1,29 @@
import 'package:equatable/equatable.dart';
/// The current outstanding bill for a business.
///
/// Returned by `GET /client/billing/current-bill`.
class CurrentBill extends Equatable {
/// Creates a [CurrentBill] instance.
const CurrentBill({required this.currentBillCents});
/// Deserialises a [CurrentBill] from a V2 API JSON map.
factory CurrentBill.fromJson(Map<String, dynamic> json) {
return CurrentBill(
currentBillCents: (json['currentBillCents'] as num).toInt(),
);
}
/// Outstanding bill amount in cents.
final int currentBillCents;
/// Serialises this [CurrentBill] to a JSON map.
Map<String, dynamic> toJson() {
return <String, dynamic>{
'currentBillCents': currentBillCents,
};
}
@override
List<Object?> get props => <Object?>[currentBillCents];
}

View File

@@ -1,148 +1,88 @@
import 'package:equatable/equatable.dart';
/// The workflow status of an [Invoice].
enum InvoiceStatus {
/// Generated but not yet sent/finalized.
open,
import 'package:krow_domain/src/entities/enums/invoice_status.dart';
/// Client has disputed a line item.
disputed,
/// Dispute has been handled.
resolved,
/// Invoice accepted by client.
verified,
/// Payment received.
paid,
/// Payment reconciled in accounting.
reconciled,
/// Payment not received by due date.
overdue,
}
/// Represents a bill sent to a [Business] for services rendered.
/// An invoice issued to a business for services rendered.
///
/// Returned by `GET /client/billing/invoices/*`.
class Invoice extends Equatable {
/// Creates an [Invoice] instance.
const Invoice({
required this.id,
required this.eventId,
required this.businessId,
required this.invoiceId,
required this.invoiceNumber,
required this.amountCents,
required this.status,
required this.totalAmount,
required this.workAmount,
required this.addonsAmount,
this.invoiceNumber,
this.issueDate,
this.title,
this.clientName,
this.locationAddress,
this.staffCount,
this.totalHours,
this.workers = const [],
this.dueDate,
this.paymentDate,
this.vendorId,
this.vendorName,
});
/// Deserialises an [Invoice] from a V2 API JSON map.
factory Invoice.fromJson(Map<String, dynamic> json) {
return Invoice(
invoiceId: json['invoiceId'] as String,
invoiceNumber: json['invoiceNumber'] as String,
amountCents: (json['amountCents'] as num).toInt(),
status: InvoiceStatus.fromJson(json['status'] as String?),
dueDate: json['dueDate'] != null
? DateTime.parse(json['dueDate'] as String)
: null,
paymentDate: json['paymentDate'] != null
? DateTime.parse(json['paymentDate'] as String)
: null,
vendorId: json['vendorId'] as String?,
vendorName: json['vendorName'] as String?,
);
}
/// Unique identifier.
final String id;
/// The [Event] this invoice covers.
final String eventId;
/// The [Business] being billed.
final String businessId;
/// Current payment/approval status.
final InvoiceStatus status;
/// Grand total amount.
final double totalAmount;
/// Total amount for labor costs.
final double workAmount;
/// Total amount for addons/extras.
final double addonsAmount;
final String invoiceId;
/// Human-readable invoice number.
final String? invoiceNumber;
final String invoiceNumber;
/// Date when the invoice was issued.
final DateTime? issueDate;
/// Total amount in cents.
final int amountCents;
/// Human-readable title (e.g. event name).
final String? title;
/// Current invoice lifecycle status.
final InvoiceStatus status;
/// Name of the client business.
final String? clientName;
/// When payment is due.
final DateTime? dueDate;
/// Address of the event/location.
final String? locationAddress;
/// When the invoice was paid (history endpoint).
final DateTime? paymentDate;
/// Number of staff worked.
final int? staffCount;
/// Vendor ID associated with this invoice.
final String? vendorId;
/// Total hours worked.
final double? totalHours;
/// Vendor company name.
final String? vendorName;
/// List of workers associated with this invoice.
final List<InvoiceWorker> workers;
/// Serialises this [Invoice] to a JSON map.
Map<String, dynamic> toJson() {
return <String, dynamic>{
'invoiceId': invoiceId,
'invoiceNumber': invoiceNumber,
'amountCents': amountCents,
'status': status.toJson(),
'dueDate': dueDate?.toIso8601String(),
'paymentDate': paymentDate?.toIso8601String(),
'vendorId': vendorId,
'vendorName': vendorName,
};
}
@override
List<Object?> get props => <Object?>[
id,
eventId,
businessId,
status,
totalAmount,
workAmount,
addonsAmount,
invoiceId,
invoiceNumber,
issueDate,
title,
clientName,
locationAddress,
staffCount,
totalHours,
workers,
];
}
/// Represents a worker entry in an [Invoice].
class InvoiceWorker extends Equatable {
const InvoiceWorker({
required this.name,
required this.role,
required this.amount,
required this.hours,
required this.rate,
this.checkIn,
this.checkOut,
this.breakMinutes = 0,
this.avatarUrl,
});
final String name;
final String role;
final double amount;
final double hours;
final double rate;
final DateTime? checkIn;
final DateTime? checkOut;
final int breakMinutes;
final String? avatarUrl;
@override
List<Object?> get props => [
name,
role,
amount,
hours,
rate,
checkIn,
checkOut,
breakMinutes,
avatarUrl,
amountCents,
status,
dueDate,
paymentDate,
vendorId,
vendorName,
];
}

View File

@@ -1,26 +0,0 @@
import 'package:equatable/equatable.dart';
/// Represents a reason or log for a declined [Invoice].
class InvoiceDecline extends Equatable {
const InvoiceDecline({
required this.id,
required this.invoiceId,
required this.reason,
required this.declinedAt,
});
/// Unique identifier.
final String id;
/// The [Invoice] that was declined.
final String invoiceId;
/// Reason provided by the client.
final String reason;
/// When the decline happened.
final DateTime declinedAt;
@override
List<Object?> get props => <Object?>[id, invoiceId, reason, declinedAt];
}

View File

@@ -1,36 +0,0 @@
import 'package:equatable/equatable.dart';
/// Represents a line item in an [Invoice].
///
/// Corresponds to the work done by one [Staff] member.
class InvoiceItem extends Equatable {
const InvoiceItem({
required this.id,
required this.invoiceId,
required this.staffId,
required this.workHours,
required this.rate,
required this.amount,
});
/// Unique identifier.
final String id;
/// The [Invoice] this item belongs to.
final String invoiceId;
/// The [Staff] member whose work is being billed.
final String staffId;
/// Total billed hours.
final double workHours;
/// Hourly rate applied.
final double rate;
/// Total line item amount (workHours * rate).
final double amount;
@override
List<Object?> get props => <Object?>[id, invoiceId, staffId, workHours, rate, amount];
}

View File

@@ -0,0 +1,37 @@
import 'package:equatable/equatable.dart';
/// A single data point in the staff payment chart.
///
/// Returned by `GET /staff/payments/chart`.
class PaymentChartPoint extends Equatable {
/// Creates a [PaymentChartPoint] instance.
const PaymentChartPoint({
required this.bucket,
required this.amountCents,
});
/// Deserialises a [PaymentChartPoint] from a V2 API JSON map.
factory PaymentChartPoint.fromJson(Map<String, dynamic> json) {
return PaymentChartPoint(
bucket: DateTime.parse(json['bucket'] as String),
amountCents: (json['amountCents'] as num).toInt(),
);
}
/// Time bucket start (day, week, or month).
final DateTime bucket;
/// Aggregated payment amount in cents for this bucket.
final int amountCents;
/// Serialises this [PaymentChartPoint] to a JSON map.
Map<String, dynamic> toJson() {
return <String, dynamic>{
'bucket': bucket.toIso8601String(),
'amountCents': amountCents,
};
}
@override
List<Object?> get props => <Object?>[bucket, amountCents];
}

View File

@@ -1,24 +1,29 @@
import 'package:equatable/equatable.dart';
/// Summary of staff earnings.
/// Aggregated payment summary for a staff member over a date range.
///
/// Returned by `GET /staff/payments/summary`.
class PaymentSummary extends Equatable {
/// Creates a [PaymentSummary] instance.
const PaymentSummary({required this.totalEarningsCents});
const PaymentSummary({
required this.weeklyEarnings,
required this.monthlyEarnings,
required this.pendingEarnings,
required this.totalEarnings,
});
final double weeklyEarnings;
final double monthlyEarnings;
final double pendingEarnings;
final double totalEarnings;
/// Deserialises a [PaymentSummary] from a V2 API JSON map.
factory PaymentSummary.fromJson(Map<String, dynamic> json) {
return PaymentSummary(
totalEarningsCents: (json['totalEarningsCents'] as num).toInt(),
);
}
/// Total earnings in cents for the queried period.
final int totalEarningsCents;
/// Serialises this [PaymentSummary] to a JSON map.
Map<String, dynamic> toJson() {
return <String, dynamic>{
'totalEarningsCents': totalEarningsCents,
};
}
@override
List<Object?> get props => <Object?>[
weeklyEarnings,
monthlyEarnings,
pendingEarnings,
totalEarnings,
];
List<Object?> get props => <Object?>[totalEarningsCents];
}

View File

@@ -0,0 +1,29 @@
import 'package:equatable/equatable.dart';
/// Accumulated savings for a business.
///
/// Returned by `GET /client/billing/savings`.
class Savings extends Equatable {
/// Creates a [Savings] instance.
const Savings({required this.savingsCents});
/// Deserialises a [Savings] from a V2 API JSON map.
factory Savings.fromJson(Map<String, dynamic> json) {
return Savings(
savingsCents: (json['savingsCents'] as num).toInt(),
);
}
/// Total savings amount in cents.
final int savingsCents;
/// Serialises this [Savings] to a JSON map.
Map<String, dynamic> toJson() {
return <String, dynamic>{
'savingsCents': savingsCents,
};
}
@override
List<Object?> get props => <Object?>[savingsCents];
}

View File

@@ -0,0 +1,43 @@
import 'package:equatable/equatable.dart';
/// A single category in the spend breakdown.
///
/// Returned by `GET /client/billing/spend-breakdown`.
class SpendItem extends Equatable {
/// Creates a [SpendItem] instance.
const SpendItem({
required this.category,
required this.amountCents,
required this.percentage,
});
/// Deserialises a [SpendItem] from a V2 API JSON map.
factory SpendItem.fromJson(Map<String, dynamic> json) {
return SpendItem(
category: json['category'] as String,
amountCents: (json['amountCents'] as num).toInt(),
percentage: (json['percentage'] as num).toDouble(),
);
}
/// Role/category name.
final String category;
/// Total spend in cents for this category.
final int amountCents;
/// Percentage of total spend.
final double percentage;
/// Serialises this [SpendItem] to a JSON map.
Map<String, dynamic> toJson() {
return <String, dynamic>{
'category': category,
'amountCents': amountCents,
'percentage': percentage,
};
}
@override
List<Object?> get props => <Object?>[category, amountCents, percentage];
}

View File

@@ -1,76 +1,88 @@
import 'package:equatable/equatable.dart';
/// Status of a staff payout.
enum PaymentStatus {
/// Payout calculated but not processed.
pending,
import 'package:krow_domain/src/entities/enums/payment_status.dart';
/// Submitted to banking provider.
processing,
/// Successfully transferred to staff.
paid,
/// Transfer failed.
failed,
/// Status unknown.
unknown,
}
/// Represents a payout to a [Staff] member for a completed [Assignment].
class StaffPayment extends Equatable {
const StaffPayment({
required this.id,
required this.staffId,
required this.assignmentId,
required this.amount,
/// A single payment record for a staff member.
///
/// Returned by `GET /staff/payments/history`.
class PaymentRecord extends Equatable {
/// Creates a [PaymentRecord] instance.
const PaymentRecord({
required this.paymentId,
required this.amountCents,
required this.date,
required this.status,
this.paidAt,
this.shiftTitle,
this.shiftLocation,
this.locationAddress,
this.hoursWorked,
this.hourlyRate,
this.workedTime,
this.shiftName,
this.location,
this.hourlyRateCents,
this.minutesWorked,
});
/// Deserialises a [PaymentRecord] from a V2 API JSON map.
factory PaymentRecord.fromJson(Map<String, dynamic> json) {
return PaymentRecord(
paymentId: json['paymentId'] as String,
amountCents: (json['amountCents'] as num).toInt(),
date: DateTime.parse(json['date'] as String),
status: PaymentStatus.fromJson(json['status'] as String?),
shiftName: json['shiftName'] as String?,
location: json['location'] as String?,
hourlyRateCents: json['hourlyRateCents'] != null
? (json['hourlyRateCents'] as num).toInt()
: null,
minutesWorked: json['minutesWorked'] != null
? (json['minutesWorked'] as num).toInt()
: null,
);
}
/// Unique identifier.
final String id;
final String paymentId;
/// The recipient [Staff].
final String staffId;
/// Payment amount in cents.
final int amountCents;
/// The [Assignment] being paid for.
final String assignmentId;
/// Date the payment was processed or created.
final DateTime date;
/// Amount to be paid.
final double amount;
/// Processing status.
/// Payment processing status.
final PaymentStatus status;
/// When the payment was successfully processed.
final DateTime? paidAt;
/// Title of the associated shift.
final String? shiftName;
/// Title of the shift worked.
final String? shiftTitle;
/// Location/hub name.
final String? location;
/// Location/hub name of the shift.
final String? shiftLocation;
/// Hourly pay rate in cents.
final int? hourlyRateCents;
/// Address of the shift location.
final String? locationAddress;
/// Total minutes worked for this payment.
final int? minutesWorked;
/// Number of hours worked.
final double? hoursWorked;
/// Hourly rate for the shift.
final double? hourlyRate;
/// Work session duration or status.
final String? workedTime;
/// Serialises this [PaymentRecord] to a JSON map.
Map<String, dynamic> toJson() {
return <String, dynamic>{
'paymentId': paymentId,
'amountCents': amountCents,
'date': date.toIso8601String(),
'status': status.toJson(),
'shiftName': shiftName,
'location': location,
'hourlyRateCents': hourlyRateCents,
'minutesWorked': minutesWorked,
};
}
@override
List<Object?> get props => <Object?>[id, staffId, assignmentId, amount, status, paidAt, shiftTitle, shiftLocation, locationAddress, hoursWorked, hourlyRate, workedTime];
}
List<Object?> get props => <Object?>[
paymentId,
amountCents,
date,
status,
shiftName,
location,
hourlyRateCents,
minutesWorked,
];
}

View File

@@ -1,78 +1,88 @@
import 'package:equatable/equatable.dart';
/// Status of a time card.
enum TimeCardStatus {
/// Waiting for approval or payment.
pending,
/// Approved by manager.
approved,
/// Payment has been issued.
paid,
/// Disputed by staff or client.
disputed;
/// Whether the card is approved.
bool get isApproved => this == TimeCardStatus.approved;
/// Whether the card is paid.
bool get isPaid => this == TimeCardStatus.paid;
/// Whether the card is disputed.
bool get isDisputed => this == TimeCardStatus.disputed;
/// Whether the card is pending.
bool get isPending => this == TimeCardStatus.pending;
}
/// Represents a time card for a staff member.
class TimeCard extends Equatable {
/// Creates a [TimeCard].
const TimeCard({
required this.id,
required this.shiftTitle,
required this.clientName,
/// A single time-card entry for a completed shift.
///
/// Returned by `GET /staff/profile/time-card`.
class TimeCardEntry extends Equatable {
/// Creates a [TimeCardEntry] instance.
const TimeCardEntry({
required this.date,
required this.startTime,
required this.endTime,
required this.totalHours,
required this.hourlyRate,
required this.totalPay,
required this.status,
required this.shiftName,
this.location,
this.clockInAt,
this.clockOutAt,
required this.minutesWorked,
this.hourlyRateCents,
required this.totalPayCents,
});
/// Unique identifier of the time card (often matches Application ID).
final String id;
/// Title of the shift.
final String shiftTitle;
/// Name of the client business.
final String clientName;
/// Deserialises a [TimeCardEntry] from a V2 API JSON map.
factory TimeCardEntry.fromJson(Map<String, dynamic> json) {
return TimeCardEntry(
date: DateTime.parse(json['date'] as String),
shiftName: json['shiftName'] as String,
location: json['location'] as String?,
clockInAt: json['clockInAt'] != null
? DateTime.parse(json['clockInAt'] as String)
: null,
clockOutAt: json['clockOutAt'] != null
? DateTime.parse(json['clockOutAt'] as String)
: null,
minutesWorked: (json['minutesWorked'] as num).toInt(),
hourlyRateCents: json['hourlyRateCents'] != null
? (json['hourlyRateCents'] as num).toInt()
: null,
totalPayCents: (json['totalPayCents'] as num).toInt(),
);
}
/// Date of the shift.
final DateTime date;
/// Actual or scheduled start time.
final String startTime;
/// Actual or scheduled end time.
final String endTime;
/// Total hours worked.
final double totalHours;
/// Hourly pay rate.
final double hourlyRate;
/// Total pay amount.
final double totalPay;
/// Current status of the time card.
final TimeCardStatus status;
/// Location name.
/// Title of the shift.
final String shiftName;
/// Location/hub name.
final String? location;
/// Clock-in timestamp.
final DateTime? clockInAt;
/// Clock-out timestamp.
final DateTime? clockOutAt;
/// Total minutes worked (regular + overtime).
final int minutesWorked;
/// Hourly pay rate in cents.
final int? hourlyRateCents;
/// Gross pay in cents.
final int totalPayCents;
/// Serialises this [TimeCardEntry] to a JSON map.
Map<String, dynamic> toJson() {
return <String, dynamic>{
'date': date.toIso8601String(),
'shiftName': shiftName,
'location': location,
'clockInAt': clockInAt?.toIso8601String(),
'clockOutAt': clockOutAt?.toIso8601String(),
'minutesWorked': minutesWorked,
'hourlyRateCents': hourlyRateCents,
'totalPayCents': totalPayCents,
};
}
@override
List<Object?> get props => <Object?>[
id,
shiftTitle,
clientName,
date,
startTime,
endTime,
totalHours,
hourlyRate,
totalPay,
status,
shiftName,
location,
clockInAt,
clockOutAt,
minutesWorked,
hourlyRateCents,
totalPayCents,
];
}

View File

@@ -0,0 +1,75 @@
import 'package:equatable/equatable.dart';
import 'coverage_metrics.dart';
import 'live_activity_metrics.dart';
import 'spending_summary.dart';
/// Client dashboard data aggregating key business metrics.
///
/// Returned by `GET /client/dashboard`.
class ClientDashboard extends Equatable {
/// Creates a [ClientDashboard] instance.
const ClientDashboard({
required this.userName,
required this.businessName,
required this.businessId,
required this.spending,
required this.coverage,
required this.liveActivity,
});
/// Deserialises a [ClientDashboard] from a V2 API JSON map.
factory ClientDashboard.fromJson(Map<String, dynamic> json) {
return ClientDashboard(
userName: json['userName'] as String,
businessName: json['businessName'] as String,
businessId: json['businessId'] as String,
spending:
SpendingSummary.fromJson(json['spending'] as Map<String, dynamic>),
coverage:
CoverageMetrics.fromJson(json['coverage'] as Map<String, dynamic>),
liveActivity: LiveActivityMetrics.fromJson(
json['liveActivity'] as Map<String, dynamic>),
);
}
/// Display name of the logged-in user.
final String userName;
/// Name of the business.
final String businessName;
/// Business ID.
final String businessId;
/// Spending summary.
final SpendingSummary spending;
/// Today's coverage metrics.
final CoverageMetrics coverage;
/// Live activity metrics.
final LiveActivityMetrics liveActivity;
/// Serialises this [ClientDashboard] to a JSON map.
Map<String, dynamic> toJson() {
return <String, dynamic>{
'userName': userName,
'businessName': businessName,
'businessId': businessId,
'spending': spending.toJson(),
'coverage': coverage.toJson(),
'liveActivity': liveActivity.toJson(),
};
}
@override
List<Object?> get props => <Object?>[
userName,
businessName,
businessId,
spending,
coverage,
liveActivity,
];
}

View File

@@ -0,0 +1,42 @@
import 'package:equatable/equatable.dart';
/// Today's coverage metrics nested in [ClientDashboard].
class CoverageMetrics extends Equatable {
/// Creates a [CoverageMetrics] instance.
const CoverageMetrics({
required this.neededWorkersToday,
required this.filledWorkersToday,
required this.openPositionsToday,
});
/// Deserialises a [CoverageMetrics] from a V2 API JSON map.
factory CoverageMetrics.fromJson(Map<String, dynamic> json) {
return CoverageMetrics(
neededWorkersToday: (json['neededWorkersToday'] as num).toInt(),
filledWorkersToday: (json['filledWorkersToday'] as num).toInt(),
openPositionsToday: (json['openPositionsToday'] as num).toInt(),
);
}
/// Workers needed today.
final int neededWorkersToday;
/// Workers filled today.
final int filledWorkersToday;
/// Open (unfilled) positions today.
final int openPositionsToday;
/// Serialises this [CoverageMetrics] to a JSON map.
Map<String, dynamic> toJson() {
return <String, dynamic>{
'neededWorkersToday': neededWorkersToday,
'filledWorkersToday': filledWorkersToday,
'openPositionsToday': openPositionsToday,
};
}
@override
List<Object?> get props =>
<Object?>[neededWorkersToday, filledWorkersToday, openPositionsToday];
}

View File

@@ -1,45 +0,0 @@
import 'package:equatable/equatable.dart';
/// Entity representing dashboard data for the home screen.
///
/// This entity provides aggregated metrics such as spending and shift counts
/// for both the current week and the upcoming 7 days.
class HomeDashboardData extends Equatable {
/// Creates a [HomeDashboardData] instance.
const HomeDashboardData({
required this.weeklySpending,
required this.next7DaysSpending,
required this.weeklyShifts,
required this.next7DaysScheduled,
required this.totalNeeded,
required this.totalFilled,
});
/// Total spending for the current week.
final double weeklySpending;
/// Projected spending for the next 7 days.
final double next7DaysSpending;
/// Total shifts scheduled for the current week.
final int weeklyShifts;
/// Shifts scheduled for the next 7 days.
final int next7DaysScheduled;
/// Total workers needed for today's shifts.
final int totalNeeded;
/// Total workers filled for today's shifts.
final int totalFilled;
@override
List<Object?> get props => <Object?>[
weeklySpending,
next7DaysSpending,
weeklyShifts,
next7DaysScheduled,
totalNeeded,
totalFilled,
];
}

View File

@@ -0,0 +1,43 @@
import 'package:equatable/equatable.dart';
/// Live activity metrics nested in [ClientDashboard].
class LiveActivityMetrics extends Equatable {
/// Creates a [LiveActivityMetrics] instance.
const LiveActivityMetrics({
required this.lateWorkersToday,
required this.checkedInWorkersToday,
required this.averageShiftCostCents,
});
/// Deserialises a [LiveActivityMetrics] from a V2 API JSON map.
factory LiveActivityMetrics.fromJson(Map<String, dynamic> json) {
return LiveActivityMetrics(
lateWorkersToday: (json['lateWorkersToday'] as num).toInt(),
checkedInWorkersToday: (json['checkedInWorkersToday'] as num).toInt(),
averageShiftCostCents:
(json['averageShiftCostCents'] as num).toInt(),
);
}
/// Workers marked late/no-show today.
final int lateWorkersToday;
/// Workers who have checked in today.
final int checkedInWorkersToday;
/// Average shift cost in cents.
final int averageShiftCostCents;
/// Serialises this [LiveActivityMetrics] to a JSON map.
Map<String, dynamic> toJson() {
return <String, dynamic>{
'lateWorkersToday': lateWorkersToday,
'checkedInWorkersToday': checkedInWorkersToday,
'averageShiftCostCents': averageShiftCostCents,
};
}
@override
List<Object?> get props =>
<Object?>[lateWorkersToday, checkedInWorkersToday, averageShiftCostCents];
}

View File

@@ -1,51 +0,0 @@
import 'package:equatable/equatable.dart';
/// Summary of a completed order used for reorder suggestions.
class ReorderItem extends Equatable {
const ReorderItem({
required this.orderId,
required this.title,
required this.location,
required this.totalCost,
required this.workers,
required this.type,
this.hourlyRate = 0,
this.hours = 0,
});
/// Unique identifier of the order.
final String orderId;
/// Display title of the order (e.g., event name or first shift title).
final String title;
/// Location of the order (e.g., first shift location).
final String location;
/// Total calculated cost for the order.
final double totalCost;
/// Total number of workers required for the order.
final int workers;
/// The type of order (e.g., ONE_TIME, RECURRING).
final String type;
/// Average or primary hourly rate (optional, for display).
final double hourlyRate;
/// Total hours for the order (optional, for display).
final double hours;
@override
List<Object?> get props => <Object?>[
orderId,
title,
location,
totalCost,
workers,
type,
hourlyRate,
hours,
];
}

View File

@@ -0,0 +1,37 @@
import 'package:equatable/equatable.dart';
/// Spending summary nested in [ClientDashboard].
class SpendingSummary extends Equatable {
/// Creates a [SpendingSummary] instance.
const SpendingSummary({
required this.weeklySpendCents,
required this.projectedNext7DaysCents,
});
/// Deserialises a [SpendingSummary] from a V2 API JSON map.
factory SpendingSummary.fromJson(Map<String, dynamic> json) {
return SpendingSummary(
weeklySpendCents: (json['weeklySpendCents'] as num).toInt(),
projectedNext7DaysCents:
(json['projectedNext7DaysCents'] as num).toInt(),
);
}
/// Total spend this week in cents.
final int weeklySpendCents;
/// Projected spend for the next 7 days in cents.
final int projectedNext7DaysCents;
/// Serialises this [SpendingSummary] to a JSON map.
Map<String, dynamic> toJson() {
return <String, dynamic>{
'weeklySpendCents': weeklySpendCents,
'projectedNext7DaysCents': projectedNext7DaysCents,
};
}
@override
List<Object?> get props =>
<Object?>[weeklySpendCents, projectedNext7DaysCents];
}

View File

@@ -0,0 +1,80 @@
import 'package:equatable/equatable.dart';
import '../benefits/benefit.dart';
/// Staff dashboard data with shifts and benefits overview.
///
/// Returned by `GET /staff/dashboard`.
class StaffDashboard extends Equatable {
/// Creates a [StaffDashboard] instance.
const StaffDashboard({
required this.staffName,
this.todaysShifts = const <Map<String, dynamic>>[],
this.tomorrowsShifts = const <Map<String, dynamic>>[],
this.recommendedShifts = const <Map<String, dynamic>>[],
this.benefits = const <Benefit>[],
});
/// Deserialises a [StaffDashboard] from a V2 API JSON map.
factory StaffDashboard.fromJson(Map<String, dynamic> json) {
final dynamic benefitsRaw = json['benefits'];
final List<Benefit> benefitsList = benefitsRaw is List
? benefitsRaw
.map((dynamic e) => Benefit.fromJson(e as Map<String, dynamic>))
.toList()
: const <Benefit>[];
return StaffDashboard(
staffName: json['staffName'] as String,
todaysShifts: _castShiftList(json['todaysShifts']),
tomorrowsShifts: _castShiftList(json['tomorrowsShifts']),
recommendedShifts: _castShiftList(json['recommendedShifts']),
benefits: benefitsList,
);
}
/// Display name of the staff member.
final String staffName;
/// Shifts assigned for today.
final List<Map<String, dynamic>> todaysShifts;
/// Shifts assigned for tomorrow.
final List<Map<String, dynamic>> tomorrowsShifts;
/// Recommended open shifts.
final List<Map<String, dynamic>> recommendedShifts;
/// Active benefits.
final List<Benefit> benefits;
/// Serialises this [StaffDashboard] to a JSON map.
Map<String, dynamic> toJson() {
return <String, dynamic>{
'staffName': staffName,
'todaysShifts': todaysShifts,
'tomorrowsShifts': tomorrowsShifts,
'recommendedShifts': recommendedShifts,
'benefits': benefits.map((Benefit b) => b.toJson()).toList(),
};
}
static List<Map<String, dynamic>> _castShiftList(dynamic raw) {
if (raw is List) {
return raw
.map((dynamic e) =>
Map<String, dynamic>.from(e as Map<dynamic, dynamic>))
.toList();
}
return const <Map<String, dynamic>>[];
}
@override
List<Object?> get props => <Object?>[
staffName,
todaysShifts,
tomorrowsShifts,
recommendedShifts,
benefits,
];
}

View File

@@ -0,0 +1,58 @@
import 'package:equatable/equatable.dart';
import 'package:krow_domain/src/entities/enums/application_status.dart';
/// Summary of a worker assigned to an order line item.
///
/// Nested within [OrderItem].
class AssignedWorkerSummary extends Equatable {
/// Creates an [AssignedWorkerSummary] instance.
const AssignedWorkerSummary({
this.applicationId,
this.workerName,
this.role,
this.confirmationStatus,
});
/// Deserialises an [AssignedWorkerSummary] from a V2 API JSON map.
factory AssignedWorkerSummary.fromJson(Map<String, dynamic> json) {
return AssignedWorkerSummary(
applicationId: json['applicationId'] as String?,
workerName: json['workerName'] as String?,
role: json['role'] as String?,
confirmationStatus: json['confirmationStatus'] != null
? ApplicationStatus.fromJson(json['confirmationStatus'] as String?)
: null,
);
}
/// Application ID for this worker assignment.
final String? applicationId;
/// Display name of the worker.
final String? workerName;
/// Role the worker is assigned to.
final String? role;
/// Confirmation status of the assignment.
final ApplicationStatus? confirmationStatus;
/// Serialises this [AssignedWorkerSummary] to a JSON map.
Map<String, dynamic> toJson() {
return <String, dynamic>{
'applicationId': applicationId,
'workerName': workerName,
'role': role,
'confirmationStatus': confirmationStatus?.toJson(),
};
}
@override
List<Object?> get props => <Object?>[
applicationId,
workerName,
role,
confirmationStatus,
];
}

View File

@@ -1,98 +0,0 @@
import 'package:equatable/equatable.dart';
import 'one_time_order_position.dart';
/// Represents a customer's request for a single event or shift.
///
/// Encapsulates the date, primary location, and a list of specific [OneTimeOrderPosition] requirements.
class OneTimeOrder extends Equatable {
const OneTimeOrder({
required this.date,
required this.location,
required this.positions,
this.hub,
this.eventName,
this.vendorId,
this.hubManagerId,
this.roleRates = const <String, double>{},
});
/// The specific date for the shift or event.
final DateTime date;
/// The primary location where the work will take place.
final String location;
/// The list of positions and headcounts required for this order.
final List<OneTimeOrderPosition> positions;
/// Selected hub details for this order.
final OneTimeOrderHubDetails? hub;
/// Optional order name.
final String? eventName;
/// Selected vendor id for this order.
final String? vendorId;
/// Optional hub manager id.
final String? hubManagerId;
/// Role hourly rates keyed by role id.
final Map<String, double> roleRates;
@override
List<Object?> get props => <Object?>[
date,
location,
positions,
hub,
eventName,
vendorId,
hubManagerId,
roleRates,
];
}
/// Minimal hub details used during order creation.
class OneTimeOrderHubDetails extends Equatable {
const OneTimeOrderHubDetails({
required this.id,
required this.name,
required this.address,
this.placeId,
this.latitude,
this.longitude,
this.city,
this.state,
this.street,
this.country,
this.zipCode,
});
final String id;
final String name;
final String address;
final String? placeId;
final double? latitude;
final double? longitude;
final String? city;
final String? state;
final String? street;
final String? country;
final String? zipCode;
@override
List<Object?> get props => <Object?>[
id,
name,
address,
placeId,
latitude,
longitude,
city,
state,
street,
country,
zipCode,
];
}

View File

@@ -1,62 +0,0 @@
import 'package:equatable/equatable.dart';
/// Represents a specific position requirement within a [OneTimeOrder].
///
/// Defines the role, headcount, and scheduling details for a single staffing requirement.
class OneTimeOrderPosition extends Equatable {
const OneTimeOrderPosition({
required this.role,
required this.count,
required this.startTime,
required this.endTime,
this.lunchBreak = 'NO_BREAK',
this.location,
});
/// The job role or title required.
final String role;
/// The number of workers required for this position.
final int count;
/// The scheduled start time (e.g., "09:00 AM").
final String startTime;
/// The scheduled end time (e.g., "05:00 PM").
final String endTime;
/// The break duration enum value (e.g., NO_BREAK, MIN_15, MIN_30).
final String lunchBreak;
/// Optional specific location for this position, if different from the order's main location.
final String? location;
@override
List<Object?> get props => <Object?>[
role,
count,
startTime,
endTime,
lunchBreak,
location,
];
/// Creates a copy of this position with the given fields replaced.
OneTimeOrderPosition copyWith({
String? role,
int? count,
String? startTime,
String? endTime,
String? lunchBreak,
String? location,
}) {
return OneTimeOrderPosition(
role: role ?? this.role,
count: count ?? this.count,
startTime: startTime ?? this.startTime,
endTime: endTime ?? this.endTime,
lunchBreak: lunchBreak ?? this.lunchBreak,
location: location ?? this.location,
);
}
}

View File

@@ -1,117 +1,137 @@
import 'package:equatable/equatable.dart';
import 'order_type.dart';
import 'package:krow_domain/src/entities/enums/order_type.dart';
import 'package:krow_domain/src/entities/enums/shift_status.dart';
/// Represents a customer's view of an order or shift.
import 'assigned_worker_summary.dart';
/// A line item within an order, representing a role needed for a shift.
///
/// This entity captures the details necessary for the dashboard/view orders screen,
/// including status and worker assignments.
/// Returned by `GET /client/orders/view`.
class OrderItem extends Equatable {
/// Creates an [OrderItem].
/// Creates an [OrderItem] instance.
const OrderItem({
required this.id,
required this.itemId,
required this.orderId,
required this.orderType,
required this.title,
required this.clientName,
required this.status,
required this.roleName,
required this.date,
required this.startTime,
required this.endTime,
required this.location,
required this.locationAddress,
required this.filled,
required this.workersNeeded,
required this.hourlyRate,
required this.eventName,
this.hours = 0,
this.totalValue = 0,
this.confirmedApps = const <Map<String, dynamic>>[],
this.hubManagerId,
this.hubManagerName,
required this.startsAt,
required this.endsAt,
required this.requiredWorkerCount,
required this.filledCount,
required this.hourlyRateCents,
required this.totalCostCents,
this.locationName,
required this.status,
this.workers = const <AssignedWorkerSummary>[],
});
/// Unique identifier of the order.
final String id;
/// Deserialises an [OrderItem] from a V2 API JSON map.
factory OrderItem.fromJson(Map<String, dynamic> json) {
final dynamic workersRaw = json['workers'];
final List<AssignedWorkerSummary> workersList = workersRaw is List
? workersRaw
.map((dynamic e) => AssignedWorkerSummary.fromJson(
e as Map<String, dynamic>))
.toList()
: const <AssignedWorkerSummary>[];
/// Parent order identifier.
return OrderItem(
itemId: json['itemId'] as String,
orderId: json['orderId'] as String,
orderType: OrderType.fromJson(json['orderType'] as String?),
roleName: json['roleName'] as String,
date: DateTime.parse(json['date'] as String),
startsAt: DateTime.parse(json['startsAt'] as String),
endsAt: DateTime.parse(json['endsAt'] as String),
requiredWorkerCount: (json['requiredWorkerCount'] as num).toInt(),
filledCount: (json['filledCount'] as num).toInt(),
hourlyRateCents: (json['hourlyRateCents'] as num).toInt(),
totalCostCents: (json['totalCostCents'] as num).toInt(),
locationName: json['locationName'] as String?,
status: ShiftStatus.fromJson(json['status'] as String?),
workers: workersList,
);
}
/// Shift-role ID (primary key).
final String itemId;
/// Parent order ID.
final String orderId;
/// The type of order (e.g., ONE_TIME, PERMANENT).
/// Order type (ONE_TIME, RECURRING, PERMANENT, RAPID).
final OrderType orderType;
/// Title or name of the role.
final String title;
/// Name of the role.
final String roleName;
/// Name of the client company.
final String clientName;
/// Shift date.
final DateTime date;
/// status of the order (e.g., 'open', 'filled', 'completed').
final String status;
/// Shift start time.
final DateTime startsAt;
/// Date of the shift (ISO format).
final String date;
/// Shift end time.
final DateTime endsAt;
/// Start time of the shift.
final String startTime;
/// Total workers required.
final int requiredWorkerCount;
/// End time of the shift.
final String endTime;
/// Workers currently assigned/filled.
final int filledCount;
/// Location name.
final String location;
/// Billing rate in cents per hour.
final int hourlyRateCents;
/// Full address of the location.
final String locationAddress;
/// Total cost in cents.
final int totalCostCents;
/// Number of workers currently filled.
final int filled;
/// Location/hub name.
final String? locationName;
/// Total number of workers required.
final int workersNeeded;
/// Shift status.
final ShiftStatus status;
/// Hourly pay rate.
final double hourlyRate;
/// Assigned workers for this line item.
final List<AssignedWorkerSummary> workers;
/// Total hours for the shift role.
final double hours;
/// Total value for the shift role.
final double totalValue;
/// Name of the event.
final String eventName;
/// List of confirmed worker applications.
final List<Map<String, dynamic>> confirmedApps;
/// Optional ID of the assigned hub manager.
final String? hubManagerId;
/// Optional Name of the assigned hub manager.
final String? hubManagerName;
/// Serialises this [OrderItem] to a JSON map.
Map<String, dynamic> toJson() {
return <String, dynamic>{
'itemId': itemId,
'orderId': orderId,
'orderType': orderType.toJson(),
'roleName': roleName,
'date': date.toIso8601String(),
'startsAt': startsAt.toIso8601String(),
'endsAt': endsAt.toIso8601String(),
'requiredWorkerCount': requiredWorkerCount,
'filledCount': filledCount,
'hourlyRateCents': hourlyRateCents,
'totalCostCents': totalCostCents,
'locationName': locationName,
'status': status.toJson(),
'workers': workers.map((AssignedWorkerSummary w) => w.toJson()).toList(),
};
}
@override
List<Object?> get props => <Object?>[
id,
orderId,
orderType,
title,
clientName,
status,
date,
startTime,
endTime,
location,
locationAddress,
filled,
workersNeeded,
hourlyRate,
hours,
totalValue,
eventName,
confirmedApps,
hubManagerId,
hubManagerName,
];
itemId,
orderId,
orderType,
roleName,
date,
startsAt,
endsAt,
requiredWorkerCount,
filledCount,
hourlyRateCents,
totalCostCents,
locationName,
status,
workers,
];
}

View File

@@ -0,0 +1,235 @@
import 'package:equatable/equatable.dart';
/// A preview of an order for reordering purposes.
///
/// Returned by `GET /client/orders/:id/reorder-preview`.
class OrderPreview extends Equatable {
/// Creates an [OrderPreview] instance.
const OrderPreview({
required this.orderId,
required this.title,
this.description,
this.startsAt,
this.endsAt,
this.locationName,
this.locationAddress,
this.metadata = const <String, dynamic>{},
this.shifts = const <OrderPreviewShift>[],
});
/// Deserialises an [OrderPreview] from a V2 API JSON map.
factory OrderPreview.fromJson(Map<String, dynamic> json) {
final dynamic shiftsRaw = json['shifts'];
final List<OrderPreviewShift> shiftsList = shiftsRaw is List
? shiftsRaw
.map((dynamic e) =>
OrderPreviewShift.fromJson(e as Map<String, dynamic>))
.toList()
: const <OrderPreviewShift>[];
return OrderPreview(
orderId: json['orderId'] as String,
title: json['title'] as String,
description: json['description'] as String?,
startsAt: json['startsAt'] != null
? DateTime.parse(json['startsAt'] as String)
: null,
endsAt: json['endsAt'] != null
? DateTime.parse(json['endsAt'] as String)
: null,
locationName: json['locationName'] as String?,
locationAddress: json['locationAddress'] as String?,
metadata: json['metadata'] is Map
? Map<String, dynamic>.from(json['metadata'] as Map<dynamic, dynamic>)
: const <String, dynamic>{},
shifts: shiftsList,
);
}
/// Order ID.
final String orderId;
/// Order title.
final String title;
/// Order description.
final String? description;
/// Order start time.
final DateTime? startsAt;
/// Order end time.
final DateTime? endsAt;
/// Location name.
final String? locationName;
/// Location address.
final String? locationAddress;
/// Flexible metadata bag.
final Map<String, dynamic> metadata;
/// Shifts with their roles from the original order.
final List<OrderPreviewShift> shifts;
/// Serialises this [OrderPreview] to a JSON map.
Map<String, dynamic> toJson() {
return <String, dynamic>{
'orderId': orderId,
'title': title,
'description': description,
'startsAt': startsAt?.toIso8601String(),
'endsAt': endsAt?.toIso8601String(),
'locationName': locationName,
'locationAddress': locationAddress,
'metadata': metadata,
'shifts': shifts.map((OrderPreviewShift s) => s.toJson()).toList(),
};
}
@override
List<Object?> get props => <Object?>[
orderId,
title,
description,
startsAt,
endsAt,
locationName,
locationAddress,
metadata,
shifts,
];
}
/// A shift within a reorder preview.
class OrderPreviewShift extends Equatable {
/// Creates an [OrderPreviewShift] instance.
const OrderPreviewShift({
required this.shiftId,
required this.shiftCode,
required this.title,
required this.startsAt,
required this.endsAt,
this.roles = const <OrderPreviewRole>[],
});
/// Deserialises an [OrderPreviewShift] from a V2 API JSON map.
factory OrderPreviewShift.fromJson(Map<String, dynamic> json) {
final dynamic rolesRaw = json['roles'];
final List<OrderPreviewRole> rolesList = rolesRaw is List
? rolesRaw
.map((dynamic e) =>
OrderPreviewRole.fromJson(e as Map<String, dynamic>))
.toList()
: const <OrderPreviewRole>[];
return OrderPreviewShift(
shiftId: json['shiftId'] as String,
shiftCode: json['shiftCode'] as String,
title: json['title'] as String,
startsAt: DateTime.parse(json['startsAt'] as String),
endsAt: DateTime.parse(json['endsAt'] as String),
roles: rolesList,
);
}
/// Shift ID.
final String shiftId;
/// Shift code.
final String shiftCode;
/// Shift title.
final String title;
/// Shift start time.
final DateTime startsAt;
/// Shift end time.
final DateTime endsAt;
/// Roles in this shift.
final List<OrderPreviewRole> roles;
/// Serialises this [OrderPreviewShift] to a JSON map.
Map<String, dynamic> toJson() {
return <String, dynamic>{
'shiftId': shiftId,
'shiftCode': shiftCode,
'title': title,
'startsAt': startsAt.toIso8601String(),
'endsAt': endsAt.toIso8601String(),
'roles': roles.map((OrderPreviewRole r) => r.toJson()).toList(),
};
}
@override
List<Object?> get props =>
<Object?>[shiftId, shiftCode, title, startsAt, endsAt, roles];
}
/// A role within a reorder preview shift.
class OrderPreviewRole extends Equatable {
/// Creates an [OrderPreviewRole] instance.
const OrderPreviewRole({
required this.roleId,
required this.roleCode,
required this.roleName,
required this.workersNeeded,
required this.payRateCents,
required this.billRateCents,
});
/// Deserialises an [OrderPreviewRole] from a V2 API JSON map.
factory OrderPreviewRole.fromJson(Map<String, dynamic> json) {
return OrderPreviewRole(
roleId: json['roleId'] as String,
roleCode: json['roleCode'] as String,
roleName: json['roleName'] as String,
workersNeeded: (json['workersNeeded'] as num).toInt(),
payRateCents: (json['payRateCents'] as num).toInt(),
billRateCents: (json['billRateCents'] as num).toInt(),
);
}
/// Role ID.
final String roleId;
/// Role code.
final String roleCode;
/// Role name.
final String roleName;
/// Workers needed for this role.
final int workersNeeded;
/// Pay rate in cents per hour.
final int payRateCents;
/// Bill rate in cents per hour.
final int billRateCents;
/// Serialises this [OrderPreviewRole] to a JSON map.
Map<String, dynamic> toJson() {
return <String, dynamic>{
'roleId': roleId,
'roleCode': roleCode,
'roleName': roleName,
'workersNeeded': workersNeeded,
'payRateCents': payRateCents,
'billRateCents': billRateCents,
};
}
@override
List<Object?> get props => <Object?>[
roleId,
roleCode,
roleName,
workersNeeded,
payRateCents,
billRateCents,
];
}

View File

@@ -1,30 +0,0 @@
/// Defines the type of an order.
enum OrderType {
/// A single occurrence shift.
oneTime,
/// A long-term or permanent staffing position.
permanent,
/// Shifts that repeat on a defined schedule.
recurring,
/// A quickly created shift.
rapid;
/// Creates an [OrderType] from a string value (typically from the backend).
static OrderType fromString(String value) {
switch (value.toUpperCase()) {
case 'ONE_TIME':
return OrderType.oneTime;
case 'PERMANENT':
return OrderType.permanent;
case 'RECURRING':
return OrderType.recurring;
case 'RAPID':
return OrderType.rapid;
default:
return OrderType.oneTime;
}
}
}

View File

@@ -1,41 +0,0 @@
import 'package:equatable/equatable.dart';
import 'one_time_order.dart';
import 'one_time_order_position.dart';
/// Represents a customer's request for permanent/ongoing staffing.
class PermanentOrder extends Equatable {
const PermanentOrder({
required this.startDate,
required this.permanentDays,
required this.positions,
this.hub,
this.eventName,
this.vendorId,
this.hubManagerId,
this.roleRates = const <String, double>{},
});
final DateTime startDate;
/// List of days (e.g., ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday'])
final List<String> permanentDays;
final List<OneTimeOrderPosition> positions;
final OneTimeOrderHubDetails? hub;
final String? eventName;
final String? vendorId;
final String? hubManagerId;
final Map<String, double> roleRates;
@override
List<Object?> get props => <Object?>[
startDate,
permanentDays,
positions,
hub,
eventName,
vendorId,
hubManagerId,
roleRates,
];
}

View File

@@ -1,60 +0,0 @@
import 'package:equatable/equatable.dart';
/// Represents a specific position requirement within a [PermanentOrder].
class PermanentOrderPosition extends Equatable {
const PermanentOrderPosition({
required this.role,
required this.count,
required this.startTime,
required this.endTime,
this.lunchBreak = 'NO_BREAK',
this.location,
});
/// The job role or title required.
final String role;
/// The number of workers required for this position.
final int count;
/// The scheduled start time (e.g., "09:00 AM").
final String startTime;
/// The scheduled end time (e.g., "05:00 PM").
final String endTime;
/// The break duration enum value (e.g., NO_BREAK, MIN_15, MIN_30).
final String lunchBreak;
/// Optional specific location for this position, if different from the order's main location.
final String? location;
@override
List<Object?> get props => <Object?>[
role,
count,
startTime,
endTime,
lunchBreak,
location,
];
/// Creates a copy of this position with the given fields replaced.
PermanentOrderPosition copyWith({
String? role,
int? count,
String? startTime,
String? endTime,
String? lunchBreak,
String? location,
}) {
return PermanentOrderPosition(
role: role ?? this.role,
count: count ?? this.count,
startTime: startTime ?? this.startTime,
endTime: endTime ?? this.endTime,
lunchBreak: lunchBreak ?? this.lunchBreak,
location: location ?? this.location,
);
}
}

View File

@@ -0,0 +1,65 @@
import 'package:equatable/equatable.dart';
import 'package:krow_domain/src/entities/enums/order_type.dart';
/// A recently completed order available for reordering.
///
/// Returned by `GET /client/reorders`.
class RecentOrder extends Equatable {
/// Creates a [RecentOrder] instance.
const RecentOrder({
required this.id,
required this.title,
this.date,
this.hubName,
required this.positionCount,
required this.orderType,
});
/// Deserialises a [RecentOrder] from a V2 API JSON map.
factory RecentOrder.fromJson(Map<String, dynamic> json) {
return RecentOrder(
id: json['id'] as String,
title: json['title'] as String,
date: json['date'] != null
? DateTime.parse(json['date'] as String)
: null,
hubName: json['hubName'] as String?,
positionCount: (json['positionCount'] as num).toInt(),
orderType: OrderType.fromJson(json['orderType'] as String?),
);
}
/// Order ID.
final String id;
/// Order title.
final String title;
/// Order date.
final DateTime? date;
/// Hub/location name.
final String? hubName;
/// Number of positions in the order.
final int positionCount;
/// Type of order (ONE_TIME, RECURRING, PERMANENT, RAPID).
final OrderType orderType;
/// Serialises this [RecentOrder] to a JSON map.
Map<String, dynamic> toJson() {
return <String, dynamic>{
'id': id,
'title': title,
'date': date?.toIso8601String(),
'hubName': hubName,
'positionCount': positionCount,
'orderType': orderType.toJson(),
};
}
@override
List<Object?> get props => <Object?>[id, title, date, hubName, positionCount, orderType];
}

View File

@@ -1,106 +0,0 @@
import 'package:equatable/equatable.dart';
import 'recurring_order_position.dart';
/// Represents a recurring staffing request spanning a date range.
class RecurringOrder extends Equatable {
const RecurringOrder({
required this.startDate,
required this.endDate,
required this.recurringDays,
required this.location,
required this.positions,
this.hub,
this.eventName,
this.vendorId,
this.hubManagerId,
this.roleRates = const <String, double>{},
});
/// Start date for the recurring schedule.
final DateTime startDate;
/// End date for the recurring schedule.
final DateTime endDate;
/// Days of the week to repeat on (e.g., ["S", "M", ...]).
final List<String> recurringDays;
/// The primary location where the work will take place.
final String location;
/// The list of positions and headcounts required for this order.
final List<RecurringOrderPosition> positions;
/// Selected hub details for this order.
final RecurringOrderHubDetails? hub;
/// Optional order name.
final String? eventName;
/// Selected vendor id for this order.
final String? vendorId;
/// Optional hub manager id.
final String? hubManagerId;
/// Role hourly rates keyed by role id.
final Map<String, double> roleRates;
@override
List<Object?> get props => <Object?>[
startDate,
endDate,
recurringDays,
location,
positions,
hub,
eventName,
vendorId,
hubManagerId,
roleRates,
];
}
/// Minimal hub details used during recurring order creation.
class RecurringOrderHubDetails extends Equatable {
const RecurringOrderHubDetails({
required this.id,
required this.name,
required this.address,
this.placeId,
this.latitude,
this.longitude,
this.city,
this.state,
this.street,
this.country,
this.zipCode,
});
final String id;
final String name;
final String address;
final String? placeId;
final double? latitude;
final double? longitude;
final String? city;
final String? state;
final String? street;
final String? country;
final String? zipCode;
@override
List<Object?> get props => <Object?>[
id,
name,
address,
placeId,
latitude,
longitude,
city,
state,
street,
country,
zipCode,
];
}

View File

@@ -1,60 +0,0 @@
import 'package:equatable/equatable.dart';
/// Represents a specific position requirement within a [RecurringOrder].
class RecurringOrderPosition extends Equatable {
const RecurringOrderPosition({
required this.role,
required this.count,
required this.startTime,
required this.endTime,
this.lunchBreak = 'NO_BREAK',
this.location,
});
/// The job role or title required.
final String role;
/// The number of workers required for this position.
final int count;
/// The scheduled start time (e.g., "09:00 AM").
final String startTime;
/// The scheduled end time (e.g., "05:00 PM").
final String endTime;
/// The break duration enum value (e.g., NO_BREAK, MIN_15, MIN_30).
final String lunchBreak;
/// Optional specific location for this position, if different from the order's main location.
final String? location;
@override
List<Object?> get props => <Object?>[
role,
count,
startTime,
endTime,
lunchBreak,
location,
];
/// Creates a copy of this position with the given fields replaced.
RecurringOrderPosition copyWith({
String? role,
int? count,
String? startTime,
String? endTime,
String? lunchBreak,
String? location,
}) {
return RecurringOrderPosition(
role: role ?? this.role,
count: count ?? this.count,
startTime: startTime ?? this.startTime,
endTime: endTime ?? this.endTime,
lunchBreak: lunchBreak ?? this.lunchBreak,
location: location ?? this.location,
);
}
}

View File

@@ -1,76 +0,0 @@
import 'package:equatable/equatable.dart';
import 'one_time_order.dart';
import 'order_type.dart';
/// Represents the full details of an order retrieved for reordering.
class ReorderData extends Equatable {
const ReorderData({
required this.orderId,
required this.orderType,
required this.eventName,
required this.vendorId,
required this.hub,
required this.positions,
this.date,
this.startDate,
this.endDate,
this.recurringDays = const <String>[],
this.permanentDays = const <String>[],
});
final String orderId;
final OrderType orderType;
final String eventName;
final String? vendorId;
final OneTimeOrderHubDetails hub;
final List<ReorderPosition> positions;
// One-time specific
final DateTime? date;
// Recurring/Permanent specific
final DateTime? startDate;
final DateTime? endDate;
final List<String> recurringDays;
final List<String> permanentDays;
@override
List<Object?> get props => <Object?>[
orderId,
orderType,
eventName,
vendorId,
hub,
positions,
date,
startDate,
endDate,
recurringDays,
permanentDays,
];
}
class ReorderPosition extends Equatable {
const ReorderPosition({
required this.roleId,
required this.count,
required this.startTime,
required this.endTime,
this.lunchBreak = 'NO_BREAK',
});
final String roleId;
final int count;
final String startTime;
final String endTime;
final String lunchBreak;
@override
List<Object?> get props => <Object?>[
roleId,
count,
startTime,
endTime,
lunchBreak,
];
}

View File

@@ -0,0 +1,138 @@
import 'package:equatable/equatable.dart';
/// Status of an attire checklist item.
enum AttireItemStatus {
/// Photo has not been uploaded yet.
notUploaded,
/// Upload is pending review.
pending,
/// Photo has been verified/approved.
verified,
/// Photo was rejected.
rejected,
/// Document has expired.
expired,
}
/// An attire checklist item for a staff member.
///
/// Returned by `GET /staff/profile/attire`. Joins the `documents` catalog
/// (filtered to ATTIRE type) with the staff-specific `staff_documents` record.
class AttireChecklist extends Equatable {
/// Creates an [AttireChecklist] instance.
const AttireChecklist({
required this.documentId,
required this.name,
this.description = '',
this.mandatory = true,
this.staffDocumentId,
this.photoUri,
required this.status,
this.verificationStatus,
});
/// Deserialises an [AttireChecklist] from the V2 API JSON response.
factory AttireChecklist.fromJson(Map<String, dynamic> json) {
return AttireChecklist(
documentId: json['documentId'] as String,
name: json['name'] as String,
description: json['description'] as String? ?? '',
mandatory: json['mandatory'] as bool? ?? true,
staffDocumentId: json['staffDocumentId'] as String?,
photoUri: json['photoUri'] as String?,
status: _parseStatus(json['status'] as String?),
verificationStatus: json['verificationStatus'] as String?,
);
}
/// Catalog document definition ID (UUID).
final String documentId;
/// Human-readable attire item name.
final String name;
/// Description of the attire requirement.
final String description;
/// Whether this attire item is mandatory.
final bool mandatory;
/// Staff-specific document record ID, or null if not uploaded.
final String? staffDocumentId;
/// URI to the uploaded attire photo.
final String? photoUri;
/// Current status of the attire item.
final AttireItemStatus status;
/// Detailed verification status string (from metadata).
final String? verificationStatus;
/// Whether a photo has been uploaded.
bool get isUploaded => staffDocumentId != null;
/// Serialises this [AttireChecklist] to a JSON map.
Map<String, dynamic> toJson() {
return <String, dynamic>{
'documentId': documentId,
'name': name,
'description': description,
'mandatory': mandatory,
'staffDocumentId': staffDocumentId,
'photoUri': photoUri,
'status': _statusToString(status),
'verificationStatus': verificationStatus,
};
}
@override
List<Object?> get props => <Object?>[
documentId,
name,
description,
mandatory,
staffDocumentId,
photoUri,
status,
verificationStatus,
];
/// Parses a status string from the API.
static AttireItemStatus _parseStatus(String? value) {
switch (value?.toUpperCase()) {
case 'NOT_UPLOADED':
return AttireItemStatus.notUploaded;
case 'PENDING':
return AttireItemStatus.pending;
case 'VERIFIED':
return AttireItemStatus.verified;
case 'REJECTED':
return AttireItemStatus.rejected;
case 'EXPIRED':
return AttireItemStatus.expired;
default:
return AttireItemStatus.notUploaded;
}
}
/// Converts an [AttireItemStatus] to its API string.
static String _statusToString(AttireItemStatus status) {
switch (status) {
case AttireItemStatus.notUploaded:
return 'NOT_UPLOADED';
case AttireItemStatus.pending:
return 'PENDING';
case AttireItemStatus.verified:
return 'VERIFIED';
case AttireItemStatus.rejected:
return 'REJECTED';
case AttireItemStatus.expired:
return 'EXPIRED';
}
}
}

View File

@@ -1,86 +0,0 @@
import 'package:equatable/equatable.dart';
import 'attire_verification_status.dart';
/// Represents an attire item that a staff member might need or possess.
///
/// Attire items are specific clothing or equipment required for jobs.
class AttireItem extends Equatable {
/// Creates an [AttireItem].
const AttireItem({
required this.id,
required this.code,
required this.label,
this.description,
this.imageUrl,
this.isMandatory = false,
this.verificationStatus,
this.photoUrl,
this.verificationId,
});
/// Unique identifier of the attire item (UUID).
final String id;
/// String code for the attire item (e.g. BLACK_TSHIRT).
final String code;
/// Display name of the item.
final String label;
/// Optional description for the attire item.
final String? description;
/// URL of the reference image.
final String? imageUrl;
/// Whether this item is mandatory for onboarding.
final bool isMandatory;
/// The current verification status of the uploaded photo.
final AttireVerificationStatus? verificationStatus;
/// The URL of the photo uploaded by the staff member.
final String? photoUrl;
/// The ID of the verification record.
final String? verificationId;
@override
List<Object?> get props => <Object?>[
id,
code,
label,
description,
imageUrl,
isMandatory,
verificationStatus,
photoUrl,
verificationId,
];
/// Creates a copy of this [AttireItem] with the given fields replaced.
AttireItem copyWith({
String? id,
String? code,
String? label,
String? description,
String? imageUrl,
bool? isMandatory,
AttireVerificationStatus? verificationStatus,
String? photoUrl,
String? verificationId,
}) {
return AttireItem(
id: id ?? this.id,
code: code ?? this.code,
label: label ?? this.label,
description: description ?? this.description,
imageUrl: imageUrl ?? this.imageUrl,
isMandatory: isMandatory ?? this.isMandatory,
verificationStatus: verificationStatus ?? this.verificationStatus,
photoUrl: photoUrl ?? this.photoUrl,
verificationId: verificationId ?? this.verificationId,
);
}
}

View File

@@ -1,39 +0,0 @@
/// Represents the verification status of an attire item photo.
enum AttireVerificationStatus {
/// Job is created and waiting to be processed.
pending('PENDING'),
/// Job is currently being processed by machine or human.
processing('PROCESSING'),
/// Machine verification passed automatically.
autoPass('AUTO_PASS'),
/// Machine verification failed automatically.
autoFail('AUTO_FAIL'),
/// Machine results are inconclusive and require human review.
needsReview('NEEDS_REVIEW'),
/// Human reviewer approved the verification.
approved('APPROVED'),
/// Human reviewer rejected the verification.
rejected('REJECTED'),
/// An error occurred during processing.
error('ERROR');
const AttireVerificationStatus(this.value);
/// The string value expected by the Core API.
final String value;
/// Creates a [AttireVerificationStatus] from a string.
static AttireVerificationStatus fromString(String value) {
return AttireVerificationStatus.values.firstWhere(
(AttireVerificationStatus e) => e.value == value,
orElse: () => AttireVerificationStatus.error,
);
}
}

View File

@@ -0,0 +1,147 @@
import 'package:equatable/equatable.dart';
/// Status of a staff certificate.
enum CertificateStatus {
/// Certificate uploaded, pending verification.
pending,
/// Certificate has been verified.
verified,
/// Certificate was rejected.
rejected,
/// Certificate has expired.
expired,
}
/// A staff certificate record (e.g. food hygiene, SIA badge).
///
/// Returned by `GET /staff/profile/certificates`. Maps to the V2
/// `certificates` table with additional metadata fields.
/// Named [StaffCertificate] to distinguish from the catalog [Certificate]
/// definition in `skills/certificate.dart`.
class StaffCertificate extends Equatable {
/// Creates a [StaffCertificate] instance.
const StaffCertificate({
required this.certificateId,
required this.certificateType,
required this.name,
this.fileUri,
this.issuer,
this.certificateNumber,
this.issuedAt,
this.expiresAt,
required this.status,
this.verificationStatus,
});
/// Deserialises a [StaffCertificate] from the V2 API JSON response.
factory StaffCertificate.fromJson(Map<String, dynamic> json) {
return StaffCertificate(
certificateId: json['certificateId'] as String,
certificateType: json['certificateType'] as String,
name: json['name'] as String,
fileUri: json['fileUri'] as String?,
issuer: json['issuer'] as String?,
certificateNumber: json['certificateNumber'] as String?,
issuedAt: json['issuedAt'] != null ? DateTime.parse(json['issuedAt'] as String) : null,
expiresAt: json['expiresAt'] != null ? DateTime.parse(json['expiresAt'] as String) : null,
status: _parseStatus(json['status'] as String?),
verificationStatus: json['verificationStatus'] as String?,
);
}
/// Certificate record UUID.
final String certificateId;
/// Type code (e.g. "FOOD_HYGIENE", "SIA_BADGE").
final String certificateType;
/// Human-readable name (from metadata or falls back to certificateType).
final String name;
/// URI to the uploaded certificate file.
final String? fileUri;
/// Issuing authority name (from metadata).
final String? issuer;
/// Certificate number/ID.
final String? certificateNumber;
/// When the certificate was issued.
final DateTime? issuedAt;
/// When the certificate expires.
final DateTime? expiresAt;
/// Current verification status.
final CertificateStatus status;
/// Detailed verification status string (from metadata).
final String? verificationStatus;
/// Whether the certificate has expired based on [expiresAt].
bool get isExpired => expiresAt != null && expiresAt!.isBefore(DateTime.now());
/// Serialises this [StaffCertificate] to a JSON map.
Map<String, dynamic> toJson() {
return <String, dynamic>{
'certificateId': certificateId,
'certificateType': certificateType,
'name': name,
'fileUri': fileUri,
'issuer': issuer,
'certificateNumber': certificateNumber,
'issuedAt': issuedAt?.toIso8601String(),
'expiresAt': expiresAt?.toIso8601String(),
'status': _statusToString(status),
'verificationStatus': verificationStatus,
};
}
@override
List<Object?> get props => <Object?>[
certificateId,
certificateType,
name,
fileUri,
issuer,
certificateNumber,
issuedAt,
expiresAt,
status,
verificationStatus,
];
/// Parses a status string from the API.
static CertificateStatus _parseStatus(String? value) {
switch (value?.toUpperCase()) {
case 'PENDING':
return CertificateStatus.pending;
case 'VERIFIED':
return CertificateStatus.verified;
case 'REJECTED':
return CertificateStatus.rejected;
case 'EXPIRED':
return CertificateStatus.expired;
default:
return CertificateStatus.pending;
}
}
/// Converts a [CertificateStatus] to its API string.
static String _statusToString(CertificateStatus status) {
switch (status) {
case CertificateStatus.pending:
return 'PENDING';
case CertificateStatus.verified:
return 'VERIFIED';
case CertificateStatus.rejected:
return 'REJECTED';
case CertificateStatus.expired:
return 'EXPIRED';
}
}
}

View File

@@ -1,25 +0,0 @@
/// Represents the broad category of a compliance certificate.
enum ComplianceType {
backgroundCheck('BACKGROUND_CHECK'),
foodHandler('FOOD_HANDLER'),
rbs('RBS'),
legal('LEGAL'),
operational('OPERATIONAL'),
safety('SAFETY'),
training('TRAINING'),
license('LICENSE'),
other('OTHER');
const ComplianceType(this.value);
/// The string value expected by the backend.
final String value;
/// Creates a [ComplianceType] from a string.
static ComplianceType fromString(String value) {
return ComplianceType.values.firstWhere(
(ComplianceType e) => e.value == value,
orElse: () => ComplianceType.other,
);
}
}

View File

@@ -1,39 +0,0 @@
/// Represents the verification status of a compliance document.
enum DocumentVerificationStatus {
/// Job is created and waiting to be processed.
pending('PENDING'),
/// Job is currently being processed by machine or human.
processing('PROCESSING'),
/// Machine verification passed automatically.
autoPass('AUTO_PASS'),
/// Machine verification failed automatically.
autoFail('AUTO_FAIL'),
/// Machine results are inconclusive and require human review.
needsReview('NEEDS_REVIEW'),
/// Human reviewer approved the verification.
approved('APPROVED'),
/// Human reviewer rejected the verification.
rejected('REJECTED'),
/// An error occurred during processing.
error('ERROR');
const DocumentVerificationStatus(this.value);
/// The string value expected by the Core API.
final String value;
/// Creates a [DocumentVerificationStatus] from a string.
static DocumentVerificationStatus fromString(String value) {
return DocumentVerificationStatus.values.firstWhere(
(DocumentVerificationStatus e) => e.value == value,
orElse: () => DocumentVerificationStatus.error,
);
}
}

View File

@@ -1,75 +1,89 @@
import 'package:equatable/equatable.dart';
import 'relationship_type.dart';
/// Represents an emergency contact for a user.
/// An emergency contact for a staff member.
///
/// Critical for staff safety during shifts.
/// Maps to the V2 `emergency_contacts` table. Returned by
/// `GET /staff/profile/emergency-contacts`.
class EmergencyContact extends Equatable {
/// Creates an [EmergencyContact] instance.
const EmergencyContact({
required this.id,
required this.name,
required this.relationship,
required this.contactId,
required this.fullName,
required this.phone,
required this.relationshipType,
this.isPrimary = false,
});
/// Unique identifier.
final String id;
/// Deserialises an [EmergencyContact] from a V2 API JSON response.
factory EmergencyContact.fromJson(Map<String, dynamic> json) {
return EmergencyContact(
contactId: json['contactId'] as String,
fullName: json['fullName'] as String,
phone: json['phone'] as String,
relationshipType: json['relationshipType'] as String,
isPrimary: json['isPrimary'] as bool? ?? false,
);
}
/// Full name of the contact.
final String name;
/// Unique contact record ID (UUID).
final String contactId;
/// Relationship to the user (e.g. "Spouse", "Parent").
final RelationshipType relationship;
/// Full name of the contact person.
final String fullName;
/// Phone number.
final String phone;
@override
List<Object?> get props => <Object?>[id, name, relationship, phone];
/// Relationship to the staff member (e.g. "SPOUSE", "PARENT", "FRIEND").
final String relationshipType;
/// Whether this is the primary emergency contact.
final bool isPrimary;
/// Serialises this [EmergencyContact] to a JSON map.
Map<String, dynamic> toJson() {
return <String, dynamic>{
'contactId': contactId,
'fullName': fullName,
'phone': phone,
'relationshipType': relationshipType,
'isPrimary': isPrimary,
};
}
/// Returns a copy of this [EmergencyContact] with the given fields replaced.
EmergencyContact copyWith({
String? id,
String? name,
String? contactId,
String? fullName,
String? phone,
RelationshipType? relationship,
String? relationshipType,
bool? isPrimary,
}) {
return EmergencyContact(
id: id ?? this.id,
name: name ?? this.name,
contactId: contactId ?? this.contactId,
fullName: fullName ?? this.fullName,
phone: phone ?? this.phone,
relationship: relationship ?? this.relationship,
relationshipType: relationshipType ?? this.relationshipType,
isPrimary: isPrimary ?? this.isPrimary,
);
}
/// Returns an empty [EmergencyContact].
/// Returns an empty [EmergencyContact] for form initialisation.
static EmergencyContact empty() {
return const EmergencyContact(
id: '',
name: '',
contactId: '',
fullName: '',
phone: '',
relationship: RelationshipType.family,
relationshipType: 'FAMILY',
);
}
/// Converts a string value to a [RelationshipType].
static RelationshipType stringToRelationshipType(String? value) {
if (value != null) {
final String strVal = value.toUpperCase();
switch (strVal) {
case 'FAMILY':
return RelationshipType.family;
case 'SPOUSE':
return RelationshipType.spouse;
case 'FRIEND':
return RelationshipType.friend;
case 'OTHER':
return RelationshipType.other;
default:
return RelationshipType.other;
}
}
return RelationshipType.other;
}
@override
List<Object?> get props => <Object?>[
contactId,
fullName,
phone,
relationshipType,
isPrimary,
];
}

View File

@@ -1,30 +0,0 @@
enum ExperienceSkill {
foodService('food_service'),
bartending('bartending'),
eventSetup('event_setup'),
hospitality('hospitality'),
warehouse('warehouse'),
customerService('customer_service'),
cleaning('cleaning'),
security('security'),
retail('retail'),
cooking('cooking'),
cashier('cashier'),
server('server'),
barista('barista'),
hostHostess('host_hostess'),
busser('busser'),
driving('driving');
final String value;
const ExperienceSkill(this.value);
static ExperienceSkill? fromString(String value) {
try {
return ExperienceSkill.values.firstWhere((ExperienceSkill e) => e.value == value);
} catch (_) {
return null;
}
}
}

View File

@@ -1,21 +0,0 @@
enum Industry {
hospitality('hospitality'),
foodService('food_service'),
warehouse('warehouse'),
events('events'),
retail('retail'),
healthcare('healthcare'),
other('other');
final String value;
const Industry(this.value);
static Industry? fromString(String value) {
try {
return Industry.values.firstWhere((Industry e) => e.value == value);
} catch (_) {
return null;
}
}
}

View File

@@ -0,0 +1,32 @@
import 'package:equatable/equatable.dart';
/// Staff privacy settings returned by `GET /staff/profile/privacy`.
///
/// Currently contains a single visibility flag stored in the staff
/// metadata blob. May expand as more privacy controls are added.
class PrivacySettings extends Equatable {
/// Creates a [PrivacySettings] instance.
const PrivacySettings({
this.profileVisible = true,
});
/// Deserialises [PrivacySettings] from the V2 API JSON response.
factory PrivacySettings.fromJson(Map<String, dynamic> json) {
return PrivacySettings(
profileVisible: json['profileVisible'] as bool? ?? true,
);
}
/// Whether the staff profile is visible to businesses.
final bool profileVisible;
/// Serialises this [PrivacySettings] to a JSON map.
Map<String, dynamic> toJson() {
return <String, dynamic>{
'profileVisible': profileVisible,
};
}
@override
List<Object?> get props => <Object?>[profileVisible];
}

View File

@@ -0,0 +1,81 @@
import 'package:equatable/equatable.dart';
/// Profile completion data returned by `GET /staff/profile-completion`.
///
/// Contains per-field completion booleans derived from the staff metadata,
/// plus an overall completion flag and a list of missing fields.
class ProfileCompletion extends Equatable {
/// Creates a [ProfileCompletion] instance.
const ProfileCompletion({
required this.staffId,
this.completed = false,
this.missingFields = const <String>[],
this.fields = const <String, bool>{},
});
/// Deserialises a [ProfileCompletion] from the V2 API JSON response.
factory ProfileCompletion.fromJson(Map<String, dynamic> json) {
final Object? rawFields = json['fields'];
final Map<String, bool> fields = <String, bool>{};
if (rawFields is Map<String, dynamic>) {
for (final MapEntry<String, dynamic> entry in rawFields.entries) {
fields[entry.key] = entry.value == true;
}
}
return ProfileCompletion(
staffId: json['staffId'] as String,
completed: json['completed'] as bool? ?? false,
missingFields: _parseStringList(json['missingFields']),
fields: fields,
);
}
/// Staff profile UUID.
final String staffId;
/// Whether all required fields are complete.
final bool completed;
/// List of field names that are still missing.
final List<String> missingFields;
/// Per-field completion map (field name to boolean).
///
/// Known keys: firstName, lastName, email, phone, preferredLocations,
/// skills, industries, emergencyContact.
final Map<String, bool> fields;
/// Percentage of fields completed (0 - 100).
int get percentComplete {
if (fields.isEmpty) return 0;
final int completedCount = fields.values.where((bool v) => v).length;
return ((completedCount / fields.length) * 100).round();
}
/// Serialises this [ProfileCompletion] to a JSON map.
Map<String, dynamic> toJson() {
return <String, dynamic>{
'staffId': staffId,
'completed': completed,
'missingFields': missingFields,
'fields': fields,
};
}
@override
List<Object?> get props => <Object?>[
staffId,
completed,
missingFields,
fields,
];
/// Parses a dynamic value into a list of strings.
static List<String> _parseStringList(Object? value) {
if (value is List) {
return value.map((Object? e) => e.toString()).toList();
}
return const <String>[];
}
}

View File

@@ -0,0 +1,183 @@
import 'package:equatable/equatable.dart';
/// Status of a profile document.
enum ProfileDocumentStatus {
/// Document has not been uploaded yet.
notUploaded,
/// Upload is pending review.
pending,
/// Document has been verified/approved.
verified,
/// Document was rejected.
rejected,
/// Document has expired.
expired,
}
/// Type category of a profile document.
enum ProfileDocumentType {
/// General compliance document.
document,
/// Government-issued ID.
governmentId,
/// Attire/uniform photo.
attire,
/// Tax form (I-9, W-4).
taxForm,
}
/// A profile document (or document requirement) for a staff member.
///
/// Returned by `GET /staff/profile/documents`. Joins the `documents` catalog
/// with the staff-specific `staff_documents` record (if uploaded).
class ProfileDocument extends Equatable {
/// Creates a [ProfileDocument] instance.
const ProfileDocument({
required this.documentId,
required this.documentType,
required this.name,
this.staffDocumentId,
this.fileUri,
required this.status,
this.expiresAt,
this.metadata = const <String, dynamic>{},
});
/// Deserialises a [ProfileDocument] from the V2 API JSON response.
factory ProfileDocument.fromJson(Map<String, dynamic> json) {
return ProfileDocument(
documentId: json['documentId'] as String,
documentType: _parseDocumentType(json['documentType'] as String?),
name: json['name'] as String,
staffDocumentId: json['staffDocumentId'] as String?,
fileUri: json['fileUri'] as String?,
status: _parseStatus(json['status'] as String?),
expiresAt: json['expiresAt'] != null ? DateTime.parse(json['expiresAt'] as String) : null,
metadata: (json['metadata'] as Map<String, dynamic>?) ?? const <String, dynamic>{},
);
}
/// Catalog document definition ID (UUID).
final String documentId;
/// Type category of this document.
final ProfileDocumentType documentType;
/// Human-readable document name.
final String name;
/// Staff-specific document record ID, or null if not yet uploaded.
final String? staffDocumentId;
/// URI to the uploaded file.
final String? fileUri;
/// Current status of the document.
final ProfileDocumentStatus status;
/// When the document expires, if applicable.
final DateTime? expiresAt;
/// Flexible metadata JSON blob.
final Map<String, dynamic> metadata;
/// Whether the document has been uploaded.
bool get isUploaded => staffDocumentId != null;
/// Serialises this [ProfileDocument] to a JSON map.
Map<String, dynamic> toJson() {
return <String, dynamic>{
'documentId': documentId,
'documentType': _documentTypeToString(documentType),
'name': name,
'staffDocumentId': staffDocumentId,
'fileUri': fileUri,
'status': _statusToString(status),
'expiresAt': expiresAt?.toIso8601String(),
'metadata': metadata,
};
}
@override
List<Object?> get props => <Object?>[
documentId,
documentType,
name,
staffDocumentId,
fileUri,
status,
expiresAt,
metadata,
];
/// Parses a document type string from the API.
static ProfileDocumentType _parseDocumentType(String? value) {
switch (value?.toUpperCase()) {
case 'DOCUMENT':
return ProfileDocumentType.document;
case 'GOVERNMENT_ID':
return ProfileDocumentType.governmentId;
case 'ATTIRE':
return ProfileDocumentType.attire;
case 'TAX_FORM':
return ProfileDocumentType.taxForm;
default:
return ProfileDocumentType.document;
}
}
/// Parses a status string from the API.
static ProfileDocumentStatus _parseStatus(String? value) {
switch (value?.toUpperCase()) {
case 'NOT_UPLOADED':
return ProfileDocumentStatus.notUploaded;
case 'PENDING':
return ProfileDocumentStatus.pending;
case 'VERIFIED':
return ProfileDocumentStatus.verified;
case 'REJECTED':
return ProfileDocumentStatus.rejected;
case 'EXPIRED':
return ProfileDocumentStatus.expired;
default:
return ProfileDocumentStatus.notUploaded;
}
}
/// Converts a [ProfileDocumentType] to its API string.
static String _documentTypeToString(ProfileDocumentType type) {
switch (type) {
case ProfileDocumentType.document:
return 'DOCUMENT';
case ProfileDocumentType.governmentId:
return 'GOVERNMENT_ID';
case ProfileDocumentType.attire:
return 'ATTIRE';
case ProfileDocumentType.taxForm:
return 'TAX_FORM';
}
}
/// Converts a [ProfileDocumentStatus] to its API string.
static String _statusToString(ProfileDocumentStatus status) {
switch (status) {
case ProfileDocumentStatus.notUploaded:
return 'NOT_UPLOADED';
case ProfileDocumentStatus.pending:
return 'PENDING';
case ProfileDocumentStatus.verified:
return 'VERIFIED';
case ProfileDocumentStatus.rejected:
return 'REJECTED';
case ProfileDocumentStatus.expired:
return 'EXPIRED';
}
}
}

Some files were not shown because too many files have changed in this diff Show More