lot of new functionalities
This commit is contained in:
393
core/application/vehicles.go
Executable file
393
core/application/vehicles.go
Executable file
@@ -0,0 +1,393 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user