01Overview & vision 02Technology stack 03Architecture 04Data model 05Module map 06Feature inventory 07Access control 08Design system 09Security & hardening 10REST API 11Quality & testing 12Delivery timeline 13What still needs adding 14Appendix
Technical document · v1.3 · reviewed 2026-09-30

Smart Billing.
Smarter Inventory.
RFID-Powered Jewellery Management.

The complete technical reference for MageTech Jewellery Smart — what the system is, how it is built, every module and screen it ships, the controls around it, and the work that is deliberately still to come.

Laravel 13 · PHP 8.3 Blade + Livewire 3.8 Tailwind CSS 4 MariaDB / MySQL Sanctum REST API RFID tagging GST invoicing Multi-tenant · multi-branch Shared-hosting ready
0
Spec sections
0
Milestones M0–M9
0
Tests passing
0
Assertions
0
Migrations
0
Eloquent models
0
Controllers
0
Blade views
Contents

Fourteen parts, one document

Everything below is drawn from the repository as it stands today — the requirements specification, the schema, the route table and the test suite — not from intent.

01 — Overview

What the system is

A jewellery-store management system for Indian jewellers running one or more counters, built around RFID-tagged stock and metal-rate-aware billing — and deployable on ordinary shared hosting, with no long-running processes.

01The till

Counter sessions open before anything can be billed; a server-held cart prices every line through one shared pricer, so the figure on the screen and the figure on the bill can never disagree.

  • Hold / resume across terminals
  • Exchange and return against the original bill
  • Split payment, advances, round-off
  • Thermal 80 mm and A5 via the browser print dialog

02The book

An append-only stock ledger and append-only party ledgers are the only writers of a balance. Nothing else in the application may adjust a total, so the number on screen is always the sum of evidence.

  • Immutable movements, reference-linked
  • Physical counts post whole or not at all
  • Ageing derived from entry dates, FIFO
  • GST working sheets from the same rows

03The tag

RFID batches mint sequential codes, bind one tag per variant, and record what a reader actually returned — matched, unbound or unknown — as evidence rather than as a verdict.

  • Batch lifecycle with 5,000-tag cap
  • Bind / unbind with full history
  • Scan sessions with expected manifests
  • Label sheet printing in batch order
Success criteria
CriterionTargetHow it is met
Counter bill latency< 300 ms, warm DB, cached assetsServer-rendered Blade, file cache, no SPA round trip
Concurrent users per branch10+Stateless sessions on disk; stock writes serialised per branch
Uptime on shared hostingNo long-running processesDatabase queue, file cache/session, cron-only scheduling
Offline tolerance at counterRefresh-safe — a reload loses nothingThe cart lives on the server; every line posts to it
GST invoice correctness100% of statutory fieldsdompdf statutory copy + GSTR-1/GSTR-3B working sheets
RFID scan → verify latency< 500 msIn-request classification against the session manifest
Scope, stated honestly. Repairs appeared in the original outline but are not among the 52 sections and no milestone ever owned them — they were dropped rather than left as a promise with no date. No-network billing (a queue holding sales against a dead line) was never in scope either; the guarantee is refresh-safety, not offline operation.

✓In scope

POS/counter billing · inventory and stock control · RFID tagging · product and catalogue management · metal rates · purchases and supplier settlement · customer management · reporting · analytics · multi-branch multi-tenant operation · role-based access · audit trail · backups.

✕Out of scope by decision

Redis · Docker/containerisation · JVM services · PostgreSQL · WebSockets/SSR · Spatie tenancy/permissions/activitylog/backup packages · a Node SSR front-end. Each was excluded because the deployment target is cPanel shared hosting, not a container platform.

02 — Stack

Built on boring, hostable parts

Every dependency was chosen against one constraint: it has to run on shared hosting with cron as the only background mechanism. That single rule removes Redis, containers, sockets and supervisors from the picture.

LayerChoiceVersionWhy
RuntimePHP^8.3Enums, typed constants, readonly — used throughout the domain layer
FrameworkLaravel^13.17Routing, Eloquent, validation, migrations, scheduling
UI runtimeLivewire^3.8Counter interactions without a client-side build of app logic
InteractivityAlpine.js^3.4Local state in Blade views — dropdowns, confirmations, line editing
StylingTailwind CSS^4.0Vite plugin, brand tokens in namespaces: brand, navy, gold, cyan
BuildVite^8.0Committed public/build — deploy needs no npm install
DatabaseMariaDB / MySQL · SQLite for testsutf8mb4_unicode_ciWhat shared hosting provides; SQLite in-memory keeps CI fast
API authLaravel Sanctum^4.3Personal access tokens for the read-only REST surface
PDFbarryvdh/laravel-dompdf^3.1Statutory GST invoice copy and printable statements
QRchillerlan/php-qrcode^5.0RFID label sheets
ChartsChart.js^4.5Sales and inventory analytics, drawn client-side from rendered data
Auth scaffoldLaravel Breeze^2.0Used to start, then fully re-skinned; remains in require-dev
Import/exportmagetech/laravel-import-export · magetech/laravel-query-toolkit^1.0Local path packages — metal-rate CSV import, report queries
Local packagesquery-toolkit, import-exportpath reposResolved from ../MTS-Laravel-Packages in development

↺Queues & schedule

QUEUE_CONNECTION=database with no queued jobs today; CACHE_STORE=file, SESSION_DRIVER=file. One scheduled task — magetech:backup --auto at 03:00 — reached through schedule:run from cron.

AaZero external requests

Figtree and Playfair Display are self-hosted woff2. No CDN, no Google Fonts, no analytics beacon — enforced by BrandTest, which fails the build if a stylesheet or font URL leaves the origin.

§Hand-rolled, not packaged

Tenancy, permissions, audit logging and backups are written in app/ rather than pulled from Spatie. Four small middleware, two Artisan commands, ~92 support classes.

03 — Architecture

Decisions that shape everything

Six confirmed architecture decisions govern the whole codebase. They are recorded in docs/REQUIREMENTS.md §3 and asserted by tests, so a refactor that breaks one fails the suite rather than silently changing the product.

MageTech Jewellery Smart architecture: client requests through nginx, the Laravel application with middleware, controllers and domain services, down to MariaDB, the filesystem and the scheduler.
Architecture MageTech Jewellery Smart — request path from the browser to MariaDB, the private filesystem and the scheduler. MageTech-Jewellery-Smart-Architecture.png
DecisionDetailConsequence
Tenancy resolution Path → domain → session → signed-in user's tenant → default The URL segment wins, but a deliberate default never outranks a known owner. No public signup path creates a tenant.
Tenant storage Single MariaDB database, tenant_id global scope One backup, one migration path. User carries the scope too, so provider lookup is confined by construction.
Branches A tenant owns many branches; branch_id on stock and sales Branch is a separate axis from tenant — group reporting falls out of the schema, not out of a view.
RBAC Named roles, fixed permission lists in code No UI can grant a capability the repository does not already contain. No privilege-escalation surface to guard.
First admin php artisan magetech:install-admin No default credentials ship anywhere; no route can mint an owner.
Audit & backups Custom append-only table; custom zip archive Audit rows throw on update/delete. Archives land in storage/app/private/backups where the web root cannot serve them.
Request pipeline

Middleware order is a security property

The tenant resolver must run after the session starts and before route-model binding. Global middleware fires too early (no session store); resolving after SubstituteBindings is worse and quieter — the scope would be inert while another business's row bound successfully. So SubstituteBindings is moved to the end of the web group and the resolver takes its place.

RequestHTTP
→
SecurityHeadersCSP · nosniff · HSTS
→
StartSessionfile driver
→
ResolveTenantpath · domain · session · user
→
auth + tenant scopeglobal tenant_id
→
EnsurePermissionenum-backed
→
SubstituteBindingslast in group
→
Controllerview or redirect
Asserted, not assumed. tests/Feature/MiddlewareOrderTest.php pins this ordering directly, because it is a security property rather than a style preference. A reordering that looks harmless would otherwise bind another tenant's row instead of 404ing.

⌘Four middleware, two commands

  • ResolveTenant — the five-step resolution chain
  • EnsureTenantIsResolved — route-group guard
  • EnsurePermission — one permission enum case per route
  • SecurityHeaders — every response, error pages included
  • magetech:install-admin — first tenant, branch, counter, settings, owner as one unit
  • magetech:backup --auto — nightly per-tenant archive with retention

§Domain layer: ~92 support classes

Business rules live outside controllers and views, grouped by bounded context. Controllers stay thin; views render; app/Support decides.

Billing · 21Inventory · 13Catalogue · 12 Parties · 11Purchases · 9Rfid · 8 Reports · 4Tenancy · 3Analytics · 2 Backup · 2Documents · 2Audit · 1 Dashboard · 1Auth · 2
04 — Data model

Forty-six tables, two invariants

48 migrations create 46 business tables plus the framework's own cache, jobs and sessions. Two rules hold across all of them: every tenant-owned row carries a non-null tenant_id filtered by a global scope, and a balance is only ever written by the one writer the module names for it.

TTenancy & identity

  • tenants — slug, name, domains, locale, timezone, defaults
  • users — tenant-scoped, role enum, soft state
  • branches, counters — document prefixes per branch
  • business_settings — one row per tenant, branch overrides
  • audit_logs — append-only, immutable
  • personal_access_tokens — Sanctum API keys

PCatalogue & rates

  • products — code, SKU, HSN, two GST rates, pricing mode
  • product_variants — size, weight, fineness, barcode, price
  • product_images — credited photography per product
  • product_categories — hierarchical, reorderable, cycle-safe
  • metal_rates — append-only, effective-dated per purity

SStock

  • stock_movements — append-only, reference-linked
  • stock_levels — cached balance, only the writer sets it
  • stock_counts, stock_count_lines — sheets that post whole or not at all
  • Transfer writes a row pair: out at source, in at destination

RRFID

  • rfid_tag_batches — sequential MT- codes, 5,000 cap, closes for good
  • rfid_tags — active / lost / retired / reissued
  • rfid_tag_bindings — one tag per variant, history retained
  • rfid_scan_sessions, rfid_scan_results — every read kept verbatim

BBilling & sales

  • counter_sessions — open/close, counted cash, variance
  • sales_invoices, ..._lines, ..._payments
  • held_bills — stored as items, re-priced on resume
  • credit_notes, ..._lines — reversed back out of stock
  • loyalty_point_entries — append-only, balance cached

LParties, purchases & system

  • customers / suppliers + their ledger entries
  • purchase_orders → goods_receipts → purchase_invoices
  • metal_bookings — rate-lock commitments, settle once
  • held_bills, jobs, sessions, cache
Write disciplineSingle writerWhat it prevents
Stock balanceStockMovementWriter — serialised per branchTwo counters selling the last piece of the same variant
Party balancesPartyLedgerWriter — row lock, append-onlyA balance that drifts from the sum of its entries
Bill line pricesSaleLinePricer — same class the till usesThe screen and the printed bill disagreeing by a rupee
Audit trailAuditLogger — throws on update/deleteA silent edit to the record of who did what
Document numbersDocumentNumber — per branch, per financial yearGaps or collisions in statutory invoice sequences
05 — Module map

Thirteen modules, 52 sections

The specification is divided into 52 numbered sections across 13 modules; numbering is stable so review comments can cite a section number. Each module below lists its sections, its controllers and where the logic sits.

1 · Dashboard

§1.1–1.3

Role-aware cards composed by DashboardSummary — the role decides which cards exist, and every figure reads live from the tables the rest of the system writes.

DashboardControllerSupport\Dashboard3 views

2 · Products & Catalogue

§2.1–2.5

Product master with variants and images, hierarchical categories that refuse to become cyclic, metal & purity as closed enums with fineness as data, two pricing modes.

ProductControllerProductCategoryControllerSupport\Catalogue · 12

3 · Inventory & Stock

§3.1–3.5

Append-only ledger, per-location transfers, physical counts with variance reason codes, reorder advice and purity-adjusted valuation in paise.

StockControllerStockCountControllerStockMovementControllerSupport\Inventory · 13

4 · RFID Tagging

§4.1–4.5

Batches, bindings, scan sessions with manifests, label sheets and a lifecycle of active, lost, retired and reissued — history kept on every rebind.

TagControllerScanSessionControllerSupport\Rfid · 8

5 · Billing / POS

§5.1–5.6

Counter shift, server-held cart, hold/resume, exchange and return, split payment and advances, thermal and statutory output — the rate locked at bill creation.

BillingControllerCounterSessionControllerHeldBillControllerSupport\Billing · 21

6 · Sales & Invoices

§6.1–6.4

Financial-year gapless numbering, dompdf GST copy, day book / counter / shift registers, credit notes with return reason codes.

SalesControllerCreditNoteController4 views

7 · Customers

§7.1–7.4

Profiles with GSTIN-derived party type, purchase history read from bills, advances and dues, printable statements and a simple earn/redeem loyalty scheme.

CustomerControllerSupport\Parties · 11

8 · Suppliers

§8.1–8.3

Profiles, a payable ledger with advances kept separate so ageing cannot count the same rupee twice, and period-based printable statements.

SupplierControllerLedger entries

9 · Purchases

§9.1–9.4

Draft → approved → placed → received orders, GRNs as the only place inbound stock moves, rate-lock metal bookings, purchase bills with per-line metal and making buckets.

PurchaseOrderControllerGoodsReceiptControllerMetalBookingControllerPurchaseInvoiceController

10 · Metal Rates

§10.1–10.3

Effective-dated append-only rates with no retroactive overwrite, live and history screens, audited manual overrides, all-or-nothing CSV import.

MetalRateControllerMetalRateImporter3 views

11 · Reports

§11.1–11.3

Sales, stock, purchases and party ageing — all CSV — plus GSTR-1 and GSTR-3B working sheets bucketed by HSN and rate, exportable and printable.

7 report controllersSupport\Reports · 410 views

12 · Analytics

§12.1–12.2

Period-over-period sales comparison with a least-squares seven-day projection honestly labelled, plus fast/slow/dead stock and lifetime-average margin.

SalesAnalyticsControllerInventoryAnalyticsControllerChart.js

13 · Settings & Administration

§13.1–13.5

Business profile and GST, branches and counters, users with a read-only role matrix, tenancy scoping, audit log and tenant backups with restore.

8 settings controllersSupport\TenancySupport\BackupSupport\Audit
Section arithmetic. 3+5+5+5+6+4+4+3+4+3+3+2+5 = 52. Milestone M0 carries no sections — it is toolchain, scaffold, brand system and re-skin.
06 — Feature inventory

Every screen, 184 routes

184 web endpoints across 13 modules, 13 auth endpoints and 6 API endpoints — 203 in all. The tables below group them by module and name the behaviours a user actually meets — the state machines, the guards and the outputs.

ModuleRoutesScreens & behaviour
Dashboard2Role-aware cards (today's sales, stock on hand, bound tags, party dues), alerts for low stock / flagged scans / bindable tags / parties past 30 days / bookings expiring in a week, shortcuts that check both permission and whether the route exists.
Catalogue21Product CRUD with multi-image upload, variant rows with per-variant SKU/barcode/price, category create/edit/reorder, rate create/import/template/download with effective dating.
Inventory13Stock screen with location cut, movement register, manual adjust and branch transfer forms, count sheets — create, add lines, post, cancel — with independent piece and weight tallies.
RFID22Tag batches (create/close/print), per-tag bind/unbind/found/lost/reissue, scan sessions (start, add reads, complete, abort), label sheet, bindable-tag picker.
Billing / POS18Counter open/close with opening float and counted cash, till screen with line add/remove, quick customer create, hold/resume/clear, exchange panel, bill post and reprint.
Sales & returns13Register with day/counter/shift cuts, invoice detail, PDF and 80 mm/A5 print, return creation against a bill, credit-note detail, print and withdraw.
Parties18Customer and supplier CRUD, ledger entries by hand, printable statements with closing balance, purchase history read from bills.
Purchases33Orders draft→approve→place→cancel with line editing, receipts with over/short variance explanation, metal bookings create/settle/cancel, purchase invoices post/settle/cancel with line returns.
Reports15Sales, stock, purchases, parties — each with CSV export — plus GSTR-1 and GSTR-3B as CSV and PDF.
Analytics2Sales trends with previous-window comparison and a labelled projection; inventory fast/slow/dead classification and margin against lifetime weighted-average cost.
Settings24Business profile, branch CRUD with prefixes, user CRUD with last-owner/last-branch guards, read-only role matrix, filterable audit log, API tokens, backup create/restore/download.
Profile3Name, email, password — Breeze re-skinned.
Auth13Login, logout, forgot/reset password, email verification, password confirmation, register.

⏱State machines that matter

  • Counter session — must be open before a bill exists; one shift open per till; close needs counted cash and shows the variance.
  • Purchase order — draft → approved → placed → received; goods cannot be booked against a supplier who has never been told.
  • Goods receipt — posted only with an explained variance; writes the movement and updates order and booking in one transaction.
  • Credit note — withdrawable: stock comes back out and the account is corrected.
  • RFID batch — closes for good; a closed batch refuses a reprint.
  • Scan session — accepts no further reads once closed; flash copy never reads as a pass.

⚑Guards the UI cannot bypass

  • The last active owner cannot be deactivated; nobody deletes or demotes their own account.
  • The last branch cannot be deleted; super-admin status is never accepted from a form.
  • Supplier payments beyond the payable are refused at the counter; advances are a separate entry type.
  • A return cannot credit more than the pieces on the bill allow.
  • A posted purchase bill refuses edits; a cancelled bill cannot lock its receipt out of re-billing.
  • Rates are locked at bill creation — a reprint reads the row, never the rate table.
Money is paise, everywhere. Every monetary figure is an integer in paise. Each component of a line — metal value, making charge, wastage, stones — is computed and rounded once where it is computed; the payable alone is rounded to the nearest rupee at the very end, printed as its own round off line. That line is the only place a displayed rupee figure is not the exact sum of what sits above it.
07 — Access control

Five roles, thirty-three permissions

Permissions are string cases in App\Support\Auth\Permission, not rows in a table — so an authorisation decision always traces to a literal in the repository, and no role can acquire a capability through the UI. There is deliberately no permission editor.

Permission OwnerManagerCashierAccountantStockroom
dashboard.view●●●●●
products.view · rates.view · inventory.view · rfid.view●●●●●
products.manage●●——●
rates.manage●●———
inventory.manage●●——●
rfid.manage●●——●
billing.create · billing.hold · billing.reprint●●●——
billing.discount · billing.cancel●●———
sales.view●●●●—
sales.view_all · sales.return●●—●—
customers.view●●●●—
customers.manage●●●——
suppliers.view · purchases.view●●—●●
suppliers.manage · purchases.manage●●——●
reports.view · analytics.view●●—●—
reports.financial●——●—
settings.view●●———
branches.manage · users.view · audit.view●●———
settings.manage · users.manage · backups.manage●————

OOwner

Full access, including users, settings and backups. 33 / 33 permissions.

MManager

Runs the outlet day to day, without backups or user provisioning. 29 permissions.

CCashier

Bills at the counter and handles customers. 11 permissions — no discount, no cancel, no reports.

AAccountant

Reporting, GST working sheets and party ledgers. 12 permissions — read-only across the book, no billing.

SStockroom

Stock, RFID tags, product records and purchases. 11 permissions — no billing, no sales.

⊕API tokens

A token carries its owner's permissions and nothing more — a cashier's token can no more read a report than the cashier can in a browser.

08 — Design system

A brand that reads as jewellery and technology

Confirmed brand tokens live in config/brand.php and the Tailwind v4 theme. Brand colours sit in their own brand, navy, gold and cyan namespaces — Tailwind's built-in blue and gray scales are deliberately left alone so a utility class can never silently mean the wrong colour.

RoleTokenHexUsage
PrimaryRoyal Tech Blue#1261E8Actions, links, primary buttons, active state
Enterprise foundationDeep Jewellery Navy#071A3DSidebar, headings, invoice header, theme colour
Dark surface—#0B2148Elevated dark panels
Jewellery / valuePremium Gold · Luxury Gold#F5A900 · #FFC928Estimates, rates, precious stock, display accents
RFID / smart techTech Cyan · Light Cyan#19D9FF · #67E8F9RFID surfaces only — reserved by rule
Page / card—#F5F8FC · #FFFFFFApp background and card surface
Text—#172033 · #64748BPrimary and secondary ink
Border—#D9E2F0Cards, tables, inputs
Statussuccess · warning · error · info#16A34A #F59E0B #DC2626 #2563EBToasts, badges, ledger states

◑Distribution

40% navy + blue · 25% white/light · 15% royal blue · 10% gold · 5% cyan · 5% neutrals. Cyan never touches anything that is not RFID; gold never touches anything that is not value.

▨Gradients

#1261E8→#063B9E brand · #FFD84D→#F5A900→#C97800 gold · #1261E8→#19D9FF technology · #071A3D→#0B2D68→#1261E8 hero · #071A3D→#0B3B8F→#1261E8 RFID.

AaTypography

Figtree 400/500/600/700 for UI; Playfair Display 400–700 for headings and gold-gradient accents. Self-hosted woff2 — a test fails the build on any external font request.

Why the constraint matters here. This very document follows the same rules as the product it describes: self-hosted fonts from public/fonts/, the supplied logo and favicon from this folder, and no CDN, tracker or external request of any kind. Open devtools — the network tab stays empty apart from the document itself.
09 — Security

Hardening as middleware, not a checklist

SecurityHeaders runs on every response — error pages, downloads and the health endpoint included, because a header that only guards the screens that render often is one an attacker simply avoids.

ControlWhat it does
Content-Security-Policydefault-src 'self' with object-src 'none', base-uri 'self', frame-ancestors 'self', form-action 'self'. Scripts and styles from any other origin are refused outright.
Permissions-PolicyEight sensors named as unavailable: accelerometer, camera, geolocation, gyroscope, magnetometer, microphone, payment, USB.
Transport & framingX-Content-Type-Options: nosniff, X-Frame-Options: SAMEORIGIN, Referrer-Policy: strict-origin-when-cross-origin, HSTS only where the request is already https in production.
Rate limitingA named api limiter at sixty requests a minute per user, applied to every API route.
Error handlingBranded 403, 404, 419, 429 and 500 pages instead of framework traces; /up for probes.
Tenant isolationGlobal tenant_id scope on every owned table, including User — reach is confined by construction rather than by remembering to filter.
Audit trailAppend-only: updates and deletes throw a LogicException rather than silently corrupting the record of who did what.
BackupsWritten as JSON + manifest inside a zip under storage/app/private/backups — outside the web root. Restore refuses another business's file and refuses column drift before the first delete.
BootstrapOwners exist only through magetech:install-admin. No default credentials ship; no route can mint an admin.
Secrets.env is git-ignored; API tokens are shown once in plaintext and revocable instantly from the settings screen.
Honest note on CSP. The policy permits inline script and style because the Blade views use inline event handlers (confirm-before-delete), so blocking them outright would mean rewriting every form to ship a nonce. What it removes is the part that matters for injection: code from anywhere but this origin, plugin objects, a foreign base tag, off-site form posts and framing by anyone else.
10 — REST API

Six read-only endpoints, same permissions

A Sanctum-authenticated, read-only surface for till displays, stock devices and reporting hooks. It answers in one shape, prices in integer paise, and carries the caller's own permissions — the token grants nothing its owner does not already hold.

auth:sanctumbearer token
→
ResolveTenantsame resolver
→
tenant scopeglobal
→
throttle:api60 / min / user
→
ReadControllerdata + meta
EndpointPermissionReturns
GET /api/meauthenticatedCaller identity, role and permission list — the introspection point for a device
GET /api/ratesrates.viewLive metal rates by metal and purity, in paise
GET /api/productsproducts.viewCatalogue with variants, SKUs and barcodes; supports search and single-product fetch
GET /api/stockinventory.viewStock levels per variant and branch
GET /api/invoicessales.viewSales invoices with lines and payments, date-filtered
GET /api/reports/salesreports.viewSales totals for a window, the same cut the report screen draws

⇄Response contract

Every response is a single { data, meta } envelope. Money is integer paise — never a float, never a formatted string — so a consumer never has to guess a rounding rule.

⊘No writes

There is no POST, PUT or DELETE. The API observes the book; it cannot change it. Adding a write surface later is a design decision, not an oversight.

11 — Quality

732 tests, 3,103 assertions

PHPUnit 12 on SQLite in-memory — no MySQL server needed — across 70 test files. Style is enforced by Laravel Pint; the front-end by a production Vite build. All figures below are from the current green run.

SuiteFilesWhat it pins down
Feature · Billing6Till screen, held bills, exchanges, credit notes, invoice service, till interactions
Feature · Catalogue4Products, categories, metal rates, CSV import
Feature · Purchases4Orders, goods receipts, metal bookings, purchase invoices
Feature · Reports5Sales, stock, purchases, party ageing, GSTR-1 and GSTR-3B
Feature · Settings5Users, branches, business profile, role matrix, audit log
Feature · Auth6Login, registration, reset, verification, confirmation, password update
Feature · Inventory / Parties / Rfid / Analytics / Api / Backups / Tenancy12Stock screens and counts, customer/supplier controllers, batches and scan sessions, both analytics cuts, the REST surface, backup isolation and restore, tenant resolution
Feature · Cross-cutting14RouteSweepTest (every parameterless GET), MiddlewareOrderTest, SecurityHardeningTest, BrandTest (no CDN), CompiledStylesheetTest, dashboard, demo seeder, install command, profile
Unit11Price calculator, reorder advisor, stock valuation, movement writer, stock count, tag binder/printer, scan verifier, RFID tag/session, photo thumber

✓Run

php artisan test — 732 passing, 3,103 assertions, ~73 s. SQLite in-memory, four bcrypt rounds, array mail and session.

✎Style

vendor/bin/pint over app/, tests/, config/, database/, routes/. Laravel preset, no custom rule set to argue about.

▣Build

npm run build → committed public/build. Deploy needs no Node. CompiledStylesheetTest fails if the built CSS is missing or stale.

Tests that exist to stop a regression, not to raise a number. MiddlewareOrderTest guards tenant-scope ordering. BrandTest fails on any external font or stylesheet URL. RouteSweepTest walks every parameterless GET route looking for a 500. DemoDataSeederTest asserts that no product ships without a photograph — the kind of defect that only shows up in a customer demo.
12 — Delivery

Ten milestones, every one shipped

M0 through M9 are complete and marked done in docs/REQUIREMENTS.md. Each milestone was runnable and testable on its own; the assertion counts recorded alongside are the state of the suite at the time it closed.

M0 Toolchain, scaffold, brand system done

No sections

Laravel 13 on PHP 8.3, Tailwind 4 + Vite 8, self-hosted Figtree and Playfair, brand tokens in config/brand.php, Breeze scaffold re-skinned, Pint and PHPUnit wired.

M1 Tenancy, RBAC, audit, settings done

§13.1–13.5 · backups reassigned to M9

Five-step tenant resolver with the signed-in-user step, single-database tenant_id scope, fixed-list role enum, append-only audit log, business profile and branch CRUD with last-owner/last-branch guards, CLI admin bootstrap. 128 tests at close.

M2 Products, catalogue, metal rates done

§2.1–2.5, §10.1–10.3

Product master with variants, images and both pricing modes; hierarchical categories that refuse cycles; closed metal/purity enums with fineness as data; percentage or fixed making charges; append-only effective-dated rates with audited override and all-or-nothing CSV import. 273 tests.

M3 Inventory and stock done

§3.1–3.5

Append-only ledger as the only writer of balances, one row pair per transfer, hand-entered adjustments, physical counts that change nothing until posting, independent piece and weight tallies, reorder advice, purity-adjusted valuation in paise. 356 tests.

M4 RFID tagging done

§4.1–4.5

Batches minting sequential MT- codes with a 5,000 cap and a permanent close, free/bound inventory split, one binding per variant with history retained on rebind, scan sessions recording matched/unbound/unknown reads against an expected manifest, label sheets in batch order.

M5 Customers and suppliers done

§7.1, §7.3, §8.1–8.3

GSTIN-derived party types, PartyLedgerWriter as the sole writer of balances under a row lock, supplier overpayment refused at the counter with advances as a separate entry type, FIFO ageing from entry dates, printable period statements. 500 tests, 1,555 assertions.

M6 Purchases, GRN, metal booking done

§9.1–9.4

Orders through draft → approved → placed → received with figures read off receipts, GRNs as the only inbound stock movement posted with an explained variance, rate-lock bookings settled by weight that actually arrived, purchase bills holding per-line metal and making buckets, supplier advance settlement as an advance_applied entry that cannot be double-counted in ageing. 587 tests, 2,194 assertions.

M7 Billing/POS, sales, GST done

§5.1–5.6, §6.1–6.4, §7.2, §7.4

Counter shifts against counted cash, one pricer for screen and bill, financial-year branch-prefixed numbering, statutory dompdf copy, returns crediting each bucket by its share of the pieces, loyalty earned and reversed with the bill, purchase history read from bills, browser-printed thermal output.

M8 Reports, analytics, REST API done

§11.1–11.3, §12.1–12.2

One shared half-open ReportRange behind every screen and export, CSV on every report, GSTR-1 and GSTR-3B bucketed by HSN and rate with credit notes netted, period-over-period sales analytics with a labelled projection, fast/slow/dead classification and lifetime-average margin, six read-only Sanctum endpoints.

M9 Dashboard, backups, hardening done

§1.1–1.3, §13.5

Dashboard composed by DashboardSummary with role-aware cards, five alert types and shortcut checks, JSON-based tenant backups with a restore that refuses column drift, nightly magetech:backup --auto with fourteen-archive retention, SecurityHeaders on every response, branded error pages, sixty-per-minute API limiter.

13 — Roadmap

What still needs adding

The 52 sections are complete. What follows is the honest gap list: capabilities a jewellery business will ask for next, each with the reason it is not there yet and where the change would land. Nothing here is a bug — it is scope that has not been opened.

High value Loyalty scheme configuration

Loyalty is built and working — loyalty_enabled, loyalty_points_per_100, loyalty_paise_per_point and loyalty_min_redeem exist on business_settings, with the arithmetic in BusinessSetting and the earn/redeem UI on the till. But there is no settings screen for those four fields, and BusinessProfileRequest does not validate them — a shop cannot turn loyalty on without a migration or tinker.

→ settings/business.blade.php + BusinessProfileRequest

High value Barcode label printing

Products and variants carry a barcode field and the till searches on it, but there is no sheet that prints barcodes for the pieces themselves — only RFID label sheets. A jeweller without RFID still needs adhesive labels at the counter.

→ new view beside rfid/batches/{batch}/sheet

High value Offline / no-network billing

Explicitly out of scope and named as such: the cart is server-held, so a reload is safe, but a dead line stops billing. A service-worker queue that replays sales when the connection returns is the largest remaining engineering item in the product.

→ excluded in REQUIREMENTS §2, revisiting needs a decision

Medium Notifications & reminders

Low stock, flagged scans and parties past 30 days appear as dashboard alerts only. There is no email/SMS/WhatsApp delivery, no birthday or anniversary reminder despite those fields existing on the customer record, and no due-payment nudge.

→ queue jobs + a notification channel; QUEUE_CONNECTION is already `database`

Medium Scheduled housekeeping

Only one scheduled task exists — the nightly backup. Audit-log retention is described as "the one legitimate deletion" but no pruning command writes it; rate history, held bills and expired scan sessions also accumulate indefinitely.

→ routes/console.php + a magetech:prune command

Medium Custom tenant domains UI

The resolver reads tenants.domains as step two of five, but no screen sets it — the column is only writable by hand. Multi-shop groups running one custom domain per outlet currently need database access.

→ settings screen + StoreTenantRequest

Medium Custom roles & permission editor

Deliberately fixed in code — the role matrix screen is read-only. A shop that wants "senior cashier" (discount but no reports) or "purchasing assistant" cannot express it. Opening this up means accepting the privilege-escalation surface the current design refuses to have.

→ a decision, not a defect: see Role.php header comment

Medium Estimates & quotations

estimate_prefix is stored per branch and validated, but no estimate screen writes it — only invoices and GRNs do. Quotation-to-bill conversion, expiry and customer acceptance are unimplemented.

→ new module; prefix is already reserved in the schema

Medium GSTR-1 direct filing

GSTR-1 and GSTR-3B are working sheets: CSV and PDF exports a human pastes into the portal. There is no NIC/IRN e-invoice integration and no e-way bill — a regulatory integration, not an application feature.

→ external API; out of scope until requested

Low Queue-backed work

app/Jobs does not exist. Backups, rate imports and PDF generation all run inside the request. Adding queued jobs is cheap now — the database driver is configured — but nothing yet is slow enough to justify it.

→ app/Jobs + QUEUE_CONNECTION already `database`

Low Translations

Views are wrapped in __() — 2,094 call sites — but there is no lang/ directory, so every string falls back to English. Hindi or regional copy is a content job, not a code job; the plumbing is already in place.

→ lang/en.php + lang/hi.php

Low PWA offline shell

site.webmanifest, icons and three shortcuts ship, and the app installs on a phone. There is no service worker, so the shell does not cache and opening it with no signal shows the browser's error page.

→ public/sw.js + a registration snippet

Low Hardware peripherals

Thermal printing targets the browser's print dialog by design — no driver, no socket, no long-running process. Barcode scanner input, weighing scales and cash drawers are read through the keyboard or not at all; RFID reads arrive as pasted or posted lists.

→ shared-hosting constraint: browser is the integration point

Low Multi-currency & international

Currency is a three-letter field defaulting to INR with a decimals setting, and state codes drive GST. Foreign-currency billing, conversion rates and non-GST tax regimes are not modelled.

→ schema assumes Indian GST; a real decision, not a tweak

Low Repairs & karigar tracking

Present in the original outline, then dropped because nothing among the 52 sections owned it — no table, no screen, no milestone. Reopening it is a genuine scope addition with its own lifecycle, not a finishing touch.

→ removed from §2 in v1.0; requires a spec revision
How to read this list. The three high value items are the ones a live shop will hit first: loyalty cannot be switched on, barcodes cannot be printed, and billing stops when the internet does. Everything else is either a decision with a trade-off (custom roles, e-invoicing, repairs) or plumbing already half-laid (queue, translations, service worker).
14 — Appendix

Repository at a glance

⌘Layout

app/
  Console/Commands/     install-admin, backup
  Http/Controllers/     47 (web + API + auth)
  Http/Middleware/       4: tenant, permission, headers
  Models/               39 Eloquent models
  Support/              92 domain classes in 14 groups
  View/Components/      AppLayout, GuestLayout
config/                 brand.php + framework config
database/migrations/    48
docs/REQUIREMENTS.md    the 52-section specification
public/
  brand/                icons, logo derivatives, OG card
  fonts/                self-hosted woff2 (8 files)
  storage/document/     this document
resources/
  css/app.css           Tailwind 4 design system
  views/                143 Blade templates in 17 groups
routes/
  web.php               184 endpoints
  auth.php              13 endpoints
  api.php               6 endpoints
tests/
  Feature/              56 files
  Unit/                 11 files
  Concerns/             2 shared builders

▶Commands

composer setupinstall, key, migrate, npm build
composer testconfig:clear + full suite
npm run devVite HMR on :5173
npm run buildproduction assets
php artisan serveapp on :8000
magetech:install-adminfirst tenant + owner
magetech:backup --autonightly archive + prune
db:seed --class=DemoDataSeederfull demo book
vendor/bin/pintstyle fix

✓Definition of done

  • Full suite green — 732 tests, 3,103 assertions
  • Pint clean
  • npm run build succeeds and public/build is committed
  • No external network request in devtools
  • Every product photograph resolves on both origins
  • Requirements spec, README and roadmap reconciled
Glossary
TermMeaning in this system
TenantOne business. Owns branches, users, settings and every row tagged with its tenant_id.
Branch / counterWhere stock sits and where a till opens. Branch carries its own document prefixes.
Counter sessionA shift: opened with a float, closed against counted cash, with the variance shown.
Metal rateEffective-dated price per metal per purity, append-only. Locked onto each bill line at creation.
Making chargeWorkmanship, either a percentage of metal value or a fixed amount per piece.
GRNGoods receipt note — the only place inbound stock moves, posted with an explained variance.
Metal bookingA rate-lock commitment with a supplier before goods arrive; settled exactly once at receipt.
Credit noteA return written against the bill it reverses, withdrawable back out of stock and off the account.
Party ledgerAppend-only customer/supplier account. Balances are cached; entries are evidence.
Working sheetGSTR-1/GSTR-3B computed for export, not filed directly.