125 lines
3.9 KiB
Dart
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(),
|
|
);
|
|
}
|
|
}
|