package com.pollex.pam.service; import com.pollex.pam.config.ApplicationProperties; import com.pollex.pam.domain.Appointment; import com.pollex.pam.domain.AppointmentExpiringNotifyRecord; import com.pollex.pam.domain.Consultant; import com.pollex.pam.enums.AppointmentStatusEnum; import com.pollex.pam.enums.ContactStatusEnum; import com.pollex.pam.repository.AppointmentExpiringNotifyRecordRepository; import com.pollex.pam.repository.AppointmentRepository; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; import org.thymeleaf.context.Context; import org.thymeleaf.spring5.SpringTemplateEngine; import java.time.Instant; import java.time.LocalDate; import java.time.ZoneId; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.stream.Collectors; @Service public class ScheduleTaskService { /** * 電話及email在建立預約單(T)後的N天視為未處理預約單 * 目前N皆暫定為2 */ private static final int APPOINTMENT_PENDING_PHONE_INTERVAL = 2; private static final int APPOINTMENT_PENDING_EMAIL_INTERVAL = 2; /** * 電話及email在建立預約單(T)後的N天會被視為未處理預約單,當天批次會發送提醒給顧問 * 而在後一天(T+N+1),就會發送批次給客戶告知 該顧問可能忙碌無法處理,是否需要取消 */ private static final int APPOINTMENT_EXPIRING_PHONE_INTERVAL = APPOINTMENT_PENDING_PHONE_INTERVAL + 1; private static final int APPOINTMENT_EXPIRING_EMAIL_INTERVAL = APPOINTMENT_PENDING_EMAIL_INTERVAL + 1; /** * 通知客戶的次數限制 */ private static final int SEND_EXPIRING_NOTIFY_LIMIT = 1; private static final String NOT_CONTACTED_NOTIFY_SUBJECT = "預約單未進行聯繫通知"; private static final Logger log = LoggerFactory.getLogger(ScheduleTaskService.class); @Autowired ConsultantService consultantService; @Autowired AppointmentService appointmentService; @Autowired AppointmentRepository appointmentRepository; @Autowired SendMsgService sendMsgService; @Autowired SpringTemplateEngine springTemplateEngine; @Autowired ApplicationProperties applicationProperties; @Autowired AppointmentExpiringNotifyRecordRepository appointmentExpiringNotifyRecordRepository; @Scheduled(cron = "0 30 8 * * *") public void sendAppointmentPendingNotifyToConsultant() { log.info("Starting send appointment pending notify to consultant"); Map> consultantWithPendingAppointments = appointmentRepository.findAllByCommunicateStatusAndStatus(ContactStatusEnum.RESERVED, AppointmentStatusEnum.AVAILABLE) .stream() .filter(appointment -> isAppointmentInInterval(appointment, APPOINTMENT_PENDING_PHONE_INTERVAL, APPOINTMENT_PENDING_EMAIL_INTERVAL)) .collect(Collectors.groupingBy(Appointment::getAgentNo)); consultantWithPendingAppointments.forEach((agentNo, pendingAppointments) -> { int pendingAppointmentsSum = pendingAppointments.size(); Consultant consultant = consultantService.findByAgentNo(agentNo); String consultantPhoneNumber = consultant.getPhoneNumber(); String consultantEmail = consultant.getEmail(); String emailContent = getAppointmentPendingNotifyEmailContent(pendingAppointmentsSum); sendMsgService.sendMsgBySMS(consultantPhoneNumber, String.format("您有%s則預約單未進行聯繫,請盡速處理", pendingAppointmentsSum)); sendMsgService.sendMsgByEmail(consultantEmail, NOT_CONTACTED_NOTIFY_SUBJECT, emailContent, true); }); log.info("Sending appointment pending notify to consultant finish"); } @Scheduled(cron = "0 30 8 * * *") public void sendAppointmentExpiringNotifyToCustomer() { log.info("Starting send appointment expiring notify to customer"); List allByCommunicateStatus = appointmentRepository.findAllByCommunicateStatusAndStatus(ContactStatusEnum.RESERVED, AppointmentStatusEnum.AVAILABLE) .stream() .filter(appointment -> isAppointmentInInterval(appointment, APPOINTMENT_EXPIRING_PHONE_INTERVAL, APPOINTMENT_EXPIRING_EMAIL_INTERVAL)) .filter(this::isAppointmentNotifyNotOnLimit) .collect(Collectors.toList()); allByCommunicateStatus.forEach(appointment -> { Consultant consultant = consultantService.findByAgentNo(appointment.getAgentNo()); Optional optionalPhone = Optional.ofNullable(appointment.getPhone()).filter(StringUtils::hasText); Optional optionalEmail = Optional.ofNullable(appointment.getEmail()).filter(StringUtils::hasText); optionalPhone.ifPresent(phone -> sendMsgService.sendMsgBySMS(phone, String.format("很抱歉!您預約%s顧問正忙碌中,請您取消預約並改選其他顧問,請點擊網址:%s" , consultant.getName(), getAppointmentUrl(appointment.getId()))) ); optionalEmail.ifPresent(email -> sendMsgService.sendMsgByEmail(email, NOT_CONTACTED_NOTIFY_SUBJECT, getAppointmentExpiringNotifyEmail(consultant.getName(), getAppointmentUrl(appointment.getId())), true) ); AppointmentExpiringNotifyRecord record = new AppointmentExpiringNotifyRecord(); record.setAppointmentId(appointment.getId()); record.setSendTime(Instant.now()); appointmentExpiringNotifyRecordRepository.save(record); }); log.info("Sending appointment expiring notify to customer finish"); } private boolean isAppointmentInInterval(Appointment appointment, int phoneInterval, int emailInterval) { final boolean isHavePhone = StringUtils.hasText(appointment.getPhone()); final boolean isHaveEmail = StringUtils.hasText(appointment.getEmail()); LocalDate appointmentDate = appointment.getAppointmentDate().atZone(ZoneId.systemDefault()).toLocalDate(); LocalDate nowDate = Instant.now().atZone(ZoneId.systemDefault()).toLocalDate(); long intervalDays = nowDate.toEpochDay() - appointmentDate.toEpochDay(); final boolean isAppointmentExpiringByPhone = isHavePhone && intervalDays >= phoneInterval; final boolean isAppointmentExpiringByEmail = isHaveEmail && intervalDays >= emailInterval; return isAppointmentExpiringByPhone || isAppointmentExpiringByEmail; } private boolean isAppointmentNotifyNotOnLimit(Appointment appointment) { int sendNotifyToCustomerRecordSum = appointmentExpiringNotifyRecordRepository.findAllByAppointmentId(appointment.getId()).size(); return sendNotifyToCustomerRecordSum < SEND_EXPIRING_NOTIFY_LIMIT; } private String getAppointmentUrl(Long appointmentId) { return applicationProperties.getFrontEndDomain() + "?notContactAppointmentId=" + appointmentId; } private String getAppointmentPendingNotifyEmailContent(int sum) { Context context = new Context(); context.setVariable("pendingAppointmentSum", sum); return springTemplateEngine.process("mail/appointmentPendingNotifyEmail", context); } private String getAppointmentExpiringNotifyEmail(String consultantName, String notifyUrl) { Context context = new Context(); context.setVariable("consultantName", consultantName); context.setVariable("notifyUrl", notifyUrl); return springTemplateEngine.process("mail/appointmentExpiringNotifyEmail", context); } }