# -*- coding: utf-8 -*-
"""
Generates public/docs/Hotel-POS-User-Guide.pdf.
Run: python3 scripts/generate_user_guide.py
"""
import os
from reportlab.lib.pagesizes import A4
from reportlab.lib.units import mm
from reportlab.lib import colors
from reportlab.lib.styles import ParagraphStyle
from reportlab.lib.enums import TA_LEFT, TA_CENTER
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
from reportlab.platypus import (
    BaseDocTemplate, PageTemplate, Frame, Paragraph, Spacer, Table, TableStyle,
    KeepTogether, PageBreak, NextPageTemplate, ListFlowable, ListItem, HRFlowable
)
from reportlab.platypus.tableofcontents import TableOfContents

# ---------------------------------------------------------------- fonts
FONT_DIR = "/usr/share/fonts/truetype/dejavu"
pdfmetrics.registerFont(TTFont("DejaVuSans", f"{FONT_DIR}/DejaVuSans.ttf"))
pdfmetrics.registerFont(TTFont("DejaVuSans-Bold", f"{FONT_DIR}/DejaVuSans-Bold.ttf"))
pdfmetrics.registerFont(TTFont("DejaVuSans-Oblique", f"{FONT_DIR}/DejaVuSans-Oblique.ttf"))

# ---------------------------------------------------------------- palette
INK = colors.HexColor("#16211C")
MUTED = colors.HexColor("#5B6B62")
FAINT = colors.HexColor("#8B978F")
BORDER = colors.HexColor("#D9E2DC")
GOLD = colors.HexColor("#C98A2C")
GOLD_TINT = colors.HexColor("#FBF1E1")
BLUE = colors.HexColor("#2A6F97")
BLUE_TINT = colors.HexColor("#EAF2F8")
GREEN = colors.HexColor("#2E8B57")
GREEN_TINT = colors.HexColor("#E7F3EC")
RED = colors.HexColor("#B3402F")
RED_TINT = colors.HexColor("#FBEAE7")
HEAD_BG = colors.HexColor("#F5F7F6")
MAROON = colors.HexColor("#5C2210")   # Delight theme primary, used as this edition's accent rule

PAGE_W, PAGE_H = A4
MARGIN = 20 * mm

# ---------------------------------------------------------------- styles
styles = {}
styles["CoverTitle"] = ParagraphStyle("CoverTitle", fontName="DejaVuSans-Bold", fontSize=34, leading=40, textColor=INK)
styles["CoverSub"] = ParagraphStyle("CoverSub", fontName="DejaVuSans-Bold", fontSize=16, leading=20, textColor=GOLD)
styles["CoverTag"] = ParagraphStyle("CoverTag", fontName="DejaVuSans", fontSize=11.5, leading=16.5, textColor=MUTED)
styles["Eyebrow"] = ParagraphStyle("Eyebrow", fontName="DejaVuSans-Bold", fontSize=8, leading=10, textColor=FAINT)
styles["H1"] = ParagraphStyle("H1", fontName="DejaVuSans-Bold", fontSize=17, leading=21, textColor=INK, spaceBefore=2, spaceAfter=10)
styles["H2"] = ParagraphStyle("H2", fontName="DejaVuSans-Bold", fontSize=12.2, leading=15, textColor=INK, spaceBefore=12, spaceAfter=5)
styles["Body"] = ParagraphStyle("Body", fontName="DejaVuSans", fontSize=9.6, leading=14, textColor=INK, spaceAfter=6)
styles["BodyMuted"] = ParagraphStyle("BodyMuted", parent=styles["Body"], textColor=MUTED)
styles["StepTitle"] = ParagraphStyle("StepTitle", fontName="DejaVuSans-Bold", fontSize=9.8, leading=13, textColor=INK, spaceBefore=7, spaceAfter=1)
styles["StepBody"] = ParagraphStyle("StepBody", fontName="DejaVuSans", fontSize=9.6, leading=13.5, textColor=INK, spaceAfter=2, leftIndent=0)
styles["Bullet"] = ParagraphStyle("Bullet", fontName="DejaVuSans", fontSize=9.6, leading=13.6, textColor=INK, spaceAfter=3, leftIndent=10, bulletIndent=0)
styles["CalloutLabel"] = ParagraphStyle("CalloutLabel", fontName="DejaVuSans-Bold", fontSize=8.6, leading=11, spaceAfter=2)
styles["CalloutBody"] = ParagraphStyle("CalloutBody", fontName="DejaVuSans", fontSize=9.3, leading=13, textColor=INK)
styles["TableHead"] = ParagraphStyle("TableHead", fontName="DejaVuSans-Bold", fontSize=8.6, leading=11, textColor=colors.white)
styles["TableCell"] = ParagraphStyle("TableCell", fontName="DejaVuSans", fontSize=8.6, leading=12, textColor=INK)
styles["TableCellB"] = ParagraphStyle("TableCellB", fontName="DejaVuSans-Bold", fontSize=8.6, leading=12, textColor=INK)
styles["Menu"] = ParagraphStyle("Menu", fontName="DejaVuSans-Oblique", fontSize=9.2, leading=13, textColor=BLUE, spaceAfter=8)
styles["TOCHeading"] = ParagraphStyle("TOCHeading", fontName="DejaVuSans-Bold", fontSize=17, leading=21, textColor=INK, spaceAfter=12)
styles["TOC1"] = ParagraphStyle("TOC1", fontName="DejaVuSans", fontSize=10.3, leading=19, textColor=INK)
styles["Small"] = ParagraphStyle("Small", fontName="DejaVuSans", fontSize=8, leading=11, textColor=FAINT)

CALLOUTS = {
    "TIP": (BLUE, BLUE_TINT),
    "GOOD TO KNOW": (GREEN, GREEN_TINT),
    "BE CAREFUL": (RED, RED_TINT),
    "NEW": (GOLD, GOLD_TINT),
}

# ---------------------------------------------------------------- helpers
def P(text, style="Body"):
    return Paragraph(text, styles[style])

def h1(number, title):
    return [
        Paragraph(f'<font color="#C98A2C">{number}.</font> {title}', styles["H1"]),
        HRFlowable(width="100%", thickness=1.4, color=BORDER, spaceAfter=10),
    ]

def h2(text):
    return P(text, "H2")

def menu(text):
    return P(f"Menu: {text}" if "," not in text and ":" not in text else text, "Menu")

def body(text):
    return P(text, "Body")

def bullets(items):
    return ListFlowable(
        [ListItem(P(t, "Bullet"), leftIndent=12, value="•", bulletFontName="DejaVuSans-Bold", bulletColor=GOLD) for t in items],
        bulletType="bullet", start="•", leftIndent=6, spaceAfter=8,
    )

def step(number, title, desc_html):
    tbl = Table(
        [[Paragraph(str(number), ParagraphStyle("num", fontName="DejaVuSans-Bold", fontSize=9.5, textColor=colors.white, alignment=TA_CENTER)),
          [Paragraph(f"<b>{title}</b>", styles["StepTitle"]), Paragraph(desc_html, styles["StepBody"])]]],
        colWidths=[7.2 * mm, None],
    )
    tbl.setStyle(TableStyle([
        ("VALIGN", (0, 0), (-1, -1), "TOP"),
        ("BACKGROUND", (0, 0), (0, 0), MAROON),
        ("ROUNDEDCORNERS", [3, 3, 3, 3]) if hasattr(TableStyle, "roundedCorners") else ("LEFTPADDING", (0, 0), (0, 0), 0),
        ("TOPPADDING", (0, 0), (0, 0), 3),
        ("LEFTPADDING", (1, 0), (1, 0), 8),
        ("TOPPADDING", (1, 0), (1, 0), 0),
        ("BOTTOMPADDING", (0, 0), (-1, -1), 2),
    ]))
    return KeepTogether([tbl, Spacer(1, 2)])

def steps(items):
    """items: list of (title, desc_html)"""
    return [step(i + 1, t, d) for i, (t, d) in enumerate(items)]

def callout(kind, title, text):
    color, tint = CALLOUTS[kind]
    label = f'<font color="{color.hexval()[2:] and "#"+color.hexval()[2:]}">{kind}</font>'
    content = [
        Paragraph(f'<b><font color="{"#"+color.hexval()[2:]}">{kind}</font></b>  <b>{title}</b>', styles["CalloutLabel"]),
        Paragraph(text, styles["CalloutBody"]),
    ]
    tbl = Table([[content]], colWidths=[PAGE_W - 2 * MARGIN - 3])
    tbl.setStyle(TableStyle([
        ("BACKGROUND", (0, 0), (-1, -1), tint),
        ("BOX", (0, 0), (-1, -1), 0.6, tint),
        ("LINEBEFORE", (0, 0), (0, 0), 2.6, color),
        ("LEFTPADDING", (0, 0), (-1, -1), 10),
        ("RIGHTPADDING", (0, 0), (-1, -1), 10),
        ("TOPPADDING", (0, 0), (-1, -1), 7),
        ("BOTTOMPADDING", (0, 0), (-1, -1), 7),
    ]))
    return KeepTogether([Spacer(1, 3), tbl, Spacer(1, 8)])

def grid(headers, rows, col_widths, header_bg=None):
    header_bg = header_bg or INK
    data = [[Paragraph(h, styles["TableHead"]) for h in headers]]
    for r in rows:
        data.append([Paragraph(c, styles["TableCellB"]) if i == 0 else Paragraph(c, styles["TableCell"]) for i, c in enumerate(r)])
    tbl = Table(data, colWidths=col_widths, repeatRows=1)
    style = [
        ("BACKGROUND", (0, 0), (-1, 0), header_bg),
        ("TOPPADDING", (0, 0), (-1, -1), 5),
        ("BOTTOMPADDING", (0, 0), (-1, -1), 5),
        ("LEFTPADDING", (0, 0), (-1, -1), 7),
        ("RIGHTPADDING", (0, 0), (-1, -1), 7),
        ("VALIGN", (0, 0), (-1, -1), "TOP"),
        ("LINEBELOW", (0, 0), (-1, -1), 0.5, BORDER),
        ("ROWBACKGROUNDS", (0, 1), (-1, -1), [colors.white, HEAD_BG]),
    ]
    tbl.setStyle(TableStyle(style))
    return KeepTogether([tbl, Spacer(1, 8)])

def matrix(headers, rows, col_widths):
    """Role/permission matrix with dot markers."""
    head_style = ParagraphStyle("matHead", fontName="DejaVuSans-Bold", fontSize=7.1, leading=8.6, textColor=colors.white, alignment=TA_CENTER)
    data = [[Paragraph(headers[0], styles["TableHead"])] + [Paragraph(h, head_style) for h in headers[1:]]]
    for r in rows:
        row = [Paragraph(r[0], styles["TableCellB"])]
        for v in r[1:]:
            txt = '<font color="#2E8B57"><b>&#9679;</b></font>' if v else '<font color="#C7D0CB">&#8211;</font>'
            row.append(Paragraph(txt, ParagraphStyle("dot", fontName="DejaVuSans", fontSize=9.5, alignment=TA_CENTER)))
        data.append(row)
    tbl = Table(data, colWidths=col_widths, repeatRows=1)
    tbl.setStyle(TableStyle([
        ("BACKGROUND", (0, 0), (-1, 0), INK),
        ("ALIGN", (1, 0), (-1, -1), "CENTER"),
        ("VALIGN", (0, 0), (-1, -1), "MIDDLE"),
        ("TOPPADDING", (0, 0), (-1, 0), 5),
        ("BOTTOMPADDING", (0, 0), (-1, 0), 5),
        ("LEFTPADDING", (1, 0), (-1, 0), 1.5),
        ("RIGHTPADDING", (1, 0), (-1, 0), 1.5),
        ("TOPPADDING", (0, 1), (-1, -1), 4.5),
        ("BOTTOMPADDING", (0, 1), (-1, -1), 4.5),
        ("LEFTPADDING", (0, 0), (0, -1), 5),
        ("RIGHTPADDING", (0, 0), (0, -1), 5),
        ("LINEBELOW", (0, 0), (-1, -1), 0.4, BORDER),
        ("ROWBACKGROUNDS", (0, 1), (-1, -1), [colors.white, HEAD_BG]),
        ("FONTSIZE", (0, 1), (-1, -1), 7.6),
    ]))
    return tbl

def spacer(h=8):
    return Spacer(1, h)

# ---------------------------------------------------------------- doc template with header/footer + TOC
class GuideDocTemplate(BaseDocTemplate):
    def afterFlowable(self, flowable):
        if isinstance(flowable, Paragraph):
            style = flowable.style.name
            text = flowable.getPlainText()
            if style == "H1":
                self.notify("TOCEntry", (0, text, self.page))
                self.canv.bookmarkPage(text)
                self.canv.addOutlineEntry(text, text, 0, 0)

def draw_page(canv, doc):
    canv.saveState()
    # header eyebrow
    if doc.page > 2:
        canv.setFont("DejaVuSans-Bold", 8)
        canv.setFillColor(FAINT)
        canv.drawString(MARGIN, PAGE_H - 13 * mm, "HOTEL POS User Guide  ·  Delight Restaurant edition")
        canv.setStrokeColor(BORDER)
        canv.setLineWidth(0.6)
        canv.line(MARGIN, PAGE_H - 14.5 * mm, PAGE_W - MARGIN, PAGE_H - 14.5 * mm)
    if doc.page > 1:
        canv.setStrokeColor(BORDER)
        canv.setLineWidth(0.6)
        canv.line(MARGIN, 15 * mm, PAGE_W - MARGIN, 15 * mm)
        canv.setFont("DejaVuSans", 7.6)
        canv.setFillColor(FAINT)
        canv.drawString(MARGIN, 11 * mm, "Need help? Ask your manager or administrator.")
        canv.drawRightString(PAGE_W - MARGIN, 11 * mm, f"Page {doc.page - 1}")
    canv.restoreState()

def draw_cover(canv, doc):
    canv.saveState()
    canv.setFillColor(colors.HexColor("#FBF1E1"))
    canv.rect(0, PAGE_H - 100 * mm, PAGE_W, 100 * mm, stroke=0, fill=1)
    canv.setFillColor(MAROON)
    canv.rect(0, PAGE_H - 6 * mm, PAGE_W, 6 * mm, stroke=0, fill=1)
    canv.setFillColor(GOLD)
    canv.rect(0, PAGE_H - 100 * mm, PAGE_W, 1.4 * mm, stroke=0, fill=1)
    canv.restoreState()

# ---------------------------------------------------------------- build
def build():
    out_path = os.path.join(os.path.dirname(__file__), "..", "public", "docs", "Hotel-POS-User-Guide.pdf")
    out_path = os.path.abspath(out_path)

    doc = GuideDocTemplate(out_path, pagesize=A4,
                            leftMargin=MARGIN, rightMargin=MARGIN,
                            topMargin=22 * mm, bottomMargin=20 * mm,
                            title="Hotel POS User Guide", author="Hotel POS")

    frame_cover = Frame(MARGIN, MARGIN, PAGE_W - 2 * MARGIN, PAGE_H - 2 * MARGIN, id="cover")
    frame_normal = Frame(MARGIN, 18 * mm, PAGE_W - 2 * MARGIN, PAGE_H - 22 * mm - 18 * mm, id="normal")

    doc.addPageTemplates([
        PageTemplate(id="Cover", frames=[frame_cover], onPage=draw_cover),
        PageTemplate(id="Normal", frames=[frame_normal], onPage=draw_page),
    ])

    toc = TableOfContents()
    toc.levelStyles = [styles["TOC1"]]
    toc.dotsMinLevel = 0

    story = []

    # ---------------- Cover
    story.append(Spacer(1, 34 * mm))
    story.append(Paragraph("Hotel POS", styles["CoverTitle"]))
    story.append(Spacer(1, 2))
    story.append(Paragraph("User Guide", ParagraphStyle("t2", parent=styles["CoverTitle"], fontSize=22, leading=26, textColor=MAROON)))
    story.append(Spacer(1, 14))
    story.append(Paragraph("DELIGHT RESTAURANT EDITION", styles["CoverSub"]))
    story.append(Spacer(1, 62 * mm))
    story.append(Paragraph(
        "A simple, step-by-step guide to running your restaurant, bar, stores and tills "
        "— written for everyone on the team.", styles["CoverTag"]))
    story.append(Spacer(1, 4))
    story.append(Paragraph(
        "Keep this guide at the till, in the store and in the manager's office.", styles["CoverTag"]))
    story.append(Spacer(1, 10))
    story.append(Paragraph(
        "This edition covers PIN sign-in, holding and merging bills, kitchen/bar routing, "
        "happy hour, M-Pesa payments, work locations and the new Appearance settings.",
        ParagraphStyle("coverNote", parent=styles["CoverTag"], textColor=FAINT, fontSize=9.5)))

    story.append(NextPageTemplate("Normal"))
    story.append(PageBreak())

    # ---------------- Contents
    story.append(Paragraph("Contents", styles["TOCHeading"]))
    story.append(toc)
    story.append(PageBreak())

    # ================= 1. Welcome =================
    story += h1(1, "Welcome")
    story.append(body(
        "This guide explains, in plain words, how to use the Hotel POS system every day. You do not "
        "need any computer knowledge. If you can use a phone, you can use this system."))
    story.append(body(
        '"POS" means Point of Sale — the place where an order is taken, billed and paid for. Hotel POS '
        "does that for your restaurant and your bar, and it also looks after the things behind the scenes: "
        "what is in the store, what you owe suppliers, what customers owe you, the money in each till, "
        "and the reports the owner and the accountant need."))
    story.append(h2("What the system does for you"))
    story.append(bullets([
        "Takes orders at tables and at the bar — on the same bill if you serve both — and sends food orders to the kitchen screen and drinks to the bar.",
        "Lets a waiter <b>hold or suspend</b> a bill and hands the money-collecting job to the cashier, who sees every held bill in one queue.",
        "Prints bills and receipts and records how each customer paid — cash, M-Pesa (including a live prompt to their phone), card or on account — and lets one bill be paid with more than one method.",
        "Keeps count of your stock automatically. Every plate or drink sold reduces the store.",
        "Shows how the business is doing — sales, profit, expenses and what needs re-ordering — including a live view of cash and payments for the cashier's current shift.",
        "Records tax invoices so the hotel stays in order with the tax authority (KRA).",
        "Remembers who did what, so mistakes can be traced and honest staff are protected.",
    ]))
    story.append(h2("Who sees what"))
    story.append(body(
        "Nobody sees everything. Each person has a role (for example Waiter, Cashier or Manager), and the "
        "role decides which menu items, buttons and figures that person sees. A waiter sees the tables and "
        "the bar; a storekeeper sees the store; the owner sees the reports. If something in this guide is "
        "not on your screen, your role is simply not meant to use it — that is normal, not a fault."))
    story.append(callout("GOOD TO KNOW", "Roles can be changed",
        "Your administrator can change what each role can see and do at any time (see Chapter 12), and can "
        "also restrict a person to one work area — for example \"Bar only\" — even if their role covers more. "
        "The changes take effect the next time that person opens a page."))
    story.append(PageBreak())

    # ================= 2. Getting started =================
    story += h1(2, "Getting started")
    story.append(h2("Signing in"))
    story += steps([
        ("Open the system",
         "On the till or phone, open your web browser (Chrome is best) and go to the address your manager gave you. Save it as a bookmark so it is always one tap away."),
        ("Waiters, cashiers and bartenders: use your PIN",
         "Tap the number pad on the sign-in screen and enter your 4- or 5-digit PIN, then tap <b>Sign in</b>. There is no email or password for these roles — the PIN is all you need, and only you should know it."),
        ("Managers and office roles: use email and password",
         "Tap <b>Sign in with email</b> below the PIN pad, then enter the email and password your administrator gave you."),
        ("Look at the menu on the left",
         "Only the things you are allowed to use are shown. On a phone, tap the &#9776; button at the top left to open the menu."),
    ])
    story.append(callout("BE CAREFUL", "Protect your PIN or password",
        "Never share your PIN or password, and never sign in for someone else. Everything you do is recorded "
        "under your name. Always sign out when you leave the till. Getting the PIN wrong several times in a "
        "row locks it out for a short while, to stop someone guessing it."))
    story.append(callout("TIP", "Changing your PIN or password",
        "Tap your name (top right) → <b>My Profile</b>. If you sign in with a PIN you will see <b>Change PIN</b>; "
        "everyone else sees <b>Password</b>. You can do this any time you like, without asking an administrator — "
        "unless you have forgotten it, in which case see Chapter 14."))
    story.append(h2("A tour of the screen"))
    story.append(grid(
        ["Where", "What it is"],
        [
            ["Left menu", "The list of places you can go. It is grouped: Overview, Service, Operations, Compliance, Insights, Help and System."],
            ["Search box (top)", "Type at least two letters of a product, order number, table or customer name. Results appear in groups — tap one to jump straight to it."],
            ["Shift badge (top right)", "Green \u201cShift Open\u201d means your till shift is running. Grey \u201cNo Shift\u201d means it has not been opened yet — tap it to open one."],
            ["Connection badge (top right)", "Only appears if the connection drops. Amber \u201cOffline\u201d means actions you take (like adding an item) are being saved on the device and will be sent the moment the connection returns."],
            ["Your name (top right)", "Shows who is signed in and which role you have."],
            ["Dashboard", "The first page for most people: figures and charts about sales, stock and money."],
        ],
        [38 * mm, None],
    ))
    story.append(callout("NEW", "Cashiers go straight to Open Shift",
        "If you sign in and your role collects payments but you don't have a shift running yet, the system "
        "takes you straight to <b>Cashier Shifts</b> to open one, instead of the Dashboard. If you already have "
        "a shift open, you land on the Dashboard as normal."))
    story.append(h2("Choosing a time period"))
    story.append(body(
        "The Dashboard and every report have the same row of buttons: <b>Today</b>, <b>This week</b>, "
        "<b>This month</b> and <b>Custom</b>. Tap one and the figures change. With Custom, pick a start and "
        "end date and tap Apply."))
    story.append(h2("Downloading what you see"))
    story.append(body("Where you see an Export button, tap it and choose a format. The file contains exactly what is on your screen for the period you chose."))
    story.append(grid(
        ["Choose…", "Best for"],
        [
            ["PDF", "Printing, or sending to someone who only needs to read it."],
            ["Excel (.xlsx)", "Working with the numbers — adding, sorting, making your own charts. Each section opens on its own tab."],
            ["CSV (.csv)", "Moving figures into another program. Opens in Excel too."],
        ],
        [38 * mm, None],
    ))
    story.append(callout("TIP", "No Export button?", "Your role has not been given permission to download. Ask your manager if you need it."))
    story.append(PageBreak())

    # ================= 3. A day in the life =================
    story += h1(3, "A day in the life of the system")
    story.append(body("Everything in Hotel POS follows the same rhythm. Read this page first — the rest of the guide zooms in on each part."))
    story.append(h2("The daily flow"))
    story.append(grid(
        ["Step", "What happens"],
        [
            ["1. Open shift", "Cashier counts the starting cash (the float) and opens their shift."],
            ["2. Take orders", "Waiter or bartender takes the order — food, drinks, or both on one bill."],
            ["3. Kitchen / Bar", "Food is sent to the kitchen screen; drinks are sent to the bar. Each prints its own ticket."],
            ["4. Bill, or hold", "Waiter prints the bill and either collects payment themselves (if allowed) or holds the order for the cashier."],
            ["5. Collect payment", "Cash, M-Pesa (including a phone prompt), card or account — one bill can be paid with more than one method."],
            ["6. Print receipt", "Give it to the customer. The order closes once it is fully paid."],
            ["7. Close shift", "Cashier counts the cash and closes their shift; the system compares it with what it expected."],
            ["8. Manager checks", "Approves any cash difference and reviews the day."],
        ],
        [30 * mm, None],
    ))
    story.append(h2("Behind the scenes, every day"))
    story.append(bullets([
        "Stock goes down automatically with every sale that uses ingredients.",
        "Storekeeper checks the low-stock list and orders more from suppliers.",
        "Goods are received and stock goes back up.",
    ]))
    story.append(h2("Who does what"))
    story.append(grid(
        ["Role", "Main job"],
        [
            ["Waiter", "Opens tables (and, if allowed, the bar), takes orders, sends food to the kitchen and drinks to the bar, prints bills, and can hold a bill for the cashier or merge their own bills together."],
            ["Bartender", "Available for hotels that want a dedicated bar-only person — otherwise a Waiter can do everything a Bartender does."],
            ["Chef", "Watches the Kitchen Display and moves orders from New to Ready."],
            ["Cashier", "Opens and closes the till shift, collects payments (cash, M-Pesa, card, account), clears bills waiters have held, records small expenses."],
            ["Storekeeper", "Keeps stock right: receives goods, transfers, stock counts, wastage."],
            ["Accountant", "Watches expenses, tax, customer and supplier balances, and profit reports."],
            ["Manager", "Approves things (expenses, orders, cash differences), fixes mistakes, reads reports."],
            ["Administrator", "Sets up staff, roles, prices, taxes, printers, payment methods, the logo and colour theme."],
        ],
        [26 * mm, None],
    ))
    story.append(PageBreak())

    # ================= 4. Waiters =================
    story += h1(4, "Waiters — taking orders")
    story.append(menu("Restaurant POS"))
    story.append(h2("Opening a table and taking an order"))
    story += steps([
        ("Go to Restaurant POS",
         "You will see the floor plan. Tables are grouped by floor and area. Colours show what is happening: free, occupied, billed, merged."),
        ("Tap a free table — or start a sale with no table",
         "A small window asks for the number of covers (guests). Enter it and confirm. To resume a table that already has an order, just tap it. If the customer isn't sitting anywhere — a takeaway, a delivery, or someone buying at the counter — tap <b>Quick sale</b> or <b>Takeaway / Delivery</b> instead; neither needs a table."),
        ("Tap items to add them",
         "Choose a category, then tap each dish or drink. If your role allows selling bar items from this screen, the bar's menu appears too (marked with a small glass icon) so food and drinks can sit on the same bill. Add any special request (for example \u201cno onions\u201d) in the instructions box. Change the quantity if needed."),
        ("Send to kitchen and send to bar",
         "Two separate buttons: <b>Send to kitchen</b> sends only the food items; <b>Send to bar</b> sends only the drinks. Each shows how many new items are waiting to go. Direct-sale items (bottled drinks, packaged snacks) need neither — they are simply on the bill."),
        ("Print — tick what you need",
         "The Print panel has three tick boxes: <b>Kitchen order</b>, <b>Bar order</b> and <b>Bill</b>. Tick any combination and press <b>Print selected</b> to print them together in one go."),
        ("Add more later",
         "You can keep adding items during the meal. Send each new batch to the kitchen or bar as before."),
    ])
    story.append(callout("BE CAREFUL", "Once it's sent or printed, it's locked",
        "Before an item has been sent to the kitchen/bar or printed, you can remove it yourself if something "
        "was tapped by mistake — look for <b>Remove</b> next to it. Once it has been sent or printed, it is "
        "committed: only someone with full void rights (usually a manager) can cancel it, and a reason is "
        "recorded. This protects the kitchen from food that quietly disappears after it's already cooking."))
    story.append(callout("NEW", "Cancel the whole order",
        "Started an order by mistake, or the table changed their mind before anything was sent? While nothing "
        "on the order has been sent to the kitchen/bar or printed yet, a <b>Cancel whole order</b> button lets "
        "you cancel it in one step. Once anything is locked, cancel items one at a time instead."))
    story.append(h2("Happy hour"))
    story.append(body(
        "If the hotel is running happy hour, a banner at the top of the screen shows the discount that is "
        "currently active and the price of every item updates automatically while it's on. Anything already "
        "on the bill keeps the price it was added at, even if happy hour is switched off afterwards. If your "
        "role allows it, you can switch happy hour on or off from the same banner."))
    story.append(h2("Merging bills"))
    story.append(body(
        "If two tables turn out to be one group, or a customer wants to move their tab onto a table bill, "
        "tap <b>Merge bills</b>. Tick the other open bill(s) to bring in — you can only merge your own bills "
        "unless your role allows merging anyone's. The items move across and the old bill is kept and marked "
        "<b>Merged</b> so nothing is lost from the record; nothing is voided."))
    story.append(h2("Holding or suspending a bill"))
    story.append(body(
        "If your role collects payment itself, follow the billing steps below. If it does not — most Waiter "
        "roles work this way — tap <b>Hold / suspend</b> instead of collecting money. The order is parked and "
        "appears in the cashier's <b>Held Orders</b> queue with your name on it, ready for you to hand over the "
        "cash when the customer pays. You can <b>Resume</b> a held order yourself at any time before the "
        "cashier clears it."))
    story.append(h2("Asking for the bill"))
    story += steps([
        ("Tap Generate bill",
         "A window shows the bill. If your role may give discounts, a Discount box is shown. A service charge box may also be shown."),
        ("Print the bill",
         "The bill opens in a new tab ready to print, or use the tick-box Print panel above. You can print it again at any time."),
        ("Hold, or collect payment",
         "If the cashier collects payment, tap Hold / suspend and hand over. If you collect it yourself, follow Chapter 6."),
    ])
    story.append(h2("Sharing one bill between several people"))
    story += steps([
        ("Tap Split bill", "Choose how many people, and whether to divide the items or share the total equally."),
        ("Give each person their part", "If you divide by items, place each item with the right person."),
        ("Print and collect separately", "Each person gets their own printed bill and pays on their own — one may pay cash and another M-Pesa."),
    ])
    story.append(h2("Finding an old order"))
    story.append(body(
        "Open <b>Retrieve Orders</b> in the menu. Choose a tab — Open &amp; billed, Awaiting payment, Held, "
        "Closed (last 7 days) or Void — or type an order number in the search box. Tap an order to open it, "
        "or reprint its receipt."))
    story.append(PageBreak())

    # ================= 5. Kitchen =================
    story += h1(5, "The kitchen screen")
    story.append(menu("Kitchen Display"))
    story.append(body(
        "The Kitchen Display replaces paper tickets shouted across the kitchen. Every order a waiter sends "
        "<i>to the kitchen</i> appears here with the table, the waiter, the items and how long it has been "
        "waiting. Drinks sent to the bar do not appear here — they go to the bar printer instead (see Chapter 7)."))
    story.append(h2("The four steps of every dish"))
    story.append(grid(
        ["Step", "Meaning"],
        [
            ["New", "Just arrived."],
            ["Preparing", "Chef has started."],
            ["Ready", "Waiter can collect."],
            ["Served", "Delivered to the table."],
        ],
        [30 * mm, None],
    ))
    story += steps([
        ("Watch for new orders",
         "They appear at the top. Orders for different stations (grill, cold kitchen and so on) go to the right station."),
        ("Tap the arrow (&#8594;) beside an item",
         "Each tap moves that item to the next step: New &#8594; Preparing &#8594; Ready &#8594; Served."),
        ("Call the waiter",
         "When you mark an item Ready, the waiter knows to collect it."),
    ])
    story.append(callout("TIP", "Reprinting a ticket", "If a kitchen printer runs out of paper, the waiter can tick \u201cKitchen order\u201d in the Print panel and print it again."))
    story.append(PageBreak())

    # ================= 6. Cashiers =================
    story += h1(6, "Cashiers — payments and the till")
    story.append(menu("Cashier Shifts, Restaurant POS, Bar POS, Held Orders, Customer Accounts"))
    story.append(h2("Starting your day: open your shift"))
    story.append(body(
        "As soon as you sign in, if you don't already have a shift running the system takes you straight to "
        "Cashier Shifts to open one — you don't need to find the page yourself."))
    story += steps([
        ("Tap Open shift", "Tap Open shift."),
        ("Count the starting cash (the \u201cfloat\u201d)", "Enter the amount of cash in your drawer. Your manager will tell you how much it should be."),
        ("Confirm", "The badge at the top turns green: Shift Open. Sales you handle are now counted against your shift."),
    ])
    story.append(callout("BE CAREFUL", "Open your shift first", "Bills and payments are recorded against your shift. If your shift is not open, you may be stopped when billing."))
    story.append(h2("Your shift at a glance"))
    story.append(body(
        "The Cashier Shifts page shows a live <b>My shift</b> panel while your shift is open: how much cash "
        "should be in the drawer right now, a breakdown of everything received so far by method — cash, "
        "M-Pesa, card, bank and so on — and a list of every product and service sold this shift with its "
        "quantity. Use it any time during the day to check your progress without waiting for the closing count."))
    story.append(h2("Held Orders — bills waiters have parked"))
    story.append(body(
        "Open <b>Held Orders</b> to see every order any waiter has put on hold or suspended, with the order "
        "number and the waiter's name, newest first. When a waiter hands you the money, find their order here "
        "(or search by order number), tap <b>Clear</b>, choose how they paid, and the bill is billed and "
        "closed in one step."))
    story.append(h2("Collecting a payment"))
    story += steps([
        ("Open the order or tab", "Find it on the floor plan, in Retrieve Orders, in Held Orders, or on the Bar POS screen."),
        ("Tap Collect payment", "Choose how the customer is paying: Cash, M-Pesa, Card or Credit / account (or any other method your hotel has set up)."),
        ("Enter the amount",
         "For cash, enter what applies to the bill. To split the payment — part cash, part M-Pesa — record the "
         "first method and amount, then open <b>Collect payment</b> again for the rest with a different "
         "method; the window shows everything already received so far, and the balance updates automatically."),
        ("Or send an M-Pesa prompt",
         "If M-Pesa is set up, tap <b>M-Pesa</b> instead, enter the customer's number and the amount, and their "
         "phone shows the payment prompt. The window waits and confirms automatically — no need to ask them to "
         "read out a code."),
        ("Print the receipt", "A receipt opens in a new tab. Give it to the customer. The order closes once it is fully paid."),
    ])
    story.append(callout("GOOD TO KNOW", "The system won't take more than the balance", "If an amount entered is more than what is left on the bill, it is refused with the correct balance shown — so a mistaken double-entry can't overcharge a customer's record."))
    story.append(h2("When a customer pays \u201con account\u201d (credit)"))
    story.append(body(
        "Some regular customers are allowed to pay later. When you choose a credit method, a Customer box "
        "appears. You must choose the customer — this is how the hotel knows who owes the money."))
    story.append(callout("BE CAREFUL", "Credit limits", "Every customer can have a limit. If the sale would take them over it, the system stops the sale and tells you. Only people who have been given the right may tick \u201cOverride credit limit\u201d. If you do not see that box, call your manager."))
    story.append(h2("Receiving money from a credit customer"))
    story += steps([
        ("Open Customer Accounts", "Find the customer and tap their name to see their statement — everything they bought and paid."),
        ("Tap Record payment", "Enter the amount received and how it was paid. Their balance goes down."),
        ("Share their statement", "The statement page has a Copy shareable statement link button. Send the link to the customer by WhatsApp, SMS or email. It opens without a password and stops working after 30 days."),
    ])
    story.append(h2("End of day: close your shift"))
    story += steps([
        ("Count the cash in your drawer", "Count carefully, twice if you can."),
        ("Go to Cashier Shifts and tap Close my shift", "Type in the amount you counted."),
        ("Read the result", "The system compares your count with what it expected: starting cash + cash sales \u2212 cash expenses. If there is a difference, it is flagged for your manager to review."),
    ])
    story.append(callout("TIP", "Small expenses from the till", "If you pay for something small from the till (for example, ice), record it under Expenses so your count still adds up."))
    story.append(PageBreak())

    # ================= 7. Bartenders =================
    story += h1(7, "Bartenders — the bar")
    story.append(menu("Bar POS"))
    story.append(callout("NEW", "Waiters can run the bar too", "A Waiter's role now includes everything below by default, so one person can serve both the floor and the bar. This chapter still applies to anyone working the bar screen, whichever role they have. A separate Bartender role is still available for a hotel that wants a dedicated bar-only person."))
    story.append(body("At the bar, customers are handled with tabs. A tab is like an open bill: you add drinks as they are ordered, and the customer pays when they finish."))
    story.append(h2("Working with a tab"))
    story += steps([
        ("Tap Open tab", "Enter the customer's name or a table number so you can find the tab again — or leave it blank for a walk-in."),
        ("Select the tab on the right", "Tabs are listed on the right. Tap the one you are serving."),
        ("Tap drinks to add them", "Choose a category (beer, spirits, soft drinks, cocktails\u2026) and tap each drink. Bottles, glasses and shots are separate items on the menu. If happy hour is on, discounted prices show automatically."),
        ("Bill the tab", "When the customer is ready, tap Bill. A bill opens in a new tab to print."),
        ("Collect payment", "Choose how they are paying, exactly as in Chapter 6, including M-Pesa and split payments. Credit customers must be chosen from the list."),
    ])
    story.append(h2("Free drinks (complimentary)"))
    story.append(body("To give a drink free of charge, tick <b>Next item is complimentary</b> at the top, then tap the drink. The drink is not charged, but the stock is still reduced and it is listed in the reports, so nothing is hidden. If you do not see this tick box your role is not allowed to give free drinks."))
    story.append(h2("Splitting a tab"))
    story.append(body("Just like at the restaurant, tap <b>Split bill</b> to divide a tab between several people, each paying and printing their own share."))
    story.append(callout("GOOD TO KNOW", "Closed tabs are locked", "Once a tab is closed or cancelled it cannot be changed. You can still print its receipt. If something is wrong, ask a manager."))
    story.append(PageBreak())

    # ================= 8. Storekeepers =================
    story += h1(8, "Storekeepers — stock and suppliers")
    story.append(menu("Stock, Stock Transfers, Stock Takes, Suppliers, Purchase Orders, Supplier Returns"))
    story.append(h2("How stock works"))
    story.append(body("Each product has a number showing how much is on hand. It goes down by itself when something is sold, and goes up when you receive goods. Where a dish has a recipe, selling one dish reduces each ingredient by the right amount — one chicken burger takes off one bun, one piece of chicken, and so on."))
    story.append(h2("Checking stock"))
    story.append(body("Open Stock to see every product and how much is in each store. Items at or below their re-order level are flagged so you know what to buy. The Dashboard also shows a Low Stock list."))
    story.append(h2("Ordering from a supplier"))
    story += steps([
        ("Purchase Orders \u2192 New purchase order", "Choose the supplier, add the products and quantities, and save."),
        ("Wait for approval", "A person with approval rights opens the order and taps Approve."),
        ("When the goods arrive, tap Receive goods", "Enter what was actually delivered for each line. Stock is updated straight away, and the amount owed to that supplier goes up."),
    ])
    story.append(h2("Sending goods back to a supplier"))
    story.append(body("If some goods are damaged or wrong, open the purchase order and tap Return goods. Enter how many to return and the reason. Stock goes down and the amount you owe the supplier goes down. You can never return more than was received."))
    story.append(h2("Paying a supplier"))
    story.append(body("Open Suppliers, tap the supplier's name to see their statement, then tap Record payment. The statement shows goods received, payments made and returns, with a running balance."))
    story.append(h2("Moving stock between stores"))
    story.append(body("Use Stock Transfers \u2192 New transfer: pick where it is coming from, where it is going, the product and the quantity. Both stores are updated."))
    story.append(h2("Correcting stock and recording wastage"))
    story.append(body("Use Stock \u2192 Adjust stock. Start typing the product name and pick it from the list, choose the type \u2014 Opening stock, Adjustment, Wastage, Breakage or Complimentary \u2014 enter the quantity and give a reason. Adjustments are recorded with your name."))
    story.append(h2("Counting stock (stock take)"))
    story += steps([
        ("Stock Takes \u2192 Start stock take", "Choose the store to count."),
        ("Count the shelves", "Type in the real count next to each product. The system shows what it expected."),
        ("Tap \u201cComplete stock take & reconcile\u201d", "Stock is corrected to your count, and every difference is recorded so it can be reviewed."),
    ])
    story.append(callout("TIP", "Recipes and dish costs", "The Recipes & Production page lists every dish with its ingredients, cost and profit. Roles allowed to see costs will see the cost and profit columns; others will not."))
    story.append(PageBreak())

    # ================= 9. Expenses =================
    story += h1(9, "Expenses and money owed")
    story.append(menu("Expenses, Customer Accounts, Suppliers"))
    story.append(h2("Recording an expense"))
    story += steps([
        ("Open Expenses and tap Record expense", "Fill in the category, amount, how it was paid, who was paid and a short description. Attach a photo of the receipt if you have one."),
        ("Wait for approval", "The expense shows as Pending. A manager or accountant taps Approve or Reject."),
        ("It counts once approved", "Only approved expenses are taken off profit in the reports."),
    ])
    story.append(h2("Who owes what"))
    story.append(grid(
        ["Question", "Where to look"],
        [
            ["Who owes the hotel money?", "Customer Accounts \u2014 every customer with their balance and statement."],
            ["Whom does the hotel owe?", "Suppliers \u2014 each supplier's balance and statement."],
            ["What is the total of both?", "Financial Reports \u2014 shown together with profit and cash movement."],
        ],
        [55 * mm, None],
    ))
    story.append(PageBreak())

    # ================= 10. Reports =================
    story += h1(10, "Reports and the Dashboard")
    story.append(menu("Dashboard and Insights"))
    story.append(h2("The Dashboard"))
    story.append(body("The Dashboard is the health check of the business. The coloured boxes at the top show the main figures for the period you chose. Below them are tabs with charts and lists. Each person only sees the boxes and tabs their role has been given, so two people can have different-looking Dashboards."))
    story.append(grid(
        ["Figure", "What it means"],
        [
            ["Total sales", "All money billed at the restaurant and bar (cancelled and merged-away orders are not counted twice)."],
            ["Gross profit", "Sales minus approved expenses for the period."],
            ["Transactions", "How many orders and tabs were made."],
            ["Levy collected", "Tourism or hotel levy charged on bills."],
            ["Stock value", "What the stock in your stores cost to buy."],
            ["Low stock items", "How many products are at or below their re-order level."],
            ["Expenses", "Approved expenses in the period."],
        ],
        [34 * mm, None],
    ))
    story.append(h2("The reports"))
    story.append(grid(
        ["Report", "Answers the question\u2026"],
        [
            ["Sales Reports", "How much did we sell, when, and who sold it? Includes discounts, cancelled items and free items."],
            ["Restaurant Reports", "Which meals sell best? What did food cost and earn? What was cancelled and why?"],
            ["Bar Reports", "Which drinks sell best? How much stock was used or wasted? Any differences at the last count?"],
            ["Inventory Reports", "What is the stock worth? What is low? What was wasted this month?"],
            ["Financial Reports (P&L)", "Are we making a profit? Restaurant and bar profit, expenses, cash movement, and what is owed."],
            ["VAT Report", "How much of our sales carries VAT, is zero-rated or exempt, and how much VAT was collected?"],
            ["Daily Levy Report", "How much levy is due today, department by department?"],
            ["Audit Trail", "Who did what, and when? (Managers and administrators.)"],
        ],
        [34 * mm, None],
    ))
    story.append(callout("TIP", "Every report can be downloaded", "Use the Export button at the top right of any report or the Dashboard, and choose PDF, Excel or CSV. The file has the period and your name on it."))
    story.append(h2("Tax invoices for KRA"))
    story.append(body("Whenever an order or tab is closed, the system prepares a tax invoice automatically. Open eTIMS to see them all with their status: Submitted (accepted), Pending (waiting to be sent) or Failed (did not go through). A manager can tap Retry on a failed one, and can issue a Credit note if a bill has to be corrected after closing."))
    story.append(PageBreak())

    # ================= 11. Managers =================
    story += h1(11, "Managers — approvals and oversight")
    story.append(body("A manager's day usually has four short jobs."))
    story += steps([
        ("Morning: check the Dashboard", "Yesterday's sales, low stock, and anything that needs attention."),
        ("Approve what is waiting", "Expenses (Expenses page), purchase orders (Purchase Orders page), and cash differences (Cashier Shifts page)."),
        ("During service: help staff", "Void a locked item, give approved discounts, override a credit limit, merge bills across different waiters, or force-close a stuck order \u2014 only where your role allows."),
        ("End of day: review shifts", "Open Cashier Shifts, look at each closed shift and tap Approve once you are satisfied with any difference."),
    ])
    story.append(h2("Force-closing an order"))
    story.append(body("Occasionally an order cannot be paid in full (for example, a write-off or a compliment from management). On the order page a manager can use Force close. The system asks you to confirm and records who did it."))
    story.append(callout("BE CAREFUL", "Use sparingly", "Force-closing, discounts, free items and cancelled items are the most common ways money is lost. They are recorded in the Audit Trail \u2014 and reviewed there."))
    story.append(h2("Taxes and levies"))
    story.append(body("Open Taxes & Levies to see the VAT rates and levies. If your role allows it you can Edit a rate, Add a new one, or Deactivate one that no longer applies. A new rate applies to the next sale \u2014 nothing needs to be installed."))
    story.append(h2("The Audit Trail"))
    story.append(body("Open Audit Trail and filter by staff member, type of action or dates. Each line shows who did something, what they did and when \u2014 including PIN changes, holding/resuming/merging/cancelling orders, and happy hour being switched on or off. Use Export to keep a copy."))
    story.append(PageBreak())

    # ================= 12. Administrators =================
    story += h1(12, "Administrators — staff, roles and settings")
    story.append(menu("System group"))
    story.append(h2("Adding a staff member"))
    story += steps([
        ("Open Users", "Tap Add user."),
        ("Choose their role first",
         "For a PIN-only role (Waiter, Cashier, Bartender by default) the form switches to just asking for a "
         "<b>PIN</b> \u2014 no email or password needed. For any other role, fill in name, email, phone, staff "
         "code and a starting password as usual."),
        ("Set their work location (optional)",
         "Leave it as <b>All areas</b> unless you want to restrict this person \u2014 for example a waiter set to "
         "<b>Bar only</b> can no longer open Restaurant POS even though the Waiter role includes it."),
        ("Give them their login", "Tell them their PIN, or their email and password, and ask them to change it after signing in the first time."),
    ])
    story.append(body("Other things you can do on the Users page: edit details, change a role, change someone's work location, <b>Reset PIN</b> or Reset password, and Deactivate someone who has left. A deactivated person can no longer sign in, but their history is kept. You cannot deactivate yourself."))
    story.append(h2("Deciding what each role can see and do"))
    story.append(body("Open Roles & Permissions. This is where you control the whole system. On the left is the list of roles; tap one to work on it. On the right you tick or untick what that role is allowed to do, then tap Save access."))
    story.append(grid(
        ["Tab", "What you are deciding"],
        [
            ["Menus & Pages", "Which screens the role can open. An unticked screen disappears from the menu and cannot be reached, even by typing its address."],
            ["Actions", "What the role can do on those screens: give discounts, cancel items, hold or merge bills, cash out, send M-Pesa prompts, give free drinks, split bills, approve expenses, adjust stock, receive goods, change taxes, add staff and so on."],
            ["Dashboard Widgets", "Which boxes, charts and lists appear on that role's Dashboard \u2014 for example, a waiter can see \u201cSales by waiter\u201d but not \u201cGross profit\u201d."],
            ["Other Widgets & Data", "Downloading reports, seeing cost prices and profit margins, seeing customer and supplier balances, the search box and the User Guide link."],
        ],
        [34 * mm, None],
    ))
    story.append(body("Tips for using the screen:"))
    story.append(bullets([
        "Select all / Clear all at the top of each group ticks or unticks the whole group at once.",
        "If you tick an action, its screen is ticked for you (you cannot press a button on a screen you cannot open).",
        "If you untick a screen, the actions on it are unticked too.",
        "Reset to standard access puts a built-in role back the way it came with the system.",
        "New role lets you create your own \u2014 for example \u201cNight Supervisor\u201d. You can start from nothing or copy an existing role and then adjust it.",
        "A role that still has staff in it cannot be deleted. Move the people to another role first.",
    ]))
    story.append(callout("GOOD TO KNOW", "The Administrator role is locked", "Administrators always have full access. This cannot be changed, so nobody can lock themselves out of the system."))
    story.append(callout("TIP", "Changes are immediate", "As soon as you save, the change applies. A person who is already signed in will see the difference the next time they open a page."))
    story.append(PageBreak())

    story.append(h2("Other settings"))
    story.append(grid(
        ["Screen", "What it is for"],
        [
            ["General Settings", "The hotel name, tax PIN, currency symbol, M-Pesa till/paybill number and the tax-invoice connection. What you save here appears on receipts and invoices."],
            ["Appearance", "Upload the business logo (shown on sign-in, the sidebar and receipts) and choose the colour theme \u2014 a ready-made preset or your own primary/accent colours. Applies immediately, everywhere, with no restart."],
            ["Happy hour", "Turn happy hour on or off, set the discount percentage, and optionally limit it to certain days and times."],
            ["M-Pesa", "Shows whether M-Pesa (STK push) is connected. The actual keys are set up by whoever manages the server, not typed in here, so they are never stored in the ordinary settings."],
            ["Payment Methods", "The ways customers can pay \u2014 cash, M-Pesa, card, credit and any others you add."],
            ["Printers", "Set up receipt, kitchen and bar printers and press Test to check each one prints."],
            ["Manage tables & floors", "Add, rename or remove floors, areas and tables. A floor or table that is in use cannot be removed."],
            ["Taxes & Levies", "VAT rates and levies; see Chapter 11."],
        ],
        [32 * mm, None],
    ))
    story.append(callout("TIP", "Try the Delight theme", "Settings \u2192 Appearance \u2192 Colour theme includes a \u201cDelight\u201d preset built from your own logo colours, alongside the original green \u201cGarden\u201d look and a blue \u201cOcean\u201d option \u2014 or pick your own primary and accent colours with the colour pickers."))
    story.append(PageBreak())

    # ================= 13. Role table =================
    story += h1(13, "What each standard role can do")
    story.append(body("These are the starting settings that come with the system. Your administrator may have changed them."))
    headers = ["", "Waiter", "Bartender", "Cashier", "Chef", "Storekeeper", "Accountant", "Manager", "Admin"]
    rows = [
        ["Sign in with PIN (not email)", 1, 1, 1, 0, 0, 0, 0, 0],
        ["Restaurant POS (tables & orders)", 1, 0, 1, 0, 0, 0, 1, 1],
        ["Bar POS", 1, 1, 1, 0, 0, 0, 1, 1],
        ["Kitchen Display", 0, 0, 0, 1, 0, 0, 1, 1],
        ["Hold / suspend a bill", 1, 0, 1, 0, 0, 0, 1, 1],
        ["Cash out (collect payment)", 0, 1, 1, 0, 0, 0, 1, 1],
        ["Held Orders queue", 0, 0, 1, 0, 0, 0, 1, 1],
        ["Send M-Pesa prompts", 0, 1, 1, 0, 0, 0, 1, 1],
        ["Merge own bills", 1, 0, 1, 0, 0, 0, 1, 1],
        ["Merge anyone's bills", 0, 0, 0, 0, 0, 0, 1, 1],
        ["Void a locked item", 0, 0, 0, 0, 0, 0, 1, 1],
        ["Cashier shifts", 0, 0, 1, 0, 0, 1, 1, 1],
        ["Stock & purchasing (manage)", 0, 0, 0, 0, 1, 0, 1, 1],
        ["Expenses", 0, 0, 1, 0, 0, 1, 1, 1],
        ["Customer accounts", 0, 0, 1, 0, 0, 1, 1, 1],
        ["Reports", 0, 0, 0, 0, 1, 1, 1, 1],
        ["Financial (profit) reports", 0, 0, 0, 0, 0, 1, 1, 1],
        ["Give discounts", 0, 0, 0, 0, 0, 0, 1, 1],
        ["Approve expenses & orders", 0, 0, 0, 0, 0, 1, 1, 1],
        ["Change taxes", 0, 0, 0, 0, 0, 0, 1, 1],
        ["Appearance & branding", 0, 0, 0, 0, 0, 0, 0, 1],
        ["Staff, roles & settings", 0, 0, 0, 0, 0, 0, 0, 1],
    ]
    col_widths = [36 * mm] + [17.2 * mm] * 8
    story.append(matrix(headers, rows, col_widths))
    story.append(spacer(6))
    story.append(P(
        "&#9679; means the role can use it by default. \u201cReports\u201d for a Storekeeper means Inventory Reports "
        "only; for a Cashier or Waiter it is not shown. The Chef can also view recipes and stock levels but not "
        "change them. Since this edition, a Waiter's default access already includes Bar POS \u2014 a separate "
        "Bartender is optional. This table shows only the most common items and a person's <b>work location</b> "
        "can narrow it further; the full list is on the Roles & Permissions screen.", "Small"))
    story.append(PageBreak())

    # ================= 14. Q&A =================
    story += h1(14, "Questions and answers")
    qa = [
        ("I cannot see a menu item or button that this guide mentions.",
         "Your role has not been given it, or your work location doesn't cover it. This is normal. If you need it for your job, ask your manager or administrator."),
        ("The screen says \u201cYou do not have permission\u201d.",
         "You tried to open a page your role, or your work location, does not include. Go back and ask your manager if you should have access."),
        ("I can only sign in with a PIN, not email and password.",
         "That's normal for Waiters, Cashiers and Bartenders \u2014 it's faster on a shared till. If your role should sign in with email instead, ask your administrator to change it."),
        ("I forgot my PIN or password.",
         "Ask your administrator to reset it from the Users page. You will be given a new one, which you can then change yourself from My Profile."),
        ("My PIN stopped working after a few tries.",
         "Wrong PINs are limited for a short time to stop someone guessing it. Wait a little and try again carefully, or ask an administrator to reset it."),
        ("I can't find the button to collect payment.",
         "Some roles \u2014 most Waiters \u2014 can hold a bill but not cash it out. Tap Hold / suspend and hand the money to the cashier, who will clear it against your name in Held Orders."),
        ("The bill will not print.",
         "Check the printer is on and has paper. Try Print selected again. Ask an administrator to press Test on the printer under Settings \u2192 Printers."),
        ("I gave the wrong item.",
         "If it has not been sent to the kitchen/bar or printed yet, tap Remove next to it. If it has already been sent or printed, ask a manager to void it."),
        ("I want to cancel the whole order, not just one item.",
         "While nothing on it has been sent or printed, use Cancel whole order on the order screen. Once something is locked, void items one at a time instead."),
        ("How do I sell to someone who isn't sitting at a table?",
         "On the floor plan, tap Quick sale for a simple walk-in sale, or Takeaway / Delivery if it's going out."),
        ("A customer's payment was recorded wrongly.",
         "Tell a manager straight away. Do not try to fix it by making another order. Managers can use the Audit Trail to trace what happened."),
        ("The M-Pesa prompt didn't arrive, or the customer says nothing happened.",
         "Check the number was typed correctly and ask them to check their phone again \u2014 it can take a few seconds. The payment window keeps checking by itself; it will tell you if it times out."),
        ("The stock number looks wrong.",
         "Tell the storekeeper. They can check the movements and correct it with a stock adjustment or a stock take."),
        ("I cannot bill because there is no shift.",
         "Open your shift first from Cashier Shifts \u2014 or simply sign out and back in, which now takes cashiers there automatically."),
        ("A tax invoice shows \u201cFailed\u201d.",
         "Usually the internet was down at the moment the bill closed. A manager can tap Retry on the eTIMS page."),
        ("I need the figures in Excel.",
         "Tap Export at the top of the report and choose Excel."),
    ]
    for q, a in qa:
        story.append(Paragraph(f"<b>{q}</b>", ParagraphStyle("qa_q", fontName="DejaVuSans-Bold", fontSize=9.7, leading=13, textColor=MAROON, spaceBefore=8, spaceAfter=2)))
        story.append(Paragraph(a, styles["Body"]))
    story.append(PageBreak())

    # ================= Glossary =================
    story.append(Paragraph("Words used in this guide", styles["H1"]))
    story.append(HRFlowable(width="100%", thickness=1.4, color=BORDER, spaceAfter=10))
    words = [
        ["PIN", "A short 4\u2013 or 5\u2013digit code some roles use to sign in instead of an email and password."],
        ["Work location", "A limit put on one person \u2014 Restaurant, Bar or Kitchen only \u2014 on top of what their role can already do."],
        ["Hold / suspend", "Parking a bill so the cashier can collect payment for it later, instead of the waiter cashing it out."],
        ["Merge (bills)", "Combining two or more open bills into one; the items move across and the old bill is kept, marked Merged."],
        ["Destination", "Whether an item needs to go to the Kitchen, the Bar, or neither (a direct-sale item)."],
        ["Happy hour", "A period, or an always-on switch, during which every product and service is discounted by a set percentage."],
        ["Quick sale", "A sale rung up with no table attached, for a walk-in customer."],
        ["Tab (bar)", "An open bill for a customer at the bar, closed when they pay."],
        ["Float", "The starting cash put in the till at the beginning of a shift."],
        ["Shift", "The period one cashier is responsible for a till, from opening to closing."],
        ["Void", "To cancel an item or order that should not be charged."],
        ["Complimentary", "Given free of charge, but still recorded."],
        ["Split bill", "Dividing one bill so several people can pay separately."],
        ["Till / Paybill number", "The M-Pesa number customers use to pay the hotel directly; printed on receipts once set in Settings."],
        ["Theme", "The set of colours the system is shown in \u2014 changeable in Settings \u2192 Appearance without needing any technical work."],
        ["Re-order level", "The stock amount at which it is time to buy more."],
        ["Stock take", "A full count of what is really on the shelves."],
        ["Purchase order", "A written order sent to a supplier."],
        ["Levy", "A charge added to bills that is passed on to the authorities (for example tourism levy)."],
        ["VAT", "Value Added Tax \u2014 the tax added to most sales."],
        ["Tax invoice", "The official invoice recorded for each sale for KRA."],
        ["Role", "A named set of permissions, such as Waiter or Manager, given to staff."],
        ["Audit trail", "The record of who did what and when."],
    ]
    story.append(grid(["Word", "Plain meaning"], words, [40 * mm, None]))

    doc.multiBuild(story)
    print(f"Wrote {out_path}")


if __name__ == "__main__":
    build()
