From 93f269676490bcb569ca180e17175afaec7424f2 Mon Sep 17 00:00:00 2001 From: jpmvaz Date: Thu, 29 Oct 2026 20:41:01 +0000 Subject: [PATCH] Flatten budget-app contents into repo root --- budget-app/.env.example => .env.example | 0 budget-app/.gitignore => .gitignore | 0 budget-app/CHANGELOG.md => CHANGELOG.md | 0 README.md | 186 +++++++++++++++++- budget-app/VERSION => VERSION | 0 {budget-app/backend => backend}/.dockerignore | 0 {budget-app/backend => backend}/Dockerfile | 0 {budget-app/backend => backend}/alembic.ini | 0 .../backend => backend}/alembic/env.py | 0 .../alembic/script.py.mako | 0 .../alembic/versions/0001_initial.py | 0 .../alembic/versions/0002_departments.py | 0 .../alembic/versions/0003_mail.py | 0 .../alembic/versions/0004_selected_months.py | 0 .../alembic/versions/0005_reopen_archive.py | 0 .../alembic/versions/0006_upper_management.py | 0 .../alembic/versions/0007_mfa_avatar_theme.py | 0 .../backend => backend}/app/__init__.py | 0 {budget-app/backend => backend}/app/audit.py | 0 .../backend => backend}/app/bootstrap.py | 0 {budget-app/backend => backend}/app/calc.py | 0 {budget-app/backend => backend}/app/config.py | 0 {budget-app/backend => backend}/app/db.py | 0 .../backend => backend}/app/importer.py | 0 {budget-app/backend => backend}/app/mailer.py | 0 {budget-app/backend => backend}/app/main.py | 0 {budget-app/backend => backend}/app/mfa.py | 0 {budget-app/backend => backend}/app/models.py | 0 {budget-app/backend => backend}/app/notify.py | 0 .../backend => backend}/app/permissions.py | 0 .../backend => backend}/app/preupgrade.py | 0 .../app/reports/__init__.py | 0 .../backend => backend}/app/reports/charts.py | 0 .../backend => backend}/app/reports/excel.py | 0 .../backend => backend}/app/reports/i18n.py | 0 .../backend => backend}/app/reports/render.py | 0 .../app/reports/templates/budget.html | 0 .../app/reports/templates/cost_report.html | 0 .../app/reports/templates/report.css | 0 .../app/routers/__init__.py | 0 .../app/routers/activity.py | 0 .../backend => backend}/app/routers/admin.py | 0 .../backend => backend}/app/routers/auth.py | 0 .../backend => backend}/app/routers/mail.py | 0 .../app/routers/reports.py | 0 .../backend => backend}/app/routers/years.py | 0 .../backend => backend}/app/security.py | 0 {budget-app/backend => backend}/app/seed.py | 0 .../backend => backend}/app/serializers.py | 0 .../backend => backend}/app/summary.py | 0 .../backend => backend}/app/template.py | 0 .../backend => backend}/docker-entrypoint.sh | 0 .../backend => backend}/requirements.txt | 0 .../backend => backend}/tests/conftest.py | 0 .../tests/fixtures/budget_2027.xlsx | Bin .../backend => backend}/tests/test_api.py | 0 .../backend => backend}/tests/test_calc.py | 0 .../tests/test_departments.py | 0 .../backend => backend}/tests/test_mail.py | 0 .../backend => backend}/tests/test_months.py | 0 .../backend => backend}/tests/test_v5.py | 0 .../backend => backend}/tests/test_v6.py | 0 .../backend => backend}/tests/test_v7.py | 0 {budget-app/backup => backup}/backup.sh | 0 {budget-app/backup => backup}/restore.sh | 0 budget-app/README.md | 185 ----------------- .../docker-compose.yml => docker-compose.yml | 0 {budget-app/docs => docs}/guide.html | 0 .../frontend => frontend}/.dockerignore | 0 {budget-app/frontend => frontend}/Caddyfile | 0 {budget-app/frontend => frontend}/Dockerfile | 0 {budget-app/frontend => frontend}/index.html | 0 .../frontend => frontend}/package-lock.json | 0 .../frontend => frontend}/package.json | 0 {budget-app/frontend => frontend}/src/App.jsx | 0 {budget-app/frontend => frontend}/src/api.js | 0 {budget-app/frontend => frontend}/src/auth.js | 0 .../src/components/EmailDialog.jsx | 0 .../src/components/Layout.jsx | 0 .../src/components/ui.jsx | 0 .../frontend => frontend}/src/format.js | 0 {budget-app/frontend => frontend}/src/i18n.js | 0 .../frontend => frontend}/src/main.jsx | 0 .../src/pages/Activity.jsx | 0 .../src/pages/Departments.jsx | 0 .../frontend => frontend}/src/pages/Login.jsx | 0 .../frontend => frontend}/src/pages/Mail.jsx | 0 .../src/pages/Profile.jsx | 0 .../src/pages/Reports.jsx | 0 .../src/pages/Settings.jsx | 0 .../frontend => frontend}/src/pages/Users.jsx | 0 .../src/pages/YearEditor.jsx | 0 .../frontend => frontend}/src/pages/Years.jsx | 0 .../frontend => frontend}/src/styles.css | 0 .../frontend => frontend}/vite.config.js | 0 95 files changed, 184 insertions(+), 187 deletions(-) rename budget-app/.env.example => .env.example (100%) rename budget-app/.gitignore => .gitignore (100%) rename budget-app/CHANGELOG.md => CHANGELOG.md (100%) rename budget-app/VERSION => VERSION (100%) rename {budget-app/backend => backend}/.dockerignore (100%) rename {budget-app/backend => backend}/Dockerfile (100%) rename {budget-app/backend => backend}/alembic.ini (100%) rename {budget-app/backend => backend}/alembic/env.py (100%) rename {budget-app/backend => backend}/alembic/script.py.mako (100%) rename {budget-app/backend => backend}/alembic/versions/0001_initial.py (100%) rename {budget-app/backend => backend}/alembic/versions/0002_departments.py (100%) rename {budget-app/backend => backend}/alembic/versions/0003_mail.py (100%) rename {budget-app/backend => backend}/alembic/versions/0004_selected_months.py (100%) rename {budget-app/backend => backend}/alembic/versions/0005_reopen_archive.py (100%) rename {budget-app/backend => backend}/alembic/versions/0006_upper_management.py (100%) rename {budget-app/backend => backend}/alembic/versions/0007_mfa_avatar_theme.py (100%) rename {budget-app/backend => backend}/app/__init__.py (100%) rename {budget-app/backend => backend}/app/audit.py (100%) rename {budget-app/backend => backend}/app/bootstrap.py (100%) rename {budget-app/backend => backend}/app/calc.py (100%) rename {budget-app/backend => backend}/app/config.py (100%) rename {budget-app/backend => backend}/app/db.py (100%) rename {budget-app/backend => backend}/app/importer.py (100%) rename {budget-app/backend => backend}/app/mailer.py (100%) rename {budget-app/backend => backend}/app/main.py (100%) rename {budget-app/backend => backend}/app/mfa.py (100%) rename {budget-app/backend => backend}/app/models.py (100%) rename {budget-app/backend => backend}/app/notify.py (100%) rename {budget-app/backend => backend}/app/permissions.py (100%) rename {budget-app/backend => backend}/app/preupgrade.py (100%) rename {budget-app/backend => backend}/app/reports/__init__.py (100%) rename {budget-app/backend => backend}/app/reports/charts.py (100%) rename {budget-app/backend => backend}/app/reports/excel.py (100%) rename {budget-app/backend => backend}/app/reports/i18n.py (100%) rename {budget-app/backend => backend}/app/reports/render.py (100%) rename {budget-app/backend => backend}/app/reports/templates/budget.html (100%) rename {budget-app/backend => backend}/app/reports/templates/cost_report.html (100%) rename {budget-app/backend => backend}/app/reports/templates/report.css (100%) rename {budget-app/backend => backend}/app/routers/__init__.py (100%) rename {budget-app/backend => backend}/app/routers/activity.py (100%) rename {budget-app/backend => backend}/app/routers/admin.py (100%) rename {budget-app/backend => backend}/app/routers/auth.py (100%) rename {budget-app/backend => backend}/app/routers/mail.py (100%) rename {budget-app/backend => backend}/app/routers/reports.py (100%) rename {budget-app/backend => backend}/app/routers/years.py (100%) rename {budget-app/backend => backend}/app/security.py (100%) rename {budget-app/backend => backend}/app/seed.py (100%) rename {budget-app/backend => backend}/app/serializers.py (100%) rename {budget-app/backend => backend}/app/summary.py (100%) rename {budget-app/backend => backend}/app/template.py (100%) rename {budget-app/backend => backend}/docker-entrypoint.sh (100%) rename {budget-app/backend => backend}/requirements.txt (100%) rename {budget-app/backend => backend}/tests/conftest.py (100%) rename {budget-app/backend => backend}/tests/fixtures/budget_2027.xlsx (100%) rename {budget-app/backend => backend}/tests/test_api.py (100%) rename {budget-app/backend => backend}/tests/test_calc.py (100%) rename {budget-app/backend => backend}/tests/test_departments.py (100%) rename {budget-app/backend => backend}/tests/test_mail.py (100%) rename {budget-app/backend => backend}/tests/test_months.py (100%) rename {budget-app/backend => backend}/tests/test_v5.py (100%) rename {budget-app/backend => backend}/tests/test_v6.py (100%) rename {budget-app/backend => backend}/tests/test_v7.py (100%) rename {budget-app/backup => backup}/backup.sh (100%) rename {budget-app/backup => backup}/restore.sh (100%) delete mode 100644 budget-app/README.md rename budget-app/docker-compose.yml => docker-compose.yml (100%) rename {budget-app/docs => docs}/guide.html (100%) rename {budget-app/frontend => frontend}/.dockerignore (100%) rename {budget-app/frontend => frontend}/Caddyfile (100%) rename {budget-app/frontend => frontend}/Dockerfile (100%) rename {budget-app/frontend => frontend}/index.html (100%) rename {budget-app/frontend => frontend}/package-lock.json (100%) rename {budget-app/frontend => frontend}/package.json (100%) rename {budget-app/frontend => frontend}/src/App.jsx (100%) rename {budget-app/frontend => frontend}/src/api.js (100%) rename {budget-app/frontend => frontend}/src/auth.js (100%) rename {budget-app/frontend => frontend}/src/components/EmailDialog.jsx (100%) rename {budget-app/frontend => frontend}/src/components/Layout.jsx (100%) rename {budget-app/frontend => frontend}/src/components/ui.jsx (100%) rename {budget-app/frontend => frontend}/src/format.js (100%) rename {budget-app/frontend => frontend}/src/i18n.js (100%) rename {budget-app/frontend => frontend}/src/main.jsx (100%) rename {budget-app/frontend => frontend}/src/pages/Activity.jsx (100%) rename {budget-app/frontend => frontend}/src/pages/Departments.jsx (100%) rename {budget-app/frontend => frontend}/src/pages/Login.jsx (100%) rename {budget-app/frontend => frontend}/src/pages/Mail.jsx (100%) rename {budget-app/frontend => frontend}/src/pages/Profile.jsx (100%) rename {budget-app/frontend => frontend}/src/pages/Reports.jsx (100%) rename {budget-app/frontend => frontend}/src/pages/Settings.jsx (100%) rename {budget-app/frontend => frontend}/src/pages/Users.jsx (100%) rename {budget-app/frontend => frontend}/src/pages/YearEditor.jsx (100%) rename {budget-app/frontend => frontend}/src/pages/Years.jsx (100%) rename {budget-app/frontend => frontend}/src/styles.css (100%) rename {budget-app/frontend => frontend}/vite.config.js (100%) diff --git a/budget-app/.env.example b/.env.example similarity index 100% rename from budget-app/.env.example rename to .env.example diff --git a/budget-app/.gitignore b/.gitignore similarity index 100% rename from budget-app/.gitignore rename to .gitignore diff --git a/budget-app/CHANGELOG.md b/CHANGELOG.md similarity index 100% rename from budget-app/CHANGELOG.md rename to CHANGELOG.md diff --git a/README.md b/README.md index ce2e817..1736550 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,185 @@ -# Budget_Tool_Martinhal +# Budget & Capex -Budget & Capex app guides and app versions \ No newline at end of file +A self-hosted web app for building yearly department budgets and the "Capex e Custos" cost report. +Users enter cost lines in the browser; the app produces the monthly budget (like the *Budget IT* +sheet) and the cost report (like *Capex_e_Custos_2027*), online and as PDF and Excel. +The interface and the reports are available in Portuguese and English. + +## What's inside + +| Container | Role | +| --- | --- | +| `web` | Caddy: serves the web app and forwards `/api` to the API. Optional HTTPS. | +| `api` | Python (FastAPI): business rules, Excel import, PDF and Excel exports. | +| `db` | PostgreSQL 16. All data lives in the `db-data` Docker volume. | +| `backup` | Dumps the database every night to `./backups` and keeps 30 days. | + +## Install + +You need a server on your network with Docker Engine and the Docker Compose plugin. + +```sh +# 1. copy this folder to the server, then inside it: +cp .env.example .env +# 2. edit .env: set POSTGRES_PASSWORD, SECRET_KEY (openssl rand -hex 32) and ADMIN_PASSWORD +# 3. build and start +docker compose up -d --build +``` + +Open `http://:8080` and sign in with `ADMIN_EMAIL` / `ADMIN_PASSWORD` from `.env`. +Change the password under **My profile** right away. The admin account is created only on the first start; +changing `.env` later does not change it. + +## First steps + +1. **Departments**: the app starts with an **IT** department. Add the others (F&B, Rooms, …); each new + department starts from the default chart of accounts, from no categories, or from a copy of another + department's categories. +2. **Budgets → Import Excel**: choose the department and upload `Budget para 2027.xlsx` (a copy is in + `backend/tests/fixtures/`) or a filled-in template (see below). + The import reads the *Sheet1* and *Budget IT* sheets: 54 lines, the 5.5% rate, every multiplier + (including the ×2.055 and ×3.055 hardware increases) and the remarks. Formulas such as `=5512*12` + are kept as typed calculations. +3. **Users → New user** for each colleague, with their department and role: + * **Viewer**: view and export every budget. + * **Upper Management**: view, download and email the budgets and reports of every department, switching + between departments; changes nothing. A department is optional. + * **Editor**: edit their own department's draft budgets and submit them for review. + * **Approver**: also approve their department's budgets and manage its categories. + * **Administrator**: everything, including departments, workflows, users and sites. + + Everyone can view every department's budgets and reports; only the budget's own department can + change it, and only the people named in its workflow can approve it. + Each user can turn on **MFA** in *My profile* (authenticator app + recovery codes). If someone loses + their phone, an administrator opens *Users → Edit* and presses **Reset MFA**. MFA secrets are encrypted + with `SECRET_KEY`: keep that value when you upgrade, or everyone's MFA has to be reset. +4. **Reports**: pick the year, language and optionally one site, then *View online*, *PDF* or *Excel*. + +## Entering data + +* Each line has a **prior-year actual**, a **base** for the new year and an **adjustment**: + *year rate* (the "Inflation + adjustments" % on the budget page), a custom *%*, a *factor ×*, + or *none*. The adjusted value is rounded to cents per line. +* Amount fields accept calculations, as in Excel: `12*839,67+3292,56` or `=5512*12`. + Both `1 234,56` and `1,234.56` are understood. +* Months are **split over 12** (December absorbs the rounding cents), **split over chosen months** + (tick for example Jan to Apr: the adjusted amount is divided equally over those months and the rest + stay at zero), or typed one by one (**monthly amounts**), as the revenue lines are. +* Lines can belong to one **site** (Sagres, Quinta, Chiado, Oriente) or to the **group**. Filter any page + or report by site. +* **New year** can copy every line from an earlier year: last year's adjusted budget becomes the + new year's actual and base, which you then replace with real figures. + +## Approval workflow + +Each department has its own workflow, set by an administrator under **Departments**: an ordered list of +steps, each approved either by *the department's approvers* or by *selected users* (who can be in any +department, e.g. finance). With no steps set, one approval by the department's approvers is needed. + +`Draft → step 1 → step 2 → … → Approved`. Only drafts can be edited. An editor submits; whoever may +approve the current step approves (moving it to the next step) or returns it to draft; an approver of +the department or an administrator can reopen an approved budget. Every change (who, when, old and new +value) is in **History** on the budget page. + +## Reopening an approved budget + +Anyone can press **Request reopening** on an approved budget and must say why. The department's approvers +and the administrators see the request on the budget (and get an email when email is set up), and +**Accept and reopen** it (it goes back to draft) or **Decline** it, giving a reason. Approvers can also +reopen directly, again with a reason. All requests stay in the budget's **History**. + +## Download, change and upload again + +On a budget page, **Download → Budget file** gives the budget as an editable Excel file (the template +filled with its lines). Change it and upload it with **Upload Excel** on the same page (or Budgets → +Import Excel). Because that budget already exists, the app asks first. On OK, the current version is +kept but renamed with the date and time, for example `Budget 2027 (2026-10-28 23:00)` and +`Orcamento_IT_2027_2026-10-28_2300.xlsx`, and locked so it can't be edited; the uploaded file becomes +the current budget. Only drafts can be replaced; an approved budget must be reopened first. Archived +versions are under **Budgets → Show archived versions**. + +## Excel template + +**Budgets → Excel template** downloads a workbook for a department and year, in Portuguese or English, +blank or pre-filled with the lines of an existing budget. It has an instructions sheet and a *Lines* +sheet with drop-down lists for category, site and adjustment type. Fill it in and upload it with +**Import Excel**; unknown categories are created in the department and anything skipped is listed. + +## Email + +Administrators set up the sending account under **Email → Sending account**: the SMTP server, port and +security (STARTTLS on 587 or SSL/TLS on 465), the login, the sender address and name, and an optional +signature. With **Save a copy in the Sent folder** on, every message is also stored over IMAP in the +mailbox's Sent folder (found automatically, or named by hand), just like a message sent from Outlook. +Gmail already keeps that copy for SMTP, so turn the option off there to avoid duplicates. For Gmail and +Microsoft 365, use an app password or an account with SMTP sign-in allowed. **Test connection** signs in +without sending; **Send test email** sends a short message. + +Then every user gets **Send by email** on the Reports page: choose recipients, subject, message and the +reports to attach (PDF and/or Excel, in the language and site chosen on the page). + +Passwords are stored encrypted with `SECRET_KEY`. If you change `SECRET_KEY`, enter them again. + +## Logs + +- **Activity** (administrators): who did what and when: sign-ins and failed sign-ins, budgets and + lines created, changed, imported, submitted, approved and deleted, reports viewed, downloaded and + emailed, and admin changes. Filter by user, area, action, date or text, and export to CSV. +- **Email → Mail log** (administrators): every message sent: who sent it, to whom, the subject, + message and attachments, whether it was delivered to the server, and whether the copy reached the + Sent folder. Also exportable to CSV. + +Only administrators can delete a budget (in any status), from the budget's page. It asks you to type the +year to confirm; the deletion stays in the activity log. + +## Backups and restore + +Backups are written to `BACKUP_DIR` (default `./backups`) at `BACKUP_TIME` every night and once at start. + +```sh +docker compose exec backup ls /backups +docker compose exec backup restore.sh /backups/budget_20270101_023000.sql.gz +docker compose restart api +``` + +Copy the `backups` folder to another machine or a NAS regularly; a backup on the same disk does not +protect against losing that disk. + +## Updating + +Your data lives in Docker volumes, not in this folder, so replacing the folder does not touch it. + +1. Replace the folder contents with the new version, keeping your `.env` and `backups` folder. +2. Run `docker compose up -d --build`. + +Before the API applies database changes (migrations), it asks the backup container for a fresh dump +(`backups/budget_..._before_upgrade.sql.gz`) and waits for it; if the backup fails, the upgrade does not +run. So there is always a backup from just before an upgrade. +Upgrading from version 1 moves all existing budgets, categories and users into the **IT** department. +Never run `docker compose down -v`: `-v` deletes the data volumes. + +## HTTPS (optional) + +Plain HTTP on port 8080 is the default, which is fine on a trusted network. For HTTPS, set in `.env`: + +``` +SITE_ADDRESS=budget.yourdomain.local +HTTP_PORT=80 +HTTPS_PORT=443 +COOKIE_SECURE=true +``` + +and make that name resolve to the server. Caddy issues a certificate from its own local authority for +internal names. Browsers will warn until you install its root certificate on each PC: +`docker compose cp web:/data/caddy/pki/authorities/local/root.crt caddy-root.crt`. +With a public domain pointing at the server, Caddy gets a Let's Encrypt certificate instead. + +## Development + +```sh +cd backend && pip install -r requirements.txt pytest httpx && python -m pytest tests # 44 tests +uvicorn app.main:app --reload # API on :8000 (SQLite) +cd frontend && npm install && npm run dev # web on :5173 +``` + +API documentation is at `/api/docs` when the app is running. diff --git a/budget-app/VERSION b/VERSION similarity index 100% rename from budget-app/VERSION rename to VERSION diff --git a/budget-app/backend/.dockerignore b/backend/.dockerignore similarity index 100% rename from budget-app/backend/.dockerignore rename to backend/.dockerignore diff --git a/budget-app/backend/Dockerfile b/backend/Dockerfile similarity index 100% rename from budget-app/backend/Dockerfile rename to backend/Dockerfile diff --git a/budget-app/backend/alembic.ini b/backend/alembic.ini similarity index 100% rename from budget-app/backend/alembic.ini rename to backend/alembic.ini diff --git a/budget-app/backend/alembic/env.py b/backend/alembic/env.py similarity index 100% rename from budget-app/backend/alembic/env.py rename to backend/alembic/env.py diff --git a/budget-app/backend/alembic/script.py.mako b/backend/alembic/script.py.mako similarity index 100% rename from budget-app/backend/alembic/script.py.mako rename to backend/alembic/script.py.mako diff --git a/budget-app/backend/alembic/versions/0001_initial.py b/backend/alembic/versions/0001_initial.py similarity index 100% rename from budget-app/backend/alembic/versions/0001_initial.py rename to backend/alembic/versions/0001_initial.py diff --git a/budget-app/backend/alembic/versions/0002_departments.py b/backend/alembic/versions/0002_departments.py similarity index 100% rename from budget-app/backend/alembic/versions/0002_departments.py rename to backend/alembic/versions/0002_departments.py diff --git a/budget-app/backend/alembic/versions/0003_mail.py b/backend/alembic/versions/0003_mail.py similarity index 100% rename from budget-app/backend/alembic/versions/0003_mail.py rename to backend/alembic/versions/0003_mail.py diff --git a/budget-app/backend/alembic/versions/0004_selected_months.py b/backend/alembic/versions/0004_selected_months.py similarity index 100% rename from budget-app/backend/alembic/versions/0004_selected_months.py rename to backend/alembic/versions/0004_selected_months.py diff --git a/budget-app/backend/alembic/versions/0005_reopen_archive.py b/backend/alembic/versions/0005_reopen_archive.py similarity index 100% rename from budget-app/backend/alembic/versions/0005_reopen_archive.py rename to backend/alembic/versions/0005_reopen_archive.py diff --git a/budget-app/backend/alembic/versions/0006_upper_management.py b/backend/alembic/versions/0006_upper_management.py similarity index 100% rename from budget-app/backend/alembic/versions/0006_upper_management.py rename to backend/alembic/versions/0006_upper_management.py diff --git a/budget-app/backend/alembic/versions/0007_mfa_avatar_theme.py b/backend/alembic/versions/0007_mfa_avatar_theme.py similarity index 100% rename from budget-app/backend/alembic/versions/0007_mfa_avatar_theme.py rename to backend/alembic/versions/0007_mfa_avatar_theme.py diff --git a/budget-app/backend/app/__init__.py b/backend/app/__init__.py similarity index 100% rename from budget-app/backend/app/__init__.py rename to backend/app/__init__.py diff --git a/budget-app/backend/app/audit.py b/backend/app/audit.py similarity index 100% rename from budget-app/backend/app/audit.py rename to backend/app/audit.py diff --git a/budget-app/backend/app/bootstrap.py b/backend/app/bootstrap.py similarity index 100% rename from budget-app/backend/app/bootstrap.py rename to backend/app/bootstrap.py diff --git a/budget-app/backend/app/calc.py b/backend/app/calc.py similarity index 100% rename from budget-app/backend/app/calc.py rename to backend/app/calc.py diff --git a/budget-app/backend/app/config.py b/backend/app/config.py similarity index 100% rename from budget-app/backend/app/config.py rename to backend/app/config.py diff --git a/budget-app/backend/app/db.py b/backend/app/db.py similarity index 100% rename from budget-app/backend/app/db.py rename to backend/app/db.py diff --git a/budget-app/backend/app/importer.py b/backend/app/importer.py similarity index 100% rename from budget-app/backend/app/importer.py rename to backend/app/importer.py diff --git a/budget-app/backend/app/mailer.py b/backend/app/mailer.py similarity index 100% rename from budget-app/backend/app/mailer.py rename to backend/app/mailer.py diff --git a/budget-app/backend/app/main.py b/backend/app/main.py similarity index 100% rename from budget-app/backend/app/main.py rename to backend/app/main.py diff --git a/budget-app/backend/app/mfa.py b/backend/app/mfa.py similarity index 100% rename from budget-app/backend/app/mfa.py rename to backend/app/mfa.py diff --git a/budget-app/backend/app/models.py b/backend/app/models.py similarity index 100% rename from budget-app/backend/app/models.py rename to backend/app/models.py diff --git a/budget-app/backend/app/notify.py b/backend/app/notify.py similarity index 100% rename from budget-app/backend/app/notify.py rename to backend/app/notify.py diff --git a/budget-app/backend/app/permissions.py b/backend/app/permissions.py similarity index 100% rename from budget-app/backend/app/permissions.py rename to backend/app/permissions.py diff --git a/budget-app/backend/app/preupgrade.py b/backend/app/preupgrade.py similarity index 100% rename from budget-app/backend/app/preupgrade.py rename to backend/app/preupgrade.py diff --git a/budget-app/backend/app/reports/__init__.py b/backend/app/reports/__init__.py similarity index 100% rename from budget-app/backend/app/reports/__init__.py rename to backend/app/reports/__init__.py diff --git a/budget-app/backend/app/reports/charts.py b/backend/app/reports/charts.py similarity index 100% rename from budget-app/backend/app/reports/charts.py rename to backend/app/reports/charts.py diff --git a/budget-app/backend/app/reports/excel.py b/backend/app/reports/excel.py similarity index 100% rename from budget-app/backend/app/reports/excel.py rename to backend/app/reports/excel.py diff --git a/budget-app/backend/app/reports/i18n.py b/backend/app/reports/i18n.py similarity index 100% rename from budget-app/backend/app/reports/i18n.py rename to backend/app/reports/i18n.py diff --git a/budget-app/backend/app/reports/render.py b/backend/app/reports/render.py similarity index 100% rename from budget-app/backend/app/reports/render.py rename to backend/app/reports/render.py diff --git a/budget-app/backend/app/reports/templates/budget.html b/backend/app/reports/templates/budget.html similarity index 100% rename from budget-app/backend/app/reports/templates/budget.html rename to backend/app/reports/templates/budget.html diff --git a/budget-app/backend/app/reports/templates/cost_report.html b/backend/app/reports/templates/cost_report.html similarity index 100% rename from budget-app/backend/app/reports/templates/cost_report.html rename to backend/app/reports/templates/cost_report.html diff --git a/budget-app/backend/app/reports/templates/report.css b/backend/app/reports/templates/report.css similarity index 100% rename from budget-app/backend/app/reports/templates/report.css rename to backend/app/reports/templates/report.css diff --git a/budget-app/backend/app/routers/__init__.py b/backend/app/routers/__init__.py similarity index 100% rename from budget-app/backend/app/routers/__init__.py rename to backend/app/routers/__init__.py diff --git a/budget-app/backend/app/routers/activity.py b/backend/app/routers/activity.py similarity index 100% rename from budget-app/backend/app/routers/activity.py rename to backend/app/routers/activity.py diff --git a/budget-app/backend/app/routers/admin.py b/backend/app/routers/admin.py similarity index 100% rename from budget-app/backend/app/routers/admin.py rename to backend/app/routers/admin.py diff --git a/budget-app/backend/app/routers/auth.py b/backend/app/routers/auth.py similarity index 100% rename from budget-app/backend/app/routers/auth.py rename to backend/app/routers/auth.py diff --git a/budget-app/backend/app/routers/mail.py b/backend/app/routers/mail.py similarity index 100% rename from budget-app/backend/app/routers/mail.py rename to backend/app/routers/mail.py diff --git a/budget-app/backend/app/routers/reports.py b/backend/app/routers/reports.py similarity index 100% rename from budget-app/backend/app/routers/reports.py rename to backend/app/routers/reports.py diff --git a/budget-app/backend/app/routers/years.py b/backend/app/routers/years.py similarity index 100% rename from budget-app/backend/app/routers/years.py rename to backend/app/routers/years.py diff --git a/budget-app/backend/app/security.py b/backend/app/security.py similarity index 100% rename from budget-app/backend/app/security.py rename to backend/app/security.py diff --git a/budget-app/backend/app/seed.py b/backend/app/seed.py similarity index 100% rename from budget-app/backend/app/seed.py rename to backend/app/seed.py diff --git a/budget-app/backend/app/serializers.py b/backend/app/serializers.py similarity index 100% rename from budget-app/backend/app/serializers.py rename to backend/app/serializers.py diff --git a/budget-app/backend/app/summary.py b/backend/app/summary.py similarity index 100% rename from budget-app/backend/app/summary.py rename to backend/app/summary.py diff --git a/budget-app/backend/app/template.py b/backend/app/template.py similarity index 100% rename from budget-app/backend/app/template.py rename to backend/app/template.py diff --git a/budget-app/backend/docker-entrypoint.sh b/backend/docker-entrypoint.sh similarity index 100% rename from budget-app/backend/docker-entrypoint.sh rename to backend/docker-entrypoint.sh diff --git a/budget-app/backend/requirements.txt b/backend/requirements.txt similarity index 100% rename from budget-app/backend/requirements.txt rename to backend/requirements.txt diff --git a/budget-app/backend/tests/conftest.py b/backend/tests/conftest.py similarity index 100% rename from budget-app/backend/tests/conftest.py rename to backend/tests/conftest.py diff --git a/budget-app/backend/tests/fixtures/budget_2027.xlsx b/backend/tests/fixtures/budget_2027.xlsx similarity index 100% rename from budget-app/backend/tests/fixtures/budget_2027.xlsx rename to backend/tests/fixtures/budget_2027.xlsx diff --git a/budget-app/backend/tests/test_api.py b/backend/tests/test_api.py similarity index 100% rename from budget-app/backend/tests/test_api.py rename to backend/tests/test_api.py diff --git a/budget-app/backend/tests/test_calc.py b/backend/tests/test_calc.py similarity index 100% rename from budget-app/backend/tests/test_calc.py rename to backend/tests/test_calc.py diff --git a/budget-app/backend/tests/test_departments.py b/backend/tests/test_departments.py similarity index 100% rename from budget-app/backend/tests/test_departments.py rename to backend/tests/test_departments.py diff --git a/budget-app/backend/tests/test_mail.py b/backend/tests/test_mail.py similarity index 100% rename from budget-app/backend/tests/test_mail.py rename to backend/tests/test_mail.py diff --git a/budget-app/backend/tests/test_months.py b/backend/tests/test_months.py similarity index 100% rename from budget-app/backend/tests/test_months.py rename to backend/tests/test_months.py diff --git a/budget-app/backend/tests/test_v5.py b/backend/tests/test_v5.py similarity index 100% rename from budget-app/backend/tests/test_v5.py rename to backend/tests/test_v5.py diff --git a/budget-app/backend/tests/test_v6.py b/backend/tests/test_v6.py similarity index 100% rename from budget-app/backend/tests/test_v6.py rename to backend/tests/test_v6.py diff --git a/budget-app/backend/tests/test_v7.py b/backend/tests/test_v7.py similarity index 100% rename from budget-app/backend/tests/test_v7.py rename to backend/tests/test_v7.py diff --git a/budget-app/backup/backup.sh b/backup/backup.sh similarity index 100% rename from budget-app/backup/backup.sh rename to backup/backup.sh diff --git a/budget-app/backup/restore.sh b/backup/restore.sh similarity index 100% rename from budget-app/backup/restore.sh rename to backup/restore.sh diff --git a/budget-app/README.md b/budget-app/README.md deleted file mode 100644 index 1736550..0000000 --- a/budget-app/README.md +++ /dev/null @@ -1,185 +0,0 @@ -# Budget & Capex - -A self-hosted web app for building yearly department budgets and the "Capex e Custos" cost report. -Users enter cost lines in the browser; the app produces the monthly budget (like the *Budget IT* -sheet) and the cost report (like *Capex_e_Custos_2027*), online and as PDF and Excel. -The interface and the reports are available in Portuguese and English. - -## What's inside - -| Container | Role | -| --- | --- | -| `web` | Caddy: serves the web app and forwards `/api` to the API. Optional HTTPS. | -| `api` | Python (FastAPI): business rules, Excel import, PDF and Excel exports. | -| `db` | PostgreSQL 16. All data lives in the `db-data` Docker volume. | -| `backup` | Dumps the database every night to `./backups` and keeps 30 days. | - -## Install - -You need a server on your network with Docker Engine and the Docker Compose plugin. - -```sh -# 1. copy this folder to the server, then inside it: -cp .env.example .env -# 2. edit .env: set POSTGRES_PASSWORD, SECRET_KEY (openssl rand -hex 32) and ADMIN_PASSWORD -# 3. build and start -docker compose up -d --build -``` - -Open `http://:8080` and sign in with `ADMIN_EMAIL` / `ADMIN_PASSWORD` from `.env`. -Change the password under **My profile** right away. The admin account is created only on the first start; -changing `.env` later does not change it. - -## First steps - -1. **Departments**: the app starts with an **IT** department. Add the others (F&B, Rooms, …); each new - department starts from the default chart of accounts, from no categories, or from a copy of another - department's categories. -2. **Budgets → Import Excel**: choose the department and upload `Budget para 2027.xlsx` (a copy is in - `backend/tests/fixtures/`) or a filled-in template (see below). - The import reads the *Sheet1* and *Budget IT* sheets: 54 lines, the 5.5% rate, every multiplier - (including the ×2.055 and ×3.055 hardware increases) and the remarks. Formulas such as `=5512*12` - are kept as typed calculations. -3. **Users → New user** for each colleague, with their department and role: - * **Viewer**: view and export every budget. - * **Upper Management**: view, download and email the budgets and reports of every department, switching - between departments; changes nothing. A department is optional. - * **Editor**: edit their own department's draft budgets and submit them for review. - * **Approver**: also approve their department's budgets and manage its categories. - * **Administrator**: everything, including departments, workflows, users and sites. - - Everyone can view every department's budgets and reports; only the budget's own department can - change it, and only the people named in its workflow can approve it. - Each user can turn on **MFA** in *My profile* (authenticator app + recovery codes). If someone loses - their phone, an administrator opens *Users → Edit* and presses **Reset MFA**. MFA secrets are encrypted - with `SECRET_KEY`: keep that value when you upgrade, or everyone's MFA has to be reset. -4. **Reports**: pick the year, language and optionally one site, then *View online*, *PDF* or *Excel*. - -## Entering data - -* Each line has a **prior-year actual**, a **base** for the new year and an **adjustment**: - *year rate* (the "Inflation + adjustments" % on the budget page), a custom *%*, a *factor ×*, - or *none*. The adjusted value is rounded to cents per line. -* Amount fields accept calculations, as in Excel: `12*839,67+3292,56` or `=5512*12`. - Both `1 234,56` and `1,234.56` are understood. -* Months are **split over 12** (December absorbs the rounding cents), **split over chosen months** - (tick for example Jan to Apr: the adjusted amount is divided equally over those months and the rest - stay at zero), or typed one by one (**monthly amounts**), as the revenue lines are. -* Lines can belong to one **site** (Sagres, Quinta, Chiado, Oriente) or to the **group**. Filter any page - or report by site. -* **New year** can copy every line from an earlier year: last year's adjusted budget becomes the - new year's actual and base, which you then replace with real figures. - -## Approval workflow - -Each department has its own workflow, set by an administrator under **Departments**: an ordered list of -steps, each approved either by *the department's approvers* or by *selected users* (who can be in any -department, e.g. finance). With no steps set, one approval by the department's approvers is needed. - -`Draft → step 1 → step 2 → … → Approved`. Only drafts can be edited. An editor submits; whoever may -approve the current step approves (moving it to the next step) or returns it to draft; an approver of -the department or an administrator can reopen an approved budget. Every change (who, when, old and new -value) is in **History** on the budget page. - -## Reopening an approved budget - -Anyone can press **Request reopening** on an approved budget and must say why. The department's approvers -and the administrators see the request on the budget (and get an email when email is set up), and -**Accept and reopen** it (it goes back to draft) or **Decline** it, giving a reason. Approvers can also -reopen directly, again with a reason. All requests stay in the budget's **History**. - -## Download, change and upload again - -On a budget page, **Download → Budget file** gives the budget as an editable Excel file (the template -filled with its lines). Change it and upload it with **Upload Excel** on the same page (or Budgets → -Import Excel). Because that budget already exists, the app asks first. On OK, the current version is -kept but renamed with the date and time, for example `Budget 2027 (2026-10-28 23:00)` and -`Orcamento_IT_2027_2026-10-28_2300.xlsx`, and locked so it can't be edited; the uploaded file becomes -the current budget. Only drafts can be replaced; an approved budget must be reopened first. Archived -versions are under **Budgets → Show archived versions**. - -## Excel template - -**Budgets → Excel template** downloads a workbook for a department and year, in Portuguese or English, -blank or pre-filled with the lines of an existing budget. It has an instructions sheet and a *Lines* -sheet with drop-down lists for category, site and adjustment type. Fill it in and upload it with -**Import Excel**; unknown categories are created in the department and anything skipped is listed. - -## Email - -Administrators set up the sending account under **Email → Sending account**: the SMTP server, port and -security (STARTTLS on 587 or SSL/TLS on 465), the login, the sender address and name, and an optional -signature. With **Save a copy in the Sent folder** on, every message is also stored over IMAP in the -mailbox's Sent folder (found automatically, or named by hand), just like a message sent from Outlook. -Gmail already keeps that copy for SMTP, so turn the option off there to avoid duplicates. For Gmail and -Microsoft 365, use an app password or an account with SMTP sign-in allowed. **Test connection** signs in -without sending; **Send test email** sends a short message. - -Then every user gets **Send by email** on the Reports page: choose recipients, subject, message and the -reports to attach (PDF and/or Excel, in the language and site chosen on the page). - -Passwords are stored encrypted with `SECRET_KEY`. If you change `SECRET_KEY`, enter them again. - -## Logs - -- **Activity** (administrators): who did what and when: sign-ins and failed sign-ins, budgets and - lines created, changed, imported, submitted, approved and deleted, reports viewed, downloaded and - emailed, and admin changes. Filter by user, area, action, date or text, and export to CSV. -- **Email → Mail log** (administrators): every message sent: who sent it, to whom, the subject, - message and attachments, whether it was delivered to the server, and whether the copy reached the - Sent folder. Also exportable to CSV. - -Only administrators can delete a budget (in any status), from the budget's page. It asks you to type the -year to confirm; the deletion stays in the activity log. - -## Backups and restore - -Backups are written to `BACKUP_DIR` (default `./backups`) at `BACKUP_TIME` every night and once at start. - -```sh -docker compose exec backup ls /backups -docker compose exec backup restore.sh /backups/budget_20270101_023000.sql.gz -docker compose restart api -``` - -Copy the `backups` folder to another machine or a NAS regularly; a backup on the same disk does not -protect against losing that disk. - -## Updating - -Your data lives in Docker volumes, not in this folder, so replacing the folder does not touch it. - -1. Replace the folder contents with the new version, keeping your `.env` and `backups` folder. -2. Run `docker compose up -d --build`. - -Before the API applies database changes (migrations), it asks the backup container for a fresh dump -(`backups/budget_..._before_upgrade.sql.gz`) and waits for it; if the backup fails, the upgrade does not -run. So there is always a backup from just before an upgrade. -Upgrading from version 1 moves all existing budgets, categories and users into the **IT** department. -Never run `docker compose down -v`: `-v` deletes the data volumes. - -## HTTPS (optional) - -Plain HTTP on port 8080 is the default, which is fine on a trusted network. For HTTPS, set in `.env`: - -``` -SITE_ADDRESS=budget.yourdomain.local -HTTP_PORT=80 -HTTPS_PORT=443 -COOKIE_SECURE=true -``` - -and make that name resolve to the server. Caddy issues a certificate from its own local authority for -internal names. Browsers will warn until you install its root certificate on each PC: -`docker compose cp web:/data/caddy/pki/authorities/local/root.crt caddy-root.crt`. -With a public domain pointing at the server, Caddy gets a Let's Encrypt certificate instead. - -## Development - -```sh -cd backend && pip install -r requirements.txt pytest httpx && python -m pytest tests # 44 tests -uvicorn app.main:app --reload # API on :8000 (SQLite) -cd frontend && npm install && npm run dev # web on :5173 -``` - -API documentation is at `/api/docs` when the app is running. diff --git a/budget-app/docker-compose.yml b/docker-compose.yml similarity index 100% rename from budget-app/docker-compose.yml rename to docker-compose.yml diff --git a/budget-app/docs/guide.html b/docs/guide.html similarity index 100% rename from budget-app/docs/guide.html rename to docs/guide.html diff --git a/budget-app/frontend/.dockerignore b/frontend/.dockerignore similarity index 100% rename from budget-app/frontend/.dockerignore rename to frontend/.dockerignore diff --git a/budget-app/frontend/Caddyfile b/frontend/Caddyfile similarity index 100% rename from budget-app/frontend/Caddyfile rename to frontend/Caddyfile diff --git a/budget-app/frontend/Dockerfile b/frontend/Dockerfile similarity index 100% rename from budget-app/frontend/Dockerfile rename to frontend/Dockerfile diff --git a/budget-app/frontend/index.html b/frontend/index.html similarity index 100% rename from budget-app/frontend/index.html rename to frontend/index.html diff --git a/budget-app/frontend/package-lock.json b/frontend/package-lock.json similarity index 100% rename from budget-app/frontend/package-lock.json rename to frontend/package-lock.json diff --git a/budget-app/frontend/package.json b/frontend/package.json similarity index 100% rename from budget-app/frontend/package.json rename to frontend/package.json diff --git a/budget-app/frontend/src/App.jsx b/frontend/src/App.jsx similarity index 100% rename from budget-app/frontend/src/App.jsx rename to frontend/src/App.jsx diff --git a/budget-app/frontend/src/api.js b/frontend/src/api.js similarity index 100% rename from budget-app/frontend/src/api.js rename to frontend/src/api.js diff --git a/budget-app/frontend/src/auth.js b/frontend/src/auth.js similarity index 100% rename from budget-app/frontend/src/auth.js rename to frontend/src/auth.js diff --git a/budget-app/frontend/src/components/EmailDialog.jsx b/frontend/src/components/EmailDialog.jsx similarity index 100% rename from budget-app/frontend/src/components/EmailDialog.jsx rename to frontend/src/components/EmailDialog.jsx diff --git a/budget-app/frontend/src/components/Layout.jsx b/frontend/src/components/Layout.jsx similarity index 100% rename from budget-app/frontend/src/components/Layout.jsx rename to frontend/src/components/Layout.jsx diff --git a/budget-app/frontend/src/components/ui.jsx b/frontend/src/components/ui.jsx similarity index 100% rename from budget-app/frontend/src/components/ui.jsx rename to frontend/src/components/ui.jsx diff --git a/budget-app/frontend/src/format.js b/frontend/src/format.js similarity index 100% rename from budget-app/frontend/src/format.js rename to frontend/src/format.js diff --git a/budget-app/frontend/src/i18n.js b/frontend/src/i18n.js similarity index 100% rename from budget-app/frontend/src/i18n.js rename to frontend/src/i18n.js diff --git a/budget-app/frontend/src/main.jsx b/frontend/src/main.jsx similarity index 100% rename from budget-app/frontend/src/main.jsx rename to frontend/src/main.jsx diff --git a/budget-app/frontend/src/pages/Activity.jsx b/frontend/src/pages/Activity.jsx similarity index 100% rename from budget-app/frontend/src/pages/Activity.jsx rename to frontend/src/pages/Activity.jsx diff --git a/budget-app/frontend/src/pages/Departments.jsx b/frontend/src/pages/Departments.jsx similarity index 100% rename from budget-app/frontend/src/pages/Departments.jsx rename to frontend/src/pages/Departments.jsx diff --git a/budget-app/frontend/src/pages/Login.jsx b/frontend/src/pages/Login.jsx similarity index 100% rename from budget-app/frontend/src/pages/Login.jsx rename to frontend/src/pages/Login.jsx diff --git a/budget-app/frontend/src/pages/Mail.jsx b/frontend/src/pages/Mail.jsx similarity index 100% rename from budget-app/frontend/src/pages/Mail.jsx rename to frontend/src/pages/Mail.jsx diff --git a/budget-app/frontend/src/pages/Profile.jsx b/frontend/src/pages/Profile.jsx similarity index 100% rename from budget-app/frontend/src/pages/Profile.jsx rename to frontend/src/pages/Profile.jsx diff --git a/budget-app/frontend/src/pages/Reports.jsx b/frontend/src/pages/Reports.jsx similarity index 100% rename from budget-app/frontend/src/pages/Reports.jsx rename to frontend/src/pages/Reports.jsx diff --git a/budget-app/frontend/src/pages/Settings.jsx b/frontend/src/pages/Settings.jsx similarity index 100% rename from budget-app/frontend/src/pages/Settings.jsx rename to frontend/src/pages/Settings.jsx diff --git a/budget-app/frontend/src/pages/Users.jsx b/frontend/src/pages/Users.jsx similarity index 100% rename from budget-app/frontend/src/pages/Users.jsx rename to frontend/src/pages/Users.jsx diff --git a/budget-app/frontend/src/pages/YearEditor.jsx b/frontend/src/pages/YearEditor.jsx similarity index 100% rename from budget-app/frontend/src/pages/YearEditor.jsx rename to frontend/src/pages/YearEditor.jsx diff --git a/budget-app/frontend/src/pages/Years.jsx b/frontend/src/pages/Years.jsx similarity index 100% rename from budget-app/frontend/src/pages/Years.jsx rename to frontend/src/pages/Years.jsx diff --git a/budget-app/frontend/src/styles.css b/frontend/src/styles.css similarity index 100% rename from budget-app/frontend/src/styles.css rename to frontend/src/styles.css diff --git a/budget-app/frontend/vite.config.js b/frontend/vite.config.js similarity index 100% rename from budget-app/frontend/vite.config.js rename to frontend/vite.config.js