1482 lines
		
	
	
		
			49 KiB
		
	
	
	
		
			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
 | 
						|
}
 |