# Business rules — webportal (Aloecate)

Evidence date: 2026-09-28. Source: `master` at `0cd0456`.

Rules below are taken from code and SQL. Each inferred rule is marked **Inferred**. Nothing here is a new product decision.

## Identity and access

1. A staff password is stored as SHA-256 of the plaintext. Login compares `hash('sha256', posted password)` to `users.password`. **Implemented** (`Users_model::attempt`).
2. A staff account with `status` other than `'1'` cannot sign in. Message: "You are not allowed to Login ! Contact Admin". **Implemented**.
3. Staff username or email must already exist. Minimum length 5. Password minimum length 6. **Implemented**.
4. Remember-me keeps cookies for 7 days. **Implemented**.
5. Every successful staff login sets the user language to `es`. **Implemented** (`setUserlang('es')`). **Inferred** that this is accidental rather than a business choice. **Requires Confirmation**.
6. Google reCAPTCHA runs on staff login only when `settings` key `google_recaptcha_enabled` is `1`. **Implemented**.
7. App-user passwords are stored as the posted `pass` value. No hash call is in `User_model::insert` or `update_password_cell`. **Implemented**.
8. An app user cannot register twice on the same `cellnumber`. Message: "This mobile number has already been used". **Implemented**.
9. Registration requires name, surname, cellphone, password, location, and terms accepted. **Implemented**.
10. A new app user is unverified (`verified=0`) until `cellnumber` and the 4-digit `code` match. Login requires `verified=1`. **Implemented**.
11. The one-time code is `rand(1111, 9999)` (or `strtoupper` of that on register, which does not change digits). **Implemented**. **Inferred**: this is a 4-digit numeric OTP, not a long random token.
12. The mobile API does not require an API key or a logged-in session. `rest_auth` and `rest_enable_keys` are false, and CORS allows any origin. **Implemented**. **Inferred** risk: anyone who can reach the host can read `GET api/user` and act on an id. Human review required. This document does not describe how to call that.
13. Staff menu entries for app users, activity log, and the settings tree are hidden without the matching permission. Several other entries (items, engine, transactions, receipts, cash-out, SMS, referrals) are always rendered. The controllers behind items, engine, transactions, cash-out, messages, and jobs still check `user_list` on the list action. **Implemented**. Receipts check `reciept_files`.
14. `jobs_list` exists as a permission code in the June 2022 dump. `Jobs::index` checks `user_list` instead. **Implemented**. **Inferred** mismatch.

## Registration, referral, and balance

15. The system assigns a 5-character referral code from digits and lowercase letters. The posted `referral_code` is stored as `referred_code` (the code this user claims to have used). **Implemented**.
16. Staff `addpoint` adds 3 to the referred person's balance and, on success, marks the other user `processed=1` and sends "you have been rewarded". **Implemented**. **Inferred** rule: a successful referral is worth 3 currency units. The unit (rand or points) is not named in code.
17. No controller was found that credits `balance` from a slip rebate. Uploading a slip writes a transaction of amount 0. **Implemented**.
18. `submit_slip_post` stores rebate 10 and amounts 10 on a new `aloecate_items` row. **Implemented**. **Inferred**: 10 is a placeholder, not a confirmed commercial rate. **Requires Confirmation**.
19. Cash-out request does not change the balance and does not reject an amount above the balance. **Implemented**.
20. Processing a cash-out subtracts the posted amount from the balance, stores that amount as `paid_out_amount`, sets `processed=1` and `process_date`, and writes transaction type `cashout_process`. **Implemented**.
21. There is no minimum cash-out, no fee, and no check that the user has bank details at request time. Bank fields are updated separately via `api/user_update`. **Implemented**. **Requires Confirmation** of the real payout policy.
22. Balance is an integer column. Transaction amounts are integers. **Documented** in `db/update.5.liam.sql` and the `app_user` dump.

## Slips, items, and the engine

23. A receipt file is listed for staff only when `file.type=0` and the created year is 2023. **Implemented** (`Files::index`). **Inferred** defect relative to a portal still in use after 2023. **Requires Confirmation**.
24. Cash-out list uses the same year filter on `cashout.created_at`. Unprocessed cash-outs on the dashboard do not use the year filter (`processed=0` only). **Implemented**.
25. An engine rule is stored with status `active` and fields product, location, date_from, date_to, rebate_amount. **Implemented**.
26. Search treats location as `app_user.location` and product as a `LIKE` on `aloecate_items.product`. **Implemented**. Job creation overwrites `location` with the employer name, so later engine searches by venue can miss users who added a job. **Inferred** interaction from the two writes.
27. Posting a rebate sets the item `processed=1`. It does not pay the user. **Implemented**.
28. Dashboard "items scanned" counts `aloecate_items` where `processed=1`. "Total" counts rows where `processed != 10`. The percentage shown is total divided by scanned, times 100. **Implemented**. **Inferred** defect.
29. Textract is not part of the upload. A staff user must open `files/process/{id}`. The response is dumped to the browser and not saved. **Implemented**.

## SMS and messages

30. Registration SMS body is fixed: "Welcome to aloecate. Here is your onetime code:" plus the code. **Implemented**.
31. Password-reset SMS body is fixed: "Aloecate: Here is your reset password code: " plus the code. **Implemented**.
32. Cash-out SMS body is fixed: "Hi {name}. Your cashout request is being processed". The number is rewritten from a leading 0 to country code 27. **Implemented**.
33. Broadcast SMS uses the body the staff member typed. Numbers are rewritten the same way. A row is marked processed even if BulkSMS does not return 201. **Implemented**.
34. Cellphone validation on login and reset is 10 to 13 digits. Registration only requires a non-empty cell number. **Implemented**.
35. `include_sender` stores a message to the staff `users.id` and looks up `users.cellnumber` because `is_backend_user=1`. The `users` table in the dump has `phone`, not `cellnumber`. **Implemented** lookup. **Inferred**: the staff copy can fail at runtime if `cellnumber` is absent. **Requires Confirmation** against the live table.

## Data and tenancy

36. SQL scripts name the database `aloecate`. **Documented** in `db/update.4.liam.sql`, `db/update.5.liam.sql`, and the dump header.
37. One deployment, one database. No tenant id is on the tables that were read. **Inferred** from the schema.
38. App user `role` defaults to 4. The dump titles role 4 "App-User". **Documented** in SQL. Staff permissions are a separate `users.role`.
39. `terms_accepted` must be posted at registration. The column default in the dump is 0. **Implemented** / **Documented**.

## Audit

40. Staff login, slip detail upload, rebate processing, cash-out processing, and message create write `activity_logs` with a type string. **Implemented**.
41. IP address is an argument of `Activity_model::add` and defaults when omitted. The exact default is in the model. **Implemented**.

## Secrets and personal data (constraint, not a product rule)

42. BulkSMS username and password are hardcoded in `Api.php`, `Messages.php`, `App_user.php`, and `basic_helper.php`. Values are omitted. **Implemented**. Human review: rotate and remove from git.
43. `db/FullDBdump17062022.sql` contains personal and banking data and password material. **Documented** by the file's presence. This rules document does not copy rows. Human review: treat the file as sensitive.
44. `db/starter.sql` contains a seeded admin password hash and reCAPTCHA keys from the AdminPRO template. Values omitted.

## Conflicts inside the implementation

- Engine flash message says a message was created. **Implemented** copy does not match the action.
- `Cashout::view` renders a receipt. **Implemented** action does not match the menu label.
- `staging` pipeline variables are named as production credentials. **Implemented** in `bitbucket-pipelines.yml`. **Requires Confirmation** of which AWS account that branch deploys to.
- Comment in `aws_helper.php` says parameter-store region `af-south-1`. `get_secret` uses `eu-west-1`. **Implemented** code wins over the comment unless a human says otherwise.
