- Added maximum discount settings configurable by the administrator. - Implemented discount calculations in the quote confirmation process. - Introduced transport options with associated costs, including handling for exceeding maximum piece limits. - Updated frontend to display discounts and transport information in the quote summary. - Created new database migrations for storing discount and transport settings. - Developed a new Filament page for managing global discount settings. - Enhanced email templates to include discount and transport details. - Added tests to ensure proper functionality of discounts and transport calculations.
7.1 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Project overview
Web-based 3D configurator for infissi (windows/doors) — "kreiosinfissi-vite". It renders a Three.js scene
(GLB models) driven by JSON product data and a jQuery-based menu UI, and is meant to run inside an
<iframe> embedded on https://www.kreiosinfissi.it (or https://kreiosinfissi.test in dev), with the
final "add to cart" action posted back to the parent window via postMessage.
Commands
Package manager is pnpm (pnpm-lock.yaml is the lockfile of record even though package-lock.json is
also present).
pnpm install # install deps
pnpm dev # vite dev server on 0.0.0.0:5173
pnpm build # production build -> dist/ (root is src/, see vite.config.js)
pnpm preview # preview the production build
There is no test suite (pnpm test is a stub that exits with an error) and no linter configured.
Regenerating PVC price lists
scripts/extract_listini_pvc.py parses PVC_LISTINO.xls and regenerates
src/assets/data/modelli/pvc/<modello>/listino.json files. Requires a Python venv with xlrd
(pip install -r scripts/requirements.txt). Run with python3 scripts/extract_listini_pvc.py from repo root.
Any new title in the spreadsheet needs an entry in that script's KNOWN_FOLDERS map or it won't be matched
to an existing model folder.
Architecture
Entry point is src/js/starter.js, loaded from src/index.html. Flow on load (start()):
ParamsParser.getQueryParams()(src/js/params_parser.js) reads the URL query string. Two params drive everything:sistema(pvc|alluminio|legno|acciaio) andtype(model id). Seedocs/URL_PARAMS.mdfor the full param contract and the list of validtypevalues persistema. Note: that doc describes a more modular refactor (Application.js,communication/UrlParamsParser.js,communication/IframeBridge.js) that does not exist yet insrc/js— the current implementation is the flatterstarter.js/params_parser.jsdescribed here.- Catalog data comes from the Laravel backoffice (
backoffice/, API base insrc/js/api_base.js):GET /api/sistemi(systems + active models that have a price list; models without one are hidden and flagged in the admin list) andGET /api/sistemi/<sistema>/modelli/<modello>/configurazione, which returns the menu wiring (form: radios, selects, finish menus — the oldform.jsonshape), finishes, handles, glass types and the price-grid sizes, with image URLs served byGET /api/media/...(CORS-enabled, needed for WebGL textures). Onlyavvolgibile.jsonandsistemi/<sistema>/altezza_maniglia_<categoria>.jsonare still read fromsrc/assets/data. MenuBuilder.build()(src/js/menu_builder.js) injects an HTML template (assets/_menu.html, orassets/_menu-cassonetto.htmlwhentype=cassonetto) into#left-container, builds the model-picker list, handles the mobile-portrait/landscape layout reflow, and initializesPriceCalculator. It returns the price calculator instance used by the rest of the app.Configurator.init()/.enable()(src/js/configurator.js) wires a single delegatedinputhandler on the size fields, selects and radios. On every change (debounced) it sends the selected codes toPOST /api/sistemi/<sistema>/modelli/<modello>/prezzo, stores the result inwindow.configurazioneand updates the DOM. "Add to cart" sends the same codes (scelte): the backend recomputes the price and ignores any price coming from the client.APP.Player(src/js/app.js) wraps a Three.js renderer/scene/camera (scene graph loaded fromsrc/assets/app.json, a three.js-editor-style export) plus the actual product GLB (src/assets/models/<sistema>/<modello.modello>). Two players are created:player(visible canvas) andplayerOff(offscreen, used only to render the PNG snapshot sent to the cart). Key methods:swapModel/hideModel(toggle between pre-loaded variants in the same scene),changeTexture/changeColor(applied peruserData.categoriamesh tag — categories likeinterno,esterno,maniglia,vetro,cerniere),flipView(180° GSAP-animated turn between front/back view),addMarker(in-scene hotspot with a video popup).- Pricing is server-side only:
backoffice/app/Support/Configuratore/CalcolatorePrezzo.php. Computed in cascading phases, each phase's percentages applied to the previous phase's total (VAT excluded):serramento = base × (1 + finitura %), then profili extra on the serramento (measures + finishes only), then the other options (telaio, apertura, accessori), then+ variazione vetro. Base is the first grid size ≥ the requested one (mm). Supplements come fromopzionirows (finish type bianco/uniforme/bicolore, hinges, profili extra, ...): percentage, fixed, or per linear metre (ml from L/H multipliers + extra mm). Groups withui = multipla(profili extra) accept several codes, with same-alternativaoptions mutually exclusive. Sistema-level rows can be overridden per model by group. Option groups themselves live ingruppi_opzioni(title, control radio/select/multipla, menu section, and the modelmodulikey that enables them): a group without fixed markup in_menu.htmlis rendered byMenuBuilderinto.opzioni-sistema/.opzioni-accessori. Select options can call awindow[func](e.g.nascondiCategoriawithcategoria: manigliafor the motor) via theirparametri. The glass variation is Σ max(pane area, minimo_mq) × maggiorazione_mq, with panes from the model'sregole_vetro(spec: "Specifica tecnica vetri"). All of it is edited in Filament under "Configuratore".src/js/price_calculator.jsonly wraps the API call and the grid sizes.
Data conventions
- Every 3D-visible part is tagged with
userData.categoriain the GLB/scene so JS can show/hide/retexture it without knowing mesh names (interno,esterno,maniglia,cerniere,vetro, ...). - Which menu sections a model shows (and which items enter the price) is driven by its
moduliin the backoffice (Modello::MODULI, e.g.finitura_esterna,apertura,telaio,vetro). The per-model JSON files undersrc/assets/data/modelli/are legacy and no longer read (exceptavvolgibile.json). - Texture and icon images live in the backoffice (
storage/app/public/configuratore/..., seeded once byConfiguratoreSeederfrombackoffice/database/seeders/data/configuratore/) and are loaded lazily per selection (APP.Player.changeTexture) from absolute URLs; a monotonically-checkedisStale()guard discards a texture load if the user changed the selection again before it finished. window.*globals (window.configurazione,window.cambiaTexture,window.caricaModello,window.rotate,window.sistema, etc.) are the integration surface between the jQuery-driven menu markup (inlineonclick/onchangein the_menu*.htmltemplates) and the JS modules — expect to grep for awindow.<name> =assignment when tracing menu behavior, not for exported module functions.