diff --git a/.gitignore b/.gitignore
index 6ac465db..244268f9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,3 @@
target/
/.idea/
+.env
diff --git a/README.md b/README.md
index 5150e50f..2ab82cb2 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,4 @@
+[](https://classroom.github.com/a/_uV8Mn8f)
# 📘 Projektarbete: JPA + Hibernate med GitHub-flöde
Projektet genomförs som antingen en Java CLI-applikation eller med hjälp av JavaFX om ni vill ha ett grafiskt gränssnitt.
@@ -9,11 +10,11 @@ Kommunikation med databasen ska ske med JPA och Hibernate, enligt code first-met
## 🗓️ Veckoplanering med Checklista
### ✅ Vecka 1 – Grundläggning och struktur
-- [ ] Klona GitHub-repo
-- [ ] Konfigurera persistence.xml eller använd PersistenceConfiguration i kod
-- [ ] Skapa entiteter och verifiera tabellgenerering
-- [ ] Lägg till relationer (One-to-Many, Many-to-Many)
-- [ ] Arbeta på feature-branches och använd pull requests för kodgranskning
+- [x] Klona GitHub-repo
+- [x] Konfigurera persistence.xml eller använd PersistenceConfiguration i kod
+- [x] Skapa entiteter och verifiera tabellgenerering
+- [x] Lägg till relationer (One-to-Many, Many-to-Many)
+- [x] Arbeta på feature-branches och använd pull requests för kodgranskning
### ✅ Vecka 2 – Funktionalitet och relationer
- [ ] Dela upp funktioner mellan gruppmedlemmar
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 00000000..afd5779f
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,17 @@
+version: '3.8'
+
+services:
+ mysql:
+ image: mysql:9.5.0
+ container_name: restaurant_booking_db
+ environment:
+ MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
+ MYSQL_DATABASE: ${MYSQL_DATABASE}
+ ports:
+ - "3306:3306"
+ volumes:
+ - mysql_data:/var/lib/mysql
+ restart: unless-stopped
+
+volumes:
+ mysql_data:
diff --git a/pom.xml b/pom.xml
index 909503d0..20c6f873 100644
--- a/pom.xml
+++ b/pom.xml
@@ -16,6 +16,16 @@
5.21.0
+
+ com.zaxxer
+ HikariCP
+ 7.0.2
+
+
+ org.hibernate.orm
+ hibernate-hikaricp
+ 7.2.0.Final
+
org.junit.jupiter
junit-jupiter
@@ -50,5 +60,10 @@
classgraph
4.8.184
+
+ jakarta.persistence
+ jakarta.persistence-api
+ 3.2.0
+
diff --git a/src/main/java/org/example/App.java b/src/main/java/org/example/App.java
index 165e5cd5..6e0a41dd 100644
--- a/src/main/java/org/example/App.java
+++ b/src/main/java/org/example/App.java
@@ -1,7 +1,347 @@
package org.example;
+import io.github.classgraph.ClassGraph;
+import io.github.classgraph.ScanResult;
+import jakarta.persistence.*;
+import org.example.entity.*;
+import org.example.entity.Table;
+import org.example.entity.service.BookingService;
+import org.hibernate.jpa.HibernatePersistenceConfiguration;
+
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
+import java.util.List;
+
public class App {
public static void main(String[] args) {
- System.out.println("Hello There!");
+
+ List> entities = getEntities("org.example.entity");
+
+ final PersistenceConfiguration cfg = new HibernatePersistenceConfiguration("emf")
+ .jdbcUrl("jdbc:mysql://localhost:3306/restaurant_booking")
+ .jdbcUsername(System.getenv("MYSQL_USER"))
+ .jdbcPassword(System.getenv("MYSQL_ROOT_PASSWORD"))
+ .property("hibernate.connection.provider_class", "org.hibernate.hikaricp.internal.HikariCPConnectionProvider")
+ .property("hibernate.hikari.maximumPoolSize", "10")
+ .property("hibernate.hikari.minimumIdle", "5")
+ .property("hibernate.hikari.idleTimeout", "300000")
+ .property("hibernate.hikari.connectionTimeout", "20000")
+ .property("hibernate.hbm2ddl.auto", "update")
+ .property("hibernate.show_sql", "true")
+ .property("hibernate.format_sql", "true")
+ .property("hibernate.highlight_sql", "true")
+ .managedClasses(entities);
+
+ try (EntityManagerFactory emf = cfg.createEntityManagerFactory()) {
+
+ // Skapa initial data om den inte finns
+ createInitialData(emf);
+
+ // Starta huvudmeny
+ BookingService bookingService = new BookingService(emf);
+ mainMenu(bookingService, emf);
+
+ }
+ }
+
+ private static void createInitialData(EntityManagerFactory emf) {
+ // Kolla om data redan finns
+ Long count = emf.callInTransaction(em ->
+ em.createQuery("SELECT COUNT(ts) FROM TimeSlot ts", Long.class).getSingleResult()
+ );
+
+ if (count == 0) {
+ hours(emf);
+ createGuest(emf);
+ System.out.println("Initial data created!");
+ System.out.println("Create tables manually in the database if necessary!");
+ }
+ }
+
+ private static void createGuest(EntityManagerFactory emf) {
+ emf.runInTransaction(em -> {
+ em.persist(new Guest("Gabriela", "Bord för fyra", "072762668"));
+ em.persist(new Guest("Samuel", "Bord för 3", "072778882"));
+ em.persist(new Guest("Anna", "VIP", "0701234567"));
+ em.persist(new Guest("Erik", "Allergisk mot nötter", "0709876543"));
+ });
+ }
+
+ private static void hours(EntityManagerFactory emf) {
+ emf.runInTransaction(em -> {
+ String[] times = {"16:00", "16:30", "17:00", "17:30", "18:00", "18:30", "19:00"};
+ for (String start : times) {
+ String[] parts = start.split(":");
+ int hour = Integer.parseInt(parts[0]) + 2;
+ String end = hour + ":" + parts[1];
+ em.persist(new TimeSlot(start, end));
+ }
+ });
+ }
+
+ private static List> getEntities(String pkg) {
+ try (ScanResult scanResult = new ClassGraph()
+ .enableClassInfo()
+ .enableAnnotationInfo()
+ .acceptPackages(pkg)
+ .scan()) {
+ return scanResult.getClassesWithAnnotation(Entity.class).loadClasses();
+ }
+ }
+
+ public static void mainMenu(BookingService bookingService, EntityManagerFactory emf) {
+ boolean running = true;
+ while (running) {
+ String menu = """
+
+ ╔════════════════════════════════════╗
+ ║ RESTAURANT BOOKING SYSTEM ║
+ ╠════════════════════════════════════╣
+ ║ 1. CREATE BOOKING ║
+ ║ 2. UPDATE BOOKING ║
+ ║ 3. VIEW ALL BOOKINGS ║
+ ║ 4. DELETE BOOKING ║
+ ║ 5. VIEW TABLES ║
+ ║ 6. VIEW GUESTS ║
+ ║ 7. EXIT ║
+ ╚════════════════════════════════════╝
+ """;
+
+ String select = IO.readln(menu + "\nSelect option: ").toLowerCase();
+
+ switch (select) {
+ case "create booking", "cb", "1" -> createBookingMenu(bookingService);
+ case "update booking", "ub", "2" -> updateBookingMenu(bookingService);
+ case "view all bookings", "rb", "3" -> showBookings(bookingService);
+ case "delete booking", "db", "4" -> deleteBookingMenu(bookingService);
+ case "view tables", "5" -> viewTables(bookingService);
+ case "view guests", "6" -> viewGuests(bookingService);
+ case "exit", "7" -> {
+ System.out.println("Goodbye!");
+ running = false;
+ }
+ default -> System.out.println("Invalid option!");
+ }
+ }
+ }
+
+ private static void createBookingMenu(BookingService bookingService) {
+ System.out.println("\n═══ CREATE NEW BOOKING ═══");
+
+ try {
+ // Visa tillgängliga bord
+ List tables = bookingService.getAllTables();
+ System.out.println("\n📋 Available Tables:");
+ tables.forEach(t -> System.out.println(" " + t.getId() + ". Table " + t.getTableNumber() + " (Capacity: " + t.getCapacity() + ")"));
+
+ Long tableId = Long.parseLong(IO.readln("\nEnter Table ID: "));
+
+ // Visa tillgängliga tider
+ List timeSlots = bookingService.getAllTimeSlots();
+ System.out.println("\n⏰ Available Time Slots:");
+ timeSlots.forEach(ts -> System.out.println(" " + ts.getId() + ". " + ts.getStartTime() + " - " + ts.getFinishTime()));
+
+ Long timeSlotId = Long.parseLong(IO.readln("\nEnter TimeSlot ID: "));
+
+ // Datum med validering
+ LocalDate date = null;
+ while (date == null) {
+ String dateStr = IO.readln("\nEnter date (YYYY-MM-DD): ");
+ try {
+ date = LocalDate.parse(dateStr, DateTimeFormatter.ISO_LOCAL_DATE);
+
+ // Validera att datumet är korrekt
+ LocalDate today = LocalDate.now();
+ // hur många månader fram man kan boka(kan ä
+ LocalDate maxDate = today.plusMonths(3);
+
+ if (date.isBefore(today)) {
+ System.out.println("Date cannot be in the past! Please enter a future date.");
+ date = null;
+ } else if (date.isAfter(maxDate)) {
+ System.out.println("Date cannot be more than 3 months in the future! (Max: " + maxDate + ")");
+ date = null;
+ }
+ } catch (Exception e) {
+ System.out.println("Invalid date format! Please use YYYY-MM-DD");
+ }
+ }
+
+ // Antal gäster
+ int partySize = Integer.parseInt(IO.readln("\nEnter party size: "));
+
+ // Lägg till gäster
+ List guestIds = new ArrayList<>();
+ String addMore = "y";
+
+ while (addMore.equalsIgnoreCase("y")) {
+ System.out.println("\n👥 ADD GUEST:");
+ System.out.println("1. Select existing guest");
+ System.out.println("2. Create new guest");
+
+ String guestChoice = IO.readln("Choose option (1 or 2): ").trim();
+
+ if (guestChoice.equals("1")) {
+ // Välj befintlig gäst
+ List guests = bookingService.getAllGuests();
+ System.out.println("\n📋 Available Guests:");
+ guests.forEach(g -> System.out.println(" " + g.getId() + ". " + g.getName() + " (" + g.getContact() + ")"));
+
+ Long guestId = Long.parseLong(IO.readln("\nEnter Guest ID: "));
+ guestIds.add(guestId);
+
+ } else if (guestChoice.equals("2")) {
+ // Skapa ny gäst
+ System.out.println("\n═══ CREATE NEW GUEST ═══");
+ String name = IO.readln("Enter guest name: ");
+ String contact = IO.readln("Enter contact (phone/email): ");
+ String note = IO.readln("Enter note (allergies, preferences, etc.): ");
+
+ try {
+ Long newGuestId = bookingService.createGuest(name, note, contact);
+ guestIds.add(newGuestId);
+ System.out.println("Guest created successfully!");
+ } catch (Exception e) {
+ System.out.println("Error creating guest: " + e.getMessage());
+ }
+ } else {
+ System.out.println("Invalid option! Please enter 1 or 2.");
+ continue;
+ }
+
+ addMore = IO.readln("\nAdd another guest? (y/n): ").trim();
+ }
+
+ // Skapa bokning med validering
+ try {
+ bookingService.createBooking(tableId, timeSlotId, date, partySize, guestIds);
+ } catch (IllegalArgumentException e) {
+ System.out.println("Booking failed: " + e.getMessage());
+ }
+
+ } catch (NumberFormatException e) {
+ System.out.println("Invalid input! Please enter valid numbers.");
+ } catch (Exception e) {
+ System.out.println("Error: " + e.getMessage());
+ }
+ }
+
+ private static void updateBookingMenu(BookingService bookingService) {
+ System.out.println("\n═══ UPDATE BOOKING ═══");
+
+ List bookings = bookingService.getAllBookings();
+
+ if (bookings.isEmpty()) {
+ System.out.println("No bookings found to update.");
+ return;
+ }
+
+ // visar alla bokningar
+ showBookings(bookings);
+
+ try {
+ Long bookingId = Long.parseLong(IO.readln("\nEnter Booking ID to update: "));
+
+ String statusMenu = """
+
+ Select new status:
+ 1. PENDING
+ 2. CONFIRMED
+ 3. CANCELLED
+ 4. COMPLETED
+ 5. NO_SHOW
+ """;
+
+ String choice = IO.readln(statusMenu + "\nEnter choice: ");
+
+ BookingStatus newStatus = switch (choice) {
+ case "1" -> BookingStatus.PENDING;
+ case "2" -> BookingStatus.CONFIRMED;
+ case "3" -> BookingStatus.CANCELLED;
+ case "4" -> BookingStatus.COMPLETED;
+ case "5" -> BookingStatus.NO_SHOW;
+ default -> null;
+ };
+
+ if (newStatus != null) {
+ try {
+ bookingService.updateBookingStatus(bookingId, newStatus);
+ } catch (Exception e) {
+ System.out.println("Error: " + e.getMessage());
+ }
+ } else {
+ System.out.println("Invalid status!");
+ }
+ } catch (NumberFormatException e) {
+ System.out.println("Invalid ID format!");
+ }
+ }
+
+
+
+ private static void deleteBookingMenu(BookingService bookingService) {
+ System.out.println("\n═══ DELETE BOOKING ═══");
+
+ List bookings = bookingService.getAllBookings();
+
+ if (bookings.isEmpty()) {
+ System.out.println("No bookings found to delete.");
+ return;
+ }
+ //Visar alla bokningar - metod längre ner
+ showBookings(bookings);
+
+ try {
+ Long bookingId = Long.parseLong(IO.readln("\nEnter Booking ID to delete: "));
+ String confirm = IO.readln("Are you sure? (y/n): ");
+
+ if (confirm.equalsIgnoreCase("y")) {
+ bookingService.deleteBooking(bookingId);
+ } else {
+ System.out.println("Deletion cancelled.");
+ }
+ } catch (NumberFormatException e) {
+ System.out.println("Invalid ID format!");
+ }
+ }
+
+ private static void showBookings(BookingService bookingService) {
+ System.out.println("\n═══ ALL BOOKINGS ═══");
+
+ List bookings = bookingService.getAllBookings();
+
+ if (bookings.isEmpty()) {
+ System.out.println("📭 No bookings found.");
+ return;
+ }
+
+ showBookings(bookings);
+ }
+
+ private static void showBookings(List bookings) {
+ bookings.forEach(b -> {
+ System.out.println("\n📅 Booking ID: " + b.getId());
+ System.out.println(" Date: " + b.getDate());
+ System.out.println(" Time: " + b.getTimeSlot().getStartTime() + " - " + b.getTimeSlot().getFinishTime());
+ System.out.println(" Table: " + b.getTable().getTableNumber());
+ System.out.println(" Party Size: " + b.getParty());
+ System.out.println(" Status: " + b.getStatus());
+ System.out.println(" Guests: " + b.getGuests().stream().map(Guest::getName).toList());
+ });
+ }
+
+ private static void viewTables(BookingService bookingService) {
+ System.out.println("\n═══ ALL TABLES ═══");
+ bookingService.getAllTables().forEach(t ->
+ System.out.println("Table " + t.getTableNumber() + " - Capacity: " + t.getCapacity())
+ );
+ }
+
+ private static void viewGuests(BookingService bookingService) {
+ System.out.println("\n═══ ALL GUESTS ═══");
+ bookingService.getAllGuests().forEach(g ->
+ System.out.println(g.getName() + " - " + g.getContact() + " (" + g.getNote() + ")")
+ );
}
}
diff --git a/src/main/java/org/example/entity/Booking.java b/src/main/java/org/example/entity/Booking.java
new file mode 100644
index 00000000..84df01e9
--- /dev/null
+++ b/src/main/java/org/example/entity/Booking.java
@@ -0,0 +1,128 @@
+package org.example.entity;
+
+import jakarta.persistence.*;
+
+import java.time.LocalDate;
+import java.util.ArrayList;
+import java.util.List;
+
+@Entity
+@jakarta.persistence.Table(name="Bookings")
+
+public class Booking {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+
+ @Column(name="booking_date", nullable = false)
+ private LocalDate date; //day of the reservation
+
+ @ManyToOne
+ @JoinColumn(name="timeslot_id", nullable = false)
+ private TimeSlot timeSlot; //time selected from the determited times
+
+ @Column(name="party_size", nullable = false)
+ private int partySize;
+
+ @Enumerated(EnumType.STRING)
+ @Column(name = "status", nullable = false)
+ private BookingStatus status = BookingStatus.PENDING;
+
+ @ManyToOne
+ @JoinColumn(name="table_id", nullable = false)
+ private Table table;
+
+ @ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
+ @JoinTable(
+ name = "booking_guests",
+ joinColumns = @JoinColumn(name = "booking_id"),
+ inverseJoinColumns = @JoinColumn (name = "guest_id"))
+ private List guests = new ArrayList<>();
+
+ public void addGuest(Guest guest){
+ guests.add(guest);
+ guest.getBookings().add(this);
+ }
+
+ public void removeGuest(Guest guest){
+ guests.remove(guest);
+ guest.getBookings().remove(this);
+ }
+
+ public BookingStatus getStatus() {
+ return status;
+ }
+ public void confirmBooking(){
+ this.status = BookingStatus.CONFIRMED;
+ }
+ public void cancelBooking(){
+ this.status = BookingStatus.CANCELLED;
+ }
+ public void completeBooking(){
+ this.status = BookingStatus.COMPLETED;
+ }
+ public void pendingBooking() { this.status = BookingStatus.PENDING; }
+ public void noShowBooking(){
+ this.status = BookingStatus.NO_SHOW;
+ }
+ public Booking() {}
+
+ public LocalDate getDate() {
+ return date;
+ }
+
+ public void setDate(LocalDate date) {
+ this.date = date;
+ }
+
+ public TimeSlot getTimeSlot() {
+ return timeSlot;
+ }
+
+ public void setTimeSlot(TimeSlot timeSlot) {
+ this.timeSlot = timeSlot;
+ }
+
+ public int getParty() {
+ return partySize;
+ }
+
+ public void setParty(int partySize) {
+ this.partySize = partySize;
+ }
+
+ public Table getTable() {
+ return table;
+ }
+
+ public void setTable(Table table) {
+ this.table = table;
+ }
+
+ public List getGuests() {
+ return guests;
+ }
+
+ public void setGuests(List guests) {
+ this.guests = guests;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public Long getId() {
+ return id;
+ }
+
+ @Override
+ public String toString() {
+ return "Booking{" +
+ "id=" + id +
+ ", date=" + date +
+ ", party=" + partySize +
+ ", status=" + status +
+ '}';
+ }
+}
diff --git a/src/main/java/org/example/entity/BookingStatus.java b/src/main/java/org/example/entity/BookingStatus.java
new file mode 100644
index 00000000..8bf2935c
--- /dev/null
+++ b/src/main/java/org/example/entity/BookingStatus.java
@@ -0,0 +1,10 @@
+package org.example.entity;
+
+public enum BookingStatus {
+ PENDING,
+ CONFIRMED,
+ CANCELLED,
+ COMPLETED,
+ NO_SHOW
+}
+// Status system
diff --git a/src/main/java/org/example/entity/Guest.java b/src/main/java/org/example/entity/Guest.java
new file mode 100644
index 00000000..804b5c61
--- /dev/null
+++ b/src/main/java/org/example/entity/Guest.java
@@ -0,0 +1,78 @@
+package org.example.entity;
+
+import jakarta.persistence.*;
+
+import java.time.LocalDateTime;
+import java.util.ArrayList;
+import java.util.List;
+
+@Entity
+@jakarta.persistence.Table(name = "Guests")
+
+public class Guest {
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+
+ @Column(name="Name", nullable = false)
+ private String name;
+
+ @Column(name="Note", nullable = false)
+ private String note;
+
+ @Column(name="Contact_info", nullable = false)
+ private String contact;
+
+ @ManyToMany(mappedBy = "guests")
+ private List bookings = new ArrayList<>();
+
+ public Guest(String name, String note, String contact){
+ this.name = name;
+ this.note = note;
+ this.contact = contact;
+ }
+
+ public Guest(){}
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+
+ public String getNote() {
+ return note;
+ }
+
+ public void setNote(String note) {
+ this.note = note;
+ }
+
+ public String getContact() {
+ return contact;
+ }
+
+ public void setContact(String contact) {
+ this.contact = contact;
+ }
+
+ public List getBookings() {
+ return bookings;
+ }
+
+ public void setBookings(List bookings) {
+ this.bookings = bookings;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public Long getId() {
+ return id;
+ }
+
+}
diff --git a/src/main/java/org/example/entity/Table.java b/src/main/java/org/example/entity/Table.java
new file mode 100644
index 00000000..8fe91186
--- /dev/null
+++ b/src/main/java/org/example/entity/Table.java
@@ -0,0 +1,69 @@
+package org.example.entity;
+
+import jakarta.persistence.*;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+
+@Entity
+@jakarta.persistence.Table(name = "Tables")
+
+public class Table {
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+
+ @OneToMany(mappedBy = "table", cascade = CascadeType.PERSIST)
+ private List bookings = new ArrayList<>();
+
+
+ @Column(name="table_number", nullable = false, unique = true)
+ private String tableNumber;
+
+ @Column(name="capacity", nullable = false)
+ private int capacity;
+
+
+ public Table(int capacity, String tableNumber){
+ this.capacity = capacity;
+ this.tableNumber = tableNumber;
+
+ }
+
+ public List getBookings() {
+ return bookings;
+ }
+
+ public void setBookings(List bookings) {
+ this.bookings = bookings;
+ }
+
+ public String getTableNumber() {
+ return tableNumber;
+ }
+
+ public void setTableNumber(String tableNumber) {
+ this.tableNumber = tableNumber;
+ }
+
+ public int getCapacity() {
+ return capacity;
+ }
+
+ public void setCapacity(int capacity) {
+ this.capacity = capacity;
+ }
+
+ public Table() {}
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public Long getId() {
+ return id;
+ }
+
+}
diff --git a/src/main/java/org/example/entity/TimeSlot.java b/src/main/java/org/example/entity/TimeSlot.java
new file mode 100644
index 00000000..c9f96cd7
--- /dev/null
+++ b/src/main/java/org/example/entity/TimeSlot.java
@@ -0,0 +1,59 @@
+package org.example.entity;
+
+import jakarta.persistence.*;
+
+@Entity
+public class TimeSlot {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+
+ @Column(nullable = false, unique = true)
+ private String startTime;
+
+ @Column(nullable = false, unique = true)
+ private String finishTime;
+
+ public TimeSlot(String startTime, String finishTime) {
+ this.startTime = startTime;
+ this.finishTime = finishTime;
+ }
+
+ public TimeSlot() {
+ }
+
+
+ public Long getId() {
+ return id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public String getStartTime() {
+ return startTime;
+ }
+
+ public void setStartTime(String startTime) {
+ this.startTime = startTime;
+ }
+
+ public String getFinishTime() {
+ return finishTime;
+ }
+
+ public void setFinishTime(String finishTime) {
+ this.finishTime = finishTime;
+ }
+
+ @Override
+ public String toString() {
+ return "TimeSlot{" +
+ "id=" + id +
+ ", startTime='" + startTime + '\'' +
+ ", finishTime='" + finishTime + '\'' +
+ '}';
+ }
+}
diff --git a/src/main/java/org/example/entity/service/BookingService.java b/src/main/java/org/example/entity/service/BookingService.java
new file mode 100644
index 00000000..7468b542
--- /dev/null
+++ b/src/main/java/org/example/entity/service/BookingService.java
@@ -0,0 +1,185 @@
+package org.example.entity.service;
+
+import jakarta.persistence.EntityManagerFactory;
+import org.example.entity.*;
+
+import java.time.LocalDate;
+import java.util.List;
+
+public class BookingService {
+
+ private final EntityManagerFactory emf;
+
+ public BookingService(EntityManagerFactory emf) {
+ this.emf = emf;
+ }
+
+ // Skapa gäst
+ public Long createGuest(String name, String note, String contact) {
+ return emf.callInTransaction(em -> {
+ Guest guest = new Guest(name, note, contact);
+ em.persist(guest);
+ em.flush();
+ return guest.getId();
+ });
+ }
+
+ // Skapa bokning MED validering
+ public void createBooking(Long tableId, Long timeSlotId, LocalDate date, int partySize, List guestIds) {
+ emf.runInTransaction(em -> {
+ // 1. Hämta bord
+ Table table = em.find(Table.class, tableId);
+ if (table == null) {
+ throw new IllegalArgumentException("Table not found!");
+ }
+
+ // 2. Hämta tidslucka
+ TimeSlot timeSlot = em.find(TimeSlot.class, timeSlotId);
+ if (timeSlot == null) {
+ throw new IllegalArgumentException("TimeSlot not found!");
+ }
+
+ // 3. VALIDERA KAPACITET
+ if (partySize > table.getCapacity()) {
+ throw new IllegalArgumentException(
+ "Party size (" + partySize + ") exceeds table capacity (" + table.getCapacity() + ")!"
+ );
+ }
+
+ if (partySize < 1) {
+ throw new IllegalArgumentException("Party size must be at least 1!");
+ }
+
+ // 4. VALIDERA DATUM
+ LocalDate today = LocalDate.now();
+ LocalDate maxDate = today.plusMonths(3);
+
+ if (date.isBefore(today)) {
+ throw new IllegalArgumentException("Cannot book a date in the past!");
+ }
+
+ if (date.isAfter(maxDate)) {
+ throw new IllegalArgumentException("Cannot book more than 3 months in advance!");
+ }
+
+ // 5. VALIDERA ATT BORDET INTE ÄR BOKAT FÖR SAMMA TID/DATUM
+ Long existingBookings = em.createQuery(
+ "SELECT COUNT(b) FROM Booking b " +
+ "WHERE b.table.id = :tableId " +
+ "AND b.date = :date " +
+ "AND b.timeSlot.id = :timeSlotId " +
+ "AND b.status != :cancelledStatus",
+ Long.class
+ )
+ .setParameter("tableId", tableId)
+ .setParameter("date", date)
+ .setParameter("timeSlotId", timeSlotId)
+ .setParameter("cancelledStatus", BookingStatus.CANCELLED)
+ .getSingleResult();
+
+ if (existingBookings > 0) {
+ throw new IllegalArgumentException(
+ "Table " + table.getTableNumber() +
+ " is already booked for " + date +
+ " at " + timeSlot.getStartTime() + "!"
+ );
+ }
+
+ // 6. Validera att minst en gäst finns
+ if (guestIds == null || guestIds.isEmpty()) {
+ throw new IllegalArgumentException("Booking must have at least one guest!");
+ }
+
+ // 7. Skapa bokning
+ Booking booking = new Booking();
+ booking.setDate(date);
+ booking.setTimeSlot(timeSlot);
+ booking.setParty(partySize);
+ booking.setTable(table);
+
+ // 8. Lägg till gäster
+ for (Long guestId : guestIds) {
+ Guest guest = em.find(Guest.class, guestId);
+ if (guest != null) {
+ booking.addGuest(guest);
+ } else {
+ throw new IllegalArgumentException("Guest with ID " + guestId + " not found!");
+ }
+ }
+
+ em.persist(booking);
+ System.out.println("Booking created successfully!");
+ });
+ }
+
+ public List getAllTables() {
+ return emf.callInTransaction(em ->
+ em.createQuery("SELECT t FROM Table t", Table.class).getResultList()
+ );
+ }
+
+ public List getAllTimeSlots() {
+ return emf.callInTransaction(em ->
+ em.createQuery("SELECT ts FROM TimeSlot ts", TimeSlot.class).getResultList()
+ );
+ }
+
+ public List getAllGuests() {
+ return emf.callInTransaction(em ->
+ em.createQuery("SELECT g FROM Guest g", Guest.class).getResultList()
+ );
+ }
+
+ public List getAllBookings() {
+ return emf.callInTransaction(em ->
+ em.createQuery(
+ "SELECT DISTINCT b FROM Booking b " +
+ "LEFT JOIN FETCH b.guests " +
+ "LEFT JOIN FETCH b.table " +
+ "LEFT JOIN FETCH b.timeSlot",
+ Booking.class
+ ).getResultList()
+ );
+ }
+
+ public void updateBookingStatus(Long bookingId, BookingStatus newStatus) {
+ emf.runInTransaction(em -> {
+ Booking booking = em.find(Booking.class, bookingId);
+ if (booking == null) {
+ System.out.println("Booking with ID " + bookingId + " not found!");
+ return;
+ }
+
+ switch (newStatus) {
+ case CONFIRMED -> booking.confirmBooking();
+ case CANCELLED -> booking.cancelBooking();
+ case COMPLETED -> booking.completeBooking();
+ case PENDING -> booking.pendingBooking();
+ case NO_SHOW -> booking.noShowBooking();
+ }
+
+ System.out.println("Booking status updated to: " + newStatus);
+ });
+ }
+
+ public void deleteBooking(Long bookingId) {
+ emf.runInTransaction(em -> {
+ try {
+ Booking booking = em.createQuery(
+ "SELECT b FROM Booking b " +
+ "LEFT JOIN FETCH b.guests " +
+ "WHERE b.id = :id",
+ Booking.class
+ )
+ .setParameter("id", bookingId)
+ .getSingleResult();
+
+ em.remove(booking);
+ System.out.println("Booking deleted successfully!");
+
+ } catch (jakarta.persistence.NoResultException e) {
+ System.out.println("Booking with ID " + bookingId + " not found!");
+ }
+ });
+ }
+}
diff --git a/src/main/resources/META-INF/persistence.xml b/src/main/resources/META-INF/persistence.xml
new file mode 100644
index 00000000..df99ab67
--- /dev/null
+++ b/src/main/resources/META-INF/persistence.xml
@@ -0,0 +1,29 @@
+
+
+
+ org.example.entity.Table
+ org.example.entity.Booking
+ org.example.entity.Guest
+ org.example.entity.TimeSlot
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+