Merge pull request '0002429-endpoint-prenotazioni' (#14) from 0002429-endpoint-prenotazioni into dev

Reviewed-on: #14
This commit is contained in:
Francesco Di Sciascio 2025-04-30 10:03:04 +00:00
commit f88167d87e
18 changed files with 440 additions and 13 deletions

View File

@ -2,6 +2,7 @@ package eu.maiora.db
import eu.maiora.model.*
import kotlinx.coroutines.Dispatchers
import kotlinx.datetime.toKotlinLocalDateTime
import org.jetbrains.exposed.dao.IntEntity
import org.jetbrains.exposed.dao.IntEntityClass
import org.jetbrains.exposed.dao.LongEntity
@ -82,6 +83,7 @@ object SlotPrenotabiliTable : IdTable<Long>("view_slot_prenotabili"){
val giorno = date("giorno")
val dataInizio = datetime("data_inizio")
val dataFine = datetime("data_fine")
val dataLimiteCancellazione = datetime("data_limite_cancellazione")
val idModalita = long("id_modalita")
val modalitaPrenotazione = varchar("modalita_prenotazione", 255)
val idPuntoDistribuzione = long("id_punto_distribuzione")
@ -117,6 +119,32 @@ object ResidenzeTable : IdTable<Long>("residenze"){
val nome = varchar("nome", 255)
}
object PrenotazioniPastiTable : IdTable<Long>("prenotazioni_pasti"){
override val id = long("id").autoIncrement("seq_prenotazioni_pasti").entityId()
val idTessera = long("id_tessera")
val idSlotPuntoCassa = long("id_slot_punto_cassa")
val idResidenza = long("id_residenza").nullable()
val idVassoio = long("id_vassoio")
val importoPagato = double("importo_pagato")
val codiceRistocloud = varchar("codice_ristocloud", 255).nullable()
}
object PrenotazioniPastiDettaglioTable : IdTable<Long>("prenotazioni_pasti_dettaglio"){
override val id = long("id").autoIncrement("seq_pren_past_dettaglio").entityId()
val idPrenotazione = long("id_prenotazione")
val idProdotto = long("id_prodotto")
val idStato = long("id_stato")
}
object ViewPrenotazioniPastiTable : IdTable<Long>("view_prenotazioni_pasti"){
override val id = long("id").entityId()
val idPrenotazione = long("id_prenotazione")
val idStato = long("id_stato")
val stato = varchar("stato", 255)
val dataLimiteCancellazione = datetime("data_limite_cancellazione")
}
class AccountsDAO(id: EntityID<Int>) :IntEntity(id) {
companion object : IntEntityClass<AccountsDAO>(AccountsTable)
@ -184,6 +212,7 @@ class SlotPrenotabiliDao(id: EntityID<Long>) :LongEntity(id) {
var giorno by SlotPrenotabiliTable.giorno
var dataInizio by SlotPrenotabiliTable.dataInizio
var dataFine by SlotPrenotabiliTable.dataFine
var dataLimiteCancellazione by SlotPrenotabiliTable.dataLimiteCancellazione
var idModalita by SlotPrenotabiliTable.idModalita
var modalitaPrenotazione by SlotPrenotabiliTable.modalitaPrenotazione
var idPuntoDistribuzione by SlotPrenotabiliTable.idPuntoDistribuzione
@ -221,6 +250,34 @@ class ResidenzeDao(id: EntityID<Long>) :LongEntity(id){
val nome by ResidenzeTable.nome
}
class PrenotazioniPastiDao(id: EntityID<Long>) :LongEntity(id){
companion object : LongEntityClass<PrenotazioniPastiDao>(PrenotazioniPastiTable)
val idTessera by PrenotazioniPastiTable.idTessera
val idSlotPuntoCassa by PrenotazioniPastiTable.idSlotPuntoCassa
val idResidenza by PrenotazioniPastiTable.idResidenza
val idVassoio by PrenotazioniPastiTable.idVassoio
val importoPagato by PrenotazioniPastiTable.importoPagato
val codiceRistocloud by PrenotazioniPastiTable.codiceRistocloud
}
class PrenotazioniPastiDettaglioDao(id: EntityID<Long>) :LongEntity(id){
companion object : LongEntityClass<PrenotazioniPastiDettaglioDao>(PrenotazioniPastiDettaglioTable)
val idPrenotazione by PrenotazioniPastiDettaglioTable.idPrenotazione
val idProdotto by PrenotazioniPastiDettaglioTable.idProdotto
val idStato by PrenotazioniPastiDettaglioTable.idStato
}
class ViewPrenotazioniPastiDao(id: EntityID<Long>) :LongEntity(id){
companion object : LongEntityClass<ViewPrenotazioniPastiDao>(ViewPrenotazioniPastiTable)
val idPrenotazione by ViewPrenotazioniPastiTable.idPrenotazione
val idStato by ViewPrenotazioniPastiTable.idStato
val stato by ViewPrenotazioniPastiTable.stato
val dataLimiteCancellazione by ViewPrenotazioniPastiTable.dataLimiteCancellazione
}
fun accountsDaoToModel(dao: AccountsDAO) = Accounts(
dao.id.value,
dao.username,
@ -291,12 +348,14 @@ fun slotOrariDaoToModel(dao: SlotPrenotabiliDao) :SlotOrari{
val formattedGiorno = dao.giorno.format(formatterGiorno)
val formattedDataInizio = dao.dataInizio.format(formatterConOra)
val formattedDataFine = dao.dataFine.format(formatterConOra)
val formattedDataLimiteCancellazione = dao.dataLimiteCancellazione.format(formatterConOra)
return SlotOrari(
dao.id.value,
formattedGiorno,
formattedDataInizio,
formattedDataFine,
formattedDataLimiteCancellazione,
dao.idPuntoDistribuzione,
dao.puntoDistribuzione,
dao.idPuntoCassa,
@ -340,6 +399,37 @@ fun residenzeDaoToModel(dao: ResidenzeDao) :Residenze{
)
}
fun prenotazioniPastiDaoToModel(dao: PrenotazioniPastiDao) : PrenotazioniPasti{
return PrenotazioniPasti(
dao.id.value,
dao.idTessera,
dao.idSlotPuntoCassa,
dao.idResidenza,
dao.idVassoio,
dao.importoPagato,
dao.codiceRistocloud
)
}
fun prenotazioniPastiDettaglioDaoToModel(dao: PrenotazioniPastiDettaglioDao) : PrenotazioniPastiDettaglio{
return PrenotazioniPastiDettaglio(
dao.id.value,
dao.idPrenotazione,
dao.idProdotto,
dao.idStato
)
}
fun viewPrenotazioniPastiDaoToModel(dao: ViewPrenotazioniPastiDao) : ViewPrenotazioniPasti{
return ViewPrenotazioniPasti(
dao.id.value,
dao.idPrenotazione,
dao.idStato,
dao.stato,
dao.dataLimiteCancellazione.toKotlinLocalDateTime()
)
}
suspend fun <T> suspendTransaction(block: Transaction.() -> T): T =
newSuspendedTransaction(Dispatchers.IO, statement = block)

View File

@ -0,0 +1,21 @@
package eu.maiora.model
import kotlinx.serialization.Serializable
@Serializable
data class Prenotazioni(
val id: Long? = null,
val codiceRistocloud: String? = null,
val idTessera : Long,
val idSlotPuntoCassa : Long,
val giorno : String? = null,
val turno : String? = null,
val dataInizio : String? = null,
val dataFine : String? = null,
val dataLimiteCancellazione : String? = null,
val idVassoio : Long,
val idStato : Long? = null,
val stato : String? = null,
val listaProdotti : List<ProdottiPrenotabili>? = null,
val importoPagato : Double
)

View File

@ -0,0 +1,14 @@
package eu.maiora.model
import kotlinx.serialization.Serializable
@Serializable
data class PrenotazioniPasti(
val id: Long,
val idTessera : Long,
val idSlotPuntoCassa : Long,
val idResidenza : Long? = null,
val idVassoio : Long,
val importoPagato : Double,
val codiceRistocloud : String? = null
)

View File

@ -0,0 +1,11 @@
package eu.maiora.model
import kotlinx.serialization.Serializable
@Serializable
data class PrenotazioniPastiDettaglio(
val id: Long? = null,
val idPrenotazione : Long,
val idProdotto : Long,
val idStato : Long
)

View File

@ -0,0 +1,7 @@
package eu.maiora.model
interface PrenotazioniPastiDettaglioRepository {
suspend fun insert(prenotazionePastoDettaglio : PrenotazioniPastiDettaglio): PrenotazioniPastiDettaglio
suspend fun update(prenotazionePastoDettaglio: PrenotazioniPastiDettaglio): PrenotazioniPastiDettaglio
suspend fun listaDettagliByIdPrenotazione(idPrenotazione : Long): List<PrenotazioniPastiDettaglio>
}

View File

@ -0,0 +1,42 @@
package eu.maiora.model
import eu.maiora.db.*
import org.jetbrains.exposed.sql.insertAndGetId
import org.jetbrains.exposed.sql.update
class PrenotazioniPastiDettaglioRepositoryImpl : PrenotazioniPastiDettaglioRepository {
override suspend fun insert(prenotazionePastoDettaglio : PrenotazioniPastiDettaglio): PrenotazioniPastiDettaglio = suspendTransaction {
// Inserisci un nuovo dettaglio di una prenotazione pasto
// ID non definito: viene gestito dalla sequence
val idPrenPastiDettInserted = PrenotazioniPastiDettaglioTable.insertAndGetId {
it[idPrenotazione] = prenotazionePastoDettaglio.idPrenotazione
it[idProdotto] = prenotazionePastoDettaglio.idProdotto
it[idStato] = prenotazionePastoDettaglio.idStato
}.value
PrenotazioniPastiDettaglio(
idPrenPastiDettInserted,
prenotazionePastoDettaglio.idPrenotazione,
prenotazionePastoDettaglio.idProdotto,
prenotazionePastoDettaglio.idStato
)
}
override suspend fun update (prenotazionePastoDettaglio : PrenotazioniPastiDettaglio): PrenotazioniPastiDettaglio = suspendTransaction {
PrenotazioniPastiDettaglioTable.update({PrenotazioniPastiDettaglioTable.id eq prenotazionePastoDettaglio.id}){
it[idPrenotazione] = prenotazionePastoDettaglio.idPrenotazione
it[idProdotto] = prenotazionePastoDettaglio.idProdotto
it[idStato] = prenotazionePastoDettaglio.idStato
}
prenotazionePastoDettaglio
}
override suspend fun listaDettagliByIdPrenotazione(idPrenotazione : Long): List<PrenotazioniPastiDettaglio> = suspendTransaction {
// Cerca la lista di dettagli della prenotazione (ovvero i prodotti)
PrenotazioniPastiDettaglioDao.find { PrenotazioniPastiDettaglioTable.idPrenotazione eq idPrenotazione }
.toList() // Restituisce la lista dei prodotti
.map { prenotazioniPastiDettaglioDaoToModel(it) } // Converte il DAO in un oggetto PrenotazioniPastiDettaglio
}
}

View File

@ -0,0 +1,5 @@
package eu.maiora.model
interface PrenotazioniPastiRepository {
suspend fun insert(prenotazionePasto : PrenotazioniPasti): PrenotazioniPasti
}

View File

@ -0,0 +1,29 @@
package eu.maiora.model
import eu.maiora.db.*
import org.jetbrains.exposed.sql.insertAndGetId
class PrenotazioniPastiRepositoryImpl : PrenotazioniPastiRepository {
override suspend fun insert(prenotazionePasto : PrenotazioniPasti): PrenotazioniPasti = suspendTransaction {
// Inserisci una nuova prenotazione pasto
// ID non definito: viene gestito dalla sequence
val idPrenPastiInserted = PrenotazioniPastiTable.insertAndGetId {
it[idTessera] = prenotazionePasto.idTessera
it[idSlotPuntoCassa] = prenotazionePasto.idSlotPuntoCassa
it[idResidenza] = prenotazionePasto.idResidenza
it[idVassoio] = prenotazionePasto.idVassoio
it[importoPagato] = prenotazionePasto.importoPagato
it[codiceRistocloud] = prenotazionePasto.codiceRistocloud
}.value
PrenotazioniPasti(
idPrenPastiInserted,
prenotazionePasto.idTessera,
prenotazionePasto.idSlotPuntoCassa,
prenotazionePasto.idResidenza,
prenotazionePasto.idVassoio,
prenotazionePasto.importoPagato,
prenotazionePasto.codiceRistocloud
)
}
}

View File

@ -5,17 +5,17 @@ import kotlinx.serialization.Serializable
@Serializable
data class ProdottiPrenotabili(
val id: Long,
val giorno : String,
val idPuntoDistribuzione : Long,
val puntoDistribuzione : String,
val idTurno : Long,
val turno : String,
val idProdotto : Long,
val prodotto : String,
val codiceProdotto : String,
val descrizioneProdotto : String,
val idCategoria : Long,
val categoria : String,
val idModalita : Long,
val modalitaPrenotazione : String
val giorno : String? = null,
val idPuntoDistribuzione : Long? = null,
val puntoDistribuzione : String? = null,
val idTurno : Long? = null,
val turno : String? = null,
val idProdotto : Long? = null,
val prodotto : String? = null,
val codiceProdotto : String? = null,
val descrizioneProdotto : String? = null,
val idCategoria : Long? = null,
val categoria : String? = null,
val idModalita : Long? = null,
val modalitaPrenotazione : String? = null
)

View File

@ -8,6 +8,7 @@ data class SlotOrari(
val giorno : String,
val dataInizio : String,
val dataFine : String,
val dataLimiteCancellazione : String,
val idPuntoDistribuzione : Long,
val puntoDistribuzione : String,
val idPuntoCassa : Long,

View File

@ -5,4 +5,8 @@ interface SlotOrariRepository {
idTurno : Long,
idPuntoDistribuzione : Long,
idModalita : Long): List<SlotOrari>?
suspend fun idResidenzaByIdSlotOrario(idSlot : Long) : Long?
suspend fun slotOrarioById(idSlot : Long) : SlotOrari?
}

View File

@ -22,4 +22,17 @@ class SlotOrariRepositoryImpl : SlotOrariRepository {
.map { slotOrariDaoToModel(it) } // Converte il DAO in un oggetto SlotOrari
}
override suspend fun idResidenzaByIdSlotOrario(idSlot : Long) : Long? = suspendTransaction {
SlotPrenotabiliDao.find{ SlotPrenotabiliTable.id eq idSlot }
.single() // Restituisce un singolo risultato o null se non trovato
.let { slotOrariDaoToModel(it) } // Converte il DAO in un oggetto SlotOrari
.idResidenza
}
override suspend fun slotOrarioById(idSlot : Long) : SlotOrari? = suspendTransaction {
SlotPrenotabiliDao.find{ SlotPrenotabiliTable.id eq idSlot }
.singleOrNull() // Restituisce un singolo risultato o null se non trovato
?.let { slotOrariDaoToModel(it) } // Converte il DAO in un oggetto SlotOrari
}
}

View File

@ -0,0 +1,13 @@
package eu.maiora.model
import kotlinx.datetime.LocalDateTime
import kotlinx.serialization.Serializable
@Serializable
data class ViewPrenotazioniPasti(
val id: Long,
val idPrenotazione: Long,
val idStato: Long,
val stato: String,
val dataLimiteCancellazione: LocalDateTime
)

View File

@ -0,0 +1,5 @@
package eu.maiora.model
interface ViewPrenotazioniPastiRepository {
suspend fun prenotazioniPastiById(id : Long): ViewPrenotazioniPasti
}

View File

@ -0,0 +1,15 @@
package eu.maiora.model
import eu.maiora.db.*
import org.jetbrains.exposed.sql.SortOrder
import org.jetbrains.exposed.sql.insertAndGetId
class ViewPrenotazioniPastiRepositoryImpl : ViewPrenotazioniPastiRepository {
override suspend fun prenotazioniPastiById(id : Long): ViewPrenotazioniPasti = suspendTransaction {
// Cerca la prenotazione
ViewPrenotazioniPastiDao.find { ViewPrenotazioniPastiTable.idPrenotazione eq id }
.first() //prendo solo il primo record, dato che ci sono tanti record quanti prodotti prenotati
.let { viewPrenotazioniPastiDaoToModel(it) } // Converte il DAO in un oggetto ViewPrenotazioniPasti
}
}

View File

@ -22,5 +22,7 @@ fun Application.configureRouting() {
slotOrari(SlotOrariRepositoryImpl())
prodottiPrenotabili(ProdottiPrenotabiliRepositoryImpl())
residenze(ResidenzeRepositoryImpl())
prenotazioni(PrenotazioniPastiRepositoryImpl(), SlotOrariRepositoryImpl(), PrenotazioniPastiDettaglioRepositoryImpl())
annullaPrenotazioni(ViewPrenotazioniPastiRepositoryImpl(), PrenotazioniPastiDettaglioRepositoryImpl())
}
}

View File

@ -0,0 +1,71 @@
package eu.maiora.routes
import eu.maiora.model.*
import io.ktor.http.*
import io.ktor.server.application.*
import io.ktor.server.auth.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import kotlinx.datetime.toJavaLocalDateTime
import java.time.LocalDateTime
fun Route.annullaPrenotazioni(
viewPrenotazioniPastiRepository: ViewPrenotazioniPastiRepositoryImpl,
prenotazioniPastiDettaglioRepository: PrenotazioniPastiDettaglioRepositoryImpl
) {
route("/api/annullaPrenotazioni") {
authenticate("auth-jwt") {
patch("{idPrenotazione}") {
//ottieni l'id della prenotazione dal percorso
val idPrenotazione = call.parameters["idPrenotazione"]
if (idPrenotazione == null) {
call.respondText("ID prenotazione non valido", status = HttpStatusCode.BadRequest)
return@patch
}
val prenotazione = viewPrenotazioniPastiRepository.prenotazioniPastiById(idPrenotazione.toLong())
val statoPrenotazione = prenotazione.stato
//prenotazione annullabile se lo stato non è SERVITA 2L
if (prenotazione.idStato == 2L) {
call.respond(
HttpStatusCode.BadRequest,
"Prenotazione non annullata. Stato corrente della prenotazione: $statoPrenotazione"
)
return@patch
}
//prenotazione annullabile se la data limite per la cancellazione non è stato superato
if (LocalDateTime.now().isAfter(prenotazione.dataLimiteCancellazione.toJavaLocalDateTime())) {
call.respond(
HttpStatusCode.BadRequest,
"Prenotazione non annullata: data limite cancellazione superata"
)
return@patch
}
val listaProdottiPrenotati =
prenotazioniPastiDettaglioRepository.listaDettagliByIdPrenotazione(idPrenotazione.toLong())
//aggiorna lo stato di ogni prodotto per annullare la prenotazione
listaProdottiPrenotati.forEach { el ->
val prodottoDaAggiornare = PrenotazioniPastiDettaglio(
el.id,
el.idPrenotazione,
el.idProdotto,
3L //ANNULLATA
)
prenotazioniPastiDettaglioRepository.update(prodottoDaAggiornare)
}
call.respond(HttpStatusCode.OK, "Prenotazione $idPrenotazione annullata")
}
}
}
}

View File

@ -0,0 +1,84 @@
package eu.maiora.routes
import eu.maiora.model.*
import io.ktor.http.*
import io.ktor.server.application.*
import io.ktor.server.auth.*
import io.ktor.server.request.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
fun Route.prenotazioni(prenotazioniPastiRepository: PrenotazioniPastiRepository,
slotOrariRepository: SlotOrariRepository,
prenotazioniPastiDettaglioRepository : PrenotazioniPastiDettaglioRepository){
route("/api/prenotazioni"){
authenticate("auth-jwt") {
post(){
try{
val listaDettagliInseriti = mutableListOf<PrenotazioniPastiDettaglio>()
val prenotazioneRequest = call.receive<Prenotazioni>()
//recupera lo slot orario selezionato
val idSlotOrario = slotOrariRepository.slotOrarioById(prenotazioneRequest.idSlotPuntoCassa)
if(idSlotOrario == null){
call.respond(HttpStatusCode.BadRequest,"Slot orario non esistente")
return@post
}
val prenotazionePasto = PrenotazioniPasti(
-1,
prenotazioneRequest.idTessera,
prenotazioneRequest.idSlotPuntoCassa,
idSlotOrario.idResidenza,
prenotazioneRequest.idVassoio,
prenotazioneRequest.importoPagato,
prenotazioneRequest.codiceRistocloud
)
//Inserisci la prenotazione nel database
val prenotazioneInserita = prenotazioniPastiRepository.insert(prenotazionePasto)
//per ogni prodotto, inserisci il dettaglio della prenotazione
prenotazioneRequest.listaProdotti?.forEach { el ->
val dettaglioPrenotazione = PrenotazioniPastiDettaglio(
-1,
prenotazioneInserita.id,
el.id,
0L
)
listaDettagliInseriti.add(prenotazioniPastiDettaglioRepository.insert(dettaglioPrenotazione))
}
//restituisci la prenotazione
val prenotazione = Prenotazioni(
prenotazioneInserita.id,
prenotazioneRequest.codiceRistocloud,
prenotazioneInserita.idTessera,
prenotazioneInserita.idSlotPuntoCassa,
idSlotOrario.giorno,
idSlotOrario.turno,
idSlotOrario.dataInizio,
idSlotOrario.dataFine,
idSlotOrario.dataLimiteCancellazione,
prenotazioneRequest.idVassoio,
prenotazioneRequest.idStato,
prenotazioneRequest.stato,
prenotazioneRequest.listaProdotti,
prenotazioneRequest.importoPagato
)
call.respond(HttpStatusCode.Created, prenotazione)
}
catch (e: Exception){
call.respond(
HttpStatusCode.BadRequest,
"Errore nel processare la richiesta: ${e.cause}"
)
}
}
}
}
}