Files
2026-10-26 11:46:41 -03:00

8.0 KiB

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.