Files
lab-app/lib/features/lab/connections/connection_stats_repository.dart
T
2026-06-10 23:22:15 +03:00

125 lines
3.9 KiB
Dart

import 'package:pocketbase/pocketbase.dart';
import '../../../core/api/pocketbase_client.dart';
import '../../../models/job.dart';
class ConnectionStats {
const ConnectionStats({
required this.totalJobs,
required this.byStatus,
required this.byType,
required this.totalRevenue,
required this.pendingRevenue,
required this.thisMonthJobs,
required this.lastMonthJobs,
required this.revisionCount,
required this.recentJobs,
});
final int totalJobs;
final Map<String, int> byStatus; // 'in_progress', 'sent', 'delivered', 'cancelled'
final Map<String, int> byType; // prosthetic_type -> count
final double totalRevenue;
final double pendingRevenue;
final int thisMonthJobs;
final int lastMonthJobs;
final int revisionCount;
final List<Job> recentJobs;
int get deliveredJobs => byStatus['delivered'] ?? 0;
int get activeJobs => (byStatus['in_progress'] ?? 0) + (byStatus['sent'] ?? 0);
int get cancelledJobs => byStatus['cancelled'] ?? 0;
double get revisionRate => totalJobs > 0 ? revisionCount / totalJobs * 100 : 0;
double get completionRate => totalJobs > 0 ? deliveredJobs / totalJobs * 100 : 0;
}
class ConnectionStatsRepository {
ConnectionStatsRepository._();
static final instance = ConnectionStatsRepository._();
PocketBase get _pb => PocketBaseClient.instance.pb;
Future<ConnectionStats> fetchStats({
required String labTenantId,
required String clinicTenantId,
}) async {
final now = DateTime.now();
final thisMonthStart = DateTime(now.year, now.month, 1);
final lastMonthStart = DateTime(now.year, now.month - 1, 1);
final lastMonthEnd = thisMonthStart.subtract(const Duration(seconds: 1));
final filter = 'lab_tenant_id = "$labTenantId" && clinic_tenant_id = "$clinicTenantId"';
final results = await Future.wait([
_pb.collection('jobs').getList(
filter: filter,
perPage: 500,
expand: 'clinic_tenant_id,lab_tenant_id',
),
_pb.collection('finance_entries').getList(
filter: 'tenant_id = "$labTenantId" && job_id.clinic_tenant_id = "$clinicTenantId"',
perPage: 500,
),
]);
final jobsResult = results[0];
final financeResult = results[1];
final allJobs = jobsResult.items
.map((r) => Job.fromJson(r.toJson()))
.toList();
// Status breakdown
final byStatus = <String, int>{};
final byType = <String, int>{};
int thisMonthJobs = 0;
int lastMonthJobs = 0;
int revisionCount = 0;
for (final job in allJobs) {
// Status
final s = job.status.value;
byStatus[s] = (byStatus[s] ?? 0) + 1;
// Type
final t = job.prostheticType.value;
byType[t] = (byType[t] ?? 0) + 1;
// Monthly
final created = job.dateCreated;
if (created.isAfter(thisMonthStart)) thisMonthJobs++;
else if (created.isAfter(lastMonthStart) && created.isBefore(lastMonthEnd)) lastMonthJobs++;
// Revision
if (job.status == JobStatus.inProgress && job.currentStep == null) revisionCount++;
}
// Finance
double totalRevenue = 0;
double pendingRevenue = 0;
for (final r in financeResult.items) {
final j = r.toJson();
final amount = (j['amount'] as num?)?.toDouble() ?? 0;
totalRevenue += amount;
if (j['status'] == 'pending') pendingRevenue += amount;
}
// Recent jobs (last 5 delivered or sent)
final recent = allJobs
.where((j) => j.status == JobStatus.delivered || j.status == JobStatus.sent)
.toList()
..sort((a, b) => b.dateCreated.compareTo(a.dateCreated));
return ConnectionStats(
totalJobs: allJobs.length,
byStatus: byStatus,
byType: byType,
totalRevenue: totalRevenue,
pendingRevenue: pendingRevenue,
thisMonthJobs: thisMonthJobs,
lastMonthJobs: lastMonthJobs,
revisionCount: revisionCount,
recentJobs: recent.take(5).toList(),
);
}
}