|
|
|
|
@@ -1,261 +1,58 @@
|
|
|
|
|
import 'package:firebase_data_connect/firebase_data_connect.dart' as fdc;
|
|
|
|
|
import 'package:krow_data_connect/krow_data_connect.dart' as data_connect;
|
|
|
|
|
import 'package:krow_data_connect/krow_data_connect.dart' as dc;
|
|
|
|
|
import 'package:krow_domain/krow_domain.dart';
|
|
|
|
|
import '../../domain/models/billing_period.dart';
|
|
|
|
|
import '../../domain/repositories/billing_repository.dart';
|
|
|
|
|
|
|
|
|
|
/// Implementation of [BillingRepository] in the Data layer.
|
|
|
|
|
/// Implementation of [BillingRepository] that delegates to [dc.BillingConnectorRepository].
|
|
|
|
|
///
|
|
|
|
|
/// This class is responsible for retrieving billing data from the
|
|
|
|
|
/// Data Connect layer and mapping it to Domain entities.
|
|
|
|
|
/// This implementation follows the "Buffer Layer" pattern by using a dedicated
|
|
|
|
|
/// connector repository from the data_connect package.
|
|
|
|
|
class BillingRepositoryImpl implements BillingRepository {
|
|
|
|
|
/// Creates a [BillingRepositoryImpl].
|
|
|
|
|
final dc.BillingConnectorRepository _connectorRepository;
|
|
|
|
|
final dc.DataConnectService _service;
|
|
|
|
|
|
|
|
|
|
BillingRepositoryImpl({
|
|
|
|
|
data_connect.DataConnectService? service,
|
|
|
|
|
}) : _service = service ?? data_connect.DataConnectService.instance;
|
|
|
|
|
dc.BillingConnectorRepository? connectorRepository,
|
|
|
|
|
dc.DataConnectService? service,
|
|
|
|
|
}) : _connectorRepository = connectorRepository ??
|
|
|
|
|
dc.DataConnectService.instance.getBillingRepository(),
|
|
|
|
|
_service = service ?? dc.DataConnectService.instance;
|
|
|
|
|
|
|
|
|
|
final data_connect.DataConnectService _service;
|
|
|
|
|
|
|
|
|
|
/// Fetches bank accounts associated with the business.
|
|
|
|
|
@override
|
|
|
|
|
Future<List<BusinessBankAccount>> getBankAccounts() async {
|
|
|
|
|
return _service.run(() async {
|
|
|
|
|
final String businessId = await _service.getBusinessId();
|
|
|
|
|
|
|
|
|
|
final fdc.QueryResult<
|
|
|
|
|
data_connect.GetAccountsByOwnerIdData,
|
|
|
|
|
data_connect.GetAccountsByOwnerIdVariables> result =
|
|
|
|
|
await _service.connector
|
|
|
|
|
.getAccountsByOwnerId(ownerId: businessId)
|
|
|
|
|
.execute();
|
|
|
|
|
|
|
|
|
|
return result.data.accounts.map(_mapBankAccount).toList();
|
|
|
|
|
});
|
|
|
|
|
final businessId = await _service.getBusinessId();
|
|
|
|
|
return _connectorRepository.getBankAccounts(businessId: businessId);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Fetches the current bill amount by aggregating open invoices.
|
|
|
|
|
@override
|
|
|
|
|
Future<double> getCurrentBillAmount() async {
|
|
|
|
|
return _service.run(() async {
|
|
|
|
|
final String businessId = await _service.getBusinessId();
|
|
|
|
|
|
|
|
|
|
final fdc.QueryResult<data_connect.ListInvoicesByBusinessIdData,
|
|
|
|
|
data_connect.ListInvoicesByBusinessIdVariables> result =
|
|
|
|
|
await _service.connector
|
|
|
|
|
.listInvoicesByBusinessId(businessId: businessId)
|
|
|
|
|
.execute();
|
|
|
|
|
|
|
|
|
|
return result.data.invoices
|
|
|
|
|
.map(_mapInvoice)
|
|
|
|
|
.where((Invoice i) => i.status == InvoiceStatus.open)
|
|
|
|
|
.fold<double>(
|
|
|
|
|
0.0,
|
|
|
|
|
(double sum, Invoice item) => sum + item.totalAmount,
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
final businessId = await _service.getBusinessId();
|
|
|
|
|
return _connectorRepository.getCurrentBillAmount(businessId: businessId);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Fetches the history of paid invoices.
|
|
|
|
|
@override
|
|
|
|
|
Future<List<Invoice>> getInvoiceHistory() async {
|
|
|
|
|
return _service.run(() async {
|
|
|
|
|
final String businessId = await _service.getBusinessId();
|
|
|
|
|
|
|
|
|
|
final fdc.QueryResult<data_connect.ListInvoicesByBusinessIdData,
|
|
|
|
|
data_connect.ListInvoicesByBusinessIdVariables> result =
|
|
|
|
|
await _service.connector
|
|
|
|
|
.listInvoicesByBusinessId(
|
|
|
|
|
businessId: businessId,
|
|
|
|
|
)
|
|
|
|
|
.limit(10)
|
|
|
|
|
.execute();
|
|
|
|
|
|
|
|
|
|
return result.data.invoices.map(_mapInvoice).toList();
|
|
|
|
|
});
|
|
|
|
|
final businessId = await _service.getBusinessId();
|
|
|
|
|
return _connectorRepository.getInvoiceHistory(businessId: businessId);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Fetches pending invoices (Open or Disputed).
|
|
|
|
|
@override
|
|
|
|
|
Future<List<Invoice>> getPendingInvoices() async {
|
|
|
|
|
return _service.run(() async {
|
|
|
|
|
final String businessId = await _service.getBusinessId();
|
|
|
|
|
|
|
|
|
|
final fdc.QueryResult<data_connect.ListInvoicesByBusinessIdData,
|
|
|
|
|
data_connect.ListInvoicesByBusinessIdVariables> result =
|
|
|
|
|
await _service.connector
|
|
|
|
|
.listInvoicesByBusinessId(businessId: businessId)
|
|
|
|
|
.execute();
|
|
|
|
|
|
|
|
|
|
return result.data.invoices
|
|
|
|
|
.map(_mapInvoice)
|
|
|
|
|
.where(
|
|
|
|
|
(Invoice i) =>
|
|
|
|
|
i.status == InvoiceStatus.open ||
|
|
|
|
|
i.status == InvoiceStatus.disputed,
|
|
|
|
|
)
|
|
|
|
|
.toList();
|
|
|
|
|
});
|
|
|
|
|
final businessId = await _service.getBusinessId();
|
|
|
|
|
return _connectorRepository.getPendingInvoices(businessId: businessId);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Fetches the estimated savings amount.
|
|
|
|
|
@override
|
|
|
|
|
Future<double> getSavingsAmount() async {
|
|
|
|
|
// Simulating savings calculation (e.g., comparing to market rates).
|
|
|
|
|
await Future<void>.delayed(const Duration(milliseconds: 0));
|
|
|
|
|
// Simulating savings calculation
|
|
|
|
|
return 0.0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Fetches the breakdown of spending.
|
|
|
|
|
@override
|
|
|
|
|
Future<List<InvoiceItem>> getSpendingBreakdown(BillingPeriod period) async {
|
|
|
|
|
return _service.run(() async {
|
|
|
|
|
final String businessId = await _service.getBusinessId();
|
|
|
|
|
|
|
|
|
|
final DateTime now = DateTime.now();
|
|
|
|
|
final DateTime start;
|
|
|
|
|
final DateTime end;
|
|
|
|
|
if (period == BillingPeriod.week) {
|
|
|
|
|
final int daysFromMonday = now.weekday - DateTime.monday;
|
|
|
|
|
final DateTime monday = DateTime(
|
|
|
|
|
now.year,
|
|
|
|
|
now.month,
|
|
|
|
|
now.day,
|
|
|
|
|
).subtract(Duration(days: daysFromMonday));
|
|
|
|
|
start = DateTime(monday.year, monday.month, monday.day);
|
|
|
|
|
end = DateTime(
|
|
|
|
|
monday.year, monday.month, monday.day + 6, 23, 59, 59, 999);
|
|
|
|
|
} else {
|
|
|
|
|
start = DateTime(now.year, now.month, 1);
|
|
|
|
|
end = DateTime(now.year, now.month + 1, 0, 23, 59, 59, 999);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
final fdc.QueryResult<
|
|
|
|
|
data_connect.ListShiftRolesByBusinessAndDatesSummaryData,
|
|
|
|
|
data_connect.ListShiftRolesByBusinessAndDatesSummaryVariables>
|
|
|
|
|
result = await _service.connector
|
|
|
|
|
.listShiftRolesByBusinessAndDatesSummary(
|
|
|
|
|
businessId: businessId,
|
|
|
|
|
start: _service.toTimestamp(start),
|
|
|
|
|
end: _service.toTimestamp(end),
|
|
|
|
|
)
|
|
|
|
|
.execute();
|
|
|
|
|
|
|
|
|
|
final List<data_connect.ListShiftRolesByBusinessAndDatesSummaryShiftRoles>
|
|
|
|
|
shiftRoles = result.data.shiftRoles;
|
|
|
|
|
if (shiftRoles.isEmpty) {
|
|
|
|
|
return <InvoiceItem>[];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
final Map<String, _RoleSummary> summary = <String, _RoleSummary>{};
|
|
|
|
|
for (final data_connect
|
|
|
|
|
.ListShiftRolesByBusinessAndDatesSummaryShiftRoles role
|
|
|
|
|
in shiftRoles) {
|
|
|
|
|
final String roleId = role.roleId;
|
|
|
|
|
final String roleName = role.role.name;
|
|
|
|
|
final double hours = role.hours ?? 0.0;
|
|
|
|
|
final double totalValue = role.totalValue ?? 0.0;
|
|
|
|
|
final _RoleSummary? existing = summary[roleId];
|
|
|
|
|
if (existing == null) {
|
|
|
|
|
summary[roleId] = _RoleSummary(
|
|
|
|
|
roleId: roleId,
|
|
|
|
|
roleName: roleName,
|
|
|
|
|
totalHours: hours,
|
|
|
|
|
totalValue: totalValue,
|
|
|
|
|
);
|
|
|
|
|
} else {
|
|
|
|
|
summary[roleId] = existing.copyWith(
|
|
|
|
|
totalHours: existing.totalHours + hours,
|
|
|
|
|
totalValue: existing.totalValue + totalValue,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return summary.values
|
|
|
|
|
.map(
|
|
|
|
|
(_RoleSummary item) => InvoiceItem(
|
|
|
|
|
id: item.roleId,
|
|
|
|
|
invoiceId: item.roleId,
|
|
|
|
|
staffId: item.roleName,
|
|
|
|
|
workHours: item.totalHours,
|
|
|
|
|
rate: item.totalHours > 0 ? item.totalValue / item.totalHours : 0,
|
|
|
|
|
amount: item.totalValue,
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
.toList();
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Invoice _mapInvoice(data_connect.ListInvoicesByBusinessIdInvoices invoice) {
|
|
|
|
|
return Invoice(
|
|
|
|
|
id: invoice.id,
|
|
|
|
|
eventId: invoice.orderId,
|
|
|
|
|
businessId: invoice.businessId,
|
|
|
|
|
status: _mapInvoiceStatus(invoice.status),
|
|
|
|
|
totalAmount: invoice.amount,
|
|
|
|
|
workAmount: invoice.amount,
|
|
|
|
|
addonsAmount: invoice.otherCharges ?? 0,
|
|
|
|
|
invoiceNumber: invoice.invoiceNumber,
|
|
|
|
|
issueDate: _service.toDateTime(invoice.issueDate)!,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
BusinessBankAccount _mapBankAccount(
|
|
|
|
|
data_connect.GetAccountsByOwnerIdAccounts account,
|
|
|
|
|
) {
|
|
|
|
|
return BusinessBankAccountAdapter.fromPrimitives(
|
|
|
|
|
id: account.id,
|
|
|
|
|
bank: account.bank,
|
|
|
|
|
last4: account.last4,
|
|
|
|
|
isPrimary: account.isPrimary ?? false,
|
|
|
|
|
expiryTime: _service.toDateTime(account.expiryTime),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
InvoiceStatus _mapInvoiceStatus(
|
|
|
|
|
data_connect.EnumValue<data_connect.InvoiceStatus> status,
|
|
|
|
|
) {
|
|
|
|
|
if (status is data_connect.Known<data_connect.InvoiceStatus>) {
|
|
|
|
|
switch (status.value) {
|
|
|
|
|
case data_connect.InvoiceStatus.PAID:
|
|
|
|
|
return InvoiceStatus.paid;
|
|
|
|
|
case data_connect.InvoiceStatus.OVERDUE:
|
|
|
|
|
return InvoiceStatus.overdue;
|
|
|
|
|
case data_connect.InvoiceStatus.DISPUTED:
|
|
|
|
|
return InvoiceStatus.disputed;
|
|
|
|
|
case data_connect.InvoiceStatus.APPROVED:
|
|
|
|
|
return InvoiceStatus.verified;
|
|
|
|
|
case data_connect.InvoiceStatus.PENDING_REVIEW:
|
|
|
|
|
case data_connect.InvoiceStatus.PENDING:
|
|
|
|
|
case data_connect.InvoiceStatus.DRAFT:
|
|
|
|
|
return InvoiceStatus.open;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return InvoiceStatus.open;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class _RoleSummary {
|
|
|
|
|
const _RoleSummary({
|
|
|
|
|
required this.roleId,
|
|
|
|
|
required this.roleName,
|
|
|
|
|
required this.totalHours,
|
|
|
|
|
required this.totalValue,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
final String roleId;
|
|
|
|
|
final String roleName;
|
|
|
|
|
final double totalHours;
|
|
|
|
|
final double totalValue;
|
|
|
|
|
|
|
|
|
|
_RoleSummary copyWith({
|
|
|
|
|
double? totalHours,
|
|
|
|
|
double? totalValue,
|
|
|
|
|
}) {
|
|
|
|
|
return _RoleSummary(
|
|
|
|
|
roleId: roleId,
|
|
|
|
|
roleName: roleName,
|
|
|
|
|
totalHours: totalHours ?? this.totalHours,
|
|
|
|
|
totalValue: totalValue ?? this.totalValue,
|
|
|
|
|
final businessId = await _service.getBusinessId();
|
|
|
|
|
return _connectorRepository.getSpendingBreakdown(
|
|
|
|
|
businessId: businessId,
|
|
|
|
|
period: period,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|