Company Handbook
The AkbAI compliance pillar of the Helpdesk module: HR uploads a born-digital PDF handbook (or starts from a built-in DOLE-aware template), AkbAI extracts and splits it into draft sections, HR reviews and publishes, and employees read a searchable, employees-only handbook.
What the Company Handbook is
The Company Handbook turns a finished PDF handbook into a living, searchable, employees-only reference inside ERPat. Instead of emailing a 40-page PDF that nobody reads, HR uploads the file once and AkbAI does the tedious work: it reads the PDF, finds the sections, rewrites each one into clean HTML, classifies it into a compliance category, and stages every section as a draft for HR to review. HR approves and publishes, and from that moment every authenticated employee with the handbook permit can browse the handbook by category, open an article, and search it.
Everything here is confidential company content. The reader (Handbook controller) extends App_Controller and is gated by module_helpdesk plus the handbook permission — it is never exposed to guests or the public web. The admin side (Handbook_admin controller) is where uploads, review and publishing happen.
modules/Helpdesk/: controllers Handbook and Handbook_admin, three models (Handbook_import_model, Handbook_category_model, Handbook_article_model), four libraries (extractor, section detector, AI sectioner, HTML sanitizer), one cron job, and the handbook_helper taxonomy/template. Data is per-tenant across three tables: handbook_imports, handbook_categories, and handbook_articles.Two ways to start a handbook
Upload a PDF
Upload your existing, born-digital handbook PDF. AkbAI extracts the text, detects sections, and drafts each one in the background. Source type upload.
Start from DOLE template
Seed a starter handbook from the built-in, DOLE-aware 14-category template. No AI cost, no PDF — pre-written PH-compliance drafts land straight in review. Source type template.
The AkbAI processing pipeline
When you upload a PDF, the heavy work runs in the background via the cron job helpdesk_process_handbook_imports (ProcessHandbookImportJob) — the upload request returns immediately with "Uploaded. AkbAI is processing your handbook — check back shortly." Here is the full path from raw PDF to a published article.
1. Upload & queue
HR uploads a PDF via the modal. A handbook_imports row is created first (so files land under a stable per-import path), the file is validated as PDF both client-side and server-side, and the import is set to status pending. The reader/admin UI shows it as Queued.
2. Extract text
Pdf_handbook_extractor (smalot/pdfparser, pure PHP) reads the PDF page by page. It records per-page character stats, collapses whitespace, strips recurring running headers/footers and page numbers, and flags the document as scanned if more than half the pages fall below SCANNED_CHARS_PER_PAGE (60 non-whitespace chars). Status becomes extracting.
3. Detect sections
Handbook_section_detector splits the plain text into sections using heading heuristics only (no font info): numbering depth (1., 1.1, I., A.), SECTION/ARTICLE/CHAPTER/PART markers, short ALL-CAPS lines, and isolated Title-Case lines. Each heading owns the body up to the next heading. Content before the first heading becomes an "Introduction" section. Status becomes processing.
4. AI map (per section)
For each detected section, Handbook_ai_sectioner makes one AkbAI call and gets back strict JSON {title, html, category, excerpt}. The section text is sent as data only (prompt-injection containment — handbook text is treated as untrusted), the model has no tools, the category is constrained to the fixed 14-item enum, and parsing has bounded retry/backoff. If AkbAI is not configured, this step is skipped and the section is drafted from the cleaned source text instead.
5. Sanitize
Handbook_html_sanitizer runs the returned HTML through HTMLPurifier with a strict allow-list (headings, paragraphs, lists, simple tables, emphasis, blockquotes, safe links). Scripts, styles, event handlers, classes and inline styles are stripped. This always runs on the way into storage, so the employees-only reader can echo the body safely.
6. Save as draft
Each sanitized section is written to handbook_articles with status draft, its category, an excerpt, a sort order, and a section_hash. When all sections are drafted, the import is set to needs_review and its section_count is recorded.
7. HR review
HR opens the review screen, reads each draft, edits titles/categories/excerpts/body as needed, and deletes any junk sections. Nothing is visible to employees yet.
8. Publish
HR clicks "Approve all & Publish". Every draft article of the import flips to published with a published_at timestamp, and the import is marked published. Employees can now read it.
*/5 * * * *). After an upload, refresh the review screen — it polls the import status and shows drafts as soon as processing finishes.Import status legend
Every upload (and template seed) is an handbook_imports row whose status drives the whole pipeline. These are the states you will see in the admin imports table and on the review screen (labels come from the handbook_status_* lang keys).
| Status value | Badge | What it means |
|---|---|---|
pending | Queued | Freshly uploaded and waiting for the next cron sweep. Nothing has been processed yet. |
extracting | Extracting | The cron picked it up and is pulling text out of the PDF (Pdf_handbook_extractor). |
processing | Processing | Text extracted; sections detected; AkbAI is mapping and drafting each section. |
needs_review | Needs review | Drafts are staged and waiting for HR. This is the normal resting state after successful processing (and the state a DOLE template starts in). |
published | Published | HR approved and published; the articles are live to employees. |
failed | Failed | Processing could not complete (unreadable PDF, missing file, or too many attempts). The error_message explains why. Re-upload or add sections manually. |
needs_review with an explicit reason. In both cases HR adds the sections by hand.Article status & idempotency
Inside an import, each section is an handbook_articles row with its own status:
| Article status | Meaning |
|---|---|
draft | Created by the pipeline or the template seed. Editable by HR, invisible to employees. Editing needs handbook_update. |
published | Live on the employees-only reader. Editing a published article needs the stronger handbook_publish permit (see RBAC). |
section_hash derived from stable content (import id + section path + prompt version + a whitespace-normalized title+body signature). Before creating a draft, the cron checks find_by_section_hash() — if the section was already drafted, it is skipped. So a re-picked or resumed import never produces duplicate drafts, and the hash is deliberately matched to the unique index across all rows (soft-deleted included) so it stays correct even after HR deletes a bad draft.The background cron job
The pipeline is driven entirely by ProcessHandbookImportJob. It is registered as a discoverable module cron job and is per-tenant (handbook data is tenant-scoped), so it only runs for tenants where module_helpdesk is enabled.
helpdesk_process_handbook_imports*/5 * * * * (every 5 min)Retry bounding & stale recovery
The job is built to be safe against killed runs and pathological PDFs:
- Attempt cap. Each pickup increments the
attemptscolumn. AfterMAX_ATTEMPTS(3) the import is set tofailedwith the "processing failed repeatedly" message — a PDF that keeps timing out or OOM-ing is given up on rather than looping forever. - Stale in-flight re-pick.
get_pending()always picks uppendingrows, but only re-picks anextracting/processingrow once it is older thanSTALE_SECONDS(900s) — i.e. a previous run was killed mid-flight and never reached a terminal state. A legitimately live run is never re-picked (900s > the 600s timeout). - Per-section isolation. A throw on one section (sanitizer error, malformed UTF-8, oversized body) is logged and skipped; the rest of the import still drafts.
Running & inspecting the job
php erpat cron:list # see all jobs incl. the handbook job
php erpat cron:run helpdesk_process_handbook_imports # run it now for this tenant
php erpat cron:dry-run helpdesk_process_handbook_imports
php erpat cron:tick helpdesk_process_handbook_imports # run if due per its schedule
Requirements & edge cases
AKBAI_HOST / AKBAI_KEY). Without it, uploads still work: the PDF is extracted, sections are detected, and each section is drafted from the cleaned source text (wrapped in safe paragraphs) for HR to edit manually. With AkbAI on, each section is rewritten into clean, semantic HTML and auto-classified into a category. The review screen and the cron log both indicate whether AI was on or off.needs_review with the message "This looks like a scanned/image-only PDF. OCR is not available in this version — please add the sections manually." Upload a text PDF, or add the sections by hand..pdf files. The dropzone validates each file client-side via validate_file, and save_upload re-checks the moved file server-side (client checks are bypassable) — a non-PDF deletes the import and returns "Only PDF files are accepted for the handbook."tempnam) before extraction and unlinked immediately after. For local storage the path is built under the fixed app root and hardened against traversal (basename + realpath containment). This is all handled by the job — no operator action needed.Handbook_html_sanitizer purifies every body on the way into storage — through all write paths (cron, template seed, manual edit), enforced centrally in Handbook_article_model::save(). If HTMLPurifier is unavailable, a conservative strip_tags allow-list fallback runs and an error is logged so operators notice the degraded (link-stripped) path.The DOLE-aware starter template
Don't have a handbook yet? Click Start from DOLE Template to seed a ready-to-review starter. It creates a template import at status needs_review and populates deterministic, pre-written Philippine-compliance draft articles — no AI, no PDF, no token cost. Every article carries a "Review & customize this section for your company before publishing" note, and the set is anchored by a legal disclaimer.
The 14-category taxonomy
The same 14 categories serve as (a) the seeded per-tenant taxonomy and (b) the fixed enum AkbAI classifies uploaded sections into. They are seeded on first use (handbook_seed_categories()) and are compliance-aligned to the PH context.
| # | Category | Covers |
|---|---|---|
| 1 | About & Welcome | Company overview, mission, values, handbook purpose, and the legal disclaimer. |
| 2 | Employment & Classification | Employment status, probation, regularization, and security of tenure. |
| 3 | Recruitment & Onboarding | Hiring, pre-employment requirements, and orientation. |
| 4 | Work Hours & Attendance | Schedules, overtime, night differential, holidays, and timekeeping. |
| 5 | Leaves & Time Off | Statutory and company leaves and how to file them. |
| 6 | Compensation & Payroll | Pay periods, payslips, deductions, and 13th-month pay. |
| 7 | Government-Mandated Benefits | SSS, PhilHealth, Pag-IBIG, and BIR withholding. |
| 8 | Performance Management | Goal setting, appraisals, and promotions. |
| 9 | Code of Conduct & Discipline | Standards of conduct, offenses, and the twin-notice due-process rule. |
| 10 | Health, Safety & Mental Health | OSH, drug-free workplace, and mental-health programs. |
| 11 | Data Privacy & Confidentiality | Handling of personal data and confidential information. |
| 12 | Anti-Harassment & Equal Opportunity | Safe Spaces, anti-sexual-harassment, and non-discrimination. |
| 13 | IT & Acceptable Use | Use of company devices, accounts, and systems. |
| 14 | Separation & Offboarding | Resignation, final pay, clearance, and Certificate of Employment. |
Admin SOPs (HR)
- Open the Handbook reader (
/handbook) and click the Edit button (visible withhandbook_createorhandbook_update) to reach the admin screen at/admin/handbook. - Click Upload Handbook PDF. In the modal, enter a handbook title (e.g. "2026 Employee Handbook") and choose your PDF (born-digital, not a scan).
- Click Upload & Process. You'll see "Uploaded. AkbAI is processing your handbook — check back shortly." The import appears in the table as Queued.
- Wait for the background cron (runs every 5 minutes). The status will move through
extracting→processing→ Needs review. - Click Review on the row to open the drafts. Proceed to the review SOP below.
- On the admin screen (
/admin/handbook), click Start from DOLE Template and confirm the prompt. - A "DOLE Template Handbook" import is created at
needs_reviewwith pre-written draft articles, and you're taken straight to its review screen. - Edit each draft to match your company (the template articles contain bracketed placeholders like "[Add your company mission…]" and "verify the current issuance" notes).
- Have the content reviewed by qualified Philippine labor counsel before publishing (per the built-in Legal Disclaimer).
- Publish when ready (see below).
- On the review screen (
/admin/handbook/review/{id}), each section shows its title, category label, status badge, and excerpt. - Click Edit on a draft (needs
handbook_update) to open the article modal. Adjust the title, pick the correct category, refine the excerpt, and edit the body in the rich-text editor. - Save. The body is re-sanitized on save; the page reloads so you see the change.
- Delete any junk sections the detector picked up (needs
handbook_delete). Add missing sections manually where extraction fell short (e.g. scanned pages). - If the banner shows a warning (scanned PDF, no sections found, processing error), read the message and act accordingly — add sections by hand or re-upload.
- On the review screen, confirm every draft reads correctly and is in the right category.
- Click Approve all & Publish (visible only with
handbook_publish, and only while the import isn't already published) and confirm. - Every
draftarticle of that import flips topublishedwith a timestamp; the import is marked Published. You'll see "Published N article(s) to employees." - Employees with the
handbookpermit can now read the handbook at/handbook.
handbook_update alone governs drafts. Editing an already-published, employee-visible article requires handbook_publish — if you lack it, the save is refused. This prevents an update-only role from silently changing live policy.The employees-only reader
Once published, employees read the handbook through the Handbook controller. It's a clean, card-and-sidebar reader with category browsing, article view, and search — all gated by module_helpdesk + the handbook permission.
- Landing (
/handbook). A hero, a category sidebar with per-category published-article counts, and a card grid of categories. Empty until the handbook is published. - Category (
/handbook/category/{id}). Lists the published articles in one active category, with excerpts. - Article (
/handbook/view/{id}). Renders a single published article's sanitized body; the view counter (total_views) increments on each read. Only published articles are reachable — an unpublished or bad id returns 404. - Search (
/handbook/search). A typeahead POST endpoint that matches published article titles and excerpts and returns id/label pairs.
- The reader shows an Edit button in the hero for users with
handbook_createorhandbook_update, linking to/admin/handbook. - The admin screen lists all imports (any status) in a DataTable with title, source, section count, status, date, and a Review action.
- The admin controller is reached via that Edit button — the handbook is intentionally not a sidebar menu item.
Routes reference
All routes are module-owned (declared in modules/Helpdesk/config/routes.php). The reader lives under /handbook; the HR admin under /admin/handbook.
| Route | Controller / method | Purpose | Audience |
|---|---|---|---|
handbook | Handbook/index | Reader landing — categories & cards. | Employee |
handbook/category/(:num) | Handbook/category/$1 | Published articles in one category. | Employee |
handbook/view/(:num) | Handbook/view/$1 | Read one published article (increments views). | Employee |
handbook/search | Handbook/search | Typeahead search (published title/excerpt). | Employee |
admin/handbook | Handbook_admin/index | Admin — list of imports. | HR |
admin/handbook/imports-list-data | Handbook_admin/imports_list_data | DataTable JSON for the imports table. | HR |
admin/handbook/upload_form | Handbook_admin/upload_form | Upload modal (needs handbook_create). | HR |
admin/handbook/upload_file | Handbook_admin/upload_file | Dropzone temp upload endpoint. | HR |
admin/handbook/validate_file | Handbook_admin/validate_file | Per-file PDF validation. | HR |
admin/handbook/save_upload | Handbook_admin/save_upload | Create the import & queue processing. | HR |
admin/handbook/start_template | Handbook_admin/start_template | Seed the DOLE template handbook. | HR |
admin/handbook/review/(:num) | Handbook_admin/review/$1 | Review & publish drafts of an import. | HR |
admin/handbook/article_form/(:num) | Handbook_admin/article_form/$1 | Edit-article modal (needs handbook_update). | HR |
admin/handbook/save_article | Handbook_admin/save_article | Save an edited article. | HR |
admin/handbook/delete_article | Handbook_admin/delete_article | Soft-delete an article (needs handbook_delete). | HR |
admin/handbook/publish/(:num) | Handbook_admin/publish/$1 | Approve all & publish (needs handbook_publish). | HR |
admin/handbook/status/(:num) | Handbook_admin/status/$1 | Poll a single import's status (progress UI). | HR |
Permissions & RBAC
Access to the whole pillar is module toggle enabled AND the user's permission grants the pillar permit: the master module_helpdesk setting must be on, and the user needs the relevant handbook* permit. These permission keys are module-owned (declared in modules/Helpdesk/config/permissions.php) and appear in the Roles UI under the Help Center: Handbook group when module_helpdesk is enabled.
| Permission key | Label | Grants |
|---|---|---|
handbook | Handbook (read) | Read the published, employees-only handbook (reader landing, category, article, search). Gate on the Handbook controller. |
handbook_create | Upload / Import Handbook | Upload a PDF and seed the DOLE template. Required for the upload modal and save_upload/start_template. |
handbook_update | Edit Handbook Drafts | Open the review screen's edit modal and save draft article changes. Governs drafts only. |
handbook_delete | Delete Handbook Articles | Soft-delete an article from an import. |
handbook_publish | Publish Handbook to Employees | Approve all & publish an import's drafts, and edit an already-published (live) article. |
/admin/handbook requires module_helpdesk plus any of handbook, handbook_create, or handbook_update. Individual admin actions then re-check their specific permit (upload → handbook_create, edit → handbook_update, delete → handbook_delete, publish → handbook_publish). See the Permissions Reference for the full Helpdesk permission surface.Who uses what
| Persona | Typical permits | Does |
|---|---|---|
| HR Admin | handbook, handbook_create, handbook_update, handbook_delete, handbook_publish | Uploads PDFs, seeds templates, reviews and edits drafts, publishes to employees, and maintains live articles. |
| Employee | handbook | Reads the published handbook, browses by category, opens articles, and searches. No editing. |
Terminology
One ingestion run — an uploaded PDF or a seeded template. Its status drives the pipeline; it owns a set of articles.
One handbook section — a draft or published unit of content with a title, body, category, excerpt, and sort order.
One of the 14 compliance-aligned buckets; doubles as the AI classification enum and the reader's navigation.
Heuristic splitting of extracted text into sections by heading patterns — no font info, dependency-free.
The per-section AkbAI call that returns strict JSON {title, html, category, excerpt}; the section text is data-only.
A content-derived idempotency key so re-runs skip already-drafted sections and never duplicate them.
A PDF with real embedded text (exported, not scanned). The only kind the extractor can read — no OCR in v1.
Troubleshooting & FAQ
Processing runs on the cron job, which sweeps every 5 minutes. Give it a few minutes and refresh. If it never moves, confirm the cron runtime is ticking and that module_helpdesk is enabled for this tenant (the job returns early otherwise). You can force a run with php erpat cron:run helpdesk_process_handbook_imports.
Open the review screen; the warning banner shows the error_message. Common cases: "We couldn't read this PDF" (corrupt or password-protected — try another file), "The uploaded PDF could not be located" (re-upload), or "Processing failed repeatedly and was stopped" (hit the 3-attempt cap — re-upload or add sections manually).
The PDF is likely scanned/image-only or blank. There is no OCR in v1 — the import lands in needs_review with an explanatory message. Add the sections manually, or re-export the source document as a born-digital (text) PDF and re-upload.
AkbAI is probably not configured (AKBAI_HOST / AKBAI_KEY). The pipeline still extracts, detects, and drafts each section from the cleaned source text so you can edit it manually — it just doesn't auto-rewrite or auto-classify. Configure AkbAI and re-upload for the full AI experience.
Editing a published article requires handbook_publish, not just handbook_update. Ask an admin to grant the publish permit, or have someone with it make the change.
Confirm the import status shows Published (publishing flips the import too), that the employees have the handbook permit, and that module_helpdesk is enabled. Only published articles appear in the reader; drafts never do.
Delete the junk draft on the review screen (needs handbook_delete). Because section_hash is matched against all rows including soft-deleted ones, deleting a bad draft won't cause a re-run to re-create it as a duplicate.