feat: extra properties dynamiques, filtrage meta_status et alertes retard
Some checks failed
Build and Push Docker Image / build_and_push (push) Failing after 2m37s
Some checks failed
Build and Push Docker Image / build_and_push (push) Failing after 2m37s
This commit is contained in:
12
config.go
12
config.go
@@ -233,10 +233,20 @@ func ReadConfig() (*viper.Viper, error) {
|
|||||||
"vehicles": map[string]any{
|
"vehicles": map[string]any{
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"default_booking_duration_days": 90,
|
"default_booking_duration_days": 90,
|
||||||
"status_management": "automatic",
|
"status_management": "automatic",
|
||||||
|
"booking_extra_properties": []map[string]any{
|
||||||
|
{"name": "start_kilometers", "label": "Kilométrage de départ", "type": "number"},
|
||||||
|
{"name": "enddate", "label": "Date et heure de restitution", "type": "datetime-local", "target": "enddate"},
|
||||||
|
{"name": "end_kilometers", "label": "Kilométrage de fin", "type": "number"},
|
||||||
|
{"name": "kilometers_done", "label": "Kilomètres réalisés", "type": "computed", "operation": "subtract", "operands": []string{"end_kilometers", "start_kilometers"}, "unit": "km"},
|
||||||
|
{"name": "loan_duration", "label": "Durée du prêt", "type": "computed", "operation": "duration", "operands": []string{"booking.startdate", "booking.enddate"}},
|
||||||
|
{"name": "unavailableto", "label": "Sera à nouveau disponible le", "type": "date", "target": "unavailableto"},
|
||||||
|
},
|
||||||
"status_options": []map[string]any{
|
"status_options": []map[string]any{
|
||||||
{"name": "requested", "label": "Demandé", "initial": true, "meta_status": "open"},
|
{"name": "requested", "label": "Demandé", "initial": true, "meta_status": "open"},
|
||||||
{"name": "accepted", "label": "Accepté", "meta_status": "active"},
|
{"name": "accepted", "label": "Accepté", "meta_status": "active"},
|
||||||
|
{"name": "en_pret", "label": "En prêt", "meta_status": "active", "requested_properties": []map[string]any{{"name": "start_kilometers", "required": true}, {"name": "enddate"}}},
|
||||||
|
{"name": "completed", "label": "Terminé", "meta_status": "closed", "requested_properties": []map[string]any{{"name": "end_kilometers", "required": true}, {"name": "unavailableto"}}},
|
||||||
{"name": "refused", "label": "Refusé", "meta_status": "closed"},
|
{"name": "refused", "label": "Refusé", "meta_status": "closed"},
|
||||||
{"name": "cancelled", "label": "Annulé", "meta_status": "closed"},
|
{"name": "cancelled", "label": "Annulé", "meta_status": "closed"},
|
||||||
{"name": "not_completed", "label": "Non réalisé", "meta_status": "closed"},
|
{"name": "not_completed", "label": "Non réalisé", "meta_status": "closed"},
|
||||||
|
|||||||
@@ -3,7 +3,10 @@ package application
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math"
|
||||||
"sort"
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.coopgo.io/coopgo-apps/parcoursmob/core/utils/identification"
|
"git.coopgo.io/coopgo-apps/parcoursmob/core/utils/identification"
|
||||||
@@ -28,7 +31,7 @@ type VehiclesManagementOverviewResult struct {
|
|||||||
Bookings []fleetsstorage.Booking
|
Bookings []fleetsstorage.Booking
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *ApplicationHandler) GetVehiclesManagementOverview(ctx context.Context, groupID string) (*VehiclesManagementOverviewResult, error) {
|
func (h *ApplicationHandler) GetVehiclesManagementOverview(ctx context.Context, groupID, vehicleType, status, dateStart, dateEnd, vType, vStatus string) (*VehiclesManagementOverviewResult, error) {
|
||||||
request := &fleets.GetVehiclesRequest{
|
request := &fleets.GetVehiclesRequest{
|
||||||
Namespaces: []string{"parcoursmob"},
|
Namespaces: []string{"parcoursmob"},
|
||||||
}
|
}
|
||||||
@@ -41,14 +44,82 @@ func (h *ApplicationHandler) GetVehiclesManagementOverview(ctx context.Context,
|
|||||||
bookings := []fleetsstorage.Booking{}
|
bookings := []fleetsstorage.Booking{}
|
||||||
vehiclesMap := map[string]fleetsstorage.Vehicle{}
|
vehiclesMap := map[string]fleetsstorage.Vehicle{}
|
||||||
|
|
||||||
|
isManualStatus := h.config.GetString("modules.vehicles.status_management") == "manual"
|
||||||
|
|
||||||
|
// Parse date filters
|
||||||
|
var startdate time.Time
|
||||||
|
if dateStart != "" {
|
||||||
|
if parsed, err := time.Parse("2006-01-02", dateStart); err == nil {
|
||||||
|
startdate = parsed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var enddate time.Time
|
||||||
|
if dateEnd != "" {
|
||||||
|
if parsed, err := time.Parse("2006-01-02", dateEnd); err == nil {
|
||||||
|
enddate = parsed.Add(24 * time.Hour)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for _, vehicle := range resp.Vehicles {
|
for _, vehicle := range resp.Vehicles {
|
||||||
if h.filterVehicleByGroup(vehicle, groupID) {
|
if h.filterVehicleByGroup(vehicle, groupID) {
|
||||||
v := vehicle.ToStorageType()
|
v := vehicle.ToStorageType()
|
||||||
vehicleBookings := []fleetsstorage.Booking{}
|
vehicleBookings := []fleetsstorage.Booking{}
|
||||||
for _, b := range v.Bookings {
|
for _, b := range v.Bookings {
|
||||||
log.Debug().Any("booking", b).Msg("debug")
|
log.Debug().Any("booking", b).Msg("debug")
|
||||||
if b.Status() != fleetsstorage.StatusOld {
|
if !b.Deleted {
|
||||||
if !b.Deleted {
|
include := true
|
||||||
|
|
||||||
|
// Apply date filters (intersection: booking overlaps with [startdate, enddate])
|
||||||
|
if !startdate.IsZero() && b.Enddate.Before(startdate) {
|
||||||
|
include = false
|
||||||
|
}
|
||||||
|
if !enddate.IsZero() && b.Startdate.After(enddate) {
|
||||||
|
include = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply vehicle type filter on bookings
|
||||||
|
if include && vehicleType != "" && v.Type != vehicleType {
|
||||||
|
include = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply status filter on bookings
|
||||||
|
if include && status != "" {
|
||||||
|
isAdminUnavail, _ := b.Data["administrator_unavailability"].(bool)
|
||||||
|
if isAdminUnavail {
|
||||||
|
include = false
|
||||||
|
} else if isManualStatus {
|
||||||
|
if strings.HasPrefix(status, "meta:") {
|
||||||
|
metaParts := strings.Split(strings.TrimPrefix(status, "meta:"), ",")
|
||||||
|
metaSet := map[string]bool{}
|
||||||
|
for _, ms := range metaParts {
|
||||||
|
metaSet[ms] = true
|
||||||
|
}
|
||||||
|
allowedStatuses := h.resolveMetaStatuses(metaSet)
|
||||||
|
if !allowedStatuses[b.ManualStatus] {
|
||||||
|
include = false
|
||||||
|
}
|
||||||
|
} else if b.ManualStatus != status {
|
||||||
|
include = false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
var filterStatusInt int
|
||||||
|
switch status {
|
||||||
|
case "FORTHCOMING":
|
||||||
|
filterStatusInt = 1
|
||||||
|
case "ONGOING":
|
||||||
|
filterStatusInt = 0
|
||||||
|
case "TERMINATED":
|
||||||
|
filterStatusInt = -1
|
||||||
|
default:
|
||||||
|
filterStatusInt = 999
|
||||||
|
}
|
||||||
|
if b.Status() != filterStatusInt {
|
||||||
|
include = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if include {
|
||||||
bookings = append(bookings, b)
|
bookings = append(bookings, b)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -57,8 +128,48 @@ func (h *ApplicationHandler) GetVehiclesManagementOverview(ctx context.Context,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
v.Bookings = vehicleBookings
|
v.Bookings = vehicleBookings
|
||||||
vehicles = append(vehicles, v)
|
|
||||||
|
// Always add to vehiclesMap for booking lookups
|
||||||
vehiclesMap[v.ID] = v
|
vehiclesMap[v.ID] = v
|
||||||
|
|
||||||
|
// Apply vehicle type filter
|
||||||
|
if vType != "" && v.Type != vType {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply vehicle reservation status filter
|
||||||
|
if vStatus != "" {
|
||||||
|
hasActive := false
|
||||||
|
hasUpcoming := false
|
||||||
|
hasRetired := false
|
||||||
|
for _, b := range v.Bookings {
|
||||||
|
isAdminUnavail, _ := b.Data["administrator_unavailability"].(bool)
|
||||||
|
if isAdminUnavail {
|
||||||
|
hasRetired = true
|
||||||
|
} else if b.Status() == 0 {
|
||||||
|
hasActive = true
|
||||||
|
} else if b.Status() == 1 {
|
||||||
|
hasUpcoming = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match := false
|
||||||
|
switch vStatus {
|
||||||
|
case "DISPONIBLE":
|
||||||
|
match = !hasActive && !hasUpcoming && !hasRetired
|
||||||
|
case "RESERVE":
|
||||||
|
match = hasUpcoming
|
||||||
|
case "EN_PRET":
|
||||||
|
match = hasActive
|
||||||
|
case "RETIRE":
|
||||||
|
match = hasRetired
|
||||||
|
}
|
||||||
|
if !match {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
vehicles = append(vehicles, v)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,7 +206,7 @@ type VehiclesManagementBookingsListResult struct {
|
|||||||
CacheID string
|
CacheID string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *ApplicationHandler) GetVehiclesManagementBookingsList(ctx context.Context, groupID, status, startDate, endDate string) (*VehiclesManagementBookingsListResult, error) {
|
func (h *ApplicationHandler) GetVehiclesManagementBookingsList(ctx context.Context, groupID, status, startDate, endDate, vehicleType string) (*VehiclesManagementBookingsListResult, error) {
|
||||||
request := &fleets.GetVehiclesRequest{
|
request := &fleets.GetVehiclesRequest{
|
||||||
Namespaces: []string{"parcoursmob"},
|
Namespaces: []string{"parcoursmob"},
|
||||||
IncludeDeleted: true,
|
IncludeDeleted: true,
|
||||||
@@ -129,11 +240,26 @@ func (h *ApplicationHandler) GetVehiclesManagementBookingsList(ctx context.Conte
|
|||||||
v := vehicle.ToStorageType()
|
v := vehicle.ToStorageType()
|
||||||
vehiclesMap[v.ID] = v
|
vehiclesMap[v.ID] = v
|
||||||
for _, b := range v.Bookings {
|
for _, b := range v.Bookings {
|
||||||
if v, ok := b.Data["administrator_unavailability"].(bool); !ok || !v {
|
if isAdminUnavail, ok := b.Data["administrator_unavailability"].(bool); !ok || !isAdminUnavail {
|
||||||
|
// Apply vehicle type filter
|
||||||
|
if vehicleType != "" && v.Type != vehicleType {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
// Apply status filter
|
// Apply status filter
|
||||||
if status != "" {
|
if status != "" {
|
||||||
if h.config.GetString("modules.vehicles.status_management") == "manual" {
|
if h.config.GetString("modules.vehicles.status_management") == "manual" {
|
||||||
if b.ManualStatus != status {
|
if strings.HasPrefix(status, "meta:") {
|
||||||
|
metaParts := strings.Split(strings.TrimPrefix(status, "meta:"), ",")
|
||||||
|
metaSet := map[string]bool{}
|
||||||
|
for _, ms := range metaParts {
|
||||||
|
metaSet[ms] = true
|
||||||
|
}
|
||||||
|
allowedStatuses := h.resolveMetaStatuses(metaSet)
|
||||||
|
if !allowedStatuses[b.ManualStatus] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
} else if b.ManualStatus != status {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -167,8 +293,8 @@ func (h *ApplicationHandler) GetVehiclesManagementBookingsList(ctx context.Conte
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply date filter (on startdate)
|
// Apply date filter (intersection: booking overlaps with [startdate, enddate])
|
||||||
if !startdate.IsZero() && b.Startdate.Before(startdate) {
|
if !startdate.IsZero() && b.Enddate.Before(startdate) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if !enddate.IsZero() && b.Startdate.After(enddate) {
|
if !enddate.IsZero() && b.Startdate.After(enddate) {
|
||||||
@@ -330,13 +456,14 @@ func (h *ApplicationHandler) UpdateBooking(ctx context.Context, bookingID string
|
|||||||
}
|
}
|
||||||
|
|
||||||
type BookingDisplayResult struct {
|
type BookingDisplayResult struct {
|
||||||
Booking fleetsstorage.Booking
|
Booking fleetsstorage.Booking
|
||||||
Vehicle fleetsstorage.Vehicle
|
Vehicle fleetsstorage.Vehicle
|
||||||
Beneficiary mobilityaccountsstorage.Account
|
Beneficiary mobilityaccountsstorage.Account
|
||||||
Group storage.Group
|
Group storage.Group
|
||||||
Documents []filestorage.FileInfo
|
Documents []filestorage.FileInfo
|
||||||
FileTypesMap map[string]string
|
FileTypesMap map[string]string
|
||||||
Alternatives []any
|
Alternatives []any
|
||||||
|
ComputedExtraProperties map[string]string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *ApplicationHandler) GetBookingDisplay(ctx context.Context, bookingID string) (*BookingDisplayResult, error) {
|
func (h *ApplicationHandler) GetBookingDisplay(ctx context.Context, bookingID string) (*BookingDisplayResult, error) {
|
||||||
@@ -388,14 +515,17 @@ func (h *ApplicationHandler) GetBookingDisplay(ctx context.Context, bookingID st
|
|||||||
documents := h.filestorage.List(filestorage.PREFIX_BOOKINGS + "/" + bookingID)
|
documents := h.filestorage.List(filestorage.PREFIX_BOOKINGS + "/" + bookingID)
|
||||||
fileTypesMap := h.config.GetStringMapString("storage.files.file_types")
|
fileTypesMap := h.config.GetStringMapString("storage.files.file_types")
|
||||||
|
|
||||||
|
computedProps := h.computeExtraProperties(booking)
|
||||||
|
|
||||||
return &BookingDisplayResult{
|
return &BookingDisplayResult{
|
||||||
Booking: booking,
|
Booking: booking,
|
||||||
Vehicle: booking.Vehicle,
|
Vehicle: booking.Vehicle,
|
||||||
Beneficiary: beneficiary,
|
Beneficiary: beneficiary,
|
||||||
Group: groupresp.Group.ToStorageType(),
|
Group: groupresp.Group.ToStorageType(),
|
||||||
Documents: documents,
|
Documents: documents,
|
||||||
FileTypesMap: fileTypesMap,
|
FileTypesMap: fileTypesMap,
|
||||||
Alternatives: alternatives,
|
Alternatives: alternatives,
|
||||||
|
ComputedExtraProperties: computedProps,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -620,7 +750,25 @@ func getStatusOptions(raw interface{}) []map[string]any {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *ApplicationHandler) UpdateBookingStatus(ctx context.Context, bookingID, newStatus, comment, userID string, userClaims map[string]any, currentGroup any) error {
|
// getBookingExtraProperties extracts booking extra property definitions from Viper config,
|
||||||
|
// handling both Go defaults ([]map[string]any) and YAML-parsed ([]interface{}) types.
|
||||||
|
func getBookingExtraProperties(raw interface{}) []map[string]any {
|
||||||
|
if props, ok := raw.([]map[string]any); ok {
|
||||||
|
return props
|
||||||
|
}
|
||||||
|
if rawSlice, ok := raw.([]interface{}); ok {
|
||||||
|
result := make([]map[string]any, 0, len(rawSlice))
|
||||||
|
for _, item := range rawSlice {
|
||||||
|
if prop, ok := item.(map[string]any); ok {
|
||||||
|
result = append(result, prop)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ApplicationHandler) UpdateBookingStatus(ctx context.Context, bookingID, newStatus, comment, userID string, userClaims map[string]any, currentGroup any, extraProperties map[string]string) error {
|
||||||
group := currentGroup.(storage.Group)
|
group := currentGroup.(storage.Group)
|
||||||
|
|
||||||
// Validate that newStatus is a valid status option
|
// Validate that newStatus is a valid status option
|
||||||
@@ -655,6 +803,70 @@ func (h *ApplicationHandler) UpdateBookingStatus(ctx context.Context, bookingID,
|
|||||||
Comment: comment,
|
Comment: comment,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Process extra properties
|
||||||
|
if len(extraProperties) > 0 {
|
||||||
|
propDefs := getBookingExtraProperties(h.config.Get("modules.vehicles.booking_extra_properties"))
|
||||||
|
|
||||||
|
// Build a lookup map of property definitions by name
|
||||||
|
propDefsMap := map[string]map[string]any{}
|
||||||
|
for _, def := range propDefs {
|
||||||
|
if name, ok := def["name"].(string); ok {
|
||||||
|
propDefsMap[name] = def
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize extra_properties in booking data if needed
|
||||||
|
storedExtras, _ := booking.Data["extra_properties"].(map[string]any)
|
||||||
|
if storedExtras == nil {
|
||||||
|
storedExtras = map[string]any{}
|
||||||
|
}
|
||||||
|
|
||||||
|
for propName, propValue := range extraProperties {
|
||||||
|
if propValue == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
def, exists := propDefsMap[propName]
|
||||||
|
if !exists {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if target, ok := def["target"].(string); ok && target != "" {
|
||||||
|
// Update structural booking field
|
||||||
|
switch target {
|
||||||
|
case "unavailableto":
|
||||||
|
if t, err := time.Parse("2006-01-02", propValue); err == nil {
|
||||||
|
booking.Unavailableto = t
|
||||||
|
}
|
||||||
|
case "unavailablefrom":
|
||||||
|
if t, err := time.Parse("2006-01-02", propValue); err == nil {
|
||||||
|
booking.Unavailablefrom = t
|
||||||
|
}
|
||||||
|
case "enddate":
|
||||||
|
paris, _ := time.LoadLocation("Europe/Paris")
|
||||||
|
if t, err := time.ParseInLocation("2006-01-02T15:04", propValue, paris); err == nil {
|
||||||
|
booking.Enddate = t
|
||||||
|
if t.After(booking.Unavailableto) || t.Equal(booking.Unavailableto) {
|
||||||
|
booking.Unavailableto = t.Add(24 * time.Hour)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "startdate":
|
||||||
|
paris, _ := time.LoadLocation("Europe/Paris")
|
||||||
|
if t, err := time.ParseInLocation("2006-01-02T15:04", propValue, paris); err == nil {
|
||||||
|
booking.Startdate = t
|
||||||
|
if t.Before(booking.Unavailablefrom) {
|
||||||
|
booking.Unavailablefrom = t
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Store in extra_properties
|
||||||
|
storedExtras[propName] = propValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
booking.Data["extra_properties"] = storedExtras
|
||||||
|
}
|
||||||
|
|
||||||
b, err := fleets.BookingFromStorageType(&booking)
|
b, err := fleets.BookingFromStorageType(&booking)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to convert booking: %w", err)
|
return fmt.Errorf("failed to convert booking: %w", err)
|
||||||
@@ -666,3 +878,205 @@ func (h *ApplicationHandler) UpdateBookingStatus(ctx context.Context, bookingID,
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Computed extra properties ---
|
||||||
|
|
||||||
|
func (h *ApplicationHandler) computeExtraProperties(booking fleetsstorage.Booking) map[string]string {
|
||||||
|
defs := getBookingExtraProperties(h.config.Get("modules.vehicles.booking_extra_properties"))
|
||||||
|
extras, _ := booking.Data["extra_properties"].(map[string]any)
|
||||||
|
if extras == nil {
|
||||||
|
extras = map[string]any{}
|
||||||
|
}
|
||||||
|
|
||||||
|
result := map[string]string{}
|
||||||
|
for _, def := range defs {
|
||||||
|
defType, _ := def["type"].(string)
|
||||||
|
if defType != "computed" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
name, _ := def["name"].(string)
|
||||||
|
operation, _ := def["operation"].(string)
|
||||||
|
unit, _ := def["unit"].(string)
|
||||||
|
operands := resolveStringSlice(def["operands"])
|
||||||
|
if name == "" || operation == "" || len(operands) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
val := evaluateOperation(operation, operands, booking, extras)
|
||||||
|
if val != "" {
|
||||||
|
if unit != "" {
|
||||||
|
val = val + " " + unit
|
||||||
|
}
|
||||||
|
result[name] = val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// resolveStringSlice handles both Go defaults ([]string) and YAML-parsed ([]interface{}) types.
|
||||||
|
func resolveStringSlice(raw interface{}) []string {
|
||||||
|
if s, ok := raw.([]string); ok {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
if rawSlice, ok := raw.([]interface{}); ok {
|
||||||
|
result := make([]string, 0, len(rawSlice))
|
||||||
|
for _, item := range rawSlice {
|
||||||
|
if str, ok := item.(string); ok {
|
||||||
|
result = append(result, str)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func evaluateOperation(operation string, operands []string, booking fleetsstorage.Booking, extras map[string]any) string {
|
||||||
|
switch operation {
|
||||||
|
case "add":
|
||||||
|
if len(operands) < 2 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
a, okA := resolveNumericOperand(operands[0], booking, extras)
|
||||||
|
b, okB := resolveNumericOperand(operands[1], booking, extras)
|
||||||
|
if !okA || !okB {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return formatNumber(a + b)
|
||||||
|
|
||||||
|
case "subtract":
|
||||||
|
if len(operands) < 2 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
a, okA := resolveNumericOperand(operands[0], booking, extras)
|
||||||
|
b, okB := resolveNumericOperand(operands[1], booking, extras)
|
||||||
|
if !okA || !okB {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return formatNumber(a - b)
|
||||||
|
|
||||||
|
case "multiply":
|
||||||
|
if len(operands) < 2 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
a, okA := resolveNumericOperand(operands[0], booking, extras)
|
||||||
|
b, okB := resolveNumericOperand(operands[1], booking, extras)
|
||||||
|
if !okA || !okB {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return formatNumber(a * b)
|
||||||
|
|
||||||
|
case "divide":
|
||||||
|
if len(operands) < 2 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
a, okA := resolveNumericOperand(operands[0], booking, extras)
|
||||||
|
b, okB := resolveNumericOperand(operands[1], booking, extras)
|
||||||
|
if !okA || !okB || b == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return formatNumber(a / b)
|
||||||
|
|
||||||
|
case "duration":
|
||||||
|
if len(operands) < 2 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
d1, ok1 := resolveDateOperand(operands[0], booking, extras)
|
||||||
|
d2, ok2 := resolveDateOperand(operands[1], booking, extras)
|
||||||
|
if !ok1 || !ok2 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return formatDuration(d2.Sub(d1))
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func resolveNumericOperand(name string, booking fleetsstorage.Booking, extras map[string]any) (float64, bool) {
|
||||||
|
if v, ok := extras[name]; ok {
|
||||||
|
switch val := v.(type) {
|
||||||
|
case float64:
|
||||||
|
return val, true
|
||||||
|
case string:
|
||||||
|
if f, err := strconv.ParseFloat(val, 64); err == nil {
|
||||||
|
return f, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func resolveDateOperand(name string, booking fleetsstorage.Booking, extras map[string]any) (time.Time, bool) {
|
||||||
|
if strings.HasPrefix(name, "booking.") {
|
||||||
|
field := strings.TrimPrefix(name, "booking.")
|
||||||
|
switch field {
|
||||||
|
case "startdate":
|
||||||
|
return booking.Startdate, !booking.Startdate.IsZero()
|
||||||
|
case "enddate":
|
||||||
|
return booking.Enddate, !booking.Enddate.IsZero()
|
||||||
|
case "unavailablefrom":
|
||||||
|
return booking.Unavailablefrom, !booking.Unavailablefrom.IsZero()
|
||||||
|
case "unavailableto":
|
||||||
|
return booking.Unavailableto, !booking.Unavailableto.IsZero()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if v, ok := extras[name]; ok {
|
||||||
|
if dateStr, ok := v.(string); ok {
|
||||||
|
for _, layout := range []string{"2006-01-02T15:04", "2006-01-02"} {
|
||||||
|
if t, err := time.Parse(layout, dateStr); err == nil {
|
||||||
|
return t, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return time.Time{}, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatNumber(v float64) string {
|
||||||
|
if v == math.Trunc(v) {
|
||||||
|
return strconv.FormatInt(int64(v), 10)
|
||||||
|
}
|
||||||
|
return strconv.FormatFloat(v, 'f', 2, 64)
|
||||||
|
}
|
||||||
|
|
||||||
|
// resolveMetaStatuses returns a set of status names matching the given meta_status values.
|
||||||
|
// metaValues is a set like {"open": true, "active": true}.
|
||||||
|
func (h *ApplicationHandler) resolveMetaStatuses(metaValues map[string]bool) map[string]bool {
|
||||||
|
result := map[string]bool{}
|
||||||
|
options := getStatusOptions(h.config.Get("modules.vehicles.status_options"))
|
||||||
|
for _, opt := range options {
|
||||||
|
ms, _ := opt["meta_status"].(string)
|
||||||
|
name, _ := opt["name"].(string)
|
||||||
|
if metaValues[ms] {
|
||||||
|
result[name] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatDuration(d time.Duration) string {
|
||||||
|
if d < 0 {
|
||||||
|
d = -d
|
||||||
|
}
|
||||||
|
totalHours := int(d.Hours())
|
||||||
|
days := totalHours / 24
|
||||||
|
hours := totalHours % 24
|
||||||
|
|
||||||
|
parts := []string{}
|
||||||
|
if days > 0 {
|
||||||
|
s := "jour"
|
||||||
|
if days > 1 {
|
||||||
|
s = "jours"
|
||||||
|
}
|
||||||
|
parts = append(parts, fmt.Sprintf("%d %s", days, s))
|
||||||
|
}
|
||||||
|
if hours > 0 {
|
||||||
|
s := "heure"
|
||||||
|
if hours > 1 {
|
||||||
|
s = "heures"
|
||||||
|
}
|
||||||
|
parts = append(parts, fmt.Sprintf("%d %s", hours, s))
|
||||||
|
}
|
||||||
|
if len(parts) == 0 {
|
||||||
|
return "0 heure"
|
||||||
|
}
|
||||||
|
return strings.Join(parts, " ")
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -170,6 +170,14 @@ func IsGuaranteedTripMotivation(globalConfig *viper.Viper) func(string) bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsPast returns true if the given time is before the current time
|
||||||
|
func IsPast(d any) bool {
|
||||||
|
if date, ok := d.(time.Time); ok {
|
||||||
|
return date.Before(time.Now())
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// GetTemplateFuncMap returns the common template functions for rendering
|
// GetTemplateFuncMap returns the common template functions for rendering
|
||||||
func GetTemplateFuncMap(group groupsstorage.Group, globalConfig *viper.Viper, fileStorage filestorage.FileStorage) template.FuncMap {
|
func GetTemplateFuncMap(group groupsstorage.Group, globalConfig *viper.Viper, fileStorage filestorage.FileStorage) template.FuncMap {
|
||||||
return template.FuncMap{
|
return template.FuncMap{
|
||||||
@@ -191,6 +199,7 @@ func GetTemplateFuncMap(group groupsstorage.Group, globalConfig *viper.Viper, fi
|
|||||||
"beneficiaryValidatedProfile": validatedprofile.ValidateProfile(globalConfig.Sub("modules.beneficiaries.validated_profile")),
|
"beneficiaryValidatedProfile": validatedprofile.ValidateProfile(globalConfig.Sub("modules.beneficiaries.validated_profile")),
|
||||||
"solidarityDriverValidatedProfile": validatedprofile.ValidateProfile(globalConfig.Sub("modules.solidarity_transport.drivers.validated_profile")),
|
"solidarityDriverValidatedProfile": validatedprofile.ValidateProfile(globalConfig.Sub("modules.solidarity_transport.drivers.validated_profile")),
|
||||||
"carpoolDriverValidatedProfile": validatedprofile.ValidateProfile(globalConfig.Sub("modules.organized_carpool.drivers.validated_profile")),
|
"carpoolDriverValidatedProfile": validatedprofile.ValidateProfile(globalConfig.Sub("modules.organized_carpool.drivers.validated_profile")),
|
||||||
|
"isPast": IsPast,
|
||||||
"isGuaranteedTripMotivation": IsGuaranteedTripMotivation(globalConfig),
|
"isGuaranteedTripMotivation": IsGuaranteedTripMotivation(globalConfig),
|
||||||
"beneficiaryDocuments": func(id string) []filestorage.FileInfo {
|
"beneficiaryDocuments": func(id string) []filestorage.FileInfo {
|
||||||
return fileStorage.List(filestorage.PREFIX_BENEFICIARIES + "/" + id)
|
return fileStorage.List(filestorage.PREFIX_BENEFICIARIES + "/" + id)
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import (
|
|||||||
|
|
||||||
const vehiclesmanagementMenu = "vehicles_management"
|
const vehiclesmanagementMenu = "vehicles_management"
|
||||||
|
|
||||||
func (renderer *Renderer) VehiclesManagementOverview(w http.ResponseWriter, r *http.Request, vehicles []fleetsstorage.Vehicle, vehicles_map map[string]fleetsstorage.Vehicle, driversMap map[string]mobilityaccountsstorage.Account, bookings []fleetsstorage.Booking) {
|
func (renderer *Renderer) VehiclesManagementOverview(w http.ResponseWriter, r *http.Request, vehicles []fleetsstorage.Vehicle, vehicles_map map[string]fleetsstorage.Vehicle, driversMap map[string]mobilityaccountsstorage.Account, bookings []fleetsstorage.Booking, filters map[string]string, vehicleTypes []string, tab string) {
|
||||||
files := renderer.ThemeConfig.GetStringSlice("views.vehicles_management.overview.files")
|
files := renderer.ThemeConfig.GetStringSlice("views.vehicles_management.overview.files")
|
||||||
state := NewState(r, renderer.ThemeConfig, vehiclesmanagementMenu)
|
state := NewState(r, renderer.ThemeConfig, vehiclesmanagementMenu)
|
||||||
state.ViewState = map[string]any{
|
state.ViewState = map[string]any{
|
||||||
@@ -18,6 +18,10 @@ func (renderer *Renderer) VehiclesManagementOverview(w http.ResponseWriter, r *h
|
|||||||
"bookings": bookings,
|
"bookings": bookings,
|
||||||
"vehicles_map": vehicles_map,
|
"vehicles_map": vehicles_map,
|
||||||
"drivers_map": driversMap,
|
"drivers_map": driversMap,
|
||||||
|
"tab": tab,
|
||||||
|
"filters": filters,
|
||||||
|
"vehicle_types": vehicleTypes,
|
||||||
|
"hide_date_filters": false,
|
||||||
"status_management": renderer.GlobalConfig.GetString("modules.vehicles.status_management"),
|
"status_management": renderer.GlobalConfig.GetString("modules.vehicles.status_management"),
|
||||||
"status_options": renderer.GlobalConfig.Get("modules.vehicles.status_options"),
|
"status_options": renderer.GlobalConfig.Get("modules.vehicles.status_options"),
|
||||||
}
|
}
|
||||||
@@ -25,7 +29,7 @@ func (renderer *Renderer) VehiclesManagementOverview(w http.ResponseWriter, r *h
|
|||||||
renderer.Render("fleet overview", w, r, files, state)
|
renderer.Render("fleet overview", w, r, files, state)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (renderer *Renderer) VehiclesManagementBookingsList(w http.ResponseWriter, r *http.Request, vehiclesMap map[string]fleetsstorage.Vehicle, driversMap map[string]mobilityaccountsstorage.Account, bookings []fleetsstorage.Booking, cacheid string, filters map[string]string) {
|
func (renderer *Renderer) VehiclesManagementBookingsList(w http.ResponseWriter, r *http.Request, vehiclesMap map[string]fleetsstorage.Vehicle, driversMap map[string]mobilityaccountsstorage.Account, bookings []fleetsstorage.Booking, cacheid string, filters map[string]string, vehicleTypes []string) {
|
||||||
files := renderer.ThemeConfig.GetStringSlice("views.vehicles_management.bookings_list.files")
|
files := renderer.ThemeConfig.GetStringSlice("views.vehicles_management.bookings_list.files")
|
||||||
state := NewState(r, renderer.ThemeConfig, vehiclesmanagementMenu)
|
state := NewState(r, renderer.ThemeConfig, vehiclesmanagementMenu)
|
||||||
state.ViewState = map[string]any{
|
state.ViewState = map[string]any{
|
||||||
@@ -34,6 +38,7 @@ func (renderer *Renderer) VehiclesManagementBookingsList(w http.ResponseWriter,
|
|||||||
"drivers_map": driversMap,
|
"drivers_map": driversMap,
|
||||||
"cacheid": cacheid,
|
"cacheid": cacheid,
|
||||||
"filters": filters,
|
"filters": filters,
|
||||||
|
"vehicle_types": vehicleTypes,
|
||||||
"status_management": renderer.GlobalConfig.GetString("modules.vehicles.status_management"),
|
"status_management": renderer.GlobalConfig.GetString("modules.vehicles.status_management"),
|
||||||
"status_options": renderer.GlobalConfig.Get("modules.vehicles.status_options"),
|
"status_options": renderer.GlobalConfig.Get("modules.vehicles.status_options"),
|
||||||
}
|
}
|
||||||
@@ -82,19 +87,21 @@ func (renderer *Renderer) VehiclesFleetUpdate(w http.ResponseWriter, r *http.Req
|
|||||||
renderer.Render("fleet display vehicle", w, r, files, state)
|
renderer.Render("fleet display vehicle", w, r, files, state)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (renderer *Renderer) VehicleManagementBookingDisplay(w http.ResponseWriter, r *http.Request, booking any, vehicle any, beneficiary any, group any, documents []filestorage.FileInfo, file_types_map map[string]string, alternative_vehicles []any) {
|
func (renderer *Renderer) VehicleManagementBookingDisplay(w http.ResponseWriter, r *http.Request, booking any, vehicle any, beneficiary any, group any, documents []filestorage.FileInfo, file_types_map map[string]string, alternative_vehicles []any, computed_extra_properties map[string]string) {
|
||||||
files := renderer.ThemeConfig.GetStringSlice("views.vehicles_management.booking_display.files")
|
files := renderer.ThemeConfig.GetStringSlice("views.vehicles_management.booking_display.files")
|
||||||
state := NewState(r, renderer.ThemeConfig, vehiclesmanagementMenu)
|
state := NewState(r, renderer.ThemeConfig, vehiclesmanagementMenu)
|
||||||
state.ViewState = map[string]any{
|
state.ViewState = map[string]any{
|
||||||
"booking": booking,
|
"booking": booking,
|
||||||
"vehicle": vehicle,
|
"vehicle": vehicle,
|
||||||
"beneficiary": beneficiary,
|
"beneficiary": beneficiary,
|
||||||
"group": group,
|
"group": group,
|
||||||
"documents": documents,
|
"documents": documents,
|
||||||
"file_types_map": file_types_map,
|
"file_types_map": file_types_map,
|
||||||
"alternative_vehicles": alternative_vehicles,
|
"alternative_vehicles": alternative_vehicles,
|
||||||
"status_management": renderer.GlobalConfig.GetString("modules.vehicles.status_management"),
|
"status_management": renderer.GlobalConfig.GetString("modules.vehicles.status_management"),
|
||||||
"status_options": renderer.GlobalConfig.Get("modules.vehicles.status_options"),
|
"status_options": renderer.GlobalConfig.Get("modules.vehicles.status_options"),
|
||||||
|
"booking_extra_properties": renderer.GlobalConfig.Get("modules.vehicles.booking_extra_properties"),
|
||||||
|
"computed_extra_properties": computed_extra_properties,
|
||||||
}
|
}
|
||||||
|
|
||||||
renderer.Render("vehicles search", w, r, files, state)
|
renderer.Render("vehicles search", w, r, files, state)
|
||||||
|
|||||||
@@ -10,6 +10,31 @@ import (
|
|||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// resolveStatusLabel returns the display label for a manual status name
|
||||||
|
func resolveStatusLabel(statusOptions interface{}, manualStatus string) string {
|
||||||
|
switch opts := statusOptions.(type) {
|
||||||
|
case []map[string]any:
|
||||||
|
for _, opt := range opts {
|
||||||
|
if name, _ := opt["name"].(string); name == manualStatus {
|
||||||
|
if label, ok := opt["label"].(string); ok {
|
||||||
|
return label
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case []interface{}:
|
||||||
|
for _, opt := range opts {
|
||||||
|
if optMap, ok := opt.(map[string]interface{}); ok {
|
||||||
|
if name, _ := optMap["name"].(string); name == manualStatus {
|
||||||
|
if label, ok := optMap["label"].(string); ok {
|
||||||
|
return label
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return manualStatus
|
||||||
|
}
|
||||||
|
|
||||||
func (r *XLSXRenderer) VehicleBookings(w http.ResponseWriter, bookings []fleetsstorage.Booking, vehiclesMap map[string]fleetsstorage.Vehicle, driversMap map[string]mobilityaccountsstorage.Account) {
|
func (r *XLSXRenderer) VehicleBookings(w http.ResponseWriter, bookings []fleetsstorage.Booking, vehiclesMap map[string]fleetsstorage.Vehicle, driversMap map[string]mobilityaccountsstorage.Account) {
|
||||||
// Create Excel spreadsheet
|
// Create Excel spreadsheet
|
||||||
spreadsheet := r.NewSpreadsheet("Réservations véhicules")
|
spreadsheet := r.NewSpreadsheet("Réservations véhicules")
|
||||||
@@ -55,6 +80,10 @@ func (r *XLSXRenderer) VehicleBookings(w http.ResponseWriter, bookings []fleetss
|
|||||||
|
|
||||||
spreadsheet.SetHeaders(headers)
|
spreadsheet.SetHeaders(headers)
|
||||||
|
|
||||||
|
// Read status management config
|
||||||
|
isManualStatus := r.Config.GetString("modules.vehicles.status_management") == "manual"
|
||||||
|
statusOptions := r.Config.Get("modules.vehicles.status_options")
|
||||||
|
|
||||||
// Add data rows
|
// Add data rows
|
||||||
for _, booking := range bookings {
|
for _, booking := range bookings {
|
||||||
vehicle := vehiclesMap[booking.Vehicleid]
|
vehicle := vehiclesMap[booking.Vehicleid]
|
||||||
@@ -69,6 +98,8 @@ func (r *XLSXRenderer) VehicleBookings(w http.ResponseWriter, bookings []fleetss
|
|||||||
status := ""
|
status := ""
|
||||||
if booking.Deleted {
|
if booking.Deleted {
|
||||||
status = "Annulé"
|
status = "Annulé"
|
||||||
|
} else if isManualStatus {
|
||||||
|
status = resolveStatusLabel(statusOptions, booking.ManualStatus)
|
||||||
} else {
|
} else {
|
||||||
switch booking.Status() {
|
switch booking.Status() {
|
||||||
case 1:
|
case 1:
|
||||||
@@ -216,6 +247,10 @@ func (r *XLSXRenderer) VehicleBookingsAdmin(w http.ResponseWriter, bookings []fl
|
|||||||
|
|
||||||
spreadsheet.SetHeaders(headers)
|
spreadsheet.SetHeaders(headers)
|
||||||
|
|
||||||
|
// Read status management config
|
||||||
|
isManualStatusAdmin := r.Config.GetString("modules.vehicles.status_management") == "manual"
|
||||||
|
statusOptionsAdmin := r.Config.Get("modules.vehicles.status_options")
|
||||||
|
|
||||||
// Add data rows
|
// Add data rows
|
||||||
for _, booking := range bookings {
|
for _, booking := range bookings {
|
||||||
// Get vehicle from map
|
// Get vehicle from map
|
||||||
@@ -243,6 +278,8 @@ func (r *XLSXRenderer) VehicleBookingsAdmin(w http.ResponseWriter, bookings []fl
|
|||||||
status := ""
|
status := ""
|
||||||
if booking.Deleted {
|
if booking.Deleted {
|
||||||
status = "Annulé"
|
status = "Annulé"
|
||||||
|
} else if isManualStatusAdmin {
|
||||||
|
status = resolveStatusLabel(statusOptionsAdmin, booking.ManualStatus)
|
||||||
} else {
|
} else {
|
||||||
switch booking.Status() {
|
switch booking.Status() {
|
||||||
case 1:
|
case 1:
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.coopgo.io/coopgo-apps/parcoursmob/core/utils/identification"
|
"git.coopgo.io/coopgo-apps/parcoursmob/core/utils/identification"
|
||||||
@@ -22,14 +23,37 @@ func (h *Handler) VehiclesManagementOverviewHTTPHandler() http.HandlerFunc {
|
|||||||
groupID = group.ID
|
groupID = group.ID
|
||||||
}
|
}
|
||||||
|
|
||||||
result, err := h.applicationHandler.GetVehiclesManagementOverview(r.Context(), groupID)
|
// Extract tab and filter parameters from query
|
||||||
|
tab := r.URL.Query().Get("tab")
|
||||||
|
vehicleType := r.URL.Query().Get("vehicle_type")
|
||||||
|
status := r.URL.Query().Get("status")
|
||||||
|
if _, hasStatus := r.URL.Query()["status"]; !hasStatus {
|
||||||
|
status = "meta:open,active"
|
||||||
|
}
|
||||||
|
dateStart := r.URL.Query().Get("date_start")
|
||||||
|
dateEnd := r.URL.Query().Get("date_end")
|
||||||
|
vType := r.URL.Query().Get("v_type")
|
||||||
|
vStatus := r.URL.Query().Get("v_status")
|
||||||
|
|
||||||
|
result, err := h.applicationHandler.GetVehiclesManagementOverview(r.Context(), groupID, vehicleType, status, dateStart, dateEnd, vType, vStatus)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Msg("error retrieving vehicles management overview")
|
log.Error().Err(err).Msg("error retrieving vehicles management overview")
|
||||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
h.renderer.VehiclesManagementOverview(w, r, result.Vehicles, result.VehiclesMap, result.DriversMap, result.Bookings)
|
filters := map[string]string{
|
||||||
|
"vehicle_type": vehicleType,
|
||||||
|
"status": status,
|
||||||
|
"date_start": dateStart,
|
||||||
|
"date_end": dateEnd,
|
||||||
|
"v_type": vType,
|
||||||
|
"v_status": vStatus,
|
||||||
|
}
|
||||||
|
|
||||||
|
vehicleTypes, _ := h.applicationHandler.GetVehicleTypes(r.Context())
|
||||||
|
|
||||||
|
h.renderer.VehiclesManagementOverview(w, r, result.Vehicles, result.VehiclesMap, result.DriversMap, result.Bookings, filters, vehicleTypes, tab)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,6 +70,7 @@ func (h *Handler) VehiclesManagementBookingsListHTTPHandler() http.HandlerFunc {
|
|||||||
status := r.URL.Query().Get("status")
|
status := r.URL.Query().Get("status")
|
||||||
dateStart := r.URL.Query().Get("date_start")
|
dateStart := r.URL.Query().Get("date_start")
|
||||||
dateEnd := r.URL.Query().Get("date_end")
|
dateEnd := r.URL.Query().Get("date_end")
|
||||||
|
vehicleType := r.URL.Query().Get("vehicle_type")
|
||||||
|
|
||||||
// Default to last month if no dates specified
|
// Default to last month if no dates specified
|
||||||
if dateStart == "" {
|
if dateStart == "" {
|
||||||
@@ -55,7 +80,7 @@ func (h *Handler) VehiclesManagementBookingsListHTTPHandler() http.HandlerFunc {
|
|||||||
dateEnd = time.Now().Format("2006-01-02")
|
dateEnd = time.Now().Format("2006-01-02")
|
||||||
}
|
}
|
||||||
|
|
||||||
result, err := h.applicationHandler.GetVehiclesManagementBookingsList(r.Context(), groupID, status, dateStart, dateEnd)
|
result, err := h.applicationHandler.GetVehiclesManagementBookingsList(r.Context(), groupID, status, dateStart, dateEnd, vehicleType)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Msg("error retrieving vehicles management bookings list")
|
log.Error().Err(err).Msg("error retrieving vehicles management bookings list")
|
||||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||||
@@ -64,12 +89,15 @@ func (h *Handler) VehiclesManagementBookingsListHTTPHandler() http.HandlerFunc {
|
|||||||
|
|
||||||
// Prepare filters map for template
|
// Prepare filters map for template
|
||||||
filters := map[string]string{
|
filters := map[string]string{
|
||||||
"status": status,
|
"status": status,
|
||||||
"date_start": dateStart,
|
"date_start": dateStart,
|
||||||
"date_end": dateEnd,
|
"date_end": dateEnd,
|
||||||
|
"vehicle_type": vehicleType,
|
||||||
}
|
}
|
||||||
|
|
||||||
h.renderer.VehiclesManagementBookingsList(w, r, result.VehiclesMap, result.DriversMap, result.Bookings, result.CacheID, filters)
|
vehicleTypes, _ := h.applicationHandler.GetVehicleTypes(r.Context())
|
||||||
|
|
||||||
|
h.renderer.VehiclesManagementBookingsList(w, r, result.VehiclesMap, result.DriversMap, result.Bookings, result.CacheID, filters, vehicleTypes)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -223,7 +251,7 @@ func (h *Handler) VehicleManagementBookingDisplayHTTPHandler() http.HandlerFunc
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
h.renderer.VehicleManagementBookingDisplay(w, r, result.Booking, result.Vehicle, result.Beneficiary, result.Group, result.Documents, result.FileTypesMap, result.Alternatives)
|
h.renderer.VehicleManagementBookingDisplay(w, r, result.Booking, result.Vehicle, result.Beneficiary, result.Group, result.Documents, result.FileTypesMap, result.Alternatives, result.ComputedExtraProperties)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -364,11 +392,19 @@ func (h *Handler) VehicleManagementUpdateBookingStatusHTTPHandler() http.Handler
|
|||||||
newStatus := r.FormValue("new_status")
|
newStatus := r.FormValue("new_status")
|
||||||
comment := r.FormValue("comment")
|
comment := r.FormValue("comment")
|
||||||
|
|
||||||
|
// Collect extra properties from prop_* form fields
|
||||||
|
extraProperties := map[string]string{}
|
||||||
|
for key, values := range r.Form {
|
||||||
|
if strings.HasPrefix(key, "prop_") && len(values) > 0 {
|
||||||
|
extraProperties[strings.TrimPrefix(key, "prop_")] = values[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
currentUserToken := r.Context().Value(identification.IdtokenKey).(*oidc.IDToken)
|
currentUserToken := r.Context().Value(identification.IdtokenKey).(*oidc.IDToken)
|
||||||
currentUserClaims := r.Context().Value(identification.ClaimsKey).(map[string]any)
|
currentUserClaims := r.Context().Value(identification.ClaimsKey).(map[string]any)
|
||||||
currentGroup := r.Context().Value(identification.GroupKey)
|
currentGroup := r.Context().Value(identification.GroupKey)
|
||||||
|
|
||||||
err := h.applicationHandler.UpdateBookingStatus(r.Context(), bookingID, newStatus, comment, currentUserToken.Subject, currentUserClaims, currentGroup)
|
err := h.applicationHandler.UpdateBookingStatus(r.Context(), bookingID, newStatus, comment, currentUserToken.Subject, currentUserClaims, currentGroup, extraProperties)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Msg("error updating booking status")
|
log.Error().Err(err).Msg("error updating booking status")
|
||||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||||
|
|||||||
@@ -53,7 +53,9 @@ func (h *Handler) FleetBookingsInGroup(w http.ResponseWriter, r *http.Request) {
|
|||||||
dateStart := r.URL.Query().Get("date_start")
|
dateStart := r.URL.Query().Get("date_start")
|
||||||
dateEnd := r.URL.Query().Get("date_end")
|
dateEnd := r.URL.Query().Get("date_end")
|
||||||
|
|
||||||
result, err := h.applicationHandler.GetVehiclesManagementBookingsList(r.Context(), group.ID, status, dateStart, dateEnd)
|
vehicleType := r.URL.Query().Get("vehicle_type")
|
||||||
|
|
||||||
|
result, err := h.applicationHandler.GetVehiclesManagementBookingsList(r.Context(), group.ID, status, dateStart, dateEnd, vehicleType)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Msg("Failed to get vehicle bookings for export")
|
log.Error().Err(err).Msg("Failed to get vehicle bookings for export")
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
|||||||
Reference in New Issue
Block a user