NEWModules that talk to each other — HR, attendance, payroll & sales in one workspace.Learn more

Documentation

Everything about OpsCalify

OpsCalify is a multi-tenant operations platform (an agency CRM + back office) that unifies HR, attendance, payroll, sales, commissions, projects, quotations, forms, documents and finance behind one authenticated dashboard. This page explains what it is, the problems it removes, and exactly how every part behaves.

Overview

One system of record for an agency’s people, work and money. Hours become salaries, booked deals become commissions and payouts, and quotations become projects — without anyone re-typing data between tools.

Each organisation (a Company, the tenant) gets a fully isolated workspace. Every row of data carries a companyId, and optionally a shiftId, so one organisation — and one shift within it — can never see another’s data. A single person (User) can belong to many organisations through an OrganizationMembership.

Everything is enforced on the server. UI that a user shouldn’t see is hidden, but the page, the API route and the database query all re-check the same permission, so a hand-typed URL gets the same answer as the nav.

Problems it solves

Most agencies run on a stack of disconnected spreadsheets and point tools. Numbers drift, month-end is manual, and no one trusts the totals. OpsCalify closes those gaps.

ProblemHow OpsCalify solves it
Payroll reconciled against attendance by handPayroll is calculated directly from attendance — present days, absences, late count and configured deductions — for the exact pay period and shift.
Commission tracked in a side spreadsheetCommission is split on the sale at the moment it’s recorded, frozen, and rolled into the employee’s payroll once the payout is accepted.
Different shifts / teams seeing each other’s dataStrict shift isolation at the query level. An employee in the Night shift never sees Morning-shift sales, roles, attendance or payroll.
“Who can see the salary page?” is a guessA single RBAC model with a permission catalog, a role matrix, and a read-only Employee Access View that shows every person’s effective access.
Revenue counted twice (client + project + invoice)One finance model. Revenue is the company-profit portion of sales; there is no separate client/invoice ledger to double-count.
Quotes, currencies and FX handled ad hocOne org-wide currency for all finance math; quotations are the single exception and may carry their own currency.
Editing a form breaks old responsesSubmissions store a snapshot of the fields as they were, so later edits never corrupt history.
Deleting records loses the audit trailFinancial deletes write immutable adjustment/audit rows; finalized payroll is voided-and-replaced, never edited.
An admin locks out or demotes the ownerThe organisation founder is permanently protected — no admin can remove them from a role or strip their access.

Core concepts

Multi-tenancy

  • Company is the tenant. Every data row carries companyId.
  • User is an identity (email, password, sessions) and is not tenant-scoped — one person can belong to many companies.
  • OrganizationMembership links a user to a company and holds one or more Role records.

Employee vs User

  • User — authentication only.
  • Employee — the HR profile: department, job title, salary, employment type, attendance, shift. Linked 1:1 to a membership.
  • The founder may have no Employee record (owner / admin-only access).

The founder

Company.createdById is the founder. They are protected everywhere: they can’t be removed from any role, can’t be demoted, and are excluded from employee selectors (e.g. project assignees) so operational lists stay clean.

Employment types

FULL_TIMEPART_TIMEINTERNCOMMISSION

COMMISSION employees have no base salary in payroll — they earn only from accepted sale commissions.

Shifts & data isolation

A Shift (e.g. “Morning 09:00–17:00”) is an isolated data silo inside an organisation.

  • Every employee belongs to exactly one shift.
  • Shift-scoped entities: departments, teams, projects, sales, attendance, payroll, quotations, expenses, forms, and custom roles.
  • shiftId = null means organisation-wide — visible in every shift (system roles are global this way).
  • The active shift is resolved per request from a cookie (the shift switcher in the top bar). All queries filter to it.
  • Custom roles are created per shift, so “Manager” in Shift 1 and “Manager” in Shift 2 are independent permission sets.

Organisations are capped at two shifts during onboarding. Data created in one shift never appears in another without an explicit cross-shift reason.

Roles & permissions

Access is decoupled into three clean layers: who you are (roles), which pages you can open (module access), and what you can do on them (read / create / update / delete).

How access is resolved

Effective permissions = the union of every permission on the roles assigned for the active shift, plus any attendance-palette grants. If a shift-specific role is assigned it takes precedence over the base roles.

  • Admin — a protected system role that implies every permission on every module.
  • Member — the baseline system role.
  • Custom roles — per shift, built from the permission catalog in the Roles & Permissions matrix.

Admin hierarchy

  • Admins and the founder may edit their own details.
  • Only the founder may create, edit, or delete other admins or change the Admin role’s permission matrix / assignments.
  • Custom roles can never create, edit, or delete admins or the founder.

Permission catalog

Permissions are resource:action pairs (e.g. sales:read, payroll:create). Some resources add narrower grants — *:read_assigned (see only rows assigned to you), forms:view_responses, salaries:read, profit:read. The catalog is seeded automatically and shown in full on the Roles & Permissions → System Permissions Catalog tab.

Employee Access View

A dedicated, read-only page (permission employee_access:read; founder and admin always pass) that lists every employee with their roles, page access, granular permissions and access status. It reuses the same resolver the rest of the app enforces, so what it shows can never drift from what’s actually allowed. Non-admin viewers with the grant can’t inspect admin or founder profiles.

Modules

Every module is gated by its own permission and scoped to the active shift. Switch modules on independently — they all speak to each other.

Dashboard & employee dashboards

Admins get an operational overview. Each employee can be given a custom dashboard configuration that controls exactly which modules appear for them, independent of their role.

Employees

The HR directory: profiles, employment type, department, job title, shift, salary history, per-employee monthly attendance summary, and lifecycle events (hired → promoted → transferred → resigned → terminated, append-only). Founder rows are protected from edit/delete.

Employee Access View

Read-only visibility of the org-wide access matrix (see Roles & permissions). Search and filter by role or access level; open any employee for the full breakdown of roles, modules and granular permissions.

Departments & teams

  • Departments are hierarchical, can have a head, and can define late / leave deduction rules used by payroll.
  • Teams are many-to-many with employees, can have a head and a department, and are shift-scoped. Being a member or head of a team surfaces the Teams page even without a full read permission.

Attendance & time

  • Daily statuses: PRESENT, ABSENT, LATE, HALF_DAY, ON_LEAVE, HOLIDAY.
  • Configured office off days hard-block marking (server and UI) and are never counted, deducted or shown.
  • Late detection compares the punch against the shift start plus any grace configured on the department.
  • Correction requests go through an approval flow; leave has types, balances and requests; overtime is tracked separately.
  • The Attendance Palette lets an admin grant a user access to mark attendance on behalf of a group — this grant also feeds effective permissions.

Payroll

  • One immutable payroll row per employee per period. It snapshots base salary, working days, present / absent days, late count, deductions and sales commission.
  • Working days come from the org working-day calendar minus off days and holidays.
  • COMMISSION employees have baseSalary = 0 — they are paid only from accepted commissions.
  • Voiding a finalized run replaces the row (void + regenerate); it’s never edited in place.
  • If a sale is deleted after payroll was finalized, the clawback is booked as a deduction in the current period.
  • Runs export to PDF.

Sales

  • A Sale records a client purchase credited to a single employee, with the customer details captured on the sale itself (there is no separate Client entity).
  • Sale Value (saleAmount) is in the org currency and locked. Actual Sale Value (saleActualCurrency / saleActualAmount) is display / filter only and never used in finance math.
  • Payout is COMMISSION (a %) or FIXED (a flat amount). employeePayout and companyProfit are computed once at creation and never recomputed.
  • payoutStatus: PENDING → ACCEPTED. Acceptance creates a commission record and is what makes the commission count toward payroll.
  • Deleting a sale writes an immutable SaleAdjustment audit row and reverses its finance contribution.
  • Sales visibility supports assigned-only (sales:read_assigned) and a separate company-profit gate.

Finance & profit

A single finance model powers the dashboard. Revenue is the company-profit portion of sales for the shift and period; net profit subtracts salaries, expenses and any manual project budgets. There is no Client / Project / Accounts-Receivable ledger to reconcile or double-count. Profit & P&L visibility is its own permission (profit:read), distinct from general finance read.

Expenses & quotations

  • Expenses — monthly organisation and shift expenses; statuses DRAFT → SUBMITTED → APPROVED → REJECTED → REIMBURSED.
  • Quotations — pre-sale documents with line items; statuses DRAFT | SENT | ACCEPTED | REJECTED | EXPIRED | CANCELLED. The only place a per-record currency is allowed.

Projects

  • A project is either SALE-based (budget = the frozen companyProfit of a sale, locked) or MANUAL (editable budget).
  • Statuses: NOT_STARTED | ACTIVE | IN_PROGRESS | COMPLETED | CANCELLED.
  • Team assignment, per-project documents, soft-delete, and cascading soft-delete when the source sale is removed.
  • projects:read_assigned limits the view to projects the employee is a member / head / assignee / creator of.
  • projects:view_budget is a read-only gate on the project budget and linked-sale amounts. Without it (and without projects:update, which implies it) budget figures are stripped from every API response and hidden in the UI.

Custom forms

  • Typed fields: short / long text, number, email, phone, date, dropdown, radio, checkbox.
  • Published at a public URL — /opscalify/<department>/<form>/<publicId> — with no login required to submit.
  • Lifecycle DRAFT → ACTIVE → ARCHIVED; EXPIRED is derived from the expiry date.
  • Submissions snapshot the field definitions; shift-scoped; soft-delete; forms:* permissions including a view-responses-only grant.

Documents

A document repository with version history, access logs and sharing. Files can be scoped to a project, team, employee or expense. Shift-scoped.

Approvals & notifications

  • A generic multi-step approval engine handles leave requests, expenses, timesheets and invoices with approve / reject / escalate / reassign / comment actions.
  • Notifications deliver in-app and by email (push / SMS channels exist in the model) with queued → sent → delivered → read tracking.

Currency

  • Company.currency is the single org-wide currency, chosen at onboarding, used for all financial math — salaries, sales, payroll, expenses and the finance dashboard.
  • Supported: USD, AUD, GBP, EUR, PKR, NZD, CAD.
  • Changing it is founder/admin-only and restricted once financial records exist.
  • The only exception is Quotation.currency, which can differ per quotation.
  • A sale’s “actual” currency/amount is a display-only real-world reference and is never used in calculations.

Authentication & security

  • NextAuth v5 with credentials (email + bcrypt) and optional Google OAuth (only when the Google env vars are set).
  • Brute-force attempts are tracked; there is an email-token password reset flow with expiry.
  • Middleware protects every /dashboard and /api route; unauthenticated requests are redirected to login.
  • Authorization is server-enforced at three points for every feature — nav / UI, the page or API route, and the database query — all checking the same permission and the same companyId. There is no client-only gate.
  • Tenant isolation: a request can only ever read data for the caller’s own company and (when shifts are in use) their active shift.

Architectural rules

Invariants upheld throughout the platform.

  1. Shift isolation is strict — data is never mixed across shifts without an explicit reason.
  2. Payroll rows are immutable after finalization — void and regenerate, never edit.
  3. Sale monetary splits (employeePayout, companyProfit) are frozen at creation.
  4. No Client model — customer details live on the Sale (and Project).
  5. No Task / Timesheet models.
  6. The org founder is protected and can never be removed or demoted by another admin.
  7. Commission employees have no base salary in payroll.
  8. saleActualCurrency / saleActualAmount are display only.
  9. Audit and adjustment rows are append-only.
  10. Idempotency keys guard financial mutations against duplicate execution.
  11. shiftId = null means organisation-wide for every shift-scoped entity.

Getting started

  1. Sign up and sign in (credentials or Google).
  2. Complete onboarding: create the organisation, pick the org currency, define up to two shifts, and optionally add the first employees. The website URL is optional.
  3. Open Roles & Permissions to build custom roles from the permission catalog and assign them to people (per shift).
  4. Switch on the modules you need — Attendance, Payroll, Sales, Projects, Forms — and start recording. Everything downstream (commissions, payroll, finance) updates automatically.
  5. Use Employee Access View to confirm each person ends up with exactly the access you intended.

Tech stack

Framework

Next.js 15 (App Router) with React 19 and TypeScript 5.

Data

Prisma 6 over PostgreSQL, with a multi-file schema per domain.

Auth

NextAuth v5 — credentials plus optional Google OAuth.

UI

Vanilla CSS with theme tokens — light and dark, no utility framework.

Email & PDF

Nodemailer for transactional email; pdf-lib for payroll and quotation PDFs.

Deployment

Vercel, with pooled and direct database connections.