# Go-live runbook

How to bring an existing business centre into this system: one branch at a
time, from the files you already have, in an order the system enforces.

Everything happens on one page — **Setup → Go-live** — which sets the
go-live date, holds every import in order, shows the readiness checks with
their figures, lets you take a mistaken record back out, and completes the
go-live. Only head office and super admin can open it.

---

## 1. The model, in one paragraph

Each branch has a **go-live date**: the first day this system runs it.
Everything before that day is summarised as opening balances, dated **the
day before go-live**. Every opening balance posts against a single account,
**3900 Opening Balance Clearing**. Your accountant posts the rest of the
opening trial balance — bank, fixed assets, equity, and every other account
no sub-ledger owns — by ordinary manual journal against 3900. When the
opening position is complete, **3900 nets to zero**. That one number is why
the readiness checks exist.

Completing go-live closes the branch's books through the day before go-live
and locks the opening position: after it, no opening balance can be posted,
re-imported or removed, the go-live date cannot change, and 3900 is closed
to every writer.

---

## 2. Before you start

Have ready, as spreadsheets:

- your branches, customers and units;
- every tenancy still running on the go-live date, with its terms and its
  deposit;
- for cheque-driven tenancies, the remaining instalments and the cheques in
  the drawer;
- every invoice a customer still owed on the go-live date, with the amount
  still outstanding **including VAT** and the **original** due date;
- every advance a customer had paid and you had not yet applied;
- every supplier bill you had not yet paid;
- the rest of the opening trial balance, from the old system, as at the day
  before go-live.

Go live **at the start of a VAT period**, after the previous period has been
filed and paid in the old system. See §7.

### A queue worker has to be running

Every import on this page is a **queued job**. The application runs on the
`database` queue driver, so with no worker running an upload says *Import
started* and then nothing at all happens: no rows appear, no failed-rows
file is produced, and every readiness check sits at zero however long you
wait. Nothing is lost — the job is waiting in the `jobs` table — but nothing
happens either.

Before the first import, start a worker on the server and leave it running
for the whole go-live:

```
php artisan queue:work --queue=default
```

If a go-live seems to have stalled, check that first: `php artisan
queue:monitor default` says how many jobs are waiting, and
`php artisan queue:failed` lists any that gave up.

### The scheduler is separate, and one of its jobs drafts invoices

The queue worker above runs the imports. The **scheduler** is a different
thing — one cron line, `docs/deployment.md` §9 — and none of the eight
commands it runs is queued. A go-live does not need it, and it changes
nothing on this page.

One thing to expect if it is already running: `billing:run --all-branches`
fires at **07:45** every day and drafts an invoice for every lease
instalment due today or earlier. So a go-live that spans a morning can
produce draft invoices you did not ask for, for the instalments you imported
in §3.6.

They are **drafts** — no number, nothing posted to the ledger, nothing sent
to anyone — and you can delete any you do not want from Billing → Invoices.
They do not affect the readiness checks in §5 or completion in §6. If you
would rather not see them at all, do the go-live with the cron line
commented out and put it back when you complete.

What the run drafts and what §3.7 imports do not overlap, as long as each
lease's `billed_through` is right: opening invoices are what was already
billed in the old system, and §3.6's instalments start the day after that.
A `billed_through` set too early is the one way to end up with both.

---

## 3. The order, and the files

The page greys out each step until the one before it has something to build
on, and every importer refuses a row whose dependencies are missing, by
name. Every import modal carries a **Download example CSV** button that
produces the exact columns in the exact order, with a worked example row —
that file is the template. Column names are the CSV header row.

### 3.1 Branches

| Column | Example | Notes |
|---|---|---|
| `name` | Business Bay Centre | required |
| `code` | BB | required, the natural key — re-importing updates by code |
| `address` | Office 1204, Bay Square Building 6, Business Bay, Dubai | |
| `emirate` | Dubai | unmatched text becomes blank and is logged |
| `trn` | 100234567800003 | |
| `trade_licence_no` | CN-9911223 | |
| `licence_expiry` | 2027-05-31 | |
| `is_active` | yes | `0`, `false` or `no` deactivates |

### 3.2 Customers

| Column | Example | Notes |
|---|---|---|
| `name` | Acme Trading FZ-LLC | required |
| `type` | company | `company` or `individual` |
| `trade_licence_no` | CN-1234567 | the natural key — **every later file names a customer by this** |
| `licence_expiry` | 2027-03-31 | |
| `trn` | 100111222333003 | |
| `email` / `phone` / `address` | accounts@acmetrading.ae / +971 4 555 0100 | |
| `status` | active | |

### 3.3 Units

| Column | Example | Notes |
|---|---|---|
| `code` | B2-301 | required, the natural key, upper-cased |
| `unit_type` | office | must match a unit type's slug or name |
| `floor` / `area_sqft` / `seating_capacity` | 2 / 450 / 6 | |
| `capacity` | 1 | how many tenancies the unit takes at once |
| `list_price_monthly` | 12000.00 | **used to split a lease's rent when the lease file leaves `unit_rents` blank** |
| `status` | available | recomputed by the lease import anyway |

### 3.4 Set the go-live date

On the page: **Set the go-live date**. Opening balances will be dated the
day before. The date can still be changed until the first opening balance
is posted; after that it is fixed, and after completion it is final.

### 3.5 Live leases

One row per tenancy still running at go-live. The lease is created
**Active**, keeping its own contract number. Its deposit is recorded here,
and its instalments **from go-live onwards** — what is still owed from
before go-live is an *opening invoice* (§3.7), not a past instalment.

| Column | Example | Notes |
|---|---|---|
| `contract_no` | LSE-2025-0042 | required, kept as it is |
| `customer` | CN-1234567 | trade licence number, or the exact name |
| `units` | B2-301;B2-302 | unit codes separated by `;` |
| `unit_rents` | 7000;5000 | AED per unit, same order; **blank splits by list price** |
| `start_date` / `end_date` | 2026-03-01 / 2027-02-28 | the real contract dates |
| `rent_monthly` | 12000.00 | AED, excluding VAT |
| `billing_cycle` | monthly | `monthly`, `quarterly`, `half_yearly`, `annual` |
| `schedule` | auto | `auto` generates the remaining instalments; `manual` takes them from §3.6 |
| `billed_through` | 2026-08-31 | the last date the old system invoiced; instalments start after it |
| `security_deposit` | 24000.00 | AED, or blank for none |
| `deposit_held_as` | cheque | `bank` (already banked) or `cheque` (still uncleared) |
| `deposit_cheque_no` / `deposit_bank` | 000451 / Emirates NBD | **both required** when held as a cheque |
| `notice_period_days` | 60 | |
| `ejari_no` | EJ-77120034 | |
| `late_fee_type` … `late_fee_vat_rate` | percentage / 7 / / 200 / 1000.00 / 5 | leave the whole group blank for no late fee |
| `credit_hours_per_month` | 8 | meeting-room entitlement, in hours |
| `charges` | Service charge:500:monthly;DEWA recharge:250:monthly | recurring charges — see below |

A `billed_through` that falls **inside** a billing period drops that whole
period from the instalments. What is still owed from it belongs in the
opening invoices file.

**Recurring charges come in here, in one cell.** Write each charge as
`Name:amount:frequency` and separate them with semicolons:

```
Service charge:500:monthly;DEWA recharge:250:monthly
```

The amount is AED **per occurrence** — 500 a month, not 6,000 a year — and
the frequency is `one_off`, `monthly`, `quarterly` or `annual`. An optional
fourth field of `no` says VAT does not apply to that charge; leave it out
and it does. **A charge name may contain neither a colon nor a semicolon**,
because those are the separators; a name that carries one is refused by
name, quoting the entry back, as is an unknown frequency, an amount that is
not AED, and the same name twice on one lease.

This cell is not a convenience. A lease's instalments are generated **once**,
at import, from the rent *and the charges together* — and nothing in the
system regenerates a schedule afterwards. A charge added to a lease by hand
after its import would therefore never reach an invoice: every invoice for
the rest of the term would bill rent alone, silently. If a lease comes in
with the wrong charges, remove it under **Opening records** (§5) and import
the corrected row.

On a `manual` lease the instalments come from the schedule file with their
own figures, which already carry whatever the old system charged. The
charges are still recorded on the lease — for the record, and so it reads
correctly on its own page — but the schedule is left exactly as the file
states it.

Late-fee terms, when given, take effect from the **go-live date** — penalties
start where this system's history starts, never over the old system's
periods.

### 3.6 Lease schedules

One row per remaining instalment of a `manual` lease, and one row per rent
cheque you hold for **either** kind of lease.

| Column | Example | Notes |
|---|---|---|
| `contract_no` | LSE-2025-0099 | the lease must already be imported |
| `due_date` | 2026-09-01 | |
| `period_start` / `period_end` | 2026-09-01 / 2026-09-30 | |
| `amount` | 8000.00 | AED, excluding VAT |
| `vat_amount` | 400.00 | |
| `cheque_no` / `bank_name` | 000601 / Emirates NBD | both together, or neither |

Rules worth knowing before you build the file:

- A manual lease's rows must run **contiguously** from the day after
  `billed_through` (or the lease's start) to the **end date**. A gap, an
  overlap or a period past the end date refuses the row by name, and a file
  that simply stops early is caught by the readiness checks (§5).
- For an `auto` lease the instalments already exist, so a row can only
  attach a cheque: it must carry one, must name an instalment by its
  `due_date`, and its `amount + vat_amount` must equal that instalment's
  total.
- One instalment carries one cheque. A second one under a different number
  is refused.

### 3.7 Opening invoices

What each customer still owed on the go-live date.

| Column | Example | Notes |
|---|---|---|
| `customer` | CN-1234567 | |
| `legacy_number` | INV-0412 | becomes `OB-INV-0412` here |
| `invoice_date` | 2026-06-01 | the old invoice's date, for the description — optional |
| `due_date` | 2026-06-30 | the **original** due date, however long past |
| `amount_outstanding` | 10500.00 | **what is still owed, VAT included** |

The VAT was charged and declared in the old system, so none is re-posted
here and these invoices never appear on a VAT return. They age, appear on
statements, take receipts and credit notes like any other invoice. They
never accrue a late-payment penalty (they carry no instalment) — a known
limitation.

### 3.8 Opening advances

Money a customer had paid that you had not yet applied.

| Column | Example | Notes |
|---|---|---|
| `customer` | CN-7654321 | |
| `legacy_reference` | RCT-0088 | becomes `OB-RCT-0088` |
| `method` | bank_transfer | **not `cheque`** — a cleared cheque is recorded as a bank transfer |
| `amount` | 2000.00 | |

They arrive wholly unallocated and are applied through the ordinary
**Apply advance** action.

### 3.9 Opening vendor bills

Add your vendors first, under **Purchases → Vendors** (there is no vendor
import — see §7).

| Column | Example | Notes |
|---|---|---|
| `vendor` | 100777888999003 | TRN, or the exact name |
| `legacy_number` | SUP-7781 | becomes `OB-SUP-7781`, and the bill's supplier invoice number |
| `bill_date` | 2026-07-15 | the supplier's own invoice date; must be on or before the opening balance date |
| `due_date` | 2026-08-15 | the original due date |
| `amount_outstanding` | 3000.00 | what is still owed |

No expense and no input VAT are posted — the old system accounted for both.
The bill is Approved and payable through the ordinary vendor payment flow.

---

## 4. What the accountant posts by hand

Everything a control account does **not** own. One or more ordinary manual
journals, **dated the day before go-live** (the system requires exactly that
date for any line on 3900), each balanced against 3900:

| Opening item | The journal |
|---|---|
| Bank and cash balances | Dr 1100 / 1110 … Cr 3900 |
| Fixed assets at cost, with their accumulated depreciation | **Dr 1500 / Cr 1510 / Cr 3900** |
| Prepayments, other receivables | Dr … / Cr 3900 |
| Accruals, other payables, loans | Dr 3900 / Cr … |
| Share capital, retained earnings | Dr 3900 / Cr 3000-series |

**This is a two-person job, and the system enforces it.** A manual journal
is prepared by one person and posted by another: `ManualJournalService`
refuses to let anyone post a journal they prepared or last edited
themselves. So a single head-office operator working alone **cannot** post
the opening trial balance at all — 3900 never reaches zero, and go-live
refuses to complete for ever with nothing on the page explaining why.

Arrange a second user before you start, under **Setup → Staff** — *New staff
member* asks for a name, an email address, one role, the branches they work
at, and a password typed twice. Only `head_office` and `super_admin` can
open that screen, so the first administrator creates the rest.

Preparing a journal is open to the finance roles; **posting one needs
`head_office` or `super_admin`**, and it must be someone other than whoever
prepared or last edited it. The usual shape is an `accounts` user preparing
the journal and a `head_office` user posting it; two `head_office` users
work equally well.

Post them, then look at the **3900 nets to zero** check. If it is not zero,
the opening trial balance is incomplete or one side of it is wrong: the
figure shown is what is missing, and its sign tells you which way.

3900 accepts manual journals **only while the branch is going live**. After
completion it is closed to everyone, and it disappears from the trial
balance and the balance sheet, because it is zero.

---

## 5. The readiness checks

> Rehearsed on 2026-09-28 against a scratch database installed from empty -
> migrate, the five seeders of deployment.md §10, then §3.1-§3.5 driven
> through the real importers. Branch, customer and two units imported clean;
> the go-live date set; one live lease produced an Active lease on two units
> with five instalments starting after its `billed_through`, one recurring
> charge, a AED 24,000 deposit held as a cheque, an eight-hour credit
> entitlement, and two journal lines balancing to the fil. All nine checks
> below read Balanced, with 2200 and 1150 each showing AED 24,000 on both
> sides. Every column name in §3 matched the importers exactly.
>
> The queue was rehearsed separately, through the panel, on the same day, and
> §2's warning is exactly right in both directions. A customer import
> submitted from §3.2's modal with **no worker running** reported *importing
> in background* and then did nothing at all: no customer created, no
> failed-rows file, no error - while the `imports` row and one job sat
> waiting, `failed_jobs` empty. Nothing was lost. Starting
> `php artisan queue:work --queue=default` drained it within seconds and the
> customer appeared, 1 of 1 row successful.
>
> That is the shape of a go-live that looks broken and is not. If a go-live
> seems to have stalled, check the worker before anything else:
> `php artisan queue:monitor default` says how many jobs are waiting, and the
> work is still there when the worker starts.

The page shows nine checks, each with **both** figures — what the ledger
holds and what the records say — and the difference when they disagree.
Every figure is the branch's position now: the ledger read through today —
or through the opening balance date, whenever that is still ahead, because
the date is set before any import and every opening posting is dated there —
against the records as they stand. So a deposit cheque that bounces during
go-live moves both sides together, and a branch preparing its opening
position weeks before its first live day sees the same figures it will see
on the day.

| Check | Ledger | Records | If it is off |
|---|---|---|---|
| 3900 nets to zero | the 3900 balance | zero | The opening trial balance is incomplete — §4 |
| 1200 Receivables | the 1200 balance | open invoices' outstanding | An opening invoice was imported twice, or removed without its posting |
| 2400 Customer Advances | the 2400 balance | receipts' unapplied amounts | As above, for an advance |
| 2100 Payables | the 2100 balance | approved bills' outstanding | As above, for a bill |
| 2200 Security Deposits Held | the 2200 balance | deposits still held | As above, for a deposit |
| 1150 Cheques in Hand | the 1150 balance | deposit cheques still in the drawer | As above, for a deposit cheque |
| Every imported lease promising a deposit has one on record | unbacked leases | none | The lease's contract says it holds a deposit and no record backs it. Named by contract number: either the deposit was never imported, or it bounced — §5 |
| No imported lease still a draft | drafts | none | Named by contract number — remove it and import the corrected row |
| Every manual schedule covers its term | short schedules | none | The schedule file stopped early; it names the date it reaches and the date it must reach |

**Prepare early, complete on the day.** Every check above can be brought to
Balanced weeks before the branch's first live day — that is what setting the
date first is for. **Completion itself cannot happen until the go-live date
arrives** (§6), because completing closes the books through the day before
go-live and a period can only be closed through a day already past. While
the date is still ahead the page says so beneath the actions: *Completion
opens on …*.

**Correcting a mistake.** A record already imported and posted is handed
back as *skipped* on a re-import — correcting the spreadsheet alone changes
nothing. Use **Opening records** on the page to remove the wrong record
first (its posting is reversed on the opening balance date and its number is
freed), then import the corrected row. A record something has already used —
a receipt, a credit note, a payment, a booking — refuses removal by name,
because that use would be left pointing at a debt that never existed.

**Opening records** holds five groups: imported leases, opening invoices,
opening advances, **imported deposits** and opening vendor bills. The
deposits are listed separately from the leases that hold them, because 2200
and 1150 are usually wrong on their own: a deposit keyed at the wrong
amount, or recorded as banked when the cheque is still in the drawer, is
removed by itself. Removing a deposit reverses its posting on the opening
balance date and deletes the Pending cheque that held it.

**Re-importing the same lease will not put the deposit back.** An import of a
lease that already exists is a no-op — it matches on the contract's locked
terms and returns the lease untouched — so the only route to a corrected
deposit is to remove the **lease** and import the corrected row, which writes
the lease and its deposit together. Until then the ninth check names the
lease as promising a deposit nothing records, and go-live cannot complete.

A deposit that has already moved — released, forfeited, bounced, or with a
refund requested or approved — refuses removal: each of those is a decision
taken against money this would say was never held. A **bounced** deposit has
one route back: replace the bounced cheque on the cheque register, which
takes the deposit again, re-posts Dr 1150 / Cr 2200 and returns the deposit
to held. If the tenant never supplies a replacement the lease stays blocked,
because the deposit amount is a locked term on an active lease.

---

## 6. Completing

**Complete go-live** is only possible **on or after the go-live date
itself**. Completing closes the branch's books through the day before
go-live, and the period close refuses a date that has not arrived — so a
branch whose first live day is still ahead is refused by name (*"Branch BB
goes live on 1 Oct 2026. Complete go-live on or after that day."*). Prepare
the whole opening position in the weeks before; come back on the day and
press the button. Until then the page says when completion opens.

It also refuses unless every check passes, and will not submit
until you type the branch's own code into the modal: completion cannot be
undone, and the checks alone cannot tell a finished branch from one nothing
was ever imported into (every check is an equality, and nothing equals
nothing).

On success it closes the branch's books through the day before go-live —
through the ordinary period close, so it appears in the period-close history
like any other — and stamps the branch live. The one exception is a branch a
super admin has already closed through a **later** date by hand: its books
are already shut against the old system's periods, so completion leaves them
alone and writes no period-close history row. It still stamps the branch
live.

After completion:

- no opening balance can be posted, re-imported or removed;
- the go-live date is final;
- 3900 is closed to every writer, including manual journals;
- nothing can post into the old system's periods.

Reopening the period (super admin only) does **not** reopen the go-live.

---

## 7. Not imported — do these by hand

- **VAT balances.** Go live at the start of a VAT period, after the previous
  period is filed and paid in the old system. Any VAT still owed to the FTA
  is an ordinary payable in the accountant's journal, not a 2300 balance.
- **Employees.** Few records — key them through the form under **People**.
- **Assets.** The asset form carries opening cost and accumulated
  depreciation; the matching journal (Dr 1500 / Cr 1510 / Cr 3900) is
  ordinary — §4.
- **Payroll history.** Not imported.
- **Vendors.** No importer; add them under **Purchases → Vendors**.

---

## 8. The first month

Nothing after go-live is special. The next billing run — 07:45 daily, or the
**Draft due invoices** button on Billing → Invoices — drafts the imported
leases' instalments as it drafts any other, and somebody reviews and issues
each draft; a rent cheque clears into a
receipt that settles its invoice; an opening invoice takes a receipt or a
credit note; an opening advance is applied; an opening bill is paid; a
deposit is released or forfeited. Each control account keeps equalling its
sub-ledger, which is what the go-live was for.

A write-off of an opening balance is an ordinary credit note. It posts
Dr **5800 Bad Debts Written Off** / Cr 1200 and no VAT — it never touches
3900, which is closed.

An opening invoice carries a legacy debt, VAT included, that the old system
already declared as income in its own periods. Crediting it to 4100 Rental
Income would reduce rent this application never booked, understating rental
income by a debt that was never its revenue and hiding the write-off inside
the rent figure — nobody could see how much had been written off without
reading journals. So it goes to its own expense account instead, where the
P&L shows it on its own line and the corporate-tax estimate treats it as the
deductible cost it is.

Everything the accountant needs is therefore in the reports: the balance of
5800 for a period is what was written off in it, and 4100 is rent this
application actually billed. Nothing has to be kept on a list or corrected by
year-end journal.

A credit note against an **ordinary** invoice is unchanged: that one reverses
revenue recognised here, so it still debits the income account its line was
credited to — 4100 for rent, an adjustment or a lease charge, 4300 for room
hire, 4400 for a late-payment penalty.
