feat: improve patient flow and pricing workflow

This commit is contained in:
egecankomur
2026-06-12 00:04:53 +03:00
parent e12587398b
commit b42f68214e
26 changed files with 1283 additions and 243 deletions
+23 -4
View File
@@ -273,8 +273,10 @@ class _PendingJobsTabState extends ConsumerState<_PendingJobsTab> {
if (q.isEmpty) return jobs;
return jobs.where((j) =>
j.patientCode.toLowerCase().contains(q) ||
(j.patientName?.toLowerCase().contains(q) ?? false) ||
(j.clinicName?.toLowerCase().contains(q) ?? false) ||
j.prostheticType.label.toLowerCase().contains(q)
j.prostheticType.label.toLowerCase().contains(q) ||
(j.prostheticName?.toLowerCase().contains(q) ?? false)
).toList();
}
@@ -591,8 +593,10 @@ class _LabJobsTabState extends ConsumerState<_LabJobsTab> {
if (q.isNotEmpty) {
list = list.where((j) {
return j.patientCode.toLowerCase().contains(q) ||
(j.patientName?.toLowerCase().contains(q) ?? false) ||
(j.clinicName?.toLowerCase().contains(q) ?? false) ||
j.prostheticType.label.toLowerCase().contains(q) ||
(j.prostheticName?.toLowerCase().contains(q) ?? false) ||
(j.currentStep?.label.toLowerCase().contains(q) ?? false);
}).toList();
}
@@ -722,12 +726,15 @@ class _LabJobCard extends StatelessWidget {
@override
Widget build(BuildContext context) {
final title = job.patientName?.trim().isNotEmpty == true
? job.patientName!
: job.patientCode;
final isOverdue =
job.dueDate != null && job.dueDate!.isBefore(DateTime.now());
final accentColor = _statusColor(job.status);
return Semantics(
label: job.patientCode,
label: title,
button: true,
excludeSemantics: true,
child: Material(
@@ -771,7 +778,7 @@ class _LabJobCard extends StatelessWidget {
children: [
Expanded(
child: Text(
job.patientCode,
title,
style: const TextStyle(
fontSize: 15,
fontWeight: FontWeight.w700,
@@ -798,6 +805,16 @@ class _LabJobCard extends StatelessWidget {
),
],
),
if (job.patientName?.isNotEmpty == true) ...[
const SizedBox(height: 4),
Text(
job.patientCode,
style: const TextStyle(
fontSize: 12,
color: AppColors.textMuted,
),
),
],
const SizedBox(height: 5),
Row(
children: [
@@ -827,7 +844,9 @@ class _LabJobCard extends StatelessWidget {
borderRadius: BorderRadius.circular(6),
),
child: Text(
job.prostheticType.label,
job.prostheticName?.isNotEmpty == true
? '${job.prostheticType.label} · ${job.prostheticName}'
: job.prostheticType.label,
style: const TextStyle(
fontSize: 11,
color: AppColors.textSecondary,
@@ -258,7 +258,9 @@ class _LabJobDetailScreenState extends ConsumerState<LabJobDetailScreen> {
children: [
Expanded(
child: Text(
job.patientCode,
job.patientName?.isNotEmpty == true
? job.patientName!
: job.patientCode,
style: Theme.of(context)
.textTheme
.headlineSmall
@@ -289,10 +291,40 @@ class _LabJobDetailScreenState extends ConsumerState<LabJobDetailScreen> {
icon: Icons.business,
label: 'Klinik',
value: job.clinicName ?? '-'),
if (job.patientName != null &&
job.patientName!.isNotEmpty)
_InfoRow(
icon: Icons.person_outline,
label: 'Hasta',
value: job.patientName!,
),
_InfoRow(
icon: Icons.tag_outlined,
label: 'Protokol No',
value: job.patientCode,
),
_InfoRow(
icon: Icons.medical_services_outlined,
label: 'Protez Tipi',
value: job.prostheticType.label),
if (job.prostheticName != null &&
job.prostheticName!.isNotEmpty)
_InfoRow(
icon: Icons.category_outlined,
label: 'Ürün',
value: job.prostheticName!,
),
if (job.workflowType != null)
_InfoRow(
icon: Icons.tune_rounded,
label: 'İş Tipi',
value: job.workflowType!.label,
),
_InfoRow(
icon: Icons.fact_check_outlined,
label: 'Prova',
value: job.provaRequired ? 'Provalı' : 'Provasız',
),
_InfoRow(
icon: Icons.format_list_numbered,
label: 'Üye Sayısı',
@@ -761,4 +793,3 @@ class _JobStepper extends StatelessWidget {
);
}
}
@@ -188,8 +188,11 @@ class _InboundJobCardState extends State<_InboundJobCard> {
@override
Widget build(BuildContext context) {
final job = widget.job;
final title = job.patientName?.trim().isNotEmpty == true
? job.patientName!
: job.patientCode;
return Semantics(
label: job.patientCode,
label: title,
button: true,
excludeSemantics: true,
child: Dismissible(
@@ -246,9 +249,17 @@ class _InboundJobCardState extends State<_InboundJobCard> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
job.patientCode,
title,
style: const TextStyle(fontWeight: FontWeight.bold),
),
if (job.patientName?.isNotEmpty == true) ...[
const SizedBox(height: 2),
Text(
job.patientCode,
style: TextStyle(
color: AppColors.textMuted, fontSize: 12),
),
],
const SizedBox(height: 2),
Text(
job.clinicName ?? 'Klinik',
@@ -259,7 +270,9 @@ class _InboundJobCardState extends State<_InboundJobCard> {
Row(
children: [
_Chip(
label: job.prostheticType.label,
label: job.prostheticName?.isNotEmpty == true
? '${job.prostheticType.label} · ${job.prostheticName}'
: job.prostheticType.label,
color: AppColors.inProgressBg,
textColor: AppColors.inProgress,
),
@@ -1,11 +1,12 @@
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';
const _detailExpand = 'clinic_tenant_id,lab_tenant_id,patient_id';
const _listExpand = 'clinic_tenant_id,lab_tenant_id,patient_id';
const _detailExpand = 'clinic_tenant_id,lab_tenant_id,patient_id,prosthetic_id';
class LabJobsRepository {
LabJobsRepository._();
@@ -96,6 +97,7 @@ class LabJobsRepository {
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,