Back to projects
Live SaaS

Print Prima — Print Shop Management SaaS

Quote-to-Cash, Production Queues & Printer Audit for Large-Format Print Shops

LaravelPHP 8.2LivewireMySQLSaaSSubscription BillingRBACMulti-Branch
Invoice Errors
-98%
Job Turnaround
50%+faster
Full Onboarding
14days
Subscription Plans
5tiers

Print Prima is a cloud SaaS that runs an entire large-format print business from one dashboard: quotes and invoices, order and job flow, inventory and stores, fixed assets, payroll, expenses, staff permissions, and a print audit that reads job data straight from the printers. It is built for shops ranging from a few machines to multi-branch operations, with Nigeria-ready taxes and NGN pricing alongside USD, and is sold on tiered plans gated by the number of printers monitored.

Architecture & System Design

Built on Laravel with Livewire-driven dashboards. Every shop is an isolated account with its own branches, staff roles, and plan limits. A quote becomes an invoice, then a production job that moves through a machine queue; material usage is deducted from inventory as the job runs, and what the printer actually reports is reconciled against what was invoiced to surface unbilled prints.

Request Flow
Quote / Invoice (Front Desk)
Proof Approval
Job Queue & Machine Assignment
Printer Audit Capture
Inventory Deduction
Invoice-vs-Print Reconciliation
Branch KPI Roll-Up
  • Role-based views for Front Desk, Production, Finance, and Management over the same underlying job data
  • Print audit capturing dimensions, bitmaps, status, and timings from each printer for every job
  • Plan-level feature gating — printer monitoring limits of 3, 6, 10, or unlimited per subscription tier
  • Multi-branch access with activity trails and KPI roll-ups across locations

Engineering Challenges

Catching Unbilled Prints (Theft Prevention)

Problem

In a print shop, staff can run jobs on a machine that never appear on an invoice. Owners had no way to compare what was printed with what was sold.

Solution

Built a print audit pipeline that records dimensions, preview bitmaps, status, and timings from each printer, then matches every captured print against an invoiced job. Prints with no matching job, or with a size that doesn't match the order, are flagged as anomalies for management.

Result

Owners get a real-time view of every print per machine, with unmatched jobs surfaced instead of discovered at stock-take.

One Workflow from Quote to Cash

Problem

Quotes, production, stock, and payments typically live in separate spreadsheets and paper job tickets, so invoices get re-typed and jobs get lost between front desk and production.

Solution

Modelled the order as a single record that changes state — quote, invoice, proof approval, queued, printing, done, paid — so each team works on the same job from its own role-based view, with automated follow-ups on unpaid invoices.

Result

Shops on the platform report 98% fewer invoice errors and 50%+ faster job turnaround.

Subscription Tiers with Hard Limits

Problem

Plans differ by printers monitored and feature set (Base Pack has no monitoring; Starter, Pro, and Pro Plus allow 3, 6, and 10 printers), billed monthly, quarterly, or yearly in USD or NGN.

Solution

Centralised plan entitlements in one service that every module checks before enabling a feature or registering a printer, so limits are enforced server-side rather than hidden in the UI.

Result

Five tiers and three billing cycles run from a single entitlement layer, and upgrading a plan unlocks capacity immediately.

Core Features

Invoices & Quotes

Quote-to-invoice conversion with automated follow-ups, collections tracking, and Nigerian tax and NGN support.

Clean Code

PrintAuditService — Matching Printer Output to Invoiced Jobs
1<?php
2
3namespace App\Services;
4
5use App\Events\PrintAnomalyDetected;
6use App\Models\PrintJob;
7use App\Models\PrintLog;
8
9class PrintAuditService
10{
11 private const AREA_TOLERANCE = 0.05; // 5% over the invoiced size
12
13 public function reconcile(PrintLog $log): void
14 {
15 $job = PrintJob::where('printer_id', $log->printer_id)
16 ->where('file_name', $log->file_name)
17 ->whereIn('status', ['queued', 'printing'])
18 ->first();
19
20 if (! $job) {
21 $this->flag($log, 'unbilled_print');
22 return;
23 }
24
25 $allowedArea = $job->width * $job->height * (1 + self::AREA_TOLERANCE);
26
27 if ($log->width * $log->height > $allowedArea) {
28 $this->flag($log, 'oversized_print', $job);
29 }
30
31 $log->update(['print_job_id' => $job->id]);
32 }
33
34 private function flag(PrintLog $log, string $reason, ?PrintJob $job = null): void
35 {
36 $log->update(['anomaly' => $reason]);
37
38 event(new PrintAnomalyDetected($log, $job));
39 }
40}