diff --git a/build.gradle b/build.gradle index 50cd2ae52efc..cef7e85c77b8 100644 --- a/build.gradle +++ b/build.gradle @@ -41,6 +41,11 @@ jacocoTestReport { dependencies { String testFxVersion = '4.0.7-alpha' + // https://mvnrepository.com/artifact/org.apache.poi/poi + compile group: 'org.apache.poi', name: 'poi', version: '3.17' + // https://mvnrepository.com/artifact/org.apache.poi/poi-ooxml + compile group: 'org.apache.poi', name: 'poi-ooxml', version: '3.17' + compile group: 'org.fxmisc.easybind', name: 'easybind', version: '1.0.3' compile group: 'org.controlsfx', name: 'controlsfx', version: '8.40.11' compile group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.7.0' diff --git a/src/main/java/seedu/address/logic/commands/CommandWords.java b/src/main/java/seedu/address/logic/commands/CommandWords.java index 0acdd45d25a9..59448b54e71a 100644 --- a/src/main/java/seedu/address/logic/commands/CommandWords.java +++ b/src/main/java/seedu/address/logic/commands/CommandWords.java @@ -30,6 +30,7 @@ public class CommandWords implements Serializable { FindCommand.COMMAND_WORD, HelpCommand.COMMAND_WORD, HistoryCommand.COMMAND_WORD, + ImportAllCommand.COMMAND_WORD, ListCommand.COMMAND_WORD, RedoCommand.COMMAND_WORD, SelectCommand.COMMAND_WORD, @@ -124,16 +125,7 @@ public String getCommandKey(String value) throws CommandWordException { */ public void setCommandWord(String currentWord, String newWord) throws CommandWordException { requireNonNull(currentWord, newWord); - if (currentWord.equals(newWord)) { - throw new CommandWordException(getMessageNoChange()); - } - if (isDefaultCommandWord(newWord) - && !commands.get(newWord).equals(currentWord)) { - throw new CommandWordException(getMessageOverwriteDefault(newWord)); - } - if (commands.containsValue(newWord)) { - throw new CommandWordException(getMessageUsed(newWord)); - } + checkCommandWordValidity(currentWord, newWord); if (isDefaultCommandWord(currentWord)) { commands.remove(currentWord); commands.put(currentWord, newWord); @@ -152,6 +144,22 @@ public void setCommandWord(String currentWord, String newWord) throws CommandWor throw new CommandWordException(getMessageUnused(currentWord)); } + /** + * throws a (@code CommandWordException) if (@code currentWord) or (@code newWord) is not valid + */ + private void checkCommandWordValidity(String currentWord, String newWord) throws CommandWordException { + if (currentWord.equals(newWord)) { + throw new CommandWordException(getMessageNoChange()); + } + if (isDefaultCommandWord(newWord) + && !commands.get(newWord).equals(currentWord)) { + throw new CommandWordException(getMessageOverwriteDefault(newWord)); + } + if (commands.containsValue(newWord)) { + throw new CommandWordException(getMessageUsed(newWord)); + } + } + /** * Copies key and value of (@code command) from (@code commands) * to (@code verifiedCommands). Creates a new entry with default diff --git a/src/main/java/seedu/address/logic/commands/ImportAllCommand.java b/src/main/java/seedu/address/logic/commands/ImportAllCommand.java new file mode 100644 index 000000000000..4060e5141539 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/ImportAllCommand.java @@ -0,0 +1,74 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import java.io.IOException; + +import java.util.ArrayList; +import java.util.List; + +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.job.Job; +import seedu.address.model.session.ImportSession; +import seedu.address.model.session.exceptions.DataIndexOutOfBoundsException; +import seedu.address.model.session.exceptions.FileAccessException; +import seedu.address.model.session.exceptions.FileFormatException; + +//@@author yuhongherald +/** + * Attempts to import all (@code JobEntry) into Servicing Manager + */ +public class ImportAllCommand extends UndoableCommand { + + public static final String COMMAND_WORD = "importAll"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Imports job entries from from an excel file. " + + "Parameters: FILEPATH\n" + + "Example: " + COMMAND_WORD + "yourfile.xls"; + + public static final String MESSAGE_SUCCESS = "%s has been imported, with %d job entries!"; + + private final String filePath; + + public ImportAllCommand(String filePath) { + requireNonNull(filePath); + this.filePath = filePath; + } + + public String getMessageSuccess(int entries) { + return String.format(MESSAGE_SUCCESS, filePath, entries); + } + + @Override + public CommandResult executeUndoableCommand() throws CommandException { + ImportSession importSession = ImportSession.getInstance(); + try { + importSession.initializeSession(filePath); + } catch (FileAccessException e) { + e.printStackTrace(); + } catch (FileFormatException e) { + throw new CommandException("Excel file first row headers are not defined properly. " + + "Type 'help' to read more."); + } + try { + importSession.reviewAllRemainingJobEntries(true); + List jobs = new ArrayList<>(importSession.getSessionData().getReviewedJobEntries()); + model.addJobs(jobs); + importSession.closeSession(); + return new CommandResult(getMessageSuccess(jobs.size())); + } catch (DataIndexOutOfBoundsException e) { + throw new CommandException("Excel file has bad format. Try copying the cell values into a new excel file " + + "before trying again"); + } catch (IOException e) { + throw new CommandException("Unable to export file. Please close the application and try again."); + } + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof ImportAllCommand // instanceof handles nulls + && filePath.equals(((ImportAllCommand) other).filePath)); + } + +} diff --git a/src/main/java/seedu/address/logic/commands/SetCommand.java b/src/main/java/seedu/address/logic/commands/SetCommand.java index 36ee573a8666..518d9e075b29 100644 --- a/src/main/java/seedu/address/logic/commands/SetCommand.java +++ b/src/main/java/seedu/address/logic/commands/SetCommand.java @@ -14,9 +14,8 @@ public class SetCommand extends UndoableCommand { public static final String COMMAND_WORD = "set"; public static final String MESSAGE_USAGE = COMMAND_WORD + ": Sets a command word to user preference. " - + "Parameters: CURRENT_COMMAND_WORD NEW_COMMAND_WORD" - + "Example: " + "set" + " " - + "OLD_COMMAND" + "NEW_COMMAND"; + + "Parameters: CURRENT_COMMAND_WORD NEW_COMMAND_WORD\n" + + "Example: " + COMMAND_WORD + " set st"; public static final String MESSAGE_SUCCESS = "%s has been replaced with %s!"; diff --git a/src/main/java/seedu/address/logic/commands/exceptions/CommandWordException.java b/src/main/java/seedu/address/logic/commands/exceptions/CommandWordException.java index 2798ee2d5327..ccc6dc552f4b 100644 --- a/src/main/java/seedu/address/logic/commands/exceptions/CommandWordException.java +++ b/src/main/java/seedu/address/logic/commands/exceptions/CommandWordException.java @@ -2,7 +2,7 @@ //@@author yuhongherald /** - * Represents an error which occurs during execution of {@link SetCommand}. + * Represents an error which occurs during execution of {@link seedu.address.logic.commands.SetCommand}. */ public class CommandWordException extends Exception { public CommandWordException(String message) { diff --git a/src/main/java/seedu/address/logic/parser/AddressBookParser.java b/src/main/java/seedu/address/logic/parser/AddressBookParser.java index 601dcdea5c06..68b8ae90ebc5 100644 --- a/src/main/java/seedu/address/logic/parser/AddressBookParser.java +++ b/src/main/java/seedu/address/logic/parser/AddressBookParser.java @@ -17,6 +17,7 @@ import seedu.address.logic.commands.FindCommand; import seedu.address.logic.commands.HelpCommand; import seedu.address.logic.commands.HistoryCommand; +import seedu.address.logic.commands.ImportAllCommand; import seedu.address.logic.commands.ListCommand; import seedu.address.logic.commands.RedoCommand; import seedu.address.logic.commands.SelectCommand; @@ -95,6 +96,9 @@ public Command parseCommand(String userInput) throws ParseException { case FindCommand.COMMAND_WORD: return new FindCommandParser().parse(arguments); + case ImportAllCommand.COMMAND_WORD: + return new ImportAllCommandParser().parse(arguments); + case ListCommand.COMMAND_WORD: return new ListCommand(); diff --git a/src/main/java/seedu/address/logic/parser/ImportAllCommandParser.java b/src/main/java/seedu/address/logic/parser/ImportAllCommandParser.java new file mode 100644 index 000000000000..fed657a79ae9 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/ImportAllCommandParser.java @@ -0,0 +1,32 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.ParserUtil.parseFilename; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.logic.commands.ImportAllCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +//@@author yuhongherald + +/** + * Parses input arguments and creates a new AddCommand object + */ +public class ImportAllCommandParser implements Parser { + + /** + * Parses the given {@code String} of arg + * uments in the context of the ImportAllCommand + * and returns an ImportAllCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public ImportAllCommand parse(String args) throws ParseException { + try { + String filePath = parseFilename(args); + return new ImportAllCommand(filePath); + } catch (IllegalValueException ive) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, ImportAllCommand.MESSAGE_USAGE)); + } + } + +} diff --git a/src/main/java/seedu/address/logic/parser/ParserUtil.java b/src/main/java/seedu/address/logic/parser/ParserUtil.java index 94b0700ac293..0285dc66a0d0 100644 --- a/src/main/java/seedu/address/logic/parser/ParserUtil.java +++ b/src/main/java/seedu/address/logic/parser/ParserUtil.java @@ -2,6 +2,7 @@ import static java.util.Objects.requireNonNull; +import java.io.File; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; @@ -32,14 +33,15 @@ public class ParserUtil { public static final String MESSAGE_INSUFFICIENT_PARTS = "Number of parts must be more than 1."; public static final String MESSAGE_INSUFFICIENT_WORDS = "Command word to be changed and new command word must " + "be provided, separated by a space."; + public static final String MESSAGE_INVALID_FILENAME = "File name must be the path to an existing file, in the " + + "same folder as this application"; public static final String WHITESPACE = "\\s+"; + public static final String APPLICATION_DIRECTORY = ".\\"; /** * Parses {@code multipleWordString} into an {@code String[]} containing command words and returns it. * Leading and trailing whitespaces will be trimmed. - * @param multipleWordString - * @return - * @throws IllegalValueException + * @throws IllegalValueException if words found is not equal to 2 */ public static String[] parseWords(String multipleWordString) throws IllegalValueException { String[] commandWords = multipleWordString.trim().split(WHITESPACE); @@ -50,6 +52,20 @@ public static String[] parseWords(String multipleWordString) throws IllegalValue } + /** + * Parses {@code filePath} and checks if (@code file) specified exists. + * Leading and trailing whitespaces will be trimmed. + * @throws IllegalValueException if file does not exist + */ + public static String parseFilename(String filePath) throws IllegalValueException { + File file = new File(filePath.trim()); + if (!file.exists()) { + throw new IllegalValueException(MESSAGE_INVALID_FILENAME); + } + return APPLICATION_DIRECTORY + filePath.trim(); + + } + /** * Parses {@code oneBasedIndex} into an {@code Index} and returns it. Leading and trailing whitespaces will be * trimmed. diff --git a/src/main/java/seedu/address/logic/parser/SetCommandParser.java b/src/main/java/seedu/address/logic/parser/SetCommandParser.java index 11ab6d280d09..282897e1eb11 100644 --- a/src/main/java/seedu/address/logic/parser/SetCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/SetCommandParser.java @@ -15,8 +15,8 @@ public class SetCommandParser implements Parser { /** * Parses the given {@code String} of arg - * uments in the context of the AddCommand - * and returns an AddCommand object for execution. + * uments in the context of the SetCommand + * and returns a SetCommand object for execution. * @throws ParseException if the user input does not conform the expected format */ public SetCommand parse(String args) throws ParseException { diff --git a/src/main/java/seedu/address/model/Model.java b/src/main/java/seedu/address/model/Model.java index bd5aa20f22c6..108d910de1e0 100644 --- a/src/main/java/seedu/address/model/Model.java +++ b/src/main/java/seedu/address/model/Model.java @@ -1,5 +1,7 @@ package seedu.address.model; +import java.util.List; +import java.util.Set; import java.util.function.Predicate; import javafx.collections.ObservableList; @@ -48,6 +50,12 @@ public interface Model { /** Adds the given employee */ void addPerson(Employee employee) throws DuplicateEmployeeException; + /** Adds a list of (@code Job) into (@code AddressBook), and automatically imports new employees */ + void addJobs(List job); + + /** Adds employees in list into (@code AddressBook) if it is not present */ + void addMissingEmployees(Set employees); + /** Sort all persons' name in list alphabetically. */ void sortPersonList(); diff --git a/src/main/java/seedu/address/model/ModelManager.java b/src/main/java/seedu/address/model/ModelManager.java index 1802f1287263..9d5aea787dfa 100644 --- a/src/main/java/seedu/address/model/ModelManager.java +++ b/src/main/java/seedu/address/model/ModelManager.java @@ -3,6 +3,9 @@ import static java.util.Objects.requireNonNull; import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; +import java.util.Iterator; +import java.util.List; +import java.util.Set; import java.util.function.Predicate; import java.util.logging.Logger; @@ -64,7 +67,7 @@ public void initJobNumber() { } int largest = filteredJobs.get(0).getJobNumber().asInteger(); for (Job job : filteredJobs) { - if ( job.getJobNumber().asInteger() > largest) { + if (job.getJobNumber().asInteger() > largest) { largest = job.getJobNumber().asInteger(); } } @@ -125,6 +128,26 @@ public synchronized void addPerson(Employee employee) throws DuplicateEmployeeEx indicateAddressBookChanged(); } + @Override + public void addJobs(List jobs) { + for (Job job : jobs) { + addMissingEmployees(job.getAssignedEmployees()); + addJob(job); + } + } + + @Override + public void addMissingEmployees(Set employees) { + Iterator employeeIterator = employees.iterator(); + while (employeeIterator.hasNext()) { + try { + addPerson(employeeIterator.next()); + } catch (DuplicateEmployeeException e) { + // discard the result + } + } + } + @Override public void updatePerson(Employee target, Employee editedEmployee) throws DuplicateEmployeeException, EmployeeNotFoundException { diff --git a/src/main/java/seedu/address/model/job/Job.java b/src/main/java/seedu/address/model/job/Job.java index f6017020b577..5704cc19bd2c 100644 --- a/src/main/java/seedu/address/model/job/Job.java +++ b/src/main/java/seedu/address/model/job/Job.java @@ -17,6 +17,8 @@ * Represents a Job in the car servicing manager */ public class Job { + protected final RemarkList remarks; + private final Person client; private final VehicleNumber vehicleNumber; private final JobNumber jobNumber; @@ -24,7 +26,6 @@ public class Job { private final Status status; private final UniqueEmployeeList assignedEmployees; - private final RemarkList remarks; public Job(Person client, VehicleNumber vehicleNumber, JobNumber jobNumber, Date date, UniqueEmployeeList assignedEmployees, Status status, RemarkList remarks) { diff --git a/src/main/java/seedu/address/model/session/ExcelColumnSpannable.java b/src/main/java/seedu/address/model/session/ExcelColumnSpannable.java new file mode 100644 index 000000000000..9f3fe2f33ddb --- /dev/null +++ b/src/main/java/seedu/address/model/session/ExcelColumnSpannable.java @@ -0,0 +1,20 @@ +package seedu.address.model.session; + +import java.util.ArrayList; + +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.Workbook; + +import seedu.address.model.session.exceptions.DataIndexOutOfBoundsException; + +//@@author yuhongherald +/** + * For fields that resides in one or more columns + */ +public interface ExcelColumnSpannable { + public int getStartIndex(); + public int getEndIndex(); + public ArrayList readData(Workbook workbook, int sheetNumber, int rowNumber) + throws DataIndexOutOfBoundsException; + public ArrayList readDataFromSheet(Sheet sheet, int rowNumber) throws DataIndexOutOfBoundsException; +} diff --git a/src/main/java/seedu/address/model/session/ExcelRowReference.java b/src/main/java/seedu/address/model/session/ExcelRowReference.java new file mode 100644 index 000000000000..a5ca26d287b5 --- /dev/null +++ b/src/main/java/seedu/address/model/session/ExcelRowReference.java @@ -0,0 +1,18 @@ +package seedu.address.model.session; + +/** + * For row entries of an excel sheet + */ +public interface ExcelRowReference { + + /** + * Returns the excel sheet number of this element. + */ + public int getSheetNumber(); + + /** + * Returns the excel row number of this element + * @return + */ + public int getRowNumber(); +} diff --git a/src/main/java/seedu/address/model/session/ImportSession.java b/src/main/java/seedu/address/model/session/ImportSession.java new file mode 100644 index 000000000000..82eb84680ea1 --- /dev/null +++ b/src/main/java/seedu/address/model/session/ImportSession.java @@ -0,0 +1,177 @@ +package seedu.address.model.session; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.text.SimpleDateFormat; +import java.util.Date; + +import org.apache.poi.openxml4j.exceptions.InvalidFormatException; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.ss.usermodel.WorkbookFactory; + +import seedu.address.model.session.exceptions.DataIndexOutOfBoundsException; +import seedu.address.model.session.exceptions.FileAccessException; +import seedu.address.model.session.exceptions.FileFormatException; + +//@@author yuhongherald +/** + * Used to store data relevant to importing of (@code Job) from (@code inFile) and + * exporting (@code Job) with commens to (@code outFile). Implements a Singleton design pattern. + */ +public class ImportSession { + public static final String ERROR_MESSAGE_FILE_OPEN = "An excel file is already open."; + public static final String ERROR_MESSAGE_INVALID_FILEPATH = "Please check the path to your file."; + public static final String ERROR_MESSAGE_READ_PERMISSION = "Please enable file read permission."; + public static final String ERROR_MESSAGE_FILE_FORMAT = "Unable to read the format of file. " + + "Please ensure the file is in .xls or .xlsx format"; + public static final String ERROR_MESSAGE_IO_EXCEPTION = "Unable to read file. Please close the file and try again."; + private static ImportSession session; + + private boolean initialized; + private File inFile; + private File tempFile; + private Workbook workbook; // write comments to column after last row, with approval status + private SessionData sessionData; + private File outFile; + + private ImportSession() { + initialized = false; + } + + public static ImportSession getInstance() { + if (session == null) { + session = new ImportSession(); + } + return session; + } + + public static String getTimeStamp() { + return new SimpleDateFormat("yyyy.MM.dd.HH.mm.ss").format(new Date()); + } + + /** + * Returns whether (@code ImportSession) has been initialized with an excel spreadsheet + */ + public boolean isInitialized() { + return initialized; + } + + /** + * Opens excel file specified by (@code filepath) and initializes (@code SessionData) to support import operations + */ + public void initializeSession(String filePath) throws FileAccessException, FileFormatException { + if (inFile != null) { + throw new FileAccessException(ERROR_MESSAGE_FILE_OPEN); + } + File file = new File (filePath); + if (!file.exists()) { + throw new FileAccessException(ERROR_MESSAGE_INVALID_FILEPATH); + } else if (!file.canRead()) { + throw new FileFormatException(ERROR_MESSAGE_READ_PERMISSION); + } + try { + workbook = createWorkBook(file); + } catch (InvalidFormatException e) { + throw new FileFormatException(ERROR_MESSAGE_FILE_FORMAT); + } catch (IOException e) { + throw new FileFormatException(ERROR_MESSAGE_IO_EXCEPTION); + } + // the file is good to go + inFile = file; + initializeSessionData(); + initialized = true; + } + + /** + * Attempts to parse the column headers and retrieve job entries + */ + private void initializeSessionData() throws FileFormatException { + SheetWithHeaderFields sheetWithHeaderFields; + SheetParser sheetParser; + Sheet sheet; + sessionData = new SessionData(); + + for (int i = 0; i < workbook.getNumberOfSheets(); i++) { + sheet = workbook.getSheetAt(workbook.getFirstVisibleTab() + i); + sheetParser = new SheetParser(sheet); + sheetWithHeaderFields = sheetParser.parseSheetWithHeaderField(); + sessionData.addSheet(sheetWithHeaderFields); + } + } + + /** + * Attempts to create a (@code Workbook) for a given (@code File) + */ + private Workbook createWorkBook(File file) throws IOException, InvalidFormatException { + tempFile = new File(file.getPath() + getTimeStamp() + file.getName()); + FileOutputStream fileOutputStream = new FileOutputStream(tempFile); + Workbook workbook = WorkbookFactory.create(file); + workbook.write(fileOutputStream); + workbook.close(); + workbook = WorkbookFactory.create(tempFile); + return workbook; + } + + public void reviewAllRemainingJobEntries(boolean approve) throws DataIndexOutOfBoundsException { + sessionData.reviewAllRemainingJobEntries(approve, "Imported with no comments."); + } + + public SessionData getSessionData() { + return sessionData; + } + + /** + * Flushes feedback to (@code outFile) and releases resources. Currently not persistent. + */ + public void closeSession() throws DataIndexOutOfBoundsException, IOException { + if (!initialized) { + return; + } + if (outFile == null) { // does not check if a file exists + String timeStamp = getTimeStamp(); + outFile = new File(inFile.getPath() + timeStamp + inFile.getName()); + } + FileOutputStream fileOut = new FileOutputStream(outFile); + System.out.println(outFile.getName()); + workbook.write(fileOut); + fileOut.close(); + workbook.close(); + tempFile.deleteOnExit(); + freeResources(); + } + + /** + * Releases resources associated with ImportSession by nulling field + */ + private void freeResources() { + workbook = null; + sessionData = null; + inFile = null; + outFile = null; + } + + /** + * For localized testing purposes + */ + public static void main(String[] args) { + ImportSession importSession = getInstance(); + String path; + try { + path = new File(".").getCanonicalPath(); + System.out.println(path); + } catch (IOException e) { + e.printStackTrace(); + } + try { + importSession.initializeSession( + ".\\src\\test\\resources\\model.session.ImportSessionTest\\CS2103-testsheet.xlsx"); + importSession.reviewAllRemainingJobEntries(true); + importSession.closeSession(); + } catch (Exception e) { + e.printStackTrace(); + } + + } +} diff --git a/src/main/java/seedu/address/model/session/JobEntry.java b/src/main/java/seedu/address/model/session/JobEntry.java new file mode 100644 index 000000000000..76b7118a8db3 --- /dev/null +++ b/src/main/java/seedu/address/model/session/JobEntry.java @@ -0,0 +1,92 @@ +package seedu.address.model.session; + +import static seedu.address.model.remark.Remark.isValidRemark; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import seedu.address.model.job.Date; +import seedu.address.model.job.Job; +import seedu.address.model.job.JobNumber; +import seedu.address.model.job.Status; +import seedu.address.model.job.VehicleNumber; +import seedu.address.model.person.Person; +import seedu.address.model.person.UniqueEmployeeList; +import seedu.address.model.remark.Remark; +import seedu.address.model.remark.RemarkList; + +//@@author yuhongherald +/** + * Represents a job entry in an (@link ImportSession) + */ +public class JobEntry extends Job implements ExcelRowReference { + public static final String NEWLINE = "\n"; + + private final int sheetNumber; + private final int rowNumber; + + private boolean reviewed; + private boolean approved; + private final ArrayList comments; + + public JobEntry (Person client, VehicleNumber vehicleNumber, JobNumber jobNumber, Date date, + UniqueEmployeeList assignedEmployees, Status status, RemarkList remarks, + int sheetNumber, int rowNumber, String importComment) { + super(client, vehicleNumber, jobNumber, date, assignedEmployees, status, remarks); + this.sheetNumber = sheetNumber; + this.rowNumber = rowNumber; + comments = new ArrayList<>(); + addComment(importComment); + reviewed = false; + } + + /** + * Adds a non-empty comment to both remarks and comments. + */ + private void addComment(String comment) { + if (comment != null && isValidRemark(comment)) { + remarks.add(new Remark(comment)); + comments.add(comment); + } + } + + public boolean isReviewed() { + return reviewed; + } + + public boolean isApproved() { + return approved; + } + + /** + * Marks (@code JobEntry) as reviewed. + * @param approved whether (@code JobEntry) is going to be added to CarviciM + * @param comment feedback for (@code JobEntry) in String representation + */ + public void review(boolean approved, String comment) { + this.approved = approved; + addComment(comment); + } + + public List getComments() { + return Collections.unmodifiableList(comments); + } + + public String getCommentsAsString() { + StringBuilder stringBuilder = new StringBuilder(); + for (String comment : comments) { + stringBuilder.append(comment); + stringBuilder.append(NEWLINE); + } + return stringBuilder.toString(); + } + + @Override public int getSheetNumber() { + return sheetNumber; + } + + @Override public int getRowNumber() { + return rowNumber; + } +} diff --git a/src/main/java/seedu/address/model/session/RowData.java b/src/main/java/seedu/address/model/session/RowData.java new file mode 100644 index 000000000000..e39145e97287 --- /dev/null +++ b/src/main/java/seedu/address/model/session/RowData.java @@ -0,0 +1,61 @@ +package seedu.address.model.session; + +import java.util.ArrayList; + +import org.apache.poi.ss.usermodel.DataFormatter; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.Workbook; + +import seedu.address.model.session.exceptions.DataIndexOutOfBoundsException; + +//@@author yuhongherald +/** + * Represents a field that spans one or more columns + */ +public class RowData implements ExcelColumnSpannable { + private final int startIndex; + private final int endIndex; + + public RowData(int startIndex, int endIndex) { + this.startIndex = startIndex; + this.endIndex = endIndex; + } + + @Override + public int getStartIndex() { + return startIndex; + } + + @Override + public int getEndIndex() { + return endIndex; + } + + @Override + public ArrayList readData(Workbook workbook, int sheetNumber, int rowNumber) + throws DataIndexOutOfBoundsException { + if (sheetNumber < workbook.getFirstVisibleTab() + || sheetNumber >= workbook.getNumberOfSheets() + workbook.getFirstVisibleTab()) { + throw new DataIndexOutOfBoundsException("Sheets", workbook.getFirstVisibleTab(), + workbook.getNumberOfSheets() + workbook.getFirstVisibleTab(), sheetNumber); + } + Sheet sheet = workbook.getSheetAt(sheetNumber); + return readDataFromSheet(sheet, rowNumber); + } + + @Override + public ArrayList readDataFromSheet(Sheet sheet, int rowNumber) + throws DataIndexOutOfBoundsException { + if (rowNumber < sheet.getFirstRowNum() || rowNumber > sheet.getLastRowNum()) { + throw new DataIndexOutOfBoundsException("Rows", sheet.getFirstRowNum(), sheet.getLastRowNum(), rowNumber); + } + Row row = sheet.getRow(rowNumber); + ArrayList data = new ArrayList<>(); + DataFormatter dataFormatter = new DataFormatter(); + for (int i = startIndex; i <= endIndex; i++) { + data.add(dataFormatter.formatCellValue(row.getCell(i))); + } + return data; + } +} diff --git a/src/main/java/seedu/address/model/session/SessionData.java b/src/main/java/seedu/address/model/session/SessionData.java new file mode 100644 index 000000000000..02bf773c05bd --- /dev/null +++ b/src/main/java/seedu/address/model/session/SessionData.java @@ -0,0 +1,89 @@ +package seedu.address.model.session; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; + +import java.util.List; + +import seedu.address.model.session.exceptions.DataIndexOutOfBoundsException; + +//@@author yuhongherald +/** + * A data structure used to keep track of job entries in an (@code ImportSession) + */ +public class SessionData { + public static final String ERROR_MESSAGE_EMPTY_UNREVIWED_JOB_LIST = "There are no unreviewed job entries left!"; + + private final ArrayList unreviewedJobEntries; + private final ArrayList reviewedJobEntries; + private final ArrayList sheets; + + // will be using an ObservableList + + SessionData() { + unreviewedJobEntries = new ArrayList<>(); + reviewedJobEntries = new ArrayList<>(); + sheets = new ArrayList<>(); + } + + /** + * @return a copy of unreviewed job entries stored in this sheet + */ + public List getUnreviewedJobEntries() { + return Collections.unmodifiableList(unreviewedJobEntries); + } + + /** + * @return a copy of reviewed job entries stored in this sheet + */ + public List getReviewedJobEntries() { + return Collections.unmodifiableList(reviewedJobEntries); + } + + /** + * Adds job entries from (@code sheetWithHeaderFields) into (@code SessionData) + */ + public void addSheet(SheetWithHeaderFields sheetWithHeaderFields) { + Iterator jobEntryIterator = sheetWithHeaderFields.iterator(); + while (jobEntryIterator.hasNext()) { + unreviewedJobEntries.add(jobEntryIterator.next()); + } + sheets.add(sheetWithHeaderFields.getSheetIndex(), sheetWithHeaderFields); + } + + /** + * Reviews all remaining jobs using (@code reviewJobEntry) + */ + void reviewAllRemainingJobEntries(boolean approved, String comments) throws DataIndexOutOfBoundsException { + while (!getUnreviewedJobEntries().isEmpty()) { + reviewJobEntry(0, approved, comments); + } + } + + /** + * Reviews a (@code JobEntry) specified by (@code listIndex) + * @param listIndex index of (@code JobEntry) in (@code unreviewedJobEntries) + * @param approved whether job entry will be added to CarviciM + * @param comments feedback in string representation + */ + void reviewJobEntry(int listIndex, boolean approved, String comments) throws DataIndexOutOfBoundsException { + if (unreviewedJobEntries.isEmpty()) { + throw new IllegalStateException(ERROR_MESSAGE_EMPTY_UNREVIWED_JOB_LIST); + } else if (listIndex < 0 || listIndex >= unreviewedJobEntries.size()) { + throw new DataIndexOutOfBoundsException("Rows", 0, unreviewedJobEntries.size(), listIndex); + } + + JobEntry jobEntry = unreviewedJobEntries.get(listIndex); + jobEntry.review(approved, comments); + unreviewedJobEntries.remove(jobEntry); + reviewedJobEntries.add(jobEntry); + SheetWithHeaderFields sheet = sheets.get(jobEntry.getSheetNumber()); + sheet.commentJobEntry(jobEntry.getRowNumber(), jobEntry.getCommentsAsString()); + if (approved) { + sheet.approveJobEntry(jobEntry.getRowNumber()); + } else { + sheet.rejectJobEntry(jobEntry.getRowNumber()); + } + } +} diff --git a/src/main/java/seedu/address/model/session/SheetParser.java b/src/main/java/seedu/address/model/session/SheetParser.java new file mode 100644 index 000000000000..bac11adaf0d3 --- /dev/null +++ b/src/main/java/seedu/address/model/session/SheetParser.java @@ -0,0 +1,147 @@ +package seedu.address.model.session; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; + +import org.apache.poi.ss.usermodel.DataFormatter; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Sheet; + +import seedu.address.model.session.exceptions.FileFormatException; + +//@@author yuhongherald +/** + * a + */ +public class SheetParser { + public static final String INVALID_FIELD = "INVALID_FIELD"; + public static final String ERROR_MESSAGE_MISSING_FIELDS = "Missing header fields: "; + public static final String ERROR_MESSAGE_DUPLICATE_FIELDS = "Duplicate header field: "; + + // Compulsory header fields + public static final String CLIENT_NAME = "client name"; + public static final String CLIENT_PHONE = "client phone"; + public static final String CLIENT_EMAIL = "client email"; + public static final String VEHICLE_NUMBER = "vehicle number"; + public static final String EMPLOYEE_NAME = "employee name"; + public static final String EMPLOYEE_PHONE = "employee phone"; + public static final String EMPLOYEE_EMAIL = "employee email"; + + // Comment header fields + public static final String APPROVAL_STATUS = "approval status"; + public static final String COMMENTS = "comments"; + public static final int APPROVAL_STATUS_INDEX = 0; + public static final int COMMENTS_INDEX = 1; + + // Optional header fields + // public static final String JOB_NUMBER = "job number"; + // public static final String DATE = "date"; + public static final String STATUS = "status"; + public static final String REMARKS = "remarks"; + + + public static final String[] JOB_ENTRY_COMPULSORY_FIELDS = { // ignore case when reading headings + CLIENT_NAME, CLIENT_PHONE, CLIENT_EMAIL, VEHICLE_NUMBER, EMPLOYEE_NAME, EMPLOYEE_PHONE, EMPLOYEE_EMAIL + }; + public static final String[] JOB_ENTRY_OPTIONAL_FIELDS = { // ignore case when reading headings + STATUS, REMARKS + }; + public static final String MESSAGE_SEPARATOR = ", "; + + private final Sheet sheet; + private final ArrayList missingCompulsoryFields; + private final ArrayList missingOptionalFields; + private final HashMap compulsoryFields; + private final HashMap commentFields; + private final HashMap optionalFields; + + + public SheetParser(Sheet sheet) { + this.sheet = sheet; + missingCompulsoryFields = new ArrayList<>( + Arrays.asList(JOB_ENTRY_COMPULSORY_FIELDS)); + missingOptionalFields = new ArrayList<>( + Arrays.asList(JOB_ENTRY_OPTIONAL_FIELDS)); + compulsoryFields = new HashMap<>(); + commentFields = new HashMap<>(); + optionalFields = new HashMap<>(); + } + + /** + * Reads the (@code Sheet) and converts it into (@code SheetWithHeaderFields) + */ + public SheetWithHeaderFields parseSheetWithHeaderField() throws FileFormatException { + parseFirstRow(); + if (!missingCompulsoryFields.isEmpty()) { + StringBuilder stringBuilder = new StringBuilder(ERROR_MESSAGE_MISSING_FIELDS); + for (String field : missingCompulsoryFields) { + stringBuilder.append(field); + stringBuilder.append(MESSAGE_SEPARATOR); + } + throw new FileFormatException(stringBuilder.toString()); + } + createCommentField(APPROVAL_STATUS, APPROVAL_STATUS_INDEX); + createCommentField(COMMENTS, COMMENTS_INDEX); + return new SheetWithHeaderFields(sheet, compulsoryFields, commentFields, optionalFields); + } + + /** + * Creates a new column with header (@code name) (@code offset) columns after last column. + * @param offset + */ + private void createCommentField(String name, int offset) { + int index = sheet.getRow(sheet.getFirstRowNum()).getLastCellNum() + offset; + commentFields.put(name, new RowData(index, index)); + } + + /** + * Processes the header fields in the first row into (@code headerFields) and throws (@code FileFormatException) + * if there are missing compulsory header fields + */ + private void parseFirstRow() throws FileFormatException { + Row firstRow = sheet.getRow(sheet.getFirstRowNum()); + DataFormatter dataFormatter = new DataFormatter(); + int lastFieldIndex = firstRow.getLastCellNum(); + String lastField = INVALID_FIELD; + String currentField; + // traverse the row from the back to assist detecting end of row + for (int i = firstRow.getLastCellNum(); i >= firstRow.getFirstCellNum(); i--) { + currentField = dataFormatter.formatCellValue(firstRow.getCell(i)).toLowerCase(); + if (currentField.equals(lastField)) { + continue; + } + if (!isFieldPresent(currentField)) { + lastField = INVALID_FIELD; + lastFieldIndex = i - 1; + continue; + } + addHeaderField(currentField, new RowData(i, lastFieldIndex)); + lastField = currentField; + lastFieldIndex = i - 1; + } + } + + /** + * Removes header field from (@code missingCompulsoryFields) or (@code missingOptionalFields) and + * places it into (@code headerFields) + */ + private void addHeaderField(String currentField, RowData rowData) throws FileFormatException { + if (missingCompulsoryFields.contains(currentField)) { + missingCompulsoryFields.remove(currentField); + compulsoryFields.put(currentField, rowData); + } else if (missingOptionalFields.contains(currentField)) { + missingOptionalFields.remove(currentField); + optionalFields.put(currentField, rowData); + } else { + throw new FileFormatException(ERROR_MESSAGE_DUPLICATE_FIELDS + currentField); + } + } + + /** + * Checks if (@code field) is present in (@code fields), ignoring case + */ + private boolean isFieldPresent(String field) { + return missingCompulsoryFields.contains(field) || missingOptionalFields.contains(field); + } +} diff --git a/src/main/java/seedu/address/model/session/SheetWithHeaderFields.java b/src/main/java/seedu/address/model/session/SheetWithHeaderFields.java new file mode 100644 index 000000000000..be2ff3113a76 --- /dev/null +++ b/src/main/java/seedu/address/model/session/SheetWithHeaderFields.java @@ -0,0 +1,319 @@ +package seedu.address.model.session; + +import static java.util.Objects.requireNonNull; +import static seedu.address.model.job.Status.STATUS_CLOSED; +import static seedu.address.model.job.Status.STATUS_ONGOING; +import static seedu.address.model.job.VehicleNumber.isValidVehicleNumber; +import static seedu.address.model.person.Email.isValidEmail; +import static seedu.address.model.person.Name.isValidName; +import static seedu.address.model.person.Phone.isValidPhone; +import static seedu.address.model.remark.Remark.isValidRemark; +import static seedu.address.model.session.SheetParser.APPROVAL_STATUS; +import static seedu.address.model.session.SheetParser.CLIENT_EMAIL; +import static seedu.address.model.session.SheetParser.CLIENT_NAME; +import static seedu.address.model.session.SheetParser.CLIENT_PHONE; +import static seedu.address.model.session.SheetParser.COMMENTS; +import static seedu.address.model.session.SheetParser.EMPLOYEE_EMAIL; +import static seedu.address.model.session.SheetParser.EMPLOYEE_NAME; +import static seedu.address.model.session.SheetParser.EMPLOYEE_PHONE; +import static seedu.address.model.session.SheetParser.REMARKS; +import static seedu.address.model.session.SheetParser.STATUS; +import static seedu.address.model.session.SheetParser.VEHICLE_NUMBER; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; + +import java.util.Iterator; + +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.CellStyle; +import org.apache.poi.ss.usermodel.Font; +import org.apache.poi.ss.usermodel.IndexedColors; +import org.apache.poi.ss.usermodel.Sheet; + +import seedu.address.model.job.Date; +import seedu.address.model.job.JobNumber; +import seedu.address.model.job.Status; +import seedu.address.model.job.VehicleNumber; +import seedu.address.model.person.Email; +import seedu.address.model.person.Employee; +import seedu.address.model.person.Name; +import seedu.address.model.person.Person; +import seedu.address.model.person.Phone; +import seedu.address.model.person.UniqueEmployeeList; +import seedu.address.model.person.exceptions.DuplicateEmployeeException; +import seedu.address.model.remark.Remark; +import seedu.address.model.remark.RemarkList; +import seedu.address.model.session.exceptions.DataIndexOutOfBoundsException; +import seedu.address.model.session.exceptions.FileFormatException; + +//@@uathor yuhongherald +/** + * A data structure used to store header field information of a given excel sheet + */ +public class SheetWithHeaderFields implements Iterable { + public static final String SEPARATOR = ", "; + + private static final String ERROR_MESSAGE_EMPTY_SHEET = "Sheet %d contains no valid job entries!"; + private static final String ERROR_MESSAGE_CORRUPT_JOB_ENTRY = "The following fields are corrupt: "; + + private final Sheet sheet; + private final HashMap compulsoryFields; + private final HashMap commentFields; + private final HashMap optionalFields; + private final JobEntry firstJobEntry; + + SheetWithHeaderFields(Sheet sheet, HashMap compulsoryFields, + HashMap commentFields, + HashMap optionalFields) throws FileFormatException { + this.sheet = sheet; + this.compulsoryFields = new HashMap<>(compulsoryFields); + this.commentFields = new HashMap<>(commentFields); + this.optionalFields = new HashMap<>(optionalFields); + firstJobEntry = getFirstJobEntry(); + if (firstJobEntry == null) { + throw new FileFormatException(getEmptySheetMessage()); + } + } + + /** + * Marks job at (@code row) as rejected + */ + public void rejectJobEntry(int row) { + int index = commentFields.get(APPROVAL_STATUS).getStartIndex(); + Cell cell = sheet.getRow(row).createCell(index); + Font fontStyle = sheet.getWorkbook().createFont(); + fontStyle.setBold(true); + fontStyle.setFontHeightInPoints((short) 14); + fontStyle.setColor(IndexedColors.RED.getIndex()); + CellStyle cellStyle = sheet.getWorkbook().createCellStyle(); + cellStyle.setFont(fontStyle); + cell.setCellValue("rejected"); + } + + /** + * Marks job at (@code row) as accepted + */ + public void approveJobEntry(int row) { + int index = commentFields.get(APPROVAL_STATUS).getStartIndex(); + Cell cell = sheet.getRow(row).createCell(index); + Font fontStyle = sheet.getWorkbook().createFont(); + fontStyle.setBold(true); + fontStyle.setFontHeightInPoints((short) 14); + fontStyle.setColor(IndexedColors.GREEN.getIndex()); + CellStyle cellStyle = sheet.getWorkbook().createCellStyle(); + cellStyle.setFont(fontStyle); + cell.setCellValue("accepted"); + } + + /** + * Appends a comment for job at (@oode row) + */ + public void commentJobEntry(int row, String comment) { + int index = commentFields.get(COMMENTS).getStartIndex(); + Cell cell = sheet.getRow(row).createCell(index); + Font fontStyle = sheet.getWorkbook().createFont(); + fontStyle.setBold(true); + fontStyle.setFontHeightInPoints((short) 14); + fontStyle.setColor(IndexedColors.BLUE.getIndex()); + CellStyle cellStyle = sheet.getWorkbook().createCellStyle(); + cellStyle.setFont(fontStyle); + cell.setCellValue(comment); + } + + public int getSheetIndex() { + return sheet.getWorkbook().getSheetIndex(sheet); + } + + public String getEmptySheetMessage() { + return String.format(ERROR_MESSAGE_EMPTY_SHEET, getSheetIndex()); + } + + public String getCorruptedFieldsMessage(Person client, VehicleNumber vehicleNumber, Employee employee) { + StringBuilder corruptedComponents = new StringBuilder(ERROR_MESSAGE_CORRUPT_JOB_ENTRY); + if (client == null) { + corruptedComponents.append("client"); + corruptedComponents.append(SEPARATOR); + } + if (vehicleNumber == null) { + corruptedComponents.append("vehicle number"); + corruptedComponents.append(SEPARATOR); + } + if (employee == null) { + corruptedComponents.append("employee"); + corruptedComponents.append(SEPARATOR); + } + return corruptedComponents.toString(); + } + + /** + * Looks for first (@code JobEtnry) with no missing fields and returns it + * @throws FileFormatException if no valid job entries + */ + private JobEntry getFirstJobEntry() throws FileFormatException { + Person client; + VehicleNumber vehicleNumber; + Employee employee; + + UniqueEmployeeList employeeList; + Status status; + RemarkList remarkList; + for (int i = sheet.getFirstRowNum() + 1; i <= sheet.getLastRowNum(); i++) { + client = getClient(i); + vehicleNumber = getVehicleNumber(i); + employee = getEmployee(i); + + if (client == null || vehicleNumber == null || employee == null) { + rejectJobEntry(i); + commentJobEntry(i, getCorruptedFieldsMessage(client, vehicleNumber, employee)); + continue; + } + employeeList = new UniqueEmployeeList(); + try { + employeeList.add(employee); + } catch (DuplicateEmployeeException e) { + e.printStackTrace(); // should not happen + } + status = getStatus(i); + remarkList = getRemarks(i); + return new JobEntry(client, vehicleNumber, new JobNumber(), new Date(), employeeList, status, remarkList, + getSheetIndex(), i, ""); + } + throw new FileFormatException(getEmptySheetMessage()); + } + + private Person getClient(int rowNumber) { + String name = readFirstData(compulsoryFields.get(CLIENT_NAME), rowNumber); + String phone = readFirstData(compulsoryFields.get(CLIENT_PHONE), rowNumber); + String email = readFirstData(compulsoryFields.get(CLIENT_EMAIL), rowNumber); + if (isValidName(name) && isValidPhone(phone) && isValidEmail(email)) { + return new Person(new Name(name), new Phone(phone), new Email(email)); + } + return null; + } + + private VehicleNumber getVehicleNumber(int rowNumber) { + String vehicleNumber = readFirstData(compulsoryFields.get(VEHICLE_NUMBER), rowNumber); + if (vehicleNumber != null && !vehicleNumber.isEmpty() && isValidVehicleNumber(vehicleNumber)) { + return new VehicleNumber(vehicleNumber); + } + return null; + } + + private Employee getEmployee(int rowNumber) { + String name = readFirstData(compulsoryFields.get(EMPLOYEE_NAME), rowNumber); + String phone = readFirstData(compulsoryFields.get(EMPLOYEE_PHONE), rowNumber); + String email = readFirstData(compulsoryFields.get(EMPLOYEE_EMAIL), rowNumber); + if (isValidName(name) && isValidPhone(phone) && isValidEmail(email)) { + return new Employee(new Name(name), new Phone(phone), new Email(email), Collections.emptySet()); + } + return null; + } + + private Status getStatus(int rowNumber) { + RowData optionalStatus = optionalFields.get(STATUS); + if (optionalStatus == null) { + return new Status(STATUS_ONGOING); + } + String status = readFirstData(optionalStatus, rowNumber).toLowerCase(); + if (status.equals((STATUS_CLOSED))) { + return new Status(status); + } + return new Status(STATUS_ONGOING); + } + + private RemarkList getRemarks(int rowNumber) { + RowData optionalRemarks = optionalFields.get(REMARKS); + if (optionalRemarks == null) { + return new RemarkList(); + } + RemarkList remarkList = new RemarkList(); + ArrayList remarks = readListData(optionalFields.get(REMARKS), rowNumber); + for (String remark : remarks) { + if (isValidRemark(remark)) { + remarkList.add(new Remark(remark)); + } + } + return remarkList; + } + + /** + * Reads single entry (@ocde rowDta) from row (@code rowNumber) + */ + private String readFirstData(RowData rowData, int rowNumber) { + requireNonNull(rowData); + try { + return rowData.readDataFromSheet(sheet, rowNumber).get(0); + } catch (DataIndexOutOfBoundsException e) { + throw new IndexOutOfBoundsException(e.getMessage()); // should be within bounds + } + } + + /** + * Reads all entries (@ocde rowDta) from row (@code rowNumber) + */ + private ArrayList readListData(RowData rowData, int rowNumber) { + requireNonNull(rowData); + try { + return rowData.readDataFromSheet(sheet, rowNumber); + } catch (DataIndexOutOfBoundsException e) { + throw new IndexOutOfBoundsException(e.getMessage()); // should be within bounds + } + } + /** + * Retrieves the job at (@code rowNumber), and missing details from those in first job entry. + * Adds missing detail in remarks. + */ + private JobEntry getJobEntryAt(int rowNumber, JobEntry previousEntry) { + Person client = getClient(rowNumber); + VehicleNumber vehicleNumber = getVehicleNumber(rowNumber); + Employee employee = getEmployee(rowNumber); + String importMessage = ""; + if (client == null || vehicleNumber == null || employee == null) { + importMessage = getCorruptedFieldsMessage(client, vehicleNumber, employee); + } + // commentJobEntry(rowNumber, importMessage); moved to SessionData when reviewing + if (client == null) { + client = previousEntry.getClient(); + } + if (vehicleNumber == null) { + vehicleNumber = previousEntry.getVehicleNumber(); + } + if (employee == null) { + employee = previousEntry.getAssignedEmployees().iterator().next(); + } + + UniqueEmployeeList employeeList = new UniqueEmployeeList(); + try { + employeeList.add(employee); + } catch (DuplicateEmployeeException e) { + e.printStackTrace(); // should not happen + } + Status status = getStatus(rowNumber); + RemarkList remarkList = getRemarks(rowNumber); + return new JobEntry(client, vehicleNumber, new JobNumber(), new Date(), employeeList, status, remarkList, + sheet.getWorkbook().getSheetIndex(sheet), rowNumber, importMessage); + } + + @Override + public Iterator iterator() { + return new Iterator() { + private int currentRow = firstJobEntry.getRowNumber(); + private JobEntry previousEntry; + + @Override public boolean hasNext() { + return (currentRow <= sheet.getLastRowNum()); + } + + @Override public JobEntry next() { + if (!hasNext()) { + return null; + } + previousEntry = getJobEntryAt(currentRow, previousEntry); + currentRow++; + return previousEntry; + } + }; + } +} diff --git a/src/main/java/seedu/address/model/session/exceptions/DataIndexOutOfBoundsException.java b/src/main/java/seedu/address/model/session/exceptions/DataIndexOutOfBoundsException.java new file mode 100644 index 000000000000..80cc0ca85754 --- /dev/null +++ b/src/main/java/seedu/address/model/session/exceptions/DataIndexOutOfBoundsException.java @@ -0,0 +1,13 @@ +package seedu.address.model.session.exceptions; + +//@@author yuhongherald +/** + * Represents an error which occurs when trying to access data out of specified range. + */ +public class DataIndexOutOfBoundsException extends Exception { + public static final String ERROR_MESSAGE = "%s expected index %d to %d, but got %d"; + + public DataIndexOutOfBoundsException(String field, int lower, int upper, int actual) { + super(String.format(ERROR_MESSAGE, field, lower, upper, actual)); + } +} diff --git a/src/main/java/seedu/address/model/session/exceptions/FileAccessException.java b/src/main/java/seedu/address/model/session/exceptions/FileAccessException.java new file mode 100644 index 000000000000..305d450c7071 --- /dev/null +++ b/src/main/java/seedu/address/model/session/exceptions/FileAccessException.java @@ -0,0 +1,11 @@ +package seedu.address.model.session.exceptions; + +//@@author yuhongherald +/** + * Represents an error from attempting to read an excel file in {@link seedu.address.model.session.ImportSession}. + */ +public class FileAccessException extends Exception { + public FileAccessException(String message) { + super(message); + } +} diff --git a/src/main/java/seedu/address/model/session/exceptions/FileFormatException.java b/src/main/java/seedu/address/model/session/exceptions/FileFormatException.java new file mode 100644 index 000000000000..ca4a29e5fd74 --- /dev/null +++ b/src/main/java/seedu/address/model/session/exceptions/FileFormatException.java @@ -0,0 +1,11 @@ +package seedu.address.model.session.exceptions; + +//@@author yuhongherald +/** + * Represents an error from attempting to read an excel file in {@link seedu.address.model.session.ImportSession}. + */ +public class FileFormatException extends Exception { + public FileFormatException(String message) { + super(message); + } +} diff --git a/src/main/java/seedu/address/model/session/exceptions/InvalidDataException.java b/src/main/java/seedu/address/model/session/exceptions/InvalidDataException.java new file mode 100644 index 000000000000..6f22d20f4d09 --- /dev/null +++ b/src/main/java/seedu/address/model/session/exceptions/InvalidDataException.java @@ -0,0 +1,11 @@ +package seedu.address.model.session.exceptions; + +//@@author yuhongherald +/** + * Represents an error when data supplied to {@link seedu.address.model.session.SessionData} is in wrong format. + */ +public class InvalidDataException extends Exception { + public InvalidDataException(String message) { + super(message); + } +} diff --git a/src/test/java/seedu/address/logic/commands/AddCommandTest.java b/src/test/java/seedu/address/logic/commands/AddCommandTest.java index 9dccea95bd1c..59252dd80637 100644 --- a/src/test/java/seedu/address/logic/commands/AddCommandTest.java +++ b/src/test/java/seedu/address/logic/commands/AddCommandTest.java @@ -8,6 +8,8 @@ import java.util.ArrayList; import java.util.Arrays; +import java.util.List; +import java.util.Set; import java.util.function.Predicate; import org.junit.Rule; @@ -113,6 +115,16 @@ public void addPerson(Employee employee) throws DuplicateEmployeeException { fail("This method should not be called."); } + @Override + public void addJobs(List job) { + fail("This method should not be called."); + } + + @Override + public void addMissingEmployees(Set employees) { + fail("This method should not be called."); + } + @Override public void resetData(ReadOnlyAddressBook newData, CommandWords newCommandWords) { fail("This method should not be called."); diff --git a/src/test/java/seedu/address/logic/commands/AddJobCommandTest.java b/src/test/java/seedu/address/logic/commands/AddJobCommandTest.java index 3b69977eb474..1eaf61b9c8c0 100644 --- a/src/test/java/seedu/address/logic/commands/AddJobCommandTest.java +++ b/src/test/java/seedu/address/logic/commands/AddJobCommandTest.java @@ -8,6 +8,8 @@ import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; import java.util.ArrayList; +import java.util.List; +import java.util.Set; import java.util.function.Predicate; import org.junit.Rule; @@ -71,6 +73,16 @@ public void addPerson(Employee employee) throws DuplicateEmployeeException { fail("This method should not be called."); } + @Override + public void addJobs(List job) { + fail("This method should not be called."); + } + + @Override + public void addMissingEmployees(Set employees) { + fail("This method should not be called."); + } + @Override public void resetData(ReadOnlyAddressBook newData, CommandWords newCommandWords) { fail("This method should not be called."); diff --git a/src/test/resources/model.session.ImportSessionTest/CS2103-testsheet.xlsx b/src/test/resources/model.session.ImportSessionTest/CS2103-testsheet.xlsx new file mode 100644 index 000000000000..6f9fadbc3489 Binary files /dev/null and b/src/test/resources/model.session.ImportSessionTest/CS2103-testsheet.xlsx differ diff --git a/src/test/resources/model.session.ImportSessionTest/CS2103-testsheet.xlsx2018.03.25.23.24.19CS2103-testsheet.xlsx b/src/test/resources/model.session.ImportSessionTest/CS2103-testsheet.xlsx2018.03.25.23.24.19CS2103-testsheet.xlsx new file mode 100644 index 000000000000..34e731464b2f Binary files /dev/null and b/src/test/resources/model.session.ImportSessionTest/CS2103-testsheet.xlsx2018.03.25.23.24.19CS2103-testsheet.xlsx differ