Appearance
Tables
What each table is for, and the business requirements, business rules and user journeys it serves. This page is generated by pnpm docs:schema; the structure comes from the real schema, so it matches the migrations (Apps/Api/Migrations).
access_request
A visitor's request to use Crest, from submission to approval or rejection. No user exists until a Super Admin approves it.
Columns
| Column | Type | Nullable | Default |
|---|---|---|---|
id | uuid | no | gen_random_uuid() |
full_name | text | no | |
email | citext | no | |
country | text | no | |
message | text | yes | |
consent_at | timestamp with time zone | no | |
status | access_request_status | no | 'Pending'::access_request_status |
rejection_note | text | yes | |
user_id | uuid | yes | |
source_ip_hash | text | yes | |
created_at | timestamp with time zone | no | now() |
created_by | uuid | yes | |
updated_at | timestamp with time zone | no | now() |
updated_by | uuid | yes | |
deleted_at | timestamp with time zone | yes |
Constraints
access_request_country_check: check,CHECK ((country ~ '^[A-Z]{2}$'::text))access_request_full_name_check: check,CHECK ((length(TRIM(BOTH FROM full_name)) > 0))access_request_created_by_fkey: foreign key,FOREIGN KEY (created_by) REFERENCES app_user(id) ON DELETE SET NULLaccess_request_updated_by_fkey: foreign key,FOREIGN KEY (updated_by) REFERENCES app_user(id) ON DELETE SET NULLaccess_request_user_id_fkey: foreign key,FOREIGN KEY (user_id) REFERENCES app_user(id) ON DELETE SET NULLaccess_request_pkey: primary key,PRIMARY KEY (id)
Indexes
CREATE UNIQUE INDEX access_request_pending_email_idx ON public.access_request USING btree (email) WHERE ((status = 'Pending'::access_request_status) AND (deleted_at IS NULL))CREATE INDEX access_request_status_idx ON public.access_request USING btree (status, created_at DESC)
Relations
Business requirements
FR-REQ-1Anyone can submit an access request with full name, email, country, and optional reason/messageFR-REQ-1aThe request form is a page on the public website with its own link (to share with friends), also reachable from the app's Connect screen (More → Conn…FR-REQ-2The request form is protected against spam (rate limiting and bot protection)FR-REQ-3The requester sees a confirmation that the request was received; no user is created at this pointFR-REQ-4A duplicate request for an email that is already pending or active is not created; the requester sees a neutral message that does not reveal whether…FR-REQ-5The requester receives an email when their request is approved (with activation link)FR-REQ-6The request form links to the Privacy Policy and Terms of Use (in the visitor's language); submitting requires accepting both and confirming the visi…FR-ADM-4Super Admin sees a list of access requests, filterable by status (Pending, Approved, Rejected)FR-ADM-5Super Admin can approve a request, which creates the user and sends the activation linkFR-ADM-6Super Admin can reject a request, with an optional reasonFR-ADM-6aSuper Admin can approve a request that was previously rejected (to reverse a rejection); this works like a normal approvalFR-ADM-7aIf the email in FR-ADM-7 already has a Pending request, that request is approved instead of creating a separate userFR-ADM-13Super Admin is notified (email) when a new access request arrivesFR-ADM-18Super Admin can select several access requests and approve or reject them togetherNFR-3PrivacyNFR-18Data retention
Business rules
BR-14A rejected requester may submit a new request after 30 daysBR-44Crest users must be 18 or older, confirmed on the first launch of the app (local mode) and again on the access-request form
User journeys
UJ-01Request accessUJ-02Try to register or log in without approvalUJ-09Review and approve an access requestUJ-10Reject an access requestUJ-11Create a user directly
app_user
One row per person who can log in: identity, status, preferences, and the Super Admin flag.
Columns
| Column | Type | Nullable | Default |
|---|---|---|---|
id | uuid | no | gen_random_uuid() |
name | text | no | |
email | citext | no | |
email_verified | boolean | no | false |
pending_email | citext | yes | |
password_hash | text | yes | |
status | user_status | no | 'Invited'::user_status |
is_super_admin | boolean | no | false |
base_currency | text | no | 'IDR'::text |
language | text | no | 'en'::text |
time_zone | text | no | 'Asia/Jakarta'::text |
theme | theme_preference | no | 'System'::theme_preference |
daily_reminder_time | time without time zone | yes | |
pin_hash | text | yes | |
last_login_at | timestamp with time zone | yes | |
paid_until | date | yes | |
contribution_exempt | boolean | no | false |
created_at | timestamp with time zone | no | now() |
created_by | uuid | yes | |
updated_at | timestamp with time zone | no | now() |
updated_by | uuid | yes |
Constraints
app_user_base_currency_check: check,CHECK ((base_currency ~ '^[A-Z]{3}$'::text))app_user_language_check: check,CHECK ((language = ANY (ARRAY['en'::text, 'id'::text])))app_user_name_check: check,CHECK ((length(TRIM(BOTH FROM name)) > 0))app_user_created_by_fkey: foreign key,FOREIGN KEY (created_by) REFERENCES app_user(id) ON DELETE SET NULLapp_user_updated_by_fkey: foreign key,FOREIGN KEY (updated_by) REFERENCES app_user(id) ON DELETE SET NULLapp_user_pkey: primary key,PRIMARY KEY (id)app_user_email_key: unique,UNIQUE (email)
Relations
Business requirements
FR-AUTH-1There is no public sign-up for connected modeFR-AUTH-2An approved user activates their login through a single-use activation link and sets a passwordFR-AUTH-4Users log in with email and passwordFR-AUTH-6Users can lock Crest with a PINFR-AUTH-8Users can delete themselves (their login) and all their dataFR-AUTH-9A deactivated or deleted user is logged out of all devices immediatelyFR-ADM-1Only users with the Super Admin role can access the portalFR-ADM-7Super Admin can create a user directly (without a request), which sends an activation linkFR-ADM-8Super Admin sees a list of all users with name, email, status, created date, and last login; searchable and sortableFR-ADM-9Super Admin can deactivate and reactivate a userFR-ADM-11Super Admin can permanently delete a user and all their data, after typed confirmationFR-ADM-12Super Admin can edit a user's name and emailFR-ADM-16Super Admin can give the Super Admin role to any Active user, or remove it (never from the last Super Admin)FR-SET-1Users can choose their base currency (any ISO 4217 currency; also the currency of their Personal space) and language (English, Bahasa Indonesia at la…FR-SET-2Users can choose light/dark themeFR-NOT-1Optional daily reminder to log transactions
Business rules
BR-8A user can only be created by the Super Admin, either by approving an access request or by creating it directlyBR-9One email address maps to at most one userBR-11Deactivating a user keeps their data; only deletion removes itBR-12The Super Admin role manages access only; it grants no access to users' financial dataBR-13There must always be at least one active Super Admin; the last one cannot be deactivated, deleted, or demotedBR-41Super Admin is a role held by a user
User journeys
UJ-03Activate login from invitationUJ-04Log in and unlock CrestUJ-07Get logged out after deactivationUJ-08Delete myself and all my dataUJ-09Review and approve an access requestUJ-11Create a user directlyUJ-12Deactivate and reactivate a userUJ-13Permanently delete a userUJ-14Help a user who cannot log inUJ-15First-time portal setup, daily check, and audit reviewUJ-16First-time setupUJ-37Change preferences and reminders
app_user_public
A view of only the id and name of the people you share a space with, so the app can show "Added by Jojo" without exposing anyone's profile.
Columns
| Column | Type | Nullable | Default |
|---|---|---|---|
id | uuid | yes | |
name | text | yes |
Business requirements
FR-SPC-7Every transaction shows who added it and, if changed, who last edited it
Business rules
BR-30Every financial account, category, budget, and savings goal belongs to exactly one spaceBR-35In a transfer between spaces, members see only the side in their own space
User journeys
UJ-44Add a transaction in a shared spaceUJ-45Top up a shared fund from a Personal financial accountUJ-46See how a shared space's money is usedUJ-47Pay yourself back from a shared fund
audit_log
Append-only record of every Super Admin action. It never holds financial data or space details.
Columns
| Column | Type | Nullable | Default |
|---|---|---|---|
id | bigint | no | |
action | text | no | |
target_user_id | uuid | yes | |
target_request_id | uuid | yes | |
details | jsonb | no | '{}'::jsonb |
ip_hash | text | yes | |
created_at | timestamp with time zone | no | now() |
created_by | uuid | yes |
Constraints
audit_log_created_by_fkey: foreign key,FOREIGN KEY (created_by) REFERENCES app_user(id) ON DELETE SET NULLaudit_log_pkey: primary key,PRIMARY KEY (id)
Indexes
CREATE INDEX audit_log_created_idx ON public.audit_log USING btree (created_at DESC)
Relations
Business requirements
FR-ADM-3aAs a last resort, the setup script can reset a Super Admin's 2FA (requires server access); the reset is recorded in the audit logFR-ADM-14Every admin action is recorded in an audit log (who, what, which user, when), viewable in the portalFR-ADM-16Super Admin can give the Super Admin role to any Active user, or remove it (never from the last Super Admin)FR-ADM-17Super Admin cannot view users' financial data (financial accounts, transactions, budgets) or anything about their spaces, including space names and m…NFR-12AuditNFR-18Data retention
Business rules
None.
User journeys
UJ-09Review and approve an access requestUJ-10Reject an access requestUJ-11Create a user directlyUJ-12Deactivate and reactivate a userUJ-13Permanently delete a userUJ-14Help a user who cannot log inUJ-15First-time portal setup, daily check, and audit review
auth_session
One row per logged-in device. Deleting or revoking a row logs that device out.
Columns
| Column | Type | Nullable | Default |
|---|---|---|---|
id | uuid | no | gen_random_uuid() |
user_id | uuid | no | |
client | auth_client | no | |
refresh_token_hash | text | no | |
device_name | text | yes | |
two_factor_verified | boolean | no | false |
last_used_at | timestamp with time zone | no | now() |
expires_at | timestamp with time zone | no | |
revoked_at | timestamp with time zone | yes | |
created_at | timestamp with time zone | no | now() |
created_by | uuid | yes | |
updated_at | timestamp with time zone | no | now() |
updated_by | uuid | yes |
Constraints
auth_session_created_by_fkey: foreign key,FOREIGN KEY (created_by) REFERENCES app_user(id) ON DELETE SET NULLauth_session_updated_by_fkey: foreign key,FOREIGN KEY (updated_by) REFERENCES app_user(id) ON DELETE SET NULLauth_session_user_id_fkey: foreign key,FOREIGN KEY (user_id) REFERENCES app_user(id) ON DELETE CASCADEauth_session_pkey: primary key,PRIMARY KEY (id)auth_session_refresh_token_hash_key: unique,UNIQUE (refresh_token_hash)
Indexes
CREATE INDEX auth_session_user_idx ON public.auth_session USING btree (user_id)
Relations
Business requirements
FR-AUTH-4Users log in with email and passwordFR-AUTH-9A deactivated or deleted user is logged out of all devices immediatelyFR-ADM-2Super Admin login requires two-factor authentication (authenticator app)NFR-2SecurityNFR-11Admin security
Business rules
None.
User journeys
UJ-04Log in and unlock CrestUJ-07Get logged out after deactivationUJ-12Deactivate and reactivate a userUJ-13Permanently delete a user
auth_token
Single-use links sent by email: login activation, password reset, and email change.
Columns
| Column | Type | Nullable | Default |
|---|---|---|---|
id | uuid | no | gen_random_uuid() |
user_id | uuid | no | |
purpose | auth_token_purpose | no | |
token_hash | text | no | |
expires_at | timestamp with time zone | no | |
used_at | timestamp with time zone | yes | |
created_at | timestamp with time zone | no | now() |
created_by | uuid | yes | |
updated_at | timestamp with time zone | no | now() |
updated_by | uuid | yes |
Constraints
auth_token_created_by_fkey: foreign key,FOREIGN KEY (created_by) REFERENCES app_user(id) ON DELETE SET NULLauth_token_updated_by_fkey: foreign key,FOREIGN KEY (updated_by) REFERENCES app_user(id) ON DELETE SET NULLauth_token_user_id_fkey: foreign key,FOREIGN KEY (user_id) REFERENCES app_user(id) ON DELETE CASCADEauth_token_pkey: primary key,PRIMARY KEY (id)auth_token_token_hash_key: unique,UNIQUE (token_hash)
Indexes
CREATE INDEX auth_token_user_idx ON public.auth_token USING btree (user_id, purpose)
Relations
Business requirements
FR-AUTH-2An approved user activates their login through a single-use activation link and sets a passwordFR-AUTH-3Activation links expire after 72 hours; the Super Admin can resend a new linkFR-AUTH-7Users can reset their passwordFR-ADM-10Super Admin can resend an activation link or trigger a password-reset email for a userFR-ADM-12Super Admin can edit a user's name and emailFR-APP-7Activating a login and resetting a password happen on the public website, so they work before the user has ever opened the app
Business rules
BR-10Activation links are single-use and expire after 72 hours
User journeys
UJ-03Activate login from invitationUJ-05Reset a forgotten passwordUJ-09Review and approve an access requestUJ-11Create a user directlyUJ-14Help a user who cannot log in
auth_totp
Two-factor authenticator secret and backup codes for Super Admins.
Columns
| Column | Type | Nullable | Default |
|---|---|---|---|
id | uuid | no | gen_random_uuid() |
user_id | uuid | no | |
secret_encrypted | text | no | |
confirmed_at | timestamp with time zone | yes | |
backup_code_hashes | text[] | no | '{}'::text[] |
created_at | timestamp with time zone | no | now() |
created_by | uuid | yes | |
updated_at | timestamp with time zone | no | now() |
updated_by | uuid | yes |
Constraints
auth_totp_created_by_fkey: foreign key,FOREIGN KEY (created_by) REFERENCES app_user(id) ON DELETE SET NULLauth_totp_updated_by_fkey: foreign key,FOREIGN KEY (updated_by) REFERENCES app_user(id) ON DELETE SET NULLauth_totp_user_id_fkey: foreign key,FOREIGN KEY (user_id) REFERENCES app_user(id) ON DELETE CASCADEauth_totp_pkey: primary key,PRIMARY KEY (id)auth_totp_user_id_key: unique,UNIQUE (user_id)
Relations
Business requirements
FR-ADM-2Super Admin login requires two-factor authentication (authenticator app)FR-ADM-2aWhen setting up 2FA, the Super Admin receives one-time backup codes to log in if the authenticator device is lostFR-ADM-3aAs a last resort, the setup script can reset a Super Admin's 2FA (requires server access); the reset is recorded in the audit logNFR-11Admin security
Business rules
None.
User journeys
UJ-15First-time portal setup, daily check, and audit review
budget
A monthly spending limit per category, in the space's currency. The latest row on or before a period applies.
Columns
| Column | Type | Nullable | Default |
|---|---|---|---|
id | uuid | no | gen_random_uuid() |
space_id | uuid | no | |
category_id | uuid | no | |
amount | bigint | no | |
effective_from | date | no | |
created_at | timestamp with time zone | no | now() |
created_by | uuid | yes | |
updated_at | timestamp with time zone | no | now() |
updated_by | uuid | yes | |
deleted_at | timestamp with time zone | yes |
Constraints
budget_amount_check: check,CHECK ((amount > 0))budget_category_id_fkey: foreign key,FOREIGN KEY (category_id) REFERENCES category(id) ON DELETE CASCADEbudget_created_by_fkey: foreign key,FOREIGN KEY (created_by) REFERENCES app_user(id) ON DELETE SET NULLbudget_space_id_fkey: foreign key,FOREIGN KEY (space_id) REFERENCES space(id) ON DELETE CASCADEbudget_updated_by_fkey: foreign key,FOREIGN KEY (updated_by) REFERENCES app_user(id) ON DELETE SET NULLbudget_pkey: primary key,PRIMARY KEY (id)
Indexes
CREATE INDEX budget_space_idx ON public.budget USING btree (space_id, category_id, effective_from DESC)
Relations
Business requirements
FR-BUD-1The space's Owners can set a monthly budget per category, in the space's currencyFR-BUD-2Users see progress against each budget (spent / remaining)FR-BUD-3Members of the space get notified at 80% and 100% of a budgetFR-BUD-7Expenses in currencies other than the space's currency count toward budgets after conversion with the viewing user's saved rate; if the rate is missi…FR-BUD-8A budget on a category with sub-categories includes the spending in its sub-categoriesFR-NOT-2Budget threshold alerts (see FR-BUD-3)
Business rules
BR-4Budget periods start on the space's configured start day (default: 1st of month)BR-22Budgets are always set in the space's currencyBR-24A refund reduces spending in its category (and the related budget)
User journeys
category
Income and expense categories of a space, seeded with defaults when the space is created.
Columns
| Column | Type | Nullable | Default |
|---|---|---|---|
id | uuid | no | gen_random_uuid() |
space_id | uuid | no | |
parent_id | uuid | yes | |
kind | category_kind | no | |
name | text | no | |
color | text | yes | |
icon | text | yes | |
system_key | text | yes | |
sort_order | integer | no | 0 |
archived_at | timestamp with time zone | yes | |
created_at | timestamp with time zone | no | now() |
created_by | uuid | yes | |
updated_at | timestamp with time zone | no | now() |
updated_by | uuid | yes | |
deleted_at | timestamp with time zone | yes |
Constraints
category_name_check: check,CHECK ((length(TRIM(BOTH FROM name)) > 0))category_created_by_fkey: foreign key,FOREIGN KEY (created_by) REFERENCES app_user(id) ON DELETE SET NULLcategory_parent_id_fkey: foreign key,FOREIGN KEY (parent_id) REFERENCES category(id) ON DELETE CASCADEcategory_space_id_fkey: foreign key,FOREIGN KEY (space_id) REFERENCES space(id) ON DELETE CASCADEcategory_updated_by_fkey: foreign key,FOREIGN KEY (updated_by) REFERENCES app_user(id) ON DELETE SET NULLcategory_pkey: primary key,PRIMARY KEY (id)
Indexes
CREATE INDEX category_space_idx ON public.category USING btree (space_id)CREATE UNIQUE INDEX category_system_key_idx ON public.category USING btree (space_id, system_key) WHERE (system_key IS NOT NULL)
Relations
Business requirements
FR-CAT-1Every space starts with a default set of income and expense categories, named in the language of the person who created the spaceFR-CAT-2The space's Owners can create, rename, recolor, and archive its categoriesFR-CAT-3Categories can have sub-categoriesFR-CAT-4A transaction's category always comes from the space of its financial account: when adding a transaction, the category picker shows that space's cate…FR-TRX-2eOn any transfer (same or different currency) the user can optionally record a fee, e.g
Business rules
BR-5Deleting a category does not delete its transactions; they become "Uncategorized"BR-30Every financial account, category, budget, and savings goal belongs to exactly one spaceBR-31A transaction's category must belong to the same space as its financial account
User journeys
UJ-16First-time setupUJ-17Log a daily expenseUJ-34Manage categoriesUJ-42Create a shared spaceUJ-44Add a transaction in a shared space
contribution_payment
A yearly contribution recorded by the Super Admin, which moves a user's paid-until date. Phase 4, reserved.
Columns
| Column | Type | Nullable | Default |
|---|---|---|---|
id | uuid | no | gen_random_uuid() |
user_id | uuid | no | |
paid_until | date | no | |
note | text | yes | |
created_at | timestamp with time zone | no | now() |
created_by | uuid | yes | |
updated_at | timestamp with time zone | no | now() |
updated_by | uuid | yes |
Constraints
contribution_payment_created_by_fkey: foreign key,FOREIGN KEY (created_by) REFERENCES app_user(id) ON DELETE SET NULLcontribution_payment_updated_by_fkey: foreign key,FOREIGN KEY (updated_by) REFERENCES app_user(id) ON DELETE SET NULLcontribution_payment_user_id_fkey: foreign key,FOREIGN KEY (user_id) REFERENCES app_user(id) ON DELETE CASCADEcontribution_payment_pkey: primary key,PRIMARY KEY (id)
Indexes
CREATE INDEX contribution_payment_user_idx ON public.contribution_payment USING btree (user_id)
Relations
Business requirements
FR-SUB-1Super Admin can set a paid-until date for each user, and see who is paid, due soon, or lapsedFR-SUB-2Super Admin can mark a user as exempt (e.gFR-SUB-7Every change to paid-until or exempt status is recorded in the audit log
Business rules
BR-27Crest never processes paymentsBR-28A user whose contribution has lapsed always keeps read and export access to their own data and their spacesBR-29The contribution amount is set only to cover Crest's running costs, not to make a profit
User journeys
device_push_token
A device's push notification subscription, used for reminders and alerts. Phase 2.
Columns
| Column | Type | Nullable | Default |
|---|---|---|---|
id | uuid | no | gen_random_uuid() |
user_id | uuid | no | |
token | text | no | |
platform | device_platform | no | |
device_name | text | yes | |
last_success_at | timestamp with time zone | yes | |
created_at | timestamp with time zone | no | now() |
created_by | uuid | yes | |
updated_at | timestamp with time zone | no | now() |
updated_by | uuid | yes |
Constraints
device_push_token_created_by_fkey: foreign key,FOREIGN KEY (created_by) REFERENCES app_user(id) ON DELETE SET NULLdevice_push_token_updated_by_fkey: foreign key,FOREIGN KEY (updated_by) REFERENCES app_user(id) ON DELETE SET NULLdevice_push_token_user_id_fkey: foreign key,FOREIGN KEY (user_id) REFERENCES app_user(id) ON DELETE CASCADEdevice_push_token_pkey: primary key,PRIMARY KEY (id)device_push_token_token_key: unique,UNIQUE (token)
Indexes
CREATE INDEX device_push_token_user_idx ON public.device_push_token USING btree (user_id)
Relations
Business requirements
FR-NOT-1Optional daily reminder to log transactionsFR-NOT-2Budget threshold alerts (see FR-BUD-3)FR-NOT-3Notifications need connected modeFR-APP-3When browser push notifications are introduced, the app asks once for permission, explaining what they are forFR-SPC-16Each member can turn on Notify me when someone adds a transaction for a shared space (off by default)
Business rules
None.
User journeys
UJ-28Get a budget alert and reactUJ-37Change preferences and remindersUJ-38Add Crest to the home screen
exchange_rate
A user's own manually entered rate from a currency to their base currency. It is private to that user and used only for converted totals.
Columns
| Column | Type | Nullable | Default |
|---|---|---|---|
id | uuid | no | gen_random_uuid() |
user_id | uuid | no | |
currency | text | no | |
rate_to_base | numeric(24,12) | no | |
created_at | timestamp with time zone | no | now() |
created_by | uuid | yes | |
updated_at | timestamp with time zone | no | now() |
updated_by | uuid | yes |
Constraints
exchange_rate_currency_check: check,CHECK ((currency ~ '^[A-Z]{3}$'::text))exchange_rate_rate_to_base_check: check,CHECK ((rate_to_base > (0)::numeric))exchange_rate_created_by_fkey: foreign key,FOREIGN KEY (created_by) REFERENCES app_user(id) ON DELETE SET NULLexchange_rate_updated_by_fkey: foreign key,FOREIGN KEY (updated_by) REFERENCES app_user(id) ON DELETE SET NULLexchange_rate_user_id_fkey: foreign key,FOREIGN KEY (user_id) REFERENCES app_user(id) ON DELETE CASCADEexchange_rate_pkey: primary key,PRIMARY KEY (id)exchange_rate_user_id_currency_key: unique,UNIQUE (user_id, currency)
Relations
Business requirements
FR-FIN-6Users can maintain their own exchange rates (one rate per currency to their base currency) in settings, with the date each rate was last updatedFR-FIN-7Totals across currencies (net worth, dashboard) are converted into the base currency using the user's saved rates; the app shows the rate date and pr…FR-TRX-2dThe rate field is pre-filled with the user's saved rate for that currency pair (FR-FIN-6), if one exists; the user can override it for this transfer…FR-BUD-7Expenses in currencies other than the space's currency count toward budgets after conversion with the viewing user's saved rate; if the rate is missi…
Business rules
BR-15Exchange rates are always entered manually by the user; Crest does not fetch rates from external servicesBR-16A cross-currency transfer stores the amount sent, the amount received, and the rate usedBR-17Converted totals are estimates for display only; financial account balances always stay in that financial account's own currency
User journeys
UJ-22Transfer between currencies with a manual rateUJ-31Check net worth across currenciesUJ-35Maintain exchange rates
financial_account
A place money lives: cash, bank, e-wallet, or credit card, with one currency. Its balance is derived from its transactions.
Columns
| Column | Type | Nullable | Default |
|---|---|---|---|
id | uuid | no | gen_random_uuid() |
space_id | uuid | no | |
name | text | no | |
type | financial_account_type | no | |
currency | text | no | |
opening_balance | bigint | no | 0 |
provider | text | yes | |
color | text | yes | |
icon | text | yes | |
last4 | text | yes | |
credit_limit | bigint | yes | |
statement_day | smallint | yes | |
due_day | smallint | yes | |
sort_order | integer | no | 0 |
archived_at | timestamp with time zone | yes | |
created_at | timestamp with time zone | no | now() |
created_by | uuid | yes | |
updated_at | timestamp with time zone | no | now() |
updated_by | uuid | yes | |
deleted_at | timestamp with time zone | yes |
Constraints
financial_account_check: check,CHECK (((type = 'CreditCard'::financial_account_type) OR ((credit_limit IS NULL) AND (statement_day IS NULL) AND (due_day IS NULL))))financial_account_credit_limit_check: check,CHECK ((credit_limit >= 0))financial_account_currency_check: check,CHECK ((currency ~ '^[A-Z]{3}$'::text))financial_account_due_day_check: check,CHECK (((due_day >= 1) AND (due_day <= 31)))financial_account_last4_check: check,CHECK ((last4 ~ '^[0-9]{4}$'::text))financial_account_name_check: check,CHECK ((length(TRIM(BOTH FROM name)) > 0))financial_account_statement_day_check: check,CHECK (((statement_day >= 1) AND (statement_day <= 31)))financial_account_created_by_fkey: foreign key,FOREIGN KEY (created_by) REFERENCES app_user(id) ON DELETE SET NULLfinancial_account_space_id_fkey: foreign key,FOREIGN KEY (space_id) REFERENCES space(id) ON DELETE CASCADEfinancial_account_updated_by_fkey: foreign key,FOREIGN KEY (updated_by) REFERENCES app_user(id) ON DELETE SET NULLfinancial_account_pkey: primary key,PRIMARY KEY (id)
Indexes
CREATE INDEX financial_account_space_idx ON public.financial_account USING btree (space_id)
Relations
Business requirements
FR-FIN-1Users can create any number of financial accounts, each with a name, type (cash, bank, e-wallet / online payment, credit card), currency, and opening…FR-FIN-2Users can edit, archive, and reorder financial accountsFR-FIN-3The app shows each financial account's current balance, derived from its transactionsFR-FIN-4Each space shows its net worth: assets (cash, bank, e-wallet) minus liabilities (credit cards)FR-FIN-5Users can hold financial accounts in different currenciesFR-FIN-8Users can optionally add the provider name (e.gFR-FIN-9Financial accounts are grouped by type on the Financial Accounts screen, with a subtotal per groupFR-FIN-10Credit card financial accounts show the amount owed; users can optionally set a credit limit and see available creditFR-FIN-10aUnusual balances are allowed but highlighted: a cash / bank / e-wallet balance below zero, a credit card over its limit, and a credit card paid more…FR-FIN-11Credit card financial accounts can optionally have a statement day and payment due day, with a reminder to the space's Owners before the due dateFR-FIN-14A financial account's currency can be changed only while it has no transactions; after that the field is lockedFR-FIN-15Before a financial account is deleted, the warning lists the other financial accounts it has transfers with, and explains that those transfers become…FR-SPC-3Every financial account, category, budget, and savings goal belongs to exactly one spaceFR-SPC-17Financial accounts can't be moved between spaces
Business rules
BR-1Each financial account has exactly one currency; transactions inherit the financial account's currencyBR-3Financial account balance = opening balance + sum of all its transactionsBR-6Deleting a financial account requires confirmation and deletes its transactions; archiving keeps themBR-19Crest stores at most the last 4 digits of any card or bank account number, never the full number, CVV, PIN, or login detailsBR-23Crest records what really happened: negative balances, spending over a credit limit, and overpaid credit cards are allowed, never blockedBR-26A financial account's currency is fixed once it has any transactionBR-30Every financial account, category, budget, and savings goal belongs to exactly one spaceBR-37Financial accounts can't be moved between spaces
User journeys
UJ-16First-time setupUJ-20Pay with a credit cardUJ-21Pay a credit card billUJ-31Check net worth across currenciesUJ-32Add and organize financial accountsUJ-33Archive or delete a financial account
goal_contribution
A manually recorded contribution to a savings goal that is not linked to a financial account.
Columns
| Column | Type | Nullable | Default |
|---|---|---|---|
id | uuid | no | gen_random_uuid() |
goal_id | uuid | no | |
amount | bigint | no | |
created_at | timestamp with time zone | no | now() |
created_by | uuid | yes | |
updated_at | timestamp with time zone | no | now() |
updated_by | uuid | yes | |
deleted_at | timestamp with time zone | yes |
Constraints
goal_contribution_amount_check: check,CHECK ((amount <> 0))goal_contribution_created_by_fkey: foreign key,FOREIGN KEY (created_by) REFERENCES app_user(id) ON DELETE SET NULLgoal_contribution_goal_id_fkey: foreign key,FOREIGN KEY (goal_id) REFERENCES savings_goal(id) ON DELETE CASCADEgoal_contribution_updated_by_fkey: foreign key,FOREIGN KEY (updated_by) REFERENCES app_user(id) ON DELETE SET NULLgoal_contribution_pkey: primary key,PRIMARY KEY (id)
Indexes
CREATE INDEX goal_contribution_goal_idx ON public.goal_contribution USING btree (goal_id)
Relations
Business requirements
FR-BUD-6If no financial account is linked, the user records contributions to the goal manually
Business rules
None.
User journeys
UJ-29Create and track a savings goal
recurrence
A repeating transaction (daily, weekly, monthly, yearly) that the server creates on its due date.
Columns
| Column | Type | Nullable | Default |
|---|---|---|---|
id | uuid | no | gen_random_uuid() |
space_id | uuid | no | |
template | jsonb | no | |
frequency | recurrence_frequency | no | |
every | smallint | no | 1 |
anchor_day | smallint | yes | |
start_on | date | no | |
end_on | date | yes | |
next_run_on | date | yes | |
stopped_at | timestamp with time zone | yes | |
created_at | timestamp with time zone | no | now() |
created_by | uuid | yes | |
updated_at | timestamp with time zone | no | now() |
updated_by | uuid | yes | |
deleted_at | timestamp with time zone | yes |
Constraints
recurrence_anchor_day_check: check,CHECK (((anchor_day >= 1) AND (anchor_day <= 31)))recurrence_every_check: check,CHECK ((every >= 1))recurrence_created_by_fkey: foreign key,FOREIGN KEY (created_by) REFERENCES app_user(id) ON DELETE SET NULLrecurrence_space_id_fkey: foreign key,FOREIGN KEY (space_id) REFERENCES space(id) ON DELETE CASCADErecurrence_updated_by_fkey: foreign key,FOREIGN KEY (updated_by) REFERENCES app_user(id) ON DELETE SET NULLrecurrence_pkey: primary key,PRIMARY KEY (id)
Indexes
CREATE INDEX recurrence_due_idx ON public.recurrence USING btree (next_run_on) WHERE ((stopped_at IS NULL) AND (deleted_at IS NULL))CREATE INDEX recurrence_space_idx ON public.recurrence USING btree (space_id)
Relations
Business requirements
FR-TRX-5Users can set up recurring transactions (daily, weekly, monthly, yearly)FR-TRX-5aWhen editing or deleting a transaction created by a recurrence, the user chooses Only this one or This and all future onesFR-TRX-5bRecurring transactions are created by the server on their due date in connected mode, so occurrences are never missed while the phone is off or offli…FR-TRX-5cA recurring transaction in a shared space belongs to the member who set it up; it stops if that member leaves the space
Business rules
BR-25A recurrence on day 29, 30, or 31 falls on the last day of shorter monthsBR-40A recurring transaction in a shared space belongs to the member who set it up and stops when that member leaves the space
User journeys
savings_goal
A savings goal with a target amount and date, optionally linked to one financial account whose balance is the progress.
Columns
| Column | Type | Nullable | Default |
|---|---|---|---|
id | uuid | no | gen_random_uuid() |
space_id | uuid | no | |
name | text | no | |
target_amount | bigint | no | |
target_date | date | yes | |
financial_account_id | uuid | yes | |
completed_at | timestamp with time zone | yes | |
created_at | timestamp with time zone | no | now() |
created_by | uuid | yes | |
updated_at | timestamp with time zone | no | now() |
updated_by | uuid | yes | |
deleted_at | timestamp with time zone | yes |
Constraints
savings_goal_name_check: check,CHECK ((length(TRIM(BOTH FROM name)) > 0))savings_goal_target_amount_check: check,CHECK ((target_amount > 0))savings_goal_created_by_fkey: foreign key,FOREIGN KEY (created_by) REFERENCES app_user(id) ON DELETE SET NULLsavings_goal_financial_account_id_fkey: foreign key,FOREIGN KEY (financial_account_id) REFERENCES financial_account(id) ON DELETE SET NULLsavings_goal_space_id_fkey: foreign key,FOREIGN KEY (space_id) REFERENCES space(id) ON DELETE CASCADEsavings_goal_updated_by_fkey: foreign key,FOREIGN KEY (updated_by) REFERENCES app_user(id) ON DELETE SET NULLsavings_goal_pkey: primary key,PRIMARY KEY (id)
Indexes
CREATE INDEX savings_goal_space_idx ON public.savings_goal USING btree (space_id)
Relations
Business requirements
FR-BUD-4The space's Owners can create savings goals with name, target amount, and target dateFR-BUD-5A savings goal is linked to one financial account in the same space (e.gFR-BUD-6If no financial account is linked, the user records contributions to the goal manually
Business rules
None.
User journeys
UJ-29Create and track a savings goal
schema_migration
Bookkeeping for the migration runner. Not a business table, so it has no requirements or journeys.
Columns
| Column | Type | Nullable | Default |
|---|---|---|---|
name | text | no | |
checksum | text | no | |
applied_at | timestamp with time zone | no | now() |
Constraints
schema_migration_pkey: primary key,PRIMARY KEY (name)
Business requirements
None.
Business rules
None.
User journeys
None.
space
A Personal space (one per user) or a shared space. Every financial account, category, budget, and goal belongs to exactly one space.
Columns
| Column | Type | Nullable | Default |
|---|---|---|---|
id | uuid | no | gen_random_uuid() |
kind | space_kind | no | |
name | text | no | |
currency | text | no | |
time_zone | text | no | |
budget_start_day | smallint | no | 1 |
created_at | timestamp with time zone | no | now() |
created_by | uuid | yes | |
updated_at | timestamp with time zone | no | now() |
updated_by | uuid | yes | |
deleted_at | timestamp with time zone | yes |
Constraints
space_budget_start_day_check: check,CHECK (((budget_start_day >= 1) AND (budget_start_day <= 28)))space_currency_check: check,CHECK ((currency ~ '^[A-Z]{3}$'::text))space_name_check: check,CHECK ((length(TRIM(BOTH FROM name)) > 0))space_created_by_fkey: foreign key,FOREIGN KEY (created_by) REFERENCES app_user(id) ON DELETE SET NULLspace_updated_by_fkey: foreign key,FOREIGN KEY (updated_by) REFERENCES app_user(id) ON DELETE SET NULLspace_pkey: primary key,PRIMARY KEY (id)
Indexes
CREATE UNIQUE INDEX space_personal_owner_idx ON public.space USING btree (created_by) WHERE (kind = 'Personal'::space_kind)
Relations
Business requirements
FR-SPC-1Every user gets a Personal space automatically when their login is activated (connected mode; in local mode the Personal space is created on the devi…FR-SPC-2Any Active user can create a shared space with a name, currency, and budget start day, and becomes its OwnerFR-SPC-3Every financial account, category, budget, and savings goal belongs to exactly one spaceFR-SPC-10Owners can delete a shared space after typed confirmationFR-SPC-11A space switcher at the top of Crest moves between spacesFR-SPC-12Each space has a currency used for its budgets and reportsFR-SET-3Users can set the start day of the budget month for their Personal space (e.gFR-SET-4Changing the base currency (or a shared space's currency) shows a warning, then asks the user (or Owner) to review that space's budgets, pre-converte…
Business rules
BR-4Budget periods start on the space's configured start day (default: 1st of month)BR-30Every financial account, category, budget, and savings goal belongs to exactly one spaceBR-32A Personal space always has exactly one member, its user
User journeys
UJ-16First-time setupUJ-27Set monthly budgetsUJ-37Change preferences and remindersUJ-42Create a shared spaceUJ-49Hand over ownership or delete a shared space
space_invitation
An Owner's invitation for someone to join a shared space. It expires after 14 days.
Columns
| Column | Type | Nullable | Default |
|---|---|---|---|
id | uuid | no | gen_random_uuid() |
space_id | uuid | no | |
email | citext | no | |
role | space_role | no | 'Member'::space_role |
invitee_user_id | uuid | yes | |
status | invitation_status | no | 'Pending'::invitation_status |
expires_at | timestamp with time zone | no | (now() + '14 days'::interval) |
created_at | timestamp with time zone | no | now() |
created_by | uuid | yes | |
updated_at | timestamp with time zone | no | now() |
updated_by | uuid | yes | |
deleted_at | timestamp with time zone | yes |
Constraints
space_invitation_created_by_fkey: foreign key,FOREIGN KEY (created_by) REFERENCES app_user(id) ON DELETE SET NULLspace_invitation_invitee_user_id_fkey: foreign key,FOREIGN KEY (invitee_user_id) REFERENCES app_user(id) ON DELETE CASCADEspace_invitation_space_id_fkey: foreign key,FOREIGN KEY (space_id) REFERENCES space(id) ON DELETE CASCADEspace_invitation_updated_by_fkey: foreign key,FOREIGN KEY (updated_by) REFERENCES app_user(id) ON DELETE SET NULLspace_invitation_pkey: primary key,PRIMARY KEY (id)
Indexes
CREATE INDEX space_invitation_invitee_idx ON public.space_invitation USING btree (invitee_user_id) WHERE (status = 'Pending'::invitation_status)CREATE INDEX space_invitation_space_idx ON public.space_invitation USING btree (space_id)
Relations
Business requirements
FR-SPC-4Owners can invite other people by emailFR-SPC-5Invitations expire after 14 daysFR-SPC-6Members have the role Owner or Member, with the permissions in the table aboveFR-NOT-4Users are notified of invitations to a shared space, of being removed from one, and of a shared space being deleted
Business rules
BR-39Only Active users can join a shared space, and only by accepting an invitation, which expires after 14 days
User journeys
UJ-43Invite someone and accept the invitation
space_member
Who belongs to a space and as what (Owner or Member), plus their per-space settings.
Columns
| Column | Type | Nullable | Default |
|---|---|---|---|
id | uuid | no | gen_random_uuid() |
space_id | uuid | no | |
user_id | uuid | no | |
role | space_role | no | |
include_in_totals | boolean | no | |
notify_new_txn | boolean | no | false |
created_at | timestamp with time zone | no | now() |
created_by | uuid | yes | |
updated_at | timestamp with time zone | no | now() |
updated_by | uuid | yes |
Constraints
space_member_created_by_fkey: foreign key,FOREIGN KEY (created_by) REFERENCES app_user(id) ON DELETE SET NULLspace_member_space_id_fkey: foreign key,FOREIGN KEY (space_id) REFERENCES space(id) ON DELETE CASCADEspace_member_updated_by_fkey: foreign key,FOREIGN KEY (updated_by) REFERENCES app_user(id) ON DELETE SET NULLspace_member_user_id_fkey: foreign key,FOREIGN KEY (user_id) REFERENCES app_user(id) ON DELETE CASCADEspace_member_pkey: primary key,PRIMARY KEY (id)space_member_space_id_user_id_key: unique,UNIQUE (space_id, user_id)
Indexes
CREATE INDEX space_member_user_idx ON public.space_member USING btree (user_id)
Relations
Business requirements
FR-SPC-6Members have the role Owner or Member, with the permissions in the table aboveFR-SPC-8A member can leave a shared space, and Owners can remove members (including other Owners, FR-SPC-6)FR-SPC-9The last Owner can't leave until they make someone else Owner or delete the spaceFR-SPC-13Each member has an Include in my totals setting per shared space: on by default for Owners, off by default for MembersFR-SPC-16Each member can turn on Notify me when someone adds a transaction for a shared space (off by default)FR-NOT-4Users are notified of invitations to a shared space, of being removed from one, and of a shared space being deleted
Business rules
BR-32A Personal space always has exactly one member, its userBR-33Every shared space has at least one OwnerBR-34When a member leaves or is removed, transactions they added stay in the space with their nameBR-39Only Active users can join a shared space, and only by accepting an invitation, which expires after 14 daysBR-43Deactivating a user does not change their space memberships
User journeys
UJ-08Delete myself and all my dataUJ-31Check net worth across currenciesUJ-42Create a shared spaceUJ-43Invite someone and accept the invitationUJ-44Add a transaction in a shared spaceUJ-48Leave a space or remove a memberUJ-49Hand over ownership or delete a shared space
transfer
Links the two legs of a transfer between financial accounts, in the same or different spaces, and holds the exchange rate used.
Columns
| Column | Type | Nullable | Default |
|---|---|---|---|
id | uuid | no | gen_random_uuid() |
rate | numeric(24,12) | yes | |
created_at | timestamp with time zone | no | now() |
created_by | uuid | yes | |
updated_at | timestamp with time zone | no | now() |
updated_by | uuid | yes | |
deleted_at | timestamp with time zone | yes |
Constraints
transfer_rate_check: check,CHECK ((rate > (0)::numeric))transfer_created_by_fkey: foreign key,FOREIGN KEY (created_by) REFERENCES app_user(id) ON DELETE SET NULLtransfer_updated_by_fkey: foreign key,FOREIGN KEY (updated_by) REFERENCES app_user(id) ON DELETE SET NULLtransfer_pkey: primary key,PRIMARY KEY (id)
Relations
Business requirements
FR-TRX-2Users can record a transfer between financial accounts they can access, in the same space or in different spaces (FR-SPC-14)FR-TRX-2aUsers can transfer between financial accounts in different currencies (e.gFR-TRX-2bIn a cross-currency transfer the user enters the amount sent plus either the exchange rate or the amount received; the app calculates the missing val…FR-TRX-2cThe user can edit the calculated amount received to match what actually arrived (e.gFR-TRX-2dThe rate field is pre-filled with the user's saved rate for that currency pair (FR-FIN-6), if one exists; the user can override it for this transfer…FR-TRX-2eOn any transfer (same or different currency) the user can optionally record a fee, e.gFR-TRX-3aEditing a cross-currency transfer uses the same form as creating one: changing any of amount sent, rate, or amount received recalculates the thirdFR-FIN-12Paying a credit card bill is recorded as a transfer from a bank / e-wallet / cash financial account to the credit card financial accountFR-FIN-13A top-up (e.gFR-SPC-14A user can transfer money between financial accounts in any spaces they belong to (e.g
Business rules
BR-2Transfers between financial accounts, in the same space or across spaces, do not count as income or expenseBR-15Exchange rates are always entered manually by the user; Crest does not fetch rates from external servicesBR-16A cross-currency transfer stores the amount sent, the amount received, and the rate usedBR-35In a transfer between spaces, members see only the side in their own spaceBR-38Requests for money between members (e.g
User journeys
UJ-19Top up an e-wallet or withdraw cashUJ-21Pay a credit card billUJ-22Transfer between currencies with a manual rateUJ-45Top up a shared fund from a Personal financial accountUJ-47Pay yourself back from a shared fund
txn
Every expense, income, refund, and transfer leg. Its created_at is the transaction's date, and its amount is the signed effect on the financial account's balance.
Columns
| Column | Type | Nullable | Default |
|---|---|---|---|
id | uuid | no | gen_random_uuid() |
space_id | uuid | no | |
financial_account_id | uuid | no | |
kind | txn_kind | no | |
amount | bigint | no | |
category_id | uuid | yes | |
note | text | yes | |
transfer_id | uuid | yes | |
fee_for_transfer_id | uuid | yes | |
refund_of_txn_id | uuid | yes | |
recurrence_id | uuid | yes | |
counterparty_label | text | yes | |
receipt_path | text | yes | |
created_at | timestamp with time zone | no | now() |
created_by | uuid | yes | |
updated_at | timestamp with time zone | no | now() |
updated_by | uuid | yes | |
deleted_at | timestamp with time zone | yes |
Constraints
txn_check: check,CHECK ((((kind = 'Expense'::txn_kind) AND (amount < 0)) OR ((kind = ANY (ARRAY['Income'::txn_kind, 'Refund'::txn_kind])) AND (amount > 0)) OR ((kind = 'Transfer'::txn_kind) AND (amount <> 0))))txn_check1: check,CHECK (((kind = 'Transfer'::txn_kind) = (transfer_id IS NOT NULL)))txn_check2: check,CHECK (((kind <> 'Transfer'::txn_kind) OR (category_id IS NULL)))txn_check3: check,CHECK (((fee_for_transfer_id IS NULL) OR (kind = 'Expense'::txn_kind)))txn_check4: check,CHECK (((refund_of_txn_id IS NULL) OR (kind = 'Refund'::txn_kind)))txn_category_id_fkey: foreign key,FOREIGN KEY (category_id) REFERENCES category(id) ON DELETE SET NULLtxn_created_by_fkey: foreign key,FOREIGN KEY (created_by) REFERENCES app_user(id) ON DELETE SET NULLtxn_fee_for_transfer_id_fkey: foreign key,FOREIGN KEY (fee_for_transfer_id) REFERENCES transfer(id) ON DELETE SET NULLtxn_financial_account_id_fkey: foreign key,FOREIGN KEY (financial_account_id) REFERENCES financial_account(id) ON DELETE CASCADEtxn_recurrence_id_fkey: foreign key,FOREIGN KEY (recurrence_id) REFERENCES recurrence(id) ON DELETE SET NULLtxn_refund_of_txn_id_fkey: foreign key,FOREIGN KEY (refund_of_txn_id) REFERENCES txn(id) ON DELETE SET NULLtxn_space_id_fkey: foreign key,FOREIGN KEY (space_id) REFERENCES space(id) ON DELETE CASCADEtxn_transfer_id_fkey: foreign key,FOREIGN KEY (transfer_id) REFERENCES transfer(id) ON DELETE CASCADEtxn_updated_by_fkey: foreign key,FOREIGN KEY (updated_by) REFERENCES app_user(id) ON DELETE SET NULLtxn_pkey: primary key,PRIMARY KEY (id)
Indexes
CREATE INDEX txn_account_date_idx ON public.txn USING btree (financial_account_id, created_at)CREATE INDEX txn_note_trgm_idx ON public.txn USING gin (note gin_trgm_ops)CREATE INDEX txn_space_category_date_idx ON public.txn USING btree (space_id, category_id, created_at)CREATE INDEX txn_space_creator_idx ON public.txn USING btree (space_id, created_by)CREATE INDEX txn_space_date_idx ON public.txn USING btree (space_id, created_at)CREATE INDEX txn_space_updated_idx ON public.txn USING btree (space_id, updated_at)CREATE INDEX txn_transfer_idx ON public.txn USING btree (transfer_id) WHERE (transfer_id IS NOT NULL)
Relations
Business requirements
FR-TRX-1Users can add an expense or income with amount, financial account, category, date and time (defaults to now; can be set to a past date for entries lo…FR-TRX-2Users can record a transfer between financial accounts they can access, in the same space or in different spaces (FR-SPC-14)FR-TRX-3Users can edit and delete transactions, within their role in the space (§8.4): Members only their own, Owners anyFR-TRX-4Users can search and filter transactions in the current space by date, financial account, category, amount, and textFR-TRX-5aWhen editing or deleting a transaction created by a recurrence, the user chooses Only this one or This and all future onesFR-TRX-7Users can always add, edit, and delete transactions without internetFR-TRX-8Users can record a refund: money returned for an earlier purchase, into any financial account, with an expense category (normally the one the origina…FR-TRX-9Future-dated transactions are shown in an Upcoming section of the transaction listFR-SPC-7Every transaction shows who added it and, if changed, who last edited itFR-FIN-3The app shows each financial account's current balance, derived from its transactionsFR-DAT-1Users can export to CSV the transactions of any space they belong to (one space or all), shared through the device's share menu or downloadedFR-DAT-2Users can import transactions from CSV with column mappingNFR-1Accuracy
Business rules
BR-2Transfers between financial accounts, in the same space or across spaces, do not count as income or expenseBR-3Financial account balance = opening balance + sum of all its transactionsBR-6Deleting a financial account requires confirmation and deletes its transactions; archiving keeps themBR-18Spending with a credit card is an expense when the purchase is made; paying the card bill is a transfer, so the same spending is not counted twiceBR-23Crest records what really happened: negative balances, spending over a credit limit, and overpaid credit cards are allowed, never blockedBR-24A refund reduces spending in its category (and the related budget)BR-31A transaction's category must belong to the same space as its financial accountBR-34When a member leaves or is removed, transactions they added stay in the space with their nameBR-35In a transfer between spaces, members see only the side in their own spaceBR-36Members can edit or delete only transactions they added; Owners can edit or delete any transaction in their spaceBR-42Balances and net worth are always shown as of now
User journeys
UJ-17Log a daily expenseUJ-18Log incomeUJ-19Top up an e-wallet or withdraw cashUJ-20Pay with a credit cardUJ-21Pay a credit card billUJ-22Transfer between currencies with a manual rateUJ-23Fix or delete a transactionUJ-24Find a past transactionUJ-25Log a transaction without internetUJ-36Export and import transactions (CSV)UJ-44Add a transaction in a shared spaceUJ-45Top up a shared fund from a Personal financial accountUJ-46See how a shared space's money is usedUJ-47Pay yourself back from a shared fundUJ-48Leave a space or remove a member