Set up needed

The Supabase credentials haven't been configured yet. Open this file and fill in two values near the top of the script:

const SUPABASE_URL = "https://YOUR-PROJECT.supabase.co";
const SUPABASE_ANON_KEY = "your-publishable-or-anon-key";

Then re-deploy the file. The full setup guide lives in SETUP-cloud-inventory.md next to this file.

sixforty

Sign in to HQ

Use the email Leo invited you with.

Access is invite-only. Ask Leo to add you.
sixforty HQ
Work
Stock
Money
Insight

Tasks

Connecting...

Stock value, top 12 SKUs (at cost)

Inventory

Columns

No inventory yet

Stock, prices and variants arrive from Shopify every night. Use Add product if you need one before then.

Recently deleted (0)
SKUProductDeletedActions

No recently deleted rows.

Sales performance

Weekly trend

Top sellers

Ranked by units sold
#ProductUnitsRevenue

Velocity

Units per week, weeks of cover
#ProductUnits/wkOn handCoverPace

Margin leaders

Most profit earned in this period
#ProductUnitsMargin/unitTotal marginMargin %

Customer economics

ARPU, CAC, LTV and repeat rate

Top customers

Ranked by revenue
#CustomerOrdersRevenueAOV

Geography

Where the orders ship
Country / CityCustomersOrdersRevenue

No sales yet

Orders arrive from Shopify every night. Analytics fill in as soon as the first paid order lands.

Spend & reconciliation

Transactions

DatePayeeDebitCreditAmountSourceAllocated

No transactions yet

Bank transactions arrive from Monzo as they happen. Use Add transaction for anything that never touched the bank.

Financial statements

Fiscal year start (click to edit)
Used for YTD calculations. All balances are derived from the ledger; no manual opening balances needed.

Marketing performance

Spend over time

Campaigns

Campaign Period Spend Impressions Reach Clicks CTR CPC CPM Conv. Conv. value Meta ROAS

No marketing data yet

Export a campaign report from Meta Ads Manager as CSV and use Import CSV above. The columns are detected automatically.

Ledger sanity check

Assets must equal Liabilities + Equity

Bank reconciliation

Ledger Bank balance at end of month vs Monzo statement balance
Add or correct a Monzo balance point

Sales reconciliation

HQ sales for the month vs Shopify-reported revenue for the same month
Add or correct a Shopify snapshot

Inventory valuation (subledger to ledger)

Stock on hand at cost by product, tied to the Inventory (1200) ledger balance

Inventory units (HQ vs Shopify)

HQ-derived units (opening + received - sold - written off) per product vs Shopify on hand. Negative derived means receipts not yet allocated; large positive gap means off-Shopify sales or an overstated receipt.

Owner loan position

Outstanding balance owed to you by the business

Marketing reconciliation

Meta-reported ad spend vs ledger marketing transactions

Shipping cost recovery

Postage charged to customers vs courier cost in the ledger

Sales coverage

Every sale and whether its units, cost and revenue are captured correctly

Revenue by product

Shopify sales (web + POS)

Grouped by order. Units and cost come from the sales table; revenue arrives via Stripe payouts in the ledger.

Direct sales (no Shopify order)

Bank-paid sales that predate POS. Revenue is the ledger transaction; units and cost need an "Add sale" allocation.

Stripe payments with no sale

Payment-link charges that don't yet match a Shopify order. Each is a sale to enter, the payment -> order half of the reconciliation.

Payment exceptions

Orders booked as sales but not marked paid in Shopify, and Shopify orders not yet imported into HQ. Each needs a call from you or George.

Affiliate summary

All-time totals across every discount code

By affiliate code

Net sales and commission due per code. Set the rate to match each affiliate's agreement.

Recent activity

WhenWhoActionTargetChanges

No activity yet

Every change you make to inventory or sales will appear here.

Backups

Save snapshot stores the current inventory and sales in a rollback table, useful before a stock count, a big price change, or a bulk edit. Export books downloads every table (the full ledger: transactions, allocations, splits, batches, plus sales, inventory and the reconciliation feeds) as CSVs in a zip; keep a copy somewhere outside Supabase. Supabase also takes daily automatic Postgres backups, so this is belt-and-braces.
All clear. Every payment ties to a sale and every sale to a payment, nothing needs attention right now.

Bank transactions needing a category

Auto-imported from Monzo but the payee wasn't recognised, so booked to Uncategorised (9998). Categorise each one, and the rule learns nothing yet, so tell Leo if a payee recurs.

Sales with no Shopify match

Bank payments (excluding Stripe) with no matching Shopify order, enter in POS or allocate

Stripe payments not in Shopify

Stripe charges with no matching Shopify order, enter the sale in Shopify

Orders booked but not paid in Shopify

A sale is recorded in HQ but Shopify has not marked the order paid, confirm it was collected or fix the order

Shopify orders not imported to HQ

Paid Shopify orders with no sale lines in HQ yet. The nightly sync books these; if one stays here after a sync, tell Leo.

Sales with no payment recorded

A sale is booked but no payment is in the ledger yet (no Stripe charge, not card-at-checkout, no matching bank deposit). Often a recent cash or POS order whose deposit has not reached Monzo yet.

Products needing a cost

Cost is £0, so sales of these post no COGS

Inventory purchases not fully allocated

Spend on Inventory (1200) not yet assigned to products

Purchases & returns not assigned to a batch

Assign each payment to a batch so FIFO can cost it. Receipt carries the units; extra payments go on the same batch as cost-only (quantity 0). Inventory returns (money back, "(inventory return)") attach to the batch the stock came from and reduce it.

Inventory stock gaps (Shopify vs HQ)

Every product where HQ's derived stock (opening + purchases, less sales and write-offs) differs from Shopify's on-hand at all. A positive diff means HQ shows more than Shopify (often gifts/samples that left unrecorded); a negative diff means Shopify shows more (often an unrecorded receipt or a count difference). Monitoring only, not counted as an action.

How the books flow

What happens at each stage, and the two steps you need to do by hand
You do this Automatic in HQ 1 · Inventory cost → COGS Pay supplier for stock Dr Inventory (1200) · Cr Bank (1010) Arrives from Monzo automatically AUTO Allocate the spend to product(s) Expand txn → pick product + batch, qty, £ Product needs a SKU + handle YOU Product cost updates FIFO: each batch keeps its own cost AUTO A sale books the cost Dr COGS (5000) · Cr Inventory (1200) HQ recosts itself and flags when it cannot AUTO 2 · Revenue → Inventory Money in from a sale Dr Bank (1010) · Cr Sales (4000) Stripe payout or direct transfer AUTO Record the sale in Shopify Web order, or enter it in POS "Add sale" only if never in Shopify YOU Stock drawn down + COGS Dr COGS (5000) · Cr Inventory (1200) AUTO Check the reconciliations Sales & units should tie (green) Revenue stays on the transaction YOU

The two jobs that are yours

Plus one habit: stock lives in Shopify

1. Allocate inventory purchases to a batch. When money goes out to a supplier (Dr Inventory), open that transaction in Transactions, expand it, then under "Allocate" pick the product, choose or create a batch (one production run), and enter the quantity received and total spent. A second payment for the same batch (deposit/balance, shipping, duty) goes on that batch as a cost-only line (quantity 0), so it adds cost without inflating units. A supplier refund is recorded as a return against the batch it came from. Cost is then derived per batch using FIFO (oldest batch sold first) and is read-only, you never type a product's cost by hand. The product must already exist with a SKU and a handle.

2. Record the sale in Shopify. Every sale should go through Shopify, a web order, or one George enters in Shopify POS. The units and cost then come into HQ from the Shopify order. The "Add sale" allocation is only a fallback for older sales that were never put through Shopify (it records the units and cost against the bank payment). Use it for the historical backlog, not day-to-day; a sale should never be in both Shopify and an allocation.

Keep stock in Shopify. Every night HQ copies stock levels, prices and variant names from Shopify, so any change you make to those in HQ is overwritten by morning. Adjust counts in Shopify and let HQ follow. One delivery of shirts is one batch, however many payments it took: the first payment carries the units, later payments for the same shirts go on the same batch with quantity 0.

The golden rule: revenue is always recognised from the bank ledger (a credit to Sales 4000). Allocations only add units-out and COGS, they never touch revenue, so nothing gets double-counted.

How each page works

The notes that used to sit at the top of each page
How data gets into HQ
Almost everything arrives on its own. Every night at 03:30 UTC HQ pulls from Shopify: every paid order becomes sale lines (unpaid or pending orders are not booked until Shopify marks them paid), and every product's stock, price and variant names are copied across, so Shopify is the place to change stock. Stripe payments are pulled in the same run. Monzo transactions arrive within seconds of happening and are booked using payee rules; the nightly run also re-checks the last five days and records the closing bank balance. Cost is never imported: it comes from the purchase allocations you make in Transactions. Import CSV (in Inventory, Transactions and Marketing) is now a fallback. It still recognises a Shopify products or orders export, a Monzo statement (balance points only) and a Meta Ads report, and skips anything already in HQ, so re-importing is safe.
How transactions work
Every transaction is a double-entry journal. Money flows from one account (credited) into another (debited). Buying inventory: Debit Inventory, Credit Bank. Paying for marketing: Debit Marketing, Credit Bank. A refund reverses the direction. Monzo transactions arrive automatically and are categorised by payee rules (Evri to Courier, Shopify to Software, Stripe payouts to Sales, and so on). Anything the rules do not recognise is booked to Uncategorised (9998) and shows up in Tasks for you to recategorise; tell Leo when a new payee recurs and a rule will be added. Inventory purchases need one more step from you: expand the transaction and allocate it to a product and batch (see "The two jobs that are yours" above). New accounts are added by Leo in the database and appear in the dropdowns straight away.
How marketing import works
Marketing is the one feed that is still manual. Export campaign performance from Meta Ads Manager (or Google Ads) as a CSV, then use Import CSV on the Marketing page. Sixforty auto-detects Meta exports by their column headers (Campaign name, Amount spent, Reporting starts, Reporting ends, Impressions, Reach, Clicks, CTR, CPC, CPM, Frequency, Results, Purchases conversion value). One CSV row becomes one campaign-period record. Re-imports for the same campaign and date range update in place. Blended ROAS below is computed against actual Shopify revenue in the same period, so you can compare Meta's reported attribution to what hit the till.
How reconciliation works
Each panel below checks a specific consistency rule in your books at a chosen as-of date. Green means the numbers match, amber means there's a gap worth investigating. Bank balances come from Monzo each night (yesterday's closing balance) and the ledger is compared with them; you can still add a balance point by hand for an older date. Shopify revenue snapshots for the current and previous month are written by the nightly sync, so the sales reconciliation needs no input from you. Stock units are compared with Shopify's live count after each nightly pull.
How the Sales tab works
Revenue by product at the top answers "what did each product earn?": pick a period and channel (web checkout against in-person and other), then click a product for its sizes, months and top customers. Refunded items are excluded. Sell-through (sold against what is still on the shelf) and GMROI (annualised margin earned per pound of average stock) show which products earn their shelf space; tick Show unsold to see stock that is not moving. Export gives the table as a CSV. Below that is a two-way reconciliation: every sale should tie to a payment, and every payment to a sale. Order -> payment: each Shopify order shows how it was paid, a Stripe payment link, a direct bank credit, or a card sale settled inside a bundled payout (reconciled in aggregate, not per order). Payment -> order: "Stripe payments with no sale" and direct sales not yet allocated are payments still waiting on a sale to be entered. Each order also carries its units + cost of goods from the sales table; Shopify sales should be costed and not refunded. Sale lines are booked from Shopify at what the customer actually paid, with shipping and tax split out, so discounted and comped orders show at their real value. Orders that Shopify has not marked paid are not booked at all; they appear under Payment exceptions until they are paid or cancelled.
How the Affiliates tab works
Affiliates promote Sixforty with a unique discount code. When a customer uses the code at checkout, the order is credited to that affiliate (attribution is by code, which arrives with the order from Shopify each night). Each row below is one code: net sales (what customers actually paid, after the discount, excluding refunds), the orders and units it drove, and the commission due at the rate you set. Set the commission rate per code; it saves straight away. Commission is a guide for what to pay monthly in arrears, book the payout to account 6700 (Affiliate commissions) in Transactions. Recommended default: 15% customer discount, 10% commission, stepping to 15% (Growth) and 20% (Hero) as an affiliate produces.
How the Tasks tab works
Open actions that keep the books complete and reconciled. The count on the Tasks item in the sidebar is the number of things waiting for you; the list rebuilds after every sync. The "Allocate", "Assign" and "Categorise" buttons jump straight to the transaction in Transactions. Products needing a cost are resolved by allocating their purchase: cost is derived from allocations, not entered by hand. "Sales with no Shopify match" lists bank payments that don't tie to a Shopify order, either a recent in-person sale to enter in Shopify POS, or an older off-Shopify sale to record via "Add sale". The match is on amount and date, so treat it as a review list, not gospel. Monzo charges £1 per cash deposit, which the matcher allows for. "Inventory stock gaps" compares what HQ thinks you should have (bought, less sold and written off) with Shopify's count. A gap usually means gifts, samples or in-person sales that were never recorded; tell Leo how to treat them. It is monitoring only and does not count towards the task total. Only categories with open items are shown; anything fully reconciled is hidden.