lot of new functionalities

This commit is contained in:
Arnaud Delcasse
2025-10-14 18:11:13 +02:00
parent a6f70a6e85
commit d992a7984f
164 changed files with 15113 additions and 9442 deletions

View File

@@ -0,0 +1,273 @@
package xlsx
import (
"fmt"
"net/http"
"git.coopgo.io/coopgo-apps/parcoursmob/core/application"
"git.coopgo.io/coopgo-apps/parcoursmob/core/utils/gender"
"github.com/rs/zerolog/log"
)
func (r *XLSXRenderer) OrganizedCarpoolBookings(w http.ResponseWriter, result *application.OrganizedCarpoolBookingsResult) {
// Create Excel spreadsheet
spreadsheet := r.NewSpreadsheet("Covoiturage solidaire")
// Build headers dynamically based on configuration
headers := []string{
"ID Réservation",
"Statut",
"Motif de réservation",
"Date de prise en charge",
"Heure de prise en charge",
}
// Add driver fields from config
driverOptionalFields := r.Config.Get("modules.organized_carpool.profile_optional_fields")
driverFields := []string{"last_name", "first_name", "email", "phone_number"}
driverHeaders := []string{"Conducteur - Nom", "Conducteur - Prénom", "Conducteur - Email", "Conducteur - Téléphone"}
if driverOptionalFieldsList, ok := driverOptionalFields.([]interface{}); ok {
for _, field := range driverOptionalFieldsList {
if fieldMap, ok := field.(map[string]interface{}); ok {
if name, ok := fieldMap["name"].(string); ok {
driverFields = append(driverFields, name)
label := name
if labelVal, ok := fieldMap["label"].(string); ok {
label = labelVal
}
driverHeaders = append(driverHeaders, fmt.Sprintf("Conducteur - %s", label))
}
}
}
}
headers = append(headers, driverHeaders...)
// Add beneficiary fields from config
beneficiaryOptionalFields := r.Config.Get("modules.beneficiaries.profile_optional_fields")
beneficiaryFields := []string{"last_name", "first_name", "email", "phone_number"}
beneficiaryHeaders := []string{"Bénéficiaire - Nom", "Bénéficiaire - Prénom", "Bénéficiaire - Email", "Bénéficiaire - Téléphone"}
if beneficiaryOptionalFieldsList, ok := beneficiaryOptionalFields.([]interface{}); ok {
for _, field := range beneficiaryOptionalFieldsList {
if fieldMap, ok := field.(map[string]interface{}); ok {
if name, ok := fieldMap["name"].(string); ok {
beneficiaryFields = append(beneficiaryFields, name)
label := name
if labelVal, ok := fieldMap["label"].(string); ok {
label = labelVal
}
beneficiaryHeaders = append(beneficiaryHeaders, fmt.Sprintf("Bénéficiaire - %s", label))
}
}
}
}
headers = append(headers, beneficiaryHeaders...)
// Add journey information headers
headers = append(headers,
"Lieu de départ - Adresse",
"Destination - Adresse",
"Distance passager (km)",
"Durée trajet (minutes)",
"Prix passager",
"Devise prix passager",
"Compensation conducteur",
"Devise compensation",
)
spreadsheet.SetHeaders(headers)
// Add data rows
for _, booking := range result.Bookings {
driver := result.DriversMap[booking.Driver.Id]
beneficiary := result.BeneficiariesMap[booking.Passenger.Id]
row := []interface{}{}
// Booking information
row = append(row, booking.Id)
row = append(row, booking.Status.String())
// Motivation
motivation := ""
if booking.Motivation != nil {
motivation = *booking.Motivation
}
row = append(row, motivation)
// Journey date and time
row = append(row, booking.PassengerPickupDate.AsTime().Format("2006-01-02"))
row = append(row, booking.PassengerPickupDate.AsTime().Format("15:04"))
// Driver data
for _, field := range driverFields {
row = append(row, getAccountFieldValue(driver.Data, field))
}
// Beneficiary data
for _, field := range beneficiaryFields {
row = append(row, getAccountFieldValue(beneficiary.Data, field))
}
// Journey information
if booking.PassengerPickupAddress != nil {
row = append(row, *booking.PassengerPickupAddress)
} else {
row = append(row, "")
}
if booking.PassengerDropAddress != nil {
row = append(row, *booking.PassengerDropAddress)
} else {
row = append(row, "")
}
// Distance
if booking.Distance != nil {
row = append(row, *booking.Distance)
} else {
row = append(row, "")
}
// Duration
if booking.Duration != nil {
row = append(row, *booking.Duration)
} else {
row = append(row, "")
}
// Pricing
if booking.Price != nil && booking.Price.Amount != nil {
row = append(row, fmt.Sprintf("%.2f", *booking.Price.Amount))
if booking.Price.Currency != nil {
row = append(row, *booking.Price.Currency)
} else {
row = append(row, "")
}
} else {
row = append(row, "", "")
}
// Driver compensation
if booking.DriverCompensationAmount != nil {
row = append(row, fmt.Sprintf("%.2f", *booking.DriverCompensationAmount))
if booking.DriverCompensationCurrency != nil {
row = append(row, *booking.DriverCompensationCurrency)
} else {
row = append(row, "")
}
} else {
row = append(row, "", "")
}
spreadsheet.AddRow(row)
}
// Write Excel to response
w.Header().Set("Content-Type", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
w.Header().Set("Content-Disposition", "attachment; filename=\"export-covoiturage-solidaire.xlsx\"")
if err := spreadsheet.GetFile().Write(w); err != nil {
log.Error().Err(err).Msg("Error generating Excel file")
http.Error(w, "Error generating Excel file", http.StatusInternalServerError)
return
}
}
func (r *XLSXRenderer) OrganizedCarpoolDrivers(w http.ResponseWriter, result *application.OrganizedCarpoolOverviewResult) {
// Create Excel spreadsheet
spreadsheet := r.NewSpreadsheet("Covoitureurs solidaires")
// Build headers dynamically based on configuration
driverOptionalFields := r.Config.Get("modules.organized_carpool.drivers.profile_optional_fields")
driverFields := []string{"last_name", "first_name", "email", "phone_number", "birthdate", "gender", "file_number"}
headers := []string{"ID", "Nom", "Prénom", "Email", "Téléphone", "Date de naissance", "Genre", "Numéro de dossier"}
if driverOptionalFieldsList, ok := driverOptionalFields.([]interface{}); ok {
for _, field := range driverOptionalFieldsList {
if fieldMap, ok := field.(map[string]interface{}); ok {
if name, ok := fieldMap["name"].(string); ok {
driverFields = append(driverFields, name)
label := name
if labelVal, ok := fieldMap["label"].(string); ok {
label = labelVal
}
headers = append(headers, label)
}
}
}
}
// Add address columns
headers = append(headers, "Adresse départ", "Adresse destination", "Archivé")
spreadsheet.SetHeaders(headers)
// Add data rows
for _, driver := range result.Accounts {
row := []interface{}{}
// Driver ID
row = append(row, driver.ID)
// Driver data
for _, field := range driverFields {
value := getAccountFieldValue(driver.Data, field)
// Convert gender code to text
if field == "gender" && value != "" {
value = gender.ISO5218ToString(value)
}
row = append(row, value)
}
// Address departure
addressDeparture := ""
if addr, ok := driver.Data["address"]; ok {
if addrMap, ok := addr.(map[string]interface{}); ok {
if props, ok := addrMap["properties"]; ok {
if propsMap, ok := props.(map[string]interface{}); ok {
if label, ok := propsMap["label"].(string); ok {
addressDeparture = label
}
}
}
}
}
row = append(row, addressDeparture)
// Address destination
addressDestination := ""
if addr, ok := driver.Data["address_destination"]; ok {
if addrMap, ok := addr.(map[string]interface{}); ok {
if props, ok := addrMap["properties"]; ok {
if propsMap, ok := props.(map[string]interface{}); ok {
if label, ok := propsMap["label"].(string); ok {
addressDestination = label
}
}
}
}
}
row = append(row, addressDestination)
// Archived status
archived := "Non"
if archivedVal, ok := driver.Data["archived"].(bool); ok && archivedVal {
archived = "Oui"
}
row = append(row, archived)
spreadsheet.AddRow(row)
}
// Write Excel to response
w.Header().Set("Content-Type", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
w.Header().Set("Content-Disposition", "attachment; filename=\"export-covoitureurs-solidaires.xlsx\"")
if err := spreadsheet.GetFile().Write(w); err != nil {
log.Error().Err(err).Msg("Error generating Excel file")
http.Error(w, "Error generating Excel file", http.StatusInternalServerError)
return
}
}

View File

@@ -0,0 +1,320 @@
package xlsx
import (
"fmt"
"net/http"
"git.coopgo.io/coopgo-apps/parcoursmob/core/application"
"git.coopgo.io/coopgo-apps/parcoursmob/core/utils/gender"
"github.com/paulmach/orb/geojson"
"github.com/rs/zerolog/log"
)
func (r *XLSXRenderer) SolidarityTransportBookings(w http.ResponseWriter, result *application.SolidarityTransportBookingsResult) {
// Create Excel spreadsheet
spreadsheet := r.NewSpreadsheet("Transport solidaire")
// Build headers dynamically based on configuration
headers := []string{
"ID Réservation",
"Statut",
"Motif de réservation",
"Raison d'annulation",
"Date de prise en charge",
"Heure de prise en charge",
}
// Add driver fields from config
driverOptionalFields := r.Config.Get("modules.solidarity_transport.profile_optional_fields")
driverFields := []string{"last_name", "first_name", "email", "phone_number"}
driverHeaders := []string{"Conducteur - Nom", "Conducteur - Prénom", "Conducteur - Email", "Conducteur - Téléphone"}
if driverOptionalFieldsList, ok := driverOptionalFields.([]interface{}); ok {
for _, field := range driverOptionalFieldsList {
if fieldMap, ok := field.(map[string]interface{}); ok {
if name, ok := fieldMap["name"].(string); ok {
driverFields = append(driverFields, name)
label := name
if labelVal, ok := fieldMap["label"].(string); ok {
label = labelVal
}
driverHeaders = append(driverHeaders, fmt.Sprintf("Conducteur - %s", label))
}
}
}
}
headers = append(headers, driverHeaders...)
// Add beneficiary fields from config
beneficiaryOptionalFields := r.Config.Get("modules.beneficiaries.profile_optional_fields")
beneficiaryFields := []string{"last_name", "first_name", "email", "phone_number"}
beneficiaryHeaders := []string{"Bénéficiaire - Nom", "Bénéficiaire - Prénom", "Bénéficiaire - Email", "Bénéficiaire - Téléphone"}
if beneficiaryOptionalFieldsList, ok := beneficiaryOptionalFields.([]interface{}); ok {
for _, field := range beneficiaryOptionalFieldsList {
if fieldMap, ok := field.(map[string]interface{}); ok {
if name, ok := fieldMap["name"].(string); ok {
beneficiaryFields = append(beneficiaryFields, name)
label := name
if labelVal, ok := fieldMap["label"].(string); ok {
label = labelVal
}
beneficiaryHeaders = append(beneficiaryHeaders, fmt.Sprintf("Bénéficiaire - %s", label))
}
}
}
}
headers = append(headers, beneficiaryHeaders...)
// Add journey information headers
headers = append(headers,
"Lieu de départ - Adresse",
"Lieu de départ - Latitude",
"Lieu de départ - Longitude",
"Destination - Adresse",
"Destination - Latitude",
"Destination - Longitude",
"Distance passager (km)",
"Distance conducteur totale (km)",
"Durée trajet (minutes)",
"Prix passager",
"Devise prix passager",
"Compensation conducteur",
"Devise compensation",
"Temps d'attente retour",
"Aller simple",
"Départ conducteur - Adresse",
"Départ conducteur - Latitude",
"Départ conducteur - Longitude",
"Arrivée conducteur - Adresse",
"Arrivée conducteur - Latitude",
"Arrivée conducteur - Longitude",
)
spreadsheet.SetHeaders(headers)
// Add data rows
for _, booking := range result.Bookings {
driver := result.DriversMap[booking.DriverId]
beneficiary := result.BeneficiariesMap[booking.PassengerId]
row := []interface{}{}
// Booking information
row = append(row, booking.Id)
row = append(row, booking.Status)
// Motivation (from booking.Data)
motivation := ""
if booking.Data != nil {
if motivationVal, ok := booking.Data["motivation"]; ok && motivationVal != nil {
motivation = fmt.Sprint(motivationVal)
}
}
row = append(row, motivation)
// Cancellation reason (from booking.Data)
cancellationReason := ""
if booking.Data != nil {
if reasonVal, ok := booking.Data["reason"]; ok && reasonVal != nil {
cancellationReason = fmt.Sprint(reasonVal)
}
}
row = append(row, cancellationReason)
// Journey date and time
if booking.Journey != nil {
row = append(row, booking.Journey.PassengerPickupDate.Format("2006-01-02"))
row = append(row, booking.Journey.PassengerPickupDate.Format("15:04"))
} else {
row = append(row, "", "")
}
// Driver data
for _, field := range driverFields {
row = append(row, getAccountFieldValue(driver.Data, field))
}
// Beneficiary data
for _, field := range beneficiaryFields {
row = append(row, getAccountFieldValue(beneficiary.Data, field))
}
// Journey locations and details
if booking.Journey != nil {
// Passenger pickup
pickupAddr, pickupLat, pickupLon := getLocationData(booking.Journey.PassengerPickup)
row = append(row, pickupAddr, pickupLat, pickupLon)
// Passenger drop
dropAddr, dropLat, dropLon := getLocationData(booking.Journey.PassengerDrop)
row = append(row, dropAddr, dropLat, dropLon)
// Distances and duration
row = append(row, booking.Journey.PassengerDistance)
row = append(row, booking.Journey.DriverDistance)
row = append(row, int64(booking.Journey.Duration.Minutes()))
// Pricing
row = append(row, fmt.Sprintf("%.2f", booking.Journey.Price.Amount))
row = append(row, booking.Journey.Price.Currency)
// Driver compensation
if booking.DriverCompensationAmount > 0 {
row = append(row, fmt.Sprintf("%.2f", booking.DriverCompensationAmount))
row = append(row, booking.DriverCompensationCurrency)
} else {
row = append(row, "", "")
}
// Return wait time
row = append(row, "")
// One way trip (Noreturn field)
if booking.Journey.Noreturn {
row = append(row, "Oui")
} else {
row = append(row, "Non")
}
// Driver departure
driverDepartAddr, driverDepartLat, driverDepartLon := getLocationData(booking.Journey.DriverDeparture)
row = append(row, driverDepartAddr, driverDepartLat, driverDepartLon)
// Driver arrival
driverArrivalAddr, driverArrivalLat, driverArrivalLon := getLocationData(booking.Journey.DriverArrival)
row = append(row, driverArrivalAddr, driverArrivalLat, driverArrivalLon)
} else {
// No journey data - fill with empty values
for i := 0; i < 21; i++ {
row = append(row, "")
}
}
spreadsheet.AddRow(row)
}
// Write Excel to response
w.Header().Set("Content-Type", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
w.Header().Set("Content-Disposition", "attachment; filename=\"export-transport-solidaire.xlsx\"")
if err := spreadsheet.GetFile().Write(w); err != nil {
log.Error().Err(err).Msg("Error generating Excel file")
http.Error(w, "Error generating Excel file", http.StatusInternalServerError)
return
}
}
func getAccountFieldValue(data map[string]interface{}, field string) string {
// First check direct field
if val, ok := data[field]; ok && val != nil {
return fmt.Sprint(val)
}
// Check in other_properties
if otherProps, ok := data["other_properties"]; ok {
if otherPropsMap, ok := otherProps.(map[string]interface{}); ok {
if val, ok := otherPropsMap[field]; ok && val != nil {
return fmt.Sprint(val)
}
}
}
return ""
}
func getLocationData(feature *geojson.Feature) (address string, lat interface{}, lon interface{}) {
if feature != nil && feature.Properties != nil {
if label, ok := feature.Properties["label"].(string); ok {
address = label
}
if feature.Geometry != nil {
coords := feature.Geometry.Bound().Center()
lat = fmt.Sprintf("%.6f", coords.Lat())
lon = fmt.Sprintf("%.6f", coords.Lon())
}
}
return
}
func (r *XLSXRenderer) SolidarityTransportDrivers(w http.ResponseWriter, result *application.SolidarityTransportOverviewResult) {
// Create Excel spreadsheet
spreadsheet := r.NewSpreadsheet("Conducteurs solidaires")
// Build headers dynamically based on configuration
driverOptionalFields := r.Config.Get("modules.solidarity_transport.drivers.profile_optional_fields")
driverFields := []string{"last_name", "first_name", "email", "phone_number", "birthdate", "gender", "file_number"}
headers := []string{"ID", "Nom", "Prénom", "Email", "Téléphone", "Date de naissance", "Genre", "Numéro de dossier"}
if driverOptionalFieldsList, ok := driverOptionalFields.([]interface{}); ok {
for _, field := range driverOptionalFieldsList {
if fieldMap, ok := field.(map[string]interface{}); ok {
if name, ok := fieldMap["name"].(string); ok {
driverFields = append(driverFields, name)
label := name
if labelVal, ok := fieldMap["label"].(string); ok {
label = labelVal
}
headers = append(headers, label)
}
}
}
}
// Add address and archived columns
headers = append(headers, "Adresse", "Archivé")
spreadsheet.SetHeaders(headers)
// Add data rows
for _, driver := range result.Accounts {
row := []interface{}{}
// Driver ID
row = append(row, driver.ID)
// Driver data
for _, field := range driverFields {
value := getAccountFieldValue(driver.Data, field)
// Convert gender code to text
if field == "gender" && value != "" {
value = gender.ISO5218ToString(value)
}
row = append(row, value)
}
// Address
address := ""
if addr, ok := driver.Data["address"]; ok {
if addrMap, ok := addr.(map[string]interface{}); ok {
if props, ok := addrMap["properties"]; ok {
if propsMap, ok := props.(map[string]interface{}); ok {
if label, ok := propsMap["label"].(string); ok {
address = label
}
}
}
}
}
row = append(row, address)
// Archived status
archived := "Non"
if archivedVal, ok := driver.Data["archived"].(bool); ok && archivedVal {
archived = "Oui"
}
row = append(row, archived)
spreadsheet.AddRow(row)
}
// Write Excel to response
w.Header().Set("Content-Type", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
w.Header().Set("Content-Disposition", "attachment; filename=\"export-conducteurs-solidaires.xlsx\"")
if err := spreadsheet.GetFile().Write(w); err != nil {
log.Error().Err(err).Msg("Error generating Excel file")
http.Error(w, "Error generating Excel file", http.StatusInternalServerError)
return
}
}

View File

@@ -0,0 +1,350 @@
package xlsx
import (
"fmt"
"net/http"
fleetsstorage "git.coopgo.io/coopgo-platform/fleets/storage"
groupsstorage "git.coopgo.io/coopgo-platform/groups-management/storage"
mobilityaccountsstorage "git.coopgo.io/coopgo-platform/mobility-accounts/storage"
"github.com/rs/zerolog/log"
)
func (r *XLSXRenderer) VehicleBookings(w http.ResponseWriter, bookings []fleetsstorage.Booking, vehiclesMap map[string]fleetsstorage.Vehicle, driversMap map[string]mobilityaccountsstorage.Account) {
// Create Excel spreadsheet
spreadsheet := r.NewSpreadsheet("Réservations véhicules")
// Build headers
headers := []string{
"ID Réservation",
"Statut",
"Type de véhicule",
"Nom du véhicule",
"Immatriculation",
}
// Add beneficiary fields from config
beneficiaryOptionalFields := r.Config.Get("modules.beneficiaries.profile_optional_fields")
beneficiaryFields := []string{"last_name", "first_name", "email", "phone_number"}
beneficiaryHeaders := []string{"Bénéficiaire - Nom", "Bénéficiaire - Prénom", "Bénéficiaire - Email", "Bénéficiaire - Téléphone"}
if beneficiaryOptionalFieldsList, ok := beneficiaryOptionalFields.([]interface{}); ok {
for _, field := range beneficiaryOptionalFieldsList {
if fieldMap, ok := field.(map[string]interface{}); ok {
if name, ok := fieldMap["name"].(string); ok {
beneficiaryFields = append(beneficiaryFields, name)
label := name
if labelVal, ok := fieldMap["label"].(string); ok {
label = labelVal
}
beneficiaryHeaders = append(beneficiaryHeaders, fmt.Sprintf("Bénéficiaire - %s", label))
}
}
}
}
headers = append(headers, beneficiaryHeaders...)
// Add booking date headers
headers = append(headers,
"Date de début",
"Date de fin",
"Durée (jours)",
"Commentaire",
"Raison d'annulation",
)
spreadsheet.SetHeaders(headers)
// Add data rows
for _, booking := range bookings {
vehicle := vehiclesMap[booking.Vehicleid]
beneficiary := driversMap[booking.Driver]
row := []interface{}{}
// Booking information
row = append(row, booking.ID)
// Status
status := ""
if booking.Deleted {
status = "Annulé"
} else {
switch booking.Status() {
case 1:
status = "A venir"
case 0:
status = "En cours"
case -1:
status = "Terminé"
}
}
row = append(row, status)
// Vehicle information
row = append(row, vehicle.Type)
if vehicleName, ok := vehicle.Data["name"].(string); ok {
row = append(row, vehicleName)
} else {
row = append(row, "")
}
if licencePlate, ok := vehicle.Data["licence_plate"].(string); ok {
row = append(row, licencePlate)
} else {
row = append(row, "")
}
// Beneficiary data (including other_properties)
for _, field := range beneficiaryFields {
value := ""
// First check direct field
if val, ok := beneficiary.Data[field]; ok && val != nil {
value = fmt.Sprint(val)
} else {
// Check in other_properties
if otherProps, ok := beneficiary.Data["other_properties"]; ok {
if otherPropsMap, ok := otherProps.(map[string]interface{}); ok {
if val, ok := otherPropsMap[field]; ok && val != nil {
value = fmt.Sprint(val)
}
}
}
}
row = append(row, value)
}
// Booking dates
row = append(row, booking.Startdate.Format("2006-01-02"))
row = append(row, booking.Enddate.Format("2006-01-02"))
// Duration in days
duration := booking.Enddate.Sub(booking.Startdate).Hours() / 24
row = append(row, fmt.Sprintf("%.0f", duration))
// Comment
comment := ""
if booking.Data != nil {
if commentVal, ok := booking.Data["comment"]; ok && commentVal != nil {
comment = fmt.Sprint(commentVal)
}
}
row = append(row, comment)
// Cancellation reason
cancellationReason := ""
if booking.Deleted && booking.Data != nil {
if reasonVal, ok := booking.Data["reason"]; ok && reasonVal != nil {
cancellationReason = fmt.Sprint(reasonVal)
}
}
row = append(row, cancellationReason)
spreadsheet.AddRow(row)
}
// Write Excel to response
w.Header().Set("Content-Type", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
w.Header().Set("Content-Disposition", "attachment; filename=\"export-reservations-vehicules.xlsx\"")
if err := spreadsheet.GetFile().Write(w); err != nil {
log.Error().Err(err).Msg("Error generating Excel file")
http.Error(w, "Error generating Excel file", http.StatusInternalServerError)
return
}
}
func (r *XLSXRenderer) VehicleBookingsAdmin(w http.ResponseWriter, bookings []fleetsstorage.Booking, vehiclesMap map[string]interface{}, driversMap map[string]interface{}, groupsMap map[string]any) {
// Create Excel spreadsheet
spreadsheet := r.NewSpreadsheet("Réservations véhicules")
// Build headers
headers := []string{
"ID Réservation",
"Statut",
"Date de début",
"Date de fin",
"Durée (jours)",
"Commentaire",
"Raison d'annulation",
"Type de véhicule",
"Nom du véhicule",
"Immatriculation",
"Gestionnaire véhicule",
}
// Add vehicle optional fields from config
vehicleOptionalFields := r.Config.Get("modules.fleets.vehicle_optional_fields")
vehicleFields := []string{}
vehicleHeaders := []string{}
if vehicleOptionalFieldsList, ok := vehicleOptionalFields.([]interface{}); ok {
for _, field := range vehicleOptionalFieldsList {
if fieldMap, ok := field.(map[string]interface{}); ok {
if name, ok := fieldMap["name"].(string); ok {
vehicleFields = append(vehicleFields, name)
label := name
if labelVal, ok := fieldMap["label"].(string); ok {
label = labelVal
}
vehicleHeaders = append(vehicleHeaders, fmt.Sprintf("Véhicule - %s", label))
}
}
}
}
headers = append(headers, vehicleHeaders...)
// Add beneficiary fields from config
beneficiaryOptionalFields := r.Config.Get("modules.beneficiaries.profile_optional_fields")
beneficiaryFields := []string{"last_name", "first_name", "email", "phone_number"}
beneficiaryHeaders := []string{"Bénéficiaire - Nom", "Bénéficiaire - Prénom", "Bénéficiaire - Email", "Bénéficiaire - Téléphone"}
if beneficiaryOptionalFieldsList, ok := beneficiaryOptionalFields.([]interface{}); ok {
for _, field := range beneficiaryOptionalFieldsList {
if fieldMap, ok := field.(map[string]interface{}); ok {
if name, ok := fieldMap["name"].(string); ok {
beneficiaryFields = append(beneficiaryFields, name)
label := name
if labelVal, ok := fieldMap["label"].(string); ok {
label = labelVal
}
beneficiaryHeaders = append(beneficiaryHeaders, fmt.Sprintf("Bénéficiaire - %s", label))
}
}
}
}
headers = append(headers, beneficiaryHeaders...)
spreadsheet.SetHeaders(headers)
// Add data rows
for _, booking := range bookings {
// Get vehicle from map
var vehicle fleetsstorage.Vehicle
if v, ok := vehiclesMap[booking.Vehicleid]; ok {
if vTyped, ok := v.(fleetsstorage.Vehicle); ok {
vehicle = vTyped
}
}
// Get beneficiary from map
var beneficiary mobilityaccountsstorage.Account
if d, ok := driversMap[booking.Driver]; ok {
if dTyped, ok := d.(mobilityaccountsstorage.Account); ok {
beneficiary = dTyped
}
}
row := []interface{}{}
// Booking information
row = append(row, booking.ID)
// Status
status := ""
if booking.Deleted {
status = "Annulé"
} else {
switch booking.Status() {
case 1:
status = "A venir"
case 0:
status = "En cours"
case -1:
status = "Terminé"
}
}
row = append(row, status)
// Booking dates
row = append(row, booking.Startdate.Format("2006-01-02"))
row = append(row, booking.Enddate.Format("2006-01-02"))
// Duration in days
duration := booking.Enddate.Sub(booking.Startdate).Hours() / 24
row = append(row, fmt.Sprintf("%.0f", duration))
// Comment
comment := ""
if booking.Data != nil {
if commentVal, ok := booking.Data["comment"]; ok && commentVal != nil {
comment = fmt.Sprint(commentVal)
}
}
row = append(row, comment)
// Cancellation reason
cancellationReason := ""
if booking.Deleted && booking.Data != nil {
if reasonVal, ok := booking.Data["reason"]; ok && reasonVal != nil {
cancellationReason = fmt.Sprint(reasonVal)
}
}
row = append(row, cancellationReason)
// Vehicle information
row = append(row, vehicle.Type)
if vehicleName, ok := vehicle.Data["name"].(string); ok {
row = append(row, vehicleName)
} else {
row = append(row, "")
}
if licencePlate, ok := vehicle.Data["licence_plate"].(string); ok {
row = append(row, licencePlate)
} else {
row = append(row, "")
}
// Vehicle administrator (group name)
administratorName := ""
if len(vehicle.Administrators) > 0 {
if group, ok := groupsMap[vehicle.Administrators[0]]; ok {
if groupTyped, ok := group.(groupsstorage.Group); ok {
if name, ok := groupTyped.Data["name"]; ok {
administratorName = fmt.Sprint(name)
}
}
}
}
row = append(row, administratorName)
// Vehicle optional fields
for _, field := range vehicleFields {
value := ""
if val, ok := vehicle.Data[field]; ok && val != nil {
value = fmt.Sprint(val)
}
row = append(row, value)
}
// Beneficiary data (including other_properties)
for _, field := range beneficiaryFields {
value := ""
// First check direct field
if val, ok := beneficiary.Data[field]; ok && val != nil {
value = fmt.Sprint(val)
} else {
// Check in other_properties
if otherProps, ok := beneficiary.Data["other_properties"]; ok {
if otherPropsMap, ok := otherProps.(map[string]interface{}); ok {
if val, ok := otherPropsMap[field]; ok && val != nil {
value = fmt.Sprint(val)
}
}
}
}
row = append(row, value)
}
spreadsheet.AddRow(row)
}
// Write Excel to response
w.Header().Set("Content-Type", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
w.Header().Set("Content-Disposition", "attachment; filename=\"all_bookings.xlsx\"")
if err := spreadsheet.GetFile().Write(w); err != nil {
log.Error().Err(err).Msg("Error generating Excel file")
http.Error(w, "Error generating Excel file", http.StatusInternalServerError)
return
}
}

60
renderer/xlsx/xlsx.go Normal file
View File

@@ -0,0 +1,60 @@
package xlsx
import (
"github.com/spf13/viper"
"github.com/xuri/excelize/v2"
)
// XLSXRenderer handles rendering data to Excel XLSX format
type XLSXRenderer struct {
Config *viper.Viper
}
func NewXLSXRenderer(config *viper.Viper) *XLSXRenderer {
return &XLSXRenderer{
Config: config,
}
}
// Spreadsheet represents an Excel spreadsheet
type Spreadsheet struct {
file *excelize.File
sheetName string
rowIndex int
}
// NewSpreadsheet creates a new Excel spreadsheet
func (r *XLSXRenderer) NewSpreadsheet(sheetName string) *Spreadsheet {
f := excelize.NewFile()
// Rename default sheet
f.SetSheetName("Sheet1", sheetName)
return &Spreadsheet{
file: f,
sheetName: sheetName,
rowIndex: 1,
}
}
// SetHeaders sets the header row
func (s *Spreadsheet) SetHeaders(headers []string) {
for i, header := range headers {
cell, _ := excelize.CoordinatesToCellName(i+1, s.rowIndex)
s.file.SetCellValue(s.sheetName, cell, header)
}
s.rowIndex++
}
// AddRow adds a data row
func (s *Spreadsheet) AddRow(values []interface{}) {
for i, value := range values {
cell, _ := excelize.CoordinatesToCellName(i+1, s.rowIndex)
s.file.SetCellValue(s.sheetName, cell, value)
}
s.rowIndex++
}
// GetFile returns the underlying excelize File
func (s *Spreadsheet) GetFile() *excelize.File {
return s.file
}