Refactor billing data parsing and filtering, update invoice queries, and remove the dedicated timesheets page.
This commit is contained in:
@@ -6,16 +6,21 @@ import '../../domain/repositories/billing_connector_repository.dart';
|
|||||||
|
|
||||||
/// Implementation of [BillingConnectorRepository].
|
/// Implementation of [BillingConnectorRepository].
|
||||||
class BillingConnectorRepositoryImpl implements BillingConnectorRepository {
|
class BillingConnectorRepositoryImpl implements BillingConnectorRepository {
|
||||||
BillingConnectorRepositoryImpl({
|
BillingConnectorRepositoryImpl({dc.DataConnectService? service})
|
||||||
dc.DataConnectService? service,
|
: _service = service ?? dc.DataConnectService.instance;
|
||||||
}) : _service = service ?? dc.DataConnectService.instance;
|
|
||||||
|
|
||||||
final dc.DataConnectService _service;
|
final dc.DataConnectService _service;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<List<BusinessBankAccount>> getBankAccounts({required String businessId}) async {
|
Future<List<BusinessBankAccount>> getBankAccounts({
|
||||||
|
required String businessId,
|
||||||
|
}) async {
|
||||||
return _service.run(() async {
|
return _service.run(() async {
|
||||||
final QueryResult<dc.GetAccountsByOwnerIdData, dc.GetAccountsByOwnerIdVariables> result = await _service.connector
|
final QueryResult<
|
||||||
|
dc.GetAccountsByOwnerIdData,
|
||||||
|
dc.GetAccountsByOwnerIdVariables
|
||||||
|
>
|
||||||
|
result = await _service.connector
|
||||||
.getAccountsByOwnerId(ownerId: businessId)
|
.getAccountsByOwnerId(ownerId: businessId)
|
||||||
.execute();
|
.execute();
|
||||||
|
|
||||||
@@ -26,21 +31,32 @@ class BillingConnectorRepositoryImpl implements BillingConnectorRepository {
|
|||||||
@override
|
@override
|
||||||
Future<double> getCurrentBillAmount({required String businessId}) async {
|
Future<double> getCurrentBillAmount({required String businessId}) async {
|
||||||
return _service.run(() async {
|
return _service.run(() async {
|
||||||
final QueryResult<dc.ListInvoicesByBusinessIdData, dc.ListInvoicesByBusinessIdVariables> result = await _service.connector
|
final QueryResult<
|
||||||
|
dc.ListInvoicesByBusinessIdData,
|
||||||
|
dc.ListInvoicesByBusinessIdVariables
|
||||||
|
>
|
||||||
|
result = await _service.connector
|
||||||
.listInvoicesByBusinessId(businessId: businessId)
|
.listInvoicesByBusinessId(businessId: businessId)
|
||||||
.execute();
|
.execute();
|
||||||
|
|
||||||
return result.data.invoices
|
return result.data.invoices
|
||||||
.map(_mapInvoice)
|
.map(_mapInvoice)
|
||||||
.where((Invoice i) => i.status == InvoiceStatus.open)
|
.where((Invoice i) => i.status == InvoiceStatus.open)
|
||||||
.fold<double>(0.0, (double sum, Invoice item) => sum + item.totalAmount);
|
.fold<double>(
|
||||||
|
0.0,
|
||||||
|
(double sum, Invoice item) => sum + item.totalAmount,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<List<Invoice>> getInvoiceHistory({required String businessId}) async {
|
Future<List<Invoice>> getInvoiceHistory({required String businessId}) async {
|
||||||
return _service.run(() async {
|
return _service.run(() async {
|
||||||
final QueryResult<dc.ListInvoicesByBusinessIdData, dc.ListInvoicesByBusinessIdVariables> result = await _service.connector
|
final QueryResult<
|
||||||
|
dc.ListInvoicesByBusinessIdData,
|
||||||
|
dc.ListInvoicesByBusinessIdVariables
|
||||||
|
>
|
||||||
|
result = await _service.connector
|
||||||
.listInvoicesByBusinessId(businessId: businessId)
|
.listInvoicesByBusinessId(businessId: businessId)
|
||||||
.limit(20)
|
.limit(20)
|
||||||
.execute();
|
.execute();
|
||||||
@@ -55,14 +71,22 @@ class BillingConnectorRepositoryImpl implements BillingConnectorRepository {
|
|||||||
@override
|
@override
|
||||||
Future<List<Invoice>> getPendingInvoices({required String businessId}) async {
|
Future<List<Invoice>> getPendingInvoices({required String businessId}) async {
|
||||||
return _service.run(() async {
|
return _service.run(() async {
|
||||||
final QueryResult<dc.ListInvoicesByBusinessIdData, dc.ListInvoicesByBusinessIdVariables> result = await _service.connector
|
final QueryResult<
|
||||||
|
dc.ListInvoicesByBusinessIdData,
|
||||||
|
dc.ListInvoicesByBusinessIdVariables
|
||||||
|
>
|
||||||
|
result = await _service.connector
|
||||||
.listInvoicesByBusinessId(businessId: businessId)
|
.listInvoicesByBusinessId(businessId: businessId)
|
||||||
.execute();
|
.execute();
|
||||||
|
|
||||||
return result.data.invoices
|
return result.data.invoices
|
||||||
.map(_mapInvoice)
|
.map(_mapInvoice)
|
||||||
.where((Invoice i) =>
|
.where(
|
||||||
i.status != InvoiceStatus.paid)
|
(Invoice i) =>
|
||||||
|
i.status != InvoiceStatus.paid &&
|
||||||
|
i.status != InvoiceStatus.disputed &&
|
||||||
|
i.status != InvoiceStatus.open,
|
||||||
|
)
|
||||||
.toList();
|
.toList();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -76,19 +100,28 @@ class BillingConnectorRepositoryImpl implements BillingConnectorRepository {
|
|||||||
final DateTime now = DateTime.now();
|
final DateTime now = DateTime.now();
|
||||||
final DateTime start;
|
final DateTime start;
|
||||||
final DateTime end;
|
final DateTime end;
|
||||||
|
|
||||||
if (period == BillingPeriod.week) {
|
if (period == BillingPeriod.week) {
|
||||||
final int daysFromMonday = now.weekday - DateTime.monday;
|
final int daysFromMonday = now.weekday - DateTime.monday;
|
||||||
final DateTime monday = DateTime(now.year, now.month, now.day)
|
final DateTime monday = DateTime(
|
||||||
.subtract(Duration(days: daysFromMonday));
|
now.year,
|
||||||
|
now.month,
|
||||||
|
now.day,
|
||||||
|
).subtract(Duration(days: daysFromMonday));
|
||||||
start = monday;
|
start = monday;
|
||||||
end = monday.add(const Duration(days: 6, hours: 23, minutes: 59, seconds: 59));
|
end = monday.add(
|
||||||
|
const Duration(days: 6, hours: 23, minutes: 59, seconds: 59),
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
start = DateTime(now.year, now.month, 1);
|
start = DateTime(now.year, now.month, 1);
|
||||||
end = DateTime(now.year, now.month + 1, 0, 23, 59, 59);
|
end = DateTime(now.year, now.month + 1, 0, 23, 59, 59);
|
||||||
}
|
}
|
||||||
|
|
||||||
final QueryResult<dc.ListShiftRolesByBusinessAndDatesSummaryData, dc.ListShiftRolesByBusinessAndDatesSummaryVariables> result = await _service.connector
|
final QueryResult<
|
||||||
|
dc.ListShiftRolesByBusinessAndDatesSummaryData,
|
||||||
|
dc.ListShiftRolesByBusinessAndDatesSummaryVariables
|
||||||
|
>
|
||||||
|
result = await _service.connector
|
||||||
.listShiftRolesByBusinessAndDatesSummary(
|
.listShiftRolesByBusinessAndDatesSummary(
|
||||||
businessId: businessId,
|
businessId: businessId,
|
||||||
start: _service.toTimestamp(start),
|
start: _service.toTimestamp(start),
|
||||||
@@ -96,16 +129,18 @@ class BillingConnectorRepositoryImpl implements BillingConnectorRepository {
|
|||||||
)
|
)
|
||||||
.execute();
|
.execute();
|
||||||
|
|
||||||
final List<dc.ListShiftRolesByBusinessAndDatesSummaryShiftRoles> shiftRoles = result.data.shiftRoles;
|
final List<dc.ListShiftRolesByBusinessAndDatesSummaryShiftRoles>
|
||||||
|
shiftRoles = result.data.shiftRoles;
|
||||||
if (shiftRoles.isEmpty) return <InvoiceItem>[];
|
if (shiftRoles.isEmpty) return <InvoiceItem>[];
|
||||||
|
|
||||||
final Map<String, _RoleSummary> summary = <String, _RoleSummary>{};
|
final Map<String, _RoleSummary> summary = <String, _RoleSummary>{};
|
||||||
for (final dc.ListShiftRolesByBusinessAndDatesSummaryShiftRoles role in shiftRoles) {
|
for (final dc.ListShiftRolesByBusinessAndDatesSummaryShiftRoles role
|
||||||
|
in shiftRoles) {
|
||||||
final String roleId = role.roleId;
|
final String roleId = role.roleId;
|
||||||
final String roleName = role.role.name;
|
final String roleName = role.role.name;
|
||||||
final double hours = role.hours ?? 0.0;
|
final double hours = role.hours ?? 0.0;
|
||||||
final double totalValue = role.totalValue ?? 0.0;
|
final double totalValue = role.totalValue ?? 0.0;
|
||||||
|
|
||||||
final _RoleSummary? existing = summary[roleId];
|
final _RoleSummary? existing = summary[roleId];
|
||||||
if (existing == null) {
|
if (existing == null) {
|
||||||
summary[roleId] = _RoleSummary(
|
summary[roleId] = _RoleSummary(
|
||||||
@@ -123,14 +158,16 @@ class BillingConnectorRepositoryImpl implements BillingConnectorRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return summary.values
|
return summary.values
|
||||||
.map((_RoleSummary item) => InvoiceItem(
|
.map(
|
||||||
id: item.roleId,
|
(_RoleSummary item) => InvoiceItem(
|
||||||
invoiceId: item.roleId,
|
id: item.roleId,
|
||||||
staffId: item.roleName,
|
invoiceId: item.roleId,
|
||||||
workHours: item.totalHours,
|
staffId: item.roleName,
|
||||||
rate: item.totalHours > 0 ? item.totalValue / item.totalHours : 0,
|
workHours: item.totalHours,
|
||||||
amount: item.totalValue,
|
rate: item.totalHours > 0 ? item.totalValue / item.totalHours : 0,
|
||||||
))
|
amount: item.totalValue,
|
||||||
|
),
|
||||||
|
)
|
||||||
.toList();
|
.toList();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -146,7 +183,10 @@ class BillingConnectorRepositoryImpl implements BillingConnectorRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> disputeInvoice({required String id, required String reason}) async {
|
Future<void> disputeInvoice({
|
||||||
|
required String id,
|
||||||
|
required String reason,
|
||||||
|
}) async {
|
||||||
return _service.run(() async {
|
return _service.run(() async {
|
||||||
await _service.connector
|
await _service.connector
|
||||||
.updateInvoice(id: id)
|
.updateInvoice(id: id)
|
||||||
@@ -159,36 +199,100 @@ class BillingConnectorRepositoryImpl implements BillingConnectorRepository {
|
|||||||
// --- MAPPERS ---
|
// --- MAPPERS ---
|
||||||
|
|
||||||
Invoice _mapInvoice(dynamic invoice) {
|
Invoice _mapInvoice(dynamic invoice) {
|
||||||
final List<dynamic> rolesData = invoice.roles is List ? invoice.roles : [];
|
List<InvoiceWorker> workers = <InvoiceWorker>[];
|
||||||
final List<InvoiceWorker> workers = rolesData.map((dynamic r) {
|
|
||||||
final Map<String, dynamic> role = r as Map<String, dynamic>;
|
|
||||||
|
|
||||||
// Handle various possible key naming conventions in the JSON data
|
|
||||||
final String name = role['name'] ?? role['staffName'] ?? role['fullName'] ?? 'Unknown';
|
|
||||||
final String roleTitle = role['role'] ?? role['roleName'] ?? role['title'] ?? 'Staff';
|
|
||||||
final double amount = (role['amount'] as num?)?.toDouble() ??
|
|
||||||
(role['totalValue'] as num?)?.toDouble() ?? 0.0;
|
|
||||||
final double hours = (role['hours'] as num?)?.toDouble() ??
|
|
||||||
(role['workHours'] as num?)?.toDouble() ??
|
|
||||||
(role['totalHours'] as num?)?.toDouble() ?? 0.0;
|
|
||||||
final double rate = (role['rate'] as num?)?.toDouble() ??
|
|
||||||
(role['hourlyRate'] as num?)?.toDouble() ?? 0.0;
|
|
||||||
|
|
||||||
final dynamic checkInVal = role['checkInTime'] ?? role['startTime'] ?? role['check_in_time'];
|
|
||||||
final dynamic checkOutVal = role['checkOutTime'] ?? role['endTime'] ?? role['check_out_time'];
|
|
||||||
|
|
||||||
return InvoiceWorker(
|
// Try to get workers from denormalized 'roles' field first
|
||||||
name: name,
|
final List<dynamic> rolesData = invoice.roles is List
|
||||||
role: roleTitle,
|
? invoice.roles
|
||||||
amount: amount,
|
: <dynamic>[];
|
||||||
hours: hours,
|
if (rolesData.isNotEmpty) {
|
||||||
rate: rate,
|
workers = rolesData.map((dynamic r) {
|
||||||
checkIn: _service.toDateTime(checkInVal),
|
final Map<String, dynamic> role = r as Map<String, dynamic>;
|
||||||
checkOut: _service.toDateTime(checkOutVal),
|
|
||||||
breakMinutes: role['breakMinutes'] ?? role['break_minutes'] ?? 0,
|
// Handle various possible key naming conventions in the JSON data
|
||||||
avatarUrl: role['avatarUrl'] ?? role['photoUrl'] ?? role['staffPhoto'],
|
final String name =
|
||||||
);
|
role['name'] ?? role['staffName'] ?? role['fullName'] ?? 'Unknown';
|
||||||
}).toList();
|
final String roleTitle =
|
||||||
|
role['role'] ?? role['roleName'] ?? role['title'] ?? 'Staff';
|
||||||
|
final double amount =
|
||||||
|
(role['amount'] as num?)?.toDouble() ??
|
||||||
|
(role['totalValue'] as num?)?.toDouble() ??
|
||||||
|
0.0;
|
||||||
|
final double hours =
|
||||||
|
(role['hours'] as num?)?.toDouble() ??
|
||||||
|
(role['workHours'] as num?)?.toDouble() ??
|
||||||
|
(role['totalHours'] as num?)?.toDouble() ??
|
||||||
|
0.0;
|
||||||
|
final double rate =
|
||||||
|
(role['rate'] as num?)?.toDouble() ??
|
||||||
|
(role['hourlyRate'] as num?)?.toDouble() ??
|
||||||
|
0.0;
|
||||||
|
|
||||||
|
final dynamic checkInVal =
|
||||||
|
role['checkInTime'] ?? role['startTime'] ?? role['check_in_time'];
|
||||||
|
final dynamic checkOutVal =
|
||||||
|
role['checkOutTime'] ?? role['endTime'] ?? role['check_out_time'];
|
||||||
|
|
||||||
|
return InvoiceWorker(
|
||||||
|
name: name,
|
||||||
|
role: roleTitle,
|
||||||
|
amount: amount,
|
||||||
|
hours: hours,
|
||||||
|
rate: rate,
|
||||||
|
checkIn: _service.toDateTime(checkInVal),
|
||||||
|
checkOut: _service.toDateTime(checkOutVal),
|
||||||
|
breakMinutes: role['breakMinutes'] ?? role['break_minutes'] ?? 0,
|
||||||
|
avatarUrl:
|
||||||
|
role['avatarUrl'] ?? role['photoUrl'] ?? role['staffPhoto'],
|
||||||
|
);
|
||||||
|
}).toList();
|
||||||
|
}
|
||||||
|
// Fallback: If roles is empty, try to get workers from shift applications
|
||||||
|
else if (invoice.shift != null &&
|
||||||
|
invoice.shift.applications_on_shift != null) {
|
||||||
|
final List<dynamic> apps = invoice.shift.applications_on_shift;
|
||||||
|
workers = apps.map((dynamic app) {
|
||||||
|
final String name = app.staff?.fullName ?? 'Unknown';
|
||||||
|
final String roleTitle = app.shiftRole?.role?.name ?? 'Staff';
|
||||||
|
final double amount =
|
||||||
|
(app.shiftRole?.totalValue as num?)?.toDouble() ?? 0.0;
|
||||||
|
final double hours = (app.shiftRole?.hours as num?)?.toDouble() ?? 0.0;
|
||||||
|
|
||||||
|
// Calculate rate if not explicitly provided
|
||||||
|
double rate = 0.0;
|
||||||
|
if (hours > 0) {
|
||||||
|
rate = amount / hours;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map break type to minutes
|
||||||
|
int breakMin = 0;
|
||||||
|
final String? breakType = app.shiftRole?.breakType?.toString();
|
||||||
|
if (breakType != null) {
|
||||||
|
if (breakType.contains('10'))
|
||||||
|
breakMin = 10;
|
||||||
|
else if (breakType.contains('15'))
|
||||||
|
breakMin = 15;
|
||||||
|
else if (breakType.contains('30'))
|
||||||
|
breakMin = 30;
|
||||||
|
else if (breakType.contains('45'))
|
||||||
|
breakMin = 45;
|
||||||
|
else if (breakType.contains('60'))
|
||||||
|
breakMin = 60;
|
||||||
|
}
|
||||||
|
|
||||||
|
return InvoiceWorker(
|
||||||
|
name: name,
|
||||||
|
role: roleTitle,
|
||||||
|
amount: amount,
|
||||||
|
hours: hours,
|
||||||
|
rate: rate,
|
||||||
|
checkIn: _service.toDateTime(app.checkInTime),
|
||||||
|
checkOut: _service.toDateTime(app.checkOutTime),
|
||||||
|
breakMinutes: breakMin,
|
||||||
|
avatarUrl: app.staff?.photoUrl,
|
||||||
|
);
|
||||||
|
}).toList();
|
||||||
|
}
|
||||||
|
|
||||||
return Invoice(
|
return Invoice(
|
||||||
id: invoice.id,
|
id: invoice.id,
|
||||||
@@ -202,8 +306,10 @@ class BillingConnectorRepositoryImpl implements BillingConnectorRepository {
|
|||||||
issueDate: _service.toDateTime(invoice.issueDate)!,
|
issueDate: _service.toDateTime(invoice.issueDate)!,
|
||||||
title: invoice.order?.eventName,
|
title: invoice.order?.eventName,
|
||||||
clientName: invoice.business?.businessName,
|
clientName: invoice.business?.businessName,
|
||||||
locationAddress: invoice.order?.teamHub?.hubName ?? invoice.order?.teamHub?.address,
|
locationAddress:
|
||||||
staffCount: invoice.staffCount ?? (workers.isNotEmpty ? workers.length : 0),
|
invoice.order?.teamHub?.hubName ?? invoice.order?.teamHub?.address,
|
||||||
|
staffCount:
|
||||||
|
invoice.staffCount ?? (workers.isNotEmpty ? workers.length : 0),
|
||||||
totalHours: _calculateTotalHours(rolesData),
|
totalHours: _calculateTotalHours(rolesData),
|
||||||
workers: workers,
|
workers: workers,
|
||||||
);
|
);
|
||||||
@@ -256,10 +362,7 @@ class _RoleSummary {
|
|||||||
final double totalHours;
|
final double totalHours;
|
||||||
final double totalValue;
|
final double totalValue;
|
||||||
|
|
||||||
_RoleSummary copyWith({
|
_RoleSummary copyWith({double? totalHours, double? totalValue}) {
|
||||||
double? totalHours,
|
|
||||||
double? totalValue,
|
|
||||||
}) {
|
|
||||||
return _RoleSummary(
|
return _RoleSummary(
|
||||||
roleId: roleId,
|
roleId: roleId,
|
||||||
roleName: roleName,
|
roleName: roleName,
|
||||||
@@ -268,4 +371,3 @@ class _RoleSummary {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -62,13 +62,13 @@ class BillingBloc extends Bloc<BillingEvent, BillingState>
|
|||||||
action: () async {
|
action: () async {
|
||||||
final List<dynamic> results =
|
final List<dynamic> results =
|
||||||
await Future.wait<dynamic>(<Future<dynamic>>[
|
await Future.wait<dynamic>(<Future<dynamic>>[
|
||||||
_getCurrentBillAmount.call(),
|
_getCurrentBillAmount.call(),
|
||||||
_getSavingsAmount.call(),
|
_getSavingsAmount.call(),
|
||||||
_getPendingInvoices.call(),
|
_getPendingInvoices.call(),
|
||||||
_getInvoiceHistory.call(),
|
_getInvoiceHistory.call(),
|
||||||
_getSpendingBreakdown.call(state.period),
|
_getSpendingBreakdown.call(state.period),
|
||||||
_getBankAccounts.call(),
|
_getBankAccounts.call(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
final double savings = results[1] as double;
|
final double savings = results[1] as double;
|
||||||
final List<Invoice> pendingInvoices = results[2] as List<Invoice>;
|
final List<Invoice> pendingInvoices = results[2] as List<Invoice>;
|
||||||
@@ -78,10 +78,12 @@ class BillingBloc extends Bloc<BillingEvent, BillingState>
|
|||||||
results[5] as List<BusinessBankAccount>;
|
results[5] as List<BusinessBankAccount>;
|
||||||
|
|
||||||
// Map Domain Entities to Presentation Models
|
// Map Domain Entities to Presentation Models
|
||||||
final List<BillingInvoice> uiPendingInvoices =
|
final List<BillingInvoice> uiPendingInvoices = pendingInvoices
|
||||||
pendingInvoices.map(_mapInvoiceToUiModel).toList();
|
.map(_mapInvoiceToUiModel)
|
||||||
final List<BillingInvoice> uiInvoiceHistory =
|
.toList();
|
||||||
invoiceHistory.map(_mapInvoiceToUiModel).toList();
|
final List<BillingInvoice> uiInvoiceHistory = invoiceHistory
|
||||||
|
.map(_mapInvoiceToUiModel)
|
||||||
|
.toList();
|
||||||
final List<SpendingBreakdownItem> uiSpendingBreakdown =
|
final List<SpendingBreakdownItem> uiSpendingBreakdown =
|
||||||
_mapSpendingItemsToUiModel(spendingItems);
|
_mapSpendingItemsToUiModel(spendingItems);
|
||||||
final double periodTotal = uiSpendingBreakdown.fold(
|
final double periodTotal = uiSpendingBreakdown.fold(
|
||||||
@@ -101,10 +103,8 @@ class BillingBloc extends Bloc<BillingEvent, BillingState>
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
onError: (String errorKey) => state.copyWith(
|
onError: (String errorKey) =>
|
||||||
status: BillingStatus.failure,
|
state.copyWith(status: BillingStatus.failure, errorMessage: errorKey),
|
||||||
errorMessage: errorKey,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,8 +115,8 @@ class BillingBloc extends Bloc<BillingEvent, BillingState>
|
|||||||
await handleError(
|
await handleError(
|
||||||
emit: emit.call,
|
emit: emit.call,
|
||||||
action: () async {
|
action: () async {
|
||||||
final List<InvoiceItem> spendingItems =
|
final List<InvoiceItem> spendingItems = await _getSpendingBreakdown
|
||||||
await _getSpendingBreakdown.call(event.period);
|
.call(event.period);
|
||||||
final List<SpendingBreakdownItem> uiSpendingBreakdown =
|
final List<SpendingBreakdownItem> uiSpendingBreakdown =
|
||||||
_mapSpendingItemsToUiModel(spendingItems);
|
_mapSpendingItemsToUiModel(spendingItems);
|
||||||
final double periodTotal = uiSpendingBreakdown.fold(
|
final double periodTotal = uiSpendingBreakdown.fold(
|
||||||
@@ -131,10 +131,8 @@ class BillingBloc extends Bloc<BillingEvent, BillingState>
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
onError: (String errorKey) => state.copyWith(
|
onError: (String errorKey) =>
|
||||||
status: BillingStatus.failure,
|
state.copyWith(status: BillingStatus.failure, errorMessage: errorKey),
|
||||||
errorMessage: errorKey,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,10 +146,8 @@ class BillingBloc extends Bloc<BillingEvent, BillingState>
|
|||||||
await _approveInvoice.call(event.invoiceId);
|
await _approveInvoice.call(event.invoiceId);
|
||||||
add(const BillingLoadStarted());
|
add(const BillingLoadStarted());
|
||||||
},
|
},
|
||||||
onError: (String errorKey) => state.copyWith(
|
onError: (String errorKey) =>
|
||||||
status: BillingStatus.failure,
|
state.copyWith(status: BillingStatus.failure, errorMessage: errorKey),
|
||||||
errorMessage: errorKey,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -167,10 +163,8 @@ class BillingBloc extends Bloc<BillingEvent, BillingState>
|
|||||||
);
|
);
|
||||||
add(const BillingLoadStarted());
|
add(const BillingLoadStarted());
|
||||||
},
|
},
|
||||||
onError: (String errorKey) => state.copyWith(
|
onError: (String errorKey) =>
|
||||||
status: BillingStatus.failure,
|
state.copyWith(status: BillingStatus.failure, errorMessage: errorKey),
|
||||||
errorMessage: errorKey,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -180,15 +174,18 @@ class BillingBloc extends Bloc<BillingEvent, BillingState>
|
|||||||
? 'N/A'
|
? 'N/A'
|
||||||
: formatter.format(invoice.issueDate!);
|
: formatter.format(invoice.issueDate!);
|
||||||
|
|
||||||
final List<BillingWorkerRecord> workers = invoice.workers.map((InvoiceWorker w) {
|
final List<BillingWorkerRecord> workers = invoice.workers.map((
|
||||||
|
InvoiceWorker w,
|
||||||
|
) {
|
||||||
|
final DateFormat timeFormat = DateFormat('h:mm a');
|
||||||
return BillingWorkerRecord(
|
return BillingWorkerRecord(
|
||||||
workerName: w.name,
|
workerName: w.name,
|
||||||
roleName: w.role,
|
roleName: w.role,
|
||||||
totalAmount: w.amount,
|
totalAmount: w.amount,
|
||||||
hours: w.hours,
|
hours: w.hours,
|
||||||
rate: w.rate,
|
rate: w.rate,
|
||||||
startTime: w.checkIn != null ? '${w.checkIn!.hour.toString().padLeft(2, '0')}:${w.checkIn!.minute.toString().padLeft(2, '0')}' : '--:--',
|
startTime: w.checkIn != null ? timeFormat.format(w.checkIn!) : '--:--',
|
||||||
endTime: w.checkOut != null ? '${w.checkOut!.hour.toString().padLeft(2, '0')}:${w.checkOut!.minute.toString().padLeft(2, '0')}' : '--:--',
|
endTime: w.checkOut != null ? timeFormat.format(w.checkOut!) : '--:--',
|
||||||
breakMinutes: w.breakMinutes,
|
breakMinutes: w.breakMinutes,
|
||||||
workerAvatarUrl: w.avatarUrl,
|
workerAvatarUrl: w.avatarUrl,
|
||||||
);
|
);
|
||||||
@@ -196,33 +193,35 @@ class BillingBloc extends Bloc<BillingEvent, BillingState>
|
|||||||
|
|
||||||
String? overallStart;
|
String? overallStart;
|
||||||
String? overallEnd;
|
String? overallEnd;
|
||||||
|
|
||||||
// Find valid times from workers instead of just taking the first one
|
// Find valid times from actual DateTime checks to ensure chronological sorting
|
||||||
final validStartTimes = workers
|
final List<DateTime> validCheckIns = invoice.workers
|
||||||
.where((w) => w.startTime != '--:--')
|
.where((InvoiceWorker w) => w.checkIn != null)
|
||||||
.map((w) => w.startTime)
|
.map((InvoiceWorker w) => w.checkIn!)
|
||||||
.toList();
|
.toList();
|
||||||
final validEndTimes = workers
|
final List<DateTime> validCheckOuts = invoice.workers
|
||||||
.where((w) => w.endTime != '--:--')
|
.where((InvoiceWorker w) => w.checkOut != null)
|
||||||
.map((w) => w.endTime)
|
.map((InvoiceWorker w) => w.checkOut!)
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
if (validStartTimes.isNotEmpty) {
|
final DateFormat timeFormat = DateFormat('h:mm a');
|
||||||
validStartTimes.sort();
|
|
||||||
overallStart = validStartTimes.first;
|
if (validCheckIns.isNotEmpty) {
|
||||||
|
validCheckIns.sort();
|
||||||
|
overallStart = timeFormat.format(validCheckIns.first);
|
||||||
} else if (workers.isNotEmpty) {
|
} else if (workers.isNotEmpty) {
|
||||||
overallStart = workers.first.startTime;
|
overallStart = workers.first.startTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (validEndTimes.isNotEmpty) {
|
if (validCheckOuts.isNotEmpty) {
|
||||||
validEndTimes.sort();
|
validCheckOuts.sort();
|
||||||
overallEnd = validEndTimes.last;
|
overallEnd = timeFormat.format(validCheckOuts.last);
|
||||||
} else if (workers.isNotEmpty) {
|
} else if (workers.isNotEmpty) {
|
||||||
overallEnd = workers.first.endTime;
|
overallEnd = workers.first.endTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
return BillingInvoice(
|
return BillingInvoice(
|
||||||
id: invoice.invoiceNumber ?? invoice.id,
|
id: invoice.id,
|
||||||
title: invoice.title ?? 'N/A',
|
title: invoice.title ?? 'N/A',
|
||||||
locationAddress: invoice.locationAddress ?? 'Remote',
|
locationAddress: invoice.locationAddress ?? 'Remote',
|
||||||
clientName: invoice.clientName ?? 'N/A',
|
clientName: invoice.clientName ?? 'N/A',
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ class _BillingViewState extends State<BillingView> {
|
|||||||
leading: Center(
|
leading: Center(
|
||||||
child: UiIconButton(
|
child: UiIconButton(
|
||||||
icon: UiIcons.arrowLeft,
|
icon: UiIcons.arrowLeft,
|
||||||
backgroundColor: UiColors.white.withOpacity(0.15),
|
backgroundColor: UiColors.white.withValues(alpha: 0.15),
|
||||||
iconColor: UiColors.white,
|
iconColor: UiColors.white,
|
||||||
useBlur: true,
|
useBlur: true,
|
||||||
size: 40,
|
size: 40,
|
||||||
@@ -119,7 +119,7 @@ class _BillingViewState extends State<BillingView> {
|
|||||||
Text(
|
Text(
|
||||||
t.client_billing.current_period,
|
t.client_billing.current_period,
|
||||||
style: UiTypography.footnote2r.copyWith(
|
style: UiTypography.footnote2r.copyWith(
|
||||||
color: UiColors.white.withOpacity(0.7),
|
color: UiColors.white.withValues(alpha: 0.7),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: UiConstants.space1),
|
const SizedBox(height: UiConstants.space1),
|
||||||
@@ -232,77 +232,4 @@ class _BillingViewState extends State<BillingView> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildEmptyState(BuildContext context) {
|
|
||||||
return Center(
|
|
||||||
child: Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: <Widget>[
|
|
||||||
const SizedBox(height: UiConstants.space12),
|
|
||||||
Container(
|
|
||||||
padding: const EdgeInsets.all(UiConstants.space6),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: UiColors.bgPopup,
|
|
||||||
shape: BoxShape.circle,
|
|
||||||
border: Border.all(color: UiColors.border),
|
|
||||||
),
|
|
||||||
child: const Icon(
|
|
||||||
UiIcons.file,
|
|
||||||
size: 48,
|
|
||||||
color: UiColors.textSecondary,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: UiConstants.space4),
|
|
||||||
Text(
|
|
||||||
t.client_billing.no_invoices_period,
|
|
||||||
style: UiTypography.body1m.textSecondary,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _InvoicesReadyBanner extends StatelessWidget {
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return InkWell(
|
|
||||||
onTap: () => Modular.to.toInvoiceReady(),
|
|
||||||
child: Container(
|
|
||||||
padding: const EdgeInsets.all(UiConstants.space4),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: UiColors.success.withValues(alpha: 0.1),
|
|
||||||
borderRadius: UiConstants.radiusLg,
|
|
||||||
border: Border.all(color: UiColors.success.withValues(alpha: 0.3)),
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
const Icon(UiIcons.file, color: UiColors.success),
|
|
||||||
const SizedBox(width: UiConstants.space3),
|
|
||||||
Expanded(
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
t.client_billing.invoices_ready_title,
|
|
||||||
style: UiTypography.body1b.copyWith(
|
|
||||||
color: UiColors.success,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
t.client_billing.invoices_ready_subtitle,
|
|
||||||
style: UiTypography.footnote2r.copyWith(
|
|
||||||
color: UiColors.success,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const Icon(UiIcons.chevronRight, color: UiColors.success),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,85 +0,0 @@
|
|||||||
import 'package:design_system/design_system.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:core_localization/core_localization.dart';
|
|
||||||
|
|
||||||
class ClientTimesheetsPage extends StatelessWidget {
|
|
||||||
const ClientTimesheetsPage({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Scaffold(
|
|
||||||
appBar: AppBar(
|
|
||||||
title: Text(context.t.client_billing.timesheets.title),
|
|
||||||
elevation: 0,
|
|
||||||
backgroundColor: UiColors.white,
|
|
||||||
foregroundColor: UiColors.primary,
|
|
||||||
),
|
|
||||||
body: ListView.separated(
|
|
||||||
padding: const EdgeInsets.all(UiConstants.space5),
|
|
||||||
itemCount: 3,
|
|
||||||
separatorBuilder: (context, index) => const SizedBox(height: 16),
|
|
||||||
itemBuilder: (context, index) {
|
|
||||||
final workers = ['Sarah Miller', 'David Chen', 'Mike Ross'];
|
|
||||||
final roles = ['Cashier', 'Stocker', 'Event Support'];
|
|
||||||
return Container(
|
|
||||||
padding: const EdgeInsets.all(16),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: UiColors.white,
|
|
||||||
borderRadius: UiConstants.radiusLg,
|
|
||||||
border: Border.all(color: UiColors.separatorPrimary),
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Text(workers[index], style: UiTypography.body2b.textPrimary),
|
|
||||||
Text('\$84.00', style: UiTypography.body2b.primary),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
Text(roles[index], style: UiTypography.footnote2r.textSecondary),
|
|
||||||
const SizedBox(height: 12),
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
const Icon(UiIcons.clock, size: 14, color: UiColors.iconSecondary),
|
|
||||||
const SizedBox(width: 6),
|
|
||||||
Text('09:00 AM - 05:00 PM (8h)', style: UiTypography.footnote2r.textSecondary),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: UiButton.secondary(
|
|
||||||
text: context.t.client_billing.timesheets.decline_button,
|
|
||||||
style: OutlinedButton.styleFrom(
|
|
||||||
foregroundColor: UiColors.destructive,
|
|
||||||
side: const BorderSide(color: UiColors.destructive),
|
|
||||||
),
|
|
||||||
onPressed: () {},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 12),
|
|
||||||
Expanded(
|
|
||||||
child: UiButton.primary(
|
|
||||||
text: context.t.client_billing.timesheets.approve_button,
|
|
||||||
onPressed: () {
|
|
||||||
UiSnackbar.show(
|
|
||||||
context,
|
|
||||||
message: context.t.client_billing.timesheets.approved_message,
|
|
||||||
type: UiSnackbarType.success,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,6 +2,7 @@ import 'package:core_localization/core_localization.dart';
|
|||||||
import 'package:design_system/design_system.dart';
|
import 'package:design_system/design_system.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_modular/flutter_modular.dart';
|
import 'package:flutter_modular/flutter_modular.dart';
|
||||||
|
import 'package:krow_core/core.dart';
|
||||||
|
|
||||||
import '../../blocs/billing_bloc.dart';
|
import '../../blocs/billing_bloc.dart';
|
||||||
import '../../blocs/billing_event.dart';
|
import '../../blocs/billing_event.dart';
|
||||||
@@ -72,8 +73,7 @@ class CompletionReviewActions extends StatelessWidget {
|
|||||||
Modular.get<BillingBloc>().add(
|
Modular.get<BillingBloc>().add(
|
||||||
BillingInvoiceDisputed(invoiceId, controller.text),
|
BillingInvoiceDisputed(invoiceId, controller.text),
|
||||||
);
|
);
|
||||||
Navigator.pop(dialogContext);
|
Modular.to.toClientBilling();
|
||||||
Modular.to.pop();
|
|
||||||
UiSnackbar.show(
|
UiSnackbar.show(
|
||||||
context,
|
context,
|
||||||
message: t.client_billing.flagged_success,
|
message: t.client_billing.flagged_success,
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ class InvoiceHistorySection extends StatelessWidget {
|
|||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: UiColors.white,
|
color: UiColors.white,
|
||||||
borderRadius: UiConstants.radiusLg,
|
borderRadius: UiConstants.radiusLg,
|
||||||
border: Border.all(color: UiColors.border.withOpacity(0.5)),
|
border: Border.all(color: UiColors.border.withValues(alpha: 0.5)),
|
||||||
boxShadow: <BoxShadow>[
|
boxShadow: <BoxShadow>[
|
||||||
BoxShadow(
|
BoxShadow(
|
||||||
color: UiColors.black.withValues(alpha: 0.04),
|
color: UiColors.black.withValues(alpha: 0.04),
|
||||||
@@ -77,7 +77,7 @@ class _InvoiceItem extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
child: Icon(
|
child: Icon(
|
||||||
UiIcons.file,
|
UiIcons.file,
|
||||||
color: UiColors.iconSecondary.withOpacity(0.6),
|
color: UiColors.iconSecondary.withValues(alpha: 0.6),
|
||||||
size: 20,
|
size: 20,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -86,10 +86,7 @@ class _InvoiceItem extends StatelessWidget {
|
|||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Text(
|
Text(invoice.title, style: UiTypography.body1r.textPrimary),
|
||||||
invoice.id,
|
|
||||||
style: UiTypography.body1b.textPrimary.copyWith(fontSize: 15),
|
|
||||||
),
|
|
||||||
Text(
|
Text(
|
||||||
invoice.date,
|
invoice.date,
|
||||||
style: UiTypography.footnote2r.textSecondary,
|
style: UiTypography.footnote2r.textSecondary,
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ class PendingInvoicesSection extends StatelessWidget {
|
|||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: UiColors.white,
|
color: UiColors.white,
|
||||||
borderRadius: UiConstants.radiusLg,
|
borderRadius: UiConstants.radiusLg,
|
||||||
border: Border.all(color: UiColors.border.withOpacity(0.5)),
|
border: Border.all(color: UiColors.border.withValues(alpha: 0.5)),
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
@@ -41,9 +41,9 @@ class PendingInvoicesSection extends StatelessWidget {
|
|||||||
Expanded(
|
Expanded(
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: <Widget>[
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: <Widget>[
|
||||||
Text(
|
Text(
|
||||||
t.client_billing.awaiting_approval,
|
t.client_billing.awaiting_approval,
|
||||||
style: UiTypography.body1b.textPrimary,
|
style: UiTypography.body1b.textPrimary,
|
||||||
@@ -79,7 +79,7 @@ class PendingInvoicesSection extends StatelessWidget {
|
|||||||
Icon(
|
Icon(
|
||||||
UiIcons.chevronRight,
|
UiIcons.chevronRight,
|
||||||
size: 20,
|
size: 20,
|
||||||
color: UiColors.iconSecondary.withOpacity(0.5),
|
color: UiColors.iconSecondary.withValues(alpha: 0.5),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -180,7 +180,7 @@ class PendingInvoiceCard extends StatelessWidget {
|
|||||||
Container(
|
Container(
|
||||||
width: 1,
|
width: 1,
|
||||||
height: 32,
|
height: 32,
|
||||||
color: UiColors.border.withOpacity(0.3),
|
color: UiColors.border.withValues(alpha: 0.3),
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: _buildStatItem(
|
child: _buildStatItem(
|
||||||
@@ -192,7 +192,7 @@ class PendingInvoiceCard extends StatelessWidget {
|
|||||||
Container(
|
Container(
|
||||||
width: 1,
|
width: 1,
|
||||||
height: 32,
|
height: 32,
|
||||||
color: UiColors.border.withOpacity(0.3),
|
color: UiColors.border.withValues(alpha: 0.3),
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: _buildStatItem(
|
child: _buildStatItem(
|
||||||
@@ -225,7 +225,11 @@ class PendingInvoiceCard extends StatelessWidget {
|
|||||||
Widget _buildStatItem(IconData icon, String value, String label) {
|
Widget _buildStatItem(IconData icon, String value, String label) {
|
||||||
return Column(
|
return Column(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Icon(icon, size: 20, color: UiColors.iconSecondary.withOpacity(0.8)),
|
Icon(
|
||||||
|
icon,
|
||||||
|
size: 20,
|
||||||
|
color: UiColors.iconSecondary.withValues(alpha: 0.8),
|
||||||
|
),
|
||||||
const SizedBox(height: 6),
|
const SizedBox(height: 6),
|
||||||
Text(
|
Text(
|
||||||
value,
|
value,
|
||||||
|
|||||||
@@ -61,6 +61,29 @@ query listInvoices(
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
shift {
|
||||||
|
id
|
||||||
|
title
|
||||||
|
applications_on_shift {
|
||||||
|
id
|
||||||
|
status
|
||||||
|
checkInTime
|
||||||
|
checkOutTime
|
||||||
|
staff {
|
||||||
|
fullName
|
||||||
|
photoUrl
|
||||||
|
}
|
||||||
|
shiftRole {
|
||||||
|
role {
|
||||||
|
name
|
||||||
|
}
|
||||||
|
totalValue
|
||||||
|
hours
|
||||||
|
breakType
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,6 +146,29 @@ query getInvoiceById($id: UUID!) @auth(level: USER) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
shift {
|
||||||
|
id
|
||||||
|
title
|
||||||
|
applications_on_shift {
|
||||||
|
id
|
||||||
|
status
|
||||||
|
checkInTime
|
||||||
|
checkOutTime
|
||||||
|
staff {
|
||||||
|
fullName
|
||||||
|
photoUrl
|
||||||
|
}
|
||||||
|
shiftRole {
|
||||||
|
role {
|
||||||
|
name
|
||||||
|
}
|
||||||
|
totalValue
|
||||||
|
hours
|
||||||
|
breakType
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -194,6 +240,29 @@ query listInvoicesByVendorId(
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
shift {
|
||||||
|
id
|
||||||
|
title
|
||||||
|
applications_on_shift {
|
||||||
|
id
|
||||||
|
status
|
||||||
|
checkInTime
|
||||||
|
checkOutTime
|
||||||
|
staff {
|
||||||
|
fullName
|
||||||
|
photoUrl
|
||||||
|
}
|
||||||
|
shiftRole {
|
||||||
|
role {
|
||||||
|
name
|
||||||
|
}
|
||||||
|
totalValue
|
||||||
|
hours
|
||||||
|
breakType
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -265,6 +334,29 @@ query listInvoicesByBusinessId(
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
shift {
|
||||||
|
id
|
||||||
|
title
|
||||||
|
applications_on_shift {
|
||||||
|
id
|
||||||
|
status
|
||||||
|
checkInTime
|
||||||
|
checkOutTime
|
||||||
|
staff {
|
||||||
|
fullName
|
||||||
|
photoUrl
|
||||||
|
}
|
||||||
|
shiftRole {
|
||||||
|
role {
|
||||||
|
name
|
||||||
|
}
|
||||||
|
totalValue
|
||||||
|
hours
|
||||||
|
breakType
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -336,6 +428,29 @@ query listInvoicesByOrderId(
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
shift {
|
||||||
|
id
|
||||||
|
title
|
||||||
|
applications_on_shift {
|
||||||
|
id
|
||||||
|
status
|
||||||
|
checkInTime
|
||||||
|
checkOutTime
|
||||||
|
staff {
|
||||||
|
fullName
|
||||||
|
photoUrl
|
||||||
|
}
|
||||||
|
shiftRole {
|
||||||
|
role {
|
||||||
|
name
|
||||||
|
}
|
||||||
|
totalValue
|
||||||
|
hours
|
||||||
|
breakType
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -407,6 +522,29 @@ query listInvoicesByStatus(
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
shift {
|
||||||
|
id
|
||||||
|
title
|
||||||
|
applications_on_shift {
|
||||||
|
id
|
||||||
|
status
|
||||||
|
checkInTime
|
||||||
|
checkOutTime
|
||||||
|
staff {
|
||||||
|
fullName
|
||||||
|
photoUrl
|
||||||
|
}
|
||||||
|
shiftRole {
|
||||||
|
role {
|
||||||
|
name
|
||||||
|
}
|
||||||
|
totalValue
|
||||||
|
hours
|
||||||
|
breakType
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -501,6 +639,29 @@ query filterInvoices(
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
shift {
|
||||||
|
id
|
||||||
|
title
|
||||||
|
applications_on_shift {
|
||||||
|
id
|
||||||
|
status
|
||||||
|
checkInTime
|
||||||
|
checkOutTime
|
||||||
|
staff {
|
||||||
|
fullName
|
||||||
|
photoUrl
|
||||||
|
}
|
||||||
|
shiftRole {
|
||||||
|
role {
|
||||||
|
name
|
||||||
|
}
|
||||||
|
totalValue
|
||||||
|
hours
|
||||||
|
breakType
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -576,5 +737,28 @@ query listOverdueInvoices(
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
shift {
|
||||||
|
id
|
||||||
|
title
|
||||||
|
applications_on_shift {
|
||||||
|
id
|
||||||
|
status
|
||||||
|
checkInTime
|
||||||
|
checkOutTime
|
||||||
|
staff {
|
||||||
|
fullName
|
||||||
|
photoUrl
|
||||||
|
}
|
||||||
|
shiftRole {
|
||||||
|
role {
|
||||||
|
name
|
||||||
|
}
|
||||||
|
totalValue
|
||||||
|
hours
|
||||||
|
breakType
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user