# WBMCZ — deploy notes · 06 Sep 2026
## One page per category, three new desks, feature-level access

Everything here is additive. **No table is created, no column is added, no column
is dropped.** The only database write the migration can make is filling in a
missing `service_categories.category_key`.

---

## 1 · UPLOAD LIST

### New files

    includes/product_pages.php                     names the per-category pages, creates missing ones
    admin/includes/product_engine.php              the catalogue screen, shared by every category page
    admin/includes/pending_services_engine.php     the submission screen, shared the same way

    admin/product_all.php                          the whole catalogue on one screen
    admin/product_cat.php                          fallback door, ?cat=N

    admin/product_gov_portals.php                  category 1
    admin/product_imp_tools.php                    category 2
    admin/product_service_booking.php              category 3
    admin/product_print_order.php                  category 4
    admin/product_agency_software.php              category 5
    admin/product_cyber_accessories.php            category 6
    admin/product_online_courses.php               category 7
    admin/product_web_dev.php                      category 8
    admin/product_digi_marketing.php               category 9
    admin/product_shopping.php                     category 10
    admin/product_b2b_shopping.php                 category 11

    admin/service_gov_portals.php                  submissions, category 1
    admin/service_imp_tools.php                    …2
    admin/service_service_booking.php              …3
    admin/service_print_order.php                  …4
    admin/service_agency_software.php              …5
    admin/service_cyber_accessories.php            …6
    admin/service_online_courses.php               …7
    admin/service_web_dev.php                      …8
    admin/service_digi_marketing.php               …9
    admin/service_shopping.php                     …10
    admin/service_b2b_shopping.php                 …11

    productsplitmigration.php                      run once, then delete

The 22 per-category files can also be left out of the upload — the migration
writes them from the catalogue. Uploading them is faster and works even on a
host where `admin/` is read-only.

### Changed files

    includes/acl.php                one permission per category, plus subcat / import
    includes/pipeline.php           three new desks
    admin/includes/sidebar.php      the menu lists categories instead of two links
    admin/product_management.php    now three lines
    admin/product_management_gov.php now three lines
    admin/pending_services.php      now three lines
    admin/service_import.php        accepts a locked category and a page to return to
    admin/services.php              writes a new category its two pages

### Order

1. `includes/` first — `product_pages.php`, `acl.php`, `pipeline.php`
2. `admin/includes/` — the two engines and `sidebar.php`
3. the rest of `admin/`
4. `productsplitmigration.php`, open it once signed in as admin, then delete it

Uploading `admin/` before `includes/` gives a page that cannot find
`pmp_file()`; it degrades to the old-style links rather than breaking, but the
order above avoids even that.

---

## 2 · WHY THE CATALOGUE IS SPLIT

One `product_management.php` filtered by category meant one permission for the
whole catalogue: give somebody the product list and they have every category in
it. Now each category is its own file with its own permission, so
*"you look after Government Portals"* is something the system can actually
express.

The screen itself is not copied. All 13 catalogue pages include
`admin/includes/product_engine.php`, and all 12 submission pages include
`admin/includes/pending_services_engine.php`. A column added once appears on all
of them; a bug fixed once is fixed on all of them. Each page file is about
fifteen lines, and all but two of those are comment.

**The category is fixed by the file, never by the query string.** There is no
`?cat=` on `product_shopping.php` that turns it into the seller list. That is
what makes the narrow permission mean something.

### A category made next month

`admin/services.php` calls `pmp_sync()` on every load, so creating a category
writes its two pages there and then, and the menu shows them immediately. If the
`admin/` folder is read-only the pages cannot be written — that is not an error:
the menu falls back to `product_cat.php?cat=N` and `pending_services.php?cat_id=N`,
which show the same screens but need the whole-catalogue permission. The category
screen says so in a banner when it happens.

---

## 3 · EVERYTHING FOR A CATEGORY, IN ONE PLACE

Each catalogue page now carries three buttons at the top, next to Filters:

* **Add one** — a single product, straight onto this shelf. This did not exist
  in the admin panel before; the office had the CSV import and nothing else, so
  adding one portal meant writing a one-row CSV. The category is not asked for,
  because the page already knows it.
* **Bulk upload** — the CSV panel, with the category already fixed. It hands over
  to `service_import.php` for the row-by-row preview, which shows the category as
  a locked label rather than a menu, and comes back to the page you started from.
* **Sub categories** — create, rename, hide and remove, showing only this
  category's groups with a count of what is using each. Removing a sub-category
  that still holds products moves those products back to *no sub category* rather
  than leaving them pointing at nothing, and says how many it moved.

`services.php` still has the old combined sub-category list. Nothing was taken
away.

---

## 4 · MONEY TILES ONLY WHERE THERE IS MONEY

Government Portals showed five tiles — Seller Paid, Subscriber Savings, Referral
Paid, Staff Payable, Company Profit — all reading ₹0.00, for ever, because a
portal is free.

Every category page now leads with counts: **Products · Live · Off/pending ·
Sub categories · Needs KYC**. The five money tiles are drawn underneath only when
the category actually charges for something; where it does not, a single line
says why there is no money row.

Whether a category counts as free is read from the catalogue, not hard-coded:
Government Portals always is, and any other category counts as free once it holds
products and not one of them carries a price. An **empty** category counts as
paid, so the first product added to it can still be given a price.

> Worth a look: Government Portals holds 12 products and **one of them has a
> price on it**. The page still treats the category as free — that is deliberate,
> category 1 is free by definition — but if that price is a slip it is worth
> clearing.

---

## 5 · THREE NEW DESKS IN ACTIVITY & TASK

On the **Office** tab, in the order money moves:

| Desk | Shows | Accept does |
|---|---|---|
| **Visit Collection** | orders that need somebody to go out — with what is still owed | closes the visit (`visit_status = done`) |
| **Verify Collection** | cash a seller says they took, not yet checked | marks the collection approved, with who approved it and when |
| **Seller Dues** | one row per seller still holding money, worked out from approved collections less what has come in | records the hand-over in `seller_settlements` |

Two deliberate limits:

* **Visit Collection never records a payment.** Ticking it closes the visit and
  nothing else. Money actually handed over is entered by whoever took it, on the
  order screen. A tick on a desk cannot invent a payment.
* **Seller Dues works the amount out itself.** It is exactly what is outstanding
  at that moment, not a number typed into a box, so the ledger cannot drift. The
  remark becomes the reference (cash, UPI, receipt number).

All three are assignable exactly like the other desks — the access matrix picks
them up on its own, so an admin can give Visit Collection to one person and
Seller Dues to another and neither sees the other's work.

---

## 6 · FEATURE-LEVEL ACCESS

A button whose permission has not been granted **is not drawn**. It is not greyed
out and it is not disabled — it is not in the page, and the POST behind it is
refused as well, so it cannot be reached by hand either.

| Button | Key |
|---|---|
| Add one | `products.create` / `gov_products.create` |
| Bulk upload | `products.import` / `gov_products.import` |
| Sub categories | `products.subcat` / `gov_products.subcat` |
| Delete Selected | `products.delete` / `gov_products.delete` |
| Export CSV | `products.export` / `gov_products.export` |

### New permission keys on the Roles screen

    products.view_imp_tools           products.view_web_dev
    products.view_service_booking     products.view_digi_marketing
    products.view_print_order         products.view_shopping
    products.view_agency_software     products.view_b2b_shopping
    products.view_cyber_accessories   products.subcat
    products.view_online_courses      products.import
    gov_products.subcat               gov_products.import

Government Portals keeps its own module, `gov_products`, so it has no
`products.view_gov_portals` — `gov_products.view` is that key.

Two permissions open a category page: the broad `products.view` for the whole
catalogue, or the category's own. **Nobody loses anything**: an account that has
`products.view` today still opens every category page tomorrow. The per-category
keys only make it possible to give less.

---

## 7 · CHECKED BEFORE HANDOVER

* every changed and new file passes `php -l`
* all 31 admin pages load with no fatal, no warning and no notice, and redirect
  to the staff sign-in
* all 22 per-category files exist and resolve to the right category and key
* 360 permission keys in the registry, including the 14 new ones
* all 13 desks answer their count query without error; the three new ones render
  4 of 4 cells against real and shaped rows
* the free/paid rule was run over all 11 categories and gives the expected answer
  for each

What is **not** checked, because it needs an admin session: the screens behind
the sign-in. Worth clicking once — Add one, Bulk upload and Sub categories on
`product_gov_portals.php`, and the three new desks on the Office tab.

---

## 8 · STILL OPEN FROM BEFORE

The OTP text still ends with *"This SMS has been sent from GuestRAR"* — the
reseller's brand. Sending under your own name needs your own DLT entity, header
and approved templates. No code change: only the Sender ID box and the two
template boxes on the SMS screen.

`withdrawal_requests` does not exist in this database, so the **Wallet** and
**Payout** desks will always read 0. They are written against that table and will
start working the day it is created; nothing errors in the meantime.
