PROJECT: Centralised Human Resource System (CHRS)


Overview

Centralised Human Resource System (CHRS) is an application for managing employees within the company. The application is created to assist the Human Resource Department of the company to better manage the employees' information. CHRS is capable of checking work schedule, creating recruitment posts, checking of expenses claims and storage of various information of each employee such as salary, department, position, etc.

Summary of contributions

  • Major enhancement: Added filter and enhanced find command

    • What it does: The filter command allows user to filter out employees of a certain department, rank/position or both and the find command allows user to search for a specific employee via their name or employee ID.

    • Justification: This feature improves the product significantly as the user is able to search for the required employee’s details quickly.

    • Highlights: This enhancement enhances the user experience as the user can now search for an employee’s details without having the need to go through the whole list of employees. This feature required in-depth analysis of possible design alternatives and in-depth understanding of the HR needs when searching. The implementation of this feature was challenging as it required enhancement to existing commands and understanding of predicates.

  • Minor enhancements:

    • Added sorting to PersonList, ExpensesList and ScheduleList to ensure that the lists stay sorted in ascending order. (Pull request: #219)

    • Enhanced add and edit commands to reject having employees with duplication in these fields: EmployeeId OR Email OR Phone OR Name and DateOfBirth. Should any attempt of duplication occur, the application will display the employee being duplicated and reject the command. (Pull requests: #124, #134, #136, #219)

  • Code contributed: [Reposense Dashboard]

  • Other contributions:

    • Project management:

      • Managed releases v1.1 - v1.4 (5 releases) on GitHub

        • Managed .jar file release and description of each release

      • Managed the approval of pull requests

      • Set up issue tracker, milestones and labels

      • Ensures that the team is on track and is able to complete each milestone’s requirements

    • Enhancements to existing features:

      • Added EmployeeId, DateOfBirth, Department, Rank/Position, Salary and Bonus fields (Pull request: #18)

    • Test Cases:

      • Wrote additional tests for existing features to increase coverage from 88.7% to 92.6% (#240), 61.2% to 63.7% (#223), 53.7% to 61.2% (#221) and 72.9% to 74.1% (#104)

    • Documentation:

      • Updated Read Me (Pull requests: #3, #235)

      • Updated About Us to include team members' images, roles and responsibilities alongside GitHub and Portfolio links (Pull requests: #1, #3, #8, #41, #127)

      • Updated Developer Guide on filter feature alongside model component, user stories, use cases and instructions for manual testing (Pull requests: #11, #13, #14, #15, #97, #109, #235, #240)

      • Updated User Guide to ensure add, edit, find and filter commands alongside the fields added by me are up to date (Pull requests: #95, #156, #167, #172, #183)

    • Community:

      • PRs reviewed (with non-trivial review comments): #73, #123, #125, #130, #140, #182, #218

      • Contributed to forum discussions (examples: 1, 2)

      • Reported bugs and suggestions for other teams in the class (examples: 1, 2, 3, 4, 5)

    • Tools:

      • Travis-CI (#3) and AppVeyor (#9)

Contributions to the User Guide

Given below are sections I contributed to the User Guide. They showcase my ability to write documentation targeting end-users.

Add employee’s data : add

Adds employee’s data to the database

Format: add id/EMPLOYEEID n/NAME dob/DATE_OF_BIRTH p/PHONE_NUMBER e/EMAIL d/DEPARTMENT r/RANK_POSITION a/ADDRESS s/SALARY t/[TAGS]…​

Examples:

  • add id/000001 n/John Doe dob/13/12/2000 p/98765432 e/johnd@example.com d/IT r/Assistant a/John street, block 123, #01-01 s/3000.00 t/FlyKite
    Adds an employee with the fields listed above

  • add id/888888 n/Betsy dob/23/05/1987 p/95544332 e/betsy@example.com d/Account r/Manager a/Betsy street, block 3, #11-01 s/5000.00
    Adds an employee with the fields listed above

Any usage of add command that will result in duplicated employeeId or phone number or email will be rejected. Additionally, duplicated name alongside date of birth will also be rejected.

Edit an existing employee’s data : edit

Edit an existing employee’s data in CHRS.

Format: edit INDEX [n/NAME] [p/PHONE_NUMBER] [a/ADDRESS] [e/EMAIL] [d/DEPARTMENT] [r/RANK_POSITION]

Include at least one field alongside the INDEX. The existing values of the employee will be updated to the input values.

Examples:

  • edit 1 p/98765432 d/HR r/Manager
    Edits employee at index 1 to have the new input of phone, department and rank/position

Any usage of edit command that will result in duplicated phone number or email will be rejected. Additionally, usage of this command to edit an employee’s name to be the same as another employee who has the same date of birth will be rejected.

Locating persons by name or employee ID : find

Find the employee’s name that contains the input or find the employee id that matches the input. The expenses and schedule list will also be updated accordingly to show only the matched employee(s)' expenses and schedule.

Format: find [NAME] [EMPLOYEEID]

Include only one of the fields, either Name or Employee Id.
The NAME parameter is case-insensitive, i.e. The command find john will find the instances of JoHn, joHN, etc.

Examples:

  • find John
    Find all instances of John, and his associated expenses and schedule

  • find 000001
    Find the employee with employee ID 000001, and his/her associated expenses and schedule

Any usage of find command will be rejected if it contains special characters or alphanumeric input. It accepts only either alphabets or numbers in a single input, not both.

Filter and list of specific employee’s data : filter

Filters out the employees whose department and/or rank/position contains the keyword(s) input from the user and list them in ascending or descending name order. The expenses and schedule list will also be updated accordingly to show only the matched employees' expenses and schedule.

Format: filter SORT_ORDER [d/DEPARTMENT] [r/POSITION]

The SORT_ORDER parameter should either be asc for ascending or dsc for descending. The SORT_ORDER parameter is case-insensitive.
Include either department or rank/position or both, at least one of the field must be included alongside the sort order. The keywords are delimited by a space, i.e filter asc d/human resource would mean the keywords are "human" and "resource". The keywords matching is case-insensitive.

Examples:

  • filter asc d/Human Resource r/Manager
    List all employees whose department contains the keyword of either human or resource and rank/position contains the keyword of manager in ascending name order. Expenses and schedule list will also be updated to show only matched employee(s)' expenses and schedule(s).

  • filter dsc d/Finance
    List all employees whose department contains the keyword of finance in descending name order. Expenses and schedule list will also be updated to show only matched employee(s)' expenses and schedule(s).

Any usage of filter command that results in the same prefix appearing more than once will be rejected. Example: filter asc d/Human d/Finance will be rejected.

Keyword suggestion and auto complete feature [Upcoming in 2.0]

This feature will suggest keywords when filtering or finding employee and the keyword will be auto-completed by pressing TAB key. [Upcoming in 2.0]

Contributions to the Developer Guide

Given below are sections I contributed to the Developer Guide. They showcase my ability to write technical documentation and the technical depth of my contributions to the project.

Filter Feature

The command filter allows the user to filter employees based on their respective department and/or position within the company.

Current Implementation

The implementation of this command is separated into two phases.

Phase 1

The filter command’s mechanism is facilitated by FilterCommandParser. The FilterCommandParser implements the Parser interface, parses the arguments parameter, which is the input from the user, and creates a new FilterCommand object. Apart from parsing the input, the FilterCommandParser also checks whether the input complies with the required format. The checking of input compliance is assisted by a helper method, didPrefixesAppearOnlyOnce, which checks whether the department prefix and/or position prefix appears only once.

If the input does not comply with the required format, an error message will be shown to the user.

Code snippet of the parse method within FilterCommandParser showing the checks for input compliance:

public FilterCommand parse(String args) throws ParseException {
    ...
    ...

    ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_DEPARTMENT, PREFIX_POSITION);

    if (trimmedArgs.isEmpty() || (!argMultimap.getValue(PREFIX_DEPARTMENT).isPresent()
            && !argMultimap.getValue(PREFIX_POSITION).isPresent()) || !didPrefixesAppearOnlyOnce(trimmedArgs)
            || !ACCEPTED_ORDERS.contains(sortOrder)) {
        throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, FilterCommand.MESSAGE_USAGE));
    }

    ...
    ...
}

Code snippet of the helper method didPrefixesAppearOnlyOnce:

public boolean didPrefixesAppearOnlyOnce(String argument) {
    String departmentPrefix = " " + PREFIX_DEPARTMENT.toString();
    String positionPrefix = " " + PREFIX_POSITION.toString();

    return argument.indexOf(departmentPrefix) == argument.lastIndexOf(departmentPrefix)
            && argument.indexOf(positionPrefix) == argument.lastIndexOf(positionPrefix);
}

If the input complies with the required format, further checks will be performed on the keywords to ensure that the keywords are valid. This keywords checking is completed by the processDepartmentKeywords and processPositionKeywords methods that are both within the FilterCommandParser class. This guide will focus on explaining how processDepartmentKeywords work instead of both as the methods processDepartmentKeywords and processPositionKeywords are similar.

Code snippet of the parse method in FilterCommandParser calling processDepartmentKeywords method:

public FilterCommand parse(String args) throws ParseException {
    ...
    ...

    if (!argMultimap.getValue(PREFIX_DEPARTMENT).isPresent()) {
        filterCommand.setIsDepartmentPrefixPresent(false);
    } else if (argMultimap.getValue(PREFIX_DEPARTMENT).isPresent()
            && !processDepartmentKeywords(argMultimap, filterCommand)) {
        throw new ParseException(Department.MESSAGE_DEPARTMENT_KEYWORD_CONSTRAINTS);
    }

    ...
    ...
}

Code snippet of processDepartmentKeywords helper method:

public boolean processDepartmentKeywords(ArgumentMultimap argMultimap, FilterCommand command) {
    String trimmedDepartment = (argMultimap.getValue(PREFIX_DEPARTMENT).get().trim());
    String[] departmentKeywords = trimmedDepartment.split("\\s+");

    return validityCheckForDepartments(command, departmentKeywords);
}

The processDepartmentKeywords is further assisted by the validityCheckForDepartments method which calls another method areDepartmentKeywordsValid to check whether the given keywords comply with the DEPARTMENT_KEYWORD_VALIDATION_REGEX. If the keywords from the user are all valid, the departmentPredicate will be created and passed to the FilterCommand object through a setter.

The DEPARTMENT_KEYWORD_VALIDATION_REGEX is a regular expression that accepts only spaces and case-insensitive alphabets. It only allows 1 to 30 characters per keyword.

Code snippet of validityCheckForDepartments and areDepartmentKeywordsValid methods in FilterCommandParser class:

public boolean validityCheckForDepartments(FilterCommand command, String[] keywords) {
    if (!areDepartmentKeywordsValid(keywords)) {
        return false;
    }

    command.setIsDepartmentPrefixPresent(true);
    command.setDepartmentPredicate(new DepartmentContainsKeywordsPredicate(Arrays.asList(keywords)));
    return true;
}

public boolean areDepartmentKeywordsValid(String[] keywords) {
    for (String keyword: keywords) {
        if (!keyword.matches(DEPARTMENT_KEYWORD_VALIDATION_REGEX)) {
            return false;
        }
    }
    return true;
}
Phase 2

The FilterCommand is being executed in this phase. FilterCommand first checks which prefix(es) is(are) present prior to calling the updateFilteredPersonList method, which updates filteredPersonList with the employees of the relevant department(s) and/or position(s) that matches the keyword(s) input from the user. Additionally, the methods updateFilteredExpensesList and updateFilteredScheduleList will also be called to update the filteredExpensesList and filteredScheduleList respectively to show only the matched employees' expenses and schedules.

If the keywords input from the user results in 0 person found. The CLI will show the user a list of currently available department(s) and/or position(s) in the PersonList. The listing of available department(s) and/or position(s) depends on the presence of the prefix.
Example: If filter asc d/hello results in 0 person found, the CLI will list all the currently available department(s) in CHRS.

Code snippet of FilterCommand that checks which prefix is present prior to updating filteredPersonList, filteredExpensesList and filteredScheduleList:

public class FilterCommand extends Command {
    ...
    ...

    @Override
    public CommandResult execute(Model model, CommandHistory history) {
        requireNonNull(model);
        String allAvailableDepartments = listAvailableDepartments(model);
        String allAvailablePositions = listAvailablePositions(model);

        if (isDepartmentPrefixPresent && !isPositionPrefixPresent) {
            model.updateFilteredPersonList(departmentPredicate, sortOrder);
        } else if (isPositionPrefixPresent && !isDepartmentPrefixPresent) {
            model.updateFilteredPersonList(positionPredicate, sortOrder);
        } else if (isDepartmentPrefixPresent && isPositionPrefixPresent) {
            model.updateFilteredPersonList(departmentPredicate.and(positionPredicate), sortOrder);
        }

        EmployeeIdExpensesContainsKeywordsPredicate expensesPredicate = generateEmployeeIdExpensesPredicate(model);
        EmployeeIdScheduleContainsKeywordsPredicate schedulePredicate = generateEmployeeIdSchedulePredicate(model);
        model.updateFilteredExpensesList(expensesPredicate);
        model.updateFilteredScheduleList(schedulePredicate);

        return new CommandResult(feedbackToUser(model, allAvailableDepartments, allAvailablePositions));
    }

    ...
    ...
}

The result of the execution of FilterCommand is then encapsulated as a CommandResult object which is returned to the LogicManager. During the execution of FilterCommand, the filteredPersonList, filteredExpensesList and filteredScheduleList will be updated accordingly to display only employees, that belongs to the department(s) and/or hold the specified position(s) as specified by the user input.

The sequence diagram below illustrates the operation of filter command:

filterCommandSequenceDiagram

Design Considerations

Aspect: Implementation of filter command

  • Alternative 1 (current choice): One command to handle filtering by department(s) and/or position(s)

    • Pros: Does not require several command and parser files to handle filtering of different fields

    • Cons: Additional methods required to check for presence of prefix and input validity

  • Alternative 2: Separate commands for filtering department and position, one for department and one for position

    • Pros: Easy to implement as it follows the existing find command

    • Cons: More commands to create and implementation can get quite complicated when filter requires both fields

Future improvements: Keyword suggestion and auto complete feature [Upcoming in 2.0]

This feature will suggest keywords when filtering or finding employee and the keyword will be auto-completed by pressing TAB key. [Upcoming in 2.0]