Skip to content

Commit 9d00e89

Browse files
committed
feat: Implement Hotel Booking Integration feature
✨ Features: - Hotel search by location and station code - Hotel booking with availability checking - Package deals (Train + Hotel) with 10% discount - Hotel recommendations based on booking history - Price filtering and rating-based search - Amenities filtering 🏗️ Architecture: - Hotel entity for hotel information - HotelBooking entity for reservations - HotelService with search, booking, and recommendation logic - HotelController with REST APIs - Integration with train bookings for package deals 📡 APIs: - GET /api/hotels/search - Search hotels with filters - POST /api/hotels/book - Book a hotel - GET /api/hotels/packages - Get Train + Hotel packages - GET /api/hotels/recommendations - Get personalized recommendations 🔍 Search Features: - Search by location (city/area) - Search by nearest railway station code - Filter by price range (min/max) - Filter by rating (minimum rating) - Filter by amenities (comma-separated) - Check availability for dates - Calculate total price for stay duration 🎫 Booking Features: - Validate hotel availability - Check room availability for dates - Conflict detection for overlapping bookings - Automatic price calculation - Package deal discount (10%) - Guest information capture - Special requests handling - Unique booking reference generation 📦 Package Deals: - Train + Hotel combo offers - 10% discount on combined bookings - Route-based suggestions (ORIGIN-DESTINATION) - Price comparison and savings calculation - Link hotel bookings with train bookings 🎯 Recommendations: - Analyze user's train booking history - Extract destination stations - Find highly-rated hotels (4.0+) near stations - Personalized hotel suggestions 📊 Database: - hotels table with indexes - hotel_bookings table with foreign keys - Migrations V11 and V12 - Multi-tenant support 🔄 Integration: - Kafka events: hotel-booking-created - Link with train bookings for packages - Update hotel room availability - Event publishing for notifications 🧪 Testing: - HotelServiceTest (8 tests passing) - HotelControllerTest (4 tests passing) - Comprehensive coverage of all features 📚 Documentation: - Complete implementation guide - API documentation with examples - Usage examples - Production considerations Resolves: Hotel Booking Integration feature from NEW_FEATURES_PROPOSAL.md
1 parent b71b947 commit 9d00e89

16 files changed

Lines changed: 1859 additions & 0 deletions

HOTEL_BOOKING_INTEGRATION_IMPLEMENTATION.md

Lines changed: 520 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
package com.irctc.booking.controller;
2+
3+
import com.irctc.booking.dto.*;
4+
import com.irctc.booking.service.HotelService;
5+
import org.springframework.beans.factory.annotation.Autowired;
6+
import org.springframework.format.annotation.DateTimeFormat;
7+
import org.springframework.http.ResponseEntity;
8+
import org.springframework.web.bind.annotation.*;
9+
10+
import java.math.BigDecimal;
11+
import java.time.LocalDate;
12+
import java.util.List;
13+
import java.util.Map;
14+
15+
/**
16+
* Controller for hotel booking operations
17+
*/
18+
@RestController
19+
@RequestMapping("/api/hotels")
20+
public class HotelController {
21+
22+
@Autowired
23+
private HotelService hotelService;
24+
25+
/**
26+
* GET /api/hotels/search
27+
* Search hotels by location, dates, and criteria
28+
*/
29+
@GetMapping("/search")
30+
public ResponseEntity<Map<String, Object>> searchHotels(
31+
@RequestParam(required = false) String location,
32+
@RequestParam(required = false) String stationCode,
33+
@RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate checkIn,
34+
@RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate checkOut,
35+
@RequestParam(required = false, defaultValue = "1") Integer numberOfRooms,
36+
@RequestParam(required = false, defaultValue = "1") Integer numberOfGuests,
37+
@RequestParam(required = false) BigDecimal minPrice,
38+
@RequestParam(required = false) BigDecimal maxPrice,
39+
@RequestParam(required = false) BigDecimal minRating,
40+
@RequestParam(required = false) String amenities) {
41+
42+
HotelSearchRequest request = new HotelSearchRequest();
43+
request.setLocation(location);
44+
request.setStationCode(stationCode);
45+
request.setCheckInDate(checkIn);
46+
request.setCheckOutDate(checkOut);
47+
request.setNumberOfRooms(numberOfRooms);
48+
request.setNumberOfGuests(numberOfGuests);
49+
request.setMinPrice(minPrice);
50+
request.setMaxPrice(maxPrice);
51+
request.setMinRating(minRating);
52+
request.setAmenities(amenities);
53+
54+
List<HotelSearchResponse> hotels = hotelService.searchHotels(request);
55+
56+
return ResponseEntity.ok(Map.of(
57+
"hotels", hotels,
58+
"count", hotels.size()
59+
));
60+
}
61+
62+
/**
63+
* POST /api/hotels/book
64+
* Book a hotel
65+
*/
66+
@PostMapping("/book")
67+
public ResponseEntity<HotelBookingResponse> bookHotel(@RequestBody HotelBookingRequest request) {
68+
HotelBookingResponse response = hotelService.bookHotel(request);
69+
return ResponseEntity.ok(response);
70+
}
71+
72+
/**
73+
* GET /api/hotels/packages
74+
* Get hotel packages for a route (Train + Hotel combo)
75+
*/
76+
@GetMapping("/packages")
77+
public ResponseEntity<HotelPackageResponse> getHotelPackages(@RequestParam String route) {
78+
HotelPackageResponse response = hotelService.getHotelPackages(route);
79+
return ResponseEntity.ok(response);
80+
}
81+
82+
/**
83+
* GET /api/hotels/recommendations
84+
* Get hotel recommendations based on user's booking history
85+
*/
86+
@GetMapping("/recommendations")
87+
public ResponseEntity<Map<String, Object>> getRecommendedHotels(@RequestParam Long userId) {
88+
List<HotelSearchResponse> recommendations = hotelService.getRecommendedHotels(userId);
89+
return ResponseEntity.ok(Map.of(
90+
"userId", userId,
91+
"recommendations", recommendations,
92+
"count", recommendations.size()
93+
));
94+
}
95+
}
96+
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package com.irctc.booking.dto;
2+
3+
import lombok.Data;
4+
import java.math.BigDecimal;
5+
import java.time.LocalDate;
6+
7+
/**
8+
* DTO for hotel booking request
9+
*/
10+
@Data
11+
public class HotelBookingRequest {
12+
private Long userId;
13+
private Long hotelId;
14+
private Long trainBookingId; // Optional: for package deals
15+
private LocalDate checkInDate;
16+
private LocalDate checkOutDate;
17+
private Integer numberOfRooms;
18+
private Integer numberOfGuests;
19+
private String guestName;
20+
private String guestEmail;
21+
private String guestPhone;
22+
private String specialRequests;
23+
private Boolean isPackageDeal = false;
24+
private BigDecimal discountAmount; // Discount for package deals
25+
}
26+
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package com.irctc.booking.dto;
2+
3+
import lombok.Data;
4+
import java.math.BigDecimal;
5+
import java.time.LocalDate;
6+
import java.time.LocalDateTime;
7+
8+
/**
9+
* DTO for hotel booking response
10+
*/
11+
@Data
12+
public class HotelBookingResponse {
13+
private Long id;
14+
private Long userId;
15+
private Long hotelId;
16+
private Long trainBookingId;
17+
private String bookingReference;
18+
private LocalDate checkInDate;
19+
private LocalDate checkOutDate;
20+
private Integer numberOfRooms;
21+
private Integer numberOfGuests;
22+
private String guestName;
23+
private String guestEmail;
24+
private String guestPhone;
25+
private BigDecimal totalAmount;
26+
private BigDecimal discountAmount;
27+
private BigDecimal finalAmount;
28+
private String status;
29+
private String paymentStatus;
30+
private Boolean isPackageDeal;
31+
private LocalDateTime confirmedAt;
32+
private LocalDateTime createdAt;
33+
}
34+
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package com.irctc.booking.dto;
2+
3+
import lombok.Data;
4+
import java.math.BigDecimal;
5+
import java.util.List;
6+
7+
/**
8+
* DTO for hotel package deals (Train + Hotel)
9+
*/
10+
@Data
11+
public class HotelPackageResponse {
12+
private String route; // Train route
13+
private String originStation;
14+
private String destinationStation;
15+
private List<HotelPackage> packages;
16+
17+
@Data
18+
public static class HotelPackage {
19+
private Long hotelId;
20+
private String hotelName;
21+
private String location;
22+
private BigDecimal hotelPricePerNight;
23+
private BigDecimal trainFare;
24+
private BigDecimal packagePrice; // Combined price
25+
private BigDecimal discountAmount; // Discount for package
26+
private BigDecimal finalPrice; // Final price after discount
27+
private BigDecimal savings; // Amount saved with package
28+
private Integer nights;
29+
private String description;
30+
}
31+
}
32+
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package com.irctc.booking.dto;
2+
3+
import lombok.Data;
4+
import java.math.BigDecimal;
5+
import java.time.LocalDate;
6+
7+
/**
8+
* DTO for hotel search request
9+
*/
10+
@Data
11+
public class HotelSearchRequest {
12+
private String location; // City or area name
13+
private String stationCode; // Nearest railway station code
14+
private LocalDate checkInDate;
15+
private LocalDate checkOutDate;
16+
private Integer numberOfRooms = 1;
17+
private Integer numberOfGuests = 1;
18+
private BigDecimal minPrice;
19+
private BigDecimal maxPrice;
20+
private BigDecimal minRating; // Minimum rating (1.0 to 5.0)
21+
private String amenities; // Comma-separated amenities filter
22+
}
23+
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package com.irctc.booking.dto;
2+
3+
import lombok.Data;
4+
import java.math.BigDecimal;
5+
import java.util.List;
6+
7+
/**
8+
* DTO for hotel search response
9+
*/
10+
@Data
11+
public class HotelSearchResponse {
12+
private Long id;
13+
private String name;
14+
private String location;
15+
private String nearestStationCode;
16+
private String address;
17+
private String city;
18+
private String state;
19+
private BigDecimal rating;
20+
private BigDecimal pricePerNight;
21+
private Integer availableRooms;
22+
private String amenities;
23+
private String description;
24+
private String imageUrl;
25+
private BigDecimal totalPrice; // Total price for the stay duration
26+
private Integer nights; // Number of nights
27+
}
28+
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
package com.irctc.booking.entity;
2+
3+
import com.irctc.booking.tenant.TenantAware;
4+
import jakarta.persistence.*;
5+
import lombok.Data;
6+
import java.math.BigDecimal;
7+
import java.time.LocalDateTime;
8+
import java.util.List;
9+
10+
/**
11+
* Entity for hotel information
12+
*/
13+
@Entity
14+
@Table(
15+
name = "hotels",
16+
indexes = {
17+
@Index(name = "idx_hotels_location", columnList = "location"),
18+
@Index(name = "idx_hotels_station_code", columnList = "nearestStationCode"),
19+
@Index(name = "idx_hotels_rating", columnList = "rating"),
20+
@Index(name = "idx_hotels_tenant_id", columnList = "tenantId")
21+
}
22+
)
23+
@Data
24+
public class Hotel implements TenantAware {
25+
@Id
26+
@GeneratedValue(strategy = GenerationType.IDENTITY)
27+
private Long id;
28+
29+
@Column(nullable = false)
30+
private String name;
31+
32+
@Column(nullable = false)
33+
private String location; // City or area name
34+
35+
@Column(name = "nearest_station_code", length = 10)
36+
private String nearestStationCode; // Nearest railway station code
37+
38+
@Column(length = 500)
39+
private String address;
40+
41+
@Column(length = 20)
42+
private String city;
43+
44+
@Column(length = 20)
45+
private String state;
46+
47+
@Column(length = 10)
48+
private String pincode;
49+
50+
@Column(length = 15)
51+
private String phone;
52+
53+
@Column(length = 100)
54+
private String email;
55+
56+
@Column(precision = 3, scale = 1)
57+
private BigDecimal rating; // 1.0 to 5.0
58+
59+
@Column(name = "price_per_night", precision = 10, scale = 2)
60+
private BigDecimal pricePerNight;
61+
62+
@Column(name = "total_rooms")
63+
private Integer totalRooms;
64+
65+
@Column(name = "available_rooms")
66+
private Integer availableRooms;
67+
68+
@Column(length = 500)
69+
private String amenities; // Comma-separated amenities
70+
71+
@Column(length = 1000)
72+
private String description;
73+
74+
@Column(name = "image_url", length = 500)
75+
private String imageUrl;
76+
77+
@Column(name = "is_active")
78+
private Boolean isActive = true;
79+
80+
@Column(name = "partner_hotel_id", length = 100)
81+
private String partnerHotelId; // ID from external hotel partner
82+
83+
@Column(name = "tenant_id", length = 50)
84+
private String tenantId;
85+
86+
private LocalDateTime createdAt;
87+
private LocalDateTime updatedAt;
88+
89+
@PrePersist
90+
protected void onCreate() {
91+
createdAt = LocalDateTime.now();
92+
updatedAt = LocalDateTime.now();
93+
}
94+
95+
@PreUpdate
96+
protected void onUpdate() {
97+
updatedAt = LocalDateTime.now();
98+
}
99+
}
100+

0 commit comments

Comments
 (0)