package jnpf.limsService.impl; import jnpf.limsEntity.*; import jnpf.limsMapper.LimsBusinessReminderMapper; import jnpf.limsMapper.LimsMessageAudienceMapper; import jnpf.limsService.LimsBusinessReminderService; import jnpf.limsService.LimsMessageService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.Collections; import java.util.Date; import java.util.ArrayList; import java.util.Arrays; import java.util.LinkedHashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @Slf4j @Service @RequiredArgsConstructor public class LimsBusinessReminderServiceImpl implements LimsBusinessReminderService { private static final DateTimeFormatter DATE = DateTimeFormatter.ofPattern("yyyy-MM-dd"); private static final DateTimeFormatter KEY_DATE = DateTimeFormatter.ofPattern("yyyyMMdd"); private final LimsBusinessReminderMapper reminderMapper; private final LimsMessageAudienceMapper audienceMapper; private final LimsMessageService messageService; @Value("${lims.message.enabled:false}") private boolean messageEnabled; @Scheduled(cron = "${lims.message.business-reminder-cron:0 10 8 * * ?}") public void cronTrigger() { if (!messageEnabled) { log.info("[lims-business-reminder] skipped because lims.message.enabled=false"); return; } scanAndRemind(false); } @Override public Map> scanAndRemind(boolean dryRun) { Map> stats = new LinkedHashMap<>(); stats.put("equipmentCalibration", remindEquipment(dryRun)); stats.put("equipmentVerification", remindEquipmentVerification(dryRun)); stats.put("retainObservation", remindRetainObservation(dryRun)); stats.put("environmentSampling", remindEnvironmentSampling(dryRun)); stats.put("consumableExpiry", remindConsumableExpiry(dryRun)); stats.put("retainDestruction", remindRetainDestruction(dryRun)); stats.put("stabilityPeriod", remindStabilityPeriod(dryRun)); log.info("[lims-business-reminder] done, dryRun={}, stats={}", dryRun, stats); return stats; } private Map remindEquipment(boolean dryRun) { List rows = reminderMapper.selectEquipmentCalibrationDue(); int sent = 0; for (LimsEquipmentCalibrationReminderRow row : rows) { Map parameters = new LinkedHashMap<>(); parameters.put("bizId", row.getId()); parameters.put("equipmentName", value(row.getName())); parameters.put("equipmentCode", value(row.getCode())); parameters.put("reminderDate", format(row.getReminderDate())); parameters.put("dueDate", format(row.getDueDate())); LimsMessageSendResult result = messageService.send(LimsMessageRequest.builder() .event(LimsMessageEvent.EQUIPMENT_CALIBRATION_DUE) .tenantId(row.getTenantId()) .bizId(row.getId()) .remindStage("DUE") .occurrenceKey(LimsMessageEvent.EQUIPMENT_CALIBRATION_DUE.getCode() + ":" + row.getId() + ":" + keyDate(row.getDueDate())) .recipients(Collections.singletonList(LimsRecipientRef.user(row.getRecipientUserId()))) .parameters(parameters) .dryRun(dryRun) .build()); if (isComplete(result)) { sent++; } } return stat(rows.size(), sent); } private Map remindRetainObservation(boolean dryRun) { List rows = reminderMapper.selectRetainObservationDue(); int sent = 0; for (LimsRetainObservationReminderRow row : rows) { Map parameters = new LinkedHashMap<>(); parameters.put("bizId", row.getId()); parameters.put("planId", value(row.getPlanId())); parameters.put("sampleName", value(row.getSampleName())); parameters.put("sampleCode", value(row.getSampleCode())); parameters.put("batchNo", value(row.getBatchNo())); parameters.put("observationDate", format(row.getObservationDate())); LimsMessageSendResult result = messageService.send(LimsMessageRequest.builder() .event(LimsMessageEvent.RETAIN_SAMPLE_OBSERVATION_DUE) .tenantId(row.getTenantId()) .bizId(row.getId()) .remindStage("DUE") .occurrenceKey(LimsMessageEvent.RETAIN_SAMPLE_OBSERVATION_DUE.getCode() + ":" + row.getId() + ":" + keyDate(row.getObservationDate())) .recipients(Collections.singletonList(LimsRecipientRef.user(row.getRecipientUserId()))) .parameters(parameters) .dryRun(dryRun) .build()); if (isComplete(result)) { sent++; } } return stat(rows.size(), sent); } private Map remindEquipmentVerification(boolean dryRun) { List rows = reminderMapper.selectEquipmentVerificationDue(); int sent = 0; for (LimsEquipmentCalibrationReminderRow row : rows) { Map parameters = new LinkedHashMap<>(); parameters.put("bizId", row.getId()); parameters.put("equipmentName", value(row.getName())); parameters.put("equipmentCode", value(row.getCode())); parameters.put("reminderDate", format(row.getReminderDate())); parameters.put("dueDate", format(row.getDueDate())); LimsMessageSendResult result = send(LimsMessageEvent.EQUIPMENT_VERIFICATION_DUE, row.getTenantId(), row.getId(), "DUE", keyDate(row.getDueDate()), Collections.singletonList(LimsRecipientRef.user(row.getRecipientUserId())), parameters, dryRun); if (isComplete(result)) { sent++; } } return stat(rows.size(), sent); } private Map remindEnvironmentSampling(boolean dryRun) { List rows = reminderMapper.selectEnvironmentSamplingDue(); int sent = 0; for (LimsEnvironmentSamplingReminderRow row : rows) { Map parameters = new LinkedHashMap<>(); parameters.put("bizId", row.getPlanId()); parameters.put("planName", value(row.getPlanName())); parameters.put("planType", value(row.getPlanType())); parameters.put("plannedDate", format(row.getPlannedDate())); parameters.put("monitoringArea", value(row.getMonitoringArea())); parameters.put("cleanlinessLevel", value(row.getCleanlinessLevel())); parameters.put("monitoringItems", value(row.getMonitoringItems())); LimsMessageSendResult result = send(LimsMessageEvent.ENVIRONMENT_SAMPLING_DUE, row.getTenantId(), row.getPlanId(), "DUE", keyDate(row.getPlannedDate()), Collections.singletonList(LimsRecipientRef.roleEnCode("ROLE.004")), parameters, dryRun); if (isComplete(result)) { sent++; } } return stat(rows.size(), sent); } private Map remindConsumableExpiry(boolean dryRun) { List rows = reminderMapper.selectConsumableExpiryDue(); int sent = 0; for (LimsConsumableExpiryReminderRow row : rows) { Map parameters = new LinkedHashMap<>(); parameters.put("bizId", row.getId()); parameters.put("batchCode", value(row.getBatchCode())); parameters.put("barcode", value(row.getBarcode())); parameters.put("consumableName", value(row.getName())); parameters.put("specification", value(row.getSpecification())); parameters.put("remainingQuantity", value(row.getRemainingQuantity())); parameters.put("unit", value(row.getUnit())); parameters.put("storageLocation", value(row.getStorageLocation())); parameters.put("expiryDate", format(row.getExpiryDate())); parameters.put("reminderStage", row.getReminderStage()); parameters.put("remainingDays", row.getRemainingDays()); LimsMessageSendResult result = send(LimsMessageEvent.CONSUMABLE_BATCH_EXPIRY_DUE, row.getTenantId(), row.getId(), row.getReminderStage(), keyDate(row.getExpiryDate()) + ":" + row.getReminderStage(), Collections.singletonList(LimsRecipientRef.roleEnCode("ROLE.005")), parameters, dryRun); if (isComplete(result)) { sent++; } } return stat(rows.size(), sent); } private Map remindRetainDestruction(boolean dryRun) { List rows = reminderMapper.selectRetainDestructionDue(); int sent = 0; for (LimsRetainDestructionReminderRow row : rows) { Map parameters = new LinkedHashMap<>(); parameters.put("bizId", row.getId()); parameters.put("sampleName", value(row.getSampleName())); parameters.put("sampleCode", value(row.getSampleCode())); parameters.put("batchNo", value(row.getBatchNo())); parameters.put("currentQuantity", value(row.getCurrentQuantity())); parameters.put("unit", value(row.getUnit())); parameters.put("storageLocation", value(row.getStorageLocation())); parameters.put("retentionDeadline", format(row.getRetentionDeadline())); LimsMessageSendResult result = send(LimsMessageEvent.RETAIN_SAMPLE_DESTRUCTION_DUE, row.getTenantId(), row.getId(), "DUE", keyDate(row.getRetentionDeadline()), Collections.singletonList(LimsRecipientRef.roleEnCode("ROLE.005")), parameters, dryRun); if (isComplete(result)) { sent++; } } return stat(rows.size(), sent); } private Map remindStabilityPeriod(boolean dryRun) { List rows = reminderMapper.selectStabilityPeriodDue(); int sent = 0; for (LimsStabilityPeriodReminderRow row : rows) { LinkedHashSet users = new LinkedHashSet<>(); if (value(row.getOrganizationIds()).length() > 0) { Arrays.stream(row.getOrganizationIds().split(",")) .map(String::trim).filter(item -> !item.isEmpty()) .forEach(organizationId -> users.addAll(audienceMapper.selectOrganizationRoleUsers( row.getTenantId(), organizationId, "ROLE.004"))); } List recipients = new ArrayList<>(); users.forEach(userId -> recipients.add(LimsRecipientRef.user(userId))); Map parameters = new LinkedHashMap<>(); parameters.put("bizId", row.getId()); parameters.put("planId", row.getPlanId()); parameters.put("planCode", value(row.getPlanCode())); parameters.put("planName", value(row.getPlanName())); parameters.put("sampleName", value(row.getSampleName())); parameters.put("batchNo", value(row.getBatchNo())); parameters.put("period", row.getPeriod()); parameters.put("periodUnit", value(row.getPeriodUnit())); parameters.put("plannedDate", format(row.getPlannedDate())); parameters.put("inspectionItems", value(row.getInspectionItems())); LimsMessageSendResult result = send(LimsMessageEvent.STABILITY_PERIOD_DUE, row.getTenantId(), row.getId(), "DUE", keyDate(row.getPlannedDate()), recipients, parameters, dryRun); if (isComplete(result)) { sent++; } } return stat(rows.size(), sent); } private LimsMessageSendResult send(LimsMessageEvent event, String tenantId, String bizId, String stage, String keySuffix, List recipients, Map parameters, boolean dryRun) { return messageService.send(LimsMessageRequest.builder().event(event).tenantId(tenantId) .bizId(bizId).remindStage(stage) .occurrenceKey(event.getCode() + ":" + bizId + ":" + keySuffix) .recipients(recipients).parameters(parameters).dryRun(dryRun).build()); } private boolean isComplete(LimsMessageSendResult result) { return result.isAccepted() && result.getRecipientCount() > 0 && result.getSentCount() == result.getRecipientCount() && result.getFailedCount() == 0; } private Map stat(int hit, int sent) { Map stats = new LinkedHashMap<>(); stats.put("hit", hit); stats.put("sent", sent); stats.put("skipped", hit - sent); return stats; } private String format(LocalDateTime value) { return value == null ? "" : value.format(DATE); } private String format(Date value) { return value == null ? "" : new java.text.SimpleDateFormat("yyyy-MM-dd").format(value); } private String keyDate(LocalDateTime value) { return value == null ? "NO_DATE" : value.format(KEY_DATE); } private String keyDate(Date value) { return value == null ? "NO_DATE" : new java.text.SimpleDateFormat("yyyyMMdd").format(value); } private String value(String value) { return value == null ? "" : value; } }