PROJECT: MooLah


Overview

This Project Portfolio documents my contributions to MooLah, a student project for a Software Engineering module. In teams of five, we were tasked with developing the seedu project "AddressBook - Level 3" to a product of our own choice. Our team decided to morph it to a hassle-free expense tracker - MooLah.

Expense tracking is an inherently troublesome task, because it includes managing multiple budgets, monitoring complicated statistics, and anticipating future spending. MooLah aims to streamline this process by providing a user-friendly desktop Command-Line Interface (CLI) solution. MooLah is written in Java, and has about 23 kLoC. It improves efficiency of expense tracking through autocomplete and prefix suggestions. With a natural language time parser, MooLah accepts more intuitive and flexible time input. The robust budgeting feature help users cultivate healthy spending habits. With powerful statistics, users can visualize their expenditure in graphical forms. Besides, the menu feature is specially tailored to students in National University of Singapore, allowing them to record an expense in school canteens directly from preset menus. Furthermore, MooLah’s support for undo-redo creates a seamless user experience. All these solid and well-developed features make MooLah a perfect personal finance assistant.

Summary of Contributions

My main role in the team was to develop the budgeting feature. The following is a summary of my contributions to this feature.

  • Major Enhancement: added the ability to track expenses under different budgets, and flexibly manage multiple budgets.
    The budgeting feature is a significant enhancement to MooLah. Users need a tool to help them monitor their expenditure and avoid overspending. This is especially relevant to our main target users - university students, most of whom do not have a stable income. The budgeting feature enables users to be more controlled with their own expenses, grow healthy saving habits, and cultivate good financial management skills to prepare for the future.

    • What It Does: (i) Allows users to monitor spending through a budget progress bar and receive timely notifications when total expenditure hits important thresholds. (ii) Allows users to add, modify, delete, and clear budgets. (iii) Allows users to switch primary budget. (iv) Allows users to view expenses in a past budgeting period.

    • Highlights: The budgeting feature involves adjustments to the existing model, logic, storage and UI components. I developed ten budgeting-related commands, added utility classes such as Percentage, BudgetPeriod and BudgetWindow, and wrote unit tests for all, achieving 100% lines coverage for my part. One highlight is BudgetWindow#normalize(), which shifts the budgeting period to another anchored by a specific date. While developing this method, it was necessary for me to take into account marginal cases such as February, leap year, and months with 30 days, and painstakingly fix emerging bugs to ensure good functionality. Besides, budgeting integrates well with undo-redo. I made a great effort to check through every command to ensure all undo-redo changes are immediately reflected both on GUI and in storage files. Considering that undo revolves around Model, I implemented this in a way that a deep copy of the budget is made upon every single modification of existing budgets in the model, which was challenging and time-consuming to debug.

  • Minor Enhancement: added two commands (EditExpenseFromBudgetCommand & DeleteExpenseFromBudgetCommand) that allow users to conveniently edit or delete expenses directly from the budget panel, without having to switch to general expense list first.

  • Code Contributed: [link to RepoSense]

  • Other Contributions:

    • Project Management:

      • Managed issues on GitHub, set up various labels, and assigned team members to corresponding issues

      • Set up milestones with suitable deadlines and wrapped up regularly, closing resolved issues and moving unresolved ones to next milestone

      • Managed releases v1.1, v1.2.1, v1.3.0, v1.3.1 (4 releases) on GitHub

    • Refactor:

      • Replaced all traces of "address book" (from parameter names, class names, javadoc comments and tests in the code base) with "moolah" (PR #123)

      • Organized commands, parsers and UI components into different packages according to their features in both main and test directories (PR #126)

      • Changed the package "seedu.address" to "seedu.moolah" in both main and test directories (PR #253)

      • Changed output jar name from "addressbook.jar" to "moolah.jar" in Gradle build file (PR #155)

      • Created an app icon for MooLah (PR #126)

    • Enhancements to existing features:

      • Updated the GUI to adopt a more elegant and harmonic color scheme with purple and yellow (PR #166)

      • Adjusted font colors on GUI to a darker one, increasing contrast against background and improving readability (PR #245)

      • Populated SampleDataUtil class with various budgets and expenses so that MooLah has sufficient data upon first launch (PR #156)

    • Documentation:

      • Improved introduction section of the User Guide (PR #259)

      • Transferred user stories from GitHub issues to Developer Guide and formatted them in table form for better readability (PR #53)

      • Updated Gradle configuration (site name, etc.) and URLs in User Guide and Developer Guide to suit MooLah project site (PR #43, #54)

      • Updated About Us, Contact Us, and README to match Project MooLah (PR #51, #56)

    • Community:

      • Reviewed PRs of peer teams with non-trivial review comments (examples: #28)

      • Reported bugs and suggestions for other teams in the class (examples: #234, #224, #230)

    • Tools:

      • Set up Netlify to the team repo, which facilitated previewing changes to documentation files if the PR contains updates to documentation.

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.

Let’s Do Budgeting

Feel a need to cut your spending? Try MooLah’s awesome budgeting feature! It can help you cultivate healthier saving habits and better financial management skills, in these following ways:

  • Each budget has a progress bar with a percentage (rounded to the nearest integer), which indicates how much you have spent as a proportion to the budget limit.

  • The color of the progress bar implies the following 4 status:

    • GREEN: 0% - 49% of the limit.

    • YELLOW: 50% - 89% of the limit (MooLah will notify you’re halfway through)

    • ORANGE: 90% - 100% of the limit. (MooLah will remind you of the approaching limit)

    • RED: > 100% of the limit. (MooLah will give a warning and advise you to cut down on your spending. However, the percentage will continue to update even if you exceed the budget, to give you a better idea of how much you have overspent.)

When the budget is exceeded too much (more than 10^7 times the budget limit), you will not be able to add any new expense — I hope that won’t happen ;)

Create a New Budget : addbudget

Want your expenses tracked under a recurring budget? Simple enough with this command: addbudget.

The concrete format is:

addbudget d/<DESCRIPTION> p/<AMOUNT> sd/<START_DATE> pr/<PERIOD>

For example, after typing:

addbudget d/school p/300 sd/01-10-2019 pr/month

You’ll see that a new budget school is created, and set to $300, recurring monthly, starting from 1 Oct 2019.

All expenses that do not have a budget will go under Default Budget, which is not deletable nor modifiable.
Each budget must have a unique (case-insensitive) name.
MooLah supports 4 types of budget period input: day, week, month, and year (all in lower case).
The year in <START_DATE> is optional, i.e. you can simply input 01-10 and the year will be automatically set to the current year.
The <START_DATE> can be any time in the past or future, the budget period will automatically normalize to the current period. For example, suppose today is 23-10-2019, when you type sd/01-07 pr/month, the resulting budget period will be 01-10-2019 to 31-10-2019, since that’s the period anchored by today’s date.
All budgets are recurring. Continuing from the example above, on 1 Nov 2019, you’ll see the budget’s period refreshed to 01-11-2019 to 30-11-2019, and all past expenses archived, giving you an empty budget panel to start with.
When you add an expense that’s not within the current budgeting period, the budget panel will stay at the current period. To see the expense you’ve just added, switch to that corresponding period first. See View Expenses in a Different Period: switchperiod

Switch Between Budgets : switchbudget

Now that you’ve successfully added a few different budgets, wonder how to toggle between them? Try this magical command: switchbudget, which switches the primary budget to any other budget in one click!

The concrete format is:

switchbudget d/<BUDGET_NAME>

For example, suppose you’re at primary budget school now. After typing:

switchbudget d/outside school

You will see that the primary budget panel is switched from school to outside school. Every expense you add from now on will be tracked under the outside school budget instead.

There is one, and only one, primary budget in MooLah at all times. Every expense you enter automatically goes to this current primary budget. If you wish to let an expense be tracked by a different budget, switch to that corresponding budget first, before you add the expense.
The <BUDGET_NAME> is case-insensitive.

List All Budgets: listbudget

To get an overview of all the budgets at hand, simply type:

listbudget

You’ll see a list of all budgets in MooLah.

The primary budget is marked with a red border.
To go back to primary budget panel, type view primary budget.

Edit a Budget: editbudget

A typo? On a second thought? No worries, you can easily modify your budget with editbudget.

The concrete format is:

listbudget (first go to list of budgets)
editbudget <INDEX> [d/<DESCRIPTION>] [p/<AMOUNT>] [sd/<START_DATE>] [pr/<PERIOD>]

For example, if the second budget shown in the list is school, recurring monthly, amount set as $300, refreshed on the 1st of each month, after typing:

listbudget
editbudget 2 d/school expenses p/400 sd/05-10

It will change to school expenses, capped at $400, refreshed on the 5th of each month.

You may choose to edit any of these 4 attributes of a budget: DESCRIPTION, AMOUNT, START_DATE and PERIOD, more than one at a time.

Delete a Budget (by Name) : deletebudget

Don’t want it any more? Use deletebudget to say bye to your budget!

The concrete format is:

deletebudget d/<BUDGET_NAME>

For example, after typing:

deletebudget d/school

The budget with the name school will be deleted.

The Default Budget cannot be deleted or modified. It archives all expenses without a proper budget defined by you.
After a budget is deleted, its expenses will be transferred to the Default Budget.

Delete a Budget (by Index) : deletebudget-id

Budget names are too long? Don’t worry, there’s an easier way to delete them: deletebudget-id.

The concrete format is:

listbudget (first go to list of budgets)
deletebudget-id <INDEX>

You’ll see the corresponding budget disappear from the list.

Regretted? Type undo to get it back ;D

Delete all budgets: clearbudget

Don’t feel like living on budgets any more? You can clear them all, just using this simple command: clearbudget.

View Expenses in a Different Period: switchperiod

Wanna see your archived expenses in the past? The command switchperiod is the time machine you need.

The concrete format is:

switchperiod t/<DATE>

For example, suppose you have a monthly budget school, refreshed on the 1st of each month; and suppose it is November now. After typing:

switchperiod t/01-05

You’ll see all expenses tracked under school from 1 May to 31 May this year.

The time machine can only travel back in time! That is, you will only be able to switch to periods before or equal to the current period. If the date you enter is in a future period, you’ll need to input again.
Only expenses tracked under the current budget are shown.
To switch back to the current period, type switchperiod t/now.

Edit Expenses Inside a Budget: editexpense-primary

When you are staring at the primary budget panel, and suddenly want to modify an expense…​ Rather than switch back to the general expense list, there’s a quicker way to do it: you can edit it directly from this budget panel! The trick is: editexpense-primary.

The concrete format is:

editexpense-primary <INDEX> [d/<DESCRIPTION>] [p/<PRICE>] [c/<CATEGORY>] [t/<TIMESTAMP>]

The INDEX depends on the current budget’s expenses, instead of the general expense list.

Editing an expense from a budget has the same effect as editing it from the general expense list. That is, this expense will also be updated in the general expense list.

Delete expense inside a budget: deleteexpense-primary

Similarly to editing expense inside a budget, you can delete an expense directly from the budget panel: deleteexpense-primary.

The concrete format is:

deleteexpense-primary <INDEX>

The INDEX depends on the current budget’s expenses, instead of the general expense list.

Deleting an expense from a budget has the same effect as deleting it from the general expense list. That is, this expense will also disappear from the general expense list.

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.

Budgeting Feature

The budgeting feature allows users to: (i) add, delete and modify budgets; (ii) monitor spending by the budget’s progress bar and pop up notifications; (iii) easily modify or remove expenses from the budget; (iv) switch period to view past expenses; and (v) switch the primary budget to flexibly track expenses under different budgets.

Basics

Budgets form a partition of all expenses. That is, an Expense must belong to one and only one Budget, and all expenses from all Budget must add up to the total number in the general expense list. Each Expense keeps track of its own Budget through the budgetName attribute — this is possible because UniqueBudgetList disallows duplicate budget names. On the other hand, each Budget keeps track of a list of Expense added to this budget, as shown in the figure below.

ExpenseAndBudgetRelationshipClassDiagram

Add, Edit, Delete, Clear Budgets

There are five commands related to the addition, modification and removal of budgets, namely: AddBudgetCommand, EditBudgeCommand, DeleteBudgetByIndexCommand, DeleteBudgetByNameCommand, and ClearBudgetsCommand.

Here is the class diagram of AddBudgetCommand:

AddBudgetCommandClassDiagram

As shown in the figure above, AddBudgetCommand has 2 methods: (i) validate(model), which checks whether the command is legal (i.e. will not result in duplicate budgets being added to MooLah), and throws CommandException to notify user of the illegal input if any; (ii) execute(model), which runs this command in model and successfully adds a new Budget.

The following sequence diagram shows how the add budget operation works:

AddBudgetSequenceDiagram
The lifeline for AddBudgetCommandParser and AddBudgetCommand should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram.

As shown in the figure above, when user inputs "addbudget …​", LogicManager executes the String, and MooLahParser creates the corresponding CommandParser which parses the input into an AddBudgetCommand. AddBudgetCommand then validates itself by checking if there is an identical Budget already existing in MooLah. If no duplicate budgets are found, it executes the command, adding the new Budget to MooLah. After that, AddBudgetCommand creates a CommandResult and passes it back to LogicManager. LogicManager then saves the updated MooLah as Json file, and returns the CommandResult to GUI to be displayed to user.

The implementation of other commands follows a similar flow as AddBudgetCommand, differing only in the parameters and corresponding methods in ModelManager. A noteworthy implementation in EditBudgetCommand is that it has a inner class EditBudgetDescriptor to record the updated attributes of the budget, which is then used in the method EditBudgetCommand#createEditedBudget(Budget, EditBudgetDescriptor) to create an edited Budget, maintaining non-updated attributes the same as the original Budget.

When a Budget is edited, the original budget’s expenses will be transferred to the updated budget through Budget#transferExpensesTo(Budget other). The transfer process does two things: (i) set the expenses’s budgetName to the new budget’s name; (ii) add the expenses to the new budget’s expense list. Similarly, when a Budget is deleted, all its expenses will be transferred to Default Budget through the same method.

Primary Budget and SwitchBudgetCommand

At any time, there is one, and only one, primary budget in MooLah. All expenses added will go to this primary budget. If the user wants to track expenses under a different budget, he switches to the target budget first, before adding the expense. A newly created budget is automatically set to primary. MooLah creates a Default Budget upon first launching. If no budget is created by the user, expenses will be tracked under this Default Budget, which has a huge budget limit (10^21) and budgeting period (100 years).

The SwitchBudgetCommand takes in a budget name and switches primary budget to that budget. Given below is an example usage scenario of SwitchBudgetCommand:

Step 1. The user launches MooLah for the first time. The default budget will be created and set as the primary budget.

PrimaryBudget1

Step 2. The user adds an expense "bubble tea". Since the default budget is the primary one, this expense will go under default budget.

PrimaryBudget2

Step 3. The user creates a budget "School". Since "School" is newly added, it will be set as the primary budget.

PrimaryBudget3

Step 4. The user adds an expense "chicken rice". Since "School" is the primary budget, this expense will go under "School".

PrimaryBudget4

Step 5. The user now wants to add another expense "movie", but does not want it tracked under "School". As such, the user executes switchbudget d/default budget. The default budget is now the primary budget.

PrimaryBudget5

Step 6. The user adds the expense "movie", which is tracked under the current primary budget - default budget.

PrimaryBudget6

BudgetWindow and SwitchPeriodCommand

A budget is like a sliding window that moves along the time axis. The window has a fixed size determined by BudgetPeriod, an enum class with five values: DAY, WEEK, MONTH, YEAR, INFINITY (for Default Budget). Only expenses within the current window are shown in the app. Every time the budget refreshes, the window "slides" to the next period, starting with an empty screen that gradually gets populated with newly added expenses over time. This is achieved through the BudgetWindow class.

Here is the class diagram of BudgetWindow:

BudgetWindowClassDiagram

As shown in the figure above, BudgetWindow has 3 attributes: startDate, endDate, and period. The start and end dates are modifiable, but the period is fixed. This corresponds to the above-mentioned "sliding window" concept.

A budget keeps a list of all expenses ever been tracked by this budget, including historical ones. Additionally, it has a method Budget#getCurrentPeriodExpenses() that filters from this list expenses within the current budgeting period, which in turn get to be shown on MooLah’s GUI.

When users want to view expenses in a past period, they can do so by executing SwitchPeriodCommand. This command takes in a Timestamp and switches the budget window to a period anchored by that Timestamp. This is achieved by BudgetWindow#normalize(Timestamp anchor) method.

Given below is an example usage scenario of SwitchPeriodCommand:

Step 1. The user has a monthly budget "school" that recurs on the 5th of each month. Suppose the current date is 10 Nov. As a result, the current budgeting period will be 5 Nov - 4 Dec.

Step 2. The user wants to view expenses in September. As such, the user executes switchperiod t/20-09 (20-09 is interchangeable with any other dates within 5 Sep - 4 Oct). This command calls BudgetWindow#normalize, which shifts the window’s start and end dates to 5 Sep and 4 Oct respectively, while maintaining the fixed period — MONTH. As a result, the user sees expenses tracked under the budget during 5 Sep - 4 Oct.

The BudgetWindow#normalize method is also called upon adding a new Budget to MooLah. No matter how far the start date inputted by the user is from now, it will be normalized to the current period, such that expenses added subsequently will correctly be reflected in the budget panel.

Each budget, once added, will recur infinitely. This is achieved by Budget#refresh(), along with the Timekeeper class. Every 0.05 second, Timekeeper checks if the system time is at day break, and call Budget#refresh() if necessary. Subsequently, BudgetWindow#normalize is called; the anchor passed in is current time. As a result, the Budget is successfully normalized to the current period.

ListBudgetsCommand

ListBudgetsCommand updates Model’s FilteredBudgetList with a Predicate to show all budgets. The command returns a CommandResult with view request to BudgetListPanel.PANEL_NAME. Subsequently, MainWindow#changePanel(PanelName) will switch the currently shown panel to BudgetListPanel. ListBudgetsCommand has the same effect as "view budget list".

UI Component of Budget

There are three classes related to the UI display of budgets:

  • BudgetCard, which shows the budget name, period, and progress bar — GUI representation of a Budget;

  • BudgetListPanel, which displays a list of BudgetCard — called through ListBudgetsCommand or "view budget list";

  • BudgetPanel, which displays a BudgetCard at the top, and a list of current period expenses below — called through view primary budget.

The progress bar in BudgetCard shows the proportion used against the budget limit. This is calculated by Budget#calculateProportionUsed(), which divides the sum of all current period expenses against the budget limit, returning a Percentage that wraps the result (rounded to the nearest integer).

Besides, the budget also gives popup notification when proportion used reaches 50%, 90%, and 100%. These 3 status correspond to Budget#isHalf(), Budget#isNear() and Budget#isExceeded(). Before each command in MooLah is executed, the initial values of these 3 booleans are recorded by LogicManager#recordInitialPrimaryBudgetStatus(), which returns a boolean array of size 3. At the end of each command, the final values of these 3 booleans are recorded again by LogicManager#recordFinalPrimaryBudgetStatus(). The two arrays are then passed into MainWindow#showWarningIfAny() for a comparision, and if any values have changed, MainWindow#showPopupMessage() will show the corresponding pop up notification to remind the user of the budget progress.

Storage of Budgets

Budget objects are stored in Json format, through the JsonAdaptedBudget class. The Json file has the same properties as the budget, except: (i) it flattens the BudgetWindow field into start date, end date and period, for clearer display; (ii) it stores a list of UniqueIdentifier to expenses, instead of a list of JsonAdaptedExpense.

The following activity diagram summarizes what happens to budgets when the app launches and when a new expense is added:

BudgetActivityDiagram

Integration with Undo-Redo

Budgeting-related commands integrate well with Undo-Redo feature, by extending UndoableCommand. Undoable commands are those modifying data in MooLah, for example, AddBudgetCommand and EditBudgetCommand. On the other hand, commands that only result in GUI changes, such as ListBudgetsCommand, are not undoable.

A difficulty here is to make every undo (or redo) change immediately reflected on GUI. Since undo rollbacks models, this dictates that every change must result in a new Budget object being created and replacing the old one. Otherwise, even though the change is effective in the back end, it might not show in the front end because the Budget in two models points to the same object. This issue is handled by Budget#deepCopy(), which is widely called from UniqueBudgetList, in every method that modifies the existing budgets.

Design Considerations

Aspect: Since Budget already keeps a list of Expense, should Expense have a Budget field?
  • Alternative 1: Expense has a Budget field

    • Pros: Easier to manage expenses, as budget is directly referred to by the expense.

    • Cons: Causes cyclical dependency, and reduces testability. Also, this will cause infinite loop when creating JsonAdaptedExpense and JsonAdaptedBudget.

  • Alternative 2: Expense does not have any reference to Budget

    • Pros: Less coupling.

    • Cons: When reconstructing MooLah from Json file, system does not know which budget to add the past expenses to.

  • Solution (Current implementation): Each Expense keeps a budgetName field which indicates the name of the Budget it is tracked under. Since UniqueBudgetList disallows duplicate budget names, a name is sufficient to identify a Budget. Besides, since budgetName is of Description type instead of Budget type, it is easy to convert to String, and resolves the problem of infinite loop when creating Json files. Also, the resulting JsonAdaptedExpense in storage files will not be too long, since the budgetName property stores only the budget name instead of the full Budget object.

Aspect: Can one Expense be tracked under multiple Budget? Or under no Budget at all?
  • Alternative 1: Every Expense must be tracked under one, and only one Budget.

    • Pros: Budgets would be a partition of all expenses, which makes generating statistics (pie charts, etc.) easier.

    • Cons: Reduces flexibility in tracking expenses - users would not be able to track expenses that fall under multiple budgets.

  • Alternative 2: Allow Expense with multiple Budget, or without any Budget.

    • Pros: Users would enjoy more flexible budgeting experience.

    • Cons: This might result in duplicate calculation of expenses and difficulty in generating pie charts, as statistics revolves around budgets.

  • Solution (Current implementation): Adopt Alternative 1. Every Expense must be tracked under one and only one Budget. If the Expense is added before adding any Budget, a Default Budget with a huge limit and period is created for tracking all expenses without a proper user-defined Budget. This ensures that the main screen shows a budget status bar at all times. Besides, it leads to neater statistics.

Aspect: How should JsonAdaptedBudget keep track of the budget’s expenses?
  • Alternative 1: JsonAdaptedBudget stores a list of JsonAdaptedExpense

    • Pros: More intuitive, improving readability of Json file. Also eases reconstruction of MooLah from storage file, since JsonAdaptedExpense can be easily converted to model type Expense.

    • Cons: Json file is subject to user modification. If expenses are modified such that those in general expense list differ from their counterparts in budget’s expense list, this will cause data inconsistency and introduce bugs into MooLah.

  • Alternative 2: JsonAdaptedBudget stores a list of UniqueIdentifier

    • Pros: Guards against accidental user modification of Json file.

    • Cons: More hassle when reconstructing MooLah from storage, since JsonAdaptedBudget#toModelType will need to refer to MooLah’s expense list, identify those expenses by their UniqueIdentifier, and add them to the Budget.

  • Solution (Current implementation): Adopt Alternative 2. Since every expense has a unique identifier, a list of UniqueIdentifier, supplemented with a list of MooLah’s general expenses, is sufficient to correctly reconstruct those expenses tracked under this budget. The benefit of protecting MooLah from external Json modification outweighs the extra hassle in reconstructing MooLah from storage.