diff --git a/hms-api/src/main/java/com/hospital/hms/config/SecurityConfig.java b/hms-api/src/main/java/com/hospital/hms/config/SecurityConfig.java index 5bbefa0..73a0382 100644 --- a/hms-api/src/main/java/com/hospital/hms/config/SecurityConfig.java +++ b/hms-api/src/main/java/com/hospital/hms/config/SecurityConfig.java @@ -38,6 +38,7 @@ public class SecurityConfig { "/swagger-ui/**", "/swagger-ui.html", "/v3/api-docs/**", + "/v1/medicines/**", // Actuator health check "/actuator/health", "/actuator/info", diff --git a/hms-api/src/main/java/com/hospital/hms/pharmacy/controller/MedicineController.java b/hms-api/src/main/java/com/hospital/hms/pharmacy/controller/MedicineController.java new file mode 100644 index 0000000..a731f36 --- /dev/null +++ b/hms-api/src/main/java/com/hospital/hms/pharmacy/controller/MedicineController.java @@ -0,0 +1,205 @@ +package com.hospital.hms.pharmacy.controller; + +import com.hospital.hms.base.api.ApiResponse; +import com.hospital.hms.base.api.ResponseMetadata; +import com.hospital.hms.base.response.PaginatedResponse; +import com.hospital.hms.pharmacy.dto.request.*; +import com.hospital.hms.pharmacy.dto.response.MedicineResponse; +import com.hospital.hms.pharmacy.service.*; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.UUID; + +@Slf4j +@RestController +@RequestMapping("/v1/medicines") +@RequiredArgsConstructor +public class MedicineController { + private final GetMedicineDetailService getMedicineDetailService; + private final GetAllMedicineService getAllMedicineService; + private final CreateMedicineService createMedicineService; + private final DeActiveMedicineService deActiveMedicineService; + private final UpdateMedicineService updateMedicineService; + + @PatchMapping("/deactivate") + public ResponseEntity> deActiveMedicine(@Valid @RequestBody DeActiveMedicineRequest request, HttpServletRequest httpRequest) { + long startTime = System.currentTimeMillis(); + String traceId = java.util.UUID.randomUUID().toString(); + + log.info("[TraceID: {}] De-active medicine with medicine id : {}", traceId, request.getId()); + + deActiveMedicineService.execute(request); + + long duration = System.currentTimeMillis() - startTime; + + ResponseMetadata metadata = ResponseMetadata.builder() + .traceId(traceId) + .path(httpRequest.getRequestURI()) + .method(httpRequest.getMethod()) + .duration(duration) + .apiVersion("v1") + .build(); + + ApiResponse response = ApiResponse.success( + null, + "De-active medicine successfully", + HttpStatus.OK.value(), + metadata + ); + + return ResponseEntity + .status(HttpStatus.OK) + .body(response); + } + + @PostMapping("/create") + public ResponseEntity> createMedicine(@Valid @RequestBody CreateMedicineRequest request, HttpServletRequest httpRequest) { + + long startTime = System.currentTimeMillis(); + String traceId = java.util.UUID.randomUUID().toString(); + + log.info("[TraceID: {}] Creating new medicine with name: {}", traceId, request.getName()); + + MedicineResponse response = createMedicineService.execute(request); + + long duration = System.currentTimeMillis() - startTime; + + ResponseMetadata metadata = ResponseMetadata.builder() + .traceId(traceId) + .path(httpRequest.getRequestURI()) + .method(httpRequest.getMethod()) + .duration(duration) + .apiVersion("v1") + .build(); + + ApiResponse apiResponse = ApiResponse.success( + response, + "Medicine created successfully", + HttpStatus.CREATED.value(), + metadata + ); + + log.info("[TraceID: {}] Medicine created successfully with ID: {} (took {}ms)", + traceId, response.id(), duration); + + return ResponseEntity + .status(HttpStatus.CREATED) + .body(apiResponse); + } + + @GetMapping + public ResponseEntity>> getAllMedicines(@Valid @ModelAttribute GetAllMedicineRequest request, HttpServletRequest httpRequest) { + + long startTime = System.currentTimeMillis(); + String traceId = java.util.UUID.randomUUID().toString(); + + log.info("[TraceID: {}] Fetching all medicines{}", traceId, request.getId() != null); + + PaginatedResponse medicines = getAllMedicineService.execute(request); + + long duration = System.currentTimeMillis() - startTime; + + ResponseMetadata metadata = ResponseMetadata.builder() + .traceId(traceId) + .path(httpRequest.getRequestURI()) + .method(httpRequest.getMethod()) + .duration(duration) + .apiVersion("v1") + .build(); + + ApiResponse> apiResponse = ApiResponse.success( + medicines, + "Medicine retrieved successfully", + HttpStatus.OK.value(), + metadata + ); + + log.info("[TraceID: {}] Retrieved {} medicine successfully (took {}ms)", + traceId, medicines.size(), duration); + + return ResponseEntity + .status(HttpStatus.OK) + .body(apiResponse); + } + + @GetMapping("/detail/{id}") + public ResponseEntity> getMedicine( + @PathVariable UUID id, + HttpServletRequest httpRequest) { + + long startTime = System.currentTimeMillis(); + String traceId = java.util.UUID.randomUUID().toString(); + + log.info("[TraceID: {}] Fetching medicine with Medicine Id: {}", traceId, id); + + // Tạo request object từ id + GetMedicineDetailRequest request = new GetMedicineDetailRequest(id); + + MedicineResponse response = getMedicineDetailService.execute(request); + long duration = System.currentTimeMillis() - startTime; + + ResponseMetadata metadata = ResponseMetadata.builder() + .traceId(traceId) + .path(httpRequest.getRequestURI()) + .method(httpRequest.getMethod()) + .duration(duration) + .apiVersion("v1") + .build(); + + ApiResponse apiResponse = ApiResponse.success( + response, + "medicine retrieved successfully", + HttpStatus.OK.value(), + metadata + ); + + log.info("[TraceID: {}] Retrieved medicine: {} successfully (took {}ms)", + traceId, response.name(), duration); + + return ResponseEntity + .status(HttpStatus.OK) + .body(apiResponse); + } + + @PatchMapping("/update") + public ResponseEntity> updateMedicine(@Valid @RequestBody UpdateMedicineRequest request, HttpServletRequest httpRequest) { + + long startTime = System.currentTimeMillis(); + String traceId = java.util.UUID.randomUUID().toString(); + + log.info("[TraceID: {}] Updating medicine with id: {}", traceId, request.getId()); + + MedicineResponse response = updateMedicineService.execute(request); + + long duration = System.currentTimeMillis() - startTime; + + ResponseMetadata metadata = ResponseMetadata.builder() + .traceId(traceId) + .path(httpRequest.getRequestURI()) + .method(httpRequest.getMethod()) + .duration(duration) + .apiVersion("v1") + .build(); + + ApiResponse apiResponse = ApiResponse.success( + response, + "Medicine updated successfully", + HttpStatus.OK.value(), + metadata + ); + + log.info("[TraceID: {}] Updated medicine: {} successfully (took {}ms)", + traceId, response.name(), duration); + + return ResponseEntity + .status(HttpStatus.OK) + .body(apiResponse); + + } +} diff --git a/hms-api/src/main/java/com/hospital/hms/pharmacy/dto/request/CreateMedicineRequest.java b/hms-api/src/main/java/com/hospital/hms/pharmacy/dto/request/CreateMedicineRequest.java new file mode 100644 index 0000000..a2b8ca3 --- /dev/null +++ b/hms-api/src/main/java/com/hospital/hms/pharmacy/dto/request/CreateMedicineRequest.java @@ -0,0 +1,67 @@ +package com.hospital.hms.pharmacy.dto.request; + +import com.hospital.hms.base.request.BaseRequest; +import jakarta.validation.constraints.DecimalMin; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +import java.math.BigDecimal; + +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class CreateMedicineRequest extends BaseRequest { + + @NotBlank(message = "Name is required") + @Size(min = 3, max = 200, message = "Name must be between 3 and 200 characters") + private String name; + + @NotNull(message = "Quantity is required") + private Integer quantity; + + @NotNull(message = "Price is required") + @DecimalMin(value = "0.01", message = "Price must be greater than 0") + private BigDecimal price; + + @NotBlank(message = "Description is required") + @Size(min = 20, max = 5000, message = "Description must be between 20 and 5000 characters") + private String description; + + @NotNull(message = "Active status is required") + private Boolean isActive; + + @Override + public void validate() { + super.validate(); + + if (name != null && !name.matches(".*[a-zA-Z]{2,}.*")) { + throw new IllegalArgumentException("Name must contain at least 2 alphabetic characters"); + } + if (quantity != null && quantity <= 0) { + throw new IllegalArgumentException("Quantity must be greater than 0"); + } + if (price != null && price.compareTo(BigDecimal.ZERO) <= 0) { + throw new IllegalArgumentException("Price must be greater than 0"); + } + if (description != null && description.trim().length() < 20) { + throw new IllegalArgumentException("Description must have at least 20 non-whitespace characters"); + } + if (isActive == null) { + throw new IllegalArgumentException("Active status is required"); + } + if (name != null && name.equals(description)) { + throw new IllegalArgumentException("Name and description cannot be identical"); + } + if (description != null && description.length() > 5000) { + throw new IllegalArgumentException("Description is too long"); + } + } +} diff --git a/hms-api/src/main/java/com/hospital/hms/pharmacy/dto/request/DeActiveMedicineRequest.java b/hms-api/src/main/java/com/hospital/hms/pharmacy/dto/request/DeActiveMedicineRequest.java new file mode 100644 index 0000000..81970d5 --- /dev/null +++ b/hms-api/src/main/java/com/hospital/hms/pharmacy/dto/request/DeActiveMedicineRequest.java @@ -0,0 +1,22 @@ +package com.hospital.hms.pharmacy.dto.request; + +import com.hospital.hms.base.request.BaseRequest; +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +import java.util.UUID; + +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class DeActiveMedicineRequest extends BaseRequest { + + @NotNull(message = "Medicine id are required ") + private UUID id; +} diff --git a/hms-api/src/main/java/com/hospital/hms/pharmacy/dto/request/GetAllMedicineRequest.java b/hms-api/src/main/java/com/hospital/hms/pharmacy/dto/request/GetAllMedicineRequest.java new file mode 100644 index 0000000..adbbffa --- /dev/null +++ b/hms-api/src/main/java/com/hospital/hms/pharmacy/dto/request/GetAllMedicineRequest.java @@ -0,0 +1,34 @@ +package com.hospital.hms.pharmacy.dto.request; + +import com.hospital.hms.base.request.PaginatedRequest; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +import java.math.BigDecimal; +import java.util.UUID; + +@Data +@SuperBuilder +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class GetAllMedicineRequest extends PaginatedRequest { + + private UUID id; + + private String name; + + private BigDecimal price; + + private BigDecimal minPrice; + + private BigDecimal maxPrice; + + private Integer quantity; + + private String description; + + public void validate() { + } +} diff --git a/hms-api/src/main/java/com/hospital/hms/pharmacy/dto/request/GetMedicineDetailRequest.java b/hms-api/src/main/java/com/hospital/hms/pharmacy/dto/request/GetMedicineDetailRequest.java new file mode 100644 index 0000000..c4b3793 --- /dev/null +++ b/hms-api/src/main/java/com/hospital/hms/pharmacy/dto/request/GetMedicineDetailRequest.java @@ -0,0 +1,22 @@ +package com.hospital.hms.pharmacy.dto.request; + +import com.hospital.hms.base.request.BaseRequest; +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +import java.util.UUID; + +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class GetMedicineDetailRequest extends BaseRequest { + + @NotNull(message = "Medicine id are required ") + private UUID id; +} diff --git a/hms-api/src/main/java/com/hospital/hms/pharmacy/dto/request/UpdateMedicineRequest.java b/hms-api/src/main/java/com/hospital/hms/pharmacy/dto/request/UpdateMedicineRequest.java new file mode 100644 index 0000000..f4f42cf --- /dev/null +++ b/hms-api/src/main/java/com/hospital/hms/pharmacy/dto/request/UpdateMedicineRequest.java @@ -0,0 +1,63 @@ +package com.hospital.hms.pharmacy.dto.request; + +import com.hospital.hms.base.request.BaseRequest; +import jakarta.validation.constraints.DecimalMin; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +import java.math.BigDecimal; +import java.util.UUID; + +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class UpdateMedicineRequest extends BaseRequest { + + @NotNull(message = "Medicine id are required ") + private UUID id; + + @NotBlank(message = "Name is required") + @Size(min = 3, max = 200, message = "Name must be between 3 and 200 characters") + private String name; + + private Integer quantity; + + @DecimalMin(value = "0.01", message = "Price must be greater than 0") + private BigDecimal price; + + @Size(min = 20, max = 5000, message = "Description must be between 20 and 5000 characters") + private String description; + + @Override + public void validate() { + super.validate(); + + if (name != null && !name.matches(".*[a-zA-Z]{2,}.*")) { + throw new IllegalArgumentException("Name must contain at least 2 alphabetic characters"); + } + if (quantity != null && quantity <= 0) { + throw new IllegalArgumentException("Quantity must be greater than 0"); + } + if (price != null && price.compareTo(BigDecimal.ZERO) <= 0) { + throw new IllegalArgumentException("Price must be greater than 0"); + } + if (description != null && description.trim().length() < 20) { + throw new IllegalArgumentException("Description must have at least 20 non-whitespace characters"); + } + if (name != null && name.equals(description)) { + throw new IllegalArgumentException("Name and description cannot be identical"); + } + if (description != null && description.length() > 5000) { + throw new IllegalArgumentException("Description is too long"); + } + } + +} diff --git a/hms-api/src/main/java/com/hospital/hms/pharmacy/dto/response/MedicineResponse.java b/hms-api/src/main/java/com/hospital/hms/pharmacy/dto/response/MedicineResponse.java new file mode 100644 index 0000000..4f04417 --- /dev/null +++ b/hms-api/src/main/java/com/hospital/hms/pharmacy/dto/response/MedicineResponse.java @@ -0,0 +1,18 @@ +package com.hospital.hms.pharmacy.dto.response; + + +import lombok.Builder; + +import java.math.BigDecimal; +import java.util.UUID; + + +@Builder +public record MedicineResponse(UUID id, + String name, + String description, + BigDecimal price, + Integer quantity, + Boolean isActive) { + +} diff --git a/hms-api/src/main/java/com/hospital/hms/pharmacy/mapper/MedicineMapper.java b/hms-api/src/main/java/com/hospital/hms/pharmacy/mapper/MedicineMapper.java new file mode 100644 index 0000000..3717610 --- /dev/null +++ b/hms-api/src/main/java/com/hospital/hms/pharmacy/mapper/MedicineMapper.java @@ -0,0 +1,20 @@ +package com.hospital.hms.pharmacy.mapper; + +import com.hospital.hms.pharmacy.dto.request.CreateMedicineRequest; +import com.hospital.hms.pharmacy.dto.response.MedicineResponse; +import com.hospital.hms.pharmacy.entity.Medicine; +import org.mapstruct.*; + +@Mapper(componentModel = "spring") +public interface MedicineMapper { + + @Mapping(source = "medicine.id", target = "id") + MedicineResponse toResponse(Medicine medicine); + + @Mapping(target = "id", ignore = true) + Medicine toEntity(CreateMedicineRequest createRequest); + + @BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE) + @Mapping(target = "id", ignore = true) + void update(@MappingTarget Medicine entity, CreateMedicineRequest request); +} diff --git a/hms-api/src/main/java/com/hospital/hms/pharmacy/repository/MedicineRepository.java b/hms-api/src/main/java/com/hospital/hms/pharmacy/repository/MedicineRepository.java index 68c8dfe..61b34ab 100644 --- a/hms-api/src/main/java/com/hospital/hms/pharmacy/repository/MedicineRepository.java +++ b/hms-api/src/main/java/com/hospital/hms/pharmacy/repository/MedicineRepository.java @@ -2,10 +2,16 @@ import com.hospital.hms.pharmacy.entity.Medicine; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; import org.springframework.stereotype.Repository; +import java.util.Optional; import java.util.UUID; @Repository -public interface MedicineRepository extends JpaRepository { +public interface MedicineRepository extends JpaRepository, + JpaSpecificationExecutor { + Optional findById(UUID id); + + Optional findByName(String name); } diff --git a/hms-api/src/main/java/com/hospital/hms/pharmacy/repository/MedicineSpecification.java b/hms-api/src/main/java/com/hospital/hms/pharmacy/repository/MedicineSpecification.java new file mode 100644 index 0000000..8bc1e31 --- /dev/null +++ b/hms-api/src/main/java/com/hospital/hms/pharmacy/repository/MedicineSpecification.java @@ -0,0 +1,54 @@ +package com.hospital.hms.pharmacy.repository; + +import com.hospital.hms.pharmacy.dto.request.GetAllMedicineRequest; +import com.hospital.hms.pharmacy.entity.Medicine; +import jakarta.persistence.criteria.Predicate; +import org.springframework.data.jpa.domain.Specification; + +import java.util.ArrayList; +import java.util.List; + +public class MedicineSpecification { + + private MedicineSpecification() { + } + + public static Specification withFilters(GetAllMedicineRequest request) { + return (root, query, criteriaBuilder) -> { + List predicates = new ArrayList<>(); + + if (request.getName() != null && !request.getName().isBlank()) { + //add LIKE query + predicates.add(criteriaBuilder.like( + criteriaBuilder.lower(root.get("name")), + +// -- LIKE operator (the keyword) +// WHERE title LIKE '...' +// +// -- % is the pattern/wildcard (the value) +// WHERE title LIKE '%java%' -- matches anything containing "java" +// WHERE title LIKE 'java%' -- matches anything starting with "java" +// WHERE title LIKE '%java' -- matches anything ending with "java" +// WHERE title LIKE 'java' -- exact match only + "%" + request.getName().toLowerCase() + "%" + )); + } + if (request.getDescription() != null && !request.getDescription().isBlank()) { + predicates.add(criteriaBuilder.like(criteriaBuilder.lower(root.get("description")), + "%" + request.getDescription().toLowerCase() + "%" + )); + + } + if (request.getMinPrice() != null) { + predicates.add(criteriaBuilder.greaterThanOrEqualTo(root.get("price"), request.getMinPrice())); + } + + if (request.getMaxPrice() != null) { + predicates.add(criteriaBuilder.lessThanOrEqualTo(root.get("price"), request.getMaxPrice())); + } + // this will add AND this make List to Array + return criteriaBuilder.and(predicates.toArray(new Predicate[0])); + // -> after this will make a WHERE condition1 AND condition2 AND ... + }; + } +} diff --git a/hms-api/src/main/java/com/hospital/hms/pharmacy/service/CreateMedicineService.java b/hms-api/src/main/java/com/hospital/hms/pharmacy/service/CreateMedicineService.java new file mode 100644 index 0000000..c073e93 --- /dev/null +++ b/hms-api/src/main/java/com/hospital/hms/pharmacy/service/CreateMedicineService.java @@ -0,0 +1,57 @@ +package com.hospital.hms.pharmacy.service; + +import com.hospital.hms.base.service.BaseService; +import com.hospital.hms.exception.BusinessException; +import com.hospital.hms.exception.ValidationException; +import com.hospital.hms.pharmacy.dto.request.CreateMedicineRequest; +import com.hospital.hms.pharmacy.dto.response.MedicineResponse; +import com.hospital.hms.pharmacy.entity.Medicine; +import com.hospital.hms.pharmacy.mapper.MedicineMapper; +import com.hospital.hms.pharmacy.repository.MedicineRepository; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Slf4j +@Service +@RequiredArgsConstructor +public class CreateMedicineService extends BaseService { + + private final MedicineRepository medicineRepository; + private final MedicineMapper medicineMapper; + + @Override + @Transactional + protected MedicineResponse doProcess(CreateMedicineRequest request) { + log.debug("Processing medicine creation request: {}", request.getName()); + + Medicine medicine = medicineMapper.toEntity(request); + + medicineRepository.save(medicine); + + return null; + } + + @Override + public void validate(CreateMedicineRequest request) { + super.validate(request); + + log.debug("Starting service-level validation for medicine : {}", request.getName()); + + boolean nameExists = medicineRepository.findByName(request.getName()).isPresent(); + + if (nameExists) { + throw new BusinessException( + "A medicine with the name '" + request.getName() + "' already exists. Please use a unique name." + ); + } + + if (request.getDescription().length() < 200) { + throw new ValidationException( + "Medicine require very detailed descriptions (minimum 200 characters)" + ); + } + } + +} diff --git a/hms-api/src/main/java/com/hospital/hms/pharmacy/service/DeActiveMedicineService.java b/hms-api/src/main/java/com/hospital/hms/pharmacy/service/DeActiveMedicineService.java new file mode 100644 index 0000000..83f6a24 --- /dev/null +++ b/hms-api/src/main/java/com/hospital/hms/pharmacy/service/DeActiveMedicineService.java @@ -0,0 +1,48 @@ +package com.hospital.hms.pharmacy.service; + +import com.hospital.hms.base.service.BaseService; +import com.hospital.hms.exception.BusinessException; +import com.hospital.hms.exception.NotFoundException; +import com.hospital.hms.pharmacy.dto.request.DeActiveMedicineRequest; +import com.hospital.hms.pharmacy.entity.Medicine; +import com.hospital.hms.pharmacy.repository.MedicineRepository; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; + +@Slf4j +@Service +@RequiredArgsConstructor +public class DeActiveMedicineService extends BaseService { + + private final MedicineRepository medicineRepository; + + @Override + @Transactional + protected Void doProcess(DeActiveMedicineRequest request) { + log.debug("Processing medicine de-active request: {}", request.getId()); + + + Medicine medicine = medicineRepository.findById(request.getId()).orElseThrow( + () -> new NotFoundException("medicine", request.getId()) + ); + + if (!medicine.getIsActive()) { + throw new BusinessException("Medicine with ID " + request.getId() + " is already deactivated"); + } + + medicine.setIsActive(false); + medicine.setUpdatedAt(LocalDateTime.now()); + + Medicine deActiveMedicine = medicineRepository.save(medicine); + + log.info("Medicine de-active with ID: {}", + deActiveMedicine.getId()); + + return null; + } + +} diff --git a/hms-api/src/main/java/com/hospital/hms/pharmacy/service/GetAllMedicineService.java b/hms-api/src/main/java/com/hospital/hms/pharmacy/service/GetAllMedicineService.java new file mode 100644 index 0000000..11bd64c --- /dev/null +++ b/hms-api/src/main/java/com/hospital/hms/pharmacy/service/GetAllMedicineService.java @@ -0,0 +1,47 @@ +package com.hospital.hms.pharmacy.service; + +import com.hospital.hms.base.response.PaginatedResponse; +import com.hospital.hms.base.service.BaseService; +import com.hospital.hms.pharmacy.dto.request.GetAllMedicineRequest; +import com.hospital.hms.pharmacy.dto.response.MedicineResponse; +import com.hospital.hms.pharmacy.entity.Medicine; +import com.hospital.hms.pharmacy.mapper.MedicineMapper; +import com.hospital.hms.pharmacy.repository.MedicineRepository; +import com.hospital.hms.pharmacy.repository.MedicineSpecification; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Slf4j +@Service +@RequiredArgsConstructor +public class GetAllMedicineService extends BaseService> { + + private final MedicineRepository medicineRepository; + private final MedicineMapper medicineMapper; + + @Override + @Transactional(readOnly = true) + protected PaginatedResponse doProcess(GetAllMedicineRequest request) { + log.debug("Fetching medicine with pagination - page: {}, size: {}, name: {}, quantity: {}, price: {}, description: {}", request.getPage(), request.getSize(), request.getName(), request.getQuantity(), request.getPrice(), request.getDescription()); + + Pageable pageable = request.toPageable(); + Specification spec = MedicineSpecification.withFilters(request); + + Page medicinePage = medicineRepository.findAll(spec, pageable); + + if (medicinePage.isEmpty()) { + log.debug("No medicines found for given filters"); + } + + log.info("Found {} medicine on page {} of {}", medicinePage.getNumberOfElements(), medicinePage.getNumber(), medicinePage.getTotalPages()); + + Page responsePage = medicinePage.map(medicineMapper::toResponse); + return PaginatedResponse.from(responsePage); + } + +} diff --git a/hms-api/src/main/java/com/hospital/hms/pharmacy/service/GetMedicineDetailService.java b/hms-api/src/main/java/com/hospital/hms/pharmacy/service/GetMedicineDetailService.java new file mode 100644 index 0000000..6a43d80 --- /dev/null +++ b/hms-api/src/main/java/com/hospital/hms/pharmacy/service/GetMedicineDetailService.java @@ -0,0 +1,49 @@ +package com.hospital.hms.pharmacy.service; + +import com.hospital.hms.base.service.BaseService; +import com.hospital.hms.exception.NotFoundException; +import com.hospital.hms.exception.ValidationException; +import com.hospital.hms.pharmacy.dto.request.GetMedicineDetailRequest; +import com.hospital.hms.pharmacy.dto.response.MedicineResponse; +import com.hospital.hms.pharmacy.entity.Medicine; +import com.hospital.hms.pharmacy.mapper.MedicineMapper; +import com.hospital.hms.pharmacy.repository.MedicineRepository; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Optional; + +@Service +@Slf4j +@Transactional +@RequiredArgsConstructor +public class GetMedicineDetailService extends BaseService { + + private final MedicineRepository medicineRepository; + private final MedicineMapper medicineMapper; + + @Override + protected MedicineResponse doProcess(GetMedicineDetailRequest request) { + Optional medicineOpt = medicineRepository.findById(request.getId()); + + if (medicineOpt.isEmpty()) { + log.warn("Medicine not found with id: {}", request.getId()); + throw new NotFoundException("Medicine not found"); + } + + Medicine medicine = medicineOpt.get(); + + // Use mapper to dto response + return medicineMapper.toResponse(medicine); + } + + @Override + public void validate(GetMedicineDetailRequest request) { + if (request.getId() == null) { + throw new ValidationException("Medicine id must not be null"); + } + } + +} diff --git a/hms-api/src/main/java/com/hospital/hms/pharmacy/service/UpdateMedicineService.java b/hms-api/src/main/java/com/hospital/hms/pharmacy/service/UpdateMedicineService.java new file mode 100644 index 0000000..9408560 --- /dev/null +++ b/hms-api/src/main/java/com/hospital/hms/pharmacy/service/UpdateMedicineService.java @@ -0,0 +1,64 @@ +package com.hospital.hms.pharmacy.service; + +import com.hospital.hms.base.service.BaseService; +import com.hospital.hms.exception.NotFoundException; +import com.hospital.hms.exception.ValidationException; +import com.hospital.hms.pharmacy.dto.request.UpdateMedicineRequest; +import com.hospital.hms.pharmacy.dto.response.MedicineResponse; +import com.hospital.hms.pharmacy.entity.Medicine; +import com.hospital.hms.pharmacy.mapper.MedicineMapper; +import com.hospital.hms.pharmacy.repository.MedicineRepository; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; + +@Slf4j +@Service +@RequiredArgsConstructor +public class UpdateMedicineService extends BaseService { + + private final MedicineRepository medicineRepository; + private final MedicineMapper medicineMapper; + + @Override + @Transactional + protected MedicineResponse doProcess(UpdateMedicineRequest request) { + log.debug("Processing medicine updating request: {}", request.getName()); + + Medicine medicine = medicineRepository.findById(request.getId()).orElseThrow( + () -> new NotFoundException("Medicine", request.getId()) + ); + + medicine.setDescription(request.getDescription()); + medicine.setName(request.getName()); + medicine.setPrice(request.getPrice()); + medicine.setUpdatedAt(LocalDateTime.now()); + + Medicine updatedMedicine = medicineRepository.save(medicine); + + log.info("Medicine saved with ID: {}", + updatedMedicine.getId()); + + MedicineResponse response = medicineMapper.toResponse(updatedMedicine); + + return response; + + } + + @Override + public void validate(UpdateMedicineRequest request) { + super.validate(request); + log.debug("Starting service-level validation for medicine update: {}", request.getName()); + + log.info("Service-level validation passed for medicine update: {}", request.getName()); + + if (request.getDescription().length() < 200) { + throw new ValidationException( + "Medicine require very detailed descriptions (minimum 200 characters)" + ); + } + } +} diff --git a/hms-api/src/main/resources/application.yml b/hms-api/src/main/resources/application.yml index 1289b2d..7eea3ca 100644 --- a/hms-api/src/main/resources/application.yml +++ b/hms-api/src/main/resources/application.yml @@ -9,8 +9,8 @@ spring: # ------ MySQL (JPA/Hibernate) ------ datasource: url: jdbc:mysql://localhost:3306/hms_db?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC - username: ${MYSQL_USER:sa} - password: ${MYSQL_PASSWORD:123} + username: ${MYSQL_USER} + password: ${MYSQL_PASSWORD} driver-class-name: com.mysql.cj.jdbc.Driver jpa: diff --git a/hms-api/src/main/resources/db/migration/V1__init.sql b/hms-api/src/main/resources/db/migration/V1__init.sql index e456b05..92282ab 100644 --- a/hms-api/src/main/resources/db/migration/V1__init.sql +++ b/hms-api/src/main/resources/db/migration/V1__init.sql @@ -7,14 +7,28 @@ -- ============================================================= -- Example: a simple lookup table to verify Flyway is working -CREATE TABLE IF NOT EXISTS app_metadata ( - id BIGINT NOT NULL AUTO_INCREMENT, - meta_key VARCHAR(100) NOT NULL UNIQUE, - meta_value TEXT, - created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - PRIMARY KEY (id) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +CREATE TABLE IF NOT EXISTS app_metadata +( + id + BIGINT + NOT + NULL + AUTO_INCREMENT, + meta_key + VARCHAR +( + 100 +) NOT NULL UNIQUE, + meta_value TEXT, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY +( + id +) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE =utf8mb4_unicode_ci; -INSERT INTO app_metadata (meta_key, meta_value) VALUES ('schema_version', '1.0.0'); -INSERT INTO app_metadata (meta_key, meta_value) VALUES ('app_name', 'Hospital Management System'); +INSERT INTO app_metadata (meta_key, meta_value) +VALUES ('schema_version', '1.0.0'); +INSERT INTO app_metadata (meta_key, meta_value) +VALUES ('app_name', 'Hospital Management System'); diff --git a/hms-api/src/main/resources/db/migration/V2__create_domain_tables.sql b/hms-api/src/main/resources/db/migration/V2__create_domain_tables.sql index 45ed4dc..6278fc8 100644 --- a/hms-api/src/main/resources/db/migration/V2__create_domain_tables.sql +++ b/hms-api/src/main/resources/db/migration/V2__create_domain_tables.sql @@ -4,271 +4,291 @@ -- Generated from Hibernate ddl-auto. -- ============================================================= -CREATE TABLE `role` ( - `id` binary(16) NOT NULL, - `created_at` datetime(6) DEFAULT NULL, - `updated_at` datetime(6) DEFAULT NULL, - `description` text, - `name` varchar(50) NOT NULL, - PRIMARY KEY (`id`) +CREATE TABLE `role` +( + `id` binary(16) NOT NULL, + `created_at` datetime(6) DEFAULT NULL, + `updated_at` datetime(6) DEFAULT NULL, + `description` text, + `name` varchar(50) NOT NULL, + PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; -CREATE TABLE `department` ( - `id` binary(16) NOT NULL, - `created_at` datetime(6) DEFAULT NULL, - `updated_at` datetime(6) DEFAULT NULL, - `is_active` tinyint(1) NOT NULL, - `name` varchar(100) NOT NULL, - PRIMARY KEY (`id`) +CREATE TABLE `department` +( + `id` binary(16) NOT NULL, + `created_at` datetime(6) DEFAULT NULL, + `updated_at` datetime(6) DEFAULT NULL, + `is_active` tinyint(1) NOT NULL, + `name` varchar(100) NOT NULL, + PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; -CREATE TABLE `service` ( - `id` binary(16) NOT NULL, - `created_at` datetime(6) DEFAULT NULL, - `updated_at` datetime(6) DEFAULT NULL, - `name` varchar(100) NOT NULL, - `price` decimal(10,2) NOT NULL, - PRIMARY KEY (`id`) +CREATE TABLE `service` +( + `id` binary(16) NOT NULL, + `created_at` datetime(6) DEFAULT NULL, + `updated_at` datetime(6) DEFAULT NULL, + `name` varchar(100) NOT NULL, + `price` decimal(10, 2) NOT NULL, + PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; -CREATE TABLE `medicine` ( - `id` binary(16) NOT NULL, - `created_at` datetime(6) DEFAULT NULL, - `updated_at` datetime(6) DEFAULT NULL, - `description` text, - `is_active` tinyint(1) NOT NULL, - `name` varchar(100) NOT NULL, - `price` decimal(10,2) NOT NULL, - `quantity` int NOT NULL, - PRIMARY KEY (`id`) +CREATE TABLE `medicine` +( + `id` binary(16) NOT NULL, + `created_at` datetime(6) DEFAULT NULL, + `updated_at` datetime(6) DEFAULT NULL, + `description` text, + `is_active` tinyint(1) NOT NULL, + `name` varchar(100) NOT NULL, + `price` decimal(10, 2) NOT NULL, + `quantity` int NOT NULL, + PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; -CREATE TABLE `account` ( - `id` binary(16) NOT NULL, - `created_at` datetime(6) DEFAULT NULL, - `updated_at` datetime(6) DEFAULT NULL, - `address` varchar(255) DEFAULT NULL, - `dob` date DEFAULT NULL, - `full_name` varchar(100) DEFAULT NULL, - `gender` enum('FEMALE','MALE') DEFAULT NULL, - `is_active` tinyint(1) NOT NULL, - `phone` varchar(20) DEFAULT NULL, - `username` varchar(50) NOT NULL, - `role_id` binary(16) NOT NULL, - PRIMARY KEY (`id`), - UNIQUE KEY `UK_account_username` (`username`), - KEY `FK_account_role` (`role_id`), - CONSTRAINT `FK_account_role` FOREIGN KEY (`role_id`) REFERENCES `role` (`id`) +CREATE TABLE `account` +( + `id` binary(16) NOT NULL, + `created_at` datetime(6) DEFAULT NULL, + `updated_at` datetime(6) DEFAULT NULL, + `address` varchar(255) DEFAULT NULL, + `dob` date DEFAULT NULL, + `full_name` varchar(100) DEFAULT NULL, + `gender` enum('FEMALE','MALE') DEFAULT NULL, + `is_active` tinyint(1) NOT NULL, + `phone` varchar(20) DEFAULT NULL, + `username` varchar(50) NOT NULL, + `role_id` binary(16) NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `UK_account_username` (`username`), + KEY `FK_account_role` (`role_id`), + CONSTRAINT `FK_account_role` FOREIGN KEY (`role_id`) REFERENCES `role` (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; -CREATE TABLE `employee_info` ( - `id` binary(16) NOT NULL, - `created_at` datetime(6) DEFAULT NULL, - `updated_at` datetime(6) DEFAULT NULL, - `code` varchar(20) DEFAULT NULL, - `hire_date` date DEFAULT NULL, - `account_id` binary(16) NOT NULL, - `department_id` binary(16) NOT NULL, - PRIMARY KEY (`id`), - UNIQUE KEY `UK_employee_info_account` (`account_id`), - KEY `FK_employee_info_department` (`department_id`), - CONSTRAINT `FK_employee_info_account` FOREIGN KEY (`account_id`) REFERENCES `account` (`id`), - CONSTRAINT `FK_employee_info_department` FOREIGN KEY (`department_id`) REFERENCES `department` (`id`) +CREATE TABLE `employee_info` +( + `id` binary(16) NOT NULL, + `created_at` datetime(6) DEFAULT NULL, + `updated_at` datetime(6) DEFAULT NULL, + `code` varchar(20) DEFAULT NULL, + `hire_date` date DEFAULT NULL, + `account_id` binary(16) NOT NULL, + `department_id` binary(16) NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `UK_employee_info_account` (`account_id`), + KEY `FK_employee_info_department` (`department_id`), + CONSTRAINT `FK_employee_info_account` FOREIGN KEY (`account_id`) REFERENCES `account` (`id`), + CONSTRAINT `FK_employee_info_department` FOREIGN KEY (`department_id`) REFERENCES `department` (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; -CREATE TABLE `patient_info` ( - `id` binary(16) NOT NULL, - `created_at` datetime(6) DEFAULT NULL, - `updated_at` datetime(6) DEFAULT NULL, - `allergies` text, - `blood_type` varchar(5) DEFAULT NULL, - `account_id` binary(16) NOT NULL, - PRIMARY KEY (`id`), - UNIQUE KEY `UK_patient_info_account` (`account_id`), - CONSTRAINT `FK_patient_info_account` FOREIGN KEY (`account_id`) REFERENCES `account` (`id`) +CREATE TABLE `patient_info` +( + `id` binary(16) NOT NULL, + `created_at` datetime(6) DEFAULT NULL, + `updated_at` datetime(6) DEFAULT NULL, + `allergies` text, + `blood_type` varchar(5) DEFAULT NULL, + `account_id` binary(16) NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `UK_patient_info_account` (`account_id`), + CONSTRAINT `FK_patient_info_account` FOREIGN KEY (`account_id`) REFERENCES `account` (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; -CREATE TABLE `health_insurance` ( - `id` binary(16) NOT NULL, - `created_at` datetime(6) DEFAULT NULL, - `updated_at` datetime(6) DEFAULT NULL, - `due_date` date DEFAULT NULL, - `is_valid` tinyint(1) NOT NULL, - `name` varchar(100) NOT NULL, - `patient_id` binary(16) NOT NULL, - PRIMARY KEY (`id`), - KEY `FK_health_insurance_patient` (`patient_id`), - CONSTRAINT `FK_health_insurance_patient` FOREIGN KEY (`patient_id`) REFERENCES `patient_info` (`id`) +CREATE TABLE `health_insurance` +( + `id` binary(16) NOT NULL, + `created_at` datetime(6) DEFAULT NULL, + `updated_at` datetime(6) DEFAULT NULL, + `due_date` date DEFAULT NULL, + `is_valid` tinyint(1) NOT NULL, + `name` varchar(100) NOT NULL, + `patient_id` binary(16) NOT NULL, + PRIMARY KEY (`id`), + KEY `FK_health_insurance_patient` (`patient_id`), + CONSTRAINT `FK_health_insurance_patient` FOREIGN KEY (`patient_id`) REFERENCES `patient_info` (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; -CREATE TABLE `doctor_schedule` ( - `id` binary(16) NOT NULL, - `created_at` datetime(6) DEFAULT NULL, - `updated_at` datetime(6) DEFAULT NULL, - `date` date NOT NULL, - `end_time` time(6) NOT NULL, - `is_available` tinyint(1) NOT NULL, - `max_patients` int NOT NULL, - `start_time` time(6) NOT NULL, - `doctor_id` binary(16) NOT NULL, - PRIMARY KEY (`id`), - KEY `FK_doctor_schedule_doctor` (`doctor_id`), - CONSTRAINT `FK_doctor_schedule_doctor` FOREIGN KEY (`doctor_id`) REFERENCES `account` (`id`) +CREATE TABLE `doctor_schedule` +( + `id` binary(16) NOT NULL, + `created_at` datetime(6) DEFAULT NULL, + `updated_at` datetime(6) DEFAULT NULL, + `date` date NOT NULL, + `end_time` time(6) NOT NULL, + `is_available` tinyint(1) NOT NULL, + `max_patients` int NOT NULL, + `start_time` time(6) NOT NULL, + `doctor_id` binary(16) NOT NULL, + PRIMARY KEY (`id`), + KEY `FK_doctor_schedule_doctor` (`doctor_id`), + CONSTRAINT `FK_doctor_schedule_doctor` FOREIGN KEY (`doctor_id`) REFERENCES `account` (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; -CREATE TABLE `appointment` ( - `id` binary(16) NOT NULL, - `created_at` datetime(6) DEFAULT NULL, - `updated_at` datetime(6) DEFAULT NULL, - `date` datetime(6) NOT NULL, - `reason` text, - `status` enum('CANCELLED','COMPLETED','CONFIRMED','PENDING') NOT NULL, - `doctor_id` binary(16) NOT NULL, - `patient_id` binary(16) NOT NULL, - `schedule_id` binary(16) NOT NULL, - PRIMARY KEY (`id`), - KEY `FK_appointment_doctor` (`doctor_id`), - KEY `FK_appointment_patient` (`patient_id`), - KEY `FK_appointment_schedule` (`schedule_id`), - CONSTRAINT `FK_appointment_patient` FOREIGN KEY (`patient_id`) REFERENCES `patient_info` (`id`), - CONSTRAINT `FK_appointment_schedule` FOREIGN KEY (`schedule_id`) REFERENCES `doctor_schedule` (`id`), - CONSTRAINT `FK_appointment_doctor` FOREIGN KEY (`doctor_id`) REFERENCES `account` (`id`) +CREATE TABLE `appointment` +( + `id` binary(16) NOT NULL, + `created_at` datetime(6) DEFAULT NULL, + `updated_at` datetime(6) DEFAULT NULL, + `date` datetime(6) NOT NULL, + `reason` text, + `status` enum('CANCELLED','COMPLETED','CONFIRMED','PENDING') NOT NULL, + `doctor_id` binary(16) NOT NULL, + `patient_id` binary(16) NOT NULL, + `schedule_id` binary(16) NOT NULL, + PRIMARY KEY (`id`), + KEY `FK_appointment_doctor` (`doctor_id`), + KEY `FK_appointment_patient` (`patient_id`), + KEY `FK_appointment_schedule` (`schedule_id`), + CONSTRAINT `FK_appointment_patient` FOREIGN KEY (`patient_id`) REFERENCES `patient_info` (`id`), + CONSTRAINT `FK_appointment_schedule` FOREIGN KEY (`schedule_id`) REFERENCES `doctor_schedule` (`id`), + CONSTRAINT `FK_appointment_doctor` FOREIGN KEY (`doctor_id`) REFERENCES `account` (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; -CREATE TABLE `medical_record` ( - `id` binary(16) NOT NULL, - `created_at` datetime(6) DEFAULT NULL, - `updated_at` datetime(6) DEFAULT NULL, - `description` text, - `doctor_advice` text, - `doctor_id` binary(16) NOT NULL, - `patient_id` binary(16) NOT NULL, - PRIMARY KEY (`id`), - KEY `FK_medical_record_doctor` (`doctor_id`), - KEY `FK_medical_record_patient` (`patient_id`), - CONSTRAINT `FK_medical_record_patient` FOREIGN KEY (`patient_id`) REFERENCES `patient_info` (`id`), - CONSTRAINT `FK_medical_record_doctor` FOREIGN KEY (`doctor_id`) REFERENCES `account` (`id`) +CREATE TABLE `medical_record` +( + `id` binary(16) NOT NULL, + `created_at` datetime(6) DEFAULT NULL, + `updated_at` datetime(6) DEFAULT NULL, + `description` text, + `doctor_advice` text, + `doctor_id` binary(16) NOT NULL, + `patient_id` binary(16) NOT NULL, + PRIMARY KEY (`id`), + KEY `FK_medical_record_doctor` (`doctor_id`), + KEY `FK_medical_record_patient` (`patient_id`), + CONSTRAINT `FK_medical_record_patient` FOREIGN KEY (`patient_id`) REFERENCES `patient_info` (`id`), + CONSTRAINT `FK_medical_record_doctor` FOREIGN KEY (`doctor_id`) REFERENCES `account` (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; -CREATE TABLE `medical_examination_history` ( - `id` binary(16) NOT NULL, - `created_at` datetime(6) DEFAULT NULL, - `updated_at` datetime(6) DEFAULT NULL, - `date` date DEFAULT NULL, - `description` text, - `patient_id` binary(16) NOT NULL, - `reception_counter_staff_id` binary(16) NOT NULL, - PRIMARY KEY (`id`), - KEY `FK_med_exam_history_patient` (`patient_id`), - KEY `FK_med_exam_history_reception` (`reception_counter_staff_id`), - CONSTRAINT `FK_med_exam_history_reception` FOREIGN KEY (`reception_counter_staff_id`) REFERENCES `account` (`id`), - CONSTRAINT `FK_med_exam_history_patient` FOREIGN KEY (`patient_id`) REFERENCES `patient_info` (`id`) +CREATE TABLE `medical_examination_history` +( + `id` binary(16) NOT NULL, + `created_at` datetime(6) DEFAULT NULL, + `updated_at` datetime(6) DEFAULT NULL, + `date` date DEFAULT NULL, + `description` text, + `patient_id` binary(16) NOT NULL, + `reception_counter_staff_id` binary(16) NOT NULL, + PRIMARY KEY (`id`), + KEY `FK_med_exam_history_patient` (`patient_id`), + KEY `FK_med_exam_history_reception` (`reception_counter_staff_id`), + CONSTRAINT `FK_med_exam_history_reception` FOREIGN KEY (`reception_counter_staff_id`) REFERENCES `account` (`id`), + CONSTRAINT `FK_med_exam_history_patient` FOREIGN KEY (`patient_id`) REFERENCES `patient_info` (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; -CREATE TABLE `medicine_invoice` ( - `id` binary(16) NOT NULL, - `created_at` datetime(6) DEFAULT NULL, - `updated_at` datetime(6) DEFAULT NULL, - `due_date` date DEFAULT NULL, - `is_paid` tinyint(1) NOT NULL, - `total_price` decimal(10,2) DEFAULT NULL, - `record_id` binary(16) NOT NULL, - PRIMARY KEY (`id`), - KEY `FK_medicine_invoice_record` (`record_id`), - CONSTRAINT `FK_medicine_invoice_record` FOREIGN KEY (`record_id`) REFERENCES `medical_record` (`id`) +CREATE TABLE `medicine_invoice` +( + `id` binary(16) NOT NULL, + `created_at` datetime(6) DEFAULT NULL, + `updated_at` datetime(6) DEFAULT NULL, + `due_date` date DEFAULT NULL, + `is_paid` tinyint(1) NOT NULL, + `total_price` decimal(10, 2) DEFAULT NULL, + `record_id` binary(16) NOT NULL, + PRIMARY KEY (`id`), + KEY `FK_medicine_invoice_record` (`record_id`), + CONSTRAINT `FK_medicine_invoice_record` FOREIGN KEY (`record_id`) REFERENCES `medical_record` (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; -CREATE TABLE `medicine_prescription` ( - `id` binary(16) NOT NULL, - `created_at` datetime(6) DEFAULT NULL, - `updated_at` datetime(6) DEFAULT NULL, - `doctor_id` binary(16) NOT NULL, - `medicine_invoice_id` binary(16) NOT NULL, - PRIMARY KEY (`id`), - KEY `FK_med_prescription_doctor` (`doctor_id`), - KEY `FK_med_prescription_invoice` (`medicine_invoice_id`), - CONSTRAINT `FK_med_prescription_invoice` FOREIGN KEY (`medicine_invoice_id`) REFERENCES `medicine_invoice` (`id`), - CONSTRAINT `FK_med_prescription_doctor` FOREIGN KEY (`doctor_id`) REFERENCES `account` (`id`) +CREATE TABLE `medicine_prescription` +( + `id` binary(16) NOT NULL, + `created_at` datetime(6) DEFAULT NULL, + `updated_at` datetime(6) DEFAULT NULL, + `doctor_id` binary(16) NOT NULL, + `medicine_invoice_id` binary(16) NOT NULL, + PRIMARY KEY (`id`), + KEY `FK_med_prescription_doctor` (`doctor_id`), + KEY `FK_med_prescription_invoice` (`medicine_invoice_id`), + CONSTRAINT `FK_med_prescription_invoice` FOREIGN KEY (`medicine_invoice_id`) REFERENCES `medicine_invoice` (`id`), + CONSTRAINT `FK_med_prescription_doctor` FOREIGN KEY (`doctor_id`) REFERENCES `account` (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; -CREATE TABLE `medicine_item` ( - `medicine_id` binary(16) NOT NULL, - `medicine_prescription_id` binary(16) NOT NULL, - `quantity` int NOT NULL, - PRIMARY KEY (`medicine_id`,`medicine_prescription_id`), - KEY `FK_medicine_item_prescription` (`medicine_prescription_id`), - CONSTRAINT `FK_medicine_item_prescription` FOREIGN KEY (`medicine_prescription_id`) REFERENCES `medicine_prescription` (`id`), - CONSTRAINT `FK_medicine_item_medicine` FOREIGN KEY (`medicine_id`) REFERENCES `medicine` (`id`) +CREATE TABLE `medicine_item` +( + `medicine_id` binary(16) NOT NULL, + `medicine_prescription_id` binary(16) NOT NULL, + `quantity` int NOT NULL, + PRIMARY KEY (`medicine_id`, `medicine_prescription_id`), + KEY `FK_medicine_item_prescription` (`medicine_prescription_id`), + CONSTRAINT `FK_medicine_item_prescription` FOREIGN KEY (`medicine_prescription_id`) REFERENCES `medicine_prescription` (`id`), + CONSTRAINT `FK_medicine_item_medicine` FOREIGN KEY (`medicine_id`) REFERENCES `medicine` (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; -CREATE TABLE `service_invoice` ( - `id` binary(16) NOT NULL, - `created_at` datetime(6) DEFAULT NULL, - `updated_at` datetime(6) DEFAULT NULL, - `due_date` date DEFAULT NULL, - `total_fee` decimal(10,2) DEFAULT NULL, - `medical_record_id` binary(16) NOT NULL, - PRIMARY KEY (`id`), - KEY `FK_service_invoice_record` (`medical_record_id`), - CONSTRAINT `FK_service_invoice_record` FOREIGN KEY (`medical_record_id`) REFERENCES `medical_record` (`id`) +CREATE TABLE `service_invoice` +( + `id` binary(16) NOT NULL, + `created_at` datetime(6) DEFAULT NULL, + `updated_at` datetime(6) DEFAULT NULL, + `due_date` date DEFAULT NULL, + `total_fee` decimal(10, 2) DEFAULT NULL, + `medical_record_id` binary(16) NOT NULL, + PRIMARY KEY (`id`), + KEY `FK_service_invoice_record` (`medical_record_id`), + CONSTRAINT `FK_service_invoice_record` FOREIGN KEY (`medical_record_id`) REFERENCES `medical_record` (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; -CREATE TABLE `service_item` ( - `service_id` binary(16) NOT NULL, - `service_invoice_id` binary(16) NOT NULL, - `number_service_use` int NOT NULL, - PRIMARY KEY (`service_id`,`service_invoice_id`), - KEY `FK_service_item_invoice` (`service_invoice_id`), - CONSTRAINT `FK_service_item_service` FOREIGN KEY (`service_id`) REFERENCES `service` (`id`), - CONSTRAINT `FK_service_item_invoice` FOREIGN KEY (`service_invoice_id`) REFERENCES `service_invoice` (`id`) +CREATE TABLE `service_item` +( + `service_id` binary(16) NOT NULL, + `service_invoice_id` binary(16) NOT NULL, + `number_service_use` int NOT NULL, + PRIMARY KEY (`service_id`, `service_invoice_id`), + KEY `FK_service_item_invoice` (`service_invoice_id`), + CONSTRAINT `FK_service_item_service` FOREIGN KEY (`service_id`) REFERENCES `service` (`id`), + CONSTRAINT `FK_service_item_invoice` FOREIGN KEY (`service_invoice_id`) REFERENCES `service_invoice` (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; -CREATE TABLE `hospital_fee_payment` ( - `id` binary(16) NOT NULL, - `created_at` datetime(6) DEFAULT NULL, - `updated_at` datetime(6) DEFAULT NULL, - `due_date` date DEFAULT NULL, - `is_paid` tinyint(1) NOT NULL, - `total_fee` decimal(10,2) DEFAULT NULL, - `cashier_counter_staff_id` binary(16) NOT NULL, - `record_id` binary(16) NOT NULL, - `patient_id` binary(16) NOT NULL, - PRIMARY KEY (`id`), - KEY `FK_hospital_fee_cashier` (`cashier_counter_staff_id`), - KEY `FK_hospital_fee_record` (`record_id`), - KEY `FK_hospital_fee_patient` (`patient_id`), - CONSTRAINT `FK_hospital_fee_cashier` FOREIGN KEY (`cashier_counter_staff_id`) REFERENCES `account` (`id`), - CONSTRAINT `FK_hospital_fee_record` FOREIGN KEY (`record_id`) REFERENCES `medical_record` (`id`), - CONSTRAINT `FK_hospital_fee_patient` FOREIGN KEY (`patient_id`) REFERENCES `patient_info` (`id`) +CREATE TABLE `hospital_fee_payment` +( + `id` binary(16) NOT NULL, + `created_at` datetime(6) DEFAULT NULL, + `updated_at` datetime(6) DEFAULT NULL, + `due_date` date DEFAULT NULL, + `is_paid` tinyint(1) NOT NULL, + `total_fee` decimal(10, 2) DEFAULT NULL, + `cashier_counter_staff_id` binary(16) NOT NULL, + `record_id` binary(16) NOT NULL, + `patient_id` binary(16) NOT NULL, + PRIMARY KEY (`id`), + KEY `FK_hospital_fee_cashier` (`cashier_counter_staff_id`), + KEY `FK_hospital_fee_record` (`record_id`), + KEY `FK_hospital_fee_patient` (`patient_id`), + CONSTRAINT `FK_hospital_fee_cashier` FOREIGN KEY (`cashier_counter_staff_id`) REFERENCES `account` (`id`), + CONSTRAINT `FK_hospital_fee_record` FOREIGN KEY (`record_id`) REFERENCES `medical_record` (`id`), + CONSTRAINT `FK_hospital_fee_patient` FOREIGN KEY (`patient_id`) REFERENCES `patient_info` (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; -CREATE TABLE `notification` ( - `id` binary(16) NOT NULL, - `created_at` datetime(6) DEFAULT NULL, - `updated_at` datetime(6) DEFAULT NULL, - `is_read` tinyint(1) NOT NULL, - `message` text, - `payload` json DEFAULT NULL, - `title` varchar(255) NOT NULL, - `type` varchar(50) NOT NULL, - `recipient_id` binary(16) NOT NULL, - PRIMARY KEY (`id`), - KEY `FK_notification_recipient` (`recipient_id`), - CONSTRAINT `FK_notification_recipient` FOREIGN KEY (`recipient_id`) REFERENCES `account` (`id`) +CREATE TABLE `notification` +( + `id` binary(16) NOT NULL, + `created_at` datetime(6) DEFAULT NULL, + `updated_at` datetime(6) DEFAULT NULL, + `is_read` tinyint(1) NOT NULL, + `message` text, + `payload` json DEFAULT NULL, + `title` varchar(255) NOT NULL, + `type` varchar(50) NOT NULL, + `recipient_id` binary(16) NOT NULL, + PRIMARY KEY (`id`), + KEY `FK_notification_recipient` (`recipient_id`), + CONSTRAINT `FK_notification_recipient` FOREIGN KEY (`recipient_id`) REFERENCES `account` (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; -CREATE TABLE `audit_log` ( - `id` binary(16) NOT NULL, - `created_at` datetime(6) DEFAULT NULL, - `updated_at` datetime(6) DEFAULT NULL, - `action` enum('DELETE','INSERT','UPDATE') NOT NULL, - `changed_at` datetime(6) NOT NULL, - `new_value` json DEFAULT NULL, - `old_value` json DEFAULT NULL, - `record_id` int NOT NULL, - `table_name` varchar(50) NOT NULL, - `changed_by` binary(16) NOT NULL, - PRIMARY KEY (`id`), - KEY `FK_audit_log_account` (`changed_by`), - CONSTRAINT `FK_audit_log_account` FOREIGN KEY (`changed_by`) REFERENCES `account` (`id`) +CREATE TABLE `audit_log` +( + `id` binary(16) NOT NULL, + `created_at` datetime(6) DEFAULT NULL, + `updated_at` datetime(6) DEFAULT NULL, + `action` enum('DELETE','INSERT','UPDATE') NOT NULL, + `changed_at` datetime(6) NOT NULL, + `new_value` json DEFAULT NULL, + `old_value` json DEFAULT NULL, + `record_id` int NOT NULL, + `table_name` varchar(50) NOT NULL, + `changed_by` binary(16) NOT NULL, + PRIMARY KEY (`id`), + KEY `FK_audit_log_account` (`changed_by`), + CONSTRAINT `FK_audit_log_account` FOREIGN KEY (`changed_by`) REFERENCES `account` (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; diff --git a/hms-api/src/main/resources/db/migration/V5__insert_roles_data.sql b/hms-api/src/main/resources/db/migration/V5__insert_roles_data.sql index bbd7edb..d76b59b 100644 --- a/hms-api/src/main/resources/db/migration/V5__insert_roles_data.sql +++ b/hms-api/src/main/resources/db/migration/V5__insert_roles_data.sql @@ -4,10 +4,9 @@ -- ============================================================= INSERT INTO `role` (`id`, `created_at`, `updated_at`, `name`, `description`) -VALUES - (UUID_TO_BIN(UUID()), NOW(), NOW(), 'admin', 'Administrator role'), - (UUID_TO_BIN(UUID()), NOW(), NOW(), 'cashier', 'Cashier role'), - (UUID_TO_BIN(UUID()), NOW(), NOW(), 'doctor', 'Doctor role'), - (UUID_TO_BIN(UUID()), NOW(), NOW(), 'patient', 'Patient role'), - (UUID_TO_BIN(UUID()), NOW(), NOW(), 'pharmacist', 'Pharmacist role'), - (UUID_TO_BIN(UUID()), NOW(), NOW(), 'receptionist', 'Receptionist role'); +VALUES (UUID_TO_BIN(UUID()), NOW(), NOW(), 'admin', 'Administrator role'), + (UUID_TO_BIN(UUID()), NOW(), NOW(), 'cashier', 'Cashier role'), + (UUID_TO_BIN(UUID()), NOW(), NOW(), 'doctor', 'Doctor role'), + (UUID_TO_BIN(UUID()), NOW(), NOW(), 'patient', 'Patient role'), + (UUID_TO_BIN(UUID()), NOW(), NOW(), 'pharmacist', 'Pharmacist role'), + (UUID_TO_BIN(UUID()), NOW(), NOW(), 'receptionist', 'Receptionist role'); diff --git a/hms-api/src/main/resources/db/migration/V6__insert_departments_data.sql b/hms-api/src/main/resources/db/migration/V6__insert_departments_data.sql index 3b62e1e..a85ba4c 100644 --- a/hms-api/src/main/resources/db/migration/V6__insert_departments_data.sql +++ b/hms-api/src/main/resources/db/migration/V6__insert_departments_data.sql @@ -4,14 +4,13 @@ -- ============================================================= INSERT INTO `department` (`id`, `created_at`, `updated_at`, `name`, `is_active`) -VALUES - (UUID_TO_BIN(UUID()), NOW(), NOW(), 'Internal Medicine', 1), - (UUID_TO_BIN(UUID()), NOW(), NOW(), 'Surgery', 1), - (UUID_TO_BIN(UUID()), NOW(), NOW(), 'Obstetrics & Gynecology', 1), - (UUID_TO_BIN(UUID()), NOW(), NOW(), 'Pediatrics', 1), - (UUID_TO_BIN(UUID()), NOW(), NOW(), 'Ophthalmology', 1), - (UUID_TO_BIN(UUID()), NOW(), NOW(), 'ENT (Ear, Nose & Throat)', 1), - (UUID_TO_BIN(UUID()), NOW(), NOW(), 'Dermatology', 1), - (UUID_TO_BIN(UUID()), NOW(), NOW(), 'Dental & Maxillofacial', 1), - (UUID_TO_BIN(UUID()), NOW(), NOW(), 'Emergency', 1), - (UUID_TO_BIN(UUID()), NOW(), NOW(), 'Laboratory', 1); +VALUES (UUID_TO_BIN(UUID()), NOW(), NOW(), 'Internal Medicine', 1), + (UUID_TO_BIN(UUID()), NOW(), NOW(), 'Surgery', 1), + (UUID_TO_BIN(UUID()), NOW(), NOW(), 'Obstetrics & Gynecology', 1), + (UUID_TO_BIN(UUID()), NOW(), NOW(), 'Pediatrics', 1), + (UUID_TO_BIN(UUID()), NOW(), NOW(), 'Ophthalmology', 1), + (UUID_TO_BIN(UUID()), NOW(), NOW(), 'ENT (Ear, Nose & Throat)', 1), + (UUID_TO_BIN(UUID()), NOW(), NOW(), 'Dermatology', 1), + (UUID_TO_BIN(UUID()), NOW(), NOW(), 'Dental & Maxillofacial', 1), + (UUID_TO_BIN(UUID()), NOW(), NOW(), 'Emergency', 1), + (UUID_TO_BIN(UUID()), NOW(), NOW(), 'Laboratory', 1);