Skip to content

Tutorial 8: Other Modules

Pages: 2


📖 Page 1: Practitioner & Organization

Practitioner Module (Same Pattern as Patient)

1. Create PractitionerDTO.java:

@Data
public class PractitionerDTO {
    private String id;
    @NotBlank private String firstName;
    @NotBlank private String lastName;
    @NotBlank private String gender;
    @Email private String email;
    private String phone;
    private String specialty; // Cardiology, Neurology, etc.
    private String qualification;
    private Boolean active = true;

    public String getFullName() {
        return firstName + " " + lastName;
    }
}

2. Create PractitionerMapper.java: (Same pattern as PatientMapper)

3. Create PractitionerService.java: (Same CRUD methods)

4. Create PractitionerController.java: (Same endpoints)

5. Create templates: practitioners/list.html, form.html, view.html

Organization Module

1. Create OrganizationDTO.java:

@Data
public class OrganizationDTO {
    private String id;
    @NotBlank private String name;
    @NotBlank private String type; // Hospital, Clinic, Department
    private String email;
    private String phone;
    private String addressLine;
    private String city;
    private String state;
    private Boolean active = true;
}

2-5: Same pattern - Mapper, Service, Controller, Templates

Quick copy from Patient module and adapt!


📖 Page 2: Appointment Module

AppointmentDTO

Create AppointmentDTO.java:

@Data
public class AppointmentDTO {
    private String id;

    @NotBlank
    private String patientId;
    private String patientName; // For display

    @NotBlank
    private String practitionerId;
    private String practitionerName; // For display

    @NotNull
    @Future
    @DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm")
    private LocalDateTime appointmentDateTime;

    private Integer durationMinutes = 30;

    @NotBlank
    private String status; // booked, fulfilled, cancelled

    private String appointmentType; // Routine, Followup, Emergency
    private String reason;
    private String notes;
}

AppointmentMapper

Key differences from Patient/Practitioner:

@Component
@RequiredArgsConstructor
public class AppointmentMapper {

    private final PatientService patientService;
    private final PractitionerService practitionerService;

    public Appointment toFhirResource(AppointmentDTO dto) {
        Appointment apt = new Appointment();

        if (dto.getId() != null) apt.setId(dto.getId());

        // Status
        apt.setStatus(Appointment.AppointmentStatus.valueOf(
            dto.getStatus().toUpperCase()
        ));

        // DateTime
        if (dto.getAppointmentDateTime() != null) {
            Date start = Date.from(dto.getAppointmentDateTime()
                .atZone(ZoneId.systemDefault()).toInstant());
            apt.setStart(start);

            // End = start + duration
            LocalDateTime end = dto.getAppointmentDateTime()
                .plusMinutes(dto.getDurationMinutes());
            apt.setEnd(Date.from(end
                .atZone(ZoneId.systemDefault()).toInstant()));
        }

        apt.setMinutesDuration(dto.getDurationMinutes());

        // Participants - Patient
        Appointment.AppointmentParticipantComponent patientParticipant = 
            new Appointment.AppointmentParticipantComponent();
        Reference patientRef = new Reference("Patient/" + dto.getPatientId());
        patientRef.setDisplay(dto.getPatientName());
        patientParticipant.setActor(patientRef);
        patientParticipant.setRequired(Appointment.ParticipantRequired.REQUIRED);
        patientParticipant.setStatus(Appointment.ParticipationStatus.ACCEPTED);
        apt.addParticipant(patientParticipant);

        // Participants - Practitioner
        Appointment.AppointmentParticipantComponent practitionerParticipant = 
            new Appointment.AppointmentParticipantComponent();
        Reference practitionerRef = new Reference(
            "Practitioner/" + dto.getPractitionerId()
        );
        practitionerRef.setDisplay(dto.getPractitionerName());
        practitionerParticipant.setActor(practitionerRef);
        practitionerParticipant.setRequired(Appointment.ParticipantRequired.REQUIRED);
        practitionerParticipant.setStatus(Appointment.ParticipationStatus.ACCEPTED);
        apt.addParticipant(practitionerParticipant);

        // Type and Reason
        if (dto.getAppointmentType() != null) {
            CodeableConcept type = new CodeableConcept();
            type.setText(dto.getAppointmentType());
            apt.addAppointmentType(type);
        }

        if (dto.getReason() != null) {
            CodeableConcept reason = new CodeableConcept();
            reason.setText(dto.getReason());
            apt.addReasonCode(reason);
        }

        apt.setComment(dto.getNotes());

        return apt;
    }

    public AppointmentDTO toDTO(Appointment apt) {
        AppointmentDTO dto = new AppointmentDTO();

        dto.setId(apt.getIdElement().getIdPart());

        if (apt.hasStatus()) {
            dto.setStatus(apt.getStatus().toCode());
        }

        // DateTime
        if (apt.hasStart()) {
            dto.setAppointmentDateTime(apt.getStart().toInstant()
                .atZone(ZoneId.systemDefault()).toLocalDateTime());
        }

        dto.setDurationMinutes(apt.getMinutesDuration());

        // Extract participants
        for (Appointment.AppointmentParticipantComponent participant : 
             apt.getParticipant()) {
            Reference ref = participant.getActor();
            if (ref.getReference().startsWith("Patient/")) {
                dto.setPatientId(ref.getReference().replace("Patient/", ""));
                dto.setPatientName(ref.getDisplay());
            } else if (ref.getReference().startsWith("Practitioner/")) {
                dto.setPractitionerId(
                    ref.getReference().replace("Practitioner/", "")
                );
                dto.setPractitionerName(ref.getDisplay());
            }
        }

        if (apt.hasAppointmentType() && !apt.getAppointmentType().isEmpty()) {
            dto.setAppointmentType(
                apt.getAppointmentType().get(0).getText()
            );
        }

        if (apt.hasReasonCode() && !apt.getReasonCode().isEmpty()) {
            dto.setReason(apt.getReasonCode().get(0).getText());
        }

        dto.setNotes(apt.getComment());

        return dto;
    }
}

AppointmentService & Controller

Same CRUD pattern, but: - Load patient and practitioner names for dropdown - Filter by date range - Filter by status

Appointment Form

Key fields:

<div class="mb-3">
    <label>Patient *</label>
    <select th:field="*{patientId}" class="form-control">
        <option value="">Select Patient</option>
        <option th:each="patient : ${patients}" 
                th:value="${patient.id}" 
                th:text="${patient.fullName}"></option>
    </select>
</div>

<div class="mb-3">
    <label>Practitioner *</label>
    <select th:field="*{practitionerId}" class="form-control">
        <option value="">Select Practitioner</option>
        <option th:each="prac : ${practitioners}" 
                th:value="${prac.id}" 
                th:text="${prac.fullName}"></option>
    </select>
</div>

<div class="mb-3">
    <label>Date & Time *</label>
    <input type="datetime-local" th:field="*{appointmentDateTime}" 
           class="form-control">
</div>

<div class="mb-3">
    <label>Status</label>
    <select th:field="*{status}" class="form-control">
        <option value="booked">Booked</option>
        <option value="fulfilled">Fulfilled</option>
        <option value="cancelled">Cancelled</option>
    </select>
</div>

✅ All Modules Complete

  • Patient Module - Complete with all features
  • Practitioner Module - Same pattern as Patient
  • Organization Module - Same pattern as Patient
  • Appointment Module - Links Patient + Practitioner

All 4 resources working with CRUD, filter, sort, pagination!


🚀 Next

All modules done! Now add audit trail for compliance.

→ Tutorial 9: Audit Trail


💡 Quick Tips

Code Reuse

Copy Patient module, change class names, done!

References

Appointments link resources using Reference("Patient/123")

DateTime

Use @DateTimeFormat for proper datetime-local binding