Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions modules/expenses/index.py
Original file line number Diff line number Diff line change
Expand Up @@ -235,10 +235,17 @@ def _list_expenses(self):
contractors = repo.get_all_contractors()
categories = repo.get_unique_categories()

# Module Expense model have no 'contractor' relationship (core backref
# live on a different mapped class), so template cannot reach
# expense.contractor. Build id->name map and let template look up by
# contractor_id — same trick report code already use.
contractors_map = {c.id: c.name for c in contractors}

return render_template('expenses.html',
expenses_by_year=expenses_by_year,
years=years,
contractors=contractors,
contractors_map=contractors_map,
categories=categories)

def _create_expense(self):
Expand Down
2 changes: 1 addition & 1 deletion modules/expenses/templates/expenses.html
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ <h1>Expenses</h1>
<tr>
<td>{{ expense.expense_date.strftime('%d/%m/%Y') }}</td>
<td>{{ expense.invoice_number or '-' }}</td>
<td>{{ expense.contractor.name if expense.contractor else '-' }}</td>
<td>{{ contractors_map.get(expense.contractor_id, '-') }}</td>
<td>{{ expense.category or '-' }}</td>
<td>{{ expense.description or '-' }}</td>
<td>{{ "%.2f"|format(expense.amount) }} {{ expense.currency }}</td>
Expand Down
93 changes: 93 additions & 0 deletions tests/test_expenses_list.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
"""Regression test: Expenses list view must render the Contractor column.

Bug: module Expense model have no 'contractor' relationship (core backref live
on a separate mapped class), so template `expense.contractor` resolve to
Undefined and Contractor column show empty for every row. List view now pass a
contractor id->name map; this test guard that the saved contractor name reach
the rendered table.
"""
from datetime import date

import pytest

from module_manager import ModuleManager


@pytest.fixture(scope='module')
def expenses_module():
"""Load the Expenses module once against the app + a fresh DB.

Module-scoped so the ModuleManager (which registers the singleton
`module_enabled` table) is built only once — re-building it per test would
redefine that table in the shared metadata and error.
"""
import app as appmod

Check notice

Code scanning / CodeQL

Module is imported with 'import' and 'import from' Note test

Module 'app' is imported with both 'import' and 'import from'.
from app import app as flask_app, db, Settings

with flask_app.app_context():
db.create_all()

mm = ModuleManager(flask_app, db)
mm.core._settings_model = Settings
mm.init_db()
mm.discover_modules()

enabled_model = mm._get_module_enabled_model()
db.session.add(enabled_model(module_id='expenses', enabled=True))
db.session.commit()
mm.load_enabled_modules()

# Templates reference these Jinja globals.
appmod.module_manager = mm
flask_app.jinja_env.globals.setdefault('module_manager', mm)
flask_app.jinja_env.globals.setdefault('app_version', 'test')

yield mm.modules['expenses']

db.session.remove()
db.drop_all()


def test_list_view_renders_contractor_name(expenses_module):
from app import app as flask_app, db

em = expenses_module
contractor = em.Contractor(name='ACME Corp')
db.session.add(contractor)
db.session.flush()
db.session.add(em.Expense(
contractor_id=contractor.id,
amount=42.0,
currency='EUR',
category='Tools',
expense_date=date(2026, 1, 15),
))
db.session.commit()

with flask_app.test_request_context('/expenses/'):
html = em._list_expenses()

# Assert on the table CELL, not just any mention — the contractor also
# shows up in the filter dropdown, so a bare substring check would pass even
# with the bug present.
assert '<td>ACME Corp</td>' in html


def test_list_view_handles_missing_contractor(expenses_module):
from app import app as flask_app, db

em = expenses_module
db.session.add(em.Expense(
contractor_id=None,
amount=9.0,
currency='EUR',
category='MiscNoContractor',
expense_date=date(2026, 2, 1),
))
db.session.commit()

with flask_app.test_request_context('/expenses/'):
html = em._list_expenses()

# No contractor -> placeholder, and rendering must not raise.
assert 'MiscNoContractor' in html
Loading