Skip to content

Allow negative amounts in any category for refunds and paybacks #388

Description

@emavgl

Description

Currently, Oinkoin forces the sign of a record's value based on its category type:

  • Expense category → value is always stored as negative (e.g., -50€)
  • Income category → value is always stored as positive (e.g., +100€)

This prevents users from recording refunds or paybacks in the same category where the original transaction occurred. For example:

  • A 10€ refund on groceries cannot be entered in the "Groceries" expense category (the app turns it into -10€, making expenses look even larger)
  • A 100€ payback of income (employer mistake) cannot be entered as negative income

The workaround (creating a separate "Refunds" income category) inflates statistics — showing more expenses AND more income than reality.

Requested Behavior (inspired by "Mein Haushaltsbuch")

Users should be able to enter negative amounts in any category:

  • -10€ in an expense category → reduces that category's expense total (net expense of -40€ instead of separate -50€ expense + 10€ refund in another category)
  • -100€ in an income category → reduces income totals
  • Statistics show realistic net totals per category

Technical Analysis

No database changes needed

The records table stores value REAL with no CHECK constraints (lib/services/database/sqlite-migration-service.dart:37). The sign of the value is not enforced at the DB level.

What needs to change

  1. lib/records/edit-record-page.dart (line 967-989):

    • Change allowNegative: falseallowNegative: true on the AmountInputField
    • Remove the forced sign assignment in changeRecordValue() — stop calling abs() and re-negating based on category type. Let the user's entered sign be stored as-is.
  2. lib/components/amount_input_field.dart — Already supports allowNegative: true (line 24), no changes needed.

  3. lib/helpers/records-utility-functions.dart — The parsers tryParseCurrencyString and tryParseSignedCurrencyString already exist (lines 213-220).

Statistics impact

Most aggregation code groups records by CategoryType and sums values directly, which handles refunds correctly:

  • records_per_day.dart — sums all values for a given CategoryType → refunds naturally reduce totals ✓
  • statistics-page.dart — filters by CategoryType → refunds in expense categories show up in the Expenses tab ✓
  • balance-chart-models.dart — uses value.abs() → may need review for records with "wrong" signs

Some edge cases in lib/models/records-per-category.dart — uses value < 0 / value > 0 to split income/expense within a category. A positive refund in an expense category would show as "income" in the per-category breakdown rather than reducing expenses. This may need updating.

Files Likely Affected

  • lib/records/edit-record-page.dart — Remove sign enforcement
  • lib/models/records-per-category.dart — Review sign-based splitting
  • lib/statistics/balance-chart-models.dart — Review value.abs() usage
  • lib/records/controllers/tab_records_controller.dart — Review applyTransferAwareWalletFilter for edge cases

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions