Three architectures: dispatch, document intake and material ledger
Most operational problems don't need fancy code — they need the database to refuse bad data before it gets saved. Below are reference PostgreSQL schemas, shown in full, and data flows for three common problems: scheduling field crews without double bookings, extracting incoming documents without duplicate entry, and tracking materials through an append-only ledger.
01 · Field dispatch board
Jobs come in by phone, email and web form. A dispatcher keeps the schedule in a spreadsheet or whiteboard, checks who's certified for what from memory, and finds out about a double booking when two technicians call from the same site — or none show up.
Data model (simplified)
| Table | Holds |
|---|---|
| technicians | Name, home base, active status |
| skills | Trade or certification: refrigerant handling, roofing, glazing, welding |
| technician_skills | Who holds which skill, and until when it's valid |
| sites | Customer, address, access notes |
| jobs | Site, required skill, time window, priority, status |
| assignments | Which technician does which job, from when to when |
| job_events | Every status change: who, when, from the office or the field |
The constraint that does the dispatcher's double-checking
A technician can't be booked for two overlapping jobs. The database rejects it via exclusion constraints, whatever the web interface or an import script attempts to write.
-- PostgreSQL Exclusion Constraint stopping double-booking
create extension if not exists btree_gist;
create table assignments (
id bigint generated always as identity primary key,
job_id bigint not null references jobs(id),
technician_id bigint not null references technicians(id),
starts_at timestamptz not null,
ends_at timestamptz not null,
cancelled boolean not null default false,
check (ends_at > starts_at),
exclude using gist (
technician_id with =,
tstzrange(starts_at, ends_at) with &&
) where (not cancelled)
);*Skill and certification checks execute in the same transaction: an assignment is refused if the technician does not hold the job's required trade skill, or if it expires before the job date.
How a job moves
Calls, emails and forms become one job record with site, required skill, and time window. Duplicate requests for the same site and day are flagged.
The dispatcher drags jobs onto technician timelines. Overlaps and missing certifications are refused with the reason shown on screen.
Technicians change status from their phone — en route, on site, completed — and attach job photos and customer notes directly.
A finished job records actual materials used (feeding the material ledger) and generates an invoice draft for office approval.
- Technician unavailable: reassigning a job keeps the complete assignment history; nothing is overwritten.
- Conflicting edits: two dispatchers moving the same job — the second save is refused with optimistic locking and the current state is refreshed.
- Spreadsheet imports: rows breaking validation rules land in an actionable review queue instead of corrupting the schedule.
02 · Document intake pipeline
Supplier invoices, customer POs, bills of lading and delivery receipts arrive as PDFs, scans and email attachments. Someone re-types them into the system, matches them to orders by eye, and occasionally enters the same invoice twice.
Data model (simplified)
| Table | Holds |
|---|---|
| documents | Source, file fingerprint (SHA-256), document type, status |
| extracted_fields | Field, value, page it came from, model and prompt version |
| validation_results | Each rule checked, passed or failed, and why |
| review_tasks | What a person needs to look at, who took it, the decision |
| posted_records | The invoice, order or receipt written to your system, linked to source file |
The constraints that stop duplicate entry
The same file is never processed twice, and the same supplier invoice number can't be posted twice. Likely duplicates go to review.
-- Deduplication by cryptographic file hash and supplier invoice number
create table documents (
id bigint generated always as identity primary key,
source text not null check (source in ('email', 'upload', 'api')),
file_sha256 text not null unique, -- the exact file cannot be processed twice
doc_type text,
status text not null default 'received'
check (status in ('received', 'extracted', 'validated',
'needs_review', 'posted', 'rejected')),
received_at timestamptz not null default now()
);
-- the same supplier invoice number can't be posted twice, even under a different file
create unique index one_invoice_per_supplier
on supplier_invoices (supplier_id, invoice_number);How a document moves
A shared inbox, upload folder or webhook receives the file. SHA-256 fingerprint checked first; duplicate stops here.
AI / OCR reads the document and returns typed fields — supplier, line items, totals. No production tables written yet.
Code validates arithmetic: line items match total, supplier exists, quantities and prices match PO within ±2% tolerance.
Discrepancies go to an operator with side-by-side original PDF preview. Operator corrections tune future extraction rules.
Validated records commit in one database transaction, permanently linked to the original file for instant audit drill-down.
- Unreadable scan: diverts to manual review with the extraction failure reason; no numbers are ever guessed.
- AI provider temporary outage: incoming files wait in a background queue with automatic retries until the provider responds.
- Model updates: regression benchmark of past verified documents runs before any new model or prompt version deploys.
03 · Material movement ledger
Stock levels live in a spreadsheet that someone edits by hand. Material goes to a job and isn't written off; a correction overwrites the old number; at month-end nobody can say where 40 sheets of steel or 15 rolls of TPO membrane went.
Data model (simplified)
| Table | Holds |
|---|---|
| items | Material, unit, unit conversions (sheet → m², roll → linear ft) |
| locations | Warehouse, shop floor, truck, job site |
| stock_movements | Every movement: item, location, quantity, reason, document, who, when |
| jobs | Where material was issued to, linking actual cost to bid |
| stock_counts | Physical counts, used to reconcile against the ledger |
The schema that keeps history honest
Append-only security: application roles have rights to insert movements, but update and delete permissions are revoked at the database level.
-- Append-only inventory ledger: changes are logged, never overwritten
create table stock_movements (
id bigint generated always as identity primary key,
item_id bigint not null references items(id),
location_id bigint not null references locations(id),
qty numeric(14,3) not null check (qty <> 0), -- positive: in, negative: out
reason text not null check (reason in
('receipt', 'issue_to_job', 'transfer', 'return', 'adjustment')),
job_id bigint references jobs(id),
document_ref text,
created_by bigint not null references users(id),
created_at timestamptz not null default now()
);
-- the application can only append records, never alter past events
revoke update, delete on stock_movements from app_role;
-- live stock on hand is computed dynamically
create view stock_on_hand as
select item_id, location_id, sum(qty) as on_hand
from stock_movements
group by item_id, location_id;*A transfer is recorded as two movements in a single transaction (negative at source, positive at destination). Material cannot disappear between locations.
How material moves
Delivery verified against the vendor purchase order. Short or over deliveries are immediately flagged.
Material written off to a specific job ID, so job cost includes actual materials pulled, not just theoretical takeoffs.
Warehouse truck site, each transit step timestamped with the driver or technician accountable.
Physical cycle counts compare to the ledger. Discrepancies generate adjustment entries requiring supervisor sign-off.
- Scheduled reconciliation check: item and location totals are recomputed from the full movement journal and checked against expected stock balances; alerts fire on any mismatch.
- Mandatory adjustment approvals: inventory write-offs require an explicit reason code and manager authorization; no quiet quantity changes.
- Estimate vs actual tracking: material consumed on a job compares against the estimated budget, exposing job site overruns before invoice closeout.
Five rules we apply to every system
The architectural foundation across every database, API gateway, and internal tool engineered by SolviLab.
The database enforces the rules, not just the screen.
Overlaps, duplicates and impossible values are refused by constraints, so an import, an integration or a bug in the interface can't get around them.
History is added to, never overwritten.
Anything involving money, stock or status keeps a full trail of who changed what and when. A mistake is fixed with a reversing entry.
Outside inputs are validated before they're saved.
Emails, files, AI output and third-party APIs land in a staging step first. Only checked data reaches your production records.
Every failure has a place to go.
Retries for temporary network errors, a review queue for anomalies. Nothing is silently dropped or lost in unmonitored logs.
Boring technology, sized to your load.
PostgreSQL does most of the heavy lifting. We add a queue, cache or extra microservice only when your actual volume requires it.
Built in your accounts. Yours to keep.
We build internal software on standard open-source foundations that live entirely on your infrastructure.
You own the code and the data
Repositories, database schemas, migrations and Docker setups live in your GitHub or GitLab and run on your servers or cloud account — Hetzner, DigitalOcean, AWS or your own VPS. Storage encryption and backups are configured on your infrastructure from day one.
No vendor lock-in
Open, widely supported technologies — Vue / Nuxt, Laravel, PostgreSQL, Docker — and documented REST APIs. Any competent in-house or outside team can maintain and extend the code without paying us monthly licensing fees.
NDA before your data
We sign a mutual NDA before you share internal documents, workflows or database access. Your proprietary business rules and customer records are never reused for another client or shown on this site.
Your blueprint comes out of the audit
In 5–7 working days we map how jobs, documents or materials move through your business today, and deliver four tangible assets:
Questions technical teams ask
Direct, verifiable answers about database schemas, migration code, load sizing, and team handovers.
Related systems & analysis
Roofing Takeoff Engine
Interactive calculation system showing live formulas, pitch factors, and material lists.
ServiceAI Workflow Automation
How the document intake and verification pipeline works for BOLs, invoices, and supplier orders.
StandardEngineering Methodology
How mistake-proofing and code-calculated rules are architected from initial scoping to production code.
Want to see what your data model would look like?
Book a 30-minute call. Tell us how jobs, documents or materials move today — we'll sketch the core tables and tell you honestly whether you need a custom system or a few fixes to what you have.
Prefer email? Reach us at info@solvilab.com
