parcoursmob/core/application/organized-carpool.go

1482 lines
49 KiB
Go

package application
import (
"cmp"
"context"
"encoding/json"
"fmt"
"io"
"slices"
"strconv"
"strings"
"time"
formvalidators "git.coopgo.io/coopgo-apps/parcoursmob/core/utils/form-validators"
filestorage "git.coopgo.io/coopgo-apps/parcoursmob/core/utils/storage"
"git.coopgo.io/coopgo-platform/carpool-service/servers/grpc/proto"
groupstorage "git.coopgo.io/coopgo-platform/groups-management/storage"
mobilityaccounts "git.coopgo.io/coopgo-platform/mobility-accounts/grpcapi"
mobilityaccountsstorage "git.coopgo.io/coopgo-platform/mobility-accounts/storage"
"git.coopgo.io/coopgo-platform/payments/pricing"
"github.com/google/uuid"
"github.com/paulmach/orb"
"github.com/paulmach/orb/geojson"
"github.com/rs/zerolog/log"
"google.golang.org/protobuf/types/known/structpb"
"google.golang.org/protobuf/types/known/timestamppb"
)
type OrganizedCarpoolDriversForm struct {
FirstName string `json:"first_name" validate:"required"`
LastName string `json:"last_name" validate:"required"`
Email string `json:"email" validate:"required,email"`
Birthdate *time.Time `json:"birthdate" validate:"required"`
PhoneNumber string `json:"phone_number" validate:"required,phoneNumber"`
FileNumber string `json:"file_number"`
Address any `json:"address,omitempty"`
AddressDestination any `json:"address_destination,omitempty"`
Gender string `json:"gender"`
}
type OrganizedCarpoolOverviewResult struct {
Accounts []mobilityaccountsstorage.Account
AccountsMap map[string]mobilityaccountsstorage.Account
BeneficiariesMap map[string]mobilityaccountsstorage.Account
Bookings []*proto.CarpoolServiceBooking
BookingsHistory []*proto.CarpoolServiceBooking
}
func (h *ApplicationHandler) getOrganizedCarpoolDrivers(ctx context.Context, searchFilter string, archivedFilter bool) ([]mobilityaccountsstorage.Account, error) {
request := &mobilityaccounts.GetAccountsRequest{
Namespaces: []string{"organized_carpool_drivers"},
}
resp, err := h.services.GRPC.MobilityAccounts.GetAccounts(ctx, request)
if err != nil {
return nil, err
}
// Create iterator that filters and transforms accounts
filteredAccounts := func(yield func(mobilityaccountsstorage.Account) bool) {
for _, account := range resp.Accounts {
if h.filterOrganizedCarpoolDriver(account, searchFilter, archivedFilter) {
if !yield(account.ToStorageType()) {
return
}
}
}
}
return slices.Collect(filteredAccounts), nil
}
func (h *ApplicationHandler) filterOrganizedCarpoolDriver(a *mobilityaccounts.Account, searchFilter string, archivedFilter bool) bool {
// Search filter
if searchFilter != "" {
name := a.Data.AsMap()["first_name"].(string) + " " + a.Data.AsMap()["last_name"].(string)
if !strings.Contains(strings.ToLower(name), strings.ToLower(searchFilter)) {
return false
}
}
// Archived filter
if archivedFilter {
if archived, ok := a.Data.AsMap()["archived"].(bool); ok && archived {
return true
}
return false
} else {
if archived, ok := a.Data.AsMap()["archived"].(bool); ok && archived {
return false
}
}
return true
}
// filterOrganizedCarpoolBookingsByGeography filters bookings by departure and destination geography
func filterOrganizedCarpoolBookingsByGeography(bookings []*proto.CarpoolServiceBooking, departurePolygons, destinationPolygons []orb.Polygon) []*proto.CarpoolServiceBooking {
if len(departurePolygons) == 0 && len(destinationPolygons) == 0 {
return bookings
}
filtered := []*proto.CarpoolServiceBooking{}
for _, booking := range bookings {
includeBooking := true
// Check departure filter if provided
if len(departurePolygons) > 0 {
departureMatch := false
pickupPoint := orb.Point{booking.PassengerPickupLng, booking.PassengerPickupLat}
departureMatch = isPointInGeographies(pickupPoint, departurePolygons)
if !departureMatch {
includeBooking = false
}
}
// Check destination filter if provided
if len(destinationPolygons) > 0 && includeBooking {
destinationMatch := false
dropPoint := orb.Point{booking.PassengerDropLng, booking.PassengerDropLat}
destinationMatch = isPointInGeographies(dropPoint, destinationPolygons)
if !destinationMatch {
includeBooking = false
}
}
if includeBooking {
filtered = append(filtered, booking)
}
}
return filtered
}
// filterOrganizedCarpoolBookingsByPassengerAddressGeography filters bookings where passenger address is within geography
func filterOrganizedCarpoolBookingsByPassengerAddressGeography(bookings []*proto.CarpoolServiceBooking, beneficiariesMap map[string]mobilityaccountsstorage.Account, addressPolygons []orb.Polygon) []*proto.CarpoolServiceBooking {
if len(addressPolygons) == 0 {
return bookings
}
filtered := []*proto.CarpoolServiceBooking{}
for _, booking := range bookings {
passenger, ok := beneficiariesMap[booking.Passenger.Id]
if !ok {
continue
}
// Check if passenger has address - unmarshal as GeoJSON Feature
if pa, ok := passenger.Data["address"]; ok {
jsonpa, err := json.Marshal(pa)
if err == nil {
passGeojson, err := geojson.UnmarshalFeature(jsonpa)
if err == nil && passGeojson.Geometry != nil {
if point, ok := passGeojson.Geometry.(orb.Point); ok {
if isPointInGeographies(point, addressPolygons) {
filtered = append(filtered, booking)
}
}
}
}
}
}
return filtered
}
func (h *ApplicationHandler) GetOrganizedCarpoolOverview(ctx context.Context, status, driverID, startDate, endDate, departureGeoLayer, departureGeoCode, destinationGeoLayer, destinationGeoCode, passengerAddressGeoLayer, passengerAddressGeoCode, histStatus, histDriverID, histStartDate, histEndDate, histDepartureGeoLayer, histDepartureGeoCode, histDestinationGeoLayer, histDestinationGeoCode, histPassengerAddressGeoLayer, histPassengerAddressGeoCode string, archivedFilter bool, driverAddressGeoLayer, driverAddressGeoCode string) (*OrganizedCarpoolOverviewResult, error) {
// Get ALL drivers for the accountsMap (used in bookings display)
allDrivers, err := h.getOrganizedCarpoolDrivers(ctx, "", false)
if err != nil {
log.Error().Err(err).Msg("issue getting all organized carpool drivers")
allDrivers = []mobilityaccountsstorage.Account{}
}
// Build accountsMap with ALL drivers (for bookings to reference)
accountsMap := map[string]mobilityaccountsstorage.Account{}
for _, a := range allDrivers {
accountsMap[a.ID] = a
}
// Get filtered drivers for the drivers tab display
accounts, err := h.getOrganizedCarpoolDrivers(ctx, "", archivedFilter)
if err != nil {
log.Error().Err(err).Msg("issue getting organized carpool drivers")
accounts = []mobilityaccountsstorage.Account{}
}
// Apply driver address geography filtering only to the drivers tab list
if driverAddressGeoLayer != "" && driverAddressGeoCode != "" {
driverAddressPolygons, err := h.loadGeographyPolygon(driverAddressGeoLayer, driverAddressGeoCode)
if err != nil {
log.Warn().Err(err).Msg("failed to load driver address geography filter")
} else {
filtered := []mobilityaccountsstorage.Account{}
for _, account := range accounts {
// Check if driver has address - unmarshal as GeoJSON Feature
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, driverAddressPolygons) {
filtered = append(filtered, account)
}
}
}
}
}
}
accounts = filtered
}
}
beneficiariesMap, err := h.services.GetBeneficiariesMap()
if err != nil {
beneficiariesMap = map[string]mobilityaccountsstorage.Account{}
}
// Parse start date or use default
var startdate time.Time
if startDate != "" {
if parsed, err := time.Parse("2006-01-02", startDate); err == nil {
startdate = parsed
} else {
startdate = time.Now()
}
} else {
startdate = time.Now()
}
// Parse end date or use default
var enddate time.Time
if endDate != "" {
if parsed, err := time.Parse("2006-01-02", endDate); err == nil {
enddate = parsed.Add(24 * time.Hour) // End of day
} else {
enddate = time.Now().Add(24 * 365 * time.Hour)
}
} else {
enddate = time.Now().Add(24 * 365 * time.Hour)
}
bookingsproto, err := h.services.GRPC.CarpoolService.GetCarpoolBookings(ctx, &proto.GetCarpoolBookingsRequest{
MinDate: timestamppb.New(startdate),
MaxDate: timestamppb.New(enddate),
})
if err != nil {
log.Error().Err(err).Msg("issue retrieving bookings")
}
bookings := []*proto.CarpoolServiceBooking{}
if err == nil {
for _, b := range bookingsproto.Bookings {
// Apply driver filter if specified
if driverID != "" && b.Driver.Id != driverID {
continue
}
// Apply status filter if specified
if status != "" && b.Status.String() != status {
continue
}
bookings = append(bookings, b)
}
}
// Apply geography filtering for current bookings
var departurePolygons, destinationPolygons, passengerAddressPolygons []orb.Polygon
if departureGeoLayer != "" && departureGeoCode != "" {
departurePolygons, err = h.loadGeographyPolygon(departureGeoLayer, departureGeoCode)
if err != nil {
log.Warn().Err(err).Msg("failed to load departure geography filter")
}
}
if destinationGeoLayer != "" && destinationGeoCode != "" {
destinationPolygons, err = h.loadGeographyPolygon(destinationGeoLayer, destinationGeoCode)
if err != nil {
log.Warn().Err(err).Msg("failed to load destination geography filter")
}
}
if passengerAddressGeoLayer != "" && passengerAddressGeoCode != "" {
passengerAddressPolygons, err = h.loadGeographyPolygon(passengerAddressGeoLayer, passengerAddressGeoCode)
if err != nil {
log.Warn().Err(err).Msg("failed to load passenger address geography filter")
}
}
bookings = filterOrganizedCarpoolBookingsByGeography(bookings, departurePolygons, destinationPolygons)
bookings = filterOrganizedCarpoolBookingsByPassengerAddressGeography(bookings, beneficiariesMap, passengerAddressPolygons)
// Get bookings history with filters
// Parse history start date or use default (1 month ago)
var histStartdate time.Time
if histStartDate != "" {
if parsed, err := time.Parse("2006-01-02", histStartDate); err == nil {
histStartdate = parsed
} else {
histStartdate = time.Now().Add(-30 * 24 * time.Hour)
}
} else {
histStartdate = time.Now().Add(-30 * 24 * time.Hour)
}
// Parse history end date or use default (yesterday)
var histEnddate time.Time
if histEndDate != "" {
if parsed, err := time.Parse("2006-01-02", histEndDate); err == nil {
histEnddate = parsed.Add(24 * time.Hour) // End of day
} else {
histEnddate = time.Now().Add(-24 * time.Hour)
}
} else {
histEnddate = time.Now().Add(-24 * time.Hour)
}
historyBookingsproto, err := h.services.GRPC.CarpoolService.GetCarpoolBookings(ctx, &proto.GetCarpoolBookingsRequest{
MinDate: timestamppb.New(histStartdate),
MaxDate: timestamppb.New(histEnddate),
})
bookingsHistory := []*proto.CarpoolServiceBooking{}
if err == nil {
for _, b := range historyBookingsproto.Bookings {
// Apply driver filter if specified
if histDriverID != "" && b.Driver.Id != histDriverID {
continue
}
// Apply status filter if specified
if histStatus != "" && b.Status.String() != histStatus {
continue
}
bookingsHistory = append(bookingsHistory, b)
}
}
// Apply geography filtering for history bookings
var histDeparturePolygons, histDestinationPolygons, histPassengerAddressPolygons []orb.Polygon
if histDepartureGeoLayer != "" && histDepartureGeoCode != "" {
histDeparturePolygons, err = h.loadGeographyPolygon(histDepartureGeoLayer, histDepartureGeoCode)
if err != nil {
log.Warn().Err(err).Msg("failed to load history departure geography filter")
}
}
if histDestinationGeoLayer != "" && histDestinationGeoCode != "" {
histDestinationPolygons, err = h.loadGeographyPolygon(histDestinationGeoLayer, histDestinationGeoCode)
if err != nil {
log.Warn().Err(err).Msg("failed to load history destination geography filter")
}
}
if histPassengerAddressGeoLayer != "" && histPassengerAddressGeoCode != "" {
histPassengerAddressPolygons, err = h.loadGeographyPolygon(histPassengerAddressGeoLayer, histPassengerAddressGeoCode)
if err != nil {
log.Warn().Err(err).Msg("failed to load history passenger address geography filter")
}
}
bookingsHistory = filterOrganizedCarpoolBookingsByGeography(bookingsHistory, histDeparturePolygons, histDestinationPolygons)
bookingsHistory = filterOrganizedCarpoolBookingsByPassengerAddressGeography(bookingsHistory, beneficiariesMap, histPassengerAddressPolygons)
// Sort accounts by last name, then first name
slices.SortFunc(accounts, func(a, b mobilityaccountsstorage.Account) int {
lastNameA := strings.ToLower(a.Data["last_name"].(string))
lastNameB := strings.ToLower(b.Data["last_name"].(string))
if lastNameA != lastNameB {
return strings.Compare(lastNameA, lastNameB)
}
firstNameA := strings.ToLower(a.Data["first_name"].(string))
firstNameB := strings.ToLower(b.Data["first_name"].(string))
return strings.Compare(firstNameA, firstNameB)
})
slices.SortFunc(bookings, func(a, b *proto.CarpoolServiceBooking) int {
return cmp.Compare(a.PassengerPickupDate.AsTime().Unix(), b.PassengerPickupDate.AsTime().Unix())
})
// Sort history bookings by date (most recent first)
slices.SortFunc(bookingsHistory, func(a, b *proto.CarpoolServiceBooking) int {
return cmp.Compare(b.PassengerPickupDate.AsTime().Unix(), a.PassengerPickupDate.AsTime().Unix())
})
return &OrganizedCarpoolOverviewResult{
Accounts: accounts,
AccountsMap: accountsMap,
BeneficiariesMap: beneficiariesMap,
Bookings: bookings,
BookingsHistory: bookingsHistory,
}, nil
}
type OrganizedCarpoolBookingDataResult struct {
Booking *proto.CarpoolServiceBooking
Driver mobilityaccountsstorage.Account
Passenger mobilityaccountsstorage.Account
DriverDepartureAddress string
DriverArrivalAddress string
}
func (h *ApplicationHandler) GetOrganizedCarpoolBookingData(ctx context.Context, bookingID string) (*OrganizedCarpoolBookingDataResult, error) {
resp, err := h.services.GRPC.CarpoolService.GetBooking(ctx, &proto.GetCarpoolBookingRequest{
BookingId: bookingID,
})
if err != nil {
return nil, fmt.Errorf("could not get carpool booking: %w", err)
}
if resp.Booking == nil {
return nil, fmt.Errorf("carpool booking not found")
}
driver, err := h.services.GetAccount(resp.Booking.Driver.Id)
if err != nil {
return nil, fmt.Errorf("driver retrieval issue: %w", err)
}
passenger, err := h.services.GetAccount(resp.Booking.Passenger.Id)
if err != nil {
return nil, fmt.Errorf("passenger retrieval issue: %w", err)
}
// Extract driver departure and arrival addresses from DriverRoute GeoJSON
var driverDepartureAddress, driverArrivalAddress string
if resp.Booking.DriverRoute != nil && resp.Booking.DriverRoute.Serialized != "" {
fc, err := geojson.UnmarshalFeatureCollection([]byte(resp.Booking.DriverRoute.Serialized))
if err != nil {
log.Error().Err(err).Msg("could not unmarshal driver route geojson")
} else {
// Extract departure address (first feature)
if len(fc.Features) > 0 {
if addr, ok := fc.Features[0].Properties["label"]; ok {
if addrStr, ok := addr.(string); ok {
driverDepartureAddress = addrStr
}
}
}
// Extract arrival address (last feature)
if len(fc.Features) > 1 {
if addr, ok := fc.Features[1].Properties["label"]; ok {
if addrStr, ok := addr.(string); ok {
driverArrivalAddress = addrStr
}
}
}
}
}
return &OrganizedCarpoolBookingDataResult{
Booking: resp.Booking,
Driver: driver,
Passenger: passenger,
DriverDepartureAddress: driverDepartureAddress,
DriverArrivalAddress: driverArrivalAddress,
}, nil
}
func (h *ApplicationHandler) UpdateOrganizedCarpoolBookingStatus(ctx context.Context, bookingID, action string) error {
var status proto.CarpoolServiceBookingStatus
if action == "confirm" {
status = proto.CarpoolServiceBookingStatus_CONFIRMED
} else if action == "cancel" {
status = proto.CarpoolServiceBookingStatus_CANCELLED
} else if action == "waitconfirmation" {
status = proto.CarpoolServiceBookingStatus_WAITING_DRIVER_CONFIRMATION
} else {
return fmt.Errorf("unknown booking action: %s", action)
}
// Get booking details BEFORE updating to capture previous status
result, err := h.GetOrganizedCarpoolBookingData(ctx, bookingID)
if err != nil {
return err
}
booking := result.Booking
driver := result.Driver
passenger := result.Passenger
previousStatus := booking.Status
// Update booking status
_, err = h.services.GRPC.CarpoolService.UpdateBooking(ctx, &proto.UpdateCarpoolBookingRequest{
BookingId: bookingID,
Status: status,
})
if err != nil {
return fmt.Errorf("update carpool booking status issue: %w", err)
}
// Handle wallet operations based on status transitions
// Credit driver / debit passenger when previous status was not CONFIRMED and new status is CONFIRMED
if previousStatus != proto.CarpoolServiceBookingStatus_CONFIRMED && status == proto.CarpoolServiceBookingStatus_CONFIRMED {
if booking.Price != nil && booking.Price.Amount != nil {
if err := h.CreditWallet(ctx, passenger.ID, -1*(*booking.Price.Amount), "Covoiturage solidaire", "Débit covoiturage solidaire"); err != nil {
return fmt.Errorf("could not debit passenger wallet: %w", err)
}
}
if booking.DriverCompensationAmount != nil && *booking.DriverCompensationAmount > 0 {
if err := h.CreditWallet(ctx, driver.ID, *booking.DriverCompensationAmount, "Covoiturage solidaire", "Crédit covoiturage solidaire"); err != nil {
return fmt.Errorf("could not credit driver wallet: %w", err)
}
}
}
// Credit passenger / debit driver when previous status was CONFIRMED and new status is not CONFIRMED anymore
if previousStatus == proto.CarpoolServiceBookingStatus_CONFIRMED && status != proto.CarpoolServiceBookingStatus_CONFIRMED {
if booking.Price != nil && booking.Price.Amount != nil {
if err := h.CreditWallet(ctx, passenger.ID, *booking.Price.Amount, "Covoiturage solidaire", "Remboursement annulation covoiturage solidaire"); err != nil {
return fmt.Errorf("could not credit passenger wallet: %w", err)
}
}
if booking.DriverCompensationAmount != nil && *booking.DriverCompensationAmount > 0 {
if err := h.CreditWallet(ctx, driver.ID, -1*(*booking.DriverCompensationAmount), "Covoiturage solidaire", "Débit annulation covoiturage solidaire"); err != nil {
return fmt.Errorf("could not debit driver wallet: %w", err)
}
}
}
return nil
}
func (h *ApplicationHandler) CreateOrganizedCarpoolDriver(ctx context.Context, firstName, lastName, email string, birthdate *time.Time, phoneNumber, fileNumber string, address, addressDestination any, gender string) (string, error) {
dataMap := map[string]any{
"first_name": firstName,
"last_name": lastName,
"email": email,
"phone_number": phoneNumber,
"file_number": fileNumber,
"gender": gender,
}
// Convert birthdate to string format for structpb compatibility
if birthdate != nil {
dataMap["birthdate"] = birthdate.Format("2006-01-02")
}
if address != nil {
dataMap["address"] = address
}
if addressDestination != nil {
dataMap["address_destination"] = addressDestination
}
// Validate the data
formData := OrganizedCarpoolDriversForm{
FirstName: firstName,
LastName: lastName,
Email: email,
Birthdate: birthdate,
PhoneNumber: phoneNumber,
FileNumber: fileNumber,
Address: address,
AddressDestination: addressDestination,
Gender: gender,
}
validate := formvalidators.New()
if err := validate.Struct(formData); err != nil {
return "", err
}
data, err := structpb.NewValue(dataMap)
if err != nil {
return "", err
}
request := &mobilityaccounts.RegisterRequest{
Account: &mobilityaccounts.Account{
Namespace: "organized_carpool_drivers",
Data: data.GetStructValue(),
},
}
resp, err := h.services.GRPC.MobilityAccounts.Register(ctx, request)
if err != nil {
return "", err
}
return resp.Account.Id, nil
}
type OrganizedCarpoolDriverDataResult struct {
Driver mobilityaccountsstorage.Account
Trips []*geojson.FeatureCollection
Documents []filestorage.FileInfo
Bookings any
BeneficiariesMap map[string]mobilityaccountsstorage.Account
Stats map[string]any
WalletBalance float64
}
func (h *ApplicationHandler) GetOrganizedCarpoolDriverData(ctx context.Context, driverID string) (*OrganizedCarpoolDriverDataResult, error) {
documents := h.filestorage.List(filestorage.PREFIX_ORGANIZED_CARPOOL_DRIVERS + "/" + driverID)
driver, err := h.services.GetAccount(driverID)
if err != nil {
return nil, fmt.Errorf("issue retrieving driver account: %w", err)
}
// Security check: ensure this is actually an organized carpool driver account
if driver.Namespace != "organized_carpool_drivers" {
return nil, fmt.Errorf("account %s is not an organized carpool driver (namespace: %s)", driverID, driver.Namespace)
}
trips := []*geojson.FeatureCollection{}
resp, err := h.services.GRPC.CarpoolService.GetRegularRoutes(ctx, &proto.GetRegularRoutesRequest{
UserId: driverID,
})
for _, r := range resp.Routes {
t, err := geojson.UnmarshalFeatureCollection([]byte(r.Serialized))
if err != nil {
log.Error().Err(err).Msg("could not unmarshall feature collection")
continue
}
trips = append(trips, t)
}
// Get driver bookings
bookingsRequest := &proto.GetUserBookingsRequest{
UserId: driverID,
MinDate: timestamppb.New(time.Now().Add(-365 * 24 * time.Hour)),
MaxDate: timestamppb.New(time.Now().Add(365 * 24 * time.Hour)),
}
bookingsResp, err := h.services.GRPC.CarpoolService.GetUserBookings(ctx, bookingsRequest)
bookings := []*proto.CarpoolServiceBooking{}
if err == nil {
bookings = bookingsResp.Bookings
}
// Collect unique passenger IDs
passengerIDs := []string{}
passengerIDsMap := make(map[string]bool)
for _, booking := range bookings {
if booking.Passenger != nil && booking.Passenger.Id != "" {
if !passengerIDsMap[booking.Passenger.Id] {
passengerIDs = append(passengerIDs, booking.Passenger.Id)
passengerIDsMap[booking.Passenger.Id] = true
}
}
}
// Get beneficiaries in batch
beneficiariesMap := make(map[string]mobilityaccountsstorage.Account)
if len(passengerIDs) > 0 {
beneficiariesResp, err := h.services.GRPC.MobilityAccounts.GetAccountsBatch(ctx, &mobilityaccounts.GetAccountsBatchRequest{
Accountids: passengerIDs,
})
if err == nil {
for _, account := range beneficiariesResp.Accounts {
a := account.ToStorageType()
beneficiariesMap[a.ID] = a
}
}
}
// Calculate stats
confirmedCount := 0
kmnb := 0
for _, booking := range bookings {
if booking.Status.String() == "CONFIRMED" {
confirmedCount++
if booking.Distance != nil {
kmnb += int(*booking.Distance)
}
}
}
stats := map[string]any{
"bookings": map[string]any{
"count": len(bookings),
"confirmed": confirmedCount,
"km": kmnb,
},
}
// Calculate wallet balance
walletBalance := h.calculateWalletBalance(driver)
return &OrganizedCarpoolDriverDataResult{
Driver: driver,
Trips: trips,
Documents: documents,
Bookings: bookings,
BeneficiariesMap: beneficiariesMap,
Stats: stats,
WalletBalance: walletBalance,
}, nil
}
type OrganizedCarpoolDriverResult struct {
Driver mobilityaccountsstorage.Account
}
func (h *ApplicationHandler) GetOrganizedCarpoolDriver(ctx context.Context, driverID string) (*OrganizedCarpoolDriverResult, error) {
driver, err := h.services.GetAccount(driverID)
if err != nil {
return nil, fmt.Errorf("issue retrieving driver account: %w", err)
}
// Security check: ensure this is actually an organized carpool driver account
if driver.Namespace != "organized_carpool_drivers" {
return nil, fmt.Errorf("account %s is not an organized carpool driver (namespace: %s)", driverID, driver.Namespace)
}
return &OrganizedCarpoolDriverResult{
Driver: driver,
}, nil
}
func (h *ApplicationHandler) UpdateOrganizedCarpoolDriver(ctx context.Context, driverID, firstName, lastName, email string, birthdate *time.Time, phoneNumber, fileNumber string, address, addressDestination any, gender, otherProperties string) (string, error) {
// Security check: verify the account exists and is an organized carpool driver
driver, err := h.services.GetAccount(driverID)
if err != nil {
return "", fmt.Errorf("issue retrieving driver account: %w", err)
}
if driver.Namespace != "organized_carpool_drivers" {
return "", fmt.Errorf("account %s is not an organized carpool driver (namespace: %s)", driverID, driver.Namespace)
}
dataMap := map[string]any{
"first_name": firstName,
"last_name": lastName,
"email": email,
"phone_number": phoneNumber,
"file_number": fileNumber,
"gender": gender,
}
// Convert birthdate to string format for structpb compatibility
if birthdate != nil {
dataMap["birthdate"] = birthdate.Format("2006-01-02")
}
if address != nil {
dataMap["address"] = address
}
if addressDestination != nil {
dataMap["address_destination"] = addressDestination
}
// Handle other_properties for update form
if otherProperties != "" {
var otherProps map[string]any
if err := json.Unmarshal([]byte(otherProperties), &otherProps); err == nil {
if dataMap["other_properties"] == nil {
dataMap["other_properties"] = make(map[string]any)
}
for k, v := range otherProps {
dataMap["other_properties"].(map[string]any)[k] = v
}
}
}
// Validate the data
formData := OrganizedCarpoolDriversForm{
FirstName: firstName,
LastName: lastName,
Email: email,
Birthdate: birthdate,
PhoneNumber: phoneNumber,
FileNumber: fileNumber,
Address: address,
AddressDestination: addressDestination,
Gender: gender,
}
validate := formvalidators.New()
if err := validate.Struct(formData); err != nil {
return "", err
}
data, err := structpb.NewValue(dataMap)
if err != nil {
return "", err
}
request := &mobilityaccounts.UpdateDataRequest{
Account: &mobilityaccounts.Account{
Id: driverID,
Namespace: "organized_carpool_drivers",
Data: data.GetStructValue(),
},
}
resp, err := h.services.GRPC.MobilityAccounts.UpdateData(ctx, request)
if err != nil {
return "", err
}
return resp.Account.Id, nil
}
func (h *ApplicationHandler) ArchiveOrganizedCarpoolDriver(ctx context.Context, driverID string) error {
// Security check: verify the account exists and is an organized carpool driver
driver, err := h.services.GetAccount(driverID)
if err != nil {
return fmt.Errorf("issue retrieving driver account: %w", err)
}
if driver.Namespace != "organized_carpool_drivers" {
return fmt.Errorf("account %s is not an organized carpool driver (namespace: %s)", driverID, driver.Namespace)
}
data, err := structpb.NewValue(map[string]any{
"archived": true,
})
if err != nil {
return err
}
request := &mobilityaccounts.UpdateDataRequest{
Account: &mobilityaccounts.Account{
Id: driverID,
Namespace: "organized_carpool_drivers",
Data: data.GetStructValue(),
},
}
_, err = h.services.GRPC.MobilityAccounts.UpdateData(ctx, request)
return err
}
func (h *ApplicationHandler) UnarchiveOrganizedCarpoolDriver(ctx context.Context, driverID string) error {
// Security check: verify the account exists and is an organized carpool driver
driver, err := h.services.GetAccount(driverID)
if err != nil {
return fmt.Errorf("issue retrieving driver account: %w", err)
}
if driver.Namespace != "organized_carpool_drivers" {
return fmt.Errorf("account %s is not an organized carpool driver (namespace: %s)", driverID, driver.Namespace)
}
data, err := structpb.NewValue(map[string]any{
"archived": false,
})
if err != nil {
return err
}
request := &mobilityaccounts.UpdateDataRequest{
Account: &mobilityaccounts.Account{
Id: driverID,
Namespace: "organized_carpool_drivers",
Data: data.GetStructValue(),
},
}
_, err = h.services.GRPC.MobilityAccounts.UpdateData(ctx, request)
return err
}
func (h *ApplicationHandler) AddOrganizedCarpoolDriverDocument(ctx context.Context, driverID string, file io.Reader, filename string, fileSize int64, documentType, documentName string) error {
// Security check: verify the account exists and is an organized carpool driver
driver, err := h.services.GetAccount(driverID)
if err != nil {
return fmt.Errorf("issue retrieving driver account: %w", err)
}
if driver.Namespace != "organized_carpool_drivers" {
return fmt.Errorf("account %s is not an organized carpool driver (namespace: %s)", driverID, driver.Namespace)
}
fileid := uuid.NewString()
metadata := map[string]string{
"type": documentType,
"name": documentName,
}
if err := h.filestorage.Put(file, filestorage.PREFIX_ORGANIZED_CARPOOL_DRIVERS, fmt.Sprintf("%s/%s_%s", driverID, fileid, filename), fileSize, metadata); err != nil {
return err
}
return nil
}
func (h *ApplicationHandler) GetOrganizedCarpoolDriverDocument(ctx context.Context, driverID, document string) (io.Reader, *filestorage.FileInfo, error) {
return h.GetDocument(ctx, OrganizedCarpoolDriverDocumentConfig, driverID, document)
}
func (h *ApplicationHandler) DeleteOrganizedCarpoolDriverDocument(ctx context.Context, driverID, document string) error {
return h.DeleteDocument(ctx, OrganizedCarpoolDriverDocumentConfig, driverID, document)
}
func (h *ApplicationHandler) AddOrganizedCarpoolTrip(ctx context.Context, driverID, outwardtime, returntime string, departure, destination *geojson.Feature, days map[string]bool) error {
// Security check: verify the account exists and is an organized carpool driver
driver, err := h.services.GetAccount(driverID)
if err != nil {
return fmt.Errorf("issue retrieving driver account: %w", err)
}
if driver.Namespace != "organized_carpool_drivers" {
return fmt.Errorf("account %s is not an organized carpool driver (namespace: %s)", driverID, driver.Namespace)
}
trips := []*proto.CarpoolFeatureCollection{}
outwardroute, err := h.services.Routing.Route([]orb.Point{departure.Point(), destination.Point()})
if err != nil {
return fmt.Errorf("failed calling route search: %w", err)
}
returnroute, err := h.services.Routing.Route([]orb.Point{destination.Point(), departure.Point()})
if err != nil {
return fmt.Errorf("failed calling route search: %w", err)
}
outwardschedules := []map[string]any{}
returnschedules := []map[string]any{}
dayMap := map[string]string{
"monday": "MON",
"tuesday": "TUE",
"wednesday": "WED",
"thursday": "THU",
"friday": "FRI",
"saturday": "SAT",
"sunday": "SUN",
}
for day, enabled := range days {
if enabled {
dayCode := dayMap[day]
outwardschedules = append(outwardschedules, map[string]any{
"day": dayCode,
"time_of_day": outwardtime,
})
returnschedules = append(returnschedules, map[string]any{
"day": dayCode,
"time_of_day": returntime,
})
}
}
outward_fc := geojson.NewFeatureCollection()
outward_fc.Append(departure)
outward_fc.Append(destination)
outward_fc.ExtraMembers = geojson.Properties{}
outward_fc.ExtraMembers["properties"] = map[string]any{
"is_driver": true,
"is_passenger": false,
"user": mobilityaccountsstorage.Account{
ID: driverID,
},
"polyline": outwardroute.Summary.Polyline,
"schedules": outwardschedules,
"driver_options": map[string]any{},
"passenger_options": map[string]any{},
}
outwardtrip, err := outward_fc.MarshalJSON()
if err != nil {
return fmt.Errorf("failed marshaling outward geojson: %w", err)
}
return_fc := geojson.NewFeatureCollection()
return_fc.Append(destination)
return_fc.Append(departure)
return_fc.ExtraMembers = geojson.Properties{}
return_fc.ExtraMembers["properties"] = map[string]any{
"is_driver": true,
"is_passenger": false,
"user": mobilityaccountsstorage.Account{
ID: driverID,
},
"polyline": returnroute.Summary.Polyline,
"schedules": returnschedules,
"driver_options": map[string]any{},
"passenger_options": map[string]any{},
}
returntrip, err := return_fc.MarshalJSON()
if err != nil {
return fmt.Errorf("failed marshaling return geojson: %w", err)
}
trips = append(trips, &proto.CarpoolFeatureCollection{
Serialized: string(outwardtrip),
})
trips = append(trips, &proto.CarpoolFeatureCollection{
Serialized: string(returntrip),
})
req := &proto.CreateRegularRoutesRequest{
Routes: trips,
}
_, err = h.services.GRPC.CarpoolService.CreateRegularRoutes(ctx, req)
if err != nil {
return fmt.Errorf("could not create regular routes: %w", err)
}
return nil
}
func (h *ApplicationHandler) DeleteOrganizedCarpoolTrip(ctx context.Context, tripID string) error {
req := &proto.DeleteRegularRoutesRequest{
Ids: []string{tripID},
}
_, err := h.services.GRPC.CarpoolService.DeleteRegularRoutes(ctx, req)
if err != nil {
return fmt.Errorf("could not delete regular routes: %w", err)
}
return nil
}
type OrganizedCarpoolJourneyDataResult struct {
Journey *geojson.FeatureCollection
Driver mobilityaccountsstorage.Account
Passenger mobilityaccountsstorage.Account
Beneficiaries []mobilityaccountsstorage.Account
PassengerWalletBalance float64
PricingResult map[string]pricing.Price
}
func (h *ApplicationHandler) GetOrganizedCarpoolJourneyData(ctx context.Context, driverID, journeyID, passengerID string, currentUserGroup groupstorage.Group) (*OrganizedCarpoolJourneyDataResult, error) {
// Get the planned trip data
journeyResp, err := h.services.GRPC.CarpoolService.GetPlannedTrip(ctx, &proto.GetPlannedTripRequest{
Id: journeyID,
})
if err != nil {
return nil, fmt.Errorf("could not get carpool journey: %w", err)
}
journey, err := geojson.UnmarshalFeatureCollection([]byte(journeyResp.PlannedTrip.Serialized))
if err != nil {
return nil, fmt.Errorf("could not unmarshal carpool journey: %w", err)
}
driver, err := h.services.GetAccount(driverID)
if err != nil {
return nil, fmt.Errorf("could not get driver: %w", err)
}
var passenger mobilityaccountsstorage.Account
if passengerID != "" {
passenger, err = h.services.GetAccount(passengerID)
if err != nil {
return nil, fmt.Errorf("could not get passenger account: %w", err)
}
}
// Get beneficiaries in current user's group
beneficiaries, err := h.services.GetBeneficiariesInGroup(currentUserGroup)
if err != nil {
return nil, fmt.Errorf("could not get beneficiaries: %w", err)
}
// Calculate pricing
pricingResult, err := h.calculateOrganizedCarpoolPricing(ctx, journey, passengerID, passenger)
if err != nil {
log.Error().Err(err).Msg("error calculating organized carpool pricing")
pricingResult = map[string]pricing.Price{
"passenger": {Amount: 0.0, Currency: "EUR"},
"driver": {Amount: 0.0, Currency: "EUR"},
}
}
// Calculate passenger wallet balance
passengerWalletBalance := h.calculateWalletBalance(passenger)
return &OrganizedCarpoolJourneyDataResult{
Journey: journey,
Driver: driver,
Passenger: passenger,
Beneficiaries: beneficiaries,
PassengerWalletBalance: passengerWalletBalance,
PricingResult: pricingResult,
}, nil
}
func (h *ApplicationHandler) CreateOrganizedCarpoolJourneyBooking(ctx context.Context, driverID, journeyID, passengerID, motivation, message string, doNotSend bool) (string, error) {
if passengerID == "" {
return "", fmt.Errorf("missing passenger ID for carpool booking")
}
// Get the planned trip data
journeyResp, err := h.services.GRPC.CarpoolService.GetPlannedTrip(ctx, &proto.GetPlannedTripRequest{
Id: journeyID,
})
if err != nil {
return "", fmt.Errorf("could not get carpool journey: %w", err)
}
journey, err := geojson.UnmarshalFeatureCollection([]byte(journeyResp.PlannedTrip.Serialized))
if err != nil {
return "", fmt.Errorf("could not unmarshal carpool journey: %w", err)
}
departureDate := journey.ExtraMembers["departure_date"]
var departureTime *timestamppb.Timestamp
if departureDate != nil {
if dd, ok := departureDate.(string); ok {
dt, _ := time.Parse("2006-01-02 15:04", dd)
departureTime = timestamppb.New(dt)
}
}
// Extract operator from journey data
var operatorID string
if operator, ok := journey.ExtraMembers["operator"]; ok {
if op, ok := operator.(string); ok {
operatorID = op
}
}
if operatorID == "" {
operatorID = "example.coopgo.fr" // fallback to default
}
if departureTime == nil {
// Fallback to current time if we can't extract departure time
departureTime = timestamppb.New(time.Now())
}
// Extract journey properties from the geojson data
var journeyProps map[string]any
if props, ok := journey.ExtraMembers["properties"]; ok {
if propsMap, ok := props.(map[string]any); ok {
journeyProps = propsMap
}
}
if journeyProps == nil {
return "", fmt.Errorf("could not extract journey properties")
}
// Extract departure date from journey ExtraMembers
if depDate, ok := journey.ExtraMembers["departure_date"]; ok {
if depDateStr, ok := depDate.(string); ok {
if parsedTime, err := time.Parse("2006-01-02T15:04:05Z", depDateStr); err == nil {
departureTime = timestamppb.New(parsedTime)
} else if parsedTime, err := time.Parse("2006-01-02", depDateStr); err == nil {
departureTime = timestamppb.New(parsedTime)
} else {
log.Warn().Str("departure_date", depDateStr).Msg("could not parse departure date, using current time")
}
}
}
// Extract passenger pickup/drop coordinates and addresses from stored passenger data
var pickupLat, pickupLng, dropLat, dropLng float64
var pickupAddress, dropAddress string
// Check if we have passenger pickup and drop features in the journey's extra members
if pickupData, ok := journey.ExtraMembers["passenger_pickup"]; ok {
if pickupMap, ok := pickupData.(map[string]interface{}); ok {
if geometry, ok := pickupMap["geometry"].(map[string]interface{}); ok {
if coords, ok := geometry["coordinates"].([]interface{}); ok && len(coords) >= 2 {
if lng, ok := coords[0].(float64); ok {
pickupLng = lng
}
if lat, ok := coords[1].(float64); ok {
pickupLat = lat
}
}
}
if properties, ok := pickupMap["properties"].(map[string]interface{}); ok {
if label, ok := properties["label"].(string); ok {
pickupAddress = label
}
}
}
}
if dropData, ok := journey.ExtraMembers["passenger_drop"]; ok {
if dropMap, ok := dropData.(map[string]interface{}); ok {
if geometry, ok := dropMap["geometry"].(map[string]interface{}); ok {
if coords, ok := geometry["coordinates"].([]interface{}); ok && len(coords) >= 2 {
if lng, ok := coords[0].(float64); ok {
dropLng = lng
}
if lat, ok := coords[1].(float64); ok {
dropLat = lat
}
}
}
if properties, ok := dropMap["properties"].(map[string]interface{}); ok {
if label, ok := properties["label"].(string); ok {
dropAddress = label
}
}
}
}
// Extract time from schedules if available and no specific departure_date was found
if departureTime.AsTime().Equal(time.Now().Truncate(time.Second)) {
if schedules, ok := journeyProps["schedules"]; ok {
if schedulesList, ok := schedules.([]any); ok && len(schedulesList) > 0 {
if schedule, ok := schedulesList[0].(map[string]any); ok {
if timeOfDay, ok := schedule["time_of_day"].(string); ok {
// Parse time and combine with current date
now := time.Now()
timeStr := fmt.Sprintf("%s %s", now.Format("2006-01-02"), timeOfDay)
if depTime, err := time.Parse("2006-01-02 15:04", timeStr); err == nil {
departureTime = timestamppb.New(depTime)
}
}
}
}
}
}
// Get passenger account and calculate pricing
var passenger mobilityaccountsstorage.Account
if passengerID != "" {
passenger, err = h.services.GetAccount(passengerID)
if err != nil {
return "", fmt.Errorf("could not get passenger account: %w", err)
}
}
pricingResult, err := h.calculateOrganizedCarpoolPricing(ctx, journey, passengerID, passenger)
if err != nil {
log.Error().Err(err).Msg("error calculating organized carpool pricing")
pricingResult = map[string]pricing.Price{
"passenger": {Amount: 0.0, Currency: "EUR"},
"driver": {Amount: 0.0, Currency: "EUR"},
}
}
// Extract price values
priceAmount := pricingResult["passenger"].Amount
priceCurrency := pricingResult["passenger"].Currency
driverCompensationAmount := pricingResult["driver"].Amount
driverCompensationCurrency := pricingResult["driver"].Currency
// Determine price type
priceType := proto.CarpoolServicePriceType_PAYING
if priceAmount == 0 {
priceType = proto.CarpoolServicePriceType_FREE
}
// Extract passenger distance from journey
var passengerDistance *int64
if dist, ok := journey.ExtraMembers["passenger_distance"].(float64); ok {
distInt := int64(dist)
passengerDistance = &distInt
}
// Create carpool booking using extracted journey data
booking := &proto.CarpoolServiceBooking{
Id: uuid.NewString(),
Driver: &proto.CarpoolServiceUser{
Id: driverID,
Operator: operatorID,
},
Passenger: &proto.CarpoolServiceUser{
Id: passengerID,
Operator: operatorID,
},
PassengerPickupDate: departureTime,
PassengerPickupLat: pickupLat,
PassengerPickupLng: pickupLng,
PassengerDropLat: dropLat,
PassengerDropLng: dropLng,
PassengerPickupAddress: &pickupAddress,
PassengerDropAddress: &dropAddress,
Status: proto.CarpoolServiceBookingStatus_WAITING_DRIVER_CONFIRMATION,
Distance: passengerDistance,
DriverJourneyId: journeyID,
Price: &proto.CarpoolServicePrice{
Type: &priceType,
Amount: &priceAmount,
Currency: &priceCurrency,
},
DriverCompensationAmount: &driverCompensationAmount,
DriverCompensationCurrency: &driverCompensationCurrency,
Motivation: &motivation,
}
bookingRes, err := h.services.GRPC.CarpoolService.CreateBooking(ctx, &proto.CreateCarpoolBookingRequest{
Booking: booking,
})
if err != nil {
return "", fmt.Errorf("cannot create carpool booking: %w", err)
}
// Send SMS notification if requested
if message != "" && !doNotSend {
send_message := strings.ReplaceAll(message, "{booking_id}", bookingRes.Booking.Id)
log.Debug().Str("message", send_message).Msg("Carpool booking created: sending message")
h.GenerateSMS(driverID, send_message)
}
return bookingRes.Booking.Id, nil
}
func (h *ApplicationHandler) calculateOrganizedCarpoolPricing(ctx context.Context, tripGeoJSON *geojson.FeatureCollection, passengerID string, passenger mobilityaccountsstorage.Account) (map[string]pricing.Price, error) {
// For organized carpool, use simple pricing based on distance
// Extract distance from journey features if available
var passengerDistance int64 = 0
var driverDistance int64 = 0
// Try to extract distance from journey extra members
if tripGeoJSON != nil && tripGeoJSON.ExtraMembers != nil {
if dist, ok := tripGeoJSON.ExtraMembers["passenger_distance"].(float64); ok {
passengerDistance = int64(dist)
}
if dist, ok := tripGeoJSON.ExtraMembers["driver_distance"].(float64); ok {
driverDistance = int64(dist)
}
}
benefParams := pricing.BeneficiaryParams{}
if passengerID == "" {
benefParams = pricing.BeneficiaryParams{
History: 99,
Priority: false,
}
} else {
// Get organized carpool history for passenger
carpoolBookings, err := h.services.GRPC.CarpoolService.GetUserBookings(ctx, &proto.GetUserBookingsRequest{
UserId: passengerID,
})
// Check priority status
priority := false
if a, ok := passenger.Data["other_properties"]; ok {
if b, ok := a.(map[string]any); ok {
if c, ok := b["status"]; ok {
if p, ok := c.(string); ok {
priority = (p == "Prioritaire")
}
}
}
}
// Calculate history from previous bookings and stored value
history := 0
if op, ok := passenger.Data["other_properties"]; ok {
if op_map, ok := op.(map[string]any); ok {
if poc, ok := op_map["previous_organized_carpool"]; ok {
if poc_str, ok := poc.(string); ok {
if poc_str != "" {
if n, err := strconv.Atoi(poc_str); err == nil {
history = history + n
} else {
log.Error().Err(err).Str("n", poc_str).Msg("string to int conversion error")
}
}
}
}
}
}
// Count only WAITING_DRIVER_CONFIRMATION, CONFIRMED and VALIDATED bookings
if err == nil {
for _, booking := range carpoolBookings.Bookings {
if booking.Status == proto.CarpoolServiceBookingStatus_WAITING_DRIVER_CONFIRMATION ||
booking.Status == proto.CarpoolServiceBookingStatus_CONFIRMED ||
booking.Status == proto.CarpoolServiceBookingStatus_VALIDATED {
history++
}
}
}
benefParams = pricing.BeneficiaryParams{
History: history,
Priority: priority,
}
}
pricingParams := pricing.PricingParams{
MobilityType: "organized_carpool",
Beneficiary: benefParams,
SharedMobility: pricing.SharedMobilityParams{
DriverDistance: driverDistance,
PassengerDistance: passengerDistance,
},
}
log.Info().
Str("mobility_type", pricingParams.MobilityType).
Int("beneficiary_history", pricingParams.Beneficiary.History).
Bool("beneficiary_priority", pricingParams.Beneficiary.Priority).
Int64("driver_distance", pricingParams.SharedMobility.DriverDistance).
Int64("passenger_distance", pricingParams.SharedMobility.PassengerDistance).
Str("passenger_id", passengerID).
Msg("calling pricing service for organized carpool")
pricingResult, err := h.services.Pricing.Prices(pricingParams)
if err != nil {
log.Error().Err(err).Msg("pricing service returned error")
return nil, err
}
log.Info().
Float64("passenger_price", pricingResult["passenger"].Amount).
Str("passenger_currency", pricingResult["passenger"].Currency).
Float64("driver_price", pricingResult["driver"].Amount).
Str("driver_currency", pricingResult["driver"].Currency).
Msg("pricing service result for organized carpool")
return pricingResult, nil
}
type OrganizedCarpoolBookingsResult struct {
Bookings []*proto.CarpoolServiceBooking
DriversMap map[string]mobilityaccountsstorage.Account
BeneficiariesMap map[string]mobilityaccountsstorage.Account
}
func (h *ApplicationHandler) GetOrganizedCarpoolBookings(ctx context.Context, startDate, endDate *time.Time, status, driverID, departureGeoLayer, departureGeoCode, destinationGeoLayer, destinationGeoCode, passengerAddressGeoLayer, passengerAddressGeoCode string) (*OrganizedCarpoolBookingsResult, error) {
// Get all drivers
drivers, err := h.getOrganizedCarpoolDrivers(ctx, "", false)
if err != nil {
log.Error().Err(err).Msg("issue getting organized carpool drivers")
drivers = []mobilityaccountsstorage.Account{}
}
driversMap := map[string]mobilityaccountsstorage.Account{}
for _, d := range drivers {
driversMap[d.ID] = d
}
// Get beneficiaries
beneficiariesMap, err := h.services.GetBeneficiariesMap()
if err != nil {
beneficiariesMap = map[string]mobilityaccountsstorage.Account{}
}
// Determine date range
var start, end time.Time
if startDate != nil {
start = *startDate
} else {
start = time.Now().Add(-365 * 24 * time.Hour)
}
if endDate != nil {
end = *endDate
} else {
end = time.Now()
}
// Get bookings from gRPC service
request := &proto.GetCarpoolBookingsRequest{
MinDate: timestamppb.New(start),
MaxDate: timestamppb.New(end),
}
resp, err := h.services.GRPC.CarpoolService.GetCarpoolBookings(ctx, request)
if err != nil {
return nil, err
}
bookings := []*proto.CarpoolServiceBooking{}
for _, b := range resp.Bookings {
// Apply driver filter if specified
if driverID != "" && b.Driver.Id != driverID {
continue
}
// Apply status filter if specified
if status != "" && b.Status.String() != status {
continue
}
bookings = append(bookings, b)
}
// Apply geography filtering
var departurePolygons, destinationPolygons, passengerAddressPolygons []orb.Polygon
if departureGeoLayer != "" && departureGeoCode != "" {
departurePolygons, err = h.loadGeographyPolygon(departureGeoLayer, departureGeoCode)
if err != nil {
log.Warn().Err(err).Msg("failed to load departure geography filter")
}
}
if destinationGeoLayer != "" && destinationGeoCode != "" {
destinationPolygons, err = h.loadGeographyPolygon(destinationGeoLayer, destinationGeoCode)
if err != nil {
log.Warn().Err(err).Msg("failed to load destination geography filter")
}
}
if passengerAddressGeoLayer != "" && passengerAddressGeoCode != "" {
passengerAddressPolygons, err = h.loadGeographyPolygon(passengerAddressGeoLayer, passengerAddressGeoCode)
if err != nil {
log.Warn().Err(err).Msg("failed to load passenger address geography filter")
}
}
bookings = filterOrganizedCarpoolBookingsByGeography(bookings, departurePolygons, destinationPolygons)
bookings = filterOrganizedCarpoolBookingsByPassengerAddressGeography(bookings, beneficiariesMap, passengerAddressPolygons)
// Sort by date (most recent first)
slices.SortFunc(bookings, func(a, b *proto.CarpoolServiceBooking) int {
return cmp.Compare(b.PassengerPickupDate.AsTime().Unix(), a.PassengerPickupDate.AsTime().Unix())
})
return &OrganizedCarpoolBookingsResult{
Bookings: bookings,
DriversMap: driversMap,
BeneficiariesMap: beneficiariesMap,
}, nil
}