# OB/GYN Clinic System — Complete Technical Analysis (Companion to obgy-erp-analysis-report.html)

> **Purpose:** Machine-consumable technical companion for Claude Code. Produced 2026-06-10 by a 21-agent
> parallel analysis of the legacy OB/GYN clinic system at `/home/amrtechogate/public_html/obgy`
> (PHP "aw framework" + Smarty + RedBeanPHP, MySQL `obgy_12-7-2024.sql`, 312 tables, zero declared FKs).
> Use this file as the source-of-truth context when implementing the new `Modules/Obgy` module in Moon ERP
> (Laravel nwidart modules + Angular). Coverage: all 312 tables documented; all relationships inferred
> from column naming and verified against controller SQL.

## Index

| # | Module | Section |
|---|--------|---------|
| 00 | Technical Architecture | aw framework, routing, data layer, security posture, tech debt |
| 01 | Patients & Registration | patient/spouse demographics, files, records |
| 02 | Visits & Appointments | booking, periods, queue, visit-money coupling |
| 03 | Antenatal Care (ANC) | pregnancy sheets, visits, EDD, EPC pregnancy loss |
| 04 | Gynecology | gyna sheets, menstrual/local-exam lookups |
| 05 | Infertility | couple-centric sheets, treatment lookups |
| 06 | IVF / ICSI / IUI | cycle monitoring sheet + 10 children, protocols |
| 07 | Andrology & Semen | semen analyses, hormonal profiles, duplicate tables |
| 08 | Ultrasound & Imaging | OB/gyna US, TVS, HSG, 4D, media storage |
| 09 | Laparoscopy & Hysteroscopy | per-organ findings, lookup decode |
| 10 | Operations & Deliveries | waiting list, operative notes, histopath |
| 11 | Clinical Examination & Diagnosis | per-system exam, Q&A engine, diagnosis catalogs |
| 12 | Patient Medical History | ph* tables: menstrual/obstetric/contraception/past |
| 13 | Follow-up & Cards | follow-up records, printable cards |
| 14 | Investigations & Lab Orders | catalog, per-sheet orders, EAV pattern |
| 15 | Pharmacy & Drugs | catalog, store, purchase/sale bills |
| 16 | Financial & Reports | balances, payments, report subsystem |
| 17 | Medical Board & Sessions | case discussions, opinions |
| 18 | Platform, Auth, Roles & Settings | aw* RBAC, branches, mobile API, screen app |
| 90 | Master ERD | hub entities, full relationship list, design patterns |
| 95 | ERP Migration Blueprint | 312→~85 tables, model mapping, phases, risks |

---


# 00 — Technical Architecture Deep-Dive (obgy clinic system)

Audit scope: `/home/amrtechogate/public_html/obgy` (referred to below as `obgy/`).
Audience: engineering input for the management migration report (legacy PHP → Laravel + Angular ERP).

---

## 1. System Overview

A custom, in-house PHP framework self-described as **"awframework from aw.inc"** (header comment present in nearly every controller; artifacts date to ~2015, e.g. the seed admin user registered `2015-01-25`). No standard framework, no Composer, no autoloading, no version control artifacts, no tests.

Four sub-applications share one MySQL database and one shared library directory:

| Sub-app | Path | Controllers | Purpose |
|---|---|---|---|
| core | `obgy/core/` | 70 PHP files, ~37,878 lines total | Main clinic: patients, visits, clinical sheets, financial reports, settings, backups, mobile API |
| pharmacy | `obgy/pharmacy/` | 6 (`buys, drugs, receipt, store, stuff, test`) | Pharmacy/stock |
| board | `obgy/board/` | 4 (`requests, sessions, stuff, test`) | Teaching board |
| screen | `obgy/screen/` | 1 (`index.php`) | Waiting-room queue display; directly `require`s `core/controllers/imp/` |

Shared:
- `obgy/_library/` — `db_main/rb.php` (ORM), `login_system/`, `php-jwt-master/`, `smarty_plg/`, `plugins/` (Breadcrumbs, classupload, PHPMailer).
- `obgy/_public/aw_config.php` (template config) + `api_config.php`.
- `obgy/upload/` — 3.2 GB total; `upload/sonar` alone is 3.1 GB (1,846 files). Inside web root.
- `obgy/_db/` — three full SQL dumps in web root (`obgy_12-7-2024.sql`, `obgy_app_20-04-2024.sql`, `obgy_app_22-12-2021.sql`).

Database: `amrtechogate_obgy` — **312 tables** (307 InnoDB, 5 MyISAM — including `awusers`).

Web server config: root `.htaccess` sets `DirectoryIndex core/controllers/index.php`, error documents, `Options -Indexes`, and `RewriteEngine off` (no rewriting/no protective rewrite rules).

---

## 2. The "aw framework"

### 2.1 Routing — direct file access + `?ac=` dispatcher

There is **no front controller**. Every screen is a standalone file hit directly, e.g. `/obgy/core/controllers/patients.php?ac=search`.

Dispatcher — `obgy/core/controllers/imp/_imp.php` (entire routing layer, 23 lines):

```php
$action = filter_input(INPUT_GET, 'ac');
if (!$action) { $action = 'index'; }
$call_func = new Controllers();
if (method_exists($call_func, $action)) {
    $call_func->$action();
} else {
    $call_func->index();
}
```

Implications:
- **Every `public` method of the controller class is URL-invokable** — there is no action allow-list, no HTTP-verb discrimination, no CSRF token anywhere.
- All 81 controller files define a class with the *same name* `Controllers` (works only because each file is its own entry point).

### 2.2 Controller pattern

Each controller (`obgy/core/controllers/_controll.php` is the canonical example) repeats ~60 lines of boilerplate in `__construct()`:
1. `require_once("../public/aw_config.php")` → DB connect + Smarty instance.
2. `require_once("imp/_autho.php")` → static `autho` class (login + RBAC check).
3. Breadcrumbs plugin, sets `$active[]` menu-highlight array.
4. `require_once` of `_header.php`, `_sidebar.php`, `_leftsidebar.php`, `_footer.php` — each defines a static class (`header::headershow()`, `sidebar::sidebarshow()`, etc.).
5. `__destruct()` calls `R::close()`.

Each action method follows the template:
```php
autho::checkautho($this->hosturl);                                   // login check
autho::checkauthoize($this->controllname, $this->hosturl, $_SESSION['role_id']);  // RBAC
// ... RedBean queries, $this->smarty->assign(...)
header::headershow($this->smarty, $this->hosturl);
sidebar::sidebarshow($this->smarty, $this->hosturl, $this->active);
$this->smarty->display('xxx/yyy.html');
leftsidebar::leftsidebarshow(...); footer::footershow(...);
```

### 2.3 View binding

- **Smarty 3.1.11** (`const SMARTY_VERSION = 'Smarty-3.1.11'` in `obgy/_library/smarty_plg/libs/Smarty.class.php`; released 2012). Plus `SmartyPaginate.class.php` and `modifier.mb_truncate.php`.
- Templates in each sub-app's `views/`, compiled to `temp/`. Pages are rendered piecemeal (head → header → sidebar → content template → left sidebar → footer), all server-side.
- No default output escaping configured — XSS exposure depends on per-template discipline.

### 2.4 Permission model (`awcontroll` / `awrole*`)

A genuinely well-designed, fine-grained DB-driven RBAC:

| Table | Role |
|---|---|
| `awcontroll` | Registered screen/controller (`name`, `label`) |
| `awcontrollprop` | Action within a screen (`proplabel` = the `?ac=` value, `checkval` = default allow) |
| `awrole` | Roles |
| `awrolecontrollprop` | role × action permission (`checkval`) |
| `awmenu` / `awrolemenu` | Hierarchical menu per role (sidebar built at runtime from `awmenu`) |
| `awrolebtn` | UI button visibility per role |
| `awusers.role_id` | Role assignment |

Enforcement — `obgy/core/controllers/imp/_autho.php`:
- `checkautho()` instantiates `Login` (`_library/login_system/classes/Login.php`) and redirects to login if `isUserLoggedIn()` is false (session-based).
- `checkauthoize()` resolves controller name → `awcontroll` → `awcontrollprop` for current `?ac=` → checks `awrolecontrollprop` for `$_SESSION['role_id']`, falling back to the prop's default `checkval`; redirects to `error.php?ac=autho` on denial.

**Weakness:** enforcement is *opt-in per method*. Several actions skip `checkauthoize` (e.g. `index.php::onesetup()` — a destructive cleanup that trashes menus/roles — only checks login), and `mobileservices.php` has **all auth checks commented out** (see §4).

---

## 3. Data Layer

- **ORM: RedBeanPHP 4.3** — `obgy/_library/db_main/rb.php`, single 12,380-line file; `const C_REDBEANPHP_VERSION = '4.3'` (line 9759). Circa 2016; current is 5.x.
- Bootstrap in `obgy/_public/aw_config.php` (duplicated into each sub-app's `public/aw_config.php`):

```php
R::setup('mysql:host=localhost;dbname=amrtechogate_obgy', 'amrtechogate_obgy', '53i#WRqAw8r121618');
R::exec('SET NAMES latin1');
R::freeze(FALSE);
```

Notable:
- **`R::freeze(FALSE)` in production** — RedBean "fluid mode": the ORM may CREATE/ALTER tables and columns at runtime when new bean properties are stored. Schema integrity depends on runtime behavior.
- **`SET NAMES latin1`** against utf8 tables storing Arabic — classic double-encoding setup. Data migration to Laravel must transcode carefully or Arabic text will mojibake.
- Query style is a mix:
  - Safe parameterized ORM calls: `R::find('visits', 'visitdate = ? ...', [...])`, `R::dispense/store/trash`, `R::begin/commit/rollback` (transactions used for composite inserts — but no-ops on the 5 MyISAM tables, which include `awusers`).
  - **Raw SQL with string concatenation** via `R::getAll/getRow/exec` — pervasive (see §5.1).

---

## 4. Auth & API

### 4.1 Login system
`obgy/_library/login_system/classes/Login.php` (+ `Registration.php`) — the open-source "panique/php-login" pattern:
- PDO with **prepared statements** throughout.
- **bcrypt** via `password_hash(..., PASSWORD_DEFAULT, ['cost' => HASH_COST_FACTOR])` (cost 10 in config), `password_verify`, and `password_needs_rehash` auto-upgrade (Login.php lines 286-340).
- PHP sessions (`session_start()`), `session_destroy()` on logout; remember-me cookie keyed by hardcoded `COOKIE_SECRET_KEY` in `aw_config.php` (2-week lifetime).
- Failed-login throttling via `awusers.user_failed_logins` / `user_last_failed_login`.

`awusers` schema (confirmed in `/home/amrtechogate/public_html/obgy/_db/obgy_12-7-2024.sql`, line 4471): `user_password_hash varchar(255)` storing `$2y$10$...` bcrypt hashes — **passwords are NOT stored in plaintext or MD5**. Engine: **MyISAM**.

Login flow controller: `obgy/core/controllers/login.php` (also contains `deviceId()`/`serialcheck()` — a homegrown licensing check).

### 4.2 Mobile / external API
- `obgy/core/controllers/mobileservices.php` (671 lines): JSON endpoints to add/update patients, etc. `header("Access-Control-Allow-Origin: *")` at top; **every `autho::checkautho` / `checkauthoize` call is commented out** — the endpoints are unauthenticated and CORS-open. Patient passwords are at least bcrypt-hashed on create. Also performs ERP sync via cURL (`curlAddClient`/`curlUpdateClient` → `HOST_URL_ERP . '/controllers/clientControllerAjax.php'`) with no auth token visible.
- `obgy/_public/api_config.php`: separate bootstrap that connects to the same DB, sets `putenv("api_key=...")`, `api_user=webApi8`, `api_password=ey8yrw4r` **hardcoded**, and turns `display_errors` **ON** (line 4). **No file inside `obgy/` references `api_config.php`** — it appears to serve external/SMS/investigation integrations (`$hosturlApi = 'http://api.gt4it.com'`, plain HTTP).
- `obgy/_library/php-jwt-master/` (Firebase php-jwt): **never used** — `grep` finds zero `JWT::encode/decode` or `php-jwt-master` references in any sub-app code. Dead dependency; there is no token-based API auth anywhere.

---

## 5. Security Posture (evidence)

### 5.1 SQL injection — CONFIRMED, widespread (CRITICAL)
Sampled controllers, direct string concatenation of user input into SQL executed through `R::getAll/getRow/exec`:

- `obgy/core/controllers/patients.php` (~lines 311-419): POST fields (`search`, `husbandname`, `address`, `phone`, `mobile`, `age`, `nationalid`, job titles) concatenated into `LIKE '%$input%'` clauses (`$q1..$q8, $qW`) then `R::getAll($query)`. Sanitizing `clean()` calls are present but **commented out**.
- `obgy/core/controllers/financialreport.php` (`commonSearch2`/`executeSql`, lines ~60-98): `" AND patients.address LIKE '%$address%'"`, `' and visits.patientid = ' . $patientId`, dates interpolated into the WHERE clause.
- `obgy/core/controllers/visits.php` (lines 282, 309, 650, 788, 1133+): `R::getRow('SELECT * FROM patients WHERE id = "' . $visit->patientid . '"')` etc.
- `obgy/core/controllers/sonar.php` line 243: `R::exec('UPDATE sonar SET tempdelete = 1 WHERE id = ' . $id)`.
- `obgy/core/controllers/index.php` lines 82-121: `R::getRow("SELECT * FROM patients WHERE id = $visit->patientid")`, detections by interpolated id.
- `obgy/core/controllers/_header.php`: `R::getRow('select * from awusers where user_id  = ' . $_SESSION['user_id'])` (session-sourced, lower risk, same pattern).

Combined with the unauthenticated `mobileservices.php`, parts of this surface are reachable **pre-auth**.

### 5.2 Other findings
| Finding | Evidence | Severity |
|---|---|---|
| Unauthenticated mobile API, CORS `*` | `core/controllers/mobileservices.php` | Critical |
| No CSRF protection anywhere; every public method URL-invokable | `imp/_imp.php` dispatch pattern | High |
| Hardcoded DB credentials (`amrtechogate_obgy` / `53i#WRqAw8r121618`) duplicated in ≥5 configs | `_public/aw_config.php`, `_public/api_config.php`, `core|pharmacy|board|screen/public/aw_config.php` | High |
| Hardcoded API creds + cookie secret | `api_config.php` (api_key/user/password), `aw_config.php` `COOKIE_SECRET_KEY` | High |
| Full DB dumps (all patient data) inside web root | `core/db_backups/` — 1.6 GB, 39 daily dumps up to `opgy_2026-06-11.sql`; `_db/*.sql`; `upload/dbs/` | High |
| Backup runs `system("mysqldump --password=$db_password ...")` — password on the process command line, executed synchronously inside the homepage request | `core/controllers/index.php::takeackup()` (line ~280) | High |
| Destructive admin action without role check | `index.php::onesetup()` (login check only) | Med-High |
| `display_errors = on` in API config; SMS API over plain HTTP | `api_config.php` line 4; `aw_config.php` `$hosturlApi = 'http://api.gt4it.com'` | Medium |
| `R::freeze(FALSE)` fluid ORM in production | `aw_config.php` line 45 | Medium |
| Smarty without default escaping (XSS depends on template discipline) | Smarty 3.1.11 config | Medium |

**Strengths (to state fairly):** bcrypt password storage with rehash-on-login; PDO prepared statements in the entire login subsystem; fine-grained DB-driven RBAC per action; `filter_input()` used consistently for input retrieval; `display_errors off` in main config; `Options -Indexes`; failed-login counters; daily automated backups (existence, if not placement).

---

## 6. Tech Debt Inventory

| Item | State | Notes |
|---|---|---|
| PHP runtime | **PHP 5.6.40** on the server (`php -v`) | EOL Dec 2018 — 7+ years without security patches; blocks modern libraries |
| Smarty | 3.1.11 (2012) | Old; known CVEs in 3.1.x line |
| RedBeanPHP | 4.3 (~2016), vendored single file | No Composer/dependency management at all |
| Code duplication | ~60-line constructor boilerplate copied across 81 controllers; config duplicated per sub-app; `screen` cross-requires `core` internals | Any framework-level change = 81 edits |
| God files | `patients.php` 2,259 lines, `visits.php` 1,946, `merge.php` 1,879, `excel.php` 1,791, `gyna.php` 1,685 | |
| Upload dir | 3.2 GB in web root (`upload/sonar` 3.1 GB, 1,846 files); files read straight off the filesystem (e.g. `upload/patientfiles/{id}`), DB index table (`patientfiles`) unused | Migration & backup burden; direct-URL exposure risk |
| Backup strategy | mysqldump triggered by first homepage visit each day (synchronous `system()` call); 39 dumps / 1.6 GB accumulating in `core/db_backups` with no rotation; optional secondary path + optional Gmail email (creds stored in `programesetting` table); `core/excel_backups` 19 MB | Same-disk backups = no disaster protection; random latency for first morning user |
| Charset | `SET NAMES latin1` vs utf8 tables (Arabic content) | Major data-migration hazard |
| Timezones | `America/Los_Angeles` in `aw_config.php` vs `Africa/Cairo` set ad hoc in code and `api_config.php` | Date bugs likely |
| Dead code | php-jwt unused; commented-out auth and sanitization; stray files in controllers dir (`1713107881.html`, `err.txt`, `out.txt`, `error_log`, `sh.php`, `test.php` controllers) | |
| Engineering process | No git, no tests, no CI, no staging artifacts visible | Every change is unmeasured risk |

---

## 7. Migration-Relevant Conclusions

1. The functional/clinical breadth and the `aw*` RBAC model are worth porting conceptually (Laravel policies/permissions map cleanly onto `awcontroll`/`awcontrollprop`/`awrolecontrollprop`).
2. The hardest migration work is **data**: 312 tables, latin1/utf8 double encoding, fluid-schema drift, MyISAM stragglers, and 3.2 GB of filesystem-only patient media with partial DB indexing.
3. Regardless of migration timeline, three issues warrant immediate remediation on the legacy system: SQL injection in the search/report controllers, the open `mobileservices.php` API, and web-accessible database dumps (`core/db_backups`, `_db/`).

---


# Module: Patients & Registration (patients)

## Purpose
The hub of the entire OB/GYN system. `patients` holds the full demographic record of the couple (wife = the patient, plus husband data embedded in the same row). Almost every other table in the system references it via `patientid` (sometimes `pid`). The module covers: draft-based patient registration with auto file number, Egyptian national-ID parsing, duplicate checks, multi-criteria search, lookup-table management (titles, jobs, education, blood types, marital status), the shared patient-header strip rendered on every clinical screen, quick medical records + prescriptions, patient file uploads, end-of-care reports, and synchronous cURL sync of patients into an external ERP database as `client` rows.

## Controllers & Views (file paths)
- `/home/amrtechogate/public_html/obgy/core/controllers/patients.php` (2259 lines) — registration wizard (`index`, `add`, `addfinish`, `update`, `updateajax`), search (`search`), soft delete (`del`), full CRUD for all lookup tables (wifetypes, husbandtypes, education, wifejobs, husbandjobs, wifestatus, bloodtypes), previous marriages (`addprevmarr`), end-visit reports (`showendvisits`, `addendvisits`), file listing/download (`patientfiles`, `download`), ERP sync (`erpClient`, `erpClientDel`, `erpSellbill`, `addAllPatientsToERP`, `curlAddClient/curlUpdateClient/curlDelClient`, `erpGetClientTreeId`).
- `/home/amrtechogate/public_html/obgy/core/controllers/patientindex.php` — thin standalone wrapper that renders the patient add form plus the patient-data strip (used by sub-apps).
- `/home/amrtechogate/public_html/obgy/core/controllers/_patientdata.php` — class `patientdata::patientdatashow()`; shared header strip: resolves all lookups, computes ages, marriage duration, obstetric formula counters (P/CS/AB/Ectopic/VM/SVD) from `phobstetric` + baseline columns on `patients`, shows history summaries (`phpastmedical`, `phpastsurgical`, `phpastgynecological`, `phpastart`, `phfamily`, `phmenstrual`, `phcontraception`), previous marriages; **auto-inserts an empty `examination` row if none exists (write-on-read)**.
- `/home/amrtechogate/public_html/obgy/core/controllers/record.php` — quick medical record screen (`records` table) + prescriptions (`recorddrugs`/`drugs`) per date for wife (`forhusband=0`) or husband (`forhusband=1`), printable via `gyna/print.html`.
- `/home/amrtechogate/public_html/obgy/core/controllers/gtimage.php` — genital-tract image annotation (`gtimage`/`gtdetail` tables, patientid-linked); the "patient files" aspect: uploads go to `upload/patientfiles/{patientid}` via `_library/plugins/classupload/uploadImages.php` + `views/obgy/_multiupload/multiuplaod.html`; listing/download reads the directory directly (the `patientfiles` table is NOT used).
- Views: `/home/amrtechogate/public_html/obgy/core/views/obgy/patients/` (`add.html`, `edit.html`, `show.html`, `patientdata.html`, `patientdatageneral.html`, `files.html`, `previous_marriage.html`, `showendvisits.html`, `siglemodeltodelete/`, `siglemodeltoupdate/`, `wifetypeafterdelete/`), `views/obgy/record/`, `views/obgy/searchresult.html`.

## Tables

### patients (utf8mb4, InnoDB) — core entity
Columns (from CREATE TABLE):
- `id` int(11) PK AI
- Wife: `wifename` text, `wiftypeid` int (→ wifetypes.id), `dateofbirth` date, `wifebl` int (→ bloodtypes.id), `wifeage` int (redundant with dateofbirth), `wifeeducation` varchar(255) (stores education.id as string), `wifejob` varchar(255) (stores wifejobs.id), `wifenationalid` varchar(255), `statuesid` int (→ wifestatus.id, note misspelling)
- Marriage: `mirragefrom` float ("Y.M" string-ish duration), `mirragefromdate` date (back-computed), `boyson` int, `girlson` int
- Husband: `husdandname` varchar(255) (misspelled), `husbandtypeid` int (→ husbandtypes.id), `husbandage` int, `husbanddateofbirth` date, `husbandbl` varchar(255) (→ bloodtypes.id as string), `husbandjob` varchar(255) (→ husbandjobs.id), `husbandeducation` varchar(255) (→ education.id), `husbandnationalid` varchar(255), `husband_notes` varchar(191), `husband_habbits` text
- Contact: `address`, `phone`, `mobile` varchar(255)
- Clinical flags: `importantnote` varchar(255), `risk` varchar(255), `risktype` int (→ risktype.id), `spacialnotes` varchar(255)
- File number: `statusno` varchar(255) (KEY statusno(191)); generated `MAX(statusno)+1` in `getstatusno()`
- Obstetric baseline counters (added to phobstetric-derived counts in _patientdata): `pno`, `ab`, `ectopic`, `vmodel`, `svd`, `cs` all varchar(191)
- Lifecycle: `done` tinyint(1) default 0 (draft flag), `deleted` int default 0 (soft delete), `entrydate` datetime, `createdate` datetime, `userid` int (→ awusers.user_id, creator), `doctorid` varchar(191) (→ awusers.user_id where role_id=4), `insert_from` int, `gender` tinyint(1)
- Portal: `patient_password_hash` varchar(191) (password_hash), `phone_qid` varchar(191), `patientid` int unsigned (self-reference column of unclear use — استنتاج: merge/import linkage)

### patients_updates (utf8mb4)
Exact clone of `patients` columns plus audit columns: `userid_edit` int default 0, `date_edit` datetime, `patientid_edit` int default 0 (→ patients.id). `statusno` is int here (vs varchar in patients). **No active controller writes/reads it** — legacy audit-trail table (inference). 

### patients_tmp (latin1)
`pit_id` int PK AI, `pit_name` varchar(55). Zero code references — dead scratch table.

### patientfiles (utf8mb4)
`id` int PK AI, `patientid` int NOT NULL (→ patients.id), `file` varchar(240). Table exists but file management is purely filesystem-based (`upload/patientfiles/{patientid}` scanned with `opendir` in `patients.php::patientfiles()`); effectively unused.

### registeration (utf8mb4) [sic — "registration"]
Delivery booking rows attached to an ANC sheet:
`id` int unsigned PK AI, `ancsheetid` int unsigned (→ ancsheet.id), `place2` varchar(191) (delivery place), `origin` varchar(191) (→ origin.id), `type` varchar(191), `awifep` varchar(191), `coast` varchar(191) (cost, misspelled), `rplace` varchar(191).
Created via the generic ajax row-append in `ancsheet.php` (`data-table="registeration"` in `views/obgy/ancsheet/append.html`); read in `ancsheet.php` (lines 224, 391, 554), `Deliveries.php`, `Completesreport.php`, `sh.php` (joins `origin` lookup).

### records (utf8mb4)
Quick medical record lines: `id` int PK AI, `patientid` int NOT NULL (→ patients.id), `date` date NOT NULL, `diagnosis` text, `ttt` text (treatment), `del` int default 0. Managed by `record.php` (auto-creates an empty row per patient if none; inline ajax updates; soft delete). Functionally paired with `recorddrugs` (patientid + date + forhusband) for prescriptions.

### origin (lookup, utf8mb4)
`id` int unsigned PK AI, `title` varchar(191), `del` varchar(191). No seed rows in dump. Referenced by `registeration.origin`; loaded in `Deliveries.php`/`sh.php`.

### education (lookup, latin1)
`id` int PK AI, `title` varchar(150), `del` int unsigned. No seed rows in dump. Used for both `patients.wifeeducation` and `patients.husbandeducation`. CRUD in patients.php.

### bloodtypes (lookup, latin1)
`id` int PK AI, `title` varchar(40), `del` int unsigned. No seed rows in dump. Used by `patients.wifebl` (int) and `patients.husbandbl` (varchar). CRUD in patients.php.

### wifejobs (lookup, latin1)
`id` int PK AI, `title` varchar(55), `del` int unsigned. No seed rows. → `patients.wifejob`; joined in search. CRUD in patients.php.

### husbandjobs (lookup, latin1)
`id` int PK AI, `title` varchar(55), `del` int unsigned. No seed rows. → `patients.husbandjob`; INNER JOIN in husband-job search. CRUD in patients.php.

### wifestatus (lookup, latin1)
`id` int PK AI, `title` varchar(55), `del` int unsigned default 0. **Seeded values** (UTF-8 stored in latin1 table, mojibake in dump): (1) انسة "single", (2) متزوجة "married", (3) مطلقة "divorced", (8) متزجه للمره الثانيه "married for the 2nd time", (9) ارمله "widow". → `patients.statuesid`. CRUD in patients.php.

### wifetypes (lookup, latin1)
`id` int PK AI, `title` varchar(55), `del` int unsigned. No seed rows. → `patients.wiftypeid`; printed as the honorific/title before the wife's name on prescriptions (`record.php::printpre`). CRUD in patients.php.

### husbandtypes (lookup, latin1)
`id` int PK AI, `title` varchar(50), `del` int unsigned. No seed rows. → `patients.husbandtypeid`; honorific on husband prescriptions. CRUD in patients.php.

### complaint (lookup, latin1)
`id` int PK AI, `name` varchar(255), `conditions` int (soft-delete flag, 0 = active). No seed rows. Chief-complaint vocabulary for gyna/infertility flows: read in `gyna.php`, `gynasheet00.php`, `infertilitysheet00.php`, `ancsheet00.php`, `completereport.php`. Deletable lookup per `setup.php` whitelist.

### complaintant (lookup, latin1)
`id` int PK AI, `name` varchar(255), `conditions` int. Structural twin of `complaint`, used for antenatal visit complaints: `antenalvisit.php` (read + generic add with `$table = "complaintant"`), `completereport.php`.

### locations (MyISAM, latin1/utf8mb4 name column)
`id` int PK AI, `name` varchar(255), `deleted` int default 0. **No controller references — dead table.**

## Relationships (explicit list; NO declared FKs anywhere — all inferred from code)
- patients.wiftypeid -> wifetypes.id
- patients.statuesid -> wifestatus.id
- patients.wifeeducation -> education.id (stored as varchar)
- patients.husbandeducation -> education.id (varchar)
- patients.wifejob -> wifejobs.id (varchar)
- patients.husbandjob -> husbandjobs.id (varchar)
- patients.wifebl -> bloodtypes.id
- patients.husbandbl -> bloodtypes.id (varchar)
- patients.husbandtypeid -> husbandtypes.id
- patients.risktype -> risktype.id (risk module)
- patients.userid -> awusers.user_id ; patients.doctorid -> awusers.user_id (role_id=4)
- patients_updates.patientid_edit -> patients.id ; patients_updates.userid_edit -> awusers.user_id (inferred)
- patientfiles.patientid -> patients.id (unused)
- records.patientid -> patients.id
- registeration.ancsheetid -> ancsheet.id ; registeration.origin -> origin.id
- Cross-module consumers of patients.id: visits.patientid, lastvisit.patientid, endvisitreports.patientid, examination.patientid, phmenstrual/phcontraception/phobstetric/phpastmedical/phpastsurgical/phpastgynecological/phpastart/phfamily.patientid, previous_marriage.patientid, hus_previous_marriage.patientid, recorddrugs.patientid (+ recorddrugs.drugid -> drugs.id), gtimage.patientid, ancsheet/gynasheet/infertilitysheet.patientid
- External ERP DB: client.obygyPatientId -> patients.id (cURL sync to `clientControllerAjax.php` do=addclient/updateFull/deleteFinaly; sellbillController.php do=addObgyVisit)

## Business Workflows (traced from code)
1. **Draft registration**: `patients.php?ac=index` finds an unfinished draft (`done=0 AND userid=current user`) or dispenses a new `patients` row immediately with auto `statusno = MAX(statusno)+1` (race-prone). Upload session dir is set per patient id.
2. **National ID**: `checknationalid()` validates the Egyptian 14-digit NID with a regex and derives the birth date (century digit 2/3, YYMMDD, governorate codes); ages displayed as "xY, xM, xD".
3. **Inline saving**: every field saves via ajax (`updateajax`, `saveage`, `saveagehus`, `caldatefromage`); live duplicate checks: `checkingwifename`, `checkingwifestatusno`, `checkingnationalwifename`, `checkingnationalhusband`, `checkmobile`.
4. **Finish (`addfinish`)**: sets `done=1`, persists full wife+husband payload, hashes patient password, marriage duration converted to a back-dated `mirragefromdate`; optionally inserts a `visits` row (detectionid, cash/visa amounts, discount, visit_period, urgent, enterordered queue number, payedflag) inside one RedBean transaction; redirects by role (role 3 → reception index, others → patienthistory).
5. **ERP sync**: if `programesetting.erpdb` is set, RedBean attaches a second DB; `client` matched by `obygyPatientId`; insert/update/delete done by synchronous cURL to the ERP controllers; `erpSellbill` posts the visit fee as a sell bill; `addAllPatientsToERP` bulk-migrates in batches of 30.
6. **Search**: `?ac=search` builds dynamic LIKE clauses (wifename/statusno, husband name, husband job join, wife job join, address, phone, mobile, wifeage, national id), `done=1 AND deleted=0`, LIMIT 60; per-result link target from `lastvisit.control`; hospital mode searches by department through `visits.detectionid`, excluding patients with a later `endvisitreports.enddate`.
7. **Patient header strip** (`_patientdata.php`, included by virtually every clinical controller): resolves all lookups, computes obstetric formula — P = SVD(phobstetric obstermination=1) + CS(…=2) + baselines (`pno`,`svd`,`cs`), AB (…=4 + `ab`), Ectopic (…=3 + `ectopic`), VM (…=5 + `vmodel`); shows top-5 summaries of past medical/surgical/gyn/ART/family history; auto-creates today's `examination` row if missing.
8. **Medical record** (`record.php`): per-date `records` rows (diagnosis/ttt) with inline ajax edit and soft delete; prescriptions in `recorddrugs` per date and per person (wife/husband), printed with the person's title from wifetypes/husbandtypes.
9. **Files**: multiupload plugin saves to `upload/patientfiles/{id}`; `patientfiles()` lists by directory scan; `download()` streams with force-download headers (no path sanitization — traversal risk).
10. **Delivery registration**: from the ANC sheet, generic ajax row-append creates `registeration` rows (place, origin, type, cost) keyed by `ancsheetid`; consumed by Deliveries/Completesreport/sh reports.
11. **End of care**: `addendvisits`/`showendvisits` create/list `endvisitreports` rows (status, enddate, reason, notes, doctorid, patientid).
12. **Delete**: soft delete `deleted=1` + ERP client final delete.

## ERP Migration Notes
- **Laravel models**: `Patient` (patients, demographics of the woman only), `Spouse` or `PatientHusband` (1:1, extracted husband columns), `ObstetricBaseline` (pno/ab/ectopic/vmodel/svd/cs as integers), `MedicalRecord` (records), `Prescription`/`PrescriptionItem` (recorddrugs, owned by pharmacy module), `DeliveryRegistration` (registeration, FK anc_sheet_id + origin_id, `coast`→`cost` decimal), `Origin`, `EducationLevel`, `JobTitle` (merge wifejobs+husbandjobs with no gender split), `PersonTitle` (merge wifetypes+husbandtypes), `MaritalStatus` (seed the 5 Arabic values), `BloodType` (or PHP enum), `Complaint` (merge complaint+complaintant with `context` enum: gyna|antenatal|infertility), `PreviousMarriage` (covers both previous_marriage and hus_previous_marriage with a `for_spouse` flag).
- **Drop**: patients_tmp, locations, patientfiles (replace with Spatie MediaLibrary / `patient_attachments`, importing files from `upload/patientfiles/{id}`), patients_updates (replace with owen-it/laravel-auditing on Patient).
- **Normalization/cleanup**: rename misspelled columns (`husdandname`→husband_name, `wiftypeid`, `statuesid`, `mirragefrom`, `spacialnotes`, `registeration`); convert varchar lookup-id columns to `foreignId`; `0000-00-00` → NULL dates; drop stored `wifeage`/`husbandage` (computed accessors); unique index on national id + file number; file number via DB sequence/transaction instead of MAX+1.
- **Auth**: move `patient_password_hash` to a `patient_users` table / separate Laravel guard (mobile app login).
- **Integration**: kill the cURL dual-DB sync — in the target ERP the patient IS the client (`patient_id` on the client/account record); visit billing becomes an event-driven invoice (queued job) instead of synchronous curl in the save path.
- **Search**: replace LIKE-on-unindexed-columns with indexed columns + Laravel Scout/fulltext; replicate the multi-criteria search UX in Angular.
- **Behavioral parity to preserve**: draft-style progressive registration (or replace with client-side form state), NID→birthdate auto-fill, duplicate checks (name/NID/phone/file no), obstetric formula computation, role-based post-registration redirect.

---


# Module: Visits & Appointments (visits)

## Purpose
Operational core of the OB/GYN clinic system. Handles appointment booking (reception + mobile app), daily doctor queue, day-period slotting with capacity limits, and — inside the *same* `visits` table — the financial side of each visit (cash/visa split, discount, outstanding debt, rest payments, refunds, installments). Also routes the doctor from the queue to the patient's last-used clinical sheet, records infertility follow-up visits, and closes treatment episodes via end-of-visit reports. Syncs paid visits to an external ERP DB as sell bills via cURL.

## Controllers & Views (file paths)
- `/home/amrtechogate/public_html/obgy/core/controllers/visits.php` (1946 lines) — booking, queue lists, financial transactions, detections CRUD, visit_periods admin, ERP sync.
- `/home/amrtechogate/public_html/obgy/core/controllers/index.php` — home page = today's queue (hospital vs clinic modes), drag-sort of `visitorder`, reads `lastvisit` for sheet routing.
- `/home/amrtechogate/public_html/obgy/core/controllers/mobileservices.php` — mobile booking API (`addVisit`, `updateVisit`, `delVisit`, `visitType`, `queryAllVisitsByDate`, `queryAllVisitsByPatientAndDate`). NO auth checks (commented out).
- `/home/amrtechogate/public_html/obgy/core/controllers/patients.php` — `showendvisits`, `addendvisits`, `endvisit` (writes `endvisitreports`).
- `lastvisit` upserts in: `antenalvisit.php` (~line 307), `ancsheet.php`, `ancsheet00.php`, `gynasheet.php`, `gynasheet00.php`, `iui.php`, `infertilitysheet.php` — each stores its own `controllname` as `lastvisit.control`.
- `newvisit` read/written in: `infertilitysheet.php` (line 252, generic AJAX cell-save), reports in `sh.php`, `Completesreport.php`.
- Views: `/home/amrtechogate/public_html/obgy/core/views/obgy/visits/` — `add.html`, `visitdiv.html`, `waiting_list.html`, `lastvisits.html`, `rest.html`, `periodeditmodel.html`, `detectionmodel.html`, `showdepts.html`, etc. Queue view: `/core/views/obgy/index.html`.

## Tables

### visits (core, transactional)
Appointment + encounter + payment ledger in one table.

| Column | Type | Notes |
|---|---|---|
| id | int(11) PK AI | |
| patientid | int(11) NOT NULL | FK -> patients.id |
| visitdate | date NOT NULL | stored 'Y/m/d' |
| visittime | time NULL | set on payment rows |
| detectionid | int(11) NOT NULL | FK -> detections.id; MAGIC: -99=installment, 999=pay-rest, 9999=refund |
| totaldetectionvalue | decimal(10,0) | total fee |
| discount | decimal(10,0) | |
| detectionvalue_cash | decimal(10,0) | cash paid |
| detectionvalue_visa | decimal(10,0) | visa paid |
| restdetectionvalue | decimal(10,0) | outstanding debt at creation |
| visitorder | int(11) NOT NULL | queue position (drag-sortable) |
| enterordered | int(11) NOT NULL | entry sequence (MAX+1 on insert) |
| view | tinyint(1) def 0 | 0=waiting, 1=entered (hospital mode) |
| deleted | int(11) def 0 | soft delete |
| end | int(11) def 0 | legacy end flag |
| enddate | date | legacy |
| reason / notes | text | |
| enddoctorid | int(11) | FK -> awusers.user_id (inferred) |
| mobileappvisit | int(11) uns | 1 = booked via mobile app |
| payedflag | int(11) uns | 1 = paid (triggers ERP sell bill) |
| visit_period | int(11) def 0 | FK -> visit_periods.id |
| user_id | int(11) def 0 | FK -> awusers.user_id (creator) |
| urgent | int(11) def 0 | 1 = waiting-list/urgent future visit |
| createdate | datetime | |
| visitid | int(11) def 0 | SELF-FK -> visits.id (original visit for 999/9999 payment rows) |
| patient_edit | int(11) def 0 | |
| approx_time | time | approximate appointment time |
| card_add / printserial / sms_send | int(11) def 0 | flags |
| branch_id | varchar(11) def '0' | multi-branch (string type!) |
| visit_order | int(11) uns | duplicate of visitorder (redundant) |

### visits_updates (dead/reserved)
Same columns as `visits` plus: `detectionvalue` int uns, `clinic_id` int uns, `visit_order` int, `userid_edit` int, `date_edit` datetime, `patientid_edit` int. **Not referenced by any PHP code found** in obgy or www apps — looks like an intended edit-audit shadow table that was never wired up (inferred).

### visit_periods (lookup)
| Column | Type |
|---|---|
| id | int(11) PK AI |
| name | varchar(255) |
| timing | varchar(255) |
| max_no | int(11) def 0 (capacity per day-period) |
| deleted | int(11) def 0 |

No seeded rows in the dump. Managed via `visits.php::visitPeriodEdit/addNewRow/updateAjax` (generic AJAX). Active only when `programesetting.visit_period = 1`. Capacity check: `visits.php::patPerPeriod` counts visits per period+date vs `max_no` (enforced client-side).

### newvisit (infertility sheet follow-up visits)
| Column | Type |
|---|---|
| id | int(11) PK AI |
| infertilitysheetid | int(11) uns — FK -> infertilitysheet.id |
| newvisitcycles | varchar(191) — FK -> newvisitcycles.id (stored as string) |
| date | varchar(191) — visit date as text |
| lmp | varchar(191) — last menstrual period |
| bw | varchar(191) — body weight (inferred) |
| ut | varchar(191) — uterus finding |
| ov | varchar(191) — ovaries finding |
| ttt | varchar(191) — treatment |

All clinical fields are varchar — no typing. Rows created/edited via generic AJAX cell-save from `infertilitysheet/add.html`.

### newvisitcycles (lookup)
`id` int uns PK AI, `title` varchar(191), `del` int uns. No seeded rows in dump. Shared by `newvisit` (infertility) and `newvisitg` (gyna sheet follow-ups, other module).

### lastvisit (routing pointer)
`id` int uns PK AI, `patientid` int uns (FK -> patients.id), `control` varchar(191) = controller name of last clinical sheet opened (`antenalvisit`, `ancsheet`, `gynasheet`, `iui`, `infertilitysheet`). Upserted whenever a sheet is opened; read by `index.php` to render the "go" link on the queue.

### old_visits (legacy archive)
Pre-refactor copy of visits with single `detectionvalue` decimal(10,0) instead of cash/visa split. Columns: id, patientid, visitdate, detectionid, detectionvalue, visitorder, enterordered, view, deleted, end, enddate, reason, notes, enddoctorid, mobileappvisit, payedflag. Only used by one-off migration `visits.php::fixOldVisits()` (copies detectionvalue -> totaldetectionvalue + detectionvalue_cash by matching `visits.id = old_visits.id`, hardcoded id ranges up to 9092). The migration function is still web-callable.

### endvisitreports (treatment closure)
| Column | Type |
|---|---|
| id | int(11) PK AI |
| patientid | int(11) NOT NULL — FK -> patients.id |
| reason | text |
| enddate | date |
| notes | text |
| doctorid | int(11) NOT NULL — FK -> awusers.user_id |
| status | int(11) uns |

Written by `patients.php::showendvisits/addendvisits` and bulk-closed per department by `visits.php::emptydeptvisit`.

### vacations (dead/reserved)
`id` int uns PK AI, `vacation_date` date, `vacation_reason` varchar(191), `created_at`/`updated_at` datetime, `created_by`/`updated_by` int uns (FK -> awusers, inferred). Laravel-style timestamps. **Not referenced by any controller/view found** — unused or for newer external code (inferred).

## Relationships (inferred — NO declared FKs anywhere)
- visits.patientid -> patients.id
- visits.detectionid -> detections.id (service type + price `detectionval`; magic values -99/999/9999 are NOT real detections)
- visits.visit_period -> visit_periods.id
- visits.user_id -> awusers.user_id
- visits.enddoctorid -> awusers.user_id (inferred)
- visits.visitid -> visits.id (self-ref: payment/refund row -> original visit)
- visits_updates.patientid -> patients.id; visits_updates.userid_edit -> awusers.user_id (inferred)
- old_visits.id -> visits.id (1:1 by id, per fixOldVisits); old_visits.patientid -> patients.id
- newvisit.infertilitysheetid -> infertilitysheet.id
- newvisit.newvisitcycles -> newvisitcycles.id
- lastvisit.patientid -> patients.id; lastvisit.control -> controller name (antenalvisit/ancsheet/gynasheet/iui/infertilitysheet)
- endvisitreports.patientid -> patients.id; endvisitreports.doctorid -> awusers.user_id
- Installments: visits rows with detectionid=-99 settle balances in totalbalance.patientid
- External ERP DB: sellbill.obygyVisitId -> visits.id; product.obygyDetectionId -> detections.id; client.obygyPatientId -> patients.id (cURL to `sellbillController.php?do=addObgyVisit|updateObgyVisit|delObgyVisit`, `buyBillControllerAjax.php`, `productControllerAjax.php`)
- Config dependencies: programesetting.visitsform (1=hospital queue / else clinic), .visit_period (slots on/off), .addshours (after-midnight extra hours show yesterday's visits), .erpdb / .erpdbsave (ERP target)

## Business Workflows (traced from code)
1. **Reception booking** (`visits.php::index/add`): find patient by statusno (`searchpatient`) or name autocomplete (`getnames`); pick detection (price auto-filled via `getdtprice`); enter cash/visa/discount/rest, period, urgent, approx_time, notes. Insert with `payedflag=1`, `enterordered = MAX+1`, `createdate=now`, `user_id=session`. Paid visit fires `erpSellbill()` (cURL sell bill).
2. **Mobile booking** (`mobileservices.php::addVisit`): rejects if patient already has a visit that day (flag 2) or if 10 mobile visits already exist that day (flag 3, hardcoded). Inserts with `mobileappvisit=1`, `payedflag=0`. On arrival, reception `payvisit()` sets payedflag=1 and creates the ERP bill. `delVisit` refuses to delete paid app visits.
3. **Daily queue** (`index.php::index`): hospital mode — waiting list (`view=0` ORDER BY visitorder) vs entered list (`view=1` ORDER BY enterordered); `visitviewed()` marks entry. Clinic mode — all today's visits ORDER BY visitorder, approx_time; if current hour <= `addshours`, yesterday's visits are included (late-night clinics). `sortable()` persists drag-reorder into `visitorder`.
4. **Sheet routing**: queue row shows a "go" link from `lastvisit.control`; opening any clinical sheet (ANC/gyna/infertility/IUI/antenatal visit) upserts `lastvisit` with that controller name.
5. **Financial follow-ups** (all rows in `visits`): `payval()` inserts detectionid=999 row linked via `visitid`; `refund()` inserts detectionid=9999 after validating refund <= net paid (SUM of 999 rows + original − SUM of 9999 rows); installments `addbalance/gettotalbalance` use detectionid=-99 vs `totalbalance`. `del()` blocks deleting a visit that has refunds. Debt per visit computed on the fly in `visitCommon()`/`getrestvalues()`.
6. **Period view** (`loadVisits/visitsWithPeriod`): when slots enabled, visit list is grouped per day per visit_period with Arabic day names.
7. **Waiting list** (`waitingList/updateWaiting`): future `urgent=1` visits; move to today (`visitdate=today`) or cancel (`deleted=1`).
8. **Treatment closure**: `patients.php::addendvisits/showendvisits` insert `endvisitreports` (status 0, today, doctor=session user); `visits.php::emptydeptvisit` bulk-closes every patient of a detection/department whose latest visit has no later end report.

## ERP Migration Notes
**Proposed Laravel models:**
- `Appointment` (from visits booking fields): patient_id, service_id, scheduled_date, approx_time, visit_period_id, source enum(reception, mobile), urgent bool, status enum(booked, arrived, in_consultation, done, cancelled) — replaces view/deleted/urgent flags.
- `Encounter` or queue fields on Appointment: queue_position (replaces visitorder + visit_order + enterordered).
- `PaymentTransaction`: appointment_id, type enum(fee, rest_payment, refund, installment), cash, visa, discount — **eliminates magic detectionid -99/999/9999 and self-FK visitid**; keep running balance instead of recomputed SUMs.
- `Service` (from detections — owned by services/billing module): title, price.
- `VisitPeriod` / `AppointmentSlot`: name, timing, capacity (max_no); enforce capacity in a DB transaction (current check is client-side only / race-prone).
- `ClinicClosure` (from vacations): wire it into booking validation — table exists but was never used.
- `TreatmentClosure` (from endvisitreports): patient_id, doctor_id, closed_at, reason, notes; or model as status on an EpisodeOfCare.
- `InfertilityFollowUp` + `CycleType` (from newvisit + newvisitcycles): belongs to clinical infertility module; convert varchar date/lmp/bw to date/decimal types; unify CycleType with gyna sheet's `newvisitg` usage.

**Drop/merge:**
- Drop `old_visits` (archive only; fixOldVisits already applied) and `visits_updates` (never wired); replace audit with laravel-auditing/activity-log.
- Drop `lastvisit`; derive "last sheet type" from latest clinical sheet record or a `patients.last_sheet_type` column.
- Normalize branch_id (varchar '0') to a proper branches FK.

**Risks/cleanup during migration:**
- Magic detectionid rows must be ETL-split into PaymentTransaction rows; rows with detectionid in (-99,999,9999) are NOT appointments.
- SQL injection throughout (`search`, `getnames`, `emptydeptvisit`, string-concat queries) and generic AJAX endpoints accepting arbitrary table/column names (`updateAjax`, `addNewRow`, `editselect`) — do not port; replace with validated REST endpoints.
- `mobileservices.php` has all auth commented out — public unauthenticated API; replace with Sanctum-protected mobile API; make the 10-visits/day cap a setting.
- Dates stored as 'Y/m/d' strings in `date` columns and as varchar in newvisit — normalize during ETL.
- Replace cURL-based ERP sell-bill sync with internal invoicing in the unified Laravel ERP (events/jobs), preserving the obygyVisitId mapping for historical bills.

---


# Module: Antenatal Care (antenatal)

## Purpose
Full pregnancy follow-up for an OB/GYN clinic: EDD calculation from LMP (Naegele's rule, hardcoded `LMP + 9 months + 7 days`), ANC sheets, per-visit vitals, per-pregnancy drugs/investigations, antenatal ultrasound records, first-trimester (T-scan) and anomaly (TT-scan) scan tracking with auto-booking into the 4D scan waiting list, past pregnancy-loss history (EPC), and reports (expected deliveries, EPC by date, risk type, termination type).

IMPORTANT ARCHITECTURAL FACT: the system contains **two parallel, duplicated ANC implementations**:
1. "ANC Sheet" path: `ancsheet` + `ancnewvisit` + `ancsheetdrugs` + `ancsheetinvest` (controller `ancsheet.php`, legacy copy `ancsheet00.php`).
2. "Antenatal Visit" path: `mainantenental` + `antenalvisit` + `mainantenentaldrugs` + `mainantenentalinvest` + `mainantenentalus` (controller `antenalvisit.php`).
Both can hold data for the same pregnancy.

## Controllers & Views (file paths)
- `/home/amrtechogate/public_html/obgy/core/controllers/ancsheet.php` — main ANC sheet (auto-create sheet, eedlmp EDD calc, ancnewvisit append, drugs/invest CRUD + print, end-pregnancy, archive, history, dynamic lookup CRUD via `getselectajax`/`getdataselect`).
- `/home/amrtechogate/public_html/obgy/core/controllers/ancsheet00.php` — older duplicate of ancsheet.php (uses `Ancsheet/` view dir, no 4D integration). Dead/legacy.
- `/home/amrtechogate/public_html/obgy/core/controllers/antenalvisit.php` — parallel antenatal module on `mainantenental` (auto gravida count from `phobstetric` + `patients` counters, visits, US rows, drugs/invest, end-preg `done=1`, archive by `g`, pregdetail).
- `/home/amrtechogate/public_html/obgy/core/controllers/edd.php` — report: `ancsheet` rows with `sheetedd` = today / in range (+ per-patient filter); also gestational week calc.
- `/home/amrtechogate/public_html/obgy/core/controllers/expected.php` — report: `mainantenental` rows with `eed` = today / in range, `done=0`.
- `/home/amrtechogate/public_html/obgy/core/controllers/risktype.php` — report: patients by `patients.risktype` (raw SQL concat — SQLi).
- `/home/amrtechogate/public_html/obgy/core/controllers/epc.php` — report: `wifeepc` by `wifeepcdate`, joins `infertilitysheet` -> `patients`, resolves wifeepctype/ttt/obst lookups.
- `/home/amrtechogate/public_html/obgy/core/controllers/termination.php` — report: `phobstetric` joined to `patients` by `obstermination` type (raw SQL concat — SQLi).

Views: `/home/amrtechogate/public_html/obgy/core/views/obgy/ancsheet/` (add, anchistory, append, archive, select, delselect, showInvestigation, showPrescriptions, newrowdrugedit, newrowUS), `/home/amrtechogate/public_html/obgy/core/views/obgy/antenalvisit/` (add, archive, pregdetail, newrow, newrowUS, editmodel, showInvestigation, showPrescriptions, newrowdrugedit), `/home/amrtechogate/public_html/obgy/core/views/obgy/reports/` (expected*, risktype*, termination*), `/home/amrtechogate/public_html/obgy/core/views/obgy/reports2/` (edd*, epc*). Print templates reused from `gyna/print.html`, `gyna/printinv.html`.

Schema source: `/home/amrtechogate/public_html/obgy/_db/obgy_12-7-2024.sql` (lookup seed data only present in older dumps `obgy_app_20-04-2024.sql` / `obgy_app_22-12-2021.sql`).

## Tables

### ancsheet — pregnancy master file (ANC Sheet path)
Columns: `id` int PK AI; `patientid` int unsigned; `endpreg` int unsigned (0=active, 1=ended/archived); `sheetlmp` varchar(191); `sheetedd` varchar(191); `sheetlast` varchar(191); `placenta` varchar(191); `sheetnte` varchar(191); `tscandate/tscanw/tscanresult/tscanreport` varchar(191) (first-trimester scan); `ttscandate/ttscanw/ttscanresult/ttscanreport` varchar(191) (anomaly scan); `historytoday` datetime (set at end-pregnancy); `obstgn/obstpn/obstepc` int unsigned (G/P/EPC snapshot copied from `infertilitysheet` at end-pregnancy); `importantnote` varchar(191); `4d_list_id` int default 0.
Inferred FKs: `patientid -> patients.id`; `4d_list_id -> op_4d_list.id`; `placenta` holds title text chosen from `placenta` lookup (stored as text, not id — by `getselectajax` select).

### ancnewvisit — ANC follow-up visit row
Columns: `id` int PK AI; `ancsheetid` int unsigned; `newvisitdate` varchar(191); `newvisitw` varchar(191) (gestational age "12W 3D", recomputed from `ancsheet.sheetlmp` on every page load and stored back); `usn` varchar (US number, lookup `usn`); `usplace` varchar (lookup `usplace`); `usaf` varchar (amniotic fluid); `newvisitbw` varchar (body weight); `newvisitbp` varchar (blood pressure); `newvisitnote`, `newvisitnote2` varchar; `usefw` varchar (estimated fetal weight); `usbiom` varchar (biometry); `plan` varchar.
Inferred FK: `ancsheetid -> ancsheet.id`.

### ancsheetdrugs — prescriptions on ANC sheet
Columns: `id` int PK AI; `patientid` int; `ancsheetid` int; `date` date; `drugid` varchar(150); `drugtype` varchar(100); `drugdos` varchar(255); `deleted` int default 0; `doctorid` int; `drugname` varchar(191) (denormalized copy from drugs); `forhusband` int unsigned (0=wife, 1=husband); `recepittmpid` int default 0; `recepitdrugid` int default 0.
Inferred FKs: `ancsheetid -> ancsheet.id`; `drugid -> drugs.id`; `doctorid -> awusers.user_id`; `patientid -> patients.id`; `recepittmpid/recepitdrugid` -> receipt module.

### ancsheetinvest — investigation requests on ANC sheet
Columns: `id` int PK AI; `patientid` int; `ancsheetid` int; `date` date; `investid` int; `investresult` varchar(250); `deleted` int default 0; `doctorid` int; `forhusband` int.
Inferred FKs: `ancsheetid -> ancsheet.id`; `investid -> invests.id`; `doctorid -> awusers.user_id`.

### mainantenental — pregnancy master (Antenatal Visit path; "antenatal" misspelled)
Columns: `id` int unsigned PK AI; `patientid` varchar(191) (TYPE MISMATCH — varchar vs int elsewhere); `doctorid` int unsigned; `lmp` date; `eed` date (EDD = lmp+9m+7d); `notes` varchar(191); `done` int unsigned (0=current, 1=ended); `ivf` date (= lmp+9m-7d, auto-set); `g` int unsigned (gravida, auto-computed: count(`phobstetric` by obstermination) + `patients.pno/cs/svd/ab/ectopic/vmodel` counters + 1); `sysdate` date; `hb` double; `sex` varchar(255).
Inferred FKs: `patientid -> patients.id`; `doctorid -> awusers.user_id`.

### antenalvisit — antenatal visit row (parallel path)
Columns: `id` int PK AI; `patientid` int unsigned; `doctorid` int unsigned; `antenaldate` date; `complaint` text; `wt` varchar(191); `bp` varchar(191); `pulse` varchar(255); `notes` varchar(191); `conditions` int unsigned (soft-delete flag: 0=active, 1=deleted); `mainantenentalid` int unsigned; `diagnosisid` varchar(191) (CSV of `diagnosisant` ids); `complaintid` varchar(191) (CSV of `complaintant` ids).
Inferred FKs: `mainantenentalid -> mainantenental.id`; `patientid -> patients.id`; `doctorid -> awusers.user_id`.

### mainantenentaldrugs — prescriptions (parallel path; structure identical to ancsheetdrugs)
Columns: `id` int PK AI; `patientid` int; `mainantenatalid` int (NOTE different spelling from table name); `date` date; `drugid` varchar(150); `drugtype` varchar(100); `drugdos` varchar(255); `deleted` int; `doctorid` int; `drugname` varchar(191); `forhusband` int unsigned; `recepittmpid` int; `recepitdrugid` int.
Inferred FK: `mainantenatalid -> mainantenental.id`; `drugid -> drugs.id`.

### mainantenentalinvest — investigations (parallel path; identical to ancsheetinvest)
Columns: `id` int PK AI; `patientid` int; `mainantenatalid` int; `date` date; `investid` int; `investresult` varchar(250); `deleted` int; `doctorid` int; `forhusband` int.
Inferred FK: `mainantenatalid -> mainantenental.id`; `investid -> invests.id`.

### mainantenentalus — antenatal ultrasound record
Columns: `id` int PK AI; `mainantenatalid` int; `doctorid` int; `patientid` int; `date` date; `nga` varchar(100) (GA by US); `gs` (gestational sac); `crl`; `fhr`; `bpd`; `fl`; `placenta`; `ai` (amniotic index); `fwt` (fetal weight) — all varchar(100) NOT NULL; `notes` text; `deleted` int.
Inferred FK: `mainantenatalid -> mainantenental.id`; `doctorid -> awusers.user_id`. Last row's `placenta` is shown as "current placenta" on the visit screen; `instruction.php` and `completereport.php` also read it.

### placenta — lookup: placental site (used by `ancsheet.placenta` select)
Columns: `id` int unsigned PK AI; `title` varchar(191); `del` int unsigned.
Seeded values (older dump): 'ant', 'post high', 'post high rt lat', 'ant high', 'fundal ant to lt', 'fundal', 'Ant fundal', 'post placenta', 'ant low lying', 'pl pr compl cent to rt', 'post', 'post low to lt', '1st->ant / 2nd-> post', 'ant low to lt' + several NULL rows and deleted Arabic test rows. Dirty data.

### pla2cen — lookup: ICSI place (BELONGS TO IVF MODULE)
Columns: `id` int unsigned PK AI; `title` int(11) unsigned (TYPE BUG — should be varchar); `del` tinyint.
No seed data in any dump. Used only by `ivfsheet.php` (`ivfsheet.pla2cen -> pla2cen.id`, rendered as `icsiplace`) and `sh.php`.

### anprotocol — lookup: stimulation protocol (LEGACY/ORPHANED, IVF domain)
Columns: `id` int unsigned PK AI; `title` varchar(191); `del` tinyint.
Seeded values: 'long ag', 'antag', 'short ag'. The column `ivfsheet.anprotocol` is resolved in code against table `icsiprotocol`, NOT `anprotocol` — this table appears abandoned (استنتاج).

### antype — lookup: infertility type (IVF domain)
Columns: `id` int unsigned PK AI; `title` varchar(191); `del` int unsigned.
Seeded values: '1ry', '2ry', 'sex selection', '2ry inf + sex selec' (+1 deleted NULL). Used by `ivfsheet.antype` (sh.php, Ivfstatistics.php).

### antypes — lookup: cycle type (IVF domain)
Columns: `id` int unsigned PK AI; `title` varchar(191); `del` tinyint.
Seeded values: 'fresh', 'frozen'. Used by `ivfsheet.antypes` (sh.php).

### wifeepc — past pregnancy-loss / early pregnancy complication record (EPC)
Columns: `id` int PK AI; `infertilitysheetid` int unsigned; `wifeepcttt` varchar(191) (treatment lookup id); `wifeepcobst` varchar(191) (provider lookup id); `wifeepctype` varchar(191) (loss type lookup id); `wifeepcw` varchar(191) (gestational weeks at loss); `histopath` varchar(191); `wifeepcp` varchar(191); `wifeepcepc` varchar(191); `coast` varchar(191) (cost, misspelled); `wifeepcdate` varchar(191).
Inferred FKs: `infertilitysheetid -> infertilitysheet.id`; `wifeepctype -> wifeepctype.id`; `wifeepcttt -> wifeepcttt.id`; `wifeepcobst -> wifeepcobst.id`. Edited inline from ancsheet/gynasheet/infertilitysheet screens; reported by epc.php.

### wifeepctype — lookup: pregnancy-loss type
Columns: `id` int unsigned PK AI; `title` varchar(191); `del` int unsigned.
Seeded values: 'EPL' (early pregnancy loss), 'A T1' (abortion 1st trimester), 'A T2', 'C VM' (complete vesicular mole), 'P VM' (partial), 'EP' (ectopic pregnancy).

### wifeepcttt — lookup: pregnancy-loss treatment
Columns: `id` int unsigned PK AI; `title` varchar(191); `del` int unsigned.
Seeded values: 'conservative', 'medical (miso)', 'Medical (MTX)', 'D&C', 'laparoscopy otomy', 'Laparoscopy ectomy', 'Laparotomy Otomy', 'Laparotomy ectomy', 'medical+ d&c'.

### wifeepcobst — lookup: obstetrician/place that managed the loss
Columns: `id` int unsigned PK AI; `title` varchar(191); `del` tinyint.
Seeded values: ~30+ free-text rows mixing Arabic doctor names (د.علي المصري, د.هاله هدايه...), hospitals (م. التخصصي), and countries ('s.arabia', السعودية) plus junk ('rtyrt').

### wifeobst — lookup: obstetrician/place of prior deliveries (children)
Columns: `id` int unsigned PK AI; `title` varchar(191); `del` int unsigned.
Seeded values: ~68 free-text rows, same mix of doctor names/hospitals/countries, with duplicated soft-deleted rows. Consumed by `awifep.wifeobst` as CSV multi-select (`R::findAll('wifeobst', "id in (0$data->wifeobst)")` in Deliveries.php / Completesreport.php).

### wifemodeofd — lookup: mode of delivery
Columns: `id` int unsigned PK AI; `title` varchar(191); `del` tinyint.
Seeded values: 'CS', 'sVD', 'nVD'. Consumed by `awifep.wifemodeofd`.

### wifetypeofd — lookup: delivery term
Columns: `id` int unsigned PK AI; `title` varchar(191); `del` tinyint.
Seeded values: 'FT' (full term), 'PT' (preterm). Consumed by `awifep.wifetypeofd`; infertilitysheet.php counts FT/PT children by title match (`R::findOne('wifetypeofd', 'title = "FT"')`).

Related (NOT assigned but central): `awifep` — children/prior deliveries table under `infertilitysheet` with columns `wifesex, wifemodeofd, wifeobst, wifetypeofd, name, date, wifew, wifel, duration, stopped, comment, awifepmethod, wifeage`.

## Relationships (explicit list)
- ancsheet.patientid -> patients.id
- ancsheet.4d_list_id -> op_4d_list.id (and op_4d_list.ancsheetid -> ancsheet.id, bidirectional)
- ancnewvisit.ancsheetid -> ancsheet.id
- ancsheetdrugs.ancsheetid -> ancsheet.id; ancsheetdrugs.drugid -> drugs.id; ancsheetdrugs.doctorid -> awusers.user_id; ancsheetdrugs.patientid -> patients.id
- ancsheetinvest.ancsheetid -> ancsheet.id; ancsheetinvest.investid -> invests.id (invests.investcatid -> investcats.id)
- mainantenental.patientid -> patients.id (varchar column); mainantenental.doctorid -> awusers.user_id
- antenalvisit.mainantenentalid -> mainantenental.id; antenalvisit.diagnosisid -> diagnosisant.id (CSV); antenalvisit.complaintid -> complaintant.id (CSV)
- mainantenentaldrugs.mainantenatalid -> mainantenental.id; .drugid -> drugs.id
- mainantenentalinvest.mainantenatalid -> mainantenental.id; .investid -> invests.id
- mainantenentalus.mainantenatalid -> mainantenental.id; .doctorid -> awusers.user_id
- wifeepc.infertilitysheetid -> infertilitysheet.id; wifeepc.wifeepctype -> wifeepctype.id; wifeepc.wifeepcttt -> wifeepcttt.id; wifeepc.wifeepcobst -> wifeepcobst.id
- awifep.wifemodeofd -> wifemodeofd.id; awifep.wifetypeofd -> wifetypeofd.id; awifep.wifeobst -> wifeobst.id (CSV multi)
- ancsheet.placenta <- placenta.title (value copied as text via select)
- registeration.ancsheetid -> ancsheet.id; investigations.ancsheetid / ovst.ancsheetid -> ancsheet.id (legacy attachments shown in sheet)
- IVF module: ivfsheet.antype -> antype.id; ivfsheet.antypes -> antypes.id; ivfsheet.pla2cen -> pla2cen.id; ivfsheet.anprotocol -> icsiprotocol.id (anprotocol table orphaned)
- Gravida computation reads phobstetric (obstermination in 1..5) + patients.pno/cs/svd/ab/ectopic/vmodel; reports read phobstericterminate / phobstericterplace lookups; patients.risktype for risk report

## Business Workflows (traced from code)
1. **Open ANC sheet** (`ancsheet.php::index`): loads patient; ensures one active `ancsheet` (`endpreg=0`) exists else creates it; ensures `infertilitysheet` exists; records screen entry in `lastvisit`; loads invest catalog grouped by `displayorder`; loads visits, legacy `investigations`/`ovst` attachments, `registeration` bookings, EPC and children (`awifep`) from infertility sheet.
2. **EDD calc** (`eedlmp()`): entering `sheetlmp` sets `sheetedd = lmp + 9 months + 7 days`; entering `sheetedd` back-computes lmp. First EDD set auto-creates `op_4d_list` row and stores `4d_list_id`. Field-by-field AJAX persistence (`Add()` generic table/column writer). Setting `tscandate`/`ttscandate` flips `op_4d_list.t11/t21 = 1`.
3. **New visit** (`append()`): inserts `ancnewvisit` with today's date and GA = weeks+days from lmp. GA strings are recomputed and re-stored on every page render (`loadAnc`).
4. **Prescriptions**: `getprescription()` loads today's `ancsheetdrugs` (wife/husband split via `forhusband`); rows added/updated inline; `showprescription` groups by date; `printpre` renders `gyna/print.html`. Soft delete via `deleted=1`.
5. **Investigations**: `addinvestigation()` bulk-inserts checked catalog items into `ancsheetinvest` and prints request; `showInvs` groups by date; results typed into `investresult`.
6. **End pregnancy** (`endpreg()`): sets `endpreg=1`, copies `obstg/obstp/obstepc` from infertilitysheet into the sheet, stamps `historytoday`. `archive()`/`historys()` browse ended sheets.
7. **Parallel path** (`antenalvisit.php::index`): ensures `mainantenental` (`done=0`) exists, recomputes `g` from phobstetric+patients counters each load; `newrow()` adds `antenalvisit`; `update()` handles lmp/eed bidirectional calc (also sets `ivf` date), CSV diagnosis/complaint lists, and "placentaOut" shortcut which upserts today's `mainantenentalus` row; `loadusrow()` adds US rows; own drugs/invest functions mirror ancsheet's; `endpreg()` sets `done=1`; `archive()`/`pregdetail()` browse by `g`.
8. **Reports**: `edd.php` (ancsheet.sheetedd = today / between dates / per patient, prints); `expected.php` (mainantenental.eed, done=0); `epc.php` (wifeepc by wifeepcdate, joins infertilitysheet for patient, resolves 3 lookups); `risktype.php` (patients by risktype — raw SQL); `termination.php` (phobstetric join patients by obstermination — raw SQL).
9. **Lookup management**: `getselectajax` inserts a new row (title) into any client-named lookup table; `getdataselect`/`deldataselect` list/soft-delete lookup values; `deleterow` hard-deletes child rows (R::trash).

## ERP Migration Notes
Proposed Laravel models (module `Antenatal`):
- `Pregnancy` (table `pregnancies`): merge `ancsheet` + `mainantenental`. Fields: patient_id FK, lmp DATE, edd DATE, ivf_reference_date, gravida, status enum(active,ended), ended_at, hb, fetus_sex, placenta_site_id FK, important_note, g_snapshot/p_snapshot/epc_snapshot. Migration must match the two legacy rows per pregnancy by patient + lmp/done flags; treat varchar dates and `0000-00-00` carefully.
- `AntenatalVisit` (merge `ancnewvisit` + `antenalvisit`): pregnancy_id, visit_date DATE, weight, bp, pulse, complaint, notes, plan, doctor_id. GA computed accessor — do NOT store it. Pivots `antenatal_visit_diagnoses`, `antenatal_visit_complaints` replacing CSV columns.
- `AntenatalUltrasound` (from `mainantenentalus` + ancsheet T/TT-scan columns): pregnancy_id, scan_type enum(routine,first_trimester,anomaly,four_d), date, ga, gs, crl, fhr, bpd, fl, placenta, afi, efw, result, report, doctor_id. Link 4D bookings (`op_4d_list`) here.
- Unified `Prescription`/`PrescriptionItem` and `InvestigationRequest` (+ result) with morphable or pregnancy_id context replacing the 4 duplicated tables (`ancsheetdrugs`, `mainantenentaldrugs`, `ancsheetinvest`, `mainantenentalinvest`); keep for_husband flag as subject enum(patient,husband); FK to drugs/invests catalogs and users.
- `PregnancyLoss` (from `wifeepc`): re-parent to patient_id directly (resolve via infertilitysheet.patientid during migration); loss_type_id, treatment_id, provider_id, gestational_weeks, histopathology, cost DECIMAL, occurred_on DATE.
- Lookups: `PregnancyLossType` (seed EPL/AT1/AT2/CVM/PVM/EP), `PregnancyLossTreatment`, `DeliveryMode` enum (CS/SVD/NVD), `DeliveryTerm` enum (full_term/preterm), `PlacentaSite` (clean `placenta` seed: drop NULL/free-text dups), `ExternalProvider` merging `wifeobst` + `wifeepcobst` (type: doctor/hospital/abroad; heavy dedup needed).
- Move `antype`, `antypes`, `pla2cen` to the IVF module migration; DROP `anprotocol` after verifying no live FK data (code uses `icsiprotocol`).
- Drop dead code path `ancsheet00.php`; keep only one workflow.
- Fix at migration time: varchar->DATE conversions ('Y/m/d' strings), `mainantenental.patientid` varchar->int, unify soft deletes (`del`/`deleted`/`conditions`/`endpreg`/`done`) to `deleted_at` + status, add real FKs/indexes (none exist), centralize EDD in an `EddCalculator` service, and replace the generic table/column AJAX writers and string-concatenated report SQL (risktype.php, termination.php) with validated endpoints to eliminate SQL injection and mass-assignment-by-table-name vulnerabilities.

---


# Module: Gynecology (gynecology)

## Purpose
Outpatient gynecology clinic of the legacy OB/GYN system (PHP + Smarty + RedBeanPHP "aw framework"). Covers:
- Per-patient gyna visit log (complaint, multi-diagnosis, notes, LMP).
- Prescriptions and investigation requests for wife OR husband from the gyna screen.
- Gyna ultrasound (cycle day, endometrium, ovaries, uterus, cervix) with per-follicle measurements.
- A one-page "Gyna Sheet" summary (critical note, menstrual history, local examination, follow-up visit rows).
- A compact infertility assessment (type, durations, hormones, semen, HSG) plus treatment-plan lines.
- A gyna-examination sub-record used inside the Infertility module.

## Controllers & Views (file paths)
Controllers (all in `/home/amrtechogate/public_html/obgy/core/controllers/`):
- `gyna.php` (1685 lines) — main gyna visit screen. Tables: `gyna`, `maingyna`, `gynadrugs`, `gynainvestigation`, `gynaus`, `gynausficils`, `gynainfertility`, `gynainfertilityplan`, `gynainfertilitytype`, plus cross-module `gynaph`, `lastvisit`, `diagnosis`, `complaint`, `drugs`, `invests`, `investcats`, `patients`, `programesetting`, `awusers`.
- `gynasheet.php` (775 lines) — Gyna Sheet screen. Tables: `gynasheet`, `newvisitg`, `gynasheetdrugs`, `gynasheetinvest`, plus `infertilitysheet` and generic lookup CRUD (`getselectajax`/`getselect`/`getdataselect`/`deldataselect`) over `menstrualreg`, `menstrualamount`, `menstrualdysm`, `localvulva`, `localvagina`, `localcx`, `newvisitgco`, `newvisitgdiag`.
- `gynasheet00.php` (466 lines) — legacy alternate sheet combining gyna visits + gynaus + gynasheet + infertilitysheet in one page (duplicated logic of gyna.php + gynasheet.php).
- `ultrasoundgyna.php` (316 lines) — sonographer workflow for the formal gyna US report. NOTE: operates on table `ultrasoundgyna` (ultrasound module, not in this module's table list); the gyna-visit US (`gynaus`) is managed inside `gyna.php`.
- `infertility.php` (cross-module) — creates/edits `infertilitygyna` rows (lines ~109, 144, 257).

Views (in `/home/amrtechogate/public_html/obgy/core/views/obgy/`):
- `gyna/` — add.html, newrow.html, newrowUS.html, infertility.html, newrowplan.html, addphmodel.html, showPrescriptions.html, showInvestigation.html, showph.html, print.html, printinv.html, printus.html, printph.html, newDrug.html, newrowdrugedit.html, newselectvalues.html, editmodel.html.
- `gynasheet/` — add.html, append.html, select.html, delselect.html, newrowUS.html, newrowdrugedit.html, showPrescriptions.html, showInvestigation.html.
- `Gynasheet/` (capitalized, used by gynasheet00.php) — add.html, select.html, delselect.html.
- `ultrasoundgyna/` — show.html, add.html, print.html.
- `infertility/newrow.html` — inline editing of `infertilitygyna` fields.

## Tables
No declared foreign keys anywhere; all FKs below are inferred from controller SQL/column names. All tables InnoDB utf8mb4, PK = `id`.

### gyna — gyna visit record (one row per visit)
Columns: `id` int unsigned AI PK; `patientid` int unsigned; `gynadate` date; `complaint` varchar(191); `diagnosisid` text (CSV of diagnosis ids); `notes` text; `conditions` int unsigned (soft delete: 0 active, 1 deleted); `doctorid` int unsigned; `diagnosistxt` varchar(191) (legacy free-text, merged into `complaint` on page load and blanked); `complaintid` varchar(191) (CSV of complaint ids); `inf_type`, `inf_since_y`, `inf_since_m`, `inf_stay_y`, `inf_stay_m`, `inf_stay_hus`, `inf_abortion`, `inf_semen`, `inf_fsh`, `inf_lh`, `inf_tsh`, `inf_prl`, `inf_amh`, `inf_hsg` all varchar(255); `inf_note` text; `gynalmp` date.
FKs: `patientid` -> patients.id; `doctorid` -> awusers.user_id; `diagnosisid` CSV -> diagnosis.id; `complaintid` CSV -> complaint.id.

### gynadrugs — prescription items on gyna screen
Columns: `id` int AI PK; `patientid` int; `date` date; `drugid` varchar(150); `drugtype` varchar(100); `drugdos` varchar(255); `deleted` int default 0; `doctorid` int; `drugname` varchar(191) (denormalized copy); `forhusband` int unsigned (0=wife, 1=husband); `recepittmpid` int default 0; `recepitdrugid` int default 0 (receipt-template linkage, inferred).
FKs: `patientid` -> patients.id; `drugid` -> drugs.id; `doctorid` -> awusers.user_id.

### gynainvestigation — investigation requests/results on gyna screen
Columns: `id` int AI PK; `patientid` int; `date` date; `investid` int; `investresult` varchar(250); `deleted` int default 0; `doctorid` int; `forhusband` int.
FKs: `patientid` -> patients.id; `investid` -> invests.id; `doctorid` -> awusers.user_id.

### gynasheet — one summary sheet header per patient
Columns: `id` int AI PK; `patientid` int unsigned; `usvsdtv` varchar(191); `importantnote` varchar(191) ("Critical Data" shown at top; `--`/`**` tokens rendered as `<br>`).
FKs: `patientid` -> patients.id. Parent of `newvisitg`.

### gynasheetdrugs — prescription items on Gyna Sheet screen
Identical structure to `gynadrugs` (same 12 columns). FKs: `patientid` -> patients.id; `drugid` -> drugs.id; `doctorid` -> awusers.user_id.

### gynasheetinvest — investigation requests on Gyna Sheet screen
Identical structure to `gynainvestigation` (same 8 columns). FKs: `patientid` -> patients.id; `investid` -> invests.id.

### gynaus — gyna-visit ultrasound (table comment: "ultrasound for gyna visits")
Columns: `id` int AI PK; `patientid` int; `date` date; `day` varchar(50) (cycle day); `endo` varchar(50) (endometrium); left ovary: `ovrayll`, `ovraylw`, `ovraylv`, `ovraylno`, `ovraylsize` varchar(50); right ovary: `ovrayrl`, `ovrayrw`, `ovrayrv`, `ovrayrno`, `ovrayrsize` varchar(50); uterus: `uterusl`, `uterusw`, `uterusv` varchar(50); cervix: `cervixl`, `cervixw`, `cervixv` varchar(50); `notes` text; `lfolliclesno` int default 0; `rfolliclesno` int default 0; `doctorid` int; `deleted` int default 0; `mainantenatalid` int unsigned.
FKs: `patientid` -> patients.id; `doctorid` -> awusers.user_id; `mainantenatalid` -> mainantenatal.id (inferred from name, set when US created from antenatal context).

### gynausficils — per-follicle measurements for a gynaus row
Columns: `id` int AI PK; `gynausid` int; `name` varchar(100); `length` varchar(100); `width` varchar(100); `volume` varchar(100); `type` int default 0 (0=left ovary, 1=right ovary — from `savefocil()`); `sysdate` date.
FK: `gynausid` -> gynaus.id. Saved in one transaction together with parent's `lfolliclesno`/`rfolliclesno`.

### gynainfertility — one infertility summary per patient (modal on gyna screen)
Columns: `id` int AI PK; `patientid` int; `note` text; `date` date; `type` varchar(255) (holds id of gynainfertilitytype — `addnewrecord()` stores the new type id here); `since_y`, `since_m`, `stay_y`, `stay_m`, `stay_hus`, `abortion`, `semen`, `fsh`, `lh`, `tsh`, `prl`, `amh`, `hsg` all varchar(255).
FKs: `patientid` -> patients.id; `type` -> gynainfertilitytype.id. Auto-created on first open (`getInfdata`). Duplicates `gyna.inf_*` columns.

### gynainfertilityplan — infertility treatment plan lines
Columns: `id` int AI PK; `patientid` int; `plan` text; `itr` int default 0; `deleted` int default 0.
FK: `patientid` -> patients.id. First row auto-created with empty plan.

### gynainfertilitytype — lookup: infertility types
Columns: `id` int AI PK; `name` text; `deleted` int default 0.
No seeded INSERTs in the dump (user-populated at runtime via `addnewrecord`). Referenced by `gynainfertility.type`.

### infertilitygyna — gyna examination rows inside the Infertility module
Columns: `id` int AI PK; `infertid` int; `date` date; `vagina` varchar(255); `exam` varchar(255); `sounding` varchar(255); `adenxae` varchar(255).
FK: `infertid` -> infertility.id. Created automatically when an `infertility` record is created (infertility.php ~line 109) and via `newgyna()`. WARNING: controller queries `del = 0` but the dump schema has NO `del` column — RedBeanPHP fluid mode presumably adds it at runtime (inference).

### maingyna — per-patient header notes/plan on the gyna screen
Columns: `id` int unsigned AI PK; `patientid` int unsigned; `create_date` datetime; `notes` varchar(191); `plan` varchar(191).
FK: `patientid` -> patients.id. Auto-created on first visit to gyna screen (gyna.php and gynasheet00.php index()).

### newvisitg — follow-up visit rows inside the Gyna Sheet
Columns: `id` int AI PK; `gynasheetid` int unsigned; `newvisitcycles` varchar(191); `newvisitgdiag` varchar(191) (CSV of newvisitgdiag ids); `newvisitgco` varchar(191) (CSV of newvisitgco ids); `date` varchar(191) (NOT a date type, stored "Y/m/d"); `lmp` varchar(191); `bw` varchar(191) (body weight); `ut` varchar(191) (uterus); `ov` varchar(191) (ovary); `noteg` varchar(191); `plan` text; `usvsdtv` varchar(191) (radio: 1=US, 2=TVS, 3=3D TVS — from gynasheet/add.html).
FKs: `gynasheetid` -> gynasheet.id; CSV columns -> newvisitgco.id / newvisitgdiag.id. Rows created by `gynasheet.php::append()` (tablename/tablep posted from client).

### newvisitgco — lookup: sheet-visit complaints (C/O)
Columns: `id` int unsigned AI PK; `title` varchar(191); `del` int unsigned (NULL=active, 1=deleted; queries use `del is null`).
No seeded data in dump; user-extensible via `getselectajax`. Referenced by `newvisitg.newvisitgco` (CSV, multi-select).

### newvisitgdiag — lookup: sheet-visit diagnoses
Columns: `id` int unsigned AI PK; `title` varchar(191); `del` int unsigned.
No seeded data. Referenced by `newvisitg.newvisitgdiag` (CSV, multi-select).

### menstrualamount — lookup: menstrual flow amount
Columns: `id` int unsigned AI PK; `title` varchar(191); `del` int unsigned.
No seeded data in this dump. Selected on Gyna Sheet "Menstrual History"; chosen value stored in `infertilitysheet.menstrualamount` (cross-module column).

### menstrualdysm — lookup: dysmenorrhea
Columns: `id` int unsigned AI PK; `title` varchar(191); `del` tinyint(1) unsigned.
No seeded data. Value stored in `infertilitysheet.menstrualdysm`.

### menstrualreg — lookup: menstrual regularity
Columns: `id` int unsigned AI PK; `title` varchar(191); `del` tinyint(1) unsigned.
No seeded data. Value stored in `infertilitysheet.menstrualreg`.

### localcx — lookup: local examination, cervix findings
Columns: `id` int unsigned AI PK; `title` varchar(191); `del` int unsigned.
No seeded data. Multi-select; CSV stored in `infertilitysheet.localcx`.

### localvagina — lookup: local examination, vagina findings
Columns: `id` int unsigned AI PK; `title` varchar(191); `del` int unsigned.
No seeded data. Multi-select; CSV stored in `infertilitysheet.localvagina`.

### localvulva — lookup: local examination, vulva findings
Columns: `id` int unsigned AI PK; `title` varchar(191); `del` int unsigned.
No seeded data. Multi-select; CSV stored in `infertilitysheet.localvulva`.

## Relationships (explicit list: table.column -> table.column)
- gyna.patientid -> patients.id
- gyna.doctorid -> awusers.user_id
- gyna.diagnosisid (CSV) -> diagnosis.id
- gyna.complaintid (CSV) -> complaint.id
- gynadrugs.patientid -> patients.id; gynadrugs.drugid -> drugs.id; gynadrugs.doctorid -> awusers.user_id
- gynainvestigation.patientid -> patients.id; gynainvestigation.investid -> invests.id; gynainvestigation.doctorid -> awusers.user_id
- gynasheet.patientid -> patients.id
- gynasheetdrugs.patientid -> patients.id; gynasheetdrugs.drugid -> drugs.id; gynasheetdrugs.doctorid -> awusers.user_id
- gynasheetinvest.patientid -> patients.id; gynasheetinvest.investid -> invests.id
- gynaus.patientid -> patients.id; gynaus.doctorid -> awusers.user_id; gynaus.mainantenatalid -> mainantenatal.id (inferred)
- gynausficils.gynausid -> gynaus.id
- gynainfertility.patientid -> patients.id; gynainfertility.type -> gynainfertilitytype.id
- gynainfertilityplan.patientid -> patients.id
- infertilitygyna.infertid -> infertility.id
- maingyna.patientid -> patients.id
- newvisitg.gynasheetid -> gynasheet.id
- newvisitg.newvisitgco (CSV) -> newvisitgco.id; newvisitg.newvisitgdiag (CSV) -> newvisitgdiag.id
- infertilitysheet.menstrualreg/menstrualamount/menstrualdysm -> menstrualreg.id / menstrualamount.id / menstrualdysm.id (cross-module storage column)
- infertilitysheet.localvulva/localvagina/localcx (CSV) -> localvulva.id / localvagina.id / localcx.id
- invests.investcatid -> investcats.id (catalog grouping used by the screens)

## Business Workflows (traced from code)
1. **Open gyna screen** (`gyna.php?patientid=N`): redirects to index if no patient; logs/updates `lastvisit`; auto-creates `maingyna` if missing; loads all non-deleted `gyna` rows DESC by date. Side effect during read: merges `diagnosistxt` into `complaint` and stores.
2. **New visit** (`newrow` AJAX): inserts empty `gyna` row dated today with current `doctorid`; every field then saved column-by-column via `update` (POST id/tableName/colName/value). Diagnosis/complaint multi-selects post JSON arrays stored as sorted CSV. New catalog terms added via `adddiagnosis` (appends new id to the CSV).
3. **Prescriptions**: `drawRow` (type=drug) inserts an empty `gynadrugs` (or `gynasheetdrugs`, or other modules' drug tables — shared endpoint) row for today's date and wife/husband; cascading drug selects (`search`: cat -> name -> type -> dose against `drugs`); `showprescription` lists by distinct date split wife/husband; `printpre` prints (optionally appending latest visit diagnosis when `programesetting.print_diag = 1`); `addprescription` soft-deletes an item.
4. **Investigations**: `addinvestigation` inserts one `gynainvestigation` row per checked invest (grouped UI by `investcats.displayorder`), prints the request; `showInvs` groups by date split wife/husband; results typed into `investresult` via `update`; `del` soft-deletes by date+subject or by id.
5. **Gyna ultrasound**: `loadusrow` (ussimple/uscomplete per `programesetting.ultrasound`) inserts `gynaus` row dated today; ovary follicle modal (`ltovary`/`rtovary`) lists `gynausficils`; `savefocil` stores N follicle rows + updates `lfolliclesno`/`rfolliclesno` inside an explicit transaction (R::begin/commit/rollback); `printus` prints selected `gynaus` rows; `delgynaus` soft-deletes.
6. **Infertility modal** (`getInfdata`): auto-creates one `gynainfertility` and one empty `gynainfertilityplan` per patient; `addnewplan` appends plan lines; `addnewrecord` creates a `gynainfertilitytype` and assigns its id to `gynainfertility.type`; `getalldata`/`deldata` manage the type lookup.
7. **Present history** (cross-module): `addph`/`showphs`/`printPh`/`delPh` write/read `gynaph` joined to `presenthistoryquestions`/`presenthistoryanswers`.
8. **Gyna Sheet** (`gynasheet.php`): auto-creates `gynasheet` AND `infertilitysheet` for the patient; renders menstrual history (menstrualreg/amount/dysm), sexual history, local exam (localvulva/localvagina/localcx) — all saved onto `infertilitysheet` columns; visit rows appended into `newvisitg` via `append` (client posts tablename + parent column name); generic `Add`/`update` save any posted table/column; sheet prescriptions/investigations go to `gynasheetdrugs`/`gynasheetinvest` with identical flows to step 3-4; lookups managed via `getselectajax` (insert title), `getdataselect`/`deldataselect` (list/soft-delete), `deleterow` (hard delete via R::trash).
9. **Legacy sheet** (`gynasheet00.php`): one page combining gyna visits, gynaus list, gynasheet + newvisitg, and the full infertilitysheet child-table set; duplicated code from gyna.php/gynasheet.php.
10. **Infertility module gyna exam**: creating an `infertility` file auto-creates an `infertilitygyna` row (with diagnosis/LMP/invest/notes siblings); `newgyna` appends more rows; fields (vagina/exam/sounding/adenxae) edited inline.
11. **Sonographer report** (`ultrasoundgyna.php`): on `add`, reuses latest unfinished `ultrasoundgyna` row or creates a draft (done=0, ~13 zeroed fields); sonographers fetched from `awusers where positionid = 4`; `showprint` prints and marks done=1; `delRow` soft-deletes (del=1). Table belongs to the ultrasound module.

## ERP Migration Notes (proposed Laravel model names, normalization advice, what to merge/drop)
- **GynaVisit** (`gyna_visits` from `gyna`): belongsTo Patient, Doctor. Replace CSV `diagnosisid`/`complaintid` with pivots `gyna_visit_diagnosis(visit_id, diagnosis_id)` and `gyna_visit_complaint(visit_id, complaint_id)`. Drop `diagnosistxt` (already migrated into complaint by the running code) and `conditions` (use `deleted_at`).
- **Prescriptions**: merge `gynadrugs` + `gynasheetdrugs` into `gyna_prescription_items` (or reuse a clinic-wide prescriptions table — the same row shape repeats in mainantenentaldrugs/followupdrugs/recorddrugs/etc. across modules) with `context` enum (visit|sheet) and `subject` enum (wife|husband) replacing `forhusband`. Real FK to `drugs`; drop denormalized `drugname` or keep as snapshot column intentionally.
- **Investigations**: merge `gynainvestigation` + `gynasheetinvest` into `gyna_investigation_requests` with the same `context`/`subject` pattern; FK to `invests`; structure result (value/unit/result_date) instead of free varchar `investresult`.
- **GynaUltrasound** (`gyna_ultrasounds` from `gynaus`): cast all measurements varchar(50) -> decimal; hasMany **GynaUltrasoundFollicle** (`gyna_ultrasound_follicles` from `gynausficils`) with `side` enum (left|right) replacing int `type`; drop `lfolliclesno`/`rfolliclesno` (derive via count). Keep nullable `antenatal_id` FK if the antenatal link is confirmed in data.
- **GynaSheet** (`gyna_sheets`) + **GynaSheetVisit** (`gyna_sheet_visits` from `newvisitg`): convert `date`/`lmp` varchar -> date; `usvsdtv` -> enum (US|TVS|TVS_3D); pivots for complaints/diagnoses instead of CSV.
- **Lookups**: `menstrualreg`, `menstrualamount`, `menstrualdysm`, `localcx`, `localvagina`, `localvulva`, `newvisitgco`, `newvisitgdiag`, `gynainfertilitytype` are all identical `(id, title, del)` shells, empty in this dump. Consolidate into one polymorphic `lookups` table (`type`, `title`, `is_active`) or seeded enums; keep runtime user-extensibility behind validated endpoints. Note these are stored against `infertilitysheet` columns today — the menstrual/local-exam answers belong logically to the gyna/infertility clinical sheet; decide owning model during data mapping.
- **InfertilityProfile** (`infertility_profiles`): merge `gynainfertility` with the duplicated `gyna.inf_*` columns (one canonical per-patient record; reconcile conflicts by latest date). **InfertilityPlan** from `gynainfertilityplan`. **InfertilityGynaExam** from `infertilitygyna` (FK `infertility_id`) — migrate alongside the Infertility module; remember the phantom `del` column (add it as `deleted_at`).
- **Drop/absorb**: `maingyna` -> two columns (`notes`, `plan`) on a per-patient GynaProfile/GynaSheet rather than its own table. Retire `gynasheet00.php` screen entirely (duplicate legacy).
- **Cross-cutting fixes**: add FKs + indexes on every `patient_id`/`doctor_id`/`*_id` and on (`patient_id`,`date`); unify soft delete (`deleted_at`) replacing `conditions`/`deleted`/`del`(int & NULL semantics differ!); add timestamps; replace the generic write-any-table endpoints (`update`, `Add`, `append`, `getselectajax`, `deleterow`) with per-model FormRequest-validated APIs (current design is a mass-assignment/table-injection risk; `search()` also concatenates SQL strings).

---


# Module: Infertility (infertility)

## Purpose
Couple-centric infertility / delayed-conception management. Two generations coexist:

1. **Legacy quick file** (`infertility.php` + `infertility` table family, keyed by `infertid`): one record per patient with dated child rows for diagnosis (wife+husband), LMP, gyna exam, investigations, notes, plus its own prescription table.
2. **Modern "Infertility Sheet"** (`infertilitysheet.php` + `infertilitysheet`, one row per patient, ~64 columns inline-edited via AJAX): full couple workup — infertility type/duration, menstrual/sexual/medical/surgical/family history, wife examination, 3-level husband diagnosis, previous treatment (ttt*) and response, computed obstetric formula (G/P/FT/PT/EPC/Living), per-spouse prescriptions and lab orders, structured obstetric history rows (`awifep`).

All lookup tables in this module are EMPTY in the dump (`obgy_12-7-2024.sql` has INSERTs only for drugs/invests/aw* framework tables); the clinical vocabulary is built at runtime by doctors via `getselectajax` (inline add-new-term).

## Controllers & Views (file paths)
- `/home/amrtechogate/public_html/obgy/core/controllers/infertility.php` (515 lines) — legacy quick file. Actions: `index` (auto-creates `infertility` + first child rows), `newdiagnosis`, `newlmp`, `newgyna`, `newinvest`, `newnote`, `update` (generic table/col/value), `del` (soft delete `del=1`), `addprescription` (soft-delete drug), `showprescription`, `printpre`.
- `/home/amrtechogate/public_html/obgy/core/controllers/infertilitysheet.php` (838 lines) — active sheet. Actions: `index`, `loadOne` (loads ~18 child collections by `infertilitysheetid`), `Add`/`update` (generic writes), `obstetric` (recompute G/P), `append` (insert child row into POSTed table), `folliculom`, `getselectajax` (add lookup term), `getselect` (render select of lookup), `getdataselect`/`deldataselect`/`addnewselect`/`deleterow` (lookup CRUD), prescriptions (`showprescription`, `printpre`, `addprescription` against `infertilitysheetdrugs`), investigations (`addinvestigation`, `showInvs`, `printinv` against `infertilitysheetinvest`).
- `/home/amrtechogate/public_html/obgy/core/controllers/infertilitysheet00.php` (513 lines) — OLDER duplicate of infertilitysheet.php (dead code; prescriptions still point to `gynadrugs`, loads `gyna`, `maingyna`).
- Views: `/home/amrtechogate/public_html/obgy/core/views/obgy/infertility/` (`add.html`, `newrow.html`, `newrowdrugedit.html`, `showPrescriptions.html`) and `/home/amrtechogate/public_html/obgy/core/views/obgy/infertilitysheet/` (`add.html` 1988 lines, `append.html`, `select.html`, `delselect.html`, `semen.html`, `showInvestigation.html`, `showPrescriptions.html`, `newrowdrugedit.html`). Print templates are shared from `gyna/print.html` and `gyna/printinv.html`.
- Previous-marriage tables are managed in the **patients module**: `core/controllers/patients.php` (lines 189-193, 1016-1020, 2235-2250 → `patients/previous_marriage.html`) and rendered in header via `core/controllers/_patientdata.php` (lines 297-305) / `patients/patientdata.html` (feature-flagged by `programesetting.previous_marriage`).

## Tables
All InnoDB; NO declared foreign keys anywhere — FKs below are inferred from controller code/views.

### infertility (legacy master, 1:1 patient)
- `id` int PK, `patientid` int NOT NULL, `marriage` varchar(255), `menstr` varchar(255), `operations` varchar(255), `sexualhistory` varchar(255), `breast` varchar(255), `hirsuitism` varchar(255), `sexch` varchar(255), `obesity` varchar(255)
- FK: `patientid` → patients.id. Auto-created on first visit (infertility.php:90-129).

### infertilitydiagnosis
- `id` PK, `infertid` int NOT NULL, `date` date, `diagnosiswife` text, `diagnosishusband` text, `del` int default 0
- FK: `infertid` → infertility.id. Soft delete `del`.

### infertilitydrugs (legacy prescriptions, one drug per row)
- `id` PK, `patientid` int, `date` date, `drugid` varchar(150), `drugtype` varchar(100), `drugdos` varchar(255), `deleted` int default 0, `doctorid` int, `drugname` varchar(191), `forhusband` int unsigned (0=wife, 1=husband), `recepittmpid` int default 0, `recepitdrugid` int default 0
- FK: `patientid` → patients.id; `drugid` → drugs.id (despite varchar type); `doctorid` → awusers.user_id.

### infertilityinvest (legacy investigation results, fixed columns)
- `id` PK, `infertid` int, `date` date, `semen`, `peg`, `hsg`, `pct`, `us`, `prolactin`, `thyr`, `fsh`, `lh`, `laparoscopy` (all varchar(255)), `del` int default 0
- FK: `infertid` → infertility.id. View also writes an `others` column (schema drift — RedBeanPHP auto-adds columns).

### infertilitylmp
- `id` PK, `infertid` int, `date` date. FK: `infertid` → infertility.id.

### infertilitynotes
- `id` PK, `infertid` int, `date` date, `note` text, `del` int default 0. FK: `infertid` → infertility.id.

### infertilitysheet (modern master, 1:1 patient, ~64 cols)
- `id` PK, `patientid` int unsigned
- Obstetric formula (computed by `obstetric()`): `obstg`, `obstp`, `obstft`, `obstpt`, `obstepc`, `obstliving` (int unsigned)
- Sheet header: `sheettype` (→ typeinf), `sheetyear`, `sheetmonth`, `sheetlocation` (→ sheetlocation), `historyduration`, `historyin`, `historyout`
- Husband: `husbndiagnosis1`, `husbndiagnosis2`, `husbndiagnosis3` (CSV ids → husbndiagnosis1/2/3 lookups, multi-select), `sheethusband` (CSV → sheethusband)
- Wife factors: `sheetwife` (CSV → sheetwife), `sheetother`
- Previous treatment: `sheetttt` (free text), `tttda` (→ tttda), `tttmetformin` (→ tttmetformin), `ttttype` (CSV → ttttype), `tttresponse` (→ tttresponse), `sheettrials`, `sheetdate`, `sheetresult`, `diagnosis`
- General exam: `generalbw`, `generalbmi`, `generalbreast`, `generalhirsutism`, `generalthyroid`
- Local exam: `localvulva`, `localvagina`, `localcx`, `localother`, `localother1`, `examinationgeneral`, `examinationlocalse`, `examinationlocalnote`
- Menstrual: `menstrualreg` (→ menstrualreg lookup, other module), `wmenstrualcd`, `menstrualamount`, `menstrualdysm`, `wmenstruallmp`, `menstrualh`
- History: `wsexual`, `medicalhistory`, `w2medical`, `surgicalhistory`, `wsurgical`, `familyhistory`, `wfamily`, `medicalhistorydm`, `medicalhistorynote`, `surgicalhistorydm`, `surgicalhistorynote`
- Misc: `usvsdtv`, `importantnote` (rendered with `--`/`**` → `<br>`)
- All varchar(191); dates stored as "Y/m/d" strings. FK: `patientid` → patients.id. Hub record: ~18 other-module tables key on `infertilitysheetid` (semeninfertility, semen2, hormonalprofile(2), wifeepc, tvs, dtvs, sis, hsginfertility, hysteroscopyinfertility, laparoscopyinfertility, infertilityother, icsi, folliculom, operations, newvisit).

### infertilitysheetdrugs (sheet prescriptions)
- Identical structure to infertilitydrugs: `id`, `patientid`, `date` date, `drugid` varchar(150), `drugtype`, `drugdos`, `deleted`, `doctorid`, `drugname`, `forhusband`, `recepittmpid`, `recepitdrugid`
- FK: `patientid` → patients.id; `drugid` → drugs.id; `doctorid` → awusers.user_id (set from `$_SESSION['user_id']`).

### infertilitysheetinvest (sheet lab orders + results)
- `id` PK, `patientid` int, `date` date, `investid` int, `investresult` varchar(250), `deleted` int default 0, `doctorid` int, `forhusband` int
- FK: `patientid` → patients.id; `investid` → invests.id (catalog grouped by investcats.displayorder buckets in controller); `doctorid` → awusers.user_id.

### Lookup tables — ALL share shape `id` PK, `title` varchar(191), `del` (active = `del IS NULL`); ALL EMPTY in dump; populated at runtime via getselectajax:
- **typeinf** — infertility type for `infertilitysheet.sheettype` (primary/secondary — inference; bound in infertilitysheet/add.html:115).
- **type** — NOT infertility-specific: bound to `registeration.type` in ANC sheet views (ancsheet/add.html:648, append.html:200). Generic lookup; misassigned to this module historically.
- **sheethusband** — husband-attributed infertility factors, multi-select CSV into `infertilitysheet.sheethusband` (add.html:1400).
- **sheetwife** — wife-attributed factors, multi-select CSV into `infertilitysheet.sheetwife` (add.html:1407).
- **sheetlocation** — exam/follow-up location for `infertilitysheet.sheetlocation` (add.html:164).
- **husbndiagnosis1 / husbndiagnosis2 / husbndiagnosis3** — 3-level husband (male-factor) diagnosis multi-selects (add.html:137-149).
- **wifesex** — baby sex lookup for obstetric history rows (`awifep.wifesex`, also legacy `wifep.wifesex`).
- **wifepmethod** — post-delivery contraception method, LEGACY, paired with `wifep`.
- **awifepmethod** — post-delivery contraception method, ACTIVE version, multi-select CSV in `awifep.awifepmethod` (append.html:167, add.html:576); note `wifep` also has an `awifepmethod` column (migration artifact).
- **tttda** — previous treatment: dopamine agonist / ovulation-induction agent ("D.A." — inference) for `infertilitysheet.tttda` (add.html:1188).
- **tttmetformin** — metformin use in previous treatment (add.html:1192).
- **ttttype** — previous treatment/stimulation type, multi-select CSV (add.html:1199).
- **tttresponse** — response to previous treatment (add.html:1203).

### wifep (LEGACY obstetric-history rows)
- `id` PK, `infertilitysheetid` varchar(191), `wifemodeofd`, `wifeobst`, `wifesex`, `wifepmethod`, `wifetypeofd`, `name`, `date`, `comment`, `duration` (varchar(191)), `wifel` tinyint, `awifepmethod` varchar(191)
- FK: `infertilitysheetid` → infertilitysheet.id (varchar! type mismatch); `wifesex` → wifesex.id; `wifepmethod` → wifepmethod.id; `wifemodeofd`/`wifetypeofd`/`wifeobst` → same-named lookups (other module scope). Superseded by `awifep`; still READ in `ivfsheet.php:378` (`ivfhistory` action).

### awifep (ACTIVE obstetric-history rows; "a" = newer revision — inference)
- `id` PK, `infertilitysheetid` int unsigned, `wifesex`, `wifemodeofd`, `wifeobst`, `wifetypeofd`, `name`, `date`, `wifew` (birth weight), `wifel` (living flag; counted as `wifel=1`), `duration`, `stopped`, `comment`, `awifepmethod` (CSV), `wifeage` (all varchar(191))
- FK: `infertilitysheetid` → infertilitysheet.id; `wifesex` → wifesex.id; `awifepmethod` → awifepmethod.id (CSV); `wifetypeofd` → wifetypeofd.id (values "FT"/"PT" used in `obstetric()` to count full-term/pre-term). Rows created by `append` action; `wifeage` computed at render (DateTime diff). Read by infertilitysheet.php, infertilitysheet00.php, ivfsheet.php(:114).

### previous_marriage (wife's previous marriages — patients module screen)
- `id` PK, `period` varchar(255), `males` int default 0, `females` int default 0, `last_age` varchar(255), `deleted` int default 0, `patientid` int default 0
- CHARSET **latin1** (Arabic-content risk). FK: `patientid` → patients.id. Feature flag `programesetting.previous_marriage`.

### hus_previous_marriage (husband's previous marriages)
- Identical columns/charset to previous_marriage. FK: `patientid` → patients.id.

## Relationships (explicit list)
- infertility.patientid → patients.id
- infertilitydiagnosis.infertid → infertility.id
- infertilitylmp.infertid → infertility.id
- infertilityinvest.infertid → infertility.id
- infertilitynotes.infertid → infertility.id
- infertilitydrugs.patientid → patients.id; infertilitydrugs.drugid → drugs.id; infertilitydrugs.doctorid → awusers.user_id
- infertilitysheet.patientid → patients.id
- infertilitysheet.sheettype → typeinf.id
- infertilitysheet.sheetlocation → sheetlocation.id
- infertilitysheet.husbndiagnosis1/2/3 → husbndiagnosis1/2/3.id (CSV)
- infertilitysheet.tttda → tttda.id; .tttmetformin → tttmetformin.id; .ttttype → ttttype.id (CSV); .tttresponse → tttresponse.id
- infertilitysheet.sheethusband → sheethusband.id (CSV); .sheetwife → sheetwife.id (CSV)
- infertilitysheetdrugs.patientid → patients.id; .drugid → drugs.id; .doctorid → awusers.user_id
- infertilitysheetinvest.patientid → patients.id; .investid → invests.id; .doctorid → awusers.user_id
- awifep.infertilitysheetid → infertilitysheet.id; awifep.wifesex → wifesex.id; awifep.awifepmethod → awifepmethod.id (CSV); awifep.wifemodeofd → wifemodeofd.id; awifep.wifetypeofd → wifetypeofd.id
- wifep.infertilitysheetid → infertilitysheet.id (legacy); wifep.wifepmethod → wifepmethod.id; wifep.wifesex → wifesex.id
- previous_marriage.patientid → patients.id; hus_previous_marriage.patientid → patients.id
- registeration.type → type.id (ANC module usage of assigned lookup `type`)
- Cross-module reads: obstetric() reads ancsheet (patientid, endpreg=0, sheetlmp) and wifeepc (infertilitysheetid); ivfsheet reads infertilitysheet/awifep/wifep/wifeepc by infertilitysheetid; gynasheet auto-created from sheet screen; lastvisit upserted per patient; invests/investcats catalog; programesetting; wifetypes/husbandtypes for print headers.

## Business Workflows (traced from code)
1. **Open sheet** (`infertilitysheet.php?patientid=N&ac=index`): loads patient, auto-creates `infertilitysheet` (and `gynasheet`) if absent, upserts `lastvisit`, loads drug catalog, invest catalog (5 displayorder buckets + favorites), and via `loadOne()` all 18 child collections keyed by `infertilitysheetid`. Renders `infertilitysheet/add.html`.
2. **Field-level autosave**: every input/select POSTs `Add`/`update` with `table`,`id`,`name`/`colName`,`value` → RedBeanPHP load/store. `update` special-cases `infertilitysheetdrugs.drugid` (copies drugname) and date flags (`Y/m/d` reformat).
3. **Lookup-driven selects**: `getselect` renders a lookup's options (active = `del IS NULL`) with current value(s) (`explode(',')`); `getselectajax` inserts a new term typed by the doctor; `getdataselect`/`deldataselect` manage (soft-delete) terms.
4. **Obstetric history**: `append` (POST tablename=awifep, tablep=infertilitysheetid) inserts a dated row, rendered by `append.html`; row fields autosaved; `deleterow` hard-deletes (R::trash). `obstetric()` recounts: FT/PT from awifep.wifetypeofd matching wifetypeofd titles "FT"/"PT", abortions from wifeepc (wifeepctype>0), living from awifep.wifel=1, +1 gravida if active ancsheet (endpreg=0) has sheetlmp; stores obst* on sheet and echoes CSV for UI.
5. **Prescriptions** (per spouse, per date): rows in `infertilitysheetdrugs` with forhusband 0/1; `showprescription` (type=show) groups by distinct date with wife/husband drug-name summaries; (type=edit) reloads a date for editing; `printpre` prints via shared `gyna/print.html` using patient wife/husband names + wifetypes/husbandtypes titles.
6. **Investigations**: `addinvestigation` bulk-inserts checked catalog items into `infertilitysheetinvest` (doctorid from session, forhusband flag) and immediately prints (`gyna/printinv.html`); `showInvs` lists per-date husband/wife orders with results; results entered via generic update into `investresult`.
7. **Legacy quick file** (`infertility.php?patientid=N`): auto-creates `infertility` + one initial row each in infertilitydiagnosis/infertilitylmp/infertilitygyna/infertilityinvest/infertilitynotes; per-section "new row" AJAX actions; generic `update`/`del` (del=1); own prescription set in `infertilitydrugs` with identical show/edit/print flow.
8. **Previous marriages** (patients module): added/edited from patient screen (patients.php case at :2235 chooses `previous_marriage` vs `hus_previous_marriage`), displayed in the shared patient-data header on all module screens when `programesetting.previous_marriage = 1`.

## ERP Migration Notes
**Proposed Laravel models:**
- `InfertilityFile` (merge `infertility` + `infertilitysheet`; one per patient, FK patients) with structured satellites: `InfertilityHistory`, `InfertilityExamination`, `InfertilityMenstrualHistory`, `InfertilityTreatmentHistory` (tttda/tttmetformin/ttttype/tttresponse/trials/result), `InfertilityDiagnosisEntry` (dated wife/husband diagnosis ← infertilitydiagnosis), `InfertilityNote` (← infertilitynotes), `LmpRecord` (← infertilitylmp).
- `ObstetricHistory` — single model replacing `wifep` + `awifep` (migrate both, dedupe; columns: sheet FK, baby_sex_id, delivery_mode_id, delivery_type_id, birth_date date, weight, living bool, contraception via pivot, duration, stopped, comment). Drop stored `wifeage`; compute.
- `Prescription` + `PrescriptionItem` — unify `infertilitydrugs` + `infertilitysheetdrugs` (and gynadrugs etc. from other modules) with `context` enum and `for_spouse` enum(wife,husband); FK drugs, users. Fix drugid varchar→FK int.
- `LabOrder` + `LabOrderItem` (← infertilitysheetinvest; result on item) FK invests, users, for_spouse.
- `PreviousMarriage` — single table with `spouse` enum replacing previous_marriage/hus_previous_marriage; convert latin1→utf8mb4; belongs to Patient (patients module).
- `Lookup`/per-domain enum tables replacing the 15 identical lookup tables (typeinf, sheethusband, sheetwife, sheetlocation, husbndiagnosis1-3, ttt*×4, wifesex, wifepmethod+awifepmethod merged, type). Multi-select fields (husbndiagnosis1-3, ttttype, sheethusband, sheetwife, awifepmethod) become pivot tables; write a CSV-explode migration script. Note: lookups are empty in dump but production data will contain runtime-entered terms — migrate distinct titles, trim/dedupe.
- Move lookup `type` to the ANC module scope (used by `registeration.type` only).

**Normalize/fix during migration:**
- All varchar dates ("Y/m/d", "d-m-Y") → DATE columns with tolerant parser; `wifep.infertilitysheetid` varchar → int.
- Replace stored obst* counters with computed accessors from ObstetricHistory + abortions + active pregnancy.
- Standardize soft delete (`del` int / `deleted` int / `del IS NULL` semantics) → Laravel SoftDeletes.
- RedBeanPHP schema drift: production tables may contain extra auto-created columns (e.g. infertilityinvest.others, infertilitygyna) — diff live schema vs dump before migration.

**Drop/security:**
- Drop `infertilitysheet00.php` (dead duplicate). Decommission generic AJAX endpoints (`Add`, `update`, `append`, `getselect*`, `deleterow`) that accept arbitrary table/column names from POST — replace with per-model FormRequest-validated endpoints + Policies (current code allows arbitrary DB writes/deletes; only login is checked, several actions skip even role authorization).

---


# Module: IVF / ICSI / IUI & Cycle Monitoring (ivf)

## Purpose
Manages assisted-reproduction treatment cycles in the OB/GYN clinic system:

1. **IVF/ICSI Sheet** (`ivfsheet` + child tables `ovst`, `eprep`, plus runtime table `sefo`): one active sheet per patient covering pre-stimulation evaluation (AMH, AFC, protocol), the ovarian-stimulation grid (follicle counts by diameter 10-20mm, endometrium, E2), trigger (TOO), oocyte pickup (OPU), semen data, fertilization, fresh embryo transfer, freezing, frozen-cycle endometrial preparation/thawing/transfer, and pregnancy outcome (HCG / gestational sac / delivery). Archiving a sheet generates denormalized summary row(s) in `icsi` attached to the patient's infertility sheet.
2. **Monitoring Sheet** (`mointoringsheet` + `mointoringsheetvisits` + 9 lookup tables + drugs/investigations tables): an independent cycle-monitoring workflow with attempt number, procedure, stimulation protocol, sperm source (ejaculate/PESA/TESE/cryo), HMG/agonist/HCG drug choices, daily monitoring visits (cycle day, HMG dose, endometrium, right/left follicles, E2), wife/husband prescriptions and investigations, OPU/embryology summary, and outcome, then archive + trial history.
3. **Folliculometry** (`folliculom`): simple ovulation-induction tracking rows attached to the infertility sheet.
4. **Reports**: IVF statistics (per-period/per-patient cycle outcomes) and a daily/period IUI report (reads `infertilitysheet.sheetdate`/`sheetresult`).

ORM: RedBeanPHP (`R::`) in fluid mode; templating: Smarty. No declared foreign keys anywhere; all relationships inferred.

## Controllers & Views (file paths)
Controllers (`/home/amrtechogate/public_html/obgy/core/controllers/`):
- `ivfsheet.php` — main IVF/ICSI sheet (active sheet, AJAX cell saves, ovst/eprep/sefo row append, cycle-day date math, archive on end-of-trial, history). Current version.
- `ivfsheet00.php` — older duplicate of `ivfsheet.php` (no `sefo` support, no BMI copy). Dead/legacy code.
- `monitoring.php` — monitoring sheet (auto-open sheet, 9 lookup dropdowns w/ inline add, daily visit rows, prescriptions, investigations, archive/trials/trialdetail with 9-way LEFT JOIN, financial `search()` that actually belongs to the finance report).
- `Ivfstatistics.php` — IVF statistics search/print over `ivfsheet` by `lmpfresh`/`lmpfrozen` range.
- `iui.php` — IUI daily list / period search / print over `infertilitysheet` (`sheetdate`, `sheetresult`).
- Read-only consumers: `infertilitysheet.php` / `infertilitysheet00.php` (display `icsi` and `folliculom`, append `folliculom` rows, cycle-day calc), `Completesreport.php`, `sh.php`, `patienthistory.php` (full patient report).

Views (`/home/amrtechogate/public_html/obgy/core/views/obgy/`):
- `ivfsheet/`: `add.html` (main sheet: Infertility/ICSI-trial header, OVST grid CD|Date|Ag-Antag|HMG|F(10..20,N)|E|E2|Note, TOO, OPU, Semen, F/E D2, fresh & frozen ET, eprep, outcomes), `archive.html`, `ivfhistory.html`, `append.html`, `select.html`, `delselect.html`.
- `monitoring/`: `add.html`, `newrow.html`, `newrowUS.html`, `trials.html`, `archive.html`, `drugmodal.html`, `newrowdrugedit.html`, `showPrescriptions.html`, `showInvestigation.html`, `editmodel.html`.
- `reports2/`: `Ivfstatistics.html`, `Ivfstatisticssearch.html`, `Ivfstatisticsprint.html`, `iui.html`, `iuisearch.html`, `iuiprint.html`.
- Print templates reused from gyna module: `gyna/print.html`, `gyna/printinv.html`.

## Tables
Schema source: `/home/amrtechogate/public_html/obgy/_db/obgy_12-7-2024.sql`. **None of the 24 assigned tables has INSERT rows in this dump** — all lookup vocabularies are user-managed at runtime (`addNewItem` / `getselectajax`), so seed data must be exported from production.

### ivfsheet — active/archived IVF-ICSI trial sheet (one active per patient, `endpreg = 0`)
All columns `varchar(191)` unless noted.
- `id` int(11) PK AI
- `patientid` int unsigned — FK → patients.id
- `endpreg` int unsigned — 0 = active, 1 = archived
- Evaluation: `anevaluation`, `anevaluations`, `antype` (FK → antype.id), `anduration`, `anamh` (AMH), `annn` (AFC), `antypes` (FK → antypes.id), `anprotocol` (FK → icsiprotocol.id), `anttt` (treatment), `annotes`
- Fresh cycle: `lmpfresh`, `toocd`/`toodate`/`toodrug` (trigger of ovulation), `opun`/`opumll`/`opuml`/`opugv`/`opuat`/`oopuu`/`opunote` (OPU: total, MII, MI, GV, anesthesia/time, notes), `ssemen`/`sseemen` (FK → sseemen.id)/`sseemmen` (FK → icsisemen.id)/`sseemmeen` (semen data), `fefn`/`fefa`/`fefb`/`fefc` (fertilization/embryos D2 by grade), `ettcd`-family: `ettn`/`ettg`/`ettdate`/`ettnote`/`ettcancel` (fresh ET number/grade/date/cancel), `frrcd`/`frrst`/`frren` (freezing: cycle day, stage, embryo count), `sssnofte`/`sssresult`/`ssscd` (post-ET test), fresh outcomes `ooutcomehsgd`/`ooutcomehsgn` (HCG), `ooutcometvsd`/`ooutcometvsn` (TVS sac), `ooutcomedeld`/`ooutcomedeln` (delivery)
- Frozen cycle: `lmpfrozen`, `ssnofte`/`ssresult`, `etcd`/`etdate`/`etn`/`etg`/`etnote`/`etcancel` (frozen ET), `refren` (re-freeze count), frozen outcomes `outcomehsgd`/`outcomehsgn`, `outcometvsd`/`outcometvsn`, `outcomedeld`/`outcomedeln`
- Closure/meta: `frozenor` ("fresh"/"frozen"), `historytoday` datetime, `e2d2`, `pla2ce`, `pla2cen` (FK → pla2cen.id, ICSI place), `importantnote`, `sheetyear` int, `sheetmonth` int, `bmi` varchar(255) (copied from `examination.bmi` on archive)

### ovst — ovarian stimulation grid rows (child of ivfsheet)
- `id` int PK AI; `ivfsheetid` int unsigned — FK → ivfsheet.id
- `ovstcdfh` int unsigned (cycle day), `ovstdate`, `ovstag` (agonist/antagonist), `ovsthmg` (HMG dose)
- 22 cryptic varchar columns `ovstfr`, `ovstfrr`, `ovstfrrr`, `ovstfrrrr`, `ovstffrrrr`, `ovstfffrrr`, `ovstffffrr`, `ovstfffffr`, `ovstffffff`, `ovstfrrffff`, `ovstfrfffff`, `ovstdfr` … `ovstdffffff`, `ovstdfrrffff`, `ovstdfrfffff` = grid cells for follicle counts per diameter 10,11,12,…,20 mm + N (mapping defined only by column order in `ivfsheet/add.html`; `d`-prefixed set is the second row of the grid)
- `eovst` (endometrium), `eeovst` (E2), `noteovst`

### eprep — endometrial preparation for frozen ET (child of ivfsheet)
- `id` PK AI; `ivfsheetid` int unsigned — FK → ivfsheet.id
- `epreplps` — FK → epreplps.id (luteal phase support regimen)
- `eprepcd`, `cdlps`, `lmpfrozen`, `eprepdate`, `eprepttt` (treatment), `eprepe` (endometrium), `eprepro`/`epreplo` (right/left ovary), `eprepcancel`
- Thawing/transfer: `thawingdate`, `thawingstn` (thawed n), `thawingen` (transferred n)

### epreplps — lookup: LPS regimens
- `id` PK AI, `title` varchar(191), `del` tinyint. Empty in dump. Soft-delete convention: `del is null` = active.

### icsi — denormalized ICSI trial history (generated by `endpreg()`, displayed on infertility sheet)
- `id` PK AI; `infertilitysheetid` int unsigned — FK → infertilitysheet.id
- `icsiprotocol`, `icsiplace`, `icsisemen`, `icsiresult`, `icsiss` — **text snapshots** of lookup titles (not FKs)
- `refr` (re-freeze), `fr` (frozen n), `vd`, `et` (transferred n), `fert`, `opu`, `date`, `e2d2`
- Runtime-added column `sefoid` (FK → sefo.id) used by `ivfsheet.php` but absent from the dump (RedBean fluid mode).

### icsiplace / icsiprotocol / icsiresult / icsisemen / icsiss — lookups
All: `id` PK AI, `title` varchar(191), `del` (int or tinyint). All empty in dump.
- `icsiprotocol` — stimulation protocols; referenced by id from `ivfsheet.anprotocol`, copied as text into `icsi.icsiprotocol`.
- `icsisemen` — semen source/state; referenced by id from `ivfsheet.sseemmen`.
- `icsiplace` — ICSI center/lab; loaded by id in reports (`Completesreport.php`, `sh.php`) from `icsi.icsiplace` (mixed id/text usage).
- `icsiresult` — trial result vocabulary.
- `icsiss` — post-transfer pregnancy-test vocabulary (stored into `icsi.icsiss`).

### mointoringsheet — cycle monitoring sheet header (note misspelling, latin1 table)
- `id` PK AI, `doctorid` int — FK → awusers (session user), `patientid` int — FK → patients.id
- `monitordate` date (cycle start), `lmp` date, `attempno` int
- Lookup FKs (int): `procedure` → mointoringsheetprocedure.id, `protocol` → mointoringsheetprotocol.id, `ejac` → mointoringsheetejac.id, `pesa` → mointoringsheetpesa.id, `tese` → mointoringsheettese.id, `sryo` → mointoringsheetsryo.id, `hmg` → mointoringsheethmg.id, `agonist` → mointoringsheetagonist.id, `hcg` → mointoringsheethcg.id
- `remarks` text, `place`, `amp` (HCG ampoules)
- Embryology summary: `oocytestotal`, `oocytesm1`, `oocytesm2`, `oocytesgv`, `ettotal`, `etg1`, `cyroday`, `cyrow`, `p4`, `outcome` (all varchar(255))
- `status` int (0 open, 1 archived — comment in schema), `sysdate` date, `deleted` int, `notes` text

### mointoringsheetvisits — daily monitoring visit rows
- `id` PK AI, `mointoringsheetid` int — FK → mointoringsheet.id
- `visitdate` date, `cycleday` varchar (e.g. "5th", computed from monitordate), `hmg` text, `endo` text, `follicright` text, `follicleft` text, `e2` text, `sysdate` date, `deleted` int

### mointoringsheetdrugs — monitoring prescriptions (wife/husband)
- `id` PK AI, `patientid` int — FK → patients.id, `date` date, `drugid` varchar(150) — FK → drugs.id, `drugtype`, `drugdos`, `drugname`, `deleted` int, `doctorid` int — FK → awusers, `forhusband` int (0 wife / 1 husband), `recepittmpid` int / `recepitdrugid` int — pharmacy receipt links (inferred), `mointoringsheetid` int — FK → mointoringsheet.id

### mointoringsheetinvestigation — monitoring lab orders (wife/husband)
- `id` PK AI, `patientid` int — FK → patients.id, `date` date, `investid` int — FK → invests.id, `investresult` varchar(250), `deleted` int, `doctorid` int — FK → awusers, `forhusband` int, `mointoringsheetid` int — FK → mointoringsheet.id

### mointoringsheetprocedure / mointoringsheetprotocol / mointoringsheetejac / mointoringsheetpesa / mointoringsheettese / mointoringsheetsryo / mointoringsheethmg / mointoringsheetagonist / mointoringsheethcg — 9 lookups
All identical: `id` int PK AI, `name` varchar(255), `deleted` int default 0. All empty in dump; values entered live from the monitoring screen (`addNewItem`). Semantics: procedure type (IVF/ICSI/IUI…), stimulation protocol, ejaculate option, PESA option, TESE option, cryo-sperm option ("sryo" = cryo), HMG drug, agonist/antagonist drug, HCG trigger drug.

### folliculom — folliculometry rows (ovulation induction, child of infertility sheet)
- `id` PK AI, `infertilitysheetid` int unsigned — FK → infertilitysheet.id
- `lmp`, `ttt` (induction drug), `cd` (cycle day), `date`, `ro`/`lo` (right/left ovary follicles), `endo`, `pelvis`, `too` (trigger), `result` — all varchar(191)

## Relationships (explicit list: table.column -> table.column)
- ivfsheet.patientid -> patients.id
- ivfsheet.antype -> antype.id ; ivfsheet.antypes -> antypes.id
- ivfsheet.anprotocol -> icsiprotocol.id
- ivfsheet.sseemmen -> icsisemen.id ; ivfsheet.sseemen -> sseemen.id
- ivfsheet.pla2cen -> pla2cen.id (ICSI place lookup, other module)
- ovst.ivfsheetid -> ivfsheet.id
- eprep.ivfsheetid -> ivfsheet.id ; eprep.epreplps -> epreplps.id
- sefo.ivfsheetid -> ivfsheet.id (runtime table, NOT in dump)
- icsi.infertilitysheetid -> infertilitysheet.id ; icsi.sefoid -> sefo.id (runtime column)
- folliculom.infertilitysheetid -> infertilitysheet.id
- mointoringsheet.patientid -> patients.id ; mointoringsheet.doctorid -> awusers.user_id
- mointoringsheet.{procedure,protocol,ejac,pesa,tese,sryo,hmg,agonist,hcg} -> mointoringsheet{procedure,protocol,ejac,pesa,tese,sryo,hmg,agonist,hcg}.id (confirmed by 9-way LEFT JOIN in `monitoring.php::trialdetail`)
- mointoringsheetvisits.mointoringsheetid -> mointoringsheet.id
- mointoringsheetdrugs.patientid -> patients.id ; .drugid -> drugs.id ; .mointoringsheetid -> mointoringsheet.id ; .doctorid -> awusers ; .recepittmpid/.recepitdrugid -> pharmacy receipts (inferred)
- mointoringsheetinvestigation.patientid -> patients.id ; .investid -> invests.id ; .mointoringsheetid -> mointoringsheet.id
- ivfsheet.bmi <- copied from examination.bmi (latest, on archive)
- IUI report: reads infertilitysheet.sheetdate / sheetresult / patientid -> patients.id
- lastvisit.patientid -> patients.id (screen-entry tracking written by ivfsheet/iui controllers)

## Business Workflows (traced from code)
1. **Open IVF sheet** (`ivfsheet.php::index?patientid=`): finds `ivfsheet` with `endpreg=0` or auto-creates it; also auto-creates `infertilitysheet` and `gynasheet` if missing; loads child `ovst`, `eprep`, `sefo` rows; records entry in `lastvisit`.
2. **Field-level autosave**: every input posts to `Add()` with `{table, id, name, value}` → direct `R::store`. No save button, no validation, table/column names come from the client.
3. **Stimulation rows**: `append()` dispenses a new `ovst`/`eprep`/`sefo` row bound to the sheet; entering a cycle day calls `ovstcdfh()`/`newcd()` which compute the date as `lmpfresh + (cd-1) days` (raw `R::exec` UPDATE in `newcd`).
4. **Trial closure** (`endpreg(kind)`): sets `endpreg=1`, `frozenor=fresh|frozen`, `historytoday=now`, copies latest `examination.bmi`; then dispenses an `icsi` summary row (frozen branch copies `lmpfrozen/fefn/etn/frren/refren/ssnofte/outcomehsgn`; fresh branch copies `lmpfresh/semen title/opun/fefn/ettn/frren/sssnofte/ooutcomehsgn/ssscd`), resolving `icsiprotocol`/`icsisemen`/`pla2cen` ids to **titles**, linked to `infertilitysheet.id`; fresh branch additionally writes one `icsi` row per `sefo` row. Patient can then start a fresh `ivfsheet`.
5. **IVF archive/history**: `archive()` lists `endpreg=1` sheets; `historys(ivfsheetid)` renders a past sheet read-only with its ovst/eprep/sefo children.
6. **Monitoring sheet** (`monitoring.php::index`): finds/creates the open sheet (`status=0`) per patient; loads all 9 lookup lists, today's prescriptions, investigation catalog (`investcats`/`invests` grouped by displayorder). Lookups support inline create (`addNewItem`, optionally setting the parent FK), edit (`editselect` + generic `update`), soft delete (`del`).
7. **Daily visits**: `addNewVisit()` creates a `mointoringsheetvisits` row with cycle day = days(monitordate→today)+1 with ordinal suffix; cells (hmg/endo/follicright/follicleft/e2) update via generic `update()`; `getCycleDay()` recomputes on date edits.
8. **Prescriptions / investigations**: `mointoringsheetdrugs` and `mointoringsheetinvestigation` per date and per partner (`forhusband` 0/1), with history modals (`showprescription`, `showInvs`) and print pages reusing gyna templates; partner name/title resolved from `patients` + `wifetypes`/`husbandtypes`.
9. **Monitoring closure**: `remarkSheet()` fetches hcg/remarks for the trigger modal; `trigSheet()` sets `status=1` (archive). `trials()` lists archived sheets (resolving procedure/protocol names); `trialdetail(id)` renders full detail via a 9-way LEFT JOIN to lookup tables plus its visits.
10. **Folliculometry**: rows appended from the infertility sheet screen (`infertilitysheet.php::append` with tablename=folliculom); `folliculom()` AJAX computes date = latest non-null `lmp` + cd days.
11. **IVF statistics** (`Ivfstatistics.php::search`): filters `ivfsheet` by `lmpfresh`/`lmpfrozen` between dates (optionally per patient); per row computes wife's age at trial from `patients.dateofbirth`, loads `antype`, `icsiprotocol`, `sseemen`, `icsisemen`, first/last `ovst`, `eprep` count and first/last. `showprint()` mistakenly queries `ancsheet` by `sheetedd` (copy-paste from ANC report — broken).
12. **IUI report** (`iui.php`): daily list of `infertilitysheet` rows with `sheetdate = today`; `search`/`showprint` filter by patient and date range via string-concatenated SQL; displays `sheetdate` and `sheetresult` (IUI clinical data itself lives in the infertility module).

## ERP Migration Notes (proposed Laravel model names, normalization advice, what to merge/drop)
Proposed models (Laravel + Angular):
- `IvfCycle` — unifies `ivfsheet` + `mointoringsheet`: patient_id, doctor_id, attempt_no, cycle_type (fresh/frozen/iui/monitoring-only), procedure_id, protocol_id, lmp, start_date, status (active/archived), bmi, amh, afc, notes, outcome fields. Enforce one-active-cycle-per-patient with a partial unique index.
- `IvfCycleVisit` — unifies `ovst` + `mointoringsheetvisits` + `folliculom`: cycle_id, visit_date, cycle_day, agonist_dose, hmg_dose, endometrium_mm, e2, p4, notes. Child `FollicleMeasurement` (visit_id, side enum R/L, diameter_mm, count) replaces the 26 cryptic `ovst` columns and the free-text follicle fields — map column order from `ivfsheet/add.html` (F columns = 10..20 mm + N; `d`-prefixed = second grid row) during ETL.
- `OocyteRetrieval` (cycle_id, date, total, mii, mi, gv, anesthesia, notes) ← `opun/opumll/opuml/opugv/opuat`, `oocytestotal/m1/m2/gv`.
- `SemenSample` (cycle_id, source enum ejaculate/pesa/tese/cryo, quality fields) ← `ssemen*` columns + `ejac/pesa/tese/sryo` lookups.
- `EmbryoTransfer` (cycle_id, kind fresh/frozen, transfer_date, cycle_day, n_transferred, grade, cancelled, notes) ← `ett*`, `et*`, plus extra fresh transfers from runtime table `sefo`.
- `Cryopreservation` (cycle_id, date/cycle_day, n_frozen, stage, re_frozen) ← `frr*`, `refren`, `cyroday/cyrow`.
- `EndometrialPrep` (cycle_id, lmp, treatment, endometrium, lps_id, thawing_date, n_thawed, n_transferred, cancelled) ← `eprep` + `epreplps`.
- `CycleOutcome` (cycle_id, hcg_date/value, tvs_date/sacs, delivery_date/babies, result_id) ← `*outcome*` columns + `icsiresult`/`icsiss`.
- `Lookup` (type, name, is_active) — merge all 15 lookup tables (`icsiprotocol`, `icsiplace`, `icsisemen`, `icsiss`, `icsiresult`, `epreplps`, 9× `mointoringsheet*`); seed from **production DB**, not this dump (all empty here). Consider enums for clinically fixed sets.
- Prescriptions/labs: fold `mointoringsheetdrugs` into the ERP-wide `prescriptions` (add cycle_id, for_partner) and `mointoringsheetinvestigation` into `lab_orders` (invest_id → investigations catalog, result, for_partner).

Drop / do not migrate as tables:
- `icsi` — derived denormalized archive; replace with a query/View over the new entities (but ETL its historical rows into `IvfCycle` records where no matching `ivfsheet` exists).
- `ivfsheet00.php` legacy controller; `mointoringsheet` vs `ivfsheet` duplication; `monitoring.php::search()` (misplaced finance report).
- `sefo` exists only in production (RedBean fluid mode) — dump it from production before ETL; same for any other runtime-added columns (e.g. `icsi.sefoid`, `ovstfrrffffn`).

ETL cautions:
- All dates in `ivfsheet`/`ovst`/`eprep`/`folliculom` are `varchar` in `Y/m/d` format (plus `0000-00-00` and empty strings) — parse defensively.
- Numbers (oocytes, embryos, E2) stored as varchar/text — cast with validation.
- Mixed id-vs-text storage for the same concepts (`ivfsheet.anprotocol` = id, `icsi.icsiprotocol` = title) — reconcile by title match.
- Inconsistent soft-delete conventions (`del is null` vs `deleted = 0`).
- Security debt to eliminate: generic `Add`/`update`/`del`/`addNewItem` endpoints accept arbitrary table/column names from POST (mass write primitive); `newcd()` and `iui.php` searches build SQL by string concatenation (SQL injection). Replace with FormRequest-validated, policy-guarded endpoints.
- Bug to not carry over: `Ivfstatistics::search` unparenthesized `AND/OR` date filter; `Ivfstatistics::showprint` queries `ancsheet` (broken print).

---


# Module: Andrology & Semen Analysis (andrology)

## Purpose
Male-factor workup of the infertility patient: semen analyses (quick per-patient entries and structured rows inside the infertility sheet), advanced andrology investigations (scrotal ultrasound, "TRUE" exam — likely TRUS, karyotyping, Y-chromosome microdeletion, sperm DNA fragmentation SDF, semen-Fr test, TESE), husband/wife hormonal profiles, and the semen attributes recorded on the IVF cycle sheet. Data is captured through two parallel paths: the generic patient Investigations screen (`semen`, `hormon`) and the infertility sheet (`semeninfertility`, `semen2`, `hormonalprofile`, `hormonalprofile2`).

## Controllers & Views (file paths)
- `/home/amrtechogate/public_html/obgy/core/controllers/investigation.php` — Investigations screen. `index()` auto-creates an empty `semen` row (and `hsg`, `pathology`, etc.) per patient if none exists; lists `semen` and `hormon` rows (`del = 0`, joined to `awusers` for doctor name). Generic `addRow()` (dispenses any `tableName` from POST with `sdate/patientid/doctorid/del`), `delData()` (soft delete `del = 1`), `update()` (generic cell update). Views: `core/views/obgy/investigation/investigation.html` and `investigation1.html` (toggled by `programesetting.investshow`).
- `/home/amrtechogate/public_html/obgy/core/controllers/infertilitysheet.php` — Infertility sheet. `index()` finds/creates `infertilitysheet` by `patientid`, loads `semeninfertility`, `semen2`, `hormonalprofile`, `hormonalprofile2` by `infertilitysheetid`. `append()` creates a child row generically (POST `tablename` + `tablep=infertilitysheetid` + `id`). `update()` is a generic per-cell AJAX save (POST `tableName`/`colName`/`value`). `getselectajax()` inserts a new option (`title`) into any lookup table named in POST `celtable`; `getselect()/getdataselect()/addnewselect()/deldataselect()` manage lookup options; `deleterow()` hard-deletes a row. Views: `core/views/obgy/infertilitysheet/add.html` (semen rows ~lines 220-340, hormonal profile 1 ~367-400, hormonal profile 2 ~708-730), `semen.html` (new-row template), `append.html`, `select.html`.
- `/home/amrtechogate/public_html/obgy/core/controllers/infertilitysheet00.php` — legacy variant; queries `R::findAll('semen', 'infertilitysheetid = ?')` although `semen` has no `infertilitysheetid` column (dead/broken legacy code).
- `/home/amrtechogate/public_html/obgy/core/controllers/sh.php` — read-only "check"/review report. Loads `semeninfertility` + `semen2` resolving all lookups (`semenplace`, `sementype`, `semen2place*`, `semen2result*`; scrotal result resolved with `id in (0$csv)` string interpolation), plus `hormonalprofile`/`hormonalprofile2`; IVF section resolves `ivfsheet.ssemen/sseemen/sseemmen/sseemmeen` against `ssemen`, `sseemen`, `icsisemen`, `sseemmeen`. Records the visit in `lastvisit`. Functions: `index()`, `Add()`, `search()`, `showprint()`, `onesetup()`.
- `/home/amrtechogate/public_html/obgy/core/controllers/ivfsheet.php` + `ivfsheet00.php` — IVF cycle sheet; four selects under the "Semen" column group of the fresh-cycle table write to `ivfsheet.ssemen/sseemen/sseemmen/sseemmeen` via the generic update; option lists come from `ssemen`, `sseemen`, `icsisemen`, `sseemmeen`. Views: `core/views/obgy/ivfsheet/add.html` (~lines 414-428), `ivfhistory.html` (~359-372).
- `/home/amrtechogate/public_html/obgy/core/controllers/drugsex.php` — misleading name; it is the drugs-catalog CRUD (`drugs` table: server-side DataTables `show()`, `adddrug()`, `updatedrug()`, `deletedrug()`, autocomplete `getthisserach()`). Not semen-related; note: DataTables search builds SQL by string concatenation with `LIKE '%$_GET[sSearch]%'` (SQL injection).
- Reporting consumers: `completereport.php`, `fullreport.php`, `Completesreport.php`, `excel.php`, `merge.php` (semen/hormon appear in combined reports), views under `core/views/obgy/reports*/`.

## Tables
All from `/home/amrtechogate/public_html/obgy/_db/obgy_12-7-2024.sql`. No declared foreign keys anywhere; all relations inferred. None of the lookup tables has seed INSERTs in the dump (vocabulary is user-entered at runtime via `getselectajax`).

### semen (dump line 10553)
Per-patient quick semen analysis on the Investigations screen.
- `id` int(11) unsigned PK AI
- `sdate` date
- `patientid` varchar(191) — FK -> patients.id (stored as string)
- `del` int(11) unsigned — soft delete flag (0/1)
- `count` varchar(191) — sperm count
- `motilitya` varchar(191) — progressive motility A
- `motilityab` text — motility A+B
- `normal` varchar(191) — normal forms %
- `puscell` varchar(191)
- `notes` varchar(191)
- `normaltype` int(191), `oligo` int(191), `astheno` int(191), `tetrato` int(191) — checkbox classification flags (Normal/Oligo/Astheno/Teratozoospermia)
- `doctorid` int(11) unsigned — FK -> awusers.user_id

### semen2 (line 10578)
Wide row of advanced andrology investigations per infertility sheet ("Other Inv" section). One trio (date/place/result) per test.
- `id` int(11) PK AI
- `infertilitysheetid` int(11) unsigned — FK -> infertilitysheet.id
- Lookup-id columns (varchar storing lookup ids): `semen2placesemenfr`, `semen2resultscrotal` (comma-separated multi-select), `semen2resulttrue`, `semen2placetese`, `semen2resultyqmicro`, `semen2resultsdf`, `semen2resultkaryo`
- Date columns (varchar, datepicker): `detescrotal`, `detetrue`, `detekaryo`, `deteyqmicro`, `detesdf`, `detesemenfr`, `detetese`, `deteother`
- Free-text place columns: `placescrotal`, `placetrue`, `placeyqmicro`, `placekaryo`, `placesdf`, `placeother`
- Free-text result columns: `resultother`, `resulttese`, `resultsemenfraf`, `resultsemenfrm`, `resultsemenfrc` (three sub-results of the "semen Fr" test — exact clinical meaning uncertain, likely fructose/fractions)

### semen2placesemenfr (10616) — lookup
`id` int unsigned PK, `title` varchar(191), `del` tinyint(1). Place options for the semen-Fr test. No seed data.

### semen2placetese (10629) — lookup
`id`, `title` varchar(191), `del` int. TESE place options. No seed data.

### semen2resultkaryo (10642) — lookup
`id`, `title`, `del` tinyint. Karyotyping result options. No seed data.

### semen2resultscrotal (10655) — lookup
`id`, `title`, `del` tinyint. Scrotal ultrasound findings; referenced as CSV multi-select from `semen2.semen2resultscrotal` and queried via `"id in (0$csv)"` string interpolation in sh.php. No seed data.

### semen2resultsdf (10668) — lookup
`id`, `title`, `del` tinyint. Sperm DNA fragmentation result options. No seed data.

### semen2resulttrue (10681) — lookup
`id`, `title`, `del` tinyint. Result options for the "TRUE" exam (inferred: TRUS — transrectal US). No seed data.

### semen2resultyqmicro (10694) — lookup
`id`, `title`, `del` tinyint. Y-chromosome microdeletion result options. No seed data.

### semeninfertility (10707)
Semen analysis rows inside the infertility sheet (WHO-style parameters).
- `id` int PK AI
- `infertilitysheetid` int unsigned — FK -> infertilitysheet.id
- `semenplace` varchar — FK -> semenplace.id
- `sementype` varchar — FK -> sementype.id
- `date` varchar(191)
- `nc` (count/concentration), `prm` (progressive motility), `tm` (total motility), `af` (abnormal forms), `vit` (vitality), `vol` (volume), `tzi` (teratozoospermia index), `sdi` (sperm deformity index) — all varchar(191)

### semenplace (10730) — lookup
`id`, `title`, `del` int. Labs/places performing semen analysis. No seed data.

### sementype (10743) — lookup
`id`, `title`, `del` int. Semen analysis types. No seed data.

### sseemen (10841) — lookup (typo-named, USED)
`id`, `title`, `del` tinyint. Option list for 2nd "Semen" select on IVF fresh-cycle table; selected id stored in `ivfsheet.sseemen`.

### sseemmeen (10854 area, line 10841/10854) — lookup (typo-named, USED)
`id`, `title`, `del` tinyint. Option list for 4th "Semen" select; stored in `ivfsheet.sseemmeen`.

### sseemmen (10854) — ORPHAN table
`id`, `title`, `del` tinyint. Never read by any controller/view: the select named `sseemmen` loads its options from `icsisemen` (IVF module table) and stores the chosen id into the `ivfsheet.sseemmen` COLUMN. The `sseemmen` TABLE itself is dead — candidate for drop.

### ssemen (10867) — lookup (typo-named, USED)
`id`, `title`, `del` tinyint. Option list for 1st "Semen" select on IVF sheet; stored in `ivfsheet.ssemen`. The four selects sit under a "Semen" column group with blank sub-headers; their clinical semantics (source/preparation/etc.) are user-defined (uncertain — confirm with clinic).

### hormon (7478)
Per-patient hormonal lab panel on the Investigations screen (mixes fertility hormones with tumor markers). All result columns varchar(191).
- `id` int unsigned PK AI, `sdate` date, `patientid` varchar(191) FK -> patients.id, `del` int (soft delete), `doctorid` int FK -> awusers.user_id, `notes` varchar
- Hormones: `lh`, `fsh`, `prl`, `etwo` (E2), `amh`, `freet` (free testosterone), `totalt` (total testosterone), `pfour` (P4), `tsh`, `tthree` (T3), `tfour` (T4), `dht`, `bhcg`, `inhibinb`, `asd` (androstenedione), `dheas`
- Tumor markers / misc: `ca125`, `cea`, `calgg`, `ldh`, `tfehprotein`

### hormonalprofile (7515)
Husband hormonal profile rows in the infertility sheet — date/result pair per hormone, all varchar(191).
- `id` int PK AI, `infertilitysheetid` int unsigned FK -> infertilitysheet.id
- Pairs: `datefsh/resultfsh`, `datelh/resultlh`, `datett/resulttt` (total testosterone), `dateprl/resultprl`, `datee2/resulte2`, `dateinhibin/resultinhibin`, `dateother/resultother`, `dateother1/resultother1`

### hormonalprofile2 (7543)
Second hormonal profile in the infertility sheet (includes AMH/TSH/P4 — inferred to be the wife's panel).
- `id` int PK AI, `infertilitysheetid` int unsigned FK -> infertilitysheet.id, `date` varchar
- Pairs: `dateamh/resultamh`, `datetsh/resulttsh`, `dateprl/resultprl`, `datefsh/resultfsh`, `datelh/resultlh`, `datee2/resulte2`, `datep4/resultp4`, `dateother/resultother`

## Relationships (inferred; no declared FKs)
- semen.patientid -> patients.id (varchar vs int mismatch)
- semen.doctorid -> awusers.user_id
- hormon.patientid -> patients.id
- hormon.doctorid -> awusers.user_id
- semeninfertility.infertilitysheetid -> infertilitysheet.id
- semeninfertility.semenplace -> semenplace.id
- semeninfertility.sementype -> sementype.id
- semen2.infertilitysheetid -> infertilitysheet.id
- semen2.semen2placesemenfr -> semen2placesemenfr.id
- semen2.semen2placetese -> semen2placetese.id
- semen2.semen2resultscrotal -> semen2resultscrotal.id (CSV multi-value)
- semen2.semen2resulttrue -> semen2resulttrue.id
- semen2.semen2resultkaryo -> semen2resultkaryo.id
- semen2.semen2resultyqmicro -> semen2resultyqmicro.id
- semen2.semen2resultsdf -> semen2resultsdf.id
- hormonalprofile.infertilitysheetid -> infertilitysheet.id
- hormonalprofile2.infertilitysheetid -> infertilitysheet.id
- ivfsheet.ssemen -> ssemen.id; ivfsheet.sseemen -> sseemen.id; ivfsheet.sseemmeen -> sseemmeen.id; ivfsheet.sseemmen -> icsisemen.id (NOT the sseemmen table)
- infertilitysheet.patientid -> patients.id (parent of this module's sheet tables)

## Business Workflows (traced from code)
1. **Quick investigations path**: opening `investigation.php?patientid=N` auto-inserts an empty `semen` row (today's date, session doctor) if the patient has none; the screen lists all non-deleted `semen` and `hormon` rows newest-first with doctor names. Cells are saved one-by-one via generic `update` (POST tableName/colName/value); "Add" posts to `addRow` (generic dispense); delete sets `del = 1`.
2. **Infertility sheet path**: `infertilitysheet.php` finds-or-creates the patient's `infertilitysheet`, then renders inline-editable tables. The "Semen" link (`addappend`, `data-add="semeninfertility"`) calls `append()` which dispenses a child row with `infertilitysheetid` set; same mechanism for `semen2`, `hormonalprofile`, `hormonalprofile2`. Every keystroke/select change posts to the generic `update()`.
3. **Runtime vocabulary**: selects with class `getselectajax` load options from the lookup table named in `data-celtable`; typing a new term posts to `getselectajax()` which inserts `{title}` into that lookup table — so semenplace/sementype/semen2* option lists grow organically. Scrotal result is a `multiple` select stored as comma-joined ids.
4. **IVF cycle**: in `ivfsheet/add.html` fresh-cycle table, four selects under the "Semen" header store ids into `ivfsheet.ssemen/sseemen/sseemmen/sseemmeen`; `ivfsheet.php` resolves `icsisemen` title for embryo-transfer table copies.
5. **Review/print**: `sh.php` renders the whole sheet read-only, resolving every lookup id to its title, and logs the access in `lastvisit`. Combined reports (`completereport.php`, `fullreport.php`, `excel.php`) include semen/hormon data.

## ERP Migration Notes
Proposed Laravel models:
- **SemenAnalysis** (`semen_analyses`): merge `semen` + `semeninfertility`. Columns: `patient_id` FK, `infertility_sheet_id` nullable FK, `analyzed_at` DATE, `place_id`, `type_id`, numeric DECIMAL columns `volume`, `concentration`, `progressive_motility`, `total_motility`, `abnormal_forms`, `vitality`, `tzi`, `sdi`, `pus_cells`, `notes`, classification booleans (`is_oligo`, `is_astheno`, `is_terato`, `is_normal`) or derive them, `doctor_id`, `deleted_at`.
- **AndrologyInvestigation** (`andrology_investigations`): normalize wide `semen2` to long format — `infertility_sheet_id`, `type` enum (scrotal_us, trus, karyotype, y_microdeletion, sdf, semen_fr, tese, other), `performed_at` DATE, `place` (or `place_id`), `result_text`, plus pivot `andrology_investigation_result_option` for multi-valued scrotal findings. `semen_fr` sub-results (af/m/c) become three value columns or child rows.
- **AndrologyResultOption** (`andrology_result_options`): merge the 7 lookups `semen2result*`/`semen2place*` plus `semenplace`/`sementype` into typed reference tables (`option_type` discriminator) seeded from production data (dump has no seeds — export live DB values at migration time).
- **HormoneResult** (`hormone_results`): normalize `hormon` + `hormonalprofile` + `hormonalprofile2` to long format — `patient_id`, `infertility_sheet_id` nullable, `hormone_id` FK to a `hormones` reference table (code, unit, reference range, category fertility/thyroid/tumor_marker), `value` DECIMAL, `sampled_at` DATE. Integrate with the ERP lab module rather than duplicating; separate tumor markers (ca125/cea/ldh) into the lab/oncology category.
- **IVF semen lookups**: drop the orphan `sseemmen` table; rename `ssemen`/`sseemen`/`sseemmeen` (+ `icsisemen` from the IVF module) into clearly named lookups after confirming clinical semantics with the clinic (column headers are blank in the UI), and reference them from the IVF cycle model with real FKs.
- Data cleanup during ETL: cast varchar dates (`Y/m/d` strings) to DATE, varchar numerics to DECIMAL, drop auto-created empty `semen` rows (all clinical fields NULL), split CSV scrotal ids into pivot rows, fix `semen.patientid` string -> bigint.
- Replace the generic table/column AJAX endpoints (mass-assignment + SQL-injection surface: `update`, `append`, `getselectajax`, `drugsex.php` DataTables search) with validated Form Requests and per-resource Angular forms.
- `drugsex.php` belongs to the Drugs catalog module, not andrology — exclude from this module's migration scope.

---


# Module: Ultrasound & Imaging (imaging)

## Purpose
Captures all diagnostic-imaging activity of the OB/GYN clinic: structured obstetric ultrasound reports (per-fetus anatomy survey + biometry), gynecological ultrasound reports, transvaginal scans (TVS / Doppler TVS / SIS) inside the infertility sheet, HSG (hysterosalpingography), MRI/CT findings, 4D-scan booking lists tied to the antenatal sheet, raw image/video attachments (sonar), and an annotatable anatomical diagram tool (GT Image). Source app: legacy PHP + Smarty + RedBeanPHP (`R::`) "aw framework", DB dump `/home/amrtechogate/public_html/obgy/_db/obgy_12-7-2024.sql`.

## Controllers & Views (file paths)
Dedicated controllers (`/home/amrtechogate/public_html/obgy/core/controllers/`):
- `ultrasound.php` — legacy combined obstetric US report (tables `ultrasound` + `ultrasounddetail`). Views: `core/views/obgy/ultrasound/{show,add,babymodel,print,selects}.html`
- `ultrasoundobst.php` — newer obstetric US report (tables `ultrasoundobst` + `ultrasoundobstdetail`). Views: `core/views/obgy/ultrasoundobst/*`
- `ultrasoundgyna.php` — gynecological US report (table `ultrasoundgyna`). Views: `core/views/obgy/ultrasoundgyna/*`
- `sonar.php` — "Pictures and Videos": image/video attachment manager (table `sonar`). Views: `core/views/obgy/sonar/{sonar,sonar1,print,result,scrollimg}.html`
- `gtimage.php` — "GT Image" drag-and-drop annotation over an anatomical base image (tables `gtimage` + `gtdetail`). Views: `core/views/obgy/gtimage/{add,show,showdata}.html`

Shared controllers that read/write this module's tables:
- `investigation.php` — auto-creates and lists `hsg`, `ustv`, `mrict` rows per patient (investigations hub, alongside semen/laparoscopy/hysteroscopy/pathology).
- `infertilitysheet.php`, `infertilitysheet00.php`, `gynasheet.php`, `gynasheet00.php`, `sh.php`, `Completesreport.php` — read `tvs`, `dtvs`, `sis`, `hsginfertility` linked to `infertilitysheetid` and resolve their CSV lookup columns.
- `ancsheet.php`, `operations.php` — create/manage `op_4d_list` 4D-scan bookings linked to `ancsheet`; `operations.php` action `four_d()` shows the booking list with `four_d` and `place` lookups.
- `Completesreport.php` (line ~503) resolves `ancnewvisit.usn / usplace / usaf` against lookup tables `usn`, `usplace`, `usaf` (ANC follow-up visit ultrasound fields).

Upload/storage directories (file-system storage model):
- `/home/amrtechogate/public_html/obgy/upload/sonar/` — ultrasound images, md5-random filenames (~1,694 files at analysis time); DB keeps only filename in `sonar.imagename`.
- `/home/amrtechogate/public_html/obgy/upload/sonarvedio/1/` — videos uploaded via `uploadHEX()` with md5 random names; original filename preserved in `sonar.originalname`, stored name in `sonar.vediourl` (longtext). Download served via `readfile()` in `sonar.php::downloadfile()`.
- `/home/amrtechogate/public_html/obgy/upload/gtimage/` — base diagram images for the GT Image tool (`1.jpg` default, or custom from `programesetting.gtimage`).

## Tables
All from CREATE TABLE statements in the dump. No declared foreign keys anywhere; all relations are inferred from column names and controller SQL. NOTE: the dump contains NO INSERT seed data for any of this module's lookup tables (only 23 INSERTs exist in the whole dump, none for imaging lookups), so seeded vocabulary values could not be extracted — vocabularies live only in the production DB.

### ultrasound (legacy obstetric US report header)
Columns: `id` int PK, `patientid` int, `del` int (soft delete), `done` int (finalized flag), `edate` date, `stype` varchar (scan type), `based` varchar (dating based on), `tcondition` int, `babyno` varchar (number of fetuses), `sid` int (sonographer = awusers.user_id where positionid=4), `indication` varchar, `gage` varchar (gestational age), `physician` varchar, `otherbased` varchar, `limit` varchar (study limitations).
FKs: `patientid -> patients.id`, `sid -> awusers.user_id`.

### ultrasounddetail (per-fetus detail of legacy report)
Columns: `id` PK, `del`, `ultrasoundid` int -> ultrasound.id, anatomy-survey coded ints (0/1/2 enums hardcoded in views): `relationc, appearance, fluid, movement, shape, cavum, falx, thalami, ventricle, cerebellum, magna, u_lip, profile, orbits, nose, nostrils, neck, thoraxshape, mass, heart, heartactivity, hsize, haxix, chamberview, loutflow, routflow, stomach, bowel, kidney, bladder, cordinsertion, cordvessels, spine, rarm, rleg, larm, lleg`; `gender` double (-1 default), `gendern` int, `conclusion` int, `plans` int; biometry varchars: `position, diameterm/diameterp` (BPD measured/percentile), `headcm/headcp` (HC), `abdominalm/abdominalp` (AC), `lengthm/lengthp` (FL), `otherm/otherp`, `othernormal` text, `finding`, `weeks`, `referred`, `oplans`, `cover`; conclusion flags `abconclusion, pconclusion, inconclusion` tinyint.
FKs: `ultrasoundid -> ultrasound.id`.

### ultrasoundobst (newer obstetric US report header)
Columns: `id` PK, `patientid`, `del`, `done`, `edate` date, `stype` int, `sid` int, `tcondition` int, `babyno` double, `physician`, `limit`, `indication`.
FKs: `patientid -> patients.id`, `sid -> awusers.user_id`.

### ultrasoundobstdetail (per-fetus detail, includes 1st-trimester fields)
Columns: `id` PK, `del`, `ultrasoundobstid` -> ultrasoundobst.id, coded ints: `appearance, shape, cranial, falx, choroid, profile, orbits, neck, pulm, diaphragm, heart, heartactivity, hsize, haxix, chamberview, stomach, bowel, kidney, bladder, cordinsertion, cordvessels, spine, rarm, rleg, larm, lleg`, `gender` double, `gendern`, `conclusion`, `plans`; varchars: `anomaly`, `crownm/crownmc` (CRL measured/centile), `nuchalm/nuchalc` (NT), `diameterm/diameterc` (BPD), `headcm/headcc` (HC), `abdominalm/abdominalc` (AC), `flengthm/flengthc` (FL), `finding`, `gaestimatew/gaestimated` (GA estimate weeks/days), `othernormal`, `oplans`, `weeks` double, `referred`; flags `pconclusion, inconclusion, abconclusion`.
FKs: `ultrasoundobstid -> ultrasoundobst.id`.

### ultrasoundgyna (gynecological US report — single flat row)
Columns: `id` PK, `patientid`, `del`, `done`, `edate` date, `etype` int (exam type), `sid` int, `tcondition` int, `position` int (uterus position), `tz` int, `myo` int (myometrium), `myowall` int, `douglas` int (Douglas pouch), `conclusion` int; varchars: `rtsize, physician, indication, measurments, thickness` (endometrium), `flesion` (focal lesion), `rtshape, rtfolicles, ltfolicles, ltshape, ltsize` (ovaries), `pmass` (pelvic mass), `lymph`, `rtarteries, ltarteries` (Doppler), `score`, `subspace`, `anymass`, `normalcon`, `limit`, `describ`, `abnormalcon`.
FKs: `patientid -> patients.id`, `sid -> awusers.user_id`.

### sonar (image/video attachments — MyISAM!)
Columns: `id` PK, `patientid` int NOT NULL, `sonardate` date, `imagename` varchar (md5 filename in upload/sonar), `notes` varchar, `vediourl` longtext (filename in upload/sonarvedio/1), `tempdelete` char(1) default '0' (soft delete), `type` int (0=image, 1=video), `originalname` varchar (original upload filename for videos).
FKs: `patientid -> patients.id`. Storage: files on disk, DB stores names only.

### tvs (TVS exam row inside infertility sheet)
Columns: `id` PK, `infertilitysheetid` int, `tvsut`, `tvsro`, `tvslo`, `tvscx`, `tvspelvis`, `dtvsplace` — all varchar holding **comma-separated lookup ids** resolved via `id in (0$csv)`, `comment`, `date` varchar.
FKs: `infertilitysheetid -> infertilitysheet.id`; CSV multi-FKs to `tvsut/tvsro/tvslo/tvscx/tvspelvis/dtvsplace`.

### tvscx / tvslo / tvspelvis / tvsro / tvsut (lookups: cervix / left ovary / pelvis / right ovary / uterus findings)
Identical structure: `id` PK, `title` varchar, `del` tinyint. No seed rows in dump.

### dtvs (Doppler TVS exam row in infertility sheet)
Columns: `id` PK, `infertilitysheetid` int, `dtvsresult` varchar (CSV of dtvsresult ids), `date` varchar.
FKs: `infertilitysheetid -> infertilitysheet.id`; CSV -> `dtvsresult.id`. Note: `tvs.dtvsplace` carries the Doppler place (CSV -> `dtvsplace.id`).

### dtvsplace / dtvsresult (lookups: Doppler TVS place / result)
`id`, `title`, `del`. No seed rows in dump.

### ustv (US TV — transvaginal US findings record from investigations hub)
Columns: `id` PK, `sdate` date, `patientid` **varchar** (type mismatch vs int elsewhere), `del` int, `uterus`, `radnexa`, `ladnexa`, `pelvis`, `notes` varchar (free text), `doctorid` int.
FKs: `patientid -> patients.id`, `doctorid -> awusers.user_id`. Auto-dispensed empty row by `investigation.php` when patient has none.

### usn / usaf / usplace (lookups used by ANC follow-up visits, NOT by ustv)
`id`, `title`, `del`. Referenced from `ancnewvisit.usn`, `ancnewvisit.usaf` (amniotic fluid, per view field label), `ancnewvisit.usplace` (placenta site) — resolved in `Completesreport.php` and ANC sheet views (`ancsheet/add.html` select boxes with `data-table="ancnewvisit"`). Inference: usn = US notes/normality, usaf = US amniotic fluid, usplace = US placenta site (استنتاج). No seed rows in dump.

### four_d (lookup: 4D scan service items)
Columns: `id` PK, `title` varchar, `del` varchar, `name` varchar. Loaded with `del is null` in `operations.php::four_d()`. No seed rows in dump.

### op_4d_list (4D scan booking/worklist row)
Columns: `id` PK, `patientid` int def 0, `ancsheetid` int def 0, `opdate` date, `four_d` varchar (CSV of four_d ids), `place` int (-> `place` lookup, operations module), `notes` text, `deleted` int, `user_id` int (creator), `createdate` datetime, `t1,t11,t12,t2,t21,t22` int flags (1st/2nd scan booking-done-paid stage flags; `ancsheet.tscandate`/`ttscandate` updates set `t11`/`t21` — exact semantics استنتاج), `done` int, `confirm` int, `t1_userid`, `t2_userid` int (staff who performed scan 1/2).
FKs: `patientid -> patients.id`, `ancsheetid -> ancsheet.id`, CSV `four_d -> four_d.id`, `place -> place.id`, `user_id/t1_userid/t2_userid -> awusers.user_id`. Reverse link: `ancsheet.4d_list_id -> op_4d_list.id` (set in `ancsheet.php::eedlmp()` when EDD is entered).

### gtimage (annotated diagram session header; "GT Image")
Columns: `id` PK, `gdate` date, `patientid`, `doctorid`, `gtid` int (unused/legacy in code paths seen), `comment` varchar, `del`, `pic` varchar (base image filename; `1.jpg` default or `programesetting.gtimage`), `programview` tinyint (copied from programesetting).
FKs: `patientid -> patients.id`, `doctorid -> awusers.user_id`. A new row is auto-created on every visit to `gtimage.php?ac=index`.

### gtdetail (positioned annotation markers on the diagram)
Columns: `id` PK, `gtid` int -> gtimage.id, `posx` double, `posy` double, `width`, `height` int, `imgno` int (which marker icon, e.g. aw/images/{1,3,8,...}.png), `del`, `comment` varchar.
FKs: `gtid -> gtimage.id`.

### mrict (MRI/CT findings record)
Columns: identical to `ustv`: `id`, `sdate` date, `patientid` varchar, `del`, `uterus`, `radnexa`, `ladnexa`, `pelvis`, `notes`, `doctorid`.
FKs: `patientid -> patients.id`, `doctorid -> awusers.user_id`. Auto-dispensed by `investigation.php`.

### sis (Saline Infusion Sonography row in infertility sheet)
Columns: `id` PK, `infertilitysheetid` int, `sisresult` varchar (CSV -> `sisresult` lookup table, which belongs to the infertility module's lookups, not assigned here), `date` varchar.
FKs: `infertilitysheetid -> infertilitysheet.id`.

### hsg (HSG findings record from investigations hub)
Columns: `id` PK, `sdate` date, `patientid` varchar, `del`, `uterus` varchar, `ltube` text (left tube), `notes`, `smear`, `rtube` varchar (right tube), `doctorid` int.
FKs: `patientid -> patients.id`, `doctorid -> awusers.user_id`. Auto-dispensed by `investigation.php`.

### hsginfertility (HSG exam row inside infertility sheet)
Columns: `id` PK, `infertilitysheetid` int, `hsgut` (single id -> hsgut), `hsgplace` (single id -> hsgplace), `hsgrt` (CSV -> hsgrt), `hsglt` (CSV -> hsglt), `hsgpelvis` (single id -> hsgpelvis), `date` varchar. Single vs CSV resolution per `Completesreport.php` (`R::load` vs `id in (0...)`).
FKs: `infertilitysheetid -> infertilitysheet.id` + lookup FKs above.

### hsglt / hsgpelvis / hsgplace / hsgrt / hsgut (lookups: HSG left tube / pelvis / place / right tube / uterus findings)
Identical structure: `id`, `title`, `del`. No seed rows in dump.

## Relationships (explicit list)
- ultrasound.patientid -> patients.id
- ultrasound.sid -> awusers.user_id (sonographer; controllers filter `positionid = 4`)
- ultrasounddetail.ultrasoundid -> ultrasound.id (1 header : N fetuses)
- ultrasoundobst.patientid -> patients.id ; ultrasoundobst.sid -> awusers.user_id
- ultrasoundobstdetail.ultrasoundobstid -> ultrasoundobst.id
- ultrasoundgyna.patientid -> patients.id ; ultrasoundgyna.sid -> awusers.user_id
- sonar.patientid -> patients.id
- gtimage.patientid -> patients.id ; gtimage.doctorid -> awusers.user_id
- gtdetail.gtid -> gtimage.id
- tvs.infertilitysheetid -> infertilitysheet.id ; tvs.{tvsut,tvsro,tvslo,tvscx,tvspelvis,dtvsplace} (CSV) -> {tvsut,tvsro,tvslo,tvscx,tvspelvis,dtvsplace}.id
- dtvs.infertilitysheetid -> infertilitysheet.id ; dtvs.dtvsresult (CSV) -> dtvsresult.id
- sis.infertilitysheetid -> infertilitysheet.id ; sis.sisresult (CSV) -> sisresult.id (infertility-module lookup)
- hsginfertility.infertilitysheetid -> infertilitysheet.id ; hsginfertility.{hsgut,hsgplace,hsgpelvis} -> single lookup ids ; hsginfertility.{hsgrt,hsglt} (CSV) -> {hsgrt,hsglt}.id
- hsg.patientid -> patients.id ; hsg.doctorid -> awusers.user_id
- ustv.patientid -> patients.id ; ustv.doctorid -> awusers.user_id
- mrict.patientid -> patients.id ; mrict.doctorid -> awusers.user_id
- op_4d_list.patientid -> patients.id ; op_4d_list.ancsheetid -> ancsheet.id ; op_4d_list.four_d (CSV) -> four_d.id ; op_4d_list.place -> place.id ; op_4d_list.{user_id,t1_userid,t2_userid} -> awusers.user_id
- ancsheet.`4d_list_id` -> op_4d_list.id (reverse pointer, set on EDD entry)
- ancnewvisit.{usn,usaf,usplace} -> {usn,usaf,usplace}.id (ANC module consumes imaging lookups)

## Business Workflows (traced from code)
1. **Obstetric US report (ultrasound.php / ultrasoundobst.php)**: `ac=add` loads the latest open report for the patient; if none or last is `done=1`, it auto-inserts a new `ultrasound(obst)` header (today's date, defaults 0) plus one default `ultrasound(obst)detail` row (first fetus, all anatomy codes 0, gender -1). Every field on the form saves immediately via AJAX `update()`/`updateradio()` (generic table/column/value writer). Extra fetuses are added with `addDeteail()` (one detail row per fetus); deleting a fetus (`delData`) soft-deletes the detail and decrements `babyno`. `showprint()` marks the header `done=1` and renders the print template; `index` lists only finalized (`done=1`) reports; `delRow` soft-deletes a whole report.
2. **Gyna US report (ultrasoundgyna.php)**: same open-or-create pattern on a single flat `ultrasoundgyna` row (uterus/endometrium/ovaries/Doppler/conclusion), AJAX field-by-field save, print finalizes with `done=1`.
3. **Images & videos (sonar.php)**: per-patient gallery split by `type` (0 images / 1 videos). `uploadmulti()` loops `file-i` POST files: images go through `uploadnew()` into `upload/sonar/` with md5 names; videos through `uploadHEX()` into `upload/sonarvedio/1/` keeping `originalname`. Single-slot `upload()` replaces a file and unlinks the old one from disk. `del()` sets `tempdelete=1` (file stays on disk). `printpic()` prints a selected set of images with clinic header; `downloadfile()` streams the video back with its original filename.
4. **GT Image (gtimage.php)**: `index` ALWAYS inserts a fresh `gtimage` row (base picture from `programesetting`) then redirects to `ac=add`, where the user drags marker icons (`imgno`) onto the diagram; each drop/resize/comment is persisted to `gtdetail` (posx/posy/width/height/comment) via AJAX. `ac=show` lists sessions; `delRow` soft-deletes header + markers.
5. **Infertility-sheet imaging (TVS/D.TVS/SIS/HSG)**: inside `infertilitysheet.php` (and reports `sh.php`, `Completesreport.php`) rows in `tvs`, `dtvs`, `sis`, `hsginfertility` are keyed by `infertilitysheetid`; multi-select findings are stored as CSV id strings and resolved with `id in (0$csv)` against the title lookups.
6. **Investigations hub (investigation.php)**: when a patient first opens the investigations page, empty `hsg`, `ustv`, `mrict` rows are auto-dispensed (sdate=today, doctorid=session user); findings are then typed as free text (uterus / rt-lt adnexa / pelvis / tubes / notes) and listed date-desc with doctor name.
7. **4D scan booking (ancsheet.php + operations.php)**: when EDD/LMP is entered on the antenatal sheet (`eedlmp`), an `op_4d_list` row is auto-created and its id is stored back in `ancsheet.4d_list_id`. Reception/operations staff manage the 4D worklist (`operations.php::four_d()`): choose service items (`four_d` CSV), place, date; entering `tscandate`/`ttscandate` on the ANC sheet flips `t11`/`t21` flags; `done`/`confirm` and `t1_userid`/`t2_userid` track execution.

## ERP Migration Notes (Laravel + Angular)
Proposed models/tables:
- `ImagingStudy` (polymorphic header): patient_id, visit_id, study_type enum [OBST_US, GYNA_US, TVS, DOPPLER_TVS, SIS, HSG, MRI, CT, 4D], study_date, sonographer_id, physician, indication, limitations, status (draft/finalized), conclusion fields. Replaces headers of `ultrasound`, `ultrasoundobst`, `ultrasoundgyna`, `ustv`, `mrict`, `hsg`, and exam rows `tvs`, `dtvs`, `sis`, `hsginfertility`.
- `FetalScanFinding` (hasMany from ImagingStudy, one per fetus): merge `ultrasounddetail` + `ultrasoundobstdetail` (they overlap ~80%; keep superset incl. CRL/NT first-trimester fields). Model anatomy survey as JSON or an EAV `finding_code/finding_value` child table referencing a `scan_finding_catalog`.
- `GynaScanFinding` (hasOne): from `ultrasoundgyna` body fields.
- `ImagingFindingOption` (single lookup table with `domain` column): replaces 16 micro-lookups `tvsut/tvsro/tvslo/tvscx/tvspelvis/dtvsplace/dtvsresult/hsgut/hsgrt/hsglt/hsgpelvis/hsgplace/usn/usaf/usplace/four_d` (+ `sisresult`). CRITICAL: export lookup titles from the LIVE database — the dump has no seed data.
- Pivot tables (e.g. `imaging_study_finding_option`) to replace ALL comma-separated id columns (`tvs.*`, `dtvs.dtvsresult`, `sis.sisresult`, `hsginfertility.hsgrt/hsglt`, `op_4d_list.four_d`).
- `MediaAttachment` via Spatie MediaLibrary (or S3 disk): replaces `sonar`; migrate `upload/sonar` + `upload/sonarvedio/1` files, keep original filename + capture date + notes; soft deletes; serve via signed URLs instead of raw `readfile()`.
- `AnnotatedDiagram` + `DiagramMarker`: replace `gtimage`/`gtdetail` (store marker icon id, x/y as percentages, comment); fix the bug where a new `gtimage` row is inserted on EVERY index visit (creates orphan empty sessions).
- `FourDScanBooking`: replaces `op_4d_list` with proper FKs to patient, antenatal_record, place, service items pivot, and explicit status enum instead of t1/t11/t12/t2/t21/t22 bit-flags; drop the back-pointer `ancsheet.4d_list_id` in favor of a hasOne relation.
Normalization / data-quality actions:
- Cast `ustv.patientid`, `mrict.patientid`, `hsg.patientid` from varchar to int FK during ETL.
- Convert `tvs.date`, `dtvs.date`, `sis.date`, `hsginfertility.date` varchar dates to DATE.
- Convert all numeric measurements stored as varchar (biometry m/p/c columns) to decimal columns.
- Unify soft-delete flags (`del`, `tempdelete`, `deleted`) into Laravel SoftDeletes.
- `sonar` is MyISAM and the only non-InnoDB table here; controllers also build SQL by string concatenation of `patientid`/`id` (SQL-injection surface) and the generic AJAX `update(table, column, value)` endpoint allows writing arbitrary tables/columns — must be replaced by validated FormRequests per resource.
- Decide deprecation: `ultrasound`/`ultrasounddetail` appear superseded by `ultrasoundobst`/`ultrasoundobstdetail` (استنتاج: both active in code; migrate both datasets into the unified model, keep source_table tag).

---


# Module: Laparoscopy & Hysteroscopy (endoscopy)

## Purpose
Documents gynecological endoscopy procedures in two parallel, **unlinked** representations:

1. **Free-text per-patient reports** (`laparoscopy`, `hysteroscopy`) edited inside the Investigations screen (`investigation.php`), one row per procedure date, free-text findings per anatomical region.
2. **Structured per-infertility-sheet records** (`laparoscopyinfertility`, `hysteroscopyinfertility`) inside the Infertility Sheet, where findings AND management/intervention per organ are picked from 26 user-extensible lookup tables (`lapar*`, `copy*`), multi-select values stored as CSV id strings. Includes intervention text and cost.

Additionally owns the surgical incision lookups `jncision` / `midlinejncision` (parent/child cascading dropdowns) consumed by the Operative Details module.

Naming decode:
- `lapar` + `ul`=uterus, `rt`=right tube, `lt`=left tube, `ro`=right ovary, `lo`=left ovary, `pelvis`, `place`=facility. `laparm*` = **m**anagement (intervention) for the same organ.
- `copy` = suffix of hystero-s-**copy**. `copydil`=cervical dilatation, `copyintrod`=scope introduction, `copycx`=cervix/cervical canal, `copycavity`=cavity & fundus, `copyrostium`/`copylostium`=right/left tubal ostium, `copyplace`=facility. `copym*` = management for cervix/cavity/ostia.
- `jncision` = misspelling of "incision"; `midlinejncision` = incision **subtype** child lookup (despite the name it holds children for ALL incision types via `jncisionid`).

## Controllers & Views (file paths)
Controllers (all under `/home/amrtechogate/public_html/obgy/core/controllers/`):
- `investigation.php` (lines ~152-195): auto-creates empty `laparoscopy`/`hysteroscopy` row per patient on first open (sdate=today, patientid, doctorid, del=0); lists rows; joins `awusers` for doctor name. Tabs LAPAROSCOPY / HYSTEROSCOPY in view.
- `infertilitysheet.php` (+ legacy `infertilitysheet00.php`): `loadOne()` loads `hysteroscopyinfertility`/`laparoscopyinfertility` by `infertilitysheetid` (lines 234-238); `append()` creates new child row via AJAX (POST tablename/tablep/id); `update()` generic single-cell save (POST tableName/colName/id/value — no whitelist); `getselectajax()` inserts a new lookup term (`title`, `del=null`); `getselect()`/`getdataselect()` load lookups with `del is null`; `deldataselect()` soft-deletes a term (`del=1`); `deleterow()` deletes sheet child rows.
- `gynasheet.php` / `gynasheet00.php` (lines ~225-228 / 269-271): display same infertility endoscopy rows inside the gyna sheet.
- `sh.php` (lines 200-231, 666-672): print/report rendering; resolves lookup ids — single ids via `R::load`, CSV multi ids via `R::findAll(t, "id in (0$csv)")`.
- `Completesreport.php` (lines 411-440): same resolution for the complete printable report.
- `completereport.php` (lines 426-437): per-patient report; selects non-empty `laparoscopy`/`hysteroscopy` rows joined to `awusers` (doctorid = user_id).
- `fullreport.php` (lines 206-208): checks existence of endoscopy rows in date range for report sectioning.
- `operations.php` (index + search actions, lines 60-250): operations-day report; lists `hysteroscopyinfertility`/`laparoscopyinfertility` LEFT JOIN `infertilitysheet` with string-concatenated search query; resolves `copyplace`/`laparplace`; writes `lastvisit`. NOTE: also queries `hysteroscopy`/`laparoscopy` by nonexistent columns `date`/`infertilitysheetid` — dead/broken code path (inference).
- `operativedetails.php` (lines 282-485, 812): loads `jncision` (del=0) and dependent `midlinejncision` (by `jncisionid`); AJAX `loadjncision()`, `addjncision()`, `addjncisionchild()` manage the cascading lookups.
- `setup.php` (line 195): `jncision`/`midlinejncision` in tables-not-allowed-to-delete list.
- `merge.php`, `excel.php`, `Completesreport.php`: patient-merge and export touchpoints (read-only references).

Views (under `/home/amrtechogate/public_html/obgy/core/views/obgy/`):
- `investigation/investigation.html` (tabs at lines 356-510: laparoscopy fields sdate/uterus/radnexa/ladnexa/pelvis/notes; hysteroscopy fields sdate/cervix/uterine/rostium/lostium/notes; `updateElement` AJAX save, `axedelete2` soft delete).
- `infertilitysheet/add.html` (hysteroscopy block lines 929-1005, headers: Date/Place/Dil/Introd/CX/Cavity-Fundus/R-Ostium/L-Ostium + management row; laparoscopy block lines 1010-1095, headers: Date/Place/UT/RT/LT/RO/LO/Pelvis + Intervention/management row + Cost).
- `infertilitysheet/append.html` (lines 355-470): AJAX-appended row templates for both procedures.
- `operativedetails/jncision.html`, `operativedetails/showdata.html`, `operativedetails/editmodel.html`, `_assets/custom/operativedetails.js`, `_assets/custom/investigation.js`.
- Reports: `reports/complete.html`, `reports/completeprint.html`, `reports/full/fullprint.html`, `reports2/operations.html`, `reports2/operationsprint.html`, `reports2/completesreport(s).html`.

## Tables
No declared FKs anywhere; all relations inferred from controller code. All from `/home/amrtechogate/public_html/obgy/_db/obgy_12-7-2024.sql`.

### laparoscopy (line 8738) — free-text laparoscopy report per patient
| column | type |
|---|---|
| id | int(11) unsigned PK AI |
| sdate | date |
| patientid | varchar(191) |
| del | int(11) unsigned |
| uterus | varchar(191) |
| radnexa | varchar(191) (right adnexa) |
| ladnexa | varchar(191) (left adnexa) |
| pelvis | varchar(191) |
| notes | varchar(191) |
| doctorid | int(11) unsigned |

FKs (inferred): `patientid -> patients.id` (stored as varchar!), `doctorid -> awusers.user_id`. No seed data.

### hysteroscopy (line 7769) — free-text hysteroscopy report per patient
| column | type |
|---|---|
| id | int(11) unsigned PK AI |
| sdate | date |
| patientid | varchar(191) |
| del | int(11) unsigned |
| cervix | varchar(191) |
| uterine | varchar(191) (uterine cavity) |
| rostium | varchar(191) (right ostium) |
| lostium | varchar(191) (left ostium) |
| notes | varchar(191) |
| doctorid | int(11) unsigned |

FKs (inferred): `patientid -> patients.id`, `doctorid -> awusers.user_id`. No seed data.

### laparoscopyinfertility (line 8758) — structured laparoscopy in infertility sheet
| column | type | meaning |
|---|---|---|
| id | int(11) PK AI | |
| infertilitysheetid | int(11) unsigned | -> infertilitysheet.id |
| laparplace | varchar(191) | single id -> laparplace.id |
| laparul | varchar(191) | CSV ids -> laparul (uterus findings) |
| laparrt | varchar(191) | CSV ids -> laparrt (right tube) |
| laparlt | varchar(191) | CSV ids -> laparlt (left tube) |
| laparro | varchar(191) | CSV ids -> laparro (right ovary) |
| laparlo | varchar(191) | CSV ids -> laparlo (left ovary) |
| laparpelvis | varchar(191) | CSV ids -> laparpelvis |
| laparmut | varchar(191) | CSV ids -> laparmut (management uterus) |
| laparmrt | varchar(191) | CSV ids -> laparmrt |
| laparmlt | varchar(191) | CSV ids -> laparmlt |
| laparmro | varchar(191) | CSV ids -> laparmro |
| laparmlo | varchar(191) | CSV ids -> laparmlo |
| laparmpelvis | varchar(191) | CSV ids -> laparmpelvis |
| lapintervention | varchar(191) | free-text intervention |
| date | varchar(191) | procedure date as text |
| coast | varchar(191) | cost (misspelled, text) |

### hysteroscopyinfertility (line 7789) — structured hysteroscopy in infertility sheet
| column | type | meaning |
|---|---|---|
| id | int(11) PK AI | |
| infertilitysheetid | int(11) unsigned | -> infertilitysheet.id |
| copyplace | varchar(191) | single id -> copyplace.id |
| copydil | varchar(191) | single id -> copydil (dilatation) |
| copyintrod | varchar(191) | single id -> copyintrod (introduction) |
| copycx | varchar(191) | CSV ids -> copycx (cervix) |
| copycavity | varchar(191) | CSV ids -> copycavity (cavity-fundus) |
| copyrostium | varchar(191) | single id -> copyrostium |
| copylostium | varchar(191) | single id -> copylostium |
| copymcx | varchar(191) | CSV ids -> copymcx (management cervix) |
| copymcavity | varchar(191) | CSV ids -> copymcavity |
| copymrostium | varchar(191) | CSV ids -> copymrostium |
| copymlostium | varchar(191) | CSV ids -> copymlostium |
| hysintervention | varchar(191) | free-text intervention |
| date | varchar(191) | procedure date as text |
| coast | varchar(191) | cost |

### Lookup tables — identical schema `(id int unsigned PK AI, title varchar(191), del tinyint(1)/int unsigned)`
All EMPTY in this dump (AUTO_INCREMENT=1, no INSERT statements); vocabulary is built at runtime by doctors via `getselectajax` (new terms saved with `del=null`; lists filter `del is null`; delete sets `del=1`).

Laparoscopy lookups (dump lines 8634-8849):
- `laparplace` — procedure facility
- `laparul` — uterus findings
- `laparrt` — right tube findings
- `laparlt` — left tube findings
- `laparro` — right ovary findings
- `laparlo` — left ovary findings
- `laparpelvis` — pelvis findings
- `laparmut` — management/intervention uterus
- `laparmrt` — management right tube
- `laparmlt` — management left tube
- `laparmro` — management right ovary
- `laparmlo` — management left ovary
- `laparmpelvis` — management pelvis

Hysteroscopy lookups (dump lines 4635-4776):
- `copyplace` — procedure facility
- `copydil` — cervical dilatation findings
- `copyintrod` — scope introduction findings
- `copycx` — cervix/cervical canal findings
- `copycavity` — uterine cavity & fundus findings
- `copyrostium` — right tubal ostium findings
- `copylostium` — left tubal ostium findings
- `copymcx` — management cervix
- `copymcavity` — management cavity
- `copymrostium` — management right ostium
- `copymlostium` — management left ostium

Minor type drift among the clones: `del` is `tinyint(1) unsigned` in some, `int(11) unsigned` in others (e.g. `copycx`, `copymcx`, `laparlt`, `laparrt`, `laparmlt`, `laparmpelvis`, `laparplace`).

### jncision (line 8591) — surgical incision type lookup
`(id int PK AI, name varchar(255) NOT NULL, del int NOT NULL)` — SEEDED (AUTO_INCREMENT=25):
Active (del=0): `midline`, `transverse`, `paramedian`, `Pfannistel` [sic], plus junk rows `6666`, `iiiii`, `aaa`, `cccc`, `bbb`. Soft-deleted test junk (del=1): sss, wwwaa, aaaaaqq, eeee, www, ee21, rr1q111, 444, ww, 444444, ffff, bbb, "لل", ddd, aaaaa.

### midlinejncision (line 9103) — incision subtype child lookup
`(id int PK AI, name varchar(255) NOT NULL, del int NOT NULL, jncisionid int NOT NULL)` — SEEDED (AUTO_INCREMENT=23):
Meaningful rows: `upper`(->midline), `lower`(->midline), `pfnnestiel` [sic](->transverse), `joel cohen`(->transverse), `RT`(->paramedian), `1-T`(->paramedian); remainder is test junk (ccc, rr, ee11, aaa, ww, 22 ...).

## Relationships (inferred, no declared FKs)
- `laparoscopy.patientid` -> `patients.id` (varchar vs int type mismatch)
- `laparoscopy.doctorid` -> `awusers.user_id`
- `hysteroscopy.patientid` -> `patients.id`
- `hysteroscopy.doctorid` -> `awusers.user_id`
- `laparoscopyinfertility.infertilitysheetid` -> `infertilitysheet.id`
- `hysteroscopyinfertility.infertilitysheetid` -> `infertilitysheet.id`
- `laparoscopyinfertility.laparplace` -> `laparplace.id` (single)
- `laparoscopyinfertility.{laparul,laparrt,laparlt,laparro,laparlo,laparpelvis,laparmut,laparmrt,laparmlt,laparmro,laparmlo,laparmpelvis}` -> matching lookup `.id` (CSV multi-valued)
- `hysteroscopyinfertility.{copyplace,copydil,copyintrod,copyrostium,copylostium}` -> matching lookup `.id` (single)
- `hysteroscopyinfertility.{copycx,copycavity,copymcx,copymcavity,copymrostium,copymlostium}` -> matching lookup `.id` (CSV multi-valued)
- `midlinejncision.jncisionid` -> `jncision.id`
- `operativedetails.jncisionid` -> `jncision.id` (cross-module: operations)
- `operativedetails.midlineid` -> `midlinejncision.id` (cross-module: operations)
- `infertilitysheet.patientid` -> `patients.id` (transitive path from structured endoscopy rows to patient)
- `lastvisit.patientid` -> `patients.id` (written by operations.php when opening patient ops view)

## Business Workflows (traced from code)
1. **Free-text path (investigation.php index)**: on opening a patient's Investigations screen, if no non-deleted `laparoscopy` row exists, an empty one is auto-inserted (sdate=today, patientid, doctorid from session, del=0); same for `hysteroscopy`. Tabs render all rows newest-first with doctor name from `awusers`. Each input is an `updateElement` → POST to generic `update` action (load bean, set column, store). Delete buttons set `del=1`. Reports (`completereport.php`) later filter out rows where ALL clinical fields are NULL — compensating for the auto-created empties.
2. **Structured path (infertilitysheet.php / gynasheet.php)**: the infertility sheet's Investigations area has "Hysteroscopy" / "Laparoscopy" add links → AJAX `append` (dispense bean of `hysteroscopyinfertility`/`laparoscopyinfertility`, set `infertilitysheetid`, date=today) → returns row HTML from `infertilitysheet/append.html`. Each row = 2 visual rows: findings (place + per-organ selects) and management (intervention text + per-organ management selects + cost). Selects are populated via `getselect` (terms where `del is null`); new clinical terms added inline via `getselectajax`; terms removed via `deldataselect` (del=1); whole record removed via `deleterow`.
3. **Operations report (operations.php)**: index lists today's procedures; `search`/`searchresult` build `$searchQuery` by string concatenation (date range, patientid) and run `hysteroscopyinfertility`/`laparoscopyinfertility` LEFT JOIN `infertilitysheet`, then hydrate patient via `infertilitysheet.patientid` and place names. Writes/updates `lastvisit`. The parallel queries against `hysteroscopy`/`laparoscopy` use columns `date` and `infertilitysheetid` that don't exist in those tables → broken/dead branch (inference).
4. **Reporting/printing**: `sh.php` and `Completesreport.php` re-read both structured tables per infertility sheet and resolve every lookup id (CSV expansion via `id in (0$csv)`); `fullreport.php` gates report sections on existence of endoscopy rows in a date span; `completereport.php` prints free-text rows with doctor names.
5. **Incision lookups (operativedetails.php)**: operative details form shows `jncision` dropdown (del=0); selecting one AJAX-loads its `midlinejncision` children (`loadjncision`); `addjncision`/`addjncisionchild` insert new types inline; `setup.php` protects both tables from deletion in the generic table manager.

## ERP Migration Notes
Proposed Laravel models:
- **`EndoscopyProcedure`** (`endoscopy_procedures`): unify all 4 record tables. Columns: `patient_id` FK, `infertility_sheet_id` nullable FK, `type` enum(laparoscopy, hysteroscopy), `performed_at` date, `place_id` FK, `intervention` text, `notes` text, `cost` decimal(10,2), `doctor_id` FK, timestamps, softDeletes.
- **`EndoscopyFinding`** (`endoscopy_findings`): replaces the 26 CSV columns. Columns: `procedure_id` FK, `site` enum(uterus, right_tube, left_tube, right_ovary, left_ovary, pelvis, cervix, cavity_fundus, right_ostium, left_ostium, dilatation, introduction, right_adnexa, left_adnexa), `category` enum(finding, management), `term_id` nullable FK, `free_text` nullable. Multi-select becomes multiple rows (or a pivot `endoscopy_finding_term`).
- **`EndoscopyTerm`** (`endoscopy_terms`): merge all 26 identical lookup tables. Columns: `scope` enum(laparoscopy, hysteroscopy), `site`, `category` (finding/management/place), `title`, `is_active`. Source table name maps deterministically to (scope, site, category).
- **`IncisionType`** (`incision_types`, self-referencing `parent_id`): merge `jncision` + `midlinejncision`; belongs to the Operations module (`operative_details.incision_type_id`, `incision_subtype_id`). Cleanse junk seed rows (keep midline/transverse/paramedian/Pfannenstiel + real children; fix spellings) before migration.

Migration/normalization advice:
- Import free-text `laparoscopy`/`hysteroscopy` rows only where at least one clinical field is non-null (replicate completereport.php filter) to drop auto-created empties; map free-text region fields into `EndoscopyFinding.free_text` rows.
- Explode CSV id columns into finding rows; cast `patientid` varchar → int; parse `date` varchar and `coast` varchar with cleanup pass; normalize `del` (0/1/null) → `deleted_at`.
- Replace the generic `update` endpoint (arbitrary table/column write — mass-assignment/SQLi risk) with dedicated REST resources + FormRequest validation; replace `id in (0$csv)` and concatenated search queries (operations.php SQLi) with Eloquent whereIn/parameter binding.
- Drop legacy duplicates after migration: `gynasheet00.php`/`infertilitysheet00.php` read paths, the broken operations.php branch, and all 26 lookup tables + 4 record tables.
- Keep inline vocabulary growth as a feature (doctors add terms on the fly) but gate it with permissions and dedupe (case-insensitive unique index on scope+site+category+title).

---


# Module: Operations & Deliveries (operations)

## Purpose
Covers the surgical lifecycle of the OB/GYN clinic: operation booking via a daily waiting list, the detailed operative note (anesthesia, incision, surgical team, intra-operative drugs with pharmacy receipt integration), brief operation logs attached to the infertility sheet, daily operations/deliveries reports, pregnancy-termination report, hospital transfer letters, and per-patient pathology/histopathology results.

## Controllers & Views (file paths)
- `/home/amrtechogate/public_html/obgy/core/controllers/operations.php` — operations daily report (joins `operations` + `infertilitysheet`), operation waiting list (`op_wait_list`), 4D waiting list (`op_4d_list`, not assigned), `sendToVisits` (creates `visits` row with `detections` id 36 hardcoded).
- `/home/amrtechogate/public_html/obgy/core/controllers/operativedetails.php` — operative note CRUD (`operativedetails`, `operativedetailsdrugs`), cascading lookups (`anasthesa`→`generalanasthesa`, `jncision`→`midlinejncision`), pharmacy receipt integration (`recepittmp`, `receiptdrugs`).
- `/home/amrtechogate/public_html/obgy/core/controllers/Deliveries.php` — deliveries report from `awifep` + `registeration` (place via `place2`), `wifeobst`, `wifemodeofd`.
- `/home/amrtechogate/public_html/obgy/core/controllers/termination.php` — termination report querying `phobstetric.obstermination` joined to `patients`.
- Supporting: `/home/amrtechogate/public_html/obgy/core/controllers/instruction.php` (uses `hospitalnames` for hospital transfer letters), `/home/amrtechogate/public_html/obgy/core/controllers/investigation.php` (manages `pathology` rows), `/home/amrtechogate/public_html/obgy/core/controllers/infertilitysheet.php` and `ancsheet.php` / `Completesreport.php` / `sh.php` (read `operations` by `infertilitysheetid`), `visits.php`/`index.php` (use `detections` as the fee catalog).
- Views: `/home/amrtechogate/public_html/obgy/core/views/obgy/operation/` (waitinglist.html, listdiv.html, listdivfut.html, four_d.html, listdiv_4d.html, listdivfut_4d.html), `/home/amrtechogate/public_html/obgy/core/views/obgy/operativedetails/` (show.html, showdata.html, anasthesa.html, jncision.html, editmodel.html, newrowUS.html), `/home/amrtechogate/public_html/obgy/core/views/obgy/reports2/` (operations*.html, deliveries*.html), `/home/amrtechogate/public_html/obgy/core/views/obgy/reports/termination*.html`, JS: `/home/amrtechogate/public_html/obgy/core/views/obgy/_assets/custom/operativedetails.js`.

## Tables

### operation (lookup)
Catalog of surgical procedure names. Used by waiting list (reads `title`) and operative note (reads `name`) — the two columns are used interchangeably by different screens.
- `id` int(11) PK AI
- `title` varchar(191) NULL
- `del` varchar(191) NULL (soft delete; queries use both `del is null` and `del = 0`)
- `name` varchar(191) NULL
No seed rows in the dump. Referenced by: `op_wait_list.operation` (CSV), `operations.operation` (CSV), `operativedetails.operationid`.

### operationids (legacy, unused)
- `id` int(11) PK AI
- `doctorid` int(11) NOT NULL
- `assistantid` int(11) NOT NULL
- `operationid` int(11) NOT NULL
No reference anywhere in `core/`, `board/`, `screen/`, `pharmacy/` code. Inferred legacy link table (doctor/assistant per operation) superseded by `operativedetails` staff CSV columns. Candidate for dropping.

### operations (transactional)
Brief operation log attached to the infertility sheet (shown inside infertility sheet and in the daily operations report).
- `id` int(11) unsigned PK AI
- `infertilitysheetid` int(11) unsigned NULL -> infertilitysheet.id
- `operation` varchar(191) NULL — CSV of operation.id values (resolved with `id in (0$data->operation)`)
- `date` varchar(191) NULL — stored as 'Y/m/d' string
- `place` varchar(191) NULL -> place.id
- `histopath` varchar(191) NULL — CSV of histopath.id values
- `cost` varchar(191) NULL — money as string
- `note` varchar(191) NULL
Rows are created/edited inline from infertility sheet views via generic `Add()` AJAX (table/id/col/value).

### operativedetails (transactional, core)
Full operative note per patient.
- `id` int(11) PK AI
- `operationid` int(11) NOT NULL -> operation.id
- `date` date NOT NULL
- `timestart` time, `timeend` time
- `indication` varchar(255)
- `anasthesaid` int(11) -> anasthesa.id
- `generalid` int(11) -> generalanasthesa.id
- `anasthetstsid` varchar(255) — CSV of awusers.user_id (anesthetist doctors, role_id=4 specialid=1)
- `assistantid` varchar(255) — CSV of awusers.user_id (assistant doctors, role_id=6 specialid=2)
- `complication` longtext
- `jncisionid` int(11) -> jncision.id (incision type lookup, not assigned to this module)
- `midlineid` int(11) -> midlinejncision.id (incision subtype, cascades from jncision)
- `peritonealwash` int(1) default 0
- `exdetails`, `opsteps`, `layers`, `drains`, `suture` varchar(255)
- `bloodtransfusion` int(1) default 0
- `complications` varchar(255)
- `recommendation` longtext
- `patientid` int(11) NOT NULL -> patients.id
- `del` int(11) unsigned NULL (soft delete)
- `anasthetsts2id` varchar(255) — CSV awusers (second anesthetist team, role_id=4 specialid=3)
- `assistant2id` varchar(255) — CSV awusers (second assistant team, role_id=6 specialid=4)
Empty row is auto-created when the showdata screen opens; fields are saved one-by-one via AJAX `update()`.

### operativedetailsdrugs (transactional)
Intra-operative drugs grid; integrates with pharmacy billing.
- `id` int(11) PK AI
- `operationid` int(11) NOT NULL -> operativedetails.id
- `drugid` varchar(255) -> drugs.id (-1 = unselected)
- `drugtype` varchar(255), `drugdos` varchar(255), `drugname` varchar(255)
- `doctorid` varchar(255) -> awusers.user_id (session user)
- `deleted` int(11) default 0
- `recepittmpid` int(11) default 0 -> recepittmp.id (pharmacy temp receipt; new receipt opened if status=1 i.e. already cashed)
- `recepitdrugid` int(11) default 0 -> receiptdrugs.id

### op_wait_list (transactional)
Surgical booking / waiting list.
- `id` int(11) PK AI
- `patientid` int(11) default 0 -> patients.id
- `opdate` date NULL
- `operation` varchar(255) — CSV of operation.id values
- `place` int(11) NULL -> place.id
- `notes` text
- `deleted` int(11) default 0
- `user_id` int(11) default 0 -> awusers.user_id
- `createdate` datetime
Listed grouped by day (Arabic day names) over a date range, or future bookings per patient; edit/update/soft-delete supported. Table charset is latin1 with utf8mb4 columns (inconsistent).

### anasthesa (lookup)
Anesthesia main types.
- `id` int(11) PK AI, `name` varchar(255) NOT NULL, `del` int(11) NOT NULL
No seed rows in the dump (user-maintained at runtime via "add new item" AJAX).

### generalanasthesa (lookup, child)
Anesthesia subtypes, cascading dropdown under anasthesa.
- `id` int(11) PK AI, `name` varchar(255), `del` int(11), `anasthesaid` int(11) -> anasthesa.id
No seed rows.

### hospitalnames (lookup)
Hospitals for transfer/admission letters (instruction.php `printtransfer`).
- `id` int(11) unsigned PK AI, `name` varchar(191) NULL
No seed rows. Selected hospital id is used to render the printed letter; not persisted on a transactional row (selection-only usage in current code).

### histopath (lookup)
Histopathology findings catalog.
- `id` int(11) unsigned PK AI, `title` varchar(191) NULL, `del` tinyint(1) unsigned NULL
No seed rows. Referenced as CSV ids from `operations.histopath`.

### pathology (transactional)
Per-patient pathology results, managed from investigation.php (auto-creates an empty row if patient has none).
- `id` int(11) unsigned PK AI
- `sdate` date NULL
- `patientid` varchar(191) NULL -> patients.id (varchar, type mismatch)
- `del` int(11) unsigned NULL
- `cytology` varchar(191) NULL
- `histopathology` text NULL
- `doctorid` int(11) unsigned NULL -> awusers.user_id

### detections (lookup, shared with Visits module)
Visit/service fee catalog (title + price). In this module used by `operations.php::sendToVisits()` which hardcodes detection id 36 (4D scan fee) when pushing a 4D-list patient into today's `visits` queue.
- `id` int(11) PK AI, `title` varchar(100) NOT NULL, `detectionval` float NOT NULL, `del` int(11) unsigned NULL
No seed rows in the dump.

### place (lookup)
Operation places (used by waiting list and `operations.place`).
- `id` int(11) unsigned PK AI, `title` varchar(191) NULL, `del` int(11) unsigned NULL
No seed rows.

### place2 (lookup)
Delivery places — structural duplicate of `place`. Referenced from `registeration.place2` in the Deliveries report.
- `id` int(11) unsigned PK AI, `title` varchar(191) NULL, `del` varchar(191) NULL
No seed rows.

### Related (not assigned but seeded) — clinical vocabulary for operative note
- `jncision` (incision types, seeds: midline, transverse, paramedian + soft-deleted junk test rows 'sss','www', etc.)
- `midlinejncision` (incision subtypes, seeds: upper, lower, pfnnestiel [Pfannenstiel], joel cohen, RT, 1-T ... keyed by `jncisionid`)

## Relationships (explicit list)
- operations.infertilitysheetid -> infertilitysheet.id
- operations.operation (CSV) -> operation.id
- operations.histopath (CSV) -> histopath.id
- operations.place -> place.id
- operativedetails.patientid -> patients.id
- operativedetails.operationid -> operation.id
- operativedetails.anasthesaid -> anasthesa.id
- operativedetails.generalid -> generalanasthesa.id
- operativedetails.jncisionid -> jncision.id ; operativedetails.midlineid -> midlinejncision.id
- operativedetails.anasthetstsid / anasthetsts2id / assistantid / assistant2id (CSV) -> awusers.user_id
- generalanasthesa.anasthesaid -> anasthesa.id
- operativedetailsdrugs.operationid -> operativedetails.id
- operativedetailsdrugs.drugid -> drugs.id ; operativedetailsdrugs.doctorid -> awusers.user_id
- operativedetailsdrugs.recepittmpid -> recepittmp.id ; operativedetailsdrugs.recepitdrugid -> receiptdrugs.id
- op_wait_list.patientid -> patients.id ; op_wait_list.place -> place.id ; op_wait_list.operation (CSV) -> operation.id ; op_wait_list.user_id -> awusers.user_id
- pathology.patientid -> patients.id ; pathology.doctorid -> awusers.user_id
- registeration.place2 -> place2.id (deliveries report path: awifep -> registeration -> place2)
- visits.detectionid -> detections.id (sendToVisits uses id 36 hardcoded)
- operationids.doctorid/assistantid -> awusers.user_id ; operationids.operationid -> operation.id (inferred; table unused)

## Business Workflows (traced from code)
1. **Operation booking**: `operations.php?ac=waiting_list` lists places + operations lookups; `addInList` inserts `op_wait_list` (patientid, opdate, CSV operation ids, place, notes, user_id, createdate). `reloadOpVisits`/`listVisits` render day-grouped lists (Arabic day names via `getDayNameFnAr`); `editvisit`/`updatevisit` edit; `del` sets `deleted=1`; `addnewrecord` adds new lookup rows (e.g. new place/operation) inline.
2. **Operative note**: `operativedetails.php?ac=showdata&patientid=X` auto-creates an empty `operativedetails` row, then every field saves via AJAX `update()` (table/col/value). Cascades: choosing `anasthesaid` loads `generalanasthesa` children (`loadanasthesa`); choosing `jncisionid` loads `midlinejncision` (`loadjncision`). Staff pickers query `awusers` with hardcoded role/special ids (4/1, 6/2, 4/3, 6/4) and store CSV user ids. New lookup values can be added inline (`addselect`, `addanasthesa`, `addanasthesachild`, `addjncision`, `addjncisionchild`).
3. **Intra-op drugs + pharmacy**: `drawRow` inserts an `operativedetailsdrugs` placeholder; selecting a drug in `update()` creates/reuses a `recepittmp` (status 0 = open, 1 = cashed -> open a new one) and a `receiptdrugs` line, so the pharmacy module can dispense/bill. `delodrug` soft-deletes the drug row and trashes the receipt line if not yet cashed.
4. **Operations report**: `operations.php` index/search/showprint join `operations` LEFT JOIN `infertilitysheet` to resolve `patientid`, expand CSV operation/histopath ids, and load `place`; also lists `hysteroscopy(infertility)` and `laparoscopy(infertility)` rows. Note: index uses tables `hysteroscopy`/`laparoscopy` while search/print use `hysteroscopyinfertility`/`laparoscopyinfertility` (inconsistency).
5. **Deliveries report**: `Deliveries.php` reads `awifep` (delivery record per pregnancy, antenatal module) for today/range, resolves patient via `infertilitysheet`, mode of delivery (`wifemodeofd`), obstetric outcomes (`wifeobst` CSV), and the latest `registeration` row to show `place2`, `origin`, `type`; search + print variants.
6. **Termination report**: `termination.php` lists `phobstericterminate` types, then `search` runs raw SQL on `phobstetric` filtered by `obstermination = <termtype>` joined to `patients` (in instruction.php logic: 1 = SVD, 2 = CS).
7. **Hospital transfer letter**: `instruction.php` transfer screen lists `hospitalnames` + `operationinstructions`; `printtransfer` computes obstetric formula (G = phobstetric count + pno + ab + ectopic + vmodel + 1; P = SVD + CS + pno), gestational weeks from `mainantenental.lmp`, and prints the letter with the selected hospital name.
8. **Pathology results**: `investigation.php` auto-creates a `pathology` row per patient if none exists; rows hold sdate, cytology, histopathology free text, doctorid; listed newest-first with doctor name resolved from `awusers`.
9. **4D list → visit**: `sendToVisits` loads `detections` id 36 and inserts a `visits` row (fee = detectionval, queue order = max(enterordered)+1) — cross-module write into the Visits/Finance module.

## ERP Migration Notes
**Proposed Laravel models/tables:**
- `Procedure` (merge `operation.title`/`name` into one `name` column; replaces `operation`)
- `SurgicalBooking` (replaces `op_wait_list`) + pivot `surgical_booking_procedure`
- `OperativeNote` (replaces `operativedetails`) with proper FKs, `datetime` start/end
- `operative_note_staff` pivot (note_id, user_id, role enum: anesthetist/assistant, team enum) replacing the 4 CSV columns
- `OperativeNoteDrug` (replaces `operativedetailsdrugs`); link to pharmacy via a `PrescriptionService` + domain events instead of inline receipt logic
- `InfertilityOperation` (replaces `operations`) + pivots `infertility_operation_procedure`, `infertility_operation_histopath_finding`; `date` as DATE, `cost` as DECIMAL
- `AnesthesiaType` / `AnesthesiaSubtype` (replaces `anasthesa`/`generalanasthesa`, FK subtype->type)
- `IncisionType` / `IncisionSubtype` (migrate `jncision`/`midlinejncision`, purge soft-deleted junk seeds)
- `Hospital` (replaces `hospitalnames`); persist hospital_id on a new `TransferLetter` model instead of print-only usage
- `HistopathologyFinding` (replaces `histopath`)
- `PathologyResult` (replaces `pathology`; `patientid` varchar -> unsignedBigInteger FK)
- `Location` single table with `type` enum (operation/delivery) merging `place` + `place2` (and sibling `copyplace`, `laparplace` from neighboring modules)
- `ServiceType` (the `detections` fee catalog) belongs to the Visits/Finance module; reference by configurable setting instead of hardcoded id 36

**Normalize / drop:**
- Drop `operationids` (no code references; verify production row count first).
- Replace all CSV multi-value columns with pivot tables.
- Unify soft deletes (`del` null/0/1 vs `deleted` 0/1) into Laravel `deleted_at`.
- Replace auto-create-empty-row + per-field AJAX with draft/final status or single validated FormRequest submission.
- Parameterize all queries (current code concatenates `patientid`, `parentid`, `termtype` into SQL — injection risk).
- Move hardcoded role/special ids for surgical team pickers into configuration or a staff-specialty table.
- Termination semantics (`obstermination`: 1=SVD, 2=CS, ...) should become a `delivery_modes` enum/lookup shared with the obstetric history module.

---


# Module: Clinical Examination & Diagnosis (examination)

## Purpose
Per-patient general clinical examination records (vitals + per-body-system findings), configurable lookup catalogs for examination findings (head/neck, chest/heart, abdomen, pelvis, extremities, local gynecological exam, breast, hirsutism, thyroid), a configurable Present History Q&A engine (questions + predefined answers, answered per visit), gyna/antenatal diagnosis catalogs, and a general-anesthesia subtype catalog consumed by the operations module.

## Controllers & Views (file paths)
- `/home/amrtechogate/public_html/obgy/core/controllers/examination.php` — examination CRUD (index, addNewRow, update, delData, addexitem)
- `/home/amrtechogate/public_html/obgy/core/controllers/addpresenthistory.php` — present-history Q&A catalog admin (index, addnew, update, del, getbyquesid, addtofavorite)
- Views:
  - `/home/amrtechogate/public_html/obgy/core/views/obgy/examination/add.html` (main grid; inline AJAX cell editing via `updateElement` class with `data-tableName`/`data-colName`)
  - `/home/amrtechogate/public_html/obgy/core/views/obgy/examination/newExamRow.html` (AJAX row fragment)
  - `/home/amrtechogate/public_html/obgy/core/views/obgy/addpresenthistory/show.html`, `answers.html`
- Consumers (other modules using this module's tables):
  - `core/controllers/gyna.php` — present-history rendering/saving (writes `gynaph`), `diagnosis` catalog usage, `adddiagnosis`, `deldiagnosis`
  - `core/controllers/antenalvisit.php` — `diagnosisant` catalog usage
  - `core/controllers/operativedetails.php` — `generalanasthesa`
  - `core/controllers/sh.php`, `Completesreport.php`, `combinedreport.php` — resolve lookup IDs for printed reports
  - `core/views/obgy/infertilitysheet/add.html`, `core/views/obgy/gynasheet/add.html` — selects for `generalbreast`, `generalhirsutism`, `generalthyroid`, `examinationlocalse` writing into table `infertilitysheet`
  - `core/controllers/setup.php` — DB maintenance lists (`diagnosis`/`diagnosisant` allowed to truncate; `generalanasthesa` protected)

## Tables
Schema source: `/home/amrtechogate/public_html/obgy/_db/obgy_12-7-2024.sql`. No declared foreign keys anywhere; all FKs below are inferred. ALL lookup tables in this dump are EMPTY (`AUTO_INCREMENT=1`, no INSERT statements) — actual clinical vocabulary lives only in the production DB.

### examination
Per-patient general examination record (one row per exam session).
| Column | Type |
|---|---|
| id | int(11) unsigned PK AI |
| patientid | varchar(191) NULL |
| doctorid | int(11) unsigned NULL |
| sysdate | varchar(191) NULL (entry date) |
| examinationdate | varchar(191) NULL |
| weight | varchar(191) NULL |
| height | varchar(191) NULL |
| bmi | varchar(191) NULL (JS-computed, readonly field) |
| b_p | varchar(191) NULL (blood pressure, masked input) |
| pulse | varchar(191) NULL |
| rgb | varchar(191) NULL (random blood glucose, inferred from UI position) |
| headandneck | varchar(191) NULL (free text OR examinationhead id — mixed) |
| chestandheart | varchar(191) NULL (free text OR examinationchest id) |
| abdomen | varchar(191) NULL (free text OR examinationabdomen id) |
| pelvis | varchar(191) NULL (free text OR examinationpelvis id) |
| extremitis | varchar(191) NULL (free text OR examinationextremitis id) |
| del | int(11) unsigned NULL (soft delete) |
| bp2 | int(11) NULL (second BP value) |

Inferred FKs: `patientid -> patients.id` (type mismatch: varchar vs int), `doctorid -> awusers.user_id`.

### examinationhead / examinationchest / examinationabdomen / examinationpelvis / examinationextremitis
Five structurally identical finding lookup catalogs, one per body system:
`id int(11) unsigned PK AI, name varchar(191) NULL, del int(11) unsigned NULL`.
Loaded only when the corresponding `programesetting` flag (`head`, `chest`, `abdomen`, `pelvis`, `extremitis`) != 1; otherwise the UI shows free-text input. `addexitem()` inserts a new row and stores its numeric id into the matching `examination` column (same column that otherwise holds free text). Seed data: none in dump.

### examinationlocalse
Local (gynecological) examination findings lookup: `id int(11) unsigned PK AI, title varchar(191) NULL, del tinyint(1) unsigned NULL`. Multi-select; selected ids stored as CSV in `infertilitysheet.examinationlocalse` (used by infertilitysheet view; resolved in sh.php/Completesreport.php via `R::findAll('examinationlocalse', "id in (0$csv)")`). Seed data: none in dump.

### generalbreast
Breast exam findings lookup: `id int(11) unsigned PK AI, title varchar(191) NULL, del tinyint(1) unsigned NULL`. Multi-select; CSV ids in `infertilitysheet.generalbreast` (selects present in both gynasheet/add.html and infertilitysheet/add.html, both with `data-table="infertilitysheet"`). Seed data: none in dump.

### generalhirsutism
Hirsutism grading lookup: `id int(11) unsigned PK AI, title varchar(191) NULL, del tinyint(1) unsigned NULL`. Single select; id stored in `infertilitysheet.generalhirsutism` (resolved with `R::load`). Seed data: none in dump.

### generalthyroid
Thyroid exam findings lookup: `id int(11) unsigned PK AI, title varchar(191) NULL, del tinyint(1) unsigned NULL`. Single select; id stored in `infertilitysheet.generalthyroid`. Seed data: none in dump.

### generalanasthesa
General-anesthesia subtype/detail catalog (child of `anasthesa`): `id int(11) PK AI, name varchar(255) NOT NULL, del int(11) NOT NULL, anasthesaid int(11) NOT NULL`.
Inferred FK: `anasthesaid -> anasthesa.id`. Consumed by `operativedetails.php` (`findall('generalanasthesa','anasthesaid = ? and del = 0', [$operativedetails->anasthesaid])`); rows are creatable/editable from the operative-details screen. Listed in setup.php as NOT allowed to truncate. Seed data: none in dump.

### diagnosis
Gyna-visit diagnosis catalog: `id int(11) unsigned PK AI, name varchar(191) NULL, conditions int(11) unsigned NULL, title varchar(191) NULL, del tinyint(1) unsigned NULL`.
`conditions` is the de-facto soft-delete flag: lists filter `conditions = 0`; `gyna.php::deldiagnosis()` sets `conditions = 1`; `adddiagnosis()` inserts with `conditions = 0`. The `del` and `title` columns appear unused by the traced code. Selected diagnoses stored as sorted CSV of ids in `gyna.diagnosisid` (text column; JSON array from UI decoded, sorted, joined with commas). Seed data: none in dump.

### diagnosisant
Antenatal diagnosis catalog: `id int(11) unsigned PK AI, name varchar(191) NULL, conditions int(11) unsigned NULL`. Same `conditions`-as-soft-delete pattern. CSV ids stored in `antenalvisit.diagnosisid` (varchar(191)); resolved via `explode(',')` + `R::load('diagnosisant', $id)` for printing. Inline add via `antenalvisit.php::adddiagnosis()`, which also appends the new id to the visit's CSV. Seed data: none in dump.

### presenthistoryquestions
Present-history question (category) catalog: `id int(11) PK AI, name varchar(150) NULL, deleted int(11) NOT NULL DEFAULT 0, displayorder int(11) NOT NULL DEFAULT 0`. Table comment: "cats for invests appear in gyna and antenatal". `displayorder` doubles as UI column-grouping key hardcoded in `gyna.php` (groups: 1-3, 4-6, 7-9, 10-12, >12, =0 "others"; admin list excludes `displayorder = -1`). New question gets `max(displayorder)+1`. Seed data: none in dump.

### presenthistoryanswers
Predefined answers per question: `id int(11) PK AI, presenthistorycatid int(11) NOT NULL, name varchar(150) NULL, favorite int(11) NOT NULL DEFAULT 0, deleted int(11) NOT NULL DEFAULT 0`. Table comment: "invests appear in gyna and antenatal".
Inferred FK: `presenthistorycatid -> presenthistoryquestions.id`. `favorite` toggled via `addtofavorite()`. Seed data: none in dump.

### Runtime companion table (NOT in dump): gynaph
Patient's actual present-history answers per visit date, written by `gyna.php`: columns observed in code: `patientid, date, catid, answerid, doctorid, deleted`. `catid -> presenthistoryquestions.id`, `answerid -> presenthistoryanswers.id`. Absent from the SQL dump — likely created at runtime by RedBeanPHP fluid mode (inference). Must be captured from production DB during migration.

## Relationships (explicit list)
- examination.patientid -> patients.id (varchar->int mismatch)
- examination.doctorid -> awusers.user_id
- examination.headandneck -> examinationhead.id (only when lookup mode; otherwise free text)
- examination.chestandheart -> examinationchest.id (same caveat)
- examination.abdomen -> examinationabdomen.id (same caveat)
- examination.pelvis -> examinationpelvis.id (same caveat)
- examination.extremitis -> examinationextremitis.id (same caveat)
- programesetting.head/chest/abdomen/pelvis/extremitis -> feature toggles controlling lookup vs free text
- infertilitysheet.examinationlocalse -> examinationlocalse.id (CSV multi-value)
- infertilitysheet.generalbreast -> generalbreast.id (CSV multi-value)
- infertilitysheet.generalhirsutism -> generalhirsutism.id
- infertilitysheet.generalthyroid -> generalthyroid.id
- gyna.diagnosisid -> diagnosis.id (CSV multi-value)
- antenalvisit.diagnosisid -> diagnosisant.id (CSV multi-value)
- presenthistoryanswers.presenthistorycatid -> presenthistoryquestions.id
- gynaph.catid -> presenthistoryquestions.id (gynaph not in dump)
- gynaph.answerid -> presenthistoryanswers.id
- gynaph.patientid -> patients.id; gynaph.doctorid -> awusers.user_id
- generalanasthesa.anasthesaid -> anasthesa.id
- operativedetails.anasthesaid -> anasthesa.id (joins generalanasthesa on same key)

## Business Workflows (traced from code)
1. **Examination grid**: `examination.php::index()` requires a patient in session (else redirect), loads `programesetting`, conditionally loads the five system-finding lookups (`del = 0`), and lists all `examination` rows for the patient (`del = 0`, ordered `examinationdate desc, id desc`), resolving each row's doctor name from `awusers`.
2. **Row creation**: "Add" triggers `addNewRow()` which immediately persists an empty `examination` bean (today's date, session user as doctor, all clinical fields '') and returns the `newExamRow.html` fragment — record exists before any data entry.
3. **Cell-by-cell autosave**: every input/select has `updateElement` + `data-tableName`/`data-colName`/`data-id`; blur posts to `update()` which loads the row (by id+patientid for `examination`, by id alone for lookup tables) and stores the single column. BMI is computed client-side (`calcbm` class) and saved readonly. BP uses a mask and two columns (`b_p`, `bp2`).
4. **Inline vocabulary growth**: `addexitem()` inserts a new lookup row (name, del=0) into the POSTed table name and then writes the new numeric id into the specified `examination` column for the current row.
5. **Soft delete**: `delData()` sets `del = 1` on any POSTed table/id.
6. **Present-history catalog admin** (`addpresenthistory.php`): list non-deleted questions (`displayorder != -1`); `addnew()` creates either an answer (when `questionId > 0`, linked via `presenthistorycatid`) or a question (next `displayorder`); `getbyquesid()` returns the answers fragment; `addtofavorite()` flips `favorite`; `del()` sets `deleted = 1`.
7. **Present-history capture** (`gyna.php`): questions fetched in five hardcoded `displayorder` buckets + "others" (`displayorder = 0`); checked answers per question are stored one row each in `gynaph` (patientid, date, catid, answerid, doctorid, deleted=0); review screen groups by distinct date with joins to question/answer names; date-level delete via `UPDATE gynaph SET deleted = 1 WHERE patientid = ? AND date = ?`; printable via `gyna/printph.html`.
8. **Diagnosis capture**: gyna visit multiselect posts a JSON array; controller decodes, sorts, joins to CSV into `gyna.diagnosisid`. Antenatal identical with `diagnosisant` -> `antenalvisit.diagnosisid`. Both allow inline `adddiagnosis` (insert with `conditions = 0`, append id to visit CSV). Catalog "delete" = `conditions = 1`.
9. **Sheet-level general/local exam**: gynasheet and infertilitysheet screens write `generalbreast` (multi, CSV), `generalhirsutism`, `generalthyroid`, `examinationlocalse` (multi, CSV) selections into `infertilitysheet` columns; print controllers (`sh.php`, `Completesreport.php`) resolve them with `findAll(..., "id in (0$csv)")` / `R::load`.
10. **Anesthesia subtypes**: `operativedetails.php` lists `generalanasthesa` filtered by the operation's `anasthesaid`, supports inline add/edit/soft-delete of subtypes.

## Security / Data-Quality Findings
- `examination.php::update()` has NO auth/authorization check and accepts arbitrary `tableName`/`colName` from POST -> arbitrary table/column write (mass assignment). `delData()` and `addexitem()` also take table names from the client (auth-checked but still table-injection prone).
- String-interpolated SQL in report controllers: `"id in (0$infertility->examinationlocalse)"` (sh.php:116, Completesreport.php:327) — SQL injection surface.
- Mixed-content columns: `examination.headandneck` etc. hold free text or lookup ids depending on config history — migration must disambiguate (numeric-only value matching an existing lookup id => FK; else free text).
- CSV multi-value columns (`gyna.diagnosisid`, `antenalvisit.diagnosisid`, `infertilitysheet.generalbreast`, `.examinationlocalse`) violate 1NF.
- Misleading semantics: `diagnosis.conditions` is a soft-delete flag; `diagnosis.del`/`title` unused; three different soft-delete column names across the module (`del`, `deleted`, `conditions`).
- All numerics/dates stored as varchar; `examination.patientid` varchar vs `patients.id` int.
- `gynaph` missing from dump (RedBean fluid-mode runtime table — inference); export from production before migration.

## ERP Migration Notes (Laravel + Angular)
Proposed models/tables:
- `ClinicalExamination` (`clinical_examinations`): `patient_id` FK, `doctor_id` FK (users), `examined_at` date, `weight`/`height`/`bmi` decimal, `bp_systolic`/`bp_diastolic` smallint (map `b_p`+`bp2`), `pulse` smallint, `random_blood_glucose` decimal, SoftDeletes. Keep cell-level autosave UX via PATCH endpoint with a whitelisted field set (replaces the unsafe generic `update()`).
- `ExaminationFindingOption` (`examination_finding_options`): merge the 9 identical lookups (`examinationhead`, `examinationchest`, `examinationabdomen`, `examinationpelvis`, `examinationextremitis`, `examinationlocalse`, `generalbreast`, `generalhirsutism`, `generalthyroid`) into one table with `body_system` enum (head_neck, chest_heart, abdomen, pelvis, extremities, local_gyn, breast, hirsutism, thyroid) + `name` + SoftDeletes.
- `ExaminationFinding` pivot (`examination_findings`): `examination_id` (or polymorphic to sheet), `body_system`, nullable `finding_option_id`, nullable `free_text` — cleanly separates the current mixed text/id columns and the CSV columns on `infertilitysheet`.
- `Diagnosis` (`diagnoses`): merge `diagnosis` + `diagnosisant` with `specialty` enum (gyna, antenatal); migrate `conditions=1` rows to `deleted_at`. Add `visit_diagnosis` pivot (polymorphic `diagnosable` for gyna visit / antenatal visit) replacing CSV `diagnosisid` columns. Consider ICD-10 code column for future coding.
- `HistoryQuestion` (`history_questions`): name, `display_group` (explicit, replacing the hardcoded displayorder bucketing), `sort_order`, SoftDeletes.
- `HistoryAnswerOption` (`history_answer_options`): `history_question_id` FK, name, `is_favorite`, SoftDeletes.
- `PatientHistoryAnswer` (`patient_history_answers`): replaces runtime `gynaph` — `patient_id`, `visit_date`, `history_question_id`, `history_answer_option_id`, `doctor_id`, SoftDeletes.
- `AnesthesiaType` / `AnesthesiaSubtype`: replaces `anasthesa` / `generalanasthesa` (`anesthesia_type_id` FK); consumed by the operations module.
Migration script requirements: type-cast varchar vitals/dates; resolve patientid varchar->int; disambiguate mixed text/id finding columns; explode CSV columns into pivots; export lookup seed data and `gynaph` from production (this dump's lookups are empty). Drop: `diagnosis.del`, `diagnosis.title` (unused), the `conditions` flag pattern. Enforce FormRequest validation + Policies on all endpoints; eliminate client-supplied table/column names entirely.

---


# Module: Patient Medical History (history)

## Purpose
Patient-level (not visit-level) comprehensive medical history for an OB/GYN + infertility clinic. Seven sections rendered on one screen: menstrual history, contraception history, obstetric (past pregnancies/deliveries), past medical, past surgical, past gynecological, past ART attempts (IUI/IVF/ICSI with lab details), family history. Summary data (LMP, obstetric formula G/P counts) is consumed by virtually every other clinical screen. A separate small controller manages the "present history" (chief complaint) question/answer catalog used by exam sheets.

## Controllers & Views (file paths)
- `/home/amrtechogate/public_html/obgy/core/controllers/patienthistory.php` (1270 lines) — main module controller.
- `/home/amrtechogate/public_html/obgy/core/controllers/addpresenthistory.php` (198 lines) — admin CRUD for `presenthistoryquestions` / `presenthistoryanswers` catalog (not in assigned tables, but owned by this area).
- Shared consumers: `core/controllers/_patientdata.php` (patient header: latest LMP + obstetric formula), `core/controllers/_phmain.php` (history summary panel in sheets), `core/controllers/excel.php` (Excel export), `core/controllers/merge.php` (patient merge migrates all ph* rows), `core/controllers/termination.php` (report by termination type), `core/controllers/sh.php` + `Completesreport.php` (infertility-sheet reports resolving the *history catalogs), `fullreport.php`, `completereport.php`, `combinedreport.php`.
- Views: `/home/amrtechogate/public_html/obgy/core/views/obgy/patienthistory/` — `add.html` (full form), `addsimple.html` (simple-view mode), `editmodel.html`, `newMenstrualRow.html`, `newContractRow.html`, `newObstetricRow.html`, `newPastMedicalRow.html`, `newPastSurgicalRow.html`, `newPastgynecologicalRow.html`, `newPastArtRow.html`, `newFamilyRow.html`, `test.html`. Also `core/views/obgy/addpresenthistory/{show,answers}.html` and `core/views/obgy/infertilitysheet/add.html` (multi-selects bound to the *history catalogs).
- ORM: RedBeanPHP (`R::`) in fluid mode; Smarty templates; inline AJAX cell-save pattern.

## Tables
All tables: InnoDB, utf8mb4_unicode_ci, no declared foreign keys, soft delete via `del` flag. In the 12-7-2024 dump these tables are present schema-only (AUTO_INCREMENT=1, no INSERTs); seeded values below come from `_db/obgy_app_22-12-2021.sql`.

### Patient-record tables (one row per patient event)

#### phmenstrual — menstrual history records
- `id` int(11) unsigned PK AI
- `patientid` varchar(191) -> patients.id (stored as string)
- `sysdate` varchar(191) — entry date
- `menarche` varchar(191), `duration` varchar(191), `length` varchar(191)
- `phregularity` varchar(191) -> phregularity.id
- `phamount` varchar(191) -> phamount.id
- `phdysmenorrhoea` varchar(191) -> phdysmenorrhoea.id
- `phl_mp` date — LMP, used in pregnancy dating elsewhere
- `del` int(11) unsigned, `doctorid` int(11) unsigned -> awusers.user_id

#### phcontraception — past contraception records
- `id` PK AI, `patientid` varchar(191) -> patients.id, `sysdate` varchar(191)
- `contype` varchar(191) -> phcontracttype.id
- `contduration`, `contstopped`, `contranote` varchar(191)
- `del` int unsigned, `doctorid` int unsigned -> awusers.user_id

#### phobstetric — past pregnancies/deliveries (1 row per pregnancy, up to 3 babies inline)
- `id` PK AI, `patientid` varchar(191) -> patients.id, `sysdate` varchar(191)
- `obstrduration` varchar(191) — gestation duration
- `obstermination` varchar(191) -> phobstericterminate.id (hardcoded semantics: 1=SVD, 2=CS, 3=Ectopic, 4=Abortion, 5=V.mole in `_patientdata.php`/`_phmain.php`)
- `obsplace` varchar(191) -> phobstericterplace.id
- `obsdate` varchar(191), `obsnote` text
- `del` int unsigned, `doctorid` int unsigned -> awusers.user_id
- `outcomeid` int(191) — outcome flag; controller `update()` forces `babygendernew=1` when outcomeid==1 else 0
- Baby group x3 (denormalized twins/triplets): `babytype`, `babygendernew` (default '0'), `babyweight`, `babyname`, then `babytype1/babygendernew1/babyweight1/babyname1`, `babytype2/babygendernew2/babyweight2/babyname2` (all varchar(191))

#### phpastmedical — past medical diseases
- `id` PK AI, `patientid` varchar(191) -> patients.id, `sysdate` varchar(191)
- `medicaldisease` varchar(191) -> phpastmedicaldisease.id
- `medicalnote` varchar(191), `del` int unsigned, `doctorid` int unsigned
- `for_husband` int(11) default 0 — flags husband's condition (written by sheet controllers, not patienthistory.php)

#### phpastsurgical — past general surgical operations
- `id` PK AI, `patientid` int(11) unsigned -> patients.id (note: int here, varchar elsewhere)
- `doctorid` int unsigned, `sysdate` varchar(191)
- `surgicaloperation` varchar(191) -> phpastsurgicaloperation.id
- `phsurgicalyear` varchar(191) (free text year), `phsurgicalsurgeon` varchar(191), `surgicalnote` varchar(191)
- `del` int unsigned, `for_husband` int(11) default 0

#### phpastgynecological — past gynecological operations
- `id` PK AI, `patientid` varchar(191) -> patients.id, `doctorid` int unsigned
- `sysdate` date (note: real date here, varchar elsewhere)
- `gynecologicaloperation` varchar(191) -> phpastgynecologicaloperation.id
- `gynecologicalyear`, `gynecologicalsurgeon`, `gynecologicalnote` varchar(191)
- `del` int unsigned

#### phpastart — past ART attempts (external centers)
- `id` PK AI, `patientid` varchar(191) -> patients.id, `sysdate` varchar(191)
- `arttype` varchar(191) -> phpastarttype.id (code treats 2/3 = IVF/ICSI specially)
- `artcenter` varchar(191) -> phpastartcenter.id
- `artyear` varchar(191), `artnote` varchar(191)
- `del` int unsigned, `doctorid` int unsigned
- `emdate` date — embryo (freeze) date, `emembryos` int unsigned, `straw1` int unsigned — frozen straws
- NOTE: controller also reads `ovdate`, `freezdate` which are NOT in the 2024 schema (RedBeanPHP fluid-mode drift).

#### phpasticsi — lab details of a past IVF/ICSI attempt (child of phpastart)
- `id` PK AI
- `opu` varchar(191) — oocytes picked up
- `patientid` int unsigned -> patients.id
- `partentid` int unsigned -> phpastart.id (sic, misspelled "parent")
- `gi`, `gii`, `gv` varchar(191) — oocyte maturity grades (GI/GII/GV=MII presumed)
- `et` varchar(191) — embryos transferred, `etf` varchar(191)
- `frozen` varchar(191), `bhcg` varchar(191), `fhr` varchar(191)
- NOTE: controller reads `notes` column not present in schema (fluid drift). Upserted via generic `update()` keyed on `partentid`.

#### phfamily — family history records
- `id` PK AI, `patientid` varchar(191) -> patients.id, `doctorid` int unsigned, `sysdate` varchar(191)
- `family_disease` varchar(191) -> phfamilydisease.id
- `family_relative` varchar(191) -> phfamilyrelative.id
- `familynote` varchar(191), `del` int unsigned

### Lookup tables for patienthistory screen (all: `id` PK AI, `name` varchar(191), `del`)
- **phregularity** (`del` int(11) NOT NULL) — menstrual regularity. Seed (2021): regular, irrigular [sic], menpause [sic], premenpause, iry amenorrhea.
- **phamount** — menstrual blood AMOUNT (not money; verified: only referenced by `phmenstrual.phamount`). Seed: excessive.
- **phdysmenorrhoea** — Seed: dysmenorrhea.
- **phcontracttype** — contraception types. Seed: DMPA, Implants, POP, Mirena, IUD, OC, Condom, tuba, tubal ligation, coc, progesterone only, mesocept.
- **phobstericterminate** — pregnancy termination modes. Seed: SVD(1), CS(2), Ectopic(3), Abortion(4), V.mole(5) + soft-deleted junk rows ('aaaa','777','4444', Arabic test strings). IDs 1-5 are hardcoded in obstetric-formula logic.
- **phobstericterplace** — delivery place. Has an anomalous extra `patientid` int unsigned column (unused in read code). No seed data found.
- **phpastmedicaldisease** — disease names for phpastmedical. No seed in dumps.
- **phpastsurgicaloperation** — operation names for phpastsurgical. No seed in dumps.
- **phpastgynecologicaloperation** — gyn operation names. No seed in dumps.
- **phpastarttype** — ART attempt types. Seed: IUI(1), IVF(2), ICSI(3), cryo(4), faild [sic](5).
- **phpastartcenter** — external ART center names. No seed in dumps.
- **phfamilydisease** — family disease names. No seed in dumps.
- **phfamilyrelative** — relative/kinship names. No seed in dumps.

### Lookup catalogs for the INFERTILITY SHEET (parallel duplicate vocabularies; all: `id` PK AI, `title` varchar(191), `del`)
Referenced as comma-separated ID lists stored in `infertilitysheet` columns of the same name; resolved in `sh.php` / `Completesreport.php` via `R::findAll('X', "id in (0$list)")` (string-interpolated SQL).
- **medicalhistory** — wife's medical diseases. Seed: DM, HTN, hypothyroid, hyperthyroid, MS, Rh Arthritis, SLE, Renal stone, cervical lumbar disc, sciatica, tachcardia, DKA, recurrent ICU admission, anemic, icu admission (covid 19), gastritis, V.V, lumbar disc, hypercholesterolemia, favism, etc. (contains blanks and '??' junk).
- **medicalhistorydm** — husband's ("dm" = male partner, inferred from values) medical history. Seed: DM, varicocele, STD, Smoking, addiction, HTN, bilat, rt, lt, BA, hematuria, hematospermia, Smoking(shisha), paraplegia, obese, ex smoker, ex addiction, etc.
- **surgicalhistory** — wife's surgical history. Seed: appendicectomy, myomectomy, ov cystectomy, lap bil tubal disconn, EP variants (conservative/MTX/Laparoscopy/Laparotomy -otomy/-ectomy), CS, total thyroidectomy, hysterectomy, cholecystectomy, etc. (+7 blank soft-deleted rows).
- **surgicalhistorydm** — husband's surgical history. Seed: herniorrhaphy, varicocelectomy, cystoscope, orchiectomy, unilat, lt, rt, splenectomy, appendicectomy, piles/anal fissure, hard palate tumor excision, etc.
- **familyhistory** — family history for infertility sheet. Seed: DM, HTN, cardiac, hepatic, renal, hypothyroid, cancer, breast cancer, twins, brain cancer, B.A, rh arthritis, down s, HCV, '1st marriage +ve consanguinity', thyrotoxicosis, etc. (DM duplicated 4x, 3 soft-deleted).

## Relationships (explicit list, all inferred — no declared FKs)
- phmenstrual.patientid -> patients.id; phmenstrual.doctorid -> awusers.user_id
- phmenstrual.phregularity -> phregularity.id; phmenstrual.phamount -> phamount.id; phmenstrual.phdysmenorrhoea -> phdysmenorrhoea.id
- phcontraception.patientid -> patients.id; phcontraception.doctorid -> awusers.user_id; phcontraception.contype -> phcontracttype.id
- phobstetric.patientid -> patients.id; phobstetric.doctorid -> awusers.user_id; phobstetric.obstermination -> phobstericterminate.id; phobstetric.obsplace -> phobstericterplace.id
- phpastmedical.patientid -> patients.id; phpastmedical.medicaldisease -> phpastmedicaldisease.id; phpastmedical.doctorid -> awusers.user_id
- phpastsurgical.patientid -> patients.id; phpastsurgical.surgicaloperation -> phpastsurgicaloperation.id; phpastsurgical.doctorid -> awusers.user_id
- phpastgynecological.patientid -> patients.id; phpastgynecological.gynecologicaloperation -> phpastgynecologicaloperation.id; phpastgynecological.doctorid -> awusers.user_id
- phpastart.patientid -> patients.id; phpastart.arttype -> phpastarttype.id; phpastart.artcenter -> phpastartcenter.id; phpastart.doctorid -> awusers.user_id
- phpasticsi.partentid -> phpastart.id; phpasticsi.patientid -> patients.id
- phfamily.patientid -> patients.id; phfamily.family_disease -> phfamilydisease.id; phfamily.family_relative -> phfamilyrelative.id; phfamily.doctorid -> awusers.user_id
- infertilitysheet.{medicalhistory,surgicalhistory,familyhistory,medicalhistorydm,surgicalhistorydm} -> CSV id lists into medicalhistory.id / surgicalhistory.id / familyhistory.id / medicalhistorydm.id / surgicalhistorydm.id
- Cross-module reads: mointoringsheet.patientid (internal ART history shown on history page), mointoringsheetprocedure.id / mointoringsheetprotocol.id (names), excelinfopatients.patientid (export queue), programesetting (section visibility flags: menstrual, contraception, obstetric, past, family, medical, surgical, gynecological, art; simpleview; phppath), presenthistoryanswers.presenthistorycatid -> presenthistoryquestions.id, simplediagnosis (simple-view mode).

## Business Workflows (traced from code)
1. **Open history page** (`patienthistory.php::index`): requires login + role authorization + selected patient (redirect otherwise). Loads `programesetting`; if `simpleview==1` renders `addsimple.html` with `simplediagnosis` list, else renders `add.html` with all 7 sections: each section loads its lookup table(s) (`del=0`) plus the patient's records (`del=0 and patientid=?`), resolving `doctorid` to user_name per row (N+1 query per row). Past ART rows additionally join their `phpasticsi` child by `partentid` and `internalArt()` loads completed `mointoringsheet` rows (status=1) with procedure/protocol names. On every page open: patient registered in `excelinfopatients` if absent and `exec("{phppath}\php excel.php ...")` spawns a background Excel export.
2. **Add row (insert-then-edit)**: `addnewmenstural`, `addnewconstract`, `addnewobstetric`, `addnewpasthmedical`, `addnewpasthsurgical`, `addnewpastgynecological`, `addnewpastart`, `addnewFamily` — each immediately INSERTs an empty record (patientid, doctorid from session, sysdate=today, blank fields, del=0) and returns a rendered Smarty partial row (`new*Row.html`) for the screen.
3. **Inline cell save**: generic `update()` receives `tableName`, `colName`, `id`, `value`, `patientId` from POST and writes directly via RedBean (no whitelist, no auth check on this method). Special cases: `phpasticsi` upserted by `partentid`; `colName=='outcomeid'` also sets `babygendernew`. Date fields normalized when `dated==1`.
4. **Inline lookup creation**: `addRegualrity`, `addPhamount`, `addphdysmenorrhoea`, `addphcontype`, `addphobstericterminate`, `addphobstericterplace`, `addphpastmedical`, `addphpastsurgical`, `addphgynecologicaloperation`, `addphpastarttype`, `addphpastartcenter`, `addphFamilyDisease`, `addphFamilyRelative` — insert new lookup value, echo new id, and immediately update the patient's record row to point at it. `editselect`/`updateselect` rename existing lookup values from a modal (table name passed from client).
5. **Soft delete**: `delRows` (auth) and `delRowsommedel` (NO auth checks) set `del=1` on any client-supplied table/id.
6. **Header summary** (`_patientdata.php`, `_phmain.php`): latest `phmenstrual` by `phl_mp` gives LMP; obstetric formula computed by counting `phobstetric` rows per hardcoded `obstermination` ids (1 SVD, 2 CS, 3 Ectopic, 4 Abortion, 5 V.mole); top-5 distinct past medical / ART / family entries shown via joins to their lookup tables.
7. **Present history catalog** (`addpresenthistory.php`): CRUD on `presenthistoryquestions` (with displayorder) and `presenthistoryanswers` (per question, with `favorite` flag, `deleted` flag) — admin screen feeding the chief-complaint pickers in exam sheets.
8. **Reports/consumers**: `termination.php` joins `phobstetric` to `patients` for termination-type reports; `excel.php` exports all sections per patient; `merge.php` re-points all ph* rows when merging duplicate patients; `sh.php`/`Completesreport.php` resolve infertility-sheet CSV catalog ids for printable reports.

## ERP Migration Notes (Laravel + Angular)
Proposed models/tables (all with real FKs `patient_id`, `recorded_by` (user), timestamps, `softDeletes`):
- `MenstrualHistory` (menstrual_histories): menarche_age int, cycle_duration_days, cycle_length_days, regularity enum/lookup, flow_amount lookup, dysmenorrhea lookup, lmp date.
- `ContraceptionHistory`: method_id FK, duration, stopped_at, note.
- `ObstetricHistory` + child `ObstetricBaby` (hasMany, replaces babytype/1/2 column triplication): gestation, termination_mode ENUM (SVD/CS/Ectopic/Abortion/VesicularMole — make this a code-level enum, NOT a user-editable lookup, because G/P formula logic depends on fixed ids today), place_id, occurred_on date, outcome, note.
- `PastMedicalHistory`, `PastSurgicalHistory`, `PastGynecologicalHistory`: disease/operation_id FK, year (smallint), surgeon, note, `for_husband` boolean (keep — already in legacy phpastmedical/phpastsurgical).
- `PastArtCycle` + `PastArtIcsiDetail` (hasOne; or inline the 9 ICSI columns into PastArtCycle since it's strictly 1:1 via partentid): type enum (IUI/IVF/ICSI/Cryo), center_id FK, year, opu_count, grade_gi/gii/gv, embryos_transferred, frozen_count, bhcg_result, fhr, freeze_date, frozen_straws.
- `FamilyHistory`: disease_id, relative_id, note.
- Lookups: either one polymorphic `lookup_values` table (type, name, is_active) or small dedicated tables. **Merge the duplicate vocabularies**: `medicalhistory` + `phpastmedicaldisease` -> one diseases catalog; `surgicalhistory`/`surgicalhistorydm` + `phpastsurgicaloperation` -> one operations catalog; `familyhistory` + `phfamilydisease` -> one family-disease catalog. Replace `*dm` (husband) catalogs with a `for_husband`/`subject` discriminator on the pivot rather than separate tables.
- Infertility-sheet CSV columns (`infertilitysheet.medicalhistory` etc.) -> proper many-to-many pivots (e.g. `infertility_sheet_disease`, with `subject` = wife|husband). Migration script must split comma lists and map ids.
- `PresentHistoryQuestion` hasMany `PresentHistoryAnswer` (displayorder, favorite).
- Drop/ignore: `phobstericterplace.patientid` stray column; soft-deleted junk lookup rows ('aaaa','777', blanks); dedupe repeated catalog titles (DM x4 in familyhistory). Handle fluid-schema drift columns (`phpastart.ovdate`, `freezdate`, `phpasticsi.notes`) — check production DB for these columns before writing the migration; they exist in code but not in the 2024 dump.
- Data fixes during ETL: cast varchar patientid/lookup-id columns to ints; parse varchar dates (`sysdate`, `obsdate`) to DATE; skip fully-empty rows created by the insert-then-edit pattern; note the 2024 dump ships these lookups EMPTY (live data resembles the 2021 dump — extract vocabularies from production, not from the 2024 dump).
- Security replacements: kill generic client-driven `update/delRows/delRowsommedel` endpoints (arbitrary table/column write, one with zero auth); use per-resource REST endpoints + FormRequest validation + policies; replace `exec()` Excel export with queued Laravel Excel export; eliminate string-interpolated `id IN (0$list)` queries.
- Performance: replace per-row `awusers` lookups with eager loading (`with('recordedBy')`).

---


# Module: Follow-up & Follow-up Cards (followup)

## Purpose
Pregnancy follow-up management for an OB/GYN clinic. Three sub-features:
1. **Follow-up sheet** (`followup.php`): one active pregnancy follow-up record per patient (LMP, EDD, IVF date, G/P), with dated child rows for visits (wt/BP/GA/US), diagnoses, examinations, investigations, plus in-screen prescriptions (`followupdrugs`, runtime-created table). Closed pregnancies are archived (`status = 1`) and browsable.
2. **Follow-up cards** (`followupcard.php`): printable per-encounter card (diagnosis + drug list) tied to the latest reception visit; confirming/printing a card pushes a prescription into the pharmacy module (`recepittmp` + `receiptdrugs`).
3. **Instructions** (`instruction.php`): a library of printable patient instruction texts, plus (oddly co-located) hospital transfer/referral letter generation.

## Controllers & Views (file paths)
Controllers:
- `/home/amrtechogate/public_html/obgy/core/controllers/followup.php` (657 lines)
- `/home/amrtechogate/public_html/obgy/core/controllers/followupcard.php` (408 lines)
- `/home/amrtechogate/public_html/obgy/core/controllers/instruction.php` (672 lines)

Views (Smarty):
- `/home/amrtechogate/public_html/obgy/core/views/obgy/followup/` : `add.html`, `archive.html`, `pregdetail.html`, `newrow.html`, `newrowUS.html`, `newrowdrugedit.html`, `showPrescriptions.html`
- `/home/amrtechogate/public_html/obgy/core/views/obgy/followupcard/` : `show.html`, `add.html`, `showdata.html`, `drawRow.html`, `print.html`
- `/home/amrtechogate/public_html/obgy/core/views/obgy/instruction/` : `add.html`, `show.html`, `edit.html`, `print.html`, `transfer.html`, `printtransfer.html`, `editmodel.html`, `editmodelinst.html`
- Prescription print reuses `/home/amrtechogate/public_html/obgy/core/views/obgy/gyna/print.html`

ORM: RedBeanPHP (`R::`), fluid mode (tables/columns can be created at runtime).

## Tables
Schema source: `/home/amrtechogate/public_html/obgy/_db/obgy_12-7-2024.sql`. No declared FKs anywhere; all FKs below are inferred from controller code. **None of the 8 assigned tables has any seeded INSERT rows in the dump** (all transactional/user-maintained; `instruction` is empty in this dump).

### followup (dump line 6986)
Master pregnancy follow-up record. One open record (`status=0`) per patient; auto-created on first screen entry.
| Column | Type | Notes |
|---|---|---|
| id | int(11) PK AI | |
| patientid | int(11) | FK -> patients.id |
| history | text | free text |
| lmp | date | last menstrual period; defaults to today on auto-create |
| eed | date | EDD, computed = LMP + 9 months + 7 days (strtotime) |
| ivf | date | computed = LMP + 9 months - 7 days |
| g | varchar(100) | gravida (free text) |
| p | varchar(100) | para (free text) |
| status | int(11) default 0 | comment: "1 end, 0 not yet" (archive flag) |

### followupcard (dump line 7005) — CHARSET latin1
Printable encounter card.
| Column | Type | Notes |
|---|---|---|
| id | int(11) PK AI | |
| enterdate | date NULL | copied from latest `visits.visitdate` |
| exitdate | date NULL | set to today at creation |
| date | date NULL | card date |
| patientid | int(11) | FK -> patients.id |
| doctorid | int(11) | FK -> awusers.user_id (from `$_SESSION['user_id']`) |
| diagnosis | text | free text |
| status | int(11) | 0 = draft, 1 = confirmed/printed |
| deleted | int(11) default 0 | soft delete |
| deptid | int(11) NULL | FK -> detections.id (copied from `visits.detectionid`) |

### followupcarddrugs (dump line 7025) — CHARSET latin1
Drug lines of a card.
| Column | Type | Notes |
|---|---|---|
| id | int(11) PK AI | |
| cardid | int(11) | FK -> followupcard.id |
| drugid | int(11) | FK -> drugs.id |
| drugtype | varchar(250) | free text from distinct drugs.drugtype |
| drugdos | varchar(250) | dose, free text |
| recepittmpid | int(11) default 0 | FK -> recepittmp.id (pharmacy receipt created at confirm) |

### followupdiagnosis (dump line 7041)
| Column | Type | Notes |
|---|---|---|
| id | int(11) PK AI | |
| followid | int(11) | FK -> followup.id |
| date | date | |
| diagnosis | text | free text |
| del | int(11) default 0 | soft delete |

### followupexam (dump line 7056)
Identical shape to followupdiagnosis with `exam` text column. FK `followid` -> followup.id; `del` soft delete.

### followupinvest (dump line 7071)
Identical shape with `invest` text column (free-text investigation requests/results — NOT linked to the investigations catalog). FK `followid` -> followup.id; `del` soft delete.

### followupvisit (dump line 7086)
Antenatal visit row.
| Column | Type | Notes |
|---|---|---|
| id | int(11) PK AI | |
| followid | int(11) | FK -> followup.id |
| date | date | |
| wt | varchar(50) | weight, free text |
| bp | varchar(50) | blood pressure, free text |
| ga | varchar(100) | gestational age (also recomputed in code: round(days(LMP..date)/7) weeks) |
| us | varchar(100) | ultrasound summary |
| notes | text | |
| del | int(11) default 0 | soft delete |

### instruction (dump line 8188)
Patient instruction template library.
| Column | Type | Notes |
|---|---|---|
| id | int(11) PK AI | |
| patientid | int(11) | nominally FK -> patients.id, but **hardcoded to 1** in addit() and updateit() — effectively a global library |
| name | varchar(255) | title |
| content | text | HTML-formatted instruction body |
| tempdelete | char(1) default '0' | soft delete |

No seeded values in the dump (0 INSERT statements).

### Ghost table: followupdrugs (NOT in dump)
Heavily used by `followup.php` (getprescription, showprescription, printpre, addprescription) but **absent from the SQL dump** — created at runtime by RedBeanPHP fluid mode. Columns inferred from code: `id`, `patientid` (FK->patients), `followid` (FK->followup), `drugid` (FK->drugs), `date`, `forhusband` (0=wife/1=husband), `deleted`, plus drug type/dose fields. Must be captured from a live production dump before migration.

## Relationships (explicit list)
- followup.patientid -> patients.id
- followupvisit.followid -> followup.id
- followupdiagnosis.followid -> followup.id
- followupexam.followid -> followup.id
- followupinvest.followid -> followup.id
- followupcard.patientid -> patients.id
- followupcard.doctorid -> awusers.user_id
- followupcard.deptid -> detections.id (value copied from visits.detectionid)
- followupcarddrugs.cardid -> followupcard.id
- followupcarddrugs.drugid -> drugs.id
- followupcarddrugs.recepittmpid -> recepittmp.id (pharmacy)
- instruction.patientid -> patients.id (dead column, always 1)
- followupdrugs.{patientid,followid,drugid} -> patients.id / followup.id / drugs.id (runtime table, inferred)
- Cross-module reads in instruction.php transfer letters: mainantenental, mainantenentalus, phobstetric, phpastmedical(+phpastmedicaldisease), phpastsurgical(+phpastsurgicaloperation), phpastgynecological(+phpastgynecologicaloperation), hospitalnames, operationinstructions, bloodtypes, wifetypes
- Print flows read: programesetting, wifetypes, husbandtypes, patients

## Business Workflows (traced from code)
1. **Open follow-up sheet** (`followup.php::index`): requires `?patientid`. Finds open `followup` (status=0) or auto-creates one (lmp=today, eed=lmp+9mo+7d, ivf=lmp+9mo-7d) plus an initial empty `followupvisit`. Renders 4 date-desc lists (diagnosis/exam/invest/visits); GA per visit computed as weeks since LMP; flags visits dated before LMP (`dateFlag`).
2. **Row management via AJAX**: `newdiag/newexam/newinv/newvisit` insert empty dated rows and return an HTML fragment (`newrow.html`). `update` is a generic cell editor: POST `id, tableName, colName, value, dated` -> `R::load($tablename)->$colname = $value`. Editing `lmp` recomputes and echoes eed+ivf. `del` sets `del=1` on any POSTed table.
3. **In-sheet prescriptions**: today's `followupdrugs` rows per patient+followid+forhusband shown alongside drug catalog dropdowns (distinct drugcat/drugtype/drugdos from `drugs`). `showprescription` lists historic prescription dates split wife/husband; `printpre` prints one date via `gyna/print.html` using patient name + wifetypes/husbandtypes title; `addprescription` soft-deletes a drug line.
4. **Archive**: `archive` lists `followup` rows with status=1 for the patient; `pregdetail?pregno=` re-renders a full read view of a past pregnancy.
5. **Follow-up card creation** (`followupcard.php::addit` without cardid): loads or creates draft card (status=0): enterdate = latest `visits.visitdate`, exitdate/date = today, doctorid = session user, deptid = `visits.detectionid`. Drug rows added client-side (`drawRow`).
6. **Card confirm + pharmacy push** (`printit`): if status==0: sets status=1, creates `recepittmp` (doctor_id, patient_id, rect_date, drugstablename='followupcarddrugs', status=0), then per drug row inserts `followupcarddrugs` (with recepittmpid) and `receiptdrugs` (drugs_id, amount=1, receipt_id) so pharmacy can price/dispense. Optionally prints `followupcard/print.html` with department title from `detections`. `yesOnly` mode reprints an existing card without pharmacy push.
7. **Card list** (`index`): confirmed (status=1, deleted=0) cards per patient with doctor name from `awusers` and concatenated drug names. Generic `update` endpoint also used for soft delete (note: line 270 uses `=` instead of `==` in the if condition — always true).
8. **Instruction library** (`instruction.php`): index/add/show/edit/updateit CRUD on `instruction` (patientid forced to 1); `del` soft-deletes via string-concatenated raw SQL (injection risk). `getprint` accepts a JSON array of instruction ids and prints them with patient name/title; `getinstdetail` prints a single one (auth checks commented out).
9. **Hospital transfer letter** (`instruction.php::showtransfer/printtransfer`): aggregates current pregnancy from `mainantenental` (NOT from `followup`!), computes G/P from `phobstetric` + patients counters (pno/ab/ectopic/vmodel), last placenta from `mainantenentalus`, past medical/surgical/gyna history from `ph*` tables, blood type, hospital from `hospitalnames`, operation instructions from `operationinstructions`; prints `printtransfer.html`. Side CRUD for `hospitalnames` and `operationinstructions` lives here too.

## ERP Migration Notes
Proposed Laravel models:
- **Pregnancy** (merge `followup` with `mainantenental` — same concept duplicated across modules (استنتاج/inference, field overlap: patientid, lmp, eed, g, p, status/done)): patient_id FK, lmp, edd (computed, Naegele rule LMP+280d instead of legacy strtotime "+9 months +7 days"), ivf_date, gravida/para int, status enum(active, closed).
- **AntenatalVisit** (from `followupvisit`): pregnancy_id FK, visit_date, weight decimal, bp_systolic/bp_diastolic, ga computed accessor, ultrasound_notes, notes. Add CHECK/validation visit_date >= lmp (legacy only flags it in UI).
- **ClinicalNote** — merge `followupdiagnosis` + `followupexam` + `followupinvest` into one table with `type` enum(diagnosis, examination, investigation); their schemas are 100% identical (followid, date, text, del). Optionally link investigations to the investigations catalog instead of free text.
- **Encounter/VisitCard** (from `followupcard`): visit_id FK (proper link instead of "latest visit" copy), patient_id, doctor_id (users), department_id (detections->departments), diagnosis, status enum(draft, confirmed), SoftDeletes.
- **Prescription + PrescriptionItem** — unify `followupcarddrugs`, runtime `followupdrugs`, and the other per-module drug tables into one prescription source of truth (prescribable polymorphic or encounter_id), items FK to drugs. Replace direct `recepittmp`/`receiptdrugs` inserts with a domain event (PrescriptionConfirmed) consumed by the pharmacy module.
- **InstructionTemplate** (from `instruction`): drop dead `patientid` column; keep name/content; SoftDeletes. Add **Referral** model + ReferralLetterService for the transfer-letter flow (currently print-only, no persistence) with hospital_id FK (`hospitalnames` -> Hospital lookup) and selected operation instructions.

Critical remediation during migration:
- **Capture live schema first**: `followupdrugs` (and possibly other RedBean fluid tables) is missing from the reference dump.
- Kill the generic `update/del` endpoints (client-supplied tableName/colName = mass-assignment over the whole DB) -> per-resource endpoints with FormRequest validation + Policies.
- Fix SQL injection in `instruction::del` (string-concatenated UPDATE) and re-enable the commented-out auth checks (`getinstdetail`, `addopinst`, `editopinst`, `update`).
- Fix latin1 charset on `followupcard`/`followupcarddrugs` (Arabic diagnosis text corruption risk) -> utf8mb4 with data verification.
- Normalize soft-delete flags (`del`, `deleted`, `tempdelete`, `status`) to Laravel `deleted_at` + explicit status enums.
- Convert free-text vitals (wt, bp, ga varchar) to typed numeric columns; migrate with parsing + manual review of non-numeric values.
- Note assignment-instead-of-comparison bug at followupcard.php:270 (`if($tablename = 'followupcard' && ...)`) — do not replicate behavior, it always echoes 1.

---


# Module: Investigations & Lab Orders (investigations)

## Purpose
Manages the lab-test catalog (categories + test items), per-sheet/per-visit lab ordering and result entry for the OB/GYN clinic (antenatal, gyna, infertility sheets), a dynamic user-defined "other investigations" grid (EAV pattern), and a batch Excel export of full patient records. Also (misleadingly) hosts the application's license-serial table (`excelinfo`).

No declared foreign keys anywhere; all relationships below are inferred from column names and controller queries (RedBeanPHP `R::` calls).

## Controllers & Views (file paths)
- `/home/amrtechogate/public_html/obgy/core/controllers/investigation.php` — per-patient "Investigations" screen (controller class, actions: `index`, `addRow`, `delData`, `update`, `otherinvests`, `otherinvestsAdd`, `otherinvestsRow`, `delotherinvestsRow`, `delotherinvest`, plus a maintenance `onesetup`).
- `/home/amrtechogate/public_html/obgy/core/controllers/addinvestigation.php` — catalog admin (actions: `index`, `addnewcat`, `addnewinv`, `update`, `del`, `getinvbycatid`, `addtofavorite`).
- `/home/amrtechogate/public_html/obgy/core/controllers/excel.php` — NOT a controller class: a procedural script (`startexcel()`/`createexcel()`) run directly; consumes `excelinfopatients`, builds an HTML table of the full patient record, converts to `.xlsx` via PHPExcel, saves to `../excel_backups/{statusno}/{Y-m-d}.xlsx`, then TRUNCATEs the queue. No auth check.
- Views: `/home/amrtechogate/public_html/obgy/core/views/obgy/investigation/` (investigation.html, investigation1.html, newrow.html, otherinvests.html) and `/home/amrtechogate/public_html/obgy/core/views/obgy/addinvestigation/` (show.html, invests.html).
- Cross-module consumers: `ancsheet.php` (~line 905 writes `ancsheetinvest`), `gynasheet.php` (~635 writes `gynasheetinvest`), `infertilitysheet.php` (~698 writes `infertilitysheetinvest`), `ancsheet00.php` / `antenalvisit.php` (read catalog grouped by `displayorder` ranges; read legacy `investigations`), `completereport.php` / `Completesreport.php` (reports; load `invests` names by `investid`), `patienthistory.php` + `completereport.php` (`checkpatientlist()` enqueues into `excelinfopatients`), `login.php` (reads/writes `excelinfo` serial), `setup.php` (protects `excelinfo` from deletion).
- SQL dump: `/home/amrtechogate/public_html/obgy/_db/obgy_12-7-2024.sql`.

## Tables

### investcats (lookup)
Purpose: categories of lab tests; `displayorder` drives hardcoded column grouping in ordering UIs.
Columns:
- `id` int(11) PK AI
- `name` varchar(150) NULL
- `deleted` int(11) NOT NULL DEFAULT 0 (soft delete)
- `displayorder` int(11) NOT NULL DEFAULT 0 (-1 = hidden "Old Favorite"; 0 = "others" bucket)

Seeded values (id, name, deleted, displayorder): (1,'3- Chemistry',0,2), (2,'10- Urine & Stool',0,7), (3,'1- Hematology',0,1), (4,'7- virology',0,5), (5,'4- Hormonal profile',0,3), (6,'2- coagulation profile',0,8), (7,'9- auto ab (Immunology)',0,4), (8,'5- tumor markers',0,6), (9,'12- Genetics',0,9), (10,'pre operative / ICSI',0,0), (11,'11- semen - Male',0,10), (12,'Others',0,0), (13,'Old Favorite',0,-1), (14,'Radiological',0,11), (15,'',1,12), (16,'aNC (booking)',0,13), (17,'',1,14), (18,'',1,15), (19,'ANC (27 w)',0,16), (20,'6- aneuploidy screening',0,17), (21,'8- bacteriology',0,18), (22,'13- pathology',1,19).

### invests (lookup — the central test catalog)
Purpose: individual lab test items; referenced by every `*sheetinvest` order table via `investid`.
Columns:
- `id` int(11) PK AI
- `investcatid` int(11) NOT NULL -> investcats.id
- `name` varchar(150) NULL
- `favorite` int(11) NOT NULL DEFAULT 0
- `deleted` int(11) NOT NULL DEFAULT 0

Seeded: 276 rows (IDs 1–276). Representative clinical vocabulary: Hematology (ABO group, Rh factor, CBC, Hb%, RBCs indices, platelets, WBCs, reticulocytic count, indirect coombs, Serum Ferritin, CRP, ESR, Hct, hb electrophoresis); Chemistry (SGPT, SGOT, Bilirubin, Albumin, alk ph, bile acids, Creatinine, urea, uric acid, Na, K, Ca, lipid profile, FBS, RBS, OGTT 75gm, HbA1C, FG/FI ratio); Hormonal (FSH, LH, E2, Progesterone, AMH, FT3, FT4, TSH, PRL, cortisol, testosterone, DHEA-S, 17-OHP, HCG qual/quant, PTH); Immunology (Lupus anticoagulant, Anticardiolipin IgG/IgM, Anti B2GP, ANA, Anti dsDNA, RF, anti-CCP, anti-thyroid TSI/TPO, C3, C4); Virology (TORCH IgM/IgG, HBs Ag/Ab, HCV Ab, HIV Ab); Coagulation (BT-CT, APTT, PT INR, VWF, F VIII/IX, fibrinogen, FDPs, d-dimer, thrombophilia panel); Tumor markers (CA125, CEA, CA15-3, CA19-9, AFP, LDH); Urine/Stool; Genetics (karyotyping, amniocentesis, CVS, FMR-1, FVL, prothrombin gene, AZF, RT-PCR); Semen (conventional analysis, CASA DNA fragmentation, SDF); Radiological (scrotal US, mammography, breast US, pelvic MRI, brain MRI sella); aneuploidy screening (b-HCG, PAPP-A, AFP, UE3, inhibin A); bacteriology (Widal, Brucella, ASOT, VDRL, RPR, vag discharge C/S); pre-operative/ICSI panel; ANC booking and ANC 27w panels. Dirty data present: empty names, test rows ('vvv','new33','gg edit','res3'), rows with `investcatid = 0` (orphans).

### investigations (legacy, empty)
Purpose: legacy fixed-column antenatal booking lab results — one column pair per analyte: `invesd*` = date, `invest*` = result. AUTO_INCREMENT=1 (no data). Read-only usage found in `ancsheet00.php` (`'ancsheetid = ?'`) and `Completesreport.php` (queried both with `ancsheet->id` and `ivfsheet->id` — sloppy ID reuse). No write path found in current code.
Columns:
- `id` int(11) PK AI
- `ancsheetid` int(11) unsigned NULL -> antenatal sheet id (inferred; mainantenental/ancsheet)
- `invesdhb`, `invesdplat`, `invesdfbs`, `invesdogtt`, `invesdhbs`, `invesdhcv`, `invesdcua`, `invesdferr`, `invesdogtt2` varchar(191) NULL (dates per analyte)
- `investhb`, `investplat`, `investfbs`, `investhbs`, `investhcv`, `investcua`, `investferr` varchar(191) NULL (results per analyte)

### otherinvestigations
Purpose: user-defined custom investigation definitions (the "columns" of the dynamic grid). No seeded data.
Columns:
- `id` int(11) PK AI
- `name` varchar(255) NULL
- `deleted` int(11) DEFAULT 0
Note: charset latin1 (vs utf8mb4 elsewhere) — Arabic text risk.

### otherinvestigationsrows
Purpose: EAV header — one row per patient per date of custom investigations. No seeded data.
Columns:
- `id` int(11) PK AI
- `date` date NULL
- `patientid` int(11) NULL -> patients.id
- `doctorid` int(11) NULL -> awusers.user_id
- `deleted` int(11) DEFAULT 0

### otherinvestigationsvalues
Purpose: EAV values — one record per (row x definition); auto-created empty for every active definition when a row is added (`otherinvestsRow()` in a transaction).
Columns:
- `id` int(11) PK AI
- `investrowid` int(11) NULL -> otherinvestigationsrows.id
- `investid` int(11) NOT NULL -> otherinvestigations.id
- `value` varchar(255) NULL (free text, no units/ranges/types)
- `date` date NULL

### excelinfo (misnamed — license store)
Purpose: stores the application activation serial (SHA-512 hash tied to server MAC + salt) and activation time. Checked in `login.php` (`index()` creates the row if missing; `activate()` validates POSTed serial against `serialcheck()`). Listed among protected tables in `setup.php`. Nothing to do with Excel.
Columns:
- `id` int(11) PK AI
- `time` datetime NULL
- `serial` text NULL

Seeded: 1 row — (1, '2020-07-21 03:29:34', 'd2dc0d4a...995267' [128-char hex serial]).

### excelinfopatients
Purpose: work queue of patients whose full record should be exported to Excel. Enqueued by `checkpatientlist()` in `patienthistory.php` and `completereport.php` (dedup by patientid); consumed and fully TRUNCATEd by `excel.php`. No seeded data.
Columns:
- `id` int(11) PK AI
- `patientid` int(11) NOT NULL -> patients.id
- `statusno` int(11) NOT NULL (copied from patients.statusno; used as export subfolder `excel_backups/{statusno}/`)

## Relationships (explicit list)
- invests.investcatid -> investcats.id
- ancsheetinvest.investid -> invests.id; ancsheetinvest.patientid -> patients.id; ancsheetinvest.ancsheetid -> mainantenental.id (inferred, "main Antenatal Id" comment in ancsheet.php); ancsheetinvest.doctorid -> awusers.user_id
- gynasheetinvest.investid -> invests.id; gynasheetinvest.patientid -> patients.id; gynasheetinvest.doctorid -> awusers.user_id
- infertilitysheetinvest.investid -> invests.id; same patientid/doctorid pattern
- investigations.ancsheetid -> antenatal sheet id (inferred; also queried with ivfsheet ids in Completesreport.php — data-integrity smell)
- otherinvestigationsrows.patientid -> patients.id; otherinvestigationsrows.doctorid -> awusers.user_id
- otherinvestigationsvalues.investrowid -> otherinvestigationsrows.id; otherinvestigationsvalues.investid -> otherinvestigations.id
- excelinfopatients.patientid -> patients.id; excelinfopatients.statusno copied from patients.statusno (denormalized)
- excelinfo: standalone singleton (license)
- Related sibling tables following the same catalog pattern (other modules): followupinvest, gynainvestigation, infertilityinvest, mainantenentalinvest, mointoringsheetinvestigation — all carry `investid` pointing at invests.id (inferred from structure).

## Business Workflows (traced from code)
1. Catalog administration (`addinvestigation.php`): admin lists categories (`investcats` where deleted=0 and displayorder != -1), adds a category (`addnewcat` — next displayorder), adds a test under a category (`addnewinv`), inline-edits any cell via `update(tableName, colName, value)` (AJAX), soft-deletes (`del` sets deleted=1), toggles favorites (`addtofavorite`), fetches tests per category (`getinvbycatid`).
2. Ordering from clinical sheets (`ancsheet.php`/`gynasheet.php`/`infertilitysheet.php`): UI shows categories grouped by hardcoded `displayorder` ranges (1–2 | 3 | 4–5 | 6–11 | >11 | 0=others | favorites list), duplicated across `ancsheet00.php` and `antenalvisit.php`. Doctor checks tests, picks date and wife/husband; controller dispenses one `*sheetinvest` bean per checked `investid` (patientid, sheetid, date, doctorid, forhusband), stores all, then renders a printable order using patient/husband name and title from `wifetypes`/`husbandtypes`.
3. Result entry & display: `investresult` updated per order row; sheets and `completereport.php` group orders by DISTINCT date, split wife (forhusband=0) vs husband (forhusband=1), and resolve test names via `R::load('invests', investid)`.
4. Patient investigations screen (`investigation.php?patientid=`): for each of semen, hsg, ustv, laparoscopy, hysteroscopy, mrict, pathology (infertility-module tables) it auto-creates an empty row dated today if none exists, then lists all rows with doctor names; hormon listed without auto-create. `addRow`/`delData`/`update` are generic AJAX endpoints taking tableName/colName from the client. Template switches between investigation.html / investigation1.html based on `programesetting.investshow`.
5. Dynamic other-investigations: `otherinvestsAdd` creates a new definition; `otherinvestsRow` creates a header row + one empty `otherinvestigationsvalues` per active definition (transaction); index page computes per-row column widths (presentation logic in controller); values edited inline; row/definition soft-deleted.
6. Excel backup pipeline: opening a patient in `patienthistory.php`/`completereport.php` enqueues into `excelinfopatients`; `excel.php` (run directly, presumably via cron — inferred) reads the queue, TRUNCATEs it, and per patient builds a giant HTML table (menstrual/contraception/obstetric/medical/surgical/gyn/ART/family history, examination, semen/HSG/US/laparoscopy/hysteroscopy/MRI/hormones/pathology, gyna visits/folliculometry/investigations/treatment, antenatal visits) -> PHPExcel_Reader_HTML -> styled XLSX (hardcoded row coordinates) -> `excel_backups/{statusno}/{Y-m-d}.xlsx` (same-day exports overwrite).
7. License gate: `login.php` ensures one `excelinfo` row exists; `activate()` compares POSTed serial to `serialcheck()` (sha512 of MAC + salt) and persists it. The enforcement branch is currently commented out.

## ERP Migration Notes
Proposed Laravel models:
- `LabTestCategory` (from `investcats`): name, sort_order, is_active. Drop the -1/0 magic displayorder semantics; make grouping configurable.
- `LabTest` (from `invests`): category_id FK, name, code (new), unit (new), reference_range (new), result_type (new), is_favorite, is_active. Cleanse seeds: remove empty names, test junk ('vvv','new33', etc.), reassign `investcatid = 0` orphans.
- `LabOrder` + `LabOrderItem`: unify `ancsheetinvest`, `gynasheetinvest`, `infertilitysheetinvest` (and siblings `mainantenentalinvest`, `followupinvest`, `gynainvestigation`, `infertilityinvest`, `mointoringsheetinvestigation`) into one polymorphic structure: LabOrder{patient_id, doctor_id, orderable_type/orderable_id (sheet/visit), ordered_at, subject (wife|husband)}; LabOrderItem{lab_order_id, lab_test_id, result, result_at}. Keep an old-id mapping table for historical reports.
- `CustomInvestigation` + `CustomInvestigationResult` (from otherinvestigations/rows/values) — or better, fold user-defined tests into `LabTest` with a `is_custom` flag and store results as LabOrderItems, eliminating the parallel EAV system. Convert latin1 data carefully.
- DROP: `investigations` (legacy fixed-column, empty in dump — verify empty in production first), `excelinfo` (desktop licensing — replace with ERP subscription/tenancy), `excelinfopatients` (replace with Laravel queued jobs + export log table; use Laravel Excel/PhpSpreadsheet or PDF reports instead of PHPExcel HTML hack).
Security fixes required during migration: remove client-supplied tableName/colName endpoints (arbitrary-table write); parameterize all queries (doctorid is string-concatenated into SQL in several controllers); add auth to export jobs; stop auto-creating empty placeholder rows (creates phantom records in 7 tables per first page view).

---


# Module: Pharmacy & Drugs (pharmacy)

## Purpose
Standalone sub-app (`/pharmacy/`) of the legacy OB/GYN system (PHP + Smarty + RedBeanPHP `R::` ORM, "aw framework"). Manages:
1. The unified drug catalog (~2,000 seeded rows) with category, brand name, dosage form, and Arabic dosage instruction text.
2. Pharmacy inventory as a ledger (`pharmacystore`) of before/after balances per operation.
3. Purchase invoices from suppliers (`importbill` / `importdetails`).
4. Dispensing of prescriptions ("receipts") that are auto-generated by clinical sheet controllers in the core app (`recepittmp` / `receiptdrugs`).

## Controllers & Views (file paths)
Pharmacy sub-app controllers (`/home/amrtechogate/public_html/obgy/pharmacy/controllers/`):
- `drugs.php` — drug catalog CRUD; DataTables server-side listing (`show`), per-field autocomplete (`getthisserach`), `adddrug`, `updatedrug`, `deletedrug`. View: `medicineview/drugs.html`.
- `buys.php` — purchase invoices ("Import"): `index` (add form), `addnew` (create bill + details + stock movements, transactional), `show` (list bills, joins `awusers` for receiver name), `invoice` (printable bill), `edit`/`update` (reverse-and-reapply stock logic), `del` (delete bill + correcting movements), `delup` (delete single bill line). Views: `buys/add.html`, `buys/addselect.html`, `buys/show.html`, `buys/invoice.html`, `buys/edit.html`.
- `receipt.php` — pending prescription queue: `index`/`show` (identical: list `recepittmp` with doctor name from `awusers` and patient name from `patients.wifename`), `showdetails` (line items + low-stock flag `datachecked`), `updatereceipt` (dispense: write `pharmacystore` optype=1, decrement `drugs.currentbalance`, set `recepittmp.status`), `update` (DANGEROUS generic write: loads arbitrary table/column/value from POST). Views: `receipt/show.html`, `receipt/showdetails.html`.
- `store.php` — `show`: full stock-movement report from `pharmacystore` joined (in PHP loop) to `drugs.drugname`. View: `store/show.html`.
- `stuff.php` — manages `councilstaff` (head-of-council assignment, not a pharmacy table) AND contains verbatim copies of `buys.php`'s `update` (named `updategg`), `del`, `delup`. Views: `stuff.html`, `newdatashow*.html`.

Core controllers that feed this module (`/home/amrtechogate/public_html/obgy/core/controllers/`):
- `followupcard.php` — on saving a follow-up card prescription: dispenses `recepittmp` (doctor_id = session user, patient_id, rect_date = today, drugstablename = 'followupcarddrugs', status = 0), then per drug writes `followupcarddrugs` (with `recepittmpid` back-ref) and `receiptdrugs` (drugs_id, amount = 1, receipt_id).
- `operativedetails.php` — helpers `addrecepittmp($patientid)` (drugstablename = 'operativedetailsdrugs') and `addrecepitdrug($recepittmpId, $drugid)` (amount = 1); inline-edit of `drugid` creates/updates receipt rows, reusing an open (status = 0) receipt if present.
- `gyna.php` — prescription row creation for many sheet tables (`mainantenentaldrugs`, `followupdrugs`, `ancsheetdrugs`, `recorddrugs`, `infertilitydrugs`, `mointoringsheetdrugs`, `infertilitysheetdrugs`, `gynasheetdrugs`); initializes `recepittmpid = 0`, `recepitdrugid = 0` and pulls drug pick-lists via `SELECT DISTINCT drugcat/drugtype/drugdos FROM drugs`.

## Tables

### drugs
Master drug catalog + running stock balance. 6 INSERT batches in dump; IDs range ~3..2047 (AUTO_INCREMENT=2048), i.e. ~2,000 seeded rows.
```sql
id             int(11) PK AUTO_INCREMENT
drugcat        varchar(255) NULL      -- therapeutic category, free text (e.g. 'Paracetamol', 'Ovulation induction', 'Menopause', 'Anti-inflammatory', 'Antacid', 'Anti-viruis\r\n' [sic])
drugname       varchar(230) NOT NULL  -- brand name (e.g. 'Abimol', 'Acyclovir', 'Activelle')
drugtype       varchar(100) NOT NULL  -- dosage form, free text: Tab, Syrup, Sachets, Susp, Lotion, Amp, Cream...
drugdos        varchar(255) NULL      -- Arabic dosage instruction text (e.g. "قرص بعد الأكل عند اللزوم")
initialbalance int(11) DEFAULT 0
currentbalance int(11) DEFAULT 0      -- denormalized running stock, updated by controllers
```
Inferred FKs pointing here: `importdetails.drug_id`, `importtdetails.drug_id`, `pharmacystore.drug_id`, `receiptdrugs.drugs_id`, clinical `*drugs.drugid` (note: those are varchar(150)).

### drugdos
DEAD lookup table. Zero rows (AUTO_INCREMENT=1), zero code references. `name` is mistyped as int. Real dosage text lives in `drugs.drugdos`.
```sql
id      int(11) unsigned PK AUTO_INCREMENT
name    int(11) unsigned NULL   -- mistyped; should have been varchar
deleted int(11) unsigned NULL
```

### pharmacystore
Stock-movement ledger. One row per balance change.
```sql
id           int(11) PK AUTO_INCREMENT
drug_id      int(11) NOT NULL        -- -> drugs.id
amountbefore int(11) DEFAULT 0
amountafter  int(11) DEFAULT 0
amountvary   int(11) NOT NULL        -- delta
opdate       datetime NOT NULL
optype       tinyint(1) NOT NULL     -- 0 = purchase/import, 1 = dispense/export, 2 = bill edit correction, 3 = bill/line delete correction (code comments are wrong/misleading)
```
No seeded data. Note: `buys.php update()` queries `pharmacystore` by `bill_id` (`'drug_id = ? AND bill_id = ?'`) but the table has NO `bill_id` column — latent bug (RedBean would error or the row lookup silently fails).

### receiptdrugs
Prescription (receipt) line items.
```sql
id         int(11) PK AUTO_INCREMENT
receipt_id int(11) NOT NULL   -- -> recepittmp.id
drugs_id   int(11) NOT NULL   -- -> drugs.id
amount     int(11) NOT NULL   -- always 1 when auto-generated from clinical sheets
```

### recepittmp
Prescription/receipt header awaiting dispensing ("tmp" is a misnomer; it is the permanent receipt header). Name itself is a typo of "receipttmp".
```sql
id             int(11) PK AUTO_INCREMENT
doctor_id      int(11) NOT NULL       -- -> awusers.user_id
patient_id     int(11) NOT NULL       -- -> patients.id
rect_date      date NOT NULL
drugstablename varchar(250) NULL      -- polymorphic source: 'followupcarddrugs', 'operativedetailsdrugs', ...
status         int(11) unsigned NULL  -- 0 = pending, 1 = dispensed
```

### importbill
Purchase invoice header.
```sql
id          int(11) PK AUTO_INCREMENT
invoicenum  varchar(50) NOT NULL
invoicedate date NOT NULL
suppliers   varchar(100) NOT NULL  -- free-text supplier name; no suppliers table exists
receiveuser int(11) NOT NULL       -- -> awusers.user_id (receiving employee, from $_SESSION['user_id'])
```

### importdetails
Purchase invoice line items. Only table in the module with an FK-style index (`index_foreignkey_importdetails_importbill` on `bill_id`).
```sql
id      int(11) PK AUTO_INCREMENT
bill_id int(11) NOT NULL   -- -> importbill.id
drug_id int(11) NOT NULL   -- -> drugs.id
amount  int(10) NOT NULL
```

### importtdetails
DEAD duplicate of `importdetails` (utf8mb4, unsigned variant — looks like an aborted recreate). Zero rows, zero code references anywhere in the PHP codebase.
```sql
id      int(11) unsigned PK AUTO_INCREMENT
bill_id int(11) unsigned NULL
drug_id int(11) unsigned NULL
amount  int(11) unsigned NULL
```

## Relationships (no declared FKs anywhere; all inferred from controller SQL)
- `importdetails.bill_id` -> `importbill.id`
- `importdetails.drug_id` -> `drugs.id`
- `importtdetails.bill_id` -> `importbill.id` (dead table, by analogy)
- `importtdetails.drug_id` -> `drugs.id` (dead table, by analogy)
- `pharmacystore.drug_id` -> `drugs.id`
- `receiptdrugs.receipt_id` -> `recepittmp.id`
- `receiptdrugs.drugs_id` -> `drugs.id`
- `recepittmp.doctor_id` -> `awusers.user_id`
- `recepittmp.patient_id` -> `patients.id`
- `importbill.receiveuser` -> `awusers.user_id`
- `recepittmp.drugstablename` -> table NAME string (polymorphic): `followupcarddrugs`, `operativedetailsdrugs`, ...
- Clinical prescription tables (`ancsheetdrugs`, `gynadrugs`, `gynasheetdrugs`, `followupcarddrugs`, `followupdrugs`, `mainantenentaldrugs`, `infertilitydrugs`, `infertilitysheetdrugs`, `mointoringsheetdrugs`, `operativedetailsdrugs`, `recorddrugs`):
  - `.drugid` (varchar!) -> `drugs.id`
  - `.recepittmpid` -> `recepittmp.id` (0 = not yet linked)
  - `.recepitdrugid` -> `receiptdrugs.id` (0 = not yet linked)
  - plus `.patientid` -> `patients.id`, `.doctorid` -> `awusers.user_id`, and free-text copies of `drugtype`/`drugdos`/`drugname`

## Business Workflows (traced from code)
1. **Catalog maintenance** (`drugs.php`): add drug sets `currentbalance = initialbalance`. Listing via DataTables server-side protocol; autocomplete per field via `LIKE 'term%'` queries.
2. **Purchasing** (`buys.php addnew`, transactional `R::begin/commit/rollback`): create `importbill`; per line create `importdetails`, insert `pharmacystore` row (optype=0, before = current, after = before + amount), set `drugs.currentbalance = amountafter`.
3. **Prescription generation** (core app): saving a follow-up card (status 0 -> 1) or editing an operative-details drug creates `recepittmp` (status=0, drugstablename = source table) + one `receiptdrugs` row per drug (amount hardcoded to 1); the clinical row stores `recepittmpid`/`recepitdrugid` back-references. `operativedetails.php` reuses an existing open receipt (status=0) for the same operation instead of creating a new one.
4. **Dispensing** (`receipt.php`): pharmacist sees pending receipts (doctor + patient names resolved in PHP loops); `showdetails` flags `datachecked = true` if any line's `amount > drugs.currentbalance`; `updatereceipt` writes optype=1 movements and decrements balances ONLY for lines with sufficient stock (insufficient lines silently skipped), then marks the whole receipt with the posted `status` — no partial-dispense state.
5. **Bill edit/delete** (`buys.php update/del/delup`, duplicated in `stuff.php`): reverse previous quantities, write optype=2 (edit) / optype=3 (delete) correction movements, adjust `drugs.currentbalance` (can go negative — no guard).
6. **Stock report** (`store.php show`): dump of all `pharmacystore` rows with drug names (N+1 query per row).

## ERP Migration Notes
Proposed Laravel models/tables:
- `Drug` (`drugs`): keep id, name; normalize `drugcat` -> `drug_categories` and `drugtype` -> `dosage_forms` lookup tables; keep `default_dosage_text` (Arabic). Data cleanup required: trim `\r\n`, dedupe categories ('Anti-viruis' vs 'Antiviral'), unique constraint on (name, dosage_form).
- `StockMovement` (`pharmacystore`): drug_id FK, qty_before/qty_after/qty_delta, `movement_type` enum {purchase, dispense, adjustment_edit, adjustment_delete}, reference morph (purchase invoice / prescription), user_id, timestamp. Make `drugs.current_balance` derived (SUM of movements or event-maintained inside DB transactions) with a non-negative check.
- `PurchaseInvoice` (`importbill`) + `PurchaseInvoiceItem` (`importdetails`): add `supplier_id` FK to a NEW `suppliers` table (currently free text); consider unit cost, batch number, expiry date columns (absent today — no pricing/expiry anywhere in legacy schema).
- `Prescription` (`recepittmp`) + `PrescriptionItem` (`receiptdrugs`): doctor_id, patient_id, date; replace `drugstablename` string with a standard Laravel polymorphic relation (`prescribable_type`/`prescribable_id`) — or better, collapse the 10+ clinical `*drugs` tables into one `prescription_items` table tagged by sheet type. Status enum {pending, partially_dispensed, dispensed, cancelled}; store dispensed qty per item instead of silently skipping out-of-stock lines. Real quantities instead of hardcoded amount=1.
- DROP: `drugdos` (empty, mistyped, unreferenced) and `importtdetails` (empty duplicate, unreferenced). Do not migrate.
- `stuff.php`'s `councilstaff` logic belongs to an admin/staff module, not pharmacy; its duplicated invoice functions are dead weight.
- Security fixes inherent to rewrite: parameterized queries (legacy `drugs.php show()` concatenates `sSearch` -> SQL injection), remove the generic table/column/value write endpoint (`receipt.php update()`), enforce authorization via Policies (several legacy actions have `checkauthoize` commented out).
- Known latent bug not to replicate: `buys.php update()`/`del()` query `pharmacystore` by a nonexistent `bill_id` column and can drive `currentbalance` negative; edit flow compares form drug id to a bill-row id (`$billIDn != $drug_id`) — rewrite the reversal logic from the ledger, not from last-row heuristics.

---


# Module: Financial & Reports (financial)

## Purpose
Covers the clinic's money flow and the entire reporting subsystem of the legacy OB/GYN app:
- Visit fees (recorded inline on the `visits` table, NOT in this module's tables — see workflows).
- A per-patient "total balance" (prepaid package / credit) stored in `totalbalance`, repaid via installment "visits".
- Daily/period financial reports (cash, visa, discounts, debts, refunds, new patients), outstanding-debt report, balance-movement report, drug-usage report.
- Aggregated clinical reports: full activity report, combined (epidemiological) report, complete patient report (+Excel export), complete sheets report, IVF statistics.
- DB maintenance utilities: mysqldump backup, old-DB merge, raw SQL import.

## Controllers & Views (file paths)
| Controller | Views | Role |
|---|---|---|
| /home/amrtechogate/public_html/obgy/core/controllers/financialreport.php | core/views/obgy/reports/financial.html, financialsearch.html, financialprint.html, balance.html, reports/payment/rest.html, restsearch.html, restprint.html, reports/drug/* | Daily/period revenue report, balance report, outstanding-debt ("rest") report, drug-usage report |
| /home/amrtechogate/public_html/obgy/core/controllers/fullreport.php | core/views/obgy/reports/full/full.html, fullsearch.html; reports/expectedprint.html | Per-patient activity matrix over a period (which clinical sections have data) |
| /home/amrtechogate/public_html/obgy/core/controllers/combinedreport.php | core/views/obgy/reports/combined.html, combinedsearch.html, combinedprint.html | Statistical search by age / gyna diagnosis / antenatal diagnosis / operation / termination, with % of patient base |
| /home/amrtechogate/public_html/obgy/core/controllers/completereport.php | core/views/obgy/reports/complete.html, completesearch.html, completeprint.html | Single-patient complete report (demographics + ph* history + examination + investigations + gyna + antenatal); Excel export via `exec(php excel.php)` to core/excel_backups/<statusno>/ ; registers patient in `excelinfopatients` |
| /home/amrtechogate/public_html/obgy/core/controllers/Completesreport.php | core/views/obgy/reports2/completesreport.html | Newer "sheets" report aggregating infertilitysheet + ivfsheet + ancsheet + gynasheet with dozens of lookup tables; has generic `Add()` writing any table/column from POST |
| /home/amrtechogate/public_html/obgy/core/controllers/Ivfstatistics.php | core/views/obgy/reports2/Ivfstatistics.html, Ivfstatisticssearch.html, Ivfstatisticsprint.html | IVF cycle statistics from `ivfsheet` filtered by lmpfresh/lmpfrozen; joins ovst, eprep, antype, icsiprotocol, sseemen, icsisemen; also generic `Add()` |
| /home/amrtechogate/public_html/obgy/core/controllers/backup.php | core/views/obgy/backup/add.html | mysqldump to core/db_backups/ + programesetting.backupdest, browser download; data-fix utilities (`solution()` backfills drugid in gynadrugs/mainantenentaldrugs; `getstatusno()` assigns missing patients.statusno) |
| /home/amrtechogate/public_html/obgy/core/controllers/merge.php | core/views/obgy/merge/add.html | Merges an old clinic DB into current via RedBeanPHP `R::addDatabase`/`selectDatabase`; copies ~40 tables (patients, visits, ph*, examination, investigations, gyna, mainantenental, drugs, sonar, gtimage, programesetting...) then `fixProblems()` |
| /home/amrtechogate/public_html/obgy/core/controllers/newdb.php | core/views/obgy/newdb/add.html | Uploads a .sql file (renamed .txt) and executes it line-by-line via mysqli — raw restore/import |

Related (not assigned but where the money actually lives):
- /home/amrtechogate/public_html/obgy/core/controllers/visits.php — `gettotalbalance()`, `addbalance()` (writes `totalbalance`), visit creation storing fee fields, `addAllDetectionToERP()` (syncs `detections` catalog to an external ERP DB named in `programesetting.erpdb`).
- /home/amrtechogate/public_html/obgy/core/controllers/monitoring.php — balance report search (totalbalance UNION installment visits, filter type 1=both / 2=additions / 3=installments).

## Tables

### totalbalance
Purpose: credit additions to a patient's running account (prepaid/package amount, "إضافة للمبلغ الإجمالي"). One row per top-up. Empty in this dump (AUTO_INCREMENT=1).
```sql
CREATE TABLE IF NOT EXISTS `totalbalance` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `patientid` int(11) DEFAULT NULL,
  `balance` int(11) DEFAULT NULL,
  `deleted` int(11) DEFAULT '0',
  `userid` int(11) DEFAULT NULL,
  `adddate` date NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
```
Inferred FKs: `patientid` -> patients.id; `userid` -> awusers.user_id (session user who recorded the top-up) (استنتاج).
Written by: visits.php `addbalance()` (AJAX from visit screen). Read by: visits.php `gettotalbalance()` (remaining = SUM(balance) − SUM of visits with detectionid = -99 cash+visa+discount), financialreport.php `balance()`, monitoring.php balance search.

### totalbalancepaids
Purpose (intended): payments against the total balance. **ORPHAN/DEAD table — zero references in any controller or template.** Actual installment payments are recorded as `visits` rows with `detectionid = -99`. Empty in this dump.
```sql
CREATE TABLE IF NOT EXISTS `totalbalancepaids` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `patientid` int(11) DEFAULT NULL,
  `paid` float DEFAULT NULL,
  `userid` int(11) DEFAULT NULL,
  `paydate` date DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
```
Inferred FKs: `patientid` -> patients.id; `userid` -> awusers.user_id (استنتاج).

### Context: where visit fees are recorded (not assigned, for completeness)
`visits` carries the financial fields: `detectionid` (-> `detections.id`, the service/fee catalog: id, title, detectionval, del), `totaldetectionvalue`, `discount`, `detectionvalue_cash`, `detectionvalue_visa`, `restdetectionvalue` (all `decimal(10,0)`), plus `visitid` self-reference to the original visit for follow-up money rows.
Hardcoded sentinel detection ids (magic values, no lookup rows):
- `-99` = installment payment against totalbalance ("دفع قسط")
- `999` = payment of the remaining amount of an earlier visit ("دفع متبقى"), linked via `visits.visitid`
- `9999` = customer refund ("مرتجع العملاء"), linked via `visits.visitid`, subtracted in daily report

## Relationships (explicit list)
- totalbalance.patientid -> patients.id
- totalbalance.userid -> awusers.user_id (استنتاج)
- totalbalancepaids.patientid -> patients.id (استنتاج, dead table)
- totalbalancepaids.userid -> awusers.user_id (استنتاج, dead table)
- visits.patientid -> patients.id (fee source for all financial reports)
- visits.detectionid -> detections.id (or sentinel -99/999/9999)
- visits.visitid -> visits.id (self-reference: refund / rest-payment to original visit)
- excelinfopatients.patientid -> patients.id (completereport Excel export queue)
- Report read-paths: gynadrugs/mainantenentaldrugs/ancsheetdrugs/infertilitydrugs/gynasheetdrugs/infertilitysheetdrugs.{patientid,drugid} -> patients.id / drugs.id; ph* + examination + semen/hsg/ustv/laparoscopy/hysteroscopy/mrict/pathology/hormon + gyna + mainantenental + ultrasound* + sonar + operativedetails .patientid -> patients.id; ivfsheet.patientid -> patients.id; ovst.ivfsheetid / eprep.ivfsheetid -> ivfsheet.id; infertilitysheet child tables via infertilitysheetid.

## Business Workflows (traced from code)
1. **Visit fee capture**: reception creates a visit (visits.php), choosing a `detections` service; total/cash/visa/discount/rest are POSTed and stored on the visit row. Debt is whatever stays in `restdetectionvalue`.
2. **Total-balance (package) flow**: `visits.php?do=addbalance` inserts a `totalbalance` row (patientid, balance, userid, adddate, deleted=0) and immediately returns the remaining balance (SUM(totalbalance.balance) − cash/visa/discount of all visits with detectionid=-99 for that patient). Installment payments are entered as visits with detectionid=-99.
3. **Daily financial report** (financialreport index/search/showprint): SELECT over visits JOIN patients LEFT JOIN detections for a date range (+optional wife/husband/address filter); per row resolves sentinel ids to Arabic labels, computes refund per visit (sum of detectionid=9999 children), totals cost/cash/visa/debt/discount, flags new patients (patients.entrydate in range), lists new patients separately.
4. **Balance report** (financialreport `balance()`, search via monitoring.php): UNION ALL of totalbalance additions ("إضافة للمبلغ الاجمالى") and detectionid=-99 visits ("دفع قسط"), net total computed in PHP; filter type 1=both, 2=additions, 3=payments.
5. **Outstanding debt report** (`rest`/`searchPayment`/`printPayment`): visits with restdetectionvalue>0 in range; subtracts later detectionid=999 payments + their discounts; shows remaining dept per visit.
6. **Drug usage report** (`drug`/`drugsearch`/`drugprint`): UNION across 6 prescription tables for a drugid + date range, returning distinct patients.
7. **Full report** (fullreport `search`): distinct patients with visits in range (optionally one detection type); for each, boolean flags per clinical area computed by counting rows in ~30 tables; rendered as a matrix.
8. **Combined report**: dynamic WHERE built from age range / diagnosis ids (comma-joined string equality on gyna.diagnosisid — fragile) / operationid / obstermination over Patients LEFT JOIN gyna/antenalvisit/Operativedetails/phobstetric, grouped by wifename, with percentage of all patients.
9. **Complete report / Completesreport**: per-patient aggregation of all clinical data; completereport also queues patient into `excelinfopatients` and shells out `exec("$programesetting->phppath\php excel.php ...")`, then lists generated files from core/excel_backups/<statusno>/.
10. **IVF statistics**: ivfsheet rows in LMP range (fresh or frozen), per row loads patient, computes trial age, first/last ovst, eprep counts, protocol/semen lookups.
11. **Backup**: `takeit` runs mysqldump (optionally `$backupdriver\bin\mysqldump`) into ../db_backups/obgy_YYYY-MM-DD.sql and programesetting.backupdest, then streams the file as a download. `solution` backfills drugid and statusno.
12. **Merge / newdb**: one-shot migration tools; merge copies ~40 tables from a second DB connection then fixes orphan drug names; newdb executes an uploaded SQL file statement-by-statement.

## ERP Migration Notes (Laravel)
- **Models**: `PatientLedgerEntry` (replaces totalbalance + the sentinel-coded money visits; fields: patient_id, type enum [visit_fee, balance_topup, installment_payment, rest_payment, refund, discount], amount decimal(10,2), method enum [cash, visa], reference_id nullable to original entry/invoice, user_id, entry_date), `Invoice` + `InvoiceItem` (per visit, items from a `Service` model replacing `detections`), `Payment` (one row per tender instead of cash/visa column pair). Compute outstanding debt and remaining balance with queries/accessors — never store derived `rest` values.
- **Drop** `totalbalancepaids` (dead, no data path). Migrate `totalbalance` rows (if any in production) to `balance_topup` ledger entries; migrate visits with detectionid in (-99,999,9999) to typed ledger entries with proper references.
- **Eliminate magic ids**: -99/999/9999 must become enum types; `visits.visitid` self-link becomes `reference_id` FK.
- **Money precision/charset**: legacy uses decimal(10,0) and int for money, latin1 tables; convert to decimal(10,2), utf8mb4, real FK constraints, `deleted_at` soft deletes.
- **Reports**: implement as Laravel query/report classes + API resources for Angular (daily revenue, balance movement, outstanding debts, drug usage, full activity matrix, combined statistics, complete patient file, IVF stats). Replace exec-based Excel export with maatwebsite/excel or PDF generation; drop `excelinfopatients` queue.
- **Kill backup/merge/newdb controllers**: use spatie/laravel-backup or server-level dumps; old-DB merge becomes one-time artisan ETL commands; never allow SQL upload/execution from the UI.
- **Security debt to not carry over**: pervasive string-concatenated SQL (dates, address, husband name), generic `Add()` arbitrary-write endpoints in Completesreport/Ivfstatistics, DB password on shell command line, commented-out hardcoded credentials in backup.php.

---


# Module: Medical Board & Sessions (board)

## Purpose
Standalone sub-app (`/board`) implementing a doctors' council / case-discussion board. Doctors submit consultation requests (free-text case title + details), the council secretary opens periodic sessions, marks member attendance, each attending member records an Accept/Refuse opinion per request, a final textual decision is recorded on the request, requests can be postponed to the next session, and closed sessions/requests are archived. A threaded comment system lets users discuss each request. A staff screen manages council composition (head / secretary "amin" / members).

Note: despite the clinical intent, there is NO structural link to patients — the case is described only in free text (`brequests.title`, `brequests.details`).

## Controllers & Views (file paths)
Controllers (board sub-app, awframework + RedBeanPHP `R::`, Smarty views):
- `/home/amrtechogate/public_html/obgy/board/controllers/requests.php` — request lifecycle + comments
  - actions: `index` (auto-create/load draft request), `update` (generic AJAX autosave), `sendrequest`, `showall` (status=1), `archiveshowall` (status=2), `requestDetails`, `requestcomments`, `addcomment`, `deletecomment`, `requestDetailsArchive`
- `/home/amrtechogate/public_html/obgy/board/controllers/sessions.php` — session lifecycle, attendance, opinions, decisions
  - actions: `index` (auto-create open session + bulk-insert members), `savesession` (close + archive requests, transactional), `archiveshowall`, `requestDetails`, `requestDetailsArchive`, `sessiondetails`, `addsessionmember`, `removesessionmember`, `delay`, `notdelay`, `saveopinion`, `savedecision`
- `/home/amrtechogate/public_html/obgy/board/controllers/stuff.php` — council staff management (head/amin/members in `councilstaff`) + ~200 lines of DEAD pharmacy code (`updategg`, `del`, `delup` operating on `importbill`, `importdetails`, `drugs`, `pharmacystore`) copy-pasted from the pharmacy module
- `/home/amrtechogate/public_html/obgy/board/controllers/test.php` — scratch/test file (not analyzed as assigned)

Views: `/home/amrtechogate/public_html/obgy/board/views/obgy/` — `addrequest.html`, `showrequests.html`, `requestdetails.html`, `request_model.html`, `comments.html`, `addsession.html`, `showsessions.html`, `sessiondetails.html`, `session_model.html`, `members.html`, `stuff.html`, `headinfoview/edit.html`, `amininfoview/edit.html`, `boardstuffview.html`, `stuffinfoedit.html`, `templates/*`.

## Tables
All from `/home/amrtechogate/public_html/obgy/_db/obgy_12-7-2024.sql`. No declared foreign keys anywhere. All six tables are EMPTY in the dump (`AUTO_INCREMENT=1`, no INSERT statements) — no seed/lookup data exists for this module.

### brequests (utf8mb4, dump line 4563)
Consultation request submitted to the board. Status lifecycle: 0 = draft (auto-created on screen open), 1 = submitted/pending discussion, 2 = discussed & archived.
| Column | Type | Notes |
|---|---|---|
| id | int(11) unsigned PK AI | |
| memberid | int(11) unsigned NULL | FK -> awusers.user_id (requesting doctor) |
| title | varchar(191) NULL | free-text case title |
| details | varchar(191) NULL | free-text case details — only 191 chars despite textarea UI |
| status | int(11) unsigned NULL | 0 draft / 1 submitted / 2 discussed (hardcoded) |
| date | date NULL | set to today on `sendrequest` |
| delay | int(11) unsigned DEFAULT 0 | 1 = postponed to next session; reset to 0 on session close |
| decision | varchar(191) NULL | council's final decision, free text (`savedecision`) |
| sessionid | int(11) unsigned NULL | FK -> bsession.id, set when session is closed (`savesession`) |

### bsession (latin1, dump line 4582)
Board session. Status: 0 = open (only one open at a time, auto-created), 1 = closed/archived.
| Column | Type | Notes |
|---|---|---|
| id | int(11) PK AI | |
| date | date NOT NULL | set to today on close (`savesession`) |
| status | int(11) NOT NULL | 0 open / 1 closed |
| memberid | int(11) unsigned NULL | FK -> awusers.user_id (user who opened the session) |

### bsessionmembers (utf8mb4, dump line 4596)
Session attendance join table (session x member). Row present = attended; deleted = absent. On session creation, ALL users with `awusers.positionid IN (1,2,3)` are bulk-inserted (transactional `R::storeAll`).
| Column | Type | Notes |
|---|---|---|
| id | int(11) unsigned PK AI | referenced by bmemberopenion.sessionmemberid |
| sessionid | int(11) unsigned NULL | FK -> bsession.id |
| memberid | int(11) unsigned NULL | FK -> awusers.user_id |

### bmemberopenion (utf8mb4, dump line 4535) — note misspelling "openion"
One opinion per (request x session-member); upserted by `saveopinion` (find-then-update or insert). No unique constraint enforced in schema.
| Column | Type | Notes |
|---|---|---|
| id | int(11) unsigned PK AI | |
| requestid | int(11) unsigned NULL | FK -> brequests.id |
| sessionmemberid | int(11) unsigned NULL | FK -> bsessionmembers.id (NOT a user id) |
| openion | int(11) unsigned NULL | 1 = Accept, 2 = Refuse (from views members.html / session_model.html) |

### bcomments (latin1, dump line 4507)
Threaded comments on requests. `relatedcommentid = 0` => top-level comment; otherwise points to parent comment id (one level of nesting used in code). Deleting a top-level comment deletes its replies.
| Column | Type | Notes |
|---|---|---|
| id | int(11) PK AI | |
| memberid | int(11) NOT NULL | FK -> awusers.user_id (author) |
| requestid | int(11) NOT NULL | FK -> brequests.id |
| comment | text NOT NULL | latin1 — Arabic text corruption risk |
| relatedcommentid | int(11) NOT NULL | self-FK -> bcomments.id, 0 = root |

### councilstaff (latin1, dump line 4778)
Council composition managed by stuff.php. Column comment in schema: `0 head, 1 amin, 2 member`.
| Column | Type | Notes |
|---|---|---|
| id | int(11) PK AI | |
| userid | int(11) NOT NULL | FK -> awusers.user_id |
| status | int(11) NOT NULL | 0 = head, 1 = amin (secretary), 2 = member |

FUNCTIONALLY ORPHANED: sessions.php line 83-84 has the `councilstaff` query commented out and replaced with a hardcoded `SELECT * FROM awusers WHERE positionid IN (1,2,3)`. So the council membership managed in the stuff screen does not actually drive session membership. No `positions` lookup table exists in the dump (values 1,2,3 are magic numbers).

## Relationships (explicit list)
- brequests.memberid -> awusers.user_id (requesting doctor)
- brequests.sessionid -> bsession.id (session in which it was discussed; set on close)
- bsession.memberid -> awusers.user_id (session creator)
- bsessionmembers.sessionid -> bsession.id
- bsessionmembers.memberid -> awusers.user_id
- bmemberopenion.requestid -> brequests.id
- bmemberopenion.sessionmemberid -> bsessionmembers.id
- bcomments.memberid -> awusers.user_id
- bcomments.requestid -> brequests.id
- bcomments.relatedcommentid -> bcomments.id (self, 0 = root)
- councilstaff.userid -> awusers.user_id
- (implicit) session member selection: awusers.positionid IN (1,2,3) — hardcoded, no positions table in dump
- (missing) NO patient/visit linkage anywhere in this module

## Business Workflows (traced from code)
1. **Submit request** (requests.php): `index` loads the doctor's last request; if none or last is completed, dispenses a new `brequests` row (status NULL/0, memberid = session user). Title/details auto-saved field-by-field via `update` (generic POST: id/tableName/colName/value). `sendrequest` sets status=1, date=today, redirects to pending list.
2. **Discuss via comments**: `requestcomments` renders root comments (relatedcommentid=0) plus one level of replies, joining author names from `awusers`. `addcomment` inserts; `deletecomment` cascades root -> replies manually.
3. **Open session** (sessions.php `index`): loads council members from `awusers.positionid IN (1,2,3)` and pending requests (status=1). If last `bsession` is closed or none exists: creates new `bsession` (memberid = current user) and bulk-inserts one `bsessionmembers` row per council member inside a transaction. If an open session exists, computes per-member `attend` flag from `bsessionmembers` presence.
4. **Attendance**: `addsessionmember` inserts, `removesessionmember` deletes (sessionid, memberid) rows.
5. **Per-request discussion**: `requestDetails` modal lists session members with their stored opinion (from `bmemberopenion` by sessionmemberid+requestid). `saveopinion` upserts opinion (1 Accept / 2 Refuse). `savedecision` writes free-text decision onto the request. `delay`/`notdelay` toggle `brequests.delay`.
6. **Close session** (`savesession`, transactional): session.status=1, session.date=today; all requests with status=1 AND delay=0 -> status=2, sessionid=closed session; all requests with status=1 AND delay=1 -> delay reset to 0 (stay pending for next session). Redirect to session archive.
7. **Archive review**: `archiveshowall` (sessions status=1; requests status=2), `sessiondetails` (members + requests of one session), `requestDetailsArchive` (request + comments + members' opinions read-only).
8. **Council staff management** (stuff.php): `index` shows head (councilstaff.status=0), amin (status=1), members (status=2) with names joined from awusers; `add`/`addboardstuff`/`update`/`updateamin`/`stuffupdate`/`delboard` CRUD councilstaff rows. NOTE: outputs not consumed by sessions.php (see above).

## Security/Quality Issues (must not be migrated)
- `requests.php::update()` — arbitrary table/column write: tableName, colName, id, value all from POST, passed straight to `R::load`/`R::store`. Critical vulnerability.
- `stuff.php` — all `autho::checkautho`/`checkauthoize` calls commented out (no auth), and string-concatenated SQL (`'... WHERE user_id = ' . $userid`, `NOT IN ($headuser->userid)`) — SQL injection.
- Empty `catch` blocks swallow all exceptions throughout.
- Mixed charsets (latin1 vs utf8mb4) across the six tables; Arabic text in `bcomments.comment` at risk.
- No timestamps on comments/opinions; varchar(191) for clinical details/decision; magic-number statuses; misspelled `openion`.
- ~200 lines of dead pharmacy code in stuff.php (`updategg`, `del`, `delup`).

## ERP Migration Notes
Proposed Laravel models/tables:
- `BoardRequest` (`board_requests`): requester_id FK users, **patient_id FK patients (NEW — fixes the biggest design gap)**, optional visit_id, title, details TEXT, status enum (draft/submitted/discussed), decision TEXT, submitted_at, session_id FK nullable, is_postponed bool, timestamps, soft deletes.
- `BoardSession` (`board_sessions`): opened_by FK users, status enum (open/closed), scheduled/closed dates, timestamps.
- `BoardSessionMember` (`board_session_members`): session_id, user_id, unique(session_id, user_id), timestamps. Replaces bsessionmembers.
- `BoardMemberOpinion` (`board_member_opinions`): request_id, session_member_id (or flatten to session_id+user_id), opinion enum (accept/refuse), unique(request_id, session_member_id), timestamps. Replaces bmemberopenion.
- `BoardComment` (`board_comments`): request_id, user_id, parent_id self-FK nullable, body, timestamps, soft deletes. Replaces bcomments (relatedcommentid=0 -> parent_id NULL).
- Council membership: replace BOTH `councilstaff` and the hardcoded `positionid IN (1,2,3)` with a single managed mechanism — either a `board_members` table (user_id, role enum head/secretary/member) or roles in the ERP's permission system (e.g. Spatie). Make session auto-population read from it.
- Enums: RequestStatus, SessionStatus, Opinion, CouncilRole — kill all magic numbers.
- Replace the generic autosave endpoint with `PATCH /api/board-requests/{id}` using FormRequest validation (whitelist title/details only).
- Drop entirely: dead pharmacy code in stuff.php; `test.php`.
- Data migration: all six tables are empty in this dump — likely greenfield rebuild with no historical data to migrate (verify against current production DB before finalizing).

---


# Module: Platform, Auth, Roles & Settings (platform)

## Purpose
The infrastructure layer of the legacy OB/GYN system ("aw framework"). Covers: user authentication (session + remember-me cookie via a bundled "php-login" style library), a 4-level RBAC system (role -> controller -> action(prop) -> UI button, plus role -> menu visibility), the dynamic sidebar menu tree, the singleton program-settings record that toggles almost every clinical feature, multi-branch/floor/device scaffolding (mostly dead), the waiting-room display app (`screen/`), the patient-facing mobile JSON API (`mobileservices.php`), help articles, and a handful of junk/test tables.

## Controllers & Views (file paths)
- `/home/amrtechogate/public_html/obgy/core/controllers/login.php` — login/logout/forgot-password; wraps `_library/login_system`. Contains a commented-out licensing check against `excelinfo.serial` + device id.
- `/home/amrtechogate/public_html/obgy/_library/login_system/classes/Login.php` (+ `Registration.php`, translations `ar.php`/`en.php`) — PDO auth against `awusers`: bcrypt verify, failed-login throttle (3 tries / 30s), remember-me cookie (`user_id:token:sha256`), writes `$_SESSION['user_id']`, `$_SESSION['role_id']`, etc.
- `/home/amrtechogate/public_html/obgy/core/controllers/imp/_autho.php` — `autho::checkautho()` (logged-in check) and `autho::checkauthoize($controllname, $hosturl, $roleid)` — resolves `awcontroll` by name, `awcontrollprop` by action (`?ac=`), then `awrolecontrollprop` override; falls back to the prop's default `checkval`. Called at the top of every action in every controller.
- `/home/amrtechogate/public_html/obgy/core/controllers/_role.php` (598 lines) — role CRUD; permission matrix editor: writes `awrolemenu`, `awrolecontrollprop`, and `awrolebtn` (hardcoded buttons: btn_id 1=add patient, 2=add visit, 3=edit visit, 4=delete visit, rendered in `displaybtns()`). Views: `core/views/obgy/_role/`.
- `/home/amrtechogate/public_html/obgy/core/controllers/_member.php` (356) — user CRUD on `awusers` (raw INSERT/UPDATE with `password_hash`), username-uniqueness AJAX, and AJAX loaders for `awroleposition`/`awrolespecialize` filtered by `roleid`.
- `/home/amrtechogate/public_html/obgy/core/controllers/_menu.php` (363) — `awmenu` CRUD + drag-and-drop reordering (`arrangemain`/`arrangesub` update `sort`/`parent`); icon image upload.
- `/home/amrtechogate/public_html/obgy/core/controllers/_controll.php` (303) — registry CRUD for `awcontroll` + its `awcontrollprop` action list (`delprop` action).
- `/home/amrtechogate/public_html/obgy/core/controllers/_sidebar.php` — builds the sidebar from `awmenu` (recursive on `parent`), filters by `programesetting.simpleview` vs `awmenu.form` (0=complete,1=simple,2=both), checks `awrolemenu` per role, and `withpatient` flag appends current patient id to links.
- `/home/amrtechogate/public_html/obgy/core/controllers/programesetting.php` (296) — single form that updates `programesetting` row id=1 (~75 columns of toggles), logo/background uploads.
- `/home/amrtechogate/public_html/obgy/core/controllers/setup.php` (225) — protected by hardcoded password `123456`; database "reset" utility (`settingdatabase()` truncates all tables except a hardcoded keep-list); links an external ERP database via `R::addDatabase('erpDB', ...)`, stores `programesetting.erpdb` / `erpdbsave` and autocompletes ERP `save` (treasury) records.
- `/home/amrtechogate/public_html/obgy/core/controllers/help.php` (215) — CRUD on `help` table.
- `/home/amrtechogate/public_html/obgy/core/controllers/mobileservices.php` (671) — patient mobile JSON API, **no auth** (autho calls commented out), `Access-Control-Allow-Origin: *`. Endpoints: `addPatient` (creates `patients` row with `patient_password_hash`, `devid`), `updatePatient`, `getPatientData`, `patientLogin` (mobile+password; raw string-concatenated SQL — injectable), `addVisit`/`updateVisit`/`delVisit`/`visitType`, `queryAllVisitsByPatientAndDate`, `queryAllVisitsByDate`, `roshetat`+`showprescription` (reads `gynadrugs`+`drugs`), `selectChatting` (reads `chat` join `patients`; hardcoded `http://localhost/obgy_new/` paths), ERP client sync (`erpClient`, `curlAddClient`, `curlUpdateClient`, `erpGetClientTreeId`) calling the ERP app via curl.
- `/home/amrtechogate/public_html/obgy/screen/controllers/index.php` — waiting-room display: reads `screen_slider` images, today's `visits` (view/end_visit flags) joined to `patients.wifename` for "now serving / next 5" queue; performs runtime `ALTER TABLE visits ADD end_visit` if missing; `loadInfo` is the AJAX refresh. Views: `/home/amrtechogate/public_html/obgy/screen/views/`.
- `/home/amrtechogate/public_html/obgy/_public/aw_config.php` — global config: hardcoded DB credentials (`amrtechogate_obgy` / `53i#WRqAw8r121618`), `$hosturlApi = 'http://api.gt4it.com'` (external API "like sms, invests"), RedBeanPHP bootstrap.
- `/home/amrtechogate/public_html/obgy/_public/api_config.php` — same DB creds + `putenv` of ERP api_key/api_user/api_password in plaintext.
- `/home/amrtechogate/public_html/obgy/_library/php-jwt-master/` — Firebase php-jwt bundled but **no `JWT::encode/decode` call exists anywhere outside the library** — dead dependency.

## Tables

### awcontroll (registry of controllers)
| column | type |
|---|---|
| id | int(11) unsigned PK AI |
| label | varchar(191) |
| name | varchar(191) |
Seeded 81 rows mapping every controller file to a permission unit: `_role`, `_menu`, `_member`, `_controll`, `_myprofile`, pharmacy (`drugs`,`medicine`,`buys`,`receipt`,`store`), board (`requests`,`sessions`), clinical (`gyna`,`investigation`,`patienthistory`,`antenalvisit`,`drugsex`,`instruction`,`programesetting`,`help`,`sonar`,`addinvestigation`,`expected`,`completereport`,`financialreport`,`examination`,`_phmain`,`patients`,`operativedetails`,`gtimage`,`visits`,`backup`,`setup`,`excel`,`infertility`,`record`,`followup`,`ultrasound*`,`termination`,`risktype`,`merge`,`combinedreport`,`Followupcard`,`Fullreport`,`monitoring`, sheets `Infertilitysheet`/`Ancsheet`/`Gynasheet`/`Ivfsheet`, reports `iui`/`epc`/`Deliveries`/`Operations`/`edd`/`Ivfstatistics`/`Completesreport`,`Addpresenthistory`,`adddevices`(80),`branches`(81)). Note ids 80/81 registered but **no `adddevices.php`/`branches.php` controller file exists**.

### awcontrollprop (actions per controller)
| column | type |
|---|---|
| id | int(11) unsigned PK AI |
| proplabel | varchar(191) — action name matched against `?ac=` (index, addit, show, edit, updateit, del, delprop, setting, ...) |
| checkval | tinyint(1) — default allow(1)/deny(0) when no role-specific row exists |
| awcontroll_id | int(11) unsigned FK -> awcontroll.id (indexed) |
~590 seeded rows (one per action per controller).

### awmenu (sidebar menu tree)
| column | type |
|---|---|
| id | int PK AI |
| label | varchar(191) |
| link | varchar(191) — relative controller URL |
| parent | varchar(191) — self-ref awmenu.id, '0' = root |
| sort | int |
| icon | varchar(191) |
| active_name | varchar(191) — highlight key |
| display | varchar(191) — 'image' or 'icon' |
| image | varchar(191) — uploaded file |
| form | int default 0 — 0=complete-form mode only, 1=simple mode only, 2=both (filtered by `programesetting.simpleview`) |
| withpatient | int default 1 — 1 = link carries current patient context |
102 seeded rows (Dashboard, Roles, Members, Controlls, Pharmacy, Board, clinical sheets, Reports tree, Setup, Branches at id 102 pointing to nonexistent `Branches.Php`).

### awrole
`id` int PK, `name` varchar(191). Seeded: 1 Management, 2 pharmacist, 3 reception, 4 doctors, 5 rays, 6 assistants, 7/8 (empty names), 10 'lobna role'.

### awrolebtn (per-role UI button rights)
`id`, `role_id` -> awrole.id, `btn_id` (hardcoded 1=add patient, 2=add visit, 3=edit visit, 4=delete visit), `checkval`. Rows auto-created lazily by `_role.php::displaybtns()`. Dump empty (AUTO_INCREMENT=1).

### awrolecontrollprop (role x action grants)
`id`, `role_id` -> awrole.id, `controllprop_id` -> awcontrollprop.id, `checkval` tinyint. ~3000 rows seeded. Deleted in bulk when a role is deleted (`_role.php` del).

### awrolemenu (role x menu visibility)
`id`, `role_id` -> awrole.id, `menu_id` -> awmenu.id, `checkval`. ~490 rows.

### awroleposition (job positions, scoped to a role)
`id`, `roleid` -> awrole.id, `name`. Seeded: (role 4 doctors) Council Amin, Council Head, Council Staff; (role 5 rays) Sonographer, Supervisor.

### awrolespecialize (specializations, scoped to a role)
`id`, `roleid` -> awrole.id, `name`. Seeded: Anasthests + operation for roles 4 (doctors) and 6 (assistants).

### awusers (staff accounts) — MyISAM, utf8
| column | type |
|---|---|
| user_id | int PK AI |
| user_name | varchar(64) |
| user_password_hash | varchar(255) bcrypt |
| user_email | varchar(64) |
| user_active | tinyint(1) |
| user_activation_hash | varchar(40) |
| user_password_reset_hash | char(40) |
| user_password_reset_timestamp | bigint |
| user_rememberme_token | varchar(64) |
| user_failed_logins | tinyint(1) |
| user_last_failed_login | int(10) |
| user_registration_datetime | datetime |
| user_registration_ip | varchar(39) |
| role_id | int FK -> awrole.id |
| name | varchar(2000) display name |
| specialid | int FK -> awrolespecialize.id |
| positionid | int FK -> awroleposition.id |
| attend | int (attendance flag; no code usage found in this module) |
| branch_id | varchar(11) default '0' FK -> branches.id (استنتاج; seed user has '-1' = all branches, inferred) |
Seed: single user `manager` (role 1, branch_id '-1').

### programesetting (singleton settings, row id=1) — MyISAM
~75 columns. Groups: clinic identity (`centername`,`doctorname`,`spacial`,`address`,`phone`,`logo`,`background`); print layout (`color`,`above`,`buttom`,`pleft`,`pright`,`center`,`fontsizeen`,`fontsizear`,`alignttt`,`namefontsize`,`printserial`,`barcode_print`,`print_diag`); feature toggles (`ultrasound`,`patientnoaut`,`addshours`,`ishide`,`simpleview`,`displaydoctorname`,`entryorder`,`investshow`,`prescriptioncashing`,`diagnosshow`,`visitsform`,`hideid`,`programview`,`patient_doctor`,`previous_marriage`,`infertility`,`visit_period`,`art_internal`,`art_external`); patient-history section toggles (`balance`,`head`,`chest`,`abdomen`,`pelvis`,`extremitis`,`btype`,`hbtype`,`risk`,`notes`,`menstrual`,`contraception`,`obstetric`,`past`,`family`,`medical`,`surgical`,`gynecological`,`art`,`entrydetails`,`gynalmp`); backup (`backupdest`,`backupdriver`,`phppath`); ERP link (`erpdb`,`erpdbsave`); email creds in plaintext (`email`,`password`); receipt serials (`serial_month` e.g. '2024-04', `last_serial`, `last_refund_serial`); `branches` int flag; sheet links (`gynalink`,`antenatal`).

### branches
`id` int unsigned PK, `name` varchar(191), `created_at`, `updated_at` datetime. **Empty, no controller file** (menu id 102 + awcontroll id 81 reference a missing `Branches.php`). Referenced only by `awusers.branch_id` and `programesetting.branches` flag.

### floors
`id`, `name` varchar(255), `deleted` int, `default_value` int. Empty; no code usage found.

### devices
`id`, `device_name` varchar(255), `location` int, `floor_no` int (FK -> floors.id, استنتاج), `deleted` int. Empty; `adddevices` registered in awcontroll but controller file missing.

### device_tracking (polymorphic patient-flow tracking)
`id`, `patient_id` int (FK -> patients.id), `device_id` int (FK -> devices.id), `target_id` int + `target_table` varchar(255) (polymorphic pointer to a clinical record), `visit_date` date, `visit_time` time, `user_id` int (FK -> awusers.user_id), `deleted`, `control` varchar(255). Empty; no live code usage found — planned/abandoned feature (استنتاج).

### messages (internal user-to-user messages)
`id`, `message` varchar(191), `sender_id` int unsigned (FK -> awusers.user_id, indexed), `receiver_id` int unsigned (FK -> awusers.user_id, indexed), `created_at` datetime. Empty; no controller usage found. Note: actual patient chat uses a separate `chat` table consumed by `mobileservices.php::selectChatting`.

### screen_slider (waiting-room slideshow)
`id`, `image` varchar(191), `create_date` date. Read by `screen/controllers/index.php`; empty in dump.

### help (help articles)
`id`, `name` varchar(255), `content` varchar(255), `tempdelete` char(1) default '0' (soft delete). CRUD via `help.php`.

### Junk / test tables (all empty, no code references)
- `table2`: `id`, `name2` varchar(50)
- `table3`: `id`, `name` varchar(100)
- `tablename`: `id`, `del` int — clearly a leftover from a CREATE TABLE template
- `testtbl1`: `id`, `name2` varchar(50)
- `testtbl2`: `id`, `name` varchar(55)
All five are developer test artifacts — safe to drop.

## Relationships (inferred; no declared FKs except RedBean index hints)
- awcontrollprop.awcontroll_id -> awcontroll.id
- awrolecontrollprop.role_id -> awrole.id
- awrolecontrollprop.controllprop_id -> awcontrollprop.id
- awrolemenu.role_id -> awrole.id
- awrolemenu.menu_id -> awmenu.id
- awrolebtn.role_id -> awrole.id
- awmenu.parent -> awmenu.id (self-reference, stored as varchar)
- awusers.role_id -> awrole.id
- awusers.specialid -> awrolespecialize.id
- awusers.positionid -> awroleposition.id
- awusers.branch_id -> branches.id (varchar, '-1' = all)
- awroleposition.roleid -> awrole.id
- awrolespecialize.roleid -> awrole.id
- device_tracking.patient_id -> patients.id; device_tracking.device_id -> devices.id; device_tracking.user_id -> awusers.user_id; device_tracking.(target_table,target_id) -> polymorphic
- devices.floor_no -> floors.id
- messages.sender_id / messages.receiver_id -> awusers.user_id
- Cross-module: screen app reads visits.patientid -> patients.id; mobileservices writes patients/visits, reads gynadrugs.drugid -> drugs.id, chat.patient_id -> patients.id; setup.php links external ERP DB table `save`; programesetting.erpdb/erpdbsave -> external ERP database.

## Business Workflows (traced from code)
1. **Login**: `login.php` -> `Login` class -> PDO select on `awusers` by user_name -> bcrypt verify -> session gets `user_id`, `user_name`, `role_id`; optional remember-me cookie writes `user_rememberme_token`. Failed logins throttled (3 attempts / 30 seconds). Logout destroys session. A device-serial licensing check against `excelinfo` exists but is commented out.
2. **Per-request authorization**: every controller constructor includes `_autho.php`; every action calls `autho::checkautho()` then `autho::checkauthoize(controllname, hosturl, $_SESSION['role_id'])`, which resolves awcontroll -> awcontrollprop(action) -> awrolecontrollprop(role) and redirects to `error.php?ac=autho` on deny. Default falls back to `awcontrollprop.checkval`.
3. **Role administration** (`_role.php`): create role -> open matrix editor: tree of menus (`awrolemenu` show/hide radio), per-controller action grid (`awrolecontrollprop` allow/deny), and four hardcoded clinical UI buttons (`awrolebtn`). Deleting a role bulk-deletes its awrolemenu + awrolecontrollprop rows.
4. **User administration** (`_member.php`): add user -> AJAX username uniqueness -> raw INSERT into awusers with bcrypt hash, role, and role-scoped `specialid`/`positionid` loaded via AJAX from `awrolespecialize`/`awroleposition`.
5. **Menu administration** (`_menu.php`): CRUD + drag-drop sort of the two-level sidebar; sidebar rendering (`_sidebar.php`) filters by `simpleview` mode (`awmenu.form`), per-role `awrolemenu`, and appends patient context when `withpatient=1`.
6. **Program settings** (`programesetting.php`): one giant form updates the singleton row; toggles drive sidebar mode, visible history sections, printing layout, serials, ART internal/external, etc. — this row is read by virtually every clinical controller.
7. **Setup / ERP link** (`setup.php`): password `123456` gate -> set ERP database name (`programesetting.erpdb`) -> RedBean `addDatabase('erpDB')` -> pick default treasury (`save`) record -> `erpdbsave`. Also a destructive "reset database" routine truncating all but a hardcoded keep-list.
8. **Waiting-room screen** (`screen/`): unauthenticated display page polling `loadInfo`: shows `screen_slider` images, the last called patient (`visits.view=1, end_visit=0`) and next 5 waiting (`view=0`) for today, with `patients.wifename`.
9. **Mobile patient API** (`mobileservices.php`): patient self-registration (`addPatient` with mobile+password), `patientLogin`, visit booking (`addVisit` types: kashf/ultrasound/i3ada), prescription viewing (`roshetat`/`showprescription` from `gynadrugs`+`drugs`), chat history (`chat` table), and bidirectional client sync with the external ERP via curl + api_key.

## ERP Migration Notes
- **Auth**: replace the bundled login_system with Laravel's native auth (Sanctum for the Angular SPA + mobile API). Map `awusers` -> `users` (id, name, username, email, password, is_active, branch_id FK nullable, role via spatie). Drop activation/reset/rememberme columns (framework handles them). `attend` appears unused — verify then drop.
- **RBAC**: replace the 4-table matrix (awcontroll/awcontrollprop/awrolecontrollprop/awrolemenu/awrolebtn) with **spatie/laravel-permission**: `roles`, `permissions` (one permission per controller-action, e.g. `visits.delete`, plus the four button rights as `patients.create-button` etc.), `role_has_permissions`. Menus become frontend route guards driven by permissions, not a DB menu table — or keep a `menu_items` table only if runtime menu editing is a real requirement.
- **Positions/specializations**: `awroleposition`/`awrolespecialize` -> simple `positions` and `specializations` lookup tables (or enum) with `role` scope column; FK from `users`.
- **Settings**: explode the 75-column `programesetting` into a key-value `settings` table (e.g. spatie/laravel-settings) grouped as: clinic_identity, printing, features, history_sections, serials. Move `email`/`password` (plaintext SMTP creds) to `.env`/encrypted settings. The receipt serial counters (`serial_month`,`last_serial`,`last_refund_serial`) must move to a transactional `sequences` mechanism to avoid race conditions.
- **Multi-branch**: `branches` exists but is dead code (controller missing). In the ERP make `branches` a first-class model with `branch_id` (int FK, not varchar) on users/visits/financials; replace the magic '-1 = all branches' with a pivot `branch_user` table.
- **Devices/floors/device_tracking**: abandoned patient-flow feature. If queue/room tracking is wanted, redesign as `rooms`, `devices`, and a `patient_flow_events` table (polymorphic `trackable`) — otherwise drop all three.
- **messages**: empty and unused — drop; if staff chat is needed use a dedicated package. Patient chat (`chat` table, other module) should become a proper `conversations`/`messages` schema.
- **screen_slider + screen app**: keep as a `screen_slides` model + a public Angular display route consuming a websocket/polling "queue" endpoint; remove the runtime `ALTER TABLE` hack (`visits.end_visit` must be a real migration in the visits module).
- **Mobile API**: rebuild `mobileservices.php` as authenticated REST (Sanctum tokens). Current code has critical issues to NOT carry over: no auth on endpoints, `Access-Control-Allow-Origin: *`, SQL injection in `patientLogin` (string-concatenated mobile) and `getErpSave` (LIKE concat), ERP api_key/password hardcoded in `api_config.php`, hardcoded `http://localhost/obgy_new/` paths in chat.
- **JWT library**: bundled `php-jwt-master` is never called — drop.
- **Junk tables**: drop `table2`, `table3`, `tablename`, `testtbl1`, `testtbl2` — developer leftovers, all empty.
- **Setup utility**: do not migrate the truncate-database routine or the hardcoded `123456` password; replace with artisan commands + proper authorization.
- Proposed Laravel models: `User`, `Role`/`Permission` (spatie), `Position`, `Specialization`, `Branch`, `Setting`, `MenuItem` (optional), `ScreenSlide`, `HelpArticle`, `Room`/`Device`/`PatientFlowEvent` (optional redesign).

---


# 90 — Master ERD Analysis (OB/GYN Legacy Database)

Source: `_db/obgy_12-7-2024.sql` — **312 tables, zero declared foreign keys**. All relationships are implicit, inferred from column naming conventions and verified against JOIN statements in `core/controllers/*.php`.

## 1. Hub Entity Map

| Hub | PK | Referenced by | Via columns |
|---|---|---|---|
| `patients` | `id` | **73 tables** | `patientid`, `patient_id` |
| `awusers` | `user_id` (not `id`!) | **59 tables** | `doctorid` (~45 clinical tables), `userid`/`user_id`, `memberid` (board module), `assistantid`/`assistant2id`/`anasthetstsid`/`anasthetsts2id` (surgery team), `enddoctorid`, `sender_id`/`receiver_id`, `userid_edit`, `t1_userid`/`t2_userid` |
| `visits` | `id` | only `visits_updates` (audit) | `visitid` |
| `branches` | `id` | 2 tables | `awusers.branch_id`, `visits.branch_id` |
| `infertilitysheet` | `id` | **17 tables** | `infertilitysheetid` |
| `ancsheet` | `id` | 6 tables | `ancsheetid` |
| `mointoringsheet` | `id` | 3 child tables | `mointoringsheetid` |
| `drugs` | `id` | 13 tables | `drugid`, `drug_id`, `drugs_id` |
| `invests` | `id` | 7 tables | `investid` |

**Critical architectural finding:** clinical tables never reference `visits` — there is no `visitid` column anywhere except `visits`/`visits_updates`. All clinical data links to the patient via `patientid` + a date column. It is therefore impossible to attribute a clinical record to a specific reception visit.

## 2. Full Relationship List (child.column → parent.column)

### patients hub (73 referencing tables)
`visits.patientid`, `old_visits.patientid`, `visits_updates.patientid`, `patients_updates.patientid`, `endvisitreports.patientid`, `lastvisit.patientid`, `records.patientid`, `patientfiles.patientid`, `instruction.patientid`, `excelinfopatients.patientid`, `device_tracking.patient_id`, `recepittmp.patient_id` → `patients.id`

Clinical sheets: `ancsheet`, `gynasheet`, `infertilitysheet`, `ivfsheet`, `mointoringsheet`, `followupcard`, `followup`, `mainantenental`, `maingyna`, `gyna`, `infertility`, `gynainfertility`, `gynainfertilityplan`, `antenalvisit`, `examination` (all `.patientid` → `patients.id`)

History (`ph*`): `phobstetric`, `phmenstrual`, `phfamily`, `phcontraception`, `phpastmedical`, `phpastsurgical`, `phpastgynecological`, `phpastart`, `phpasticsi`, `phobstericterplace`, `previous_marriage`, `hus_previous_marriage` (`.patientid` → `patients.id`)

Imaging/procedures: `ultrasound`, `ultrasoundgyna`, `ultrasoundobst`, `gynaus`, `ustv`, `sonar`, `gtimage`, `hsg`, `hysteroscopy`, `laparoscopy`, `mrict`, `pathology`, `semen`, `hormon`, `operativedetails`, `op_wait_list`, `op_4d_list` (`.patientid` → `patients.id`)

Drugs/invest children (also carry `patientid`): `ancsheetdrugs`, `ancsheetinvest`, `gynadrugs`, `gynainvestigation`, `gynasheetdrugs`, `gynasheetinvest`, `infertilitydrugs`, `infertilitysheetdrugs`, `infertilitysheetinvest`, `mainantenentaldrugs`, `mainantenentalinvest`, `mainantenentalus`, `mointoringsheetdrugs`, `mointoringsheetinvestigation`, `otherinvestigationsrows`

Finance: `totalbalance.patientid`, `totalbalancepaids.patientid` → `patients.id`

### visits
- `visits.patientid` → `patients.id` *(verified: `JOIN patients ON visits.patientid = patients.id` in controllers)*
- `visits.detectionid` → `detections.id` *(verified: `LEFT JOIN detections ON visits.detectionid = detections.id`)*
- `visits.visit_period` → `visit_periods.id`
- `visits.user_id`, `visits.enddoctorid` → `awusers.user_id`
- `visits.branch_id` → `branches.id`
- `visits_updates.visitid` → `visits.id` (audit copy)

### Sheet parents → children
- `ancsheetdrugs.ancsheetid`, `ancsheetinvest.ancsheetid`, `ancnewvisit.ancsheetid`, `investigations.ancsheetid`, `registeration.ancsheetid`, `op_4d_list.ancsheetid` → `ancsheet.id`
- `newvisitg.gynasheetid` → `gynasheet.id`
- `infertilitysheetid` → `infertilitysheet.id` from 17 tables: `newvisit`, `tvs`, `dtvs`, `sis`, `folliculom`, `hormonalprofile`, `hormonalprofile2`, `semen2`, `semeninfertility`, `hsginfertility`, `hysteroscopyinfertility`, `laparoscopyinfertility`, `icsi`, `operations`, `wifep`, `awifep`, `wifeepc`
- `eprep.ivfsheetid`, `ovst.ivfsheetid` → `ivfsheet.id`
- `mointoringsheetdrugs.mointoringsheetid`, `mointoringsheetinvestigation.mointoringsheetid`, `mointoringsheetvisits.mointoringsheetid` → `mointoringsheet.id`
- `followupcarddrugs.cardid` → `followupcard.id`
- `followupvisit.followid`, `followupinvest.followid`, `followupexam.followid`, `followupdiagnosis.followid` → `followup.id`
- **Anomaly:** `gynasheetdrugs`, `gynasheetinvest`, `infertilitysheetdrugs`, `infertilitysheetinvest` have **no sheetid column** — they link via `patientid` only (de-facto one sheet per patient).

### Catalogs
- `{ancsheetdrugs, gynadrugs, gynasheetdrugs, infertilitydrugs, infertilitysheetdrugs, mainantenentaldrugs, mointoringsheetdrugs, followupcarddrugs, operativedetailsdrugs}.drugid` → `drugs.id`
- `pharmacystore.drug_id`, `receiptdrugs.drugs_id`, `importdetails.drug_id`, `importtdetails.drug_id` → `drugs.id`
- `{ancsheetinvest, gynainvestigation, gynasheetinvest, infertilitysheetinvest, mainantenentalinvest, mointoringsheetinvestigation}.investid` → `invests.id`; `invests.investcatid` → `investcats.id`
- `*.recepittmpid` → `recepittmp.id`; `receiptdrugs.receipt_id` → `recepittmp.id`; `recepittmp.doctor_id` → `awusers.user_id`; `recepittmp.drugstablename` = polymorphic table-name reference
- `importdetails.bill_id` → `importbill.id`

### Monitoring-sheet lookups (verified via LEFT JOINs in controller)
`mointoringsheet.{procedure, protocol, ejac, pesa, tese, sryo, hmg, agonist, hcg}` → `mointoringsheet{procedure|protocol|ejac|pesa|tese|sryo|hmg|agonist|hcg}.id`

### EAV (free-form investigations)
- `otherinvestigationsrows.patientid` → `patients.id`; `.doctorid` → `awusers.user_id`
- `otherinvestigationsvalues.investrowid` → `otherinvestigationsrows.id`
- `otherinvestigationsvalues.investid` → `otherinvestigations.id`

### Operations
- `operations.infertilitysheetid` → `infertilitysheet.id`
- `operativedetails.operationid`, `operativedetailsdrugs.operationid`, `operationids.operationid` → `operations.id`
- `operationids.{doctorid, assistantid}`, `operativedetails.{assistantid, assistant2id, anasthetstsid, anasthetsts2id}` → `awusers.user_id` (N:M surgery team)
- `operativedetails.{anasthesaid→anasthesa.id, generalid→generalanasthesa.id, jncisionid→jncision.id, midlineid→midlinejncision.id}`

### Ultrasound detail
- `gynausficils.gynausid` → `gynaus.id` *(verified JOIN)*
- `gynaus.mainantenatalid` → `mainantenental.id`
- `ultrasounddetail` / `ultrasoundobstdetail` → `ultrasound` / `ultrasoundobst` (per-fetus detail)
- `antenalvisit.mainantenentalid` → `mainantenental.id`; `.diagnosisid` → `diagnosisant.id`; `.complaintid` → `complaintant.id`

### Auth / RBAC (aw* family)
- `awusers.role_id` → `awrole.id`; `awusers.branch_id` → `branches.id`; `awusers.specialid` → `awrolespecialize.id`; `awusers.positionid` → `awroleposition.id`
- `awrolemenu.{role_id, menu_id}` → `awrole.id` ↔ `awmenu.id` (N:M)
- `awrolecontrollprop`, `awrolebtn` → `awrole` ↔ `awcontrollprop`/buttons (N:M)
- `awmenu.parent` → `awmenu.id` (self-referencing menu tree)

### Doctors board (b* family)
- `bsession.memberid` → `awusers.user_id`; `bsessionmembers.{sessionid, memberid}` = N:M; `bcomments.memberid`, `bmemberopenion.memberid`, `brequests.memberid` → `awusers.user_id`

### Misc
- `messages.{sender_id, receiver_id}` → `awusers.user_id`
- `device_tracking.{target_id + target_table}` = polymorphic reference to any table
- `presenthistoryanswers` joined via `gynaph.catid → presenthistoryquestions.id`, `gynaph.answerid → presenthistoryanswers.id` *(verified JOIN)*
- `patients` lookups: `wiftypeid→wifetypes`, `statuesid→wifestatus`, `wifeeducation/husbandeducation→education`, `wifejob→wifejobs`, `husbandjob→husbandjobs`, `wifebl/husbandbl→bloodtypes`, `husbandtypeid→husbandtypes` *(verified JOINs)*
- `ivfsheet.{ssemen, sseemen, sseemmen, sseemmeen}` → cloned lookup tables `ssemen`/`sseemen`/`sseemmen`/`sseemmeen` (one lookup table per column!)
- `registeration.{place2→place2.id, origin→origin.id}`

## 3. Mermaid ER Diagram (~25 central tables)

```mermaid
erDiagram
    BRANCHES ||--o{ AWUSERS : "awusers.branch_id"
    BRANCHES ||--o{ VISITS : "visits.branch_id"
    AWROLE ||--o{ AWUSERS : "awusers.role_id"
    AWUSERS ||--o{ VISITS : "visits.user_id / enddoctorid"
    AWUSERS ||--o{ PATIENTS : "patients.userid / doctorid"

    PATIENTS ||--o{ VISITS : "visits.patientid"
    PATIENTS ||--o{ ANCSHEET : "ancsheet.patientid"
    PATIENTS ||--o{ GYNASHEET : "gynasheet.patientid"
    PATIENTS ||--o{ INFERTILITYSHEET : "infertilitysheet.patientid"
    PATIENTS ||--o{ IVFSHEET : "ivfsheet.patientid"
    PATIENTS ||--o{ MOINTORINGSHEET : "mointoringsheet.patientid"
    PATIENTS ||--o{ FOLLOWUPCARD : "followupcard.patientid"
    PATIENTS ||--o{ PHOBSTETRIC : "phobstetric.patientid"
    PATIENTS ||--o{ TOTALBALANCE : "totalbalance.patientid"
    PATIENTS ||--o{ OTHERINVESTIGATIONSROWS : "otherinvestigationsrows.patientid"
    PATIENTS ||--o{ ULTRASOUND : "ultrasound.patientid"
    PATIENTS ||--o{ OPERATIVEDETAILS : "operativedetails.patientid"

    DETECTIONS ||--o{ VISITS : "visits.detectionid"
    VISIT_PERIODS ||--o{ VISITS : "visits.visit_period"
    VISITS ||--o{ VISITS_UPDATES : "visits_updates.visitid"

    ANCSHEET ||--o{ ANCSHEETDRUGS : "ancsheetdrugs.ancsheetid"
    ANCSHEET ||--o{ ANCSHEETINVEST : "ancsheetinvest.ancsheetid"
    ANCSHEET ||--o{ ANCNEWVISIT : "ancnewvisit.ancsheetid"
    ANCSHEET ||--o{ REGISTERATION : "registeration.ancsheetid"

    INFERTILITYSHEET ||--o{ NEWVISIT : "newvisit.infertilitysheetid"
    INFERTILITYSHEET ||--o{ TVS : "tvs.infertilitysheetid"
    INFERTILITYSHEET ||--o{ ICSI : "icsi.infertilitysheetid"
    INFERTILITYSHEET ||--o{ SEMEN2 : "semen2.infertilitysheetid"
    INFERTILITYSHEET ||--o{ OPERATIONS : "operations.infertilitysheetid"
    GYNASHEET ||--o{ NEWVISITG : "newvisitg.gynasheetid"

    MOINTORINGSHEET ||--o{ MOINTORINGSHEETVISITS : "mointoringsheetvisits.mointoringsheetid"
    MOINTORINGSHEET ||--o{ MOINTORINGSHEETDRUGS : "mointoringsheetdrugs.mointoringsheetid"
    FOLLOWUPCARD ||--o{ FOLLOWUPCARDDRUGS : "followupcarddrugs.cardid"

    DRUGS ||--o{ ANCSHEETDRUGS : "drugid"
    DRUGS ||--o{ MOINTORINGSHEETDRUGS : "drugid"
    DRUGS ||--o{ RECEIPTDRUGS : "drugs_id"
    INVESTS ||--o{ ANCSHEETINVEST : "investid"
    INVESTCATS ||--o{ INVESTS : "invests.investcatid"
    OPERATIONS ||--o{ OPERATIVEDETAILS : "operativedetails.operationid"

    OTHERINVESTIGATIONSROWS ||--o{ OTHERINVESTIGATIONSVALUES : "investrowid"
    OTHERINVESTIGATIONS ||--o{ OTHERINVESTIGATIONSVALUES : "investid"
```

## 4. Design Patterns

1. **Sheet pattern (core design):** parent sheet per specialty + cloned standard children: `*drugs` (drugid, drugtype, drugdos, forhusband, recepittmpid, recepitdrugid), `*invest` (investid, investresult, forhusband), sometimes `*us`/`*visits`. Instances: ancsheet, gynasheet, infertilitysheet, mointoringsheet, mainantenental, followupcard, gyna (gynadrugs/gynainvestigation), infertility (infertilitydrugs/infertilityinvest), operativedetails (operativedetailsdrugs). Structure duplicated per specialty rather than unified with a type column.
2. **Lookup pattern:** ~190 of 312 tables (61%) have ≤4 columns, shape `id + title/name + del`. One table per dropdown, even per-column clones (the `ssemen` quadruplet; 9 `mointoringsheet*` lookups; `copy*` hysteroscopy lookups; `semen2result*`/`semen2place*`; `lapar*` site lookups; `tvs*`, `hsg*` finding lookups).
3. **EAV pattern:** `otherinvestigations` (attribute catalog) / `otherinvestigationsrows` (entity row: patient+date+doctor) / `otherinvestigationsvalues` (value: investrowid+investid+value).
4. **Copy-table audit pattern:** `patients_updates`, `visits_updates` = full structural clones + `userid_edit`/`date_edit`.
5. **Inconsistent soft delete:** `deleted` / `del` / `tempdelete` / `status` used interchangeably across tables.
6. **Polymorphic references:** `device_tracking.target_id + target_table`; `recepittmp.drugstablename`.
7. **No visit linkage:** clinical records join to patient + date, never to a visit row.

## 5. Duplicate / Legacy / Abandoned Tables

| Group | Tables | Note |
|---|---|---|
| Dev leftovers | `table2`, `table3`, `tablename`, `testtbl1`, `testtbl2`, `patients_tmp` | No controller usage; safe to drop after verification |
| Quadruplet clone | `ssemen`, `sseemen`, `sseemmen`, `sseemmeen` | Identical lookups, one per `ivfsheet` semen-source column |
| Near-duplicate pairs | `antype`/`antypes`, `place`/`place2`, `importdetails`/`importtdetails`, `hormonalprofile`/`hormonalprofile2`, `semen`/`semen2`, `excelinfo`/`excelinfopatients` | Successive generations kept side by side |
| `copy*` family | `copycavity`, `copycx`, `copydil`, `copyintrod`, `copylostium`, `copyrostium`, `copyplace`, `copymcavity`, `copymcx`, `copymlostium`, `copymrostium` | Hysteroscopy lookups that began as copies and became canonical, each with an `m*` second copy |
| Historical archive | `old_visits` | Pre-branch/pre-period visits structure, read-only |
| Parallel ANC generations | `ancsheet`+`ancnewvisit` vs `mainantenental`+`antenalvisit` vs `followup`+`followupvisit` | Three coexisting antenatal modules, each with own drugs/invest children |
| Defined but inert | `patientfiles`, `help`, `messages`, partially `patients_updates` | Schema exists; active code bypasses or never writes (inferred from controllers) |

## 6. Verification Evidence (controller JOINs)

- `JOIN visits ON patients.id = visits.patientid` / `JOIN patients ON visits.patientid = patients.id` (multiple controllers)
- `LEFT JOIN detections ON visits.detectionid = detections.id`
- `LEFT JOIN mointoringsheet{tese|sryo|protocol|procedure|pesa|hmg|hcg|ejac|agonist} ON {lookup}.id = mointoringsheet.{col}`
- `JOIN gynaus ON gynaus.id = gynausficils.gynausid`
- `LEFT JOIN ancsheet ON ancsheet.id = list.ancsheetid` (op_4d_list)
- `JOIN presenthistoryquestions ON presenthistoryquestions.id = gynaph.catid`; `JOIN presenthistoryanswers ON presenthistoryanswers.id = gynaph.answerid`
- `LEFT JOIN wifejobs ON wifejobs.id = patients.wifejob`; `INNER JOIN husbandjobs ON patients.husbandjob = husbandjobs.id`
- `JOIN phpastsurgicaloperation ON phpastsurgicaloperation.id = phpastsurgical.surgicaloperation`; same for `phpastgynecologicaloperation`, `phpastarttype`
- `LEFT JOIN Operativedetails ON Patients.Id = Operativedetails.Patientid`
- External ERP link: `accountstree.name = client.clientname WHERE obygyPatientId = ...` (cross-database ERP sync)

---


# 95 — ERP Migration Blueprint (Legacy OB/GYN → Moon ERP Module)

Target: absorb the legacy PHP/Smarty OB/GYN system (312 tables, MySQL, zero FKs) as a new
**nwidart module `Modules/Obgy`** (working name; alt: `Modules/Clinics`) in the existing
Laravel + Angular Moon ERP, reusing the ERP patient master, LIS, inventory/pharmacy,
accounting and auth instead of the legacy cross-database cURL sync.

Net result: **312 legacy tables → ~85 new tables** (≈55 domain models + shared `lookups`,
pivots, and media), with ~40 dead/derived/dev tables dropped outright.

---

## 1. Proposed Module Structure

```
Modules/Obgy
├── Entities/            (grouped by domain below)
│   ├── Patients/        ├── Encounters/      ├── Clinical/
│   ├── History/         ├── Antenatal/       ├── Gynecology/
│   ├── Infertility/     ├── Ivf/             ├── Andrology/
│   ├── Imaging/         ├── Endoscopy/       ├── Operations/
│   └── Board/
├── Http/Controllers/Api (Sanctum-guarded REST, Angular consumer)
├── Services/            (EddCalculator, GravidaFormula, QueueService, CycleDayService…)
├── Events|Listeners/    (PatientRegistered, VisitInvoiced, PrescriptionDispensed,
│                         LabOrderPlaced, LabResultReceived…)
├── Jobs/                (ETL backfills, Excel exports via queues)
└── Database/Migrations|Seeders (lookup seeds exported from PRODUCTION db)
```

### Cross-cutting consolidation patterns

| Pattern | Legacy | New |
|---|---|---|
| Tiny lookup tables (~190 tables, shape `id+title+del`) | one table per dropdown, incl. clone quadruplet `ssemen/sseemen/sseemmen/sseemmeen` | single **`obgy_lookups`** table `(id, domain, parent_id, title_ar, title_en, sort, is_active, deleted_at)` + PHP Enums for clinically fixed sets (blood type, delivery mode CS/SVD/NVD, delivery term FT/PT, loss types) |
| Sheet + `*drugs` clone (10 tables) | ancsheetdrugs, mainantenentaldrugs, gynadrugs, gynasheetdrugs, infertilitydrugs, infertilitysheetdrugs, mointoringsheetdrugs, followupcarddrugs, operativedetailsdrugs, recorddrugs | **`visit_prescriptions`** (`Prescription` header, morphs `prescribable` → sheet/visit/cycle) + **`prescription_items`** (drug_id FK, dose, form, subject enum patient/husband, dispensed_qty) |
| Sheet + `*invest` clone (8 tables) | ancsheetinvest, mainantenentalinvest, gynainvestigation, gynasheetinvest, infertilityinvest, infertilitysheetinvest, mointoringsheetinvestigation, followupinvest | **`visit_investigations`** (`LabOrder` header, morphs `orderable`) + **`lab_order_items`** (lab_test_id FK → LIS catalog, result, subject, status) |
| CSV id columns (~40 columns) | e.g. `gyna.diagnosisid`, `tvs.tvsut`, `operations.operation`, `semen2resultscrotal` | real pivot tables per relation |
| 3 soft-delete dialects (`del`/`deleted`/`conditions`) | mixed | Laravel `SoftDeletes` (`deleted_at`) everywhere |
| Copy-table audit (`patients_updates`, `visits_updates`) | structural clones | `owen-it/laravel-auditing` |
| Generic AJAX writers (`Add/update/del` with client-supplied table/column) | mass-write + SQLi primitives | resource APIs + FormRequests + Policies (do **not** port) |

### 1.1 Patients & Registration domain

| New model | Table | Legacy tables | Key fields / notes |
|---|---|---|---|
| `Patient` | *(ERP patient master)* | `patients` (wife half) | The legacy patient **becomes** the ERP patient/client. `obgy_patient_profiles` holds OB/GYN-only fields: file_no (transactional sequence, not MAX+1), national_id (unique, DOB derived), risk, risk_type_id, marriage_date |
| `Spouse` | `obgy_spouses` | `patients` (husband\* columns) | 1-1 with patient: name, title_id, dob, national_id, education_id, job_id, blood_type enum, habits |
| `PreviousMarriage` | `obgy_previous_marriages` | `previous_marriage`, `hus_previous_marriage` | merged, `subject` enum(patient,spouse); fix latin1 |
| `ObstetricBaseline` | `obgy_obstetric_baselines` | `patients.pno/cs/ab/ectopic/vmodel/svd` | integer opening balances for G/P formula |
| `MedicalRecord` | `obgy_medical_records` | `records` (+`recorddrugs` → Prescription) | quick diagnosis/treatment lines |
| `DeliveryRegistration` | `obgy_delivery_registrations` | `registeration` | FK pregnancy_id, origin lookup, `coast`→`cost` decimal |
| `MediaAttachment` | (spatie medialibrary) | `patientfiles` + `upload/patientfiles/*` | real media table, signed URLs |
| lookups/enums | `obgy_lookups` | `wifejobs`+`husbandjobs` → JobTitle; `wifetypes`+`husbandtypes` → PersonTitle; `complaint`+`complaintant` → Complaint(context); `education`, `wifestatus`, `risktype`, `origin`; `bloodtypes` → Enum | merged dual-coded lists |
| **Dropped** | — | `patients_tmp`, `locations`, `patients_updates` (→ auditing), `table2/3`, `tablename`, `testtbl1/2` | dead |

### 1.2 Encounters, Scheduling & Billing bridge

| New model | Legacy | Notes |
|---|---|---|
| `Appointment` | `visits` (booking half) | patient_id, service_id, date, period_id, source enum(reception,mobile), status |
| `Encounter` | `visits` (queue half) + `followupcard` | queue_no, department/service, doctor, started/ended; clinical records FK to encounter_id (fixes "no visit linkage" flaw) |
| `Service` | `detections` | price list, synced to ERP item/service catalog |
| `VisitPeriod` | `visit_periods` | capacity enforced in DB transaction |
| `ClinicClosure` | `vacations` | actually enforced in booking |
| `TreatmentClosure` | `endvisitreports` | per patient/department |
| `PatientLedgerEntry` | `totalbalance` + magic codes in `visits` (`detectionid` -99 installment / 999 settle / 9999 refund) | explicit `entry_type` enum: charge, payment, refund, package_credit, installment; balance computed |
| `Invoice` / `Payment` | derived from `visits` money columns (cash/visa/discount/remaining) | posted to ERP accounting via events |
| **Dropped** | `old_visits`, `visits_updates`, `lastvisit` (derive last sheet by query), `totalbalancepaids` | dead/derived |

### 1.3 Clinical Core (shared services)

| New model | Legacy | Notes |
|---|---|---|
| `ClinicalExamination` | `examination` | vitals typed decimal; no auto-create-on-render |
| `ExaminationFinding` + options in `obgy_lookups(domain=exam.*)` | `examinationhead/chest/abdomen/pelvis/extremitis`, `examinationlocalse`, `generalbreast/hirsutism/thyroid` (9 tables) | finding rows split free-text vs catalog id |
| `Diagnosis` + pivot `encounter_diagnoses` | `diagnosis` + `diagnosisant` | merged with `specialty` column; replaces CSV columns |
| `HistoryQuestion` / `HistoryAnswerOption` / `PatientHistoryAnswer` | `presenthistoryquestions`, `presenthistoryanswers`, runtime `gynaph` | **gynaph exists only in production — export before ETL** |
| `Prescription` / `PrescriptionItem` | the 10 `*drugs` clones | see consolidation table; feeds pharmacy via `PrescriptionDispensed` event |
| `LabOrder` / `LabOrderItem` | the 8 `*invest` clones + EAV trio (`otherinvestigations`, `otherinvestigationsrows`, `otherinvestigationsvalues`) | EAV custom tests folded into catalog as ad-hoc tests |
| `InstructionTemplate`, `Referral` | `instruction`, hospital referral letters | templates + standalone referral entity |
| **Dropped** | `investigations` (legacy fixed-column, empty), `excelinfo` (license), `excelinfopatients` (→ queued export job) | |

### 1.4 Patient History

`MenstrualHistory` (phmenstrual), `ObstetricHistory` + child `ObstetricBaby`
(phobstetric triple-baby columns normalized; **also absorbs `wifep` + `awifep`** so there is
one obstetric history source; G/P formula becomes an accessor service),
`ContraceptionHistory` (phcontraception), `PastMedicalHistory` (phpastmedical, `for_spouse`),
`PastSurgicalHistory`, `PastGynecologicalHistory`, `PastArtCycle` + `PastArtCycleIcsiDetail`
(phpastart + phpasticsi via partentid), `FamilyHistory` (phfamily).
Catalogs `medicalhistory/surgicalhistory/familyhistory` + `*dm` husband clones + `phpast*` lists
merge into `obgy_lookups` with `for_spouse` flag. Termination mode → fixed Enum (G/P logic
depends on ids 1..5).

### 1.5 Antenatal

| New model | Legacy | Notes |
|---|---|---|
| `Pregnancy` | **`ancsheet` + `mainantenental` + `followup`** (3 parallel generations) | one file per pregnancy: lmp/edd DATE, gravida snapshot, status enum(active,ended); ETL matches generations by patient+lmp/done/endpreg |
| `AntenatalVisit` | `ancnewvisit` + `antenalvisit` + `followupvisit` | GA computed accessor (never stored); pivots for diagnosis/complaint CSV |
| `AntenatalUltrasound` | `mainantenentalus` + ancsheet T/TT-scan columns | scan_type enum incl. anomaly & 4D |
| `FourDScanBooking` | `op_4d_list` | explicit status instead of bit flags t11/t21 |
| `PregnancyLoss` (+ type/treatment lookups) | `wifeepc`, `wifeepctype`, `wifeepcttt` | re-parented to patient_id (resolved via infertilitysheet) |
| `ExternalProvider` | `wifeobst` + `wifeepcobst` | type doctor/hospital/abroad, heavy dedup |
| `ClinicalNote` | `followupdiagnosis` + `followupexam` + `followupinvest` | one table, `note_type` enum |
| Lookups/Enums | `placenta` (cleaned), `wifemodeofd`→Enum, `wifetypeofd`→Enum | `antype/antypes/pla2cen` move to IVF; **drop `anprotocol`** (orphaned) |
| **Production-only** | `followupdrugs` runtime table | export schema+data from production |

### 1.6 Gynecology

`GynaVisit` (gyna; CSV diagnosis/complaint → pivots), `GynaSheet` (gynasheet + maingyna),
`GynaSheetVisit` (newvisitg), `GynaUltrasound` (gynaus) + `GynaUltrasoundFollicle`
(gynausficils, side enum), `InfertilityProfile` (gynainfertility + `gyna.inf_*` columns),
`InfertilityPlan` (gynainfertilityplan). Nine empty structural-clone dictionaries
(menstrual*, local*, sheet complaint/diagnosis, infertility type) → `obgy_lookups`,
seeded from production. Drop legacy `gynasheet00` path.

### 1.7 Infertility

`InfertilityFile` — merges gen-1 `infertility` (+ dated children `infertilitydiagnosis`,
`infertilitylmp`, `infertilityinvest`, `infertilitynotes`) with gen-2 `infertilitysheet`
(64 flat columns) into one file per patient with structured child tables; the 15 `ttt*`,
`typeinf`, `sheetlocation`, `husbndiagnosis1-3`, `sheethusband/wife` lists → `obgy_lookups`
+ pivots (replacing comma-separated varchar ids). Obstetric history rows (`wifep`/`awifep`)
move to History domain (§1.4).

### 1.8 IVF / ICSI / IUI

| New model | Legacy | Notes |
|---|---|---|
| `IvfCycle` | **`ivfsheet` + `mointoringsheet`** | attempt_no, cycle_type enum(fresh,frozen,iui,monitoring), protocol/procedure lookups, status; partial unique index = one active cycle per patient |
| `IvfCycleVisit` + `FollicleMeasurement` | `ovst` + `mointoringsheetvisits` + `folliculom` | follicle grid (side, diameter_mm 10–20, count) replaces 26 cryptic columns |
| `OocyteRetrieval`, `SemenSample`, `EmbryoTransfer`, `Cryopreservation`, `EndometrialPrep` (+`epreplps`), `CycleOutcome` | stage columns of ivfsheet + `eprep` + runtime `sefo` | stage sub-tables |
| **Dropped** | `icsi` (derived archive → query/View; ETL orphan rows into IvfCycle), 15 lookups (`icsiprotocol`, `icsisemen`, `icsiss`, `icsiresult`, `icsiplace`, 9× `mointoringsheet*`, `epreplps`) → `obgy_lookups`, `ssemen` quadruplet | seed from production (empty in dump) |

### 1.9 Andrology

`SemenAnalysis` (semen + semeninfertility, numeric columns, WHO flags),
`AndrologyInvestigation` (semen2 wide → long rows: test_type, date, place_id, result_id,
pivot for multi-value scrotal US), `HormoneResult` (hormon + hormonalprofile +
hormonalprofile2 → long rows) + `HormoneDefinition` (unit, reference range, links to LIS).
9 `semen2result*/place*` + `semenplace/sementype` lookups → `obgy_lookups`.
**Drop orphan `sseemmeen`/`sseemmen`** (real list is `icsisemen`).

### 1.10 Imaging

`ImagingStudy` (unified header for `ultrasound`, `ultrasoundobst`, `ultrasoundgyna`,
`tvs`, `dtvs`, `sis`, `hsg`, `hsginfertility`, `mrict`, `sonar` headers; study_type +
approval status), `FetalScanFinding` (ultrasounddetail + ultrasoundobstdetail, per fetus),
`GynaScanFinding` (ultrasoundgyna detail), `AnnotatedDiagram` + `DiagramMarker`
(gtimage/gtdetail, relative coordinates), `MediaAttachment` (sonar images/videos from
`upload/sonar`, `upload/sonarvedio`). 16 vocabulary tables (tvs*, hsg*, usn/usaf/usplace,
dtvs*, four_d) → `obgy_lookups(domain)`; CSV result columns → pivots.

### 1.11 Endoscopy

`EndoscopyProcedure` (laparoscopy + hysteroscopy + laparoscopyinfertility +
hysteroscopyinfertility; `kind` enum, optional infertility_file_id, cost decimal),
`EndoscopyFinding` (site + category finding/management), `EndoscopyTerm` → `obgy_lookups`
(replaces 24 `lapar*`/`copy*` tables) + pivot for multi-select. `jncision`/`midlinejncision`
→ self-referencing `incision_types` in Operations domain.

### 1.12 Operations & Deliveries

`OperativeNote` (operativedetails; team CSV → `operative_note_staff` pivot with role enum),
`SurgicalBooking` (op_wait_list + procedures pivot), `InfertilityOperation` (operations;
operation/histopath CSV → pivots), `Procedure` catalog (operation, title/name merged),
`AnesthesiaType` (anasthesa + generalanasthesa chain), `Hospital` (hospitalnames),
`HistopathologyFinding` (histopath), `PathologyResult` (pathology), `Location`
(place + place2 merged with type column). Drop `operationids`. OR drug usage
(operativedetailsdrugs) → `Prescription` with context = operative note.

### 1.13 Investigations catalog → LIS

`LabTestCategory` (investcats, 22 seeded) and `LabTest` (invests, 276 items, add code/unit/
reference range) become a **mapping layer onto the existing LIS catalog** — each legacy
investid maps to a LIS test id; unmapped items are created in LIS. Custom EAV definitions
(otherinvestigations) become ad-hoc LIS tests.

### 1.14 Pharmacy → ERP inventory

`Drug` (drugs ~2000 items, dedup + normalize), `DrugCategory`/`DosageForm` lookups,
`Supplier` (from importbill.suppliers free text), `PurchaseInvoice`/`PurchaseInvoiceItem`
(importbill/importdetails; add prices, expiry, batch), `StockMovement` (pharmacystore →
movement ledger, non-negative computed balance), `Prescription` dispensing replaces
`recepittmp` (+ table-name polymorphism) and `receiptdrugs`. **Long-term:** drug catalog and
stock delegate to ERP inventory item master; the module keeps only the prescription layer.
Drop `drugdos`, `importtdetails`.

### 1.15 Medical Board

Rebuild from scratch (tables empty in dump; verify production): `BoardRequest`
(**adds mandatory patient_id — biggest legacy gap**), `BoardSession`, `BoardSessionMember`,
`BoardMemberOpinion`, `BoardComment` (nested), council roles via ERP permissions
(drop `councilstaff` + hardcoded positionid 1–3). Enums for request/session/opinion states.

### 1.16 Platform → ERP core (replaced, not migrated)

- `awusers` → ERP users (bcrypt hashes are portable); `awrole` + 5 permission tables
  (~590 actions / 3000 grants) → `spatie/laravel-permission`; menu (`awmenu`) → Angular
  route guards driven by permissions.
- `programesetting` (75-column singleton) → key/value settings; credentials → `.env`;
  receipt counters → transactional sequences.
- `branches` becomes real FK + `branch_user` pivot (kills the `-1 = all` varchar).
- Mobile API (`mobileservices.php`, unauthenticated, SQLi) → rewritten as documented
  Sanctum REST; patient password moves to a dedicated guard.
- Waiting-screen (`screen_slider`) → small `ScreenSlide` model + queue websocket feed.
- Drop: dev tables, `devices/floors/device_tracking` (incomplete feature — keep schema design
  note for HMS), `messages`, JWT lib, DB-dump tool with hardcoded password.

---

## 2. Integration Contracts with Moon ERP

| # | Legacy mechanism | New contract |
|---|---|---|
| 2.1 **Patient master** | cURL sync to external ERP DB (`client.obygyPatientId`), per-save, synchronous | **Eliminated.** The OB/GYN patient *is* the ERP patient/client (`patient_id`). One-time ETL reconciles `obygyPatientId` mappings; `PatientRegistered/Updated` domain events for any downstream consumers. |
| 2.2 **LIS** | none (paper requests; free-text results in `*invest.investresult`) | `LabOrderPlaced` event → LIS order with mapped test ids; LIS publishes `LabResultReceived` → result lands on `lab_order_items` and renders inside the clinical sheet. Catalog governed by mapping table `obgy_lis_test_map`. |
| 2.3 **Pharmacy / inventory** | direct writes to `recepittmp`/`pharmacystore` from clinical code | `PrescriptionDispensed` event → ERP inventory issue + sale line; drug catalog mastered in ERP item master, OB/GYN keeps prescribing metadata (dose, subject). Purchases handled entirely by ERP procurement. |
| 2.4 **Accounting** | `visits` doubles as journal with magic codes; sellbill pushed by cURL (`obygyVisitId`) | `VisitInvoiced` / `PaymentReceived` / `RefundIssued` events post Invoice/Payment/ledger entries to ERP accounting; `Service` price list mastered in ERP; daily-close report reads the ledger, not visits. |
| 2.5 **Auth / RBAC** | `login_system` lib + 5 `aw*` tables + open mobile endpoint | ERP single user store, Sanctum tokens, spatie permissions (one permission per legacy controller action + named button rights), branch scoping via pivot. |
| 2.6 **HMS (planned)** | — | `Encounter`, `OperativeNote`, `SurgicalBooking`, `Hospital`, `incision_types`, and the waiting-queue service are designed module-agnostic so the HMS module can consume them. |

---

## 3. Migration Phases

| Phase | Scope | Size |
|---|---|---|
| **P0 — Foundation** | Module skeleton, `obgy_lookups` framework + Enums, auditing, ETL toolkit (date/encoding/orphan cleaners), **production exports**: lookup values, runtime tables (`sefo`, `gynaph`, `followupdrugs`), media inventory. Auth/RBAC mapping to spatie. | **M** |
| **P1 — Patients & Front desk** | Patient↔ERP master merge, Spouse split, baselines, appointments/queue/periods/closures, Service catalog, ledger + accounting events. First go-live for reception. | **L** |
| **P2 — Clinical core & catalogs** | Examination + findings, diagnosis merge, history engine, shared `visit_prescriptions`/`visit_investigations`, LIS catalog mapping, pharmacy/inventory integration, patient history (ph\*) domain. | **L** |
| **P3 — Antenatal & Gynecology** | Pregnancy (3-generation merge), antenatal visits/US, 4D bookings, pregnancy loss, gyna visits/sheets/US. Highest data volume + trickiest dedup. | **L** |
| **P4 — Infertility, IVF, Andrology** | InfertilityFile merge, IvfCycle unification + stage sub-tables + follicle grid ETL, semen/hormone normalization. Clinically richest, needs clinician validation of lookup semantics (`ssemen` quadruplet, F-column order). | **L** |
| **P5 — Imaging, Endoscopy, Operations** | ImagingStudy unification, findings pivots, endoscopy term dictionary, operative notes/bookings, pathology. | **M** |
| **P6 — Board, Follow-up cards, Reports, Mobile** | Board rebuild, instruction/referral templates, statistical reports as query layer + Angular dashboards, authenticated mobile REST. | **M** |
| **P7 — Media migration & Cutover** | 4.8 GB upload tree → media library (checksummed, staged rsync, signed URLs), parallel-run reconciliation, final delta ETL, legacy read-only freeze, decommission cURL sync + old DB. | **M** |

Sequencing rule: each phase ships behind module permissions; legacy stays read-only
reference until P7 sign-off. ETL is re-runnable (idempotent upserts keyed on legacy ids
kept in `legacy_id`/`legacy_table` columns).

---

## 4. Data Migration Risk Register

| # | Risk | Impact | Mitigation |
|---|---|---|---|
| R1 | **Zero declared FKs** across 312 tables; all joins implicit | orphan rows surface as ETL failures | Pre-flight orphan audit per relation (90-erd list is the contract); quarantine orphans to `etl_orphans` with reason; clinicians review samples |
| R2 | **Clinical rows never reference visits** (patientid+date only) | cannot attribute records to encounters | Heuristic matcher (same patient + same date + department) creates Encounter links; unmatched rows get synthetic "historical encounter" |
| R3 | **Duplicate generations** (3 antenatal paths, 2 infertility, ivfsheet vs mointoringsheet, semen×4, hormone×3) | double counting / record collisions | Deterministic merge keys (patient+lmp / patient+date); generation precedence rules signed off by doctors; merge report per patient |
| R4 | **Varchar dates** (`Y/m/d`, `0000-00-00`, empty, free text) | silent data corruption | Defensive parser with reject-queue; NULL for invalid; ETL stats per column |
| R5 | **CSV multi-value id columns** (~40) with dangling ids | broken pivots | Split + validate against lookups; dangling ids logged and mapped to "unknown (legacy)" term |
| R6 | **Arabic encoding**: mixed latin1/utf8mb4 tables, mojibake seeds | unreadable patient/lookup data | Convert via latin1→binary→utf8mb4 round-trip per table; visual QA sample of Arabic names; full target DB utf8mb4_unicode_ci |
| R7 | **Dump ≠ production**: lookup tables empty in the 2024 dump; RedBean runtime tables (`sefo`, `gynaph`, `followupdrugs`) and runtime columns absent | seeds and ETL built on wrong schema | Mandatory fresh production export before each ETL rehearsal; schema diff gate in pipeline |
| R8 | **4.8 GB media** referenced by folder-naming convention only (patient files, sonar images/videos) | broken links, partial copies | Checksum inventory → staged rsync → DB-backed media records → verify counts/bytes; old tree kept read-only 6 months |
| R9 | **Magic financial codes** (`detectionid` -99/999/9999, self-FK visitid) | wrong balances in accounting | Transactional re-derivation into ledger entry types; reconcile totals per patient vs legacy screens before accounting hand-off |
| R10 | **MAX+1 file numbers, duplicate national IDs / drafts (`done=0`)** | identity collisions in patient master merge | Dedup pass (national id, phone, name+DOB) with human review queue; drafts not migrated unless they carry clinical data |
| R11 | **Misspelled/inconsistent columns** (`husdandname`, `coast`, `registeration`, varchar ids next to int ids) | mapping errors | Central column-mapping dictionary in repo, used by all ETL jobs and reviewed once |
| R12 | **Security debt must not travel**: generic table/column AJAX writers, unauthenticated mobile API, SQL-concatenated reports, hardcoded dump password | reintroducing vulnerabilities | Hard rule: no legacy endpoint ported; all new APIs FormRequest+Policy; secrets to `.env`; pen-test gate at P6 |

---

*Companion Arabic HTML fragment: `sections/95-erp-mapping.html`.*

---

---

# ADDENDUM (2026-06-11): Second Deployment — MED / Green Nature Hospital Edition

> Discovered after the original analysis: a NEWER FORK of the same codebase at
> `/home/medgreennatureco/public_html/med` (live: med.greennature.com.sa), configured as a
> center/hospital. ~70 new controllers, 178 new tables (extracted from code — live DB not queried),
> shared clinical controllers grown 2-5x, ~20 old sheet controllers removed/replaced.
> Sections 70-78 below document each new domain; 96 amends the §95 migration blueprint.



# Addendum 70 — MED: Inpatient & OR Management

**Source deployment:** `/home/medgreennatureco/public_html/med` (live: med.greennature.com.sa) — newer CENTER/HOSPITAL edition.
**Baseline:** original 312-table dump `/home/amrtechogate/public_html/obgy/_db/obgy_12-7-2024.sql`.
**Method:** read controllers fully; tables/fields inferred from RedBeanPHP (`R::dispense`/`xdispense`/`findAll`/`findOne`/`getAll`) and raw SQL. Read-only; no DB queries.

Controllers in scope (all under `core/controllers/`): `operations_rooms.php`, `residence_rooms.php`, `operation_types.php`, `operations_reserve.php`, `operations_reserve00.php`, `operations_calendar.php`, `operations_reports.php`, `operationreport.php`, `roomsreport.php`, `today_list.php`. Smarty views under `core/views/obgy/`.

---

## 1. Architecture: TWO parallel booking engines

There are two independent reservation engines writing to different tables for the same conceptual "operation booking":

### A. Hourly engine (older) — `operations_reserve.php` / `operations_reserve00.php`
- Day split into 30-min (OR) / 60-min (residence) slots.
- `reserve()` (operations_reserve.php:292-348) creates a header row in **`operations_main`**, then ONE row per hour in **`doctors_reserves`** (OR) and **`residence_reserves`** (residence), all linked via `operation_main_id`.
- `operations_reserve00.php` is the prior variant: room set filtered by `detections.operations_rooms` (CSV of room IDs or `-1`=all), slot grid bounded by room `start_time`/`end_time`. The newer `operations_reserve.php::search()` (lines 182-228) replaced this with a per-floor view querying `operations_rooms`/`residence_rooms` and overlaying `doctors_reserves`/`residence_reserves`.

### B. Calendar engine (newer) — `operations_calendar.php`
- FullCalendar with room resources. ONE row per operation in **`operations_rooms_cal`** holding BOTH the OR slot (`room_id`,`start_time`,`end_time`) and the residence slot (`res_room_id`,`res_start_time`,`res_end_time`).
- `events()` (lines 111-235) colors by floor/VIP and detects conflicts by self-join overlap test (`start_time < end AND end_time > start AND room_id = ? AND id != ?`).
- `saveOperation()`/`updateOperation()` write the single row; `getOperationTime()` (line 306) auto-fills duration/cost/instructions from the operation type.

**Implication:** two unsynchronized sources of truth for the same domain concept.

---

## 2. Critical schema finding — extensions on EXISTING tables

Verified against the dump (`grep`/`sed` on the CREATE statements):

- **`detections`** in the original dump has only `id, title, detectionval, del` (dump line 4791+). The MED edition reuses it as the **operation-types** catalog by adding (inferred from `operation_types.php::add()` lines 116-127): `for_operation`, `operation_time`, `residence_time`, `operations_rooms` (CSV of OR ids or `-1`), `cost_details`, `instructions`, `sort`. Selected via `del != 1 and for_operation = 1`.
- **`visits`** in the original dump (line 11469+) has NO inpatient/OR columns. The MED edition adds (inferred from usage across `roomsreport.php`, `operationreport.php`, `today_list.php`): `for_department`, `for_doctor`, `for_husband`, `room_no`, `res_room_no`, `bed_no`, `enterance`, `p_exit`, `res_enterance`, `res_enterance_date`, `res_p_exit`, `recep_enterance`, `recep_p_exit`, `operation_attend`, `operation_result`, `operation_time`, `op_card_add`, `rec_card_add`, `today_list`, `today_list_dept`, `today_list_sort`, `in_clinic`, `clinic_entered`, `child`.
- Operations department = magic constant `for_department = 13`; operation doctors via `awrole(id=13).related_dr_depts` (CSV of role ids).

---

## 3. NEW tables (absent from 312-table dump)

| Table | Created in | Purpose / key fields |
|---|---|---|
| `operations_rooms` | operations_rooms.php:113 | OR rooms. `name, start_time, end_time, floor_no, branch_id, create_date, deleted` |
| `residence_rooms` | residence_rooms.php:105 | Inpatient/residence rooms. `name, start_time, end_time, floor_no, create_date, deleted` |
| `operations_rooms_cal` | operations_calendar.php:260 | Calendar-engine operation row (OR+residence combined). See §4 |
| `operations_main` | operations_reserve.php:306 | Hourly-engine booking header. `doctor_id, the_date, operation_types_id, operation_hours, residence_hours, operations_rooms_id, residence_rooms_id, create_date` |
| `doctors_reserves` | operations_reserve.php:323 | Per-hour OR slot. `doctor_id, operations_room_id, hour_text, the_date, operation_type, operation_main_id` |
| `residence_reserves` | operations_reserve.php:339 | Per-hour residence slot. `doctor_id, residence_room_id, hour_text, the_date, operation_type, operation_main_id` |
| `operation_data` | operationreport.php:738 | Per-visit operation "board" (anaesthesia/nursing). `visit_id, deleted` (+ staff cols, inferred) |
| `clinic_rooms` | today_list.php:93 | Clinic rooms for today's-list department routing. `today_list, id` |
| `clinic_reserves` | today_list.php:275 | Clinic on-call doctor schedule. `room_id, reserve_day_no, reserve_hour, doctor_id, is_active, deleted` |

`operations_rooms_cal` full field set (operations_calendar.php:260-281, operations_reports.php usage):
`room_id, res_room_id, patient_id, patient_name, doctor_id, assistant_id, operation_type_id, date, start_time, end_time, res_start_time, res_end_time, cost, cost_details, instructions, for_husband, vip, notes, contacted_patient, contacted_user_id, sms_message, created_by, updated_by, created_at, updated_at`.

Note: `patients_childs`, `investigationresults`, `raysresults_img` are also referenced (operationreport.php) and are NOT in the dump, but they belong to the patient/lab/rays domains rather than inpatient; flagged here for cross-domain reconciliation.

---

## 4. Workflows traced end-to-end

**Calendar booking:** `index()` → `rooms()`/`residenceRooms()` (JSON resources) → `events()` (read+color+conflict) → `searchPatients()` → `saveOperation()` (single `operations_rooms_cal` row) → `getOperation()`/`updateOperation()`/`removeOperation()`.

**Hourly booking:** `index()` → `search()` (OR slot grid vs `doctors_reserves`) → `residence()` (residence grid vs `residence_reserves`) → `reserve()` (writes `operations_main` + N×`doctors_reserves` + N×`residence_reserves`) → `removeRoom()`.

**Live residence occupancy** (`roomsreport.php::index()` lines 57-174): joins `residence_rooms` to current `visits` where `for_department=13 AND res_enterance_date IS NOT NULL AND res_p_exit IS NULL`; enriches with patient/doctor/operation-type and entry/exit times. `show()` filters by `floor_no`.

**Live OR occupancy** (`operationreport.php::operation_data()` lines 552-636): per-floor OR rooms, marks room busy if a `visits` row's `enterance<=now<=p_exit` today; auto-refresh 300s.

**Today's list** (`today_list.php`): patients of the day grouped by `clinic_rooms`, drag-and-drop ordering (`today_list_sort`), current-in-room tracking (`in_clinic`/`clinic_entered`), on-call doctor from `clinic_reserves` by weekday/hour.

**Operations reports** (`operations_reports.php`): `index()` OR grid floors [8,6]; `resReport()` residence grid floors [8,7,6]; `listReport()`/`opReport()` grouped by operation type + doctor (raw SQL join `operations_rooms_cal`⨝`detections`); `supportReport()` + `contactedPatient()`/`sendSms()` patient-contact follow-up.

---

## 5. Pricing

Loaded from the operation type: `detections.detectionval` (cost), `cost_details` (free text), `operation_time`/`residence_time` (durations). On calendar save these are copied into `operations_rooms_cal.cost`/`cost_details` (operations_calendar.php:273-274). No independent billing engine in this module — pricing is descriptive and type-bound (inference).

---

## 6. Security / tech debt (must address before migration)

- **SQL injection:** pervasive string-concatenation of inputs into `WHERE` (e.g. operations_reserve.php:227 `role_id in ( $related_dr_depts )`; operationreport.php:390 `operations_rooms_id in (...)`; operations_reports.php:359 `doctor_id = $doctorId`).
- **Hardcoded secret:** smsmisr gateway password in `operations_reports.php::sendSmsMisr()` lines 302-303 — rotate + move to env.
- **Auth bypass:** `operationreport.php::operation_details()` line 644 sets `$_SESSION['user_id']=123` and disables permission checks.
- **Magic constants:** `for_department=13`, `awrole id=13`, floors 6/7/8, `detections.id in (55,152,217,228)` (operations_reports.php:364).
- **Hard deletes:** calendar/hourly removals use `R::trash` (no soft-delete audit), unlike the `deleted` flag used elsewhere.

---

## 7. ERP/HIS roadmap impact

1. This module is the ready seed for the HIS **Inpatient/Bed Management (IPD)** unit — but requires first-class Room/Bed/Admission/OR-Slot entities instead of overloading `visits`/`detections`.
2. Unify the two booking engines into one Reservation entity with explicit status and DB-level conflict prevention.
3. Promote "operation type" out of `detections` into a dedicated `OperationType` with a real pricing/billing link.
4. Model residence/bed occupancy as an explicit time-bounded admission record, not `res_enterance_date`/`res_p_exit` columns on the visit.
5. Add an OR staff entity (surgeon/assistant/anaesthetist/nurse) — embryonic in `operation_data` + `assistant_id`.
6. Today's-list + live occupancy prove the need for a real-time bed/room occupancy board with patient location tracking.
7. Security remediation (SQLi, exposed secret, auth-bypass endpoints) is an acceptance gate.

---


# Addendum 71 — MED: Embryology Lab & Cryo (Full IVF Wet-Lab)

**Deployment analyzed:** `/home/medgreennatureco/public_html/med` (live `med.greennature.com.sa`), a newer CENTER/HOSPITAL edition.
**Baseline:** original 312-table dump `/home/amrtechogate/public_html/obgy/_db/obgy_12-7-2024.sql`. Every table named below was cross-checked against that list — **all embryology/cryo tables are NEW**. The original only had a flat `ivfsheet` plus `semen`/`icsisemen`; it had **no wet-lab workflow tables at all**.

## Controllers (read in full)
- `core/controllers/tanks.php`
- `core/controllers/embryoslab.php` (~1670 lines)
- `core/controllers/embryofreezing.php`
- `core/controllers/spermfreezing.php`
- `core/controllers/ovumpickupembryotrans.php`
- `core/controllers/embryology.php`
- `core/controllers/ivf_reports.php` (print/aggregation; read-only)

---

## 1. Cryo Tank Inventory — two parallel models

Selected by `programesetting.tanks_view` (1 or 2); per-user toggle `awusers.tanks_settings`.

### Model A — pre-generated 3-level grid (`tanks` / `tankcells`)
`tanks.php::add()` dispenses `tanks` (`name`, `tank_type`) then nested loops generate every cell up front into `tankcells`:

| tank_type | up_level_name | cell_name | cell_part_name | generated | UI total |
|---|---|---|---|---|---|
| 1 | 1..10 | 1..20 | 1..5 | 1000 | 20 |
| 2 | 1..10 | 1..20 | 1..5 | 1000 | 100 |
| 3 | 1..10 | 1..30 | 1..10 | 3000 | 300 |

`tankcells` fields (inferred): `tank_id, up_level_name, cell_name, cell_part_name, patient_id (0=empty), created_at`.
Addressing = **Level (cane) → Canister → Position**. Display aggregation in `cells()` / `tankLabCells()` rolls up occupancy across the 3 levels (`tanks/cells.html`, `tanks/lab_cells.html.tpl`).

Reservation: `saveTankLabCell()` sets `tankcells.patient_id` then writes a custody row. Removal: `removePatientCell()` resets `patient_id=0` and writes a custody row.

### Model B — colour-coded fixed positions (`tanks_general` / `embryoslab_tank_cells`)
Used when `tanks_view == 1`. `tankLocationSetting()` seeds `tanks_general` with `location IN ('A','B')` × `btn_no 1..21`. Each `tanks_general` row holds 9 colour slots `row1_color..row9_color` (FK to `colors.color_name`/`color_letter`) and `rows_letter`.
`storeTankCell()` writes `embryoslab_tank_cells`: `embryoslab_id, location, btn_no, row_no, col_no, cell_no, tank_cell (composite string "A_3_2_1_4"), user_id, date_add, userid_edit, date_edit, deleted`.
Conflict check (`saveTankLocation()`) blocks a cell already held by another `embryoslab_id` ("reserved to another Patient").

`embryoslab.php` + `spermfreezing.php` both contain identical `loadCellsDisplay()` / `tankStyle2()` helpers that join `tankcells` → `tanks` filtered by `patient_id`.

## 2. Chain of Custody & Witnessing
- **`tankcellhistory`** (`tanks.php::removePatientCell` / `saveTankLabCell`): `tank_id, cell_id, patient_id, action_type ('Add patient'|'Remove patient'), done_date, by_user_id`. This is the custody ledger.
- **Double witnessing (partial):** `spermfreezingreport.embryologist1` + `embryologist2`; lab sheet groups `injection_embrologist`, plus `icsi_by`, `embryologist_id`, `freeze_embryologist_id` (+ `*_tb` flags marking IDs sourced from the `embryologist` table vs `awusers`).
- **Edit trail:** `embryoslab_tank_cells.{user_id,date_add,userid_edit,date_edit}`; soft-delete `deleted=1` everywhere (no physical delete on custody data).
- **Cancellation trail:** `embryoslab.cancelled_user` / `cancelled_reverse_user` (`embryoslab.php::updateit`).
- Embryologist role = `awusers.role_id = 11`; clinician = `role_id = 4`. External embryologists live in table `embryologist` (referenced with a `*` prefix and merged via SQL UNION).

## 3. End-to-end workflows

1. **OPU** — `ovumpickup` (`ovumpickupembryotrans.php:183`): `patientid, date, consultant, anaesthetist, nurse, operation, postoperative, complications`.
2. **Operation follow-up** — `followup_operation` (`R::xdispense` line ~1160): `patient_id, nurse_id, anaesthetist_id, clinician_id, embryologist_id, ut_cx, avf_rvf, easy_difficult, postoperative, complications, date`. Lookups `ut`, `avf`, `easydifficult`, `operationotherth`, plus `follow_*` lists.
3. **Semen analysis** — `embryologyreport` (`embryology.php:180`): `patient_id, date, sperm_source, count, motility, morphology`.
4. **Lab sheet head** — `embryoslab` (one draft per patient: `not_confirm=0` → `1` on `savenow()`). Fields: `patient_id, visit_id, clinician_id, referred_dr_id, refer_doctor_tb, media, oil, incubator (CSV-in-column), injection_embrologist, icsi_by(_tb), embryologist_id(_tb), freeze_embryologist_id(_tb), date, cancelled`.
5. **Grading day2→day6** — `embryoscoring` (`scoring()` / `fertilizedData()`): row per embryo `itr`, columns `media1..6, oil1..6, incubator1..6, embrologist1..6`, plus `stage`/`grade`, `deleted`. Row count derived from fertilized count; rows soft-recycled (`itr=0` reuse).
6. **Selection** — `embryoselection` (`embryoslab_id, deleted`), lookups `embryo_result`, `embryo_emb`.
7. **Transfer (ET)** — `embryotransfer` (1 per sheet): `embryoslab_id, clinician_id, embryologist_id(_tb), et_day` + lookups `embryocathetertype, embryodifficulty, embryoblood, embryojetplace, embryolah, embryotype`. Independent OPU-flow transfer: `embryotransferovum` (`patientid, date, day, consultant, nurse, embryologist, no_of_embryos, quality, notes`).
8. **Embryo freezing** — `embryofreezingreport` (`embryofreezing.php:621`): `patient_id, date, embryologist, ref_by_prof_dr, no_of_straws, no_of_embryos, day_of_freezing, kit, device, lot_no, location, note, collapsed, completed, deleted`. Detail rows `embryo` (`embryofreezing_id, stage1..6, grade1..6`). Thaw rows `embryothawing` (`embryofreezing_id, no_of_embryos, thawing_date, recovered, lost, e_t_date, embryos, grades, embryol_oigist, clinician, cathetr`). NOTE: `updateit()` does `DELETE FROM embryothawing/embryo` then re-inserts on every save.
9. **Sperm freezing** — `spermfreezingreport` (`spermfreezing.php:204`): `patient_id, date, sperm_source, ref_by_dr_id, count, motility, a_plus_b, morphology_abn, comment, samples_no, freezing_media_id, freezing_protocol_id, purpose_of_freezing, color_of_pen, embryologist1, embryologist2, location`. Lookups `freezingmedia`, `freezingprotocol`.

## 4. NEW lookup/reference tables
`stage, grade, growthmedia, growthoil, growthincubator, growthco2, oocytequality, oocytezona, oocytecytoplasm, oocytepvs, oocyteother, embryocathetertype, embryodifficulty, embryoblood, embryojetplace, embryolah, embryotype, embryo_device, embryo_result, embryo_emb, embryo_lot_select, embryo_catheter_select, embryo_tank, embryo_kit_type, injection, injection_location, sperum_source, semen_process, freezingmedia, freezingprotocol, kit, device, cathetr, colors, detections, cancel_reason, embryologist` — all NEW. Most carry `name` + `deleted`; edited via generic `R::xdispense($table)` endpoints (`addToTable`, `addRow`, `addNewRow`).

## 5. Data-quality / security notes
- **SQL injection**: pervasive string concatenation, e.g. `R::exec('DELETE from embryoslab WHERE id = ' . $id)`, `"select * from awusers where user_id=$ref_dr_id->refer_doctor"`. Must be parameterized before migration.
- **CSV-in-column**: `media/oil/incubator/injection_embrologist` stored comma-joined in one column (`updateit()` joins JSON arrays with commas) — no junction tables.
- **Capacity mismatch**: generator creates 1000–3000 `tankcells` while UI shows 20/100/300.
- Soft-delete preserved across custody tables (good for audit).
- `spermfreezing.php::showallreports()` has a broken query (`SELECT* spermfreezingreport from where ...`) — dead/buggy path.

## 6. ERP/HIS blueprint impact
- New first-class **Embryology & IVF Lab** module absent from the 312-table baseline.
- Model cryo inventory as `Tank → Canister/Cane → Position` with `tankcellhistory` as the custody-ledger seed (CAP/JCI/ESHRE chain-of-custody).
- Unify the two parallel storage models (`tankcells` grid vs `embryoslab_tank_cells` colour) into one source of truth for specimen location.
- Promote **double-witnessing** to a mandatory field on freeze/thaw/transfer/relocation events.
- Normalize ~30 lookup tables into a single `lookup(type,value)` reference.
- Link `embryoslab.visit_id` to the HIS treatment cycle to close clinical→lab→reporting loop (`ivf_reports.php`).
- Fix SQLi and decompose CSV-in-column fields as migration prerequisites.
- Integrate lab consumables (`kit, device, cathetr, lot_no`) with the ERP inventory/procurement module (stock + expiry).

---


# 72 — MED: Extended Endoscopy Suite (Addendum)

> Source deployment: `/home/medgreennatureco/public_html/med` (live `med.greennature.com.sa`) — a newer CENTER/HOSPITAL edition of the OB/GYN codebase family.
> Cross-checked against the original 312-table dump `/home/amrtechogate/public_html/obgy/_db/obgy_12-7-2024.sql`.
> Read-only analysis; schema inferred from RedBeanPHP calls and raw SQL only.

## Files analyzed (controllers)

- `core/controllers/endoscopy.php` (337 lines) — GI gastroscopy report per patient.
- `core/controllers/endoscopy_template.php` (135 lines) — single default template (id=1) for gastroscopy.
- `core/controllers/colonoscopy.php` (341 lines) — GI colonoscopy report per patient.
- `core/controllers/colonoscopy_template.php` (137 lines) — single default template (id=1) for colonoscopy.
- `core/controllers/laparoscopic.php` (199 lines) — named template bank (gyne laparoscopy).
- `core/controllers/hystroscopic.php` (203 lines) — named template bank (gyne hysteroscopy).
- `core/controllers/oscopic.php` (691 lines) — patient-applied scope reports + unified oscopic report + dynamic lookups.

Supporting views: `core/views/obgy/{endoscopy,colonoscopy,hystroscopic,laparoscopic,oscopic}/...`
JS template injector: `core/views/obgy/_assets/custom/oscopic.js`.

## Three distinct template-engine patterns

### Pattern A — Single default template + patient record (GI endoscopy)
`endoscopy.php` / `colonoscopy.php`. A `*_template` table holds ONE default row (`R::findOne('endoscopy_template', 'id = 1')`). On the add form, `template_data` is assigned to Smarty and each anatomical field is pre-filled with the default text (e.g. `value="{$template_data->esophagus}"` — `core/views/obgy/endoscopy/endoscopy/add.html.tpl:95`). The doctor edits then saves to the per-patient table.

- Template table also stores print header/footer via `uploadupdate('header_print', ...)` (`endoscopy_template.php:83-84`).
- Patient table fields (gastroscopy): `patient_id, exam_date, indication, anesthesia, procedure, esophagus, cardio_esophageal_junction, stomach, pylorus, duodenum, conclusion, signature, created_at, updated_at` (`endoscopy.php:96-109`).
- Colonoscopy adds: `consent, preparation, instrument, preparation_2, withdrawal_time, dre, findings, plan` (`colonoscopy.php:99-109`).
- Images: `endoscopy_images` / `colonoscopy_images` with `image, patient_id, {scope}_id, create_date` (`endoscopy.php:238-242`).

### Pattern B — Named template bank + AJAX injection (gyne scopes)
`laparoscopic.php` / `hystroscopic.php` store MANY rows, each a full template keyed by `template_name`. The `render()` action returns a template row as JSON:

```php
// laparoscopic.php:134-142
public function render() {
    $id = filter_input(INPUT_POST, 'template_id');
    $laparoscopic = R::getRow('select * from laparoscopic where id = ?', [$id]);
    echo json_encode($laparoscopic);
}
```

The form-side injector (`oscopic.js:6-27`) listens on `#laparoscopic_template` change, POSTs to `laparoscopic.php?ac=render`, and writes each field via jQuery `.val()`. Hysteroscopy mirror at `oscopic.js:29-52`.

- `laparoscopic` fields: `template_name, entry, entry_procedures, uterus(_procedures), right_tube(_procedures), left_tube(_procedures), right_ovary(_procedures), left_ovary(_procedures), uterosacrals(_procedures), peritoneum(_procedures), recommendations` (`laparoscopic.php:76-94`).
- `hystroscopic` fields: `template_name, entry(_procedures), external_os, cervical_canal, internal_os, uterine_cavity, endometrium, right_ostium, left_ostium, other (+ *_procedures), recommendations` (`hystroscopic.php:77-96`).

**BUG:** `oscopic.js:19-20` fills `left_ovary` / `left_ovary_procedures` from `response.right_ovary` / `response.right_ovary_procedures` (copy-paste error — left ovary never populated from template).

### Pattern C — Patient-applied report + dynamic lookups (oscopic.php)
`oscopic.php` is the integration controller. It applies a chosen template to a real patient, persisting to `patient_laparoscopic` / `patient_hystroscopic` (template fields + `patient_id, created_at, template_id, diagnosis (CSV via implode), diagnosis_text`) — `oscopic.php:122-151, 299-323`.

It also defines a **unified report** `oscopic_report` (`reportAdd`/`getFilter_input` at `oscopic.php:534-689`): `patient_id, created_at, diagnosis (CSV), operation_id, operation_text, required_examination_id, required_examination_text, specimen_id, specimen_text, diagnosis_text, husband`. The `husband` flag switches age/doctor source between wife and husband records (`oscopic.php:644-661`).

**Dynamic, table-name-parameterized lookups** replace dozens of static lookup tables:

```php
// oscopic.php:450-459 — table name comes straight from POST
public function addToTable() {
    $table_name = filter_input(INPUT_POST, 'table_name');
    $new_data = R::xdispense($table_name);
    $new_data->name = $name; $new_data->deleted = 0;
    echo R::store($new_data);
}
```

Used for `oscopic_operations`, `oscopic_required_examinations`, `oscopic_specimens` (soft-deleted via `deleted = 1`; `oscopic.php:512-519`). `editModal`/`updateModal`/`deleteModelItem` (`oscopic.php:461-496`) likewise take `tableName`/`colName` from POST.

**SECURITY:** `addToTable`/`updateModal`/`deleteModelItem` pass user-supplied table and column names directly to RedBean with no allow-list — arbitrary-table write risk. Must be constrained on migration.

## How this differs from the original obgy lookup approach

The original 312-table dump stores one tiny lookup table per anatomical dropdown:
- Laparoscopy: `laparpelvis, laparplace, laparro, laparrt, laparul, laparlo, laparlt, laparmlo, laparmlt, laparmpelvis, laparmro, laparmrt, laparmut`, plus `laparoscopy`, `laparoscopyinfertility`.
- Copy/hysteroscopy: `copycavity, copycx, copydil, copyintrod, copylostium, copymcavity, copymcx, copymlostium, copymrostium, copyplace, copyrostium`, plus `hysteroscopy`, `hysteroscopyinfertility`.

The MED edition collapses that "one-table-per-field" sprawl into: (A) a single default template row per GI scope, (B) a named template bank per gyne scope injected by AJAX, and (C) three soft-deletable dynamic lookups managed through a generic CRUD. It also extends scope coverage **beyond gynecology** into GI (gastroscopy + colonoscopy), which the original system did not have.

## Entity inventory (NEW vs known)

| Table | Status | Purpose | Key inferred fields | Relations |
|---|---|---|---|---|
| `endoscopy` | NEW | Patient gastroscopy report | patient_id, exam_date, indication, anesthesia, procedure, esophagus, cardio_esophageal_junction, stomach, pylorus, duodenum, conclusion, signature | → patients |
| `endoscopy_template` | NEW | Single default (id=1) + print header/footer | anatomical defaults, header_print, footer_print | feeds endoscopy |
| `endoscopy_images` | NEW | Scope images | image, patient_id, endoscopy_id, create_date | → endoscopy, patients |
| `colonoscopy` | NEW | Patient colonoscopy report | + consent, preparation, instrument, preparation_2, withdrawal_time, dre, findings, plan | → patients |
| `colonoscopy_template` | NEW | Single default (id=1) | colonoscopy fields + header/footer | feeds colonoscopy |
| `colonoscopy_images` | NEW | Scope images | image, patient_id, colonoscopy_id, create_date | → colonoscopy, patients |
| `laparoscopic` | NEW | Named gyne laparoscopy template bank | template_name + anatomical (_procedures) + recommendations | template source |
| `hystroscopic` | NEW | Named gyne hysteroscopy template bank | template_name + anatomical (_procedures) + recommendations | template source |
| `patient_laparoscopic` | NEW | Template applied to patient | laparoscopic fields + patient_id, created_at, template_id, diagnosis(CSV), diagnosis_text | → patients, laparoscopic, diagnosis |
| `patient_hystroscopic` | NEW | Template applied to patient | hystroscopic fields + patient_id, created_at, template_id, diagnosis(CSV), diagnosis_text | → patients, hystroscopic, diagnosis |
| `oscopic_report` | NEW | Unified operations/scope report (wife+husband) | patient_id, created_at, diagnosis(CSV), operation_id/text, required_examination_id/text, specimen_id/text, diagnosis_text, husband | → patients, diagnosis, 3 lookups |
| `oscopic_operations` | NEW | Dynamic operations lookup | name, deleted | generic CRUD |
| `oscopic_required_examinations` | NEW | Dynamic exams lookup | name, deleted | generic CRUD |
| `oscopic_specimens` | NEW | Dynamic specimens lookup | name, deleted | generic CRUD |
| `diagnosis` | known | Shared diagnosis list (filtered `conditions = 0`) | name, conditions | shared |
| `patients` / `awusers` | known | Patients / users-doctors | dateofbirth, refer_doctor, doctor1, h_doctor1 / user_id | referenced everywhere |

## ERP/HIS impacts

1. Add 14 NEW tables (3 template patterns) to the migration blueprint — none exist in the original 312.
2. Scope of the HIS widens from pure OB/GYN to a multi-specialty Endoscopy Suite (adds GI gastroscopy + colonoscopy).
3. Adopt a unified template engine in the new ERP instead of the original per-field lookup sprawl (`lapar*`/`copy*`).
4. Generic dynamic-lookup CRUD with soft delete replaces static lookup tables — but needs a table/column allow-list (security).
5. Normalize CSV `diagnosis` columns into a proper many-to-many link table.
6. Centralize image archives (`endoscopy_images`, `colonoscopy_images`) under a PACS/attachment service.
7. Fix the `left_ovary` injection bug in `oscopic.js` before porting to avoid carrying a clinical defect forward.

---


# Addendum 73 — MED: Andrology Clinic & Radiology Department

**Scope:** Newer/extended "center/hospital" edition discovered at `/home/medgreennatureco/public_html/med` (live: med.greennature.com.sa). Compared against the original 312-table dump (`/home/amrtechogate/public_html/obgy/_db/obgy_12-7-2024.sql`). All schema knowledge below is extracted from controller code and Smarty views only — no live DB was queried.

**Controllers analyzed (full reads):**
- `core/controllers/and_visits.php` (1182 lines)
- `core/controllers/and_history.php` (456 lines)
- `core/controllers/and_examination.php` (197 lines)
- `core/controllers/addrays.php` (218 lines)
- `core/controllers/raysdept.php` (646 lines)
- `core/controllers/radiation.php` (698 lines)
- Helper: `core/controllers/invsdept_common.php` → `loadRays()` (line 3567)
- Views: `core/views/obgy/andrology/*` (examination.html, history.html, visits.html, printrows.html, newrowUS.html)

---

## 1. Dedicated Andrology Clinic (NEW domain)

The original system treated the male side as **semen/hormone lab rows inside the infertility sheet** (`semen`, `semen2`, `icsisemen`, `ssemen`, `hormonalprofile*` — see report section 07). The MED edition adds a **standalone andrology clinic** with its own visit, history, and exam screens, all keyed by `patientid`.

### `and_visits.php` → `andvisits`
- `index()` (line 71): loads `andvisits` filtered by `deleted=0 and patientid=…`, decodes CSV `diagnosisid`/`complaintid` against `anddiagnosis`/`andcomplaint` (lines 122-125). Merges legacy `diagnosistxt` into `complaint` then clears it (lines 99-104).
- Embeds five sub-workflows, each its own NEW table:
  - **Rays order** → `andvisitsrays` via `addray()` (lines 499-558); `forhusband` flag (1=husband/0=wife).
  - **Investigation order** → `andvisitsinvestigation` via `addinvestigation()` (lines 663-744); supports bundle offers `invoffer`/`invofferdetails` (lines 705-721, `offer_id`/`inv_id`).
  - **Prescription** → `andvisitsdrugs` via `loadPrescriptionDetails()`/`showprescription()` (lines 275-294, 849-926); `drugid` → `drugs`.
  - **Male ultrasound** → `andvisitsus` + follicles `andvisitsficils` via `loadusrow()`/`savefocil()` (lines 982-1101). Types `uscompleteAnd`/`ussimpleAnd`. Follicle row: `andvisitsusid`, `name`, `length`, `width`, `volume`, `type` (0=left/1=right), `sysdate`.
  - **Semen** → `andvisitssemen`; **Genetic testing** → `andvisitsgenetictesting`.
- `printselected()` (lines 1105-1172): decodes genetic `chromosome` (1=normal,2=abnormal) and `azf` (1=normal,2=deletion).
- Semen field names (inferred from `views/obgy/andrology/visits.html` colName attrs): `conc`, `nf` (motility), `ph`, `viscosity`, `aggl`, `vol`, `pus`.
- Generic `update()` does single-column inline edits; `del()` is soft delete (`deleted=1`).

### `and_history.php` → 5 sub-tables
- `index()` (line 71) calls `history_data`, `mProblems_data`, `erection_data`, `ejaculation_data`, `contraception_data`.
- `andhistory` (lines 371-395): `durationid`→`infertileduration`, `fertiletype`, `smoking`/`smokinglevel`, `inflammation`, `trauma`, `marriage`, `stopped`, `notes`. `abortionno` auto-computed by counting `phobstetric` rows with `obstermination='4'` (lines 383-389).
- `andmedicalproblems` → `medicaldisease` (**KNOWN** table reused) via `diseaseid`.
- `anderection` → `erectiondisease` (NEW); `andejaculation` → `ejaculationdisease` (NEW); `andcontraception` → `andcontratype` (NEW).
- Each lookup has an AJAX add endpoint: `addduration`, `adddmdisease`, `addderectiondisease`, `addejaculationdisease`, `addcontraception` (lines 168-286).

### `and_examination.php` → `andexamination`
- Bilateral exam, colName attrs from `views/obgy/andrology/examination.html`: `testesl`/`testesr`, `epidl`/`epidr`, `cordl`/`cordr`, `vasl`/`vasr`, `notes`, `date`.
- `newexam()` dispenses a row; inline `update()`; soft `del()`.

---

## 2. Radiology Department Workflow (`raysdept.php`) — NEW

Standalone department driven by shared `visits.for_department = 5`. Order → Perform → Report → Archive.

- **Order:** `newvisit()` (line 232) sets `fordepartment=5`, lists `rayscats`/`rays`; `savenewvisit()` (line 312) inserts `visits` then calls `sendToDept()` (line 358) which dispenses `raysresults` per selected ray: `rayid`, `raytype` (invType), `visitid`, `patientid`, `doctorid=0`, `deleted=0`, `forhusband`, `date`. Internal requests (`invType==1`) set `internalrequestid`/`internaltable` (lines 375-378). For `for_department==12` it writes `investigationresults` instead (line 369).
- **Worklist:** `index()` (line 73) raw SQL joins `raysresults`⋈`visits`⋈`patients`⋈`patients_childs`, filtered `raysresults.status=0` (today). Subject resolution by `visits.for_husband` (1=husband / 0=wife / 2=child via `patients_childs.child_name`/`gender`).
- **Perform/Report:** `showdetails()` (line 105) lists `raysresults` for visit + `raysresults_img`. `update()` (line 199): writing `rayresult` stamps `date` and `doctorid`; if `raytype==1` propagates result to the internal source table (`R::load($obj->internaltable, $obj->internalrequestid)`).
- **Images:** `upload()` → single `raysresults.rayimg`; `uploadMulti()` (line 469) → multiple `raysresults_img` rows (`raysresults_id`, `rayimg`, `deleted`) via `R::xdispense`. Files in `upload/rayimg`.
- **Finish/Archive:** `finishRequest()` (line 589) sets `status=1`; `archive()` (line 391) lists `status=1`; `printresults()` (line 523) prints report, resolves referring doctor via `patients.refer_doctor` or last visit `for_doctor`.
- **`loadRays()`** in `invsdept_common.php` (line 3567): per-patient rays history across `visits` where `for_department=5`, attaching `raysresults` and `raysresults_img`.

### `addrays.php` — Ray catalog admin
- `rayscats` (`name`, `displayorder`, `deleted`) and `rays` (`name`, `rayscatid`, `price`, `range`, `favorite`, `deleted`). `displayorder` groups display into 5 buckets (mirrored in `and_visits.loadRaysDetails()` lines 167-213 and `raysdept.newvisit()` lines 255-300).

---

## 3. `radiation.php` — NOT radiation therapy

Internal controllername is `instruction`; operates on the **KNOWN** `instruction` table for patient instructions + hospital transfer letters. Uses NEW `operationinstructions` (`insttext`) and KNOWN `hospitalnames`. Transfer letter aggregates obstetric/medical/surgical/gyn history (`phobstetric`, `phpastmedical`, `phpastsurgical`, `phpastgynecological`). Included only to clarify it is unrelated to diagnostic radiology.

---

## NEW vs KNOWN cross-check

Verified against `grep CREATE TABLE` of the original dump. The original contains semen-lab tables only: `semen`, `semen2`, `icsisemen`, `ssemen`, `semeninfertility`, `semenplace`, `sementype`, `semen2result*`, `semen2place*` — but **no** andrology-clinic tables.

**NEW:** `andvisits`, `andhistory`, `andexamination`, `andmedicalproblems`, `anderection`, `andejaculation`, `andcontraception`, `andcontratype`, `anddiagnosis`, `andcomplaint`, `infertileduration`, `erectiondisease`, `ejaculationdisease`, `andvisitssemen`, `andvisitsgenetictesting`, `andvisitsus`, `andvisitsficils`, `andvisitsdrugs`, `andvisitsrays`, `andvisitsinvestigation`, `rays`, `rayscats`, `raysresults`, `raysresults_img`, `investigationresults`, `patients_childs`, `operationinstructions`, `invoffer`, `invofferdetails`.

**KNOWN (reused):** `medicaldisease`, `instruction`, `hospitalnames`, `visits`, `invests`, `investcats`, `detections`, `patients`, `awusers`, `drugs`, `programesetting`, `wifetypes`, `husbandtypes`, `phobstetric`.

---

## ERP/HIS plan impacts

1. Add an **independent Andrology Clinic domain** (17+ `and*` tables) to the migration blueprint — not an appendix of the infertility sheet.
2. Model a **generic Orders/Results service-department** pattern (`raysresults`+`investigationresults` over `visits.for_department`) instead of sheet-bound results.
3. Introduce a **Subject/beneficiary** model: one order can target husband/wife/child (`forhusband`, `visits.for_husband`, `patients_childs`).
4. Model the **polymorphic internal-request link** (`raytype`/`internaltable`/`internalrequestid`) as a flexible Order↔SourceRef relation.
5. Wire **priced catalogs + bundles** (`rays`/`rayscats`, `invoffer`/`invofferdetails`) to the billing module.
6. Centralize **user-extensible lookup management** (many AJAX-addable reference tables).
7. Exclude `radiation.php` from radiology scope (it is transfer-letter/instructions).
8. Plan an **attachment/PACS-lite store** (or DICOM integration) for `raysresults_img` / `upload/rayimg` (inference).

---


# Addendum 74 — MED: Branches, Regions & ERP Sync

**Scope:** Newer "center/hospital" edition discovered at `/home/medgreennatureco/public_html/med` (live `med.greennature.com.sa`). This digest covers the multi-branch / multi-region core, the Organizations (contracted parties) layer, the `syncstructure` DB-migration tool, the Moon-ERP bridge (`erp_common`), and the external `api_web` API. Read-only analysis; schema inferred from RedBeanPHP/raw SQL only. Cross-checked against the original 312-table dump `/home/amrtechogate/public_html/obgy/_db/obgy_12-7-2024.sql`.

## Files analyzed
- `core/controllers/beauty_branches.php` — Branch CRUD on `branches`.
- `core/controllers/branch_common.php` — Branch permission helpers (`branchInfos`, `branchInfosVisit`, `showBranchCol`).
- `core/controllers/regions.php` — `regions` / `sub_regions` tree CRUD.
- `core/controllers/center_reports.php` — Temporary-cases & "Center's Knowledge" cross-branch reports.
- `core/controllers/commons.php`, `fn_common.php` — Generic update/delete and helper functions.
- `core/controllers/erp_common.php` — Moon-ERP bridge + NasLab integration + PDF.
- `core/controllers/syncstructure.php` — Old↔new DB structure sync / DBA maintenance.
- `core/controllers/api_web.php` — External JSON API (lab-device results + organizations gateway).
- `core/controllers/excelread.php` — One-shot Excel patient import.

---

## 1. Multi-branch / multi-region core

Activation flags live in `programesetting`:
- `branches` — **already existed** in original dump (`programesetting` DDL line in dump contains `erpdb`, `erpdbsave`, `branches`).
- `regions`, `organizations` — **NEW columns** (set in `programesetting.php:316-317,237`; absent from dump).

Per-user branch scoping uses the **NEW column** `awusers.related_branches` (CSV of branch IDs). Logic centralized in `branch_common.php`:
- `branchInfos($branchesActivate, $smarty)` (lines 6-49) — report screens.
- `branchInfosVisit(...)` (51-89) — visit/entry screens (also assigns `allBranches`).
- `showBranchCol(...)` (91-113) — whether to render the branch column.

Inferred rule (from `branch_common.php:16-40`): `related_branches == 0/empty` → all branches; single value → locked to one branch (no selector); multiple → selector restricted via `R::findAll('branches', 'id IN (...)')`.

Report filtering helpers `branchSql()` / `billBranchSql()` (`financialreport.php:116-130`, also in `financiallab.php`, `cashtransferdectorreport.php`) append `and visits.branch_id = ?` / `and bill_paying.branch_id = ?`; sentinel `-1` = all branches.

`beauty_branches.php` — full CRUD on `branches`. **`branches` is KNOWN** (dump line 4549) with original cols `id, name, created_at, updated_at`; this edition writes the **NEW columns** `phone1, phone2, location` (`beauty_branches.php:74-76`). Optional push to external site `royal-fc.net/appv/visit_details/branches` via `sendToSite()` (currently commented out).

### Regions hierarchy
`regions.php`: `regions` (parent) → `sub_regions` (`sub_regions.region_id`, `regions.php:80`). Generic inline-edit via `R::xdispense($tableName)` where the table name comes from POST (`addRow()` line 101; `update/updateajax/del` operate on arbitrary `tableName`). **Both tables NEW.**

## 2. Organizations (contracted parties / insurers)

NEW entity. Linked to a visit via **NEW column** `visits.organization_id` (`visits.php:768,919,960`). Per-organization patient number stored in junction `organizations_patient_no` (`patients.php:1743,4135`). Activation `programesetting.organizations` has 3 inferred levels (1/2/3) gating org binding & debt disclosure (`visits.php:919,941`).

External gateway in `api_web.php`:
- `ac=orglogin` (632) → `checkUserLogin()`; `password_verify` against `organizations.user_password_hash`; returns `org_id`, `org_id` (org number), `organization_name`.
- `ac=orgpatientinfo` (681) → `checkPatientVisits()`; joins `visits.organization_id` + `organizations_patient_no` (`organization_id`, `patient_id`, `patient_no`); counts delivered results.

## 3. syncstructure — DB structure migration tool

`syncstructure.php` is a DBA one-shot, not a live sync. `sync()` adds `R::addDatabase("old"/"new")` and iterates new-DB tables from `information_schema.tables`:
- `checkTableExist()` → if present, `compareTables()` diffs columns via `information_schema.columns`; on diff, `copyTable()` builds `{table}_temp LIKE new.{table}`, inserts old rows by shared columns, then `DROP`+`RENAME`.
- If missing, `createTable()` runs `SHOW CREATE TABLE` DDL on the old DB.

**Operational hazard:** `sync()` is gated by `if ($i == 203)` (line 120) — limits execution to one table (manual debug residue). Must be removed before real use.

Hardcoded DB/environment names reveal multiple deployments (inference): `dr_app_old`, `obgy_app` (`check_missing`), `obor_db` (`check_auto_increment`), `alielhad_obgy` (`improve`). Maintenance methods: `check_missing`, `check_auto_increment`, `set_auto_increment`, `calcBalanceAll` (recompute `patients.balance` from `visits`), `improve` (copy specific columns between DBs).

## 4. Moon-ERP bridge — erp_common

`orderFromErp($erpApiLink, $data=0)` (`erp_common.php:3`) cURLs `localhost/erp/controllers/api_web.php?do={endpoint}` with header `Open-Key: {hardcoded JWT}`. **The only active call** is client-debt retrieval:
- `visits.php:3120` `getrestvaluesP()` branches on `programesetting.erpdb`: if set → `getrestvaluesPErp()` → `orderFromErp('clientdept&id=0&royalid='.$patientId)` (line 3137); else local debt calc from `visits` (`getrestvaluesPRoyal`).
- Target ERP is **Moon ERP** (inference from `localhost/erp/...` path and `clientdept` endpoint).

Also in `erp_common.php`: NasLab integration `getPatientLink()` → `api.naslab.gt4it.com/api/patient`, stores result URL in **NEW column** `patients.inv_link` (line 90); TCPDF `generatePdf()`; file upload `sendFileWithData()`. JWT tokens are hardcoded (security risk).

## 5. api_web — external lab-device API

`api_web.php` (JWT auth currently commented out, lines 12-39). Endpoints:
- `ac=databysample` → `dataBySampleId()` joins `investigationresults` + `invests` + **NEW** `lab_devices_ranges` (`investid↔device_id`, `send_code/receive_code`); returns tests + patient demographics.
- `ac=saveresults` / `ac=saveresultsMulti` → `saveResultsCommon()` logs every call to **NEW** `saveresultslog` (cols: `sample_id, device_id, device_name, result, receiving_code, sending_code, lab_devices_ranges_id, investigationresults_id, element_name, element_table_name, element_table_id, create_date`). `invTypeSwitch()` routes by `special_inv_type` (16 types) to NEW per-type tables `investigationresults_blood/urine/stool/culture/esr/cross/times/pt/aborh/custom/lipid/egfr/semen`. Derived calcs: `lipidCalculations()`, `egfrCalculations()`.
- `ac=orglogin` / `ac=orgpatientinfo` → organizations gateway (section 2).

## 6. excelread — one-shot import

`excelread.php` reads `excel_backups/newcases.xlsx` via PHPExcel, dispenses `patients`, auto-creates doctors in `awusers` (`checkDrTable`/`addToDrTable`) and `nationality` rows (`checkNatTable`). Hardcoded `row <= 8789`. Migration script, not an operational feature.

---

## Entity classification (vs original 312-table dump)

| Table / Column | Status | Notes |
|---|---|---|
| `branches` | KNOWN (dump:4549) | extended with NEW cols `phone1, phone2, location` |
| `regions` | NEW | `id, name, deleted` |
| `sub_regions` | NEW | `region_id → regions.id` |
| `organizations` | NEW | `org_id, organization_name, user_name, user_password_hash` |
| `organizations_patient_no` | NEW | junction `organization_id, patient_id, patient_no` |
| `lab_devices_ranges` | NEW | `investid, device_id, invest_child, special_inv_type, send_code, receive_code` |
| `saveresultslog` | NEW | device-result audit log |
| `investigationresults_*` (13 types) | NEW | per-type lab result detail |
| `bill_paying` | NEW | payment vouchers (`branch_id`); not in dump |
| `knownfrom` | NEW | patient-acquisition sources |
| `nationality` | NEW | auto-created on import |
| `awusers.related_branches` | NEW column | branch scoping CSV |
| `awrole.custom_detections`, `awrole.is_doctor` | NEW columns | per-dept tests + doctor flag |
| `programesetting.regions`, `programesetting.organizations` | NEW columns | center-mode flags |
| `programesetting.branches`, `erpdb`, `erpdbsave` | KNOWN columns | already in dump |
| `visits.organization_id` | NEW column | org link (`visits.branch_id` was KNOWN, dump line 33 of DDL) |
| `patients.branch_id`, `patients.inv_link` | NEW columns | branch + NasLab URL |

## ERP / HIS plan impact
1. Original 312-table model is incomplete: ≥7 new entities + ≥5 new columns on known tables must extend the Modules/Obgy migration blueprint to cover the multi-branch center layer.
2. Multi-tenant/branch dimension is mandatory in the HIS: branch/region/organization as first-class dimensions on visits, patients, billing; permission model built on `related_branches`.
3. Organizations = the insurance/corporate module: model "patient number per org" and external API gateway; ties directly to billing & debt.
4. Moon-ERP financial integration already exists (`clientdept`): the ERP should be the financial source of truth, not local calc; unify auth and remove hardcoded tokens.
5. Lab-device bridge (`lab_devices_ranges` + `saveresultslog` + `investigationresults_*`) is a full bidirectional LIS unit; adopt as a standalone HIS module.
6. Security debt: hardcoded JWTs in `erp_common.php`/`api_web.php`, commented-out API auth, explicit DB/env names in `syncstructure.php` — move to env vars and enable auth before production.
7. One-shot migration tools (`syncstructure`, `excelread`) carry hand-edited limiters (`if($i==203)`, `row<=8789`) and must not ship in the production HIS codebase.

---


# 75 — MED Addendum: Clinic Ops, Customer Service, SMS, Declarations, Archive & Vote

**Source deployment:** `/home/medgreennatureco/public_html/med` (live: med.greennature.com.sa) — newer/extended "center/hospital" edition of the OB/GYN codebase. Cross-checked against the original 312-table dump (`/home/amrtechogate/public_html/obgy/_db/obgy_12-7-2024.sql`). Read-only, schema inferred from code only (RedBeanPHP + raw SQL + Smarty).

## Files analyzed

| File | Role |
|---|---|
| `core/controllers/reserve_clinic.php` | Weekly room/doctor reservation grid (h/v versions, branches) |
| `core/controllers/clinic_visits.php` | In-clinic reception board: enter/out per room |
| `core/controllers/visits00.php` | Extended visit booking + department/doctor/reserve routing |
| `core/controllers/visits_common.php` | Shared helpers: SMS send, short URLs, archive/device tracking, enc_id |
| `core/controllers/customer_service.php` | Doctor performance, waiting-time, complaints, price list |
| `core/controllers/sms_control.php` | SMS template/campaign CRUD |
| `core/controllers/sms_sample.php` | Standalone SMS test (hardcoded smsmisr creds) |
| `core/controllers/decleration.php` | Consent/declaration templates |
| `core/controllers/patientdecleration.php` | Per-patient signed consents w/ file upload |
| `core/controllers/fast_options.php` | Quick-link shortcuts |
| `core/controllers/archive.php` | Device-based file/patient archive tracking |
| `core/controllers/case_summary.php` | Read-only clinical case aggregator |
| `vote/controllers/index.php` | Kiosk satisfaction-survey app (Arabic kioskboard) |
| `core/controllers/vote.php`, `vote_devices.php` | Admin/reporting for the vote app |

## 1. Clinic operations

- **`reserve_clinic.php`** builds a 7-day × hourly grid from `programesetting.start_hour`/`end_hour`. Rooms in `clinic_rooms`; recurring weekly reservations in `clinic_reserves` (`room_id`, `doctor_id`, `reserve_day_no` 1=Sat..7=Fri, `reserve_hour`, `start_date`, `cancel_date`). Supports horizontal/vertical layouts and multi-branch via `programesetting.branches`/`rooms`. Day-number mapping: Sun=2, Mon=3 … Sat=1 (`getDayN0`/`getTodayNumber`). Cancel = `cancel_date` + `is_active=0` + `deleted=1`. Generic `update()`/`del()` accept `tableName`/`colName` from POST (mass-assignment surface — note for security review).
- **`clinic_visits.php`** (`index`): for each active `clinic_rooms` row, lists today's reserved doctors (`clinic_reserves` join `awusers`) and their `visits` (today, `for_doctor`, `deleted=0`). `enter()` sets `visits.clinic_entered=1`, `out()` sets `visits.clinic_out=1`.
- **`visits00.php`**: routes a visit to `for_department`/`for_doctor`, may bind a clinic reservation (`reserveId`, dept 13), and pushes to department queues (`sendToDept`/`removeFromDept` for depts 5 & 12). Confirms `visits` carries new columns: `clinic_entered, clinic_out, arrived_time, arrived_userid, kashf_enterance, enc_id, visit_order, visit_time, rfc, branch_id, customer_add, view`.

## 2. Customer service (`customer_service.php`)

- **Prices**: merges `detections`, `invests`/`investcats`, `rays`/`rayscats`.
- **Doctor performance** (`getvisits`/`visitbydoctor`/`printvisits`): counts visits, confirmed (`customer_add != 1`), attended (`view = 1`) per doctor/dept/branch; computes waiting time = `kashf_enterance − arrived_time`. Medical depts gated by `awrole.doctor_visit_show = 1`; excludes `detectionid in (999, 9999, -88)`. Doctor scoping via `awusers.related_dr` for role 19.
- **Arrival**: `arrivedHos()` stamps `visits.arrived_time`/`arrived_userid`.
- **Complaints**: full CRUD on NEW table `patients_complaints` (`patientid, complaiv, reply, call_date_time, user_id, deleted`); button perms via `awrolebtn` (btn 5 edit / 6 delete).

## 3. SMS campaigns (`sms_control.php` + `visits_common.php`)

- Templates in NEW `sms_control`: `name, message, message_type (1..5), message_place, sender_name, fixed_link, en_link, for_depts (-1=all), status, deleted`. Global settings in NEW `sms_control_setting` (`server_type`).
- `sendSms($sourceId, $messagePlace)` (called from `visits.php`, `patients.php`, `operations_reports.php`): resolves phone (`patients.phone/mobile/hus_mobile`), active templates for the place, dept allow-list (`allowedDept`), then builds message by type — plain / +link / +patient name / +visit short link. Generates unique `visits.enc_id`, persists `short_urls` (`origin_url, short_url, create_date`).
- Two providers: legacy `smsmisr.com` (`sendItSms`) and current `sms.naslab.gt4it.com` (`sendItSmsApi`). When `server_type=1`, pushes visit details to external portal `royal-fc.net/appv/visit_details/{add,update}` (`prepareToSite`/`sendToSite`/`updateToSite`).
- **Security:** `sms_sample.php` hardcodes smsmisr username/password/sender; `visits_common.php` hardcodes a naslab JWT Bearer token. Both must be externalized and rotated.

## 4. Declarations / consents

- `decleration.php` — NEW `decleration` template table (`title, content, doctor_id, doctor_name, husband, wife, reception, date`).
- `patientdecleration.php` — NEW `patientdecleration` linking a signed consent to a patient (`decleration_id, patientid, uploadfile` → `upload/decleration_files`, `user_id, date`); print views pull patient + doctor + `programesetting`. Note: `$patientId` vs `$patientid` case bug in `addit()` (loads wrong patient var); `edit_file` used unquoted constant in `updateit()`.

## 5. Case summary (`case_summary.php`)

Read-only aggregator over many clinical tables. Mostly known `ph*` history tables, but references NEW extended-sheet tables: `obstetric_case`, `obstetric_case_ex`, `summary_obstetric`, `summary_obstetric_abortion`, `previous_pregnancies`, `previous_puerperium`, `pre_anaesthetic`, `blood_transfusion`, `complaint_analysis`, `recommend`, `symptoms` (these belong to the extended clinical-sheets domain, listed here only as encountered).

## 6. Archive tracking (`archive.php` + `visits_common.php`)

- Device scan flow: `devices.location` 4=in / 5=out. NEW `archive_tracking` (`patient_id, in_date/in_time/in_device_id, out_date/out_time/out_device_id, target_id, target_table, receiver, notes, control, user_id, deleted`). An open "out" record (out set, in empty) is completed on the next "in" scan. Parallel generic `device_tracking` (KNOWN). Floor location resolved via `devices.floor_no` → `floors`. Search by patient or by date; "exited but not returned" list.

## 7. Vote / satisfaction kiosk (`/med/vote` + `vote.php`/`vote_devices.php`)

- Standalone Smarty app with Arabic kioskboard (`kioskboard-2.2.0`, `kioskboard-keys-arabic.json`, jQKeyboard).
- Schema (all NEW): `vote_devices` (`name, local_ip`), `votes` (`vote_device_id, voter_required, email, role_id`), `vote_questions` (`vote_id, question`), `vote_answers` (`vote_question_id, answer, face, send_mail`), `clients_votes` (`vote_device_id, vote_id, user_id, voter_name, voter_phone, ip/local_ip/remote_ip, user_agent, is_contact, reply, created_at`), `clients_votes_answers` (`client_vote_id, question_id, answer_id, answer_face, answer_text, user_id`).
- Flow (`vote/controllers/index.php`): `?ac=vote&device=ID` → verify `vote_devices` exists → resolve logged-in employee from NEW `login_tacking` (latest `user_id` where `login_ip = device.local_ip` and `status=1`) → load `votes`+`vote_questions`+`vote_answers` → `submit()` writes `clients_votes` + `clients_votes_answers` → redirect to `thank_you`.
- Reporting (`vote.php`): per-employee satisfaction reports (`report`/`reportDetails`/`empReport`) with date range, distinct question/answer counts, answer-text distribution; complaint follow-up via `updateClientVoteContact` (`is_contact`) and `updateVoteReply` (`reply`). Email-on-answer via `vote_answers.send_mail` + `votes.email` tied to `votes.role_id`.

## NEW tables (not in the 312-table dump)

`clinic_rooms`, `clinic_reserves`, `patients_complaints`, `sms_control`, `sms_control_setting`, `short_urls`, `visits_site` (likely external API only), `decleration`, `patientdecleration`, `fastoptions`, `archive_tracking`, `vote_devices`, `votes`, `vote_questions`, `vote_answers`, `clients_votes`, `clients_votes_answers`, `login_tacking`, plus extended-sheet tables referenced by `case_summary` (`obstetric_case`, `obstetric_case_ex`, `summary_obstetric`, `summary_obstetric_abortion`, `previous_pregnancies`, `previous_puerperium`, `pre_anaesthetic`, `blood_transfusion`, `complaint_analysis`, `recommend`, `symptoms`).

KNOWN tables reused: `visits` (extended w/ new columns), `patients`, `awusers`, `awrole`, `awrolebtn`, `detections`, `invests`/`investcats`, `rays`/`rayscats`, `programesetting`, `branches`, `devices`, `device_tracking`, `floors`, `wifetypes`, `refer`.

## ERP/HIS impact

1. New **Clinic Scheduling / Roster** domain (`clinic_rooms`/`clinic_reserves`) absent from the 312-table blueprint — model with branch support.
2. New **Patient Experience / Customer Service** domain: waiting-time KPIs, confirmed/attended visit metrics, complaints (`patients_complaints`).
3. Unify **Notifications**: two SMS providers + hardcoded secrets → single Notification service, externalized/rotated secrets.
4. **Short-URL engine + external portal push** (`royal-fc.net`) = integration interface to document.
5. **Digital consents** (`decleration`/`patientdecleration` + uploads) → Consent/Document Management.
6. **Physical MRD tracking** (`archive_tracking` + `devices.location` + `floors`) → Medical Records Tracking module.
7. **Patient Satisfaction Surveys** (6 vote tables) attributed to employees via `login_tacking` → Surveys + Employee Performance + complaint follow-up.
8. `visits` schema in the new edition is wider than the original — update the visits-module blueprint.

---


# Addendum 76 — MED: Stores, Extended Finance & Devices (Technical Digest)

**Scope:** Second deployment at `/home/medgreennatureco/public_html/med` (live: med.greennature.com.sa), the newer **Center/Hospital** edition of the OB/GYN codebase family. This digest covers the Stores / Lab-Supplies, package offers, contract/insurance pricing, financial & cash-transfer reports, services (detections) management with **live external-ERP sync**, pharmacy dispensing, and three separate device registries.

**Method:** Code-only inference (RedBeanPHP `R::*` calls, raw SQL, Smarty views). Tables cross-checked against the original 312-table dump `obgy/_db/obgy_12-7-2024.sql`. Read-only; no DB access.

Controllers read in full (all under `/med/core/controllers/`):
`invsdept.php` (4061), `invsdept_common.php` (4842), `invsdeptapp.php` (1029), `invsdeptapp_1.php` (628), `invoffers.php` (426), `detection.php` (2297), `financiallab.php` (781), `cashtransferdectorreport.php` (731), `financialreport_old.php` (1686), `financialreport_new_old.php` (1832), `pharmacy.php` (181), `labdevices.php` (361), `adddevices.php` (3038), `vote_devices.php` (169).

---

## 1. Stores / Lab Supplies

- `suppliescats` (categories) → `supplies` (`name`, `catid`) → `invests_supplies` junction (`supply_id`, `supply_no` qty, `usedate`, `deleted`).
- Consumption report: `invsdept.php::suppliesPrint($from,$to)` (lines ~3121-3167) — `SELECT sum(invests_supplies.supply_no) ... WHERE usedate BETWEEN ? AND ? AND deleted=0`, grouped by category/supply.
- Sample typing via `sampletype` (`invests.sampletype` FK), `sampletype.name` joined in `printinvs()`/`searchinvs()`.
- Re-normalized lab results: `investigationresults` + specialized child tables `investigationresults_{blood,culture,urine,stool,semen,cross,pt,esr,egfr,lipid,aborh,custom,times}` replace the legacy `*sheet*` model. Reference ranges in `inv_ranges`; lab config in `programesettinglab` (separate from global `programesetting`).
- Barcode per sample via `createbarcode($statusno)`; `investigationresults.sample_id` used in `searchrecord()`.

## 2. Package Offers — `invoffers.php`

- `invoffer` (`name`, `active`, `del`) 1-M `invofferdetails` (`invoffer_id`, `inv_id`, `price`, `del`).
- Offer price computed dynamically: `SELECT sum(price) FROM invofferdetails WHERE invoffer_id=? AND del=0` (`index()` line 77).
- `addit()`/`updateit()` — update soft-deletes all details (`set del=1`) then re-inserts selected.
- Consumed by visit billing via `investigationresults.offer_id`; price = `contract_price - patient_hold`.

## 3. Contract / Insurance Pricing — `detection.php`

- `price_lists` (`name`, `deleted`).
- `organizations` (`org_edit`/org create ~line 1706): `organization_name`, `org_type`, `parent_id`, `location`, `phone`, `contact_person`, `price_list`/`list_id`, `sales_persons`, `contract_start`, `email`, `vat_no`, `credit_limit`, `user_id`, `create_date`, **`user_name` + `user_password_hash`** (portal login), `deleted`.
- `organizations_parents` (hierarchy), `organizations_patient_no` (`patient_id`, `organization_id`, `patient_no`, `deleted`) — `patientOrgCode()` line 2113.
- `organization_discount` (`listDiscountObj()` line 383): `list_id`, `detect_id`, `detect_cat` (1=detection, 2=invest, 3=ray, 4=offer), `offer_id`, `contract_price`, `patient_hold`, `discount_type`, `deleted`. Joined to detections/invests/rays via `LEFT JOIN organization_discount ON x.id = organization_discount.detect_id` (lines 637/803/969).
- `investigationresults` carries `contract_price`, `patient_hold`, `offer_id` for org/patient split (`visitTests()` line 2120+).

## 4. Detections + Live ERP Sync — `detection.php` (KEY FINDING)

- Service catalog `detections` (`title`, `detectionval`, `create_date`, `del`).
- `erpProduct($detection)` (line 169): if `programesetting->erpdb` is set, `R::addDatabase('erpDB', mysql:host=DB_HOST;dbname={erpdb}, DB_USER, DB_PASS)`, `R::selectDatabase('erpDB')`, then `SELECT productId FROM product WHERE obygyDetectionId = {detection.id}`.
  - Not found → `curlAddProduct()` → POST to `HOST_URL_ERP/controllers/buyBillControllerAjax.php?do=temp...` (category fixed `Services`, `isservice=1`).
  - Found → `curlupdateProduct()` → POST to `HOST_URL_ERP/controllers/productControllerAjax.php?do=update`.
  - Delete → `erpProductDel()`/`curlDelProduct()`.
  - Restores `R::selectDatabase('default')`.
- Implication: `detections` is source-of-truth; ERP `product` catalog is mirrored via cURL with a stable `obygyDetectionId ↔ productId` link. **External ERP DB (`product`) is NOT part of the 312-table OB/GYN schema.**

## 5. Financial / Cash-Transfer Reports — `cashtransferdectorreport.php`, `financiallab.php`

- Unified report = `visits` UNION ALL `bill_paying` (pharmacy payments). See `visitSql()` + `billsPayingSql()`.
- `visits` financial columns: `detectionvalue_cash`, `detectionvalue_visa`, `totaldetectionvalue`, `restdetectionvalue`, `discount`, `detectionid`, `dr_salary`, `refer_doctor`, `refer_doctor_tb`, `contract_price`, `branch_id`, `printserial`, `customer_add`, `for_department`, `for_doctor`, `rfc`.
- Magic `detectionid` values: `999` = remaining payment ("دفع متبقي"), `9999` = customer refund ("مرتجع العملاء").
- Visibility gated by `awrole.financial_visits` (1=dept-scoped, 2=doctor-scoped), `awusers.financial_user`, `awusers.cashtransferdoctor_user`.
- `bill_paying`: `type` (1=refund), `user_id`, `patient_id`, `pay_date`, `discount`, `printserial`, `branch_id`, `notes`.
- Aggregation grouped by `branch_id` + referring doctor (`refer`); detail expanded from `investigationresults`/`raysresults`; booking time from `visit_hours`.
- `financialreport_old.php` = legacy variant using `awcontroll/awmenu/awrole*` permission tables; adds no new core entity.

## 6. Pharmacy — `pharmacy.php`

- `prescription` search by date/doctor/patient. Columns: `patient_id`, `doctor_id`, `pres_date`, `for_husband`, `prepared`, `delivered` (+ `prepared_userid`/`delivered_userid` set in `updatePre()`).

## 7. Device Registries

- **Lab devices** (`labdevices.php`): `lab_devices` (`completed`, `user_id`, `deleted`, `delete_userid`, `delete_date`) + `lab_devices_ranges` per-device ranges copied from `inv_ranges` (`device_id`, `investid`, `special_inv_type`, `invest_child`, `range_txt`, `agefrom`, `ageto`, `age_units`, `gender`, `range_low`, `range_high`, `invest_units`, `deleted`).
- **Kiosk/vote devices** (`vote_devices.php`): `vote_devices` (`name`, `local_ip`, `created_at`, `updated_at`); blocks delete if referenced by `votes.vote_device_id`.
- **Room medical devices** (`adddevices.php`): `devices` (`device_name`, `location`, `deleted`) on `floors`/`locations`/`operations_rooms`. NOTE `devices`, `floors`, `locations` already exist in the 312 dump; `operations_rooms` is NEW.

## 8. External Labs & Records Archive — `adddevices.php`

- `external_labs` (`name`, `deleted`); dispatch tracked on `investigationresults.external_lab` + `receive_userid`/`receive_date` (`receiveExternalTest()` line 593) + `delivered_userid`/`delivered_date` (`deliverExternalTest()` line 623).
- `dna_results` (paternity): `receivername`, `delivered_userid`/`date`.
- `archive_request` (records archive workflow, line 1267): `patient_id`, `doctor_id`, `visit_id`, `branch_id`, four-stage audit `request/ready/receive/return _userid`+`_date`, `request_note`, `receive_note`, `request_note_userid`, `receive_note_userid`, `cancel_userid`, `deleted`.
- `manual_operation` (manual ops, `del()` line 415).

---

## NEW tables (not in original 312)

`supplies`, `suppliescats`, `invests_supplies`, `sampletype`, `investigationresults` (+ `_blood/_culture/_urine/_stool/_semen/_cross/_pt/_esr/_egfr/_lipid/_aborh/_custom/_times`), `inv_ranges`, `programesettinglab`, `invoffer`, `invofferdetails`, `price_lists`, `organizations`, `organizations_parents`, `organizations_patient_no`, `organization_discount`, `sales_persons`, `bill_paying`, `prescription`, `visit_hours`, `lab_devices`, `lab_devices_ranges`, `vote_devices`, `votes`, `external_labs`, `dna_results`, `archive_request`, `operations_rooms`, `manual_operation`, `rays`, `raysresults`, `refer`, `patients_childs`. External ERP DB: `product`.

**KNOWN (already in 312):** `detections`, `devices`, `floors`, `locations`, `investcats`, `invests`, `semen`, `hsg`.

## ERP/HIS plan impacts

1. Live ERP integration exists (cURL + `obygyDetectionId↔product.productId`) — replace direct ERP-DB writes with a clean API in the target.
2. Contract/insurance model (`organizations`+`price_lists`+`organization_discount`+VAT/credit_limit) = ready foundation for a standalone Contracts/Insurance ERP module.
3. Income is split across `visits`+`bill_paying` via UNION with magic `detectionid` 999/9999 — build a unified financial-ledger table instead.
4. `supplies`/`invests_supplies` model consumption only (no stock balances/suppliers) — add a full inventory layer.
5. `investigationresults*`+`inv_ranges` replaced legacy `*sheet*` — update lab table maps in the 312 report.
6. Three device registries (`lab_devices`/`vote_devices`/`devices`) should unify into one asset registry.
7. `archive_request` + external-lab dispatch are reusable workflow/state-machine templates.
8. New permission flags (`financial_visits`, `financial_user`, `cashtransferdoctor_user`, `external_lab`) on `awrole`/`awusers` need a proper role model.

---


# MED: Shared-Core Diff — Settings or Fork? (Technical Digest)

**Question answered:** Is `med.greennature.com.sa` "نفس البرنامج بإعدادات مختلفة" (same program, different settings) or a newer **fork** with real code divergence?

**Verdict: NEWER FORK with real code divergence** — yet heavily config-driven *within the new code*. The framework core is shared (`_controll.php` byte-identical), but MED adds ~70 new controllers and grows shared clinical controllers 2x–5x with code that does not exist in OBGY at all. Feature flags toggle these new paths; they cannot be "turned on by config" in OBGY because the code is absent.

## Quantified diff (`diff -qr obgy/core/controllers med/core/controllers`)

| Metric | Value |
|---|---|
| Controllers OBGY | 77 |
| Controllers MED | 134 |
| Shared filenames | 57 |
| Shared & **identical** | 21 |
| Shared but **differ** | 36 |
| **Removed** in MED | 21 |
| **Added** in MED | 78 |

Source: `/tmp/ctrl_diff.txt` produced by `diff -qr`.

## "Same program" evidence (identical / cosmetic)

- `core/controllers/_controll.php` — **IDENTICAL** (303 == 303 lines). Controller dispatch core unchanged.
- `examination.php` — diff `+2/-2`, **whitespace only** (blank-line shuffles at lines 139/142/202/227).
- `_header.php` — formatting/spacing only.
- `_menu.php` — diff `+6/-6`: commented out the `error.php?ac=relate` redirect+`exit()` on permission failure (2 sites), and re-enabled `R::findAll('awrolemenu',...)` + `R::trashAll`. Minor behavioral toggle, same logic.

## "Fork" evidence (structural divergence)

### Composer / Monolog — decisive
- OBGY: **no** `composer.json`, **no** `vendor/`.
- MED `_public/aw_config.php` (line ~8): `require_once __DIR__ . '/../vendor/autoload.php';` + `use Monolog\Handler\StreamHandler; use Monolog\Logger;`. `med/vendor/` contains `monolog/`, `psr/`, `composer/`. A Composer toolchain dependency cannot be a "setting."
- Other config deltas: timezone `Africa/Cairo` (was `America/Los_Angeles`); DB `medgreennatureco_med`; `//R::freeze(true)` toggled; OBGY-only `$hosturlApi`/`$redirectLink` (gt4it) removed.

### Shared controllers exploded in size

| File | OBGY lines | MED lines | diff +/− | functions OBGY→MED |
|---|---|---|---|---|
| `visits.php` | 1946 | 9660 | +8599 / -885 | 65 → 220 (+155 new) |
| `patients.php` | 2259 | 4157 | +2332 / -434 | 77 → 127 |
| `_member.php` | 356 | 1018 | (large) | + ERP doctor sync |
| `_role.php` | 598 | 991 | (large) | + separate user menus |
| `antenalvisit.php` | 1361 | 1756 | +395 | — |
| `_sidebar.php` | 160 | 361 | +241 / -40 | + `userMenuSidebar` |
| `programesetting.php` | 296 | 376 | +177 / -97 | +44 setting keys |
| `setup.php` | 225 | 354 | +149 | + ERP visa accounts, `tablesToClean` |

New `visits.php` methods (sample): `erpClient`, `erpSellbill*` (via `erpSellbillUpdate`/`erpSellbillDel` calls), `curlAddClient`, `addLabVisit`, `editlabvisit`, `completeLabPrint`, `createbarcode`, `createSampleBarCode`, `createQRcode`, `billPdf*`, `addAdvancePay`, `getLastAdvance*`, `branchSql`, `getDetectionsBy*`, `getOperationDates`, `arrange_visits`. (155 total new.)

`_member.php` new methods: `addAllDoctorsToERP`, `curlAddDoctor`, `curlupdateDoctor`, `curlDelDoctor`, `erpDoctor`, `updateerpsaveid`, `updateMenuUserSetting`, `displaymenush`, `setting`.

`setup.php` new methods: `geterpVisaAccounts`, `saveERPDBVisaAccount`, `tablesToClean`.

## Config-driven layer (real, but governs new code)

`programesetting.php` in MED is a feature-flag hub. **44 new columns** absent from OBGY (OBGY `programesetting.php` references `programesettingex`/`programesettinglab`/`programesetting2` = **0** times):
`system_type, system_lang, branches, regions, organizations, advance_payment, surgeon, rooms, triggering, ovum_tab, embro_tab, others_tab, tanks_view, whatsapp_sms, whatsapp_txt, voice_record, visit_phones, national_id_req, confirm_attend, birth_notify, case_summary, barcode_print, printserial, refer_doctor, fin_doc_dept, temp_patient, start_hour, end_hour, attend_today, past_search, diagnos_ant, diagnos_gyna, search_result_no, search_letters_no, presquantity, visittype, loginbar, rfc, personal, sexual, above2, buttom2, fontsizeen2, fontsizear2`.

New settings tables created on demand: `programesettingex` (dispensed with `revisioned=0`, columns filled from `programesetting2.colum_name`) and `programesettinglab` (`lab_style=1`).

Menus remain **DB-driven** via `awmenu` (`_sidebar.php` filters by `programesetting.simpleview` → `a.form in (0,1,2)`); MED adds a second `userMenuSidebar`. Framework menu structure unchanged.

## Removed sheet controllers → modular replacements

The architectural shift is single-page clinical **sheets → modular controllers**:

| Removed (OBGY) | Replacement (MED) |
|---|---|
| `ancsheet`, `ancsheet00` | expanded `antenalvisit.php` + `mainantenental*` flow |
| `gynasheet`, `gynasheet00` | `gyna.php` + `ultrasoundgyna.php` |
| `infertilitysheet`, `infertilitysheet00` | `infertility.php` |
| `ivfsheet`, `ivfsheet00` | `embryology.php`, `embryoslab.php`, `embryofreezing.php`, `spermfreezing.php`, `ovumpickupembryotrans.php`, `ivf_reports.php` |
| `iui`, `monitoring`, `edd`, `epc` | folded into visit/report flows |
| `Deliveries` | `birth_notify` flag + reports |
| `operations` | `operations_reserve.php`, `operations_calendar.php`, `operations_rooms.php`, `operations_reports.php`, `operation_types.php` |
| `mobileservices`, `sh`, `addpresenthistory`, `Completesreport`, `Ivfstatistics` | removed / replaced by center reports |

## ERP integration (live)

- `med/core/controllers/erp_common.php`: `orderFromErp()` curls `localhost/erp/controllers/api_web.php?do=...` with a **hardcoded JWT** in `$apiKey` (security note). Also `getPatientLink()` → `api.naslab.gt4it.com`.
- `visits.php` includes `erp_common.php` and calls `erpSellbill`, `erpSellbillUpdate`, `erpSellbillDel` on visit add/update/delete (lines ~1104, 1838, 2257, 2312–2393).
- `_member.php` syncs doctors to ERP; `setup.php` configures ERP visa/bank accounts.

## NEW tables/entities (vs original 312-table dump)

`setup.php → tablesToClean` enumerates ~171 entities; cross-check vs `/tmp/obgy_tables.txt` (312 names from `obgy_12-7-2024.sql`) yields **~91 NEW** entities. Domain-relevant new tables referenced by the shared-modified controllers: `programesettingex`, `programesettinglab`, `organizations`, `organizations_patient_no`, `organization_discount`, `advance_payment`, `archive_request`, `patients_childs`, `governorate`, `governorate_centers`, `regions`, `sub_regions`, `clinic_rooms`, `day_times`, `visit_hours`, `operations_main`, `operation_room`, `operations_rooms`, `operationotherth`, `residence_room`, `residence_rooms`, `rays`, `raysresults`, `raysresults_img`, `gynarays`, `mainantenentalrays`, `andvisitsrays`, `investigationresults` (+ culture/blood/urine/etc. lineage), `invoffer`, `invofferdetails`, `refer`, `visits_follows`, plus ERP bridge tables `sellbill`, `returnsellbill`, `client`, `clientdebtchange`, `bank`, `bankaccount`. All schema knowledge inferred from RedBeanPHP calls / raw SQL / `tablesToClean` — code only, no DB queried.

## ERP/HIS plan impacts

1. Use **MED as the migration baseline**, not OBGY — MED is the evolved center/hospital HIS.
2. Add full new modules: operations+rooms, residence/admission, endoscopy, radiology, advanced lab, pharmacy, andrology, IVF lab (6 controllers).
3. Multi-branch/region/organization model → ERP must be multi-tenant/branch aware.
4. Document the existing ERP bridge (`api_web.php`: sell-bills, clients, doctors); remediate hardcoded JWT in `erp_common.php`.
5. Migrate `programesetting` (+44 keys + 2 tables) as a feature-flag/facility-config layer.
6. Add ~91 new entities to the ERP/HIS data dictionary; verify against live schema later.

## File citations
- `/home/medgreennatureco/public_html/med/_public/aw_config.php`
- `/home/medgreennatureco/public_html/med/core/controllers/{programesetting,setup,_sidebar,_menu,visits,patients,_member,erp_common}.php`
- `/home/amrtechogate/public_html/obgy/core/controllers/*` (baseline)
- `/home/amrtechogate/public_html/obgy/_db/obgy_12-7-2024.sql` (312-table reference)

---


# Addendum 78 — MED: New Tables Extracted from Code

**Scope:** Second deployment discovered at `/home/medgreennatureco/public_html/med` (live: med.greennature.com.sa). Newer/extended edition of the same codebase family, configured as a **center/hospital** rather than a single OB/GYN clinic.

**Method:** Read-only, code-only schema extraction. No live DB access. Sources:
- RedBeanPHP calls: `R::dispense|find|findAll|findOne|load|getAll|exec|count(...)` across `med/{core,board,pharmacy,vote}/controllers` and `med/core/views`.
- Raw SQL `FROM/JOIN/INSERT INTO/UPDATE` table names.
- Cross-checked every normalized name against the original 312-table list:
  `grep -oE 'CREATE TABLE IF NOT EXISTS \`[a-z0-9_]+\`' /home/amrtechogate/public_html/obgy/_db/obgy_12-7-2024.sql` → 312 unique tables.

## Quantification

| Metric | Count |
|---|---|
| Distinct tables referenced in MED code (RedBean, noise-filtered) | **413** |
| Known (present in original 312-table dump) | **137** |
| **NEW (not in original dump)** | **276** |
| Modules scanned | core, board, pharmacy, vote |

Noise removed as confirmed non-tables (DB selectors / debug / ext, not entities): `default, new, old, obgy, obor, true, xdispense, select, insert, update, delete, from, into, index`. Verified via grep, e.g. `R::addDatabase("new")`, `R::selectDatabase('obgy')`, `R::debug('true')`, `R::ext('xdispense')`.

## Architectural finding — Multi-DB federation + dedicated ERP DB

`med/core/controllers/*.php` call `R::addDatabase()` / `R::selectDatabase()` against multiple databases:
- `erpDB` — `R::addDatabase('erpDB', ... dbname=' . $programesetting->erpdb ...)` → a separate ERP database whose name is read from `programesetting.erpdb`. Visit bean carries `iserppayment`, `enterordered` flags → financial bridge to ERP already exists in code.
- `old`, `new`, `obor`, `royalDb`, `default` — cross-deployment/branch data migration scenarios.

This proves MED is a federation layer over several databases, not a single-DB app. HIS plan must address a Master Patient Index across these, not assume one schema.

## board / pharmacy modules use KNOWN tables only

`board/controllers/{sessions,requests,stuff}.php` reference `bsession, bsessionmembers, bmemberopenion, brequests, bcomments, councilstaff` — all KNOWN.
`pharmacy/controllers/*` reference `drugs, importbill, importdetails, pharmacystore, receiptdrugs, recepittmp, storedrugs` — all KNOWN.
The true expansion is concentrated in **`core`** (the center/hospital controllers).

## New entities by domain (with field inference and file citations)

### Kiosk Voting / patient-satisfaction (module `vote`)
Files: `vote/controllers/index.php`, `core/controllers/vote.php`
- `votes` (NEW) — survey definition.
- `vote_questions` (NEW) — `vote_id, question_text`.
- `vote_answers` (NEW) — `question_id, answer_text, answer_face`.
- `vote_devices` (NEW) — kiosk devices.
- `clients_votes` / `client_votes` (NEW) — `vote_id, vote_device_id, user_id, voter_name, voter_phone, ip, local_ip, remote_ip, user_agent, created_at`.
- `clients_votes_answers` (NEW) — `client_vote_id, question_id, answer_id, answer_text, answer_face, user_id, created_at`.

### Organizations / Geo (center contracts + geography)
Files: `core/controllers/regions.php`, `core/controllers/visits.php`, `visits_common.php`
- `organizations` (NEW) — `id, name, deleted, patient_no`; contracted insurers/companies.
- `organization_discount`, `organizations_patient_no` (NEW).
- `governorate`, `governorate_centers`, `regions`, `sub_regions` (NEW) — `id, name, region_id, deleted`.
- `nationality, hnationality, religion, hreligion, husstatus` (NEW) — reference lists (h-prefix = husband).

### Visit / Reception / Reservation (central encounter)
Files: `core/controllers/visits.php`, `visits_common.php`, `reserve_clinic.php`
- `visit` (NEW) — central encounter. Fields observed: `patientid, branch_id, branch_name, for_department, for_doctor, for_husband, visitdate, visit_time, original_price, contract_price, discount, organization_id, organization_discount, center_discount, center_discount_value, detectionvalue_cash, detectionvalue_visa, dr_salary, is_redirect, enc_id, iserppayment, enterordered, user_id, printserial, subregion`.
- `visit_services, visit_hours, day_times` (NEW).
- `clinic_reserves, doctors_reserves` (NEW) — `visit_id, doctor_id, room_id, reserve_day_no, reserve_hour, start_date, is_active, cancel_date, user_id`.
- `clinic_rooms, operation_room` (NEW); `advance_payment` (NEW).

### Residence / Admission
Files: `core/controllers/residence_rooms.php`, `residence_rooms`, `exit_summary`
- `residence_rooms` / `residence_room` (NEW) — `name, floor_no, start_time, end_time, create_date, deleted`.
- `residence_reserves` (NEW); `exit_summary` (NEW, discharge summary).

### IVF / Embryology Lab (largest new domain)
Files: `core/controllers/embryoslab.php`, `embryology.php`, `embryofreezing.php`, `ovumpickupembryotrans.php`, `tanks.php`
- `embryoslab` (NEW) — `date, visit_id, patient_id, clinician_id, embryologist_id_tb, referred_dr_id, create_date`.
- `embryo` (NEW) — `embryoslab_id, embryologist_id_tb, icsi_by_tb, freeze_embryologist_id_tb, cancelled_user`.
- `embryofreezing, embryothawing, embryofreezingreport, embryotransfer, embryotransferovum, ovumpickup` (NEW).
- `tanks, tanks_general, tankcells, tankcellhistory, embryoslab_tank_cells` (NEW) — `embryoslab_id, tank_cell, cell_no, row_no, col_no, btn_no, color_name, location, date_add, user_id, deleted` (LN2 tank cell-mapping + movement history).
- Reference lists: `embryologist, embryologyreport, embryoscoring, embryotype, embryodifficulty, embryojetplace, oocytequality, oocytecytoplasm, oocytezona, oocytepvs, oocyteother, growthmedia, growthincubator, growthco2, growthoil, freezingmedia, freezingprotocol`.
- `ivf_records, ivf_report` (NEW).

### Andrology / Semen (`and*` cluster)
Files: `core/controllers/and_visits.php`, `and_examination.php`, `and_history.php`, `spermfreezing.php`, `semen_*` views
- `andvisits` (NEW) — `patientid, doctorid, doctorname, date, complaintid, diagnosisid`.
- `andvisitssemen, andvisitsus, andvisitsrays, andvisitsinvestigation, andvisitsficils, andvisitsgenetictesting, andvisitsdrugs` (NEW) — `andvisitsusid, azf, chromosome, length, width, volume, name, type, sysdate`.
- `andexamination, andhistory, andejaculation, anderection, andcontraception, andcontratype, andmedicalproblems, anddiagnosis, andcomplaint, erectiondisease, ejaculationdisease` (NEW, exam + lookups).
- `semen_analysis, semen_process, semen_processing, semen_cryopreservation, sperm_extract, spermfreezingreport, sperum_source` (NEW).

### Endoscopy / Colonoscopy
Files: `core/controllers/endoscopy.php`, `colonoscopy.php`, `endoscopy_template.php`, `colonoscopy_template.php`, `oscopic.php`
- `endoscopy`, `colonoscopy` (NEW) — `patient_id, exam_date, indication, findings, conclusion, plan, esophagus, stomach, duodenum, pylorus, cardio_esophageal_junction, dre, anesthesia, instrument, preparation, preparation_2, consent, signature, created_at, updated_at`.
- `endoscopy_images, colonoscopy_images` (NEW) — `endoscopy_id|colonoscopy_id, image`.
- `endoscopy_template, colonoscopy_template` (NEW).
- `oscopic_report, oscopic_operations, oscopic_specimens, oscopic_required_examinations` (NEW).

### Operations / OR / Anaesthesia
Files: `core/controllers/operationreport.php`, `operations_rooms.php`, `operations_calendar.php`, `operativedetails.php`
- `operations_main, operation_data, operation_form, operationgyna` (NEW).
- `operations_rooms, operations_rooms_cal, operation_room` (NEW).
- `operationinstructions, operationotherth, followup_operation, manual_operation, manual_operation_updates` (NEW).
- `pre_anaesthetic, intra_anaesthetic` (NEW).

### Lab Investigation Results (detailed)
Files: `core/controllers/investigation.php`, `financiallab.php`, `addinvestigation.php`, `labdevices.php`
- `investigationresults` (NEW) header + sample-type detail tables:
  `investigationresults_blood, _urine, _stool, _semen, _culture, _cross, _cross_donners, _lipid, _egfr, _esr, _pt, _aborh, _custom, _times` (NEW).
- Microscopy option lists: `stool_rbc, stool_rbc2, stool_wbc, stool_wbc2, urine_pus, urine_pus2, urine_pbcs, urine_pbcs2` (NEW).
- `invest_elements, inv_ranges, sampletype, esr_` (NEW).
- `lab_devices, lab_devices_ranges, saveresultslog, external_labs` (NEW).

### Radiology
Files: `core/controllers/rays.php`, `radiation.php`, `raysdept.php`
- `rays, rayscats, raysresults, raysresults_img, gynarays, mainantenentalrays` (NEW).

### Pharmacy / Prescriptions / Store (core-side)
Files: `core/controllers/pres_common.php`, `pharmacy.php`, `_phmain.php`, `drugsex.php`
- `prescription, prescription_details` (NEW) — `patient_id, drugname, drugtype, drugdos`.
- `prepared_prescriptions, prepared_prescriptions_drugs` (NEW).
- `stores, storedrugs, storedrugsvalidation` (NEW).
- `drugmovements, recorddrugs, importdrugs, followupdrugs` (NEW) — `drug_id, amountbefore, amountafter, amountvary, optype, opdate`.
- `drug_sensitivity, drug_therapy, antibiotic_sensitivity` (NEW).
- `supplies, suppliescats, invests_supplies, invoffer, invofferdetails` (NEW).

### Patient History / Forms
Files: `core/controllers/patienthistory.php`, `infertility.php`, `gyna.php`, `obstetric*`, `mensturalhistory` views
- `pasthistorymedical, pasthistorysurgical, pasthistoryart, pasthistorygynencological` (NEW).
- `phpersonal, phsexual, phsexualtypes, phpasthx, phpasthxtypes, phpastartresult, phdrugs` (NEW, infertility profile).
- `obstetric_case, obstetric_case_ex, obstetrichistorydetails, summary_obstetric, summary_obstetric_abortion, previous_pregnancies, previous_puerperium` (NEW).
- `mensturalhistory, mensamount, mensdysmo, mensregularity` (NEW).
- `patientinformation, patientdecleration, patients_childs, patients_complaints, patients_notify, relative` (NEW).
- `patient_hystroscopic, patient_laparoscopic, hystroscopic, laparoscopic` (NEW).
- `vital_sign, fetal_pulse, blood_transfusion, bltype, dna_results, complaint_analysis, decleration` (NEW).

### Follow-up / Archive / Settings / Infra
Files: `core/controllers/followup.php`, `followupcard.php`, `archive.php`, `programesetting.php`, `sms_control.php`, `api_web.php`
- `follow_drugs, follow_drain, follow_post, follow_pre, follow_rout, follow_semen, follow_uop, follow_vpack, follow_instruction, visits_follows` (NEW).
- `archive_request, archive_tracking` (NEW) — `patient_id, in_date, out_date, in_time, out_time, deleted` (paper-file checkout tracking).
- `programesetting2, programesettingex, programesettinglab` (NEW) — includes `erpdb, tanks_view, visit_phones`.
- `sms_control, sms_control_setting, login_tacking` (NEW).
- `short_urls, index_redirect` (NEW) — URL shortener / redirect (for SMS links).
- Reference/UI lists: `awusermenu, device, visibility, colors, fastoptions, custom_select, doc_instruction, recommend, refer, knownfrom, symptoms, disease, diseasefamily, medicaldisease` (NEW).
- `beauty_visits` (NEW) — aesthetic/beauty unit.

## Inference caveat
Fields marked "(inference)" in the HTML are derived from SQL column names or RedBean bean-property assignments in the controllers, not from actual table DDL — no live DB was queried. Reference-list tables share a uniform `id, name, deleted` shape across the system.

## ERP/HIS plan impacts
1. Original 312-table analysis covers only the OB/GYN clinic; MED adds **276 new tables** turning it into a multi-specialty center/hospital — migration blueprint must expand.
2. Whole missing modules to add to the HIS plan: IVF embryology lab (with LN2 tank cell tracking), Andrology, Endoscopy/Colonoscopy, OR + scheduling, Residence/Admission, detailed lab results by sample type, Radiology.
3. An ERP integration already exists in code (`erpDB`, `iserppayment`, `enterordered`) — absorb it, don't rebuild.
4. Multi-DB federation (`old/new/obor/royalDb`) implies Master Patient Index / consolidation work.
5. `visit` is the central financial+clinical encounter entity (pricing, org discounts, inter-department routing) — model it as the core Encounter.
6. `organizations + organization_discount + organizations_patient_no` add the insurance/contracted-party dimension needed for Billing/Claims.
7. `drugmovements/storedrugs/storedrugsvalidation` provide a movement ledger for inventory — bind to ERP inventory module.
8. Before migration, confirm real DDL via `SHOW CREATE TABLE` on the live MED DB (with permission; outside this code-only analysis).

---


# 96 — MED Deployment Addendum (English Synthesis)

Companion Arabic fragments: `sections/69-med-overview.html` (discovery + verdict) and
`sections/96-med-erp-impact.html` (blueprint amendments). Domain evidence: `md/70-*.md` … `md/78-*.md`.
All findings are **code-only** (RedBeanPHP calls, raw SQL strings, Smarty views); the live MED
database was never queried and nothing under either deployment was modified.

---

## 1. Deployment facts

| Item | OBGY (analyzed copy) | MED (newly discovered) |
|---|---|---|
| Path | `/home/amrtechogate/public_html/obgy` | `/home/medgreennatureco/public_html/med` |
| Domain | — (analysis copy) | `med.greennature.com.sa` (live) |
| Database | dump `obgy_12-7-2024.sql`, 312 `CREATE TABLE` | `medgreennatureco_med` + multi-DB federation via `R::addDatabase` (erpDB / old / new / obor / royalDb) |
| Mode | OB/GYN clinic | Center/Hospital (`programesetting.system_type`, branches, regions, organizations) |
| Sub-apps | core, board, pharmacy | core, board, pharmacy, **vote** (patient-satisfaction kiosk) |
| Toolchain | no Composer | Composer + Monolog/Psr; different timezone, `R::freeze` |
| ERP link | none | live Moon-ERP bridge (sell-bills, doctors, client debt) via cURL + hardcoded JWT |

## 2. Verdict: fork, not settings

The owner's question — *"did the original analysis account for the center/hospital settings?"* —
is answered **No, and it could not have**: the analyzed copy does not contain those capabilities at
all. MED is a **newer fork** of the same codebase family, heavily config-driven *within new code*:

- Controllers: **77 → 134**. Of 57 shared filenames: 21 byte-identical, 36 diverged; **21 removed**
  (the single-page "sheet" controllers), **78 added**.
- Shared-file divergence is structural: `visits.php` 1,946 → 9,660 lines (65 → 220 functions),
  `patients.php` 2,259 → 4,157, `_member.php` 356 → 1,018.
- Decisive fork proof: MED requires `vendor/autoload.php` (Monolog/Psr via Composer); OBGY has no
  composer.json/vendor at all. A Composer dependency cannot be toggled by a setting.
- The config layer is real but gates **new** code: `programesetting` gained 44 feature-flag columns
  (`system_type`, `branches`, `regions`, `organizations`, `tanks_view`, `ovum_tab`, …) plus two new
  settings tables (`programesettingex`, `programesettinglab`); OBGY references none of them.
- Architectural shift: sheets → modules (e.g. one `ivfsheet` → 6 IVF-lab controllers;
  `operations` → 5 OR/room controllers).

Schema census (code-referenced): **413 distinct tables — 137 known from the original 312, ~276 NEW**;
~178 of the new tables are documented in detail across the nine domain addenda (70–78).

## 3. New tables by domain (documented union, ~178)

| Addendum | Domain | New tables (count) | Highlights |
|---|---|---|---|
| 70 | Inpatient & OR | 9+ (`operations_rooms`, `residence_rooms`, `operations_rooms_cal`, `operations_main`, `doctors_reserves`, `residence_reserves`, `operation_data`, `clinic_rooms`, `clinic_reserves`) | Two unsynchronized OR-booking engines; no Bed entity (bed_no column on extended `visits`); live occupancy boards; operation types overloaded onto `detections` (`for_operation=1`) |
| 71 | Embryology / IVF lab | ~40 (`embryoslab`, `embryoscoring`, `embryo`, `embryothawing`, `tanks`/`tankcells`/`tankcellhistory`, `tanks_general`, `embryoslab_tank_cells`, + ~30 lookups) | Full wet-lab absent from the 312 baseline; two parallel cryo-storage models; chain-of-custody ledger; partial double-witnessing |
| 72 | Endoscopy suite | 14 (`endoscopy*`, `colonoscopy*`, `laparoscopic`, `hystroscopic`, `patient_*`, `oscopic_*`) | Adds GI scopes (gastro/colon) beyond OB/GYN; 3 template-engine patterns; per-scope image archives; left-ovary template-injection bug in `oscopic.js` |
| 73 | Andrology + Radiology | 29 (`and*` cluster, `rays`, `rayscats`, `raysresults`, `raysresults_img`, `investigationresults`, `patients_childs`, `invoffer*`) | Standalone andrology clinic; order→worklist→report→images radiology dept on `visits.for_department=5`; husband/wife/child order subjects; `radiation.php` is transfer letters, NOT radiology |
| 74 | Branches / regions / organizations / ERP bridge | 22 (`regions`, `sub_regions`, `organizations`, `organizations_patient_no`, `lab_devices_ranges`, `saveresultslog`, 13× `investigationresults_*`, `bill_paying`, …) | Per-user branch scoping (`awusers.related_branches` CSV); contracted-party (insurance/corporate) layer; bidirectional lab-device bridge; live Moon-ERP debt/sell-bill integration |
| 75 | Clinic ops & CRM | 27 (`clinic_rooms/reserves`, `patients_complaints`, `sms_control*`, `short_urls`, `decleration*`, `archive_tracking`, 6 vote tables, extended-sheet tables) | Weekly doctor×room roster; waiting-time KPIs; dual SMS providers with hardcoded secrets; signed consents; physical-file tracking; satisfaction kiosk |
| 76 | Inventory & finance | ~17 (`supplies*`, `price_lists`, `organization_discount`, `organizations_parents`, `bill_paying`, `external_labs`, `dna_results`, `archive_request`, …) | Contract pricing (contract_price/patient_hold); pharmacy POS; income = `visits` UNION `bill_paying` with magic detectionid 999/9999; live `erpDB` product sync keyed on `obygyDetectionId` |
| 77 | Config-vs-fork diff | — (verdict + entity census) | Numbers above; ~91 new entities visible in `setup.php tablesToClean` alone |
| 78 | Full schema census | 276 total NEW | `visit` is the central encounter entity (60+ columns: pricing, org discounts, routing); multi-DB federation; kiosk module |

## 4. Blueprint amendments (to §95, within the approved Platform-First architecture)

The architecture decision (Modules/HIS as generic platform, Modules/Obgy as first specialty,
nobody imports Obgy) is **confirmed, not reopened** — MED is evidence the market pulled the legacy
vendor toward hospital mode. Amendments:

| MED addition | Destination | Disposition | Size |
|---|---|---|---|
| Inpatient rooms / OR / residence | **Modules/HIS** (P1 surgery + P4 inpatient) | Already-planned `his_wards/rooms/beds/bed_assignments/encounter_movements` + `his_surgical_bookings` fit. Add `his_or_slots` calendar with DB-level conflict prevention; promote operation types out of `detections` into the procedure catalog; unify the two legacy booking engines into one Reservation with explicit status | M |
| Embryology + LN2 tanks (~40 tables) | **Modules/Obgy** (P2 scope growth) | New sub-domain: `obgy_embryology_sheets`, `obgy_embryo_records`, `obgy_embryo_scorings` (day 2–6 grading as `his_observations` rows where possible), `obgy_cryo_tanks`, `obgy_cryo_positions`, `obgy_cryo_custody_events`, `obgy_thaw_events` — extending the single planned `obgy_cryopreservations`. Unify the two storage models pre-migration; make double-witnessing mandatory; ~30 lookups → `his_lookups` | **L** |
| Endoscopy templates (incl. GI) | Obgy + **HIS forms engine** | Three legacy template patterns → `his_form_templates/versions/responses` (immutable versions, signed status). GYN procedures stay in planned `obgy_endoscopy_*`; GI scopes enter as forms *content* (no hard tables — second-specialty gate). Images → media library. Fix (do not port) the `oscopic.js` left-ovary bug | M |
| Andrology clinic (~17 tables) | **Obgy thin clinic + LIS catalog** | Per the synthesis steal: semen/hormone analyses become LIS catalog investigations with WHO ranges. Keep `obgy_andrology_visits` + `obgy_andrology_exams`; history via Q&A engine; lookups → `his_lookups`; the visit itself is a plain `his_encounter` | M |
| Radiology dept | **Future RIS note** (Phase 5, D3 contract) | No tables now. MED's order→worklist→report→images flow validates the D3 Action+encounter_id+`FolioChargePosted` contract for radiology. Priced catalog → Core services at build time. `radiation.php` explicitly out of RIS scope | S now / L later |
| Branches / regions / organizations | **Modules/Core (existing) + HIS P1 insurance generalization** | Branches already first-class (company_id/branch_id, DataScope) — replace CSV `related_branches` with the existing user↔branch pivot. `regions/sub_regions` → Core geo lookups. Organizations = the existing insurance/B2B layer: business partners + contracts; `organization_discount`/`price_lists` → the planned price-list rules; `organizations_patient_no` → `ext_scope_key` pattern; external org portal rebuilt on Sanctum | M |
| Lab-device bridge + 13 typed result tables | **Modules/LIS (existing)** — mapping only | No new tables; LIS already owns instrument interfaces and result typing. Work = ETL mapping (legacy `investid` + send/receive codes → LIS catalog; 16 special types incl. derived lipid/eGFR). `saveresultslog` → existing LIS audit. D5 (LIS untouched) holds | S |
| Vote kiosk + complaints | **CRM/quality note** — outside HIS v1 | Documented as a future patient-experience/quality module requirement; waiting KPIs later derived from `his_encounters` timestamps | note |
| SMS + short URLs | **Core Notification service** | One multi-channel service; secrets to `.env` and **rotate the exposed smsmisr/naslab credentials**; short-URL engine replaced by the `/p/:token` portal pattern | S |
| Legacy ERP-sync layer (`erp_common.php`, `api_web.php`, erpDB) | **Retired** | Native integration replaces it: patient *is* `lab_patients`; money via `his_folio_charges → ChargePostingService → CreateJournalEntry`; doctors via `lab_doctors → employees → users`. Only the id maps (`obygyPatientId/obygyVisitId/obygyDetectionId`) are consumed once during ETL reconciliation | S (teardown) |
| Signed declarations / consents | HIS forms engine + media | Templates → immutable form versions; signed consent → `his_form_responses(status=signed)`; scans → media attachments | S |
| Clinic roster + physical-file tracking | HIS P1 scheduling + medical-records note | Doctor×room×hour roster absorbed by `his_appointments` resource columns (validates the "resource columns from day one" steal). `archive_tracking/archive_request` stay a design note (as `devices/floors` already were), keeping the per-stage userid+date workflow as a request-tracking template | S |

## 5. Two-tenant ETL implications

§95 assumed ONE production database. There are now **two production deployments with diverged
schemas** (original clinic DB, 312 tables; `medgreennatureco_med` with +276 tables and many new
columns on shared tables — `visits` alone differs structurally).

1. **Run ETL per deployment**, tagging every migrated row with `legacy_db` alongside
   `legacy_id`/`legacy_table`; maintain two column-mapping dictionaries (extends R11).
2. **Cross-deployment MPI dedup** before loading `lab_patients`: the same person may exist in both
   DBs (`syncstructure.php`'s hardcoded multi-DB names prove historical cross-copies). Dedup on
   national id / phone / name+DOB with a human review queue (extends R10).
3. **Fresh production export from BOTH databases** before every ETL rehearsal (extends gate R7) —
   the entire MED schema is code-inferred; `SHOW CREATE TABLE` verification on the live MED DB
   (with authorization) is a mandatory gate (**new R13**).
4. **Intra-MED generation-precedence decisions** (extends R3): two OR-booking engines, two cryo
   models, legacy-vs-normalized lab results — clinician-signed merge rules, with per-patient/room/
   tank collision reports (**new R14**).
5. **Security debt widened (extends R12 → R15)**: hardcoded JWTs and SMS credentials, an
   auth-bypass endpoint (`operationreport.operation_details` sets `$_SESSION['user_id']=123`),
   a commented-out-auth external API, and generic table/column AJAX writers — remediate on the MED
   deployment in Phase 0b too; no legacy endpoint is ever ported. Hand-limited one-shot tools
   (`syncstructure` `if($i==203)`, `excelread` row<=8789) are excluded and disabled (**new R16**).
6. **Media (extends R8)**: inventory must include MED's upload trees (endoscopy/colonoscopy/rays
   images, declaration scans), not only the original 4.8 GB.

**Bottom line:** no architectural change and no phase reordering — scope and sizing change.
Phase 2 (Obgy) grows by the embryology lab and andrology clinic; Phase 4 (inpatient) gains a live
reference model from MED; ETL becomes two-tenant. MED slightly raises migration cost and strongly
raises confidence: the legacy vendor already proved the clinic→hospital evolution path the HIS
roadmap bets on.

---
