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.
💡 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