Initial commit: DLS - Dental Lab System
- Flutter + PocketBase dental lab management system - Clinic & lab dashboards, job tracking, patient management - Product catalog, finance tracking, multi-language support - AI assistant integration, realtime notifications - Windows installer (Inno Setup) included - Developed by kovakyazilim.com
This commit is contained in:
@@ -0,0 +1,124 @@
|
||||
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(),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user