# Deploying Semita BillPro v2 to cPanel

This is a Laravel 12 app. Unlike v1 (Next.js/Node), it runs on standard PHP, no Node
process, no Puppeteer/Chromium, no memory-limit fights. PDFs use dompdf (pure PHP).

**Requirements on the host:** PHP **8.2+**, MySQL/MariaDB, Composer, and the usual PHP
extensions (`pdo_mysql`, `mbstring`, `openssl`, `tokenizer`, `xml`, `ctype`, `json`,
`bcmath`, `fileinfo`, `curl`, `gd`). cPanel's "Select PHP Version" lets you set 8.2 and
enable extensions.

---

## 0. Build assets locally first (one time, on your PC)

The server does **not** need Node. Build the compiled CSS/JS on your machine and upload them:

```bash
npm install
npm run build          # creates public/build/  (commit or upload this folder)
```

---

## 1. Create the database (cPanel)

cPanel → **MySQL Databases**:
1. Create a database, e.g. `cpaneluser_billpro`.
2. Create a user with a strong password.
3. Add the user to the database with **All Privileges**.

Note the database name, username, password, they go in `.env`.

---

## 2. Upload the application files

**Preferred layout** (most secure): upload the project to a folder *above* `public_html`,
e.g. `/home/cpaneluser/semita-billpro`, then in cPanel → **Domains** set the domain's
**Document Root** to `/home/cpaneluser/semita-billpro/public`.

**Fallback** (if you can't change the document root): upload everything into `public_html`,
then rename `.htaccess.cpanel` → `.htaccess` in the app root (it routes all traffic into
`/public`).

Upload everything **except**: `node_modules/`, `.git/`, `tests/`, the local `.env`, and the
local sqlite db. **Do** upload `vendor/` (or run step 3 to install it on the server) and
`public/build/`.

> Tip: zip the project locally (excluding the above), upload the zip via File Manager, and
> extract on the server, much faster than FTPing thousands of files.

---

## 3. Install PHP dependencies (cPanel Terminal or SSH)

From the app root:

```bash
composer install --no-dev --optimize-autoloader
```

If Composer isn't on PATH, use the full path cPanel provides (e.g. `php /opt/cpanel/composer/bin/composer ...`).

---

## 4. Configure the environment

1. Copy the template and edit it:
   ```bash
   cp .env.production.example .env
   ```
2. Fill in `APP_URL`, the `DB_*` values from step 1, your `MAIL_*` (a cPanel email account),
   and `PAYNOW_*` if you have a Paynow merchant account (leave blank to disable online
   payments, manual banking details will show instead).
3. Generate the app key:
   ```bash
   php artisan key:generate
   ```

---

## 5. Migrate, link storage, optimize

```bash
php artisan migrate --force
php artisan storage:link           # so uploaded logos are publicly served
php artisan config:cache
php artisan route:cache
php artisan view:cache
php artisan billpro:sync-releases   # publish this build's "what's new" notes
```

> **`config:cache` must run BEFORE `billpro:sync-releases`.** The tarball excludes
> `bootstrap/cache/*.php`, so until config is re-cached the server is still reading the
> PREVIOUS deploy's `config/releases.php`. Run sync first and it prints "Version history
> already up to date" at the old version and publishes nothing.

> If `storage:link` is blocked on your host, instead create the symlink in File Manager,
> or set `FILESYSTEM_DISK=public` and copy `storage/app/public` to `public/storage`.

Make sure `storage/` and `bootstrap/cache/` are writable (755/775).

---

## 5a. Turn on notifications for phones and laptops (one time)

Staff can install BillPro to a home screen the moment the site is live over https, with
nothing to configure. Notifications on those devices need one setup step, once per
installation:

```bash
php artisan billpro:push-keys
```

Paste the two lines it prints into `.env`, add a contact address, and rebuild the cache:

```
VAPID_PUBLIC_KEY=...
VAPID_PRIVATE_KEY=...
VAPID_SUBJECT=mailto:you@yourcompany.co.zw
```

```bash
php artisan config:cache
php artisan billpro:push-check          # confirms the keys and the https requirement
```

Then check it end to end from a real device: sign in, open **Profile menu → Notifications
& app**, turn notifications on, and press **Send a test**.

Things worth knowing before you touch this again:

- **The key pair is the identity.** There is no account to create and nothing to register
  with Google or Apple.
- **Keep the private key.** Regenerating it silently un-subscribes every device already
  registered. They re-subscribe on their own next visit, but they are quiet until then, and
  nothing anywhere reports it.
- **https is not optional.** Browsers refuse to register a service worker over plain http
  (localhost excepted), so without a valid certificate the app is neither installable nor
  able to receive anything.
- **Leaving the keys blank is a safe state**, not a broken one. The bell, the emails and
  the app all carry on, and the Notifications page says the server is not set up yet.

---

## 6. Create the first login

Public sign-up is **disabled** (internal-only). Create the owner account from the terminal:

```bash
php artisan app:create-owner "Ashly Dhani" "media@webdev.co.zw"
```

It will prompt for (or auto-generate) a password and create the workspace, which is
auto-provisioned with default settings and a USD base currency. Log in, then change the
password and invite staff from **Team Settings** (assign roles: admin / accountant / staff /
viewer).

---

## 7. Schedule the background jobs (one cron entry)

The overdue marker, payment reminders, and recurring-invoice generation all run via
Laravel's scheduler. cPanel → **Cron Jobs**, add one job running **every minute**:

```
* * * * * /usr/local/bin/php /home/cpaneluser/semita-billpro/artisan schedule:run >> /dev/null 2>&1
```

(Adjust the PHP binary path and app path to your host. cPanel often lists the correct PHP
path under "Select PHP Version".)

That single entry drives: `app:mark-overdue` (02:00), `app:send-invoice-reminders` (07:00),
`app:generate-recurring-invoices` (03:00).

---

## 8. Going live checklist

- [ ] `APP_ENV=production`, `APP_DEBUG=false` in `.env`
- [ ] HTTPS works (cPanel → SSL/TLS, AutoSSL)
- [ ] `APP_URL` matches the real https URL (Paynow return/result URLs derive from it)
- [ ] You can log in via the owner account
- [ ] A test invoice → "Download PDF" works
- [ ] A test invoice → client portal link opens without login and shows branding
- [ ] (If Paynow configured) a test payment completes and records on the invoice
- [ ] Cron job is listed and running
- [ ] `php artisan billpro:push-check` reports the VAPID keys as set
- [ ] On a real phone: the browser offers to install BillPro, and **Send a test** on
      Notifications & app produces a banner with a sound

---

## Updating later (redeploy)

**Before you build:** add the new version to the top of `config/releases.php` (version,
date, title, summary and one highlight per feature, each with the `route` the walkthrough
should visit and the `ability` needed to see it). That file is the release note, it ships
inside the tarball, and `billpro:sync-releases` turns it into the in-app "What's new"
notice, the guided tour and the `/whats-new` history. The house style for that copy —
short title, one-sentence summary, at most four highlights — is documented at the top of
`config/releases.php`; keep to it, a release note nobody finishes is a release note nobody
reads.

```bash
# locally: npm run build, then upload changed files + public/build
# Do not leave public/sw.js or public/images/pwa/ behind: the service worker must be
# served from the site ROOT (a worker under /build controls only /build), and the
# manifest points at those icons by name.
composer install --no-dev --optimize-autoloader
php artisan migrate --force
php artisan config:cache && php artisan route:cache && php artisan view:cache
php artisan billpro:sync-releases      # AFTER config:cache, see the note in section 5
```

Skipping the sync is recoverable but not silent-safe: the app self-heals on the next
request (it notices the config's newest version has no row and syncs once), so users still
get the notice, just run the command so it happens at deploy time rather than on someone's
first page load. The command is idempotent; running it twice does nothing.

Each person sees the notice **once**, on their next page load after the deploy, and can
either dismiss it or click **Show me** to be walked through the new features on the real
pages. Nobody is shown a feature their permissions don't cover, and staff who joined after
a release was published are not shown its backlog.

---

## What still needs you (not automatable from here)

- **Paynow keys**, Paynow uses a separate account per currency. Add `PAYNOW_USD_ID` /
  `PAYNOW_USD_KEY` and/or `PAYNOW_ZWG_ID` / `PAYNOW_ZWG_KEY` to enable online payments for
  each currency (an invoice in a currency with no configured account simply shows manual
  banking details instead).
- **ZIMRA fiscalisation**, VAT fields are in place; fiscal-device/FDMS integration is a
  separate compliance step to scope when you're ready.
- **The actual upload**, done by you with your hosting credentials.
