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{
|
"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{
|
"vehicles_management": map[string]any{
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
@@ -254,11 +273,11 @@ func ReadConfig() (*viper.Viper, error) {
|
|||||||
"geo": map[string]any{
|
"geo": map[string]any{
|
||||||
"type": "addok", // Options: "pelias", "addok"
|
"type": "addok", // Options: "pelias", "addok"
|
||||||
"pelias": map[string]any{
|
"pelias": map[string]any{
|
||||||
"url": "https://geocode.ridygo.fr",
|
"url": "http://57.128.110.46:4000/V1",
|
||||||
"autocomplete": "/autocomplete?text=",
|
"autocomplete": "/autocomplete?text=",
|
||||||
},
|
},
|
||||||
"addok": map[string]any{
|
"addok": map[string]any{
|
||||||
"url": "https://api-adresse.data.gouv.fr",
|
"url": "https://data.geopf.fr/geocodage/",
|
||||||
"autocomplete": "/search/?q=",
|
"autocomplete": "/search/?q=",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -31,6 +31,8 @@ import (
|
|||||||
solidaritytransformers "git.coopgo.io/coopgo-platform/solidarity-transport/servers/grpc/transformers"
|
solidaritytransformers "git.coopgo.io/coopgo-platform/solidarity-transport/servers/grpc/transformers"
|
||||||
solidaritytypes "git.coopgo.io/coopgo-platform/solidarity-transport/types"
|
solidaritytypes "git.coopgo.io/coopgo-platform/solidarity-transport/types"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
"github.com/paulmach/orb"
|
||||||
|
"github.com/paulmach/orb/geojson"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
"google.golang.org/protobuf/types/known/structpb"
|
"google.golang.org/protobuf/types/known/structpb"
|
||||||
"google.golang.org/protobuf/types/known/timestamppb"
|
"google.golang.org/protobuf/types/known/timestamppb"
|
||||||
@@ -41,12 +43,38 @@ type BeneficiariesResult struct {
|
|||||||
CacheID string
|
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)
|
accounts, err := h.getBeneficiariesWithFilters(ctx, searchFilter, archivedFilter)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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))
|
sort.Sort(sorting.BeneficiariesByName(accounts))
|
||||||
|
|
||||||
cacheID := uuid.NewString()
|
cacheID := uuid.NewString()
|
||||||
|
|||||||
@@ -36,16 +36,19 @@ type SearchJourneysResult struct {
|
|||||||
Drivers map[string]mobilityaccountsstorage.Account
|
Drivers map[string]mobilityaccountsstorage.Account
|
||||||
OrganizedCarpools []*carpoolproto.CarpoolServiceDriverJourney
|
OrganizedCarpools []*carpoolproto.CarpoolServiceDriverJourney
|
||||||
KnowledgeBaseResults []any
|
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
|
// SearchJourneyOptions contains per-request options for journey search
|
||||||
type SearchJourneyOptions struct {
|
type SearchJourneyOptions struct {
|
||||||
DisableSolidarityTransport bool
|
DisableSolidarityTransport bool
|
||||||
DisableOrganizedCarpool bool
|
DisableOrganizedCarpool bool
|
||||||
DisableCarpoolOperators bool
|
DisableCarpoolOperators bool
|
||||||
DisableTransit bool
|
DisableTransit bool
|
||||||
DisableFleetVehicles bool
|
DisableFleetVehicles bool
|
||||||
DisableKnowledgeBase bool
|
DisableKnowledgeBase bool
|
||||||
|
SolidarityTransportNoreturn *bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// SearchJourneys performs the business logic for journey search
|
// SearchJourneys performs the business logic for journey search
|
||||||
@@ -111,6 +114,10 @@ func (h *ApplicationHandler) SearchJourneys(
|
|||||||
if solidarityExcludeGroupId != "" {
|
if solidarityExcludeGroupId != "" {
|
||||||
req.ExcludeGroupId = &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)
|
res, err := h.services.GRPC.SolidarityTransport.GetDriverJourneys(ctx, req)
|
||||||
if err != nil {
|
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{
|
return &SearchJourneysResult{
|
||||||
CarpoolResults: carpoolResults,
|
CarpoolResults: carpoolResults,
|
||||||
TransitResults: transitResults,
|
TransitResults: transitResults,
|
||||||
@@ -300,6 +334,8 @@ func (h *ApplicationHandler) SearchJourneys(
|
|||||||
Drivers: drivers,
|
Drivers: drivers,
|
||||||
OrganizedCarpools: organizedCarpoolResults,
|
OrganizedCarpools: organizedCarpoolResults,
|
||||||
KnowledgeBaseResults: knowledgeBaseResults,
|
KnowledgeBaseResults: knowledgeBaseResults,
|
||||||
|
DriverLastTrips: driverLastTrips,
|
||||||
|
LastTripDays: lastTripDays,
|
||||||
}, nil
|
}, 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 {
|
type SolidarityTransportDriverDataResult struct {
|
||||||
Driver mobilityaccountsstorage.Account
|
Driver mobilityaccountsstorage.Account
|
||||||
Availabilities []*gen.DriverRegularAvailability
|
Availabilities []*solidaritytypes.DriverRegularAvailability
|
||||||
Documents []filestorage.FileInfo
|
Documents []filestorage.FileInfo
|
||||||
Bookings []*solidaritytypes.Booking
|
Bookings []*solidaritytypes.Booking
|
||||||
BeneficiariesMap map[string]mobilityaccountsstorage.Account
|
BeneficiariesMap map[string]mobilityaccountsstorage.Account
|
||||||
@@ -733,6 +733,12 @@ func (h *ApplicationHandler) GetSolidarityTransportDriverData(ctx context.Contex
|
|||||||
return nil, err
|
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
|
// Get documents
|
||||||
documents := h.filestorage.List(filestorage.PREFIX_SOLIDARITY_TRANSPORT_DRIVERS + "/" + driverID)
|
documents := h.filestorage.List(filestorage.PREFIX_SOLIDARITY_TRANSPORT_DRIVERS + "/" + driverID)
|
||||||
|
|
||||||
@@ -812,7 +818,7 @@ func (h *ApplicationHandler) GetSolidarityTransportDriverData(ctx context.Contex
|
|||||||
|
|
||||||
return &SolidarityTransportDriverDataResult{
|
return &SolidarityTransportDriverDataResult{
|
||||||
Driver: driver,
|
Driver: driver,
|
||||||
Availabilities: availResp.Results,
|
Availabilities: availabilities,
|
||||||
Documents: documents,
|
Documents: documents,
|
||||||
Bookings: bookings,
|
Bookings: bookings,
|
||||||
BeneficiariesMap: beneficiariesMap,
|
BeneficiariesMap: beneficiariesMap,
|
||||||
@@ -1391,7 +1397,11 @@ func (h *ApplicationHandler) calculateSolidarityTransportPricing(ctx context.Con
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err == nil {
|
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
|
var passengerGeo pricing.GeographyParams
|
||||||
|
|||||||
@@ -3,7 +3,10 @@ package application
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math"
|
||||||
"sort"
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.coopgo.io/coopgo-apps/parcoursmob/core/utils/identification"
|
"git.coopgo.io/coopgo-apps/parcoursmob/core/utils/identification"
|
||||||
@@ -28,7 +31,7 @@ type VehiclesManagementOverviewResult struct {
|
|||||||
Bookings []fleetsstorage.Booking
|
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{
|
request := &fleets.GetVehiclesRequest{
|
||||||
Namespaces: []string{"parcoursmob"},
|
Namespaces: []string{"parcoursmob"},
|
||||||
}
|
}
|
||||||
@@ -41,14 +44,82 @@ func (h *ApplicationHandler) GetVehiclesManagementOverview(ctx context.Context,
|
|||||||
bookings := []fleetsstorage.Booking{}
|
bookings := []fleetsstorage.Booking{}
|
||||||
vehiclesMap := map[string]fleetsstorage.Vehicle{}
|
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 {
|
for _, vehicle := range resp.Vehicles {
|
||||||
if h.filterVehicleByGroup(vehicle, groupID) {
|
if h.filterVehicleByGroup(vehicle, groupID) {
|
||||||
v := vehicle.ToStorageType()
|
v := vehicle.ToStorageType()
|
||||||
vehicleBookings := []fleetsstorage.Booking{}
|
vehicleBookings := []fleetsstorage.Booking{}
|
||||||
for _, b := range v.Bookings {
|
for _, b := range v.Bookings {
|
||||||
log.Debug().Any("booking", b).Msg("debug")
|
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)
|
bookings = append(bookings, b)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -57,8 +128,48 @@ func (h *ApplicationHandler) GetVehiclesManagementOverview(ctx context.Context,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
v.Bookings = vehicleBookings
|
v.Bookings = vehicleBookings
|
||||||
vehicles = append(vehicles, v)
|
|
||||||
|
// Always add to vehiclesMap for booking lookups
|
||||||
vehiclesMap[v.ID] = v
|
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
|
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{
|
request := &fleets.GetVehiclesRequest{
|
||||||
Namespaces: []string{"parcoursmob"},
|
Namespaces: []string{"parcoursmob"},
|
||||||
IncludeDeleted: true,
|
IncludeDeleted: true,
|
||||||
@@ -129,40 +240,61 @@ func (h *ApplicationHandler) GetVehiclesManagementBookingsList(ctx context.Conte
|
|||||||
v := vehicle.ToStorageType()
|
v := vehicle.ToStorageType()
|
||||||
vehiclesMap[v.ID] = v
|
vehiclesMap[v.ID] = v
|
||||||
for _, b := range v.Bookings {
|
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
|
// Apply status filter
|
||||||
if status != "" {
|
if status != "" {
|
||||||
bookingStatus := b.Status()
|
if h.config.GetString("modules.vehicles.status_management") == "manual" {
|
||||||
statusInt := 0
|
if strings.HasPrefix(status, "meta:") {
|
||||||
|
metaParts := strings.Split(strings.TrimPrefix(status, "meta:"), ",")
|
||||||
if b.Deleted {
|
metaSet := map[string]bool{}
|
||||||
statusInt = -2 // Use -2 for cancelled to distinguish from terminated
|
for _, ms := range metaParts {
|
||||||
|
metaSet[ms] = true
|
||||||
|
}
|
||||||
|
allowedStatuses := h.resolveMetaStatuses(metaSet)
|
||||||
|
if !allowedStatuses[b.ManualStatus] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
} else if b.ManualStatus != status {
|
||||||
|
continue
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
statusInt = bookingStatus
|
bookingStatus := b.Status()
|
||||||
}
|
statusInt := 0
|
||||||
|
|
||||||
// Map status string to int
|
if b.Deleted {
|
||||||
var filterStatusInt int
|
statusInt = -2 // Use -2 for cancelled to distinguish from terminated
|
||||||
switch status {
|
} else {
|
||||||
case "FORTHCOMING":
|
statusInt = bookingStatus
|
||||||
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 {
|
// Map status string to int
|
||||||
continue
|
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)
|
// Apply date filter (intersection: booking overlaps with [startdate, enddate])
|
||||||
if !startdate.IsZero() && b.Startdate.Before(startdate) {
|
if !startdate.IsZero() && b.Enddate.Before(startdate) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if !enddate.IsZero() && b.Startdate.After(enddate) {
|
if !enddate.IsZero() && b.Startdate.After(enddate) {
|
||||||
@@ -324,13 +456,14 @@ func (h *ApplicationHandler) UpdateBooking(ctx context.Context, bookingID string
|
|||||||
}
|
}
|
||||||
|
|
||||||
type BookingDisplayResult struct {
|
type BookingDisplayResult struct {
|
||||||
Booking fleetsstorage.Booking
|
Booking fleetsstorage.Booking
|
||||||
Vehicle fleetsstorage.Vehicle
|
Vehicle fleetsstorage.Vehicle
|
||||||
Beneficiary mobilityaccountsstorage.Account
|
Beneficiary mobilityaccountsstorage.Account
|
||||||
Group storage.Group
|
Group storage.Group
|
||||||
Documents []filestorage.FileInfo
|
Documents []filestorage.FileInfo
|
||||||
FileTypesMap map[string]string
|
FileTypesMap map[string]string
|
||||||
Alternatives []any
|
Alternatives []any
|
||||||
|
ComputedExtraProperties map[string]string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *ApplicationHandler) GetBookingDisplay(ctx context.Context, bookingID string) (*BookingDisplayResult, error) {
|
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)
|
documents := h.filestorage.List(filestorage.PREFIX_BOOKINGS + "/" + bookingID)
|
||||||
fileTypesMap := h.config.GetStringMapString("storage.files.file_types")
|
fileTypesMap := h.config.GetStringMapString("storage.files.file_types")
|
||||||
|
|
||||||
|
computedProps := h.computeExtraProperties(booking)
|
||||||
|
|
||||||
return &BookingDisplayResult{
|
return &BookingDisplayResult{
|
||||||
Booking: booking,
|
Booking: booking,
|
||||||
Vehicle: booking.Vehicle,
|
Vehicle: booking.Vehicle,
|
||||||
Beneficiary: beneficiary,
|
Beneficiary: beneficiary,
|
||||||
Group: groupresp.Group.ToStorageType(),
|
Group: groupresp.Group.ToStorageType(),
|
||||||
Documents: documents,
|
Documents: documents,
|
||||||
FileTypesMap: fileTypesMap,
|
FileTypesMap: fileTypesMap,
|
||||||
Alternatives: alternatives,
|
Alternatives: alternatives,
|
||||||
|
ComputedExtraProperties: computedProps,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -596,3 +732,351 @@ func (h *ApplicationHandler) UpdateVehicle(ctx context.Context, vehicleID, name,
|
|||||||
return updateResp.Vehicle.Id, nil
|
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,
|
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{
|
request := &fleets.CreateBookingRequest{
|
||||||
Booking: booking,
|
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/agenda v1.0.0
|
||||||
git.coopgo.io/coopgo-platform/carpool-service v0.0.0-20251008165122-38cb3c5ad9b4
|
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/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/geography v0.0.0-20251010131258-ec939649e858
|
||||||
git.coopgo.io/coopgo-platform/groups-management v0.0.0-20230310123255-5ef94ee0746c
|
git.coopgo.io/coopgo-platform/groups-management v0.0.0-20230310123255-5ef94ee0746c
|
||||||
git.coopgo.io/coopgo-platform/mobility-accounts v0.0.0-20230329105908-a76c0412a386
|
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/api/v3 v3.5.12 // indirect
|
||||||
go.etcd.io/etcd/client/pkg/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 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/atomic v1.11.0 // indirect
|
||||||
go.uber.org/multierr v1.11.0 // indirect
|
go.uber.org/multierr v1.11.0 // indirect
|
||||||
go.uber.org/zap v1.21.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/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 h1:Li3dotY6raKu9+oxEgICU7nwdomYpjgu19i3mZNiqTc=
|
||||||
git.coopgo.io/coopgo-platform/emailing v0.0.0-20250212064257-167ef5864260/go.mod h1:6cvvjv0RLSwBthIQ4TiuZoXFGvQXZ55hNSJchWXAgB4=
|
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.1-0.20260226165510-6007cffdf152 h1:kczmeGHnihYQSopDzbQ23B+P8Fw3GuB6iACW7SuDE+s=
|
||||||
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/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 h1:4E0tbT8jj5oxaK66Ny61o7zqPaVc0qRN2cZG9IUR4Es=
|
||||||
git.coopgo.io/coopgo-platform/geography v0.0.0-20251010131258-ec939649e858/go.mod h1:TbR3g1Awa8hpAe6LR1z1EQbv2IBVgN5JQ/FjXfKX4K0=
|
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=
|
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/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 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/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 h1:aoXSugsrZrM8E3WhqOCM+bLgGdxdf7dZAxx/vfbYzWQ=
|
||||||
git.coopgo.io/coopgo-platform/solidarity-transport v0.0.0-20260114093602-8875adbcbbee/go.mod h1:iaFXcIn7DYtKlLrSYs9C47Dt7eeMGYkpx+unLCx8TpQ=
|
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=
|
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.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 h1:jUorfmVzljjr0FLzYQsGP8cgN/qzzxlY9Vh0C9KFXVw=
|
||||||
go.mongodb.org/mongo-driver v1.17.4/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ=
|
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 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
||||||
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||||
go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ=
|
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()
|
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")
|
files := renderer.ThemeConfig.GetStringSlice("views.beneficiaries.list.files")
|
||||||
|
|
||||||
|
geoFiltersEnabled := len(enrichedGeoFilters) > 0
|
||||||
|
|
||||||
state := NewState(r, renderer.ThemeConfig, beneficiariesMenu)
|
state := NewState(r, renderer.ThemeConfig, beneficiariesMenu)
|
||||||
state.ViewState = BeneficiariesListState{
|
state.ViewState = map[string]any{
|
||||||
Count: len(accounts),
|
"list": BeneficiariesListState{
|
||||||
CacheId: cacheid,
|
Count: len(accounts),
|
||||||
Beneficiaries: accounts,
|
CacheId: cacheid,
|
||||||
Archived: archived,
|
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)
|
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)
|
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")
|
files := renderer.ThemeConfig.GetStringSlice("views.beneficiaries.update.files")
|
||||||
profileFields := renderer.GlobalConfig.Get("modules.beneficiaries.profile_optional_fields")
|
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,
|
"beneficiary": beneficiary,
|
||||||
"profile_optional_fields": profileFields,
|
"profile_optional_fields": profileFields,
|
||||||
}
|
}
|
||||||
|
state.DynamicData = beneficiary.Data
|
||||||
|
|
||||||
renderer.Render("beneficiaries_update", w, r, files, state)
|
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
|
// GetTemplateFuncMap returns the common template functions for rendering
|
||||||
func GetTemplateFuncMap(group groupsstorage.Group, globalConfig *viper.Viper, fileStorage filestorage.FileStorage) template.FuncMap {
|
func GetTemplateFuncMap(group groupsstorage.Group, globalConfig *viper.Viper, fileStorage filestorage.FileStorage) template.FuncMap {
|
||||||
return 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")),
|
"beneficiaryValidatedProfile": validatedprofile.ValidateProfile(globalConfig.Sub("modules.beneficiaries.validated_profile")),
|
||||||
"solidarityDriverValidatedProfile": validatedprofile.ValidateProfile(globalConfig.Sub("modules.solidarity_transport.drivers.validated_profile")),
|
"solidarityDriverValidatedProfile": validatedprofile.ValidateProfile(globalConfig.Sub("modules.solidarity_transport.drivers.validated_profile")),
|
||||||
"carpoolDriverValidatedProfile": validatedprofile.ValidateProfile(globalConfig.Sub("modules.organized_carpool.drivers.validated_profile")),
|
"carpoolDriverValidatedProfile": validatedprofile.ValidateProfile(globalConfig.Sub("modules.organized_carpool.drivers.validated_profile")),
|
||||||
|
"isPast": IsPast,
|
||||||
"isGuaranteedTripMotivation": IsGuaranteedTripMotivation(globalConfig),
|
"isGuaranteedTripMotivation": IsGuaranteedTripMotivation(globalConfig),
|
||||||
"beneficiaryDocuments": func(id string) []filestorage.FileInfo {
|
"beneficiaryDocuments": func(id string) []filestorage.FileInfo {
|
||||||
return fileStorage.List(filestorage.PREFIX_BENEFICIARIES + "/" + id)
|
return fileStorage.List(filestorage.PREFIX_BENEFICIARIES + "/" + id)
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ func (s BeneficiariesCovoiturage) JSONWithLimits(a int, b int) template.JS {
|
|||||||
return s.JSON()
|
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")
|
files := renderer.ThemeConfig.GetStringSlice("views.journeys.search.files")
|
||||||
state := NewState(r, renderer.ThemeConfig, journeysMenu)
|
state := NewState(r, renderer.ThemeConfig, journeysMenu)
|
||||||
journeyTabs := renderer.ThemeConfig.Get("journey_tabs")
|
journeyTabs := renderer.ThemeConfig.Get("journey_tabs")
|
||||||
@@ -44,6 +44,7 @@ func (renderer *Renderer) JourneysSearch(w http.ResponseWriter, r *http.Request,
|
|||||||
"searched": searched,
|
"searched": searched,
|
||||||
"departuredate": departuredate,
|
"departuredate": departuredate,
|
||||||
"departuretime": departuretime,
|
"departuretime": departuretime,
|
||||||
|
"departuredatetime": departuredatetime,
|
||||||
"departure": departure,
|
"departure": departure,
|
||||||
"destination": destination,
|
"destination": destination,
|
||||||
"journeys": transitjourneys,
|
"journeys": transitjourneys,
|
||||||
@@ -52,6 +53,8 @@ func (renderer *Renderer) JourneysSearch(w http.ResponseWriter, r *http.Request,
|
|||||||
"vehicles": vehicles,
|
"vehicles": vehicles,
|
||||||
"driver_journeys": driverJourneys,
|
"driver_journeys": driverJourneys,
|
||||||
"solidarity_drivers": solidarityDrivers,
|
"solidarity_drivers": solidarityDrivers,
|
||||||
|
"driver_last_trips": driverLastTrips,
|
||||||
|
"last_trip_days": lastTripDays,
|
||||||
"querystring": r.URL.RawQuery,
|
"querystring": r.URL.RawQuery,
|
||||||
"beneficiaries": beneficiariesMap,
|
"beneficiaries": beneficiariesMap,
|
||||||
"beneficiaries_list": beneficiaries,
|
"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) {
|
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")
|
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)
|
state := NewState(r, renderer.ThemeConfig, organizedCarpoolMenu)
|
||||||
|
|
||||||
drivers_file_types := renderer.GlobalConfig.GetStringSlice("modules.organized_carpool.drivers.documents_types")
|
drivers_file_types := renderer.GlobalConfig.GetStringSlice("modules.organized_carpool.drivers.documents_types")
|
||||||
file_types_map := renderer.GlobalConfig.GetStringMapString("storage.files.file_types")
|
file_types_map := renderer.GlobalConfig.GetStringMapString("storage.files.file_types")
|
||||||
|
|
||||||
state.ViewState = map[string]any{
|
state.ViewState = map[string]any{
|
||||||
"driver": driver,
|
"driver": driver,
|
||||||
"trips": trips,
|
"trips": trips,
|
||||||
"documents": documents,
|
"documents": documents,
|
||||||
"bookings": bookings,
|
"bookings": bookings,
|
||||||
"beneficiaries_map": beneficiariesMap,
|
"beneficiaries_map": beneficiariesMap,
|
||||||
"stats": stats,
|
"stats": stats,
|
||||||
"drivers_file_types": drivers_file_types,
|
"drivers_file_types": drivers_file_types,
|
||||||
"file_types_map": file_types_map,
|
"file_types_map": file_types_map,
|
||||||
"wallet_balance": walletBalance,
|
"profile_optional_fields": profileFields,
|
||||||
"tab": tab,
|
"wallet_balance": walletBalance,
|
||||||
|
"tab": tab,
|
||||||
}
|
}
|
||||||
|
|
||||||
renderer.Render("organized carpool driver display", w, r, files, state)
|
renderer.Render("organized carpool driver display", w, r, files, state)
|
||||||
|
|||||||
@@ -105,11 +105,12 @@ func (r *Renderer) templateFile(file string) string {
|
|||||||
type RenderState struct {
|
type RenderState struct {
|
||||||
icons.IconSet
|
icons.IconSet
|
||||||
LayoutState
|
LayoutState
|
||||||
UserID string
|
UserID string
|
||||||
UserClaims map[string]any
|
UserClaims map[string]any
|
||||||
Group storage.Group
|
Group storage.Group
|
||||||
Roles any
|
Roles any
|
||||||
ViewState any // This is a state specific to a given view
|
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 {
|
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)
|
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")
|
files := renderer.ThemeConfig.GetStringSlice("views.solidarity_transport.booking_display.files")
|
||||||
bookingMotivations := renderer.GlobalConfig.Get("modules.solidarity_transport.booking_motivations")
|
bookingMotivations := renderer.GlobalConfig.Get("modules.solidarity_transport.booking_motivations")
|
||||||
state := NewState(r, renderer.ThemeConfig, solidarityTransportMenu)
|
state := NewState(r, renderer.ThemeConfig, solidarityTransportMenu)
|
||||||
@@ -124,6 +124,8 @@ func (renderer *Renderer) SolidarityTransportBookingDisplay(w http.ResponseWrite
|
|||||||
"replacement_drivers_map": replacementDriversMap,
|
"replacement_drivers_map": replacementDriversMap,
|
||||||
"replacement_pricing": replacementPricing,
|
"replacement_pricing": replacementPricing,
|
||||||
"replacement_locations": replacementLocations,
|
"replacement_locations": replacementLocations,
|
||||||
|
"driver_last_trips": driverLastTrips,
|
||||||
|
"last_trip_days": lastTripDays,
|
||||||
}
|
}
|
||||||
|
|
||||||
renderer.Render("booking display", w, r, files, state)
|
renderer.Render("booking display", w, r, files, state)
|
||||||
|
|||||||
@@ -10,28 +10,37 @@ import (
|
|||||||
|
|
||||||
const vehiclesmanagementMenu = "vehicles_management"
|
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")
|
files := renderer.ThemeConfig.GetStringSlice("views.vehicles_management.overview.files")
|
||||||
state := NewState(r, renderer.ThemeConfig, vehiclesmanagementMenu)
|
state := NewState(r, renderer.ThemeConfig, vehiclesmanagementMenu)
|
||||||
state.ViewState = map[string]any{
|
state.ViewState = map[string]any{
|
||||||
"vehicles": vehicles,
|
"vehicles": vehicles,
|
||||||
"bookings": bookings,
|
"bookings": bookings,
|
||||||
"vehicles_map": vehicles_map,
|
"vehicles_map": vehicles_map,
|
||||||
"drivers_map": driversMap,
|
"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)
|
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")
|
files := renderer.ThemeConfig.GetStringSlice("views.vehicles_management.bookings_list.files")
|
||||||
state := NewState(r, renderer.ThemeConfig, vehiclesmanagementMenu)
|
state := NewState(r, renderer.ThemeConfig, vehiclesmanagementMenu)
|
||||||
state.ViewState = map[string]any{
|
state.ViewState = map[string]any{
|
||||||
"bookings": bookings,
|
"bookings": bookings,
|
||||||
"vehicles_map": vehiclesMap,
|
"vehicles_map": vehiclesMap,
|
||||||
"drivers_map": driversMap,
|
"drivers_map": driversMap,
|
||||||
"cacheid": cacheid,
|
"cacheid": cacheid,
|
||||||
"filters": filters,
|
"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)
|
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)
|
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")
|
files := renderer.ThemeConfig.GetStringSlice("views.vehicles_management.booking_display.files")
|
||||||
state := NewState(r, renderer.ThemeConfig, vehiclesmanagementMenu)
|
state := NewState(r, renderer.ThemeConfig, vehiclesmanagementMenu)
|
||||||
state.ViewState = map[string]any{
|
state.ViewState = map[string]any{
|
||||||
"booking": booking,
|
"booking": booking,
|
||||||
"vehicle": vehicle,
|
"vehicle": vehicle,
|
||||||
"beneficiary": beneficiary,
|
"beneficiary": beneficiary,
|
||||||
"group": group,
|
"group": group,
|
||||||
"documents": documents,
|
"documents": documents,
|
||||||
"file_types_map": file_types_map,
|
"file_types_map": file_types_map,
|
||||||
"alternative_vehicles": alternative_vehicles,
|
"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)
|
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) {
|
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")
|
files := renderer.ThemeConfig.GetStringSlice("views.vehicles.search.files")
|
||||||
state := NewState(r, renderer.ThemeConfig, vehiclesMenu)
|
state := NewState(r, renderer.ThemeConfig, vehiclesMenu)
|
||||||
|
defaultBookingDurationDays := renderer.GlobalConfig.GetInt("modules.vehicles.default_booking_duration_days")
|
||||||
|
|
||||||
viewstate := map[string]any{
|
viewstate := map[string]any{
|
||||||
"beneficiaries": beneficiaries,
|
"beneficiaries": beneficiaries,
|
||||||
"searched": searched,
|
"searched": searched,
|
||||||
"vehicles_types": vehicles_types,
|
"vehicles_types": vehicles_types,
|
||||||
|
"default_booking_duration_days": defaultBookingDurationDays,
|
||||||
}
|
}
|
||||||
|
|
||||||
if searched {
|
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")
|
files := renderer.ThemeConfig.GetStringSlice("views.vehicles.booking_display.files")
|
||||||
state := NewState(r, renderer.ThemeConfig, vehiclesMenu)
|
state := NewState(r, renderer.ThemeConfig, vehiclesMenu)
|
||||||
state.ViewState = map[string]any{
|
state.ViewState = map[string]any{
|
||||||
"booking": booking,
|
"booking": booking,
|
||||||
"vehicle": vehicle,
|
"vehicle": vehicle,
|
||||||
"beneficiary": beneficiary,
|
"beneficiary": beneficiary,
|
||||||
"group": group,
|
"group": group,
|
||||||
"documents": documents,
|
"documents": documents,
|
||||||
"file_types_map": file_types_map,
|
"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)
|
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")
|
files := renderer.ThemeConfig.GetStringSlice("views.vehicles.bookings_list.files")
|
||||||
state := NewState(r, renderer.ThemeConfig, vehiclesMenu)
|
state := NewState(r, renderer.ThemeConfig, vehiclesMenu)
|
||||||
state.ViewState = map[string]any{
|
state.ViewState = map[string]any{
|
||||||
"bookings": bookings,
|
"bookings": bookings,
|
||||||
"vehicles_map": vehiclesMap,
|
"vehicles_map": vehiclesMap,
|
||||||
"groups_map": groupsMap,
|
"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)
|
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"
|
"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) {
|
func (r *XLSXRenderer) VehicleBookings(w http.ResponseWriter, bookings []fleetsstorage.Booking, vehiclesMap map[string]fleetsstorage.Vehicle, driversMap map[string]mobilityaccountsstorage.Account) {
|
||||||
// Create Excel spreadsheet
|
// Create Excel spreadsheet
|
||||||
spreadsheet := r.NewSpreadsheet("Réservations véhicules")
|
spreadsheet := r.NewSpreadsheet("Réservations véhicules")
|
||||||
@@ -55,6 +80,10 @@ func (r *XLSXRenderer) VehicleBookings(w http.ResponseWriter, bookings []fleetss
|
|||||||
|
|
||||||
spreadsheet.SetHeaders(headers)
|
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
|
// Add data rows
|
||||||
for _, booking := range bookings {
|
for _, booking := range bookings {
|
||||||
vehicle := vehiclesMap[booking.Vehicleid]
|
vehicle := vehiclesMap[booking.Vehicleid]
|
||||||
@@ -69,6 +98,8 @@ func (r *XLSXRenderer) VehicleBookings(w http.ResponseWriter, bookings []fleetss
|
|||||||
status := ""
|
status := ""
|
||||||
if booking.Deleted {
|
if booking.Deleted {
|
||||||
status = "Annulé"
|
status = "Annulé"
|
||||||
|
} else if isManualStatus {
|
||||||
|
status = resolveStatusLabel(statusOptions, booking.ManualStatus)
|
||||||
} else {
|
} else {
|
||||||
switch booking.Status() {
|
switch booking.Status() {
|
||||||
case 1:
|
case 1:
|
||||||
@@ -216,6 +247,10 @@ func (r *XLSXRenderer) VehicleBookingsAdmin(w http.ResponseWriter, bookings []fl
|
|||||||
|
|
||||||
spreadsheet.SetHeaders(headers)
|
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
|
// Add data rows
|
||||||
for _, booking := range bookings {
|
for _, booking := range bookings {
|
||||||
// Get vehicle from map
|
// Get vehicle from map
|
||||||
@@ -243,6 +278,8 @@ func (r *XLSXRenderer) VehicleBookingsAdmin(w http.ResponseWriter, bookings []fl
|
|||||||
status := ""
|
status := ""
|
||||||
if booking.Deleted {
|
if booking.Deleted {
|
||||||
status = "Annulé"
|
status = "Annulé"
|
||||||
|
} else if isManualStatusAdmin {
|
||||||
|
status = resolveStatusLabel(statusOptionsAdmin, booking.ManualStatus)
|
||||||
} else {
|
} else {
|
||||||
switch booking.Status() {
|
switch booking.Status() {
|
||||||
case 1:
|
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
|
// Bookings
|
||||||
vehiclesManagement.HandleFunc("/bookings/", ws.appHandler.VehiclesManagementBookingsListHTTPHandler())
|
vehiclesManagement.HandleFunc("/bookings/", ws.appHandler.VehiclesManagementBookingsListHTTPHandler())
|
||||||
vehiclesManagement.HandleFunc("/bookings/{bookingid}", ws.appHandler.VehicleManagementBookingDisplayHTTPHandler())
|
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}/change-vehicle", ws.appHandler.VehicleManagementBookingChangeVehicleHTTPHandler())
|
||||||
vehiclesManagement.HandleFunc("/bookings/{bookingid}/delete", ws.appHandler.DeleteBookingHTTPHandler())
|
vehiclesManagement.HandleFunc("/bookings/{bookingid}/delete", ws.appHandler.DeleteBookingHTTPHandler())
|
||||||
vehiclesManagement.HandleFunc("/bookings/{bookingid}/unbooking", ws.appHandler.UnbookingVehicleHTTPHandler())
|
vehiclesManagement.HandleFunc("/bookings/{bookingid}/unbooking", ws.appHandler.UnbookingVehicleHTTPHandler())
|
||||||
|
|||||||
@@ -5,7 +5,9 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
formvalidators "git.coopgo.io/coopgo-apps/parcoursmob/core/utils/form-validators"
|
formvalidators "git.coopgo.io/coopgo-apps/parcoursmob/core/utils/form-validators"
|
||||||
@@ -83,14 +85,64 @@ func (h *Handler) BeneficiariesListHTTPHandler() http.HandlerFunc {
|
|||||||
archivedFilter = true
|
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 {
|
if err != nil {
|
||||||
log.Error().Err(err).Msg("error retrieving beneficiaries")
|
log.Error().Err(err).Msg("error retrieving beneficiaries")
|
||||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||||
return
|
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,
|
destinationGeo,
|
||||||
departureDate,
|
departureDate,
|
||||||
departureTime,
|
departureTime,
|
||||||
|
departureDateTime,
|
||||||
result.DriverJourneys,
|
result.DriverJourneys,
|
||||||
result.Drivers,
|
result.Drivers,
|
||||||
result.OrganizedCarpools,
|
result.OrganizedCarpools,
|
||||||
@@ -167,6 +168,8 @@ func (h *Handler) JourneysSearchHTTPHandler() http.HandlerFunc {
|
|||||||
passengerID,
|
passengerID,
|
||||||
savedSearches,
|
savedSearches,
|
||||||
beneficiariesMap,
|
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"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
coreapplication "git.coopgo.io/coopgo-apps/parcoursmob/core/application"
|
||||||
"git.coopgo.io/coopgo-apps/parcoursmob/core/utils/identification"
|
"git.coopgo.io/coopgo-apps/parcoursmob/core/utils/identification"
|
||||||
groupstorage "git.coopgo.io/coopgo-platform/groups-management/storage"
|
groupstorage "git.coopgo.io/coopgo-platform/groups-management/storage"
|
||||||
mobilityaccountsstorage "git.coopgo.io/coopgo-platform/mobility-accounts/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 replacementDriversMap map[string]mobilityaccountsstorage.Account
|
||||||
var replacementPricing map[string]map[string]interface{}
|
var replacementPricing map[string]map[string]interface{}
|
||||||
var replacementLocations map[string]string
|
var replacementLocations map[string]string
|
||||||
|
var driverLastTrips map[string]time.Time
|
||||||
|
var lastTripDays int
|
||||||
if result.Booking.Status == "CANCELLED" {
|
if result.Booking.Status == "CANCELLED" {
|
||||||
// Initialize maps to avoid nil pointer in template
|
// Initialize maps to avoid nil pointer in template
|
||||||
replacementDriversMap = make(map[string]mobilityaccountsstorage.Account)
|
replacementDriversMap = make(map[string]mobilityaccountsstorage.Account)
|
||||||
replacementPricing = make(map[string]map[string]interface{})
|
replacementPricing = make(map[string]map[string]interface{})
|
||||||
replacementLocations = make(map[string]string)
|
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(
|
searchResult, err := h.applicationHandler.SearchJourneys(
|
||||||
r.Context(),
|
r.Context(),
|
||||||
result.Booking.Journey.PassengerPickupDate,
|
result.Booking.Journey.PassengerPickupDate,
|
||||||
@@ -628,11 +634,15 @@ func (h *Handler) SolidarityTransportBookingDisplayHTTPHandler() http.HandlerFun
|
|||||||
result.Booking.PassengerId,
|
result.Booking.PassengerId,
|
||||||
result.Booking.DriverId, // Exclude the original driver
|
result.Booking.DriverId, // Exclude the original driver
|
||||||
result.Booking.GroupId, // Exclude drivers with bookings in this group
|
result.Booking.GroupId, // Exclude drivers with bookings in this group
|
||||||
nil, // options - use defaults
|
&coreapplication.SearchJourneyOptions{
|
||||||
|
SolidarityTransportNoreturn: &noreturn,
|
||||||
|
},
|
||||||
)
|
)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
replacementDrivers = searchResult.DriverJourneys
|
replacementDrivers = searchResult.DriverJourneys
|
||||||
replacementDriversMap = searchResult.Drivers
|
replacementDriversMap = searchResult.Drivers
|
||||||
|
driverLastTrips = searchResult.DriverLastTrips
|
||||||
|
lastTripDays = searchResult.LastTripDays
|
||||||
|
|
||||||
// Calculate pricing for each replacement driver journey
|
// Calculate pricing for each replacement driver journey
|
||||||
for _, dj := range searchResult.DriverJourneys {
|
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"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.coopgo.io/coopgo-apps/parcoursmob/core/utils/identification"
|
"git.coopgo.io/coopgo-apps/parcoursmob/core/utils/identification"
|
||||||
@@ -22,14 +23,37 @@ func (h *Handler) VehiclesManagementOverviewHTTPHandler() http.HandlerFunc {
|
|||||||
groupID = group.ID
|
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 {
|
if err != nil {
|
||||||
log.Error().Err(err).Msg("error retrieving vehicles management overview")
|
log.Error().Err(err).Msg("error retrieving vehicles management overview")
|
||||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||||
return
|
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")
|
status := r.URL.Query().Get("status")
|
||||||
dateStart := r.URL.Query().Get("date_start")
|
dateStart := r.URL.Query().Get("date_start")
|
||||||
dateEnd := r.URL.Query().Get("date_end")
|
dateEnd := r.URL.Query().Get("date_end")
|
||||||
|
vehicleType := r.URL.Query().Get("vehicle_type")
|
||||||
|
|
||||||
// Default to last month if no dates specified
|
// Default to last month if no dates specified
|
||||||
if dateStart == "" {
|
if dateStart == "" {
|
||||||
@@ -55,7 +80,7 @@ func (h *Handler) VehiclesManagementBookingsListHTTPHandler() http.HandlerFunc {
|
|||||||
dateEnd = time.Now().Format("2006-01-02")
|
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 {
|
if err != nil {
|
||||||
log.Error().Err(err).Msg("error retrieving vehicles management bookings list")
|
log.Error().Err(err).Msg("error retrieving vehicles management bookings list")
|
||||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||||
@@ -64,12 +89,15 @@ func (h *Handler) VehiclesManagementBookingsListHTTPHandler() http.HandlerFunc {
|
|||||||
|
|
||||||
// Prepare filters map for template
|
// Prepare filters map for template
|
||||||
filters := map[string]string{
|
filters := map[string]string{
|
||||||
"status": status,
|
"status": status,
|
||||||
"date_start": dateStart,
|
"date_start": dateStart,
|
||||||
"date_end": dateEnd,
|
"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
|
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 {
|
func (h *Handler) VehiclesFleetUpdateHTTPHandler() http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ func (ws *WebServer) setupApplicationRoutes(r *mux.Router) {
|
|||||||
application := r.PathPrefix("/app").Subrouter()
|
application := r.PathPrefix("/app").Subrouter()
|
||||||
|
|
||||||
// Setup all application route groups
|
// Setup all application route groups
|
||||||
|
ws.setupSearchRoutes(application)
|
||||||
ws.setupDashboardRoutes(application)
|
ws.setupDashboardRoutes(application)
|
||||||
setupMiscRoutes(application, ws.applicationHandler)
|
setupMiscRoutes(application, ws.applicationHandler)
|
||||||
ws.setupDirectoryRoutes(application)
|
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")
|
dateStart := r.URL.Query().Get("date_start")
|
||||||
dateEnd := r.URL.Query().Get("date_end")
|
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 {
|
if err != nil {
|
||||||
log.Error().Err(err).Msg("Failed to get vehicle bookings for export")
|
log.Error().Err(err).Msg("Failed to get vehicle bookings for export")
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"git.coopgo.io/coopgo-apps/parcoursmob/core/application"
|
"git.coopgo.io/coopgo-apps/parcoursmob/core/application"
|
||||||
"git.coopgo.io/coopgo-apps/parcoursmob/core/utils/identification"
|
"git.coopgo.io/coopgo-apps/parcoursmob/core/utils/identification"
|
||||||
"git.coopgo.io/coopgo-apps/parcoursmob/renderer"
|
"git.coopgo.io/coopgo-apps/parcoursmob/renderer"
|
||||||
|
"git.coopgo.io/coopgo-apps/parcoursmob/services"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -14,14 +15,16 @@ type Handler struct {
|
|||||||
applicationHandler *application.ApplicationHandler
|
applicationHandler *application.ApplicationHandler
|
||||||
idp *identification.IdentificationProvider
|
idp *identification.IdentificationProvider
|
||||||
renderer *renderer.Renderer
|
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{
|
return &Handler{
|
||||||
config: cfg,
|
config: cfg,
|
||||||
applicationHandler: applicationHandler,
|
applicationHandler: applicationHandler,
|
||||||
idp: idp,
|
idp: idp,
|
||||||
renderer: renderer,
|
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("/solidarity-transport/drivers.xlsx", ws.exportsHandler.SolidarityTransportDrivers)
|
||||||
export.HandleFunc("/organized-carpool/bookings.xlsx", ws.exportsHandler.OrganizedCarpoolBookings)
|
export.HandleFunc("/organized-carpool/bookings.xlsx", ws.exportsHandler.OrganizedCarpoolBookings)
|
||||||
export.HandleFunc("/organized-carpool/drivers.xlsx", ws.exportsHandler.OrganizedCarpoolDrivers)
|
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.Middleware)
|
||||||
export.Use(ws.idp.GroupsMiddleware)
|
export.Use(ws.idp.GroupsMiddleware)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ func Run(cfg *viper.Viper, services *services.ServicesHandler, renderer *rendere
|
|||||||
// Initialize web handler subpackages
|
// Initialize web handler subpackages
|
||||||
appHandler: webapplication.NewHandler(cfg, renderer, applicationHandler, idp, services),
|
appHandler: webapplication.NewHandler(cfg, renderer, applicationHandler, idp, services),
|
||||||
authHandler: webauth.NewHandler(cfg, applicationHandler, idp, renderer),
|
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),
|
extHandler: webexternal.NewHandler(cfg, applicationHandler, filestorage),
|
||||||
protectedAPIHandler: webprotectedapi.NewHandler(cfg, applicationHandler),
|
protectedAPIHandler: webprotectedapi.NewHandler(cfg, applicationHandler),
|
||||||
webAPIHandler: webapi.NewHandler(cfg, idp, applicationHandler, cacheHandler),
|
webAPIHandler: webapi.NewHandler(cfg, idp, applicationHandler, cacheHandler),
|
||||||
|
|||||||
Reference in New Issue
Block a user