Home / Documentation / Product features
Documentation · Product featuresVOXOPlan — Detailed Product Features
The complete per-module reference: every entity, field, flow and cross-module impact across VOXOPlan's twelve modules, the platform core and the AI workforce.
The complete per-module feature compendium — every module's entities with their real fields (extracted from the server input validators on 2026-10-03), lifecycle flows, cross-impacts, AI action tokens and routes. Companions:PRODUCT.md(positioning),VOXOPLAN_WEBSITE_CONTENT_AND_POSITIONING.md(marketing voice),../deliverables/user-guide/VOXOPlan_User_Guide.md(click-level steps),FEATURES.md(engineering register). §Boundaries at the end lists verified limitations so nobody oversells.
Notation: (req)/(opt)/def X from the validators; enum(a|b); every admin write also takes organizationId. Module files use makeModuleGuards("<module>") (RLS + license). Channel/source vocabulary everywhere: chat|call|portal|admin|web|whatsapp|qr|pos|ota.
0. Platform-wide conventions
- Shared customers: legacy-module creates upsert the shared
customersrow +customer_contacts
+ a module profile (hs_|cr_|as_|rst_|trv_customer_profiles, hotel_guest_profiles).
- Money is server-computed (tax from
organization_settings); server rates (vehicle/room/menu/
package) override client hints. Bookings/orders/invoices/payments accept an idempotencyKey (retry ⇒ {duplicate:true}).
- AI gate (identical in all 12 modules): `authorize<Mod>AiAction{action, channel def chat,
aiEmployeeId?, conversationId?, parameters} → non-self-service tokens rejected on portal|web|qr|whatsapp → AI-employee permission (capability, level, requires_approval) → org AI policy → ai_action_requests row (pending approval or running) → complete<Mod>AiAction{requestId, ok, summary?, entityId?, error?} + audit (actor ai). In token lists below *` = self-service.
- Calendar projection: module writes project into
calendar_eventsviaprojectDomainWrite
(event types: viewing, job, rental, stay, reservation, session, trip, plus construction/tender deadline events); cancellations call cancelDomainProjection. A reconcile sweep covers viewings, rentals, stays, reservations and HS jobs.
- Background cron (1-minute,
/api/internal/outbox): outbox drain (knowledge.ingest,
workflow.run, channel.ai_reply, channel.media.ingest, webhook.deliver, knowledge.embed_backfill), workflow schedule tick, compliance + evidence-request reminders/expiry, platform maintenance (calendar reconcile, resource adoption, vehicle-compliance mirror), license-expiry notices.
- Public API:
/api/v1/customers(scope customers.read),/api/v1/tasks(tasks.read),
/api/v1/mcp (tools: search_customers, get_customer, list_tasks, create_task, update_task_status, list_hotel_bookings, list_property_leases); channel webhooks /api/public/{whatsapp,sms,email,voice}.
1. Property (incl. Property Operations) — persona Aria
Entities — Property Assistant (agency side)
- Listing
property_listings: title (req ≤200), address (req ≤300), deal enum(rent|sale) def rent, kind enum(residential|commercial) def residential, bedrooms def 0, bathrooms def 0, area def 0, price def 0, status enum(available|reserved|leased|sold|archived) def available, ownerId (opt), tourUrl ≤500 (opt). Delete = soft → archived. - Lead: name (req), phone/email/interestedIn/listingId (opt), budget def 0, deal def rent, stage enum(New|Qualified|Viewing|Offer|Won|Lost) def New, agent ≤80, source def admin, notes. Delete = soft → Lost.
- Viewing: listingId (req), leadId?, clientName (req), clientEmail/Phone, when datetime (req), agent, status enum(Scheduled|Confirmed|Completed|No-show|Cancelled) def Scheduled, reminder def true.
- Tenant: name (req), listingId?, phone/email, leaseStart/End, monthlyRent def 0, status enum(Active|Notice|Ended).
- Owner: name (req), phone/email/notes. Contract: listingId?, party (req), type enum(Rental|Sale), value def 0, status enum(Draft|Sent|Signed|Expired). Payment: contractId (req), amount (req), method enum(Card|Bank|Cash), status enum(Paid|Pending|Overdue), idempotencyKey. Commission: agent (req), contractId?, dealValue (req), rate 0–100 (req), status enum(Pending|Paid); amount is DB-generated. Maintenance (legacy): issue (req ≤500), priority enum(Low|Medium|High|Urgent), assignee, status enum(Open|In Progress|Resolved).
Entities — Property Operations (landlord/PM side)
- Property: name (req), code, propertyType enum(residential|commercial|mixed|land|compound|tower), address/location/description/amenities[]; update adds status enum(active|inactive|archived). Publication: publicationStatus enum(draft|published|hidden), slug (kebab), publicDescription ≤4000, heroImageUrl.
- Building{propertyId, name (req), code, floorsCount} · Floor{buildingId, name, floorNumber −20..500}.
- Unit: propertyId (req), building/floor?, unitCode (req, unique), unitType def apartment, usageType enum(residential|commercial|mixed), bedrooms/bathrooms/areaSqft, furnishing enum(unfurnished|semi_furnished|furnished), rentRate, salePrice?, depositAmount. Update adds occupancyStatus enum(vacant|reserved|occupied|notice|maintenance|blocked|inactive), marketStatus enum(available|listed|off_market|sold), status. Publication: status/slug/publicTitle/publicDescription/heroImageUrl/depositPublic/viewingCapacity 1–200.
- Media via
file_links: attach{entityType property_property|property_unit, entityId, fileId, makePrimary}, setPrimary, unlink. - Ownership: ownerCustomerId (req), property/unit?, ownershipPercentage 0–100 def 100, isPrimary, payoutPreference, startsAt; update adds status active|inactive + endsAt.
- Lease: unitId (req), tenant/ownerCustomerId?, leaseNumber (unique), startDate/endDate (req), rentAmount (req), billingFrequency enum(monthly|quarterly|half_yearly|annual|custom), depositAmount, noticePeriodDays def 30, rentIncreasePct, responsibleEmployeeId. Status: draft|active|pending_renewal|renewed|notice|terminated|expired + occupancyState pending|moved_in|moved_out;
startLeaseRenewal{proposedRent?}. - Rent schedule: generate per lease (rebuilds
duerows); record payment{paidAmount, paymentDate?, reference?}; status due|partial|paid|overdue|waived. Cheques: number (req), bank, date, amount (req); status received|scheduled|deposited|cleared|bounced|replaced|cancelled. Deposit (one/lease): amount, receivedAmount, status pending|received|held|partially_returned|returned|forfeited, deductions, returnedAmount. - Moves: leaseId, moveType move_in|move_out, scheduledDate, conditionNotes; status scheduled|in_progress|completed|cancelled + keysReturned/outstandingBalance/depositDeductions.
- Maintenance (ops): issue (req ≤400), listing/unit/tenant?, category, priority, assignee(+employee), scheduledAt, slaDueAt, cost; update status Open|In Progress|Resolved + confirmedByCustomer, vendorReference. Preventive: name (req), property/unit?, frequency enum(weekly|monthly|quarterly|half_yearly|annual|custom), nextDueDate; complete recalculates next due.
- Owner statement: ownerCustomerId, period (req), propertyId?, openingBalance/managementFeePct/commissions/adjustments; server computes rent_received, expenses, fees, net_payable; created
issued. Payout: amount (req), method enum(bank|cash|cheque|other), statementId?; status pending|approved|paid|cancelled — paid payout marks the statement paid. - Portal: tenant maintenance{issue ≥3, category, description, priority}; legacy portal maintenance + payment{amount>0, method, contractId?, idempotencyKey} (recorded Paid).
- Public (contact{name 2–120, phone 5–40, email?}, channel web|chat|portal): inquiry{listingId?/unitId?, message}, viewing request{listingId|unitId, whenISO (unit: future-only)}; unit filters property/slug/usageType/unitType/minBeds/minBaths/price range/furnishing.
Flows
- Lead New → Qualified → Viewing → Offer → Won|Lost (public inquiry ⇒ New lead, agent "Aria"; public viewing ⇒ Viewing lead + Scheduled viewing). Viewing Scheduled → Confirmed → Completed|No-show|Cancelled. Contract Draft → Sent → Signed|Expired.
- Lease: create (draft, + shared contract draft) → generate schedule → active/moved_in (unit occupied) → notice (unit notice) → renewal (pending_renewal + high-priority task) → renewed | terminated/moved_out (unit vacant) | expired. Rent due → partial|paid. Cheque received → deposited → cleared | bounced → replaced.
- Owner money: statement issued → payout pending → approved → paid (statement paid). Maintenance Open → In Progress → Resolved (cost feeds the owner statement).
- Publication draft → published|hidden; a unit is public only when published + market available|listed + occupancy vacant|reserved|notice.
Cross-impacts
Viewings project to calendar; unit create ⇒ shared resources row (property_unit, bookable+inspectable); lease ⇒ shared contracts (type lease) + customer_roles tenant; ownership ⇒ role property_owner; renewal ⇒ task; tenant-portal maintenance ⇒ notification; published units feed /properties; detail pages show compliance summaries.
AI tokens: book_viewing\, add_lead\, add_maintenance*, update_viewing, add/update_listing, update_lead, update_maintenance, add/update_contract, add_payment, add_tenant, add_owner, create_lease_draft, create_renewal_task, generate_owner_statement.
Routes: /property + leads|viewings|contracts|payments|tenants|owners|maintenance|commissions|mortgage|analytics · /property-ops + properties(/$id)|buildings|units(/$id)|leases|rent|maintenance|preventive|owners|statements|payouts|analytics · portal /portal/property-hub (+legacy /portal/property) · public /properties, /properties/$slug, /properties/units/$slug, /modules/property.
2. Home Services — persona Laila
Entities: Job{customerName (req), phone/email, technicianId?, category enum(Cleaning|AC Maintenance|Plumbing|Electrical|Painting|Handyman|Pest Control|Home Renovation|Movers|Beauty at Home|Car Wash at Home) def Handyman, description, address, scheduledAt, status enum(Pending|Confirmed|En Route|In Progress|Completed|Cancelled), price, notes, isEmergency, source}; delete = soft → Cancelled. Technician{name (req), phone/email, specialties[], status enum(Available|Busy|Offline), rating 0–5 def 5, lat/lng}. Customer (+profile){name (req), phone/email/address/notes} (totals derived). Invoice + lines{jobId?, customerId?, items[1–200]{description, qty def 1, unit, unitPrice}, status enum(Draft|Sent|Paid|Overdue), dueDate, idempotencyKey} — subtotal/tax/total server-side. Inventory{name, category, quantity, unit, minStock, costPerUnit}. Feedback{jobId/customerId, technician auto, rating 1–5 (req), comment}. Portal booking{category (req), address 3–400 (req), whenISO (req), notes, isEmergency} + portal feedback. Public booking (same + channel + contact) ⇒ Pending job.
Flows: Pending → Confirmed → En Route → In Progress → Completed (auto completed_at) | Cancelled. Invoice Draft → Sent → Paid|Overdue.
Cross-impacts: admin jobs project to calendar (eventType job, location=address); portal/public jobs join the calendar via the reconcile sweep; customer upsert; growth adapters read jobs+invoices.
AI: book_job\, add_hs_customer\, add_hs_feedback*, update_job, assign_technician, add/update_hs_invoice. Routes: /home-services + technicians|customers|billing|inventory|reports · /portal/home-services · /modules/home-services.
3. Car Rental — persona Noor
Entities: Vehicle{make/model (req), year def current, category enum(Economy|Compact|Sedan|SUV|Luxury|Van|Pickup|Sports), plateNumber, color, seats def 5, mileage, dailyRate, monthlyRate, status enum(Available|Rented|Maintenance|Reserved|Retired), fuelType enum(Petrol|Diesel|Hybrid|Electric), transmission enum(Automatic|Manual), features[], insuranceExpiry, lat/lng, notes}; delete = soft → Retired. Booking{vehicleId?, customerName (req) + contacts, driverId?, pickup/dropoff locations, pickupAt/dropoffAt (req), status enum(Pending|Confirmed|Active|Completed|Cancelled), insurance enum(Basic|Standard|Full) + cost, extras[]+cost, couponCode, idempotencyKey} — total_days (DB), subtotal/discount/tax/total computed; delete = hard. Customer (+profile){licenseNumber/Expiry, nationality, loyaltyTier Bronze→Platinum}. Driver{name, licence, status Available|Busy|Off, rating}. Maintenance log{vehicleId, type enum(Oil Change|Tire|Brake|AC|Full Service|Damage Repair|Inspection), cost, date, status Scheduled|In Progress|Completed}. Damage report{vehicleId, bookingId?, severity Minor|Moderate|Major|Total Loss, status Open|Under Review|Resolved, estimatedCost}. Coupon{code, type Percent|Fixed, value, minRentalDays, maxUses, expiresAt, active}. Portal booking{vehicleId (req), pickupLocation (req), pickupAt/dropoffAt (req), insurance, notes}; public same (+contact).
Flows: Pending → Confirmed → Active (pickup) → Completed (returned_at) | Cancelled. Coupon validity: active, unexpired, under max uses, meets min days; used_count++ on success. Damage Open → Under Review → Resolved; maintenance Scheduled → In Progress → Completed.
Cross-impacts: vehicle ⇒ shared resource; booking (Pending/Confirmed + vehicle) reserves a shared booking (conflict ⇒ "Vehicle is not available") + calendar rental + vehicle Reserved; Cancelled/Completed release the booking (Cancelled also cancels the event). Insurance expiry mirrors into compliance_items via sweep. ⚠ see Boundaries (status flips, hard delete).
AI: book_cr_rental*, update/cancel_cr_booking, add_cr_customer, add/update_cr_vehicle, add/update_cr_driver, assign_driver, add_cr_maintenance, add_cr_damage_report. Routes: /car-rental + bookings|customers|drivers|maintenance|reports · /portal/car-rental · /modules/car-rental.
4. Auto Services — persona Milo
Entities: Job card{customerName (req) + contacts, vehicleId? or plate/make/model/year/mileage (upserts as_vehicles), mechanicId?, category enum(Oil Change|Tire Service|Battery Service|Car Inspection|Car Wash|Detailing|Mechanical Repair|Electrical Repair|AC Service|Brake Service|Body Work|Transmission Service|Insurance Claim|Spare Parts), description, notes, status enum(Draft|Confirmed|In Progress|Awaiting Parts|Ready for Pickup|Completed|Cancelled), priority, estimatedCost, finalCost, laborHours, scheduledAt, isWarranty, isInsuranceClaim}; delete = soft → Cancelled. Mechanic{name, phone, specialty, status Available|Busy|Off Duty, rating, location}. Vehicle{customerId?, make/model (req), year, plate, vin, color, mileage, lastServiceDate}. Service catalogue{name, category, defaultPrice, active}; Parts{name, partNumber, category, quantity, minStock, unitCost, supplier, location}. Invoice{jobCardId?, items + laborCost, status Draft|Sent|Paid|Overdue, idempotencyKey}. Feedback{jobCardId, rating (req)}. Portal booking{category (req), vehicle, whenISO (req)}; public same (+year, contact) ⇒ Draft card.
Flows: Draft → Confirmed → In Progress → Awaiting Parts → Ready for Pickup → Completed | Cancelled; mechanic Busy on assign, Available on Completed. Cross-impacts: customer + vehicle upserts; no calendar projection; growth adapter reads job cards. AI: book_service\, add_as_customer\, add_as_vehicle\, add_as_feedback\, update_job_card, assign_mechanic, create/update_as_invoice. Routes: /auto-services + mechanics|customers|inventory|billing|reports · /portal/auto-services · /modules/auto-services.
5. Hotel — persona Zeno
Entities: Room{roomNumber (req), type enum(Standard|Deluxe|Suite|Executive|Presidential|Family), floor, status enum(Available|Occupied|Reserved|Cleaning|Maintenance|Out of Service), ratePerNight, maxOccupancy def 2, beds, view, amenities[]}; delete = soft → Out of Service. Booking{guestName (req) + contacts, guestId?, roomId?, checkIn/checkOut (req), adults def 2, children, addOns[]+cost, status enum(Pending|Confirmed|Checked In|Checked Out|Cancelled|No Show), specialRequests, idempotencyKey} — nights/subtotal/tax/total computed. Guest (+profile){nationality, loyaltyTier Standard→Platinum}. Housekeeping{room, type enum(Checkout Clean|Stayover|Deep Clean|Turndown|Inspection), status enum(Pending|In Progress|Completed|Inspected), priority, assignedTo (employee), scheduledAt}. Staff{name, role, department enum(Front Desk|Housekeeping|F&B|Maintenance|Concierge|Management), status On Duty|Off Duty|On Break, shift, employeeId}. Maintenance{room/area, issue (req), priority, status Open|In Progress|Resolved, cost}. Invoice{bookingId?, items, status, idempotencyKey}. Room service{bookingId/room, items[1–100], status enum(New|Preparing|Delivered|Cancelled)}. Feedback. Portal: booking{roomId (req), dates (req), guests, addOns}, room service{bookingId, items}, feedback. Public booking (+contact).
Flows & side-effects: Pending → Confirmed → Checked In (room Occupied) → Checked Out (room Cleaning + auto "Checkout Clean" housekeeping, high priority) | Cancelled/No Show (Reserved room → Available; shared booking released; calendar event cancelled). Housekeeping Pending → In Progress → Completed → Inspected. Room service New → Preparing → Delivered|Cancelled. Cross-impacts: room ⇒ shared resource; booking: overlap check → shared booking → calendar stay → room Reserved; guest upsert. ⚠ housekeeping completion doesn't flip the room (Boundaries). AI: book_room\, order_room_service\, add_hotel_guest\, report_maintenance\, add_hotel_feedback*, update_booking, check_in, check_out, update_room, add/assign_housekeeping, update_hotel_maintenance, create/update_hotel_invoice. Routes: /hotel + bookings|guests|housekeeping|staff|maintenance|billing|reports · /portal/hotel · /modules/hotel.
6. Restaurant — persona Remy
Entities: Menu item{name (req), category enum(Starters|Mains|Grills|Pizza|Pasta|Salads|Desserts|Beverages|Sides), price, description, status enum(Available|Sold Out|Hidden), isVeg/isVegan/spicy, prepMins def 15, calories}; delete = soft → Hidden. Order + lines{orderNumber auto ORD-####, type enum(Dine-in|Takeaway|Delivery), table, customer, items[1–200]{menuItemId, qty def 1, notes} (menu price snapshot wins), couponCode, status enum(New|Confirmed|Preparing|Ready|Out for Delivery|Served|Completed|Cancelled), deliveryAddress, idempotencyKey} — computed subtotal/discount/tax/total + loyalty_points = floor(total/10); patch adds deliveryStatus enum(Pending|Assigned|Picked Up|On the Way|Delivered|Failed) + driverId (staff). Table{number (req), seats def 2, section def Main, status Available|Occupied|Reserved|Cleaning}. Reservation{customerName (req), partySize def 2, table?, dateTime (req), status enum(Pending|Confirmed|Seated|Completed|Cancelled|No Show)}. Customer (+profile){loyaltyPoints, tier, favoriteItem}. Staff{department enum(Kitchen|Service|Delivery|Bar|Management)}. Inventory{name, sku, quantity, unit, parLevel, costPerUnit, supplier}. Coupon{code, type Percent|Flat|Free Item, value, minOrder, status Active|Scheduled|Expired|Disabled, usageLimit, validity}. Invoice (+Refunded status). Feedback. Portal order{type def Delivery, items{menuItemId (req), qty, notes}, couponCode, deliveryAddress} / reservation / feedback; public order (+contact).
Flows: Order New → Confirmed → Preparing → Ready → Out for Delivery|Served → Completed | Cancelled; delivery Pending → Assigned → Picked Up → On the Way → Delivered|Failed. Reservation Pending → Confirmed → Seated → Completed | Cancelled|No Show. Cross-impacts: order ⇒ coupon usage++, loyalty points credit, customer upsert. Table ⇒ shared resource; reservation with a table reserves a 2-hour shared booking (conflict ⇒ "Table is not available") + calendar reservation; updates re-reserve/re-project; Cancelled/No Show release + cancel. AI: place_order\, take_order\, book_table\, reserve_table\, add_restaurant_customer\, add_diner\, add_restaurant_feedback\, add_review\, update_order(_status), assign_driver, add/update_menu_item, update_reservation/table/customer, add/update coupon|inventory|invoice. Routes: /restaurant + kds|menu|tables|reservations|delivery|customers|loyalty|coupons|inventory|billing|reports · /portal/restaurant · /modules/restaurant.
7. Travel — persona Vera
Entities: Package{name/destination (req), country, durationDays, nights, price, category, status enum(Available|Sold Out|Draft), inclusions[], rating, description}; delete = soft → Draft. Booking{type enum(Package|Flight|Hotel|Activity), customerName (req)+contacts, packageId?, title (req), destination, travellers def 1, departDate/returnDate, amount (package price × travellers wins), paid, status enum(Pending|Confirmed|Paid|Ongoing|Completed|Cancelled|Refunded), couponCode, idempotencyKey}; sold-out rejected; Paid/Completed force paid=amount; delete = soft → Cancelled. Customer (+profile){passportNo, nationality}. Visa application{applicantName/country (req), visaType def Tourist, status enum(Draft|Submitted|Under Review|Approved|Rejected|On Hold), reference}. Itinerary + days{title (req), destination, startDate, budget, status enum(Draft|Shared|Confirmed), days[≤60]{day, title, details}} (day updates replace all). Invoice (+Cancelled). Coupon{Percent|Flat, minSpend, usageLimit, validity, active}. Review{rating (req)}. Portal booking{packageId?, title (req), travellers, dates}; public same (+contact).
Flows: booking Pending → Confirmed → Paid → Ongoing → Completed | Cancelled|Refunded; visa Draft → Submitted → Under Review → Approved|Rejected|On Hold; itinerary Draft → Shared → Confirmed. Cross-impacts: customer upsert; coupon usage. No calendar projection. AI: book_trip*, update_trip, add_travel_customer, add/update_travel_package, add/update_visa, add_itinerary, create/update_travel_invoice, add_travel_review. Routes: /travel + packages|customers|visas|itineraries|reports · /portal/travel · /modules/travel.
8. Construction
Entities: Project{name (req), projectCode (unique), projectType, client/consultant/mainContractor customers, propertyId, sharedContractId, PM employee, dates, contractValue, currency, retentionPercent, advanceAmount, location}; status enum(draft|planning|active|on_hold|substantially_complete|completed|cancelled) + progressPercent. Parties{customerId, roleKey}; Team{employeeId, role def member, discipline, allocation%}. WBS{name, code, parent, level, sequence}; Cost codes{code/name (req), category}. Site report{reportDate (req), weather, workCompleted/Planned, delays, issues, safetyObservations, visitors}. Progress{asOfDate (req), wbs/boqItem, planned/actualPercent, quantities, rollUpToProject}. RFI{subject (req), question, discipline, priority, dueDate, assignee} → respond{status enum(draft|open|answered|closed|cancelled)}. Submittal{subject (req), type enum(material|shop_drawing|method_statement|sample|technical_document), dueDate} → review{status enum(draft|submitted|under_review|approved|approved_with_comments|revise_resubmit|rejected|closed), reviewCode, comments}. Milestone{name, plannedDate, wbs, responsible} → status enum(pending|in_progress|achieved|missed|cancelled) + actualDate. Drawing{drawingNumber (req), discipline, revision} → status enum(draft|issued_for_review|issued_for_construction|superseded|as_built); supersede. Transmittal{subject, direction in|out, parties, items{drawing|submittal|file, copies}} status draft|sent|acknowledged|closed|cancelled. Subcontract{subcontractorCustomerId (req), scope, value, retention%, dates} status draft|active|on_hold|completed|terminated. Procurement requirement{description (req), material|service, quantity, requiredDate, wbs/costCode} status requested → sent_to_procurement → fulfilled|cancelled. BOQ{name, code, currency} + items{description, quantity, rate (req), wbs/costCode}; revise (copyItems) / approve. Budget lines (upsert){category, wbs/costCode, planned/revised/committed/actual/forecast, costSource internal|external_procurement|integration_pending}. Variation{title (req), costImpact ±, timeImpactDays} → decide{approved|rejected|withdrawn|under_review|submitted, approvedAmount, approvedTimeImpactDays}. Payment certificate{certificateNumber, subcontract, period, grossWork/variations/materialsOnSite/previousCertified/retention/advanceRecovery/deductions; net computed} → decide{certified|rejected|under_review|paid}.
Flows: project draft → planning → active ⇄ on_hold → substantially_complete → completed|cancelled. RFI open → answered → closed. Variation draft → submitted → under_review → approved|rejected|withdrawn (approve needs construction.variations.approve). Certificate submitted → under_review → certified|rejected → paid. BOQ draft → approved (revision supersedes). Milestone pending → in_progress → achieved|missed.
Cross-impacts: calendar events rfi_due/submittal_due/milestone/payment_certificate_due; parties & subcontractors ⇒ customer_roles; requirement → procurement seam + audit; links to property + shared contract; can be created from a Tender award. AI: create_rfi, create_site_report, create_submittal, create_variation, create_milestone, update_project_status. Routes: /construction + /construction/projects; detail /construction?project=<id> tabs: health, cost, wbs, boq, rfis, submittals, variations, certificates, subcontracts, documents, procurement, site, milestones. (No portal/public.)
9. Tender & Bids
Entities: Tender{title (req), tenderNumber, externalReference, client/consultant, type/sector/projectType, location, currency, estimatedValue, bidDueAt, clarificationDeadline, siteVisitDate, expectedAwardDate, projectDurationDays, owner, internalConfidence 0–100}; status enum(identified|reviewing|bid_no_bid|approved_to_bid|preparing|submitted|clarification|negotiation|awarded|lost|withdrawn|cancelled). Parties/Team. Bid decision (upsert){recommendation bid|no_bid|conditional, comments} + criteria{criterion, weight, score} → decide{bid|no_bid|deferred} (weighted score). Documents{title, type, revision, mandatory} + addenda. Requirement{requirementText (req), type, criticality low→critical, dueDate, responsible} → status enum(not_reviewed|assigned|in_progress|ready|blocked) + compliance enum(not_reviewed|compliant|exception|not_applicable) + responseText. Extractions{fieldKey ∈ 21 fields, value, source, confidence} → review accepted|edited|rejected → apply-to-tender / convert-to-requirement; bulk staging 1–100. Assumptions/Exclusions{text, category, classification commercial|technical}/Risks{probability 1–5, impact 1–5, mitigation, commercialAllowance}. BOQ + items{clientRate, estimatedDirectCost, markupPercent, bidRate; pricing mode manual|estimate_rollup; recalc from estimate}; CSV/XLSX import (mapping by column index; preview/commit; batches). Estimate items{costCategory enum(material|labor|equipment|subcontract|preliminaries|overhead|other), unitCost, source}. Pricing (upsert){directCost, preliminaries, overhead%, contingency%, riskAllowance, profitMarkup%} → proposed_bid_value. Price references{sourceType supplier|subcontractor|market|other, quotedAmount, validity} + request external pricing (procurement seam). Response sections{technical|commercial, content ≤50k} status draft|in_progress|ready|approved. Proposal (versioned) + sections → exportProposalHtml{confidentialityText}. Reviews{type technical|commercial|contractual|management|final_submission} → in_progress|approved|rejected|changes_requested. Bonds{type bid|tender|performance|advance_payment_guarantee|other, amount/percentage, expiry} status required|requested|issued|returned|expired|cancelled. Submissions{type original_bid|revised_bid|clarification_response|bafo|negotiated_offer, method, submittedValue}. Clarifications{subject, direction, question, dueDate} → open|answered|closed|cancelled. Negotiations{topic, previous/revisedAmount}. Outcome{awarded|lost|withdrawn|cancelled, awardReference, awardedValue, awardedDurationDays, lossReason}.
Flows: identified → reviewing → bid_no_bid → (decideBid: bid ⇒ approved_to_bid · no_bid ⇒ withdrawn · deferred stays) → preparing → submitted → clarification → negotiation → outcome. Award → Construction: convertTenderAwardToConstructionProject (awarded only, Construction enabled, once) copies parties/team/BOQ into a new project + audit. Cross-impacts: calendar bid_due/clarification/site_visit/expected_award/submission/negotiation/bond_expiry (cancel on status cancel/withdraw/lost); parties ⇒ customer_roles; bonds linkable from compliance instruments; audits on extraction apply/import/rollup/conversion. AI: create_requirement, create_clarification, create_risk, create_assumption, create_exclusion, create_response_section, create_extraction. Routes: /tender + /tender/tenders; detail /tender?tender=<id> tabs readiness, requirements, boq, cost, risks, reviews, bonds, submissions, clarifications, award.
10. Fleet & Local Logistics
Entities: Vehicle{vehicleCode, registration/plate, vin, vehicleType def van, make/model/year/color, capacity+unit, fuelType, currentOdometer, ownershipType owned|leased|rented|other, branch, resourceId, notes}; update adds status active|inactive|retired + operationalStatus available|on_trip|maintenance|out_of_service + assignedDriverId. Driver{fullName, driverCode, employeeId|customerId, phone, licence (number/class/issue/expiry), defaultVehicleId}; update adds status active|inactive|suspended + availability available|on_trip|off_duty|unavailable. Job{jobNumber auto JOB-, customer, contract, jobType enum(pickup_delivery|local_transport|multi_stop_delivery|transfer|scheduled_route), requested pickup/delivery times+locations, priority, serviceLevel, dispatcher, quotedAmount, billingReference, package metrics (count/weight/volume), specialHandling, temperatureNote, fragile, notes} status enum(draft|requested|planned|assigned|dispatched|in_progress|completed|cancelled|failed). Job stops{stopType pickup|delivery|waypoint|service|return, address, customer, planned times, contact, instructions, sequence}. Trip{jobId?, tripNumber auto TRIP-, planned start/end, origin/destination, routeNotes, copyJobStops def true} → assignVehicle/assignDriver{override}, dispatch{override}, reschedule, updateStatus{en_route|at_stop|completed|cancelled|failed, endOdometer, completionNotes}, linkJob. Trip stops (status pending|en_route|arrived|completed|failed|skipped|cancelled). Odometer readings{reading (req), source manual|trip_start|trip_end|fuel|maintenance|correction}. Fuel entries{vehicle (req), qty, unitPrice, station, receiptFileId}. Trip expenses{category toll|parking|fuel|loading|driver_allowance|other, amount, receipt} → approve|reject. Incident{type enum(accident|breakdown|delay|damage|traffic|customer_dispute|failed_delivery|other), severity low→critical, vehicle/driver/trip/job, markVehicleUnavailable} → status open|investigating|resolved|closed. Maintenance{vehicle (req), type, scheduledDate, odometerDue, provider internal|external, markVehicleUnavailable} → complete{nextDue, returnVehicleToService}. POD: confirmStopArrival; confirmPickup{contact, packageSummary}; confirmDelivery{stopId, recipientName, deliveryStatus delivered|partial|failed, exceptionReason, signatureFileId, geo, confirmationReference}; recordFailedDelivery{reason enum(customer_unavailable|wrong_address|refused|damaged|vehicle_issue|driver_issue|access_restriction|other), createReattempt}; exportPodHtml. Driver app (/driver): advance trip, confirm pickup, register evidence{signature|photo → private-docs}, confirm delivery(+photos), fail delivery, record odometer, report incident.
Flows: job requested → planned (trip created) → dispatched → completed|cancelled|failed. Trip planned → assigned (availability checked; override needs approve permission) → validateDispatch (blockers: no vehicle/driver, vehicle inactive/maintenance/overlap/shared-booking clash, driver inactive/off-duty/overlap, no stops) → dispatched → en_route → at_stop → completed (end odometer) | cancelled|failed. Stop pending → arrived → completed|failed (reattempt stop possible). One POD per stop, idempotent. Expense recorded → approved|rejected; incident open → investigating → resolved → closed.
Cross-impacts: trips project to calendar; dispatch creates a shared booking (when the vehicle has a resource), flips vehicle+driver to on_trip, marks linked jobs dispatched, audits; terminal trips release everything back to available; odometer updates the vehicle; incidents/maintenance can take the vehicle out of service; evidence lands in files + links; vehicle/driver screens show compliance summaries. AI: create_job, create_trip, assign_vehicle, assign_driver, create_incident, schedule_maintenance. Routes: /fleet + jobs|dispatch|trips|vehicles|drivers|maintenance|incidents|analytics · /driver · /portal/fleet (customer "Deliveries" with POD evidence).
11. Education & Facilities
Entities: Campus{name (req), code, address/city/country, branch, property, contact}; status active|inactive. Building{campusId, name}. Facility{campusId (req), name (req), type def classroom, building, resourceId, code auto FAC-, capacity, features[], bookable def true}; status active|inactive|unavailable. Programme{name (req), code, category, programmeType def course, defaultCapacity, durationMinutes, defaultFacilityType, allowWaitlist def true, enrolment window}; status draft|active|inactive|archived; publish{draft|published|hidden, publicTitle, slug ≤80, heroImageUrl}. Session{programmeId (req), title, facility, instructor, startsAt/endsAt, capacity, override}; code auto SES-; status enum(draft|scheduled|open|full|in_progress|completed|postponed) + registrationStatus open|closed|full; assignInstructor/reschedule/cancel{reason}; recurring generator{frequency daily|weekly|monthly, count 1–52}. Participant{customerId (req), participantNumber, preferredCampus, supportNotes}; guardians via customer_relationships{guardian|parent|emergency_contact|representative}. Instructor{fullName, code, employeeId|customerId, specialization, biography}; status active|inactive. Enrolment: enrol{sessionId, participantCustomerId, override} (RPC checks window+state) → status pending|confirmed|waitlisted|cancelled|completed|no_show; cancel{promoteWaitlist def true}; promote; complete{completionDate}. Attendance upsert entries[1–500]{status present|absent|late|excused|no_show}. Certificates{participant, programme/session/enrolment, fileId, issueDate; number auto CERT-}. Portal enrol/cancel; guardian enrol/cancel{dependentId}. Instructor app: schedule{upcoming|all}, roster, mark attendance. Public: list programmes{orgSlug, category, type}, get by slug.
Flows: programme draft → active (+publication); session draft/scheduled → open → full → in_progress → completed|postponed|cancelled; enrolment → confirmed or waitlisted (position) → cancelled (promotes first waitlisted) | completed → certificate. Cross-impacts: session create checks instructor availability + books the facility via create_booking RPC (shared bookings) + calendar session; reschedule/cancel ripple to booking + calendar; guardian actions audited. AI: create_programme, create_session, reschedule_session, assign_instructor, enrol_participant. Routes: /education + campuses|facilities|programmes|sessions|enrolments|participants|instructors|analytics · /instructor · /portal/education · public /programmes, /programmes/$slug.
12. Compliance
Entities: Requirement{requirementKey/name (req), category, subjectType, defaultRenewalWindowDays}. Obligation/item{subjectEntityType/Id (req), type, category, moduleKey, requirementId, authority, referenceNumber, issue/expiry dates, renewalWindowDays, risk low→critical, responsibleEmployee, notes}; status active|expiring|expired|renewing|waived|archived. Evidence{link fileId + role; review under_review|approved|rejected + notes}. Programme{name, code, category, reviewFrequency, owner}. Inspection template{name (req), subjectEntityType, frequency, riskProfile} + items{question (req), responseType enum(pass_fail|yes_no|text|number|date|selection|evidence_only), required def true, critical, guidance, evidenceRequired, options[]}; publish; revise (new version, supersedes); AI checklist advisor recommendInspectionChecklist{scenario}. Schedule{template, subject, recurrence daily→annual|custom, intervalDays, firstDueAt, responsible}; run advances next_due. Inspection: create → start (in_progress) → saveResponse{result pass|fail|na|info, value, notes} → complete{riskSummary}. Finding{title (req), source enum(inspection|observation|review|incident|audit|external), findingType enum(observation|non_conformance|safety_issue|compliance_gap|audit_issue|document_gap), severity, likelihood/impact 1–5, responsible, dueAt; root cause fields on update}. Action/CAPA{findingId (req), title (req), type corrective|preventive|containment|follow_up, owner, dueAt, verificationRequired, createTask}. Instrument{type enum(bid_bond|performance_bond|advance_payment_guarantee|retention_guarantee|bank_guarantee|insurance_policy|licence|permit|certificate|warranty), amounts, dates, tenderBondId}; status active|expiring|expired|claimed|released|cancelled. Evidence request{complianceItemId + recipientCustomerId + title (req), instructions, requestedEvidenceType, dueAt; ref CER-, starts sent}; extend due; cancel. Portal: prepare upload → register → submit{fileIds[]} (pdf/png/jpeg/webp/doc/docx) → discard staged.
Flows: inspection outcome compliant|compliant_with_observations|non_compliant|not_applicable (score = pass/(pass+fail)); every critical failure auto-creates a critical non-conformance finding. Finding open → acknowledged → action_required → remediation_in_progress → pending_verification (all actions complete) → closed (blocked while actions open; critical needs verification) | reopened{reason} | accepted_risk{justification}. Action open → in_progress → completed (linked task closes) → verified|rejected|cancelled. Evidence request sent → submitted → under_review → accepted|rejected (resubmit) | expired (sweep) | cancelled. Expiry sweep windows expired/7/30/60/90 ⇒ reminders ledger + task + notification. Cross-impacts: audits everywhere; tasks (compliance_action, compliance_reminder); portal notifications both directions; rental-vehicle insurance mirrored in; subject summaries on property/unit/vehicle/driver/facility/instructor screens; compliance_finding_events history. AI: create_inspection, schedule_inspection, create_finding, create_action. Routes: /compliance + register|inspections|findings|actions|evidence|evidence-requests|instruments|templates|requirements|analytics · /portal/compliance.
13. Platform core
CRM / parties / contracts
Customer{fullName 2–160 (req), companyName, customerType individual|company, primaryEmail/Phone, preferredLanguage def en, status lead|active|inactive|blocked, city, countryCode(2), source, ownerEmployeeId, branchId, tags[≤30], notes}; delete = soft → inactive. Contacts{channel email|phone|whatsapp|sms|telegram|other, identifier, isPrimary}. Roles{roleKey, moduleKey, relatedEntity, startsAt/endsAt}; relationships{sourceId, targetId, type}. Segments{definition over lifecycle_status, customer_type, module_presence, value_band, engagement, health, recency_days with in|not_in|lte|gte; system segments new_leads, at_risk, dormant, high_value}. Follow-up from an insight ⇒ task. Opportunities are derived (Property pipeline + module activity). Contract{contractType def general, number, counterparty+role, relatedEntity, owner, startsOn/endsOn, value, currency, renewalNoticeDays, moduleKey}; status draft|active|pending_signature|expired|terminated|renewed. Routes: /crm(+$customerId 360, opportunities, segments, followups), /contracts, /directory.
Tasks / calendar / resources / bookings
Task{title (req), description, priority low→urgent, taskType def general, dueAt, moduleKey, assignToMe|assigneeUserId}; status open|in_progress|blocked|waiting|completed|cancelled; creator human|ai|system|workflow; activity log + assignee notification; types in use: lease_renewal, compliance_reminder, compliance_action, follow_up, handoff, workflow. Calendar event{title/startAt (req), endAt, eventType def appointment, allDay, timezone def UTC, moduleKey, employee/resource/customer, location, relatedEntity}; status scheduled|tentative|confirmed|cancelled|completed. Resource{resourceTypeId/name (req), code, parent, branch, capacity, isBookable def true, isInspectable}; status active|inactive|maintenance|retired; operating hours{weekday, periods HH:MM} + blackouts. Booking via create_booking RPC (advisory lock; booking_unavailable:<reason>): {resourceId, startAt/endAt (req), customerId, moduleKey, source def native, quantity def 1, idempotencyKey}; availability check; cancel{reason}; reschedule (RPC + calendar); status pending|confirmed|cancelled|rejected|completed|no_show. Routes: /tasks, /my-work, /calendar, /resources, /bookings.
Files / documents / knowledge
prepareUpload{filename, mimeType, sizeBytes, visibility private|organization|public} (storage cap enforced: trial 500 MiB / licensed 10 GiB / override) → registerFile{bucket public-assets|private-docs, path, originalFilename, mimeType allow-list, sizeBytes, category, moduleKey, altText} → linkFile{entityType/Id, role def attachment, isPrimary} · getFileUrl{expiresIn 30–3600} · revisions{predecessorFileId, revisionLabel, effectiveFrom}; soft delete orphans links. Knowledge base{name, moduleKey} + sources{title (req), sourceType file|url|text|faq, url|content ≤200k|fileId}; status pending → indexing → ready|failed; reindex (50 inline + outbox queue); per-AI-employee base assignment. Routes: /files, /documents, /knowledge, /portal/documents.
AI employees / approvals / workflows
AI employee{name/roleTitle (req), moduleKey, model def google/gemini-3-flash-preview, languages def [en], channels def [web], branch/department, instructions}; status active|paused|training|retired; escalationRules; permissions{capabilityKey, level view|suggest|execute, requiresApproval def true}. Personas: Aria, Laila, Noor, Milo, Zeno, Remy, Vera. Action requests: approval_status pending|approved|rejected|not_required · execution_status pending|running|succeeded|failed|cancelled|skipped · policy decision/reason · result/error; approve (four-eyes for critical when policy demands) → executeApprovedAction → dispatch adapter or fallback human task; reject{reason} notifies. Conversations{channel web|admin|portal|mobile, moduleKey, aiEmployeeId, subject, reuse}; inbox takeover/resume/human reply{text, fileId}. Workflows{name (req), triggerType event|schedule|manual|webhook|ai, steps[≤40]{actionType enum(create_task|send_notification|run_tool|ai_analysis|request_approval|human_handoff|send_channel_message|branch|noop), config, conditions, onError stop|continue|retry, maxRetries 0–5, requiresApproval}}; status draft|active|paused|archived; manual run; runs can end awaiting_approval; gated by the workflows feature + workflow_limit. Schedules{one_time|daily|weekly|monthly|interval, timezone, timeOfDay, weekday, dayOfMonth, interval value+unit, startsAt/endsAt, catchUpLimit 0–10}. Renewal template: instantiate{contract|compliance|generic} ⇒ draft 3-step workflow. Routes: /agents, /approvals, /workflows, /inbox, /manager, /voice (demo).
Analytics / reports / command & executive
Time series/trend/forecast{sourceKey, granularity day|week|month, buckets 2–90} + scenario{−100..+500%}; sources platform.new_customers, property.leads_created, property.payments_received, <module>.recorded_value|transactions. Report definitions{dataSource, visibility private|organization} + run + snapshots; catalogue: tasks_by_status/priority, customers_by_status/type, leads_by_stage, employees_by_status, conversations_by_status/channel, ai_actions_by_execution/approval. Command center: alerts (critical→informational), task scopes, schedule views; insights acknowledge/dismiss/resolve/seen/make-task; executive report{today|last_7_days|this_month|custom} + HTML export. Routes: /dashboard, /analytics(+4), /reports, /command-center, /executive.
Planning / OKRs
Objective{title (req), owner, scopeType organization|branch|department, category, period monthly|quarterly|annual|custom, priority, dates, reviewCadence}; status draft|active|at_risk|achieved|cancelled|archived (achieved notifies). KPI{objectiveId, name (req), direction increase|decrease|maintain|threshold, unit count|percentage|currency|duration|rating|ratio, baseline/target/weight, source manual|system(+metricKey)}; values → observations; health on_track|watch|at_risk|achieved|insufficient_data. Initiative{title, progressMode manual|derived, expectedImpact, dates}; status planned|active|blocked(high-priority notify)|completed|cancelled; links to workflows/AI employees (monitor|recommend|execute|follow_up|report)/employees/tasks. Updates{objective XOR initiative, summary (req), blockers, nextSteps}; reviews chain snapshots. All audited. Routes: /planning + objectives(/$id)|kpis(/$id)|initiatives|reviews|workforce.
Governance / audit / settings / domains / websites
AI policy{maxAutonomousRiskLevel none→high, approvalRequiredAtOrAbove low→critical, allowExternalChannelExecution, allowCustomerDataModification, allowFinancialActions, allowDestructiveActions, requireReviewForSensitive, fourEyesForCritical}. Roles CRUD + per-permission grants + member scope organization|branch|department. Security toggles (privileged change, high-risk waiting, policy change, AI denied); SSO/SCIM/MFA/IP/device listed as not yet available. Data governance: retention{category, mode, days, legalHold} + data requests{export|delete, org|customer}. Audit entries{actorType user|ai|system|integration, category business|security|system|ai|billing, before/after}. Settings tabs: organization (identity, brandColor, timezone/locale/dateFormat, white-label branding), modules (setModuleEnabled = available + licensed + within plan module limit), business, ai, notifications, access, integrations (connections, credentials, API keys{scopes, expiry, rate limit}, webhooks), domains (add hostname — unique, *.voxoplan.com rejected, first = primary, A-record returned; verify/primary/Serves module|null/remove — all audited), website (per public module: publish, colours #hex, announcement, hero, social, seo; banners hero|strip; nav header|footer; pages at /p/$slug). Routes: /governance(+5), /audit, /roles, /users, /settings.
Billing / licensing / usage / portals
Tenant: plan & usage views, license snapshot, resource usage card; requestPlanChange{planCode, note}, requestCancellation, withdraw. Licensing: one module_licenses row per org+module (period_end null = lifetime; source manual|org_grant|pack|grandfathered; states none|revoked|expired|expiring≤14d|active|lifetime); 90-day trial on subscriptions; access decided on read; sweep notifies only. Platform: grants{modules|org|pack × months|custom|lifetime}, revoke, packs, plans + entitlements, assign plan, suspend/reactivate, entitlement + resource-limit overrides (storage, db records, AI, network, members, customers, MAU), registration mode, support, admins. Limits: trial storage 500 MiB HARD; soft 10k records / 200 AI / 1 GiB net / 5 members / 1k customers / 100 MAU; plan limit keys users, branches, ai_employees, modules, workflow_limit, storage_mb, knowledge_sources (ALL enforced); feature keys knowledge, workflows, manager_ai, command_center, advanced_reporting, external_channels, white_label, advanced_governance, strategic_planning, advanced_analytics, growth_intelligence. Portals: identity via claim_portal_identity RPC (customer resolved server-side, never client-sent); /portal home + notifications + documents; module portals (property-hub, education, fleet + AI-chat portals hs/cr/as/hotel/restaurant/travel + compliance); /driver (employee→driver else customer→driver) and /instructor resolve the same way. Routes: /billing, /platform/*, /tours (guided walkthroughs).
Boundaries & honest limitations (verified in code, 2026-10-03)
- Event-triggered workflows never fire from module writes —
emitWorkflowEventhas no callers (schedule/manual/AI triggers work). - Governance notifications: only
policy_changedis actually sent (approval_requested, high_risk_waiting, privileged_access_changed, ai_action_denied, export_ready are defined but unsent). - Calendar projections aren't cancelled on: tender
recordOutcome/decideBid→withdrawn(onlyupdateTenderStatuscancels), driver-app trip cancellation, and ALL construction events. - Car rental: Active/Completed don't flip vehicle status (only Reserved on booking), and booking hard-delete doesn't release the shared booking.
- Hotel: completing housekeeping doesn't return the room to Available (manual room update).
- Restaurant:
submitPublicRstReservationexists server-side but no UI calls it. - Voice (
/voice) runs on demo data; provider-dependent channels (WhatsApp/SMS/email transports) are configurable integrations, not guaranteed-on.
