601 lines
17 KiB
Go
Executable File
601 lines
17 KiB
Go
Executable File
package application
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"sort"
|
|
"time"
|
|
|
|
"git.coopgo.io/coopgo-apps/parcoursmob/core/utils/identification"
|
|
"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"
|
|
fleetsstorage "git.coopgo.io/coopgo-platform/fleets/storage"
|
|
groupsmanagement "git.coopgo.io/coopgo-platform/groups-management/grpcapi"
|
|
"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/rs/zerolog/log"
|
|
"google.golang.org/protobuf/types/known/structpb"
|
|
"google.golang.org/protobuf/types/known/timestamppb"
|
|
)
|
|
|
|
type VehiclesManagementOverviewResult struct {
|
|
Vehicles []fleetsstorage.Vehicle
|
|
VehiclesMap map[string]fleetsstorage.Vehicle
|
|
DriversMap map[string]mobilityaccountsstorage.Account
|
|
Bookings []fleetsstorage.Booking
|
|
}
|
|
|
|
func (h *ApplicationHandler) GetVehiclesManagementOverview(ctx context.Context, groupID string) (*VehiclesManagementOverviewResult, error) {
|
|
request := &fleets.GetVehiclesRequest{
|
|
Namespaces: []string{"parcoursmob"},
|
|
}
|
|
resp, err := h.services.GRPC.Fleets.GetVehicles(ctx, request)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get vehicles: %w", err)
|
|
}
|
|
|
|
vehicles := []fleetsstorage.Vehicle{}
|
|
bookings := []fleetsstorage.Booking{}
|
|
vehiclesMap := map[string]fleetsstorage.Vehicle{}
|
|
|
|
for _, vehicle := range resp.Vehicles {
|
|
if h.filterVehicleByGroup(vehicle, groupID) {
|
|
v := vehicle.ToStorageType()
|
|
vehicleBookings := []fleetsstorage.Booking{}
|
|
for _, b := range v.Bookings {
|
|
log.Debug().Any("booking", b).Msg("debug")
|
|
if b.Status() != fleetsstorage.StatusOld {
|
|
if !b.Deleted {
|
|
bookings = append(bookings, b)
|
|
}
|
|
}
|
|
if b.Unavailableto.After(time.Now()) {
|
|
vehicleBookings = append(vehicleBookings, b)
|
|
}
|
|
}
|
|
v.Bookings = vehicleBookings
|
|
vehicles = append(vehicles, v)
|
|
vehiclesMap[v.ID] = v
|
|
}
|
|
}
|
|
|
|
driversMap, _ := h.services.GetBeneficiariesMap()
|
|
|
|
sort.Sort(sorting.VehiclesByLicencePlate(vehicles))
|
|
sort.Sort(sorting.BookingsByStartdate(bookings))
|
|
|
|
return &VehiclesManagementOverviewResult{
|
|
Vehicles: vehicles,
|
|
VehiclesMap: vehiclesMap,
|
|
DriversMap: driversMap,
|
|
Bookings: bookings,
|
|
}, nil
|
|
}
|
|
|
|
func (h *ApplicationHandler) filterVehicleByGroup(v *fleets.Vehicle, groupID string) bool {
|
|
if groupID == "" {
|
|
return false
|
|
}
|
|
|
|
for _, n := range v.Administrators {
|
|
if n == groupID {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
type VehiclesManagementBookingsListResult struct {
|
|
VehiclesMap map[string]fleetsstorage.Vehicle
|
|
DriversMap map[string]mobilityaccountsstorage.Account
|
|
Bookings []fleetsstorage.Booking
|
|
CacheID string
|
|
}
|
|
|
|
func (h *ApplicationHandler) GetVehiclesManagementBookingsList(ctx context.Context, groupID, status, startDate, endDate string) (*VehiclesManagementBookingsListResult, error) {
|
|
request := &fleets.GetVehiclesRequest{
|
|
Namespaces: []string{"parcoursmob"},
|
|
IncludeDeleted: true,
|
|
}
|
|
resp, err := h.services.GRPC.Fleets.GetVehicles(ctx, request)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get vehicles: %w", err)
|
|
}
|
|
|
|
bookings := []fleetsstorage.Booking{}
|
|
vehiclesMap := map[string]fleetsstorage.Vehicle{}
|
|
|
|
// Parse start date filter
|
|
var startdate time.Time
|
|
if startDate != "" {
|
|
if parsed, err := time.Parse("2006-01-02", startDate); err == nil {
|
|
startdate = parsed
|
|
}
|
|
}
|
|
|
|
// Parse end date filter
|
|
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
|
|
}
|
|
}
|
|
|
|
for _, vehicle := range resp.Vehicles {
|
|
if h.filterVehicleByGroup(vehicle, groupID) {
|
|
v := vehicle.ToStorageType()
|
|
vehiclesMap[v.ID] = v
|
|
for _, b := range v.Bookings {
|
|
if v, ok := b.Data["administrator_unavailability"].(bool); !ok || !v {
|
|
// Apply status filter
|
|
if status != "" {
|
|
bookingStatus := b.Status()
|
|
statusInt := 0
|
|
|
|
if b.Deleted {
|
|
statusInt = -2 // Use -2 for cancelled to distinguish from terminated
|
|
} else {
|
|
statusInt = bookingStatus
|
|
}
|
|
|
|
// Map status string to int
|
|
var filterStatusInt int
|
|
switch status {
|
|
case "FORTHCOMING":
|
|
filterStatusInt = 1
|
|
case "ONGOING":
|
|
filterStatusInt = 0
|
|
case "TERMINATED":
|
|
filterStatusInt = -1
|
|
case "CANCELLED":
|
|
filterStatusInt = -2
|
|
default:
|
|
filterStatusInt = 999 // Invalid status, won't match anything
|
|
}
|
|
|
|
if statusInt != filterStatusInt {
|
|
continue
|
|
}
|
|
}
|
|
|
|
// Apply date filter (on startdate)
|
|
if !startdate.IsZero() && b.Startdate.Before(startdate) {
|
|
continue
|
|
}
|
|
if !enddate.IsZero() && b.Startdate.After(enddate) {
|
|
continue
|
|
}
|
|
|
|
bookings = append(bookings, b)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
sort.Sort(sorting.BookingsByStartdate(bookings))
|
|
|
|
cacheID := uuid.NewString()
|
|
h.cache.PutWithTTL(cacheID, bookings, 1*time.Hour)
|
|
|
|
driversMap, _ := h.services.GetBeneficiariesMap()
|
|
|
|
return &VehiclesManagementBookingsListResult{
|
|
VehiclesMap: vehiclesMap,
|
|
DriversMap: driversMap,
|
|
Bookings: bookings,
|
|
CacheID: cacheID,
|
|
}, nil
|
|
}
|
|
|
|
func (h *ApplicationHandler) CreateVehicle(ctx context.Context, name, vehicleType, informations, licencePlate, kilometers string, automatic bool, address map[string]any, otherProperties map[string]any) (string, error) {
|
|
g := ctx.Value(identification.GroupKey)
|
|
if g == nil {
|
|
return "", fmt.Errorf("no group found in context")
|
|
}
|
|
group := g.(storage.Group)
|
|
|
|
dataMap := map[string]any{}
|
|
if name != "" {
|
|
dataMap["name"] = name
|
|
}
|
|
if address != nil {
|
|
dataMap["address"] = address
|
|
}
|
|
if informations != "" {
|
|
dataMap["informations"] = informations
|
|
}
|
|
if licencePlate != "" {
|
|
dataMap["licence_plate"] = licencePlate
|
|
}
|
|
dataMap["automatic"] = automatic
|
|
if kilometers != "" {
|
|
dataMap["kilometers"] = kilometers
|
|
}
|
|
// Add other properties
|
|
for key, value := range otherProperties {
|
|
dataMap[key] = value
|
|
}
|
|
|
|
data, err := structpb.NewValue(dataMap)
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to create data struct: %w", err)
|
|
}
|
|
|
|
vehicle := &fleets.Vehicle{
|
|
Id: uuid.NewString(),
|
|
Namespace: "parcoursmob",
|
|
Type: vehicleType,
|
|
Administrators: []string{group.ID},
|
|
Data: data.GetStructValue(),
|
|
}
|
|
|
|
request := &fleets.AddVehicleRequest{
|
|
Vehicle: vehicle,
|
|
}
|
|
|
|
_, err = h.services.GRPC.Fleets.AddVehicle(ctx, request)
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to add vehicle: %w", err)
|
|
}
|
|
|
|
return vehicle.Id, nil
|
|
}
|
|
|
|
func (h *ApplicationHandler) GetVehicleTypes(ctx context.Context) ([]string, error) {
|
|
return h.config.GetStringSlice("modules.fleets.vehicle_types"), nil
|
|
}
|
|
|
|
type VehicleDisplayResult struct {
|
|
Vehicle fleetsstorage.Vehicle
|
|
Beneficiaries map[string]mobilityaccountsstorage.Account
|
|
}
|
|
|
|
func (h *ApplicationHandler) GetVehicleDisplay(ctx context.Context, vehicleID string) (*VehicleDisplayResult, error) {
|
|
request := &fleets.GetVehicleRequest{
|
|
Vehicleid: vehicleID,
|
|
}
|
|
|
|
resp, err := h.services.GRPC.Fleets.GetVehicle(ctx, request)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get vehicle: %w", err)
|
|
}
|
|
|
|
beneficiaries, err := h.services.GetBeneficiariesMap()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get beneficiaries: %w", err)
|
|
}
|
|
|
|
vehicle := resp.Vehicle.ToStorageType()
|
|
|
|
// Sort bookings by start date (most recent first)
|
|
sort.Slice(vehicle.Bookings, func(i, j int) bool {
|
|
return vehicle.Bookings[i].Startdate.After(vehicle.Bookings[j].Startdate)
|
|
})
|
|
|
|
return &VehicleDisplayResult{
|
|
Vehicle: vehicle,
|
|
Beneficiaries: beneficiaries,
|
|
}, nil
|
|
}
|
|
|
|
func (h *ApplicationHandler) UpdateBooking(ctx context.Context, bookingID, startdate, enddate, unavailablefrom, unavailableto string) error {
|
|
booking, err := h.services.GetBooking(bookingID)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get booking: %w", err)
|
|
}
|
|
|
|
newbooking, _ := fleets.BookingFromStorageType(&booking)
|
|
|
|
if startdate != "" {
|
|
newstartdate, _ := time.Parse("2006-01-02", startdate)
|
|
newbooking.Startdate = timestamppb.New(newstartdate)
|
|
|
|
if newstartdate.Before(newbooking.Unavailablefrom.AsTime()) {
|
|
newbooking.Unavailablefrom = timestamppb.New(newstartdate)
|
|
}
|
|
}
|
|
|
|
if enddate != "" {
|
|
newenddate, _ := time.Parse("2006-01-02", enddate)
|
|
newbooking.Enddate = timestamppb.New(newenddate)
|
|
|
|
if newenddate.After(newbooking.Unavailableto.AsTime()) || newenddate.Equal(newbooking.Unavailableto.AsTime()) {
|
|
newbooking.Unavailableto = timestamppb.New(newenddate.Add(24 * time.Hour))
|
|
}
|
|
}
|
|
|
|
if unavailablefrom != "" {
|
|
newunavailablefrom, _ := time.Parse("2006-01-02", unavailablefrom)
|
|
newbooking.Unavailablefrom = timestamppb.New(newunavailablefrom)
|
|
}
|
|
|
|
if unavailableto != "" {
|
|
newunavailableto, _ := time.Parse("2006-01-02", unavailableto)
|
|
newbooking.Unavailableto = timestamppb.New(newunavailableto)
|
|
}
|
|
|
|
request := &fleets.UpdateBookingRequest{
|
|
Booking: newbooking,
|
|
}
|
|
|
|
_, err = h.services.GRPC.Fleets.UpdateBooking(ctx, request)
|
|
return err
|
|
}
|
|
|
|
type BookingDisplayResult struct {
|
|
Booking fleetsstorage.Booking
|
|
Vehicle fleetsstorage.Vehicle
|
|
Beneficiary mobilityaccountsstorage.Account
|
|
Group storage.Group
|
|
Documents []filestorage.FileInfo
|
|
FileTypesMap map[string]string
|
|
Alternatives []any
|
|
}
|
|
|
|
func (h *ApplicationHandler) GetBookingDisplay(ctx context.Context, bookingID string) (*BookingDisplayResult, error) {
|
|
booking, err := h.services.GetBooking(bookingID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get booking: %w", err)
|
|
}
|
|
|
|
beneficiary := mobilityaccountsstorage.Account{}
|
|
if booking.Driver != "" {
|
|
beneficiaryrequest := &mobilityaccounts.GetAccountRequest{
|
|
Id: booking.Driver,
|
|
}
|
|
|
|
beneficiaryresp, err := h.services.GRPC.MobilityAccounts.GetAccount(ctx, beneficiaryrequest)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get beneficiary: %w", err)
|
|
}
|
|
beneficiary = beneficiaryresp.Account.ToStorageType()
|
|
}
|
|
|
|
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)
|
|
}
|
|
|
|
alternativerequest := &fleets.GetVehiclesRequest{
|
|
Namespaces: []string{"parcoursmob"},
|
|
Types: []string{booking.Vehicle.Type},
|
|
Administrators: booking.Vehicle.Administrators,
|
|
AvailabilityFrom: timestamppb.New(booking.Startdate),
|
|
AvailabilityTo: timestamppb.New(booking.Enddate.Add(24 * time.Hour)),
|
|
}
|
|
|
|
alternativeresp, err := h.services.GRPC.Fleets.GetVehicles(ctx, alternativerequest)
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("failed to get alternative vehicles")
|
|
}
|
|
|
|
alternatives := []any{}
|
|
for _, a := range alternativeresp.Vehicles {
|
|
alternatives = append(alternatives, a.ToStorageType())
|
|
}
|
|
|
|
documents := h.filestorage.List(filestorage.PREFIX_BOOKINGS + "/" + bookingID)
|
|
fileTypesMap := h.config.GetStringMapString("storage.files.file_types")
|
|
|
|
return &BookingDisplayResult{
|
|
Booking: booking,
|
|
Vehicle: booking.Vehicle,
|
|
Beneficiary: beneficiary,
|
|
Group: groupresp.Group.ToStorageType(),
|
|
Documents: documents,
|
|
FileTypesMap: fileTypesMap,
|
|
Alternatives: alternatives,
|
|
}, nil
|
|
}
|
|
|
|
func (h *ApplicationHandler) ChangeBookingVehicle(ctx context.Context, bookingID, newVehicleID string) error {
|
|
booking, err := h.services.GetBooking(bookingID)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get booking: %w", err)
|
|
}
|
|
|
|
booking.Vehicleid = newVehicleID
|
|
b, _ := fleets.BookingFromStorageType(&booking)
|
|
|
|
request := &fleets.UpdateBookingRequest{
|
|
Booking: b,
|
|
}
|
|
|
|
_, err = h.services.GRPC.Fleets.UpdateBooking(ctx, request)
|
|
return err
|
|
}
|
|
|
|
func (h *ApplicationHandler) MakeVehicleUnavailable(ctx context.Context, vehicleID, unavailablefrom, unavailableto, comment, currentUserID string, currentUserClaims map[string]any) error {
|
|
g := ctx.Value(identification.GroupKey)
|
|
if g == nil {
|
|
return fmt.Errorf("no group found in context")
|
|
}
|
|
currentGroup := g.(storage.Group)
|
|
|
|
unavailablefromTime, _ := time.Parse("2006-01-02", unavailablefrom)
|
|
unavailabletoTime, _ := time.Parse("2006-01-02", unavailableto)
|
|
|
|
data := map[string]any{
|
|
"comment": comment,
|
|
"administrator_unavailability": true,
|
|
"booked_by": map[string]any{
|
|
"user": map[string]any{
|
|
"id": currentUserID,
|
|
"display_name": currentUserClaims["display_name"],
|
|
},
|
|
"group": map[string]any{
|
|
"id": currentGroup.ID,
|
|
"name": currentGroup.Data["name"],
|
|
},
|
|
},
|
|
}
|
|
|
|
datapb, err := structpb.NewStruct(data)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create data struct: %w", err)
|
|
}
|
|
|
|
booking := &fleets.Booking{
|
|
Id: uuid.NewString(),
|
|
Vehicleid: vehicleID,
|
|
Unavailablefrom: timestamppb.New(unavailablefromTime),
|
|
Unavailableto: timestamppb.New(unavailabletoTime),
|
|
Data: datapb,
|
|
}
|
|
|
|
request := &fleets.CreateBookingRequest{
|
|
Booking: booking,
|
|
}
|
|
|
|
_, err = h.services.GRPC.Fleets.CreateBooking(ctx, request)
|
|
return err
|
|
}
|
|
|
|
func (h *ApplicationHandler) DeleteBooking(ctx context.Context, bookingID string) error {
|
|
request := &fleets.DeleteBookingRequest{
|
|
Id: bookingID,
|
|
}
|
|
|
|
_, err := h.services.GRPC.Fleets.DeleteBooking(ctx, request)
|
|
return err
|
|
}
|
|
|
|
func (h *ApplicationHandler) GetBookingForUnbooking(ctx context.Context, bookingID string) (fleetsstorage.Booking, error) {
|
|
request := &fleets.GetBookingRequest{
|
|
Bookingid: bookingID,
|
|
}
|
|
|
|
resp, err := h.services.GRPC.Fleets.GetBooking(ctx, request)
|
|
if err != nil {
|
|
return fleetsstorage.Booking{}, fmt.Errorf("failed to get booking: %w", err)
|
|
}
|
|
|
|
return resp.Booking.ToStorageType(), nil
|
|
}
|
|
|
|
func (h *ApplicationHandler) UnbookVehicle(ctx context.Context, bookingID, motif, currentUserID string, currentUserClaims map[string]any, currentGroup any) error {
|
|
group := currentGroup.(storage.Group)
|
|
|
|
// Prepare deletion metadata (microservice will add deleted_at automatically)
|
|
deletionMetadata := map[string]any{
|
|
"deleted_by": map[string]any{
|
|
"user": map[string]any{
|
|
"id": currentUserID,
|
|
"display_name": currentUserClaims["first_name"].(string) + " " + currentUserClaims["last_name"].(string),
|
|
"email": currentUserClaims["email"],
|
|
},
|
|
"group": map[string]any{
|
|
"id": group.ID,
|
|
"name": group.Data["name"],
|
|
},
|
|
},
|
|
"reason": motif,
|
|
}
|
|
|
|
deletionMetadataPb, err := structpb.NewStruct(deletionMetadata)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create deletion metadata: %w", err)
|
|
}
|
|
|
|
// Use the microservice's delete endpoint with metadata
|
|
deleteRequest := &fleets.DeleteBookingRequest{
|
|
Id: bookingID,
|
|
DeletionMetadata: deletionMetadataPb,
|
|
}
|
|
|
|
_, err = h.services.GRPC.Fleets.DeleteBooking(ctx, deleteRequest)
|
|
return err
|
|
}
|
|
|
|
type VehicleForUpdateResult struct {
|
|
Vehicle fleetsstorage.Vehicle
|
|
VehicleTypes []string
|
|
}
|
|
|
|
func (h *ApplicationHandler) GetVehicleForUpdate(ctx context.Context, vehicleID string) (*VehicleForUpdateResult, error) {
|
|
request := &fleets.GetVehicleRequest{
|
|
Vehicleid: vehicleID,
|
|
}
|
|
|
|
resp, err := h.services.GRPC.Fleets.GetVehicle(ctx, request)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get vehicle: %w", err)
|
|
}
|
|
|
|
vehicleTypes := h.config.GetStringSlice("modules.fleets.vehicle_types")
|
|
|
|
return &VehicleForUpdateResult{
|
|
Vehicle: resp.Vehicle.ToStorageType(),
|
|
VehicleTypes: vehicleTypes,
|
|
}, nil
|
|
}
|
|
|
|
func (h *ApplicationHandler) UpdateVehicle(ctx context.Context, vehicleID, name, vehicleType, informations, licencePlate, kilometers string, automatic bool, address map[string]any, otherProperties map[string]any) (string, error) {
|
|
getRequest := &fleets.GetVehicleRequest{
|
|
Vehicleid: vehicleID,
|
|
}
|
|
|
|
resp, err := h.services.GRPC.Fleets.GetVehicle(ctx, getRequest)
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to get vehicle: %w", err)
|
|
}
|
|
|
|
// Start with existing data to preserve all fields
|
|
dataMap := resp.Vehicle.Data.AsMap()
|
|
if dataMap == nil {
|
|
dataMap = map[string]any{}
|
|
}
|
|
|
|
// Update with new values
|
|
if name != "" {
|
|
dataMap["name"] = name
|
|
}
|
|
if address != nil {
|
|
dataMap["address"] = address
|
|
}
|
|
if informations != "" {
|
|
dataMap["informations"] = informations
|
|
}
|
|
if licencePlate != "" {
|
|
dataMap["licence_plate"] = licencePlate
|
|
}
|
|
if kilometers != "" {
|
|
dataMap["kilometers"] = kilometers
|
|
}
|
|
dataMap["automatic"] = automatic
|
|
// Add other properties
|
|
for key, value := range otherProperties {
|
|
dataMap[key] = value
|
|
}
|
|
|
|
data, err := structpb.NewValue(dataMap)
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to create data struct: %w", err)
|
|
}
|
|
|
|
updateRequest := &fleets.UpdateVehicleRequest{
|
|
Vehicle: &fleets.Vehicle{
|
|
Id: vehicleID,
|
|
Namespace: resp.Vehicle.Namespace,
|
|
Type: vehicleType,
|
|
Administrators: resp.Vehicle.Administrators,
|
|
Data: data.GetStructValue(),
|
|
},
|
|
}
|
|
|
|
updateResp, err := h.services.GRPC.Fleets.UpdateVehicle(ctx, updateRequest)
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to update vehicle: %w", err)
|
|
}
|
|
|
|
return updateResp.Vehicle.Id, nil
|
|
}
|
|
|