Bundle counts and costing
Disclaimer: I'm not an accountant, and nothing on this page is accounting advice. This is a plain description of what the data actually shows — collating MOM's real tables, what this app currently does, and how QuickBooks Online's API actually behaves — written so Accounting can weigh in on the real tradeoffs, not a claim about the "correct" accounting treatment. If anything here conflicts with how Accounting wants this handled, that's exactly the conversation this page is meant to start.
A bundle is a single product made up of real components sold under one SKU — for example, a Guide/Workbook Combo is really a Parent Guide and a Workbook sold together. This page covers what this app does with a bundle's count and cost today, why, and what the real alternatives are if we ever want to do something different. A reference section at the bottom covers what MOM (the old system) does instead, for anyone who wants the fuller picture.
What happens today
- The bundle line keeps the full price. An order for 25 Guide/Workbook Combos shows $548.75 on the Combo line. The component lines under it (Parent Guide, Workbook) show $0 each.
- Only the real components get counted. That same order counts as 300 Parent Guides and 300 Workbooks (12 of each per Combo) coming out of real inventory. The Combo itself is never counted — it isn't a physical thing you can pull off a shelf, only the parts inside it are.
- The bundle's cost in QuickBooks is set to $0. If you open a bundle item in QuickBooks (like
1965) and see a $0 cost, that's intentional, done on purpose as of 2026-07-22 — not a data error. The real cost isn't lost: it's always recoverable as the exact sum of the bundle's real component costs (1965's real cost, $3.42243, is exactly1963's $2.246 +1964's $1.17643).
Why the $0 cost, specifically
QuickBooks posts a real Cost of Goods Sold / Inventory Asset entry for any Inventory-type item based on quantity × cost, every time it's sold — regardless of what dollar amount is actually on the invoice line. This app already keeps the bundle's real quantity on the invoice line (25 Combos stays "25," it's never zeroed), because that's what preserves normal "units sold" reporting for bundles inside QuickBooks itself. But that means QuickBooks would post a second, unwanted Cost of Goods Sold / Inventory entry for the bundle root — on top of the correct one already happening through the real components — unless something stops it.
Setting the bundle's cost to $0 stops it cleanly: quantity × $0 = $0, no matter what the quantity is. It required no change to how this app builds or prices invoices, and it's the only fix confirmed to be a normal, unrestricted QuickBooks API field (unlike the item's type, which QuickBooks locks down once an item has real transaction history — see Option 1 below).
The one real, still-open risk with this approach
This isn't fully settled — flagging it here rather than treating it as closed. The $0 cost holds only as long as no real Bill or Purchase Order is ever posted against a bundle SKU in QuickBooks. If one ever is, QuickBooks will silently recalculate that item's cost away from $0 — with nothing anywhere flagging that it happened — and every subsequent sale of that bundle would quietly start posting real, unwanted Cost of Goods Sold again. We don't yet know whether the client's real purchasing process could ever touch a bundle SKU directly (rather than only its real components), which is the open question that would need an answer before this can be called fully safe. Until then, it's worth a periodic sanity check that bundle-root costs in QuickBooks are still $0.
The options, and their tradeoffs
Five real options were weighed for stopping the double Cost-of-Goods-Sold problem in QuickBooks. Option 2 is what's implemented today (see above). The others remain live alternatives if Option 2's open risk ever becomes a real problem.
- Change the bundle item's QuickBooks type to Non-Inventory or Service. A non-inventory item has no cost-basis concept at all, so it's completely immune to the risk described above — no Bill or Purchase Order could ever cause a problem, because there's no QuickBooks mechanism left that could post a cost against it. The real cost is migration: QuickBooks likely won't allow changing an item's type once it already has real transaction history, which would mean deactivating the old item and creating a new one — touching every past invoice line and any HubSpot-side mapping to that item. Not yet tested against a bundle SKU with real history.
- Zero the item's cost, keep its type as Inventory. (Current approach.) Quantity × $0 = $0 regardless of quantity. Needed no change to this app's invoice-building code, and is a normal, unrestricted QuickBooks field. The tradeoff is the open risk above: it depends on the negative fact that no real Bill or Purchase Order ever lands on a bundle SKU, which is an assumption about future behavior, not something enforced anywhere.
- Push a quantity of 0 to QuickBooks for the bundle line, while keeping the real dollar amount. Would also stop the double posting, and (unlike Option 2) doesn't depend on anything staying true going forward. The real cost: QuickBooks's own Sales-by-Item report reads quantity, not amount — so bundle sales would show as "0 units sold" in QuickBooks's own reports, even though the dollar amount is correct. It's also not yet confirmed whether QuickBooks's API will even accept a 0 quantity paired with a real nonzero amount on a normal line — that combination hasn't been tested.
- Same as Option 3, plus store the real quantity as hidden data on the line itself, so it's still recoverable by reading QuickBooks alone, not just by cross-referencing HubSpot. Fixes Option 3's "0 units sold in QuickBooks's own reports" problem for anyone willing to look at the hidden data instead of the native report — the native report itself would still show 0. Uses a technique already built and shipped for a different feature (price-tier savings), so the mechanism itself isn't new or risky — it's a moderate amount of additional bookkeeping work, not an unproven idea.
- Last resort: have this app actively post a real, offsetting QuickBooks Inventory Adjustment every time a bundle sells — the direct equivalent of MOM's own create/cancel pattern (see the reference section below). Guaranteed to work no matter what happens with Bills or Purchase Orders, because it corrects the effect every time rather than relying on a precondition staying true. The cost is real, ongoing complexity: a new API call on every bundle sale, and a new way for that call to fail or collide with other activity on the same item. This is a deliberate last resort, kept in reserve, not something to build ahead of actually needing it.
The one real judgment call buried in all of this
Options 3 and 4 give up QuickBooks's own native "units sold per bundle" report. Whether that's actually a cost depends on one question worth asking directly: does anyone need to open QuickBooks itself and see bundle sales volume in its own reports, or is that always going to come from HubSpot or this app's own reporting instead? If the answer is "always from HubSpot," Options 3 and 4 lose nothing real. If someone specifically wants that number inside QuickBooks, only Options 1, 2, and 5 preserve it.
Bottom line
Nothing here changes what shows up on an invoice or a packing slip — the price and the real component counts work the same regardless of which option is used. This is entirely about what QuickBooks's own item-level cost and reporting look like underneath, and about closing a real, if narrow, gap so a bundle sale can never quietly post Cost of Goods Sold twice. If you see a $0 cost on a bundle item in QuickBooks, that's Option 2, working as intended — not a mistake.
---
Reference: what MOM does today
MOM has no dedicated bundle or kit table at all. There's no single place that says "a Combo is 12 Guides + 12 Workbooks." What actually drives bundle behavior is scattered across the item master and a real transaction log (PICKKIT) that records kit-assembly events one pick at a time, as they happen — bundles are built and tracked transaction by transaction, not declared once as a static recipe the way you might expect.
The bundle root itself never carries a real on-hand count in MOM either — same as this app today. But MOM's mechanism for making the numbers work is different, and more involved: every time a bundle sells, MOM posts two transactions that cancel each other out — one that "creates" units of the bundle root (so the order screen will accept the line at all — MOM's order entry won't let staff add a zero-on-hand item), and one that "consumes" that same quantity right back out, at the identical cost.
Worked example: what one bundle sale actually looks like in MOM
Using bundle 1965's own real numbers (real cost $3.42243, built from its two components 1963 at $2.246 and 1964 at $1.17643) — this is how MOM would record a single unit of 1965 selling, illustrating the mechanism, not a literal row pulled from the raw table:
- Build entry:
1965quantity +1, cost $3.42243 — inventory value +$3.42243. - Sell entry, same day, same SKU, same cost:
1965quantity −1, cost $3.42243 — inventory value −$3.42243. - Net effect on
1965: 0 units, $0.00. Nothing left over, every single time.
That two-step pattern is why doing this in QuickBooks (Option 5, above) is complicated rather than simple: to copy it faithfully, this app would need to call QuickBooks's own adjustment API twice for every single bundle sale — once to add the unit back, once to remove it — matching the exact quantity and cost each time, with no gap in between. If either call fails, or the two calls land at the same moment as something else touching that same item (another sale, a webhook, a manual QuickBooks edit), the bundle's on-hand number could get stuck at a nonzero value with nothing flagging it. That's real, ongoing operational risk added to every bundle sale, forever, just to reproduce a workaround MOM only needed because of its own legacy order-entry screen — QuickBooks has no equivalent restriction to work around in the first place.
This isn't a one-off — it holds across the whole catalog
Checked directly against real June 2026 data, not assumed. For bundle 1965 and its two real components, summed for the whole month:
1965(bundle root): net 0 units, net $0.0002 (effectively $0).1963(real component): net −809 units, net −$1,817.01.1964(real component): net −773 units, net −$909.38.
All of the real dollar movement happens on the components. The bundle root leaves no trace at all. And it's not specific to 1965: checked across every bundle SKU with real activity that month (22 of the catalog's 48 bundles), every single one nets to $0 — 20 landed at exactly zero, and the other 2 were off by a single unit because a build/sale pair was still in progress at the exact moment the month rolled over.
MOM's cost basis, separately
MOM's cost basis in general is last-cost, not FIFO or a weighted average. The moment a new purchase order lands with a new unit cost, every subsequent sale uses that exact number, flat, until the next purchase order changes it again — confirmed across 360 consecutive real sales of one component SKU throughout 2026, all using the identical unit cost. This is also why the 1965 = 1963 + 1964 cost identity above holds so cleanly: under last-cost, there's only ever one current number per component to add up, not a blend of historical cost layers that would drift the identity out of exact alignment over time.