diff --git a/.gitignore b/.gitignore index f4a93c7..5875f4e 100644 --- a/.gitignore +++ b/.gitignore @@ -34,7 +34,7 @@ out/ /dist/ /nbdist/ /.nb-gradle/ - +data/ ### VS Code ### .vscode/ diff --git a/data/myDB.mv.db b/data/myDB.mv.db new file mode 100644 index 0000000..3338822 Binary files /dev/null and b/data/myDB.mv.db differ diff --git a/data/myDB.trace.db b/data/myDB.trace.db new file mode 100644 index 0000000..299120b --- /dev/null +++ b/data/myDB.trace.db @@ -0,0 +1,38 @@ +2025-05-20 12:36:33.423212Z jdbc[3]: exception +java.sql.SQLClientInfoException: Client info name 'ApplicationName' not supported. + at org.h2.jdbc.JdbcConnection.setClientInfo(JdbcConnection.java:1624) + at com.intellij.database.remote.jdbc.impl.RemoteConnectionImpl.setClientInfo(RemoteConnectionImpl.java:468) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) + at java.base/java.lang.reflect.Method.invoke(Method.java:580) + at java.rmi/sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.java:360) + at java.rmi/sun.rmi.transport.Transport$1.run(Transport.java:200) + at java.rmi/sun.rmi.transport.Transport$1.run(Transport.java:197) + at java.base/java.security.AccessController.doPrivileged(AccessController.java:714) + at java.rmi/sun.rmi.transport.Transport.serviceCall(Transport.java:196) + at java.rmi/sun.rmi.transport.tcp.TCPTransport.handleMessages(TCPTransport.java:598) + at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(TCPTransport.java:844) + at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(TCPTransport.java:721) + at java.base/java.security.AccessController.doPrivileged(AccessController.java:400) + at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(TCPTransport.java:720) + at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144) + at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642) + at java.base/java.lang.Thread.run(Thread.java:1583) +2025-05-20 12:36:34.044132Z jdbc[3]: exception +java.sql.SQLClientInfoException: Client info name 'ApplicationName' not supported. + at org.h2.jdbc.JdbcConnection.setClientInfo(JdbcConnection.java:1624) + at com.intellij.database.remote.jdbc.impl.RemoteConnectionImpl.setClientInfo(RemoteConnectionImpl.java:468) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) + at java.base/java.lang.reflect.Method.invoke(Method.java:580) + at java.rmi/sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.java:360) + at java.rmi/sun.rmi.transport.Transport$1.run(Transport.java:200) + at java.rmi/sun.rmi.transport.Transport$1.run(Transport.java:197) + at java.base/java.security.AccessController.doPrivileged(AccessController.java:714) + at java.rmi/sun.rmi.transport.Transport.serviceCall(Transport.java:196) + at java.rmi/sun.rmi.transport.tcp.TCPTransport.handleMessages(TCPTransport.java:598) + at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(TCPTransport.java:844) + at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(TCPTransport.java:721) + at java.base/java.security.AccessController.doPrivileged(AccessController.java:400) + at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(TCPTransport.java:720) + at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144) + at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642) + at java.base/java.lang.Thread.run(Thread.java:1583) diff --git a/src/main/kotlin/ufrpe/sbpc/botpcd/entity/MessageExchange.kt b/src/main/kotlin/ufrpe/sbpc/botpcd/entity/MessageExchange.kt index 0df1e70..ff0ff7a 100644 --- a/src/main/kotlin/ufrpe/sbpc/botpcd/entity/MessageExchange.kt +++ b/src/main/kotlin/ufrpe/sbpc/botpcd/entity/MessageExchange.kt @@ -18,6 +18,5 @@ class MessageExchange( @Column(columnDefinition = "TEXT") var message: String ) { - @CreationTimestamp - lateinit var createAt: LocalDateTime + var createAt: LocalDateTime = LocalDateTime.now() } \ No newline at end of file diff --git a/src/main/kotlin/ufrpe/sbpc/botpcd/entity/PWD.kt b/src/main/kotlin/ufrpe/sbpc/botpcd/entity/PWD.kt index e082ed4..c8657f4 100644 --- a/src/main/kotlin/ufrpe/sbpc/botpcd/entity/PWD.kt +++ b/src/main/kotlin/ufrpe/sbpc/botpcd/entity/PWD.kt @@ -27,5 +27,5 @@ class PWD( @CollectionTable(name = "tb_pwd_disabilities", joinColumns = [JoinColumn(name = "pwd_id")]) @Column(name = "disability") @Enumerated(EnumType.STRING) - var disabilities: MutableSet = mutableSetOf() + var disabilities: Set = setOf() ): User(name = name, phoneNumber = phoneNumber) \ No newline at end of file diff --git a/src/main/kotlin/ufrpe/sbpc/botpcd/entity/ServiceType.kt b/src/main/kotlin/ufrpe/sbpc/botpcd/entity/ServiceType.kt index 93b54a4..2be6e87 100644 --- a/src/main/kotlin/ufrpe/sbpc/botpcd/entity/ServiceType.kt +++ b/src/main/kotlin/ufrpe/sbpc/botpcd/entity/ServiceType.kt @@ -51,7 +51,7 @@ sealed class ServiceType( object HygieneAndNutrition : ServiceType( attendantType = Provider.COMMITTEE_MEMBER, - disability = mutableSetOf(Disability.MOBILITY_IMPAIRED, Disability.PHYSICAL_DISABILITY), + disability = mutableSetOf(Disability.PHYSICAL_DISABILITY), description = "ajuda com alimentação e higiene" ) @@ -66,11 +66,24 @@ sealed class ServiceType( fun getServicesByDisability(disability: Disability): List { return ServiceType::class.sealedSubclasses .mapNotNull { it.objectInstance } - .filter { disability in it.disability } + .filter { disability in it.disability }.sortedBy { it.description.getAlphabeticOrder() } + } + fun getServiceByMonitorAssistanceType(monitorAssistanceType: MonitorAssistanceType): ServiceType { + return ServiceType::class.sealedSubclasses + .mapNotNull { it.objectInstance } + .filterIsInstance() + .first { it.monitorAssistanceType == monitorAssistanceType } as ServiceType + } + fun getByDescription(description: String): ServiceType { + return ServiceType::class.sealedSubclasses + .mapNotNull { it.objectInstance } + .first { it.description == description } as ServiceType } - } } + +fun String.getAlphabeticOrder() = this.replace("[^a-zA-Z]".toRegex(), "").slice(0..9).reduce {prev, curr -> prev + curr.code} + interface MonitorServiceType { val monitorAssistanceType: MonitorAssistanceType } \ No newline at end of file diff --git a/src/main/kotlin/ufrpe/sbpc/botpcd/repository/AttendanceRepository.kt b/src/main/kotlin/ufrpe/sbpc/botpcd/repository/AttendanceRepository.kt index af58f5b..5437e4e 100644 --- a/src/main/kotlin/ufrpe/sbpc/botpcd/repository/AttendanceRepository.kt +++ b/src/main/kotlin/ufrpe/sbpc/botpcd/repository/AttendanceRepository.kt @@ -5,15 +5,16 @@ import org.springframework.data.jpa.repository.Modifying import org.springframework.data.jpa.repository.Query import org.springframework.data.repository.query.Param import ufrpe.sbpc.botpcd.entity.Attendance -import ufrpe.sbpc.botpcd.entity.ServiceType import ufrpe.sbpc.botpcd.entity.Attendant import ufrpe.sbpc.botpcd.entity.PWD +import ufrpe.sbpc.botpcd.entity.ServiceType import java.time.LocalDateTime interface AttendanceRepository : JpaRepository { @Query("SELECT COUNT(att) FROM Attendance att WHERE att.serviceType = :serviceType AND att.startDateTime IS NULL") fun countRequestAttendanceOfService(serviceType: ServiceType): Long - + @Query("SELECT att FROM Attendance att WHERE att.startDateTime IS NULL AND att.attendantType = 'COMMITTEE_MEMBER'") + fun findPendingCommitteeMemberAttendances(): List @Query("SELECT att from Attendance att where att.serviceType = :serviceType AND att.startDateTime is null order by att.requestDateTime asc") fun findRequestAttendanceOfService(serviceType: ServiceType) : Attendance? @@ -37,4 +38,8 @@ interface AttendanceRepository : JpaRepository { @Modifying(flushAutomatically = true, clearAutomatically = true) // Modificação aqui @Query("DELETE FROM Attendance a WHERE a.pwd = :pwd") fun deleteAllByPwd(@Param("pwd") pwd: PWD) + + @Modifying(flushAutomatically = true, clearAutomatically = true) // Modificação aqui + @Query("DELETE FROM Attendance a WHERE a.attendant = :att") + fun deleteAllByAttendant(@Param("att") att: Attendant) } diff --git a/src/main/kotlin/ufrpe/sbpc/botpcd/repository/AttendantRepository.kt b/src/main/kotlin/ufrpe/sbpc/botpcd/repository/AttendantRepository.kt index 52dcd10..b88cbbd 100644 --- a/src/main/kotlin/ufrpe/sbpc/botpcd/repository/AttendantRepository.kt +++ b/src/main/kotlin/ufrpe/sbpc/botpcd/repository/AttendantRepository.kt @@ -5,4 +5,5 @@ import ufrpe.sbpc.botpcd.entity.Attendant interface AttendantRepository: JpaRepository { fun findByPhoneNumber(phone: String): Attendant? + fun findByName(name: String): Attendant? } \ No newline at end of file diff --git a/src/main/kotlin/ufrpe/sbpc/botpcd/repository/MonitorRepository.kt b/src/main/kotlin/ufrpe/sbpc/botpcd/repository/MonitorRepository.kt index dfe6ec1..1ca4f61 100644 --- a/src/main/kotlin/ufrpe/sbpc/botpcd/repository/MonitorRepository.kt +++ b/src/main/kotlin/ufrpe/sbpc/botpcd/repository/MonitorRepository.kt @@ -8,6 +8,7 @@ import ufrpe.sbpc.botpcd.entity.UserStatus interface MonitorRepository : JpaRepository{ fun findByPhoneNumber(phoneNumber: String): Monitor? + fun findByName(phoneName: String): Monitor? fun findByStatus(status: UserStatus): List @Query("SELECT m from Monitor m where m.status = :status and m.assistanceType = :assistanceType") fun findAvailableMonitor(status: UserStatus, assistanceType: MonitorAssistanceType): List diff --git a/src/main/kotlin/ufrpe/sbpc/botpcd/service/AttendanceService.kt b/src/main/kotlin/ufrpe/sbpc/botpcd/service/AttendanceService.kt index e1b0acd..a836fbb 100644 --- a/src/main/kotlin/ufrpe/sbpc/botpcd/service/AttendanceService.kt +++ b/src/main/kotlin/ufrpe/sbpc/botpcd/service/AttendanceService.kt @@ -3,6 +3,7 @@ package ufrpe.sbpc.botpcd.service import org.slf4j.Logger import org.slf4j.LoggerFactory import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional import ufrpe.sbpc.botpcd.entity.* import ufrpe.sbpc.botpcd.entity.Attendance import ufrpe.sbpc.botpcd.entity.Attendant @@ -17,7 +18,6 @@ import ufrpe.sbpc.botpcd.repository.AttendanceRepository import ufrpe.sbpc.botpcd.repository.CommitteeMemberRepository import ufrpe.sbpc.botpcd.repository.MessageExchangeRepository import ufrpe.sbpc.botpcd.repository.MonitorRepository -import ufrpe.sbpc.botpcd.service.QueueService import ufrpe.sbpc.botpcd.util.createOptions import java.time.LocalDateTime @@ -26,12 +26,188 @@ class AttendanceService( private val queryService: QueueService, private val attendanceRepository: AttendanceRepository, private val whatsappService: WhatsappService, - private val attendantStatusService: AttendantStatusService, private val monitorRepository: MonitorRepository, private val committeeMemberRepository: CommitteeMemberRepository, private val messageExchangeRepository: MessageExchangeRepository, ) { + @Transactional + fun setMonitorStatus(monitor: Monitor, status: UserStatus) { + monitor.status = status + monitorRepository.save(monitor) + } + + @Transactional + fun setCommitteeMemberStatus(member: CommitteeMember, status: UserStatus) { + member.status = status + committeeMemberRepository.save(member) + } + + private fun updateAttendantStatus(botNumber: String, attendant: Attendant, newStatus: UserStatus) { + when (attendant) { + is Monitor -> { + setMonitorStatus(attendant, newStatus) + if(newStatus == UserStatus.AVAILABLE) { + val serviceType = ServiceType.getServiceByMonitorAssistanceType(attendant.assistanceType) + attendanceRepository.findRequestAttendanceOfService(serviceType)?.let{requestAttendance -> + directToAvailableAttendant(botNumber, requestAttendance.pwd, serviceType) + } + } + } + is CommitteeMember -> { + setCommitteeMemberStatus(attendant, newStatus) + if(newStatus == UserStatus.AVAILABLE) { + attendanceRepository.findPendingCommitteeMemberAttendances().firstOrNull()?.let { attendance -> + directToAvailableAttendant(botNumber, attendance.pwd, attendance.serviceType) + } + } + } + } + } + fun createOptionsWithCancel(options: List, header: String = "", author: String = ""): String { + var msg = createOptions(options, header, author) + return "${msg}- Escreva \"cancelar\" para sair do menu" + } + val changeStatusTextOptionsFor = mapOf( + "AVAILABLE" to createOptionsWithCancel( + listOf("Ficar Indisponível"), + "Você está *Disponível* no momento", "BotPCD" + ), + "UNAVAILABLE" to createOptionsWithCancel( + listOf("Ficar Disponível"), + "Você está *Indisponível* no momento", "BotPCD" + ), + "BUSY" to createOptionsWithCancel( + listOf( + "Encerrar atendimento e Ficar Disponível", + "Encerrar atendimento e Ficar Indisponível" + ), + "Você está *em atendimento*", "BotPCD" + ) + ) + + fun sendStatusChanger(attendant: Attendant, botPhoneNumber: String) { + val userPhoneNumber = attendant.phoneNumber + val messageToSend = when (attendant.status) { + UserStatus.AVAILABLE -> changeStatusTextOptionsFor[UserStatus.AVAILABLE.toString()] + UserStatus.UNAVAILABLE -> changeStatusTextOptionsFor[UserStatus.UNAVAILABLE.toString()] + UserStatus.BUSY -> changeStatusTextOptionsFor[UserStatus.BUSY.toString()] + } + whatsappService.sendMessage(botPhoneNumber, userPhoneNumber, messageToSend ?: "") + } + + @Transactional + fun processStatusChangeResponse(botNumber: String, attendant: Attendant, userResponse: String, botPhoneNumber: String) { + var confirmationMessage: String? = null + val userPhoneNumber = attendant.phoneNumber + + when (attendant.status) { + UserStatus.AVAILABLE -> { + when (userResponse.lowercase()) { + "1" -> { + updateAttendantStatus(botNumber, attendant, UserStatus.UNAVAILABLE) + confirmationMessage = "Seu status foi atualizado para Indisponível." + } + + "cancelar" -> { + confirmationMessage = "Você continua Disponível." + } + + else -> { + confirmationMessage = "Opção inválida. Seu status permanece Disponível." + } + } + } + + UserStatus.UNAVAILABLE -> { + when (userResponse.lowercase()) { + "1" -> { + updateAttendantStatus(botNumber, attendant, UserStatus.AVAILABLE) + confirmationMessage = "Seu status foi atualizado para Disponível." + } + + "cancelar" -> { + confirmationMessage = "Você continua Indisponível." + } + + else -> { + confirmationMessage = "Opção inválida. Seu status permanece Indisponível." + } + } + } + + UserStatus.BUSY -> { + val attendance = attendanceRepository.findStartedAttendanceOfAttendant(attendant) + if (attendance != null) { + when (userResponse.lowercase()) { + "1" -> { + updateAttendantStatus(botNumber, attendant, UserStatus.AVAILABLE) + sendFinishedAttendanceMessage(botPhoneNumber, attendance) + confirmationMessage = "Atendimento encerrado. Seu status foi atualizado para Disponível." + finishAttendance(attendance) + } + "2" -> { + updateAttendantStatus(botNumber, attendant, UserStatus.UNAVAILABLE) + sendFinishedAttendanceMessage(botPhoneNumber, attendance) + confirmationMessage = "Atendimento encerrado. Seu status foi atualizado para Indisponível." + finishAttendance(attendance) + } + + "cancelar" -> { + confirmationMessage = "Você continua em atendimento." + } + + else -> { + confirmationMessage = "Opção inválida. Seu status permanece Ocupado." + } + } + } else { + logger.warn("Atendente está ocupado ${attendant.name} sem atendimento") + } + } + } + + confirmationMessage?.let { + whatsappService.sendMessage(botPhoneNumber, userPhoneNumber, it, "BotPCD") + } + } + + private fun sendFinishedAttendanceMessage(botPhoneNumber: String, attendance: Attendance) { + whatsappService.sendMessage( + botPhoneNumber, + attendance.pwd.phoneNumber, + "Atendimento encerrado", + "BotPCD" + ) + } + + fun findAvailableMonitors(): List { + return monitorRepository.findByStatus(UserStatus.AVAILABLE) + } + + fun findAvailableCommitteeMembers(): List { + return committeeMemberRepository.findByStatus(UserStatus.AVAILABLE) + } + + fun findAvailableMonitorsByType(assistanceType: MonitorAssistanceType): List { + val availableMonitors = findAvailableMonitors() + return availableMonitors.filter { it.assistanceType == assistanceType } + } + fun finishAttendance(attendance: Attendance) { + attendance.apply { + endDateTime = LocalDateTime.now() + } + attendanceRepository.save(attendance) + } + val awaitServiceMessage = "No momento não há atendentes disponíveis. Por favor, aguarde na fila de espera e retornaremos assim que possível." + fun sendWaitListMessage(botNumber: String, pwd: PWD) { + whatsappService.sendMessage( + botNumber, + pwd.phoneNumber, + awaitServiceMessage + ) + } val logger: Logger = LoggerFactory.getLogger(AttendanceService::class.java) + fun sendServices(botNumber: String, pwd: PWD) { whatsappService.sendMessage(botNumber, pwd.phoneNumber, createSendServicesMessage(pwd.disabilities.first(), pwd)) } @@ -49,10 +225,10 @@ class AttendanceService( fun makeAttendantBusy(attendant: Attendant) { when (attendant) { is Monitor -> { - attendantStatusService.setMonitorStatus(attendant, UserStatus.BUSY) + setMonitorStatus(attendant, UserStatus.BUSY) } is CommitteeMember -> { - attendantStatusService.setCommitteeMemberStatus(attendant, UserStatus.BUSY) + setCommitteeMemberStatus(attendant, UserStatus.BUSY) } } } @@ -168,12 +344,4 @@ $message """.trimIndent() ) } - -fun sendWaitListMessage(botNumber: String, pwd: PWD) { - whatsappService.sendMessage( - botNumber, - pwd.phoneNumber, - "No momento não há atendentes disponíveis. Por favor, aguarde na fila de espera e retornaremos assim que possível." - ) -} } diff --git a/src/main/kotlin/ufrpe/sbpc/botpcd/service/AttendantFlowService.kt b/src/main/kotlin/ufrpe/sbpc/botpcd/service/AttendantFlowService.kt index 02ef2b0..b21f9cc 100644 --- a/src/main/kotlin/ufrpe/sbpc/botpcd/service/AttendantFlowService.kt +++ b/src/main/kotlin/ufrpe/sbpc/botpcd/service/AttendantFlowService.kt @@ -7,16 +7,15 @@ import ufrpe.sbpc.botpcd.repository.AttendanceRepository @Service class AttendantFlowService( - private val attendantStatusService: AttendantStatusService, private val attendanceRepository: AttendanceRepository, private val attendanceService: AttendanceService ) { val botPcdRegex = Regex("^\\s*bot\\s*pcd\\s*$", RegexOption.IGNORE_CASE) fun redirect(botNumber: String, lastBotMessageText: String?, attendant: Attendant, message: String) { if (botPcdRegex.matches(message)) { - attendantStatusService.sendStatusChanger(attendant, botNumber) - } else if (lastBotMessageText in attendantStatusService.changeStatusTextOptionsFor.values) { - attendantStatusService.processStatusChangeResponse(attendant, message, botNumber) + attendanceService.sendStatusChanger(attendant, botNumber) + } else if (lastBotMessageText in attendanceService.changeStatusTextOptionsFor.values) { + attendanceService.processStatusChangeResponse(botNumber, attendant, message, botNumber) } else { attendanceRepository.findStartedAttendanceOfAttendant(attendant)?.let { attendance -> attendanceService.redirectMessageToPwd(botNumber, message, attendance.pwd, attendant) diff --git a/src/main/kotlin/ufrpe/sbpc/botpcd/service/AttendantStatusService.kt b/src/main/kotlin/ufrpe/sbpc/botpcd/service/AttendantStatusService.kt deleted file mode 100644 index 89fb08d..0000000 --- a/src/main/kotlin/ufrpe/sbpc/botpcd/service/AttendantStatusService.kt +++ /dev/null @@ -1,177 +0,0 @@ -package ufrpe.sbpc.botpcd.service - -import org.slf4j.Logger -import org.slf4j.LoggerFactory -import org.springframework.stereotype.Service -import ufrpe.sbpc.botpcd.entity.* -import ufrpe.sbpc.botpcd.repository.MonitorRepository -import ufrpe.sbpc.botpcd.repository.AttendanceRepository -import ufrpe.sbpc.botpcd.repository.CommitteeMemberRepository -import org.springframework.transaction.annotation.Transactional -import ufrpe.sbpc.botpcd.util.createOptions -import java.time.LocalDateTime - -@Service -class AttendantStatusService( - private val whatsappService: WhatsappService, - private val monitorRepository: MonitorRepository, - private val attendanceRepository: AttendanceRepository, - private val committeeMemberRepository: CommitteeMemberRepository -) { - val logger: Logger = LoggerFactory.getLogger(AttendantStatusService::class.java) - - @Transactional - fun setMonitorStatus(monitor: Monitor, status: UserStatus) { - monitor.status = status - monitorRepository.save(monitor) - } - - @Transactional - fun setCommitteeMemberStatus(member: CommitteeMember, status: UserStatus) { - member.status = status - committeeMemberRepository.save(member) - } - - private fun updateAttendantStatus(attendant: Attendant, newStatus: UserStatus) { - when (attendant) { - is Monitor -> setMonitorStatus(attendant, newStatus) - is CommitteeMember -> setCommitteeMemberStatus(attendant, newStatus) - } - } - fun createOptionsWithCancel(options: List, header: String = "", author: String = ""): String { - var msg = createOptions(options, header, author) - return "${msg}- Escreva \"cancelar\" para sair do menu" - } - val changeStatusTextOptionsFor = mapOf( - "AVAILABLE" to createOptionsWithCancel( - listOf("Ficar Indisponível"), - "Você está *Disponível* no momento", "BotPCD" - ), - "UNAVAILABLE" to createOptionsWithCancel( - listOf("Ficar Disponível"), - "Você está *Indisponível* no momento", "BotPCD" - ), - "BUSY" to createOptionsWithCancel( - listOf( - "Encerrar atendimento e Ficar Disponível", - "Encerrar atendimento e Ficar Indisponível" - ), - "Você está *em atendimento*", "BotPCD" - ) - ) - - fun sendStatusChanger(attendant: Attendant, botPhoneNumber: String) { - val userPhoneNumber = attendant.phoneNumber - val messageToSend = when (attendant.status) { - UserStatus.AVAILABLE -> changeStatusTextOptionsFor[UserStatus.AVAILABLE.toString()] - UserStatus.UNAVAILABLE -> changeStatusTextOptionsFor[UserStatus.UNAVAILABLE.toString()] - UserStatus.BUSY -> changeStatusTextOptionsFor[UserStatus.BUSY.toString()] - } - whatsappService.sendMessage(botPhoneNumber, userPhoneNumber, messageToSend ?: "") - } - - @Transactional - fun processStatusChangeResponse(attendant: Attendant, userResponse: String, botPhoneNumber: String) { - var confirmationMessage: String? = null - val userPhoneNumber = attendant.phoneNumber - - when (attendant.status) { - UserStatus.AVAILABLE -> { - when (userResponse.lowercase()) { - "1" -> { - updateAttendantStatus(attendant, UserStatus.UNAVAILABLE) - confirmationMessage = "Seu status foi atualizado para Indisponível." - } - - "cancelar" -> { - confirmationMessage = "Você continua Disponível." - } - - else -> { - confirmationMessage = "Opção inválida. Seu status permanece Disponível." - } - } - } - - UserStatus.UNAVAILABLE -> { - when (userResponse.lowercase()) { - "1" -> { - updateAttendantStatus(attendant, UserStatus.AVAILABLE) - confirmationMessage = "Seu status foi atualizado para Disponível." - } - - "cancelar" -> { - confirmationMessage = "Você continua Indisponível." - } - - else -> { - confirmationMessage = "Opção inválida. Seu status permanece Indisponível." - } - } - } - - UserStatus.BUSY -> { - val attendance = attendanceRepository.findStartedAttendanceOfAttendant(attendant) - if (attendance != null) { - when (userResponse.lowercase()) { - "1" -> { - updateAttendantStatus(attendant, UserStatus.AVAILABLE) - whatsappService.sendMessage( - botPhoneNumber, - attendance.pwd.phoneNumber, - "Atendimento encerrado", - "BotPCD" - ) - confirmationMessage = "Atendimento encerrado. Seu status foi atualizado para Disponível." - finishAttendance(attendance) - } - "2" -> { - updateAttendantStatus(attendant, UserStatus.UNAVAILABLE) - whatsappService.sendMessage( - botPhoneNumber, - attendance.pwd.phoneNumber, - "Atendimento encerrado", - "BotPCD" - ) - confirmationMessage = "Atendimento encerrado. Seu status foi atualizado para Indisponível." - finishAttendance(attendance) - } - - "cancelar" -> { - confirmationMessage = "Você continua em atendimento." - } - - else -> { - confirmationMessage = "Opção inválida. Seu status permanece Ocupado." - } - } - } else { - logger.warn("Atendente está ocupado ${attendant.name} sem atendimento") - } - } - } - - confirmationMessage?.let { - whatsappService.sendMessage(botPhoneNumber, userPhoneNumber, it, "BotPCD") - } - } - - fun findAvailableMonitors(): List { - return monitorRepository.findByStatus(UserStatus.AVAILABLE) - } - - fun findAvailableCommitteeMembers(): List { - return committeeMemberRepository.findByStatus(UserStatus.AVAILABLE) - } - - fun findAvailableMonitorsByType(assistanceType: MonitorAssistanceType): List { - val availableMonitors = findAvailableMonitors() - return availableMonitors.filter { it.assistanceType == assistanceType } - } - fun finishAttendance(attendance: Attendance) { - attendance.apply { - endDateTime = LocalDateTime.now() - } - attendanceRepository.save(attendance) - } -} diff --git a/src/main/kotlin/ufrpe/sbpc/botpcd/service/FirstContactService.kt b/src/main/kotlin/ufrpe/sbpc/botpcd/service/FirstContactService.kt index 3fd8080..c5334bb 100644 --- a/src/main/kotlin/ufrpe/sbpc/botpcd/service/FirstContactService.kt +++ b/src/main/kotlin/ufrpe/sbpc/botpcd/service/FirstContactService.kt @@ -27,7 +27,6 @@ class FirstContactService( private val whatsappService: WhatsappService, private val attendanceService: AttendanceService, private val messageExchangeRepository: MessageExchangeRepository, - private val attendantStatusService: AttendantStatusService, private val attendanceRepository: AttendanceRepository, private val monitorRepository: MonitorRepository, private val committeeMemberRepository: CommitteeMemberRepository, diff --git a/src/main/kotlin/ufrpe/sbpc/botpcd/service/PWDFlowService.kt b/src/main/kotlin/ufrpe/sbpc/botpcd/service/PWDFlowService.kt index d0c298a..b125744 100644 --- a/src/main/kotlin/ufrpe/sbpc/botpcd/service/PWDFlowService.kt +++ b/src/main/kotlin/ufrpe/sbpc/botpcd/service/PWDFlowService.kt @@ -27,20 +27,16 @@ class PWDFlowService( ) { val disability = pwd.disabilities.first() val attendance = attendanceRepository.findStartedAttendanceOfPwd(pwd) + val requestAttendancePWD = attendanceRepository.findRequestAttendanceOfPwd(pwd) when { + requestAttendancePWD != null -> { + attendanceService.sendWaitListMessage(botNumber, pwd) + } isRegisteringName(lastBotMessage, pwd) -> { registerPWDService.registerName(pwd, message) whatsappService.sendMessage(botNumber, phoneNumber, "Cadastro realizado.") attendanceService.sendServices(botNumber, pwd) } - isChoosingService(lastBotMessage, pwd) -> { - if (isValidService(message, disability)) { - val service = ServiceType.getServicesByDisability(disability)[message.toInt() - 1] - attendanceService.startAttendance(pwd = pwd, botNumber = botNumber, service = service) - } else { - attendanceService.sendServices(botNumber, pwd) - } - } attendance != null -> { if (attendance.attendant == null) { logger?.error("Atendimento com id ${attendance.id} foi iniciado com atendente Nulo!") @@ -53,6 +49,14 @@ class PWDFlowService( ) } } + isChoosingService(lastBotMessage, pwd) -> { + if (isValidService(message, disability)) { + val service = ServiceType.getServicesByDisability(disability)[message.toInt() - 1] + attendanceService.startAttendance(pwd = pwd, botNumber = botNumber, service = service) + } else { + attendanceService.sendServices(botNumber, pwd) + } + } else -> { attendanceService.sendServices(botNumber = botNumber, pwd = pwd) } diff --git a/src/main/kotlin/ufrpe/sbpc/botpcd/service/QueueService.kt b/src/main/kotlin/ufrpe/sbpc/botpcd/service/QueueService.kt index abee750..0552fa7 100644 --- a/src/main/kotlin/ufrpe/sbpc/botpcd/service/QueueService.kt +++ b/src/main/kotlin/ufrpe/sbpc/botpcd/service/QueueService.kt @@ -1,5 +1,6 @@ package ufrpe.sbpc.botpcd.service +import jakarta.transaction.Transactional import java.time.LocalDateTime import ufrpe.sbpc.botpcd.entity.PWD import ufrpe.sbpc.botpcd.entity.Attendance @@ -18,7 +19,7 @@ class QueueService(private val attendanceRepository: AttendanceRepository){ fun len(service: ServiceType): Long { return attendanceRepository.countRequestAttendanceOfService(service) } - + @Transactional fun pop(service: ServiceType): Attendance? { val firstAtt = attendanceRepository.findRequestAttendanceOfService(service) diff --git a/src/main/kotlin/ufrpe/sbpc/botpcd/service/WhatsappService.kt b/src/main/kotlin/ufrpe/sbpc/botpcd/service/WhatsappService.kt index ae299bf..aa9123b 100644 --- a/src/main/kotlin/ufrpe/sbpc/botpcd/service/WhatsappService.kt +++ b/src/main/kotlin/ufrpe/sbpc/botpcd/service/WhatsappService.kt @@ -1,11 +1,14 @@ package ufrpe.sbpc.botpcd.service +import java.time.LocalDateTime +import org.springframework.stereotype.Service import com.whatsapp.api.domain.messages.Message +import ufrpe.sbpc.botpcd.entity.MessageExchange import com.whatsapp.api.domain.messages.TextMessage import com.whatsapp.api.impl.WhatsappBusinessCloudApi -import org.springframework.stereotype.Service -import ufrpe.sbpc.botpcd.entity.MessageExchange +import org.slf4j.LoggerFactory import ufrpe.sbpc.botpcd.repository.MessageExchangeRepository +import kotlin.math.log @Service @@ -13,17 +16,30 @@ class WhatsappService( private val cloudApi: WhatsappBusinessCloudApi, private val messageExchangeRepository: MessageExchangeRepository ) { - fun sendMessage(botNumber: String, destinyNumberID: String, msg: String, author: String = "") { - val msg = if (author != "") "*${author}:*\n ${msg}" else msg + private val logger = LoggerFactory.getLogger(WhatsappService::class.java) + fun sendMessage(botNumber: String, destinyNumberID: String, msg: String, author: String = "") { + val text = if (author != "") "*${author}:*\n ${msg}" else msg val message = Message.MessageBuilder.builder() .setTo(destinyNumberID) - .buildTextMessage(TextMessage().setBody(msg)) - cloudApi.sendMessage(botNumber, message) + .buildTextMessage(TextMessage().setBody(text)) - messageExchangeRepository.save(MessageExchange(fromPhoneNumber = botNumber, toPhoneNumber = destinyNumberID, message = msg)) - } + val lastMessageTime = messageExchangeRepository.lastExchangeMessage( + fromPhoneNumber = destinyNumberID, + toPhoneNumber = botNumber + )?.createAt ?: LocalDateTime.now().minusHours(25) + if (LocalDateTime.now().minusHours(24) < lastMessageTime) { + cloudApi.sendMessage(botNumber, message) + + messageExchangeRepository.save(MessageExchange( + fromPhoneNumber = botNumber, + toPhoneNumber = destinyNumberID, + message = text)) + } else { + logger.warn("Teve uma tentativa de enviar a mensagem ${message} para o número ${destinyNumberID}, mas ele não tinha mandando mensagem.") + } + } } diff --git a/src/test/kotlin/ufrpe/sbpc/botpcd/StepDefinitions.kt b/src/test/kotlin/ufrpe/sbpc/botpcd/StepDefinitions.kt index 42e8ef6..90d91c0 100644 --- a/src/test/kotlin/ufrpe/sbpc/botpcd/StepDefinitions.kt +++ b/src/test/kotlin/ufrpe/sbpc/botpcd/StepDefinitions.kt @@ -2,19 +2,18 @@ package ufrpe.sbpc.botpcd import com.whatsapp.api.domain.webhook.Value import com.whatsapp.api.domain.webhook.WebHook +import io.cucumber.java.After import io.cucumber.java.pt.Dado import io.cucumber.java.pt.Entao import io.cucumber.java.pt.Quando -import org.springframework.transaction.annotation.Transactional import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertNull import org.slf4j.Logger import org.slf4j.LoggerFactory -import org.springframework.context.annotation.Scope -import org.springframework.stereotype.Component import org.springframework.test.web.servlet.MockMvc import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status +import org.springframework.transaction.annotation.Transactional import ufrpe.sbpc.botpcd.entity.Attendance import ufrpe.sbpc.botpcd.entity.Attendant import ufrpe.sbpc.botpcd.entity.CommitteeMember @@ -22,38 +21,278 @@ import ufrpe.sbpc.botpcd.entity.Disability import ufrpe.sbpc.botpcd.entity.MessageExchange import ufrpe.sbpc.botpcd.entity.Monitor import ufrpe.sbpc.botpcd.entity.MonitorAssistanceType +import ufrpe.sbpc.botpcd.entity.MonitorServiceType import ufrpe.sbpc.botpcd.entity.PWD -import ufrpe.sbpc.botpcd.entity.Provider import ufrpe.sbpc.botpcd.entity.ServiceType import ufrpe.sbpc.botpcd.entity.UserStatus +import ufrpe.sbpc.botpcd.entity.Provider import ufrpe.sbpc.botpcd.repository.AttendanceRepository import ufrpe.sbpc.botpcd.repository.AttendantRepository import ufrpe.sbpc.botpcd.repository.CommitteeMemberRepository import ufrpe.sbpc.botpcd.repository.MessageExchangeRepository import ufrpe.sbpc.botpcd.repository.MonitorRepository import ufrpe.sbpc.botpcd.repository.PWDRepository -import ufrpe.sbpc.botpcd.service.AttendantStatusService +import ufrpe.sbpc.botpcd.service.AttendanceService import java.io.File import java.time.LocalDateTime +import kotlin.assert +import kotlin.test.assertFalse // import java.math.BigInteger // Não é mais necessário para os steps de atendente import kotlin.test.assertTrue +import kotlin.text.contains +@Transactional class StepDefinitions( private val mockMvc: MockMvc, val pwdRepository: PWDRepository, - val messageExchangeRepository: MessageExchangeRepository, val monitorRepository: MonitorRepository, - val committeeMemberRepository: CommitteeMemberRepository, + val attendanceService: AttendanceService, val attendantRepository: AttendantRepository, - val attendantStatusService: AttendantStatusService, - val attendanceRepository: AttendanceRepository + val attendanceRepository: AttendanceRepository, + val messageExchangeRepository: MessageExchangeRepository, + val committeeMemberRepository: CommitteeMemberRepository, ) { private var currentBotNumber: String = "15556557522" private val numberUserNotRegister: String = "558187654321" private val currentTestAttendantPhoneNumber: String = "5581999999901" // Número fixo para o atendente em teste private val pwdInAttendancePhoneNumberForAttendantChangeStatus = "558179654321" + private val pwdAcessarServicesPhoneNumber = "551965741234" + private val possibleItialsMessages = mutableListOf("Oi", "Olá", "Bom dia", "Boa noite", "Boa Tarde", "teste") + val logger: Logger = LoggerFactory.getLogger(StepDefinitions::class.java) + @Dado("que o atendente {string} de telefone {string} enviou mensagem nas últimas 24 horas") + fun `atendente enviou mensagem recente`(nomeAtendente: String, telefoneAtendente: String) { + monitorRepository.save( + Monitor( + name = nomeAtendente, + phoneNumber = telefoneAtendente, + status = UserStatus.AVAILABLE, + assistanceType = MonitorAssistanceType.NEURODIVERGENT_SUPPORT_MONITOR + ) + ) + userSendMessage("Olá, estou ativo.", telefoneAtendente) + } + + @Dado("que o PCD {string} de telefone {string} enviou mensagem nas últimas 24 horas") + fun `pcd enviou mensagem recente`(nomePcd: String, telefonePcd: String) { + pwdRepository.save( + PWD( + name = nomePcd, + phoneNumber = telefonePcd, + disabilities = setOf(Disability.NEURODIVERGENT) + ) + ) + userSendMessage("Olá, estou ativo.", telefonePcd) + } + + @Dado( + "que o atendente {string} de telefone {string} está em atendimento com o PCD {string} de telefone {string}" + ) + fun atendenteEmAtendimentoComPCD( + nomeAtendente: String, + telefoneAtendente: String, + nomePcd: String, + telefonePcd: String + ) { + val pcd = pwdRepository.findByPhoneNumber(telefonePcd)!! + val monitor = monitorRepository.findByPhoneNumber(telefoneAtendente) + userSendMessage("oi", telefonePcd) + userSendMessage("1", telefonePcd) + } + + @Dado( + "que o PCD {string} de telefone {string} não está em atendimento com {string}" + ) + fun pcdNaoEstaEmAtendimentoCom(nomePcd: String, telefonePcd: String, nomeAtendente: String) { + val pcd = pwdRepository.findByPhoneNumber(telefonePcd)!! + attendanceRepository.deleteAllByPwd(pcd) + } + + @Quando("o atendente {string} de telefone {string} envia a mensagem {string}") + fun atendenteEnviaMensagem(nomeAtendente: String, telefoneAtendente: String, mensagem: String) { + userSendMessage(mensagem, telefoneAtendente) + } + + @Entao("o PCD {string} de telefone {string} deve receber a mensagem {string} do atendente {string}") + fun pcdDeveReceberMensagemDoAtendente( + nomePcd: String, + telefonePcd: String, + mensagem: String, + nomeAtendente: String + ) { + val telefoneAtendente = monitorRepository.findByName(nomeAtendente)!!.phoneNumber + val ultimaMensagem = messageExchangeRepository.lastExchangeMessage( + toPhoneNumber = telefonePcd, + fromPhoneNumber = currentBotNumber + ) + assert(ultimaMensagem != null && ultimaMensagem!!.message.contains(mensagem)) { + "O PCD $nomePcd não recebeu a mensagem esperada \"$mensagem\" do atendente $nomeAtendente." + } + } + + @Entao("o PCD {string} de telefone {string} não deve receber nenhuma nova mensagem de {string}") + fun pcdNaoDeveReceberMensagem(nomePcd: String, telefonePcd: String, nomeAtendete: String) { + val attendant = attendantRepository.findByName(nomeAtendete) + val lastMessage = messageExchangeRepository.lastExchangeMessage( + toPhoneNumber = telefonePcd, + fromPhoneNumber = currentBotNumber// Ou ajuste conforme sua lógica para buscar todas + )!!.message + + assertFalse(lastMessage!!.contains(attendant?.name!!)) + } + + @Quando("o atendente {string} de telefone {string} encerra o atendimento") + fun atendenteEncerraAtendimento(nomeAtendente: String, telefoneAtendente: String) { + userSendMessage("botpcd", telefoneAtendente) + userSendMessage("1", telefoneAtendente) + } + + @Entao("o PCD {string} de telefone {string} deve receber a mensagem {string} do bot") + fun pcdDeveReceberMensagem(nomePcd: String, telefonePcd: String, mensagem: String) { + val ultimaMensagem = messageExchangeRepository.lastExchangeMessage( + toPhoneNumber = telefonePcd, + fromPhoneNumber = currentBotNumber + )?.message + + assert(ultimaMensagem != null && ultimaMensagem.contains(mensagem)) { + "O PCD $nomePcd não recebeu a mensagem esperada \"$mensagem\"." + } + } + + @Dado( + "que o atendente {string} de telefone {string} está disponível, mas não em atendimento" + ) + fun atendenteDisponivelNaoEmAtendimento(nomeAtendente: String, telefoneAtendente: String) { + val monitor = monitorRepository.findByPhoneNumber(telefoneAtendente)!! + monitorRepository.save(monitor.apply { status = UserStatus.AVAILABLE }) + attendanceRepository.deleteAllByAttendant(monitor) + } + + @Quando("o PCD {string} de telefone {string} envia a mensagem {string}") + fun pcdEnviaMensagem(nomePcd: String, telefonePcd: String, mensagem: String) { + // O nome do PCD é usado apenas para clareza no teste. + userSendMessage(mensagem, telefonePcd) + } + + @Entao("o atendente {string} de telefone {string} deve receber a mensagem {string} do PCD {string}") + fun atendenteDeveReceberMensagem( + nomeAtendente: String, + telefoneAtendente: String, + mensagem: String, + nomePcd: String + ) { + // O nome do atendente é para clareza no teste. + val mensagemEsperada = """ + *${nomePcd}*: + $mensagem + """.trimIndent() + + testarUltimaMensagemRecebidaDoUsuario(mensagemEsperada, telefoneAtendente) + } + + @Entao("o atendente {string} de telefone {string} não deve receber nenhuma nova mensagem do pcd {string}") + fun atendenteNaoDeveReceberMensagem(nomeAtendente: String, telefoneAtendente: String, nomePCD: String) { + val lastMensage = messageExchangeRepository.lastExchangeMessage( + toPhoneNumber = telefoneAtendente, + fromPhoneNumber = currentBotNumber + )?.message + if(lastMensage != null) { + assertFalse(lastMensage.contains(nomePCD)) + } + } + + @Dado("que o atendente {string} de {string} do tipo {string} que presta serviço {string} estava indisponível") + fun atendenteIndisponivel(nome: String, numero: String, tipoDeAtendente: String, tipoServico: String) { + val service = ServiceType.getByDescription(tipoServico) + val provider = when (tipoDeAtendente.lowercase()) { + "monitor" -> Provider.MONITOR + "membro da comissão" -> Provider.COMMITTEE_MEMBER + else -> throw IllegalArgumentException("Tipo de atendente desconhecido: $tipoDeAtendente") + } + if (provider == Provider.MONITOR) { + val monitor = monitorRepository.save( + Monitor( + name = nome, + phoneNumber = numero, + status = UserStatus.UNAVAILABLE, + assistanceType = (service as MonitorServiceType).monitorAssistanceType // ou ajuste se quiser + ) + ) + } else if (provider == Provider.COMMITTEE_MEMBER) { + val member = attendantRepository.findByPhoneNumber(numero) + ?: attendantRepository.save( + CommitteeMember( + name = nome, + phoneNumber = numero, + status = UserStatus.UNAVAILABLE + ) + ) + member.status = UserStatus.UNAVAILABLE + attendantRepository.save(member) + } + } + + @Quando("o atendente {string} de {string} fica disponível") + fun atendenteFicaDisponivel(nome: String, numero: String) { + userSendMessage(userPhoneNumber = numero, mensagemEnviada = "Bot PCD") + userSendMessage(userPhoneNumber = numero, mensagemEnviada = "1") + } + + @Dado("que {string} PCD de número {string} solicitou o serviço {string} e está na fila de espera") + fun pcdSolicitouServicoFila(adjetivoDeficiencia: String, numeroPcd: String, servicoDescricao: String) { + val service = ServiceType.getByDescription(servicoDescricao) + val disability = Disability.getByAdjective(adjetivoDeficiencia) + + // Cria ou reutiliza o PCD com o número informado + val pcd = pwdRepository.save( + PWD( + name = "PCD $adjetivoDeficiencia", + phoneNumber = numeroPcd, + disabilities = setOf(disability) + ) + ) + userSendMessage(mensagemEnviada = "Oi", numeroPcd) + userSendMessage((ServiceType.getServicesByDisability(disability).indexOfFirst { it.description == servicoDescricao } + 1).toString(), numeroPcd) + } + + @Dado("PCD possuia serviço requisitado que ainda não foi iniciado") + fun pcdPossuiServicoRequisitadoNaoIniciado() { + val service = ServiceType.NeurodivergentSupport + val disabilities = service.disability + + // Reutiliza ou cria o PCD com deficiências compatíveis com o serviço + val pcd = pwdRepository.findByPhoneNumber(pwdAcessarServicesPhoneNumber) ?: pwdRepository.save( + PWD( + name = "PCD Teste", + phoneNumber = pwdAcessarServicesPhoneNumber, + disabilities = disabilities + ) + ) + + // Evita criar duplicata se já existir + val existente = attendanceRepository.findRequestAttendanceOfPwd(pcd) + if (existente == null) { + attendanceRepository.save( + Attendance( + pwd = pcd, + serviceType = service, + attendantType = service.attendantType, + attendant = null, + endDateTime = null, + startDateTime = null, + serviceLocation = null, + monitorArrivalDateTime = null + ) + ) + } + } + + @Dado("que nenhum atendente para o {string} está disponível") + fun nenhumAtendenteDisponivel(servicoDescricao: String) { + // repositorio comeca vazio, nao precisa fazer nada + } + @Dado("usuário recebeu mensagem {string}") fun `usuário recebeu mensagem`(message: String) { mockUserRecievedMessage(numberUserNotRegister, message) @@ -70,6 +309,124 @@ class StepDefinitions( // Ex: pwdRepository.save(PWD(name = "PCD Teste Completo", phoneNumber = numberUserNotRegister, disabilities = mutableSetOf(Disability.DEAFNESS))) } + @Dado("que não tem atendente disponível") + fun `que não tem atendente disponível`() { + + } + + @Dado("que {string} PCD recebeu mensagem de opções de serviço") + fun `pcd recebue mensagem de opção de serviço`(adjetivoPCD: String) { + // ver se existe ou crirar um pcd com adjetivoPCD + val pwd = + createPWDIfNotExists(phoneNumber = pwdAcessarServicesPhoneNumber, Disability.getByAdjective(adjetivoPCD)) + val servicesText = attendanceService.createSendServicesMessage(pwd.disabilities.first(), pwd) + messageExchangeRepository.save( + MessageExchange( + fromPhoneNumber = currentBotNumber, + toPhoneNumber = pwdAcessarServicesPhoneNumber, + message = servicesText + ) + ) + // pegar no proprio código qual seria a mensagem de serviço + } + + @After + fun cleanup() { + // Esta linha deleta os dados em uma ordem que evita erros de chave estrangeira + listOf( + attendanceRepository, + messageExchangeRepository, + monitorRepository, + committeeMemberRepository, + pwdRepository, + attendantRepository + ).forEach { it.deleteAllInBatch() } + } + + @Dado("atendente que se chama {string} enviou uma mensagem nas ultimas 24 horas para o bot") + fun `atendente enviou uma mensagem nas ultimas 24 horas`(nomeAtendente: String) { + messageExchangeRepository.save( + MessageExchange( + fromPhoneNumber = currentTestAttendantPhoneNumber, + toPhoneNumber = currentBotNumber, + message = "teste " + ) + ) + } + + /** + * Cria um atendente (Monitor ou Membro da Comissão) com o nome especificado, + * define seu status como DISPONÍVEL e garante que ele seja elegível para + * o serviço desejado. Também simula uma mensagem recente do atendente para + * que o sistema o considere ativo. + */ + @Dado("atendente que se chama {string} está disponível para o {string}") + fun `atendente está disponível`(nomeAtendente: String, servicoDesejado: String) { + // Gera um número de telefone único para o atendente para evitar conflitos + attendantRepository.findByPhoneNumber(currentTestAttendantPhoneNumber)?.let { attendantRepository.delete(it) } + val serviceType = ServiceType.getByDescription(servicoDesejado) + val tipoAtendente = + if (nomeAtendente.contains("Monitor", ignoreCase = true)) "monitor" else "membro da comissão" + val attendant: Attendant = when (tipoAtendente) { + "monitor" -> { + val monitorService = serviceType as? MonitorServiceType + ?: throw IllegalArgumentException("Serviço '$servicoDesejado' não é compatível com Monitor") + monitorRepository.save( + Monitor( + name = nomeAtendente, + phoneNumber = currentTestAttendantPhoneNumber, + status = UserStatus.AVAILABLE, + assistanceType = monitorService.monitorAssistanceType + ) + ) + } + + "membro da comissão" -> { + committeeMemberRepository.save( + CommitteeMember( + name = nomeAtendente, + phoneNumber = currentTestAttendantPhoneNumber, + status = UserStatus.AVAILABLE + ) + ) + } + + else -> throw IllegalArgumentException("Tipo de atendente desconhecido: $tipoAtendente") + } + // Simula que o atendente enviou uma mensagem recentemente para ser considerado ativo + mockUserRecievedMessage(attendant.phoneNumber, "Estou online") + } + + /** + * Simula o envio de uma mensagem pelo PCD para selecionar um serviço. + * O número do serviço corresponde à opção na lista de serviços recebida. + */ + @Quando("{string} PCD envia a mensagem {string}") + fun `pcd envia mensagem de serviço`(adjetivoPCD: String, numeroServico: String) { + // O adjetivoPCD é usado para contexto, o número de telefone identifica o usuário + val pwd = + createPWDIfNotExists(phoneNumber = pwdAcessarServicesPhoneNumber, Disability.getByAdjective(adjetivoPCD)) + userSendMessage(numeroServico, pwd.phoneNumber) + } + + /** + * Verifica se o PCD recebeu a mensagem correta informando qual atendente + * irá realizar o seu atendimento. + */ + @Entao("{string} PCD receberá mensagem {string}") + fun `pcd recebe mensagem de direcionamento`(adjetivoPCD: String, mensagemEsperada: String) { + // O adjetivoPCD é usado para contexto, a verificação é feita pelo número de telefone + testarUltimaMensagemRecebidaDoUsuario(mensagemEsperada, pwdAcessarServicesPhoneNumber) + + } + @Entao("{string} PCD de número {string} receberá mensagem {string}") + fun `pcd com adjetivo e numero recebe mensagem`( + adjetivoDeficiencia: String, + numeroPcd: String, + mensagemEsperada: String + ) { + testarUltimaMensagemRecebidaDoUsuario(mensagemEsperada, numeroPcd) + } @Dado("usuário não cadastrado") fun `usuário não cadastrado`() { pwdRepository.findByPhoneNumber(numberUserNotRegister)?.let { pwdRepository.delete(it) } @@ -92,6 +449,32 @@ class StepDefinitions( userSendMessage(mensagemEnviada, numberUserNotRegister) } + @Quando("PCD {string} mandar qualquer mensagem") + fun `PCD mandar qualquer mensagem`(adjetivoPCD: String) { + val message = possibleItialsMessages.random() + val pwd = createIfNotExistsPWDWithDisability(adjetivoPCD, pwdAcessarServicesPhoneNumber) + userSendMessage(message, pwd.phoneNumber) + } + + fun createIfNotExistsPWDWithDisability(adjetivoPCD: String, pwdPhoneNumber: String): PWD { + var pwd = pwdRepository.findByPhoneNumber(pwdPhoneNumber) + val disability = Disability.getByAdjective(adjetivoPCD) + if (pwd == null) { + return pwdRepository.save( + PWD( + name = adjetivoPCD, + phoneNumber = pwdPhoneNumber, + disabilities = mutableSetOf(disability) + ) + ) + } + if (pwd.disabilities.first() != disability) { + pwd.disabilities = mutableSetOf(disability) + pwdRepository.save(pwd) + } + return pwd + } + @Entao("bot registrará que usuário tem ou possui {string}") fun `bot registra deficiencia do usuário`(deficienciaAdjtivo: String) { val pwd = pwdRepository.findByPhoneNumberWithDisabilities(numberUserNotRegister)!! @@ -124,10 +507,10 @@ class StepDefinitions( ) } - @Entao("bot enviará opcções de serviço {string} de acordo com a deficiência {string} do pcd") + @Entao("{string} PCD receberá mensagem de opções de serviço {string}") fun `bot enviará opcçoes de seviço de acordo com a deficiência do pcd`( - opcoesDeServico: String, - tipoDeDeficiencia: String + tipoDeDeficiencia: String, + opcoesDeServico: String ) { val ultimaMensagem = messageExchangeRepository.lastExchangeMessage( toPhoneNumber = numberUserNotRegister, @@ -159,6 +542,7 @@ class StepDefinitions( ) monitorRepository.save(monitor) } + "membro da comissão" -> { val committeeMember = CommitteeMember( name = "Comissão Teste $phoneNumber", @@ -167,9 +551,22 @@ class StepDefinitions( ) committeeMemberRepository.save(committeeMember) } + else -> throw IllegalArgumentException("Tipo de atendente desconhecido: $tipoAtendente") } } + + @Quando("PCD mandar qualquer mensagem") + fun `PCD mandar qualquer mensagem`() { + val message = possibleItialsMessages.random() + userSendMessage(message, pwdAcessarServicesPhoneNumber) + } + + @Entao("PCD receberá mensagem {string}") + fun `PCD recebera mensagem`(mensagemEsperada: String) { + testarUltimaMensagemRecebidaDoUsuario(mensagemEsperada, pwdAcessarServicesPhoneNumber) + } + @Dado("que sou um {string} com status inicial {string}") fun `que sou um tipo_de_atendente com status inicial`( tipoAtendente: String, @@ -179,11 +576,14 @@ class StepDefinitions( val attendant = createAttendant(currentTestAttendantPhoneNumber, tipoAtendente, status) if (status == UserStatus.BUSY) { // Limpar PWD existente com este número para evitar conflitos - val pwd = pwdRepository.findByPhoneNumber(pwdInAttendancePhoneNumberForAttendantChangeStatus) ?: pwdRepository.save(PWD( - name = "PCD em Atendimento Teste", - phoneNumber = pwdInAttendancePhoneNumberForAttendantChangeStatus, - disabilities = mutableSetOf(Disability.MOBILITY_IMPAIRED) // Deficiência padrão para o teste - )) + val pwd = pwdRepository.findByPhoneNumber(pwdInAttendancePhoneNumberForAttendantChangeStatus) + ?: pwdRepository.save( + PWD( + name = "PCD em Atendimento Teste", + phoneNumber = pwdInAttendancePhoneNumberForAttendantChangeStatus, + disabilities = mutableSetOf(Disability.MOBILITY_IMPAIRED) // Deficiência padrão para o teste + ) + ) // Determinar o tipo de serviço e provedor com base no tipo de atendente val serviceType: ServiceType @@ -208,6 +608,7 @@ class StepDefinitions( attendanceRepository.save(attendance) } } + @Quando("o {string} envia a mensagem {string}") fun `o tipo_de_atendente envia a mensagem`( tipoAtendente: String, @@ -233,9 +634,11 @@ class StepDefinitions( "Monitor" -> { assertTrue(attendant is Monitor, "Atendente $phoneNumber não é um Monitor.") } + "Membro da Comissão" -> { assertTrue(attendant is CommitteeMember, "Atendente $phoneNumber não é um Membro da Comissão.") } + else -> { throw Exception("Foi passado um tipo inválido nos testes: $tipoAtendente") } @@ -248,8 +651,8 @@ class StepDefinitions( statusAntigoStr: String ) { val statusAntigo = UserStatus.valueOf(statusAntigoStr.uppercase()) - val expectedMessageContent = attendantStatusService.changeStatusTextOptionsFor[statusAntigo.name] - ?: throw IllegalArgumentException("Mensagem de opção não encontrada para status $statusAntigoStr no AttendantStatusService.changeStatusTextOptionsFor. Chaves disponíveis: ${attendantStatusService.changeStatusTextOptionsFor.keys}") + val expectedMessageContent = attendanceService.changeStatusTextOptionsFor[statusAntigo.name] + ?: throw IllegalArgumentException("Mensagem de opção não encontrada para status $statusAntigoStr no attendanceService.changeStatusTextOptionsFor. Chaves disponíveis: ${attendanceService.changeStatusTextOptionsFor.keys}") mockUserRecievedMessage(currentTestAttendantPhoneNumber, expectedMessageContent) } @@ -262,8 +665,24 @@ class StepDefinitions( val statusNovoEsperado = UserStatus.valueOf(statusNovoEsperadoStr.uppercase()) checkIfHasCorrectAttendantType(currentTestAttendantPhoneNumber, tipoAtendente) val attendant = attendantRepository.findByPhoneNumber(currentTestAttendantPhoneNumber) - assertTrue(attendant != null, "Atendente com número $currentTestAttendantPhoneNumber não encontrado no banco de dados.") - assertEquals(statusNovoEsperado, attendant!!.status, "Status do atendente não foi atualizado corretamente para $statusNovoEsperado. Estava ${attendant.status}.") + assertTrue( + attendant != null, + "Atendente com número $currentTestAttendantPhoneNumber não encontrado no banco de dados." + ) + assertEquals( + statusNovoEsperado, + attendant!!.status, + "Status do atendente não foi atualizado corretamente para $statusNovoEsperado. Estava ${attendant.status}." + ) + } + + fun createPWDIfNotExists(phoneNumber: String, disability: Disability): PWD { + var pwd = pwdRepository.findByPhoneNumber(phoneNumber) + if (pwd == null) { + return pwdRepository.save(PWD(phoneNumber = phoneNumber, disabilities = mutableSetOf(disability))) + } else { + return pwd + } } fun mockUserRecievedMessage(userNumber: String, message: String) { @@ -281,14 +700,24 @@ class StepDefinitions( toPhoneNumber = userPhoneNumber, fromPhoneNumber = currentBotNumber )?.message - assertEquals(mensagemEsperada.trimIndent(), actualMessage?.trimIndent(), "A última mensagem recebida pelo usuário não foi a esperada.") + assertTrue( + actualMessage?.trimIndent()!!.contains(mensagemEsperada.trimIndent()), + "A última mensagem recebida pelo usuário não foi a esperada." + ) } fun testarPenultimaMensagemRecebidaDoUsuario(mensagemEsperada: String, userPhoneNumber: String) { val messageList = - messageExchangeRepository.listExchangeMessage(toPhoneNumber = userPhoneNumber, fromPhoneNumber = currentBotNumber) + messageExchangeRepository.listExchangeMessage( + toPhoneNumber = userPhoneNumber, + fromPhoneNumber = currentBotNumber + ) assertTrue(messageList.size >= 2, "Não há mensagens suficientes para verificar a penúltima.") - assertEquals(mensagemEsperada.trimIndent(), messageList[messageList.lastIndex - 1].message.trimIndent(), "A penúltima mensagem recebida pelo usuário não foi a esperada.") + assertEquals( + mensagemEsperada.trimIndent(), + messageList[messageList.lastIndex - 1].message.trimIndent(), + "A penúltima mensagem recebida pelo usuário não foi a esperada." + ) } private fun userSendMessage(mensagemEnviada: String, userPhoneNumber: String) { @@ -344,4 +773,4 @@ fun String.getBotNumber(): String { fun String.getMessageBody(): String { return this.getChange().messages[0].text.body -} \ No newline at end of file +} diff --git a/src/test/kotlin/ufrpe/sbpc/botpcd/WhatsappServiceTest.kt b/src/test/kotlin/ufrpe/sbpc/botpcd/WhatsappServiceTest.kt new file mode 100644 index 0000000..88c7c1c --- /dev/null +++ b/src/test/kotlin/ufrpe/sbpc/botpcd/WhatsappServiceTest.kt @@ -0,0 +1,86 @@ +package ufrpe.sbpc.botpcd.service + +import com.whatsapp.api.impl.WhatsappBusinessCloudApi +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.mockito.ArgumentCaptor +import org.mockito.Mockito.* +import org.mockito.junit.jupiter.MockitoExtension +import ufrpe.sbpc.botpcd.entity.MessageExchange +import ufrpe.sbpc.botpcd.repository.MessageExchangeRepository +import java.time.LocalDateTime + + +@ExtendWith(MockitoExtension::class) +class WhatsappServiceTest { + + private val cloudApi = mock(WhatsappBusinessCloudApi::class.java) + private val messageRepo = mock(MessageExchangeRepository::class.java) + private val service = WhatsappService(cloudApi, messageRepo) + private val userNumber = "551297652348" + private val botNumber = "558812389418" + private val sampleMessage = "Olá" + + @Test + fun `envia mensagem quando a ultima foi ha menos de 24h`() { + `when`( + messageRepo.lastExchangeMessage( + botNumber, + userNumber + ) + ).thenReturn( + MessageExchange( + fromPhoneNumber = userNumber, + toPhoneNumber = botNumber, + message = sampleMessage + ) + ) + service.sendMessage(botNumber, userNumber, sampleMessage) + verify(cloudApi).sendMessage(eq(botNumber), any()) + verify(messageRepo).save(any(MessageExchange::class.java)) + } + + @Test + fun `nao envia mensagem quando a ultima foi ha mais de 24h`() { + `when`( + messageRepo.lastExchangeMessage( + botNumber, + userNumber + ) + ).thenReturn( + MessageExchange( + fromPhoneNumber = userNumber, + toPhoneNumber = botNumber, + message = sampleMessage + ).apply { + createAt = LocalDateTime.now().minusHours(25) + } + ) + service.sendMessage(botNumber, userNumber, "Test message") + verify(cloudApi, never()).sendMessage(eq(userNumber), any()) + verify(messageRepo, never()).save(any(MessageExchange::class.java)) + } + + @Test + fun `envia mensagem com autor definido`() { + `when`( + messageRepo.lastExchangeMessage( + botNumber, + userNumber + ) + ).thenReturn( + MessageExchange( + fromPhoneNumber = userNumber, + toPhoneNumber = botNumber, + message = sampleMessage + ).apply { + createAt = LocalDateTime.now() + } + ) + service.sendMessage(botNumber, userNumber, "Test message", "AutorX") + val captor = ArgumentCaptor.forClass(MessageExchange::class.java) + verify(messageRepo).save(captor.capture()) + assertEquals("*AutorX:*\n Test message", captor.value.message) + } +} \ No newline at end of file diff --git a/src/test/resources/ufrpe/sbpc/botpcd/acessarServicos.feature b/src/test/resources/ufrpe/sbpc/botpcd/acessarServicos.feature new file mode 100644 index 0000000..85abda4 --- /dev/null +++ b/src/test/resources/ufrpe/sbpc/botpcd/acessarServicos.feature @@ -0,0 +1,119 @@ +# language: pt +Funcionalidade: Acessar serviços de assistência + Como um PCD, quero poder acessar os serviços que estão disponíveis para o meu tipo de deficiência, para que eu consiga aproveitar o evento da SBPC. + + Esquema do Cenário Listar serviços de acordo com meu tipo de deficiência + Quando PCD "" mandar qualquer mensagem + Entao "" PCD receberá mensagem de opções de serviço "" + + Exemplos: + | adjetivo_da_deficiencia | opções_de_serviço | + | um pessoa surda | informações em Libras,atividade com interpretação em Libras | + | mobilidade reduzida | ajuda na mobilidade,transporte para deslocamento no evento | + | deficiente físico | ajuda na mobilidade,ajuda com alimentação e higiene,transporte para deslocamento no evento | + | uma pessoa cega | ajuda na mobilidade,programação com audiodescrição | + | neurodivergente | suporte para pessoas neurodivergentes | + | uma pessoa surdocega | guia-intérprete | + + Esquema do Cenário: Direcionar para o atendente(monitor ou membro da comissão) + Dado que "" PCD recebeu mensagem de opções de serviço + E atendente que se chama "" está disponível para o "" + E atendente que se chama "" enviou uma mensagem nas ultimas 24 horas para o bot + Quando "" PCD envia a mensagem "" + Entao "" PCD receberá mensagem "O irá realizar seu atendimento." + + Exemplos: + | adjetivo_da_deficiencia | nome_do_atendente | servico_desejado | numero_servico_desejado | tipo_de_atendente | + # Deficiência Auditiva + | um pessoa surda | João Comissão | atividade com interpretação em Libras | 1 | membro da comissão | + | um pessoa surda | Ana Monitor | informações em Libras | 2 | monitor | + # Mobilidade Reduzida + | mobilidade reduzida | Pedro Monitor | ajuda na mobilidade | 1 | monitor | + | mobilidade reduzida | Carla Comissão | transporte para deslocamento no evento | 2 | membro da comissão | + # Deficiência Física + | deficiente físico | Bia Comissão | ajuda com alimentação e higiene | 1 | membro da comissão | + | deficiente físico | Pedro Monitor | ajuda na mobilidade | 2 | monitor | + | deficiente físico | Carla Comissão | transporte para deslocamento no evento | 3 | membro da comissão | + # Deficiência Visual + | uma pessoa cega | Pedro Monitor | ajuda na mobilidade | 1 | monitor | + | uma pessoa cega | Lucas Comissão | programação com audiodescrição | 2 | membro da comissão | + # Neurodivergente + | neurodivergente | Fábio Monitor | suporte para pessoas neurodivergentes | 1 | monitor | + # Surdocegueira + | uma pessoa surdocega | Maria Comissão | guia-intérprete | 1 | membro da comissão | + + + Regra: PCD precisa receber mensagem informando que ele está na fila de espera + Esquema do Cenário: PCD entra na fila de espera ao solicitar um serviço + Dado que "" PCD recebeu mensagem de opções de serviço + E que nenhum atendente para o "" está disponível + Quando "" PCD envia a mensagem "" + Entao "" PCD receberá mensagem "No momento não há atendentes disponíveis. Por favor, aguarde na fila de espera e retornaremos assim que possível." + + Exemplos: + | adjetivo_da_deficiencia | servico_desejado | numero_servico_desejado | + | um pessoa surda | atividade com interpretação em Libras | 1 | + | um pessoa surda | informações em Libras | 2 | + | mobilidade reduzida | ajuda na mobilidade | 1 | + | mobilidade reduzida | transporte para deslocamento no evento | 2 | + | deficiente físico | ajuda com alimentação e higiene | 1 | + | deficiente físico | ajuda na mobilidade | 2 | + | deficiente físico | transporte para deslocamento no evento | 3 | + | uma pessoa cega | ajuda na mobilidade | 1 | + | uma pessoa cega | programação com audiodescrição | 2 | + | neurodivergente | suporte para pessoas neurodivergentes | 1 | + | uma pessoa surdocega | guia-intérprete | 1 | + + + Cenário: PCD está na fila de espera e manda mensagem novamente + Dado PCD possuia serviço requisitado que ainda não foi iniciado + Quando PCD mandar qualquer mensagem + Então PCD receberá mensagem "No momento não há atendentes disponíveis. Por favor, aguarde na fila de espera e retornaremos assim que possível." + + Regra: Bot envia mensagem apenas para o usuário que mandaram mensagem nas últimas 24 horas + + Cenário de Fundo: + Dado que o atendente "Carlos" de telefone "558100000001" enviou mensagem nas últimas 24 horas + E que o PCD "João" de telefone "558100000002" enviou mensagem nas últimas 24 horas + E que o atendente "Ana" de telefone "558100000003" enviou mensagem nas últimas 24 horas + E que o PCD "Maria" de telefone "558100000004" enviou mensagem nas últimas 24 horas + + Cenário: Bot direciona mensagem do PCD para o atendente correto + Dado que o atendente "Carlos" de telefone "558100000001" está em atendimento com o PCD "João" de telefone "558100000002" + E que o atendente "Ana" de telefone "558100000003" está disponível, mas não em atendimento + Quando o PCD "João" de telefone "558100000002" envia a mensagem "Onde fica o banheiro mais próximo?" + Então o atendente "Carlos" de telefone "558100000001" deve receber a mensagem "Onde fica o banheiro mais próximo?" do PCD "João" + E o atendente "Ana" de telefone "558100000003" não deve receber nenhuma nova mensagem do pcd "João" + + Cenário: Bot direciona mensagem do atendente para o PCD correto em um atendimento ativo + Dado que o atendente "Carlos" de telefone "558100000001" está em atendimento com o PCD "João" de telefone "558100000002" + E que o PCD "Maria" de telefone "558100000004" não está em atendimento com "Carlos" + Quando o atendente "Carlos" de telefone "558100000001" envia a mensagem "Estou a caminho para ajudar." + Então o PCD "João" de telefone "558100000002" deve receber a mensagem "Estou a caminho para ajudar." do atendente "Carlos" + E o PCD "Maria" de telefone "558100000004" não deve receber nenhuma nova mensagem de "Carlos" + + Cenário: Atendente encerra o atendimento e PCD é notificado + Dado que o atendente "Carlos" de telefone "558100000001" está em atendimento com o PCD "João" de telefone "558100000002" + Quando o atendente "Carlos" de telefone "558100000001" encerra o atendimento + Então o PCD "João" de telefone "558100000002" deve receber a mensagem "Atendimento encerrado" do bot + + Regra: Um atendente fica disponível é redirecionado para um pcd na fila de espera + Esquema do Cenário: Atendente disponível é direcionado para PCD na fila de espera + Dado que "" PCD de número "" solicitou o serviço "" e está na fila de espera + E que o atendente "" de "" do tipo "" que presta serviço "" estava indisponível + Quando o atendente "" de "" fica disponível + Então "" PCD de número "" receberá mensagem "O irá realizar seu atendimento." + + Exemplos: + | adjetivo_da_deficiencia | numero_pcd | servico_desejado | nome_do_atendente | tipo_de_atendente | numero_atendente | + | um pessoa surda | 558100000021 | informações em Libras | Sara Monitor | monitor | 558100000031 | + | um pessoa surda | 558100000022 | atividade com interpretação em Libras | João Comissão | membro da comissão | 558100000002 | + | mobilidade reduzida | 558100000023 | ajuda na mobilidade | Joana Monitor | monitor | 558100000033 | + | mobilidade reduzida | 558100000024 | transporte para deslocamento no evento | Carla Comissão | membro da comissão | 558100000005 | + | deficiente físico | 558100000025 | ajuda na mobilidade | Pedro Monitor | monitor | 558100000006 | + | deficiente físico | 558100000026 | ajuda com alimentação e higiene | Bia Comissão | membro da comissão | 558100000007 | + | deficiente físico | 558100000027 | transporte para deslocamento no evento | Carla Comissão | membro da comissão | 558100000008 | + | uma pessoa cega | 558100000028 | ajuda na mobilidade | Pedro Monitor | monitor | 558100000009 | + | uma pessoa cega | 558100000029 | programação com audiodescrição | Lucas Comissão | membro da comissão | 558100000010 | + | neurodivergente | 558100000030 | suporte para pessoas neurodivergentes | Fábio Monitor | monitor | 558100000011 | + | uma pessoa surdocega | 558100000031 | guia-intérprete | Maria Comissão | membro da comissão | 558100000012 | \ No newline at end of file diff --git a/src/test/resources/ufrpe/sbpc/botpcd/mudarStatusAtendente.feature b/src/test/resources/ufrpe/sbpc/botpcd/mudarStatusAtendente.feature index e9d7bad..9efd43f 100644 --- a/src/test/resources/ufrpe/sbpc/botpcd/mudarStatusAtendente.feature +++ b/src/test/resources/ufrpe/sbpc/botpcd/mudarStatusAtendente.feature @@ -43,4 +43,5 @@ Funcionalidade: Mudar status do Atendente (Monitor ou Membro da Comissão) | Membro da Comissão | AVAILABLE | "1" | UNAVAILABLE | *BotPCD:*\n Seu status foi atualizado para Indisponível. | | Membro da Comissão | UNAVAILABLE | "1" | AVAILABLE | *BotPCD:*\n Seu status foi atualizado para Disponível. | | Membro da Comissão | BUSY | "1" | AVAILABLE | *BotPCD:*\n Atendimento encerrado. Seu status foi atualizado para Disponível. | - | Membro da Comissão | BUSY | "2" | UNAVAILABLE | *BotPCD:*\n Atendimento encerrado. Seu status foi atualizado para Indisponível. | \ No newline at end of file + | Membro da Comissão | BUSY | "2" | UNAVAILABLE | *BotPCD:*\n Atendimento encerrado. Seu status foi atualizado para Indisponível. | + | Membro da Comissão | BUSY | "cancelar" | BUSY | *BotPCD:*\n Você continua em atendimento. | \ No newline at end of file diff --git a/src/test/resources/ufrpe/sbpc/botpcd/registerPCD.feature b/src/test/resources/ufrpe/sbpc/botpcd/registerPCD.feature index ac08283..e8c1dff 100644 --- a/src/test/resources/ufrpe/sbpc/botpcd/registerPCD.feature +++ b/src/test/resources/ufrpe/sbpc/botpcd/registerPCD.feature @@ -78,11 +78,11 @@ Olá, qual sua deficiência? E usuário possui deficiência cadastrada de "" Quando usuário envia mensagem "João Victor" Entao A penúltima mensagem recebida pelo usuário será "Cadastro realizado." - E bot enviará opcções de serviço "" de acordo com a deficiência "" do pcd + E "" PCD receberá mensagem de opções de serviço "" E bot salvará o nome do usuário "João Victor" Exemplos: - | adjetivo_da_deficiencia | opcções_de_serviço | + | adjetivo_da_deficiencia | opções_de_serviço | | um pessoa surda | informações em Libras,atividade com interpretação em Libras | | mobilidade reduzida | ajuda na mobilidade,transporte para deslocamento no evento | | deficiente físico | ajuda na mobilidade,ajuda com alimentação e higiene,transporte para deslocamento no evento |