Files
brandize_bi/AGENTS.md
T
2026-10-26 11:46:41 -03:00

139 lines
8.0 KiB
Markdown

# AGENTS.md
Guidance for agents working in this repository (Brandize · BI Dashboard).
## What this project is
A **flat, server-free browser dashboard** for the Brandize BI webhook. It renders
gross revenue (Brutto inkl. USt) per departure country plus a VAT summary, with KPI
cards, bar charts and detail tables. There is **no build step, no framework, no
package manager** — just static files opened directly in a browser.
Supporting tools: a bash dev-server launcher (`start`) and a Python CLI (`send`) that
uploads CSV rows to an n8n webhook. SQL scripts (`sql/`) define the upstream queries
that feed the webhook (Odoo 17 / PostgreSQL).
## Layout
| Path | Purpose |
| --- | --- |
| `index.html` | Dashboard markup: sidebar nav, shared filter bar, one `<main class="view">` per report, auth overlay |
| `styles.css` | All styling (CSS custom properties in `:root`, BEM-ish class names) |
| `app.js` | Everything: data loading, parsing, rendering, CSV export, auth, navigation |
| `README.md` | User-facing docs (German/English mixed). Update it when behavior changes |
| `start` | Bash: installs `live-server` if missing and serves the folder on :8080 |
| `send` | Python CLI: batches CSV rows to the `.../webhook/VAT` n8n webhook |
| `sql/*.sql` | Upstream Odoo/PostgreSQL queries (see below) |
| `data/` | Git-ignored. Empty in the repo; holds local CSV payloads for `send` |
## Running / validating
There is **no test suite and no linter**. Validation is manual:
- Open `index.html` directly (double-click) — the intended primary usage, works over `file://`.
- Or run `./start` and browse `http://localhost:8080` for live reload.
- `./send data/<file>.csv` requires Python 3 + `requests` and network access to the webhook.
Because `fetch` hits a remote webhook protected by HTTP Basic Auth, you generally
**cannot fully exercise the UI without valid credentials and network access**. Don't
claim validation passed unless you actually loaded the page and saw it work; otherwise
state clearly what you could not run.
## Architecture notes (`app.js`)
Single file, loaded as a plain `<script>` (not a module), `"use strict"` at the top.
No bundler — keep everything in global scope as the file already does.
- **Config constants** at the top: `API_URL`, `MONTHS` (German abbreviations), `PALETTE`.
- **`els`** — a single object caching all `getElementById`/`querySelector` lookups.
Add new DOM handles here rather than querying inline.
- **State**: `currentView`, `lastData` (`{ abgangsland, vat }`), `auth`.
- **Sections** are delimited by `/* ---------- Name ---------- */` banners: Auth,
Period selector, Formatting, Data, Rendering (per report), CSV export, Navigation, Init.
- **Views**: the `VIEWS` map defines title/subtitle per view id. `showView(name)`
toggles `.hidden` on the `<main class="view">` elements and re-renders. A new report
needs: markup section in `index.html`, an entry in `VIEWS`, a render function, and a
`viewEls()` branch.
- **Data flow**: `load()` → `POST API_URL` with `{ query, month }` → `extractArray(payload, key)`
→ `normalizeAbgangsland` / `normalizeVat` → `renderActiveView()`. `load()` queries
**only** the active dashboard's dataset; it is triggered from `showView()` (when a
dashboard is displayed) and from the refresh/period controls — never prefetched.
### Webhook / data contract
- The webhook is called with **POST** and a JSON body `{ "query": <key>, "month": "YYYY-MMM" }`
(quarter dashboards send `{ "query": <key>, "quarter": "YYYY-QN" }` instead).
`query` selects the dataset. The datasets are large, so **exactly one dataset is
requested per call**, for the dashboard currently shown (no prefetching).
- Query keys: `"Summe je Abgangsland"` (Umsatz je Abgangsland), `"VAT Summary"`,
`"ODOO Rechnungen pro Monat"`, `"Alle Rechnungen pro Monat"`, `"Quartal Schweiz"`,
`"FIFO Lager Bewertung"` and `"Produkt Materialkosten"`. Each view's `query` lives in
the `VIEWS` map, which maps the active dashboard to its `normalize` function;
`quarter: true` marks the dashboards that use a quarter period. The FIFO and
Produkt-Materialkosten responses are `[{ id, query, row_count, data: [...] }]`
(unwrapped in `normalizeFifo` / `normalizeMaterialkosten`). In *Produkt Materialkosten*,
`remaining_qty` is the remaining quantity of that cost bracket (the last cost bracket, not FIFO),
not the product total.
- `extractArray` accepts the dataset either as a bare array or wrapped under the
query key (case-insensitive). If the shape changes upstream, adjust the normalizers.
- The webhook returns **German-formatted strings**, e.g. `"90.717,93 €"` and
`"1.234"`. Parsing (`parseEuro`, `parseQuantity`) strips symbols and swaps
`.`/`,` — do **not** use `parseFloat` directly on the raw values.
- `month` uses `YYYY-MMM` (e.g. `2026-SEP`), matching `%(month)s` in `sql/*.sql`;
`quarter` uses `YYYY-QN` (e.g. `2026-Q3`), matching `%(quarter)s` in `sql/Quartal_Schweiz.sql`.
### Auth
HTTP Basic. The first request goes out without credentials; a `401` triggers the
login overlay. Credentials live in `sessionStorage` (best-effort — unavailable on
`file://`, where they stay in memory only) and are sent as `Authorization: Basic ...`.
## SQL scripts (`sql/`)
Odoo 17 / PostgreSQL against table `brandize_datacenter_amazon_vat_transaction`.
Each file has a header block documenting the model, parameters and invocation.
- Parameter is named `%(month)s` for psycopg2; in `psql` use `:'month'` instead
(documented in the headers — respect this distinction).
- Money is formatted **in SQL** to German notation via
`translate(to_char(...), ',.', '.,') || ' €'` so the frontend receives display-ready
strings. Keep formatting consistent if you add columns.
- `Delete_Activity_Period.sql` is destructive — note the "ACHTUNG" warning and the
transaction-bracket guidance. Be cautious editing it.
## Conventions
- **Language**: UI text is **German**, code identifiers and comments are English
(comments occasionally German). Keep new user-facing strings German.
- **Number/date formatting**: always `de-DE` via `toLocaleString` in JS and German
`to_char` formatting in SQL. Dates as `YYYY-MMM` with the `MONTHS` abbreviations.
- **JS style**: `"use strict"`, double quotes, semicolons, 2-space indent, `const`/`let`
(no `var`), named `function` declarations, `camelCase`. Nullish-safe (`??`, optional
guards). DOM built imperatively with `document.createElement` (charts are hand-rolled
bars/lists, no chart library).
- **CSS**: use the existing `:root` custom properties for colors/spacing/radius; reuse
existing class patterns rather than inventing new naming.
- **CSV export**: semicolon-delimited, UTF-8 **with BOM** (for Excel umlauts), CRLF line
endings, includes a `Gesamt` total row, filename `*_YYYY-MMM.csv`. Follow this pattern
for any new exportable view.
- Keep changes minimal and consistent with the existing file structure — this is a small,
intentionally dependency-free static app. Prefer no new dependencies.
## Gotchas
- `data/` is git-ignored; never commit contents from it. `send` expects CSVs with a
header row (`csv.DictReader`) and posts JSON arrays in batches of 50.
- The `send` script targets a **different** webhook (`/webhook/VAT`) than the dashboard
(`/webhook/bi-dashboard`). Don't conflate them.
- `fetch` from `file://` sends `Origin: null`; the webhook reflects origins to allow CORS.
Changing how requests are made can break the "just open the file" workflow.
- README documents behavior (navigation, auth, CSV, filter). If you change any of these,
update `README.md` in the same change.
- The invoice dashboards (*ODOO Rechnungen pro Monat*, *Alle Rechnungen pro Monat*
and *Quartal Schweiz*) are pivot experiments: they load jQuery, jQuery UI and
PivotTable.js from CDNs (so they need internet), reuse `normalizeRechnungen`, and
render via `renderRechnungenPivot` (no detail table). Layouts come from
`PIVOT_PRESETS` (keyed per view; the Vorlage dropdown) / the pivotUI panel; the
renderer is fixed to *Table*. The pivot container is recreated on each render so
a preset actually applies.