Tenant Analytics Reference Public

Reference

Technical reference for ERPat Tenant Analytics: controllers, routes, models, the migration and three main-database tables, the seventeen metric groups, the cron job, and the public API endpoints.

Guide version: r1 Module version: 1.1.0 Updated: 2026-07-27 Estimated time: 8 min 3 views
Reference

Reference

The exact moving parts of Tenant Analytics — controllers, routes, models and libraries, the migration and its three tables, the seventeen metric groups, the cron job, and the public API contract. Written for developers and technical administrators.

Architecture at a glance

Tenant Analytics is a main-database-only module. Its storage lives only in the platform's primary database; the nightly job briefly switches into each tenant database to read metrics, but always writes to the main database and always restores the main connection afterwards.

Job
TenantAnalyticsSnapshotJob
Collector
switch → probe → restore
Probe model
17 collectors, tenant DB
Snapshots
main-DB storage
Rollup
identifier-free aggregate

Controllers

The two controllers and their responsibilities.
ControllerBaseRole
Tenant_analytics App_Controller (staff) The operator UI and its AJAX endpoints. Gated main-DB-only + tenant_analytics read permission in the constructor.
Tenant_analytics_api App\Core\ApiController (public) The keyless public API. No JWT/tenant/CSRF middleware; still gets the {ok,data} envelope, request-id, CORS and request logging.

Staff UI methods (Tenant_analytics)

Actions on the staff controller.
MethodPurposePermission
tab_indexRender the embedded Analytics tab body.tenant_analytics
kpi_dataRefresh the four KPI numbers for a date (AJAX).tenant_analytics
list_data/{group}/{date}Server-side snapshots DataTable.tenant_analytics
aggregate_panelThe platform aggregate for a date + group.tenant_analytics
snapshot_modal/{id}One tenant's full snapshot payload.tenant_analytics
run_sweep_modal_formThe sweep console modal.tenant_analytics_run
run_sweep_startOpen a run, return the tenant queue + run id.tenant_analytics_run
run_sweep_batchProcess one chunked batch of the sweep.tenant_analytics_run
resweep_tenantRe-sweep a single tenant + rebuild rollups.tenant_analytics_run
rebuild_rollupsRebuild the platform rollups for a date.tenant_analytics_run
settings_modalThe settings form.tenant_analytics_configure
save_settingsPersist the five settings + audit log.tenant_analytics_configure

Routes

The module owns its whole route surface. Staff UI routes live in config/routes.php; the public API routes ride the client-API stack via config/api_routes.php.

Registered routes.
RouteTarget
tenant_analyticsTenant_analytics/tab_index
tenant_analytics/(:any)Tenant_analytics/$1
GET api/v1/analytics/groupsTenant_analytics_api::groups
GET api/v1/analytics/summaryTenant_analytics_api::summary
GET api/v1/analytics/{group}Tenant_analytics_api::group ({group} matches [a-z_]+)

Models & libraries

Data layer.
ClassKindRole
Tenant_analytics_base_modelModel basePins the primary (default) connection, so storage writes always land in the main DB even mid-sweep. All storage models extend it.
Tenant_analytics_snapshots_modelStorage modelReads/writes per-tenant snapshots; DataTable source; prune past retention.
Tenant_analytics_rollups_modelStorage modelReads/writes the identifier-free platform aggregate; date helpers.
Tenant_analytics_runs_modelStorage modelSweep audit ledger + resume cursor; resolves the fleet's sweep targets from the main clients registry.
Tenant_analytics_probe_modelProbe modelRuns against the active (tenant) connection; hosts the seventeen metric collectors. Every query guards table_exists / field_exists so a drifted tenant degrades to zeros rather than erroring.
Tenant_analytics_collectorLibraryPer-tenant orchestration: switch into the tenant DB, probe each group, always restore the main connection (isolation invariant), upsert results to the main snapshot table.
Tenant_analytics_rollupLibraryCollapses a date's snapshots into the aggregate: deep-sums numeric leaves, unions scalar lists, and drops every string/boolean leaf so no identifier can survive.

Migration & tables

One migration, 20260726065412_create_tenant_analytics_tables.php, creates three tables in the main database only (a no-op on tenant connections). It is idempotent (table & index guards) and reversible.

tenant_analytics_snapshots

One row per tenant × date × group. Natural key: client_id, snapshot_date, group_key, deleted.

ColumnPurpose
client_id, company_slugWhich tenant (from the main clients registry).
snapshot_date, group_keyThe date and metric group.
payloadThe JSON measurement (with its meta block).
statusok / partial / unavailable / failed.
tenant_statusThe tenant's platform status as of the sweep.
row_count, build_duration_msScale and timing of the measurement.
error_message, run_idFailure text; the run that produced it.
created_at, updated_at, deletedAudit block (soft delete).

tenant_analytics_rollups

The platform aggregate, one row per date × groupthe only table the public API reads. Natural key: snapshot_date, group_key, deleted.

ColumnPurpose
snapshot_date, group_keyThe date and metric group.
payloadAggregate JSON — numeric totals only, no tenant identifiers.
tenants_countedHow many tenants contributed (drives small-n suppression).
tenants_total, tenants_failedFleet size and failure count for the date.
created_at, updated_at, deletedAudit block.

tenant_analytics_runs

The sweep audit ledger — one row per sweep (nightly or manual).

ColumnPurpose
run_date, sourceThe swept date; cron / manual / demo.
statusrunning / completed / partial / failed.
tenants_total, _succeeded, _failed, _skippedPer-run counters.
groups_writtenHow many group-snapshots were written.
cursorJSON resume cursor for chunked manual sweeps.
started_at, finished_at, duration_msTiming.
triggered_by, logOperator (manual runs) and a summary line.

The seventeen metric groups

Each snapshot payload also carries a meta block: group, available, row_count, module_enabled (a map of which module_* settings are on for that tenant), tables_present, and collected_at. Money reflects pending/approved payslips only; count and money metrics are sliced into windows (total, today, mtd, ytd, last_30d).

What each group measures for a single tenant.
#GroupKey metrics
1overviewTenant status, enabled-module map (total / enabled), live database size (bytes / MB / human), free space, table count, top-10 largest tables, last DB snapshot date.
2payslipsCount and by-status; pending/approved money — gross, net, deductions, tax, government contributions (and taxable when present) across windows.
3payrollsCount and by-status, by tax table, latest pay date, and payroll adjustments (count and by type).
4employeesActive / total staff, total users, admins, breakdowns by user type / status / gender, job-info and schedule coverage, new users by window.
5attendanceRecords total, by status / log type; summed metric hours (worked, late, over, under, overtime variants, night differential, PTO, absent), present/absent counts, raw taps, batches.
6leavesApplications total and by status, approved count / days / hours, leave credits (granted / consumed / balance), active leave types.
7establishmentsTotal, active, remote, geofenced, with coordinates, by location type, and assigned employees.
8schedulesTemplates (total / active / archived), users assigned / without a schedule, planning entries, publications, coverage rules.
9departmentsDepartments and groups, average members per department, members and heads covered, department profiles, nested departments.
10deminimisLedger amount / exempt / taxable, by source, YTD exempt against the BIR ₱90,000 ceiling, and payslip-side de-minimis earnings.
11loansRuntime-detected sources (core payroll loans and/or the Lending module): counts, active principal, outstanding (amortization-driven), overdue installments, payments.
12storageFiles & footprint: general_files (+ project_files) count and total size (bytes / human), by service type (local / s3 / missing), by owner (team / client / vendor), public vs private, largest file, files & bytes added per window, and the combined DB + file footprint.
13financeInvoices (count, by status), invoiced total (Σ invoice items), collected total (Σ payments), outstanding (AR), and expenses (count, total, by status), windowed. Money fields small-n suppressed in the public API.
14assetsasset_entries count, total cost (suppressed), by type / status, warranty expiring within 90 days, acquisitions per window.
15crmClients (non-lead), leads (client is_lead + legacy leads), lead funnel by status, vendors (total / by status), new clients per window.
16projectsProjects and tasks by status, overdue counts (past deadline & not done), new projects per window.
17engagementActive users (last-online 7 / 30 days), timeline posts + comments + reactions, notifications, active announcements, and activity-log volume by action.

Cron job

PropertyValue
ClassModules\TenantAnalytics\Jobs\TenantAnalyticsSnapshotJob
Slugtenant_analytics_snapshot
Schedule0 3 * * * (daily 03:00)
ScopeGlobal (runs once on main; loops the fleet itself)
Timeout / memory1800 s / 512 MB
StepsSweep → rebuild rollups → prune past retention → close run
Manual runphp erpat cron:run tenant_analytics_snapshot

Public API endpoints

All three are anonymous (no bearer token, no company key) and return the standard API envelope — { "ok": true, "data": { … } } on success, or { "ok": false, "error": { "code", "message", "details" } } on error. Every endpoint returns 404 while the public API is disabled.

GET /api/v1/analytics/groups

Lists the available metric groups and dates.

GET /api/v1/analytics/groups

200 OK
{
  "ok": true,
  "data": {
    "groups": ["overview", "payslips", "employees", ...],
    "latest_date": "2026-07-25",
    "available_dates": ["2026-07-25", "2026-07-24", ...]
  }
}

GET /api/v1/analytics/summary

Headline numbers for every group on one date. ?date=YYYY-MM-DD is optional (defaults to the latest available date).

GET /api/v1/analytics/summary?date=2026-07-25

200 OK
{
  "ok": true,
  "data": {
    "date": "2026-07-25",
    "groups": {
      "payslips": {
        "tenants_counted": 18,
        "suppressed": false,
        "headline": { "count": 4200, "gross": 15200000 }
      },
      "employees": { "tenants_counted": 18, "suppressed": false,
        "headline": { "staff_active": 1600, "staff_total": 1850, "users_total": 2100 } }
    }
  }
}

GET /api/v1/analytics/{group}

One group's full platform aggregate. {group} must be one of the seventeen keys; ?date= is optional.

GET /api/v1/analytics/payslips?date=2026-07-25

200 OK
{
  "ok": true,
  "data": {
    "group": "payslips",
    "date": "2026-07-25",
    "tenants_counted": 18,
    "tenants_total": 20,
    "tenants_failed": 0,
    "suppressed": false,
    "aggregate": { "count": { "total": 4200 }, "money": { "gross": { "total": 15200000 }, ... } }
  }
}

Errors & validation

Public API error responses.
StatusCodeWhen
404not_foundThe public API is disabled; or there is no data for the date; or an unknown group.
422validation_faileddate is not YYYY-MM-DD, or falls outside the [today − retention, today] window.
429rate_limitedThe anonymous rate limit was exceeded.
????
Small-n suppression. When tenants_counted is below tenant_analytics_min_aggregate_tenants, money fields are stripped from the payload and the response carries "suppressed": true.

Settings keys

All settings the module reads (see Administration for full descriptions).
KeyDefault
module_tenant_analyticsset by Manage Modules
tenant_analytics_retention_days400
tenant_analytics_include_inactive1
tenant_analytics_sweep_batch_size3
tenant_analytics_public_api_enabled0
tenant_analytics_min_aggregate_tenants1 (recommend 3)

Next steps

Was this guide helpful?

Report a content problem