e2e changes

This commit is contained in:
2026-09-16 17:09:04 +05:30
parent c06b029cb2
commit 2f1501883b
18 changed files with 1169 additions and 50 deletions

View File

@@ -39,11 +39,41 @@ inside a git repository. `.gitignore` excludes `*.sql` here for that reason.
## Getting something to test against
An empty schema boots but has no tenants, so there is nothing to sign in as.
Two options:
`nearledb/02-seed.sql` is committed and applied automatically, so a fresh
volume already has a merchant to sign into. It invents one rather than copying
one, which is why it can live here at all.
- **Onboard a tenant through the console** once it is pointed at localhost.
That exercises the real path and is usually what you want.
- **Copy a few rows** you actually need — a tenant, its locations, its
app_users — with `pg_dump --data-only --table=...`. Check what you are
copying: `app_users.password` is stored in clear.
| Account | Password | Opens |
|---|---|---|
| `super@nearle.invalid` | `localdev` | Nearle Admin — the platform workspace |
| `admin@testmart.invalid` | `localdev` | Store Admin — all of Testmart's branches |
| `main@testmart.invalid` | `localdev` | Store user — Testmart Main only |
It also seeds the role ladder, three aisles under category 2, and a second
merchant (`Halfmart`) deliberately left in the broken `categoryid = 0` shape as
a permanent regression fixture. The sequences are moved past the seeded ids at
the end, so the first row you create locally does not come back as id 1.
If you need something it does not cover:
- **Onboard a tenant through the console.** That exercises the real path and is
usually what you want.
- **Copy a few rows** you actually need with `pg_dump --data-only --table=...`.
Check what you are copying: `app_users.password` is stored in clear.
## The catalogue database
`cataloguedb/02-seed.sql` is committed too, and also entirely invented. The
real catalogue is another team's scrape of real retailers and a dump of it does
not belong on a laptop.
Without it the catalogue database exists but holds no catalogue: every
`brand_*` table is missing, `getbrands` answers 500, and the global catalogue
screen, the import flow and `importcatalogueproduct` cannot be exercised at
all. The seed gives you two brands:
- `brand_testbrand` — every column the reader knows about, four products, one
of them deliberately with no images.
- `brand_sparsebrand` — only `id`, `product_name` and a price, to keep the
degraded-but-still-listed path covered. Brands are discovered by table name,
so adding another is just another `brand_*` table.

View File

@@ -0,0 +1,109 @@
-- A synthetic global catalogue to develop against.
--
-- INVENTED DATA, exactly like `nearledb/02-seed.sql` and for the same reason:
-- the real catalogue is somebody else's scrape of real retailers, and a dump of
-- it does not belong on a laptop inside a git repository.
--
-- ── Why this file has to exist ──────────────────────────────────────────────
--
-- `init/cataloguedb/` was empty, so a local stack had a catalogue DATABASE with
-- no catalogue in it. Every `brand_*` table was missing, `getbrands` answered
-- 500, and the whole catalogue-import path — the global catalogue screen, the
-- import flow, `importcatalogueproduct` — could not be exercised locally at
-- all. It is a documented feature with its own integration doc and it had no
-- local coverage whatsoever.
--
-- ── The shape ───────────────────────────────────────────────────────────────
--
-- Brands are discovered from `information_schema` by table name, so a table
-- called `brand_<something>` IS a brand; there is no registry to add it to.
-- `catalogueCoreColumns` requires only `id` and `product_name` — everything
-- else is selected when present and replaced with NULL when absent, so a
-- partial table degrades rather than disappearing. These two are written full
-- so that the degraded path is a deliberate test, not the only thing available:
-- `brand_testbrand` has every column, and `brand_sparsebrand` deliberately has
-- only the core two plus a price, to exercise `columnsFor`.
--
-- `image_id` is the durable key across re-scrapes — catalogue ids are not
-- stable and `models.Products.Imageid` is what the import stores — so every
-- product here has one and they are distinct.
CREATE EXTENSION IF NOT EXISTS vector;
-- ── A brand with the full column set ────────────────────────────────────────
CREATE TABLE IF NOT EXISTS brand_testbrand (
id BIGSERIAL PRIMARY KEY,
product_name TEXT NOT NULL,
title TEXT,
description TEXT,
category TEXT,
image_id TEXT,
size TEXT,
variant_key TEXT,
product_sku TEXT,
sku_source TEXT,
-- A RANGE, not a price. The global catalogue carries what retailers were
-- seen charging; the shop sets its own price at import time, which is why
-- the console collects one before an import can be enabled.
price_range TEXT,
providers TEXT[],
fssai_license TEXT,
highlights TEXT[],
nutrients TEXT[],
search_query TEXT,
image_url TEXT,
image_urls TEXT[],
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
INSERT INTO brand_testbrand
(product_name, title, description, category, image_id, size, variant_key,
product_sku, sku_source, price_range, providers, fssai_license,
highlights, nutrients, search_query, image_url, image_urls)
VALUES
('Testbrand Basmati Rice 5kg', 'Testbrand Basmati Rice', 'Long grain basmati, aged twelve months.',
'Rice & Grains', 'IMG-TB-RICE-5K', '5 kg', 'rice-5kg', 'TB-RICE-5K', 'scrape',
'380-420', ARRAY['bigbasket','amazon'], '12345678901234',
ARRAY['Aged 12 months','Extra long grain'], ARRAY['Energy 350kcal','Protein 7g'],
'basmati rice 5kg', 'https://placehold.co/300x300?text=Rice5kg',
ARRAY['https://placehold.co/300x300?text=Rice5kg','https://placehold.co/300x300?text=Rice5kg-back']),
('Testbrand Basmati Rice 1kg', 'Testbrand Basmati Rice', 'Long grain basmati, aged twelve months.',
'Rice & Grains', 'IMG-TB-RICE-1K', '1 kg', 'rice-1kg', 'TB-RICE-1K', 'scrape',
'85-99', ARRAY['bigbasket'], '12345678901234',
ARRAY['Aged 12 months'], ARRAY['Energy 350kcal','Protein 7g'],
'basmati rice 1kg', 'https://placehold.co/300x300?text=Rice1kg',
ARRAY['https://placehold.co/300x300?text=Rice1kg']),
('Testbrand Sunflower Oil 1L', 'Testbrand Sunflower Oil', 'Refined sunflower oil, light and neutral.',
'Oils & Ghee', 'IMG-TB-OIL-1L', '1 L', 'oil-1l', 'TB-OIL-1L', 'scrape',
'150-185', ARRAY['bigbasket','jiomart'], '99999999999999',
ARRAY['Vitamin E','Light frying'], ARRAY['Energy 900kcal','Fat 100g'],
'sunflower oil 1 litre', 'https://placehold.co/300x300?text=Oil1L',
ARRAY['https://placehold.co/300x300?text=Oil1L']),
-- No images at all. `ImportCatalogueProduct` only sets `productimages` when
-- the product has photos, so this row is the one that proves an import still
-- works when it does not — the case that used to hit the jsonb empty-string
-- failure in `products`.
('Testbrand Salt 1kg', 'Testbrand Iodised Salt', 'Free-flowing iodised salt.',
'Everyday', 'IMG-TB-SALT-1K', '1 kg', 'salt-1kg', 'TB-SALT-1K', 'scrape',
'20-28', ARRAY['jiomart'], NULL,
NULL, NULL, 'iodised salt 1kg', NULL, NULL);
-- ── A brand with only the core columns ──────────────────────────────────────
--
-- Discovery used to demand all eighteen columns, which made a table like this
-- INVISIBLE rather than merely thin — 16 of 35 live brands were unreachable
-- from this side for exactly that reason. Keeping one here means the
-- degraded-but-listed path is covered by the seed and stays covered.
CREATE TABLE IF NOT EXISTS brand_sparsebrand (
id BIGSERIAL PRIMARY KEY,
product_name TEXT NOT NULL,
price_range TEXT
);
INSERT INTO brand_sparsebrand (product_name, price_range) VALUES
('Sparsebrand Biscuits 100g', '20-30'),
('Sparsebrand Tea 250g', '110-140');

View File

@@ -161,4 +161,72 @@ INSERT INTO productstocks (
(9504, 9001, 9102, 9301, NOW(), 'in', 12, 'Active')
ON CONFLICT (productstockid) DO NOTHING;
-- ── The platform operator ───────────────────────────────────────────────────
--
-- Without this there is nobody who can open the Nearle Admin workspace, which
-- is the one this console was built for first. `resolveRole` checks
-- `issuperadmin` BEFORE roleid — deliberately, because the flag is derived by
-- the server and a roleid is just a number in a row — so no amount of role 1
-- gets you in without it, and every local session landed in Store Admin
-- instead. The accounts above are one per role and this was the role they were
-- missing.
--
-- Not attached to either merchant in spirit, only in columns: a platform
-- operator has to carry a tenantid because the column is not nullable, and
-- nothing in the admin workspace reads it.
INSERT INTO app_users (
userid, authname, firstname, lastname, email, dialcode, contactno,
configid, roleid, password, tenantid, locationid, applocationid,
status, issuperadmin
) VALUES
(9299, 'super@nearle.invalid', 'Nearle', 'Operator', 'super@nearle.invalid',
'+91', '9000009999', 1, 1, 'localdev', 9001, 9101, 9001, 'Active', true)
ON CONFLICT (userid) DO NOTHING;
-- ── The role ladder ─────────────────────────────────────────────────────────
--
-- `getstaffs` LEFT JOINs app_roles for `rolename`, so an empty table is not an
-- error — every person on Users & access simply reads "—" where their role
-- should be. The ids are the ones the rest of the system already assumes:
-- 1 and 3 reach Store Admin, 4 is a branch manager, 7 and 8 are till accounts
-- and are excluded from every back-office query by the backend itself.
INSERT INTO app_roles (roleid, rolename, configid) VALUES
(1, 'Super admin', 1),
(3, 'Admin', 1),
(4, 'Manager', 1),
(7, 'Supervisor', 1),
(8, 'Cashier', 1)
ON CONFLICT (roleid) DO NOTHING;
-- ── Aisles under the category the customer app browses ──────────────────────
--
-- categoryid 2 is the only category the app lists, and the aisle a shopper
-- reads is the SUBCATEGORY. With none of these the sheet importer has nothing
-- to resolve a row's category against, so every imported product falls back to
-- subcategoryid 0 and lands under "Uncategorized".
INSERT INTO productsubcategories (subcatid, categoryid, tenantid, subcatname, status, sortorder)
VALUES
(9601, 2, 9001, 'Rice & Grains', 'Active', 1),
(9602, 2, 9001, 'Oils & Ghee', 'Active', 2),
(9603, 2, 9001, 'Snacks', 'Active', 3)
ON CONFLICT (subcatid) DO NOTHING;
-- ── Move the sequences past the seeded ids ──────────────────────────────────
--
-- Everything above inserts an explicit id, which does NOT advance the sequence
-- behind that column. So the first tenant, outlet or product created against a
-- fresh local database came back as id 1 — harmless here, but it means local
-- ids look nothing like the ones the same code produces in production, and a
-- seed that ever collides with a sequence value fails on a duplicate key.
--
-- `GREATEST(..., 1)` because setval refuses a value below the sequence minimum,
-- and a table the seed does not touch is legitimately empty.
SELECT setval('tenants_tenantid_seq', GREATEST((SELECT COALESCE(MAX(tenantid),0) FROM tenants), 1));
SELECT setval('tenantlocations_locationid_seq', GREATEST((SELECT COALESCE(MAX(locationid),0) FROM tenantlocations), 1));
SELECT setval('app_users_userid_seq', GREATEST((SELECT COALESCE(MAX(userid),0) FROM app_users), 1));
SELECT setval('products_productid_seq', GREATEST((SELECT COALESCE(MAX(productid),0) FROM products), 1));
SELECT setval('productlocations_productlocationid_seq', GREATEST((SELECT COALESCE(MAX(productlocationid),0) FROM productlocations), 1));
SELECT setval('productstocks_productstockid_seq', GREATEST((SELECT COALESCE(MAX(productstockid),0) FROM productstocks), 1));
SELECT setval('customers_customerid_seq', GREATEST((SELECT COALESCE(MAX(customerid),0) FROM customers), 1));
COMMIT;