Stop the till and Nearle Daily from sharing accounts

app_users is the only thing the two products have in common, and the code was
treating it as though it were the whole relationship. Both directions leaked.

Back-office roles were leaking into the till. PosRoleCanManageStaff returned
true for roleid 1 to 6, on the reasoning that somebody who already administers a
shop from a browser is not made less privileged by standing at the counter. That
sounds fine and is wrong: measured against live data it handed till-supervisor
powers to 68 accounts, 59 of them Nearle Daily Super admins, not one of whom is
the administrator of anybody's POS. Meanwhile the actual shop accounts carry
roleid 0 and were refused, so the mapping was backwards from intent in both
halves at once.

Till accounts were leaking into the application. GetStaffs is WHERE tenantid
with no role filter, so a Counter Cashier appeared in the tenant staff list
beside the delivery riders — a row every action on that page would fail against,
since a cashier has no app login, no rider shift and no back-office screen.

So: eligibility for a till is now granted explicitly by provisioning a
Supervisor or a Cashier, never inherited from a back-office role, and roles 7
and 8 are excluded from every Nearle Daily lookup. The exclusion lives in the
queries rather than in a check after them, because a check bolted on afterwards
has to be repeated at six call sites and is one edit away from being forgotten
at one of them — and that one would be the hole. A till account is not rejected
by the app login; it is not found.

Two things this surfaced that were not visible before.

A Supervisor could not open a till. PIN sign-in needs a session that already
exists, so once back-office roles were refused, an outlet whose only POS
accounts were PIN-only had no way in at all. Supervisors are now provisioned
with a username and password as well as a PIN; cashiers deliberately get neither,
because they sign on at a counter somebody has already opened and a second
password would be one more credential to leak for no capability gained.

UpdatePosUser silently dropped authname. It wrote the password, reported
success, and left the account unreachable by either lookup — the failure
surfaced at a counter as "not recognised" rather than on the screen that caused
it. Contactno had the same gap.

Verified against live rows rather than asserted, by scratch/posseparation: a
provisioned supervisor signs in and gets the supervisor shell; five real
back-office accounts including Super admins are refused; the supervisor is
invisible to applogin, tenant weblogin and the password-setup lookup; and no
till account appears in getallusers, while asking for role 7 by name still
returns them so the console can read its own people.

All five outlets that stock products now have a Supervisor and a Cashier.

Also moves the loose markdown into docs/, which was already staged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Suriya
2026-08-07 11:52:23 +05:30
parent f5e16b54cc
commit c0a7fbc1b1
15 changed files with 508 additions and 32 deletions

View File

@@ -393,21 +393,37 @@ func PosRoleFromName(name string) int {
return 0
}
// PosRoleEligible reports whether a role may open a till at all.
//
// The terminal and the Nearle Daily application share one `app_users` table,
// and that is the only thing they share. An account belongs to one product or
// the other and never to both: a person who administers a shop from a browser
// does not thereby get a cash drawer, and a cashier does not thereby get the
// back office.
//
// Eligibility is therefore granted explicitly — by provisioning a Supervisor or
// a Cashier from the console — and is never inherited from a back-office role.
// Anything else is refused at sign-in, including roleid 0, which is not a role
// but the absence of one.
func PosRoleEligible(roleID int) bool {
return roleID == PosRoleSupervisor || roleID == PosRoleCashier
}
// PosRoleCanManageStaff reports whether a role may create and edit till users.
//
// Supervisors, plus the back office's own admin and manager roles — somebody
// who can already administer the shop from a browser is not made less
// privileged by standing at the counter.
// Supervisors, and nobody else.
//
// A cashier is never included, and neither is roleid 0. Zero is not a role: it
// is what an account carries when nobody set one, and live data has riders and
// shop accounts sharing it.
// This used to include the back office's own roles 1 to 6, on the reasoning
// that somebody who can already administer a shop from a browser is not made
// less privileged by standing at the counter. That was wrong, and live data
// showed how wrong: it handed till-supervisor powers to 68 accounts, 59 of them
// Nearle Daily Super admins, not one of whom is the administrator of anybody's
// POS. The actual shop accounts carry roleid 0 and were refused.
//
// The back office reaches the till by *provisioning* a supervisor from the
// console, not by becoming one at the counter.
func PosRoleCanManageStaff(roleID int) bool {
switch roleID {
case PosRoleSupervisor, 1, 2, 3, 4, 5, 6:
return true
}
return false
return roleID == PosRoleSupervisor
}
// PosUser is a person who signs in at a till.