Merge pull request '0002593-endpoint-prenotazioni-adeguamento-strutture-dati' (#23) from 0002593-endpoint-prenotazioni-adeguamento-strutture-dati into dev

Reviewed-on: #23
This commit is contained in:
Francesco Di Sciascio 2025-08-20 14:35:44 +00:00
commit 07f4fab26e
9 changed files with 171 additions and 114 deletions

View File

@ -3,12 +3,7 @@ package eu.maiora.db
import eu.maiora.model.* import eu.maiora.model.*
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.datetime.toKotlinLocalDateTime import org.jetbrains.exposed.dao.*
import kotlinx.datetime.toKotlinLocalDate
import org.jetbrains.exposed.dao.IntEntity
import org.jetbrains.exposed.dao.IntEntityClass
import org.jetbrains.exposed.dao.LongEntity
import org.jetbrains.exposed.dao.LongEntityClass
import org.jetbrains.exposed.dao.id.EntityID import org.jetbrains.exposed.dao.id.EntityID
import org.jetbrains.exposed.dao.id.IdTable import org.jetbrains.exposed.dao.id.IdTable
import org.jetbrains.exposed.sql.Transaction import org.jetbrains.exposed.sql.Transaction
@ -149,10 +144,17 @@ object ViewPrenotazioniPastiTable : IdTable<Long>("view_prenotazioni_pasti"){
val giorno = date("giorno") val giorno = date("giorno")
val turno = varchar("turno", 255) val turno = varchar("turno", 255)
val dataInizio = datetime("data_inizio") val dataInizio = datetime("data_inizio")
val dataFine = datetime("data_fine")
val dataLimiteCancellazione = datetime("data_limite_cancellazione") val dataLimiteCancellazione = datetime("data_limite_cancellazione")
val idVassoio = long("id_vassoio").nullable() val idVassoio = long("id_vassoio").nullable()
val codiceRistocloud = varchar("codice_ristocloud", 255).nullable() val codiceRistocloud = varchar("codice_ristocloud", 255).nullable()
val importoPagato = double("importo_pagato") val importoPagato = double("importo_pagato")
val puntoDistribuzione = varchar("punto_distribuzione", 255)
val puntoCassa = varchar("punto_cassa", 255)
val modalita = varchar("modalita", 255)
val idProdotto = long("id_prodotto")
val prodotto = varchar("prodotto", 255)
} }
class AccountsDAO(id: EntityID<Int>) :IntEntity(id) { class AccountsDAO(id: EntityID<Int>) :IntEntity(id) {
@ -290,10 +292,17 @@ class ViewPrenotazioniPastiDao(id: EntityID<Long>) :LongEntity(id){
val giorno by ViewPrenotazioniPastiTable.giorno val giorno by ViewPrenotazioniPastiTable.giorno
val turno by ViewPrenotazioniPastiTable.turno val turno by ViewPrenotazioniPastiTable.turno
val dataInizio by ViewPrenotazioniPastiTable.dataInizio val dataInizio by ViewPrenotazioniPastiTable.dataInizio
val dataFine by ViewPrenotazioniPastiTable.dataFine
val dataLimiteCancellazione by ViewPrenotazioniPastiTable.dataLimiteCancellazione val dataLimiteCancellazione by ViewPrenotazioniPastiTable.dataLimiteCancellazione
val idVassoio by ViewPrenotazioniPastiTable.idVassoio val idVassoio by ViewPrenotazioniPastiTable.idVassoio
val codiceRistocloud by ViewPrenotazioniPastiTable.codiceRistocloud val codiceRistocloud by ViewPrenotazioniPastiTable.codiceRistocloud
val importoPagato by ViewPrenotazioniPastiTable.importoPagato val importoPagato by ViewPrenotazioniPastiTable.importoPagato
val puntoDistribuzione by ViewPrenotazioniPastiTable.puntoDistribuzione
val puntoCassa by ViewPrenotazioniPastiTable.puntoCassa
val modalita by ViewPrenotazioniPastiTable.modalita
val idProdotto by ViewPrenotazioniPastiTable.idProdotto
val prodotto by ViewPrenotazioniPastiTable.prodotto
} }
fun accountsDaoToModel(dao: AccountsDAO) = Accounts( fun accountsDaoToModel(dao: AccountsDAO) = Accounts(
@ -438,12 +447,14 @@ fun prenotazioniPastiDettaglioDaoToModel(dao: PrenotazioniPastiDettaglioDao) : P
) )
} }
fun viewPrenotazioniPastiDaoToModel(dao: ViewPrenotazioniPastiDao) : ViewPrenotazioniPasti{ /*fun viewPrenotazioniPastiDaoToModel(dao: ViewPrenotazioniPastiDao) : ViewPrenotazioniPasti{
val formatterGiorno = DateTimeFormatter.ofPattern("ddMMyyyy") val formatterGiorno = DateTimeFormatter.ofPattern("ddMMyyyy")
val formatterDataInizio = DateTimeFormatter.ofPattern("ddMMyyyy HH:mm") val formatterDataInizio = DateTimeFormatter.ofPattern("ddMMyyyy HH:mm")
val formatterDataFine = DateTimeFormatter.ofPattern("ddMMyyyy HH:mm")
val formatterDataLimiteCancellazione = DateTimeFormatter.ofPattern("ddMMyyyy HH:mm") val formatterDataLimiteCancellazione = DateTimeFormatter.ofPattern("ddMMyyyy HH:mm")
val formattedGiorno = dao.giorno.format(formatterGiorno) val formattedGiorno = dao.giorno.format(formatterGiorno)
val formattedDataInizio = dao.dataInizio.format(formatterDataInizio) val formattedDataInizio = dao.dataInizio.format(formatterDataInizio)
val formattedDataFine = dao.dataFine.format(formatterDataFine)
val formattedDataLimiteCanc = dao.dataLimiteCancellazione.format(formatterDataLimiteCancellazione) val formattedDataLimiteCanc = dao.dataLimiteCancellazione.format(formatterDataLimiteCancellazione)
@ -456,12 +467,48 @@ fun viewPrenotazioniPastiDaoToModel(dao: ViewPrenotazioniPastiDao) : ViewPrenota
formattedGiorno, formattedGiorno,
dao.turno, dao.turno,
formattedDataInizio, formattedDataInizio,
formattedDataFine,
formattedDataLimiteCanc, formattedDataLimiteCanc,
dao.idVassoio, dao.idVassoio,
dao.codiceRistocloud, dao.codiceRistocloud,
dao.importoPagato, dao.importoPagato,
dao.puntoDistribuzione,
dao.puntoCassa,
dao.modalita,
null null
) )
}*/
fun viewPrenotazioniPastiDaoToModel(dao: ViewPrenotazioniPastiDao, listaProdotti: List<ListaProdottiElement>? = null) : ViewPrenotazioniPasti{
val formatterGiorno = DateTimeFormatter.ofPattern("ddMMyyyy")
val formatterDataInizio = DateTimeFormatter.ofPattern("ddMMyyyy HH:mm")
val formatterDataFine = DateTimeFormatter.ofPattern("ddMMyyyy HH:mm")
val formatterDataLimiteCancellazione = DateTimeFormatter.ofPattern("ddMMyyyy HH:mm")
val formattedGiorno = dao.giorno.format(formatterGiorno)
val formattedDataInizio = dao.dataInizio.format(formatterDataInizio)
val formattedDataFine = dao.dataFine.format(formatterDataFine)
val formattedDataLimiteCanc = dao.dataLimiteCancellazione.format(formatterDataLimiteCancellazione)
return ViewPrenotazioniPasti(
dao.idPrenotazione,
dao.idStato,
dao.stato,
dao.idTessera,
dao.idSlotPuntoCassa,
formattedGiorno,
dao.turno,
formattedDataInizio,
formattedDataFine,
formattedDataLimiteCanc,
dao.idVassoio,
dao.codiceRistocloud,
dao.importoPagato,
dao.puntoDistribuzione,
dao.puntoCassa,
dao.modalita,
listaProdotti
)
} }

View File

@ -0,0 +1,6 @@
package eu.maiora.model
import kotlinx.serialization.Serializable
@Serializable
data class ListaProdottiElement (val id: Long, val nome: String? = null, val quantita: Int)

View File

@ -16,6 +16,7 @@ data class Prenotazioni(
val idVassoio : Long, val idVassoio : Long,
val idStato : Long? = null, val idStato : Long? = null,
val stato : String? = null, val stato : String? = null,
val listaProdotti : List<ProdottiPrenotabili>? = null, //val listaProdotti : List<ProdottiPrenotabili>? = null,
val listaProdotti: List<ListaProdottiElement>? = null,
val importoPagato : Double val importoPagato : Double
) )

View File

@ -7,5 +7,7 @@ data class PrenotazioniPastiDettaglio(
val id: Long? = null, val id: Long? = null,
val idPrenotazione : Long, val idPrenotazione : Long,
val idProdotto : Long, val idProdotto : Long,
val idStato : Long val idStato : Long,
val quantita : Int? = null,
val prodotto : String? = null
) )

View File

@ -1,7 +1,5 @@
package eu.maiora.model package eu.maiora.model
import kotlinx.datetime.LocalDate
import kotlinx.datetime.LocalDateTime
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
@Serializable @Serializable
@ -14,10 +12,14 @@ data class ViewPrenotazioniPasti(
val giorno: String, val giorno: String,
val turno: String, val turno: String,
val dataInizio: String, val dataInizio: String,
val dataFine: String,
val dataLimiteCancellazione: String, val dataLimiteCancellazione: String,
val idVassoio: Long? = null, val idVassoio: Long? = null,
val codiceRistocloud: String? = null, val codiceRistocloud: String? = null,
val importoPagato: Double, val importoPagato: Double,
var listaProdotti: List<PrenotazioniPastiDettaglio>? = null val puntoDistribuzione: String,
val puntoCassa: String,
val modalita: String,
var listaProdotti: List<ListaProdottiElement>? = null
) )

View File

@ -2,15 +2,19 @@ package eu.maiora.model
import eu.maiora.db.* import eu.maiora.db.*
import org.jetbrains.exposed.sql.SortOrder import org.jetbrains.exposed.sql.SortOrder
import org.jetbrains.exposed.sql.insertAndGetId
class ViewPrenotazioniPastiRepositoryImpl : ViewPrenotazioniPastiRepository { class ViewPrenotazioniPastiRepositoryImpl : ViewPrenotazioniPastiRepository {
override suspend fun prenotazioniPastiById(id : Long): ViewPrenotazioniPasti = suspendTransaction { override suspend fun prenotazioniPastiById(id : Long): ViewPrenotazioniPasti = suspendTransaction {
// Cerca la prenotazione // Cerca la prenotazione
ViewPrenotazioniPastiDao.find { ViewPrenotazioniPastiTable.idPrenotazione eq id } val prenotazione = ViewPrenotazioniPastiDao.find { ViewPrenotazioniPastiTable.idPrenotazione eq id }
.first() //prendo solo il primo record, dato che ci sono tanti record quanti prodotti prenotati .firstOrNull() //prendo solo il primo record, dato che ci sono tanti record quanti prodotti prenotati
.let { viewPrenotazioniPastiDaoToModel(it) } // Converte il DAO in un oggetto ViewPrenotazioniPasti
if (prenotazione == null) {
throw IllegalArgumentException("Prenotazione con ID $id non trovata")
}
viewPrenotazioniPastiDaoToModel(prenotazione, null)
} }
override suspend fun prenotazioniPastiByIdTessera(idTessera : Long, limit : Long, offset : Long, order : String): List<ViewPrenotazioniPasti> = suspendTransaction { override suspend fun prenotazioniPastiByIdTessera(idTessera : Long, limit : Long, offset : Long, order : String): List<ViewPrenotazioniPasti> = suspendTransaction {
@ -20,20 +24,41 @@ class ViewPrenotazioniPastiRepositoryImpl : ViewPrenotazioniPastiRepository {
else -> SortOrder.DESC // Default a DESC in caso di valore invalido else -> SortOrder.DESC // Default a DESC in caso di valore invalido
} }
// Cerca la lista di prenotazioni // Cerca la lista di prenotazioni
ViewPrenotazioniPastiDao.find { ViewPrenotazioniPastiTable.idTessera eq idTessera } val prenotazioniRaw = ViewPrenotazioniPastiDao
.find { ViewPrenotazioniPastiTable.idTessera eq idTessera }
.orderBy(ViewPrenotazioniPastiTable.giorno to sortOrder, ViewPrenotazioniPastiTable.id to sortOrder) .orderBy(ViewPrenotazioniPastiTable.giorno to sortOrder, ViewPrenotazioniPastiTable.id to sortOrder)
.limit(limit.toInt()) .limit(limit.toInt())
.offset(offset) .offset(offset)
.toList() // Restituisce la lista delle prenotazioni .toList()
.map { viewPrenotazioniPastiDaoToModel(it) } // Converte il DAO in un oggetto Movimenti
//raggruppo i record per idPrenotazione e, successivamente, per idProdotto per poter calcolare le quantita
val listaPrenotazioniPasti = prenotazioniRaw
.groupBy { it.idPrenotazione }
.map { (_, dettagliPrenotazione) ->
val primaRiga = dettagliPrenotazione.first() //la prima riga la uso per leggere i dati della prenotazione
val prodottiRaggruppati = dettagliPrenotazione
.groupBy { it.idProdotto }
.map { (idProdotto, prodotti) ->
ListaProdottiElement(
id = idProdotto,
nome = prodotti.first().prodotto,
quantita = prodotti.size
)
}
viewPrenotazioniPastiDaoToModel(primaRiga, prodottiRaggruppati)
}
listaPrenotazioniPasti
} }
override suspend fun countPrenotazioniPastiByIdTessera(idTessera : Long): Int = suspendTransaction { override suspend fun countPrenotazioniPastiByIdTessera(idTessera : Long): Int = suspendTransaction {
// Conta il numero totale di prenotazioni della tessera // Conta il numero totale di prenotazioni della tessera
ViewPrenotazioniPastiDao.find { ViewPrenotazioniPastiTable.idTessera eq idTessera } ViewPrenotazioniPastiDao
.toList() // Restituisce la lista delle prenotazioni .find { ViewPrenotazioniPastiTable.idTessera eq idTessera }
.count() .toList()
.map { it.idPrenotazione } // Estrai solo gli ID delle prenotazioni
.distinct() // Rimuovi duplicati
.count() // Conta le prenotazioni uniche
} }
} }

View File

@ -26,45 +26,51 @@ fun Route.annullaPrenotazioni(
return@patch return@patch
} }
val prenotazione = viewPrenotazioniPastiRepository.prenotazioniPastiById(idPrenotazione.toLong()) try{
val statoPrenotazione = prenotazione.stato val prenotazione = viewPrenotazioniPastiRepository.prenotazioniPastiById(idPrenotazione.toLong())
val statoPrenotazione = prenotazione.stato
//prenotazione annullabile se lo stato non è SERVITA 2L //prenotazione annullabile se lo stato non è SERVITA 2L
if (prenotazione.idStato == 2L) { if (prenotazione.idStato == 2L) {
call.respond( call.respond(
HttpStatusCode.BadRequest, HttpStatusCode.BadRequest,
"Prenotazione non annullata. Stato corrente della prenotazione: $statoPrenotazione" "Prenotazione non annullata. Stato corrente della prenotazione: $statoPrenotazione"
) )
return@patch return@patch
}
//prenotazione annullabile se la data limite per la cancellazione non è stata superata
val formatter = DateTimeFormatter.ofPattern("ddMMyyyy HH:mm")
val formattedDate = LocalDateTime.parse(prenotazione.dataLimiteCancellazione, formatter)
if (LocalDateTime.now().isAfter(formattedDate)) {
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")
}
catch (e: IllegalArgumentException) {
call.respond(HttpStatusCode.NotFound, "Prenotazione non trovata: ${e.message}")
} }
//prenotazione annullabile se la data limite per la cancellazione non è stata superata
val formatter = DateTimeFormatter.ofPattern("ddMMyyyy HH:mm")
val formattedDate = LocalDateTime.parse(prenotazione.dataLimiteCancellazione, formatter)
if (LocalDateTime.now().isAfter(formattedDate)) {
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

@ -44,11 +44,6 @@ fun Route.prenotazioni(prenotazioniPastiRepository: PrenotazioniPastiRepository,
offset.toLong(), offset.toLong(),
order) order)
listaPrenotazioniPasti.forEach{ el ->
el.listaProdotti =
prenotazioniPastiDettaglioRepository.listaDettagliByIdPrenotazione(el.id)
}
call.respond(ListaPrenotazioni(totalRecords, listaPrenotazioniPasti)) call.respond(ListaPrenotazioni(totalRecords, listaPrenotazioniPasti))
} }
@ -68,6 +63,15 @@ fun Route.prenotazioni(prenotazioniPastiRepository: PrenotazioniPastiRepository,
return@post return@post
} }
prenotazioneRequest.listaProdotti.forEach { prodotto ->
prodotto.quantita
?: throw IllegalArgumentException("Quantita non specificata per il prodotto con ID ${prodotto.id}")
if (prodotto.quantita < 1) {
throw IllegalArgumentException("Quantita non valida (${prodotto.quantita}) per il prodotto con ID ${prodotto.id}")
}
}
val prenotazionePasto = PrenotazioniPasti( val prenotazionePasto = PrenotazioniPasti(
-1, -1,
@ -83,15 +87,19 @@ fun Route.prenotazioni(prenotazioniPastiRepository: PrenotazioniPastiRepository,
val prenotazioneInserita = prenotazioniPastiRepository.insert(prenotazionePasto) val prenotazioneInserita = prenotazioniPastiRepository.insert(prenotazionePasto)
//per ogni prodotto, inserisci il dettaglio della prenotazione //per ogni prodotto, inserisci il dettaglio della prenotazione
prenotazioneRequest.listaProdotti?.forEach { el -> prenotazioneRequest.listaProdotti.forEach { prodotto ->
val dettaglioPrenotazione = PrenotazioniPastiDettaglio(
-1,
prenotazioneInserita.id,
el.id,
0L
)
listaDettagliInseriti.add(prenotazioniPastiDettaglioRepository.insert(dettaglioPrenotazione))
repeat(prodotto.quantita!!) {
val dettaglioPrenotazione = PrenotazioniPastiDettaglio(
-1,
prenotazioneInserita.id,
prodotto.id,
0L
)
listaDettagliInseriti.add(
prenotazioniPastiDettaglioRepository.insert(dettaglioPrenotazione)
)
}
} }
@ -117,7 +125,7 @@ fun Route.prenotazioni(prenotazioniPastiRepository: PrenotazioniPastiRepository,
catch (e: Exception){ catch (e: Exception){
call.respond( call.respond(
HttpStatusCode.BadRequest, HttpStatusCode.BadRequest,
"Errore nel processare la richiesta: ${e.cause}" "Errore nel processare la richiesta: ${e.message}"
) )
} }
} }

View File

@ -19,7 +19,6 @@ fun Route.verificaVassoio(selezionaComposizioneRepository: SelezionaComposizione
try { try {
val dati = call.receive<DatiVassoio>() val dati = call.receive<DatiVassoio>()
//val idsConcatenati = dati.listaProdotti.joinToString(separator = "|") { it.id.toString() }
val idsConcatenati = dati.listaProdotti val idsConcatenati = dati.listaProdotti
.flatMap { prodotto -> List(prodotto.quantita) { prodotto.id } } .flatMap { prodotto -> List(prodotto.quantita) { prodotto.id } }
.joinToString(separator = "|") { it.toString() } .joinToString(separator = "|") { it.toString() }
@ -35,43 +34,6 @@ fun Route.verificaVassoio(selezionaComposizioneRepository: SelezionaComposizione
call.respond(HttpStatusCode.BadRequest, "Body non valido") call.respond(HttpStatusCode.BadRequest, "Body non valido")
} }
/*val idTessera = call.parameters["idTessera"]
val idVassoio = call.parameters["idVassoio"]
val data = call.parameters["data"]
if (idTessera == null) {
call.respondText("ID tessera non valido", status = HttpStatusCode.BadRequest)
return@get
}
if (idVassoio == null) {
call.respondText("ID vassoio non valido", status = HttpStatusCode.BadRequest)
return@get
}
if (data == null) {
call.respondText("data non valida", status = HttpStatusCode.BadRequest)
return@get
}
// Cerca la tessera per codice fiscale
val valorePasto = valorePastiRepository.valorePastoByTessVassData(idTessera, idVassoio, data)
val prezzo = valorePasto.split("#").get(0)
val punti = valorePasto.split("#").get(1)
if(prezzo == "-1" && punti == "-1")
call.respondText("Prezzo e punti non trovati", status = HttpStatusCode.NotFound)
call.respond( ValorePasti(idTessera.toLong(),
idVassoio.toLong(), data,
prezzo.replace(',', '.').toDouble(),
punti.toInt()))*/
} }
} }
@ -79,7 +41,5 @@ fun Route.verificaVassoio(selezionaComposizioneRepository: SelezionaComposizione
} }
@Serializable @Serializable
data class DatiVassoio (val idUtente : Long, val listaProdotti: List<Prodotti>) data class DatiVassoio (val idUtente : Long, val listaProdotti: List<ListaProdottiElement>)
@Serializable
data class Prodotti (val id: Long, val quantita: Int)