SurfaceNet — Business Overview

SolidSurface.com · Brightpearl ERP mirror (surfacenet_admin)

loading live data…

Net sales, year-to-date

 

Like-for-like: both years measured through the same calendar day, so the partial year compares fairly.

Gross sales, all-time

 

Gross margin

 

Repeat customer rate

 

Phone vs. web AOV

 

Net sales by year

Gross margin by channel

Web (channel 2) Phone (channel 1) Blended

Why margin fell

Total blended decline

Within-channel erosion

Channel mix shift

Phone share of net sales

Methodology & caveats

Filter — matches Brightpearl

Every figure applies the canonical “Orders We Count” filter from queries/orders_we_count.sql: exactly the 8 statuses Brightpearl’s own order report uses — New web order, New phone order, Payment Pending, Review, Warehouse, Back order, Invoiced, On hold. Excluded: Cancelled, Quote sent, Draft / Quote, Quote Not Filled and Quote Filled.

Quote Filled is excluded because filling a quote creates a separate invoiced order for the real sale and leaves the original parked — so counting it would double-count. One further order (258180) is excluded as a confirmed duplicate of Invoiced 259048. Accrual basis: invoiced-but-unpaid orders do count.

Verified against the native Brightpearl report for June 2026: this filter reproduces it exactly — 386 orders / $437,886.98.

Definitions

  • Revenue = Net Sales + S&H, which is Brightpearl’s “Net Revenue” (product + shipping, excluding tax). Bare Net Sales omits shipping and runs low. Gross Revenue = that plus tax = OrderTotalAmount.
  • Gross margin is (Net Revenue − Extended COGS) / Net Revenue. Note this is Brightpearl’s own convention: shipping revenue is in the numerator but shipping cost is not in Extended COGS, which flatters margin by roughly 3–4 points. It ties to their reports; it is not a true product margin.
  • The order date is NOT KeyedDate:. That column holds the UTC date, so orders placed 17:00–23:59 Phoenix are stored a day late — it matches Brightpearl for only 90.2% of 2026 orders. We derive the true Phoenix date from bp_orderlines.WarehouseDate: (which the export populates with Brightpearl’s createdOn — the very field their report filters on), shifted UTC−7. Accuracy: 99.92%, zero wrong derivations. Brightpearl’s account timezone is America/Phoenix (UTC−7, no DST), not Pacific.
  • AOV is net sales ÷ order count.
  • The margin decomposition is revenue-weighted: the counterfactual holds the 2020 channel mix and substitutes 2025 per-channel margins.

Things to know about this data

  • 2026 is a partial year — through 16 Jul. It is charted but tagged “YTD”, and the hero comparison is cut to the same calendar day across years.
  • The mirror effectively starts in 2020. Five orders sit in 2008 ($1,816 gross) and there are zero orders from 2009 to 2019. Those five are excluded from the charts (they remain in the all-time totals). Treat “all-time” as “2020 onward” plus a rounding error.
  • Repeat rate is 25.3%, not the ~22.7% baseline. This is a real discrepancy and I could not reproduce 22.7%. Computed by distinct Customer ID: over the counted set, the rate is 25.25% (4,898 of 19,396 customers with more than one order). Variants tried: excluding partial 2026 → 25.20%; dropping the canonical filter → 24.80%; web-only → 23.98%. None land on 22.7%, so the baseline likely used a different customer-identity definition (email or company rather than Customer ID) or a cohort window. Worth reconciling before this number is quoted externally.
  • An undocumented channel 3 exists — 5 orders, $1,816, all in 2008. CLAUDE.md documents only 1 = phone and 2 = web. Immaterial to every figure here, but real.
  • IsCustomersFirstOrder: is empty on every counted row — it looks like a field the mirror never populated. Don't build on it.
  • On bp_orderlines, UnitPrice:, Quantity, Net Sales and Extended COGS are varchar, not numeric. Line-level arithmetic needs CAST. The bp_orders money columns are proper decimals, and this dashboard is built entirely from the header table.
  • Because the mirror is a faithful copy, it is corrected at the query layer, not by rewriting stored data — nothing downstream (the Laravel app, the BPDATA→RJMetrics export) is affected by these adjustments.

Source

Queries: queries/business_overview.sql. Read-only via the MySQL MCP connector. Generated 16 Jul 2026.