feat: gestion manuelle des statuts de réservation véhicule
This commit is contained in:
12
config.go
12
config.go
@@ -231,8 +231,16 @@ func ReadConfig() (*viper.Viper, error) {
|
||||
},
|
||||
},
|
||||
"vehicles": map[string]any{
|
||||
"enabled": true,
|
||||
"default_booking_duration_days": 90,
|
||||
"enabled": true,
|
||||
"default_booking_duration_days": 90,
|
||||
"status_management": "automatic",
|
||||
"status_options": []map[string]any{
|
||||
{"name": "requested", "label": "Demandé", "initial": true, "meta_status": "open"},
|
||||
{"name": "accepted", "label": "Accepté", "meta_status": "active"},
|
||||
{"name": "refused", "label": "Refusé", "meta_status": "closed"},
|
||||
{"name": "cancelled", "label": "Annulé", "meta_status": "closed"},
|
||||
{"name": "not_completed", "label": "Non réalisé", "meta_status": "closed"},
|
||||
},
|
||||
},
|
||||
"vehicles_management": map[string]any{
|
||||
"enabled": true,
|
||||
|
||||
@@ -132,32 +132,38 @@ func (h *ApplicationHandler) GetVehiclesManagementBookingsList(ctx context.Conte
|
||||
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
|
||||
if h.config.GetString("modules.vehicles.status_management") == "manual" {
|
||||
if b.ManualStatus != status {
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
statusInt = bookingStatus
|
||||
}
|
||||
bookingStatus := b.Status()
|
||||
statusInt := 0
|
||||
|
||||
// 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 b.Deleted {
|
||||
statusInt = -2 // Use -2 for cancelled to distinguish from terminated
|
||||
} else {
|
||||
statusInt = bookingStatus
|
||||
}
|
||||
|
||||
if statusInt != filterStatusInt {
|
||||
continue
|
||||
// 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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -596,3 +602,67 @@ func (h *ApplicationHandler) UpdateVehicle(ctx context.Context, vehicleID, name,
|
||||
return updateResp.Vehicle.Id, nil
|
||||
}
|
||||
|
||||
// getStatusOptions extracts status options from Viper config, handling both
|
||||
// Go defaults ([]map[string]any) and YAML-parsed ([]interface{}) types.
|
||||
func getStatusOptions(raw interface{}) []map[string]any {
|
||||
if options, ok := raw.([]map[string]any); ok {
|
||||
return options
|
||||
}
|
||||
if rawSlice, ok := raw.([]interface{}); ok {
|
||||
result := make([]map[string]any, 0, len(rawSlice))
|
||||
for _, item := range rawSlice {
|
||||
if opt, ok := item.(map[string]any); ok {
|
||||
result = append(result, opt)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *ApplicationHandler) UpdateBookingStatus(ctx context.Context, bookingID, newStatus, comment, userID string, userClaims map[string]any, currentGroup any) error {
|
||||
group := currentGroup.(storage.Group)
|
||||
|
||||
// Validate that newStatus is a valid status option
|
||||
options := getStatusOptions(h.config.Get("modules.vehicles.status_options"))
|
||||
validStatus := false
|
||||
for _, opt := range options {
|
||||
if name, ok := opt["name"].(string); ok && name == newStatus {
|
||||
validStatus = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !validStatus {
|
||||
return fmt.Errorf("invalid status: %s", newStatus)
|
||||
}
|
||||
|
||||
booking, err := h.services.GetBooking(bookingID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get booking: %w", err)
|
||||
}
|
||||
|
||||
oldStatus := booking.ManualStatus
|
||||
|
||||
booking.ManualStatus = newStatus
|
||||
booking.StatusHistory = append(booking.StatusHistory, fleetsstorage.StatusHistoryEntry{
|
||||
FromStatus: oldStatus,
|
||||
ToStatus: newStatus,
|
||||
UserID: userID,
|
||||
UserName: fmt.Sprintf("%s %s", userClaims["first_name"], userClaims["last_name"]),
|
||||
GroupID: group.ID,
|
||||
GroupName: fmt.Sprintf("%s", group.Data["name"]),
|
||||
Date: time.Now(),
|
||||
Comment: comment,
|
||||
})
|
||||
|
||||
b, err := fleets.BookingFromStorageType(&booking)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to convert booking: %w", err)
|
||||
}
|
||||
|
||||
_, err = h.services.GRPC.Fleets.UpdateBooking(ctx, &fleets.UpdateBookingRequest{
|
||||
Booking: b,
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -210,6 +210,29 @@ func (h *ApplicationHandler) BookVehicle(ctx context.Context, vehicleID, benefic
|
||||
Data: datapb,
|
||||
}
|
||||
|
||||
if h.config.GetString("modules.vehicles.status_management") == "manual" {
|
||||
options := getStatusOptions(h.config.Get("modules.vehicles.status_options"))
|
||||
for _, opt := range options {
|
||||
if initial, ok := opt["initial"].(bool); ok && initial {
|
||||
if name, ok := opt["name"].(string); ok {
|
||||
booking.ManualStatus = name
|
||||
booking.StatusHistory = []*fleets.StatusHistoryEntry{
|
||||
{
|
||||
ToStatus: name,
|
||||
UserId: currentUserID,
|
||||
UserName: fmt.Sprintf("%s %s", currentUserClaims["first_name"], currentUserClaims["last_name"]),
|
||||
GroupId: group.ID,
|
||||
GroupName: fmt.Sprintf("%s", group.Data["name"]),
|
||||
Date: timestamppb.Now(),
|
||||
Comment: "Création de la réservation",
|
||||
},
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
request := &fleets.CreateBookingRequest{
|
||||
Booking: booking,
|
||||
}
|
||||
|
||||
3
go.mod
3
go.mod
@@ -48,7 +48,7 @@ require (
|
||||
git.coopgo.io/coopgo-platform/agenda v1.0.0
|
||||
git.coopgo.io/coopgo-platform/carpool-service v0.0.0-20251008165122-38cb3c5ad9b4
|
||||
git.coopgo.io/coopgo-platform/emailing v0.0.0-20250212064257-167ef5864260
|
||||
git.coopgo.io/coopgo-platform/fleets v1.1.0
|
||||
git.coopgo.io/coopgo-platform/fleets v1.1.1-0.20260226165510-6007cffdf152
|
||||
git.coopgo.io/coopgo-platform/geography v0.0.0-20251010131258-ec939649e858
|
||||
git.coopgo.io/coopgo-platform/groups-management v0.0.0-20230310123255-5ef94ee0746c
|
||||
git.coopgo.io/coopgo-platform/mobility-accounts v0.0.0-20230329105908-a76c0412a386
|
||||
@@ -164,7 +164,6 @@ require (
|
||||
go.etcd.io/etcd/api/v3 v3.5.12 // indirect
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.5.12 // indirect
|
||||
go.mongodb.org/mongo-driver v1.17.4 // indirect
|
||||
go.mongodb.org/mongo-driver/v2 v2.1.0 // indirect
|
||||
go.uber.org/atomic v1.11.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.uber.org/zap v1.21.0 // indirect
|
||||
|
||||
8
go.sum
8
go.sum
@@ -8,8 +8,8 @@ git.coopgo.io/coopgo-platform/carpool-service/interoperability/ocss v0.0.0-20251
|
||||
git.coopgo.io/coopgo-platform/carpool-service/interoperability/ocss v0.0.0-20251008142525-4392f227836a/go.mod h1:c9aJwNtY4PJuqAFYZ9afnx46UAZtWJ3P8ICZM02/DBA=
|
||||
git.coopgo.io/coopgo-platform/emailing v0.0.0-20250212064257-167ef5864260 h1:Li3dotY6raKu9+oxEgICU7nwdomYpjgu19i3mZNiqTc=
|
||||
git.coopgo.io/coopgo-platform/emailing v0.0.0-20250212064257-167ef5864260/go.mod h1:6cvvjv0RLSwBthIQ4TiuZoXFGvQXZ55hNSJchWXAgB4=
|
||||
git.coopgo.io/coopgo-platform/fleets v1.1.0 h1:pfW/K3fWfap54yNfkLzBXjvOjjoTaEGFEqS/+VkHv7s=
|
||||
git.coopgo.io/coopgo-platform/fleets v1.1.0/go.mod h1:nuK2mi1M2+DdntinqK/8C4ttW4WWyKCCY/xD1D7XjkE=
|
||||
git.coopgo.io/coopgo-platform/fleets v1.1.1-0.20260226165510-6007cffdf152 h1:kczmeGHnihYQSopDzbQ23B+P8Fw3GuB6iACW7SuDE+s=
|
||||
git.coopgo.io/coopgo-platform/fleets v1.1.1-0.20260226165510-6007cffdf152/go.mod h1:nuK2mi1M2+DdntinqK/8C4ttW4WWyKCCY/xD1D7XjkE=
|
||||
git.coopgo.io/coopgo-platform/geography v0.0.0-20251010131258-ec939649e858 h1:4E0tbT8jj5oxaK66Ny61o7zqPaVc0qRN2cZG9IUR4Es=
|
||||
git.coopgo.io/coopgo-platform/geography v0.0.0-20251010131258-ec939649e858/go.mod h1:TbR3g1Awa8hpAe6LR1z1EQbv2IBVgN5JQ/FjXfKX4K0=
|
||||
git.coopgo.io/coopgo-platform/groups-management v0.0.0-20230310123255-5ef94ee0746c h1:bY7PyrAgYY02f5IpDyf1WVfRqvWzivu31K6aEAYbWCw=
|
||||
@@ -26,8 +26,6 @@ git.coopgo.io/coopgo-platform/saved-search v0.0.0-20251008070953-efccea3f6463 h1
|
||||
git.coopgo.io/coopgo-platform/saved-search v0.0.0-20251008070953-efccea3f6463/go.mod h1:0fuGuYub5CBy9NB6YMqxawE0HoBaxPb9gmSw1gjfDy0=
|
||||
git.coopgo.io/coopgo-platform/sms v0.0.0-20250523074631-1f1e7fc6b7af h1:KxHim1dFcOVbFhRqelec8cJ65QBD2cma6eytW8llgYY=
|
||||
git.coopgo.io/coopgo-platform/sms v0.0.0-20250523074631-1f1e7fc6b7af/go.mod h1:mad9D+WICDdpJzB+8H/wEVVbllK2mU6VLVByrppc9x0=
|
||||
git.coopgo.io/coopgo-platform/solidarity-transport v0.0.0-20251016152145-e0882db1bcbc h1:NLU5DUo5Kt3jkPhV3KkqQMahTHIrGildBvYlHwJ6JmM=
|
||||
git.coopgo.io/coopgo-platform/solidarity-transport v0.0.0-20251016152145-e0882db1bcbc/go.mod h1:iaFXcIn7DYtKlLrSYs9C47Dt7eeMGYkpx+unLCx8TpQ=
|
||||
git.coopgo.io/coopgo-platform/solidarity-transport v0.0.0-20260114093602-8875adbcbbee h1:aoXSugsrZrM8E3WhqOCM+bLgGdxdf7dZAxx/vfbYzWQ=
|
||||
git.coopgo.io/coopgo-platform/solidarity-transport v0.0.0-20260114093602-8875adbcbbee/go.mod h1:iaFXcIn7DYtKlLrSYs9C47Dt7eeMGYkpx+unLCx8TpQ=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
|
||||
@@ -362,8 +360,6 @@ go.etcd.io/etcd/client/v3 v3.5.12/go.mod h1:tSbBCakoWmmddL+BKVAJHa9km+O/E+bumDe9
|
||||
go.mongodb.org/mongo-driver v1.11.4/go.mod h1:PTSz5yu21bkT/wXpkS7WR5f0ddqw5quethTUn9WM+2g=
|
||||
go.mongodb.org/mongo-driver v1.17.4 h1:jUorfmVzljjr0FLzYQsGP8cgN/qzzxlY9Vh0C9KFXVw=
|
||||
go.mongodb.org/mongo-driver v1.17.4/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ=
|
||||
go.mongodb.org/mongo-driver/v2 v2.1.0 h1:/ELnVNjmfUKDsoBisXxuJL0noR9CfeUIrP7Yt3R+egg=
|
||||
go.mongodb.org/mongo-driver/v2 v2.1.0/go.mod h1:AWiLRShSrk5RHQS3AEn3RL19rqOzVq49MCpWQ3x/huI=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||
go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ=
|
||||
|
||||
@@ -14,10 +14,12 @@ func (renderer *Renderer) VehiclesManagementOverview(w http.ResponseWriter, r *h
|
||||
files := renderer.ThemeConfig.GetStringSlice("views.vehicles_management.overview.files")
|
||||
state := NewState(r, renderer.ThemeConfig, vehiclesmanagementMenu)
|
||||
state.ViewState = map[string]any{
|
||||
"vehicles": vehicles,
|
||||
"bookings": bookings,
|
||||
"vehicles_map": vehicles_map,
|
||||
"drivers_map": driversMap,
|
||||
"vehicles": vehicles,
|
||||
"bookings": bookings,
|
||||
"vehicles_map": vehicles_map,
|
||||
"drivers_map": driversMap,
|
||||
"status_management": renderer.GlobalConfig.GetString("modules.vehicles.status_management"),
|
||||
"status_options": renderer.GlobalConfig.Get("modules.vehicles.status_options"),
|
||||
}
|
||||
|
||||
renderer.Render("fleet overview", w, r, files, state)
|
||||
@@ -27,11 +29,13 @@ func (renderer *Renderer) VehiclesManagementBookingsList(w http.ResponseWriter,
|
||||
files := renderer.ThemeConfig.GetStringSlice("views.vehicles_management.bookings_list.files")
|
||||
state := NewState(r, renderer.ThemeConfig, vehiclesmanagementMenu)
|
||||
state.ViewState = map[string]any{
|
||||
"bookings": bookings,
|
||||
"vehicles_map": vehiclesMap,
|
||||
"drivers_map": driversMap,
|
||||
"cacheid": cacheid,
|
||||
"filters": filters,
|
||||
"bookings": bookings,
|
||||
"vehicles_map": vehiclesMap,
|
||||
"drivers_map": driversMap,
|
||||
"cacheid": cacheid,
|
||||
"filters": filters,
|
||||
"status_management": renderer.GlobalConfig.GetString("modules.vehicles.status_management"),
|
||||
"status_options": renderer.GlobalConfig.Get("modules.vehicles.status_options"),
|
||||
}
|
||||
|
||||
renderer.Render("fleet overview", w, r, files, state)
|
||||
@@ -89,6 +93,8 @@ func (renderer *Renderer) VehicleManagementBookingDisplay(w http.ResponseWriter,
|
||||
"documents": documents,
|
||||
"file_types_map": file_types_map,
|
||||
"alternative_vehicles": alternative_vehicles,
|
||||
"status_management": renderer.GlobalConfig.GetString("modules.vehicles.status_management"),
|
||||
"status_options": renderer.GlobalConfig.Get("modules.vehicles.status_options"),
|
||||
}
|
||||
|
||||
renderer.Render("vehicles search", w, r, files, state)
|
||||
|
||||
@@ -60,12 +60,14 @@ func (renderer *Renderer) VehicleBookingDisplay(w http.ResponseWriter, r *http.R
|
||||
files := renderer.ThemeConfig.GetStringSlice("views.vehicles.booking_display.files")
|
||||
state := NewState(r, renderer.ThemeConfig, vehiclesMenu)
|
||||
state.ViewState = map[string]any{
|
||||
"booking": booking,
|
||||
"vehicle": vehicle,
|
||||
"beneficiary": beneficiary,
|
||||
"group": group,
|
||||
"documents": documents,
|
||||
"file_types_map": file_types_map,
|
||||
"booking": booking,
|
||||
"vehicle": vehicle,
|
||||
"beneficiary": beneficiary,
|
||||
"group": group,
|
||||
"documents": documents,
|
||||
"file_types_map": file_types_map,
|
||||
"status_management": renderer.GlobalConfig.GetString("modules.vehicles.status_management"),
|
||||
"status_options": renderer.GlobalConfig.Get("modules.vehicles.status_options"),
|
||||
}
|
||||
|
||||
renderer.Render("vehicles search", w, r, files, state)
|
||||
@@ -75,9 +77,11 @@ func (renderer *Renderer) VehicleBookingsList(w http.ResponseWriter, r *http.Req
|
||||
files := renderer.ThemeConfig.GetStringSlice("views.vehicles.bookings_list.files")
|
||||
state := NewState(r, renderer.ThemeConfig, vehiclesMenu)
|
||||
state.ViewState = map[string]any{
|
||||
"bookings": bookings,
|
||||
"vehicles_map": vehiclesMap,
|
||||
"groups_map": groupsMap,
|
||||
"bookings": bookings,
|
||||
"vehicles_map": vehiclesMap,
|
||||
"groups_map": groupsMap,
|
||||
"status_management": renderer.GlobalConfig.GetString("modules.vehicles.status_management"),
|
||||
"status_options": renderer.GlobalConfig.Get("modules.vehicles.status_options"),
|
||||
}
|
||||
|
||||
renderer.Render("vehicles search", w, r, files, state)
|
||||
|
||||
@@ -17,6 +17,7 @@ func (ws *WebServer) setupVehiclesManagementRoutes(appRouter *mux.Router) {
|
||||
// Bookings
|
||||
vehiclesManagement.HandleFunc("/bookings/", ws.appHandler.VehiclesManagementBookingsListHTTPHandler())
|
||||
vehiclesManagement.HandleFunc("/bookings/{bookingid}", ws.appHandler.VehicleManagementBookingDisplayHTTPHandler())
|
||||
vehiclesManagement.HandleFunc("/bookings/{bookingid}/status", ws.appHandler.VehicleManagementUpdateBookingStatusHTTPHandler()).Methods("POST")
|
||||
vehiclesManagement.HandleFunc("/bookings/{bookingid}/change-vehicle", ws.appHandler.VehicleManagementBookingChangeVehicleHTTPHandler())
|
||||
vehiclesManagement.HandleFunc("/bookings/{bookingid}/delete", ws.appHandler.DeleteBookingHTTPHandler())
|
||||
vehiclesManagement.HandleFunc("/bookings/{bookingid}/unbooking", ws.appHandler.UnbookingVehicleHTTPHandler())
|
||||
|
||||
@@ -355,6 +355,30 @@ func (h *Handler) UnbookingVehicleHTTPHandler() http.HandlerFunc {
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) VehicleManagementUpdateBookingStatusHTTPHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
bookingID := vars["bookingid"]
|
||||
|
||||
r.ParseForm()
|
||||
newStatus := r.FormValue("new_status")
|
||||
comment := r.FormValue("comment")
|
||||
|
||||
currentUserToken := r.Context().Value(identification.IdtokenKey).(*oidc.IDToken)
|
||||
currentUserClaims := r.Context().Value(identification.ClaimsKey).(map[string]any)
|
||||
currentGroup := r.Context().Value(identification.GroupKey)
|
||||
|
||||
err := h.applicationHandler.UpdateBookingStatus(r.Context(), bookingID, newStatus, comment, currentUserToken.Subject, currentUserClaims, currentGroup)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error updating booking status")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
http.Redirect(w, r, fmt.Sprintf("/app/vehicles-management/bookings/%s", bookingID), http.StatusFound)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) VehiclesFleetUpdateHTTPHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
|
||||
Reference in New Issue
Block a user