# Solar Link CRM — User Manual

**Version 1.2 · 4 November 2026**

This manual describes the CRM as it actually behaves: the screens in this
installation, what each one is for, and the rules behind the buttons. It is
written for the people who use it every day — the office, the sales desk, the
support team, the warehouse, finance, installers and whoever administers the
system.

Two things to know before you start:

- **The company name, logo and colours are yours.** Where this manual says "the
  company", read the name on the sign-in screen. It is set in
  **Settings → Company profile**, not baked into the software.
- **What you can see depends on your role.** If a screen or a button described
  here is missing, that is a permission, not a fault. Section 1.5 lists what each
  role may do, and section 15 explains who to ask.

---

## Contents

**Part A — Getting started**
1. [Signing in](#1-signing-in)
2. [Finding your way around](#2-finding-your-way-around)
3. [Your own account](#3-your-own-account)

**Part B — The work**
4. [Customers and sites](#4-customers-and-sites)
5. [Jobs](#5-jobs)
6. [Sales: leads and quotations](#6-sales-leads-and-quotations)
7. [Support tickets](#7-support-tickets)
8. [Installers and compliance](#8-installers-and-compliance)
9. [Inspections and warranty reports](#9-inspections-and-warranty-reports)
10. [Money](#10-money)
11. [Warehouse](#11-warehouse)

**Part C — Running the system**
12. [Settings and administration](#12-settings-and-administration)
13. [The public doors: portal, status board, inspection links](#13-the-public-doors)
14. [Routines: daily, weekly, monthly](#14-routines)
15. [Troubleshooting and FAQs](#15-troubleshooting-and-faqs)
16. [Glossary](#16-glossary)

**Part D — Guided walkthroughs**
- [W1. Add a new client](#w1-add-a-new-client)
- [W2. Add a job and get it ready to install](#w2-add-a-job-and-get-it-ready-to-install)
- [W3. Log a ticket and work it to an outcome](#w3-log-a-ticket-and-work-it-to-an-outcome)
- [W4. Warehouse: receive, pick, dispatch](#w4-warehouse-receive-pick-dispatch)
- [W5. Request an inspection and handle the result](#w5-request-an-inspection-and-handle-the-result)
- [W6. Take a backup before a change](#w6-take-a-backup-before-a-change)

**Appendices**
- [A. Roles at a glance](#appendix-a-roles-at-a-glance)
- [B. Status vocabularies](#appendix-b-status-vocabularies)
- [C. Administrator commands](#appendix-c-administrator-commands)

---

# Part A — Getting started

## 1. Signing in

### 1.1 Two kinds of account

One table holds both, and they never mix:

- **Staff accounts** open the CRM at the normal sign-in screen. They have a role,
  and permissions come from that role.
- **Customer portal accounts** open the ticket portal at `/ticket`, and only ever
  see that customer's own jobs, tickets and documents.

A customer's portal account is refused at the staff door, and a staff account is
not a portal login. There is no self-service staff sign-up: an administrator
creates staff accounts (section 12.9).

### 1.2 Three ways in

The staff sign-in screen offers, in this order:

1. **Continue with Google** — if the company has connected it. It signs you into
   the account that already exists here, matched on your work email. It never
   creates an account, and a personal Gmail only works if that address is on your
   staff record.
2. **Sign in with a passkey** — a fingerprint, face or device PIN. Fastest on a
   phone, and phishing-resistant. You add passkeys yourself in
   **Settings → Security**.
3. **Email and password** — always available, with **Forgot your password?** under
   it. The reset email uses the company's own mail server, so it arrives from the
   office address rather than a stranger's.

> **If Google refuses you** the screen says the account is not allowed, and the
> reason is recorded in **Settings → Sign-in history** for an administrator to
> read. It is not a password problem.

### 1.3 Two-factor authentication

If you have turned 2FA on in **Settings → Security**, every door asks for the
six-digit code (or a recovery code) after it accepts your password, passkey or
Google account. The factor belongs to the account, not to the method, so no door
skips it.

**Save your recovery codes when they are shown.** They are displayed once. If you
lose the phone and the codes, an administrator has to reset 2FA for you.

### 1.4 When the site is wearing its "coming soon" page

While the office has the holding page switched on (**Settings → Coming soon**),
visitors who are not signed in see it instead of the portal. Two things still
work:

- **Staff sign-in**, so nobody locks the company out of its own system.
- **The shared ticket status board**, because a crew has no account here — the
  username and password the office issued is their door.

### 1.5 Roles: what you can do

Permissions come from your role. **Super Admin** bypasses every check; every
other role is the sum of the permissions listed in Appendix A.

| Role | In one line |
|---|---|
| **Super Admin** | Everything, including staff accounts, roles and the audit screens. |
| **Managing Director** | Full visibility and reporting, including financials. |
| **Admin** | Runs the system: staff accounts, settings, all job data. |
| **Admin Support** | Data entry, documents, customer care, tickets. |
| **Operations Manager** | Installations end to end: scheduling, crews, compliance, closeout. |
| **Sales Manager** | Owns the pipeline through to signed contracts, then hands over. |
| **Sales** | Qualifies leads, quotes and signs up customers. |
| **Installer** | Field crew: job pack, install, completion evidence. |
| **Warehouse** | Picks stock, dispatches job packs, confirms receipt. |
| **Finance** | Invoicing, reimbursements, STC and export lodgements, reporting. |
| **Technical Support L1–L4** | Support desk, escalating by level (L1 triage, L2 diagnose, L3 resolve, L4 lead). |

> **Two decisions are narrower than the rest.** The **Vault** (section 12.11) is
> open only to Admin and Operations Manager. The **Oversight** screens — activity
> log, sign-in history, recycle bin — are open only to Super Admin. The status
> board's three actions have their own switches (section 13.2).

### 1.6 If you cannot see something

In order:

1. Check your role in the account menu. A colleague in the same role should see
   the same screens.
2. A part of the system may not have been released yet. **Settings → Release**
   switches whole modules on and off; if a part is hidden its link is absent and
   its address answers 404 for everybody, not only for you.
3. Reload the page — a permission changed mid-session is applied on the next
   request.
4. Ask an administrator with **Roles & permissions** access. They can grant the
   specific permission without changing your whole role.

---

## 2. Finding your way around

### 2.1 The sidebar

The sidebar is grouped by what you are doing, not by database table:

- **Work** — Dashboard, Inbox, Customers, Jobs.
- **Field** — Installers, Inspections.
- **Support** — Tickets.
- **Sales** — Leads, Quotations.
- **Money** — Invoices, Reimbursements, Export lodgements.
- **Operations** — Department tasks.
- **Warehouse** — dashboard, Products, Stock levels, Stock ledger, Suppliers,
  Categories.
- **Admin** — Staff, Customer accounts, Roles & permissions, Settings.

Groups with nothing you may open are hidden rather than shown greyed out, so a
short sidebar is normal for a focused role.

### 2.2 The search in the top bar

One box searches **customers, their sites, jobs, tickets and staff** at once, and
groups the results by type. It is the fastest route to a record when you have a
name, a phone number, a job number or a ticket number. `Enter` on a group's
heading opens the full list for that search.

### 2.3 The dashboard

The dashboard is your queue, not a brochure:

- Work that is waiting on you (jobs by stage, tickets assigned to you).
- Upcoming installations.
- Money and pipeline figures if your role sees financials.

If a tile is empty, it usually means the filter underneath it matches nothing —
check the date range before assuming there is no work.

![The operations dashboard](screenshots/dashboard.png)

*The dashboard: what is live, what is stuck, and the jobs that cost money when
they slip.*

### 2.4 What a record page looks like

Every record — customer, job, ticket, installer — is laid out the same way: a
header with its number and state, panels down the left for the substance, and a
**timeline** at the bottom recording what changed, when, and by whom. Attachments
and documents sit in their own panel. Nothing is written only to a log: if the
timeline says it happened, the record shows the result too.

---

## 3. Your own account

Open the account menu (bottom of the sidebar) → **Profile** and **Settings**.

- **Profile** — your name, work email, phone, job title, department, photo. Your
  email address is the one Google matches, so ask an administrator before
  changing it.
- **Settings → Security** — change your password, turn 2FA on or off, add or
  remove passkeys, and view recovery codes.
- **Settings → Appearance** — light, dark, or follow the operating system. This is
  per browser, not per account.
- **Sign out** — always offered. The shared status board has its own sign-out
  (section 13.2); signing out of the CRM does not close a crew's board session
  and vice versa.

---

# Part B — The work

## 4. Customers and sites

### 4.1 What a customer is

A customer is the person or business the company deals with. It holds their
contact details, billing address, the sales and ownership fields, and everything
that hangs off them: **sites, jobs, tickets and documents**. One customer can
have many sites and many jobs across the years.

Both people and companies exist: a residential customer is a person, a commercial
one is a business with a contact. The list shows both together, with a type badge.

### 4.2 Finding and creating a customer

1. Use the top-bar search first — a duplicate is the most common data problem.
2. **Customers → New customer**. At minimum a name and a contact route.
3. Fill the **contact**, **billing address** and **sales and ownership** panels.
   The owner is the staff member who looks after the account.
4. Add the **site** (section 4.3) straight away if the work is real; a job needs
   one.

![The customer list](screenshots/customers.png)

*The list shows people and businesses together, with the owner and the last
contact, so a duplicate is easy to spot before you create one.*

### 4.3 Sites

A site is a physical property: the address, the grid details (NMI, distributor,
phase), the system installed there, and who owns it. Sites are nested under the
customer because every site belongs to someone, but a site can outlive a customer
relationship.

- **Multiple sites** are normal — a landlord with several rentals, or a business
  with two premises.
- **The property changes hands.** Record the new owner through the ownership
  panel rather than editing the old one. The job's history keeps everybody who has
  owned the property, with the date and the reason, so a warranty claim three
  years later can still be traced to the right household.
- **Grid connection** details live on the site, and the job's own grid panel
  reads them. If a job says the grid application is blocked, check the site first
  — a missing NMI or distributor is the usual cause.

### 4.4 The customer page

Panels, in order:

- **Contact** and **billing address**.
- **Sales and ownership** — owner, sales rep, how they were referred.
- **Account** — the sale value and deposits on their jobs, the amount invoiced
  and what is still outstanding. It is shown only to staff who may see customer
  financials.
- **Sites** — every property, with its address and system.
- **Jobs** — every job, whatever its stage.
- **Support tickets** — every ticket ever logged, not just open ones.
- **Documents** — certificates, contracts, compliance paperwork.

The **timeline** at the bottom records changes to the record itself.

![A customer record](screenshots/customer.png)

*A customer: contact and billing at the top, then their sites, jobs, tickets and
documents, and the history behind all of it.*

### 4.5 Customer portal accounts

A portal sign-in is what lets a homeowner follow the job and log a fault
themselves. It is created from the customer, not by the customer:

1. Open **Customer accounts** (sidebar, Admin group) or the customer's own page.
2. Create the account for that customer. The email is the sign-in; the system
   sends a verification and, separately, either an invitation or a password
   reset.
3. The customer signs in at `/ticket`, with the portal's own sign-in screen.

Things worth knowing:

- **Verification matters.** By default a new account has to confirm its email
  before it can see anything, because the portal shows a household's address and
  system history. The office can switch that requirement off in
  **Settings → Customer portal** — do it deliberately, not to save a minute.
- **Pausing is not deactivating.** Pausing a portal account blocks the sign-in and
  leaves the customer, their jobs and their tickets fully visible to staff.
  Deactivating the customer is a different decision.
- **The portal shows only their own data.** A portal user cannot reach another
  customer's job by changing a number in the URL.

### 4.6 Profile change requests

A customer who wants their details changed asks from the portal. The request
arrives for the office to approve or reject; approving writes the change to the
customer record. This is how you keep a customer from editing their own address
without the office seeing it — a billing address is also a fraud surface.

### 4.7 Documents

Documents attach to a customer, a job or a site, and carry a category (contract,
electrical certificate, compliance, invoice, photo, other). Upload from the record
they belong to. Documents are served through the application, not from a public
folder, so a link cannot be guessed.

> **Delete means hide.** Removing a record or a document hides it rather than
> destroying it. A Super Admin can put it back from the **Recycle bin**, and the
> activity log still holds the trace. Treat deletion as "no longer current", not
> as "gone".

### 4.8 Builder accounts and their clients

A building company that sends work our way is a customer of type **Builder**. It
is an account like any other — company name, ABN, contact, account manager — and
the households and businesses it sends are customers under it.

- **Clients sit under the builder.** Open the builder's page and its **Clients**
  panel lists everyone under that account. **New client** opens the customer form
  already pointed at the builder, and **Assign a client** puts a customer that
  already exists under it. Nothing is moved: the client keeps their own sites,
  jobs, tickets, billing and portal sign-in, and only the link changes.
- **The client says whose client it is.** A client's page carries a note across
  the top — *Under Rivergum Homes* — linking to the builder, and the customer
  list shows the same line under the name. **Take off builder** removes the link,
  and the client's record is otherwise untouched.
- **Work is raised against the client, not the builder.** From the builder's
  Clients panel, **New job** opens a job for that client, whose site and grid
  details are theirs. That is what makes the warranty follow the roof.
- **A builder is never somebody else's client.** A builder account cannot be put
  under another builder, and the form drops the field rather than saving it.

Assigning a client needs permission to edit both records, so somebody who may
change the building company cannot rewrite a household's record by posting to
the builder's page.

The building companies already in the list can be made builder accounts in bulk:
`php artisan crm:find-builders` prints the accounts whose name says they build,
and `--commit` converts them after taking a verified backup (Appendix C). A name
is a suggestion rather than proof, so read the list before committing to it — and
if a company is missed, change its type on its profile.

A builder that arrived as several records — the job sheet made one customer per
line, so the same company can be there fifteen times — is put back together with
`crm:merge-customers`: its sites, jobs, tickets and documents move onto one
record, the duplicates are archived with a note saying where their work went, and
the surviving record is made the builder account. It prints the plan and changes
nothing until `--commit`, and the report it writes can undo the merge.

### 4.9 Importing the old Client Information sheet (temporary)

The old **Client Information** sales workbook can be read into the clients who
are already here. This is a migration door rather than a permanent import, so it
is opened and closed by hand on **Settings → Client information import**.

1. Open the door on that settings screen. The customer list then carries a
   **Client information import** button beside **New customer**.
2. Choose the workbook. Every tab with a client name, a sale price and a balance
   is read; the `Paperwork`, `Cancelled` and contract-summary tabs are ignored.
3. Read the preview. It shows the customer and job each row matched, the sale
   price, deposit and balance, and the rows it will leave alone. Nothing is
   written yet.
4. Press **Import**. A verified database backup is taken first.
5. Close the door on the settings screen when the migration is finished.

What a row writes:

- **Sale price** becomes the job's contract value, and **Deposit** its deposit.
  The contract date becomes the job's sold date when the job has none.
- **Balance** becomes one customer invoice: the invoice is the deposit plus the
  balance, the deposit is recorded as paid, and what remains is the sheet's own
  balance. That is what Finance shows as still owed.
- **No customer is created.** A row is matched by phone number first, then by
  exact name — and by property address when two customers share a name. Rows
  that match nobody, or whose jobs have all been claimed by other rows, are
  listed in the report rather than guessed at.

Running the same workbook again updates the jobs and invoices it already wrote;
it does not make a second set. An invoice somebody typed by hand on the same job
is left alone: the job figures still update, but the manual invoice is not
replaced. The report names every row and the rows needing a person, under
`storage/app/import`.

The command-line equivalent is
`php artisan crm:import-client-information "Client_Information.xlsx"`; without
`--commit` it only reports.

---

## 5. Jobs

### 5.1 What a job is

A job is one piece of work at one site: an install, a battery, an EV charger, an
upgrade, a service call, an inspection-only visit or maintenance. It carries the
system specification, the schedule, the crew, the paperwork, the money and the
history. Most of the company's work happens on this page.

### 5.2 Creating a job

1. **Jobs → New job**, or from the customer's page when the context is obvious.
2. Choose the customer and site, the **job type**, and who owns it.
3. Fill the system specification: panel and inverter model, battery, phase,
   configuration.
4. Save. The job starts in its first pipeline stage and appears on the board.

A job with no site is a job nobody can install. If the site does not exist yet,
create it from the customer first.

![The new job form](screenshots/jobs-create.png)

*Choosing the customer, the site and the job type — the system specification
below it is what the warehouse picks against.*

### 5.3 The job page

Panels top to bottom:

- **Installation workflow** — the current stage, the next allowed move, and who
  may make it.
- **What is blocking progress** — a plain list of missing evidence: no signed
  contract, no grid approval, no stock pick, no inspection booked. Work this list
  rather than hunting through panels.
- **Grid connection** — application status, NMI, distributor, approval dates.
- **Electrical inspection** — the job's inspection state and any defects raised.
- **State rebate program** and **STC claim** — the incentives side.
- **System specification** — what is being installed.
- **Stock readiness** and **installation parts** — what the warehouse has picked
  and what the crew needs.
- **Milestone dates**, **assignments**, **owners of this property**.
- **Financials** — the money, for roles that may see it.
- **Documents and evidence**, and the **activity timeline**.

![A job page](screenshots/job.png)

*A job in the installation stage: the workflow offers the next legal move, and
the panels underneath hold the grid, the inspection, the stock and the money.*

### 5.4 The pipeline and who moves it

The stages are listed in Appendix B. They run from **Lead / enquiry** through
quote, contract, inspection, design, grid approval, stock, dispatch, install,
electrical inspection, grid connection and STC, to **Completed** or **Cancelled**.

Moving a stage is a permission, not a free-for-all: sales moves the sales stages,
operations moves scheduling and installation, compliance moves grid and
inspection outcomes, finance and closeout move the last steps. If the button is
not offered, your role does not cover that move — ask the owner of the stage
rather than working around it.

Every move is recorded on the timeline with who made it and the note that
accompanied it, so "who cancelled this and why" always has an answer.

### 5.5 Scheduling a crew

1. Move the job to **With installers / install booked**.
2. Choose the **crew from the installer register** — not a staff account. The
   register is the compliance record; a crew is not a sign-in (section 8).
3. Set the date and any access notes (gate codes, dogs, who will be home).
4. The crew's job pack is built from the job: site address, system, parts,
   documents.

If a crew is booked but the register shows an expired licence or ticket, that is
deliberate visibility — an expired credential is what makes a crew unusable on a
given day. Fix the credential before the visit, not after.

### 5.6 Completion and closeout

The end of a job is evidence, not a tick:

- **Installed** — the crew has finished and uploaded their evidence.
- **Awaiting electrical inspection** — booked, or waiting on the inspector.
- **Electrical inspection passed / failed** — failed sends the job to
  rectification; it does not "mostly pass".
- **Grid connection and metering** — the network has connected and the meter is
  changed.
- **STC claim and closeout** — paperwork and the incentive claim.
- **Completed** — everything above is done.

The blocking panel tells you what is still missing. A job cannot be completed with
outstanding defects or an unclaimed STC without somebody deliberately overriding
— and that override is recorded.

### 5.7 Importing jobs from a spreadsheet

The office can read a job sheet in three ways, each with its own settings card on
**Settings → Sheet import**: a Google Sheet by link, a workbook uploaded, or the
command line (Appendix C). The screen shows what it found **before** anything is
written, and importing is off until it is switched on.

The **Componentry** column is read the way an installer writes it. A wattage with
`W` is a panel, and the name beside it is the brand — before the number
("Fronius 5.0") or after it ("475W RISEN"); `kWh` is the battery and `kW` is the
inverter. `2 x` in front of a battery means two of them, while a total in
brackets is taken as the total. A model code can carry the size ("SAJH-5.0-S3",
"SPB5K", "10kS2"). Brands are matched to the warehouse catalogue, so the
sheet's "RISEN" is stored as the catalogue's Risen Solar, and a model is
recorded where the catalogue knows it. A clause describing the old system being
removed, a dongle, a meter, an optimiser or mounting is not part of the new
system, so "decommission old 20 panel set" never becomes the new job's panel
count. The preview shows the reading under each row before anything is written;
once imported, the job card carries the panels, the inverter, the battery and
whether the system is grid-connected, hybrid or AC-coupled.

Two more columns are read when the sheet has them: **Export limit (kW)**, the
figure set on the inverter, and **Allowed export (kW)**, the limit the network
has approved. Both are matched under the names sheets use — "Export", "Export
Limit", "Allowed Export", "Approved Export", "DNSP Export" — and a value of
`0` is kept as zero because a constrained connection may allow no export at
all. A blank stays blank rather than being guessed at.

- The importer is additive and repeatable: running the same sheet twice updates
  what it wrote rather than duplicating it.
- A row is matched to the client who is already here before a new customer is
  made. The address on file comes first, then the phone number, then the email.
  If the number has changed since the record was typed, the name is the last
  resort — but only when exactly one customer in that postcode (or failing that,
  that suburb) carries it. Two households called Smith are not one household, so
  a shared name is never guessed at and the row becomes its own record.
- A building company is a customer too. A row naming a builder already in the CRM
  is written against the builder's account rather than a second copy of the
  business, and the preview says so. The name alone is enough there, because a
  builder's billing address is its office rather than the roof the sheet is about.
- Everything a run creates is stamped with the sheet it came from, so a "replace
  what that run wrote" action never touches a row somebody typed by hand.
- A scheduled run only writes a report until **Import new rows automatically** is
  switched on. Watch a report first.

---

## 6. Sales: leads and quotations

### 6.1 Leads

A lead is an enquiry that is not yet a job. It carries where it came from, the
contact, the site, the system they are interested in, and the follow-up history.
The statuses are in Appendix B; the short version is that a lead moves from **New**
through contact attempts to **Qualified**, then either **Won** (becomes a job) or
**Lost** with a reason.

Keep the follow-up log honest. "Contacted" means a conversation happened, not that
a voicemail was left.

### 6.2 Quotations

A quotation is built from a lead or a customer, with line items and totals, and
moves **Draft → Awaiting approval → Sent → Accepted or Declined**, or **Expired**
if it sits too long. Approved quotations are what the job's financials and the
contract start from.

- A quotation that is sent but not accepted is a live conversation; do not create a
  second one for the same scope without marking the first declined or expired.
- Prices are visible to the roles with quotation access and financial visibility;
  an installer does not see the margin.

### 6.3 Handover to operations

When the contract is signed, move the job on to the pre-installation stages and
assign the operations owner. The handover is the job page, not an email: the site,
the system, the quote and the documents are already there.

---

## 7. Support tickets

Tickets are the heart of the after-sales business. One ticket is one issue, at one
site, with one conversation and one outcome.

### 7.1 Logging a ticket

1. **Tickets → New ticket**.
2. Choose the **customer** and, if you know it, the **site** and **job**. The site
   matters: a fault is diagnosed at a property, not at a person.
3. Set the **category** (7.3), the **priority**, and how the issue was reported.
4. Write the issue in the description box. It accepts formatting, and the text is
   sanitised — pasted email markup will not break the page.
5. Choose the **assignee(s)**. If you name nobody, the ticket is assigned to you:
   a ticket nobody owns is one nobody chases.
6. Attach any photos or screenshots the customer sent, then save.

The ticket gets a number you can quote on the phone, and it appears in the list
immediately.

![The ticket list](screenshots/tickets.png)

*The ticket list, with the active count, the category and how long each has been
sitting.*

### 7.2 What a ticket holds

- **The issue** — description, category, priority, source.
- **Who and what** — customer, site, job, installer, assignees.
- **Contact as reported** — the name and number the customer gave, which may not
  be the account's details.
- **The feed** — one conversation (7.5).
- **Photos and screenshots**.
- **Other tickets** on the same job, customer or installer, so a repeat fault is
  obvious.
- **Ticket status** — the panel that moves it and records the outcome (7.7).
- **The timeline** — status moves and changes, separate from the conversation.

![A ticket](screenshots/ticket.png)

*One ticket: the issue, the people and what it is attached to, the feed, and the
outcome in its own card.*

### 7.3 Categories: what the ticket is about

Category is a separate question from status. Status says how far the work has got;
category says what kind of work it is:

| Category | Use it for |
|---|---|
| **Warranty claim** | A failed part or system the manufacturer has to assess or replace. |
| **Technical fault** | Solar, inverter or monitoring not working as it should. |
| **Battery fault** | Battery or BMS: not charging, not discharging, a BMS error. |
| **Installation issue** | Workmanship, or something the crew must go back and put right. |
| **Enquiry** | A question with nothing broken — production, settings, a quote, an invoice. |
| **Paperwork and compliance** | Certificates, STC assignment, grid approval, forms. |
| **Other** | Anything else, with the detail in the notes. |

The wording is read for a starting point when a ticket arrives by SMS, but a
category a person chose is never overwritten by that guess. A blank category means
nobody has looked yet — it is better left blank than guessed wrong.

### 7.4 Statuses: how far the work has got

See Appendix B for the full list with meanings. The ones that matter most:

- **Open** — logged, not yet picked up.
- **In progress** — someone is on it.
- **Waiting on customer / installer / parts / response** — parked, and the board
  should say who or what it is waiting for.
- **Warranty approved / rejected** — the manufacturer has answered.
- **Resolved** — dealt with, not yet closed.
- **Closed** — finished, with the outcome recorded.
- **Cancelled** — not a real issue, a duplicate, or withdrawn.

The list's **active** count is the tickets somebody here can move today: open, in
progress, or waiting on the installer. Resolved, closed and cancelled are
finished, and *waiting on customer* is parked until they answer — that is why the
count differs from "all tickets".

### 7.5 Working a ticket: one feed, three kinds of entry

Everything said about a ticket lives in one feed, and every entry says what it is:

- **Customer reply** — something the customer said, or that you told them. It is
  written for the customer to read.
- **Internal note** — the office talking to itself. It is *also* the staff inbox
  thread for that ticket: one row, two places. A reply written in the inbox
  appears on the ticket, and an internal note written on the ticket shows up in
  the inbox. It counts as unread there until a colleague opens it.
- **Change** — authorless: a status move, with from, to, who and the note.
- **Imported entries** keep the identity, author and timestamp they arrived with,
  because the old desk did not record whether a note was customer-facing.

The composer asks one question before the words: **internal note** or **customer
reply**. Either can carry one photo, screenshot or PDF. Use the distinction
honestly — an internal note that says "customer is being difficult" must never be
chosen as a customer reply.

Somebody without inbox access sees no internal option at all, rather than a
composer that refuses.

### 7.6 Assignment and the inbox

- **Multiple assignees** are supported. Assign everyone who genuinely has to act,
  not everybody who might be interested.
- Opening a ticket counts as reading its discussion; unread counts clear.
- The inbox lists threads, newest activity first, with the customer's name on the
  thread it belongs to.
- **Emailing outside the company** from the inbox is a permission, and there is
  also a company-wide switch in **Settings → Messaging** (Super Admin). If a reply
  must reach a customer who is not on the portal, use the ticket's customer reply
  — it is recorded on the record where the next person will look.

### 7.7 Finishing a ticket: the outcome is a record, not a comment

Resolving or closing needs **two things at once**:

1. **The resolution** — what was actually done, in words.
2. **The outcome code** — repaired on site, parts replaced, manufacturer warranty,
   settings or firmware corrected, install corrected, no fault found, advice
   given, duplicate, or other.

It is asked together so nobody fixes one field and is sent back for the other.
The paragraph lives in the ticket's own outcome card; the feed carries a single
line pointing at it, and does not copy it.

Rules worth knowing:

- **Reopening clears the resolver and the date, and keeps the words.** What
  happened on site is still true.
- Tickets finished before outcomes existed keep their words and show a blank code.
  Do not invent a code to tidy the report.
- **The note on a status move is a different field.** It says why the ticket moved
  and what happens next; it lands in the conversation and is never presented as
  the outcome.

### 7.8 Imported tickets

Tickets brought across from the old support desk are marked as imported, with their
original number, identity and timestamps. Their conversations are filed as
*customer reply*, *internal note* or *imported* depending on what the old system
recorded. Where it recorded nothing, the entry stays staff-only. They behave like
any other ticket from then on.

### 7.9 The shared status board

The board at `/ticket-status` is the queue crews read without a CRM account. Full
details in section 13.2 — including the three actions and the switches that
govern them.

---

## 8. Installers and compliance

### 8.1 What the register is

`Installers` is the register of electrical and solar subcontractors the business
sends work to. It is a **compliance record**, not a contact list. A row holds what
an Australian crew needs before it can be booked:

- The business's **electrical contractor licence** — an REC in Victoria, an EC in
  Western Australia, a contractor licence elsewhere — with its expiry.
- The individual electrician's **own licence**, with its expiry.
- **Solar Accreditation Australia** accreditation (which replaced Clean Energy
  Council accreditation in 2024), plus the legacy CEC number where it exists.
- **Public liability** certificate of currency and the cover it names;
  **professional indemnity**.
- **Site tickets**: White Card, working at heights, ARCtick refrigerant handling,
  forklift, EWP — each with its expiry.

![The installer register](screenshots/installers.png)

*The register shows the state of every crew at a glance; one expired credential
is enough to mark a crew unavailable.*

### 8.2 Expiry is the point

The list treats one expired credential as **expired**, however current the rest
are. That is intentional: a crew with a lapsed licence is not usable that day,
whatever else is in order. Check the register before booking, and again before the
visit if the booking is weeks out.

### 8.3 Keeping and retiring crews

- A crew no longer used is kept, under "installers we do not use any more", rather
  than deleted — a job from last year should still name who did the work.
- Removing a crew soft deletes the row. It can be restored from the recycle bin,
  and existing jobs keep their reference.
- A row only needs a name to exist. A crew whose licence has not been typed in yet
  still appears, with the gap visible; filling the gap is the work.

### 8.4 How a crew attaches to work

- **A job's installer** is chosen from this register when scheduling.
- **A ticket's installer** is also a register row, not a staff account: the crew
  who did the install answers for it, and a crew is not a sign-in.
- **Free-text installer names** on an old job become a crew only by an exact
  match on the person or business name — never a near miss.

### 8.5 Importing the register

The register is one list spread across a tab per state, so the importer reads
**every tab at once** and takes each tab's name as the state (`Brisbane` and
`Regional QLD` are both QLD). A bare "Expiry Date" column belongs to the credential
whose number sits to its left, which is how the sheets are laid out.

Running the same sheet again updates what it wrote, matching on the contractor
licence, then the email, then the name within the state. Every row is stamped with
its source, so a re-run never touches a row typed by hand.

---

## 9. Inspections and warranty reports

### 9.1 What a report is

An inspection report is what a crew fills in on site after an install, for a
**handover**, a **warranty claim**, a **defect recheck** or an **audit**. It
records observations and readings; it is *not* a certificate of compliance. The
job's own electrical inspection state and certificate remain separate.

The checklist follows the standards an Australian installation is inspected
against:

- **AS/NZS 5033:2021** — the array and DC wiring.
- **AS/NZS 4777.2:2020** — the inverter and its grid connection.
- **AS/NZS 5139:2019** — battery storage.
- **AS/NZS 3000** — earthing and the electrical tests.
- Plus the labels, handover and warranty paperwork a manufacturer asks for on a
  claim.

### 9.2 Requesting a report

From **Inspections → Request an inspection**, or the job's own panel:

1. Choose the job, the purpose, and the crew from the register.
2. Send. The system mints a reference (`WIR-0001`) and a long random link, and
   emails it to the crew.
3. The link lasts 30 days. The office can **extend** or **close** it.

**A submitted report is closed for good.** A second visit is a second report with
its own reference — never a reopened one, because the first is a record of what
was true on the day.

### 9.3 The standing on-site link

Not every visit is one the office knew about. A crew sent back to a fault, a
service call somebody rang in, a system installed before this CRM existed: they
are in front of the meter already, and waiting for a form to be issued wastes the
visit.

**Inspections** carries a second door for that — **On-site warranty and service
link**, at the top of the list and again at the top of **Request an inspection**,
where somebody is already deciding how to ask a crew for a report. Open it once
and give the address to the crews you work with: it is a standing address, not
one per job. The process above is unchanged and still the right one when a job is
already on file.

- **The crew says who and where.** Customer name, phone if they have it, the
  street address, suburb, state and postcode, what they are there for, and their
  own name and business. No job number, no account.
- **We show what is already held** for that name and address: the client, the
  address and the last job at the property, with the reason each one was
  suggested. The crew confirms the right one — or presses **none of these is the
  right place**, which is a normal answer for an address nobody has typed in
  before.
- **The same report opens either way.** With a client confirmed it is filed
  against them, and against the most recent job at that address when there is
  one, so it turns up on the job card. With no match it carries the name, phone
  and address exactly as typed, and the office links it afterwards.

Opening, replacing and closing the link are decisions for the people who manage
inspections (`inspections.manage` — Admin and Operations Manager). **The address
is the credential:** it is shown only to them, **New address** retires every copy
of the old one, and closing forgets the address altogether, so opening it again
issues a fresh one.

Reports a crew started show **On site** in the list, and one that matched no
client also shows **No client linked** — there is a filter for exactly those.
Opening the report gives **Link this report to a client**, which offers the
matches the typed address supports. Linking files the report; it never changes
the client's own record.

### 9.4 What the crew sees

No account: the token is the key. The form walks the checklist by section,
records a defect list with severity, takes readings and the test instrument used,
attaches photos and documents, captures a signature, and ends with a declaration.

Drafts save themselves while the crew works, so a phone that loses signal does not
lose the answers. Files attach as they are taken.

### 9.5 What makes a submission acceptable

The form refuses to submit unless it is a record worth keeping:

- Every checklist item answered (the battery section only for hybrid and battery
  systems).
- Readings and the test instrument recorded.
- At least **three photos**.
- A **signature** and the **declaration** ticked.
- The **outcome agrees with the defects**: a pass has none, a conditional or fail
  has at least one, and a fail has a **major or critical** defect behind it.

Limits: 24 photos, 12 documents, 20 MB per file. PDFs and Word documents are
accepted.

### 9.6 Reviewing a report

Submitted reports appear in the list, and the detail page shows the answers,
readings, defects, photos and signature. **Download pack** builds a zip of the
attachments and a printable `report.html` — send it to a manufacturer with a
warranty claim.

Marking a report **Reviewed** is the office's acknowledgement that somebody read
it. A failed submission email is logged rather than shown to the crew, so their
work is never lost to a mail server problem.

**Remove this report** deletes a report in any state — a link nobody used, one
that has run out, or a finished one raised against the wrong job. The answers,
photos and documents go with it and nothing in the application brings them back;
the backup directory is the recovery. Somebody with `inspections.manage` (Admin,
Operations Manager) does it, and the confirmation says which kind of report it is
before anything is deleted.

---

## 10. Money

### 10.1 Invoices

Invoices carry a direction (money in or out), line items, GST treatment and a
status: **Draft → Awaiting approval → Approved → Issued → Partially paid → Paid**,
or **Overdue**, **Disputed**, **Cancelled**. Payments are recorded against the
invoice, which is what moves it to partially paid or paid.

- **Never mark an invoice paid before the money is there.** The board and the
  reports read the status; a hopeful tick becomes a wrong receivable.
- A disputed invoice stays disputed until the dispute is resolved — do not cancel
  it to make a list look clean.

### 10.2 Reimbursements

A claim is a staff member's out-of-pocket expense. It moves **Submitted → Under
review → Approved → Awaiting payment → Paid**, with **Information required** when
the reviewer needs a receipt or a cost centre, and **Rejected** with a reason.

#### 10.2.1 The queue

The **Reimbursements** screen is the Finance queue. The four tiles across the top
show the claims **Awaiting review**, the claims **Awaiting payment**, the total
**Paid this month**, and a shortcut to **My claims**. Click a tile to filter the
list to that group.

The search box finds a claim by reference, person, client, ticket or system. The
status filter names one state, and the tabs narrow the list to **To review**, **To
pay**, **Paid**, **Mine**, **Open** or **All**. **Paid** lists the claims the money
has already left the account for, and the **Paid this month** tile opens that same
queue. **All** is the best view when you need to follow one claim through the
whole flow.

The table shows the claim reference, expense date and claimant; the client and
linked ticket; the work performed and installer; the invoice status and number;
whether the issue is resolved; the current status; and the claimed and approved
amounts. The action at the end of each row follows the claim's state.

![The reimbursements queue](screenshots/reimbursement-overview.png)

*The Finance queue: claims waiting for review, claims waiting for payment, and
the three example claims moving from submitted to approved to paid.*

#### 10.2.2 Raising a claim

Choose **New claim** and complete the form. It records the date, category, amount,
receipt reference and description, then links the expense to the work:

- **Client** — who the work was for. Choose the client first; the ticket and job
  pickers are filled from that client's history.
- **Ticket** — the fault or support visit the expense relates to. Choosing a ticket
  carries its job and installer into the form where they are recorded.
- **Job and installer** — the job the work belongs to and the crew who fitted the
  replacement. The installer comes from the ticket when the ticket names one;
  otherwise search the installer register. A reviewer who deliberately clears it
  is not overridden later.
- **System replaced, invoice status and invoice number** — what was swapped out and
  whether the replacement was billed.
- **Resolved** — tick it when the fault itself is settled, not merely when the
  claim is paid, and record the date.

Record the receipt reference. Finance may send a claim back as **Information
required** when the evidence or the cost centre is missing.

![The new claim form](screenshots/reimbursement-new-claim.png)

*The claim form: the expense, the client and ticket, the installer, the system
replaced and the invoice in one record.*

#### 10.2.3 Reviewing a claim

A submitted claim shows **Review**. The reviewer chooses:

- **Approve** — the approved amount starts at the full claimed amount and can be
  reduced. Notes explain the decision.
- **Reject** — the claim is finished and the reason is kept with it.
- **Request information** — the claim goes back to the claimant as **Information
  required** rather than being rejected.

**Save decision** records the reviewer, the decision and the notes. Approval and
payment are separate permissions: approving a claim is not paying it.

![Reviewing a claim](screenshots/reimbursement-review.png)

*Reviewing a claim: approve the full amount or a reduced one, reject it, or ask
for more information, with the reason recorded.*

#### 10.2.4 Paying a claim

An approved claim moves to the **To pay** queue and shows **Mark paid**. The
dialog records the **Paid on** date and an optional **Payment reference**.
**Record payment** moves the claim to **Paid** and adds it to the **Paid this
month** total, while the approval and payment stay attached to the same claim.

A claim cannot be paid before it is approved. If somebody tries from an old page,
the screen now explains the claim's current status instead of showing a generic
error.

![Marking a claim paid](screenshots/reimbursement-payment.png)

*Paying a claim: the amount being paid, the date it left the account, and the
reference Finance can reconcile later.*

### 10.3 Export lodgements

Lodgements track the STC and export paperwork: what is prepared, submitted,
accepted or rejected, and for which job. Finance owns the flow; operations
provides the evidence.

---

## 11. Warehouse

![The warehouse dashboard](screenshots/warehouse.png)

*What is short against jobs, what is waiting to be picked, and what moved this
week.*

### 11.1 Catalogue

Products carry a code, description, brand, category, unit, cost and price. The
catalogue is what the job's stock pick and the crew's parts list draw from.
Categories group products for reporting and for the picker.

![Products](screenshots/warehouse-products.png)

*The catalogue: what the business installs, what it costs, and what is on hand.*

### 11.2 Suppliers

Suppliers hold contact details, account numbers and the supplier's own product
codes, which is how a purchase order and a delivery docket are matched to the
catalogue.

### 11.3 Stock levels, movements and adjustments

- **Stock levels** shows what is on hand per product and location.
- **Stock ledger** is the history: receipts, issues to jobs, returns and
  adjustments. Every movement has a reason and an author.
- **Adjustments** exist for counted stock, damage and corrections. They need the
  adjust permission and are visible on the ledger — an adjustment is not a way to
  make a number quietly agree.

![Stock levels](screenshots/warehouse-inventory.png)

*Stock levels per product, with the pick, dispatch and adjustment history one
click away.*

**Costs are a separate permission.** A warehouse user can move stock without
seeing what the company paid for it.

### 11.4 Picking a job

**Warehouse → Pick lists** is the warehouse's working screen. A job appears in
**Jobs coming into the warehouse** as soon as it has equipment or a drafted
parts list, even when no install date has been booked. The queue is not a
reservation: stock is held only when somebody starts the pick. Pre-approved jobs
are listed first, because that is the work the crews are waiting on.

The list is built from both halves of the design: the equipment named on the
job — panels by count, the inverter and the battery — and the smaller items on
the drafted parts list. An item already on the parts list is not added twice.

When the job reaches **Grid pre-approval granted** (or **Stock Pick** for a job
that does not need a grid connection), the pick list is raised automatically
and appears in the working list. A pick raised by hand does the same thing and
holds the free stock for that job.

On the list:

- **Tick collected** marks a line as fully picked. Use the quantity box for a
  partial pick.
- A short line needs a reason. If anything is short, **Lacking components**
  records what is missing or on order and keeps the stock held for the job; it
  does not pretend the job is ready.
- When every line is collected, **Ready to deliver** issues the stock to the job
  and moves the job on where the warehouse is allowed to move it.
- **Mark collected / dispatched** records that the packed job left the warehouse.
- **Warehouse note** is for the office and the next picker: the part on order,
  the shelf it is in, or anything that would otherwise be said out loud and
  forgotten.

A pick cannot be completed while a line is unrecorded or short. A short list
stays open in **Lacking components** until the stock arrives, and then the same
lines can be ticked and the job marked ready.

---

# Part C — Running the system

## 12. Settings and administration

Settings is grouped by what you came to change: **Your account**, **Help**,
**Company**, **Data**, and **Oversight**. A group you cannot use is hidden —
except **Help**, which is open to everybody who can sign in.

### 12.1 Company profile and branding

The company's legal name, ABN, address, phone, email, logo, colours, and the words
on the staff sign-in screen. This is the single source of the product name in the
sidebar, the browser tab and the holding page — change it here, not in a file.

**Sign-in page background** keeps every background uploaded for this profile.
The gallery below the preview shows each one; choosing a thumbnail puts that
photograph back on the sign-in screen without uploading it again. A new upload
is added to the gallery rather than replacing the previous file. **Remove the
background** takes the chosen image out of the library and deletes it. The same
library is offered on **Settings → Coming soon**, where a photograph can be
chosen for the holding page.

### 12.2 Email delivery

The SMTP server that sends password resets, portal verification, inspection links
and test mail: Gmail with an app password, or any host by hand. **Send a test
email** after every change; a wrong port only shows up when somebody cannot reset
a password.

### 12.3 Messaging

One company-wide decision: whether the staff inbox may email somebody outside the
company. Off means internal conversations only. Super Admin only.

### 12.4 Customer portal

Whether a new portal account must confirm its email before it can see anything. It
is on by default for a reason; switch it off only if you accept that anybody who
knows a customer's address could read their account first.

### 12.5 Status links

Issue, pause, reissue and delete the credentials for the shared ticket status
board, choose which queues each link may read, and set an expiry (14 days by
default). Three further switches decide what holders may **do** — note, request
review, complete — and only Admin and Super Admin may change them. A password is
shown once, at creation; reissuing one stops the old one immediately.

### 12.6 Coming soon

A holding page for a site that is not public yet, with the office's own words,
date and contact details. **Page background** chooses a photograph from the
backgrounds already uploaded in **Company profile**, or puts the page back on its
plain dark ground; the choice belongs to the holding page and does not change the
sign-in screen. While it is on:

- Visitors see one page, at every public address.
- Staff sign in and work normally; a signed-in customer keeps their portal.
- **The shared status board stays open**, because a crew has no account to sign in
  with — their issued link is the door.
- Inspection links and the customer portal are held.

Turning it off restores everything, with no deploy.

### 12.7 Sheet import

Three sources, each with its own card: the **client list**, the **job sheet** and
the **installer register**. Each has its own link, switches and cadence, so
connecting one never disturbs another.

Paste the ordinary Google Sheets link, press **Connect and check**, and read what
it found before saving. Importing is off until it is switched on; a scheduled run
writes a report until **Import new rows automatically** is enabled. Nothing is ever
written back to the sheet.

For a private sheet, use a Google **service account** shared as a Viewer, and point
`GOOGLE_APPLICATION_CREDENTIALS` at its key outside the repository.

### 12.8 Backup and restore

**Settings → Backup** is the one place to take, verify, download and restore a
copy. Read this section before using it.

- **Types.** Database only, files only, or full. A database backup is safe to hold
  even though the Vault is encrypted: without this installation's `APP_KEY` the
  passwords are ciphertext.
- **Verification.** Every archive is read back and checked against its recorded
  checksums before it counts. The switch stays on unless somebody turns it off;
  an archive written with it off is marked **unverified**, can be downloaded and
  checked later, and never stands in for the last verified backup.
- **Labels.** Every archive carries the label written when it was taken — "what
  you are about to do" — and the history shows it beside the id, size and time.
  Label a manual backup with the reason, not "backup".
- **Downloading** is never gated on verification: if the file exists, the
  authenticated route serves it.
- **Deleting several at once.** Tick the archives in the history and press
  **Delete selected**. The selection is checked as a whole before anything is
  removed: it will not take away every completed backup or the last verified
  one, and an archive whose file cannot be removed keeps its row.
- **Restoring** from the screen needs a **verified** archive, and takes a fresh
  verified full backup of the current state first. If that safety backup fails,
  the restore does not happen.
- **Replace everything** writes the archive over the running installation.
  **Add what is missing** (merge) only creates tables this database lacks and adds
  rows it is missing — nothing is dropped, updated or deleted, nobody is signed
  out, and no migration runs. Rows are matched column by column, so an archive
  taken before a field was added (or one holding a field this installation does
  not have yet) still merges: the older archive's rows take their default for
  the new field, and a field this installation lacks is left out of the row.
- **Retention.** `BACKUP_KEEP` is 0 by default, which means nothing is pruned
  automatically. Never delete the last verified backup.
- **Restore without signing in.** For planned work — a deploy, a migration tried
  against real data — open a **restore link** here. It carries an expiry (up to a
  week) or none at all, can be used more than once while it is open, and needs no
  login. **Keep it open for** starts on **Does not expire**: that is the standing
  way back into this database, and it works until somebody clicks **Close the
  link**. Its address is never permanent in the same sense — press **Renew
  address** and it mints a new one, stopping the old address at once while
  leaving the terms exactly as they were (a link that does not expire still does
  not expire). Choose a window instead and the link closes itself. Either way,
  treat the address as a password. The older one-time **recovery link** exists for
  an installation whose users table cannot be read; it closes itself after one
  successful restore.
- **Scheduling.** Off until switched on. When on, the same verified command runs
  from cron, so `php artisan schedule:run` has to be running.

### 12.9 Staff accounts

Create a staff account with a name, work email, role and the active flag. The
person sets their own password through **Forgot your password?**, or you send an
invitation. Deactivating is how somebody leaves: it keeps their history, their
assignments and the audit trail.

**Never** edit a password in a database or a file. Change it on the user record,
or have the person reset it.

### 12.10 Roles and permissions

Each role is a named set of permissions, grouped (customers, jobs, tickets,
inspection, stock, money, people, settings). You can change a role, or grant one
extra permission to one person, without redesigning the whole system. Super Admin
is the owner's role and bypasses checks; grant it sparingly.

Permissions that are easy to give away by accident:

- **settings.manage** — mail server, portal behaviour, backups, restores.
- **vault.view / vault.manage** — the company's own monitoring logins.
- **invoices.manage** — money in and out.
- **roles.manage** — the ability to change what every role can do.

### 12.11 The credentials vault

The company's own logins for the manufacturer portals — Solis Cloud, Sungrow
iSolarCloud, Fronius, Tesla, and the rest — used to watch a customer's system or
handed to an installer. It is **not** for a customer's password.

- Open to **Admin** and **Operations Manager** only. Everybody else gets a refusal,
  not a greyed-out list.
- Passwords are encrypted and never travel in a page payload. **Reveal** is a
  deliberate action that records who looked, when, and how many times.
- Leaving the password blank when editing keeps the stored one. It never erases a
  login because a field was not filled in.
- The password is sealed with this server's key. A second copy of the CRM cannot
  read it unless that key is listed in the server's `APP_PREVIOUS_KEYS`; **Reveal**
  says so when this is the problem. Typing the password in on that copy also fixes
  it, because the entry is then sealed with that server's own key.
- Retire an entry (`is_active` off) or remove it — never hard delete. It may be the
  only record of which account a system from three years ago was monitored under.

### 12.12 Oversight (Super Admin only)

- **Activity log** — every change, on every record, with who made it.
- **Sign-in history** — every attempt at every door, successful or refused, with
  the reason. This is where a Google refusal or a locked-out account is explained.
- **Recycle bin** — hidden records, ready to restore.

### 12.13 The user manual

**Settings → Help → User manual** is this document, inside the application, at
`/settings/manual`. Every signed-in member of staff can read it, whatever their
role, because knowing what a screen does is how somebody knows to ask for the
permission to use it.

- The chapter list on the left jumps to any section; **Find a chapter** filters
  it. On a narrow screen the list folds into a **Chapters** panel above the
  document, so the words and the screenshots keep the width.
- **Download PDF** builds the same document with its screenshots embedded, for
  reading somewhere with no signal.

The manual is kept in step with the system rather than written once: a screen and
the paragraph describing it change in the same commit, screenshots are re-taken
when a layout changes, and the test suite fails if a ticket status, category or
outcome code is added to the system but not written down in Appendix B. If you
find a paragraph that no longer matches the screen, tell an administrator rather
than working around it — a stale sentence is treated as a bug.

### 12.14 Security scan

**Settings → Data → Security scan** reads the server for code that should not be
there: a web shell, a hidden payload, PHP inside an image, a script sitting in an
upload folder, a file that appeared on disk and was never part of the code.
It is for the office, not for customers: it looks at the code of the sites, not
at the documents a customer uploads.

- Press **Scan now**. The run happens while the page waits and takes a few
  seconds on a shared account. Nothing is changed by it — a scan only reads.
- The report that follows is the last one, and it stays there: what was read,
  how many files were skipped as too large, anything that could not be read at
  all, and every finding with the file, the line and the rule that caught it.
- A finding is a reason to look, not a verdict. **Critical** is a file that is
  almost certainly not supposed to be there; **High** is code shaped like an
  intrusion; **Medium** may be the office's own old code, and always needs a
  person to read it.

**Hold the actionable files** moves the Critical and High findings out of the
web root and into `storage/app/quarantine`, where no browser can reach them.
Nothing is deleted: each held file keeps its bytes and the path it came from, so
a colleague can put it back with `php artisan crm:quarantine --restore=<id>` on
the server. A Medium finding is never moved, whatever the switch says.

The screen needs **settings.manage**, so Admin and Super Admin only. The same
scan runs from the command line as `php artisan crm:malware-scan`, which is the
one to put on a schedule, and whichever of the two ran last is what this page
shows. Before holding a batch of files, take a backup from **Backup & Restore**:
it is the copy that makes the whole afternoon reversible.

If a scan finds a shell — a Critical finding in `public/` or an upload folder —
holding the file is the first step, not the last. Change the password of every
account that could reach that folder, check the other findings on the list, and
tell an administrator which site it was in. A shell that was there for a while
is a reason to look at what else changed, which is what the Activity log and the
Sign-in history are for.

### 12.15 Release switches and the version

**Settings → Release** decides which parts of the system this installation
shows. It is the gradual handover: the client list and the job pipeline can go
live this week, the support desk next week, finance when that team is trained,
without a deploy between any of them.

The parts are **Customers and sites**, **Jobs**, **Installers**, **Support
tickets**, the shared **status board**, the **staff inbox**, **Warranty and
inspections**, **Sales**, **Finance**, **Department tasks**, the **warehouse**
and the **credentials vault**. Each switch says **Visible** or **Hidden**, and
hidden means absent rather than greyed out:

- the sidebar does not draw its link;
- the top-bar search does not return its records;
- the dashboard does not count them;
- a customer or job page does not carry its panel; and
- typing one of its addresses answers **404**.

Nothing is deleted and nobody is signed out. Switch it back on and the part
returns with every record exactly where it was left. A hidden part is not a
permission either: a role that holds `stock.view` still holds it while the
warehouse is switched off, and the switch is what makes the warehouse absent
for them too.

**Full release** is the day the handover ends. It shows every part whatever the
individual switches say, and fixes them at on. The choices underneath are kept,
so turning Full release off puts the gradual arrangement back. Only an Admin or
a Super Admin can open the screen or save it; everybody else is refused, not
shown a greyed-out form.

The pressure to hide a part never outweighs the records behind it. A part that
is switched off is still backed up, still in the database and still there when
it comes back — this is a switch in front of the system, not a second system.

**The version.** The foot of the sidebar carries the number this installation
runs, such as `v1.1.0`, read from the `VERSION` file. Every commit records a
version in `docs/releases.md`: a small change bumps the last number, a new part
or feature the middle one, and a full release the first. The same screen lists
the newest entries. On the server the command is `php artisan crm:version`;
with `--install-hook` it also puts a hook in the checkout so every commit
carries a version without anybody remembering.

### 12.16 Client information import

**Settings → Client information import** is the switch for the temporary old
sales-sheet import described in section 4.9. It is deliberately manual: nothing
opens or closes it on a timer.

- **Open the import** puts the button on the customer list and makes
  `/customers/information-import` reachable to staff holding the customer and
  invoice permissions.
- **Close the import** makes that address answer 404 and removes the button. It
  does not delete a single imported job or invoice.
- **Last run** shows what the most recent workbook read, the money totals, the
  backup it took and where its report was written.

While the door is open, anybody with the right permissions can change customer
financials by uploading a workbook. Close it as soon as the migration is done.

---

## 13. The public doors

These are the addresses that work without a staff account. Each has one
credential, and nothing else.

### 13.1 The customer portal (`/ticket`)

A customer signs in with the account the office created. They see their own jobs,
their own tickets and their own documents, and can log a new fault, upload a photo
and reply on a ticket. They cannot see another customer's anything.

If a customer cannot get in: check the account is verified and not paused, check
the customer record is active, and send a password reset from **Customer
accounts**. Do not create a second account for the same person.

### 13.2 The shared ticket status board (`/ticket-status`)

One queue at a time, narrowed to the statuses a link was issued for, with the
suburb, the crew on the ticket and how long it has sat.

**Two ways in:**

- A crew or manager types the **username and password** the office issued. The
  session lasts one shift (8 hours) and is re-checked on every page, so pausing the
  link ends an open board at once.
- A staff member signs in with their own account on the board's own screen. They
  see every queue their ticket access covers. Accounts with 2FA come through the
  CRM sign-in instead, so the second factor is never skipped.

**Three actions**, each with a switch in **Settings → Status links**:

1. **Add a note** — written as an internal note on the ticket, so the office reads
   it in the ticket's own feed. From a link there is no staff account to author it,
   so the crew's name is snapshotted against the note.
2. **Ready for review** — the crew says the work is done, in words. This creates a
   **claim** and does **not** move the ticket. Sending a second claim updates the
   one already waiting rather than stacking another.
3. **Completed** — Admin and Super Admin only, and only against a pending claim.
   The crew's words become the ticket's resolution unless the reviewer rewrites
   them, the reviewer chooses the outcome code, and the ticket becomes **Resolved**.
   Closing stays a separate step. Sending a claim back records why, which the crew
   reads on the board.

A viewer can never act on a ticket outside the queues their link covers, whatever
URL they post to. A reviewer can never be a link.

The board also stays open while the coming-soon page is up — a crew has no account
to sign in with.

### 13.3 The inspection form (`/inspection/{token}`)

The link a crew receives for a handover, warranty or defect report. No account: the
token is the key, and it lasts 30 days unless the office extends it. Section 9
covers the form itself.

The business also keeps one standing warranty and service address,
`/warranty-inspection/{token}`, that a crew can open on site and start the same
form from after confirming the client and the address. It is opened, replaced and
closed from **Inspections**, answers 404 when it is closed, and is the other door
into this page rather than a different report.

### 13.4 The restore link (`/restore/{token}`)

An administrative address that puts a backup back with no sign-in. It is closed
until an Admin or Super Admin opens it on **Settings → Backup**; it then carries
either a window of up to a week or, as it is opened by default, **no expiry at
all**, and it can be closed in one click. **Renew address** replaces the key
without changing the terms — the previous address stops working at once, and one
that does not expire is still one after a renewal. The link that does not expire
is the owner's back door: it is meant to be held, kept out of chat and tickets,
and renewed if it has ever been seen. Treat an open one as a password: close it
when the work is done. Section 12.8 covers what a restore does.

---

## 14. Routines

### 14.1 Every morning

- **Dashboard** — what is waiting on you today.
- **Tickets** — new and unassigned; assign or pick up. Check anything waiting on
  customer for a reply.
- **Jobs** — anything blocking an installation today: stock, grid approval,
  evidence.
- **Inbox** — unread internal threads, including crew notes from the board.

### 14.2 Every afternoon

- Update every ticket you touched: status, next step, and the note that explains
  it.
- Confirm tomorrow's crews against the register (licences current, tickets valid).
- Clear the jobs that finished today to the next stage, with evidence attached.

### 14.3 Weekly

- Review **leads** with no follow-up date.
- Check **inspections** invited but not submitted, links about to expire, and
  reports a crew started on site that are **not linked to a client** — those are
  the ones nobody in the office knows about yet.
- Reconcile **stock movements** against jobs dispatched.
- Look at **reimbursements** awaiting approval or payment.

### 14.4 Monthly

- **Backups:** confirm the latest verified archive and that scheduled backups have
  been running. Label any manual backup.
- **Invoices:** chase overdue, review disputed.
- **Installers:** renew credentials before their expiry, not after.
- **Roles:** remove access for anyone who has changed job.
- **Vault:** retire logins for systems no longer used.

---

## 15. Troubleshooting and FAQs

**I cannot sign in, and my password is right.**
Check the account is active, and that you are on the staff door rather than the
portal. An administrator can see the exact reason in **Sign-in history**.

**Google says my account is not allowed.**
The Google address must match your staff record. A personal Gmail will not work
unless it is your staff email. Ask an administrator to check the address, not to
create a second account.

**I lost my phone with my 2FA on it.**
Use a recovery code. If those are gone too, an administrator resets 2FA on your
account; you will set it up again from **Settings → Security**.

**A screen or button described here is missing.**
It is a permission. Section 1.5 and Appendix A show which role holds it.

**I cannot move a job to the next stage.**
Stage moves are permissions by department. Ask the owner of that stage; do not
change your own role to get past it.

**A ticket will not resolve.**
It needs both the resolution words and an outcome code, entered together. On the
status board, a ticket can only be completed against a pending claim from a crew,
and only by Admin or Super Admin.

**A crew cannot open the status board.**
Check the link is live, not past its expiry, and that it covers the queue they are
looking at. Reissuing the password stops the old one at once. The board still works
while the coming-soon page is up.

**An inspection link says expired.**
The link lasts 30 days. Extend it from the inspection, or — if it was already
submitted — open a new report. A submitted report is never reopened.

**A sheet import did not bring new rows in.**
Check the card is switched to importing (not report-only), that the link still
reads, and read the last report on the screen. Rows that fail the minimum fields
are listed with the reason rather than silently dropped.

**A backup failed.**
Nothing else may proceed until it succeeds — that is by design. Read the message
the screen shows; the usual causes are disk space and database credentials. Fix
that, take the backup again, and only then continue.

**I deleted something.**
It is hidden, not gone. A Super Admin restores it from **Recycle bin**, and the
activity log shows what happened.

---

## 16. Glossary

- **Active ticket** — open, in progress, or waiting on the installer: the work
  somebody here can move today.
- **Crew / installer** — a subcontractor in the installer register. Not a staff
  account.
- **Claim** — a crew's "ready for review" submission on the status board.
- **Closeout** — the last job stages: grid connection, STC claim, completion.
- **Draft (inspection)** — a report the crew has started and not submitted; it
  saves itself.
- **Import marker** — the stamp on every row an import created, so a re-run or a
  replace touches only its own rows.
- **Lead** — an enquiry that is not yet a job.
- **Merge (restore)** — adding only what a backup has and this database lacks,
  changing nothing already here.
- **NMI** — the national meter identifier for a property's connection.
- **Portal account** — a customer's sign-in, linked to their customer record.
- **REC / EC** — the electrical contractor licence in Victoria / Western Australia.
- **Resolution** — the words describing what was actually done, plus the outcome
  code, kept on the ticket rather than in the conversation.
- **SAA** — Solar Accreditation Australia, which replaced CEC accreditation in 2024.
- **STC** — small-scale technology certificate, the federal incentive.
- **Status link** — the username and password issued for the shared status board.
- **Verified backup** — an archive that has been read back and checked against its
  checksums. Only a verified archive may be restored.
- **Vault** — the company's own logins for manufacturer monitoring portals.

---

# Part D — Guided walkthroughs

The six jobs the office does most often, in order, with the screen you should be
looking at. Each one ends with what *done* looks like, because the expensive
mistake is rarely a wrong button — it is stopping one step early.

The screenshots are of this system, with the records already in it. Use them to
orient yourself; the record you are working on will look the same.

## W1. Add a new client

**Use it when** a new person or business signs up, or an existing customer has a
second property billed separately.

1. **Customers → New customer**.
2. Choose the **type** first: residential bills a person, commercial bills an
   organisation. It decides which fields appear.
3. Fill **Details**, **Contact details** and **Billing address**.
4. Fill **Sales and ownership**: who owns the relationship, how they were
   referred, and the lead source detail. The reports group by this.
5. **Create customer**.

![The new customer form](screenshots/customers-create.png)

6. Open the customer and add the **site**: the install address, its NMI and its
   distributor. A job cannot be booked without one.
7. Upload the signed contract and any compliance paperwork to **Documents**.

**Done looks like:** the customer is in the list with an owner and a site, and
the top-bar search finds them by name, phone or address.

**Common mistakes:** making a second record for somebody who is already there —
search first — and putting the install address in the billing fields. Billing is
where invoices go, which is often not where the panels go.

## W2. Add a job and get it ready to install

**Use it when** a quote has been accepted, or an existing customer needs more
work at a site.

1. **Jobs → New job** (or **New job** at the top of the sidebar).
2. Choose the **customer**, the **site**, the **job type** and the owner.
3. Fill the **system specification**: panel model and count, inverter, battery,
   phase and configuration. The warehouse picks against this and the crew
   installs it.
4. Save. The job opens on its page in the first stage.
5. Work down the page: lodge the **grid application**, attach the **contract and
   design**, then use the workflow to move it forward one stage at a time. Every
   move records your name and the note you write.

![The new job form](screenshots/jobs-create.png)

6. At **Grid pre-approval granted** the job reaches **Picking stock**; the
   warehouse sees it in the stock queue.
7. At **With installers / install booked**, choose the crew from the register and
   set the date. A crew with an expired licence is flagged — fix the credential
   before the visit, not after.
8. The crew attends and uploads their evidence; the job moves to **Installed**.

![A job page](screenshots/job.png)

**Done looks like:** the job sits in **Awaiting electrical inspection**, its parts
issued, its documents attached and its crew named.

**Common mistakes:** booking a crew before the stock pick, so they arrive without
panels, and leaving the note blank on a move — next week nobody knows why it
moved.

## W3. Log a ticket and work it to an outcome

1. **Tickets → New ticket**. Choose the customer and — this matters — the **site**
   and job.
2. Pick the **category**, the **priority**, and how the issue was reported.
3. Describe the fault in the customer's words. Paste their message if you have
   it; the box keeps formatting.
4. Assign it. Leave the field empty and it lands on you.
5. Attach the photos they sent — a fault shot is worth three phone calls.

![The ticket list](screenshots/tickets.png)

6. Work the conversation in **the feed**: a **customer reply** for anything the
   customer should read, an **internal note** to talk to colleagues. Never put an
   internal comment in a customer reply.
7. When a crew has to attend, move it to **Waiting on installer** and say in the
   note what is expected of them; the shared board shows it.
8. Finish with the **resolution** — the words and the **outcome code**, entered
   together — then move to **Resolved**.

![A ticket](screenshots/ticket.png)

**Done looks like:** resolved with an outcome code, a customer reply that explains
what was done, and nothing said internally that reads as if it was sent.

**Common mistakes:** resolving without the code (the screen sends you back), and
using the status-move note as the resolution. The note says why it moved; the
resolution says what was done.

## W4. Warehouse: receive, pick, dispatch

1. **Warehouse → Pick lists** shows the jobs coming in, including jobs with no
   install date, and the lists already raised.
2. **Receive stock** from the product page, with the supplier's invoice number.
   Every movement lands on the **Stock ledger**.
3. A job at **Grid pre-approval granted** gets its pick list raised
   automatically from its panels, inverter, battery and drafted parts. For an
   earlier job, press **Start pick list** in the coming-in queue; that also holds
   the free stock for it.
4. Work the lines: **Tick collected** for a full line, or enter the quantity
   found. A short line needs a sentence saying why. If the shelf cannot cover
   the job, **Lacking components** records the shortage and keeps the stock
   held; tick the lines when it arrives.
5. When every line is collected, **Ready to deliver** issues the stock to the
   job. **Mark collected / dispatched** records that the packed job left the
   warehouse.
6. Counted stock is corrected with an **adjustment**, never by editing history.
   It needs the adjust permission and it is visible on the ledger.

![The warehouse dashboard](screenshots/warehouse.png)

![Products](screenshots/warehouse-products.png)

![Stock levels](screenshots/warehouse-inventory.png)

**Done looks like:** the job has its parts, the ledger explains every movement,
and the shelf count matches the system.

**Common mistakes:** issuing stock against the wrong job, so the crew cannot book
it out, and "fixing" a count by changing a receipt instead of recording an
adjustment.

## W5. Request an inspection and handle the result

1. **Inspections → Request an inspection**, or the job's **Inspections** panel.
2. Choose the **purpose** (handover, warranty, defect, audit) and the crew from
   the register.
3. Send. The crew gets a link that lasts 30 days; the office can extend it from
   the report.
4. The crew works through the checklist, photos, readings, signature and
   declaration on their phone. Drafts save as they go.
5. When it arrives the report shows **Submitted**. Read it: the outcome has to
   agree with the defects, and a fail needs a major or critical defect behind it.
6. **Download pack** gives the zip and a printable report for a manufacturer or
   an inspector.
7. Mark it **Reviewed** once somebody has actually read it.

**If the crew is already there:** they can start the report themselves from the
standing **on-site warranty link** (section 9.3), confirming the client and the
address as they arrive. The report arrives the same way; link it to the client if
it could not match one, then carry on from step 5.

**Done looks like:** a submitted report with photos, readings and a signature,
reviewed by a person, with the job's inspection state updated to match.

**Common mistakes:** trying to reuse a submitted report for a second visit by
extending the link. A second visit is a second report with its own reference.

## W6. Take a backup before a change

1. **Settings → Backup**.
2. **Take a backup** for the type you need: database for data, files for uploads,
   full for both.
3. Give it a **label** that says what you are about to do — "before the July
   migration", not "backup".
4. Wait for the read-back. Only **verified** may be restored.
5. Check the history: the new archive is at the top with its label, size and
   time.
6. To restore, use the same screen and a verified archive. It takes a fresh
   verified full backup of the current state before it writes anything.

**Done looks like:** a verified archive with a label somebody else could
understand, sitting above the previous one.

**Common mistakes:** unlabelled archives, which nobody can tell apart a month
later, and deleting the previous backup before the new one is verified.

---

## Appendix A. Roles at a glance

The table names the *headline* abilities. A role may hold more specific
permissions inside a module (for example, viewing financials separately from
viewing a job). **Super Admin** holds everything.

| Area | MD | Admin | Admin Support | Ops Mgr | Sales Mgr | Sales | Installer | Warehouse | Finance | Tech L1–L4 |
|---|---|---|---|---|---|---|---|---|---|---|
| Customers | view | full | create/edit | view | create/edit | create/edit | view | view | view | view (L1+) |
| Sites | view | full | create/edit | create/edit | create/edit | create/edit | view | view | view | view (L1+) |
| Jobs | view | full | create/edit | full | create/edit | create/edit | update + install stages | update + logistics | view | view; update from L3 |
| Scheduling | yes | yes | — | yes | — | — | — | — | — | L4 |
| Tickets | view/assign | full + import | create/edit | full + import | create | create/edit | update | view | view | L1 triage → L4 lead |
| Inbox | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes |
| Email outside | yes | yes | yes | — | yes | — | — | — | — | L3+ |
| Installer register | view | full | view | full | view | view | view | view | — | view (L1+) |
| Inspections | view | full | view/create | full | view/create | — | view | — | — | view; create L2; manage L3 |
| Vault | — | full | — | full | — | — | — | — | — | — |
| Stock | full | full | — | view/manage | — | — | view | full | view + costs | view |
| Leads | view | full | view | view | full | create/edit | — | — | — | — |
| Quotations | view | full | — | view | full | full | — | — | — | view L3+ |
| Invoices | view | full | — | — | — | — | — | — | full | — |
| Reimbursements | view | full | submit | — | submit | submit | submit | submit | full | — |
| Lodgements | view | full | view | manage | — | — | — | — | full | — |
| Tasks | view | full | full | full | full | view | view | view | full | view; manage L2+ |
| Staff accounts | view | create/edit | view | view | view | — | — | — | — | view L4 |
| Roles | — | full | — | — | — | — | — | — | — | — |
| Company settings | — | full | — | — | — | — | — | — | — | — |
| Backups/restore | view | full | — | — | — | — | — | — | — | — |
| Oversight | — | — | — | — | — | — | — | — | — | — |

*Oversight is Super Admin only.*

## Appendix B. Status vocabularies

### B.1 Job stages

Lead / enquiry → Quoted → Contract signed → Site inspection → Design approved →
Grid application lodged → Grid pre-approval granted → Picking stock → Stock
dispatched → With installers / install booked → Installation in progress →
Installed → Awaiting electrical inspection → Electrical inspection passed (or
**Inspection failed — rectification**) → Grid connection and metering → STC claim
and closeout → Completed. **Cancelled** can happen from most stages with a reason.

### B.2 Ticket statuses

| Status | Means |
|---|---|
| Open | Logged and not yet picked up by anyone. |
| In progress | Someone is working it now. |
| Waiting on customer | We replied and need them to come back to us. |
| Waiting on installer | The crew must confirm, attend or rectify. |
| At manufacturer | The unit or claim is with the manufacturer. |
| Waiting for response | A manufacturer, supplier or insurer has to reply. |
| Waiting on parts | A replacement part is ordered and on its way. |
| Warranty approved | The claim was accepted; the repair can be booked. |
| Warranty rejected | The claim was declined; the work becomes chargeable. |
| Resolved | Dealt with, not yet closed. |
| Closed | Finished, with the outcome recorded. |
| Cancelled | Not a real issue, a duplicate, or withdrawn. |

### B.3 Outcome codes

Repaired on site · Parts replaced · Manufacturer warranty ·
Settings or firmware corrected · Install corrected · No fault found ·
Advice given, no visit · Duplicate of another ticket · Other.

### B.4 Inspection outcomes

- **Pass** — everything checked is correct and safe to hand over.
- **Conditional** — working, but the listed defects must be fixed.
- **Fail** — must not be energised or handed over until fixed and reinspected.

Report states: **Waiting on the crew**, **Submitted**, **Reviewed**, **Link
expired**.

### B.5 Other lists

- **Lead statuses:** New, Contact required, Contacted, Qualified, Site assessment
  required, Quotation in progress, Quotation sent, Follow-up required,
  Negotiation, Won, Lost, On hold.
- **Quotation statuses:** Draft, Awaiting approval, Sent, Accepted, Declined,
  Expired.
- **Invoice statuses:** Draft, Awaiting approval, Approved, Issued, Partially paid,
  Paid, Overdue, Disputed, Cancelled.
- **Reimbursement statuses:** Submitted, Under review, Information required,
  Approved, Rejected, Awaiting payment, Paid.

## Appendix C. Administrator commands

Run these on the server, from the project root. Everything that writes to the
database takes and verifies its own backup first; nothing here replaces a backup.

```bash
# A verified backup before any change. Label it with the reason.
php artisan crm:backup --type=database --label="what you are about to do"
php artisan crm:backup --type=files
php artisan crm:backup --type=full

# The version this installation reports, and the entry behind it.
php artisan crm:version
php artisan crm:version --patch --note="a small change"
php artisan crm:version --minor --note="a new part"
php artisan crm:version --install-hook   # every commit carries a version

# Check every archive, print record counts and the manual restore procedure.
php artisan crm:restore-check

# Restore one archive, deliberately.
php artisan crm:restore <backup-id> --confirm

# Job, client and installer sheets, on the command line.
php artisan crm:import-jobs --google --list
php artisan crm:import-jobs --google            # report only
php artisan crm:import-jobs --google --commit   # writes
php artisan crm:import-clients                  # report only
php artisan crm:import-installers "register.xlsx"

# The temporary Client Information sales workbook. Dry run first.
php artisan crm:import-client-information "Client_Information.xlsx"
php artisan crm:import-client-information "Client_Information.xlsx" --commit

# The install schedule: regenerate the CSV from the workbook, never the database.
python tools/parse_install_schedule.py "path/Install Schedule.xlsx"
php artisan crm:import-install-schedule         # dry run
php artisan crm:import-install-schedule --commit

# Old support desk, from the database it still lives in.
php artisan tickets:import --from-database=<old-db> --dry-run
php artisan tickets:import --from-database=<old-db>

# Building companies already in the customer list, made into builder accounts.
php artisan crm:find-builders                 # lists what it found, changes nothing
php artisan crm:find-builders --commit        # converts, after a verified backup
php artisan crm:find-builders --revert=<report.json>

# Duplicate customer records folded into one, with their sites and jobs.
php artisan crm:merge-customers CUS-000123 --from=CUS-000456 --from=CUS-000789
php artisan crm:merge-customers CUS-000123 --from=CUS-000456 --type=builder --commit
php artisan crm:merge-customers --revert=<report.json>

# Scheduled work needs the scheduler running.
php artisan schedule:run
```

**Never** run `migrate:fresh`, `migrate:refresh`, `migrate:rollback`, `db:wipe` or
`db:seed` against a database that holds real work, and never re-run
`RolePermissionSeeder` on a live database — it rewrites every role back to its
defaults. Permission changes on a live installation come from
`crm:grant-*` commands or the Roles screen, which only ever add.

---

*Keep this manual beside the system rather than in a drawer. When a screen
changes, change the paragraph that describes it in the same pull request — a
manual nobody trusts is worse than none.*
