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.
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.
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
| Criterion | Target | How it is met |
|---|---|---|
| Counter bill latency | < 300 ms, warm DB, cached assets | Server-rendered Blade, file cache, no SPA round trip |
| Concurrent users per branch | 10+ | Stateless sessions on disk; stock writes serialised per branch |
| Uptime on shared hosting | No long-running processes | Database queue, file cache/session, cron-only scheduling |
| Offline tolerance at counter | Refresh-safe — a reload loses nothing | The cart lives on the server; every line posts to it |
| GST invoice correctness | 100% of statutory fields | dompdf statutory copy + GSTR-1/GSTR-3B working sheets |
| RFID scan → verify latency | < 500 ms | In-request classification against the session manifest |
✓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.
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.
| Layer | Choice | Version | Why |
|---|---|---|---|
| Runtime | PHP | ^8.3 | Enums, typed constants, readonly — used throughout the domain layer |
| Framework | Laravel | ^13.17 | Routing, Eloquent, validation, migrations, scheduling |
| UI runtime | Livewire | ^3.8 | Counter interactions without a client-side build of app logic |
| Interactivity | Alpine.js | ^3.4 | Local state in Blade views — dropdowns, confirmations, line editing |
| Styling | Tailwind CSS | ^4.0 | Vite plugin, brand tokens in namespaces: brand, navy, gold, cyan |
| Build | Vite | ^8.0 | Committed public/build — deploy needs no npm install |
| Database | MariaDB / MySQL · SQLite for tests | utf8mb4_unicode_ci | What shared hosting provides; SQLite in-memory keeps CI fast |
| API auth | Laravel Sanctum | ^4.3 | Personal access tokens for the read-only REST surface |
| barryvdh/laravel-dompdf | ^3.1 | Statutory GST invoice copy and printable statements | |
| QR | chillerlan/php-qrcode | ^5.0 | RFID label sheets |
| Charts | Chart.js | ^4.5 | Sales and inventory analytics, drawn client-side from rendered data |
| Auth scaffold | Laravel Breeze | ^2.0 | Used to start, then fully re-skinned; remains in require-dev |
| Import/export | magetech/laravel-import-export · magetech/laravel-query-toolkit | ^1.0 | Local path packages — metal-rate CSV import, report queries |
| Local packages | query-toolkit, import-export | path repos | Resolved 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.
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.png| Decision | Detail | Consequence |
|---|---|---|
| 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. |
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.
tenant_idtests/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 chainEnsureTenantIsResolved— route-group guardEnsurePermission— one permission enum case per routeSecurityHeaders— every response, error pages includedmagetech:install-admin— first tenant, branch, counter, settings, owner as one unitmagetech: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.
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, defaultsusers— tenant-scoped, role enum, soft statebranches,counters— document prefixes per branchbusiness_settings— one row per tenant, branch overridesaudit_logs— append-only, immutablepersonal_access_tokens— Sanctum API keys
PCatalogue & rates
products— code, SKU, HSN, two GST rates, pricing modeproduct_variants— size, weight, fineness, barcode, priceproduct_images— credited photography per productproduct_categories— hierarchical, reorderable, cycle-safemetal_rates— append-only, effective-dated per purity
SStock
stock_movements— append-only, reference-linkedstock_levels— cached balance, only the writer sets itstock_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— sequentialMT-codes, 5,000 cap, closes for goodrfid_tags— active / lost / retired / reissuedrfid_tag_bindings— one tag per variant, history retainedrfid_scan_sessions,rfid_scan_results— every read kept verbatim
BBilling & sales
counter_sessions— open/close, counted cash, variancesales_invoices,..._lines,..._paymentsheld_bills— stored as items, re-priced on resumecredit_notes,..._lines— reversed back out of stockloyalty_point_entries— append-only, balance cached
LParties, purchases & system
customers/suppliers+ their ledger entriespurchase_orders→goods_receipts→purchase_invoicesmetal_bookings— rate-lock commitments, settle onceheld_bills,jobs,sessions,cache
| Write discipline | Single writer | What it prevents |
|---|---|---|
| Stock balance | StockMovementWriter — serialised per branch | Two counters selling the last piece of the same variant |
| Party balances | PartyLedgerWriter — row lock, append-only | A balance that drifts from the sum of its entries |
| Bill line prices | SaleLinePricer — same class the till uses | The screen and the printed bill disagreeing by a rupee |
| Audit trail | AuditLogger — throws on update/delete | A silent edit to the record of who did what |
| Document numbers | DocumentNumber — per branch, per financial year | Gaps or collisions in statutory invoice sequences |
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.3Role-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.
2 · Products & Catalogue
§2.1–2.5Product master with variants and images, hierarchical categories that refuse to become cyclic, metal & purity as closed enums with fineness as data, two pricing modes.
3 · Inventory & Stock
§3.1–3.5Append-only ledger, per-location transfers, physical counts with variance reason codes, reorder advice and purity-adjusted valuation in paise.
4 · RFID Tagging
§4.1–4.5Batches, bindings, scan sessions with manifests, label sheets and a lifecycle of active, lost, retired and reissued — history kept on every rebind.
5 · Billing / POS
§5.1–5.6Counter shift, server-held cart, hold/resume, exchange and return, split payment and advances, thermal and statutory output — the rate locked at bill creation.
6 · Sales & Invoices
§6.1–6.4Financial-year gapless numbering, dompdf GST copy, day book / counter / shift registers, credit notes with return reason codes.
7 · Customers
§7.1–7.4Profiles with GSTIN-derived party type, purchase history read from bills, advances and dues, printable statements and a simple earn/redeem loyalty scheme.
8 · Suppliers
§8.1–8.3Profiles, a payable ledger with advances kept separate so ageing cannot count the same rupee twice, and period-based printable statements.
9 · Purchases
§9.1–9.4Draft → 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.
10 · Metal Rates
§10.1–10.3Effective-dated append-only rates with no retroactive overwrite, live and history screens, audited manual overrides, all-or-nothing CSV import.
11 · Reports
§11.1–11.3Sales, stock, purchases and party ageing — all CSV — plus GSTR-1 and GSTR-3B working sheets bucketed by HSN and rate, exportable and printable.
12 · Analytics
§12.1–12.2Period-over-period sales comparison with a least-squares seven-day projection honestly labelled, plus fast/slow/dead stock and lifetime-average margin.
13 · Settings & Administration
§13.1–13.5Business profile and GST, branches and counters, users with a read-only role matrix, tenancy scoping, audit log and tenant backups with restore.
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.
| Module | Routes | Screens & behaviour |
|---|---|---|
| Dashboard | 2 | Role-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. |
| Catalogue | 21 | Product 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. |
| Inventory | 13 | Stock 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. |
| RFID | 22 | Tag batches (create/close/print), per-tag bind/unbind/found/lost/reissue, scan sessions (start, add reads, complete, abort), label sheet, bindable-tag picker. |
| Billing / POS | 18 | Counter 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 & returns | 13 | Register with day/counter/shift cuts, invoice detail, PDF and 80 mm/A5 print, return creation against a bill, credit-note detail, print and withdraw. |
| Parties | 18 | Customer and supplier CRUD, ledger entries by hand, printable statements with closing balance, purchase history read from bills. |
| Purchases | 33 | Orders 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. |
| Reports | 15 | Sales, stock, purchases, parties — each with CSV export — plus GSTR-1 and GSTR-3B as CSV and PDF. |
| Analytics | 2 | Sales trends with previous-window comparison and a labelled projection; inventory fast/slow/dead classification and margin against lifetime weighted-average cost. |
| Settings | 24 | Business 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. |
| Profile | 3 | Name, email, password — Breeze re-skinned. |
| Auth | 13 | Login, 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.
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 | Owner | Manager | Cashier | Accountant | Stockroom |
|---|---|---|---|---|---|
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.
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.
| Role | Token | Hex | Usage |
|---|---|---|---|
| Primary | Royal Tech Blue | #1261E8 | Actions, links, primary buttons, active state |
| Enterprise foundation | Deep Jewellery Navy | #071A3D | Sidebar, headings, invoice header, theme colour |
| Dark surface | — | #0B2148 | Elevated dark panels |
| Jewellery / value | Premium Gold · Luxury Gold | #F5A900 · #FFC928 | Estimates, rates, precious stock, display accents |
| RFID / smart tech | Tech Cyan · Light Cyan | #19D9FF · #67E8F9 | RFID surfaces only — reserved by rule |
| Page / card | — | #F5F8FC · #FFFFFF | App background and card surface |
| Text | — | #172033 · #64748B | Primary and secondary ink |
| Border | — | #D9E2F0 | Cards, tables, inputs |
| Status | success · warning · error · info | #16A34A #F59E0B #DC2626 #2563EB | Toasts, 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.
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.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.
| Control | What it does |
|---|---|
| Content-Security-Policy | default-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-Policy | Eight sensors named as unavailable: accelerometer, camera, geolocation, gyroscope, magnetometer, microphone, payment, USB. |
| Transport & framing | X-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 limiting | A named api limiter at sixty requests a minute per user, applied to every API route. |
| Error handling | Branded 403, 404, 419, 429 and 500 pages instead of framework traces; /up for probes. |
| Tenant isolation | Global tenant_id scope on every owned table, including User — reach is confined by construction rather than by remembering to filter. |
| Audit trail | Append-only: updates and deletes throw a LogicException rather than silently corrupting the record of who did what. |
| Backups | Written 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. |
| Bootstrap | Owners 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. |
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 tokentenant scopeglobalthrottle:api60 / min / user| Endpoint | Permission | Returns |
|---|---|---|
GET /api/me | authenticated | Caller identity, role and permission list — the introspection point for a device |
GET /api/rates | rates.view | Live metal rates by metal and purity, in paise |
GET /api/products | products.view | Catalogue with variants, SKUs and barcodes; supports search and single-product fetch |
GET /api/stock | inventory.view | Stock levels per variant and branch |
GET /api/invoices | sales.view | Sales invoices with lines and payments, date-filtered |
GET /api/reports/sales | reports.view | Sales 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.
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.
| Suite | Files | What it pins down |
|---|---|---|
| Feature · Billing | 6 | Till screen, held bills, exchanges, credit notes, invoice service, till interactions |
| Feature · Catalogue | 4 | Products, categories, metal rates, CSV import |
| Feature · Purchases | 4 | Orders, goods receipts, metal bookings, purchase invoices |
| Feature · Reports | 5 | Sales, stock, purchases, party ageing, GSTR-1 and GSTR-3B |
| Feature · Settings | 5 | Users, branches, business profile, role matrix, audit log |
| Feature · Auth | 6 | Login, registration, reset, verification, confirmation, password update |
| Feature · Inventory / Parties / Rfid / Analytics / Api / Backups / Tenancy | 12 | Stock screens and counts, customer/supplier controllers, batches and scan sessions, both analytics cuts, the REST surface, backup isolation and restore, tenant resolution |
| Feature · Cross-cutting | 14 | RouteSweepTest (every parameterless GET), MiddlewareOrderTest, SecurityHardeningTest, BrandTest (no CDN), CompiledStylesheetTest, dashboard, demo seeder, install command, profile |
| Unit | 11 | Price 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.
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.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
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
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
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
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
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
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
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
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
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
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.
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.
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}/sheetHigh 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 decisionMedium 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 commandMedium 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.
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 commentMedium 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.
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 requestedLow 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.
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.
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.
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 pointLow 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.
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 revisionRepository 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 setup | install, key, migrate, npm build |
composer test | config:clear + full suite |
npm run dev | Vite HMR on :5173 |
npm run build | production assets |
php artisan serve | app on :8000 |
magetech:install-admin | first tenant + owner |
magetech:backup --auto | nightly archive + prune |
db:seed --class=DemoDataSeeder | full demo book |
vendor/bin/pint | style fix |
✓Definition of done
- Full suite green — 732 tests, 3,103 assertions
- Pint clean
npm run buildsucceeds andpublic/buildis committed- No external network request in devtools
- Every product photograph resolves on both origins
- Requirements spec, README and roadmap reconciled
| Term | Meaning in this system |
|---|---|
| Tenant | One business. Owns branches, users, settings and every row tagged with its tenant_id. |
| Branch / counter | Where stock sits and where a till opens. Branch carries its own document prefixes. |
| Counter session | A shift: opened with a float, closed against counted cash, with the variance shown. |
| Metal rate | Effective-dated price per metal per purity, append-only. Locked onto each bill line at creation. |
| Making charge | Workmanship, either a percentage of metal value or a fixed amount per piece. |
| GRN | Goods receipt note — the only place inbound stock moves, posted with an explained variance. |
| Metal booking | A rate-lock commitment with a supplier before goods arrive; settled exactly once at receipt. |
| Credit note | A return written against the bill it reverses, withdrawable back out of stock and off the account. |
| Party ledger | Append-only customer/supplier account. Balances are cached; entries are evidence. |
| Working sheet | GSTR-1/GSTR-3B computed for export, not filed directly. |
MageTech