فهرست منبع

(feat) ajout du type de payement

Brin AMONKONAN 1 سال پیش
والد
کامیت
1ec8ac48e5

+ 2 - 1
app_transport_front/src/components/ReservationWorkflowModal.vue

@@ -340,7 +340,8 @@ const prepareReservationData = () => {
         dateAt: new Date().toISOString(),
         placeId: props.selectedSeats[i],
         userId: 0, // Sera remplacé par l'ID utilisateur créé après paiement
-        trajetId: props.tripId
+        trajetId: props.tripId,
+        paymentMethod: props.paymentMethod.toUpperCase()
       }
       
       reservations.push(reservationData)

+ 42 - 2
app_transport_front/src/components/employee/TripDetails.vue

@@ -113,7 +113,7 @@
                 <i class="fas fa-users text-purple-500 text-xl mr-3"></i>
                 <div>
                   <p class="text-sm font-medium text-gray-500">Passagers</p>
-                  <p class="text-lg font-semibold text-gray-900">{{ tripDetails.passengers }}/{{ tripDetails.capacity }}</p>
+                  <p class="text-lg font-semibold text-gray-900">{{ statistics.validated }}/{{ tripDetails.capacity }}</p>
                 </div>
               </div>
             </div>
@@ -257,6 +257,9 @@
                 <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
                   Statut
                 </th>
+                <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
+                  Type de paiement
+                </th>
                 <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
                   Heure validation
                 </th>
@@ -267,7 +270,7 @@
             </thead>
             <tbody class="bg-white divide-y divide-gray-200">
               <tr v-if="filteredPassengers.length === 0">
-                <td colspan="5" class="px-6 py-12 text-center text-gray-500">
+                <td colspan="6" class="px-6 py-12 text-center text-gray-500">
                   <i class="fas fa-users text-4xl mb-4 text-gray-300"></i>
                   <p>Aucun passager trouvé</p>
                 </td>
@@ -303,6 +306,13 @@
                     {{ getTicketStatusText(passenger.ticketStatus || passenger.status) }}
                   </span>
                 </td>
+                <td class="px-6 py-4 whitespace-nowrap">
+                  <span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium"
+                        :class="getPaymentMethodClass(passenger.paymentMethod)">
+                    <i :class="getPaymentMethodIcon(passenger.paymentMethod)" class="mr-1"></i>
+                    {{ getPaymentMethodText(passenger.paymentMethod) }}
+                  </span>
+                </td>
                 <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
                   {{ passenger.validatedAt ? formatDateTime(passenger.validatedAt) : '-' }}
                 </td>
@@ -526,6 +536,36 @@ const getTicketStatusText = (status: string) => {
   return texts[status] || status
 }
 
+const getPaymentMethodClass = (method: string) => {
+  const classes = {
+    'CASH': 'bg-green-100 text-green-800',
+    'MOBILE_MONEY': 'bg-blue-100 text-blue-800',
+    'BANK_CARD': 'bg-purple-100 text-purple-800',
+    'BANK_TRANSFER': 'bg-orange-100 text-orange-800'
+  }
+  return classes[method] || 'bg-gray-100 text-gray-800'
+}
+
+const getPaymentMethodIcon = (method: string) => {
+  const icons = {
+    'CASH': 'fas fa-money-bill-wave',
+    'MOBILE_MONEY': 'fas fa-mobile-alt',
+    'BANK_CARD': 'fas fa-credit-card',
+    'BANK_TRANSFER': 'fas fa-university'
+  }
+  return icons[method] || 'fas fa-question-circle'
+}
+
+const getPaymentMethodText = (method: string) => {
+  const texts = {
+    'CASH': 'Espèces',
+    'MOBILE_MONEY': 'Mobile Money',
+    'BANK_CARD': 'Carte bancaire',
+    'BANK_TRANSFER': 'Virement bancaire'
+  }
+  return texts[method] || method
+}
+
 const formatDate = (date: string) => {
   if (!date) return 'Date non définie'
   

+ 1 - 0
app_transport_front/src/services/EmployeeService.ts

@@ -54,6 +54,7 @@ export interface TripPassenger {
   ticketStatus: 'valid' | 'validated' | 'expired' | 'cancelled'
   qrCode: string
   reservationId: number
+  paymentMethod: string
   validatedAt?: string
   validatedBy?: {
     id: number

+ 1 - 0
app_transport_front/src/types/index.ts

@@ -107,6 +107,7 @@ export interface Reservation {
   placeId: number
   userId: number
   trajetId: number
+  paymentMethod?: string
   qrcode?: Qrcode
   place?: Place
   trajet?: Trip

+ 2 - 0
node_app/prisma/migrations/20250920220630_add_payment_method_to_reservation/migration.sql

@@ -0,0 +1,2 @@
+-- AlterTable
+ALTER TABLE `Reservation` ADD COLUMN `paymentMethod` ENUM('CARD', 'MOBILE', 'CASH') NOT NULL DEFAULT 'CASH';

+ 17 - 10
node_app/prisma/schema.prisma

@@ -144,16 +144,17 @@ model Trajet {
 }
 
 model Reservation {
-  id          Int          @id @default(autoincrement())
-  dateAt      DateTime
-  placeId     Int
-  userId      Int
-  trajetId    Int
-  qrcode      Qrcode?      @relation("ReservationQrcode")
-  place       Place        @relation("PlaceReservations", fields: [placeId], references: [id])
-  trajet      Trajet       @relation("TrajetReservations", fields: [trajetId], references: [id])
-  user        User         @relation("UserReservations", fields: [userId], references: [id])
-  transaction Transaction? @relation("ReservationTransaction")
+  id            Int           @id @default(autoincrement())
+  dateAt        DateTime
+  placeId       Int
+  userId        Int
+  trajetId      Int
+  paymentMethod PaymentMethod @default(CASH)
+  qrcode        Qrcode?       @relation("ReservationQrcode")
+  place         Place         @relation("PlaceReservations", fields: [placeId], references: [id])
+  trajet        Trajet        @relation("TrajetReservations", fields: [trajetId], references: [id])
+  user          User          @relation("UserReservations", fields: [userId], references: [id])
+  transaction   Transaction?  @relation("ReservationTransaction")
 
   @@index([placeId], map: "Reservation_placeId_fkey")
   @@index([trajetId], map: "Reservation_trajetId_fkey")
@@ -225,3 +226,9 @@ enum VehiculeStatus {
   INACTIF
   MAINTENANCE
 }
+
+enum PaymentMethod {
+  CARD
+  MOBILE
+  CASH
+}

+ 18 - 1
node_app/src/application/dto/create-reservation.dto.ts

@@ -1,6 +1,12 @@
-import { IsNotEmpty, IsNumber, IsDateString } from 'class-validator';
+import { IsNotEmpty, IsNumber, IsDateString, IsEnum, IsOptional } from 'class-validator';
 import { ApiProperty } from '@nestjs/swagger';
 
+export enum PaymentMethod {
+  CARD = 'CARD',
+  MOBILE = 'MOBILE',
+  CASH = 'CASH',
+}
+
 export class CreateReservationDto {
   @ApiProperty({
     description: 'Date de la réservation',
@@ -37,4 +43,15 @@ export class CreateReservationDto {
   @IsNumber()
   @IsNotEmpty()
   trajetId: number;
+
+  @ApiProperty({
+    description: 'Méthode de paiement choisie',
+    enum: PaymentMethod,
+    example: PaymentMethod.CASH,
+    required: false,
+    default: PaymentMethod.CASH,
+  })
+  @IsEnum(PaymentMethod)
+  @IsOptional()
+  paymentMethod?: PaymentMethod;
 }

+ 13 - 0
node_app/src/application/dto/responses/reservation-response.dto.ts

@@ -3,6 +3,12 @@ import { PlaceResponseDto } from './place-response.dto';
 import { UserResponseDto } from './user-response.dto';
 import { TrajetResponseDto } from './trajet-response.dto';
 
+export enum PaymentMethod {
+  CARD = 'CARD',
+  MOBILE = 'MOBILE',
+  CASH = 'CASH',
+}
+
 export class ReservationResponseDto {
   @ApiProperty()
   id: number;
@@ -18,4 +24,11 @@ export class ReservationResponseDto {
 
   @ApiProperty({ type: () => TrajetResponseDto })
   trajet: TrajetResponseDto;
+
+  @ApiProperty({
+    description: 'Méthode de paiement utilisée',
+    enum: PaymentMethod,
+    example: PaymentMethod.CASH,
+  })
+  paymentMethod: PaymentMethod;
 }

+ 12 - 2
node_app/src/application/dto/responses/trip-passenger-response.dto.ts

@@ -22,7 +22,10 @@ export class TripPassengerResponseDto {
   @ApiProperty({ description: 'Numéro de ticket' })
   ticketNumber: string;
 
-  @ApiProperty({ description: 'Statut de la réservation', enum: ['pending', 'validated', 'cancelled'] })
+  @ApiProperty({
+    description: 'Statut de la réservation',
+    enum: ['pending', 'validated', 'cancelled'],
+  })
   status: string;
 
   @ApiProperty({ description: 'Date de validation du ticket', required: false })
@@ -33,4 +36,11 @@ export class TripPassengerResponseDto {
 
   @ApiProperty({ description: 'Date de réservation' })
   reservationDate: Date;
-}
+
+  @ApiProperty({ 
+    description: 'Méthode de paiement utilisée',
+    enum: ['CARD', 'MOBILE', 'CASH'],
+    example: 'CASH'
+  })
+  paymentMethod: string;
+}

+ 1 - 0
node_app/src/application/use-cases/trajet/find-trip-passengers.use-case.ts

@@ -46,6 +46,7 @@ export class FindTripPassengersUseCase {
         validatedAt: reservation.qrcode?.scannedAt || undefined,
         qrCode: reservation.qrcode?.value || undefined,
         reservationDate: reservation.dateAt,
+        paymentMethod: reservation.paymentMethod || 'CASH',
       }),
     );
 

+ 7 - 0
node_app/src/domain/entities/reservation.entity.ts

@@ -2,6 +2,12 @@ import { Place } from './place.entity';
 import { User } from './user.entity';
 import { Trajet } from './trajet.entity';
 
+export enum PaymentMethod {
+  CARD = 'CARD',
+  MOBILE = 'MOBILE',
+  CASH = 'CASH',
+}
+
 export class Reservation {
   readonly id: number;
   dateAt: Date;
@@ -11,6 +17,7 @@ export class Reservation {
   userId: number;
   trajet: Trajet;
   trajetId: number;
+  paymentMethod: PaymentMethod;
 
   constructor(partial: Partial<Reservation>) {
     Object.assign(this, partial);

+ 40 - 1
node_app/src/infrastructure/mappers/reservation.mapper.ts

@@ -1,6 +1,6 @@
 import { Trajet } from '@prisma/client';
 import { IPrismaMapper } from './prisma-mapper.interface';
-import { Reservation } from 'src/domain/entities/reservation.entity';
+import { Reservation, PaymentMethod } from 'src/domain/entities/reservation.entity';
 import { User, UserRole } from 'src/domain/entities/user.entity';
 
 export class ReservationMapper implements IPrismaMapper<any, Reservation> {
@@ -8,6 +8,7 @@ export class ReservationMapper implements IPrismaMapper<any, Reservation> {
     return new Reservation({
       id: prismaReservation.id,
       dateAt: prismaReservation.dateAt,
+      paymentMethod: prismaReservation.paymentMethod || PaymentMethod.CASH,
 
       // On garde les IDs pour la compatibilité
       userId: prismaReservation.userId,
@@ -96,6 +97,7 @@ export class ReservationMapper implements IPrismaMapper<any, Reservation> {
     return {
       id: reservation.id,
       dateAt: reservation.dateAt,
+      paymentMethod: reservation.paymentMethod || PaymentMethod.CASH,
       userId: reservation.userId ?? null,
       placeId: reservation.placeId ?? null,
       trajetId: reservation.trajetId ?? null,
@@ -105,4 +107,41 @@ export class ReservationMapper implements IPrismaMapper<any, Reservation> {
   fromDtoToDomain(dto: unknown): Reservation {
     return new Reservation(dto as Reservation);
   }
+
+  toResponseDto(reservation: Reservation): any {
+    return {
+      id: reservation.id,
+      dateAt: reservation.dateAt,
+      paymentMethod: reservation.paymentMethod,
+      place: reservation.place ? {
+        id: reservation.place.id,
+        number: reservation.place.number,
+        isAvailable: reservation.place.isAvailable,
+        vehiculeId: reservation.place.vehiculeId,
+        occupiedAt: reservation.place.occupiedAt,
+      } : undefined,
+      user: reservation.user ? {
+        id: reservation.user.id,
+        name: reservation.user.name,
+        otherName: reservation.user.otherName,
+        email: reservation.user.email,
+        address: reservation.user.address,
+        telephone: reservation.user.telephone,
+        role: reservation.user.role,
+      } : undefined,
+      trajet: reservation.trajet ? {
+        id: reservation.trajet.id,
+        departAt: reservation.trajet.departAt,
+        arriveAt: reservation.trajet.arriveAt,
+        departStationId: reservation.trajet.departStationId,
+        arriveStationId: reservation.trajet.arriveStationId,
+        vehiculeId: reservation.trajet.vehiculeId,
+        companyId: reservation.trajet.companyId,
+        employeeId: reservation.trajet.employeeId,
+        pricePerSeat: reservation.trajet.pricePerSeat,
+        registrationClosed: reservation.trajet.registrationClosed,
+        availableSeats: reservation.trajet.availableSeats,
+      } : undefined,
+    };
+  }
 }

+ 15 - 6
node_app/src/presentation/controllers/reservation.controller.ts

@@ -34,6 +34,7 @@ import { FindQrcodeUseCase } from '../../application/use-cases/qrcode/find-qrcod
 import { Public } from '../decorators/public.decorator';
 import { Roles } from '../decorators/roles.decorator';
 import { UserRole } from 'src/domain/entities/user.entity';
+import { ReservationMapper } from '../../infrastructure/mappers/reservation.mapper';
 
 @ApiTags('reservations')
 @Controller('reservations')
@@ -47,6 +48,7 @@ export class ReservationController {
     private readonly deleteReservationUseCase: DeleteReservationUseCase,
     private readonly findReservationsByUserUseCase: FindReservationsByUserUseCase,
     private readonly findQrcodeUseCase: FindQrcodeUseCase,
+    private readonly reservationMapper: ReservationMapper,
   ) {}
 
   @Post()
@@ -60,7 +62,8 @@ export class ReservationController {
   @ApiResponse({ status: 401, description: 'Non autorisé' })
   @Public()
   async create(@Body() createReservationDto: CreateReservationDto) {
-    return this.createReservationUseCase.execute(createReservationDto);
+    const reservation = await this.createReservationUseCase.execute(createReservationDto);
+    return this.reservationMapper.toResponseDto(reservation);
   }
 
   @Get()
@@ -93,7 +96,11 @@ export class ReservationController {
   })
   @ApiResponse({ status: 401, description: 'Non autorisé' })
   async findAll(@Query('page') page?: number, @Query('limit') limit?: number) {
-    return this.findAllReservationsUseCase.execute(page || 1, limit || 10);
+    const result = await this.findAllReservationsUseCase.execute(page || 1, limit || 10);
+    return {
+      data: result.data.map(reservation => this.reservationMapper.toResponseDto(reservation)),
+      total: result.total,
+    };
   }
 
   @Get(':id')
@@ -107,7 +114,8 @@ export class ReservationController {
   @ApiResponse({ status: 404, description: 'Réservation non trouvée' })
   @ApiResponse({ status: 401, description: 'Non autorisé' })
   async findOne(@Param('id', ParseIntPipe) id: number) {
-    return this.findReservationByIdUseCase.execute(id);
+    const reservation = await this.findReservationByIdUseCase.execute(id);
+    return this.reservationMapper.toResponseDto(reservation);
   }
 
   @Put(':id')
@@ -125,7 +133,8 @@ export class ReservationController {
     @Param('id', ParseIntPipe) id: number,
     @Body() updateReservationDto: UpdateReservationDto,
   ) {
-    return this.updateReservationUseCase.execute(id, updateReservationDto);
+    const reservation = await this.updateReservationUseCase.execute(id, updateReservationDto);
+    return this.reservationMapper.toResponseDto(reservation);
   }
 
   @Delete(':id')
@@ -183,7 +192,7 @@ export class ReservationController {
     );
 
     return {
-      data: result.data,
+      data: result.data.map(reservation => this.reservationMapper.toResponseDto(reservation)),
       pagination: {
         page,
         limit,
@@ -231,7 +240,7 @@ export class ReservationController {
 
     // Retourner toutes les informations structurées
     return {
-      reservation,
+      reservation: this.reservationMapper.toResponseDto(reservation),
       trajet,
       vehicule,
       departStation,

+ 1 - 1
node_app/src/presentation/controllers/trajet.controller.ts

@@ -212,7 +212,7 @@ export class TrajetController {
     type: PaginatedResponseDto<TripPassengerResponseDto>,
   })
   @ApiResponse({ status: 404, description: 'Trajet non trouvé' })
-  @Roles(UserRole.ADMIN, UserRole.MANAGER, UserRole.EMPLOYEE)
+  @Roles(UserRole.ADMIN, UserRole.MANAGER, UserRole.EMPLOYEE, UserRole.DRIVER)
   async getTripPassengers(
     @Param('id', ParseIntPipe) id: number,
     @Query('page', new ParseIntPipe({ optional: true })) page: number = 1,