diff --git a/build.gradle b/build.gradle index 5217192..71b80e6 100644 --- a/build.gradle +++ b/build.gradle @@ -55,6 +55,7 @@ dependencies { compile group: 'org.mindrot', name: 'jbcrypt', version: '0.4' compile 'com.github.v-ladynev:fluent-hibernate-core:0.3.1' compile group: 'com.h2database', name: 'h2', version: '1.4.196' + compile group: 'org.passay', name: 'passay', version: '1.3.0' } // This task builds a jar that will make our project distributable. diff --git a/src/main/java/com/gitrekt/resort/DatabaseTestDataLoader.java b/src/main/java/com/gitrekt/resort/DatabaseTestDataLoader.java index 5b548e3..aad4c0f 100644 --- a/src/main/java/com/gitrekt/resort/DatabaseTestDataLoader.java +++ b/src/main/java/com/gitrekt/resort/DatabaseTestDataLoader.java @@ -7,123 +7,130 @@ import com.gitrekt.resort.model.entities.Booking; import com.gitrekt.resort.model.entities.Employee; import com.gitrekt.resort.model.entities.Guest; -import com.gitrekt.resort.model.entities.Room; -import com.gitrekt.resort.model.entities.RoomCategory; import com.gitrekt.resort.model.entities.GuestFeedback; import com.gitrekt.resort.model.entities.MailingAddress; import com.gitrekt.resort.model.entities.Package; +import com.gitrekt.resort.model.entities.Room; +import com.gitrekt.resort.model.entities.RoomCategory; import com.gitrekt.resort.model.services.GuestFeedbackService; -import java.util.ArrayList; +import com.gitrekt.resort.model.services.PackageService; +import com.gitrekt.resort.model.services.RoomService; import java.util.Calendar; import java.util.Date; import java.util.GregorianCalendar; import java.util.List; -import javafx.scene.image.Image; import javax.persistence.EntityManager; /** * This class is responsible for preparing the database with test data for the * program to operate on. - * + * * It's a temporary solution and pretty lame, but it's the fastest way to solve * our problem and keep from hindering further progress. If time permits, we * will try to migrate to a more permanent solution like a stored SQL script, - * but there isn't really a reason to do that right now. - * + * but there isn't really a reason to do that right now. + * * This class is just meant to be a quick and dirty solution to the problem. - * + * * It's also not finished. It doesn't create half of the data we need yet. */ public class DatabaseTestDataLoader { - + public static void initializeTestData() { - + // Populate database with data on all of the rooms available - - // Uncomment this line later once RoomService is created - // RoomService roomService = new RoomService() - - Image placeholderImg = new Image("/images/temporary_hotel_room_image_placeholder.jpg"); - - List rooms = new ArrayList<>(); - + RoomCategory basic = new RoomCategory( "Basic", - "This room is as basic as you are. Includes complimentary bedbugs and various mystery stains on the sheets.", - placeholderImg, - "Beds not provided" + "This room is as basic as you are. Includes complimentary bedbugs " + + "and various mystery stains on the sheets. Used needles also " + + "included free of charge.", + "images/rooms/basic.jpg", + "Beds not provided", + 100.00 ); - + RoomCategory familyBasic = new RoomCategory( "Family Basic", - "With the Family basic room, you can be treated like dirt, but now with the whole family!", - placeholderImg, - "2 Queen, 2 twin" + "With the Family basic room, you can be treated like the dirt you" + + " are, but now with the whole family!", + "images/rooms/family_basic.jpg", + "2 Queen, 2 twin", + 125.99 ); - + RoomCategory luxury = new RoomCategory( "Luxury", - "Because in 2017 being able to go to a resort at all is a luxury. You should be thanking us.", - placeholderImg, - "2 Queen" + "Because in 2017 being able to go to a resort at all is a luxury. " + + "You should be thanking us.", + "images/rooms/luxury.jpg", + "2 Queen", + 159.99 ); - + RoomCategory luxuryFamily = new RoomCategory( "Luxury Family", - "This room is almost bearable. Too bad you have kids and you won't be able to enjoy it.", - placeholderImg, - "2 Queen, 2 twin" + "This room is almost bearable. Too bad you have kids and you won't " + + "be able to enjoy it.", + "images/rooms/luxury_family.jpg", + "2 Queen, 2 twin", + 179.67 ); - + RoomCategory king = new RoomCategory( "King", - "The room that says, 'I'm better than everyone else, and I want them to know it.'", - placeholderImg, - "2 King" + "The room that says, \"I'm better than everyone else, and I want" + + " them to know it.\" Includes access to a large arena where" + + " basic level guests battle to the death for a small sum of" + + "money - plus, we give you javelins to throw at the winner." + + " Helipad access available.", + "images/rooms/king.jpg", + "2 King", + 259.99 ); - + EntityManager entityManager = HibernateUtil.getEntityManager(); - + // Put the data in the database entityManager.getTransaction().begin(); - + // 1st floor rooms don't exist - + // 2nd floor rooms for(int roomNumber = 200; roomNumber < 200+50; roomNumber++) { entityManager.persist(new Room(String.valueOf(roomNumber), basic)); } - + // 3rd floor rooms for(int roomNumber = 300; roomNumber < 300+50; roomNumber++) { Room room = new Room(String.valueOf(roomNumber), basic); entityManager.persist(room); } - + // 4th floor rooms for(int roomNumber = 400; roomNumber < 400+40; roomNumber++) { Room room = new Room(String.valueOf(roomNumber), familyBasic); entityManager.persist(room); } - + // 5th floor rooms for(int roomNumber = 500; roomNumber < 500+30; roomNumber++) { Room room = new Room(String.valueOf(roomNumber), luxury); entityManager.persist(room); } - + // 6th floor rooms for(int roomNumber = 600; roomNumber < 600+20; roomNumber++) { Room room = new Room(String.valueOf(roomNumber), luxuryFamily); entityManager.persist(room); } - + // 7th floor rooms for(int roomNumber = 700; roomNumber < 700+10; roomNumber++) { Room room = new Room(String.valueOf(roomNumber), king); entityManager.persist(room); } - + // Generate package data. Package package1 = new Package("Loch-Ness monster viewing", 3.50); Package package2 = new Package("Basement tour", 10.00); @@ -134,52 +141,76 @@ public static void initializeTestData() { entityManager.persist(package2); entityManager.persist(package3); entityManager.persist(package4); - + // Generate test guest data Guest g1 = new Guest("Chris", "Mailldfghoux", "mailloux.cl@gmail.com", "239-242-4256", new MailingAddress("525 fake way", null, "33969", UsState.FLORIDA, "United States")); Guest g2 = new Guest("Chrsfgmis", "Mailloux", "mailsfghux.cl@gmail.com", "239-242-4256", new MailingAddress("525 fake way", null, "33969", UsState.FLORIDA, "United States")); Guest g3 = new Guest("Chris", "Mailldfghoux", "maillsfghsfghsoux.cl@gmail.com", "239-242-4256", new MailingAddress("525 fake way", null, "33969", UsState.FLORIDA, "United States")); Guest g4 = new Guest("Chrawetis", "Mailloux", "maillojytfkdfux.cl@gmail.com", "239-242-4256", new MailingAddress("525 fake way", null, "33969", UsState.FLORIDA, "United States")); - + entityManager.persist(g1); entityManager.persist(g2); entityManager.persist(g3); entityManager.persist(g4); - - // Generate test booking data - Calendar testCalendar = new GregorianCalendar(); - Date d1 = testCalendar.getTime(); - testCalendar.add(Calendar.DATE, 2); - Date d2 = testCalendar.getTime(); - - List testBookingPackages = new ArrayList(); - testBookingPackages.add(package1); - testBookingPackages.add(package2); - - Booking b = new Booking(g1, d1, d2, new Bill(), null, testBookingPackages, null); - - entityManager.persist(b); - + // Load test employee data Employee e1 = new Employee(1L, "gitrekt", true, "Chris", "Mailloux"); Employee e2 = new Employee(2L, "bassface", false, "Chris", "Kael"); - + Employee e3 = new Employee(Long.valueOf(3),"1234", true, "Juan" , "Gomez"); + Employee e4 = new Employee(Long.valueOf(4),"1234", true, "Juanito" , "Gomez"); + Employee e5 = new Employee(Long.valueOf(5),"1234", false, "Juana" , "Gomez"); + Employee e6 = new Employee(Long.valueOf(6),"1234", false, "Juanita" , "Gomez"); + Employee e7 = new Employee(Long.valueOf(7),"1234", true, "Juanucho" , "Gomez"); + entityManager.persist(e1); entityManager.persist(e2); - + // Ignore the variable name changes here, it's from a copy and paste. + entityManager.persist(e3); + entityManager.persist(e4); + entityManager.persist(e5); + entityManager.persist(e6); + entityManager.persist(e7); + entityManager.getTransaction().commit(); - + // Don't forget to close the entityManager when done with it entityManager.close(); - + // Load test feedback data GuestFeedbackService s = new GuestFeedbackService(); s.createNewGuestFeedback(new GuestFeedback("You suck.", "mailloux.cl@gmail.com")); s.createNewGuestFeedback(new GuestFeedback("You suck a lot.", "mailloux.cl@gmail.com")); s.createNewGuestFeedback(new GuestFeedback("You're the worst programmer ever and this simple feature took you all night to implement.", "mailloux.cl@gmail.com")); s.createNewGuestFeedback(new GuestFeedback("You're bad and you should feel bad.", "mailloux.cl@gmail.com")); - - // TODO: Room pricing data + + createTestBookingData(); } - + + private static void createTestBookingData() { + EntityManager entityManager = HibernateUtil.getEntityManager(); + + Guest g1 = new Guest("Chris", "Mailldfghoux", "maillasdgoux.cl@gmail.com", "239-242-4256", new MailingAddress("525 fake way", null, "33969", UsState.FLORIDA, "United States")); + entityManager.persist(g1); + + RoomService r = new RoomService(); + List testRooms = r.getAllRoomsInCategory("Basic"); + System.out.println(testRooms.size() + " Basic rooms added to test booking"); + + Calendar testCalendar = new GregorianCalendar(); + Date d1 = new Date(); + testCalendar.add(Calendar.DAY_OF_MONTH, 3); + Date d2 = testCalendar.getTime(); + + PackageService packageService = new PackageService(); + List allPackages = packageService.getAllPackages(); + Booking b1 = new Booking(g1, d1, d2, new Bill(), "you suck", allPackages, testRooms); + Booking b2 = new Booking(g1, d1, d2, new Bill(), null, null, null); + + entityManager.getTransaction().begin(); + entityManager.persist(b1); + entityManager.persist(b2); + entityManager.getTransaction().commit(); + entityManager.close(); + } + } diff --git a/src/main/java/com/gitrekt/resort/controller/BookingDetailsScreenController.java b/src/main/java/com/gitrekt/resort/controller/BookingDetailsScreenController.java index 9f5f38f..b71b5b7 100644 --- a/src/main/java/com/gitrekt/resort/controller/BookingDetailsScreenController.java +++ b/src/main/java/com/gitrekt/resort/controller/BookingDetailsScreenController.java @@ -1,11 +1,24 @@ package com.gitrekt.resort.controller; +import com.gitrekt.resort.model.entities.Booking; +import com.gitrekt.resort.model.entities.Guest; +import com.gitrekt.resort.model.entities.Package; +import com.gitrekt.resort.model.entities.Room; +import com.gitrekt.resort.model.services.BookingService; import java.net.URL; +import java.util.Optional; import java.util.ResourceBundle; +import javafx.beans.property.SimpleIntegerProperty; +import javafx.beans.property.SimpleStringProperty; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; import javafx.fxml.FXML; import javafx.fxml.Initializable; -import javafx.scene.control.Button; +import javafx.scene.control.Alert; +import javafx.scene.control.Alert.AlertType; +import javafx.scene.control.ButtonType; import javafx.scene.control.Label; +import javafx.scene.control.TableColumn; import javafx.scene.control.TableView; /** @@ -14,53 +27,175 @@ public class BookingDetailsScreenController implements Initializable { @FXML - private Button backButton; - + private TableView bookedRoomsTableView; + @FXML - private Button cancelBookingButton; - + private TableColumn roomNumberColumn; + @FXML - private Button viewBillButton; - - // TODO fix rawtype + private TableColumn roomCategoryColumn; + @FXML - private TableView bookedRoomsTableView; - - // TODO fix rawtype + private TableView bookedPackagesTableView; + @FXML - private TableView bookedPackagesTableView; - + private TableColumn packageNameColumn; + + @FXML + private TableColumn packageQuantityColumn; + @FXML private Label guestNameLabel; - + @FXML private Label checkInDateLabel; - + @FXML private Label checkOutDateLabel; - + + @FXML + private Label bookingCanceledLabel; + + private Booking booking; + + private ObservableList bookedRooms; + + private ObservableList bookedPackagesList; + /** * Initializes the controller class. */ @Override public void initialize(URL url, ResourceBundle rb) { - // TODO - } - - public void onBackButtonClicked() { - ScreenManager.getInstance().switchToScreen( - "/fxml/GuestHomeScreen.fxml" + bookedRooms = FXCollections.observableArrayList(); + bookedRoomsTableView.setItems(bookedRooms); + roomNumberColumn.setCellValueFactory( + (param) -> { + return new SimpleStringProperty( + String.valueOf(param.getValue().getRoomNumber()) + ); + } ); - } - - public void onViewBillButtonClicked() { - ScreenManager.getInstance().switchToScreen( - "/fxml/CustomerBillScreen.fxml" + roomCategoryColumn.setCellValueFactory( + (param) -> { + return new SimpleStringProperty( + param.getValue().getRoomCategory().getName() + ); + } + ); + + bookedPackagesList = FXCollections.observableArrayList(); + bookedPackagesTableView.setItems(bookedPackagesList); + packageNameColumn.setCellValueFactory( + (param) -> { + return new SimpleStringProperty( + param.getValue().packageName + ); + } + ); + packageQuantityColumn.setCellValueFactory( + (param) -> { + return new SimpleIntegerProperty( + param.getValue().bookedQty + ).asObject(); + } ); } - - public void onCancelBookingButtonClicked() { - // TODO + + /** + * Initializes a reference to the booking in question to allow the screen to display information + * about it without having to hit the database. Once this method is called, the information is + * displayed on the screen automatically. + * + * @param booking The booking to display information about. + */ + public void intializeBookingData(Booking booking) { + this.booking = booking; + + Guest guest = booking.getGuest(); + String guestNameLastFirst = guest.getLastName() + ", " + guest.getFirstName(); + this.guestNameLabel.setText(guestNameLastFirst); + this.checkInDateLabel.setText(booking.getCheckInDate().toString()); + this.checkOutDateLabel.setText(booking.getCheckOutDate().toString()); + bookedRooms.addAll(booking.getBookedRooms()); + + // Not the most efficient way to do this but it was quick to figure out and it works. + for(Package p : booking.getPackages()) { + boolean alreadyPresent = false; + for(BookedPackagesWrapper w : bookedPackagesList) { + if(w.packageName.equals(p.getName())) { + alreadyPresent = true; + w.bookedQty++; + } + } + if(!alreadyPresent) { + BookedPackagesWrapper newWrapper = new BookedPackagesWrapper(p.getName(), 1); + bookedPackagesList.add(newWrapper); + } + } } - + + /** + * Returns to the previous screen. + */ + @FXML + private void onBackButtonClicked() { + ScreenManager.getInstance().switchToScreen("/fxml/GuestHomeScreen.fxml"); + + } + + /** + * Displays the customer bill for the booking displayed by this screen. + */ + @FXML + private void onViewBillButtonClicked() { + Object temp = ScreenManager.getInstance().switchToScreen("/fxml/CustomerBillScreen.fxml"); + CustomerBillScreenController controller = (CustomerBillScreenController) temp; + // Fix LazyInitializationException by forcing initialization here with println + System.out.println(this.booking.toString()); + controller.initializeData(this.booking); + } + + /** + * Displays a confirmation dialog showing the price that will be charged as a cancellation fee. + */ + @FXML + private void onCancelBookingButtonClicked() { + double cancellationFee = BookingService.calcCancellationFee(this.booking); + String cancellationFeeString = String.format("$%.2f", cancellationFee); + // Show confirmation dialog + Alert confirmationDialog = new Alert(AlertType.CONFIRMATION); + confirmationDialog.setTitle("Confirm"); + confirmationDialog.setHeaderText("Confirm Cancel Booking"); + confirmationDialog.setContentText("You will be assessed a cancellation fee of " + + cancellationFeeString + + " - please confirm that you want to cancel this booking. " + + "This action cannot be undone."); + + Optional result = confirmationDialog.showAndWait(); + if(result.get() == ButtonType.OK) { + // User chose OK + BookingService bookingService = new BookingService(); + bookingService.cancelBooking(this.booking); + bookingCanceledLabel.setVisible(true); + } + } + + /** + * It might have been better to use a Map here instead of an inner class but in practice it's + * simple enough. + */ + private class BookedPackagesWrapper { + + String packageName; + + Integer bookedQty; + + BookedPackagesWrapper(String packageName, Integer bookedQty) { + this.packageName = packageName; + this.bookedQty = bookedQty; + } + + } + } diff --git a/src/main/java/com/gitrekt/resort/controller/BookingsReportScreenController.java b/src/main/java/com/gitrekt/resort/controller/BookingsReportScreenController.java index 7afec94..92c5c03 100644 --- a/src/main/java/com/gitrekt/resort/controller/BookingsReportScreenController.java +++ b/src/main/java/com/gitrekt/resort/controller/BookingsReportScreenController.java @@ -1,15 +1,28 @@ package com.gitrekt.resort.controller; +import com.gitrekt.resort.model.entities.Booking; +import com.gitrekt.resort.model.entities.Room; +import com.gitrekt.resort.model.entities.RoomCategory; +import com.gitrekt.resort.model.services.BookingService; +import com.gitrekt.resort.model.services.RoomService; import java.net.URL; +import java.time.LocalDateTime; +import java.time.Year; +import java.time.ZoneOffset; +import java.time.format.TextStyle; +import java.time.temporal.TemporalAdjusters; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Locale; import java.util.ResourceBundle; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.fxml.FXML; import javafx.fxml.Initializable; -import javafx.scene.chart.StackedBarChart; +import javafx.scene.chart.CategoryAxis; +import javafx.scene.chart.LineChart; import javafx.scene.chart.XYChart; -import javafx.scene.chart.XYChart.Data; -import javafx.scene.control.Button; import javafx.scene.control.Label; /** @@ -18,59 +31,246 @@ public class BookingsReportScreenController implements Initializable { @FXML - private StackedBarChart barChart; + private LineChart lineChart; @FXML private Label monthYearLabel; - @FXML - private Button nextMonthButton; + private List categories; - @FXML - private Button previousMonthButton; + private ObservableList daysInCurrentMonth; - @FXML - private Button pickMonthButton; + private ObservableList> data; - @FXML - private Button backButton; - - private XYChart.Series roomCategoriesDataSeries; + private LocalDateTime selectedMonth; - private ObservableList> roomCategoriesList; + private List bookingsForMonth; /** * Initializes the controller class. + * + * This class needs really major refactoring, but that's just a matter of + * time. When I can, I'll be ripping this class apart and putting it back + * together much more nicely. */ @Override public void initialize(URL url, ResourceBundle rb) { - // TODO: Test using test data, then implement with real logic. - Data testData = new Data(); - roomCategoriesList = FXCollections.observableArrayList(); - roomCategoriesDataSeries = new XYChart.Series<>(); - roomCategoriesDataSeries.setData(roomCategoriesList); + // Default month is the current month + selectedMonth = LocalDateTime.now().withDayOfMonth(1); + monthYearLabel.setText(getCurrentMonthYearString()); + daysInCurrentMonth = FXCollections.observableArrayList(); + initializeChart(); } - public void onPickMonthButtonClicked() { - // TODO - } - - public void onNextMonthButtonClicked() { - // TODO + /** + * Displays the graph data for the next month. + */ + @FXML + private void onNextMonthButtonClicked() { + selectedMonth = selectedMonth.with( + TemporalAdjusters.firstDayOfNextMonth() + ); + monthYearLabel.setText(getCurrentMonthYearString()); + initializeChart(); } - public void onPreviousMonthButtonClicked() { - // TODO + /** + * Displays the data for the previous month. + */ + @FXML + private void onPreviousMonthButtonClicked() { + selectedMonth = selectedMonth.minusMonths(1); + selectedMonth = selectedMonth.withDayOfMonth(1); + monthYearLabel.setText(getCurrentMonthYearString()); + initializeChart(); } - public void onBackButtonClicked() { + /** + * Returns to the home screen for staff reports. + */ + @FXML + private void onBackButtonClicked() { ScreenManager.getInstance().switchToScreen( "/fxml/ReportsHomeScreen.fxml" ); } - public void onSelectMonthButtonClicked() { - // TODO + /** + * Initializes the chart. + */ + private void initializeChart() { + // Clear any previous data + daysInCurrentMonth.clear(); + data = FXCollections.observableArrayList(); + data.clear(); + + prepareCategoriesList(); + getBookingsForMonth(); + + // Prepare the x-axis - this is a little weird but necessary + int selectedYear = selectedMonth.getYear(); + int numDaysInMonth = selectedMonth.getMonth() + .length(Year.isLeap(selectedYear)); + for(int i = 1; i <= numDaysInMonth; i++) { + daysInCurrentMonth.add(String.valueOf(i)); + } + CategoryAxis x = (CategoryAxis) lineChart.getXAxis(); + x.setCategories(daysInCurrentMonth); + + showDataForAllCategories(); + } + + /** + * Displays on the chart the report data for the selected month. + */ + private void showDataForAllCategories() { + // Clear any existing data + this.data.clear(); + + for(String category : categories) { + XYChart.Series categoryData; + categoryData = new XYChart.Series<>(); + categoryData.setName(category); + + for(int i = 1; i <= daysInCurrentMonth.size(); i++) { + XYChart.Data dayData = new XYChart.Data<>(); + dayData.setXValue(String.valueOf(i)); + double percentBooked; + percentBooked = getPercentBookedInCategoryOnDay(category, i); + dayData.setYValue(percentBooked); + categoryData.getData().add(dayData); + } + + this.data.add(categoryData); + } + lineChart.setData(this.data); + } + + /** + * Gathers the list of all room categories from the database, and also adds + * a category called "All" which is used to display data about all + * categories. + */ + private void prepareCategoriesList() { + this.categories = FXCollections.observableArrayList(); + + RoomService roomService = new RoomService(); + List categories = roomService.getAllRoomCategories(); + List result = new ArrayList<>(); + for(RoomCategory cat : categories) { + this.categories.add(cat.getName()); + } + this.categories.add("All"); + } + + /** + * @return A string represenation of the currently selected month and year, + * for example, "November 2017". + */ + private String getCurrentMonthYearString() { + return selectedMonth.getMonth() + .getDisplayName(TextStyle.FULL, Locale.US) + + " " + + selectedMonth.getYear(); + } + + /** + * Initializes the class field containing the list of all bookings at the + * resort for the given month. + */ + private void getBookingsForMonth() { + BookingService bookingService = new BookingService(); + // We have to use the old date API here because of JPA specs + Date d1 = Date.from(selectedMonth.toInstant(ZoneOffset.UTC)); + LocalDateTime lastDayInSelectedMonth = + this.selectedMonth.with(TemporalAdjusters.lastDayOfMonth()); + Date d2 = Date.from(lastDayInSelectedMonth.toInstant(ZoneOffset.UTC)); + this.bookingsForMonth = bookingService.getBookingsBetweenDates(d1, d2); + } + + /** + * @param category The room category name in question. + * @param dayOfMonth The day of the selected month in question. + * + * @return The number of rooms booked in the given category on the given + * day of the selected month. + */ + private int getNumBookedInCategoryOnDay( + String category, int dayOfMonth + ) { + LocalDateTime day = selectedMonth.withDayOfMonth(dayOfMonth); + Date date = Date.from(day.toInstant(ZoneOffset.UTC)); + + int result = 0; + + // Handle special case of "All" category + if(category.equals("All")) { + for(Booking b : bookingsForMonth) { + if(b.getCheckInDate().compareTo(date) <= 0 + && b.getCheckOutDate().compareTo(date) >= 0) { + result += b.getBookedRooms().size(); + } + } + } + + // Handle general case of specific category + for(Booking b : bookingsForMonth) { + if(b.getCheckInDate().compareTo(date) <= 0 + && b.getCheckOutDate().compareTo(date) >= 0) { + for(Room room : b.getBookedRooms()) { + String cat = room.getRoomCategory().getName(); + if(cat.equals(category)) { + result++; + } + } + } + } + + return result; + } + + /** + * Returns the total percentage of the rooms booked in the given category + * on the given day of the month. + * + * @param category The room category in question. + * @param dayOfMonth The day of the month (e.g. 1-31) in question. + * @return The percentage booked as a double. + */ + private double getPercentBookedInCategoryOnDay( + String category, int dayOfMonth + ) { + int numBookedInCat = getNumBookedInCategoryOnDay( + category, dayOfMonth + ); + int numInCat = getNumRoomsInCategory(category); + + try { + return (numBookedInCat * 100) / numInCat; + } catch (ArithmeticException e) { // Catch divide by 0 + return 0.0; + } + } + + /** + * Gets the number of total rooms in the resort in the provided category. + */ + private int getNumRoomsInCategory(String category) { + // Handle special case of "All" category + if(category.equals("All")) { + int totalNumRoomsInResort = 0; + for(String cat : this.categories) { + if(!cat.equals("All")) { + totalNumRoomsInResort += getNumRoomsInCategory(cat); + } + } + return totalNumRoomsInResort; + } + + // Handle general case + RoomService roomService = new RoomService(); + List allRoomsInCat = roomService.getAllRoomsInCategory(category); + return allRoomsInCat.size(); } } diff --git a/src/main/java/com/gitrekt/resort/controller/BrowseRoomsListItemController.java b/src/main/java/com/gitrekt/resort/controller/BrowseRoomsListItemController.java index ad694f5..172e0f6 100644 --- a/src/main/java/com/gitrekt/resort/controller/BrowseRoomsListItemController.java +++ b/src/main/java/com/gitrekt/resort/controller/BrowseRoomsListItemController.java @@ -10,74 +10,92 @@ import javafx.scene.image.ImageView; /** - * This class controls the list items in the browse rooms screen room search - * results list. Due to the weird way that lists are updated in JavaFX, - * this class actually loads the FXML and then assigns itself as the controller - * for the screen; things are typically done the other way around in JavaFX. + * This class controls the list items in the browse rooms screen room search results list. + * + * Due to the weird way that lists are updated in JavaFX, this class actually loads the FXML and + * then assigns itself as the controller for the screen; things are typically done the other way + * around in JavaFX. */ public class BrowseRoomsListItemController implements Initializable { - + @FXML private Node root; - + @FXML private Label roomCategoryLabel; - + @FXML private Label bedsInfoLabel; - + @FXML private Label roomPriceLabel; - + @FXML private ImageView roomThumbnailView; - + @FXML private Label roomDescriptionLabel; - + + private BrowseRoomsScreenController parentController; + + private RoomSearchResult roomData; + public BrowseRoomsListItemController() { - // TODO + // Intentionally blank } - - /** - * Handles any needed initialization logic for the controller class. - */ + @Override public void initialize(URL url, ResourceBundle rb) { - // TODO + // Intentionally blank } - + + /** + * Sets the data on the screen to the values from the search result data. + * + * @param roomData The room information returned from the search. + */ public void setData(RoomSearchResult roomData) { + this.roomData = roomData; roomCategoryLabel.setText(roomData.getRoomCategory().getName()); roomThumbnailView.setImage(roomData.getRoomCategory().getImage()); bedsInfoLabel.setText(roomData.getRoomCategory().getBedsInfo()); - - // TODO: We should handle currency more flexibly at some point. + // TODO: We should handle currency more flexibly at some point String priceString = "$" + roomData.getRoomPrice() + " / night"; - roomPriceLabel.setText(priceString); - roomDescriptionLabel.setText( - roomData.getRoomCategory().getDescription() - ); + roomDescriptionLabel.setText(roomData.getRoomCategory().getDescription()); } - + + /** + * @return The graphical representation of this list item. + */ public Node getView() { return root; } - - @FXML - protected void onAddToBookingButtonClicked() { - // TODO implement program logic + + /** + * Assigns a reference the the controller of the class containing the list that this item is + * displayed in. + * + * This is needed to be able to handle the buttons in the list item easily. + * + * @param parentController + */ + public void setParentController(BrowseRoomsScreenController parentController) { + this.parentController = parentController; } - + + /** + * Notifies the parent controller to add the room to the selected rooms list. + */ @FXML - protected void onMoreInfoButtonClicked() { - // TODO + private void onAddToBookingButtonClicked() { + roomData.setNumAvailable(roomData.getNumAvailable() - 1); + parentController.onRoomSelected(roomData); } - + @FXML - protected void onRoomThumbnailClicked() { - // TODO + private void onRoomThumbnailClicked() { + // TODO: Display fullsize image. } - + } diff --git a/src/main/java/com/gitrekt/resort/controller/BrowseRoomsScreenController.java b/src/main/java/com/gitrekt/resort/controller/BrowseRoomsScreenController.java index e9a5939..f80712c 100644 --- a/src/main/java/com/gitrekt/resort/controller/BrowseRoomsScreenController.java +++ b/src/main/java/com/gitrekt/resort/controller/BrowseRoomsScreenController.java @@ -1,161 +1,205 @@ package com.gitrekt.resort.controller; -import com.gitrekt.resort.model.entities.RoomCategory; import com.gitrekt.resort.model.RoomSearchResult; +import com.gitrekt.resort.model.services.BookingService; import com.gitrekt.resort.view.BrowseRoomsListItem; -import com.gitrekt.resort.view.DeletableListItem; -import java.math.BigDecimal; +import com.gitrekt.resort.view.SelectedRoomListItem; import java.net.URL; -import java.util.Random; +import java.time.Instant; +import java.time.LocalDate; +import java.time.ZoneId; +import java.time.chrono.ChronoLocalDate; +import java.util.Date; import java.util.ResourceBundle; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.fxml.FXML; import javafx.fxml.Initializable; import javafx.scene.control.Button; +import javafx.scene.control.DateCell; +import javafx.scene.control.DatePicker; +import javafx.scene.control.Label; import javafx.scene.control.ListView; -import javafx.scene.image.Image; - -// TODO: Clean up this mess of a class; major refactoring needed. +import javafx.scene.control.Tooltip; +import javafx.util.Callback; /** * FXML Controller class for the browse rooms screen. */ -public class BrowseRoomsScreenController implements Initializable, - DeletableListItemDeletionListener { +public class BrowseRoomsScreenController implements Initializable { - @FXML - private Button backButton; - @FXML private ListView roomsListView; - + + @FXML + private ListView selectedRoomsListView; + @FXML - private ListView currentlySelectedRoomsListView; - + private DatePicker checkInDatePicker; + @FXML - private Button findAvailableRoomsButton; - + private DatePicker checkOutDatePicker; + @FXML - private Button nextButton; - - private ObservableList roomSearchResults; - - private ObservableList currentlySelectedRooms; - + private Button findAvailableRoomsButton; + + private final ObservableList roomSearchResults; + + private final ObservableList selectedRooms; + public BrowseRoomsScreenController() { roomSearchResults = FXCollections.observableArrayList(); - currentlySelectedRooms = FXCollections.observableArrayList(); + selectedRooms = FXCollections.observableArrayList(); } - - /** - * Initializes the controller class. - * - * Don't touch. Magic. - */ + @Override public void initialize(URL url, ResourceBundle rb) { roomsListView.setItems(roomSearchResults); - roomsListView.setCellFactory(param -> new BrowseRoomsListItem() { + roomsListView.setCellFactory( + param -> new BrowseRoomsListItem(this) { { + // Don't touch. Magic. prefWidthProperty().bind(roomsListView.widthProperty()); } }); - currentlySelectedRoomsListView.setCellFactory( - param -> new DeletableListItem(this) - ); - currentlySelectedRoomsListView.setItems(currentlySelectedRooms); - - uiDemonstrationCode1RemoveLater(); + roomsListView.setPlaceholder(new Label("No results.")); + selectedRoomsListView.setCellFactory(param -> new SelectedRoomListItem(this)); + selectedRoomsListView.setItems(selectedRooms); + selectedRoomsListView.setPlaceholder(new Label("No rooms selected")); + initializeDatePickers(); } - - private void uiDemonstrationCode1RemoveLater() { - Random random = new Random(); - int randInt = random.nextInt(999); - BigDecimal bd = new BigDecimal(randInt); - - Image i = new Image("/images/temporary_hotel_room_image_placeholder.jpg"); - RoomSearchResult r4 = new RoomSearchResult("512", bd, - new RoomCategory( - "Basic Family Suite", - "This room features a 40-inch flat-screen TV with high-definition channels. An iPod docking station and coffee-making facilities are also provided.\n" + "\n" + "Room Facilities: Safe, Air conditioning, Iron, Heating, Carpeted, Interconnecting room(s) available, Hairdryer, Free toiletries, Toilet, Bathroom, Bathtub or shower, Telephone, Radio, Cable channels, Flat-screen TV, Refrigerator, Alarm clock", - i, - "2 King, 2 Twin" - ) - ); - currentlySelectedRooms.add(r4); - currentlySelectedRooms.add(r4); - currentlySelectedRooms.add(r4); + /** + * Called by the search result list item controller to notify this class that the + * "add to booking" button for the class has been clicked. + * + * @param roomData The RoomSearchResult contained in the clicked item. + */ + public void onRoomSelected(RoomSearchResult roomData) { + // If the currently selected room was the last in it's category, hide it from the list. + if(roomData.getNumAvailable() == 0) { + roomSearchResults.remove(roomData); + } + selectedRooms.add(roomData); } - + /** - * Adds this RoomSearchResult to the list of search results, if it is not - * already in the list. - * - * @param result The RoomSearchResult to add. + * Called by the selected rooms list item controller to notify this controller that the room + * has been unselected and should be removed from the selected rooms list. + * + * @param roomData The RoomSearchResult for the room that was unselected. */ - public void addResult(RoomSearchResult result) { - if(!roomSearchResults.contains(result)) { - roomSearchResults.add(result); - sortResultsByPrice(); + public void onRoomUnselected(RoomSearchResult roomData) { + // If the unselected room was the last available, add back it to the list of results. + if(roomData.getNumAvailable() == 1) { + roomSearchResults.add(roomData); } + selectedRooms.remove(roomData); } - + /** - * Removes this roomSearchResult item from the list of results. - * - * @param result The RoomSearchResult to remove. + * Ensures that the date pickers only allow selection of dates within the valid booking date + * range, as defined in the specifications document. + * + * Chief among these rules is that bookings may not be placed more than one day in advance. */ - public void removeResult(RoomSearchResult result) { - roomSearchResults.remove(result); + private void initializeDatePickers() { + Callback dayCellFactory = + (final DatePicker datePicker) -> new DateCell() { + @Override + public void updateItem(LocalDate item, boolean empty) { + super.updateItem(item, empty); + + if(item.isAfter(LocalDate.now().plusYears(1))) { + setDisable(true); + } + if(item.isBefore(ChronoLocalDate.from(LocalDate.now()))) { + setDisable(true); + } + } + }; + // Disable selecting invalid check-in/check-out dates + checkInDatePicker.setDayCellFactory(dayCellFactory); + checkOutDatePicker.setDayCellFactory(dayCellFactory); } - + + /** + * Clears the results list; doesn't really need to do anything else at the moment. + */ + @FXML + private void onCheckInDateSelected() { + roomSearchResults.clear(); + } + + /** + * Validates the checkout date to ensure that it is at least one day after the checkin date. + * + * If it isn't, disables the room search button until it is. + */ + @FXML + private void onCheckOutDateSelected() { + LocalDate checkOutDate = checkOutDatePicker.getValue(); + LocalDate checkInDate = checkInDatePicker.getValue(); + if(checkOutDate.isBefore(checkInDate) || checkOutDate.isEqual(checkInDate)) { + checkOutDatePicker.getStyleClass().add("invalidField"); + checkOutDatePicker.setTooltip( + new Tooltip("Checkout date cannot be on or before checkin date!") + ); + findAvailableRoomsButton.setDisable(true); + } else { + checkOutDatePicker.getStyleClass().remove("invalidField"); + checkOutDatePicker.setTooltip(null); + findAvailableRoomsButton.setDisable(false); + } + roomSearchResults.clear(); + } + + /** + * Sorts results list by price, high to low. + */ private void sortResultsByPrice() { roomSearchResults.sort( - (RoomSearchResult r1, RoomSearchResult r2) -> + (RoomSearchResult r1, RoomSearchResult r2) -> r1.getRoomPrice().compareTo(r2.getRoomPrice()) ); } - - public void onBackButtonClicked() { - ScreenManager.getInstance().switchToScreen( - "/fxml/GuestHomeScreen.fxml" - ); - } - - // TODO: Implement real program logic, Remove test code. - public void onFindAvailableRoomsButtonClicked() { - Random random = new Random(); - int randInt = random.nextInt(999); - BigDecimal bd = new BigDecimal(randInt); - - Image i = new Image("/images/temporary_hotel_room_image_placeholder.jpg"); - RoomSearchResult r4 = new RoomSearchResult("512", bd, - new RoomCategory( - "Basic Family Suite", - "This room features a 40-inch flat-screen TV with high-definition channels. An iPod docking station and coffee-making facilities are also provided.\n" + "\n" + "Room Facilities: Safe, Air conditioning, Iron, Heating, Carpeted, Interconnecting room(s) available, Hairdryer, Free toiletries, Toilet, Bathroom, Bathtub or shower, Telephone, Radio, Cable channels, Flat-screen TV, Refrigerator, Alarm clock", - i, - "2 King, 2 Twin" - ) - ); - this.addResult(r4); - } - - public void onNextButtonClicked() { - // TODO replace with the packages screen first - this is temporary - - ScreenManager.getInstance().switchToScreen( - "/fxml/PlaceBookingScreen.fxml" - ); + + /** + * Returns to the previous screen. + */ + @FXML + private void onBackButtonClicked() { + ScreenManager.getInstance().switchToScreen("/fxml/GuestHomeScreen.fxml"); } - + /** - * Handles item deletion events for deletable list items. + * Searches the database for rooms that are available in the given date range. */ - @Override - public void onItemDeleted(DeletableListItem item) { - // TODO implement + @FXML + private void onFindAvailableRoomsButtonClicked() { + // The new date api is great. Converting back and forth, not so much. + LocalDate checkInDateTemp = checkInDatePicker.getValue(); + LocalDate checkOutDateTemp = checkOutDatePicker.getValue(); + Instant temp1 = Instant.from(checkInDateTemp.atStartOfDay(ZoneId.systemDefault())); + Instant temp2 = Instant.from(checkOutDateTemp.atStartOfDay(ZoneId.systemDefault())); + Date checkInDate = Date.from(temp1); + Date checkOutDate = Date.from(temp2); + + // Clear any existing results + roomSearchResults.clear(); + selectedRooms.clear(); + + // Get the new results + BookingService bookingService = new BookingService(); + roomSearchResults.addAll(bookingService.getRoomTypesAvailable(checkInDate, checkOutDate)); } - + + @FXML + private void onNextButtonClicked() { + // TODO replace with the packages screen first - this is temporary + if(selectedRooms.size() > 0) { + ScreenManager.getInstance().switchToScreen("/fxml/PlaceBookingScreen.fxml"); + } + } + } diff --git a/src/main/java/com/gitrekt/resort/controller/CreateStaffAccountDialogController.java b/src/main/java/com/gitrekt/resort/controller/CreateStaffAccountDialogController.java index 2a67f44..16f98b9 100644 --- a/src/main/java/com/gitrekt/resort/controller/CreateStaffAccountDialogController.java +++ b/src/main/java/com/gitrekt/resort/controller/CreateStaffAccountDialogController.java @@ -1,13 +1,21 @@ package com.gitrekt.resort.controller; +import com.gitrekt.resort.model.entities.Employee; +import com.gitrekt.resort.model.services.EmployeeService; +import com.gitrekt.resort.model.services.PasswordValidatorUtil; import java.net.URL; import java.util.ResourceBundle; import javafx.fxml.FXML; import javafx.fxml.Initializable; import javafx.scene.control.Button; import javafx.scene.control.CheckBox; +import javafx.scene.control.Label; import javafx.scene.control.TextField; import javafx.stage.Stage; +import javax.persistence.PersistenceException; +import org.passay.PasswordData; +import org.passay.RuleResult; +import org.passay.RuleResultDetail; /** * Controller class for the dialog in which managers create new staff accounts. @@ -37,19 +45,209 @@ public class CreateStaffAccountDialogController implements Initializable { @FXML private TextField confirmPasswordField; + + @FXML + private Label firstNameLabel; + + @FXML + private Label lastNameLabel; + + @FXML + private Label employeeIdLabel; + + @FXML + private Label staffErrorLabel; + + @FXML + private Label errorLabel; + + @FXML + private Label lengthLabel; + + @FXML + private Label numAndSpecialCharLabel; + + @FXML + private Label mixedCaseLabel; @Override public void initialize(URL location, ResourceBundle resources) { - //TODO + // Assign update listeners for the password fields for validation + passwordField.setOnKeyPressed(e -> + onPasswordFieldUpdated() + ); + confirmPasswordField.setOnKeyPressed(e -> + onConfirmPasswordFieldUpdated() + ); } - public void onCancelButtonClicked() { + /** + * Closes the dialog. + */ + @FXML + private void onCancelButtonClicked() { Stage dialogStage = (Stage) cancelButton.getScene().getWindow(); dialogStage.close(); } - public void onConfirmButtonClicked() { - //TODO + /** + * Creates a new account with the data from the form. + * + * Handles cases where the account already exists by display an error, and + * validates the input fields at a basic level. + */ + @FXML + private void onConfirmButtonClicked() { + // Hide any currently showing account creation errors + staffErrorLabel.setVisible(false); + // Gather data from form + String firstName = firstNameField.getText(); + String lastName = lastNameField.getText(); + String password = passwordField.getText(); + boolean isManager = managerCheckBox.isSelected(); + long employeeId = Long.valueOf(employeeIdField.getText()); + // Validate and check if we can proceed + if(validatePasswords() && validateStaffAccount()) { + Employee newEmployee = new Employee( + employeeId, password, isManager, firstName, lastName + ); + EmployeeService employeeService = new EmployeeService(); + try { + employeeService.createEmployeeAccount(newEmployee); + Stage dialogStage = (Stage) cancelButton.getScene().getWindow(); + dialogStage.close(); + } catch (PersistenceException e) { + // This exception is thrown when the entity already exists + // You'd think it would throw EntityAlreadyExistsException + // but Hibernate is weird like that + onAccountAlreadyExists(); + } + employeeService.cleanup(); + } + } + + /** + * Called when trying to create a new account with an id that already exists + * in the database. + */ + private void onAccountAlreadyExists() { + staffErrorLabel.setVisible(true); + staffErrorLabel.setText("Account with this ID already exists."); + } + + /** + * Validates the password fields. + */ + private void onPasswordFieldUpdated() { + validatePasswords(); + } + + /** + * Validates the password fields. + */ + private void onConfirmPasswordFieldUpdated() { + validatePasswords(); + } + + /** + * Performs form validation for the create employee account dialog. + * Also updates UI cues to display information related to form validation + * errors. + * + * Currently only checks that fields are not empty. + * + * Should later be expanded to include checks for incorrect data types. + * + * @return True if the form fields are valid. + */ + private boolean validateStaffAccount() { + boolean result = true; + + // Gather data from form fields + String firstName = firstNameField.getText(); + String lastName = lastNameField.getText(); + long employeeId = Long.valueOf(employeeIdField.getText()); + + //Hide any already showing errors + staffErrorLabel.setVisible(false); + firstNameLabel.getStyleClass().remove("validationErrorText"); + lastNameLabel.getStyleClass().remove("validationErrorText"); + employeeIdLabel.getStyleClass().remove("validationErrorText"); + + // Ensure fields are not empty - show error if they are + if(firstName.isEmpty() + || lastName.isEmpty() + || String.valueOf(employeeId).isEmpty()) { + staffErrorLabel.setVisible(true); + staffErrorLabel.setText("Fields cannot be empty"); + result = false; + } + + return result; + } + + + /** + * In addition to determining whether the passwords are valid, this method + * also handles the UI cues that let the user know what is wrong with their + * password. + * + * @return True if the passwords entered are valid + */ + private boolean validatePasswords() { + boolean result = true; // Innocent until proven guilty lol + + String password = passwordField.getText(); + String confirmPassword = confirmPasswordField.getText(); + + // Hide any errors that are showing already + errorLabel.setVisible(false); + lengthLabel.getStyleClass().remove("validationErrorText"); + numAndSpecialCharLabel.getStyleClass().remove("validationErrorText"); + mixedCaseLabel.getStyleClass().remove("validationErrorText"); + + // Check if there is any text written in the passwords fields + if(password.isEmpty() || confirmPassword.isEmpty()) { + errorLabel.setVisible(true); + errorLabel.setText("Fields cannot be empty"); + result = false; + } + + // Check if password matches confirmation password + if(!password.equals(confirmPassword)) { + errorLabel.setVisible(true); + errorLabel.setText("Passwords don't match"); + result = false; + } + + RuleResult ruleResult = PasswordValidatorUtil.validator + .validate(new PasswordData(password)); + + // If the password is invalid, set the flag + if(!ruleResult.isValid()) { + result = false; + } + + //Update the label requirements + for(RuleResultDetail d : ruleResult.getDetails()) { + String errorCode = d.getErrorCode(); + + switch(errorCode) { + case "TOO_SHORT": + lengthLabel.getStyleClass().add("validationErrorText"); + break; + case "INSUFFICIENT_LOWERCASE": + case "INSUFFICIENT_UPPERCASE": + mixedCaseLabel.getStyleClass().add("validationErrorText"); + break; + case "INSUFFICIENT_DIGIT": + case "INSUFFICIENT_SPECIAL": + numAndSpecialCharLabel.getStyleClass() + .add("validationErrorText"); + break; + } + } + return result; } } diff --git a/src/main/java/com/gitrekt/resort/controller/CustomerBillScreenController.java b/src/main/java/com/gitrekt/resort/controller/CustomerBillScreenController.java index bf2a152..8a4c96b 100644 --- a/src/main/java/com/gitrekt/resort/controller/CustomerBillScreenController.java +++ b/src/main/java/com/gitrekt/resort/controller/CustomerBillScreenController.java @@ -1,14 +1,18 @@ package com.gitrekt.resort.controller; -import com.gitrekt.resort.model.entities.Bill; +import com.gitrekt.resort.model.entities.BillItem; +import com.gitrekt.resort.model.entities.Booking; import com.gitrekt.resort.model.services.BillService; import java.awt.print.PrinterException; import java.io.IOException; import java.net.URL; import java.util.ResourceBundle; +import javafx.beans.property.SimpleIntegerProperty; +import javafx.beans.property.SimpleStringProperty; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; import javafx.fxml.FXML; import javafx.fxml.Initializable; -import javafx.scene.control.Button; import javafx.scene.control.Label; import javafx.scene.control.TableColumn; import javafx.scene.control.TableView; @@ -18,56 +22,104 @@ */ public class CustomerBillScreenController implements Initializable { - @FXML - private Button backButton; - @FXML private Label billTotalText; - + @FXML private Label customerNameText; - - @FXML - private Label billingPeriodText; - + @FXML private Label bookingNumberText; - - // TODO: Fix rawtype + @FXML - private TableView billTable; - - // TODO: Fix rawtype + private TableView billTable; + @FXML - private TableColumn itemNameColumn; - - // TODO: Fix rawtype + private TableColumn itemNameColumn; + @FXML - private TableColumn qtyColumn; - - // TODO: Fix rawtype + private TableColumn qtyColumn; + @FXML - private TableColumn priceColumn; - + private TableColumn priceColumn; + + private Booking booking; + + private ObservableList billItems; + /** * Initializes the controller class. */ @Override public void initialize(URL url, ResourceBundle rb) { - // TODO - } - - public void onBackButtonClicked() { - ScreenManager.getInstance().switchToScreen( - "/fxml/BookingDetailsScreen.fxml" - ); + billItems = FXCollections.observableArrayList(); + + itemNameColumn.setCellValueFactory((param) -> { + return new SimpleStringProperty(param.getValue().getName()); + }); + + qtyColumn.setCellValueFactory((param) -> { + return new SimpleIntegerProperty(param.getValue().getQuantity()) + .asObject(); + }); + + priceColumn.setCellValueFactory((param) -> { + return new SimpleStringProperty( + String.format("%.2f", param.getValue().getTotalPrice()) + ); + }); + + billTable.setItems(billItems); } - - public void onPrintBillButtonClicked() - throws IOException, PrinterException { - // TODO: Replace with an actual bill, not an empty one. + + /** + * Initializes the view bill screen with the data for the bill to display. + * Once this method is called, the proper data is displayed on the screen. + * + * @param booking The booking to display the bill for. + */ + public void initializeData(Booking booking) { + this.booking = booking; + billItems.addAll(booking.getBill().getCharges()); + initializeInfoLabels(); + } + + /** + * Returns to the previous screen, and passes a reference to the relevant booking data. + * + * I don't like that this class needs to know about it's parent screen, but this is the + * simplest and fastest way to implement it. + */ + @FXML + private void onBackButtonClicked() { + Object temp = ScreenManager.getInstance().switchToScreen("/fxml/BookingDetailsScreen.fxml"); + BookingDetailsScreenController parentController = (BookingDetailsScreenController) temp; + parentController.intializeBookingData(this.booking); + } + + /** + * Prints the guest bill on a physical printer. The user is first prompted + * with a print options dialog. + * + * @throws IOException + * @throws PrinterException + */ + @FXML + private void onPrintBillButtonClicked() throws IOException, PrinterException { BillService billService = new BillService(); - billService.printBill(new Bill()); + billService.printBillForBooking(booking); + } + + /** + * Initializes the information displayed in the various labels displayed on + * this screen, such as the customer name label, the total price label, etc. + */ + private void initializeInfoLabels() { + bookingNumberText.setText(String.valueOf(booking.getId())); + String lastName = booking.getGuest().getFirstName(); + String firstName = booking.getGuest().getLastName(); + customerNameText.setText(lastName + ", " + firstName); + billTotalText.setText(String.format("$%.2f", booking.getBill().getTotal())); } - + } diff --git a/src/main/java/com/gitrekt/resort/controller/DeletableListItemDeletionListener.java b/src/main/java/com/gitrekt/resort/controller/DeletableListItemDeletionListener.java deleted file mode 100644 index 9d2e19d..0000000 --- a/src/main/java/com/gitrekt/resort/controller/DeletableListItemDeletionListener.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.gitrekt.resort.controller; - -import com.gitrekt.resort.view.DeletableListItem; - -public interface DeletableListItemDeletionListener { - - void onItemDeleted(DeletableListItem item); - -} diff --git a/src/main/java/com/gitrekt/resort/controller/DeleteableListItemController.java b/src/main/java/com/gitrekt/resort/controller/DeleteableListItemController.java deleted file mode 100644 index 2c7f16b..0000000 --- a/src/main/java/com/gitrekt/resort/controller/DeleteableListItemController.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.gitrekt.resort.controller; - -import java.net.URL; -import java.util.ResourceBundle; -import javafx.fxml.FXML; -import javafx.fxml.Initializable; -import javafx.scene.control.Button; -import javafx.scene.control.Label; - -public class DeleteableListItemController implements Initializable { - - @FXML - private Button deleteButton; - - @FXML - private Label buttonText; - - @FXML - public void initialize(URL url, ResourceBundle rb) { - // TODO - } - -} diff --git a/src/main/java/com/gitrekt/resort/controller/GuestHomeScreenController.java b/src/main/java/com/gitrekt/resort/controller/GuestHomeScreenController.java index 99fca29..ee01671 100644 --- a/src/main/java/com/gitrekt/resort/controller/GuestHomeScreenController.java +++ b/src/main/java/com/gitrekt/resort/controller/GuestHomeScreenController.java @@ -1,73 +1,77 @@ package com.gitrekt.resort.controller; +import com.gitrekt.resort.model.entities.Booking; +import com.gitrekt.resort.model.services.BookingService; import java.io.IOException; import java.net.URL; +import java.util.Optional; import java.util.ResourceBundle; import javafx.fxml.FXML; -import javafx.fxml.FXMLLoader; import javafx.fxml.Initializable; -import javafx.scene.Parent; -import javafx.scene.Scene; import javafx.scene.control.Button; -import javafx.stage.Modality; -import javafx.stage.Stage; +import javafx.scene.control.TextInputDialog; +import javax.persistence.EntityNotFoundException; /** * FXML Controller class for the guest home screen. */ public class GuestHomeScreenController implements Initializable { - - @FXML - private Button backButton; - - @FXML - private Button browseRoomsButton; - - @FXML - private Button leaveFeedbackButton; - + @FXML private Button viewBookingButton; - + @Override public void initialize(URL url, ResourceBundle rb) { // TODO } - - public void onBrowseRoomsButtonClicked() { - ScreenManager.getInstance().switchToScreen( - "/fxml/BrowseRoomsScreen.fxml" - ); + + @FXML + private void onBrowseRoomsButtonClicked() { + ScreenManager.getInstance().switchToScreen("/fxml/BrowseRoomsScreen.fxml"); } - - public void onLeaveFeedbackButtonClicked() { - ScreenManager.getInstance().switchToScreen( - "/fxml/LeaveFeedbackScreen.fxml" - ); + + @FXML + private void onLeaveFeedbackButtonClicked() { + ScreenManager.getInstance().switchToScreen("/fxml/LeaveFeedbackScreen.fxml"); } - - public void onBackButtonClicked() { - ScreenManager.getInstance().switchToScreen( - "/fxml/HomeScreen.fxml" - ); + + @FXML + private void onBackButtonClicked() { + ScreenManager.getInstance().switchToScreen("/fxml/HomeScreen.fxml"); } - - public void onViewBookingButtonClicked() throws IOException { - Stage bookingNumberDialogStage = new Stage(); - Parent bookingNumberDialogRoot = FXMLLoader.load( - getClass().getResource("/fxml/BookingNumberDialog.fxml") - ); - Scene bookingNumberDialog = new Scene(bookingNumberDialogRoot); - - bookingNumberDialogStage.setScene(bookingNumberDialog); - bookingNumberDialogStage.initModality(Modality.APPLICATION_MODAL); - bookingNumberDialogStage.initOwner( - viewBookingButton.getScene().getWindow() - ); - bookingNumberDialogStage.setResizable(false); - bookingNumberDialogStage.setTitle("Find Booking"); - bookingNumberDialogStage.centerOnScreen(); - - bookingNumberDialogStage.show(); + + @FXML + private void onViewBookingButtonClicked() throws IOException { + TextInputDialog dialog = new TextInputDialog(); + dialog.setTitle("Find booking"); + dialog.setHeaderText("Enter booking number"); + dialog.setContentText("Please enter your booking number: "); + + Optional result = dialog.showAndWait(); + if(result.isPresent()) { + Long bookingId = Long.valueOf(result.get()); + BookingService bookingService = new BookingService(); + try { + Booking booking = bookingService.getBookingById(bookingId); + // Force loading of booking from DB (because of lazy loading) + System.out.println(booking.toString()); + Object temp = ScreenManager.getInstance().switchToScreen( + "/fxml/BookingDetailsScreen.fxml" + ); + BookingDetailsScreenController controller = (BookingDetailsScreenController) temp; + controller.intializeBookingData(booking); + } catch (EntityNotFoundException e) { + return; + } + Booking booking = bookingService.getBookingById(bookingId); + if(booking != null) { + Object temp = ScreenManager.getInstance().switchToScreen( + "/fxml/BookingDetailsScreen.fxml" + ); + BookingDetailsScreenController controller = (BookingDetailsScreenController) temp; + controller.intializeBookingData(booking); + } + + } } } diff --git a/src/main/java/com/gitrekt/resort/controller/HomeScreenController.java b/src/main/java/com/gitrekt/resort/controller/HomeScreenController.java index e64bed0..bd87eb4 100644 --- a/src/main/java/com/gitrekt/resort/controller/HomeScreenController.java +++ b/src/main/java/com/gitrekt/resort/controller/HomeScreenController.java @@ -14,50 +14,46 @@ /** * The JavaFX controller class for the home screen. - * - * The home screen is shown when the application first starts, and allows the - * user to select which mode to use the program in: guest or staff. + * + * The home screen is shown when the application first starts, and allows the user to select which + * mode to use the program in: guest or staff. */ public class HomeScreenController implements Initializable { - - @FXML - private Button guestModeButton; - - @FXML + + @FXML private Button staffModeButton; - + @Override public void initialize(URL url, ResourceBundle rb) { - // TODO: Implement + // Intentionally blank. } - + /** - * Displays the pop-up dialog which prompts staff members to log in to the - * system. - * + * Displays the pop-up dialog which prompts staff members to log in to the system. + * * @throws IOException */ @FXML public void onStaffModeButtonClicked() throws IOException { Stage staffLoginDialogStage = new Stage(); Parent staffLoginDialogRoot = FXMLLoader.load( - getClass().getResource("/fxml/StaffLoginDialog.fxml") + getClass().getResource("/fxml/StaffLoginDialog.fxml") ); Scene staffLoginDialog = new Scene(staffLoginDialogRoot); - + staffLoginDialogStage.setScene(staffLoginDialog); staffLoginDialogStage.initModality(Modality.APPLICATION_MODAL); staffLoginDialogStage.initOwner(staffModeButton.getScene().getWindow()); staffLoginDialogStage.setResizable(false); staffLoginDialogStage.setTitle("Authentication Required"); staffLoginDialogStage.centerOnScreen(); - staffLoginDialogStage.show(); } - - public void onGuestModeButtonClicked() { - ScreenManager.getInstance().switchToScreen( - "/fxml/GuestHomeScreen.fxml" - ); + + /** + * Shows the guest home screen. + */ + public void onGuestModeButtonClicked() { + ScreenManager.getInstance().switchToScreen("/fxml/GuestHomeScreen.fxml"); } } diff --git a/src/main/java/com/gitrekt/resort/controller/PlaceBookingScreenController.java b/src/main/java/com/gitrekt/resort/controller/PlaceBookingScreenController.java index 2b52155..b41bf0f 100644 --- a/src/main/java/com/gitrekt/resort/controller/PlaceBookingScreenController.java +++ b/src/main/java/com/gitrekt/resort/controller/PlaceBookingScreenController.java @@ -2,16 +2,14 @@ import com.gitrekt.resort.model.UsState; import java.net.URL; -import java.util.Arrays; import java.util.Locale; import java.util.ResourceBundle; - import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.fxml.FXML; import javafx.fxml.Initializable; -import javafx.scene.control.Button; import javafx.scene.control.ComboBox; +import javafx.scene.control.Label; import javafx.scene.control.TextArea; import javafx.scene.control.TextField; @@ -55,10 +53,7 @@ public class PlaceBookingScreenController implements Initializable { private TextArea specialInstructionsBox; @FXML - private Button finishButton; - - @FXML - private Button backButton; + private Label statePickerLabel; private ObservableList countries ; @@ -73,13 +68,13 @@ public void initialize(URL url, ResourceBundle rb) { initializeCountryPicker(); } - public void onBackButtonClicked() { - ScreenManager.getInstance().switchToScreen( - "/fxml/BrowseRoomsScreen.fxml" - ); + @FXML + private void onBackButtonClicked() { + ScreenManager.getInstance().switchToScreen("/fxml/BrowseRoomsScreen.fxml"); } - public void onFinishButtonClicked() { + @FXML + private void onFinishButtonClicked() { // TODO } @@ -98,7 +93,13 @@ public void initializeCountryPicker() { } public void onCountryPicked() { - // TODO: If country is US, show state picker (and it's associated label) + if(countryPicker.getValue().equals("United States")) { + statePicker.setVisible(true); + statePickerLabel.setVisible(true); + } else { + statePicker.setVisible(false); + statePickerLabel.setVisible(false); + } } public void initializeStatePicker() { @@ -107,5 +108,7 @@ public void initializeStatePicker() { states.add(state.getUnabbreviated()); } statePicker.setItems(states); + statePicker.setVisible(false); + statePickerLabel.setVisible(false); } } diff --git a/src/main/java/com/gitrekt/resort/controller/ReportsHomeScreenController.java b/src/main/java/com/gitrekt/resort/controller/ReportsHomeScreenController.java index 2cea5ce..b70d658 100644 --- a/src/main/java/com/gitrekt/resort/controller/ReportsHomeScreenController.java +++ b/src/main/java/com/gitrekt/resort/controller/ReportsHomeScreenController.java @@ -4,43 +4,42 @@ import java.util.ResourceBundle; import javafx.fxml.FXML; import javafx.fxml.Initializable; -import javafx.scene.control.Button; /** * FXML Controller class for reports home screen. */ public class ReportsHomeScreenController implements Initializable { - - @FXML - private Button backButton; - @FXML - private Button bookingPercentagesReportButton; - - @FXML - private Button feedbackReportButton; - - /** - * Initializes the controller class. - */ @Override public void initialize(URL url, ResourceBundle rb) { - // TODO + // Intentionally blank. } - public void onBackButtonClicked() { + /** + * Returns to the staff home screen. + */ + @FXML + private void onBackButtonClicked() { ScreenManager.getInstance().switchToScreen( "/fxml/StaffHomeScreen.fxml" ); } - public void onBookingPercentagesReportButtonClicked() { + /** + * Displays the booking percentages report screen. + */ + @FXML + private void onBookingPercentagesReportButtonClicked() { ScreenManager.getInstance().switchToScreen( "/fxml/BookingsReportScreen.fxml" ); } - public void onFeedbackReportButtonClicked() { + /** + * Displays the guest feedback report screen. + */ + @FXML + private void onFeedbackReportButtonClicked() { ScreenManager.getInstance().switchToScreen( "/fxml/FeedbackReportScreen.fxml" ); diff --git a/src/main/java/com/gitrekt/resort/controller/ResetEmployeePasswordDialogController.java b/src/main/java/com/gitrekt/resort/controller/ResetEmployeePasswordDialogController.java index 3c7caed..c43d26b 100644 --- a/src/main/java/com/gitrekt/resort/controller/ResetEmployeePasswordDialogController.java +++ b/src/main/java/com/gitrekt/resort/controller/ResetEmployeePasswordDialogController.java @@ -1,12 +1,20 @@ package com.gitrekt.resort.controller; +import com.gitrekt.resort.model.entities.Employee; +import com.gitrekt.resort.model.services.EmployeeService; +import com.gitrekt.resort.model.services.PasswordValidatorUtil; import java.net.URL; import java.util.ResourceBundle; import javafx.fxml.FXML; import javafx.fxml.Initializable; import javafx.scene.control.Button; +import javafx.scene.control.Label; import javafx.scene.control.TextField; import javafx.stage.Stage; +import javax.persistence.EntityNotFoundException; +import org.passay.PasswordData; +import org.passay.RuleResult; +import org.passay.RuleResultDetail; /** * Controller class for the dialog in which employees reset their password. @@ -20,43 +28,162 @@ public class ResetEmployeePasswordDialogController implements Initializable { private Button confirmButton; @FXML - private TextField authorizingManagerIdField; + private TextField newPasswordField; @FXML - private TextField authorizingManagerPasswordField; + private TextField confirmPasswordField; @FXML - private TextField confirmEmployeeIdField; + private Label errorLabel; @FXML - private TextField newPasswordField; + private Label lengthLabel; @FXML - private TextField confirmPasswordField; + private Label specialCharLabel; + + @FXML + private Label mixedCaseLabel; + + @FXML + private Label numberLabel; + + private Long employeeId; /** * Initializes the controller class. */ @Override public void initialize(URL url, ResourceBundle rb) { - // TODO + // Configure text change listeners for the two fields + newPasswordField.setOnKeyPressed( + e -> onNewPasswordFieldUpdated() + ); + confirmPasswordField.setOnKeyPressed(e -> + onConfirmPasswordFieldUpdated() + ); } - public void onCancelButtonClicked() { + /** + * Called to initialize the requisite data for the dialog. + * + * @param employeeId The employee id of the employee to reset the password + * for. + */ + public void setEmployeeId(Long employeeId) { + this.employeeId = employeeId; + } + + @FXML + private void onCancelButtonClicked() { Stage dialogStage = (Stage) cancelButton.getScene().getWindow(); dialogStage.close(); } - public void onConfirmButtonClicked() { - // TODO + @FXML + private void onConfirmButtonClicked() { + if(validatePasswords()) { + String password = newPasswordField.getText(); + changePassword(password); + closeDialog(); + } + } + + private void onConfirmPasswordFieldUpdated() { + validatePasswords(); } - public void onConfirmPasswordFieldUpdated() { - // TODO + private void onNewPasswordFieldUpdated() { + validatePasswords(); } - public void onNewPasswordFieldUpdated() { - // TODO + /** + * In addition to determining whether the passwords are valid, this method + * also handles the ui cues that let the user know what is wrong with their + * password. Maybe not the best instance of the SRP, but it's simple enough + * in practice. + * + * @return True if the passwords entered are valid. + */ + private boolean validatePasswords() { + boolean result = true; + + String newPassword = newPasswordField.getText(); + String matchingPassword = confirmPasswordField.getText(); + + // Hide any already showing errors + errorLabel.setVisible(false); + lengthLabel.getStyleClass().remove("validationErrorText"); + specialCharLabel.getStyleClass().remove("validationErrorText"); + numberLabel.getStyleClass().remove("validationErrorText"); + mixedCaseLabel.getStyleClass().remove("validationErrorText"); + + if(newPassword.isEmpty() || matchingPassword.isEmpty()) { + errorLabel.setVisible(true); + errorLabel.setText("Fields cannot be empty"); + result = false; + } + + if(!newPassword.equals(matchingPassword)) { + errorLabel.setVisible(true); + errorLabel.setText("Passwords don't match"); + result = false; + } + + RuleResult ruleResult = PasswordValidatorUtil.validator + .validate(new PasswordData(newPassword)); + + if(!ruleResult.isValid()) { + result = false; + } + + // Update the requirements labels + for(RuleResultDetail d : ruleResult.getDetails()) { + String errorCode = d.getErrorCode(); + switch(errorCode) { + case "TOO_SHORT": + lengthLabel.getStyleClass().add("validationErrorText"); + break; + case "INSUFFICIENT_LOWERCASE": + case "INSUFFICIENT_UPPERCASE": + mixedCaseLabel.getStyleClass().add("validationErrorText"); + break; + case "INSUFFICIENT_DIGIT": + numberLabel.getStyleClass().add("validationErrorText"); + break; + case "INSUFFICIENT_SPECIAL": + specialCharLabel.getStyleClass().add("validationErrorText"); + break; + } + } + + return result; + } + + /** + * Handles the actual act of changing the user's password. + * + * @param newPassword The password, which should be already validated. + */ + private void changePassword(String newPassword) { + // Ensure that we have an employee to change the password for + if(this.employeeId == null) { + throw new IllegalStateException("Must initialize employeeId"); + } + + EmployeeService employeeService = new EmployeeService(); + Employee employee = employeeService.getEmployeeById(this.employeeId); + try { + employee.setPassword(newPassword); + employeeService.updateEmployee(employee); + } catch (EntityNotFoundException e) { + // TODO + } + } + + private void closeDialog() { + Stage dialogStage = (Stage) this.lengthLabel.getScene().getWindow(); + dialogStage.close(); } } diff --git a/src/main/java/com/gitrekt/resort/controller/ScreenManager.java b/src/main/java/com/gitrekt/resort/controller/ScreenManager.java index 0f95b49..33fbf75 100644 --- a/src/main/java/com/gitrekt/resort/controller/ScreenManager.java +++ b/src/main/java/com/gitrekt/resort/controller/ScreenManager.java @@ -13,12 +13,12 @@ public class ScreenManager { private static Stage mainStage; - private final FXMLLoader fxmlLoader; + private FXMLLoader fxmlLoader; private static ScreenManager instance; private ScreenManager() { - fxmlLoader = new FXMLLoader(); + //fxmlLoader = new FXMLLoader(); } /** @@ -48,19 +48,23 @@ public void initialize(Stage mainStage) { /** * @param fxmlPath The path to the FXML file of the screen to switch to. + * + * @return The FXML controller of the new scene. */ - public void switchToScreen(String fxmlPath) { + public Object switchToScreen(String fxmlPath) { Parent newScreenRoot; try { URL pathToFxml = getClass().getResource(fxmlPath); - newScreenRoot = fxmlLoader.load(pathToFxml); + fxmlLoader = new FXMLLoader(pathToFxml); + newScreenRoot = fxmlLoader.load(); } catch (IOException e) { throw new IllegalArgumentException("Failed to load FXML", e); } mainStage.getScene().setRoot(newScreenRoot); mainStage.setMaximized(true); + return fxmlLoader.getController(); } /** diff --git a/src/main/java/com/gitrekt/resort/controller/SelectedRoomListItemController.java b/src/main/java/com/gitrekt/resort/controller/SelectedRoomListItemController.java new file mode 100644 index 0000000..642738d --- /dev/null +++ b/src/main/java/com/gitrekt/resort/controller/SelectedRoomListItemController.java @@ -0,0 +1,60 @@ +package com.gitrekt.resort.controller; + +import com.gitrekt.resort.model.RoomSearchResult; +import java.net.URL; +import java.util.ResourceBundle; +import javafx.fxml.FXML; +import javafx.fxml.Initializable; +import javafx.scene.control.Label; + +/** + * FXML controller class for selected room list items. + */ +public class SelectedRoomListItemController implements Initializable { + + @FXML + private Label listItemText; + + private BrowseRoomsScreenController parentController; + + private RoomSearchResult roomData; + + @Override + public void initialize(URL url, ResourceBundle rb) { + // Intentionally blank. + } + + /** + * Sets a reference to the FXML controller class for the screen that contains the list that + * this list item is in. + * + * @param parentController The controller class for the screen that controls this list. + */ + public void setParentController(BrowseRoomsScreenController parentController) { + this.parentController = parentController; + } + + /** + * Sets the room data for this list item to store. + * + * This data also determines the label's text. + * + * @param roomData The RoomSearchResult data to store in the item. + */ + public void setData(RoomSearchResult roomData) { + this.roomData = roomData; + this.listItemText.setText(roomData.getRoomCategory().getName()); + } + + /** + * Notifies the parent controller that this item should be removed from the parent list. + * + * Also, increments the count for the number of rooms of this category available. + */ + @FXML + private void onDeleteButtonClicked() { + roomData.setNumAvailable(roomData.getNumAvailable() + 1); + parentController.onRoomUnselected(roomData); + } + +} diff --git a/src/main/java/com/gitrekt/resort/controller/StaffAccountsScreenController.java b/src/main/java/com/gitrekt/resort/controller/StaffAccountsScreenController.java index 298f910..3b0373f 100644 --- a/src/main/java/com/gitrekt/resort/controller/StaffAccountsScreenController.java +++ b/src/main/java/com/gitrekt/resort/controller/StaffAccountsScreenController.java @@ -1,91 +1,196 @@ package com.gitrekt.resort.controller; +import com.gitrekt.resort.model.entities.Employee; +import com.gitrekt.resort.model.services.EmployeeService; import java.io.IOException; import java.net.URL; +import java.util.List; +import java.util.Optional; import java.util.ResourceBundle; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleStringProperty; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; import javafx.fxml.FXML; import javafx.fxml.FXMLLoader; import javafx.fxml.Initializable; import javafx.scene.Parent; import javafx.scene.Scene; +import javafx.scene.control.Alert; +import javafx.scene.control.Alert.AlertType; import javafx.scene.control.Button; -import javafx.scene.image.Image; +import javafx.scene.control.ButtonType; +import javafx.scene.control.Label; +import javafx.scene.control.TableColumn; +import javafx.scene.control.TableView; +import javafx.scene.control.cell.CheckBoxTableCell; import javafx.stage.Modality; import javafx.stage.Stage; /** - * FXML Controller class. + * FXML Controller class for the "Manage Staff Accounts" screen. */ public class StaffAccountsScreenController implements Initializable { - @FXML - private Button backButton; - - @FXML - private Button removeEmployeeButton; - @FXML private Button resetEmployeePasswordButton; - + @FXML private Button addNewEmployeeButton; - - private final Image appLogo = new Image("images/Logo.png"); - + + @FXML + private TableView staffAccountsTableView; + + @FXML + private TableColumn employeeNameColumn; + + @FXML + private TableColumn isManagerColumn; + + @FXML + private TableColumn employeeIdColumn; + + private ObservableList staffAccountsList; + /** * Initializes the controller class. */ @Override public void initialize(URL url, ResourceBundle rb) { - // TODO - } - - public void onBackButtonClicked() { - ScreenManager.getInstance().switchToScreen( - "/fxml/StaffHomeScreen.fxml" + staffAccountsList = FXCollections.observableArrayList(); + staffAccountsTableView.setItems(staffAccountsList); + + employeeIdColumn.setCellValueFactory((param) -> { + return new SimpleStringProperty( + String.valueOf(param.getValue().getId()) + ); + }); + + employeeNameColumn.setCellValueFactory((param) -> { + return new SimpleStringProperty( + param.getValue().getLastName() + ", " + param.getValue().getFirstName() + ); + }); + + isManagerColumn.setCellValueFactory((param) -> { + return new SimpleBooleanProperty(param.getValue().isManager()); + }); + + // Display the boolean column using checkboxes instead of strings + isManagerColumn.setCellFactory( + (param) -> { + return new CheckBoxTableCell<>(); + } ); + + staffAccountsTableView.setPlaceholder( + new Label("We fired everyone") + ); + + fetchTableData(); + } + + /** + * Returns to the previous screen. + */ + @FXML + private void onBackButtonClicked() { + ScreenManager.getInstance().switchToScreen("/fxml/StaffHomeScreen.fxml"); } - - public void onRemoveEmployeeButtonClicked() { - // TODO + + /** + * Displays the dialog to delete the currently selected employee account. + */ + @FXML + private void onRemoveEmployeeButtonClicked() { + Employee selectedEmployee = getSelectedEmployee(); + EmployeeService employeeService = new EmployeeService(); + + Alert alert = new Alert(AlertType.CONFIRMATION); + alert.setTitle("Remove Employee Confirmation"); + alert.setHeaderText("Warning"); + alert.setContentText("Do you wish to remove selected employee?"); + + Optional result = alert.showAndWait(); + if(result.get() == ButtonType.OK){ + employeeService.deleteEmployee(selectedEmployee); + + // TODO REMOVE TEST CODE + List employees = employeeService.getAllEmployees(); + for(Employee employ : employees) { + System.out.println("After delete we found: " + employ.getId()); + } + + staffAccountsList.remove(selectedEmployee); + } } - - public void onResetEmployeePasswordButtonClicked() throws IOException { + + /** + * Displays the reset password dialog for the currently selected employee. + * + * @throws IOException + */ + @FXML + private void onResetEmployeePasswordButtonClicked() throws IOException { Stage dialogStage = new Stage(); - dialogStage.getIcons().add(appLogo); - Parent dialogRoot = FXMLLoader.load( + FXMLLoader loader = new FXMLLoader( getClass().getResource("/fxml/ResetEmployeePasswordDialog.fxml") ); + Parent dialogRoot = loader.load(); Scene resetPasswordDialog = new Scene(dialogRoot); + dialogStage.setScene(resetPasswordDialog); dialogStage.initModality(Modality.APPLICATION_MODAL); - dialogStage.initOwner( - resetEmployeePasswordButton.getScene().getWindow() - ); + dialogStage.initOwner(resetEmployeePasswordButton.getScene().getWindow()); dialogStage.setResizable(false); - dialogStage.setTitle("Authentication Required"); + dialogStage.setTitle("Confirm"); dialogStage.centerOnScreen(); - + + ResetEmployeePasswordDialogController c; + c = (ResetEmployeePasswordDialogController) loader.getController(); + long employeeId = getSelectedEmployee().getId(); + c.setEmployeeId(employeeId); + dialogStage.show(); } - - public void onAddNewEmployeeButtonClicked() throws IOException { + + /** + * Displays the dialog to create a new employee account. + * + * @throws IOException + */ + @FXML + private void onAddNewEmployeeButtonClicked() throws IOException { Stage dialogStage = new Stage(); - dialogStage.getIcons().add(appLogo); Parent dialogRoot = FXMLLoader.load( getClass().getResource("/fxml/CreateStaffAccountDialog.fxml") ); Scene createStaffAccountDialog = new Scene(dialogRoot); dialogStage.setScene(createStaffAccountDialog); dialogStage.initModality(Modality.APPLICATION_MODAL); - dialogStage.initOwner( - addNewEmployeeButton.getScene().getWindow() - ); + dialogStage.initOwner(addNewEmployeeButton.getScene().getWindow()); dialogStage.setResizable(false); - dialogStage.setTitle("Authentication Required"); + dialogStage.setTitle("Create employee"); dialogStage.centerOnScreen(); - dialogStage.show(); } - + + /** + * @return The currently selected employee in the employee table view. + */ + private Employee getSelectedEmployee() { + Employee selectedEmployee = staffAccountsTableView.getSelectionModel().getSelectedItem(); + return selectedEmployee; + } + + /** + * Retrieves the employee data from the database and populates the tableview + * with it. + */ + private void fetchTableData() { + EmployeeService employeeService = new EmployeeService(); + staffAccountsList.addAll(employeeService.getAllEmployees()); + employeeService.cleanup(); + } + } diff --git a/src/main/java/com/gitrekt/resort/controller/StaffHomeScreenController.java b/src/main/java/com/gitrekt/resort/controller/StaffHomeScreenController.java index d1b3924..2342172 100644 --- a/src/main/java/com/gitrekt/resort/controller/StaffHomeScreenController.java +++ b/src/main/java/com/gitrekt/resort/controller/StaffHomeScreenController.java @@ -1,5 +1,6 @@ package com.gitrekt.resort.controller; +import com.gitrekt.resort.model.services.EmployeeService; import java.net.URL; import java.util.ResourceBundle; import javafx.fxml.FXML; @@ -10,19 +11,19 @@ * FXML Controller class for the staff home screen. */ public class StaffHomeScreenController implements Initializable { - + @FXML private Button backButton; @FXML private Button registryButton; - + @FXML private Button viewReportsButton; - + @FXML private Button editPricesButton; - + @FXML private Button manageStaffAccountsButton; @@ -31,34 +32,31 @@ public class StaffHomeScreenController implements Initializable { */ @Override public void initialize(URL url, ResourceBundle rb) { - // TODO + if(!EmployeeService.isManagerLoggedIn) { + viewReportsButton.setDisable(true); + editPricesButton.setDisable(true); + manageStaffAccountsButton.setDisable(true); + } } - + public void onRegistryButtonClicked() { - ScreenManager.getInstance().switchToScreen( - "/fxml/GuestRegistryScreen.fxml" - ); + ScreenManager.getInstance().switchToScreen("/fxml/GuestRegistryScreen.fxml"); } - + public void onViewReportsButtonClicked() { - ScreenManager.getInstance().switchToScreen( - "/fxml/ReportsHomeScreen.fxml" - ); + ScreenManager.getInstance().switchToScreen("/fxml/ReportsHomeScreen.fxml"); } - + public void onEditPricesButtonClicked() { // TODO } - + public void onManageStaffAccountsButtonClicked() { - ScreenManager.getInstance().switchToScreen( - "/fxml/StaffAccountsScreen.fxml" - ); + ScreenManager.getInstance().switchToScreen("/fxml/StaffAccountsScreen.fxml"); } - + public void onBackButtonClicked() { - ScreenManager.getInstance().switchToScreen( - "/fxml/HomeScreen.fxml" - ); + EmployeeService.isManagerLoggedIn = false; + ScreenManager.getInstance().switchToScreen("/fxml/HomeScreen.fxml"); } } diff --git a/src/main/java/com/gitrekt/resort/controller/StaffLoginDialogController.java b/src/main/java/com/gitrekt/resort/controller/StaffLoginDialogController.java index 096eccb..219ae2c 100644 --- a/src/main/java/com/gitrekt/resort/controller/StaffLoginDialogController.java +++ b/src/main/java/com/gitrekt/resort/controller/StaffLoginDialogController.java @@ -1,5 +1,6 @@ package com.gitrekt.resort.controller; +import com.gitrekt.resort.model.entities.Employee; import com.gitrekt.resort.model.services.EmployeeService; import com.gitrekt.resort.model.services.EmployeeService.AuthenticationResult; import java.io.IOException; @@ -12,7 +13,7 @@ import javafx.scene.control.PasswordField; import javafx.scene.control.TextField; import javafx.stage.Stage; - + /** * FXML Controller class for the staff login dialog. */ @@ -20,27 +21,27 @@ public class StaffLoginDialogController implements Initializable { @FXML private Button cancelButton; - + @FXML private Button loginButton; - + @FXML private PasswordField passwordField; - + @FXML private TextField employeeIdField; - + @FXML private Label errorLabel; - + /** * Initializes the controller class. */ @Override public void initialize(URL url, ResourceBundle rb) { - // TODO + // Intentionally blank. } - + /** * Closes the dialog. */ @@ -48,11 +49,11 @@ public void onCancelButtonClicked() { Stage dialogStage = (Stage) cancelButton.getScene().getWindow(); dialogStage.close(); } - + /** * Authenticates the user and takes appropriate action. - * - * @throws IOException because I'm too lazy to fix it right now + * + * @throws IOException */ public void onLoginButtonClicked() throws IOException { // Hide any previously displayed errors @@ -68,10 +69,13 @@ public void onLoginButtonClicked() throws IOException { EmployeeService employeeService = new EmployeeService(); Long id = new Long(employeeIdField.getText()); String password = passwordField.getText(); - AuthenticationResult authResult - = employeeService.authenticate(id, password); + AuthenticationResult authResult = employeeService.authenticate(id, password); switch(authResult) { case SUCCESS: + Employee loggedInEmployee = employeeService.getEmployeeById(id); + if(loggedInEmployee.isManager()) { + EmployeeService.isManagerLoggedIn = true; + } showStaffHomeScreen(); break; case FAILURE: @@ -81,15 +85,16 @@ public void onLoginButtonClicked() throws IOException { } employeeService.cleanup(); } - + + /** + * Should be called after a successful authentication. + */ private void showStaffHomeScreen() { Stage dialogStage = (Stage) loginButton.getScene().getWindow(); - ScreenManager.getInstance().switchToScreen( - "/fxml/StaffHomeScreen.fxml" - ); + ScreenManager.getInstance().switchToScreen("/fxml/StaffHomeScreen.fxml"); dialogStage.close(); } - + private boolean validateFormFields() { // Ensure fields are not empty if(employeeIdField.getText().isEmpty()) { @@ -107,5 +112,5 @@ private boolean validateFormFields() { // If we're here, the fields are valid return true; } - + } diff --git a/src/main/java/com/gitrekt/resort/model/GuestBill.java b/src/main/java/com/gitrekt/resort/model/GuestBill.java deleted file mode 100644 index dc56fa6..0000000 --- a/src/main/java/com/gitrekt/resort/model/GuestBill.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.gitrekt.resort.model; - -/** - * TODO: Document. - */ -public class GuestBill { - - // TODO: Implement. - -} diff --git a/src/main/java/com/gitrekt/resort/model/RoomSearchResult.java b/src/main/java/com/gitrekt/resort/model/RoomSearchResult.java index 240ccca..9d5945f 100644 --- a/src/main/java/com/gitrekt/resort/model/RoomSearchResult.java +++ b/src/main/java/com/gitrekt/resort/model/RoomSearchResult.java @@ -1,37 +1,36 @@ package com.gitrekt.resort.model; import com.gitrekt.resort.model.entities.RoomCategory; -import java.math.BigDecimal; -/** - * TODO: Document entire class. - */ public class RoomSearchResult { - private String roomNumber; + private final RoomCategory roomCategory; - private BigDecimal roomPrice; + private final Double roomPrice; - private RoomCategory roomCategory; + private int numberAvailable; - public RoomSearchResult( - String roomNumber, BigDecimal roomPrice, RoomCategory roomCategory - ) { - this.roomNumber = roomNumber; - this.roomPrice = roomPrice; + public RoomSearchResult(double roomPrice, RoomCategory roomCategory, + int numAvailable) { this.roomCategory = roomCategory; + this.roomPrice = roomPrice; + this.numberAvailable = numAvailable; } public RoomCategory getRoomCategory() { return roomCategory; } - public BigDecimal getRoomPrice() { + public Double getRoomPrice() { return roomPrice; } - public String getRoomNumber() { - return roomNumber; + public int getNumAvailable() { + return numberAvailable; + } + + public void setNumAvailable(int numAvailable) { + this.numberAvailable = numAvailable; } } diff --git a/src/main/java/com/gitrekt/resort/model/StaffMember.java b/src/main/java/com/gitrekt/resort/model/StaffMember.java deleted file mode 100644 index b161f47..0000000 --- a/src/main/java/com/gitrekt/resort/model/StaffMember.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.gitrekt.resort.model; - -/** - * TODO: Document. - */ -public class StaffMember { - - // TODO: Implement. - -} diff --git a/src/main/java/com/gitrekt/resort/model/entities/Booking.java b/src/main/java/com/gitrekt/resort/model/entities/Booking.java index 5cb48be..6059cea 100644 --- a/src/main/java/com/gitrekt/resort/model/entities/Booking.java +++ b/src/main/java/com/gitrekt/resort/model/entities/Booking.java @@ -23,10 +23,10 @@ public class Booking { @ManyToOne(cascade = CascadeType.MERGE) private Guest guest; - + @Temporal(TemporalType.DATE) private Date checkInDate; - + @Temporal(TemporalType.DATE) private Date checkOutDate; @@ -35,14 +35,14 @@ public class Booking { private String specialInstructions; - @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true) + @OneToMany(cascade = CascadeType.MERGE, orphanRemoval = true) private List packages; @OneToMany(cascade = CascadeType.MERGE) private List bookedRooms; - + private boolean isCanceled = false; - + @CreationTimestamp @Temporal(TemporalType.DATE) private Date createdDate; @@ -55,7 +55,7 @@ protected Booking() { } public Booking(Guest guest, Date checkInDate, Date checkOutDate, Bill bill, - String specialInstructions, List packages, + String specialInstructions, List packages, List bookedRooms) { this.guest = guest; @@ -90,15 +90,15 @@ public List getPackages() { public List getBookedRooms() { return bookedRooms; } - + public boolean isCanceled() { return isCanceled; } - + public Date getCreatedDate() { return createdDate; } - + public void setCanceled(boolean canceled) { this.isCanceled = canceled; } @@ -106,14 +106,14 @@ public void setCanceled(boolean canceled) { public Guest getGuest() { return guest; } - + /** * @return The booking id, which is currently being used as the confirmation - * number until a scheme for generating confirmation numbers in the + * number until a scheme for generating confirmation numbers in the * database can be properly devised. */ public Long getId() { return this.id; } - + } diff --git a/src/main/java/com/gitrekt/resort/model/entities/Employee.java b/src/main/java/com/gitrekt/resort/model/entities/Employee.java index eb29d7e..0237404 100644 --- a/src/main/java/com/gitrekt/resort/model/entities/Employee.java +++ b/src/main/java/com/gitrekt/resort/model/entities/Employee.java @@ -62,8 +62,8 @@ public void setManager(boolean isManager) { this.isManager = isManager; } - public void setHashedPassword(String hashedPassword){ - this.hashedPassword = hashedPassword; + public void setPassword(String plaintextPassword){ + encryptPassword(plaintextPassword); } private void encryptPassword(String plaintextPassword) { diff --git a/src/main/java/com/gitrekt/resort/model/entities/Guest.java b/src/main/java/com/gitrekt/resort/model/entities/Guest.java index 239533d..00105b0 100644 --- a/src/main/java/com/gitrekt/resort/model/entities/Guest.java +++ b/src/main/java/com/gitrekt/resort/model/entities/Guest.java @@ -94,5 +94,6 @@ public boolean isCheckedIn() { public Long getId(){ return id; - } + } + } diff --git a/src/main/java/com/gitrekt/resort/model/entities/RoomCategory.java b/src/main/java/com/gitrekt/resort/model/entities/RoomCategory.java index 6fc0b18..63934db 100644 --- a/src/main/java/com/gitrekt/resort/model/entities/RoomCategory.java +++ b/src/main/java/com/gitrekt/resort/model/entities/RoomCategory.java @@ -1,9 +1,9 @@ package com.gitrekt.resort.model.entities; import javafx.scene.image.Image; +import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Id; -import javax.persistence.Transient; /** * The type of room, containing properties such as the number of beds, @@ -27,21 +27,15 @@ public class RoomCategory { @Id private String name; + @Column(length = 1000) private String description; - // This should not be persisted to the database until we figure out - // what out final solution for storing images is. I'm currently thinking - // the for the purposes of our prototype we can just store the filepath - // string of the image in the database. - @Transient - private Image roomCategoryImage; - - // I really don't think our domain model is complicated enough that we need - // a dedicated Bed class, and a BedType enum to represent beds in our rooms. - // For now, we can just store a string. If things change, it won't be hard - // to added in a concrete type for beds in a room. + private Double basePrice; + private String bedsInfo; + private String imageFilePath; + /** * DO NOT CALL THIS CONSTRUCTOR. IT EXISTS ONLY BECAUSE IT IS REQUIRED BY * HIBERNATE. @@ -51,11 +45,12 @@ public class RoomCategory { } public RoomCategory(String name, String description, - Image roomCategoryImage, String bedsInfo) { + String imagePath, String bedsInfo, Double basePrice) { this.name = name; this.description = description; - this.roomCategoryImage = roomCategoryImage; this.bedsInfo = bedsInfo; + this.basePrice = basePrice; + this.imageFilePath = imagePath; } public String getName() { @@ -66,15 +61,31 @@ public String getDescription() { return description; } - // We may want to modify this so that we use S3 to fetch images, but that - // is a future problem that we don't need to solve anytime soon. This is - // good enough for now. + /** + * The image representing this room category, based on the file path string + * provided when the category was created. + */ public Image getImage() { - return roomCategoryImage; + return new Image(this.imageFilePath); } public String getBedsInfo() { return bedsInfo; } + /** + * DANGER! + * + * This method only gives you the base price of a room, which is just a part + * of what goes into the pricing of a room. Other factors like resort + * capacity, etc. affect this price. This method should only be used to + * calculate the final price of the room within the appropriate service + * class. + * + * @return The base price of the room. + */ + public Double getBasePrice() { + return basePrice; + } + } diff --git a/src/main/java/com/gitrekt/resort/model/services/BillPdfGenerator.java b/src/main/java/com/gitrekt/resort/model/services/BillPdfGenerator.java index c043b58..5a12095 100644 --- a/src/main/java/com/gitrekt/resort/model/services/BillPdfGenerator.java +++ b/src/main/java/com/gitrekt/resort/model/services/BillPdfGenerator.java @@ -13,6 +13,12 @@ import org.apache.pdfbox.pdmodel.font.PDFont; import org.apache.pdfbox.pdmodel.font.PDType1Font; +/** + * Responsible for generating a PDF representation of the guest bill. + * + * Currently very crude and incomplete, although the most basic functionality + * is in place and usable. This class still needs a lot of work. + */ public class BillPdfGenerator { private static final String LINE_ITEM_FORMAT = "%-40s %11s %13s %13s"; @@ -105,6 +111,14 @@ private String getBillTotalLine() { ); } + /** + * Gathers the fields from the guest bill items and converts them into their + * formatted string representation to be displayed as a single line-item + * on the printed guest bill. + * + * @param item The bill item to format. + * @return The formatted bill item. + */ private String convertBillItemToLineItem(BillItem item) { String lineItem; double totalPrice = item.getPrice() * item.getQuantity(); diff --git a/src/main/java/com/gitrekt/resort/model/services/BillService.java b/src/main/java/com/gitrekt/resort/model/services/BillService.java index 5125fc4..9552003 100644 --- a/src/main/java/com/gitrekt/resort/model/services/BillService.java +++ b/src/main/java/com/gitrekt/resort/model/services/BillService.java @@ -1,8 +1,6 @@ - package com.gitrekt.resort.model.services; -import com.gitrekt.resort.model.entities.Bill; -import com.gitrekt.resort.model.entities.BillItem; +import com.gitrekt.resort.model.entities.Booking; import java.awt.print.PrinterException; import java.awt.print.PrinterJob; import java.io.IOException; @@ -10,24 +8,44 @@ import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.printing.PDFPageable; +/** + * This class is responsible for handling all business logic related to bills. + * + * At the moment, the only real business logic that belongs here is printing + * related, though this may change in the future as features are implemented. + */ public class BillService { - public void printBill(Bill bill) throws IOException, PrinterException { - // TODO: Remove test code; - for(int i = 0; i < 20; i++) { - bill.getCharges().add(new BillItem("Test bill item name printed here", 15.52623, 3)); - } + /** + * Prints the bill associated with the provided booking on a printer. + * + * @param booking The booking to print the bill for. + * + * @throws IOException + * @throws PrinterException + */ + public void printBillForBooking(Booking booking) + throws IOException, PrinterException { - BillPdfGenerator pdfGenerator = new BillPdfGenerator(bill); - PDDocument pdf = pdfGenerator.getBillAsPdf(); - - PrinterJob job = PrinterJob.getPrinterJob(); - job.setPrintService(choosePrinter()); - job.setPageable(new PDFPageable(pdf)); - job.print(); - pdf.close(); + BillPdfGenerator pdfGenerator = new BillPdfGenerator(booking.getBill()); + try (PDDocument pdf = pdfGenerator.getBillAsPdf()) { + PrinterJob job = PrinterJob.getPrinterJob(); + job.setPrintService(choosePrinter()); + job.setPageable(new PDFPageable(pdf)); + job.print(); + } } + /** + * Prompts the user to choose a printer to print from, using a standard + * dialog box. + * + * The user is also able to selected from other properties such as the + * number of copies to print, collation, etc., independently of our + * software. + * + * @return The PrintService (a.k.a the printer) selected by the user. + */ public static PrintService choosePrinter() { PrinterJob printJob = PrinterJob.getPrinterJob(); if(printJob.printDialog()) { diff --git a/src/main/java/com/gitrekt/resort/model/services/BookingService.java b/src/main/java/com/gitrekt/resort/model/services/BookingService.java index 048331d..1f72bfc 100644 --- a/src/main/java/com/gitrekt/resort/model/services/BookingService.java +++ b/src/main/java/com/gitrekt/resort/model/services/BookingService.java @@ -1,18 +1,21 @@ package com.gitrekt.resort.model.services; import com.gitrekt.resort.hibernate.HibernateUtil; +import com.gitrekt.resort.model.RoomSearchResult; import com.gitrekt.resort.model.entities.BillItem; import com.gitrekt.resort.model.entities.Booking; +import com.gitrekt.resort.model.entities.Room; +import com.gitrekt.resort.model.entities.RoomCategory; import java.time.Instant; import java.time.LocalDate; import java.time.ZoneId; import java.time.ZonedDateTime; import java.time.temporal.ChronoUnit; +import java.util.ArrayList; import java.util.Date; import java.util.List; import javax.mail.MessagingException; import javax.persistence.EntityManager; -import javax.persistence.PersistenceContext; import javax.persistence.PersistenceException; import javax.persistence.Query; @@ -21,16 +24,14 @@ */ public class BookingService { - @PersistenceContext private final EntityManager entityManager; /** * Constructs a basic instance of BookingService. - * - * Initializes an entityManager for the service, which is used to manage - * Hibernate entities for database persistence. This entityManager is later - * closed by the finalize method and so does not need to (nor can it be) - * explicitly closed. + * + * Initializes an entityManager for the service, which is used to manage Hibernate entities for + * database persistence. This entityManager is later closed by the finalize method and so does + * not need to (nor can it be) explicitly closed. */ public BookingService() { this.entityManager = HibernateUtil.getEntityManager(); @@ -38,8 +39,8 @@ public BookingService() { /** * Takes care of closing the Hibernate entityManager for the class. - * - * @throws Throwable + * + * @throws Throwable */ @Override public void finalize() throws Throwable { @@ -49,15 +50,13 @@ public void finalize() throws Throwable { /** * Stores a new booking in the database. - * - * Currently handles no other business logic besides this simple store - * operation. This could change in the future before v1.0 release, before - * the service interface is locked down. - * - * Also sends a confirmation email to the user with the booking number for - * future reference. - * - * @param booking The booking object to persist to the data store. + * + * Currently handles no other business logic besides this simple store operation. This could + * change in the future before v1.0 release, before the service interface is locked down. + * + * Also sends a confirmation email to the user with the booking number for future reference. + * + * @param booking The booking object to persist to the data store. */ public void createBooking(Booking booking) { try { @@ -73,23 +72,22 @@ public void createBooking(Booking booking) { } /** - * @param id The unique id of the booking to return. - * + * @param id The unique id of the booking to return from the database. + * * @return A booking with the provided id. */ public Booking getBookingById(Long id) { - Booking booking = entityManager.getReference(Booking.class, id); - return booking; + return entityManager.getReference(Booking.class, id); } - + /** - * Updates the booking instance stored in the database with the values from - * the passed booking instance. - * - * This method should be used only when no existing service method exists - * that better suits the specific task, due to the benefits gained by finer - * transaction control and exception handling. - * + * Updates the booking instance stored in the database with the values from the passed booking + * instance. + * + * This method should be used only when no existing service method exists that better suits the + * specific task, due to the benefits gained by finer transaction control and exception + * handling. + * * @param booking The booking to update. */ public void updateBooking(Booking booking){ @@ -103,100 +101,90 @@ public void updateBooking(Booking booking){ throw e; } } - + /** * @param startDate The beginning of the date range to search. - * + * * @param endDate The end of the date range to search. - * - * @return A list of all bookings in the system where the check-in date - * falls between the provided date range. Does not return bookings which - * have been canceled. + * + * @return A list of all bookings in the system where the check-in date falls between the + * provided date range. Does not return bookings which have been canceled. */ - public List getBookingsBetweenDates(Date startDate, Date endDate) { - String queryString = + public List getBookingsBetweenDates(Date startDate, Date endDate) { + String queryString = "FROM Booking WHERE (checkInDate BETWEEN :startDate AND :endDate)" - + "OR (checkOutDate BETWEEN :startDate AND :endDate)"; + + "OR (checkOutDate BETWEEN :startDate AND :endDate)"; Query q = entityManager.createQuery(queryString); q.setParameter("startDate", startDate); q.setParameter("endDate", endDate); - - List results = q.getResultList(); - return results; + + return q.getResultList(); } - + /** - * Cancels the provided booking, which includes applying the business - * requirement to assess a cancellation fee on the bill. - * - * If provided a booking where the isCanceled field is already set to true, - * this method simply returns without doing any other work. This behavior - * can be used to implement custom cancellation logic if desired, and - * avoids the overhead of having to consult the database to resolve the - * discrepancy or doing weird stuff to control the field more strictly. - * + * Cancels the provided booking, which includes applying the business requirement to assess a + * cancellation fee on the bill. + * + * If provided a booking where the isCanceled field is already set to true, this method simply + * returns without doing any other work. This behavior can be used to implement custom + * cancellation logic if desired, and avoids the overhead of having to consult the database to + * resolve the discrepancy or doing weird stuff to control the field more strictly. + * * @param booking The booking to cancel. */ public void cancelBooking(Booking booking) { if(booking.isCanceled()) { return; - // TODO: Handle/Log/Consider some kind of error message } - + try { entityManager.getTransaction().begin(); entityManager.merge(booking); booking.setCanceled(true); double cancellationFee = calcCancellationFee(booking); - BillItem refund = new BillItem( - "Refund", booking.getBill().getTotal() * -1, 1 - ); + BillItem refund = new BillItem("Refund", booking.getBill().getTotal() * -1, 1); booking.getBill().getCharges().add(refund); - BillItem cancellationCharge = new BillItem( - "Cancellation fee", cancellationFee, 1 - ); + BillItem cancellationCharge = new BillItem("Cancellation fee", cancellationFee, 1); booking.getBill().getCharges().add(cancellationCharge); sendBookingCancellationEmail(booking); entityManager.getTransaction().commit(); } catch (PersistenceException e) { entityManager.getTransaction().rollback(); - // TODO: Handle exception behavior + // TODO: Handle } - + } - + /** - * Calculates and returns the cancellation fee that should be assessed when - * a user cancels a booking. - * - * This method uses the new Java 8 Time API, which a little confusing at - * first, but way more useful (and easier to use) than the nightmarish way - * things used to be done. - * - * It does NOT account for time zones, since in the real world, this code - * would be running server side, not client side, and then time zones would - * not matter. - * - * Sadly, the new Java Time and Date API is almost laughably annoying in - * that there is NO built in easy way to transform an existing Java Date - * object into a new time api date object, excepting the toInstant method, - * which brings with it it's own cluttered bag of complications. Why?!? - * + * Calculates and returns the cancellation fee that should be assessed when a user cancels a + * booking. + * + * This method uses the new Java 8 Time API, which a little confusing at first, but way more + * useful (and easier to use) than the nightmarish way things used to be done. + * + * It does NOT account for time zones, since in the real world, this code would be running + * server side, not client side, and then time zones would not matter. + * + * Sadly, the new Java Time and Date API is almost laughably annoying in that there is NO built + * in easy way to transform an existing Java Date object into a new time api date object, + * excepting the toInstant method, which brings with it it's own cluttered bag of complications. + * Why?!? + * * @param booking The booking to calculate cancellation fees for. - * + * * @return The cancellation fee to cancel the provided booking. */ - public static double calcCancellationFee(Booking booking) { + public static double calcCancellationFee(Booking booking) { // Define some useful dates for our calculations LocalDate today = LocalDate.now(); - Instant temp = booking.getCheckInDate().toInstant(); - ZonedDateTime zdt = temp.atZone(ZoneId.systemDefault()); + Date temp = booking.getCheckInDate(); + ZonedDateTime zdt = Instant.ofEpochMilli(temp.getTime()).atZone(ZoneId.systemDefault()); LocalDate checkInDate = zdt.toLocalDate(); - temp = booking.getCreatedDate().toInstant(); - zdt = temp.atZone(ZoneId.systemDefault()); + temp = booking.getCreatedDate(); + zdt = Instant.ofEpochMilli(temp.getTime()).atZone(ZoneId.systemDefault()); LocalDate bookingCreationDate = LocalDate.from(zdt); long daysTillCheckIn = today.until(checkInDate, ChronoUnit.DAYS); - + // Determine the fee percentage based on specifications double feePercentage; if(bookingCreationDate.plusDays(2).isAfter(today)) { @@ -208,10 +196,10 @@ public static double calcCancellationFee(Booking booking) { } else { feePercentage = 60.00; } - + // Calculate the fee from the bill and the fee percentage double fee = 0.00; - + for(BillItem item : booking.getBill().getCharges()) { double totalItemPrice = item.getTotalPrice(); // Check for cases where another discount is applied, etc. @@ -219,28 +207,26 @@ public static double calcCancellationFee(Booking booking) { fee += (totalItemPrice * feePercentage); } } - + return fee; } - + /** - * Sends an email to the user confirming their booking, along with a booking - * number that is used to reference the booking by the customer. - * - * This method probably belongs in it's own class, but for now, this is - * good enough for our needs. - * - * At some point, it would be nice to use a parameterized template system - * like ThymeLeaf or something similar, rather than the clunky StringBuilder - * approach that is currently used, as well as HTTP email rather than - * plaintext, but these enhancements are reserved for a future where I don't - * have 3 major projects running concurrently. - * - * Also, finally note that the exception handling here is basically - * nonexistent, due to the extremely limited time we have to develop this - * prototype. This is something that would need to be addressed in future - * iterations. - * + * Sends an email to the user confirming their booking, along with a booking number that is + * used to reference the booking by the customer. + * + * This method probably belongs in it's own class, but for now, this is good enough for our + * needs. + * + * At some point, it would be nice to use a parameterized template system like ThymeLeaf or + * something similar, rather than the clunky StringBuilder approach that is currently used, as + * well as HTML email rather than plaintext, but these enhancements are reserved for a future + * where I don't have 3 major projects running concurrently. + * + * Also, finally note that the exception handling here is basically nonexistent, due to the + * extremely limited time we have to develop this prototype. This is something that would need + * to be addressed in future iterations. + * * @param booking The booking which the email is being sent for. */ private void sendBookingConfirmationEmail(Booking booking) { @@ -248,7 +234,7 @@ private void sendBookingConfirmationEmail(Booking booking) { String subjectLine = "Here's your booking info"; String toAddress = booking.getGuest().getEmailAddress(); StringBuilder builder = new StringBuilder(); - + builder.append("Thanks for placing a booking at Git-Rekt Resort.\n\n"); builder.append("Your booking number is "); builder.append(booking.getId().toString()); @@ -258,7 +244,7 @@ private void sendBookingConfirmationEmail(Booking booking) { + "check-in, or cancel the booking."); builder.append("\n\nThank you for being dumb enough to book with us."); String emailText = builder.toString(); - + try { emailService.sendEmail(toAddress, subjectLine, emailText); } catch (MessagingException e) { @@ -266,14 +252,14 @@ private void sendBookingConfirmationEmail(Booking booking) { // TODO: Handle exception better. } } - + /** * Sends an email confirming the cancellation of the guest's booking. - * - * See the documentation on the sendBookingConfirmationEmail method for some - * comments that apply here as well regarding the design of this method, - * since the two methods are basically identical in design. - * + * + * See the documentation on the sendBookingConfirmationEmail method for some comments that apply + * here as well regarding the design of this method, since the two methods are basically + * identical in design. + * * @param booking The booking to send the email regarding. */ private void sendBookingCancellationEmail(Booking booking) { @@ -281,7 +267,7 @@ private void sendBookingCancellationEmail(Booking booking) { String subjectLine = "Your booking has been cancelled. Loser."; String toAddress = booking.getGuest().getEmailAddress(); StringBuilder builder = new StringBuilder(); - + builder.append("Hey loser, we cancelled your booking, booking number "); builder.append(booking.getId().toString()); builder.append(" like you asked."); @@ -294,7 +280,7 @@ private void sendBookingCancellationEmail(Booking booking) { builder.append(", which includes any applicable cancellation fees."); builder.append("\n\nPay us, or we'll be forced to take it ourselves."); String emailText = builder.toString(); - + try { emailService.sendEmail(toAddress, subjectLine, emailText); } catch (MessagingException e) { @@ -302,4 +288,62 @@ private void sendBookingCancellationEmail(Booking booking) { // TODO: Handle exception better. } } + + public List getRoomTypesAvailable( + Date checkin, Date checkout + ) { + List results = new ArrayList<>(); + + // Get the list of rooms booked during this window. + List bookings = getBookingsBetweenDates(checkout, checkout); + List bookedRooms = new ArrayList<>(); + bookings.forEach( + (booking) -> { + bookedRooms.addAll(booking.getBookedRooms()); + } + ); + + // Get the list of rooms NOT booked during this window via exclusion + RoomService roomService = new RoomService(); + List rooms = roomService.getAllRooms(); + // Can we just talk about how beautiful this syntax is? + rooms.removeAll(bookedRooms); + + // And how ugly this one is? I need to get better at streams API / HQL + for(Room r : rooms) { + boolean found = false; // Whether the category is in the result list + for(RoomSearchResult result : results) { + String existingCategory = result.getRoomCategory().getName(); + if(r.getRoomCategory().getName().equals(existingCategory)) { + result.setNumAvailable(result.getNumAvailable() + 1); + found = true; + } + } + if(!found) { + Double price = getCurrentPrice(r.getRoomCategory()); + RoomSearchResult result = new RoomSearchResult(price, r.getRoomCategory(), 1); + results.add(result); + } + } + return results; + } + + /** + * This method should be used instead of the getPrice() method in roomCategory. + * + * This should probably be handled in a better way, but it all comes down to the amount of + * design time available in the end, and that is a resource we are critically short on at the + * moment. + * + * @param roomCategory The category of room to determine the price for. + * + * @return The room price after pricing adjustments are taken into account. + */ + public Double getCurrentPrice(RoomCategory roomCategory) { + Double basePrice = roomCategory.getBasePrice(); + Double currentPrice = basePrice; + // TODO: Factor in capacity and other things + return currentPrice; + } + } diff --git a/src/main/java/com/gitrekt/resort/model/services/EmployeeService.java b/src/main/java/com/gitrekt/resort/model/services/EmployeeService.java index bbd1624..8dace7f 100644 --- a/src/main/java/com/gitrekt/resort/model/services/EmployeeService.java +++ b/src/main/java/com/gitrekt/resort/model/services/EmployeeService.java @@ -13,29 +13,30 @@ * Handles all business logic related to employee accounts. */ public class EmployeeService { - + + // I know the security of this sucks horribly but that's not the point of this project. + public static boolean isManagerLoggedIn = false; + private final EntityManager entityManager; - + /** - * This is an enum to allow for future possible values such as handling - * expired passwords, etc. + * This is an enum to allow for future possible values such as handling expired passwords, etc. */ public enum AuthenticationResult { SUCCESS, FAILURE } - + /** - * Creates an instance of the service class, along with it's associated - * Hibernate entityManager. + * Creates an instance of the service class, along with it's associated Hibernate entityManager. */ public EmployeeService(){ this.entityManager = HibernateUtil.getEntityManager(); } - + /** * Takes care of closing the Hibernate entityManager for the class. - * + * * @throws Throwable */ @Override @@ -43,43 +44,37 @@ public void finalize() throws Throwable { super.finalize(); this.cleanup(); } - + /** * Closes the database connection held by this instance. - * - * Normally called by finalize, but in cases where the garbage collector - * has not yet run, that may not be sufficient, resulting in a need for this - * type of method. + * + * Normally called by finalize, but in cases where the garbage collector has not yet run, that + * may not be sufficient, resulting in a need for this type of method. */ public void cleanup() { this.entityManager.close(); } - + /** * @param employeeId The id of the employee to retrieve. - * - * @return The employee object, if found. If not found, accessing the object - * will throw a EntityNotFoundException due to the lazy-loading behavior of - * Hibernate. + * + * @return The employee object, if found. If not found, accessing the object will throw a + * EntityNotFoundException due to the lazy-loading behavior of Hibernate. */ public Employee getEmployeeById(Long employeeId){ - Employee employee = entityManager.getReference( - Employee.class, employeeId - ); + Employee employee = entityManager.getReference(Employee.class, employeeId); return employee; } - + /** * @return A list of all employee accounts in the system. */ public List getAllEmployees(){ - String queryString = - "FROM Employee"; - Query q = entityManager.createQuery(queryString); - List results = q.getResultList(); - return results; + String queryString = "FROM Employee"; + Query q = entityManager.createQuery(queryString); + return q.getResultList(); } - + public void createEmployeeAccount(Employee employee){ try { entityManager.getTransaction().begin(); @@ -89,32 +84,32 @@ public void createEmployeeAccount(Employee employee){ entityManager.getTransaction().rollback(); // TODO: Log rollback or notify user somewhere, possibly in e. throw e; - } + } } - + /** * Removes the provided employee from the database. - * + * * This operation cannot be undone, so be sure this is what you want to do. - * + * * @param employee The employee to delete. */ - public void deleteEmployee(Employee employee){ - try { + public void deleteEmployee(Employee e){ + try { entityManager.getTransaction().begin(); - entityManager.remove(employee); + entityManager.remove(entityManager.merge(e)); entityManager.getTransaction().commit(); - } catch (PersistenceException e) { + } catch (PersistenceException ex) { entityManager.getTransaction().rollback(); // TODO: Log rollback or notify user somewhere, possibly in e. - throw e; + throw ex; } } - + /** * Updates the record of the provided employee in the database. - * - * @param employee The employee to update. + * + * @param employee The employee to update. */ public void updateEmployee(Employee employee){ try { @@ -127,18 +122,17 @@ public void updateEmployee(Employee employee){ throw e; } } - + /** * Performs authentication on the provided employee. - * + * * @param employeeId The id number of the employee to authenticate. - * + * * @param plaintextPassword The plaintext password of the employee. - * + * * @return The appropriate authenticationResult enum type. */ - public AuthenticationResult authenticate(Long employeeId, - String plaintextPassword) { + public AuthenticationResult authenticate(Long employeeId, String plaintextPassword) { Employee employee = getEmployeeById(employeeId); String hashed; // The encrypted (hashed) password of the employee. try { @@ -146,18 +140,13 @@ public AuthenticationResult authenticate(Long employeeId, } catch (EntityNotFoundException e) { return AuthenticationResult.FAILURE; } - + boolean passwordCorrect = BCrypt.checkpw(plaintextPassword, hashed); - if(passwordCorrect) { return AuthenticationResult.SUCCESS; } else { return AuthenticationResult.FAILURE; } } - - public void resetEmployeePassword(Long managerId, String managerPassword, - Long employeeId,String employeePassword){ - // TODO - } + } diff --git a/src/main/java/com/gitrekt/resort/model/services/GuestService.java b/src/main/java/com/gitrekt/resort/model/services/GuestService.java index d8de0ae..227b7dc 100644 --- a/src/main/java/com/gitrekt/resort/model/services/GuestService.java +++ b/src/main/java/com/gitrekt/resort/model/services/GuestService.java @@ -23,6 +23,13 @@ public void finalize() throws Throwable { super.finalize(); this.entityManager.close(); } + + /** + * Forcefully closes the entityManager for this service. + */ + public void cleanup() { + entityManager.close(); + } public List getCurrentlyCheckedInGuests() { String query = "FROM Guest WHERE isCheckedIn = :param"; diff --git a/src/main/java/com/gitrekt/resort/model/services/PackageService.java b/src/main/java/com/gitrekt/resort/model/services/PackageService.java new file mode 100644 index 0000000..985c5de --- /dev/null +++ b/src/main/java/com/gitrekt/resort/model/services/PackageService.java @@ -0,0 +1,37 @@ + +package com.gitrekt.resort.model.services; + +import com.gitrekt.resort.hibernate.HibernateUtil; +import com.gitrekt.resort.model.entities.Package; +import java.util.List; +import javax.persistence.EntityManager; +import javax.persistence.Query; + +/** + * Responsible retrieval, storage, and business logic related specifically to Package objects. + */ +public class PackageService { + + private final EntityManager entityManager; + + public PackageService() { + this.entityManager = HibernateUtil.getEntityManager(); + } + + @Override + public void finalize() throws Throwable { + super.finalize(); + + entityManager.close(); + } + + /** + * @return A List of all packages available in the database. + */ + public List getAllPackages() { + String queryString = "FROM Package"; + Query query = entityManager.createQuery(queryString); + return query.getResultList(); + } + +} diff --git a/src/main/java/com/gitrekt/resort/model/services/PasswordValidatorUtil.java b/src/main/java/com/gitrekt/resort/model/services/PasswordValidatorUtil.java new file mode 100644 index 0000000..a7d19c9 --- /dev/null +++ b/src/main/java/com/gitrekt/resort/model/services/PasswordValidatorUtil.java @@ -0,0 +1,19 @@ +package com.gitrekt.resort.model.services; + +import org.passay.CharacterRule; +import org.passay.EnglishCharacterData; +import org.passay.LengthRule; +import org.passay.PasswordValidator; + +public class PasswordValidatorUtil { + + public static final PasswordValidator validator = new PasswordValidator( + // 60 is around the max length of BCrypt passwords anyways + new LengthRule(10,60), + new CharacterRule(EnglishCharacterData.UpperCase, 1), + new CharacterRule(EnglishCharacterData.LowerCase, 1), + new CharacterRule(EnglishCharacterData.Digit, 1), + new CharacterRule(EnglishCharacterData.Special, 1) + ); + +} diff --git a/src/main/java/com/gitrekt/resort/model/services/RoomService.java b/src/main/java/com/gitrekt/resort/model/services/RoomService.java new file mode 100644 index 0000000..5642b90 --- /dev/null +++ b/src/main/java/com/gitrekt/resort/model/services/RoomService.java @@ -0,0 +1,52 @@ +package com.gitrekt.resort.model.services; + +import com.gitrekt.resort.hibernate.HibernateUtil; +import com.gitrekt.resort.model.entities.Room; +import com.gitrekt.resort.model.entities.RoomCategory; +import java.util.List; +import javax.persistence.EntityManager; +import javax.persistence.Query; + +/** + * No, not that kind of room service. + */ +public class RoomService { + + private final EntityManager entityManager; + + /** + * Creates an instance of the service class, along with it's associated + * Hibernate entityManager. + */ + public RoomService(){ + this.entityManager = HibernateUtil.getEntityManager(); + } + + /** + * Takes care of closing the Hibernate entityManager for the class. + * + * @throws Throwable + */ + @Override + public void finalize() throws Throwable { + super.finalize(); + this.entityManager.close(); + } + + public List getAllRooms() { + String queryString = "FROM Room"; + Query q = entityManager.createQuery(queryString); + return q.getResultList(); + } + + public List getAllRoomsInCategory(String categoryName) { + String query = "FROM Room WHERE roomCategory.name = :categoryName"; + Query q = entityManager.createQuery(query); + q.setParameter("categoryName", categoryName); + return q.getResultList(); + } + + public List getAllRoomCategories() { + return entityManager.createQuery("FROM RoomCategory").getResultList(); + } +} diff --git a/src/main/java/com/gitrekt/resort/view/BrowseRoomsListItem.java b/src/main/java/com/gitrekt/resort/view/BrowseRoomsListItem.java index 18de951..a3b92c7 100644 --- a/src/main/java/com/gitrekt/resort/view/BrowseRoomsListItem.java +++ b/src/main/java/com/gitrekt/resort/view/BrowseRoomsListItem.java @@ -1,43 +1,43 @@ package com.gitrekt.resort.view; import com.gitrekt.resort.controller.BrowseRoomsListItemController; +import com.gitrekt.resort.controller.BrowseRoomsScreenController; import com.gitrekt.resort.model.RoomSearchResult; import java.io.IOException; import javafx.fxml.FXMLLoader; import javafx.scene.Node; import javafx.scene.control.ListCell; -/** - * TODO - */ public class BrowseRoomsListItem extends ListCell { - - private final BrowseRoomsListItemController controller; - // = new BrowseRoomsListItemController(); - + + private final BrowseRoomsListItemController controller; + private final FXMLLoader fxmlLoader; - + private final Node view; - public BrowseRoomsListItem() { + private BrowseRoomsScreenController parentController; + + public BrowseRoomsListItem(BrowseRoomsScreenController parentController) { super(); - + + // Initialize a reference to the parent controller to allow callbacks + this.parentController = parentController; + try { - fxmlLoader = new FXMLLoader( - getClass().getResource("/fxml/BrowseRoomsListItem.fxml") - ); + fxmlLoader = new FXMLLoader(getClass().getResource("/fxml/BrowseRoomsListItem.fxml")); view = fxmlLoader.load(); controller = fxmlLoader.getController(); + controller.setParentController(parentController); } catch (IOException ex) { - ex.printStackTrace(); throw new IllegalStateException(ex + "FXML file loading failed."); } } - + @Override protected void updateItem(RoomSearchResult roomData, boolean empty) { super.updateItem(roomData, empty); - + if(empty) { setGraphic(null); } else { @@ -45,5 +45,5 @@ protected void updateItem(RoomSearchResult roomData, boolean empty) { setGraphic(view); } } - + } diff --git a/src/main/java/com/gitrekt/resort/view/DeletableListItem.java b/src/main/java/com/gitrekt/resort/view/DeletableListItem.java deleted file mode 100644 index fd9ced5..0000000 --- a/src/main/java/com/gitrekt/resort/view/DeletableListItem.java +++ /dev/null @@ -1,69 +0,0 @@ -package com.gitrekt.resort.view; - -import com.gitrekt.resort.controller.DeletableListItemDeletionListener; -import com.gitrekt.resort.model.RoomSearchResult; -import java.io.IOException; -import javafx.fxml.FXMLLoader; -import javafx.scene.control.Button; -import javafx.scene.control.Label; -import javafx.scene.control.ListCell; -import javafx.scene.image.ImageView; -import javafx.scene.layout.HBox; - -/** - * TODO: Document - * - * Everything about this class is a giant mess. It was only really written to - * test the UI. It needs to be redesigned from the ground up. - * - * Please, please, do not use this class in the final program. - */ -public class DeletableListItem extends ListCell { - - private HBox root = new HBox(); - private Label listItemText = new Label(""); - private Button deleteButton = new Button(); - private ImageView deleteButtonIcon; - - //private DeletableListItemController controller; - - private DeletableListItemDeletionListener listener; - - public DeletableListItem(DeletableListItemDeletionListener listener) { - super(); - - FXMLLoader fxmlLoader = new FXMLLoader(); - try { - root = fxmlLoader.load( - getClass().getResource("/fxml/DeletableListItem.fxml") - ); - //fxmlLoader.setController(controller); - } catch (IOException ex) { - // TODO - } - - this.listener = listener; - deleteButton.setOnAction(event -> onXClicked()); - } - - @Override - protected void updateItem(RoomSearchResult roomData, boolean empty) { - super.updateItem(roomData, empty); - - if(empty || roomData == null) { - setItem(null); - setGraphic(null); - } else { - listItemText.setText(roomData.getRoomCategory().getName()); - setGraphic(root); - } - } - - private void onXClicked() { - listener.onItemDeleted(this); - - // Remove the item from the list - getListView().getItems().remove((getItem())); - } - -} diff --git a/src/main/java/com/gitrekt/resort/view/SelectedRoomListItem.java b/src/main/java/com/gitrekt/resort/view/SelectedRoomListItem.java new file mode 100644 index 0000000..1217851 --- /dev/null +++ b/src/main/java/com/gitrekt/resort/view/SelectedRoomListItem.java @@ -0,0 +1,54 @@ +package com.gitrekt.resort.view; + +import com.gitrekt.resort.controller.BrowseRoomsScreenController; +import com.gitrekt.resort.controller.SelectedRoomListItemController; +import com.gitrekt.resort.model.RoomSearchResult; +import java.io.IOException; +import javafx.fxml.FXMLLoader; +import javafx.scene.control.ListCell; +import javafx.scene.layout.HBox; + +/** + * A list item meant for the rooms that are currently selected but not yet booked. + * + * This class could probably stand to be generalized into a more generic DeletableListItem class, + * but it's not worth the time right now. + */ +public class SelectedRoomListItem extends ListCell { + + private SelectedRoomListItemController controller; + + private HBox root = new HBox(); + + private BrowseRoomsScreenController parentController; + + public SelectedRoomListItem(BrowseRoomsScreenController parentController) { + super(); + + this.parentController = parentController; + FXMLLoader fxmlLoader = new FXMLLoader( + getClass().getResource("/fxml/SelectedRoomListItem.fxml") + ); + try { + root = fxmlLoader.load(); + controller = (SelectedRoomListItemController) fxmlLoader.getController(); + controller.setParentController(parentController); + } catch (IOException e) { + // TODO + } + } + + @Override + protected void updateItem(RoomSearchResult roomData, boolean empty) { + super.updateItem(roomData, empty); + + if(empty || roomData == null) { + setItem(null); + setGraphic(null); + } else { + controller.setData(roomData); + setGraphic(root); + } + } + +} diff --git a/src/main/resources/META-INF/persistence.xml b/src/main/resources/META-INF/persistence.xml index 9e090a6..43694e1 100644 --- a/src/main/resources/META-INF/persistence.xml +++ b/src/main/resources/META-INF/persistence.xml @@ -52,8 +52,11 @@ http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd" + + diff --git a/src/main/resources/fxcss/Master.css b/src/main/resources/fxcss/Master.css index 25482d6..8cf0461 100644 --- a/src/main/resources/fxcss/Master.css +++ b/src/main/resources/fxcss/Master.css @@ -31,3 +31,16 @@ .validationErrorText { -fx-text-fill: #FF0000; } + +.invalidField { + -fx-background-color: rgba(255, 0, 0, 0.5); +} + +.subheader { + -fx-font-weight: bold; +} + +.bookingStatusLabel { + -fx-font-weight: bold; + -fx-text-fill: red; +} diff --git a/src/main/resources/fxml/BookingDetailsScreen.fxml b/src/main/resources/fxml/BookingDetailsScreen.fxml index 1c3771a..372d907 100644 --- a/src/main/resources/fxml/BookingDetailsScreen.fxml +++ b/src/main/resources/fxml/BookingDetailsScreen.fxml @@ -11,9 +11,9 @@ - + - +