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
andBudgetWindow
, and wrote unit tests for all, achieving 100% lines coverage for my part. One highlight isBudgetWindow#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 aroundModel
, 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:
-
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.
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
:
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:
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.
Step 2. The user adds an expense "bubble tea". Since the default budget is the primary one, this expense will go under default budget.
Step 3. The user creates a budget "School". Since "School" is newly added, it will be set as the primary budget.
Step 4. The user adds an expense "chicken rice". Since "School" is the primary budget, this expense will go under "School".
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.
Step 6. The user adds the expense "movie", which is tracked under the current primary budget - default budget.
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
:
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 aBudget
; -
BudgetListPanel
, which displays a list ofBudgetCard
— called throughListBudgetsCommand
or "view budget list"; -
BudgetPanel
, which displays aBudgetCard
at the top, and a list of current period expenses below — called throughview 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:
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 aBudget
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
andJsonAdaptedBudget
.
-
-
Alternative 2:
Expense
does not have any reference toBudget
-
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 abudgetName
field which indicates the name of theBudget
it is tracked under. SinceUniqueBudgetList
disallows duplicate budget names, a name is sufficient to identify aBudget
. Besides, sincebudgetName
is ofDescription
type instead ofBudget
type, it is easy to convert toString
, and resolves the problem of infinite loop when creating Json files. Also, the resultingJsonAdaptedExpense
in storage files will not be too long, since thebudgetName
property stores only the budget name instead of the fullBudget
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 oneBudget
.-
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 multipleBudget
, or without anyBudget
.-
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 oneBudget
. If theExpense
is added before adding anyBudget
, aDefault Budget
with a huge limit and period is created for tracking all expenses without a proper user-definedBudget
. 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 ofJsonAdaptedExpense
-
Pros: More intuitive, improving readability of Json file. Also eases reconstruction of MooLah from storage file, since
JsonAdaptedExpense
can be easily converted to model typeExpense
. -
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 ofUniqueIdentifier
-
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 theirUniqueIdentifier
, and add them to theBudget
.
-
-
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.