Add history of operatons to wallets
This commit is contained in:
@@ -382,7 +382,11 @@ func (h *ApplicationHandler) BeneficiaryDisplay(w http.ResponseWriter, r *http.R
|
||||
for i, d := range diag {
|
||||
diagsAny[i] = d
|
||||
}
|
||||
h.Renderer.BeneficiaryDisplay(w, r, resp.Account.ToStorageType(), bookings, organizations, beneficiaries_file_types, file_types_map, documents, events_list, diagsAny, solidarityTransportStats)
|
||||
|
||||
account := resp.Account.ToStorageType()
|
||||
walletBalance := h.calculateWalletBalance(account)
|
||||
|
||||
h.Renderer.BeneficiaryDisplay(w, r, account, bookings, organizations, beneficiaries_file_types, file_types_map, documents, events_list, diagsAny, solidarityTransportStats, walletBalance)
|
||||
}
|
||||
|
||||
func (h *ApplicationHandler) BeneficiaryUpdate(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
@@ -20,7 +20,7 @@ import (
|
||||
groupstorage "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"
|
||||
"git.coopgo.io/coopgo-platform/multimodal-routing/libs/transit/motis"
|
||||
"git.coopgo.io/coopgo-platform/multimodal-routing/libs/transit/transitous"
|
||||
"git.coopgo.io/coopgo-platform/solidarity-transport/servers/grpc/proto/gen"
|
||||
"git.coopgo.io/coopgo-platform/solidarity-transport/servers/grpc/transformers"
|
||||
"github.com/google/uuid"
|
||||
@@ -42,7 +42,7 @@ var (
|
||||
func (h *ApplicationHandler) JourneysSearch(w http.ResponseWriter, r *http.Request) {
|
||||
r.ParseForm()
|
||||
var (
|
||||
transit_results []*motis.Itinerary
|
||||
transit_results []*transitous.Itinerary
|
||||
carpool_results []*geojson.FeatureCollection
|
||||
vehicle_results []fleetsstorage.Vehicle
|
||||
)
|
||||
@@ -158,13 +158,28 @@ func (h *ApplicationHandler) JourneysSearch(w http.ResponseWriter, r *http.Reque
|
||||
})
|
||||
}
|
||||
|
||||
// Get departure and destination addresses from properties
|
||||
var departureAddress, destinationAddress string
|
||||
if departuregeo.Properties != nil {
|
||||
if label, ok := departuregeo.Properties["label"].(string); ok {
|
||||
departureAddress = label
|
||||
}
|
||||
}
|
||||
if destinationgeo.Properties != nil {
|
||||
if label, ok := destinationgeo.Properties["label"].(string); ok {
|
||||
destinationAddress = label
|
||||
}
|
||||
}
|
||||
|
||||
// ORGANIZED CARPOOL
|
||||
organizedCarpoolsRes, err := h.services.GRPC.CarpoolService.DriverJourneys(context.Background(), &carpoolproto.DriverJourneysRequest{
|
||||
DepartureLat: departuregeo.Point().Lat(),
|
||||
DepartureLng: departuregeo.Point().Lon(),
|
||||
ArrivalLat: destinationgeo.Point().Lat(),
|
||||
ArrivalLng: destinationgeo.Point().Lon(),
|
||||
DepartureDate: timestamppb.New(departuredatetime),
|
||||
DepartureLat: departuregeo.Point().Lat(),
|
||||
DepartureLng: departuregeo.Point().Lon(),
|
||||
ArrivalLat: destinationgeo.Point().Lat(),
|
||||
ArrivalLng: destinationgeo.Point().Lon(),
|
||||
DepartureDate: timestamppb.New(departuredatetime),
|
||||
DepartureAddress: &departureAddress,
|
||||
ArrivalAddress: &destinationAddress,
|
||||
})
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error retrieving organized carpools")
|
||||
@@ -188,23 +203,19 @@ func (h *ApplicationHandler) JourneysSearch(w http.ResponseWriter, r *http.Reque
|
||||
}()
|
||||
|
||||
// TRANSIT
|
||||
transitch := make(chan *motis.Itinerary)
|
||||
go func(transitch chan *motis.Itinerary, departure *geojson.Feature, destination *geojson.Feature, datetime *time.Time) {
|
||||
transitch := make(chan *transitous.Itinerary)
|
||||
go func(transitch chan *transitous.Itinerary, departure *geojson.Feature, destination *geojson.Feature, datetime *time.Time) {
|
||||
defer close(transitch)
|
||||
// timetableView := false
|
||||
searchWindows := 1800
|
||||
response, err := h.services.TransitRouting.PlanWithResponse(context.Background(), &motis.PlanParams{
|
||||
response, err := h.services.TransitRouting.PlanWithResponse(context.Background(), &transitous.PlanParams{
|
||||
FromPlace: fmt.Sprintf("%f,%f", departure.Point().Lat(), departure.Point().Lon()),
|
||||
ToPlace: fmt.Sprintf("%f,%f", destination.Point().Lat(), destination.Point().Lon()),
|
||||
Time: datetime,
|
||||
// TimetableView: &timetableView,
|
||||
SearchWindow: &searchWindows,
|
||||
})
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error retrieving transit data from MOTIS server")
|
||||
log.Error().Err(err).Msg("error retrieving transit data from Transitous server")
|
||||
return
|
||||
}
|
||||
for _, i := range response.JSON200.Itineraries {
|
||||
for _, i := range response.Itineraries {
|
||||
transitch <- &i
|
||||
}
|
||||
}(transitch, departuregeo, destinationgeo, &departuredatetime)
|
||||
|
||||
@@ -67,7 +67,6 @@ func (h *ApplicationHandler) OrganizedCarpoolOverview(w http.ResponseWriter, r *
|
||||
|
||||
if err == nil {
|
||||
for _, b := range bookingsproto.Bookings {
|
||||
// booking, _ := transformers.BookingProtoToType(b)
|
||||
bookings = append(bookings, b)
|
||||
}
|
||||
}
|
||||
@@ -85,6 +84,101 @@ func (h *ApplicationHandler) OrganizedCarpoolOverview(w http.ResponseWriter, r *
|
||||
h.Renderer.OrganizedCarpoolOverview(w, r, accounts, accountsMap, beneficiariesMap, bookings)
|
||||
}
|
||||
|
||||
func (h *ApplicationHandler) OrganizedCarpoolBookingDisplay(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
bookingId := vars["bookingid"]
|
||||
|
||||
resp, err := h.services.GRPC.CarpoolService.GetBooking(context.Background(), &proto.GetCarpoolBookingRequest{
|
||||
BookingId: bookingId,
|
||||
})
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("could not get carpool booking")
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if resp.Booking == nil {
|
||||
log.Error().Msg("carpool booking not found")
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
driver, err := h.services.GetAccount(resp.Booking.Driver.Id)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("driver retrieval issue")
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
passenger, err := h.services.GetAccount(resp.Booking.Passenger.Id)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("passenger retrieval issue")
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// Extract driver departure and arrival addresses from DriverRoute GeoJSON
|
||||
var driverDepartureAddress, driverArrivalAddress string
|
||||
if resp.Booking.DriverRoute != nil && resp.Booking.DriverRoute.Serialized != "" {
|
||||
fc, err := geojson.UnmarshalFeatureCollection([]byte(resp.Booking.DriverRoute.Serialized))
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("could not unmarshal driver route geojson")
|
||||
} else {
|
||||
// Extract departure address (first feature)
|
||||
if len(fc.Features) > 0 {
|
||||
if addr, ok := fc.Features[0].Properties["label"]; ok {
|
||||
if addrStr, ok := addr.(string); ok {
|
||||
driverDepartureAddress = addrStr
|
||||
}
|
||||
}
|
||||
}
|
||||
// Extract arrival address (last feature)
|
||||
if len(fc.Features) > 1 {
|
||||
if addr, ok := fc.Features[1].Properties["label"]; ok {
|
||||
if addrStr, ok := addr.(string); ok {
|
||||
driverArrivalAddress = addrStr
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
h.Renderer.OrganizedCarpoolBookingDisplay(w, r, resp.Booking, driver, passenger, driverDepartureAddress, driverArrivalAddress)
|
||||
}
|
||||
|
||||
func (h *ApplicationHandler) OrganizedCarpoolBookingStatus(action string) func(w http.ResponseWriter, r *http.Request) {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
bookingId := vars["bookingid"]
|
||||
|
||||
var status proto.CarpoolServiceBookingStatus
|
||||
if action == "confirm" {
|
||||
status = proto.CarpoolServiceBookingStatus_CONFIRMED
|
||||
} else if action == "cancel" {
|
||||
status = proto.CarpoolServiceBookingStatus_CANCELLED
|
||||
} else if action == "waitconfirmation" {
|
||||
status = proto.CarpoolServiceBookingStatus_WAITING_DRIVER_CONFIRMATION
|
||||
} else {
|
||||
log.Error().Str("action", action).Msg("unknown booking action")
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
_, err := h.services.GRPC.CarpoolService.UpdateBooking(context.Background(), &proto.UpdateCarpoolBookingRequest{
|
||||
BookingId: bookingId,
|
||||
Status: status,
|
||||
})
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("update carpool booking status issue")
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// Redirect back to the booking display page
|
||||
http.Redirect(w, r, fmt.Sprintf("/app/organized-carpool/bookings/%s", bookingId), http.StatusSeeOther)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *ApplicationHandler) organizedCarpoolDrivers(r *http.Request) ([]mobilityaccountsstorage.Account, error) {
|
||||
accounts := []mobilityaccountsstorage.Account{}
|
||||
|
||||
@@ -549,48 +643,200 @@ func (h *ApplicationHandler) OrganizedCarpoolJourney(w http.ResponseWriter, r *h
|
||||
|
||||
var passenger mobilityaccountsstorage.Account
|
||||
passengerId := r.URL.Query().Get("passengerid")
|
||||
log.Info().Str("journeyid", journeyId).Str("driverid", driverId).Str("passengerid", passengerId).Msg("driver journey")
|
||||
log.Info().Str("journeyid", journeyId).Str("driverid", driverId).Str("passengerid", passengerId).Msg("organized carpool journey")
|
||||
|
||||
// Get the planned trip data
|
||||
journeyResp, err := h.services.GRPC.CarpoolService.GetPlannedTrip(context.Background(), &proto.GetPlannedTripRequest{
|
||||
Id: journeyId,
|
||||
})
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("could not get driver journey")
|
||||
log.Error().Err(err).Msg("could not get carpool journey")
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
journey, err := geojson.UnmarshalFeatureCollection([]byte(journeyResp.PlannedTrip.Serialized))
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("could not unmarshal driver journey")
|
||||
log.Error().Err(err).Msg("could not unmarshal carpool journey")
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
departureDate := journey.ExtraMembers["departure_date"]
|
||||
var departureTime *timestamppb.Timestamp
|
||||
if departureDate != nil {
|
||||
if dd, ok := departureDate.(string); ok {
|
||||
dt, _ := time.Parse("2006-01-02 15:04", dd)
|
||||
departureTime = timestamppb.New(dt)
|
||||
}
|
||||
}
|
||||
|
||||
// Extract operator from journey data
|
||||
var operatorID string
|
||||
if operator, ok := journey.ExtraMembers["operator"]; ok {
|
||||
if op, ok := operator.(string); ok {
|
||||
operatorID = op
|
||||
}
|
||||
}
|
||||
if operatorID == "" {
|
||||
operatorID = "example.coopgo.fr" // fallback to default
|
||||
}
|
||||
|
||||
if departureTime == nil {
|
||||
// Fallback to current time if we can't extract departure time
|
||||
departureTime = timestamppb.New(time.Now())
|
||||
}
|
||||
|
||||
if r.Method == "POST" {
|
||||
if passengerId == "" {
|
||||
log.Error().Err(err).Msg("could not get driver journey")
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
log.Error().Msg("missing passenger ID for carpool booking")
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
/*if _, err := h.services.GRPC.CarpoolService.CreateBooking(context.Background(), &proto.CreateCarpoolBookingRequest{
|
||||
Booking: &proto.CarpoolServiceBooking{
|
||||
Passenger: &proto.CarpoolServiceUser{
|
||||
Id: passengerId,
|
||||
},
|
||||
Driver: &proto.CarpoolServiceUser{
|
||||
Id: driverId,
|
||||
},
|
||||
if err := r.ParseForm(); err != nil {
|
||||
log.Error().Err(err).Msg("could not parse form input")
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// Extract journey properties from the geojson data
|
||||
var journeyProps map[string]any
|
||||
if props, ok := journey.ExtraMembers["properties"]; ok {
|
||||
if propsMap, ok := props.(map[string]any); ok {
|
||||
journeyProps = propsMap
|
||||
}
|
||||
PassengerId: passengerId,
|
||||
DriverId: driverId,
|
||||
DriverJourneyId: journeyId,
|
||||
}); err != nil {
|
||||
log.Error().Err(err).Msg("cannot create booking")
|
||||
}
|
||||
|
||||
if journeyProps == nil {
|
||||
log.Error().Msg("could not extract journey properties")
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}*/
|
||||
http.Redirect(w, r, fmt.Sprintf("/app/organized_carpool/"), http.StatusFound)
|
||||
}
|
||||
|
||||
// Extract departure date from journey ExtraMembers
|
||||
if depDate, ok := journey.ExtraMembers["departure_date"]; ok {
|
||||
if depDateStr, ok := depDate.(string); ok {
|
||||
if parsedTime, err := time.Parse("2006-01-02T15:04:05Z", depDateStr); err == nil {
|
||||
departureTime = timestamppb.New(parsedTime)
|
||||
} else if parsedTime, err := time.Parse("2006-01-02", depDateStr); err == nil {
|
||||
departureTime = timestamppb.New(parsedTime)
|
||||
} else {
|
||||
log.Warn().Str("departure_date", depDateStr).Msg("could not parse departure date, using current time")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Extract passenger pickup/drop coordinates and addresses from stored passenger data
|
||||
var pickupLat, pickupLng, dropLat, dropLng float64
|
||||
var pickupAddress, dropAddress string
|
||||
|
||||
// Check if we have passenger pickup and drop features in the journey's extra members
|
||||
if pickupData, ok := journey.ExtraMembers["passenger_pickup"]; ok {
|
||||
if pickupMap, ok := pickupData.(map[string]interface{}); ok {
|
||||
if geometry, ok := pickupMap["geometry"].(map[string]interface{}); ok {
|
||||
if coords, ok := geometry["coordinates"].([]interface{}); ok && len(coords) >= 2 {
|
||||
if lng, ok := coords[0].(float64); ok {
|
||||
pickupLng = lng
|
||||
}
|
||||
if lat, ok := coords[1].(float64); ok {
|
||||
pickupLat = lat
|
||||
}
|
||||
}
|
||||
}
|
||||
if properties, ok := pickupMap["properties"].(map[string]interface{}); ok {
|
||||
if label, ok := properties["label"].(string); ok {
|
||||
pickupAddress = label
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if dropData, ok := journey.ExtraMembers["passenger_drop"]; ok {
|
||||
if dropMap, ok := dropData.(map[string]interface{}); ok {
|
||||
if geometry, ok := dropMap["geometry"].(map[string]interface{}); ok {
|
||||
if coords, ok := geometry["coordinates"].([]interface{}); ok && len(coords) >= 2 {
|
||||
if lng, ok := coords[0].(float64); ok {
|
||||
dropLng = lng
|
||||
}
|
||||
if lat, ok := coords[1].(float64); ok {
|
||||
dropLat = lat
|
||||
}
|
||||
}
|
||||
}
|
||||
if properties, ok := dropMap["properties"].(map[string]interface{}); ok {
|
||||
if label, ok := properties["label"].(string); ok {
|
||||
dropAddress = label
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.Debug().Str("pickup_address", pickupAddress).Str("drop_address", dropAddress).Msg("Extracted addresses")
|
||||
|
||||
// Extract time from schedules if available and no specific departure_date was found
|
||||
if departureTime.AsTime().Equal(time.Now().Truncate(time.Second)) {
|
||||
if schedules, ok := journeyProps["schedules"]; ok {
|
||||
if schedulesList, ok := schedules.([]any); ok && len(schedulesList) > 0 {
|
||||
if schedule, ok := schedulesList[0].(map[string]any); ok {
|
||||
if timeOfDay, ok := schedule["time_of_day"].(string); ok {
|
||||
// Parse time and combine with current date
|
||||
now := time.Now()
|
||||
timeStr := fmt.Sprintf("%s %s", now.Format("2006-01-02"), timeOfDay)
|
||||
if depTime, err := time.Parse("2006-01-02 15:04", timeStr); err == nil {
|
||||
departureTime = timestamppb.New(depTime)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
motivation := r.PostFormValue("motivation")
|
||||
|
||||
// Create carpool booking using extracted journey data
|
||||
booking := &proto.CarpoolServiceBooking{
|
||||
Id: uuid.NewString(),
|
||||
Driver: &proto.CarpoolServiceUser{
|
||||
Id: driverId,
|
||||
Operator: operatorID,
|
||||
},
|
||||
Passenger: &proto.CarpoolServiceUser{
|
||||
Id: passengerId,
|
||||
Operator: operatorID,
|
||||
},
|
||||
PassengerPickupDate: departureTime,
|
||||
PassengerPickupLat: pickupLat,
|
||||
PassengerPickupLng: pickupLng,
|
||||
PassengerDropLat: dropLat,
|
||||
PassengerDropLng: dropLng,
|
||||
PassengerPickupAddress: &pickupAddress,
|
||||
PassengerDropAddress: &dropAddress,
|
||||
Status: proto.CarpoolServiceBookingStatus_WAITING_DRIVER_CONFIRMATION,
|
||||
DriverJourneyId: journeyId,
|
||||
}
|
||||
|
||||
bookingRes, err := h.services.GRPC.CarpoolService.CreateBooking(context.Background(), &proto.CreateCarpoolBookingRequest{
|
||||
Booking: booking,
|
||||
})
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("cannot create carpool booking")
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
log.Info().Str("booking_id", bookingRes.Booking.Id).Msg("Carpool booking created successfully")
|
||||
|
||||
// Send SMS notification if requested
|
||||
message := r.PostFormValue("message")
|
||||
doNotSend := r.PostFormValue("do_not_send")
|
||||
if message != "" && doNotSend != "on" {
|
||||
send_message := strings.ReplaceAll(message, "{booking_id}", bookingRes.Booking.Id)
|
||||
send_message = strings.ReplaceAll(send_message, "{motivation}", motivation)
|
||||
log.Debug().Str("message", send_message).Msg("Carpool booking created: sending message")
|
||||
h.GenerateSMS(driverId, send_message)
|
||||
}
|
||||
|
||||
http.Redirect(w, r, fmt.Sprintf("/app/organized-carpool/"), http.StatusFound)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -616,13 +862,6 @@ func (h *ApplicationHandler) OrganizedCarpoolJourney(w http.ResponseWriter, r *h
|
||||
return
|
||||
}
|
||||
|
||||
/*driverjourney, err := transformers.DriverJourneyProtoToType(journey.DriverJourney)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("could not transform driver journey type")
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}*/
|
||||
|
||||
h.Renderer.OrganizedCarpoolJourney(w, r, journey, driver, passenger, beneficiaries)
|
||||
}
|
||||
|
||||
|
||||
@@ -67,8 +67,13 @@ func (h *ApplicationHandler) SolidarityTransportExternalBookingProposal(w http.R
|
||||
booking.Status = status
|
||||
if status == "VALIDATED" {
|
||||
h.GenerateSMS(passenger.ID, message)
|
||||
if err := h.creditWallet(driver.ID, booking.Journey.Price.Amount, "Transport solidaire", "Crédit transport solidaire"); err != nil {
|
||||
log.Error().Err(err).Msg("could not credit driver wallet")
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
} else if status == "CANCELLED" {
|
||||
if err := h.creditWallet(passenger.ID, booking.Journey.Price.Amount); err != nil {
|
||||
if err := h.creditWallet(passenger.ID, booking.Journey.Price.Amount, "Transport solidaire", "Remboursement annulation transport solidaire"); err != nil {
|
||||
log.Error().Err(err).Msg("could not credit wallet")
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
|
||||
@@ -308,7 +308,9 @@ func (h *ApplicationHandler) SolidarityTransportDriverDisplay(w http.ResponseWri
|
||||
},
|
||||
}
|
||||
|
||||
h.Renderer.SolidarityTransportDriverDisplay(w, r, driver, availabilities, documents, bookings, stats)
|
||||
walletBalance := h.calculateWalletBalance(driver)
|
||||
|
||||
h.Renderer.SolidarityTransportDriverDisplay(w, r, driver, availabilities, documents, bookings, stats, walletBalance)
|
||||
}
|
||||
|
||||
func (h *ApplicationHandler) SolidarityTransportAddAvailability(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -724,8 +726,8 @@ func (h *ApplicationHandler) SolidarityTransportDriverJourney(w http.ResponseWri
|
||||
if doNotSend != "on" {
|
||||
h.GenerateSMS(driverId, send_message)
|
||||
|
||||
if err := h.creditWallet(passengerId, float64(-1)*journey.DriverJourney.Price.Amount); err != nil {
|
||||
log.Error().Err(err).Msg("could not credit wallet")
|
||||
if err := h.creditWallet(passengerId, float64(-1)*journey.DriverJourney.Price.Amount, "Transport solidaire", "Débit pour réservation transport solidaire"); err != nil {
|
||||
log.Error().Err(err).Msg("could not credit passenger wallet")
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
@@ -756,7 +758,12 @@ func (h *ApplicationHandler) SolidarityTransportDriverJourney(w http.ResponseWri
|
||||
return
|
||||
}
|
||||
|
||||
h.Renderer.SolidarityTransportDriverJourney(w, r, driverjourney, driver, passenger, beneficiaries)
|
||||
passengerWalletBalance := float64(0)
|
||||
if passenger.Data != nil {
|
||||
passengerWalletBalance = h.calculateWalletBalance(passenger)
|
||||
}
|
||||
|
||||
h.Renderer.SolidarityTransportDriverJourney(w, r, driverjourney, driver, passenger, beneficiaries, passengerWalletBalance)
|
||||
}
|
||||
|
||||
func (h *ApplicationHandler) SolidarityTransportDriverJourneyToggleNoreturn(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -830,7 +837,12 @@ func (h *ApplicationHandler) SolidarityTransportBookingDisplay(w http.ResponseWr
|
||||
return
|
||||
}
|
||||
|
||||
h.Renderer.SolidarityTransportBookingDisplay(w, r, booking, driver, passenger)
|
||||
passengerWalletBalance := float64(0)
|
||||
if passenger.Data != nil {
|
||||
passengerWalletBalance = h.calculateWalletBalance(passenger)
|
||||
}
|
||||
|
||||
h.Renderer.SolidarityTransportBookingDisplay(w, r, booking, driver, passenger, passengerWalletBalance)
|
||||
}
|
||||
|
||||
func (h *ApplicationHandler) SolidarityTransportBookingStatus(action string) func(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -867,7 +879,7 @@ func (h *ApplicationHandler) SolidarityTransportBookingStatus(action string) fun
|
||||
Id: bookingId,
|
||||
})
|
||||
if err == nil {
|
||||
if err := h.creditWallet(booking.Booking.PassengerId, booking.Booking.Journey.Price.Amount); err != nil {
|
||||
if err := h.creditWallet(booking.Booking.PassengerId, booking.Booking.Journey.Price.Amount, "Transport solidaire", "Remboursement annulation transport solidaire"); err != nil {
|
||||
log.Error().Err(err).Msg("could not credit wallet")
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
@@ -879,13 +891,25 @@ func (h *ApplicationHandler) SolidarityTransportBookingStatus(action string) fun
|
||||
Id: bookingId,
|
||||
})
|
||||
if err == nil {
|
||||
if err := h.creditWallet(booking.Booking.PassengerId, float64(-1)*booking.Booking.Journey.Price.Amount); err != nil {
|
||||
if err := h.creditWallet(booking.Booking.PassengerId, float64(-1)*booking.Booking.Journey.Price.Amount, "Transport solidaire", "Débit transport solidaire"); err != nil {
|
||||
log.Error().Err(err).Msg("could not credit wallet")
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
if status == "VALIDATED" {
|
||||
booking, err := h.services.GRPC.SolidarityTransport.GetSolidarityTransportBooking(context.Background(), &gen.GetSolidarityTransportBookingRequest{
|
||||
Id: bookingId,
|
||||
})
|
||||
if err == nil {
|
||||
if err := h.creditWallet(booking.Booking.DriverId, booking.Booking.Journey.Price.Amount, "Transport solidaire", "Crédit transport solidaire"); err != nil {
|
||||
log.Error().Err(err).Msg("could not credit driver wallet")
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
http.Redirect(w, r, fmt.Sprintf("/app/solidarity-transport/bookings/%s", bookingId), http.StatusFound)
|
||||
}
|
||||
|
||||
@@ -4,8 +4,10 @@ import (
|
||||
"context"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"git.coopgo.io/coopgo-platform/mobility-accounts/grpcapi"
|
||||
mobilityaccountsstorage "git.coopgo.io/coopgo-platform/mobility-accounts/storage"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
@@ -22,6 +24,8 @@ func (h ApplicationHandler) CreditWallet(w http.ResponseWriter, r *http.Request)
|
||||
r.ParseForm()
|
||||
|
||||
amountStr := r.FormValue("amount")
|
||||
paymentMethod := r.FormValue("payment_method")
|
||||
description := r.FormValue("description")
|
||||
|
||||
amount, err := strconv.ParseFloat(amountStr, 64)
|
||||
if err != nil {
|
||||
@@ -30,7 +34,11 @@ func (h ApplicationHandler) CreditWallet(w http.ResponseWriter, r *http.Request)
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.creditWallet(userid, amount); err != nil {
|
||||
if paymentMethod == "" {
|
||||
paymentMethod = "Paiement en espèce (MMS)"
|
||||
}
|
||||
|
||||
if err := h.creditWallet(userid, amount, paymentMethod, description); err != nil {
|
||||
log.Error().Err(err).Msg("could not credit wallet")
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
@@ -39,18 +47,65 @@ func (h ApplicationHandler) CreditWallet(w http.ResponseWriter, r *http.Request)
|
||||
http.Redirect(w, r, r.Referer(), http.StatusFound)
|
||||
}
|
||||
|
||||
func (h *ApplicationHandler) creditWallet(userid string, amount float64) error {
|
||||
func (h *ApplicationHandler) creditWallet(userid string, amount float64, paymentMethod string, description string) error {
|
||||
account, err := h.services.GetAccount(userid)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("could not retrieve account")
|
||||
return err
|
||||
}
|
||||
|
||||
// Initialize wallet if it doesn't exist
|
||||
if account.Data["wallet"] == nil {
|
||||
account.Data["wallet"] = float64(0)
|
||||
}
|
||||
|
||||
account.Data["wallet"] = account.Data["wallet"].(float64) + amount
|
||||
// Initialize wallet history if it doesn't exist
|
||||
if account.Data["wallet_history"] == nil {
|
||||
account.Data["wallet_history"] = []map[string]interface{}{}
|
||||
}
|
||||
|
||||
// Determine operation type based on amount sign
|
||||
operationType := "credit"
|
||||
if amount < 0 {
|
||||
operationType = "debit"
|
||||
}
|
||||
|
||||
// Create wallet operation record
|
||||
operation := map[string]interface{}{
|
||||
"timestamp": time.Now().Format(time.RFC3339),
|
||||
"amount": amount,
|
||||
"payment_method": paymentMethod,
|
||||
"description": description,
|
||||
"operation_type": operationType,
|
||||
}
|
||||
|
||||
// Add operation to history
|
||||
var history []map[string]interface{}
|
||||
if existingHistory, ok := account.Data["wallet_history"].([]interface{}); ok {
|
||||
// Convert []interface{} to []map[string]interface{}
|
||||
for _, item := range existingHistory {
|
||||
if historyItem, ok := item.(map[string]interface{}); ok {
|
||||
history = append(history, historyItem)
|
||||
}
|
||||
}
|
||||
} else if existingHistory, ok := account.Data["wallet_history"].([]map[string]interface{}); ok {
|
||||
history = existingHistory
|
||||
}
|
||||
|
||||
history = append(history, operation)
|
||||
account.Data["wallet_history"] = history
|
||||
|
||||
log.Debug().
|
||||
Str("userid", userid).
|
||||
Float64("amount", amount).
|
||||
Str("paymentMethod", paymentMethod).
|
||||
Str("description", description).
|
||||
Int("historyCount", len(history)).
|
||||
Msg("Adding operation to wallet history")
|
||||
|
||||
// Note: wallet balance is NOT updated here - it remains as initial amount
|
||||
// Balance is calculated from initial amount + sum of all operations
|
||||
|
||||
accountproto, err := grpcapi.AccountFromStorageType(&account)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("account type transformation issue")
|
||||
@@ -61,9 +116,69 @@ func (h *ApplicationHandler) creditWallet(userid string, amount float64) error {
|
||||
Account: accountproto,
|
||||
})
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("account type transformation issue")
|
||||
log.Error().Err(err).Msg("account update issue")
|
||||
return err
|
||||
}
|
||||
|
||||
log.Info().
|
||||
Str("userid", userid).
|
||||
Float64("amount", amount).
|
||||
Str("payment_method", paymentMethod).
|
||||
Str("description", description).
|
||||
Msg("Wallet credited successfully")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// calculateWalletBalance calculates the current wallet balance from initial amount + all operations
|
||||
func (h *ApplicationHandler) calculateWalletBalance(account mobilityaccountsstorage.Account) float64 {
|
||||
// Return 0 if account data is nil
|
||||
if account.Data == nil {
|
||||
log.Debug().Msg("calculateWalletBalance: account.Data is nil, returning 0")
|
||||
return float64(0)
|
||||
}
|
||||
|
||||
// Get initial wallet amount (default to 0 if not set)
|
||||
initialAmount := float64(0)
|
||||
if walletValue, exists := account.Data["wallet"]; exists && walletValue != nil {
|
||||
if val, ok := walletValue.(float64); ok {
|
||||
initialAmount = val
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate total from all operations
|
||||
operationsTotal := float64(0)
|
||||
operationCount := 0
|
||||
if historyValue, exists := account.Data["wallet_history"]; exists && historyValue != nil {
|
||||
var operations []map[string]interface{}
|
||||
|
||||
// Handle both []interface{} and []map[string]interface{} types
|
||||
if history, ok := historyValue.([]interface{}); ok {
|
||||
for _, item := range history {
|
||||
if operation, ok := item.(map[string]interface{}); ok {
|
||||
operations = append(operations, operation)
|
||||
}
|
||||
}
|
||||
} else if history, ok := historyValue.([]map[string]interface{}); ok {
|
||||
operations = history
|
||||
}
|
||||
|
||||
for _, operation := range operations {
|
||||
if amount, ok := operation["amount"].(float64); ok {
|
||||
operationsTotal += amount
|
||||
operationCount++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result := initialAmount + operationsTotal
|
||||
log.Debug().
|
||||
Str("accountId", account.ID).
|
||||
Float64("initialAmount", initialAmount).
|
||||
Float64("operationsTotal", operationsTotal).
|
||||
Int("operationCount", operationCount).
|
||||
Float64("result", result).
|
||||
Msg("calculateWalletBalance")
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user