improvement/v1-demo-improvements. #62

Merged
rameen merged 13 commits from improvement/v1-demo-improvements into master 2026-07-20 11:54:36 +03:30
7 changed files with 666 additions and 646 deletions
Showing only changes of commit 334b7841ae - Show all commits

View File

@@ -63,6 +63,7 @@ export const ErrorCode = {
CONFLICT: 'CONFLICT',
CONFLICT_FUTURE_APPOINTMENTS: 'CONFLICT_FUTURE_APPOINTMENTS',
APPOINTMENT_PATIENT_LOCKED: 'APPOINTMENT_PATIENT_LOCKED',
APPOINTMENT_HAS_TREATMENT: 'APPOINTMENT_HAS_TREATMENT',
BAD_REQUEST: 'BAD_REQUEST',
INTERNAL_ERROR: 'INTERNAL_ERROR',
} as const;

View File

@@ -1,6 +1,7 @@
import {
BadRequestException,
ForbiddenException,
HttpStatus,
Injectable,
NotFoundException,
} from '@nestjs/common';
@@ -16,6 +17,7 @@ import { ListAppointmentsDto } from './dto/list-appointments.dto';
import { UpdateAppointmentDto } from './dto/update-appointment.dto';
import { TreatmentCatalogService } from '../treatment-catalog/treatment-catalog.service';
import { hasEffectivePermission } from '../../common/membership-permissions';
import { AppException, ErrorCode } from '../../common/errors';
const MS_PER_DAY = 86_400_000;
@@ -215,9 +217,7 @@ export class AppointmentsService {
select: { id: true },
});
if (linkedTreatment) {
throw new BadRequestException(
'Cannot change the patient while a treatment is linked to this appointment',
);
throw new AppException(ErrorCode.APPOINTMENT_PATIENT_LOCKED, HttpStatus.CONFLICT);
}
}
@@ -256,13 +256,17 @@ export class AppointmentsService {
const existing = await this.prisma.appointment.findFirst({
where: { id, organizationId },
select: { id: true },
select: { id: true, treatment: { select: { id: true } } },
});
if (!existing) {
throw new NotFoundException('Appointment not found');
}
if (existing.treatment) {
throw new AppException(ErrorCode.APPOINTMENT_HAS_TREATMENT, HttpStatus.CONFLICT);
}
await this.prisma.appointment.delete({
where: { id },
});

View File

@@ -582,6 +582,7 @@
"confirmRemove": "Remove this appointment?",
"successRemoved": "Appointment removed.",
"errorDelete": "Could not delete appointment.",
"deleteBlockedHint": "This appointment cannot be deleted because a treatment is linked to it.",
"errorLoadSchedule": "Failed to load schedule.",
"successPatientSaved": "Patient {firstName} {lastName} was saved.",
"searchPlaceholder": "Search existing patients",
@@ -1013,6 +1014,7 @@
"CONFLICT": "This action conflicts with existing data.",
"CONFLICT_FUTURE_APPOINTMENTS": "You cannot stop participating in treatments while you have future appointments. Reassign or cancel them first.",
"APPOINTMENT_PATIENT_LOCKED": "Cannot change the patient while a treatment is linked to this appointment.",
"APPOINTMENT_HAS_TREATMENT": "This appointment cannot be deleted because a treatment is linked to it.",
"BAD_REQUEST": "The request could not be processed.",
"INTERNAL_ERROR": "Something went wrong on our end. Please try again later."
}

View File

@@ -583,6 +583,7 @@
"confirmRemove": "این نوبت حذف شود؟",
"successRemoved": "نوبت حذف شد.",
"errorDelete": "حذف نوبت امکان‌پذیر نبود.",
"deleteBlockedHint": "به‌دلیل وجود درمان مرتبط با این نوبت، امکان حذف آن وجود ندارد.",
"errorLoadSchedule": "بارگذاری برنامه ناموفق بود.",
"successPatientSaved": "بیمار {firstName} {lastName} ذخیره شد.",
"searchPlaceholder": "جستجوی بیماران موجود",
@@ -1014,6 +1015,7 @@
"CONFLICT": "این عمل با داده‌های موجود در تضاد است.",
"CONFLICT_FUTURE_APPOINTMENTS": "تا وقتی نوبت‌های آینده دارید نمی‌توانید مشارکت در درمان را متوقف کنید. ابتدا آن‌ها را لغو یا واگذار کنید.",
"APPOINTMENT_PATIENT_LOCKED": "تا وقتی درمانی به این نوبت متصل است، امکان تغییر بیمار وجود ندارد.",
"APPOINTMENT_HAS_TREATMENT": "به‌دلیل وجود درمان مرتبط با این نوبت، امکان حذف آن وجود ندارد.",
"BAD_REQUEST": "درخواست قابل پردازش نبود.",
"INTERNAL_ERROR": "مشکلی در سرور رخ داد. لطفاً بعداً تلاش کنید."
}

View File

@@ -582,6 +582,7 @@
"confirmRemove": "Deze afspraak verwijderen?",
"successRemoved": "Afspraak verwijderd.",
"errorDelete": "Kon afspraak niet verwijderen.",
"deleteBlockedHint": "Deze afspraak kan niet worden verwijderd omdat er een behandeling aan is gekoppeld.",
"errorLoadSchedule": "Rooster laden mislukt.",
"successPatientSaved": "Patiënt {firstName} {lastName} is opgeslagen.",
"searchPlaceholder": "Bestaande patiënten zoeken",
@@ -1013,6 +1014,7 @@
"CONFLICT": "Deze actie conflicteert met bestaande gegevens.",
"CONFLICT_FUTURE_APPOINTMENTS": "U kunt niet stoppen met deelnemen aan behandelingen zolang u toekomstige afspraken hebt. Wijs ze eerst opnieuw toe of annuleer ze.",
"APPOINTMENT_PATIENT_LOCKED": "De patiënt kan niet worden gewijzigd zolang er een behandeling aan deze afspraak is gekoppeld.",
"APPOINTMENT_HAS_TREATMENT": "Deze afspraak kan niet worden verwijderd omdat er een behandeling aan is gekoppeld.",
"BAD_REQUEST": "Het verzoek kon niet worden verwerkt.",
"INTERNAL_ERROR": "Er is iets misgegaan aan onze kant. Probeer het later opnieuw."
}

View File

@@ -310,6 +310,10 @@ export function AppointmentBookingModal({
{error && <p className="text-sm text-red-400">{error}</p>}
{editingAppointment?.hasTreatment ? (
<p className="text-sm text-text-secondary">{t('deleteBlockedHint')}</p>
) : null}
<div className="flex flex-col-reverse gap-2 sm:flex-row sm:flex-wrap sm:items-center sm:justify-between">
{editingAppointment && canDelete && onDelete ? (
<Button

View File

@@ -279,7 +279,12 @@ export function AppointmentsPage() {
}}
onSubmit={handleSaveAppointment}
loading={savingAppointment}
canDelete={
canManageAppointments &&
!isViewingPastDay &&
!!activeEditingAppointment &&
!activeEditingAppointment.hasTreatment
}
onDelete={() => void handleDeleteEditingAppointment()}
deleting={deletingAppointment}
/>