Project: Codii

[Codii] is a desktop address book application specially designed for debt collectors to manage debtors in a simple manner. It has a GUI but most of the user interactions happen using a CLI (Command Line Interface).

Debt collectors can store information such as the amount owed, debt borrow date and debt cleared date in addition to debtor’s personal information.

Unique features such as an interest calculator help debt collectors manage debts more efficiently.

Codii is evolved from [AddressBook - Level 4] which is a desktop address book application used for teaching Software Engineering principles.

Code contributed: [Functional code] [Test code]

Listing all blacklisted persons : blacklist

Shows a list of all blacklisted persons in the address book.
Format: blacklist

Listing all whitelisted persons : whitelist

Shows a list of all whitelisted persons in the address book.
Format: whitelist

End of Extract


Justification (Blacklist)

As a debt collector, there is an important requirement to categorize debtors into different categories. One of these categories include debtors who have been blacklisted. This branding could have been given by the client banks or the debt collection agencies themselves. Nonetheless, the message is clear; these blacklisted debtors must be handled with caution.

Thus, it is a requirement to have access to a list of blacklisted people. That is essentially what the blacklist command does. Moreover, working in the blacklist gives exclusive and focused control over the debtors in this list alone. Meaning, when working in the blacklist, users will not be able to access non-blacklisted persons. This is because the commands work exclusively on blacklisted debtors alone.

To access other lists, specific commands have to be used to toggle.

Justification (Whitelist)

As a debt collector, it is also important to identify and categorize debtors who have repaid all their debts. Ultimately, that is the purpose of the whitelist command. Debt collection agencies regularly need to liase with client banks to submit reports on debtors' activities and repayment amounts. Thus, having a list of debtors who have repaid their debts, along with the date that they repaid the full amount, essentially helps to track debtors' activities.

If the debt collection agency happens to be a subsidiary institution to a bank, this whitelist feature will also aid in allowing the voice-call sectors to contact people for additional loan-signups.

As aforementioned, to access other lists, specific commands have to be used to toggle.

Implementation


Start of Extract [from: Developer Guide]

List (Masterlist, Blacklist, etc.) mechanism

Having multiple lists is useful for debt collectors to view debtors of different categories. Currently, these different lists include masterlist and blacklist.

These lists could be viewed with the respective commands that will update the panel that is currently displayed. The commands to display these lists are named after the lists themselves. For example, to view the blacklist, the command typed is "blacklist" or "bl".

The following sequence diagram, Figure 4.5.1, shows further details of the interaction between the user and various application components as a whole, when the blacklist command is executed:

BlacklistCommandSequenceDiagram1

Figure 4.5.1 : Sequence diagram of how the blacklist command works

The following sequence diagram, Figure 4.5.2, shows further details of the interaction between ModelManager and ReadOnlyAddressBook to obtain the blacklist:

BlacklistCommandSequenceDiagram2

Figure 4.5.2 : Sequence diagram of how ModelManager interacts with ReadOnlyAddressBook to generate blacklist from all persons

The List mechanism is facilitated by commands which use Logic interface to obtain the copy of the list that is required. As seen from Figure 4.5.1 and Figure 4.5.2 above, to obtain the current blacklist of the addressbook, "blacklist" String is first captured by the CommandBox class. The CommandBox class then passes this String to the Logic interface for execution.

Logic interface uses LogicManager class to validate the written command and package it as a Command object. The respective command is then executed in LogicManager class. These are the instructions that are executed for this command:

@Override
public CommandResult execute() {
    requireNonNull(model);
    model.deselectPerson();
    model.changeListTo(COMMAND_WORD);
    model.updateFilteredBlacklistedPersonList(PREDICATE_SHOW_ALL_BLACKLISTED_PERSONS);
    String currentList = ListObserver.getCurrentListName();
    return new CommandResult(currentList + MESSAGE_SUCCESS);
}

Specifically for blacklist command, it calls the getFiltererdBlacklistedPersons() method residing in the Logic interface. LogicManager subsequently calls Model interface. Model uses ModelManager class to handle the command and thereafter calls ReadOnlyAddressBook interface to handle the request. ReadOnlyAddressBook uses AddressBook class to handle this request.

In the AddressBook class, there is only one persons variable that stores all ReadOnlyPerson class objects. The blacklisted persons are obtained by running a check on all debtors residing in this variable. The check is executed using the asObservableBlacklist() method, as shown below:

public ObservableList<ReadOnlyPerson> asObservableBlacklist() {
    return FXCollections.unmodifiableObservableList(mappedList.stream()
            .filter(person -> person.isBlacklisted()).collect(toCollection(FXCollections::observableArrayList)));
}

Although this way of implementation seems inefficient, it supports robust synchronisation among the various other lists. For example, if a person is deleted from the masterlist, he will also be deleted from the blacklist. Likewise for various other commands that changes the ReadOnlyPerson object. Thus, it is efficient in this aspect. Moreover, this implementation sets the groundwork for future implementations of various other lists.

One other important aspect to this implementation is that commands now have exclusive control over the list. This means that commands will only work on the persons in the current displayed list. For example, find command will only search and find persons in the current displayed list. It will not try to search in the masterlist, where all the contacts are stored. sort command will only sort the persons in the current displayed list. If the current displayed list is the blacklist, the output of the sort command will just be a list of sorted blacklisted persons.

However, if a Person is modified by a command, the modification will be reflected across all other lists. For example, if a person exists in both the blacklist and the overdue list, modifications done by an edit command in the blacklist will also update the overdue list. These changes can be observed when the user switches to the overdue list using the overduelist command.

Design Considerations:

Aspect: Syncing of lists
Alternative 1 (current choice): Use manual syncing methods to ensure lists are in sync
Pros: Easy to extend for future implementations.
Cons: Methods used for syncing are less intuitive and difficult to comprehend.
Alternative 2: Create new persons variable for each list in Addressbook class
Pros: Implementation is easier to comprehend.
Cons: Waste of memory compared to current implementation which only has 1 persons variable in Addressbook class.

End of Extract


Enhancement Added: Repaid command

External behavior


Start of Extract [from: User Guide]

Resetting the debt of a person: repaid

Resets the debt of a person to zero and sets the date repaid field of that person.
Format: repaid [INDEX]

  • Resets the debt of the person at the specified INDEX to zero. The index refers to the index number shown in the last person listing. The index must be a positive integer (e.g. 1, 2, 3, …​)

  • If no index is specified, the debt of the currently selected person is resetted instead.

  • The person will also be sent to the whitelist.

Examples:

  • repaid 1
    Resets the debt of the 1st person to zero and sets the date of repayment in his/her record.

  • select 2
    repaid
    Resets the debt of the 2nd person to zero and sets the date of repayment in his/her record.

End of Extract


Justification

A debt collector will have the job to record down the collections for the day from each debtor. If a debtor has paid off his/her total debt, there should be an easy way for the debt collector to update`debt`, totalDebt, dateRepaid and isWhitelisted fields of the debtor. That is the purpose of the Repaid command.

This is very similar to the Payback command, however, Repaid command excels in automating three different tasks so that life is indeed simpler for debt collectors to key in information in the system.

Implementation


Start of Extract [from: Developer Guide]

Repaid command mechanism

The repaid command allow users to indicate that the specified debtor has completely repaid all of his/her debt. The RepaidCommand extends UndoableCommand so that it can be undone and redone if necessary.

The command executes using the index of the person the user wishes to update. However, indicating the index is optional if the person is the current selected person in the person list panel, as the index is internally provided by the selected person card.

The RepaidCommand is executed in LogicManager. It checks if the person selected has already repaid all his/her debt. If the person has done so, the command will then throw an Exception indicating that he/she has already repaid his/her debt.

If the person has not completely repaid his/her debt, LogicManager will then call the Model interface to execute a three-step process on that person, using a single method. ModelManager will firstly reset the person’s debt to zero.

public ReadOnlyPerson resetPersonDebt(ReadOnlyPerson p) throws PersonNotFoundException {
    int index;
    index = persons.getIndexOf(p);

    Person existingPerson = new Person(p);
    try {
        existingPerson.setDebt(new Debt(Debt.DEBT_ZER0_VALUE));
    } catch (IllegalValueException e) {
        assert false : "The target value cannot be of illegal value";
    }

    persons.remove(p);

    try {
        persons.add(index, existingPerson);
    } catch (DuplicatePersonException dpe) {
        assert false : "There should be no duplicate when resetting the debt of a person";
    }

    return persons.getReadOnlyPerson(index);
}

Secondly, it will set the dateRepaid field of the person to the date the command was called.

public ReadOnlyPerson setDateRepaid(ReadOnlyPerson p) throws PersonNotFoundException {
    int index;
    index = persons.getIndexOf(p);

    Person existingPerson = new Person(p);
    existingPerson.setDateRepaid(new DateRepaid(formatDate(new Date())));

    persons.remove(p);

    try {
        persons.add(index, existingPerson);
    } catch (DuplicatePersonException dpe) {
        assert false : "There should be no duplicate when resetting the date repaid field of a person";
    }

    return persons.getReadOnlyPerson(index);
}

Finally, the person will be added into a list called Whitelist, given that he was not initially blacklisted.

public ReadOnlyPerson addWhitelistedPerson(ReadOnlyPerson p) {
    int index;
    index = persons.getIndexOf(p);

    Person newWhitelistedPerson = new Person(p);
    newWhitelistedPerson.setIsWhitelisted(true);
    try {
        updatePerson(p, newWhitelistedPerson);
    } catch (DuplicatePersonException e) {
        throw new AssertionError("The target person cannot be a duplicate");
    } catch (PersonNotFoundException e) {
        throw new AssertionError("This is not possible as prior checks have been done");
    }
    return persons.getReadOnlyPerson(index);
}

ModelManager will then return the updated ReadOnlyPerson Object back to LogicManager.

Aspect: Three-step process in one Command
Alternative 1 (current choice): The three-step process happens in a single method
Pros: Faster and simpler to implement the method.
Cons: Some methods will fail to update the person as intended. If the person is blacklisted, he/she will not be added into the whitelist.
Alternative 2: Include three separate methods in Model interface to execute the three-step process
Pros: Easy to observe the methods that will fail.
Cons: Methods in Model interface will seem to have less abstraction than intended.

End of Extract


Adding a profile picture to a person : addpic

Adds a user specified profile picture to the specified person.

A path to the pictures folder must first be set using the setpath command
The picture of the debtor should already be present in this folder and should be named after the debtor himself.

Examples:

Roy Balakrishnan’s picture should be named as RoyBalakrishnan and the extension of the file should be .jpg

Full file name: RoyBalakrishnan.jpg

Herbert He’s picture should be named as HerbertHe and the extension of the file should be .jpg

Full file name: HerbertHe.jpg

Format to add picture to the selected person : 'addpic [INDEX]'

  • Adds a profile picture to the person at the specified INDEX.

  • The index refers to the index number shown in the most recent listing.

  • The index must be a positive integer (e.g. 1, 2, 3, …​)

  • If no index is specified, the command acts on the currently selected person.

Examples:

  • list
    setpath C:\Users\acer\Desktop\SE1\profilepic
    addpic 2
    Adds a picture to the 2nd person in masterlist.

  • find Betsy
    setpath C:\Users\acer\Desktop\SE2\profilepic
    addpic 1
    Adds a profile picture to the 1st person from the results of the find command.

  • select 3
    setpath C:\Users\acer\Desktop\SE7\profilepic
    addpic
    Adds a profile picture to the 3rd person from the masterlist in the address book.

Deleting the profile picture of a person : delpic

Removes the profile picture of the specified person.
Format: 'delpic [INDEX]'

  • Removes the profile picture of the person at the specified INDEX.

  • The index refers to the index number shown in the most recent listing.

  • The index must be a positive integer (e.g. 1, 2, 3, …​)

  • If no index is specified, the command acts on currently selected person.

Examples:

  • list
    delpic 2
    Deletes the picture of the 2nd person in masterlist.

  • find Betsy
    delpic 1
    Deletes the profile picture of the 1st person from the results of the find command.

  • select 3
    delpic
    Deletes the profile picture of the 3rd person from the masterlist in the address book.

End of Extract


Justification

A debt collector might need to conduct house visits to collect debts from people. Thus, having commands that allow the user to add/remove pictures for each individual debtors helps him/her quickly identify a specific debtor during official house visits.

Implementation


Start of Extract [from: Developer Guide]

Setpath/Addpic/Delpic command mechanism

Setpath, Addpic and Delpic commands facilitate updating a debtor’s display picture. Setpath command sets the path to a folder that contains JPG format images of the debtors. This folder resides in the user’s workspace.

The images in the folder must initially be named after the debtors themselves. For example, a debtor named Alex Yeoh should have an image titled AlexYeoh.jpg in this folder.

Setpath command modifies the destined path in the ProfilePicturesFolder class:

public CommandResult executeUndoableCommand() throws CommandException {
    ProfilePicturesFolder.setPath(reformatPath(path));
    return new CommandResult(MESSAGE_SUCCESS);
}

Addpic command takes in an index as the input and uses Logic interface to execute a two-step process to update the person’s display picture status. LogicManager calls Model interface to first search for the image in the path directory set by the SetPath command. This is done by first establishing the name of the image, and then searching for it in the required folder.

@Override
    public boolean addProfilePicture(ReadOnlyPerson person) throws ProfilePictureNotFoundException {
        String imageName = person.getName().toString().replaceAll("\\s+", "");
        File imageFile = new File(ProfilePicturesFolder.getPath() + imageName + JPG_EXTENSION);

        if (imageFile.exists()) {
            addressBook.addProfilePic(person);
            indicateAddressBookChanged();
            return true;
        } else {
            throw new ProfilePictureNotFoundException();
        }
    }

ModelManager checks if the image exists in the path directory. If it does, it will then set the boolean value of the person’s hasDisplayPicture variable to true.

public ReadOnlyPerson addProfilePic(ReadOnlyPerson person) {
        int index;
        index = persons.getIndexOf(person);

        Person newUpdatedPerson = new Person(person);
        newUpdatedPerson.setHasDisplayPicture(true);
        try {
            updatePerson(person, newUpdatedPerson);
        } catch (DuplicatePersonException e) {
            throw new AssertionError("The target person cannot be a duplicate");
        } catch (PersonNotFoundException e) {
            throw new AssertionError("This is not possible as prior checks have been done");
        }

        return persons.getReadOnlyPerson(index);
    }

When the PersonCard corresponding to the person is selected, hasDisplayPicture boolean value is obtained. If the value is true, the image will be retrieved from the previously set path. If it does not exist, a default image will be displayed.

public DebtorProfilePicture(ReadOnlyPerson person) {
        super(FXML);
        String imageName = person.getName().toString().replaceAll("\\s+", "");
        String imagePath = DEFAULT_PROFILEPIC_PATH;
        Image image = new Image(getClass().getResource(imagePath).toExternalForm());

        if (person.hasDisplayPicture()) {

            imagePath = ProfilePicturesFolder.getPath() + imageName + JPG_EXTENSION;
            File imageFile = new File(imagePath);

            if (!imageFile.exists()) {
                person.setHasDisplayPicture(false);
                raise(new MissingDisplayPictureEvent(person));
            } else {
                try {
                    image = new Image(imageFile.toURI().toURL().toExternalForm());
                } catch (MalformedURLException e) {
                    e.printStackTrace();
                }
            }
        }

        profilePic.setImage(image);
        profilePic.setFitWidth(450);
        profilePic.setFitHeight(450);
        profilePicPlaceHolder.setTopAnchor(this.getImageView(), 20.0);
        profilePicPlaceHolder.setRightAnchor(this.getImageView(), 50.0);
        registerAsAnEventHandler(this);
    }

Delpic command, similarly, resets the person’s hasDisplayPicture boolean value to false.

public ReadOnlyPerson removeProfilePic(ReadOnlyPerson person) {
        int index;
        index = persons.getIndexOf(person);

        Person newUpdatedPerson = new Person(person);
        newUpdatedPerson.setHasDisplayPicture(false);
        try {
            updatePerson(person, newUpdatedPerson);
        } catch (DuplicatePersonException e) {
            throw new AssertionError("The target person cannot be a duplicate");
        } catch (PersonNotFoundException e) {
            throw new AssertionError("This is not possible as prior checks have been done");
        }

        return persons.getReadOnlyPerson(index);
    }

Aspect: Storage of profile pictures in Codii
Alternative 1 (current choice): Images are not stored in Codii as they are read from the path directory provided by the user
Pros: Straightforward and simpler to implement.
Cons: Application cannot be easily transferred from one workstation to another.
Alternative 2: Store images in Codii similar to how XML files are stored
Pros: Far less hassle in transferring application content to another workstation.
Cons: The result of both implementations are very similar. However, this implementation is harder to execute.

End of Extract


Other contributions

Project: WiredIn

[WiredIn] is a web application that collates raw csv files from various banks and displays the information variedly in a visually pleasing manner. This application was developed with [Archana Pradeep] for [CP2106 Independent Software Development Project (Orbital)].