225 lines
7.4 KiB
Dart
225 lines
7.4 KiB
Dart
import 'dart:async';
|
||
import 'package:pocketbase/pocketbase.dart';
|
||
import '../../../core/api/pocketbase_client.dart';
|
||
import '../../../core/services/finance_service.dart';
|
||
import '../../../core/services/job_history_service.dart';
|
||
import '../../../models/job.dart';
|
||
|
||
const _listExpand = 'clinic_tenant_id,lab_tenant_id,patient_id';
|
||
const _detailExpand = 'clinic_tenant_id,lab_tenant_id,patient_id,prosthetic_id';
|
||
|
||
class ClinicJobsRepository {
|
||
ClinicJobsRepository._();
|
||
static final instance = ClinicJobsRepository._();
|
||
|
||
PocketBase get _pb => PocketBaseClient.instance.pb;
|
||
|
||
Future<List<Job>> listOutbound(
|
||
String clinicTenantId, {
|
||
List<String>? statuses,
|
||
String? location,
|
||
String? filterExtra,
|
||
int page = 1,
|
||
int limit = 30,
|
||
}) async {
|
||
final filterParts = ['clinic_tenant_id = "$clinicTenantId"'];
|
||
if (statuses != null && statuses.isNotEmpty) {
|
||
final statusFilter = statuses.map((s) => 'status = "$s"').join(' || ');
|
||
filterParts.add('($statusFilter)');
|
||
}
|
||
if (location != null) {
|
||
filterParts.add('location = "$location"');
|
||
}
|
||
if (filterExtra != null) {
|
||
filterParts.add('($filterExtra)');
|
||
}
|
||
|
||
final result = await _pb.collection('jobs').getList(
|
||
page: page,
|
||
perPage: limit,
|
||
filter: filterParts.join(' && '),
|
||
expand: _listExpand,
|
||
sort: '-created',
|
||
);
|
||
return (result.items.map((r) => Job.fromJson(r.toJson())).toList()
|
||
..sort((a, b) => b.dateCreated.compareTo(a.dateCreated)));
|
||
}
|
||
|
||
Future<Job> getJob(String jobId) async {
|
||
final record =
|
||
await _pb.collection('jobs').getOne(jobId, expand: _detailExpand);
|
||
return Job.fromJson(record.toJson());
|
||
}
|
||
|
||
Future<Job> createJob({
|
||
required String clinicTenantId,
|
||
required String labTenantId,
|
||
required String clinicName,
|
||
required String labName,
|
||
required String patientCode,
|
||
String? prostheticId,
|
||
required ProstheticType prostheticType,
|
||
required List<String> teeth,
|
||
String? patientId,
|
||
String? color,
|
||
String? description,
|
||
String? dueDate,
|
||
double? price,
|
||
String? currency,
|
||
JobWorkflowType? workflowType,
|
||
bool provaRequired = true,
|
||
List<String> workflowSteps = const [],
|
||
}) async {
|
||
final record = await _pb.collection('jobs').create(body: {
|
||
'clinic_tenant_id': clinicTenantId,
|
||
'lab_tenant_id': labTenantId,
|
||
'patient_code': patientCode,
|
||
if (patientId != null) 'patient_id': patientId,
|
||
if (prostheticId != null && prostheticId.isNotEmpty)
|
||
'prosthetic_id': prostheticId,
|
||
'prosthetic_type': prostheticType.value,
|
||
'member_count': teeth.length,
|
||
'teeth': teeth,
|
||
if (color != null) 'color': color,
|
||
if (description != null) 'description': description,
|
||
if (dueDate != null) 'due_date': dueDate,
|
||
if (price != null) 'price': price,
|
||
if (currency != null && currency.isNotEmpty) 'currency': currency,
|
||
if (workflowType != null) 'workflow_type': workflowType.value,
|
||
if (workflowSteps.isNotEmpty) 'workflow_steps': workflowSteps,
|
||
'status': 'pending',
|
||
'location': 'at_clinic',
|
||
'prova_required': provaRequired,
|
||
});
|
||
final job = Job.fromJson(record.toJson());
|
||
if (price != null && price > 0) {
|
||
try {
|
||
await FinanceService.instance.ensureEntriesForJob(
|
||
jobId: job.id,
|
||
clinicTenantId: clinicTenantId,
|
||
labTenantId: labTenantId,
|
||
clinicName: clinicName,
|
||
labName: labName,
|
||
amount: price,
|
||
currency: currency ?? 'TRY',
|
||
);
|
||
} catch (_) {
|
||
await _pb.collection('jobs').delete(job.id);
|
||
rethrow;
|
||
}
|
||
}
|
||
return job;
|
||
}
|
||
|
||
Future<Job> approveAtClinic(String jobId, Job job, {String? note}) async {
|
||
if (job.status != JobStatus.inProgress ||
|
||
job.location != JobLocation.atClinic) {
|
||
throw StateError('Yalnızca klinikteki üretim işleri onaylanabilir.');
|
||
}
|
||
final nextStep = job.provaRequired ? job.currentStep : job.nextStep;
|
||
if (nextStep == null) throw Exception('Bu aşamadan ileri gidilemez.');
|
||
|
||
final record = await _pb.collection('jobs').update(jobId, body: {
|
||
'current_step': nextStep.value,
|
||
'location': 'at_lab',
|
||
});
|
||
final updated = Job.fromJson(record.toJson());
|
||
unawaited(JobHistoryService.instance.append(
|
||
jobId: jobId,
|
||
clinicTenantId: job.clinicTenantId,
|
||
labTenantId: job.labTenantId,
|
||
action: JobHistoryAction.approved,
|
||
step: job.currentStep,
|
||
note: note,
|
||
));
|
||
return updated;
|
||
}
|
||
|
||
Future<Job> requestRevision(String jobId, Job job,
|
||
{required String note}) async {
|
||
final record = await _pb.collection('jobs').update(jobId, body: {
|
||
'location': 'at_lab',
|
||
});
|
||
final updated = Job.fromJson(record.toJson());
|
||
await JobHistoryService.instance.append(
|
||
jobId: jobId,
|
||
clinicTenantId: job.clinicTenantId,
|
||
labTenantId: job.labTenantId,
|
||
action: JobHistoryAction.revisionRequested,
|
||
step: job.currentStep,
|
||
note: note,
|
||
);
|
||
return updated;
|
||
}
|
||
|
||
Future<Job> markDelivered(String jobId, Job job, {String? note}) async {
|
||
final record = await _pb.collection('jobs').update(jobId, body: {
|
||
'status': 'delivered',
|
||
});
|
||
unawaited(JobHistoryService.instance.append(
|
||
jobId: jobId,
|
||
clinicTenantId: job.clinicTenantId,
|
||
labTenantId: job.labTenantId,
|
||
action: JobHistoryAction.delivered,
|
||
note: note,
|
||
));
|
||
return Job.fromJson(record.toJson());
|
||
}
|
||
|
||
Future<Job> cancelJob(String jobId, Job job) async {
|
||
final record = await _pb.collection('jobs').update(jobId, body: {
|
||
'status': 'cancelled',
|
||
});
|
||
await FinanceService.instance.deletePendingEntriesForJob(jobId);
|
||
unawaited(JobHistoryService.instance.append(
|
||
jobId: jobId,
|
||
clinicTenantId: job.clinicTenantId,
|
||
labTenantId: job.labTenantId,
|
||
action: JobHistoryAction.cancelled,
|
||
));
|
||
return Job.fromJson(record.toJson());
|
||
}
|
||
|
||
Future<List<Map<String, dynamic>>> listApprovedLabs(
|
||
String clinicTenantId) async {
|
||
final result = await _pb.collection('connections').getList(
|
||
filter: 'clinic_tenant_id = "$clinicTenantId" && status = "approved"',
|
||
expand: 'lab_tenant_id',
|
||
perPage: 100,
|
||
);
|
||
return result.items.map((r) {
|
||
final expand = r.toJson()['expand'] as Map<String, dynamic>?;
|
||
return expand?['lab_tenant_id'] as Map<String, dynamic>? ??
|
||
{'id': r.data['lab_tenant_id']};
|
||
}).toList();
|
||
}
|
||
|
||
Future<List<Job>> listJobsByPatient(String patientId,
|
||
{int limit = 50}) async {
|
||
final result = await _pb.collection('jobs').getList(
|
||
filter: 'patient_id = "$patientId"',
|
||
perPage: limit,
|
||
expand: _listExpand,
|
||
sort: '-created',
|
||
);
|
||
return (result.items.map((r) => Job.fromJson(r.toJson())).toList()
|
||
..sort((a, b) => b.dateCreated.compareTo(a.dateCreated)));
|
||
}
|
||
|
||
Future<int> countDelivered(String clinicTenantId,
|
||
{DateTime? from, DateTime? to}) async {
|
||
final parts = [
|
||
'clinic_tenant_id = "$clinicTenantId"',
|
||
'status = "delivered"'
|
||
];
|
||
if (from != null) parts.add('updated >= "${_date(from)}"');
|
||
if (to != null) parts.add('updated < "${_date(to)}"');
|
||
final r = await _pb
|
||
.collection('jobs')
|
||
.getList(perPage: 1, filter: parts.join(' && '));
|
||
return r.totalItems;
|
||
}
|
||
|
||
static String _date(DateTime d) => d.toIso8601String().split('T').first;
|
||
}
|