Skip to content

About

Web app for steel sales teams: real-time pricing for six steel types (HRS, CR, HDG, pickled, teardrop, Magnelis), role-based offer approval, route-based transport costs, quarterly PGL scheduling, PDF/Excel export and a full analytics panel. Next.js 14 + PostgreSQL.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Latest commit

 

History

189 Commits

Folders and files

Repository files navigation

SteelQuote

A web app for steel sales teams: calculate pricing (mill surcharges, processing costs, margin, transport) for HRS, CR, HDG, PICKLED (pickled HRS), TEARDROP (Łezka) and ZM (Magnelis) steel in real time, save the result as an offer, run it through an approval workflow, and export it to PDF or Excel for a client.

Current release: 2.1 (live in production since 21 October 2026). Next version in development: 2.2.

HRS pricing defaults CR pricing defaults HDG pricing defaults

Live at steelpricinghub.abacusai.app (production runs 2.1; this repo is on 2.2). On Android the login screen offers to install it as an app.

What's new in 2.1: ArcelorMittal branding and offer version history

  • ArcelorMittal look across the whole app: light mode is the "V6 Aurora" variant, dark mode "V3 Midnight", with the AM logo, the Gilroy font and AM colours (app/brand-theme.css, assets in public/brand/). The light/dark toggle now switches the entire page
  • PDF offer on AM template 8: A4 portrait, logo top-right, Gilroy embedded, totals in a separate table so they don't repeat on every page; same content in all four languages
  • Change summary per offer version: every version (offer_X.N) carries a short label of its main changes, e.g. "HDG 2×1250 +5 t, offer value +6 082 €"; clicking it lists every old -> new value with the version's author (offers.created_by, migration 033)
  • Offer validity in hours (24h default, 48h or custom) instead of a payment term picker; the payment term now comes from the client, default 30 days (migration 034). The "validity > 48h" approval rule counts the chosen hours
  • Admin offers list can expand an offer's items; every item table shows PGL and margin (€/t and %)
  • Price book (/admin/cennik): "change every price by X €/t" per matrix or for all matrices of a steel type at once - applied to the draft, saved explicitly
  • Calculator summary flags a margin below the configured minimum with ⚠️ and a red highlight

What's new in 2.0: configurable flows, roles and approval rules

The fixed junior / senior / admin roles are replaced by configuration the admin edits in the new Uprawnienia panel (/admin/dostep), seeded from the business spec Formularz uprawnień i walidacji ofert (see finance_calculator_deployed/nextjs_space/docs/flows-v2-plan.md):

  • Flows (Flow 1 – DYSTR, Flow 2 – PROJEKTY, or any new one such as "Flow Polska"), hierarchy levels (N0, N+1, N+2, N+3, NPR) and roles with six functional permissions per flow. A user can hold one role in each of several flows and switches the active flow in the top bar
  • Validation rules (margin, base price, quotation / price validity, offer value). The required level is the highest one any triggered rule demands; NPR is a parallel approval. A completeness control flags thresholds still to be filled in, and a simulator runs the spec's test cases T1–T5
  • Visibility matrix per role per flow, enforced on the server for offer lists, the validation queue and analytics
  • The review panel lists offers waiting for your level; a reviewer may edit an offer (the rules re-run and may raise the level), reject it with instructions, or approve and send it to the client on the seller's behalf
  • The salespeople panel adds per-flow memberships and a pyramid / org-chart view showing who reports to whom
  • Only the offer's creator sends it to the client; a sent offer opens read-only, and a lost offer can be renegotiated as a new version
  • Surcharge price book (/admin/cennik): mill and processing surcharges per steel type are edited in the app, every change is logged, and saved offers keep the prices they were quoted with
  • Lost reasons (/admin/powody-przegranej): marking an offer lost requires a reason from an admin-managed list; "Inne" (other) also requires a note
  • Thousands separators (1 000 000) in every displayed amount and tonnage, including the PDF
  • Review decision bar on top of an offer under review, approvable straight from the calculator; offer items are validated server-side on save
  • Deploy hardening: the CSRF origin check honours the proxy's x-forwarded-host, and @types/react is pinned so a fresh yarn install builds

What's new in 1.9

  • Payment terms picked from day presets (prepayment / 2 / 15 / 30 / 45 / 60 / 90 / custom) instead of a date range; the PDF footer states "N days" or "prepayment"
  • Offer validity picked as Q1–Q4, "this month" or a custom range; past quarters of the current year are hidden, and choosing a quarter with a different scheduled PGL price shows a notice
  • Transport fix: the per-tonne rate of items already in the summary no longer drops while you type the quantity of the next, not-yet-added item. A "Transport cost" badge now sits in the summary header
  • Offer lists show the client company and SAP ID under the offer number, and can be searched and sorted by both, including senior and admin panels
  • Every page works on a phone in portrait orientation
  • Closable filter chips in Analytics, plus role filters in the salespeople ranking and the admin Salespeople panel
  • PDF: large amounts and tonnages no longer wrap or get cut off
  • Security: calculator warning messages now escape their values (stored XSS fix)

There's also a standalone prototype at the repo root (index.html, steel_calculator_standalone.html) — a single-file version of the calculator with no backend, useful for a quick look without installing anything.

finance_calculator_deployed/nextjs_space/

The app. Next.js 14 (App Router) + PostgreSQL, deployed via Abacus.ai.

Stack: JWT sessions (jose/bcryptjs) is the active auth — Prisma and NextAuth are installed in the scaffold but currently dormant. PDF export goes through a server-side Abacus.ai-hosted rendering endpoint (/api/generate-pdf). Road distances come from Geoapify, with a Nominatim + OSRM fallback so transport still prices without an API key.

Pricing and calculator

  • Six steel types — HRS, CR, HDG, PICKLED, TEARDROP, ZM — each with its own grade table, mill surcharges and processing (SSC) options, with totals recalculated as you type. Clicking a line item's row (not just its pencil icon) opens it for editing
  • Base PGL is configured per steel type, not as one shared value, and can be scheduled by quarter: the admin fills in Q1–Q4 for a chosen year and the calculator switches to the right quarter's price on its own, by server date — a price entered for a future quarter simply takes effect the moment that quarter starts. The settings table shows the current month next to all four quarters; where a quarter is left empty the manual base value stays in use
  • Every base-PGL change is logged (steel type, old and new value, who, when) in an admin-only history table with filtering, sorting and .xlsx download; each steel type keeps a consistent accent colour across the whole app
  • One-time grade — a grade outside the price list can be quoted by name plus a EUR/t surcharge. It lives only inside that offer: it is never written to the price list and never suggested in anyone else's quotes
  • Extra surcharge with an optional comment, which a per-item checkbox can push into the Remarks column of the PDF
  • Teardrop (Łezka) surcharge derived from sheet width, with its own grade table
  • EUR/PLN switch for every role — EUR is the single internal source of truth, PLN a display and input layer. An offer freezes the exchange rate it was saved with, so changing the rate later never rewrites a saved, pending or sent offer
  • Final unit price and line totals round up to the nearest currency unit (never undercharge); stored and intermediate values stay unrounded

Transport

  • Cost derived from the real road distance between the dispatch address and the client's, priced against a carrier tariff the admin maintains — distance bands, each carrying either a flat price or a per-km rate
  • Truck count from total offer tonnage against a configurable truck capacity: one tonne over the limit still pays for a whole extra truck. Items of 13.6–15.1 m add a per-truck oversize surcharge
  • Self-pickup checkbox zeroes transport and disables the route fields when the client collects the goods themselves
  • Routes are cached per address pair, so the same client is geocoded once
  • Manual entry stays available for anything the route engine can't price

Offers and approval workflow

  • Roles, hierarchy levels and permissions are configured per flow (since 2.0, see above) — no logic reads a role name. Workflow: draft → pending review → approved/rejected → sent
  • On submit the flow's validation rules decide which level must approve; when no rule fires (or the creator's own level covers it) the offer goes straight to the client. The offers list badges drafts "ready to send" or "needs approval" from the same server-side evaluation
  • Approvers see the offers waiting for their level in the review panel and can approve, reject with instructions, edit, or approve and send on the seller's behalf, with quick-filter tabs (awaiting me / awaiting send / reviewed by me / all)
  • Editing a saved offer never overwrites it in place: if anything actually changed, saving creates a new version (offer_<id>.1, offer_<id>.2, …) and earlier versions stay reachable under a collapsed list on the same card. An approved offer whose new version again needs approval reverts to "pending review" rather than inheriting a stale approval
  • Create, list, duplicate, delete and search offers — by name, fallback name, raw ID, client company or SAP ID — with sortable lists (date / offer name / company / SAP ID / value / status) in "My Offers" and the senior and admin panels. Optional offer name falls back to offer_<id> consistently everywhere
  • An offer cannot be sent to a client without the client's company name and NIP; it can still be created, saved, edited, duplicated and exported without them
  • Leaving a page with unsaved calculator changes prompts first, and "Nowa oferta" clears the workspace deliberately rather than by accident
  • Client decision on a sent offer — won / lost with an optional reason, or back to undecided. This is a separate axis from the internal status workflow: approved means a senior signed the offer off, never that the client bought it. Owners record their own; the reviewers of that offer and the administrator may record it too

Clients

  • Client data splits into company details (company, NIP, address, SAP ID) and optional contact details, which stay locked until company and NIP are filled
  • Company and NIP are typeahead fields over the client directory — picking a suggestion fills all four company fields, and either field falls back to the other when empty. Saving an offer adds or updates that client in the directory, so the search learns new clients; existing values are never overwritten, only blanks get filled
  • Contact people are shared across the team: a company can have many, saving an offer stores the person, and clicking into the name field lists that company's contacts — picking one fills surname, phone and e-mail. The admin Clients panel can expand, edit, add or delete a company's contacts in place

Admin panel

  • Manage accounts, clients, contacts and every offer in the company
  • Dashboard tiles (offers by status, active salespeople, total offers) link straight to the offers list pre-filtered to that status, or to the salespeople panel
  • Per-salesperson performance metrics — win rate and tonnage, expandable per row
  • Settings: EUR/PLN rate, base PGL per steel type plus the quarterly schedule, minimum margin %, base transport, carrier tariff bands, dispatch address, truck capacity and the oversize surcharge
  • An approver can build a team from people in their flows; the visibility matrix's "team" column (and, until an org model exists, branch / region) uses it

Analytics

  • /analytics for every role, PowerBI-style. Each user sees the offers the visibility matrix grants them (own, team, flow or all flows), plus a per-salesperson filter and breakdown when that covers more than themselves; scope is decided server-side, not in the browser
  • KPI tiles (tons offered / won / lost / undecided, win rate, offers, clients, value, average margin) each carry the change against the comparison period — calendar-aligned, so July compares against June and not against a 31-day window starting 31 May
  • Fifteen period presets plus a custom range; buckets by day / week / month / quarter / year; and a choice of which date to count by (created, sent or client decision), so "what did I quote in Q1" and "what did I close in Q1" are different questions with different answers
  • Filters for salesperson, steel type, status, client decision and client — a steel-type filter narrows to matching line items, so filtering a mixed HRS+HDG offer to HDG counts only its HDG tonnage
  • Timeline as line / area / stacked area / bar / stacked bar split by any dimension; breakdown as horizontal bars / pie / donut / table; a won-vs-lost share bar with win rate over time; top-clients and top-salespeople tables; the source offers behind every figure in a sortable table; and .xlsx export of the whole view. Clicking a bar, slice or tile drills the page down to it
  • Charts reuse the app's own --accent-* steel-type colours, so a steel type keeps its colour in light, dark and high contrast alike
  • Only the latest version of an edited offer counts, so a family of offer_30, offer_30.1, offer_30.2 is one quote and not three

Export and interface

  • PDF export of an offer (A4 portrait, ArcelorMittal template), generated in the seller's selected UI language. The Remarks column lists every selected mill and processing (SSC) surcharge for each line item (thickness tolerance, certificate, coating, protection, packaging, surface, finish, weld, marking, edging, labels, and so on)
  • Excel (.xlsx) export in KTS/GPAO column format, for both the calculator summary and individual offers, with column widths fitted to content
  • Four languages: PL / EN / CS / DE
  • Light and dark themes plus a High Contrast toggle on every page — one fixed bold palette independent of the light/dark choice, with larger text, for salespeople on older or very small screens

Setup

cd finance_calculator_deployed/nextjs_space
npm install --legacy-peer-deps   # legacy flag: eslint 9 vs @typescript-eslint/parser@7, lint-only conflict
cp .env.example .env.local       # fill in DATABASE_URL and JWT_SECRET for your local Postgres

# Migrations are idempotent and must run in order (001 → 034).
for f in migrations/*.sql; do psql "$DATABASE_URL" -v ON_ERROR_STOP=1 -f "$f"; done

npm run dev

Open http://localhost:3000. To create an admin account for local testing, insert a user directly:

node -e "console.log(require('bcryptjs').hashSync('yourpassword', 10))"
INSERT INTO users (email, password, full_name, is_superuser, is_active)
VALUES ('you@example.com', '<hash from above>', 'Your Name', true, true);

Then assign people to flows and roles in Handlowcy or Uprawnienia.

On Windows there's a one-shot alternative: steelquote-start.ps1 at the repo root starts a portable Postgres, creates the database, runs every migration, installs dependencies if needed, seeds test accounts into the local database only (one account per role in both flows, password 1234 — e.g. ifo.f1@steelquote.test, asm.f1@, hoc.f1@, ifo.f2@, kam.f2@, hop@, ceo@steelquote.test, admin example@gmail.com), and launches the dev server. steelquote-stop.ps1 shuts it back down. npm test runs the rule-engine unit tests (vitest).

Environment variables

Variable Required What it's for
DATABASE_URL yes PostgreSQL connection string
JWT_SECRET yes Signs the session cookie
ABACUSAI_API_KEY for PDF export Deployment token for the Abacus.ai-hosted rendering service behind /api/generate-pdf
GEOAPIFY_API_KEY optional Geocoding and truck routing for transport costs; without it the app falls back to Nominatim + OSRM
NEXTAUTH_SECRET no Belongs to the dormant NextAuth scaffold

Notes

  • Real .env/.env.local files, local Postgres data, and files generated from actually running the app locally (saved offer PDFs, uploads) are excluded via .gitignore — they're runtime state, not source code. Use .env.example as a starting point.
  • Version reports for every release live in Historia wersji oraz md/.

Roadmap

  • Actually deliver a sent offer to the client by e-mail with the PDF attached — today "sent" only moves the offer's status
  • Payment-terms table on the offer
  • Import offers from Excel (KTS/GPAO format) — analysed, deliberately deferred
  • Signup flow beyond the admin-only /api/signup endpoint
  • Password reset via email
  • Rate-limit login attempts (so test accounts don't need long passwords)
  • Resolve dormant Prisma/NextAuth scaffold (finish wiring or remove)
  • Merge the duplicated calculator data files (calc-data.ts / calculatorData.ts)

License

MIT

About

Web app for steel sales teams: real-time pricing for six steel types (HRS, CR, HDG, pickled, teardrop, Magnelis), role-based offer approval, route-based transport costs, quarterly PGL scheduling, PDF/Excel export and a full analytics panel. Next.js 14 + PostgreSQL.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages