package application import ( "context" "encoding/json" "fmt" "io" "slices" "sort" "strings" "time" "git.coopgo.io/coopgo-apps/parcoursmob/core/utils/sorting" filestorage "git.coopgo.io/coopgo-apps/parcoursmob/core/utils/storage" fleets "git.coopgo.io/coopgo-platform/fleets/grpcapi" "git.coopgo.io/coopgo-platform/fleets/storage" groupsmanagement "git.coopgo.io/coopgo-platform/groups-management/grpcapi" groupsmanagementstorage "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" "github.com/google/uuid" "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 VehiclesSearchResult struct { Vehicles []storage.Vehicle Beneficiary mobilityaccountsstorage.Account BeneficiaryDocuments []filestorage.FileInfo Groups map[string]any Searched bool StartDate string EndDate string VehicleType string Automatic bool MandatoryDocuments []string FileTypesMap map[string]string VehicleTypes []string Beneficiaries []mobilityaccountsstorage.Account } func (h *ApplicationHandler) SearchVehicles(ctx context.Context, beneficiaryID, startDateStr, endDateStr, vehicleType string, automatic bool) (*VehiclesSearchResult, error) { var beneficiary mobilityaccountsstorage.Account beneficiarydocuments := []filestorage.FileInfo{} vehicles := []storage.Vehicle{} searched := false administrators := []string{} startdate, err := time.Parse("2006-01-02", startDateStr) if err != nil { startdate = time.Time{} } enddate, err := time.Parse("2006-01-02", endDateStr) if err != nil { enddate = time.Time{} } if beneficiaryID != "" && startdate.After(time.Now().Add(-24*time.Hour)) && enddate.After(startdate) { // Handler form searched = true requestbeneficiary := &mobilityaccounts.GetAccountRequest{ Id: beneficiaryID, } respbeneficiary, err := h.services.GRPC.MobilityAccounts.GetAccount(ctx, requestbeneficiary) if err != nil { return nil, fmt.Errorf("failed to get beneficiary: %w", err) } beneficiary = respbeneficiary.Account.ToStorageType() request := &fleets.GetVehiclesRequest{ Namespaces: []string{"parcoursmob"}, AvailabilityFrom: timestamppb.New(startdate), AvailabilityTo: timestamppb.New(enddate), } if vehicleType != "" { request.Types = []string{vehicleType} } resp, err := h.services.GRPC.Fleets.GetVehicles(ctx, request) if err != nil { return nil, fmt.Errorf("failed to get vehicles: %w", err) } for _, vehicle := range resp.Vehicles { v := vehicle.ToStorageType() if vehicleType == "Voiture" && automatic { if auto, ok := v.Data["automatic"].(bool); !ok || !auto { continue } } adminfound := false for _, a := range administrators { if a == v.Administrators[0] { adminfound = true break } } if !adminfound { administrators = append(administrators, v.Administrators[0]) } vehicles = append(vehicles, v) } // Sort vehicles if beneficiary address is set if beneficiaryAddress, ok := beneficiary.Data["address"]; ok { beneficiaryAddressJson, err := json.Marshal(beneficiaryAddress) if err == nil { beneficiaryAddressGeojson, err := geojson.UnmarshalFeature(beneficiaryAddressJson) if err == nil { slices.SortFunc(vehicles, sorting.VehiclesByDistanceFrom(*beneficiaryAddressGeojson)) } else { log.Error().Err(err).Msg("error transforming beneficiary address to GeoJSON") } } else { log.Error().Err(err).Msg("error transforming beneficiary address to JSON") } } beneficiarydocuments = h.filestorage.List(filestorage.PREFIX_BENEFICIARIES + "/" + beneficiary.ID) } accounts, err := h.services.GetBeneficiariesMap() if err != nil { return nil, fmt.Errorf("failed to get beneficiaries: %w", err) } // Convert map to slice for compatibility beneficiaries := make([]mobilityaccountsstorage.Account, 0, len(accounts)) for _, account := range accounts { beneficiaries = append(beneficiaries, account) } groups := map[string]any{} if len(administrators) > 0 { admingroups, err := h.services.GRPC.GroupsManagement.GetGroupsBatch(ctx, &groupsmanagement.GetGroupsBatchRequest{ Groupids: administrators, }) if err != nil { return nil, fmt.Errorf("failed to get admin groups: %w", err) } for _, g := range admingroups.Groups { groups[g.Id] = g.ToStorageType() } } sort.Sort(sorting.BeneficiariesByName(beneficiaries)) mandatoryDocuments := h.config.GetStringSlice("modules.fleets.booking_documents.mandatory") fileTypesMap := h.config.GetStringMapString("storage.files.file_types") vehicleTypes := h.config.GetStringSlice("modules.fleets.vehicle_types") return &VehiclesSearchResult{ Vehicles: vehicles, Beneficiary: beneficiary, BeneficiaryDocuments: beneficiarydocuments, Groups: groups, Searched: searched, StartDate: startDateStr, EndDate: endDateStr, VehicleType: vehicleType, Automatic: automatic, MandatoryDocuments: mandatoryDocuments, FileTypesMap: fileTypesMap, VehicleTypes: vehicleTypes, Beneficiaries: beneficiaries, }, nil } type BookVehicleResult struct { BookingID string } func (h *ApplicationHandler) BookVehicle(ctx context.Context, vehicleID, beneficiaryID, startDateStr, endDateStr string, documents map[string]io.Reader, documentHeaders map[string]string, existingDocs map[string]string, currentUserID string, currentUserClaims map[string]any, currentGroup any) (*BookVehicleResult, error) { group := currentGroup.(groupsmanagementstorage.Group) vehicle, err := h.services.GRPC.Fleets.GetVehicle(ctx, &fleets.GetVehicleRequest{ Vehicleid: vehicleID, }) if err != nil { return nil, fmt.Errorf("vehicle not found: %w", err) } startdate, err := time.Parse("2006-01-02", startDateStr) if err != nil { return nil, fmt.Errorf("invalid start date: %w", err) } enddate, err := time.Parse("2006-01-02", endDateStr) if err != nil { return nil, fmt.Errorf("invalid end date: %w", err) } data := map[string]any{ "booked_by": map[string]any{ "user": map[string]any{ "id": currentUserID, "display_name": fmt.Sprintf("%s %s", currentUserClaims["first_name"], currentUserClaims["last_name"]), }, "group": map[string]any{ "id": group.ID, "name": group.Data["name"], }, }, } datapb, err := structpb.NewStruct(data) if err != nil { return nil, fmt.Errorf("failed to create booking metadata: %w", err) } bookingID := uuid.NewString() booking := &fleets.Booking{ Id: bookingID, Vehicleid: vehicleID, Driver: beneficiaryID, Startdate: timestamppb.New(startdate), Enddate: timestamppb.New(enddate), Unavailablefrom: timestamppb.New(startdate), Unavailableto: timestamppb.New(enddate.Add(72 * time.Hour)), Data: datapb, } request := &fleets.CreateBookingRequest{ Booking: booking, } // Handle document uploads for docType, file := range documents { fileid := uuid.NewString() filename := documentHeaders[docType] metadata := map[string]string{ "type": docType, "name": filename, } if err := h.filestorage.Put(file, filestorage.PREFIX_BOOKINGS, fmt.Sprintf("%s/%s_%s", bookingID, fileid, filename), -1, metadata); err != nil { return nil, fmt.Errorf("failed to upload document %s: %w", docType, err) } } // Handle existing documents for docType, existingFile := range existingDocs { path := strings.Split(existingFile, "/") if err := h.filestorage.Copy(existingFile, fmt.Sprintf("%s/%s/%s", filestorage.PREFIX_BOOKINGS, bookingID, path[len(path)-1])); err != nil { return nil, fmt.Errorf("failed to copy existing document %s: %w", docType, err) } } _, err = h.services.GRPC.Fleets.CreateBooking(ctx, request) if err != nil { return nil, fmt.Errorf("failed to create booking: %w", err) } // NOTIFY GROUP MEMBERS members, _, err := h.groupmembers(vehicle.Vehicle.Administrators[0]) if err != nil { log.Error().Err(err).Msg("failed to get group members for notification") } else { for _, m := range members { if email, ok := m.Data["email"].(string); ok { h.emailing.Send("fleets.bookings.creation_admin_alert", email, map[string]string{ "bookingid": bookingID, }) } } } return &BookVehicleResult{ BookingID: bookingID, }, nil } type VehicleBookingDetailsResult struct { Booking storage.Booking Vehicle storage.Vehicle Beneficiary mobilityaccountsstorage.Account Group groupsmanagementstorage.Group Documents []filestorage.FileInfo FileTypesMap map[string]string } func (h *ApplicationHandler) GetVehicleBookingDetails(ctx context.Context, bookingID string) (*VehicleBookingDetailsResult, error) { request := &fleets.GetBookingRequest{ Bookingid: bookingID, } resp, err := h.services.GRPC.Fleets.GetBooking(ctx, request) if err != nil { return nil, fmt.Errorf("failed to get booking: %w", err) } booking := resp.Booking.ToStorageType() beneficiaryrequest := &mobilityaccounts.GetAccountRequest{ Id: booking.Driver, } beneficiaryresp, err := h.services.GRPC.MobilityAccounts.GetAccount(ctx, beneficiaryrequest) if err != nil { beneficiaryresp = &mobilityaccounts.GetAccountResponse{ Account: &mobilityaccounts.Account{}, } } grouprequest := &groupsmanagement.GetGroupRequest{ Id: booking.Vehicle.Administrators[0], } groupresp, err := h.services.GRPC.GroupsManagement.GetGroup(ctx, grouprequest) if err != nil { return nil, fmt.Errorf("failed to get group: %w", err) } documents := h.filestorage.List(filestorage.PREFIX_BOOKINGS + "/" + bookingID) fileTypesMap := h.config.GetStringMapString("storage.files.file_types") return &VehicleBookingDetailsResult{ Booking: booking, Vehicle: booking.Vehicle, Beneficiary: beneficiaryresp.Account.ToStorageType(), Group: groupresp.Group.ToStorageType(), Documents: documents, FileTypesMap: fileTypesMap, }, nil } type VehicleBookingsListResult struct { Bookings []storage.Booking VehiclesMap map[string]storage.Vehicle GroupsMap map[string]groupsmanagementstorage.Group } func (h *ApplicationHandler) GetVehicleBookingsList(ctx context.Context, groupID string) (*VehicleBookingsListResult, error) { request := &fleets.GetBookingsRequest{} resp, err := h.services.GRPC.Fleets.GetBookings(ctx, request) if err != nil { return nil, fmt.Errorf("failed to get bookings: %w", err) } bookings := []storage.Booking{} for _, b := range resp.Bookings { booking := b.ToStorageType() if b1, ok := booking.Data["booked_by"].(map[string]any); ok { if b2, ok := b1["group"].(map[string]any); ok { if b2["id"] == groupID { bookings = append(bookings, booking) } } } } vehiclesMap, err := h.services.GetVehiclesMap() if err != nil { return nil, fmt.Errorf("failed to get vehicles map: %w", err) } groupsMap, err := h.services.GetGroupsMap() if err != nil { return nil, fmt.Errorf("failed to get groups map: %w", err) } return &VehicleBookingsListResult{ Bookings: bookings, VehiclesMap: vehiclesMap, GroupsMap: groupsMap, }, nil } func (h *ApplicationHandler) GetBookingDocument(ctx context.Context, bookingID, document string) (io.Reader, string, error) { file, info, err := h.filestorage.Get(filestorage.PREFIX_BOOKINGS, fmt.Sprintf("%s/%s", bookingID, document)) if err != nil { return nil, "", fmt.Errorf("failed to get document: %w", err) } return file, info.ContentType, nil } // Helper method to expose config to web handlers func (h *ApplicationHandler) GetConfig() interface{} { return h.config }