# Best Western Plus Yenagoa — Inventory Management System (IVM)

PHP + MySQL + Tailwind CSS app for hotel inventory: main store, outlets, purchase
requisitions → purchase orders → goods received, store requisitions, and a
configurable multi-stage approval workflow. Built on top of your existing `users`
table.

## What's new in this update

- **Printable PDFs** — every Purchase Requisition, Store Requisition, Purchase
  Order, Goods Received Note, and Return to Supplier now has a "🖨️ Print /
  PDF" link on its view page. It opens a clean, letterhead-styled print view
  (signatures included where relevant) in a new tab — click **Print / Save as
  PDF** and choose "Save as PDF" as the printer in the browser's print dialog.
  No extra server software needed; this uses the browser's built-in PDF
  renderer, which works identically on every host.
- **Two email options** — `config/mail_config.php` now lets you choose:
  - `MAIL_DRIVER = 'php_mail'` — the original built-in PHP `mail()`, or
  - `MAIL_DRIVER = 'smtp'` — a real SMTP send, with `SMTP_PROFILE` switching
    between:
    - `'mailtrap'` — routes everything to a [Mailtrap](https://mailtrap.io)
      test inbox so nothing goes to real staff while you're testing
    - `'production'` — your real SMTP server (cPanel mailbox, SendGrid, etc.)

  No Composer/PHPMailer required — a small dependency-free SMTP client
  (`includes/SimpleSmtpMailer.php`) handles STARTTLS/SSL + login auth.
  Visit **Administration → Test Email Setup** (admin/superadmin only) to send
  a one-off test email and confirm whichever option you've picked is working.

## What's new in this update

- **Granular permissions** — a new **Administration → Permissions** page lets
  superadmin/admin grant a *specific person* extra abilities on top of their
  role, without changing their role or affecting anyone else. This is what
  now controls things like who can issue stock out of Main Store, adjust
  stock, manage suppliers/items/outlets, create purchase orders, etc. Every
  role still has sensible defaults out of the box (nothing changes unless you
  touch this page), and it never *removes* what a role already grants — only
  adds. The sidebar automatically hides any menu item a user doesn't (yet)
  have access to. Run `sql/schema_update_v3.sql` to add the supporting table.
  Users, Departments, Approval Workflow, and Test Email Setup remain
  admin/superadmin-only and aren't grantable, since they touch system-wide
  configuration.
- **Type-to-search item picker** — the item fields in Purchase Requisition,
  Store Requisition, and Return to Supplier forms are now a search-as-you-type
  box instead of a long dropdown. Start typing an item's name and matching
  items appear below to click.

## What's new in this update

- **Fixed: unread notification count not decreasing** — clicking a
  notification now marks it read *before* redirecting you to it (via a new
  `notify_go.php`), so the 🔔 badge count correctly drops. Previously the
  "read" marker was only ever applied by the "Mark all as read" button.
- **Department-scoped requisitions** — by default, each person now only sees
  Purchase/Store Requisitions from their own department (via `users.department_id`)
  plus anything they personally requested. Direct links to a requisition
  outside your department are blocked too, not just hidden from the list.
  A new permission, **"View Requisitions From All Departments"** (under
  **Administration → Permissions**, category "Requisitions"), controls who
  can see everything — it defaults on for `admin`, `manager`, `accounts`,
  `gm`, and `audit`, and superadmin/admin can grant it to any specific
  individual too.

  No new SQL is needed for either fix — both reuse existing columns/tables.
  Make sure each user has the right `department_id` set on **Administration →
  Users** (and each outlet has the right department under **Outlets**) for
  the scoping to work as expected; a user with no department set will only
  ever see their own requests plus any requisition that also has no
  department attached.

## What's new in this update

- **True permission overrides** — the Permissions page now uses a proper
  Allow / Deny / Default selector per permission, per user. Previously you
  could only *add* extra access on top of a role; now superadmin/admin can
  also explicitly **Deny** something a role would normally include — e.g. a
  particular manager who shouldn't see other departments' requisitions, even
  though "manager" as a role can by default. Run `sql/schema_update_v4.sql`
  to add this.
- **`manager` no longer defaults to cross-department visibility** — only
  `admin`, `accounts`, `gm`, and `audit` see all departments' requisitions by
  default now. A manager only sees their own department's requisitions plus
  anything they personally submitted, unless specifically given the "View
  Requisitions From All Departments" permission.
- **Dashboard scoped to each person** — managers, supervisors, and staff now
  only see dashboard numbers relevant to them: pending requisition counts,
  the outlet count, and recent stock movements are all limited to their own
  department (or their own submissions) unless they hold "View Requisitions
  From All Departments" or "View Main Store Stock". The Low Stock card is
  hidden entirely for anyone without Main Store visibility.

## What's new in this update

- **Role defaults are no longer hardcoded** — there's now a separate
  **Administration → Role Permissions** page where superadmin/admin decide
  what each role (manager, supervisor, staff, accounts, gm, audit, opm, hr,
  even admin) gets by default. This fully replaces the old built-in default
  list. Run `sql/schema_update_v5.sql` to add the supporting table — it
  seeds **only `admin`** with full access; every other role starts with
  nothing checked until you turn permissions on for that role. superadmin is
  untouched and always has everything, on every screen.
- The existing **Permissions** page (per individual user) still works exactly
  as before on top of this — Allow/Deny for one specific person always wins
  over whatever the Role Permissions page says for their role.

## What's new in this update

- **Auto-generated item codes** — the code field is gone from the Add Item
  form; every new item automatically gets a code like `ITM-00001` based on
  its own ID. Codes are permanent once assigned (editing an item no longer
  changes its code).
- **Expiry date tracking + alerts** — mark an item as "Track expiry dates"
  in the item form, and receiving goods against it will ask for an expiry
  date per batch (different deliveries of the same item can expire on
  different dates, so this is tracked per batch, not per item). A new
  **Reports → Expiring Items** page lists everything expiring within a
  chosen window (7/30/60/90 days), colour-coded by urgency, and the
  dashboard shows an "Expiring Soon (30d)" count for anyone who can view
  Main Store. Run `sql/schema_update_v6.sql` for the supporting table.
- **Dashboard stock movements polish** — quantities now show their unit
  (e.g. "12 kg"), the transaction type is a colour-coded badge (green for
  goods in, red for returns/expiring concerns, etc.), and dates now render
  as two lines, e.g. `Jul 19, 2026` / `03:32 PM`.
- **Add Item is now a slide-out drawer** — clicking "+ Add Item" or "Edit"
  opens a panel from the right instead of a form embedded in the page; no
  page reload needed to open it.

## What's new in this update

- **Waste & Breakage module** — a dedicated place to record stock written off
  to breakage, spillage, expiry, spoilage, preparation waste, damage, theft/
  loss, etc. — separate from supplier returns and generic adjustments.
  - **Waste → Record Waste**: pick Main Store or a specific outlet, add one
    or more items with quantity, unit cost, and a reason per line. It
    immediately deducts the stock (from Main Store or that outlet) and logs
    it in the audit trail.
  - **Waste → Waste Records**: a running log of every waste entry.
  - **Reports → Waste Report**: totals by reason and by item over a date
    range, so you can see what's driving losses (e.g. "Breakage" is costing
    more than "Expired" this month).
  - **Administration → Waste Reasons**: manage the dropdown list of reasons
    yourself — add new ones (e.g. "Fridge breakdown"), deactivate old ones.
  - Controlled by a new `waste.manage` permission (Role Permissions /
    Permissions pages) — only `admin` gets it by default, same as everything
    else added recently; turn it on for whichever roles should be able to
    log waste (e.g. supervisor, staff).
  - Run `sql/schema_update_v7.sql` for the supporting tables.
- **Fully mobile responsive** — the sidebar is now a slide-out drawer on
  phones/tablets (hamburger menu + notification bell in a mobile top bar),
  every table on every page scrolls horizontally instead of squashing, forms
  and grids stack to a single column on small screens, and the printable
  documents adapt their padding/layout too.

## What's new in this update

- **Full accounting module** — a real double-entry general ledger sitting
  alongside the inventory system:
  - **Chart of Accounts** — seeded with the accounts you supplied (account
    code, description, account type). You can add/edit/deactivate more from
    the UI. Two notes on the data you gave: account `41001` was listed
    twice (kept once), and account `81000` was given the description
    "Income-Interest Received" under the "Profit/Loss on Sale of
    Non-Current Asset" type — that looked like a copy/paste slip from
    `41001`, so it's seeded as "Profit/Loss on Disposal" instead; rename it
    from **Chart of Accounts** if you meant something else.
  - **Journal Entries** — manual double-entry postings (debits must equal
    credits to save). Voiding an entry creates a reversing entry rather than
    deleting history.
  - **General Ledger** — full transaction detail for any account over a date
    range, with opening/closing balances.
  - **Trial Balance** — every account's balance as of a date, with a
    debit=credit check.
  - **Income Statement (P&L)** — Revenue, Cost of Sales, Gross Profit, Other
    Income, Administrative/Other Expense, down to Net Profit/Loss, for any
    date range.
  - **Balance Sheet** — Assets, Liabilities, Equity as of a date, including a
    live "Current Year Earnings" line computed from the P&L accounts so it
    balances before any formal year-end closing entry.
  - **Accounts Payable** — raise a bill against a supplier (you choose which
    account was purchased against; it auto-posts Dr that account / Cr Trade
    Payables), then record payments against it (Dr Trade Payables / Cr your
    chosen cash/bank account), with an aging-friendly overdue highlight.
  - **Accounts Receivable** — its own customer/debtor list, invoices (Dr
    Trade Receivables / Cr the income account you choose), and receipts (Dr
    cash/bank / Cr Trade Receivables).
  - All five new permissions (Chart of Accounts, Post Journal Entries, View
    Reports, Manage AP, Manage AR) live under **Administration → Role
    Permissions**, category "Accounting" — only `admin` gets them by
    default, same pattern as everything else.
  - Run `sql/schema_update_v8.sql` for all of this.

Note: Goods Received and Store Requisitions aren't auto-posting to the
ledger yet (e.g. a GRN doesn't automatically create an AP bill or a Dr
Inventory / Cr Payables entry) — that's a natural next step if you want
inventory movements to flow straight into the books, but it needs a bit more
design (e.g. mapping item categories to specific COGS accounts) so I've left
it as manual for now via the "Record Supplier Bill for This Delivery" button
on a Goods Received Note.

## 1. Install the database tables

1. Open phpMyAdmin for the `bestwest_ivm` database (the link you already use).
2. Go to the **SQL** tab.
3. Paste the entire contents of `sql/schema.sql` and run it.
   - This creates all new tables (departments, outlets, items, stock, purchase
     requisitions, purchase orders, GRNs, store requisitions, approval workflow, logs).
   - It does **not** touch your existing `users` or `loan_approval_stages` tables.
   - It seeds sensible default categories, units, departments, and a default
     4-stage Purchase Requisition workflow (Supervisor → Manager → Accounts → GM)
     and 2-stage Store Requisition workflow (Supervisor → Manager). You can edit
     this later from **Administration → Approval Workflow** in the app.

1b. Then also run `sql/schema_update_v2.sql` the same way — it adds `email`
and `signature_path` to `users`, plus the `notifications` and
`return_to_supplier`/`return_to_supplier_items` tables.

1c. Then run `sql/schema_update_v3.sql` — it adds the `user_permissions`
table used by the new Permissions page.

1d. Then run `sql/schema_update_v4.sql` — it upgrades `user_permissions` to
support explicit Deny overrides, not just grants.

1e. Then run `sql/schema_update_v5.sql` — it adds the `role_permissions`
table used by the new Role Permissions page. **Important:** this seeds only
`admin` with access to everything; every other role (manager, supervisor,
staff, accounts, gm, audit, opm, hr) will have **no** optional permissions
until you visit **Administration → Role Permissions** and turn on what each
role should have (e.g. turn on "View Main Store Stock" and "Issue Stock to
Outlets" for `manager` if that's how you want it). Until you do that, only
admin/superadmin will be able to use those features.

1f. Then run `sql/schema_update_v6.sql` — adds the `track_expiry` column on
items and the `item_batches` table used for expiry alerts.

1g. Then run `sql/schema_update_v7.sql` — adds the Waste & Breakage tables
(`waste_reasons`, `waste_records`, `waste_items`) and seeds `admin` with
access to the new module. Turn it on for other roles via **Administration →
Role Permissions** (permission: "Record Waste / Spoilage") if they should be
able to log waste too.

1h. Then run `sql/schema_update_v8.sql` — adds the full accounting module
(Chart of Accounts, General Ledger, Trial Balance, P&L, Balance Sheet,
Accounts Payable, Accounts Receivable) and seeds the Chart of Accounts with
the accounts you supplied. Seeds `admin` with access to all five new
accounting permissions; turn them on for accounts/finance staff via **Role
Permissions**.

## 1i. Make the uploads folder writable

The `uploads/signatures/` folder needs to be writable by PHP so users can
upload signatures. On cPanel, set its permissions to `755` (or `775` if 755
doesn't work) via File Manager, or run `chmod 755 uploads/signatures` over SSH.

## 2. Configure the database connection

Edit `config/db.php` and set your real MySQL username/password (from cPanel →
MySQL Databases), e.g.:

```php
$DB_HOST = 'localhost';
$DB_NAME = 'bestwest_ivm';
$DB_USER = 'bestwest_ivm_xxxx';
$DB_PASS = 'your-real-password';
```

## 3. Upload the files

Upload the whole `hotel-ivm` folder to your hosting, e.g. as a subfolder:
`public_html/ivm/`. Then in `config/config.php` set:

```php
define('BASE_URL', '/ivm/');
define('APP_URL', 'https://bestwesternplusyenagoa.com/ivm/'); // used in email links
```

(If you put it at the web root instead, leave `BASE_URL` as `/` and set
`APP_URL` to `https://bestwesternplusyenagoa.com/`.)

Also review `MAIL_FROM_ADDRESS` in `config/config.php` — ideally an address on
your own domain (e.g. `noreply@bestwesternplusyenagoa.com`) so emails aren't
marked as spam.

### Choosing your email method

Open `config/mail_config.php`:

```php
define('MAIL_DRIVER', 'php_mail'); // or 'smtp'
```

- Leave as `'php_mail'` if you just want the simplest option (uses your
  hosting's mail server).
- Set to `'smtp'` for more reliable delivery, then set `SMTP_PROFILE`:
  - `'mailtrap'` — fill in the `MAILTRAP_*` constants with the values from
    your Mailtrap inbox (Settings → SMTP Settings → PHP tab). Great for
    testing the approval workflow without emailing real staff.
  - `'production'` — fill in the `SMTP_*` constants with your real mailbox or
    provider (host, port, username, password, encryption).

After changing anything, go to **Administration → Test Email Setup** and send
yourself a test message to confirm it works before relying on it.

Each staff member should visit **Profile** and enter their email address once
— notifications only email people who've done this (everyone still gets the
in-app 🔔 notification either way).

## 4. Log in

Use any existing username/password from your `users` table. Passwords in the
existing table are supported whether they're already bcrypt-hashed or still
plain text — the system auto-upgrades a plain-text password to a secure hash
the first time that user logs in successfully.

Users with `must_change_password = 1` are forced to set a new password before
they can use the rest of the app.

## 5. How the roles are used

Your `role` enum (`superadmin, admin, hr, audit, gm, accounts, manager, staff,
supervisor, opm`) drives two things:

- **Menu / page access** — e.g. only `admin`/`superadmin` can manage Users,
  Departments, and the Approval Workflow. Master data (Items, Suppliers,
  Outlets) is editable by `admin`/`manager`. `superadmin` can access everything.
- **Approval stages** — Purchase Requisitions and Store Requisitions move
  through an ordered chain of roles (configurable in **Approval Workflow**).
  A requisition only advances when someone holding the *required role for its
  current stage* clicks Approve. `admin` can also approve any stage as a
  fallback, and `superadmin` can override anything.

## 6. Typical flow

**Purchasing:**
`Purchase Requisition (any staff)` → approved through the configured chain →
`Purchase Order` (assign supplier + confirm costs) → `Receive Goods (GRN)`
→ Main Store stock increases automatically (with moving-average costing).

**Store issuing:**
`Store Requisition (outlet staff)` → approved through the configured chain →
`Issue Stock` (admin/manager) → Main Store stock decreases, Outlet stock
increases, fully logged in `stock_transactions`.

Every stock movement is recorded in `stock_transactions` for a full audit
trail, viewable via **Reports → Stock Report** and on the dashboard.

## 7. Security notes before going live

- Change the default reset password (`welcome123`) logic in `users.php` if
  you'd like something different.
- Consider enabling HTTPS-only cookies and a stronger session configuration
  in `config/config.php` for production.
- Back up your database before running any new SQL.

## File map

```
config/         DB + app config
includes/       auth, helpers, header/sidebar/footer layout
sql/schema.sql  full database schema + seed data
items.php, categories.php, units, suppliers.php, outlets.php, departments.php
main_store.php  stock overview + manual adjustments
purchase_requisitions/   create, list, view/approve
purchase_orders/         create, list, view, receive (GRN)
store_requisitions/      create, list, view/approve/issue
reports/        stock_report.php, low_stock.php
users.php, approval_workflow.php   admin-only configuration
```
