Skip to content
Snippets Groups Projects
Model.java 23.4 KiB
Newer Older
eyan_'s avatar
eyan_ committed
package com.example.schedulerapp;

eyan_'s avatar
eyan_ committed
import java.net.*;
import java.io.*;
ArktikHunter's avatar
ArktikHunter committed
import java.time.LocalDate;
ArktikHunter's avatar
ArktikHunter committed
import java.time.LocalDateTime;
ArktikHunter's avatar
ArktikHunter committed
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
ArktikHunter's avatar
ArktikHunter committed
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
eyan_'s avatar
eyan_ committed
import java.util.concurrent.TimeUnit;
ArktikHunter's avatar
ArktikHunter committed

eyan_'s avatar
eyan_ committed
public class Model {
eyan_'s avatar
eyan_ committed
    private PrintWriter writer;
    private Socket socket;
    protected Map<Integer, Shift> shifts;
    protected Map<Integer, Employee> employees;
    protected  Map<Integer, TimeOff> timeOff;
ArktikHunter's avatar
ArktikHunter committed
    protected ArrayList<ModelSubscriber> subscribers;
ArktikHunter's avatar
ArktikHunter committed

eyan_'s avatar
eyan_ committed
    static LocalDate date;
    private boolean isManager;
    private int thisEmployee;
    private int selectedEmployee;
ArktikHunter's avatar
ArktikHunter committed

eyan_'s avatar
eyan_ committed
    public Model(String hostname, int port) {
ArktikHunter's avatar
ArktikHunter committed
        subscribers = new ArrayList<>();
eyan_'s avatar
eyan_ committed
        if(date == null) {
            date = LocalDate.now();
        }
        isManager = false;          // changes if manager logs in
        this.shifts =  new HashMap<>();
        this.employees = new HashMap<>();
        this.timeOff = new HashMap<>();
eyan_'s avatar
eyan_ committed
        try {
            InetAddress ip = InetAddress.getByName(hostname);
            this.socket = new Socket(ip, port);
            System.out.println("Connected to the scheduling server");

            new ReceiveThread(this.socket, this).start();

            OutputStream output = this.socket.getOutputStream();
            this.writer = new PrintWriter(output, true);

        } catch (UnknownHostException error) {
            System.out.println("Server not found: " + error.getMessage());
        } catch (IOException error) {
            System.out.println("I/O Error: " + error.getMessage());
        }
ArktikHunter's avatar
ArktikHunter committed
    }

    public void setIsManager(boolean aBool){
        isManager = aBool;
    }

    public boolean getIsManager(){
        return isManager;
    }

    public void setThisEmployee(int id){
        thisEmployee = selectedEmployee = id;
    }

    public int getThisEmployee(){
        return thisEmployee;
    }

    //for manager only, allows viewing of weekly schedule by employee
    public void setSelectedEmployee(int id){
        selectedEmployee = id;
ArktikHunter's avatar
ArktikHunter committed
        notifySubscribers();
    }

    public int getSelectedEmployee(){
        return selectedEmployee;
    }

    public void dateNext(){
        date = date.plusDays(1);
ArktikHunter's avatar
ArktikHunter committed
        notifySubscribers();
    public void weekNext(){
        date = date.plusDays(7);
        notifySubscribers();
    }

    public void datePrev(){
        date = date.minusDays(1);
ArktikHunter's avatar
ArktikHunter committed
        notifySubscribers();
    public void weekPrev(){
        date = date.minusDays(7);
        notifySubscribers();
    }

    //todo: add calender to fill jump field
    public void dateJump(LocalDate date){
ArktikHunter's avatar
ArktikHunter committed
        notifySubscribers();
    /*
    Name: addEmployee
    Parameters:
        String firstName: The new employees first name.
        String lastName: The new employees last name.
    Description: Adds an employee to the database.
    Return: String indicating the result
     */
    String addEmployee(String firstName, String lastName) {
        if (firstName.contains("/") || lastName.contains("/") || firstName.contains(",") || lastName.contains(",")){
            return "Illegal character(s)";
        }
        else {
            writer.println("addEmployee/" + firstName + "/" + lastName + "/" + false + "/" + "empty" + "/" + "empty" + "/" + 0.0f);
            return "Employee added";
        }
    void addEmployee(String firstName, String lastName, boolean isManager, String email, String phoneNumber, float wage) {
        writer.println("addEmployee/" + firstName + "/" + lastName + "/" + isManager + "/" + email + "/" + phoneNumber +
                "/" + wage);
    }

    String editEmployee(int employeeID, String firstName, String lastName, boolean isManager, String email, String phoneNumber, float wage) {
        if (employeeID <= 0){
            return "Invalid ID";
        }
        if (firstName.contains("/") || lastName.contains("/") || firstName.contains(",") || lastName.contains(",")){
            return "Name contains illegal character(s)";
        }
        if(email.contains("/") || email.contains(",")){
            return "Email contains illegal character(s)";
        }
        if(phoneNumber.contains("/") || phoneNumber.contains(",")){
            return "phoneNumber contains illegal character(s)";
        }
        if (wage < 0){
            return "Invalid wage";
        }
        if (this.employees.containsKey(employeeID)) {
            writer.println("editEmployee/" + employeeID + "/" + firstName + "/" + lastName + "/" + isManager + "/" + email + "/" + phoneNumber +
                    "/" + wage);
            return "Employee edited";
        }

        return "Employee does not exist";
    /*
    Name: removeEmployee
    Parameters:
        String ID: The employees unique ID.
    Description: Removes an employee with the given ID if it exists.
    Return: String indicating the result
     */
    String removeEmployee(String employeeID) {
        if (Integer.parseInt(employeeID) <= 0){
            return "Invalid ID";
        }
        if (this.employees.size() < Integer.parseInt(employeeID)){
            return "Employee does not exist";
        }
        if (this.employees.containsKey(Integer.parseInt(employeeID))) {
            writer.println("removeEmployee/" + employeeID);
            return "Employee removed";
        }
        return "Employee does not exist";
    /*
    Name: getEmployee
    Parameters:
        int ID: The employees unique ID.
    Description: Gets an employee with the given ID if it exists.
    Return: The employee object if it exists, null if not
     */
    Employee getEmployee(int employeeID) {
        return employees.getOrDefault(employeeID, null);
eyan_'s avatar
eyan_ committed
    void printAllEmployees(){
        ArrayList<String> employeeList = new ArrayList<>();
        for (Employee employee : this.employees.values()) {
            employeeList.add(employee.getFullName());
        }
        System.out.println(employeeList);
    /*
    Name: employeeSize
    Description: Gets the number of employees in the database.
    Return: The total number of employees.
     */
eyan_'s avatar
eyan_ committed
    }

    /*
    Name: getEmployeeByID
    Parameters:
        int ID: The employees unique ID.
    Description: Gets an employee's name with the given ID if it exists.
    Return: A string containing the employees full name
     */
eyan_'s avatar
eyan_ committed
    String getEmployeeByID(int id) {
        if (this.employees.containsKey(id)) {
            Employee employee = this.employees.get(id);
            return employee.getFullName();
eyan_'s avatar
eyan_ committed
        }
        return "No Employee with that ID";
eyan_'s avatar
eyan_ committed
    }

    public ArrayList<Employee> getAllEmployees(){
        return new ArrayList<Employee>(employees.values());
    }

    /*
    Name: addShift
    Parameters:
        int ID: The employees unique ID.
        String date: The date in yyyy-mm-dd format.
        int start: The start time in 24 hour time.
        int end: The end time in 24 hour time.
    Description: Adds a new shift on the given date.
    Return: String indicating the result
     */
    String addShift(int id, String date, int start, int end) {
        if (id <= 0) {
        if (start < 0 || end < 0 || end > 2359 || start >= end) {
            return "Invalid start or end time";
        }
ArktikHunter's avatar
ArktikHunter committed
        //todo: fix this, broke when moved from int to localtime
        /*
ArktikHunter's avatar
ArktikHunter committed
        DateTimeFormatter dbTimeFormat = DateTimeFormatter.ofPattern("kkmm");
        LocalTime startTime = LocalTime.parse(String.valueOf(start), dbTimeFormat);
        LocalTime endTime = LocalTime.parse(String.valueOf(end), dbTimeFormat);
        if (this.employees.containsKey(id)) {
ArktikHunter's avatar
ArktikHunter committed
            LocalTime[] dayOfTheWeekAvailability = this.employees.get(id).getAvailabilityForDay(LocalDate.parse(date)
                    .getDayOfWeek().getValue() % 7);
ArktikHunter's avatar
ArktikHunter committed
            if (dayOfTheWeekAvailability[0].isAfter(startTime) || dayOfTheWeekAvailability[1].isBefore(startTime) || dayOfTheWeekAvailability[1].isBefore(endTime)) {
                return "Shift time conflicts with employees availability";
            }
ArktikHunter's avatar
ArktikHunter committed

ArktikHunter's avatar
ArktikHunter committed
        */
        if (this.employees.containsKey(id)) {
            for (Shift shift : getShiftsByDay(date)) {
                if (shift.getEmployeeID() == id) {
                    /* Commented out split shift just in case
                    int otherShiftStart = Integer.parseInt(shift.getStart().toLocalTime().toString().replace(":", ""));
                    int otherShiftEnd = Integer.parseInt(shift.getEnd().toLocalTime().toString().replace(":", ""));
                    if (start <= otherShiftEnd && !(end <= otherShiftStart)){
                        return "Shift time conflict with existing shift";
                    }
                    */
                    //return "Shift time conflict for employee";
eyan_'s avatar
eyan_ committed
            }
        } else {
            return "No Employee with that ID";
eyan_'s avatar
eyan_ committed
        }
        writer.println("addShift/" + id + "/" + date + "/" + start + "/" + end + "/" + " ");
        notifySubscribers();
        return "New shift added";
eyan_'s avatar
eyan_ committed
    }

    boolean hasEmployee(int id){
            return employees.containsKey(id);
        }
    //
    void addShift(int id, String date, int start, int end, String position){
        writer.println("addShift/"+id+"/"+date+"/"+start+"/"+end+"/"+position);
        notifySubscribers();
    }
    /*
    Name: removeShift
    Parameters:
        String shiftID: The unique ID for the shift.
    Description: Removes the shift with the given ID if it exists.
    Return: String indicating the result
     */
    String removeShift(int shiftID){
        if (shiftID <= 0){
            return "Invalid ID";
        }
        if (this.shifts.containsKey(shiftID)) {
            writer.println("removeShift/" + shiftID);
            notifySubscribers();
            return "Shift removed";
        }
        return "Shift does not exist";
eyan_'s avatar
eyan_ committed
    }

    String editShift(int shiftID, String day, int start, int end, Boolean availability, int employeeID) {
        if (shiftID <= 0){
            return "Invalid shift ID";
        }
        if (employeeID <= 0){
            return "Invalid employee ID";
        }
ArktikHunter's avatar
ArktikHunter committed
        if (start < 0 || end < 0 || start > 2400 || end > 2400){
            return "Invalid start or end time";
        }
        if (this.shifts.containsKey(shiftID)) {
            writer.println("editShift/" + shiftID + "/" + day + "/" + start + "/" + end + "/" + availability + "/" + employeeID);
            notifySubscribers();
            return "Shift successfully edited";
        }
        return "Shift does not exist";
    /*
    Name: getShift
    Parameters:
        int shiftID: The unique shift ID
    Description: Gets the shift with the given ID if it exists.
    Return: The shift object if it exists, null if not
     */
        return shifts.getOrDefault(shiftID, null);
eyan_'s avatar
eyan_ committed
    void printAllShifts() {
        System.out.println(this.shifts);
    }

Rafi's avatar
Rafi committed
    /*
    Parameters:
        - String date in format 2022-03-10.
    Return: ArrayList<String> where each string is a shift of format id.yyyy-mm-dd.start.end
    */
    ArrayList<Shift> getShiftsByDay(String date){
        ArrayList<Shift> shifts = new ArrayList<>();
        for (Shift shift : this.shifts.values()) {
            if (shift.getDate().equals(date)) {
                shifts.add(shift);
Rafi's avatar
Rafi committed
            }
        }
Rafi's avatar
Rafi committed
    }

eyan_'s avatar
eyan_ committed
    /*
    Parameters:
        startOfWeek - The first day of the week in "yyyy-mm-dd" format
        endOfWeek - The last day of the week in "yyyy-mm-dd" format
    Return: ArrayList<String>
     */
    ArrayList<Shift> getShiftsByWeek(LocalDateTime startOfWeek, LocalDateTime endOfWeek){
        ArrayList<Shift> shifts = new ArrayList<>();
        for (Shift shift : this.shifts.values()) {
            LocalDateTime start = shift.getStart();
            LocalDateTime end = shift.getEnd();
            if ((start.isAfter(startOfWeek) || start.isEqual(startOfWeek)) && (end.isBefore(endOfWeek)) ||
                    end.isEqual(endOfWeek)) {
                shifts.add(shift);
eyan_'s avatar
eyan_ committed
            }
        }
eyan_'s avatar
eyan_ committed
    }

ArktikHunter's avatar
ArktikHunter committed
    //for view
    public ArrayList<Shift> getWeeklySchedule(){
        LocalDateTime start;
        LocalDateTime end;
ArktikHunter's avatar
ArktikHunter committed
        // get start and end dates to pass along
        int today = date.getDayOfWeek().getValue();     // Mon is 1, Sun is 7
        if (today == 7) {   //this is the start date
            start = date.atTime(0, 0);
            end = date.plusDays(6).atTime(0,0);
ArktikHunter's avatar
ArktikHunter committed
        }
        else {
            start = date.minusDays(today).atTime(0, 0);
            end = date.plusDays(6 - today).atTime(0, 0);
ArktikHunter's avatar
ArktikHunter committed
        }
        return getEmployeeShiftsByWeek(selectedEmployee, start, end);
ArktikHunter's avatar
ArktikHunter committed
    }

    //for view
    public ArrayList<Shift> getDailySchedule(){
        return getShiftsByDay(date.toString());
    ArrayList<Shift> getEmployeeShifts(String id) {
        int intID = -1;
        try {
            intID = Integer.parseInt(id);
        } catch (NumberFormatException exception) {
            exception.printStackTrace();
        }
        ArrayList<Shift> shifts = new ArrayList<>();
        for (Shift shift: this.shifts.values()) {
            if (shift.getEmployeeID() == intID) {
                shifts.add(shift);
eyan_'s avatar
eyan_ committed
            }
        }
        return shifts;
    }

    ArrayList<Shift> getEmployeeShiftsByWeek(int employeeID, LocalDateTime startOfWeek, LocalDateTime endOfWeek){
        ArrayList<Shift> shifts = new ArrayList<>();
        for (Shift shift : this.shifts.values()) {
            LocalDateTime start = shift.getStart();
            LocalDateTime end = shift.getEnd();
            if ((shift.getEmployeeID() == employeeID) && (start.isAfter(startOfWeek) || start.isEqual(startOfWeek)) &&
                    (end.isBefore(endOfWeek)) || end.isEqual(endOfWeek)) {
                shifts.add(shift);
eyan_'s avatar
eyan_ committed
    }

    String editShiftAvailability(int shiftID, boolean available) {
        if (shiftID <= 0){
            return "Invalid shift ID";
        }
        if (this.shifts.containsKey(shiftID)) {
            writer.println("editShiftAvailability/" + shiftID + "/" + available);
            return "Shift edited";
        }
        return "Shift does not exist";
    // adds a position if its one of the possible positions in the position table.
    String addEmployeePosition(int employeeID, String position) {
        if (employeeID <= 0){
            return "Invalid employee ID";
        }
        if (!this.positions.containsKey(position)){
            return "Position does not exist";
        }
        if (this.employees.containsKey(employeeID)) {
            writer.println("addEmployeePosition/" + employeeID + "/" + position);
            return "Position added to employee";
        }
        return "Employee does not exist";
    String removeEmployeePosition(int employeeID, String position) {
        if (employeeID <= 0){
            return "Invalid employee ID";
        }
        if (!this.positions.containsKey(position)){
            return "Position does not exist";
        }
        if (this.employees.containsKey(employeeID)) {
            writer.println("removeEmployeePosition/" + employeeID + "/" + position);
            return "Position removed from employee";
        }
        return "Employee does not exist";
    String editAvailability(int employeeID, int dayOfTheWeek, int start, int end) {
        if (employeeID <= 0){
            return "Invalid employee ID";
        }
        if (dayOfTheWeek < 0 || dayOfTheWeek > 8){
            return "Invalid day";
        }
        if (end < start){
            return "End must be after start";
        }
        if (start < 0 || end < 0 || start > 2400 || end > 2400){
            return "Invalid start or end time";
        }
        if (this.employees.containsKey(employeeID)) {
            writer.println("editAvailability/" + employeeID + "/" + dayOfTheWeek + "/" + start + "/" + end);
            editLocalAvailability(employeeID, dayOfTheWeek, start, end);
            notifySubscribers();
            return "Availability changed successfully";
        }
        return "Employee does not exist";
    private void editLocalAvailability(int employeeID, int dayOfTheWeek, int start, int end){
        employees.get(employeeID).updateAvailability(dayOfTheWeek, intToLocalTime(start), intToLocalTime(end));
    }

    public LocalTime intToLocalTime(int time){
        String resultString;
        String sub = "0000";
        String timeString = String.valueOf(time);
        if (timeString.length() < 4){
            resultString = sub.substring(0, 4-timeString.length()) + timeString;
        } else {
            resultString = timeString;
        }
        DateTimeFormatter dbTimeFormat = DateTimeFormatter.ofPattern("kkmm");
        LocalTime result = LocalTime.parse(resultString, dbTimeFormat);
        return result;
    }

    String addTimeOff(int employeeID, String startDate, String endDate, String reason) {
        if (employeeID <= 0){
            return "Invalid employee ID";
        }
        if (reason.contains("/") || reason.contains(",")){
            return "Illegal character(s)";
        }
        if (this.employees.containsKey(employeeID)) {
            writer.println("addTimeOff/" + employeeID + "/" + startDate + "/" + endDate + "/" + reason);
            return "Time off added";
        }
        return "Employee does not exist";
    String setTimeOffApproval(int timeOffID, boolean approval) {
        if (timeOffID <= 0){
            return "Invalid employee ID";
        }
        if(this.timeOff.containsKey(timeOffID)) {
            writer.println("setTimeOffApproval/" + timeOffID + "/" + approval);
            return "Time off approved";
        }
        return "Time off request doesn't exist";
    String removeTimeOff(int timeOffID) {
        if (timeOffID <= 0){
            return "Invalid employee ID";
        }
        if(this.timeOff.containsKey(timeOffID)) {
            writer.println("removeTimeOff/" + timeOffID);
            return "Time off removed";
        }
        return "Time off request does not exist";
    String addPosition(String position, float wage) {
        if (position.contains("/") || position.contains(",")){
            return "Illegal character(s)";
        }
        if (wage <= 0){
            return "Invalid wage";
        }
        writer.println("addPosition/" + position + "/" + wage);
        return "Position added";
    String editPositionWage(String position, float wage) {
        if (position.contains("/") || position.contains(",")){
            return "Illegal character(s)";
        }
        if (wage <= 0){
            return "Invalid wage";
        }
        if(this.positions.containsKey(position)) {
            writer.println("editPosition/" + position + "/" + wage);
            return "Position edited";
        }
        return "Position does not exist";
    String removePosition(String position, float wage) {
        if (position.contains("/") || position.contains(",")){
            return "Illegal character(s)";
        }
        if (wage <= 0){
            return "Invalid wage";
        }
        if(this.positions.containsKey(position)) {
            writer.println("removePosition/"+ position + "/" + wage);
            return "Position removed";
        }
        return "Position does not exist";
    //How this works is if you start within 5 minutes of your shift than you can "signed-in" and
    //assumes you work the whole shift, if you leave early of come in late the shift manager
    //must change it in the schedule.
    void checkIn(int shiftID) {
        LocalDateTime nMinutesAfter = this.shifts.get(shiftID).getStart().plusMinutes(5);
        LocalDateTime nMinutesBefore = this.shifts.get(shiftID).getStart().minusMinutes(5);
        LocalDateTime now = LocalDateTime.now();
        if (nMinutesBefore.isBefore(now) && nMinutesAfter.plusMinutes(5).isAfter(now)) {
            writer.println("checkIn/" + shiftID);
        }

    //This allows the manager to mark any shift as checked-in.
    void managerCheckIn(int shiftID) {
        writer.println("checkIn/" + shiftID);
    }

    //The dates need to be in form "yyyy-mm-dd"
    //Also the start and end are inclusive.
    float getHoursWorked(int employeeID, String start, String end) {
        float hoursWorked = 0.0f;
        try {
            SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
            Date startDate = formatter.parse(start);
            Date endDate = formatter.parse(end);
            for (Shift shift : this.shifts.values()) {
                if (shift.getEmployeeID() == employeeID) {
                    Date shiftDate = formatter.parse(shift.getDate());
                    if (!startDate.after(shiftDate) && !endDate.before(shiftDate)){
                        if (shift.isCheckedIn()) {
                            hoursWorked = (hoursWorked + shift.getEnd().getHour() - shift.getStart().getHour()) + ((shift.getEnd().getMinute() - shift.getStart().getMinute())/60f);
                        }
                    }
                }

            }
        } catch (Exception exception) {
            exception.printStackTrace();
            return -1f;
        }
        return hoursWorked;
eyan_'s avatar
eyan_ committed
    void logOut() {
        writer.println("logout");
        try {
            this.socket.close();
        } catch (IOException ex){
            System.out.println("Error: Failed to close socket");
        }
    }

    public static void main(String[] args) {
        String hostname = "localhost";
        int port = 8989;
eyan_'s avatar
eyan_ committed
        Model client = new Model(hostname, port);
        System.out.print("Retrieving Data");
        while(client.employees.isEmpty()){
eyan_'s avatar
eyan_ committed
            System.out.print(".");
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                System.out.println("Error: ScheduleClient main. Interrupted while retrieving data ");
            }
        }
        System.out.println();
        System.out.println("Data retrieved.");
        System.out.println("ScheduleClient is ready to use!");
    }


    public ArrayList<String> returnFormattedEmployeeNames(){
        ArrayList<String> result = new ArrayList<>();
        for (Employee employee: this.employees.values()) {
            result.add(employee.getFullName());
eyan_'s avatar
eyan_ committed
        }
        //System.out.println(result);
        return result;
    }

    public ArrayList<String> returnPositions(){
        ArrayList<String> result = new ArrayList<>();
        for (String position: this.positions.keySet()) {
            result.add(position);
        }
        //System.out.println(result);
eyan_'s avatar
eyan_ committed
        return result;
    }
eyan_'s avatar
eyan_ committed

    public int getIDbyIndex(int index) {
        Employee[] employeeList = this.employees.values().toArray(new Employee[0]);
        return employeeList[index].getEmployeeID();
eyan_'s avatar
eyan_ committed
    }
ArktikHunter's avatar
ArktikHunter committed

    //for publish/subscribe
    public void addSubscriber(ModelSubscriber sub){
        subscribers.add(sub);
    }

    public void notifySubscribers(){
        for (ModelSubscriber sub : subscribers){
            sub.modelChanged();
        }
    }
eyan_'s avatar
eyan_ committed
}