# Technical documentation — qoutes (Silicon Overdrive Quotes)

Evidence date: 2026-10-05.

Source: `git@bitbucket.org:overdrive/qoutes.git`, `master` at `0eb2f86480c90e824a279296b4dbfa15e7eb7ac1` (2019-11-19, rachelmogano, "First Commit").

Database passwords and other secrets in `system.php` are not copied into this document. They are present in git and should be treated as exposed.

## Architecture

Custom PHP application on the in-house Nemo framework. Each screen is a root `*.php` controller that includes Nemo, handles `Action`, then renders through a layout include. There is no front controller, no router file, and no Composer autoload.

Request path:

1. `system.php` (included from the Nemo base) selects `$DATABASE_SETTINGS` by `$_SERVER[SERVER_NAME]`, connects with MySQLi, loads every `sysSettings` row into `$SystemSettings`, and configures the session save path.
2. `NemoBasic` / `Nemo` builds the page. `Nemo` requires `$_SESSION[USER]` and loads `sysSecurity` for the user's group.
3. The controller assimilates a table (`NemoDetails::AssimulateTable`) or runs a list SQL class.
4. `layouts/layout.back.incl.php` (default `Display` index) includes the menu layout, which posts one form, `frmNemo`, back to the current script.

`login.php` uses `NemoBasic` and does not require a session. It calls `session_destroy()` before drawing the form.

## Application structure

```text
qoutes/
  index.php, login.php, quote.php, client.php, product.php, ...   controllers
  system.php                                                       environment and DB bootstrap
  system.functions.inc.php                                         shared helpers, login log, lockout
  project.functions.inc.php                                        quote/project helpers used by AJAX
  ajaxfunctions.php                                                JSON/XML endpoints
  _nemo.*.cls.php                                                  framework
  includes/*.cls.php                                               entity classes and reports
  layouts/                                                         HTML shell
  css/, js/, images/, custom_controls/
  plugins/Highcharts/                                              chart library and a Java export server
  documents/                                                       runtime PDF location is a setting; a documents directory exists
  cron.*.php                                                       scheduled scripts
```

Controllers found at the root (excluding dated copies and framework files):

| Script | Table or role |
|---|---|
| `index.php` | Quote list plus charts |
| `login.php` | `sysUser` |
| `quote.php` | `tblQuote` |
| `quote.template.php` | Quote templates |
| `quote.bundle.php` | `tblQuoteBundle` |
| `client.php` | `tblClient` |
| `client.service.360.php` | Client Service 360 report |
| `product.php` | `tblProduct` |
| `product.import.php` | CSV upload |
| `category.php` | `tblCategory` |
| `supplier.php` | `tblSupplier` |
| `sales.person.php` | `tblSalesPerson` |
| `user.php` | `sysUser` |
| `security.group.php` | `sysSecurityGroup`, `sysSecurity` |
| `settings.php` | `sysSettings` |
| `email.php` | Sent mail log |
| `email.template.php` | Templates |
| `report.php` | Report shell |
| `my.profile.php` | Current `sysUser` |
| `usage.log.php` | `sysLogin` |
| `message.php` | Static messages |
| `csp.dashboard.php`, `csp.client.php`, `csp.faq.php`, `csp.import.php` | `cspClient` and related CSP tables |
| `service.360.php`, `service.360.grouping.php` | `tblService360`, groupings |

`includes/quote.php` is a second, older quote controller. Root `quote.php` is the one that matches the later comments (currency, hide PDF total, CSP-era dates).

## Frameworks and languages

| Piece | Evidence |
|---|---|
| PHP | Controllers use `<?php`. `system.functions.inc.php` starts with a short `<?` tag and copies `$_POST`, `$_GET`, and `$_COOKIE` into `$GLOBALS` through `localize` / `gpc_extract`, so controllers read `$Action`, `$QuoteID`, and other request fields as variables. When `$_POST` is non-empty it also sets `$_GET` to null. Magic-quotes stripping runs only when `get_magic_quotes_gpc` is true |
| MySQLi | `system.php` `mysqli_connect` |
| JavaScript | jQuery 1.7.1, jQuery UI 1.8.x, Highcharts, `js/md5.js`, `js/scripts.js` |
| CSS | `css/nemo.blue.css`, `nemo.gold.css`, `nemo.red.css`, `nemo.teel.css`, menu and toolbar CSS. Which skin is selected is a setting (`NemoPrimary` is referenced in commented email code) |
| HTML mail | `NemoEmail` builds a MIME body and calls PHP `mail()` |

No `composer.json`, `package.json`, `Dockerfile`, or test directory was found.

## Databases

Profiles in `system.php`:

| Profile key | Environment field | Database | Host written in code | Second connection |
|---|---|---|---|---|
| `lamp` | `lamp` | `eit2` | `localhost` | QuickBooks database `quickbooks_webservices` on localhost |
| `quotes.overdrive.co.za` | `live` | `quoteslive` | `sql24.jnb1.host-h.net` | `DATABASE_SETTINGS2` database `eitlivedb` on the same host; QuickBooks database `quickbook_webservice` on `sql27.jnb1.host-h.net` |

If `SERVER_NAME` is not set (cron), the script sets it to `quotes.overdrive.co.za`.

The hourly cron's SQL text uses `eit2` and `quickbooks_webservices` as schema qualifiers. Those names match the lamp profile, not the live profile's `quoteslive` and `quickbook_webservice`.

### Tables named in code

Application:

- `tblQuote`, `tblQuoteItem`
- `tblClient`
- `tblProduct`, `tblCategory`, `tblSupplier`
- `tblSalesPerson`
- `tblQuoteTemplate`, `tblQuoteTemplateItem`
- `tblQuoteBundle`, `tblQuoteBundleItem`
- `tblService360`, `tblService360Grouping`
- `cspClient` (CSP). Other CSP table names exist inside `includes/csp.*.cls.php` and were not fully enumerated

Framework:

- `sysUser`, `sysSecurityGroup`, `sysSecurity`
- `sysMenuLevel1`, `sysMenuLevel2`
- `sysSettings`
- `sysLogin`
- `sysAudit_{environment}` referenced by the hourly script when it deletes the day's audit rows

QuickBooks:

- `invoice_table` with columns used in code: `refQuoteID`, `blnEmail`, `quickbooks_errmsg`, `paid`, `invoice_number`, `id`

There is no `CREATE TABLE` script in the repository. Column lists are implied by `AssimulateTable` (the framework reads column metadata from MySQL) and by the queries above.

### Relationships written down by developers

`relationships.rtf` and comments at the top of `includes/quote.cls.php`, `includes/quote.bundle.cls.php`, and `includes/service.360.cls.php` contain `ALTER TABLE ... FOREIGN KEY ... ON DELETE CASCADE` statements. The application does not run them on startup.

## APIs

`ajaxfunctions.php` switches on `type`:

| type | Behaviour |
|---|---|
| `getDefaultWarranties` | JSON from supplier id |
| `getClientDetails` | JSON |
| `getAllClientDetails` | JSON |
| `loadQuoteItemProduct` | JSON for a lower-cased SKU |
| `loadEitClientDDL` | XML-style output (default content type unless `header` is set) |
| `ImportEitClient` | Imports one eIT client |
| `loadCSPClientProductItem` | JSON for a CSP SKU |

The file includes `system.php` and does not construct `Nemo`, so the session gate in `Nemo` is not applied by this file. **Requires Confirmation** whether the web server blocks it another way.

Outbound HTTP from the quote screen:

- POST `{WebSeviceURL}/handler.php?strQuoteNumber&strPONumber&QuoteID` when status becomes `3a. To be Invoiced`.
- A call to `{WebSeviceURL}/credited.php` is commented out.

`WebSeviceURL` is loaded from `sysSettings` (the code reads `$SystemSettings[WebSeviceURL]`). The spelling omits the second `r` in service.

## External integrations

| System | How |
|---|---|
| eIT | Second MySQLi profile `DATABASE_SETTINGS2`, database `eitlivedb`, user name `dbu_billing` on the live profile. AJAX import |
| QuickBooks web services | Separate MySQL database and `invoice_table`. Hourly cron. Browser call to `handler.php` when a quote is marked to be invoiced |
| Mail | PHP `mail()` in `NemoEmail::Send` |

## Infrastructure and deployment

- `.htaccess` enables rewrite and redirects HTTP to HTTPS on the same host.
- `robots.txt` is `User-agent: *` / `Disallow: /`.
- Live session path is `{DOCUMENT_ROOT}/SESSION/`. Lamp session path is `{DOCUMENT_ROOT}/quote/SESSION/`.
- Comments in `system.php` and `_nemo.email.cls.php` mention Hetzner (`host-h.net` SQL hosts, a 2017 cron `SERVER_NAME` fix).
- No pipeline file, no CodeDeploy, no Docker, no server spec.

**Unknown / Requires Confirmation:** the vhost docroot, PHP version, and whether `documents/` is writable by the web user in production.

## Environment configuration

Selected in code, not in a committed `.env`:

- Host name `lamp` versus `quotes.overdrive.co.za`.
- Optional `SERVER_PROTOCOL` on the profile (live sets `HTTPS`).
- All other product settings (`VAT`, SMTP display name, email recipient lists, brand, title, documents directory, logout URL, session-expired URL, web service URL) are rows in `sysSettings`.

`settings.php` can edit `strValue` for an existing row. The setting name is read-only. Creating a new setting key from the UI was not shown in the controller's default path (the default is the list; Save and Edit open one row).

## Authentication

- Session user object: `ID`, `USERNAME`, `EMAIL`, `SECURITYGROUPID`.
- Password check: MD5, compared to `sysUser.strPasswordMD5`.
- `js/md5.js` is present. The primary login form posts the password and hashes it on the server. The `Action=Redirect` branch expects `strPasswordMD5` already.
- Failed attempts can store the password on `sysLogin` (the usage log selects `strPassword`).
- `info.php` calls `phpinfo()` and does not include Nemo.

`quote.php` handles `MarkQuoteAsInvoiced` before `new Nemo()`, writing a fixed session (user id 4, group 3) and then changing the quote to `4. Invoiced`. That action is reachable as a query parameter on the script.

## Background work

| Script | What it does when `Action` is empty |
|---|---|
| `cron.hourly.php` | `NemoFIM::Compare`. Emails QuickBooks rows with `blnEmail = 0`. Copies invoice error, paid, and invoiced onto `tblQuote`. Sets `blnInvoiced` where an invoice number exists |
| `cron.daily.php` | Emails the ETA report |
| `cron.daily12.php` | Emails the quote-status report when any stale New, Paid, or Error quote exists |
| `cron.monthly27.php` | Sends client follow-up mail for Sent quotes older than 7 days |

No crontab is in the repository. FIM actions `FIM: RESET MASTER`, `FIM: IGNORE`, and `FIM: UPDATE` are also switched inside `cron.hourly.php`.

`emailcrontest.php` is a short manual test page.

## Storage

- Quote PDFs: path `DocumentsDir` plus `strFilename`. Email attachment code uses the relative prefix `documents/`.
- Product import and CSP import accept an upload (`enctype` is set on the menu form).
- Highcharts Java export build output is vendored under `plugins/Highcharts/exporting-server/`. It is not invoked by the quote PDF path. Quote PDFs use mPDF (`includes/mpdf/mpdf.php` included from the hourly script and the quote class).

## Monitoring and security notes

- No application monitoring agent is configured in the tree.
- `cronjoboutput.txt` in the root contains a short captured cron note. Writing to that file from `cron.monthly27.php` is commented out.
- `NemoFIM` can record file permissions and compare to a master. The hourly script deletes today's `sysAudit_{environment}` rows, comments out `Record` and `PullData`, and still calls `Compare`.
- Secrets in `system.php` include live database passwords. This documentation does not repeat them.
- `usage.log.php` can show failed passwords.
- MD5 passwords, a `phpinfo` script, and the unauthenticated `MarkQuoteAsInvoiced` branch are properties of this snapshot.

## Development and testing

- No PHPUnit, no fixture database, no README with setup steps.
- Local profile `lamp` expects MySQL user `root` on localhost and database `eit2`. The password is in `system.php` and is not repeated here.
- `zSandBox.php` and `zindex.php` are developer pages.
- Dated file copies (`20190910`, `20190918`, `.bak`) are the only in-repo history besides comments. Git history is one commit.

## Libraries (vendored)

| Library | Path |
|---|---|
| mPDF | `includes/mpdf` |
| PHPExcel | `includes/phpExcel` and `includes/phpExcel_20190910` |
| jQuery 1.7.1 and jQuery UI | `js/` |
| Highcharts and a Java highcharts-export project | `plugins/Highcharts` |
| Calendar control | `custom_controls/calendar_control` |

## Data model sketch (inferred)

```text
tblClient 1---* tblQuote 1---* tblQuoteItem *---1 tblProduct *---1 tblSupplier
                                      |
                                      *---1 tblCategory

tblQuoteTemplate 1---* tblQuoteTemplateItem *---1 tblProduct
tblQuoteBundle 1---* tblQuoteBundleItem *---1 tblProduct

sysUser *---1 sysSecurityGroup 1---* sysSecurity *---1 sysMenuLevel2 *---1 sysMenuLevel1

cspClient --optional--> tblClient / tblQuote   (refQuoteClientID, refQuoteID)

invoice_table.refQuoteID --> tblQuote.QuoteID
```

Cardinality is **Inferred** from queries and from the foreign-key notes. It is not proven against a live schema.
