Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
9f153b2
put the acessarServicos.feature
victorYghor Jun 14, 2025
4dce5e5
improve the testes
victorYghor Jun 14, 2025
5f272a3
Consertar o bug da fila de espera
victorYghor Jun 14, 2025
22467e6
Add restriction to send message if receive a user message in less tha…
yet1dev Jun 14, 2025
c1cce57
Redirect when the committee member is avaliable
victorYghor Jun 15, 2025
0d66848
Implement Unit Tests
victorYghor Jun 15, 2025
f284646
Melhorar esquema de cenário direcionar para o atendente(monitor ou me…
victorYghor Jun 15, 2025
da2be7e
finish the Direcionar para o atendnete cenário teste
victorYghor Jun 20, 2025
6bda520
add another condition to the bot send a message
victorYghor Jun 20, 2025
bca6285
improve the esquema do cenário direionar para o atendente
victorYghor Jun 21, 2025
5767dfa
improve the cenários
victorYghor Jun 21, 2025
89c5f0f
improve the aceptan testes
victorYghor Jun 21, 2025
c65a2f9
Add step for: nenhum atendente disponivel
yet1dev Jun 22, 2025
2d4a82f
Add step for: pcd possui atendimento nao iniciado
yet1dev Jun 22, 2025
a26dc23
Add step for: pcd solicitou servico e esta na fila de espera
yet1dev Jun 22, 2025
6a37050
in-progress: Add step for: atendente do tipo esta indisponivel
yet1dev Jun 22, 2025
96aa41f
Complete step: Atendente do tipo esta indisponivel
yet1dev Jun 22, 2025
cbe7ea8
[AVALIAR] Add IA step sugestion: quanto atendente especifico fica dis…
yet1dev Jun 22, 2025
dc64e8c
Fix test not have number identifier
yet1dev Jun 22, 2025
a5f3629
Redo step with number: atendente de numero, nome e tipo esta indispon…
yet1dev Jun 22, 2025
3b111ef
Redo step with number: Atendente de nome e numero esta disponivel
yet1dev Jun 22, 2025
a3ba2c0
Add ausent tests of cucumber
victorYghor Jun 23, 2025
43b04e9
Solve the problem with redirect messages
victorYghor Jun 24, 2025
8da0451
Solve the problem when the monitor or commite member is avaliable
victorYghor Jun 24, 2025
8e193fd
Fix tests acessar serviços de assitência
victorYghor Jun 24, 2025
196f0ba
solve type and put in git ignore
victorYghor Jun 24, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ out/
/dist/
/nbdist/
/.nb-gradle/

data/
### VS Code ###
.vscode/

Expand Down
Binary file added data/myDB.mv.db
Binary file not shown.
38 changes: 38 additions & 0 deletions data/myDB.trace.db
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
2025-05-20 12:36:33.423212Z jdbc[3]: exception
Copy link

Copilot AI Jun 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A database trace log file was checked in; consider removing it from source control or adding it to .gitignore.

Copilot uses AI. Check for mistakes.
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)
3 changes: 1 addition & 2 deletions src/main/kotlin/ufrpe/sbpc/botpcd/entity/MessageExchange.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,5 @@ class MessageExchange(
@Column(columnDefinition = "TEXT")
var message: String
) {
@CreationTimestamp
lateinit var createAt: LocalDateTime
var createAt: LocalDateTime = LocalDateTime.now()
}
2 changes: 1 addition & 1 deletion src/main/kotlin/ufrpe/sbpc/botpcd/entity/PWD.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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<Disability> = mutableSetOf()
var disabilities: Set<Disability> = setOf()
): User(name = name, phoneNumber = phoneNumber)
19 changes: 16 additions & 3 deletions src/main/kotlin/ufrpe/sbpc/botpcd/entity/ServiceType.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

Expand All @@ -66,11 +66,24 @@ sealed class ServiceType(
fun getServicesByDisability(disability: Disability): List<ServiceType> {
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<MonitorServiceType>()
.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
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<Attendance, Long> {
@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<Attendance>
@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?

Expand All @@ -37,4 +38,8 @@ interface AttendanceRepository : JpaRepository<Attendance, Long> {
@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)
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ import ufrpe.sbpc.botpcd.entity.Attendant

interface AttendantRepository: JpaRepository<Attendant, Long> {
fun findByPhoneNumber(phone: String): Attendant?
fun findByName(name: String): Attendant?
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import ufrpe.sbpc.botpcd.entity.UserStatus

interface MonitorRepository : JpaRepository<Monitor, Long>{
fun findByPhoneNumber(phoneNumber: String): Monitor?
fun findByName(phoneName: String): Monitor?
fun findByStatus(status: UserStatus): List<Monitor>
@Query("SELECT m from Monitor m where m.status = :status and m.assistanceType = :assistanceType")
fun findAvailableMonitor(status: UserStatus, assistanceType: MonitorAssistanceType): List<Monitor>
Expand Down
192 changes: 180 additions & 12 deletions src/main/kotlin/ufrpe/sbpc/botpcd/service/AttendanceService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

Expand All @@ -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<String>, 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<Monitor> {
return monitorRepository.findByStatus(UserStatus.AVAILABLE)
}

fun findAvailableCommitteeMembers(): List<CommitteeMember> {
return committeeMemberRepository.findByStatus(UserStatus.AVAILABLE)
}

fun findAvailableMonitorsByType(assistanceType: MonitorAssistanceType): List<Monitor> {
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))
}
Expand All @@ -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)
}
}
}
Expand Down Expand Up @@ -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."
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Loading