ElectronERPFirebaseDesktop AppReact

How We Built PrintFlow ERP: Real-Time Firebase Sync Across 8 User Roles

H
Hammad Yousuf
2025-11-15

PrintFlow came to us with a familiar problem: their entire inventory, procurement, and production workflow was managed through a shared Excel spreadsheet. Multiple people editing the same file. No version control. No real-time visibility. No audit trail.

We built them a custom ERP in 4 months. Here is how.

Why Electron + Firebase?

The client had multiple workstations across different departments — procurement, production, delivery. They needed real-time sync: if the production manager marks a job card complete, the logistics team should see it instantly on their machine.

Firebase Firestore was perfect for this. Electron wrapped the whole thing into a cross-platform desktop app that works on Windows and Mac without any browser.

The Role System

Eight distinct user roles, each with a completely different view of the system:

  • Admin (full access)
  • Procurement Manager
  • Production Supervisor
  • Delivery Manager
  • Accountant
  • Warehouse Staff
  • Sales Representative
  • Viewer (read-only)

We implemented role-based routing at the app level. When a user logs in, Firestore returns their role, and the app loads only the modules they are permitted to access. Attempting to navigate to a restricted route redirects instantly — no server round-trip needed.

Real-Time Sync with onSnapshot

Every critical data view uses Firestore's `onSnapshot` listener instead of one-time `get()` calls:

const unsubscribe = db.collection('jobCards')
  .where('status', '==', 'in-production')
  .onSnapshot((snapshot) => {
    const jobs = snapshot.docs.map(doc => ({ id: doc.id, ...doc.data() }))
    setJobCards(jobs)
  })

// Cleanup on component unmount return () => unsubscribe() ```

When any workstation updates a job card, every other workstation sees the change within milliseconds. No polling. No manual refresh.

Cascade Updates

One of the trickiest requirements: if a product name changes, every related purchase order, delivery note, and job card across the entire database needs to update automatically.

We built a cascade utility using Firestore write batches:

async function cascadeProductUpdate(productId, newName) {
  const batch = db.batch()
  const affected = await db.collection('purchaseOrders')
    .where('productId', '==', productId).get()

affected.docs.forEach(doc => { batch.update(doc.ref, { productName: newName }) })

await batch.commit() // Atomic — all succeed or all fail } ```

Atomic batch writes guarantee consistency. Either all records update, or none do.

PDF Generation

Purchase orders and delivery notes are generated client-side using jsPDF, then printed via a hidden Electron window — the same silent printing pattern we use in our POS systems.

Lessons

  • Firestore onSnapshot is transformative for multi-user desktop apps
  • Design your role system before writing a single component
  • Cascade operations must be atomic — partial updates cause data corruption
  • Always give the user a loading state during Firestore writes, even fast ones

PrintFlow has been running in production for 8 months with zero data loss incidents. The client went from Excel chaos to a fully audited, real-time system that all 8 departments use daily.

H
Hammad Yousuf

Developer at Peacebox Studio, building games, ERP systems, and mobile apps since 2016.