Files
shopdb-flask/plugins/employees
cproudlock d8fe0a48b2
Some checks failed
CI / backend (push) Failing after 8s
CI / naming (push) Successful in 1s
CI / frontend (push) Successful in 8s
CI / migrations-mysql (push) Failing after 7s
Stop a stray click outside a modal discarding what was typed
Operators reported losing a part-filled form by clicking slightly outside it.
Every data-entry modal closed on a backdrop click with no warning and no way
back - the worst possible response to a misplaced click, and it happens most to
someone adding their first records at a new site.

Close-on-overlay is removed from 35 modals across 30 files: anything containing
an input, textarea, select or v-model. They still close by Cancel or the X.

Confirmation dialogs keep it, because a delete prompt holds nothing to lose and
dismissing one by clicking away is the behaviour people expect. VendorsList
shows the distinction - its edit form no longer closes that way, its delete
confirmation still does.

The shared Modal component now defaults closeOnOverlay to FALSE. Every current
caller holds a form, a checkout, a stock adjustment or a map position being
picked, and not one passed the prop, so all of them had the same fault. A modal
that genuinely wants dismissing that way opts in explicitly.

Also regroups the operator console menu, which had grown to numbers 1-9 plus
three letters bolted on with no order to them. Actions are now grouped by what
they touch, keyed by their first letter, and the old numbers still work so
nobody who has used it for months is stopped by a rearrangement.

The menu also warns when the server is not fully provisioned and names the key
that fixes it, instead of reporting it as ordinary status lines that read as
normal unless you already knew what to look for. That check is cached for the
session because it shells out to flask twice and the answer does not change
while somebody reads the screen.
2026-08-05 13:42:20 -04:00
..

Employees plugin - directory database contract

Read-only lookups against a separate employee/HR directory database. Powers:

  • Employee search (add people to a notification, browse the directory)
  • Single + batch SSO lookup
  • Recognition notifications (name + photo on the shopfloor / lobby displays)

The plugin never writes to this database. Use a read-only account.

This is the integration most likely to differ per site. HR / directory systems vary widely, so expect to map a site's schema to the contract below - the CREATE VIEW recipe at the end is the normal way to do it.

Connection

Credentials resolve settings-first, then environment, except the password which is env-only (never stored in the app database).

Field Setting key (editable in the setup wizard) Env var (fallback)
Host employee_db_host EMPLOYEE_DB_HOST
Database employee_db_name EMPLOYEE_DB_NAME
User employee_db_user EMPLOYEE_DB_USER
Password (not stored) EMPLOYEE_DB_PASSWORD

Set the password in .env; the setup wizard's Features step shows the exact line to paste. Engine is MySQL/MariaDB (pymysql).

Required schema

The plugin runs these queries verbatim, so a site's database must expose a table (or view - see below) named employees with these columns:

Column Type Notes
SSO INT Unique person id. Lookups require it to be numeric
First_Name VARCHAR Displayed as the given name
Last_Name VARCHAR Displayed as the surname; sort key
Team VARCHAR Team / group label
Role VARCHAR Job title / role
Picture VARCHAR Image filename (see Photos)

Queries actually executed:

-- search
SELECT SSO, First_Name, Last_Name, Team, Role, Picture
FROM employees
WHERE First_Name LIKE %s OR Last_Name LIKE %s OR CAST(SSO AS CHAR) LIKE %s
ORDER BY Last_Name, First_Name LIMIT %s;

-- single / batch
SELECT SSO, First_Name, Last_Name, Team, Role, Picture FROM employees WHERE SSO = %s;
SELECT SSO, First_Name, Last_Name, Team, Role, Picture FROM employees WHERE SSO IN (...);

Rows are returned to the API with these exact column names. The frontend (EmployeeSearch, NotificationForm, EmployeeDetail, ShopfloorDashboard) reads SSO, First_Name, Last_Name, Team, Role, Picture as-is - do not rename them in the response.

Photos

Picture holds an image filename (e.g. 123456.jpg), not a path or blob. The app renders it as /static/employees/<Picture>, so the image files must live in the app's static/employees/ directory. Leave Picture empty/NULL for people with no photo; the UI falls back to initials.

Two ways to provide the directory

Option A - map an existing HR/directory database (see the view recipe below). Use this when the site already has a system of record for people.

Option B - self-hosted directory (managed in-app) for sites with no HR database. Set employee_directory_mode to selfhosted and manage people under Settings > Employee Directory (add / edit / delete + CSV import) - no SQL needed. The app owns a directoryemployees table (migration 7d16); the lookup APIs read it automatically in this mode.

If you would rather load it with SQL, the canonical table is equivalent to:

CREATE DATABASE shopdb_directory CHARACTER SET utf8mb4;
USE shopdb_directory;

CREATE TABLE employees (
  SSO         INT          NOT NULL PRIMARY KEY,
  First_Name  VARCHAR(100) NOT NULL,
  Last_Name   VARCHAR(100) NOT NULL,
  Team        VARCHAR(100) NULL,
  Role        VARCHAR(100) NULL,
  Picture     VARCHAR(255) NULL
);

-- add people (or bulk-load from CSV with LOAD DATA INFILE)
INSERT INTO employees (SSO, First_Name, Last_Name, Team, Role, Picture)
VALUES (123456, 'Jane', 'Doe', 'Inspection', 'Quality Tech', '123456.jpg');

Put photo files (named as in Picture) under the app's static/employees/. In self-hosted mode the directoryemployees table lives in the main app DB, so no separate database is required.

Sites whose HR/directory database uses different table or column names should not be forced to rename anything. Instead, create a read-only view named employees that maps local columns to the names above:

CREATE VIEW employees AS
SELECT
  person_id        AS SSO,
  given_name       AS First_Name,
  surname          AS Last_Name,
  department       AS Team,
  job_title        AS Role,
  photo_filename   AS Picture
FROM hr_people
WHERE active = 1;

Grant the app's read-only user SELECT on the view. No app code changes - point employee_db_* at that database and the plugin works.

Notes:

  • SSO must be numeric (single/batch lookup validate isdigit()).
  • Column-name case follows your database's identifier casing; match the names above exactly on case-sensitive platforms.
  • If the directory is unreachable or the employees object is missing, lookups return a 500 and the rest of the app keeps working (the feature degrades, it does not crash the app).