Compare commits
9 Commits
b5e722ff9b
...
039111c36c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
039111c36c | ||
|
|
95365ff8ce | ||
|
|
b79cc08b06 | ||
|
|
bb525f174d | ||
|
|
8d89306a90 | ||
|
|
549ea35a8c | ||
|
|
a60466d891 | ||
|
|
092d1acfbd | ||
|
|
1b1c4443fc |
25
config.go
25
config.go
@@ -231,7 +231,26 @@ func ReadConfig() (*viper.Viper, error) {
|
||||
},
|
||||
},
|
||||
"vehicles": map[string]any{
|
||||
"enabled": true,
|
||||
"enabled": true,
|
||||
"default_booking_duration_days": 90,
|
||||
"status_management": "automatic",
|
||||
"booking_extra_properties": []map[string]any{
|
||||
{"name": "start_kilometers", "label": "Kilométrage de départ", "type": "number"},
|
||||
{"name": "enddate", "label": "Date et heure de restitution", "type": "datetime-local", "target": "enddate"},
|
||||
{"name": "end_kilometers", "label": "Kilométrage de fin", "type": "number"},
|
||||
{"name": "kilometers_done", "label": "Kilomètres réalisés", "type": "computed", "operation": "subtract", "operands": []string{"end_kilometers", "start_kilometers"}, "unit": "km"},
|
||||
{"name": "loan_duration", "label": "Durée du prêt", "type": "computed", "operation": "duration", "operands": []string{"booking.startdate", "booking.enddate"}},
|
||||
{"name": "unavailableto", "label": "Sera à nouveau disponible le", "type": "date", "target": "unavailableto"},
|
||||
},
|
||||
"status_options": []map[string]any{
|
||||
{"name": "requested", "label": "Demandé", "initial": true, "meta_status": "open"},
|
||||
{"name": "accepted", "label": "Accepté", "meta_status": "active"},
|
||||
{"name": "en_pret", "label": "En prêt", "meta_status": "active", "requested_properties": []map[string]any{{"name": "start_kilometers", "required": true}, {"name": "enddate"}}},
|
||||
{"name": "completed", "label": "Terminé", "meta_status": "closed", "requested_properties": []map[string]any{{"name": "end_kilometers", "required": true}, {"name": "unavailableto"}}},
|
||||
{"name": "refused", "label": "Refusé", "meta_status": "closed"},
|
||||
{"name": "cancelled", "label": "Annulé", "meta_status": "closed"},
|
||||
{"name": "not_completed", "label": "Non réalisé", "meta_status": "closed"},
|
||||
},
|
||||
},
|
||||
"vehicles_management": map[string]any{
|
||||
"enabled": true,
|
||||
@@ -254,11 +273,11 @@ func ReadConfig() (*viper.Viper, error) {
|
||||
"geo": map[string]any{
|
||||
"type": "addok", // Options: "pelias", "addok"
|
||||
"pelias": map[string]any{
|
||||
"url": "https://geocode.ridygo.fr",
|
||||
"url": "http://57.128.110.46:4000/V1",
|
||||
"autocomplete": "/autocomplete?text=",
|
||||
},
|
||||
"addok": map[string]any{
|
||||
"url": "https://api-adresse.data.gouv.fr",
|
||||
"url": "https://data.geopf.fr/geocodage/",
|
||||
"autocomplete": "/search/?q=",
|
||||
},
|
||||
},
|
||||
|
||||
@@ -31,6 +31,8 @@ import (
|
||||
solidaritytransformers "git.coopgo.io/coopgo-platform/solidarity-transport/servers/grpc/transformers"
|
||||
solidaritytypes "git.coopgo.io/coopgo-platform/solidarity-transport/types"
|
||||
"github.com/google/uuid"
|
||||
"github.com/paulmach/orb"
|
||||
"github.com/paulmach/orb/geojson"
|
||||
"github.com/rs/zerolog/log"
|
||||
"google.golang.org/protobuf/types/known/structpb"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
@@ -41,12 +43,38 @@ type BeneficiariesResult struct {
|
||||
CacheID string
|
||||
}
|
||||
|
||||
func (h *ApplicationHandler) GetBeneficiaries(ctx context.Context, searchFilter string, archivedFilter bool) (*BeneficiariesResult, error) {
|
||||
func (h *ApplicationHandler) GetBeneficiaries(ctx context.Context, searchFilter string, archivedFilter bool, addressGeoLayer, addressGeoCode string) (*BeneficiariesResult, error) {
|
||||
accounts, err := h.getBeneficiariesWithFilters(ctx, searchFilter, archivedFilter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Apply address geography filtering
|
||||
if addressGeoLayer != "" && addressGeoCode != "" {
|
||||
addressPolygons, err := h.loadGeographyPolygon(addressGeoLayer, addressGeoCode)
|
||||
if err != nil {
|
||||
log.Warn().Err(err).Msg("failed to load beneficiary address geography filter")
|
||||
} else {
|
||||
filtered := []mobilityaccountsstorage.Account{}
|
||||
for _, account := range accounts {
|
||||
if addr, ok := account.Data["address"]; ok {
|
||||
jsonAddr, err := json.Marshal(addr)
|
||||
if err == nil {
|
||||
addrGeojson, err := geojson.UnmarshalFeature(jsonAddr)
|
||||
if err == nil && addrGeojson.Geometry != nil {
|
||||
if point, ok := addrGeojson.Geometry.(orb.Point); ok {
|
||||
if isPointInGeographies(point, addressPolygons) {
|
||||
filtered = append(filtered, account)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
accounts = filtered
|
||||
}
|
||||
}
|
||||
|
||||
sort.Sort(sorting.BeneficiariesByName(accounts))
|
||||
|
||||
cacheID := uuid.NewString()
|
||||
|
||||
@@ -36,16 +36,19 @@ type SearchJourneysResult struct {
|
||||
Drivers map[string]mobilityaccountsstorage.Account
|
||||
OrganizedCarpools []*carpoolproto.CarpoolServiceDriverJourney
|
||||
KnowledgeBaseResults []any
|
||||
DriverLastTrips map[string]time.Time // Map of driver ID to their last completed trip date
|
||||
LastTripDays int // Number of days to look back for last trips
|
||||
}
|
||||
|
||||
// SearchJourneyOptions contains per-request options for journey search
|
||||
type SearchJourneyOptions struct {
|
||||
DisableSolidarityTransport bool
|
||||
DisableOrganizedCarpool bool
|
||||
DisableCarpoolOperators bool
|
||||
DisableTransit bool
|
||||
DisableFleetVehicles bool
|
||||
DisableKnowledgeBase bool
|
||||
DisableSolidarityTransport bool
|
||||
DisableOrganizedCarpool bool
|
||||
DisableCarpoolOperators bool
|
||||
DisableTransit bool
|
||||
DisableFleetVehicles bool
|
||||
DisableKnowledgeBase bool
|
||||
SolidarityTransportNoreturn *bool
|
||||
}
|
||||
|
||||
// SearchJourneys performs the business logic for journey search
|
||||
@@ -111,6 +114,10 @@ func (h *ApplicationHandler) SearchJourneys(
|
||||
if solidarityExcludeGroupId != "" {
|
||||
req.ExcludeGroupId = &solidarityExcludeGroupId
|
||||
}
|
||||
// Pass noreturn to filter journeys by type (one-way vs round-trip)
|
||||
if options.SolidarityTransportNoreturn != nil {
|
||||
req.Noreturn = *options.SolidarityTransportNoreturn
|
||||
}
|
||||
|
||||
res, err := h.services.GRPC.SolidarityTransport.GetDriverJourneys(ctx, req)
|
||||
if err != nil {
|
||||
@@ -291,6 +298,33 @@ func (h *ApplicationHandler) SearchJourneys(
|
||||
}
|
||||
}
|
||||
|
||||
// Get last trip dates for solidarity transport drivers
|
||||
driverLastTrips := make(map[string]time.Time)
|
||||
lastTripDays := h.config.GetInt("modules.journeys.solutions.solidarity_transport.last_trip_days")
|
||||
if lastTripDays <= 0 {
|
||||
lastTripDays = 15
|
||||
}
|
||||
if len(solidarityTransportResults) > 0 {
|
||||
// Get all validated bookings from the past N days to find last trips
|
||||
bookingsRequest := &gen.GetSolidarityTransportBookingsRequest{
|
||||
StartDate: timestamppb.New(departureDateTime.Add(-time.Duration(lastTripDays) * 24 * time.Hour)),
|
||||
EndDate: timestamppb.New(departureDateTime.Add(24 * time.Hour)),
|
||||
Status: "VALIDATED",
|
||||
}
|
||||
bookingsResp, err := h.services.GRPC.SolidarityTransport.GetSolidarityTransportBookings(ctx, bookingsRequest)
|
||||
if err == nil {
|
||||
for _, booking := range bookingsResp.Bookings {
|
||||
if booking.Journey != nil {
|
||||
tripDate := booking.Journey.PassengerPickupDate.AsTime()
|
||||
// Only consider trips that have already happened
|
||||
if lastTrip, exists := driverLastTrips[booking.DriverId]; !exists || tripDate.After(lastTrip) {
|
||||
driverLastTrips[booking.DriverId] = tripDate
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return &SearchJourneysResult{
|
||||
CarpoolResults: carpoolResults,
|
||||
TransitResults: transitResults,
|
||||
@@ -300,6 +334,8 @@ func (h *ApplicationHandler) SearchJourneys(
|
||||
Drivers: drivers,
|
||||
OrganizedCarpools: organizedCarpoolResults,
|
||||
KnowledgeBaseResults: knowledgeBaseResults,
|
||||
DriverLastTrips: driverLastTrips,
|
||||
LastTripDays: lastTripDays,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
47
core/application/search.go
Normal file
47
core/application/search.go
Normal file
@@ -0,0 +1,47 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
mobilityaccountsstorage "git.coopgo.io/coopgo-platform/mobility-accounts/storage"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
type GlobalSearchResult struct {
|
||||
Beneficiaries []mobilityaccountsstorage.Account
|
||||
SolidarityDrivers []mobilityaccountsstorage.Account
|
||||
OrganizedCarpoolDrivers []mobilityaccountsstorage.Account
|
||||
}
|
||||
|
||||
func (h *ApplicationHandler) GlobalSearch(ctx context.Context, query string) (*GlobalSearchResult, error) {
|
||||
result := &GlobalSearchResult{}
|
||||
|
||||
if h.config.GetBool("modules.beneficiaries.enabled") {
|
||||
beneficiaries, err := h.getBeneficiariesWithFilters(ctx, query, false)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("global search: error retrieving beneficiaries")
|
||||
} else {
|
||||
result.Beneficiaries = beneficiaries
|
||||
}
|
||||
}
|
||||
|
||||
if h.config.GetBool("modules.solidarity_transport.enabled") {
|
||||
drivers, err := h.solidarityDrivers(ctx, query, false)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("global search: error retrieving solidarity drivers")
|
||||
} else {
|
||||
result.SolidarityDrivers = drivers
|
||||
}
|
||||
}
|
||||
|
||||
if h.config.GetBool("modules.organized_carpool.enabled") {
|
||||
drivers, err := h.getOrganizedCarpoolDrivers(ctx, query, false)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("global search: error retrieving organized carpool drivers")
|
||||
} else {
|
||||
result.OrganizedCarpoolDrivers = drivers
|
||||
}
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
@@ -689,7 +689,7 @@ func (h *ApplicationHandler) UpdateSolidarityTransportDriver(ctx context.Context
|
||||
|
||||
type SolidarityTransportDriverDataResult struct {
|
||||
Driver mobilityaccountsstorage.Account
|
||||
Availabilities []*gen.DriverRegularAvailability
|
||||
Availabilities []*solidaritytypes.DriverRegularAvailability
|
||||
Documents []filestorage.FileInfo
|
||||
Bookings []*solidaritytypes.Booking
|
||||
BeneficiariesMap map[string]mobilityaccountsstorage.Account
|
||||
@@ -733,6 +733,12 @@ func (h *ApplicationHandler) GetSolidarityTransportDriverData(ctx context.Contex
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Convert proto availabilities to types with deserialized geojson address
|
||||
availabilities := []*solidaritytypes.DriverRegularAvailability{}
|
||||
for _, protoAvail := range availResp.Results {
|
||||
availabilities = append(availabilities, solidaritytransformers.DriverRegularAvailabilityProtoToType(protoAvail))
|
||||
}
|
||||
|
||||
// Get documents
|
||||
documents := h.filestorage.List(filestorage.PREFIX_SOLIDARITY_TRANSPORT_DRIVERS + "/" + driverID)
|
||||
|
||||
@@ -812,7 +818,7 @@ func (h *ApplicationHandler) GetSolidarityTransportDriverData(ctx context.Contex
|
||||
|
||||
return &SolidarityTransportDriverDataResult{
|
||||
Driver: driver,
|
||||
Availabilities: availResp.Results,
|
||||
Availabilities: availabilities,
|
||||
Documents: documents,
|
||||
Bookings: bookings,
|
||||
BeneficiariesMap: beneficiariesMap,
|
||||
@@ -1391,7 +1397,11 @@ func (h *ApplicationHandler) calculateSolidarityTransportPricing(ctx context.Con
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
history = history + len(solidarity.Bookings)
|
||||
for _, booking := range solidarity.Bookings {
|
||||
if booking.Status == "VALIDATED" || booking.Status == "WAITING_CONFIRMATION" {
|
||||
history++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var passengerGeo pricing.GeographyParams
|
||||
|
||||
@@ -3,7 +3,10 @@ package application
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.coopgo.io/coopgo-apps/parcoursmob/core/utils/identification"
|
||||
@@ -28,7 +31,7 @@ type VehiclesManagementOverviewResult struct {
|
||||
Bookings []fleetsstorage.Booking
|
||||
}
|
||||
|
||||
func (h *ApplicationHandler) GetVehiclesManagementOverview(ctx context.Context, groupID string) (*VehiclesManagementOverviewResult, error) {
|
||||
func (h *ApplicationHandler) GetVehiclesManagementOverview(ctx context.Context, groupID, vehicleType, status, dateStart, dateEnd, vType, vStatus string) (*VehiclesManagementOverviewResult, error) {
|
||||
request := &fleets.GetVehiclesRequest{
|
||||
Namespaces: []string{"parcoursmob"},
|
||||
}
|
||||
@@ -41,14 +44,82 @@ func (h *ApplicationHandler) GetVehiclesManagementOverview(ctx context.Context,
|
||||
bookings := []fleetsstorage.Booking{}
|
||||
vehiclesMap := map[string]fleetsstorage.Vehicle{}
|
||||
|
||||
isManualStatus := h.config.GetString("modules.vehicles.status_management") == "manual"
|
||||
|
||||
// Parse date filters
|
||||
var startdate time.Time
|
||||
if dateStart != "" {
|
||||
if parsed, err := time.Parse("2006-01-02", dateStart); err == nil {
|
||||
startdate = parsed
|
||||
}
|
||||
}
|
||||
var enddate time.Time
|
||||
if dateEnd != "" {
|
||||
if parsed, err := time.Parse("2006-01-02", dateEnd); err == nil {
|
||||
enddate = parsed.Add(24 * time.Hour)
|
||||
}
|
||||
}
|
||||
|
||||
for _, vehicle := range resp.Vehicles {
|
||||
if h.filterVehicleByGroup(vehicle, groupID) {
|
||||
v := vehicle.ToStorageType()
|
||||
vehicleBookings := []fleetsstorage.Booking{}
|
||||
for _, b := range v.Bookings {
|
||||
log.Debug().Any("booking", b).Msg("debug")
|
||||
if b.Status() != fleetsstorage.StatusOld {
|
||||
if !b.Deleted {
|
||||
if !b.Deleted {
|
||||
include := true
|
||||
|
||||
// Apply date filters (intersection: booking overlaps with [startdate, enddate])
|
||||
if !startdate.IsZero() && b.Enddate.Before(startdate) {
|
||||
include = false
|
||||
}
|
||||
if !enddate.IsZero() && b.Startdate.After(enddate) {
|
||||
include = false
|
||||
}
|
||||
|
||||
// Apply vehicle type filter on bookings
|
||||
if include && vehicleType != "" && v.Type != vehicleType {
|
||||
include = false
|
||||
}
|
||||
|
||||
// Apply status filter on bookings
|
||||
if include && status != "" {
|
||||
isAdminUnavail, _ := b.Data["administrator_unavailability"].(bool)
|
||||
if isAdminUnavail {
|
||||
include = false
|
||||
} else if isManualStatus {
|
||||
if strings.HasPrefix(status, "meta:") {
|
||||
metaParts := strings.Split(strings.TrimPrefix(status, "meta:"), ",")
|
||||
metaSet := map[string]bool{}
|
||||
for _, ms := range metaParts {
|
||||
metaSet[ms] = true
|
||||
}
|
||||
allowedStatuses := h.resolveMetaStatuses(metaSet)
|
||||
if !allowedStatuses[b.ManualStatus] {
|
||||
include = false
|
||||
}
|
||||
} else if b.ManualStatus != status {
|
||||
include = false
|
||||
}
|
||||
} else {
|
||||
var filterStatusInt int
|
||||
switch status {
|
||||
case "FORTHCOMING":
|
||||
filterStatusInt = 1
|
||||
case "ONGOING":
|
||||
filterStatusInt = 0
|
||||
case "TERMINATED":
|
||||
filterStatusInt = -1
|
||||
default:
|
||||
filterStatusInt = 999
|
||||
}
|
||||
if b.Status() != filterStatusInt {
|
||||
include = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if include {
|
||||
bookings = append(bookings, b)
|
||||
}
|
||||
}
|
||||
@@ -57,8 +128,48 @@ func (h *ApplicationHandler) GetVehiclesManagementOverview(ctx context.Context,
|
||||
}
|
||||
}
|
||||
v.Bookings = vehicleBookings
|
||||
vehicles = append(vehicles, v)
|
||||
|
||||
// Always add to vehiclesMap for booking lookups
|
||||
vehiclesMap[v.ID] = v
|
||||
|
||||
// Apply vehicle type filter
|
||||
if vType != "" && v.Type != vType {
|
||||
continue
|
||||
}
|
||||
|
||||
// Apply vehicle reservation status filter
|
||||
if vStatus != "" {
|
||||
hasActive := false
|
||||
hasUpcoming := false
|
||||
hasRetired := false
|
||||
for _, b := range v.Bookings {
|
||||
isAdminUnavail, _ := b.Data["administrator_unavailability"].(bool)
|
||||
if isAdminUnavail {
|
||||
hasRetired = true
|
||||
} else if b.Status() == 0 {
|
||||
hasActive = true
|
||||
} else if b.Status() == 1 {
|
||||
hasUpcoming = true
|
||||
}
|
||||
}
|
||||
|
||||
match := false
|
||||
switch vStatus {
|
||||
case "DISPONIBLE":
|
||||
match = !hasActive && !hasUpcoming && !hasRetired
|
||||
case "RESERVE":
|
||||
match = hasUpcoming
|
||||
case "EN_PRET":
|
||||
match = hasActive
|
||||
case "RETIRE":
|
||||
match = hasRetired
|
||||
}
|
||||
if !match {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
vehicles = append(vehicles, v)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,7 +206,7 @@ type VehiclesManagementBookingsListResult struct {
|
||||
CacheID string
|
||||
}
|
||||
|
||||
func (h *ApplicationHandler) GetVehiclesManagementBookingsList(ctx context.Context, groupID, status, startDate, endDate string) (*VehiclesManagementBookingsListResult, error) {
|
||||
func (h *ApplicationHandler) GetVehiclesManagementBookingsList(ctx context.Context, groupID, status, startDate, endDate, vehicleType string) (*VehiclesManagementBookingsListResult, error) {
|
||||
request := &fleets.GetVehiclesRequest{
|
||||
Namespaces: []string{"parcoursmob"},
|
||||
IncludeDeleted: true,
|
||||
@@ -129,40 +240,61 @@ func (h *ApplicationHandler) GetVehiclesManagementBookingsList(ctx context.Conte
|
||||
v := vehicle.ToStorageType()
|
||||
vehiclesMap[v.ID] = v
|
||||
for _, b := range v.Bookings {
|
||||
if v, ok := b.Data["administrator_unavailability"].(bool); !ok || !v {
|
||||
if isAdminUnavail, ok := b.Data["administrator_unavailability"].(bool); !ok || !isAdminUnavail {
|
||||
// Apply vehicle type filter
|
||||
if vehicleType != "" && v.Type != vehicleType {
|
||||
continue
|
||||
}
|
||||
|
||||
// Apply status filter
|
||||
if status != "" {
|
||||
bookingStatus := b.Status()
|
||||
statusInt := 0
|
||||
|
||||
if b.Deleted {
|
||||
statusInt = -2 // Use -2 for cancelled to distinguish from terminated
|
||||
if h.config.GetString("modules.vehicles.status_management") == "manual" {
|
||||
if strings.HasPrefix(status, "meta:") {
|
||||
metaParts := strings.Split(strings.TrimPrefix(status, "meta:"), ",")
|
||||
metaSet := map[string]bool{}
|
||||
for _, ms := range metaParts {
|
||||
metaSet[ms] = true
|
||||
}
|
||||
allowedStatuses := h.resolveMetaStatuses(metaSet)
|
||||
if !allowedStatuses[b.ManualStatus] {
|
||||
continue
|
||||
}
|
||||
} else if b.ManualStatus != status {
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
statusInt = bookingStatus
|
||||
}
|
||||
bookingStatus := b.Status()
|
||||
statusInt := 0
|
||||
|
||||
// Map status string to int
|
||||
var filterStatusInt int
|
||||
switch status {
|
||||
case "FORTHCOMING":
|
||||
filterStatusInt = 1
|
||||
case "ONGOING":
|
||||
filterStatusInt = 0
|
||||
case "TERMINATED":
|
||||
filterStatusInt = -1
|
||||
case "CANCELLED":
|
||||
filterStatusInt = -2
|
||||
default:
|
||||
filterStatusInt = 999 // Invalid status, won't match anything
|
||||
}
|
||||
if b.Deleted {
|
||||
statusInt = -2 // Use -2 for cancelled to distinguish from terminated
|
||||
} else {
|
||||
statusInt = bookingStatus
|
||||
}
|
||||
|
||||
if statusInt != filterStatusInt {
|
||||
continue
|
||||
// Map status string to int
|
||||
var filterStatusInt int
|
||||
switch status {
|
||||
case "FORTHCOMING":
|
||||
filterStatusInt = 1
|
||||
case "ONGOING":
|
||||
filterStatusInt = 0
|
||||
case "TERMINATED":
|
||||
filterStatusInt = -1
|
||||
case "CANCELLED":
|
||||
filterStatusInt = -2
|
||||
default:
|
||||
filterStatusInt = 999 // Invalid status, won't match anything
|
||||
}
|
||||
|
||||
if statusInt != filterStatusInt {
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Apply date filter (on startdate)
|
||||
if !startdate.IsZero() && b.Startdate.Before(startdate) {
|
||||
// Apply date filter (intersection: booking overlaps with [startdate, enddate])
|
||||
if !startdate.IsZero() && b.Enddate.Before(startdate) {
|
||||
continue
|
||||
}
|
||||
if !enddate.IsZero() && b.Startdate.After(enddate) {
|
||||
@@ -324,13 +456,14 @@ func (h *ApplicationHandler) UpdateBooking(ctx context.Context, bookingID string
|
||||
}
|
||||
|
||||
type BookingDisplayResult struct {
|
||||
Booking fleetsstorage.Booking
|
||||
Vehicle fleetsstorage.Vehicle
|
||||
Beneficiary mobilityaccountsstorage.Account
|
||||
Group storage.Group
|
||||
Documents []filestorage.FileInfo
|
||||
FileTypesMap map[string]string
|
||||
Alternatives []any
|
||||
Booking fleetsstorage.Booking
|
||||
Vehicle fleetsstorage.Vehicle
|
||||
Beneficiary mobilityaccountsstorage.Account
|
||||
Group storage.Group
|
||||
Documents []filestorage.FileInfo
|
||||
FileTypesMap map[string]string
|
||||
Alternatives []any
|
||||
ComputedExtraProperties map[string]string
|
||||
}
|
||||
|
||||
func (h *ApplicationHandler) GetBookingDisplay(ctx context.Context, bookingID string) (*BookingDisplayResult, error) {
|
||||
@@ -382,14 +515,17 @@ func (h *ApplicationHandler) GetBookingDisplay(ctx context.Context, bookingID st
|
||||
documents := h.filestorage.List(filestorage.PREFIX_BOOKINGS + "/" + bookingID)
|
||||
fileTypesMap := h.config.GetStringMapString("storage.files.file_types")
|
||||
|
||||
computedProps := h.computeExtraProperties(booking)
|
||||
|
||||
return &BookingDisplayResult{
|
||||
Booking: booking,
|
||||
Vehicle: booking.Vehicle,
|
||||
Beneficiary: beneficiary,
|
||||
Group: groupresp.Group.ToStorageType(),
|
||||
Documents: documents,
|
||||
FileTypesMap: fileTypesMap,
|
||||
Alternatives: alternatives,
|
||||
Booking: booking,
|
||||
Vehicle: booking.Vehicle,
|
||||
Beneficiary: beneficiary,
|
||||
Group: groupresp.Group.ToStorageType(),
|
||||
Documents: documents,
|
||||
FileTypesMap: fileTypesMap,
|
||||
Alternatives: alternatives,
|
||||
ComputedExtraProperties: computedProps,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -596,3 +732,351 @@ func (h *ApplicationHandler) UpdateVehicle(ctx context.Context, vehicleID, name,
|
||||
return updateResp.Vehicle.Id, nil
|
||||
}
|
||||
|
||||
// getStatusOptions extracts status options from Viper config, handling both
|
||||
// Go defaults ([]map[string]any) and YAML-parsed ([]interface{}) types.
|
||||
func getStatusOptions(raw interface{}) []map[string]any {
|
||||
if options, ok := raw.([]map[string]any); ok {
|
||||
return options
|
||||
}
|
||||
if rawSlice, ok := raw.([]interface{}); ok {
|
||||
result := make([]map[string]any, 0, len(rawSlice))
|
||||
for _, item := range rawSlice {
|
||||
if opt, ok := item.(map[string]any); ok {
|
||||
result = append(result, opt)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// getBookingExtraProperties extracts booking extra property definitions from Viper config,
|
||||
// handling both Go defaults ([]map[string]any) and YAML-parsed ([]interface{}) types.
|
||||
func getBookingExtraProperties(raw interface{}) []map[string]any {
|
||||
if props, ok := raw.([]map[string]any); ok {
|
||||
return props
|
||||
}
|
||||
if rawSlice, ok := raw.([]interface{}); ok {
|
||||
result := make([]map[string]any, 0, len(rawSlice))
|
||||
for _, item := range rawSlice {
|
||||
if prop, ok := item.(map[string]any); ok {
|
||||
result = append(result, prop)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *ApplicationHandler) UpdateBookingStatus(ctx context.Context, bookingID, newStatus, comment, userID string, userClaims map[string]any, currentGroup any, extraProperties map[string]string) error {
|
||||
group := currentGroup.(storage.Group)
|
||||
|
||||
// Validate that newStatus is a valid status option
|
||||
options := getStatusOptions(h.config.Get("modules.vehicles.status_options"))
|
||||
validStatus := false
|
||||
for _, opt := range options {
|
||||
if name, ok := opt["name"].(string); ok && name == newStatus {
|
||||
validStatus = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !validStatus {
|
||||
return fmt.Errorf("invalid status: %s", newStatus)
|
||||
}
|
||||
|
||||
booking, err := h.services.GetBooking(bookingID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get booking: %w", err)
|
||||
}
|
||||
|
||||
oldStatus := booking.ManualStatus
|
||||
|
||||
booking.ManualStatus = newStatus
|
||||
booking.StatusHistory = append(booking.StatusHistory, fleetsstorage.StatusHistoryEntry{
|
||||
FromStatus: oldStatus,
|
||||
ToStatus: newStatus,
|
||||
UserID: userID,
|
||||
UserName: fmt.Sprintf("%s %s", userClaims["first_name"], userClaims["last_name"]),
|
||||
GroupID: group.ID,
|
||||
GroupName: fmt.Sprintf("%s", group.Data["name"]),
|
||||
Date: time.Now(),
|
||||
Comment: comment,
|
||||
})
|
||||
|
||||
// Process extra properties
|
||||
if len(extraProperties) > 0 {
|
||||
propDefs := getBookingExtraProperties(h.config.Get("modules.vehicles.booking_extra_properties"))
|
||||
|
||||
// Build a lookup map of property definitions by name
|
||||
propDefsMap := map[string]map[string]any{}
|
||||
for _, def := range propDefs {
|
||||
if name, ok := def["name"].(string); ok {
|
||||
propDefsMap[name] = def
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize extra_properties in booking data if needed
|
||||
storedExtras, _ := booking.Data["extra_properties"].(map[string]any)
|
||||
if storedExtras == nil {
|
||||
storedExtras = map[string]any{}
|
||||
}
|
||||
|
||||
for propName, propValue := range extraProperties {
|
||||
if propValue == "" {
|
||||
continue
|
||||
}
|
||||
def, exists := propDefsMap[propName]
|
||||
if !exists {
|
||||
continue
|
||||
}
|
||||
|
||||
if target, ok := def["target"].(string); ok && target != "" {
|
||||
// Update structural booking field
|
||||
switch target {
|
||||
case "unavailableto":
|
||||
if t, err := time.Parse("2006-01-02", propValue); err == nil {
|
||||
booking.Unavailableto = t
|
||||
}
|
||||
case "unavailablefrom":
|
||||
if t, err := time.Parse("2006-01-02", propValue); err == nil {
|
||||
booking.Unavailablefrom = t
|
||||
}
|
||||
case "enddate":
|
||||
paris, _ := time.LoadLocation("Europe/Paris")
|
||||
if t, err := time.ParseInLocation("2006-01-02T15:04", propValue, paris); err == nil {
|
||||
booking.Enddate = t
|
||||
if t.After(booking.Unavailableto) || t.Equal(booking.Unavailableto) {
|
||||
booking.Unavailableto = t.Add(24 * time.Hour)
|
||||
}
|
||||
}
|
||||
case "startdate":
|
||||
paris, _ := time.LoadLocation("Europe/Paris")
|
||||
if t, err := time.ParseInLocation("2006-01-02T15:04", propValue, paris); err == nil {
|
||||
booking.Startdate = t
|
||||
if t.Before(booking.Unavailablefrom) {
|
||||
booking.Unavailablefrom = t
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Store in extra_properties
|
||||
storedExtras[propName] = propValue
|
||||
}
|
||||
}
|
||||
|
||||
booking.Data["extra_properties"] = storedExtras
|
||||
}
|
||||
|
||||
b, err := fleets.BookingFromStorageType(&booking)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to convert booking: %w", err)
|
||||
}
|
||||
|
||||
_, err = h.services.GRPC.Fleets.UpdateBooking(ctx, &fleets.UpdateBookingRequest{
|
||||
Booking: b,
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// --- Computed extra properties ---
|
||||
|
||||
func (h *ApplicationHandler) computeExtraProperties(booking fleetsstorage.Booking) map[string]string {
|
||||
defs := getBookingExtraProperties(h.config.Get("modules.vehicles.booking_extra_properties"))
|
||||
extras, _ := booking.Data["extra_properties"].(map[string]any)
|
||||
if extras == nil {
|
||||
extras = map[string]any{}
|
||||
}
|
||||
|
||||
result := map[string]string{}
|
||||
for _, def := range defs {
|
||||
defType, _ := def["type"].(string)
|
||||
if defType != "computed" {
|
||||
continue
|
||||
}
|
||||
name, _ := def["name"].(string)
|
||||
operation, _ := def["operation"].(string)
|
||||
unit, _ := def["unit"].(string)
|
||||
operands := resolveStringSlice(def["operands"])
|
||||
if name == "" || operation == "" || len(operands) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
val := evaluateOperation(operation, operands, booking, extras)
|
||||
if val != "" {
|
||||
if unit != "" {
|
||||
val = val + " " + unit
|
||||
}
|
||||
result[name] = val
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// resolveStringSlice handles both Go defaults ([]string) and YAML-parsed ([]interface{}) types.
|
||||
func resolveStringSlice(raw interface{}) []string {
|
||||
if s, ok := raw.([]string); ok {
|
||||
return s
|
||||
}
|
||||
if rawSlice, ok := raw.([]interface{}); ok {
|
||||
result := make([]string, 0, len(rawSlice))
|
||||
for _, item := range rawSlice {
|
||||
if str, ok := item.(string); ok {
|
||||
result = append(result, str)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func evaluateOperation(operation string, operands []string, booking fleetsstorage.Booking, extras map[string]any) string {
|
||||
switch operation {
|
||||
case "add":
|
||||
if len(operands) < 2 {
|
||||
return ""
|
||||
}
|
||||
a, okA := resolveNumericOperand(operands[0], booking, extras)
|
||||
b, okB := resolveNumericOperand(operands[1], booking, extras)
|
||||
if !okA || !okB {
|
||||
return ""
|
||||
}
|
||||
return formatNumber(a + b)
|
||||
|
||||
case "subtract":
|
||||
if len(operands) < 2 {
|
||||
return ""
|
||||
}
|
||||
a, okA := resolveNumericOperand(operands[0], booking, extras)
|
||||
b, okB := resolveNumericOperand(operands[1], booking, extras)
|
||||
if !okA || !okB {
|
||||
return ""
|
||||
}
|
||||
return formatNumber(a - b)
|
||||
|
||||
case "multiply":
|
||||
if len(operands) < 2 {
|
||||
return ""
|
||||
}
|
||||
a, okA := resolveNumericOperand(operands[0], booking, extras)
|
||||
b, okB := resolveNumericOperand(operands[1], booking, extras)
|
||||
if !okA || !okB {
|
||||
return ""
|
||||
}
|
||||
return formatNumber(a * b)
|
||||
|
||||
case "divide":
|
||||
if len(operands) < 2 {
|
||||
return ""
|
||||
}
|
||||
a, okA := resolveNumericOperand(operands[0], booking, extras)
|
||||
b, okB := resolveNumericOperand(operands[1], booking, extras)
|
||||
if !okA || !okB || b == 0 {
|
||||
return ""
|
||||
}
|
||||
return formatNumber(a / b)
|
||||
|
||||
case "duration":
|
||||
if len(operands) < 2 {
|
||||
return ""
|
||||
}
|
||||
d1, ok1 := resolveDateOperand(operands[0], booking, extras)
|
||||
d2, ok2 := resolveDateOperand(operands[1], booking, extras)
|
||||
if !ok1 || !ok2 {
|
||||
return ""
|
||||
}
|
||||
return formatDuration(d2.Sub(d1))
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func resolveNumericOperand(name string, booking fleetsstorage.Booking, extras map[string]any) (float64, bool) {
|
||||
if v, ok := extras[name]; ok {
|
||||
switch val := v.(type) {
|
||||
case float64:
|
||||
return val, true
|
||||
case string:
|
||||
if f, err := strconv.ParseFloat(val, 64); err == nil {
|
||||
return f, true
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
func resolveDateOperand(name string, booking fleetsstorage.Booking, extras map[string]any) (time.Time, bool) {
|
||||
if strings.HasPrefix(name, "booking.") {
|
||||
field := strings.TrimPrefix(name, "booking.")
|
||||
switch field {
|
||||
case "startdate":
|
||||
return booking.Startdate, !booking.Startdate.IsZero()
|
||||
case "enddate":
|
||||
return booking.Enddate, !booking.Enddate.IsZero()
|
||||
case "unavailablefrom":
|
||||
return booking.Unavailablefrom, !booking.Unavailablefrom.IsZero()
|
||||
case "unavailableto":
|
||||
return booking.Unavailableto, !booking.Unavailableto.IsZero()
|
||||
}
|
||||
}
|
||||
if v, ok := extras[name]; ok {
|
||||
if dateStr, ok := v.(string); ok {
|
||||
for _, layout := range []string{"2006-01-02T15:04", "2006-01-02"} {
|
||||
if t, err := time.Parse(layout, dateStr); err == nil {
|
||||
return t, true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return time.Time{}, false
|
||||
}
|
||||
|
||||
func formatNumber(v float64) string {
|
||||
if v == math.Trunc(v) {
|
||||
return strconv.FormatInt(int64(v), 10)
|
||||
}
|
||||
return strconv.FormatFloat(v, 'f', 2, 64)
|
||||
}
|
||||
|
||||
// resolveMetaStatuses returns a set of status names matching the given meta_status values.
|
||||
// metaValues is a set like {"open": true, "active": true}.
|
||||
func (h *ApplicationHandler) resolveMetaStatuses(metaValues map[string]bool) map[string]bool {
|
||||
result := map[string]bool{}
|
||||
options := getStatusOptions(h.config.Get("modules.vehicles.status_options"))
|
||||
for _, opt := range options {
|
||||
ms, _ := opt["meta_status"].(string)
|
||||
name, _ := opt["name"].(string)
|
||||
if metaValues[ms] {
|
||||
result[name] = true
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func formatDuration(d time.Duration) string {
|
||||
if d < 0 {
|
||||
d = -d
|
||||
}
|
||||
totalHours := int(d.Hours())
|
||||
days := totalHours / 24
|
||||
hours := totalHours % 24
|
||||
|
||||
parts := []string{}
|
||||
if days > 0 {
|
||||
s := "jour"
|
||||
if days > 1 {
|
||||
s = "jours"
|
||||
}
|
||||
parts = append(parts, fmt.Sprintf("%d %s", days, s))
|
||||
}
|
||||
if hours > 0 {
|
||||
s := "heure"
|
||||
if hours > 1 {
|
||||
s = "heures"
|
||||
}
|
||||
parts = append(parts, fmt.Sprintf("%d %s", hours, s))
|
||||
}
|
||||
if len(parts) == 0 {
|
||||
return "0 heure"
|
||||
}
|
||||
return strings.Join(parts, " ")
|
||||
}
|
||||
|
||||
|
||||
@@ -210,6 +210,29 @@ func (h *ApplicationHandler) BookVehicle(ctx context.Context, vehicleID, benefic
|
||||
Data: datapb,
|
||||
}
|
||||
|
||||
if h.config.GetString("modules.vehicles.status_management") == "manual" {
|
||||
options := getStatusOptions(h.config.Get("modules.vehicles.status_options"))
|
||||
for _, opt := range options {
|
||||
if initial, ok := opt["initial"].(bool); ok && initial {
|
||||
if name, ok := opt["name"].(string); ok {
|
||||
booking.ManualStatus = name
|
||||
booking.StatusHistory = []*fleets.StatusHistoryEntry{
|
||||
{
|
||||
ToStatus: name,
|
||||
UserId: currentUserID,
|
||||
UserName: fmt.Sprintf("%s %s", currentUserClaims["first_name"], currentUserClaims["last_name"]),
|
||||
GroupId: group.ID,
|
||||
GroupName: fmt.Sprintf("%s", group.Data["name"]),
|
||||
Date: timestamppb.Now(),
|
||||
Comment: "Création de la réservation",
|
||||
},
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
request := &fleets.CreateBookingRequest{
|
||||
Booking: booking,
|
||||
}
|
||||
|
||||
3
go.mod
3
go.mod
@@ -48,7 +48,7 @@ require (
|
||||
git.coopgo.io/coopgo-platform/agenda v1.0.0
|
||||
git.coopgo.io/coopgo-platform/carpool-service v0.0.0-20251008165122-38cb3c5ad9b4
|
||||
git.coopgo.io/coopgo-platform/emailing v0.0.0-20250212064257-167ef5864260
|
||||
git.coopgo.io/coopgo-platform/fleets v1.1.0
|
||||
git.coopgo.io/coopgo-platform/fleets v1.1.1-0.20260226165510-6007cffdf152
|
||||
git.coopgo.io/coopgo-platform/geography v0.0.0-20251010131258-ec939649e858
|
||||
git.coopgo.io/coopgo-platform/groups-management v0.0.0-20230310123255-5ef94ee0746c
|
||||
git.coopgo.io/coopgo-platform/mobility-accounts v0.0.0-20230329105908-a76c0412a386
|
||||
@@ -164,7 +164,6 @@ require (
|
||||
go.etcd.io/etcd/api/v3 v3.5.12 // indirect
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.5.12 // indirect
|
||||
go.mongodb.org/mongo-driver v1.17.4 // indirect
|
||||
go.mongodb.org/mongo-driver/v2 v2.1.0 // indirect
|
||||
go.uber.org/atomic v1.11.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.uber.org/zap v1.21.0 // indirect
|
||||
|
||||
8
go.sum
8
go.sum
@@ -8,8 +8,8 @@ git.coopgo.io/coopgo-platform/carpool-service/interoperability/ocss v0.0.0-20251
|
||||
git.coopgo.io/coopgo-platform/carpool-service/interoperability/ocss v0.0.0-20251008142525-4392f227836a/go.mod h1:c9aJwNtY4PJuqAFYZ9afnx46UAZtWJ3P8ICZM02/DBA=
|
||||
git.coopgo.io/coopgo-platform/emailing v0.0.0-20250212064257-167ef5864260 h1:Li3dotY6raKu9+oxEgICU7nwdomYpjgu19i3mZNiqTc=
|
||||
git.coopgo.io/coopgo-platform/emailing v0.0.0-20250212064257-167ef5864260/go.mod h1:6cvvjv0RLSwBthIQ4TiuZoXFGvQXZ55hNSJchWXAgB4=
|
||||
git.coopgo.io/coopgo-platform/fleets v1.1.0 h1:pfW/K3fWfap54yNfkLzBXjvOjjoTaEGFEqS/+VkHv7s=
|
||||
git.coopgo.io/coopgo-platform/fleets v1.1.0/go.mod h1:nuK2mi1M2+DdntinqK/8C4ttW4WWyKCCY/xD1D7XjkE=
|
||||
git.coopgo.io/coopgo-platform/fleets v1.1.1-0.20260226165510-6007cffdf152 h1:kczmeGHnihYQSopDzbQ23B+P8Fw3GuB6iACW7SuDE+s=
|
||||
git.coopgo.io/coopgo-platform/fleets v1.1.1-0.20260226165510-6007cffdf152/go.mod h1:nuK2mi1M2+DdntinqK/8C4ttW4WWyKCCY/xD1D7XjkE=
|
||||
git.coopgo.io/coopgo-platform/geography v0.0.0-20251010131258-ec939649e858 h1:4E0tbT8jj5oxaK66Ny61o7zqPaVc0qRN2cZG9IUR4Es=
|
||||
git.coopgo.io/coopgo-platform/geography v0.0.0-20251010131258-ec939649e858/go.mod h1:TbR3g1Awa8hpAe6LR1z1EQbv2IBVgN5JQ/FjXfKX4K0=
|
||||
git.coopgo.io/coopgo-platform/groups-management v0.0.0-20230310123255-5ef94ee0746c h1:bY7PyrAgYY02f5IpDyf1WVfRqvWzivu31K6aEAYbWCw=
|
||||
@@ -26,8 +26,6 @@ git.coopgo.io/coopgo-platform/saved-search v0.0.0-20251008070953-efccea3f6463 h1
|
||||
git.coopgo.io/coopgo-platform/saved-search v0.0.0-20251008070953-efccea3f6463/go.mod h1:0fuGuYub5CBy9NB6YMqxawE0HoBaxPb9gmSw1gjfDy0=
|
||||
git.coopgo.io/coopgo-platform/sms v0.0.0-20250523074631-1f1e7fc6b7af h1:KxHim1dFcOVbFhRqelec8cJ65QBD2cma6eytW8llgYY=
|
||||
git.coopgo.io/coopgo-platform/sms v0.0.0-20250523074631-1f1e7fc6b7af/go.mod h1:mad9D+WICDdpJzB+8H/wEVVbllK2mU6VLVByrppc9x0=
|
||||
git.coopgo.io/coopgo-platform/solidarity-transport v0.0.0-20251016152145-e0882db1bcbc h1:NLU5DUo5Kt3jkPhV3KkqQMahTHIrGildBvYlHwJ6JmM=
|
||||
git.coopgo.io/coopgo-platform/solidarity-transport v0.0.0-20251016152145-e0882db1bcbc/go.mod h1:iaFXcIn7DYtKlLrSYs9C47Dt7eeMGYkpx+unLCx8TpQ=
|
||||
git.coopgo.io/coopgo-platform/solidarity-transport v0.0.0-20260114093602-8875adbcbbee h1:aoXSugsrZrM8E3WhqOCM+bLgGdxdf7dZAxx/vfbYzWQ=
|
||||
git.coopgo.io/coopgo-platform/solidarity-transport v0.0.0-20260114093602-8875adbcbbee/go.mod h1:iaFXcIn7DYtKlLrSYs9C47Dt7eeMGYkpx+unLCx8TpQ=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
|
||||
@@ -362,8 +360,6 @@ go.etcd.io/etcd/client/v3 v3.5.12/go.mod h1:tSbBCakoWmmddL+BKVAJHa9km+O/E+bumDe9
|
||||
go.mongodb.org/mongo-driver v1.11.4/go.mod h1:PTSz5yu21bkT/wXpkS7WR5f0ddqw5quethTUn9WM+2g=
|
||||
go.mongodb.org/mongo-driver v1.17.4 h1:jUorfmVzljjr0FLzYQsGP8cgN/qzzxlY9Vh0C9KFXVw=
|
||||
go.mongodb.org/mongo-driver v1.17.4/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ=
|
||||
go.mongodb.org/mongo-driver/v2 v2.1.0 h1:/ELnVNjmfUKDsoBisXxuJL0noR9CfeUIrP7Yt3R+egg=
|
||||
go.mongodb.org/mongo-driver/v2 v2.1.0/go.mod h1:AWiLRShSrk5RHQS3AEn3RL19rqOzVq49MCpWQ3x/huI=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||
go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ=
|
||||
|
||||
@@ -30,15 +30,25 @@ func (s BeneficiariesListState) JSONWithLimits(a int, b int) template.JS {
|
||||
return s.JSON()
|
||||
}
|
||||
|
||||
func (renderer *Renderer) BeneficiariesList(w http.ResponseWriter, r *http.Request, accounts []mobilityaccountsstorage.Account, cacheid string, archived bool) {
|
||||
func (renderer *Renderer) BeneficiariesList(w http.ResponseWriter, r *http.Request, accounts []mobilityaccountsstorage.Account, cacheid string, archived bool, enrichedGeoFilters []map[string]string, selectedAddressGeo string) {
|
||||
files := renderer.ThemeConfig.GetStringSlice("views.beneficiaries.list.files")
|
||||
|
||||
geoFiltersEnabled := len(enrichedGeoFilters) > 0
|
||||
|
||||
state := NewState(r, renderer.ThemeConfig, beneficiariesMenu)
|
||||
state.ViewState = BeneficiariesListState{
|
||||
Count: len(accounts),
|
||||
CacheId: cacheid,
|
||||
Beneficiaries: accounts,
|
||||
Archived: archived,
|
||||
state.ViewState = map[string]any{
|
||||
"list": BeneficiariesListState{
|
||||
Count: len(accounts),
|
||||
CacheId: cacheid,
|
||||
Beneficiaries: accounts,
|
||||
Archived: archived,
|
||||
},
|
||||
"geography_filters_enabled": geoFiltersEnabled,
|
||||
"geography_filters_list": enrichedGeoFilters,
|
||||
"archived": archived,
|
||||
"filters": map[string]any{
|
||||
"beneficiary_address_geo": selectedAddressGeo,
|
||||
},
|
||||
}
|
||||
|
||||
renderer.Render("beneficiaries_list", w, r, files, state)
|
||||
@@ -87,7 +97,7 @@ func (renderer *Renderer) BeneficiaryDisplay(w http.ResponseWriter, r *http.Requ
|
||||
renderer.Render("beneficiaries_display", w, r, files, state)
|
||||
}
|
||||
|
||||
func (renderer *Renderer) BeneficiaryUpdate(w http.ResponseWriter, r *http.Request, beneficiary any) {
|
||||
func (renderer *Renderer) BeneficiaryUpdate(w http.ResponseWriter, r *http.Request, beneficiary mobilityaccountsstorage.Account) {
|
||||
files := renderer.ThemeConfig.GetStringSlice("views.beneficiaries.update.files")
|
||||
profileFields := renderer.GlobalConfig.Get("modules.beneficiaries.profile_optional_fields")
|
||||
|
||||
@@ -96,6 +106,7 @@ func (renderer *Renderer) BeneficiaryUpdate(w http.ResponseWriter, r *http.Reque
|
||||
"beneficiary": beneficiary,
|
||||
"profile_optional_fields": profileFields,
|
||||
}
|
||||
state.DynamicData = beneficiary.Data
|
||||
|
||||
renderer.Render("beneficiaries_update", w, r, files, state)
|
||||
}
|
||||
|
||||
@@ -170,6 +170,14 @@ func IsGuaranteedTripMotivation(globalConfig *viper.Viper) func(string) bool {
|
||||
}
|
||||
}
|
||||
|
||||
// IsPast returns true if the given time is before the current time
|
||||
func IsPast(d any) bool {
|
||||
if date, ok := d.(time.Time); ok {
|
||||
return date.Before(time.Now())
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// GetTemplateFuncMap returns the common template functions for rendering
|
||||
func GetTemplateFuncMap(group groupsstorage.Group, globalConfig *viper.Viper, fileStorage filestorage.FileStorage) template.FuncMap {
|
||||
return template.FuncMap{
|
||||
@@ -191,6 +199,7 @@ func GetTemplateFuncMap(group groupsstorage.Group, globalConfig *viper.Viper, fi
|
||||
"beneficiaryValidatedProfile": validatedprofile.ValidateProfile(globalConfig.Sub("modules.beneficiaries.validated_profile")),
|
||||
"solidarityDriverValidatedProfile": validatedprofile.ValidateProfile(globalConfig.Sub("modules.solidarity_transport.drivers.validated_profile")),
|
||||
"carpoolDriverValidatedProfile": validatedprofile.ValidateProfile(globalConfig.Sub("modules.organized_carpool.drivers.validated_profile")),
|
||||
"isPast": IsPast,
|
||||
"isGuaranteedTripMotivation": IsGuaranteedTripMotivation(globalConfig),
|
||||
"beneficiaryDocuments": func(id string) []filestorage.FileInfo {
|
||||
return fileStorage.List(filestorage.PREFIX_BENEFICIARIES + "/" + id)
|
||||
|
||||
@@ -36,7 +36,7 @@ func (s BeneficiariesCovoiturage) JSONWithLimits(a int, b int) template.JS {
|
||||
return s.JSON()
|
||||
}
|
||||
|
||||
func (renderer *Renderer) JourneysSearch(w http.ResponseWriter, r *http.Request, carpools []*geojson.FeatureCollection, transitjourneys any, vehicles any, searched bool, departure any, destination any, departuredate string, departuretime string, driverJourneys any, solidarityDrivers any, organizedCarpools any, beneficiaries any, kbData any, passengerid string, savedSearches any, beneficiariesMap any) {
|
||||
func (renderer *Renderer) JourneysSearch(w http.ResponseWriter, r *http.Request, carpools []*geojson.FeatureCollection, transitjourneys any, vehicles any, searched bool, departure any, destination any, departuredate string, departuretime string, departuredatetime any, driverJourneys any, solidarityDrivers any, organizedCarpools any, beneficiaries any, kbData any, passengerid string, savedSearches any, beneficiariesMap any, driverLastTrips any, lastTripDays int) {
|
||||
files := renderer.ThemeConfig.GetStringSlice("views.journeys.search.files")
|
||||
state := NewState(r, renderer.ThemeConfig, journeysMenu)
|
||||
journeyTabs := renderer.ThemeConfig.Get("journey_tabs")
|
||||
@@ -44,6 +44,7 @@ func (renderer *Renderer) JourneysSearch(w http.ResponseWriter, r *http.Request,
|
||||
"searched": searched,
|
||||
"departuredate": departuredate,
|
||||
"departuretime": departuretime,
|
||||
"departuredatetime": departuredatetime,
|
||||
"departure": departure,
|
||||
"destination": destination,
|
||||
"journeys": transitjourneys,
|
||||
@@ -52,6 +53,8 @@ func (renderer *Renderer) JourneysSearch(w http.ResponseWriter, r *http.Request,
|
||||
"vehicles": vehicles,
|
||||
"driver_journeys": driverJourneys,
|
||||
"solidarity_drivers": solidarityDrivers,
|
||||
"driver_last_trips": driverLastTrips,
|
||||
"last_trip_days": lastTripDays,
|
||||
"querystring": r.URL.RawQuery,
|
||||
"beneficiaries": beneficiariesMap,
|
||||
"beneficiaries_list": beneficiaries,
|
||||
|
||||
@@ -56,22 +56,24 @@ func (renderer *Renderer) OrganizedCarpoolUpdateDriver(w http.ResponseWriter, r
|
||||
|
||||
func (renderer *Renderer) OrganizedCarpoolDriverDisplay(w http.ResponseWriter, r *http.Request, driver mobilityaccountsstorage.Account, trips any, documents any, bookings any, beneficiariesMap any, stats map[string]any, walletBalance float64, tab string) {
|
||||
files := renderer.ThemeConfig.GetStringSlice("views.organized_carpool.driver_display.files")
|
||||
profileFields := renderer.GlobalConfig.Get("modules.organized_carpool.drivers.profile_optional_fields")
|
||||
state := NewState(r, renderer.ThemeConfig, organizedCarpoolMenu)
|
||||
|
||||
drivers_file_types := renderer.GlobalConfig.GetStringSlice("modules.organized_carpool.drivers.documents_types")
|
||||
file_types_map := renderer.GlobalConfig.GetStringMapString("storage.files.file_types")
|
||||
|
||||
state.ViewState = map[string]any{
|
||||
"driver": driver,
|
||||
"trips": trips,
|
||||
"documents": documents,
|
||||
"bookings": bookings,
|
||||
"beneficiaries_map": beneficiariesMap,
|
||||
"stats": stats,
|
||||
"drivers_file_types": drivers_file_types,
|
||||
"file_types_map": file_types_map,
|
||||
"wallet_balance": walletBalance,
|
||||
"tab": tab,
|
||||
"driver": driver,
|
||||
"trips": trips,
|
||||
"documents": documents,
|
||||
"bookings": bookings,
|
||||
"beneficiaries_map": beneficiariesMap,
|
||||
"stats": stats,
|
||||
"drivers_file_types": drivers_file_types,
|
||||
"file_types_map": file_types_map,
|
||||
"profile_optional_fields": profileFields,
|
||||
"wallet_balance": walletBalance,
|
||||
"tab": tab,
|
||||
}
|
||||
|
||||
renderer.Render("organized carpool driver display", w, r, files, state)
|
||||
|
||||
@@ -105,11 +105,12 @@ func (r *Renderer) templateFile(file string) string {
|
||||
type RenderState struct {
|
||||
icons.IconSet
|
||||
LayoutState
|
||||
UserID string
|
||||
UserClaims map[string]any
|
||||
Group storage.Group
|
||||
Roles any
|
||||
ViewState any // This is a state specific to a given view
|
||||
UserID string
|
||||
UserClaims map[string]any
|
||||
Group storage.Group
|
||||
Roles any
|
||||
ViewState any // This is a state specific to a given view
|
||||
DynamicData any // Data to be serialized as JSON in a <script> tag for safe JS consumption
|
||||
}
|
||||
|
||||
func NewState(r *http.Request, themeConfig *viper.Viper, menuState string) RenderState {
|
||||
|
||||
20
renderer/search.go
Normal file
20
renderer/search.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package renderer
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
mobilityaccountsstorage "git.coopgo.io/coopgo-platform/mobility-accounts/storage"
|
||||
)
|
||||
|
||||
func (renderer *Renderer) GlobalSearchResults(w http.ResponseWriter, r *http.Request, query string, beneficiaries []mobilityaccountsstorage.Account, solidarityDrivers []mobilityaccountsstorage.Account, organizedCarpoolDrivers []mobilityaccountsstorage.Account) {
|
||||
files := renderer.ThemeConfig.GetStringSlice("views.search.results.files")
|
||||
state := NewState(r, renderer.ThemeConfig, "")
|
||||
state.ViewState = map[string]any{
|
||||
"query": query,
|
||||
"beneficiaries": beneficiaries,
|
||||
"solidarity_drivers": solidarityDrivers,
|
||||
"organized_carpool_drivers": organizedCarpoolDrivers,
|
||||
}
|
||||
|
||||
renderer.Render("search results", w, r, files, state)
|
||||
}
|
||||
@@ -109,7 +109,7 @@ func (renderer *Renderer) SolidarityTransportDriverJourney(w http.ResponseWriter
|
||||
renderer.Render("solidarity transport driver creation", w, r, files, state)
|
||||
}
|
||||
|
||||
func (renderer *Renderer) SolidarityTransportBookingDisplay(w http.ResponseWriter, r *http.Request, booking any, driver any, passenger any, passengerWalletBalance float64, replacementDrivers any, replacementDriversMap any, replacementPricing any, replacementLocations any) {
|
||||
func (renderer *Renderer) SolidarityTransportBookingDisplay(w http.ResponseWriter, r *http.Request, booking any, driver any, passenger any, passengerWalletBalance float64, replacementDrivers any, replacementDriversMap any, replacementPricing any, replacementLocations any, driverLastTrips any, lastTripDays int) {
|
||||
files := renderer.ThemeConfig.GetStringSlice("views.solidarity_transport.booking_display.files")
|
||||
bookingMotivations := renderer.GlobalConfig.Get("modules.solidarity_transport.booking_motivations")
|
||||
state := NewState(r, renderer.ThemeConfig, solidarityTransportMenu)
|
||||
@@ -124,6 +124,8 @@ func (renderer *Renderer) SolidarityTransportBookingDisplay(w http.ResponseWrite
|
||||
"replacement_drivers_map": replacementDriversMap,
|
||||
"replacement_pricing": replacementPricing,
|
||||
"replacement_locations": replacementLocations,
|
||||
"driver_last_trips": driverLastTrips,
|
||||
"last_trip_days": lastTripDays,
|
||||
}
|
||||
|
||||
renderer.Render("booking display", w, r, files, state)
|
||||
|
||||
@@ -10,28 +10,37 @@ import (
|
||||
|
||||
const vehiclesmanagementMenu = "vehicles_management"
|
||||
|
||||
func (renderer *Renderer) VehiclesManagementOverview(w http.ResponseWriter, r *http.Request, vehicles []fleetsstorage.Vehicle, vehicles_map map[string]fleetsstorage.Vehicle, driversMap map[string]mobilityaccountsstorage.Account, bookings []fleetsstorage.Booking) {
|
||||
func (renderer *Renderer) VehiclesManagementOverview(w http.ResponseWriter, r *http.Request, vehicles []fleetsstorage.Vehicle, vehicles_map map[string]fleetsstorage.Vehicle, driversMap map[string]mobilityaccountsstorage.Account, bookings []fleetsstorage.Booking, filters map[string]string, vehicleTypes []string, tab string) {
|
||||
files := renderer.ThemeConfig.GetStringSlice("views.vehicles_management.overview.files")
|
||||
state := NewState(r, renderer.ThemeConfig, vehiclesmanagementMenu)
|
||||
state.ViewState = map[string]any{
|
||||
"vehicles": vehicles,
|
||||
"bookings": bookings,
|
||||
"vehicles_map": vehicles_map,
|
||||
"drivers_map": driversMap,
|
||||
"vehicles": vehicles,
|
||||
"bookings": bookings,
|
||||
"vehicles_map": vehicles_map,
|
||||
"drivers_map": driversMap,
|
||||
"tab": tab,
|
||||
"filters": filters,
|
||||
"vehicle_types": vehicleTypes,
|
||||
"hide_date_filters": false,
|
||||
"status_management": renderer.GlobalConfig.GetString("modules.vehicles.status_management"),
|
||||
"status_options": renderer.GlobalConfig.Get("modules.vehicles.status_options"),
|
||||
}
|
||||
|
||||
renderer.Render("fleet overview", w, r, files, state)
|
||||
}
|
||||
|
||||
func (renderer *Renderer) VehiclesManagementBookingsList(w http.ResponseWriter, r *http.Request, vehiclesMap map[string]fleetsstorage.Vehicle, driversMap map[string]mobilityaccountsstorage.Account, bookings []fleetsstorage.Booking, cacheid string, filters map[string]string) {
|
||||
func (renderer *Renderer) VehiclesManagementBookingsList(w http.ResponseWriter, r *http.Request, vehiclesMap map[string]fleetsstorage.Vehicle, driversMap map[string]mobilityaccountsstorage.Account, bookings []fleetsstorage.Booking, cacheid string, filters map[string]string, vehicleTypes []string) {
|
||||
files := renderer.ThemeConfig.GetStringSlice("views.vehicles_management.bookings_list.files")
|
||||
state := NewState(r, renderer.ThemeConfig, vehiclesmanagementMenu)
|
||||
state.ViewState = map[string]any{
|
||||
"bookings": bookings,
|
||||
"vehicles_map": vehiclesMap,
|
||||
"drivers_map": driversMap,
|
||||
"cacheid": cacheid,
|
||||
"filters": filters,
|
||||
"bookings": bookings,
|
||||
"vehicles_map": vehiclesMap,
|
||||
"drivers_map": driversMap,
|
||||
"cacheid": cacheid,
|
||||
"filters": filters,
|
||||
"vehicle_types": vehicleTypes,
|
||||
"status_management": renderer.GlobalConfig.GetString("modules.vehicles.status_management"),
|
||||
"status_options": renderer.GlobalConfig.Get("modules.vehicles.status_options"),
|
||||
}
|
||||
|
||||
renderer.Render("fleet overview", w, r, files, state)
|
||||
@@ -78,17 +87,21 @@ func (renderer *Renderer) VehiclesFleetUpdate(w http.ResponseWriter, r *http.Req
|
||||
renderer.Render("fleet display vehicle", w, r, files, state)
|
||||
}
|
||||
|
||||
func (renderer *Renderer) VehicleManagementBookingDisplay(w http.ResponseWriter, r *http.Request, booking any, vehicle any, beneficiary any, group any, documents []filestorage.FileInfo, file_types_map map[string]string, alternative_vehicles []any) {
|
||||
func (renderer *Renderer) VehicleManagementBookingDisplay(w http.ResponseWriter, r *http.Request, booking any, vehicle any, beneficiary any, group any, documents []filestorage.FileInfo, file_types_map map[string]string, alternative_vehicles []any, computed_extra_properties map[string]string) {
|
||||
files := renderer.ThemeConfig.GetStringSlice("views.vehicles_management.booking_display.files")
|
||||
state := NewState(r, renderer.ThemeConfig, vehiclesmanagementMenu)
|
||||
state.ViewState = map[string]any{
|
||||
"booking": booking,
|
||||
"vehicle": vehicle,
|
||||
"beneficiary": beneficiary,
|
||||
"group": group,
|
||||
"documents": documents,
|
||||
"file_types_map": file_types_map,
|
||||
"alternative_vehicles": alternative_vehicles,
|
||||
"booking": booking,
|
||||
"vehicle": vehicle,
|
||||
"beneficiary": beneficiary,
|
||||
"group": group,
|
||||
"documents": documents,
|
||||
"file_types_map": file_types_map,
|
||||
"alternative_vehicles": alternative_vehicles,
|
||||
"status_management": renderer.GlobalConfig.GetString("modules.vehicles.status_management"),
|
||||
"status_options": renderer.GlobalConfig.Get("modules.vehicles.status_options"),
|
||||
"booking_extra_properties": renderer.GlobalConfig.Get("modules.vehicles.booking_extra_properties"),
|
||||
"computed_extra_properties": computed_extra_properties,
|
||||
}
|
||||
|
||||
renderer.Render("vehicles search", w, r, files, state)
|
||||
|
||||
@@ -26,10 +26,13 @@ func selectDocumentsDefaults(beneficiarydocuments []filestorage.FileInfo, mandat
|
||||
func (renderer *Renderer) VehiclesSearch(w http.ResponseWriter, r *http.Request, beneficiaries []mobilityaccountsstorage.Account, searched bool, vehicles []fleetsstorage.Vehicle, beneficiary any, startdate any, enddate any, mandatory_documents []string, file_types_map map[string]string, beneficiarydocuments []filestorage.FileInfo, selected_type string, automatic bool, vehicles_types []string, admingroups map[string]any) {
|
||||
files := renderer.ThemeConfig.GetStringSlice("views.vehicles.search.files")
|
||||
state := NewState(r, renderer.ThemeConfig, vehiclesMenu)
|
||||
defaultBookingDurationDays := renderer.GlobalConfig.GetInt("modules.vehicles.default_booking_duration_days")
|
||||
|
||||
viewstate := map[string]any{
|
||||
"beneficiaries": beneficiaries,
|
||||
"searched": searched,
|
||||
"vehicles_types": vehicles_types,
|
||||
"beneficiaries": beneficiaries,
|
||||
"searched": searched,
|
||||
"vehicles_types": vehicles_types,
|
||||
"default_booking_duration_days": defaultBookingDurationDays,
|
||||
}
|
||||
|
||||
if searched {
|
||||
@@ -57,12 +60,14 @@ func (renderer *Renderer) VehicleBookingDisplay(w http.ResponseWriter, r *http.R
|
||||
files := renderer.ThemeConfig.GetStringSlice("views.vehicles.booking_display.files")
|
||||
state := NewState(r, renderer.ThemeConfig, vehiclesMenu)
|
||||
state.ViewState = map[string]any{
|
||||
"booking": booking,
|
||||
"vehicle": vehicle,
|
||||
"beneficiary": beneficiary,
|
||||
"group": group,
|
||||
"documents": documents,
|
||||
"file_types_map": file_types_map,
|
||||
"booking": booking,
|
||||
"vehicle": vehicle,
|
||||
"beneficiary": beneficiary,
|
||||
"group": group,
|
||||
"documents": documents,
|
||||
"file_types_map": file_types_map,
|
||||
"status_management": renderer.GlobalConfig.GetString("modules.vehicles.status_management"),
|
||||
"status_options": renderer.GlobalConfig.Get("modules.vehicles.status_options"),
|
||||
}
|
||||
|
||||
renderer.Render("vehicles search", w, r, files, state)
|
||||
@@ -72,9 +77,11 @@ func (renderer *Renderer) VehicleBookingsList(w http.ResponseWriter, r *http.Req
|
||||
files := renderer.ThemeConfig.GetStringSlice("views.vehicles.bookings_list.files")
|
||||
state := NewState(r, renderer.ThemeConfig, vehiclesMenu)
|
||||
state.ViewState = map[string]any{
|
||||
"bookings": bookings,
|
||||
"vehicles_map": vehiclesMap,
|
||||
"groups_map": groupsMap,
|
||||
"bookings": bookings,
|
||||
"vehicles_map": vehiclesMap,
|
||||
"groups_map": groupsMap,
|
||||
"status_management": renderer.GlobalConfig.GetString("modules.vehicles.status_management"),
|
||||
"status_options": renderer.GlobalConfig.Get("modules.vehicles.status_options"),
|
||||
}
|
||||
|
||||
renderer.Render("vehicles search", w, r, files, state)
|
||||
|
||||
99
renderer/xlsx/beneficiaries.go
Normal file
99
renderer/xlsx/beneficiaries.go
Normal file
@@ -0,0 +1,99 @@
|
||||
package xlsx
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"git.coopgo.io/coopgo-apps/parcoursmob/core/utils/gender"
|
||||
mobilityaccountsstorage "git.coopgo.io/coopgo-platform/mobility-accounts/storage"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
type BeneficiaryGeoInfo struct {
|
||||
Commune string
|
||||
EPCI string
|
||||
Departement string
|
||||
Region string
|
||||
}
|
||||
|
||||
func (r *XLSXRenderer) Beneficiaries(w http.ResponseWriter, accounts []mobilityaccountsstorage.Account, geoInfoMap map[string]BeneficiaryGeoInfo) {
|
||||
spreadsheet := r.NewSpreadsheet("Bénéficiaires")
|
||||
|
||||
// Build headers dynamically based on configuration
|
||||
beneficiaryOptionalFields := r.Config.Get("modules.beneficiaries.profile_optional_fields")
|
||||
beneficiaryFields := []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 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
|
||||
}
|
||||
headers = append(headers, label)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
headers = append(headers, "Adresse", "Commune", "EPCI", "Département", "Région", "Archivé")
|
||||
|
||||
spreadsheet.SetHeaders(headers)
|
||||
|
||||
for _, account := range accounts {
|
||||
row := []interface{}{}
|
||||
|
||||
row = append(row, account.ID)
|
||||
|
||||
for _, field := range beneficiaryFields {
|
||||
value := getAccountFieldValue(account.Data, field)
|
||||
if field == "gender" && value != "" {
|
||||
value = gender.ISO5218ToString(value)
|
||||
}
|
||||
row = append(row, value)
|
||||
}
|
||||
|
||||
// Address
|
||||
address := ""
|
||||
if addr, ok := account.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)
|
||||
|
||||
// Geographic info (Commune, EPCI, Département, Région)
|
||||
geoInfo := geoInfoMap[account.ID]
|
||||
row = append(row, geoInfo.Commune)
|
||||
row = append(row, geoInfo.EPCI)
|
||||
row = append(row, geoInfo.Departement)
|
||||
row = append(row, geoInfo.Region)
|
||||
|
||||
// Archived status
|
||||
archived := "Non"
|
||||
if archivedVal, ok := account.Data["archived"].(bool); ok && archivedVal {
|
||||
archived = "Oui"
|
||||
}
|
||||
row = append(row, archived)
|
||||
|
||||
spreadsheet.AddRow(row)
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
|
||||
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"export-beneficiaires.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
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,31 @@ import (
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
// resolveStatusLabel returns the display label for a manual status name
|
||||
func resolveStatusLabel(statusOptions interface{}, manualStatus string) string {
|
||||
switch opts := statusOptions.(type) {
|
||||
case []map[string]any:
|
||||
for _, opt := range opts {
|
||||
if name, _ := opt["name"].(string); name == manualStatus {
|
||||
if label, ok := opt["label"].(string); ok {
|
||||
return label
|
||||
}
|
||||
}
|
||||
}
|
||||
case []interface{}:
|
||||
for _, opt := range opts {
|
||||
if optMap, ok := opt.(map[string]interface{}); ok {
|
||||
if name, _ := optMap["name"].(string); name == manualStatus {
|
||||
if label, ok := optMap["label"].(string); ok {
|
||||
return label
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return manualStatus
|
||||
}
|
||||
|
||||
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")
|
||||
@@ -55,6 +80,10 @@ func (r *XLSXRenderer) VehicleBookings(w http.ResponseWriter, bookings []fleetss
|
||||
|
||||
spreadsheet.SetHeaders(headers)
|
||||
|
||||
// Read status management config
|
||||
isManualStatus := r.Config.GetString("modules.vehicles.status_management") == "manual"
|
||||
statusOptions := r.Config.Get("modules.vehicles.status_options")
|
||||
|
||||
// Add data rows
|
||||
for _, booking := range bookings {
|
||||
vehicle := vehiclesMap[booking.Vehicleid]
|
||||
@@ -69,6 +98,8 @@ func (r *XLSXRenderer) VehicleBookings(w http.ResponseWriter, bookings []fleetss
|
||||
status := ""
|
||||
if booking.Deleted {
|
||||
status = "Annulé"
|
||||
} else if isManualStatus {
|
||||
status = resolveStatusLabel(statusOptions, booking.ManualStatus)
|
||||
} else {
|
||||
switch booking.Status() {
|
||||
case 1:
|
||||
@@ -216,6 +247,10 @@ func (r *XLSXRenderer) VehicleBookingsAdmin(w http.ResponseWriter, bookings []fl
|
||||
|
||||
spreadsheet.SetHeaders(headers)
|
||||
|
||||
// Read status management config
|
||||
isManualStatusAdmin := r.Config.GetString("modules.vehicles.status_management") == "manual"
|
||||
statusOptionsAdmin := r.Config.Get("modules.vehicles.status_options")
|
||||
|
||||
// Add data rows
|
||||
for _, booking := range bookings {
|
||||
// Get vehicle from map
|
||||
@@ -243,6 +278,8 @@ func (r *XLSXRenderer) VehicleBookingsAdmin(w http.ResponseWriter, bookings []fl
|
||||
status := ""
|
||||
if booking.Deleted {
|
||||
status = "Annulé"
|
||||
} else if isManualStatusAdmin {
|
||||
status = resolveStatusLabel(statusOptionsAdmin, booking.ManualStatus)
|
||||
} else {
|
||||
switch booking.Status() {
|
||||
case 1:
|
||||
|
||||
9
servers/web/app_search_routes.go
Normal file
9
servers/web/app_search_routes.go
Normal file
@@ -0,0 +1,9 @@
|
||||
package web
|
||||
|
||||
import (
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
func (ws *WebServer) setupSearchRoutes(appRouter *mux.Router) {
|
||||
appRouter.HandleFunc("/search", ws.appHandler.GlobalSearchHTTPHandler())
|
||||
}
|
||||
@@ -17,6 +17,7 @@ func (ws *WebServer) setupVehiclesManagementRoutes(appRouter *mux.Router) {
|
||||
// Bookings
|
||||
vehiclesManagement.HandleFunc("/bookings/", ws.appHandler.VehiclesManagementBookingsListHTTPHandler())
|
||||
vehiclesManagement.HandleFunc("/bookings/{bookingid}", ws.appHandler.VehicleManagementBookingDisplayHTTPHandler())
|
||||
vehiclesManagement.HandleFunc("/bookings/{bookingid}/status", ws.appHandler.VehicleManagementUpdateBookingStatusHTTPHandler()).Methods("POST")
|
||||
vehiclesManagement.HandleFunc("/bookings/{bookingid}/change-vehicle", ws.appHandler.VehicleManagementBookingChangeVehicleHTTPHandler())
|
||||
vehiclesManagement.HandleFunc("/bookings/{bookingid}/delete", ws.appHandler.DeleteBookingHTTPHandler())
|
||||
vehiclesManagement.HandleFunc("/bookings/{bookingid}/unbooking", ws.appHandler.UnbookingVehicleHTTPHandler())
|
||||
|
||||
@@ -5,7 +5,9 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
formvalidators "git.coopgo.io/coopgo-apps/parcoursmob/core/utils/form-validators"
|
||||
@@ -83,14 +85,64 @@ func (h *Handler) BeneficiariesListHTTPHandler() http.HandlerFunc {
|
||||
archivedFilter = true
|
||||
}
|
||||
|
||||
result, err := h.applicationHandler.GetBeneficiaries(r.Context(), searchFilter, archivedFilter)
|
||||
// Extract beneficiary address geography filter
|
||||
beneficiaryAddressGeo := r.URL.Query().Get("beneficiary_address_geo")
|
||||
addressGeoLayer, addressGeoCode := "", ""
|
||||
if beneficiaryAddressGeo != "" {
|
||||
parts := strings.SplitN(beneficiaryAddressGeo, ":", 2)
|
||||
if len(parts) == 2 {
|
||||
addressGeoLayer, addressGeoCode = parts[0], parts[1]
|
||||
}
|
||||
}
|
||||
|
||||
result, err := h.applicationHandler.GetBeneficiaries(r.Context(), searchFilter, archivedFilter, addressGeoLayer, addressGeoCode)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error retrieving beneficiaries")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
h.renderer.BeneficiariesList(w, r, result.Accounts, result.CacheID, archivedFilter)
|
||||
// Enrich geography filters with names from geography service
|
||||
var enrichedGeoFilters []map[string]string
|
||||
if h.cfg.GetBool("geography.filters.enabled") {
|
||||
geoFilters := h.cfg.Get("geography.filters.geographies")
|
||||
if geoList, ok := geoFilters.([]any); ok {
|
||||
for _, geoItem := range geoList {
|
||||
if geoMap, ok := geoItem.(map[string]any); ok {
|
||||
layer := ""
|
||||
code := ""
|
||||
if l, ok := geoMap["layer"].(string); ok {
|
||||
layer = l
|
||||
}
|
||||
if c, ok := geoMap["code"].(string); ok {
|
||||
code = c
|
||||
}
|
||||
|
||||
enrichedGeo := map[string]string{
|
||||
"layer": layer,
|
||||
"code": code,
|
||||
"name": code,
|
||||
}
|
||||
|
||||
if layer != "" && code != "" {
|
||||
if geoFeature, err := h.services.Geography.Find(layer, code); err == nil {
|
||||
if name := geoFeature.Properties.MustString("nom"); name != "" {
|
||||
enrichedGeo["name"] = name
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enrichedGeoFilters = append(enrichedGeoFilters, enrichedGeo)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sort.Slice(enrichedGeoFilters, func(i, j int) bool {
|
||||
return enrichedGeoFilters[i]["name"] < enrichedGeoFilters[j]["name"]
|
||||
})
|
||||
}
|
||||
|
||||
h.renderer.BeneficiariesList(w, r, result.Accounts, result.CacheID, archivedFilter, enrichedGeoFilters, beneficiaryAddressGeo)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -159,6 +159,7 @@ func (h *Handler) JourneysSearchHTTPHandler() http.HandlerFunc {
|
||||
destinationGeo,
|
||||
departureDate,
|
||||
departureTime,
|
||||
departureDateTime,
|
||||
result.DriverJourneys,
|
||||
result.Drivers,
|
||||
result.OrganizedCarpools,
|
||||
@@ -167,6 +168,8 @@ func (h *Handler) JourneysSearchHTTPHandler() http.HandlerFunc {
|
||||
passengerID,
|
||||
savedSearches,
|
||||
beneficiariesMap,
|
||||
result.DriverLastTrips,
|
||||
result.LastTripDays,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
26
servers/web/application/search.go
Normal file
26
servers/web/application/search.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
func (h *Handler) GlobalSearchHTTPHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
query := r.URL.Query().Get("q")
|
||||
if query == "" {
|
||||
http.Redirect(w, r, "/app/", http.StatusFound)
|
||||
return
|
||||
}
|
||||
|
||||
result, err := h.applicationHandler.GlobalSearch(r.Context(), query)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error performing global search")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
h.renderer.GlobalSearchResults(w, r, query, result.Beneficiaries, result.SolidarityDrivers, result.OrganizedCarpoolDrivers)
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
coreapplication "git.coopgo.io/coopgo-apps/parcoursmob/core/application"
|
||||
"git.coopgo.io/coopgo-apps/parcoursmob/core/utils/identification"
|
||||
groupstorage "git.coopgo.io/coopgo-platform/groups-management/storage"
|
||||
mobilityaccountsstorage "git.coopgo.io/coopgo-platform/mobility-accounts/storage"
|
||||
@@ -614,12 +615,17 @@ func (h *Handler) SolidarityTransportBookingDisplayHTTPHandler() http.HandlerFun
|
||||
var replacementDriversMap map[string]mobilityaccountsstorage.Account
|
||||
var replacementPricing map[string]map[string]interface{}
|
||||
var replacementLocations map[string]string
|
||||
var driverLastTrips map[string]time.Time
|
||||
var lastTripDays int
|
||||
if result.Booking.Status == "CANCELLED" {
|
||||
// Initialize maps to avoid nil pointer in template
|
||||
replacementDriversMap = make(map[string]mobilityaccountsstorage.Account)
|
||||
replacementPricing = make(map[string]map[string]interface{})
|
||||
replacementLocations = make(map[string]string)
|
||||
driverLastTrips = make(map[string]time.Time)
|
||||
|
||||
// Preserve the original booking's noreturn status when searching for replacement drivers
|
||||
noreturn := result.Booking.Journey.Noreturn
|
||||
searchResult, err := h.applicationHandler.SearchJourneys(
|
||||
r.Context(),
|
||||
result.Booking.Journey.PassengerPickupDate,
|
||||
@@ -628,11 +634,15 @@ func (h *Handler) SolidarityTransportBookingDisplayHTTPHandler() http.HandlerFun
|
||||
result.Booking.PassengerId,
|
||||
result.Booking.DriverId, // Exclude the original driver
|
||||
result.Booking.GroupId, // Exclude drivers with bookings in this group
|
||||
nil, // options - use defaults
|
||||
&coreapplication.SearchJourneyOptions{
|
||||
SolidarityTransportNoreturn: &noreturn,
|
||||
},
|
||||
)
|
||||
if err == nil {
|
||||
replacementDrivers = searchResult.DriverJourneys
|
||||
replacementDriversMap = searchResult.Drivers
|
||||
driverLastTrips = searchResult.DriverLastTrips
|
||||
lastTripDays = searchResult.LastTripDays
|
||||
|
||||
// Calculate pricing for each replacement driver journey
|
||||
for _, dj := range searchResult.DriverJourneys {
|
||||
@@ -668,7 +678,7 @@ func (h *Handler) SolidarityTransportBookingDisplayHTTPHandler() http.HandlerFun
|
||||
}
|
||||
}
|
||||
|
||||
h.renderer.SolidarityTransportBookingDisplay(w, r, result.Booking, result.Driver, result.Passenger, result.PassengerWalletBalance, replacementDrivers, replacementDriversMap, replacementPricing, replacementLocations)
|
||||
h.renderer.SolidarityTransportBookingDisplay(w, r, result.Booking, result.Driver, result.Passenger, result.PassengerWalletBalance, replacementDrivers, replacementDriversMap, replacementPricing, replacementLocations, driverLastTrips, lastTripDays)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.coopgo.io/coopgo-apps/parcoursmob/core/utils/identification"
|
||||
@@ -22,14 +23,37 @@ func (h *Handler) VehiclesManagementOverviewHTTPHandler() http.HandlerFunc {
|
||||
groupID = group.ID
|
||||
}
|
||||
|
||||
result, err := h.applicationHandler.GetVehiclesManagementOverview(r.Context(), groupID)
|
||||
// Extract tab and filter parameters from query
|
||||
tab := r.URL.Query().Get("tab")
|
||||
vehicleType := r.URL.Query().Get("vehicle_type")
|
||||
status := r.URL.Query().Get("status")
|
||||
if _, hasStatus := r.URL.Query()["status"]; !hasStatus {
|
||||
status = "meta:open,active"
|
||||
}
|
||||
dateStart := r.URL.Query().Get("date_start")
|
||||
dateEnd := r.URL.Query().Get("date_end")
|
||||
vType := r.URL.Query().Get("v_type")
|
||||
vStatus := r.URL.Query().Get("v_status")
|
||||
|
||||
result, err := h.applicationHandler.GetVehiclesManagementOverview(r.Context(), groupID, vehicleType, status, dateStart, dateEnd, vType, vStatus)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error retrieving vehicles management overview")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
h.renderer.VehiclesManagementOverview(w, r, result.Vehicles, result.VehiclesMap, result.DriversMap, result.Bookings)
|
||||
filters := map[string]string{
|
||||
"vehicle_type": vehicleType,
|
||||
"status": status,
|
||||
"date_start": dateStart,
|
||||
"date_end": dateEnd,
|
||||
"v_type": vType,
|
||||
"v_status": vStatus,
|
||||
}
|
||||
|
||||
vehicleTypes, _ := h.applicationHandler.GetVehicleTypes(r.Context())
|
||||
|
||||
h.renderer.VehiclesManagementOverview(w, r, result.Vehicles, result.VehiclesMap, result.DriversMap, result.Bookings, filters, vehicleTypes, tab)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,6 +70,7 @@ func (h *Handler) VehiclesManagementBookingsListHTTPHandler() http.HandlerFunc {
|
||||
status := r.URL.Query().Get("status")
|
||||
dateStart := r.URL.Query().Get("date_start")
|
||||
dateEnd := r.URL.Query().Get("date_end")
|
||||
vehicleType := r.URL.Query().Get("vehicle_type")
|
||||
|
||||
// Default to last month if no dates specified
|
||||
if dateStart == "" {
|
||||
@@ -55,7 +80,7 @@ func (h *Handler) VehiclesManagementBookingsListHTTPHandler() http.HandlerFunc {
|
||||
dateEnd = time.Now().Format("2006-01-02")
|
||||
}
|
||||
|
||||
result, err := h.applicationHandler.GetVehiclesManagementBookingsList(r.Context(), groupID, status, dateStart, dateEnd)
|
||||
result, err := h.applicationHandler.GetVehiclesManagementBookingsList(r.Context(), groupID, status, dateStart, dateEnd, vehicleType)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error retrieving vehicles management bookings list")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
@@ -64,12 +89,15 @@ func (h *Handler) VehiclesManagementBookingsListHTTPHandler() http.HandlerFunc {
|
||||
|
||||
// Prepare filters map for template
|
||||
filters := map[string]string{
|
||||
"status": status,
|
||||
"date_start": dateStart,
|
||||
"date_end": dateEnd,
|
||||
"status": status,
|
||||
"date_start": dateStart,
|
||||
"date_end": dateEnd,
|
||||
"vehicle_type": vehicleType,
|
||||
}
|
||||
|
||||
h.renderer.VehiclesManagementBookingsList(w, r, result.VehiclesMap, result.DriversMap, result.Bookings, result.CacheID, filters)
|
||||
vehicleTypes, _ := h.applicationHandler.GetVehicleTypes(r.Context())
|
||||
|
||||
h.renderer.VehiclesManagementBookingsList(w, r, result.VehiclesMap, result.DriversMap, result.Bookings, result.CacheID, filters, vehicleTypes)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -223,7 +251,7 @@ func (h *Handler) VehicleManagementBookingDisplayHTTPHandler() http.HandlerFunc
|
||||
return
|
||||
}
|
||||
|
||||
h.renderer.VehicleManagementBookingDisplay(w, r, result.Booking, result.Vehicle, result.Beneficiary, result.Group, result.Documents, result.FileTypesMap, result.Alternatives)
|
||||
h.renderer.VehicleManagementBookingDisplay(w, r, result.Booking, result.Vehicle, result.Beneficiary, result.Group, result.Documents, result.FileTypesMap, result.Alternatives, result.ComputedExtraProperties)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -355,6 +383,38 @@ func (h *Handler) UnbookingVehicleHTTPHandler() http.HandlerFunc {
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) VehicleManagementUpdateBookingStatusHTTPHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
bookingID := vars["bookingid"]
|
||||
|
||||
r.ParseForm()
|
||||
newStatus := r.FormValue("new_status")
|
||||
comment := r.FormValue("comment")
|
||||
|
||||
// Collect extra properties from prop_* form fields
|
||||
extraProperties := map[string]string{}
|
||||
for key, values := range r.Form {
|
||||
if strings.HasPrefix(key, "prop_") && len(values) > 0 {
|
||||
extraProperties[strings.TrimPrefix(key, "prop_")] = values[0]
|
||||
}
|
||||
}
|
||||
|
||||
currentUserToken := r.Context().Value(identification.IdtokenKey).(*oidc.IDToken)
|
||||
currentUserClaims := r.Context().Value(identification.ClaimsKey).(map[string]any)
|
||||
currentGroup := r.Context().Value(identification.GroupKey)
|
||||
|
||||
err := h.applicationHandler.UpdateBookingStatus(r.Context(), bookingID, newStatus, comment, currentUserToken.Subject, currentUserClaims, currentGroup, extraProperties)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error updating booking status")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
http.Redirect(w, r, fmt.Sprintf("/app/vehicles-management/bookings/%s", bookingID), http.StatusFound)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) VehiclesFleetUpdateHTTPHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
|
||||
@@ -8,6 +8,7 @@ func (ws *WebServer) setupApplicationRoutes(r *mux.Router) {
|
||||
application := r.PathPrefix("/app").Subrouter()
|
||||
|
||||
// Setup all application route groups
|
||||
ws.setupSearchRoutes(application)
|
||||
ws.setupDashboardRoutes(application)
|
||||
setupMiscRoutes(application, ws.applicationHandler)
|
||||
ws.setupDirectoryRoutes(application)
|
||||
|
||||
63
servers/web/exports/beneficiaries.go
Normal file
63
servers/web/exports/beneficiaries.go
Normal file
@@ -0,0 +1,63 @@
|
||||
package exports
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
xlsxrenderer "git.coopgo.io/coopgo-apps/parcoursmob/renderer/xlsx"
|
||||
"github.com/paulmach/orb/geojson"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
func (h *Handler) Beneficiaries(w http.ResponseWriter, r *http.Request) {
|
||||
archivedFilter := r.URL.Query().Get("archived") == "true"
|
||||
beneficiaryAddressGeo := r.URL.Query().Get("beneficiary_address_geo")
|
||||
|
||||
addressGeoLayer, addressGeoCode := "", ""
|
||||
if beneficiaryAddressGeo != "" {
|
||||
parts := strings.SplitN(beneficiaryAddressGeo, ":", 2)
|
||||
if len(parts) == 2 {
|
||||
addressGeoLayer, addressGeoCode = parts[0], parts[1]
|
||||
}
|
||||
}
|
||||
|
||||
result, err := h.applicationHandler.GetBeneficiaries(r.Context(), "", archivedFilter, addressGeoLayer, addressGeoCode)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Failed to get beneficiaries")
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// Resolve geographic layers (EPCI, Département, Région) for each beneficiary
|
||||
geoInfoMap := map[string]xlsxrenderer.BeneficiaryGeoInfo{}
|
||||
for _, account := range result.Accounts {
|
||||
if addr, ok := account.Data["address"]; ok {
|
||||
jsonAddr, err := json.Marshal(addr)
|
||||
if err == nil {
|
||||
addrFeature, err := geojson.UnmarshalFeature(jsonAddr)
|
||||
if err == nil && addrFeature.Geometry != nil {
|
||||
geo, err := h.services.Geography.GeoSearch(addrFeature)
|
||||
if err == nil {
|
||||
info := xlsxrenderer.BeneficiaryGeoInfo{}
|
||||
if commune, ok := geo["communes"]; ok {
|
||||
info.Commune = commune.Properties.MustString("nom")
|
||||
}
|
||||
if epci, ok := geo["epci"]; ok {
|
||||
info.EPCI = epci.Properties.MustString("nom")
|
||||
}
|
||||
if dept, ok := geo["departements"]; ok {
|
||||
info.Departement = dept.Properties.MustString("nom")
|
||||
}
|
||||
if region, ok := geo["regions"]; ok {
|
||||
info.Region = region.Properties.MustString("nom")
|
||||
}
|
||||
geoInfoMap[account.ID] = info
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
h.renderer.XLSX.Beneficiaries(w, result.Accounts, geoInfoMap)
|
||||
}
|
||||
@@ -53,7 +53,9 @@ func (h *Handler) FleetBookingsInGroup(w http.ResponseWriter, r *http.Request) {
|
||||
dateStart := r.URL.Query().Get("date_start")
|
||||
dateEnd := r.URL.Query().Get("date_end")
|
||||
|
||||
result, err := h.applicationHandler.GetVehiclesManagementBookingsList(r.Context(), group.ID, status, dateStart, dateEnd)
|
||||
vehicleType := r.URL.Query().Get("vehicle_type")
|
||||
|
||||
result, err := h.applicationHandler.GetVehiclesManagementBookingsList(r.Context(), group.ID, status, dateStart, dateEnd, vehicleType)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Failed to get vehicle bookings for export")
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"git.coopgo.io/coopgo-apps/parcoursmob/core/application"
|
||||
"git.coopgo.io/coopgo-apps/parcoursmob/core/utils/identification"
|
||||
"git.coopgo.io/coopgo-apps/parcoursmob/renderer"
|
||||
"git.coopgo.io/coopgo-apps/parcoursmob/services"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
@@ -14,14 +15,16 @@ type Handler struct {
|
||||
applicationHandler *application.ApplicationHandler
|
||||
idp *identification.IdentificationProvider
|
||||
renderer *renderer.Renderer
|
||||
services *services.ServicesHandler
|
||||
}
|
||||
|
||||
func NewHandler(cfg *viper.Viper, applicationHandler *application.ApplicationHandler, idp *identification.IdentificationProvider, renderer *renderer.Renderer) *Handler {
|
||||
func NewHandler(cfg *viper.Viper, applicationHandler *application.ApplicationHandler, idp *identification.IdentificationProvider, renderer *renderer.Renderer, services *services.ServicesHandler) *Handler {
|
||||
return &Handler{
|
||||
config: cfg,
|
||||
applicationHandler: applicationHandler,
|
||||
idp: idp,
|
||||
renderer: renderer,
|
||||
services: services,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ func (ws *WebServer) setupExportsRoutes(r *mux.Router) {
|
||||
export.HandleFunc("/solidarity-transport/drivers.xlsx", ws.exportsHandler.SolidarityTransportDrivers)
|
||||
export.HandleFunc("/organized-carpool/bookings.xlsx", ws.exportsHandler.OrganizedCarpoolBookings)
|
||||
export.HandleFunc("/organized-carpool/drivers.xlsx", ws.exportsHandler.OrganizedCarpoolDrivers)
|
||||
export.HandleFunc("/beneficiaries/beneficiaries.xlsx", ws.exportsHandler.Beneficiaries)
|
||||
export.Use(ws.idp.Middleware)
|
||||
export.Use(ws.idp.GroupsMiddleware)
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ func Run(cfg *viper.Viper, services *services.ServicesHandler, renderer *rendere
|
||||
// Initialize web handler subpackages
|
||||
appHandler: webapplication.NewHandler(cfg, renderer, applicationHandler, idp, services),
|
||||
authHandler: webauth.NewHandler(cfg, applicationHandler, idp, renderer),
|
||||
exportsHandler: webexports.NewHandler(cfg, applicationHandler, idp, renderer),
|
||||
exportsHandler: webexports.NewHandler(cfg, applicationHandler, idp, renderer, services),
|
||||
extHandler: webexternal.NewHandler(cfg, applicationHandler, filestorage),
|
||||
protectedAPIHandler: webprotectedapi.NewHandler(cfg, applicationHandler),
|
||||
webAPIHandler: webapi.NewHandler(cfg, idp, applicationHandler, cacheHandler),
|
||||
|
||||
Reference in New Issue
Block a user