require a mobile number when creating a till account

A till signs in with a mobile number and a PIN, but createposuser still
accepted an account without a number. Such an account cannot reach the
sign-in screen at all, and the failure surfaces at a counter in front of
a queue rather than at the point of creation. Enforced in CreatePosUser,
so both doors are covered: POST /pos/users from the terminal and
createposuser from the console share that path.

Two checks, not one. normalisePosPhone answers ("", nil) rather than an
error for a value holding no digits, so "abc" would have passed an
emptiness check, then been written as a blank and skipped the uniqueness
check below it — which is the hole this closes.

Scope is new accounts only. The column stays nullable and UpdatePosUser
still reads an empty contactno as "leave alone", so the accounts that
predate the number keep working through the backfill and cannot have
theirs cleared. The PIN stays optional at creation.

Also in this change:

- docs: correct both phone-login handovers, which claimed creation
  already required a number. The sequencing note in the PIN handover
  said step 2 was a backfill that could never be finished; it now is
  one, and POS_LOGIN.md says which half of the pair creation enforces.

- docs: remove credentials from the examples. POS_PHONE_LOGIN_HANDOVER
  carried a real-looking back-office pair and a generated till password,
  and POS_LOGIN.md a second one.

- posController.Staff: the comment justified scoping by token because
  "the answer carries PINs". It has not since the PIN left the wire. The
  scoping is still right for a different reason, which the comment now
  gives.

- scratch/posstaffsetup: takes both mobile numbers as arguments and
  refuses to run without them. Generating stand-ins would have produced
  exactly what this change prevents. Validated before the database is
  opened, in plan mode too, so a dry run cannot print a plan that apply
  would reject halfway through and leave half a shop set up.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Suriya
2026-08-12 17:26:41 +05:30
parent d566ca5591
commit ca846f9cd6
7 changed files with 157 additions and 20 deletions

View File

@@ -278,6 +278,11 @@ in at seven is as often the cashier as the supervisor.
other cannot sign in. A username and password still work, and every account
created before this still has them.
Creation enforces half of that: `POST /pos/users` and `createposuser` refuse a
request with no mobile number, or one that holds no ten-digit number. The PIN
stays optional at creation — an account can be provisioned before somebody has
chosen one — so it is the half still worth checking before a shop goes live.
So a Cashier signs in exactly like a Supervisor does, and the *role* decides
what they get — not which credential they used:
@@ -292,7 +297,7 @@ password **once**, in the creation response only:
```json
{ "user_id": 1452, "role": "Cashier",
"authname": "cashier.1185@pos.nearle.in",
"password": "9tWx2KUJksM5Rm", "pin": "4513", "has_password": true }
"password": "<14 generated characters>", "pin": "4513", "has_password": true }
```
`GET /pos/users` never returns a password, only `has_password`. An admin who

View File

@@ -35,10 +35,10 @@ What changed is behaviour behind it.
```jsonc
// Sign in by mobile — the new way
{ "contactno": "9876543210", "password": "xHegDaH55ccWic" }
{ "contactno": "9876543210", "password": "…" }
// Sign in by username — still works, unchanged
{ "authname": "cashier.1135@pos.nearle.in", "password": "xHegDaH55ccWic" }
{ "authname": "cashier.1135@pos.nearle.in", "password": "…" }
```
`authname` wins if both are sent. The response is unchanged.
@@ -65,7 +65,7 @@ rejection. That did not change.
"full_name": "Priya Raman",
"role": "cashier",
"pin": "4731",
"contactno": "9876543210", // NEW — the sign-in number
"contactno": "9876543210", // NEW — the sign-in number, and required
"shift_id": 3 // NEW — optional, 0/omitted = unassigned
}
```
@@ -164,7 +164,8 @@ locked out on the next app update.
1. Backend deploys. *(Nothing changes for the app — username login is untouched.)*
2. Back office adds a mobile number to every existing till account through the
console. New accounts already require one.
console. New accounts already require one — `createposuser` refuses a
request without it.
3. **Only then** the app makes mobile the primary sign-in field.
**Recommendation for the app:** keep both. One field labelled *"Mobile number or
@@ -215,6 +216,6 @@ curl -s -X POST "$B/pos/login" -H 'Content-Type: application/json' \
# a back-office account is still refused with the specific message
curl -s -X POST "$B/pos/login" -H 'Content-Type: application/json' \
-d '{"authname":"rmart@gmail.com","password":"rmart@123"}'
-d '{"authname":"<a back-office account>","password":"…"}'
# expect: 403 "this account is not set up for the till"
```

View File

@@ -182,7 +182,17 @@ out on the next update.
still work, unchanged.
2. **Back office fills in a mobile number and a PIN on every existing till
account** through the console (`PUT /web/tenants/updateposuser`). New
accounts already require a number.
accounts cannot be created without a number — `createposuser` refuses one
with `400 "a mobile number is required…"`, so this is a finite backfill of
the accounts that predate the rule rather than a gap that keeps reopening.
**A PIN is still optional at creation**, so an account can be created that
cannot yet sign in. It is told so by name at the counter — see the `403` in
§6 — but the check in step 2 below is what catches it first.
Editing is unaffected: an update that does not mention `contactno` leaves the
stored number alone rather than clearing it, so a partial edit cannot strand
somebody mid-backfill.
3. **Only then** does the app make number-and-PIN the primary sign-in.
**Recommendation for the app:** ship the new screen, and keep a small