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: AddExpenses Command and DeleteExpenses Command

    • What it does: The AddExpenses command allows user to create an expenses or modify an expenses that already expenses. DeleteExpenses command allows user to delete an expenses from the expenses list.

    • Justification: This feature improves the product significantly because a user can add or modify expenses that the employee wishes to claim.

    • Highlights: User can add or modify an individual employee’s single or multiple types of expenses in a single command. This enhancement enhances the user experience as the user can store new or update existing expenses and access it at any point time.

  • Minor enhancement:

    • Added SelectExpenses Command to allow user to select any expenses in expenses list

    • Added ClearExpenses Command to allow user to clear the expenses list.

  • Code contributed: [Reposense Dashboard]

  • Other contributions:

    • Project management:

      • Assist in approving, reviewing and merging pull requests

    • Test cases:

      • Wrote additional tests for existing features to increase coverage from 68.678% to 69.978% (#114), 67.323% to 70.223% (#220), 80.621% to 80.921% (#232), 84.889% to 87.789% (#244), 87.744% to 93.744% (#248)

    • Documentation:

      • Updated Developer Guide on addExpenses feature alongside model component and instructions for manual testing (Pull requests: #102, #232, #270)

      • Updated User Guide to ensure addExpenses, deleteExpenses, SelectExpenses and clearExpenses commands alongside the fields added by me are up to date (Pull requests: #114, #154, #174)

    • Community:

      • PRs reviewed (with non-trivial review comments): #69, #103, #107, #99, #70, #129

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

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 expenses claims under a specific employee : addExpenses

Add new expenses for employee or modify expenses if there already exists an expenses

Format: addExpenses id/EMPLOYEEID [tra/TRAVELEXPENSES] [med/MEDICALEXPENSES] [misc/MISCELLANEOUS]
Or ae id/EMPLOYEEID [tra/TRAVELEXPENSES] [med/MEDICALEXPENSES] [misc/MISCELLANEOUS]

At least one of the fields, Travel Expenses, Medical Expenses, Miscellaneous Expenses must be included.

Examples:

  • ae id/000001 tra/111 med/222 misc/333
    Creates a new expenses that contain 111.00, 222.00 and 333.00 for the above fields for employee with employee id '000001'.
    Total Expenses will reflect 666.00.

  • addExpenses id/000002 med/111 misc/222
    Creates a new expenses that contain 111.00, 222.00 for the above fields for employee with employee id '000002'. Expenses will contain 0.00 for fields not included in command.
    Total Expenses will reflect 333.00.

    addExpenses id/000002 tra/111 med/222 misc/-111
    Add 111.00, 222.00 and minus 111.00 for the above fields for employee with employee id '000002'.
    Total Expenses will reflect 555.00.

Any usage of addExpenses Command that will result in negative value for any fields will be rejected.

Delete expenses claims of a specific employee : deleteExpenses

Deletes expenses claim from an employee.

Format: deleteExpenses INDEX or de INDEX

Examples:

  • deleteExpenses 1
    Deletes expenses claim from employee with Index '1' in the list.

Select an expenses claim : selectExpenses

Select an expenses based on expenses list index ID. User could use command_alias: 'se'.

Format: selectExpenses INDEX or se INDEX

Examples:

  • selectExpenses 1
    Select the expenses with the index of '1' in the list

Clear expenses list : clearExpenses

Clear all expenses at one go. User could use command_alias: 'ce'.

Format: clearExpenses or ce

Expenses Report: expensesReport [Upcoming in 2.0]

Generates an expenses report with the total expenses amount claimed and have not been claimed for a year [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.

Add Expenses

The expenses feature contains to two commands.

  • The command addExpenses allows the user to add expenses for each employee.

Current Implementation for addExpenses command

Phase 1

The addExpenses command is handled by AddExpensesCommandParser. The AddExpensesCommandParser implements the Parser interface, takes in input from the user as a form of argument, The AddExpensesCommandParser will then check if user input contains too many prefixes or check for multiple entries of same prefix.

Next, the AddExpensesCommandParser will compute the value of expensesAmount to be added by summing the the values of travelExpenses, medicalExpenses and miscellaneousExpenses. The AddExpensesCommandParser will then create the Expenses and EditExpensesDescriptor objects. Other classes will handle validation of the input, check if it has the correct input format.

The five methods that helps to valid the input are:

  • isValidEmployeeId from EmployeeId class

  • isValidExpensesAmount from ExpensesAmount class

  • isValidTravelExpenses from TravelExpenses class

  • isValidMedicalExpenses from MedicalExpenses class

  • isValidMiscellaneousExpenses from MiscellaneousExpenses class

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

Code snippet of isValidEmployeeId that shows the checking of the input compliance:

public static final String EMPLOYE_EXPENSES_ID_VALIDATION_REGEX = "\\d{3,}";

/**
     * Returns true if a given string is a valid Employee Expenses Id.
     */
    public static boolean isValidEmployeeId(String test) {
        return test.matches(EMPLOYE_EXPENSES_ID_VALIDATION_REGEX);
    }

Code snippet of isValidExpensesAmount that shows the checking of the input compliance:

public static final String EMPLOYE_EXPENSES_AMOUNT_VALIDATION_REGEX = "[-]?[0-9]+(.[0-9]{0,2})?";

/**
     * Returns true if a given string is a valid Expenses Amount.
     */
    public static boolean isValidExpensesAmount(String test) {
        return test.matches(EMPLOYE_EXPENSES_AMOUNT_VALIDATION_REGEX);
    }
Phase 2

The AddExpensesCommand will take in the Expenses and EditExpensesDescriptor objects.

The AddExpensesCommand will first check if there exists a person in the person list that has the same employeeId as Expenses object using the condition (!model.hasEmployeeId(toCheckEmployeeId). The AddExpensesCommand will next check if there exists an expenses in the expenses list that has the same employeeId as Expenses object using the condition (!model.hasExpenses(toAddExpenses).

Code snippet of condition that checks the person and expenses list for person and expenses with same employeeId:

if (!model.hasEmployeeId(toCheckEmployeeId)) {
            throw new CommandException(MESSAGE_EMPLOYEE_ID_NOT_FOUND);
        } else if (!model.hasExpenses(toAddExpenses)) {
  • If Expenses List don’t have expenses with same EmployeeId,

    • The AddExpensesCommand will then check if any of the fields will contains negative value or exceed the limit. An exception will be thrown if at least one of the fields contains negative value or exceed the limit.

    • The AddExpensesCommand will then add the expenses into CHRS.

Code snippet of If Expenses List don’t have expenses with same EmployeeId

else if (!model.hasExpenses(toAddExpenses)) {
    if (Double.parseDouble(toAddExpenses.getExpensesAmount().toString()) < 0
        || Double.parseDouble(toAddExpenses.getTravelExpenses().toString()) < 0
        || Double.parseDouble(toAddExpenses.getMedicalExpenses().toString()) < 0
        || Double.parseDouble(toAddExpenses.getMiscellaneousExpenses().toString()) < 0) {
        throw new CommandException(MESSAGE_NEGATIVE_LEFTOVER);
    } else if (Double.parseDouble(toAddExpenses.getExpensesAmount().toString()) > MAX_TOTAL_EXPENSES
        || Double.parseDouble(toAddExpenses.getTravelExpenses().toString()) > MAX_EXPENSES_AMOUNT
        || Double.parseDouble(toAddExpenses.getMedicalExpenses().toString()) > MAX_EXPENSES_AMOUNT
        || Double.parseDouble(toAddExpenses.getMiscellaneousExpenses().toString()) > MAX_EXPENSES_AMOUNT
    ) {
        throw new CommandException(MESSAGE_VALUE_OVER_LIMIT);
    } else if (Double.parseDouble(toAddExpenses.getExpensesAmount().toString()) >= 0
            && Double.parseDouble(toAddExpenses.getTravelExpenses().toString()) >= 0
            && Double.parseDouble(toAddExpenses.getMedicalExpenses().toString()) >= 0
            && Double.parseDouble(toAddExpenses.getMiscellaneousExpenses().toString()) >= 0
            && Double.parseDouble(toAddExpenses.getExpensesAmount().toString()) <= MAX_TOTAL_EXPENSES
            && Double.parseDouble(toAddExpenses.getTravelExpenses().toString()) <= MAX_EXPENSES_AMOUNT
            && Double.parseDouble(toAddExpenses.getMedicalExpenses().toString()) <= MAX_EXPENSES_AMOUNT
            && Double.parseDouble(toAddExpenses.getMiscellaneousExpenses().toString()) <= MAX_EXPENSES_AMOUNT
    ) {
        model.addExpenses(toAddExpenses);
        model.commitExpensesList();
        messageToShow = MESSAGE_SUCCESS;
    }
  • If Expenses List have expenses with same EmployeeId,

    • The AddExpensesCommand will create an expensesToEdit object The AddExpensesCommand will then call createEditedExpenses method with expensesToEdit and EditExpensesDescriptor object as parameters.

Code snippet creating expensesToEdit object and calling createdEditedExpenses method

else if (model.hasExpenses(toAddExpenses)) {
    EmployeeIdExpensesContainsKeywordsPredicate predicatEmployeeId;
    List<String> employeeIdList = new ArrayList<>();
    List<Expenses> lastShownListExpenses;

    employeeIdList.add(toCheckEmployeeId.getEmployeeId().value);
    predicatEmployeeId = new EmployeeIdExpensesContainsKeywordsPredicate(employeeIdList);

    model.updateFilteredExpensesList(predicatEmployeeId);
    lastShownListExpenses = model.getFilteredExpensesList();

    Expenses expensesToEdit = lastShownListExpenses.get(0);
    Expenses editedExpenses = createEditedExpenses(expensesToEdit, editExpensesDescriptor);
  • The createEditedExpenses method will call modifyExpensesAmount, modifyTravelExpenses, modifyMedicalExpenses, modifyMiscellaneousExpenses, methods with expensesToEdit and EditExpensesDescriptor object as parameter.

Code snippet createdEditedExpenses method

    private Expenses createEditedExpenses(Expenses expensesToEdit, EditExpensesDescriptor
            editExpensesDescriptor) {
        assert expensesToEdit != null;
        ExpensesAmount updatedExpensesAmount = null;
        TravelExpenses updatedTravelExpenses = null;
        MedicalExpenses updatedMedicalExpenses = null;
        MiscellaneousExpenses updatedMiscellaneousExpenses = null;

        EmployeeId updatedEmployeeId = expensesToEdit.getEmployeeId();
        try {
            updatedExpensesAmount = ParserUtil.parseExpensesAmount(modifyExpensesAmount(expensesToEdit,
                    editExpensesDescriptor));
            updatedTravelExpenses = ParserUtil.parseTravelExpenses(modifyTravelExpenses(expensesToEdit,
                    editExpensesDescriptor));
            updatedMedicalExpenses = ParserUtil.parseMedicalExpenses(modifyMedicalExpenses(expensesToEdit,
                    editExpensesDescriptor));
            updatedMiscellaneousExpenses = ParserUtil.parseMiscellaneousExpenses(modifyMiscellaneousExpenses(
                    expensesToEdit, editExpensesDescriptor));
        } catch (ParseException pe) {
            pe.printStackTrace();
        }

        return new Expenses(updatedEmployeeId, updatedExpensesAmount, updatedTravelExpenses, updatedMedicalExpenses,
                updatedMiscellaneousExpenses);
    }
  • Each method will edit their respective field from expensesToEdit with the same field from EditExpensesDescriptor. The method will set isNegativeLeftover to be true and set isOverLimit to be true if the values is below 0 or exceed max value.

Code snippet modifyExpensesAmount method, one of the methods being called

private String modifyExpensesAmount (Expenses expensesToEdit, EditExpensesDescriptor
        editExpensesDescriptor) {
    NumberFormat formatter = new DecimalFormat("#0.00");
    String newExpensesAmount = expensesToEdit.getExpensesAmount().toString();
    double updateExpensesAmount = Double.parseDouble(newExpensesAmount);
    String change = editExpensesDescriptor.getExpensesAmount().toString().replaceAll("[^0-9.-]",
            "");
    updateExpensesAmount += Double.parseDouble(change);
    if (updateExpensesAmount < 0) {
        setIsNegativeLeftover(true);
    } else if (updateExpensesAmount > MAX_TOTAL_EXPENSES) {
        setIsOverLimit(true);
    } else if (updateExpensesAmount >= 0) {
        newExpensesAmount = String.valueOf(formatter.format(updateExpensesAmount));
    }
    return newExpensesAmount;
}
  • The AddExpensesCommand will check for isNegativeLeftover and isOverLimit. If both are false, the AddExpensesCommand will then update the updated expenses into CHRS.

Code snippet of isNegativeLeftover and isOverLimit check

if (getIsNegativeLeftover()) {
        throw new CommandException(MESSAGE_NEGATIVE_LEFTOVER);
    } else if (getIsOverLimit()) {
        throw new CommandException(MESSAGE_VALUE_OVER_LIMIT);
    } else if (!getIsNegativeLeftover() && !getIsOverLimit()) {
        messageToShow = MESSAGE_SUCCESS;
        model.updateExpenses(expensesToEdit, editedExpenses);
        model.commitExpensesList();
    }
    model.updateFilteredExpensesList(PREDICATE_SHOW_ALL_EXPENSES);
}

The sequence diagram below illustrates the operation of the AddExpensesCommand:

Add ExpensesCommandSequenceDiagram

Design Considerations

Aspect implementation of AddExpenses command:

  • Alternative 1 (current choice): Have a single command to add and modify expenses.

    • Pros: User isn’t require to ensure that there exist an expenses for the employee before modifying the expenses

    • Cons: Have to handle more conditions and possibly more prone to bugs.

  • Alternative 2: Have separate commands to add or modify expenses.

    • Pros: User won’t have the possibly of mixing up if they want to add new expenses or modify old expenses.

    • Cons: Requires user to know more commands.

Aspect: Expenses List Storage

  • Alternative 1 (current choice): Have expenses stored in XML format.

    • Pros: Easier to read, code and test XML files.

    • Cons: Can’t compute or tabulate data from file.

  • Alternative 2: Have schedules stored in CSV format.

    • Pros: More efficient way of storing tabular data like expenses

    • Cons: Harder to read as compared to XML file.

Expenses Report: expensesReport [Upcoming in 2.0]

Generates an expenses report with the total expenses amount claimed and have not been claimed for a year [Upcoming in 2.0]