Skip to content

The counter doesn't have the seconds a search field costs, so the scanner is the input method

An invoicing app for a trade counter where the barcode scanner is how lines get added (the main path, not a shortcut beside a search box) and an invoice can be finished and reprinted from the phone that raised it.

10:42
LinedropDelgado Mechanical
0 78491 10228 4 Read · added to the invoice
Label won't read? Swipe up to type 4,182 lines on device
INV-20418Delgado Mechanical · acct D-0417$115.685 lines · incl. tax
Just now · PTFE thread tape, 1/2" × 520"Same code again: qty 5 → 60 taps0 network
6PTFE thread tape, 1/2" × 520"TT-050-520 · $0.89 ea$5.34
2Ball valve, 3/4" full port, brassPrice 09/12 · BV-FP-075 · $14.75 ea$29.50
4Push-fit coupling, 1/2"PF-CPL-050 · $6.15 ea$24.60
+ 2 more lines · swipe up for the invoice
TypeFinish · $115.68

The shape of the work

Industry
E-commerce & Retail
Duration
10 weeks
Cooperation model
Fixed price
Services
Mobile engineeringInvoicingDevice integration
Integrations
Barcode scanning SDKReceipt printer over BluetoothCatalogue sync APICrash reporting
Technologies
React NativeExpoTypeScriptSQLiteREST API
Team
1 Project lead1 Mobile engineer1 Backend engineer

Client name withheld under NDA. Engagement details are shown to the extent our agreement permits.

Introduction

What we were brought in to do

The wholesaler serves trade customers over a counter. We built the app their staff raise invoices on: scan a code, the line appears, finish the invoice, find it again in history. The product list lives on the device, so a weak signal never stops a sale.

A trade wholesaler serving customers over a counter, where staff raise invoices standing up with a queue forming behind whoever is being served. The engagement was commissioned after a stocktake week in which counter throughput was measured properly for the first time, and the app in use turned out to be the slowest step in the transaction.

Mobile Engineering

The problem

Where the old way broke

Their previous app had a search field with a scan button beside it. Staff used the search field, because the scanner took two taps to reach and failed silently on a worn label. Each line took long enough that a queue formed at busy times, and the app got blamed for the queue.

Two taps is enough to make a feature disappear. Staff had learned the fast path was typing, and typing a product code at a counter takes long enough per line that a five-line invoice holds up a queue. The scanner also resolved against the server, so on a weak signal it added a wait to a path that was already losing.

Open the app on the scanner. A scanned code resolves against a product list held on the device and lands straight on the invoice; search still exists for labels too scuffed to read. The slow path is now the exception.

The app opens on the scanner

Scanning is the default input. Nobody has to reach past a search field for it.

The app opens on the camera with the scanner live. Scanning is the screen itself, because on a busy counter the input that takes an extra tap is the input nobody uses. Search still exists for scuffed or missing labels, one gesture away, but it's now the exception where it used to be the default.

What shipped
  • Opens on a live scanner, not on a search field
  • Search is one gesture away, as the exception
  • The default input is the fast one
10:42
Scanner paused while you typeSwipe down and it's live again
elbow
7 of 4,182 linesOn device0 network
For a scuffed label · tap the one in your hand
Copper elbow 90°, 3/4"CU-E90-075 · bin A4-12 · 142 in stock$2.84Add
Copper elbow 90°, 1/2"CU-E90-050 · bin A4-11 · 305 in stock$1.68+
Copper elbow 45°, 3/4"CU-E45-075 · bin A4-14 · 64 in stock$3.12+
Copper street elbow 90°, 3/4"CU-S90-075 · bin A4-15 · 48 in stock$3.56+
PEX crimp elbow, 1/2" brassPX-CE-050 · bin C2-06 · 520 in stock$1.38+
Push-fit elbow, 1/2"PF-E90-050 · bin B2-08 · 71 in stock$9.22+
PVC DWV elbow 90°, 1-1/2"DR-E90-150 · bin D3-02 · 133 in stock$2.14+
Search is the exception. A readable label never needs this screen.
On screen

The escape hatch for a scuffed label: the scanner paused one swipe away, and a typed search for “elbow” resolving against the 4,182 lines held on the device.

10:42
INV-20418Delgado Mechanical · acct D-0417 · opened 10:36 AM
No signal · every line resolved on this phone4,182 lines in on-device SQLite · refreshed 7:58 AM
Lines · 5Edit
3Copper elbow 90°, 3/4"CU-E90-075 · 3 × $2.84$8.52
1PEX-B tubing, 1/2" × 100 ftPX-B050-100 · 1 × $38.90$38.90
4Push-fit coupling, 1/2"PF-CPL-050 · 4 × $6.15$24.60
6PTFE thread tape, 1/2" × 520"TT-050-520 · 6 × $0.89$5.34
2Ball valve, 3/4" full port, brassBV-FP-075 · 2 × $14.75Price from 09/12 record · refresh pending$29.50
Subtotal$106.86Sales tax 8.25%$8.82Total$115.68
Scan moreFinish & print
On screen

Lines on the invoice with no signal at the counter: every line resolved from the on-device catalog, and the ball valve's price flagged as coming from a stale local record instead of used silently.

Catalogue held locally

A scan resolves against on-device data, so a weak signal at the counter doesn't slow a sale.

The product catalog is held in SQLite on the device and refreshed in the background, so a scan resolves locally in milliseconds whatever the signal at the counter. Prices and stock reconcile when a connection is available, and an item whose local record is stale says so on the line instead of silently invoicing an old price.

What shipped
  • Catalog in on-device SQLite; scans resolve in milliseconds
  • Background refresh when a connection exists
  • Stale prices flagged on the line, never used silently

Rescan increments

Scanning the same item twice raises the quantity, so there's no second line to tidy up later.

Scanning the same item twice increments the quantity on the existing line instead of adding a second line to merge later, because a counter scanning six of one thing wants a line that says six. Decrement and remove are both reachable from the line without leaving the scanner, so a correction doesn't cost a mode change.

What shipped
  • Rescan increments the existing line
  • Corrections reachable from the line, without leaving the scanner
  • No tidying pass at the end of an invoice
10:42
LinedropDelgado Mechanical
0 78491 20331 8 Same code · line raised, not repeated
INV-204183 lines · still 3No duplicate line
Copper elbow 90°, 3/4"CU-E90-075 · $2.84 ea$8.5223−3+
1PEX-B tubing, 1/2" × 100 ftPX-B050-100 · $38.90 ea$38.90
4Push-fit coupling, 1/2"PF-CPL-050 · $6.15 ea$24.60
Subtotal before$69.18After$72.02+1 × $2.84exact to the cent
TypeFinish · $77.96
On screen

The same code scanned twice: the copper elbow line goes from 2 to 3 instead of gaining a duplicate, with decrement and remove on the line and the subtotal exact to the cent.

10:42
0 84512 30917 6Read, but not on any product
Unrecognised code0 84512 30917 6What are you holding? Attach it once and it scans from now on.
expansion tank
3 products · searched on device
Expansion tank, 2 galWH-XT-020$39.60
Expansion tank, 4.4 galWH-XT-044$58.75
Expansion tank, 2 gal, lead-freeWH-XT-020L$44.80
Maps for good. Not a note for the office: the next scan of this code adds WH-XT-020 with no taps.
CancelAttach & add line
On screen

An unrecognized code: the scanner reads it, finds no product, and opens the attach flow so the person holding the carton picks the product and maps the code for good, with nothing sent to the office.

Unknown codes get attached

An unmapped barcode opens a short attach flow, so the mapping is fixed by whoever is holding the item.

An unrecognized barcode opens a short attach flow right there: pick the product, confirm, done. The mapping is fixed permanently by the person actually holding the item, the only one who knows what it is. The alternative, a note for someone in the office to resolve later, is how barcode catalogs stay broken for years.

What shipped
  • Unknown codes open an attach flow immediately
  • Fixed by the person holding the item, not queued for the office
  • The mapping is permanent from the first occurrence
10:42
HistoryHeld on this phone · Counter 22 not yet synced
Customer, date or amount
TodayCustomerAmountThis week
Thu 09/17 · 7 invoices
Ruiz Plumbing & HeatingINV-20417 · 7 lines · 10:21 AM$284.16On device
Oakbrook Property MgmtINV-20416 · 3 lines · 10:02 AM$68.93On device
Delgado MechanicalINV-20415 · 12 lines · 9:47 AM$612.40 Synced
Cash saleINV-20414 · 2 lines · 9:31 AM$18.54 Synced
Hartwell BuildersINV-20413 · 9 lines · 9:08 AM$431.77 Synced
T. Nguyen Service Co.INV-20412 · 4 lines · 8:44 AM$97.22 Synced
Ruiz Plumbing & HeatingINV-20411 · 6 lines · 8:15 AM$215.30 Synced
HistoryScanCatalogue

History at the counter

On screen

Invoice history held on the phone: today's invoices with customer, lines, time and amount, reprint on every row, and two invoices not yet synced that are still here to find.

Finished invoices stay on the device, so a reprint is a tap instead of a call to the office.

Completed invoices stay on the device with their line items, so a reprint or a lookup is a tap at the counter instead of a phone call to the office and a wait. History is searchable locally by customer, date and amount, and syncs upward without the local copy depending on that sync having happened.

What shipped
  • Completed invoices retained locally with their lines
  • Reprint and lookup are a tap, not a phone call
  • Local history doesn't depend on the sync having run

What we built together

05
  1. 01

    Made the scanner the screen the app opens on, not a button next to search

    The app opens on a live scanner, with no search field standing in front of it, because the input two taps away is the one nobody uses.

  2. 02

    Held the product list on the device so a scan resolves without waiting on the network

    The product catalog is held in on-device SQLite and refreshed in the background, so a scan resolves in milliseconds whatever the counter's signal.

  3. 03

    Kept manual search for damaged labels, as an escape hatch off the main path

    Manual search remains one gesture away for scuffed labels, as the escape hatch it should always have been.

  4. 04

    Made an unrecognized code offer to attach itself to a product instead of just failing

    The attach flow exists because the alternative, a note for the office, is how barcode catalogs stay broken for years at a time.

  5. 05

    Kept finished invoices on the device so history and reprint work at the counter

    Completed invoices stay on the device with their lines, so a reprint or lookup is a tap instead of a call to the office.

Operational results after launch

0

Taps to add a scanned line

Scanner

Screen the app opens on

0

Network calls per scan

None

Unknown codes reported to office

Taps to add a scanned line is zero: the scan is the action. Screen the app opens on is a fact about the launch route. Network calls per scan is zero, because resolution is local. Unknown codes reported to office is none: the attach flow resolves them at the counter.

Client name withheld under NDA. Figures are approximate, drawn from the engagement’s own reporting.

Process

Phase by phase

  1. Phase 1: The Product List

    On The Device

    Synced the catalog to local storage so a scan resolves without a round trip. At a counter, the difference between instant and one second is whether staff trust the scanner at all.

    • Local Catalogue
    • Sync
    • Barcode Index
  2. Phase 2: Scan To Line

    The Default Path

    Built the scan screen as the app's landing surface. A recognized code adds its line immediately; scanning the same code again increments the quantity instead of adding a duplicate.

    • Scan Screen
    • Line Add
    • Quantity Increment
  3. Phase 3: Unknown Codes

    Offer, Do Not Fail

    Made an unrecognized barcode open a short attach flow, so an unmapped code gets mapped by the person holding the item, with nothing reported to the office.

    • Unknown Code Flow
    • Attach To Product
    • Confirmation
  4. Phase 4: Invoice And History

    Finish And Find Again

    Built invoice completion, on-device storage and the history view, so a customer asking for a copy gets it at the counter, no call to the office needed.

    • Invoice Completion
    • Local History
    • Reprint
10:42
CatalogueKessler Trade Supply · Counter 2Linedrop
Held on this phone4,182lines · SQLite, on this phoneBarcode index currentRefreshed 7:58 AM
No signal right nowScans, search and history carry on. Refresh runs in the background when a connection returns.
Reconcile & sync
PricesReconcile when a connection existsWaiting for signal
Stock on handReconcile when a connection existsWaiting for signal
Barcode indexIncludes codes attached at this counterCurrent
Finished invoicesSync upward; local copy kept regardless2 to send
Price from 09/12 recordAn out-of-date local record says so on the invoice line. An old price is never used silently.
HistoryScanCatalogue
On screen

The catalog on the device: 4,182 lines in SQLite with a current barcode index, prices and stock waiting to reconcile until signal returns, and the stale-price flag explained.

About our collaboration

  1. 01

    We spent two mornings behind the counter before writing anything, and that's where the core decision came from. Staff weren't avoiding the scanner because it was unreliable; they avoided it because reaching it cost more than typing did. The fix was placement, not accuracy.

    Ten weeks, fixed, with counter staff using builds from week three at real trading times, never in a quiet demo. The rescan-increments behavior came from watching that: staff scanning six of one item were producing six lines and tidying up afterward, which nobody had reported because it didn't feel like a bug.

What it settled

What we'd carry into the next one

  • A feature two taps from where work happens is a feature that doesn't exist.

    A feature two taps from where the work happens may as well not exist, however much better it is than the alternative staff actually reach for.

  • Resolve scans against on-device data. At a counter, a one-second wait is enough to lose staff trust in the scanner.

    At a counter with a customer waiting, a one-second network wait is enough for staff to stop trusting the scanner and go back to typing.

  • Rescanning should increment, because the alternative is duplicate lines somebody tidies up later.

    Rescanning has to increment. Otherwise you get duplicate lines that somebody tidies at the end: a step nobody reports and everybody resents.

  • An unknown barcode is best fixed by the person holding the item. An office queue is where it stays unknown.

    The person holding the item is the only one who knows what an unknown barcode is, so routing it to an office queue guarantees it stays unknown.

Scan simulator, no signal

The scan is the action. Four things that happen at a counter, and none of them waits on the network.

A readable code, the same code again, a code nobody has mapped yet and a label too worn to read. Each one resolves against the catalogue on the phone, and the invoice lines change the way a counter needs them to. Pick one, or use the arrow keys once a tab is focused.

Counter 2 · no signal · catalogue of 4,182 lines on the phone

Step timings are illustrative, not measured

Invoice lines3 lines
  • 1PTFE thread tape, 1/2" × 520"TT-050-520 · $0.89 ea · new line$0.89
  • 1PEX-B tubing, 1/2" × 100 ftPX-B050-100 · $38.90 ea$38.90
  • 4Push-fit coupling, 1/2"PF-CPL-050 · $6.15 ea$24.60
Subtotal$63.50$64.39
  1. Camera reads the label0 78491 10228 4
  2. Barcode index on the phoneMatched TT-050-520 among 4,182 lines
  3. No line for it yetLine added at qty 1
  4. Nothing sentThe lookup never left the device
Taps to add0the scan is the action
Network calls0this scan
Sent to officeNoneunknown codes

The line is on the invoice before anyone touches the screen.

Architecture

From a code under the camera to a line on the invoice, with no network in between

React Native with Expo, TypeScript, on-device SQLite and a REST API behind it. The network sits at the edge of the diagram on purpose: at a counter, a scan that waits on signal is a scan staff stop trusting.

  1. 01 · Trigger
    The scanner is the launch screenThe app opens on the live camera. Scanning is the screen itself; typed search is one gesture away for scuffed labels.
  2. 02 · Resolve
    Barcode index on the phoneA code resolves against the local index with zero network calls. An unrecognized code opens the attach flow instead of failing silently.
  3. 03 · Engine
    Scan to lineA recognized code adds its line with no taps; the same code again increments the quantity. A stale local price is flagged on the line.
  4. 04 · State
    SQLite on the deviceThe catalogue and every finished invoice with its lines are held locally, so history, lookup and reprint work at the counter.
  5. 05 · Sync
    REST API, in the backgroundThe catalog refreshes, and prices and stock reconcile, when a connection exists. Invoices sync upward; the local copy never waits on it.

Right lines, a working counter, no lost codes

Rescans that increment, scans that never wait on signal & unknown codes fixed at the counter

No duplicate lines, no silent old prices

Scanning the same item again increments the existing line instead of adding a second one to tidy later, and decrement and remove sit on the line without leaving the scanner. A product whose local record is stale says so on the line, so an old price never gets invoiced silently.

No signal doesn't mean a stopped sale

The catalog is held in SQLite on the phone, so a scan resolves with zero network calls whatever the signal at the counter. Finished invoices stay on the device with their lines, so history and reprint keep working, and the local copy never depends on the upward sync having run.

An unknown code gets attached on the spot

An unrecognized barcode opens a short attach flow on the spot: pick the product, confirm, and the mapping is fixed permanently by the person holding the item. It never becomes a note for the office, which is how barcode catalogs stay broken for years.

Is a queue forming at your counter while staff type product codes into an app? Scope your build in three minutes.

Scope your build
Have a project?

Let's talk

Running a large platform, shaping a first MVP, or getting a product ready for a funding round? Tell us where you are. We'll shape the process around it, and stay with you after launch.