TrackBizPro
Connecting to cloud…
⚙️
TrackBizPro ADMIN
Emalfred Control Panel
Admin Dashboard
Manage all TrackBizPro client accounts · Cloud-connected
Accounts by Sector
Recent Signups
Expiring Soon
📥 Need to load client data? Use the Import feature.
Sector Configuration
Clients by Sector
1
Select Client Account
Choose which account to import data into
2
Choose Data Type
What kind of data are you importing?
3
Download CSV Template
Fill in the template then upload in Step 4
• Dates: YYYY-MM-DD format  ·  Amounts: numbers only, no ₦ symbol  ·  Max 500 rows per import
4
Upload CSV File
📂
Drop your file here or click to browse
Accepts: CSV (.csv) · Excel (.xlsx, .xls) · Text (.txt) · Max 500 rows
↑
Export Existing Data
Download any account's data as CSV
Monthly Revenue Trend
Revenue by Account
Activity Log · Cloud Synced
🎁 Free Trial Sign-up VERSION 2
Free trial functionality is planned for Version 2 after Paystack live approval.
Currently disabled — all signups go through full payment flow.
Coming in V2
💳 Payment Settings — Paystack
Paste your Paystack Public Key here. Get it from dashboard.paystack.com → Settings → API Keys.
Use pk_test_… for testing (shows real card form with test cards) and pk_live_… when you go live.
Enter your key and click Save.
🔐 Security note: The Public Key is safe to store — it cannot be used to charge cards without server confirmation. Never paste your Secret Key here. Your Public Key starts with pk_test_ or pk_live_.
🖼 Platform Logo
Upload the TrackBizPro platform logo. It's used as a fallback on invoices and credit notes for any client who hasn't uploaded their own business logo yet.
T
Business Settings
Admin Password
Firebase Connection Status
Checking connection…
Firestore Security Rules
Copy these rules and paste them into your Firebase Console → Firestore Database → Rules tab → click Publish. These rules ensure each client can only access their own data.
rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    function isSignedIn() {
      return request.auth != null;
    }
    function isOwner(ownerUid) {
      return request.auth.uid == ownerUid;
    }
    function isActive() {
      return request.auth.token.accountStatus == 'active'
        || !('accountStatus' in request.auth.token);
    }
    function hasPlan() {
      // Accept both casings defensively: your app stores plan as Title Case
      // ("Basic"/"Pro"/"Enterprise") but this rule previously only matched
      // lowercase — verify against your actual Cloud Functions claim values
      // and drop whichever casing turns out to be unused.
      return request.auth.token.plan in ['basic','pro','enterprise','Basic','Pro','Enterprise']
        || !('plan' in request.auth.token);
    }
    function isStaffOf(ownerUid) {
      // Your app only ever assigns roles "Owner"/"Staff"/"Viewer" — 'manager'
      // never appears anywhere in the codebase and 'staff' (lowercase) never
      // matched your actual "Staff" role string. Fixed to match your real
      // role vocabulary; both casings accepted until claim casing is confirmed.
      return request.auth.token.ownerUid == ownerUid
        && request.auth.token.role in ['Staff','staff'];
    }
    function isViewerOf(ownerUid) {
      // Previously there was no read-only tier at all: a "Viewer" role could
      // never satisfy isStaffOf(), so Viewer accounts had zero access to
      // their own business's data. This restores read-only access without
      // granting Viewers any write capability.
      return request.auth.token.ownerUid == ownerUid
        && request.auth.token.role in ['Viewer','viewer'];
    }
    function isSuperAdmin() {
      return request.auth.token.superAdmin == true
        || request.auth.token.email == 'info@emalfred.com';
    }
    function isOwnerOfBiz() {
      // Same casing issue as isStaffOf: app stores "Owner" (Title Case), the
      // rule only ever matched lowercase 'owner'. An owner whose token
      // already has role:"Owner" set would previously fail this check
      // outright (the "claim absent" fallback only applies to brand-new
      // signups, not existing owners) — most likely breaking owner-invites-
      // staff and similar owner-initiated writes. Both casings accepted
      // until claim casing is confirmed against your Cloud Functions.
      return request.auth.token.role in ['Owner','owner']
        || !('role' in request.auth.token);
    }
    match /admins/{adminId} {
      allow read, write: if isSignedIn() && isSuperAdmin();
    }
    match /adminPractice/{uid}/{document=**} {
      allow read, write: if isSignedIn()
        && request.auth.uid == uid
        && isSuperAdmin();
    }
    match /users/{userId} {
      allow read: if isSignedIn()
        && (
          request.auth.uid == userId
          || isSuperAdmin()
          // Lets an Owner query their own staff list (where ownerUid ==
          // their uid) — Firestore can validate this per-document for a
          // query, unlike the uid==userId check above, which only ever
          // works for a direct single-document read of your own doc.
          // Root cause of "created staff not showing in the staff list
          // on a fresh session" — the query was always being rejected.
          || resource.data.ownerUid == request.auth.uid
        );
      allow write: if isSignedIn() && isSuperAdmin();
      allow create: if isSignedIn()
        && isActive()
        && isOwnerOfBiz()
        && request.resource.data.ownerId == request.auth.uid;
      allow update: if isSignedIn()
        && isActive()
        && (
          isSuperAdmin()
          || (isOwnerOfBiz() && resource.data.ownerId == request.auth.uid)
          || (
            // A user may edit their OWN doc, but only a safe allow-list of
            // fields — never role/ownerId/plan/accountStatus, which would
            // otherwise let a client self-elevate privileges client-side.
            request.auth.uid == userId
            && (
              request.resource.data.diff(resource.data).affectedKeys()
                .hasOnly(['logoUrl','bizCurrency','customTaxName','customTaxRate'])
              || (
                // Narrow self-repair carve-out: the designated platform-owner
                // email may restore its OWN role to SuperAdmin if it was ever
                // corrupted, and may change nothing else in the same write.
                request.auth.token.email == 'info@emalfred.com'
                && request.resource.data.diff(resource.data).affectedKeys().hasOnly(['role'])
                && request.resource.data.role == 'SuperAdmin'
              )
            )
          )
        );
      allow delete: if isSignedIn()
        && (
          isSuperAdmin()
          || (isOwnerOfBiz() && resource.data.ownerId == request.auth.uid)
        );
    }
    match /activityLog/{logId} {
      allow read, write: if isSignedIn() && isSuperAdmin();
    }
    match /payments/{paymentId} {
      allow read, write: if isSignedIn() && isSuperAdmin();
    }
    match /settings/{settingId} {
      allow read, write: if isSignedIn() && isSuperAdmin();
    }
    match /bizData/{ownerUid} {
      allow read: if isSignedIn()
        && (isOwner(ownerUid) || isStaffOf(ownerUid) || isViewerOf(ownerUid) || isSuperAdmin())
        && isActive();
      allow write: if isSignedIn()
        && (isOwner(ownerUid) || isSuperAdmin())
        && isActive();
      match /branches/{branchId} {
        allow read: if isSignedIn()
          && (isOwner(ownerUid) || isStaffOf(ownerUid) || isViewerOf(ownerUid) || isSuperAdmin())
          && isActive();
        allow write: if isSignedIn()
          && (isOwner(ownerUid) || isSuperAdmin())
          && isActive();
        match /{collection}/{docId} {
          allow read: if isSignedIn()
            && (isOwner(ownerUid) || isStaffOf(ownerUid) || isViewerOf(ownerUid) || isSuperAdmin())
            && isActive();
          allow create: if isSignedIn()
            && (isOwner(ownerUid) || isStaffOf(ownerUid) || isSuperAdmin())
            && isActive()
            && hasPlan();
          allow update: if isSignedIn()
            && (isOwner(ownerUid) || isStaffOf(ownerUid) || isSuperAdmin())
            && isActive();
          allow delete: if isSignedIn()
            && (isOwner(ownerUid) || isSuperAdmin())
            && isActive();
        }
      }
      match /stockAlerts/{alertId} {
        allow read, write: if isSignedIn()
          && (isOwner(ownerUid) || isSuperAdmin())
          && isActive();
      }
      // ── ADDED: activity log written by logAct() for client accounts.
      // Without this block, writes/reads to bizData/{ownerUid}/activityLog
      // fall through to the default deny — this is what caused the
      // "Missing or insufficient permissions" error on client logins,
      // and why the platform owner's Log tab never showed client activity.
      match /activityLog/{logId} {
        allow read: if isSignedIn()
          && (isOwner(ownerUid) || isStaffOf(ownerUid) || isViewerOf(ownerUid) || isSuperAdmin())
          && isActive();
        allow write: if isSignedIn()
          && (isOwner(ownerUid) || isStaffOf(ownerUid) || isSuperAdmin())
          && isActive();
      }
    }
    match /userData/{uid}/{document=**} {
      // Previously only the literal doc owner (request.auth.uid == uid) had
      // any access here at all — no isStaffOf/isViewerOf/isSuperAdmin branch
      // existed. That meant Basic-plan staff/viewers were completely locked
      // out of their own business's data, and SuperAdmin-side tools reading
      // a Basic-plan client's data (e.g. the Import tab) would fail with
      // permission-denied. Mirrored the same owner/staff/viewer/admin split
      // already used throughout bizData, so Basic-plan tenants get the same
      // access model as Pro/Enterprise ones — least-privilege preserved:
      // Viewers still cannot write, Staff cannot delete.
      allow read: if isSignedIn()
        && (request.auth.uid == uid || isStaffOf(uid) || isViewerOf(uid) || isSuperAdmin())
        && isActive();
      allow create, update: if isSignedIn()
        && (request.auth.uid == uid || isStaffOf(uid) || isSuperAdmin())
        && isActive()
        && hasPlan();
      allow delete: if isSignedIn()
        && (request.auth.uid == uid || isSuperAdmin())
        && isActive();
    }
  }
}
Subscription Expiry Settings
Grace period after expiry0 days (blocks immediately)
Warning shown to client30 days before expiry
Free accounts (fee = ₦0)Never blocked
SuperAdmin / Admin accountsNever blocked
💡 To renew a client: Go to Accounts → Edit their account → Update the Renewal Date → Save. The client will regain access immediately.
TrackBizPro Admin · Emalfred Digital · emalfred.com · Firebase Cloud
📊
TrackBizPRO
Emalfred Digital
🔍
Income Trend
Summary
⚠️ Payment Alerts 0
📊 Monthly Report
Full breakdown of income, expenses, invoices and profit for any month
Recent Transactions
⚠️ Low Stock Alert — These items need restocking
🚛 Trip Log
Record every depot-to-delivery run. Track litres, costs, variance and profit per trip.
No trips recorded yet. Click + New Trip to log your first depot run.
🚚 Fleet Management
Register trucks, log maintenance, get alerts before breakdowns happen.
No trucks registered. Click + Add Truck to register your first vehicle.
👤 Driver Accountability
Track each driver's trip history, average variance, and earnings.
No drivers added. Click + Add Driver to register your drivers.
⛽ Depot Price Tracker
Log daily depot prices. Get margin alerts when costs squeeze your profit.
Your Margin Alert
No depot prices logged yet.
🏪 Filling Station Customers
Track each customer's delivery history, outstanding balance and credit limit.
No stations added yet.
💳 Debtor Ledger
Track who owes you money, how much, and how old the debt is
🏭 Supplier Ledger
Track what you owe suppliers and upcoming payment due dates
📈 Profit & Loss Report
One-tap bank-ready financial statement for your business
Period:
🏢 Branch Management
Create and manage your business locations
No branches yet. Click + Add Branch to create your first location.
💰 Finance Tools
Tax calculator · Currency converter · Landed cost estimator
🧾 VAT Calculator All Plans
Loading tax rate...
📋 Withholding Tax (WHT)
👤 PAYE Calculator
Based on Nigeria PAYE tax bands (PITA): 7% · 11% · 15% · 19% · 21% · 24%.
Consolidated Relief Allowance (CRA) of ₦200,000 + 20% of gross income deducted first.
🏛 Company Income Tax (CIT)
Nigeria CIT rates: Small companies (turnover < ₦25M) = 0% · Medium (₦25M–₦100M) = 20% · Large (> ₦100M) = 30%.
Convert any currency to/from Naira
⇄
—
Calculate the true cost of imported goods in Naira
Step 1 — Goods Cost
Step 2 — Shipping & Freight
Step 3 — Customs & Clearing
Step 4 — Local Delivery & Other Costs
Step 5 — Selling Price Calculator
Fill in the fields above to calculate your landed cost
🛒 Current Sale 0 items
Tap a product to add it to the sale
Subtotal₦0
Discount (₦) VAT%
TOTAL ₦0
📋 Subscription & Account
👥 Staff Accounts
View, manage, edit roles and deactivate staff accounts
🏢 Business Profile
📤 Upload your data — Instead of typing one by one, upload a spreadsheet or use Quick Entry to add multiple transactions at once.

Upload File (CSV or Excel)
Upload a CSV or Excel file containing your income and expenses. Download the template first to see the correct format.
Column format for Transactions:
type, amount, category, note, date
type = income or expense  ·  amount = numbers only e.g. 150000  ·  date = YYYY-MM-DD e.g. 2026-05-15
📂
Drop your CSV or Excel file here
Accepts .csv · .xlsx · .xls · Max 500 rows
Quick Entry — Add Multiple Transactions at Once
Type or paste your transactions directly below. Add as many rows as you need. All will be saved at once.
TrackBizPro · Emalfred Digital · emalfred.com · Cloud Synced
Add Branch
Create Account
👥 Manage Team
➕ Add Staff Member
⚠ Required to keep you signed in as owner after creating the staff account.
Custom Sector
Manage Categories
New Transaction
New Client
New Invoice
Line Items
Total (excl. VAT)₦0
Process Sales Return
Invoice · Client:
Total Refund Amount₦0
Add Product
Record Stock Movement
Log New Trip
🚛 Record this trip's route, litres, costs and customer delivery to calculate your exact profit.
Litres
Revenue
Trip Costs
Register Truck
Maintenance Schedule
Add Driver
Log Depot Price
Add Station Customer
Sale Complete 🎉
Add Debtor
Record Payment Received
Add Supplier
Record Payment to Supplier