feat: Refactor code structure and optimize performance across multiple modules
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
const String _staffPaymentFields = '''
|
||||
id
|
||||
rate
|
||||
assignment {
|
||||
clock_in
|
||||
clock_out
|
||||
start_at
|
||||
end_at
|
||||
break_in
|
||||
break_out
|
||||
position {
|
||||
shift {
|
||||
event {
|
||||
name
|
||||
date
|
||||
business {
|
||||
id
|
||||
name
|
||||
avatar
|
||||
}
|
||||
}
|
||||
}
|
||||
business_skill {
|
||||
skill {
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
work_hours
|
||||
amount
|
||||
status
|
||||
paid_at
|
||||
created_at
|
||||
updated_at
|
||||
''';
|
||||
|
||||
const String getWorkSummaryQuerySchema = '''
|
||||
query GetWorkSummary {
|
||||
staff_work_summary {
|
||||
weekly_hours
|
||||
monthly_hours
|
||||
weekly_earnings
|
||||
monthly_earnings
|
||||
}
|
||||
}
|
||||
''';
|
||||
|
||||
const String getPaymentsQuerySchema = '''
|
||||
query GetStaffPayments (\$status: StaffPaymentStatusInput!, \$first: Int!, \$after: String) {
|
||||
staff_payments(status: \$status, first: \$first, after: \$after) {
|
||||
pageInfo {
|
||||
hasNextPage
|
||||
}
|
||||
edges {
|
||||
node {
|
||||
$_staffPaymentFields
|
||||
}
|
||||
cursor
|
||||
}
|
||||
}
|
||||
}
|
||||
''';
|
||||
|
||||
const String confirmPaymentMutationSchema = '''
|
||||
mutation ConfirmStaffPayment (\$id: ID!) {
|
||||
confirm_staff_payment(id: \$id) {
|
||||
$_staffPaymentFields
|
||||
}
|
||||
}
|
||||
''';
|
||||
|
||||
const String declinePaymentMutationSchema = '''
|
||||
mutation DeclineStaffPayment (\$id: ID!, \$reason: String!, \$details: String) {
|
||||
decline_staff_payment(id: \$id, reason: \$reason, details: \$details) {
|
||||
$_staffPaymentFields
|
||||
}
|
||||
}
|
||||
''';
|
||||
@@ -0,0 +1,67 @@
|
||||
class EarningModel {
|
||||
EarningModel({
|
||||
required this.id,
|
||||
required this.rate,
|
||||
required this.businessName,
|
||||
required this.businessAvatar,
|
||||
required this.businessSkill,
|
||||
required this.workHours,
|
||||
required this.amount,
|
||||
required this.status,
|
||||
required this.paidAt,
|
||||
required this.clockInAt,
|
||||
required this.clockOutAt,
|
||||
this.breakIn,
|
||||
this.breakOut,
|
||||
required this.eventName,
|
||||
required this.eventDate,
|
||||
});
|
||||
|
||||
factory EarningModel.fromJson(Map<String, dynamic> json) {
|
||||
final assignment = json['assignment'] as Map<String, dynamic>;
|
||||
final positionData = assignment['position'] as Map<String, dynamic>;
|
||||
final eventData = positionData['shift']['event'] as Map<String, dynamic>;
|
||||
final businessData = eventData['business'] as Map<String, dynamic>;
|
||||
|
||||
return EarningModel(
|
||||
id: json['id'] as String? ?? '',
|
||||
rate: (json['rate'] as num? ?? 0).toDouble(),
|
||||
businessName: businessData['name'] as String? ?? '',
|
||||
businessAvatar: businessData['avatar'] as String? ?? '',
|
||||
businessSkill:
|
||||
positionData['business_skill']['skill']['name'] as String? ?? '',
|
||||
workHours: (json['work_hours'] as num? ?? 0).toDouble(),
|
||||
amount: (json['amount'] as num? ?? 0).toDouble(),
|
||||
status: json['status'] as String? ?? 'failed',
|
||||
paidAt: DateTime.tryParse(
|
||||
json['paid_at'] as String? ?? '',
|
||||
),
|
||||
clockInAt: DateTime.parse(
|
||||
assignment['clock_in'] ?? assignment['start_at'] as String,
|
||||
),
|
||||
clockOutAt: DateTime.parse(
|
||||
assignment['clock_out'] ?? assignment['end_at'] as String,
|
||||
),
|
||||
breakIn: DateTime.tryParse(assignment['break_in'] ?? ''),
|
||||
breakOut: DateTime.tryParse(assignment['break_out'] ?? ''),
|
||||
eventName: eventData['name'] as String? ?? '',
|
||||
eventDate: DateTime.parse(eventData['date'] as String? ?? ''),
|
||||
);
|
||||
}
|
||||
|
||||
final String id;
|
||||
final double rate;
|
||||
final String businessName;
|
||||
final String businessAvatar;
|
||||
final String businessSkill;
|
||||
final double workHours;
|
||||
final double amount;
|
||||
final String status;
|
||||
final DateTime? paidAt;
|
||||
final DateTime clockInAt;
|
||||
final DateTime clockOutAt;
|
||||
final DateTime? breakIn;
|
||||
final DateTime? breakOut;
|
||||
final String eventName;
|
||||
final DateTime eventDate;
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
class EarningsSummaryModel {
|
||||
EarningsSummaryModel({
|
||||
required this.totalEarningsByWeek,
|
||||
required this.totalEarningsByMonth,
|
||||
required this.totalWorkedHoursByWeek,
|
||||
required this.totalWorkedHoursByMonth,
|
||||
required this.payoutByWeek,
|
||||
required this.payoutByMonth,
|
||||
required this.startDatePeriod,
|
||||
required this.endDatePeriod,
|
||||
required this.maxEarningInPeriod,
|
||||
required this.minEarningInPeriod,
|
||||
});
|
||||
|
||||
//TODO: Additional fields that are used in the Earnings History screen are for now returning default values.
|
||||
factory EarningsSummaryModel.fromJson(Map<String, dynamic> json) {
|
||||
final time = DateTime.now();
|
||||
return EarningsSummaryModel(
|
||||
totalEarningsByWeek: (json['weekly_earnings'] as num?)?.toDouble() ?? 0,
|
||||
totalEarningsByMonth: (json['monthly_earnings'] as num?)?.toDouble() ?? 0,
|
||||
totalWorkedHoursByWeek: (json['weekly_hours'] as num?)?.toDouble() ?? 0,
|
||||
totalWorkedHoursByMonth: (json['monthly_hours'] as num?)?.toDouble() ?? 0,
|
||||
payoutByWeek: 0,
|
||||
payoutByMonth: 0,
|
||||
startDatePeriod: DateTime(time.year, time.month),
|
||||
endDatePeriod: DateTime(time.year, time.month, 28),
|
||||
maxEarningInPeriod: 0,
|
||||
minEarningInPeriod: 0,
|
||||
);
|
||||
}
|
||||
|
||||
final double totalEarningsByWeek;
|
||||
final double totalEarningsByMonth;
|
||||
final double totalWorkedHoursByWeek;
|
||||
final double totalWorkedHoursByMonth;
|
||||
final double payoutByWeek;
|
||||
final double payoutByMonth;
|
||||
final DateTime? startDatePeriod;
|
||||
final DateTime? endDatePeriod;
|
||||
final int maxEarningInPeriod;
|
||||
final int minEarningInPeriod;
|
||||
|
||||
EarningsSummaryModel copyWith({
|
||||
double? totalEarningsByWeek,
|
||||
double? totalEarningsByMonth,
|
||||
double? totalWorkedHoursByWeek,
|
||||
double? totalWorkedHoursByMonth,
|
||||
double? payoutByWeek,
|
||||
double? payoutByMonth,
|
||||
DateTime? startDatePeriod,
|
||||
DateTime? endDatePeriod,
|
||||
int? maxEarningInPeriod,
|
||||
int? minEarningInPeriod,
|
||||
}) {
|
||||
return EarningsSummaryModel(
|
||||
totalEarningsByWeek: totalEarningsByWeek ?? this.totalEarningsByWeek,
|
||||
totalEarningsByMonth: totalEarningsByMonth ?? this.totalEarningsByMonth,
|
||||
totalWorkedHoursByWeek:
|
||||
totalWorkedHoursByWeek ?? this.totalWorkedHoursByWeek,
|
||||
totalWorkedHoursByMonth:
|
||||
totalWorkedHoursByMonth ?? this.totalWorkedHoursByMonth,
|
||||
payoutByWeek: payoutByWeek ?? this.payoutByWeek,
|
||||
payoutByMonth: payoutByMonth ?? this.payoutByMonth,
|
||||
startDatePeriod: startDatePeriod,
|
||||
endDatePeriod: endDatePeriod,
|
||||
maxEarningInPeriod: maxEarningInPeriod ?? this.maxEarningInPeriod,
|
||||
minEarningInPeriod: minEarningInPeriod ?? this.minEarningInPeriod,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
import 'package:graphql_flutter/graphql_flutter.dart';
|
||||
import 'package:injectable/injectable.dart';
|
||||
import 'package:krow/core/application/clients/api/api_client.dart';
|
||||
import 'package:krow/core/data/models/pagination_wrapper/pagination_wrapper.dart';
|
||||
import 'package:krow/features/earning/data/earning_qgl.dart';
|
||||
import 'package:krow/features/earning/data/models/earning_model.dart';
|
||||
import 'package:krow/features/earning/data/models/earnings_summary_model.dart';
|
||||
|
||||
@injectable
|
||||
class StaffEarningApiProvider {
|
||||
StaffEarningApiProvider({required ApiClient client}) : _client = client;
|
||||
|
||||
final ApiClient _client;
|
||||
|
||||
Future<EarningsSummaryModel> fetchStaffEarningsSummary() async {
|
||||
final QueryResult result = await _client.query(
|
||||
schema: getWorkSummaryQuerySchema,
|
||||
);
|
||||
|
||||
if (result.hasException) {
|
||||
throw Exception(result.exception.toString());
|
||||
}
|
||||
|
||||
return EarningsSummaryModel.fromJson(
|
||||
result.data?['staff_work_summary'] as Map<String, dynamic>? ?? {},
|
||||
);
|
||||
}
|
||||
|
||||
Future<PaginationWrapper<EarningModel>> fetchEarnings({
|
||||
required String status,
|
||||
required int limit,
|
||||
String? cursor,
|
||||
}) async {
|
||||
final QueryResult result = await _client.query(
|
||||
schema: getPaymentsQuerySchema,
|
||||
body: {'status': status, 'first': limit, 'after': cursor},
|
||||
);
|
||||
|
||||
if (result.hasException) {
|
||||
throw Exception(result.exception.toString());
|
||||
}
|
||||
|
||||
return PaginationWrapper<EarningModel>.fromJson(
|
||||
result.data?['staff_payments'] ?? {},
|
||||
EarningModel.fromJson,
|
||||
);
|
||||
}
|
||||
|
||||
Future<EarningModel?> _processPaymentMutation({
|
||||
required String schema,
|
||||
required Map<String, dynamic> body,
|
||||
required String mutationName,
|
||||
}) async {
|
||||
final QueryResult result = await _client.mutate(
|
||||
schema: schema,
|
||||
body: body,
|
||||
);
|
||||
|
||||
if (result.hasException) throw Exception(result.exception.toString());
|
||||
|
||||
if (result.data == null) {
|
||||
throw Exception('Payment data is missing on mutation $mutationName');
|
||||
}
|
||||
|
||||
final jsonData = result.data?[mutationName] as Map<String, dynamic>;
|
||||
return EarningModel.fromJson(jsonData);
|
||||
}
|
||||
|
||||
Future<EarningModel?> confirmPayment({required String id}) {
|
||||
return _processPaymentMutation(
|
||||
schema: confirmPaymentMutationSchema,
|
||||
body: {'id': id},
|
||||
mutationName: 'confirm_staff_payment',
|
||||
);
|
||||
}
|
||||
|
||||
Future<EarningModel?> declinePayment({
|
||||
required String id,
|
||||
required String reason,
|
||||
String? details,
|
||||
}) {
|
||||
return _processPaymentMutation(
|
||||
schema: declinePaymentMutationSchema,
|
||||
body: {'id': id, 'reason': reason, 'details': details},
|
||||
mutationName: 'decline_staff_payment',
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
import 'package:injectable/injectable.dart';
|
||||
import 'package:krow/features/earning/data/models/earning_model.dart';
|
||||
import 'package:krow/features/earning/data/staff_earning_api_provider.dart';
|
||||
import 'package:krow/features/earning/domain/entities/earning_shift_entity.dart';
|
||||
import 'package:krow/features/earning/domain/entities/earnings_batch_entity.dart';
|
||||
import 'package:krow/features/earning/domain/entities/earnings_summary_entity.dart';
|
||||
import 'package:krow/features/earning/domain/staff_earning_repository.dart';
|
||||
|
||||
@Injectable(as: StaffEarningRepository)
|
||||
class StaffEarningRepositoryImpl implements StaffEarningRepository {
|
||||
StaffEarningRepositoryImpl({
|
||||
required StaffEarningApiProvider apiProvider,
|
||||
}) : _apiProvider = apiProvider;
|
||||
|
||||
final StaffEarningApiProvider _apiProvider;
|
||||
|
||||
EarningShiftEntity _convertEarningModel(EarningModel data) {
|
||||
return EarningShiftEntity(
|
||||
id: data.id,
|
||||
status: EarningStatus.fromString(data.status),
|
||||
businessImageUrl: data.businessAvatar,
|
||||
skillName: data.businessSkill,
|
||||
businessName: data.businessName,
|
||||
totalBreakTime: data.breakIn != null
|
||||
? data.breakOut?.difference(data.breakIn!).inSeconds
|
||||
: null,
|
||||
paymentStatus: 1,
|
||||
earned: data.amount,
|
||||
clockIn: data.clockInAt,
|
||||
clockOut: data.clockOutAt,
|
||||
eventName: data.eventName,
|
||||
eventDate: data.eventDate,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<EarningsSummaryEntity> getStaffEarningsData() async {
|
||||
final data = await _apiProvider.fetchStaffEarningsSummary();
|
||||
|
||||
return EarningsSummaryEntity(
|
||||
totalEarningsByWeek: data.totalEarningsByWeek,
|
||||
totalEarningsByMonth: data.totalEarningsByMonth,
|
||||
totalWorkedHoursByWeek: data.totalWorkedHoursByWeek,
|
||||
totalWorkedHoursByMonth: data.totalWorkedHoursByMonth,
|
||||
payoutByWeek: data.payoutByWeek,
|
||||
payoutByMonth: data.payoutByMonth,
|
||||
startDatePeriod: data.startDatePeriod,
|
||||
endDatePeriod: data.endDatePeriod,
|
||||
maxEarningInPeriod: data.maxEarningInPeriod,
|
||||
minEarningInPeriod: data.minEarningInPeriod,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<EarningsBatchEntity> getEarningsBatch({
|
||||
required String status,
|
||||
int limit = 10,
|
||||
String? lastEntryCursor,
|
||||
}) async {
|
||||
final paginationInfo = await _apiProvider.fetchEarnings(
|
||||
status: status,
|
||||
limit: limit,
|
||||
cursor: lastEntryCursor,
|
||||
);
|
||||
|
||||
return EarningsBatchEntity(
|
||||
batchStatus: status,
|
||||
hasNextBatch: paginationInfo.pageInfo?.hasNextPage??false,
|
||||
cursor: paginationInfo.pageInfo?.endCursor ??
|
||||
paginationInfo.edges.lastOrNull?.cursor,
|
||||
earnings: paginationInfo.edges.map(
|
||||
(edgeData) {
|
||||
return _convertEarningModel(edgeData.node);
|
||||
},
|
||||
).toList(),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<EarningShiftEntity?> confirmStaffEarning({
|
||||
required String earningId,
|
||||
}) async {
|
||||
final result = await _apiProvider.confirmPayment(id: earningId);
|
||||
|
||||
if (result == null) return null;
|
||||
|
||||
return _convertEarningModel(result);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<EarningShiftEntity?> disputeStaffEarning({
|
||||
required String id,
|
||||
required String reason,
|
||||
String? details,
|
||||
}) async {
|
||||
final result = await _apiProvider.declinePayment(
|
||||
id: id,
|
||||
reason: reason,
|
||||
details: details,
|
||||
);
|
||||
|
||||
if (result == null) return null;
|
||||
|
||||
return _convertEarningModel(result);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user