# 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:

```sql
-- 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:

```sql
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.

## Adapting a different site schema (recommended: a view)

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:

```sql
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).
