# Listings: "Qty to Push" column (2026-09-29)

On Channels → Listings, for eBay, Amazon, and Shopify channel views: a column showing the
exact quantity the next sync would send for that listing, computed by deliberately mirroring
the real sync job's own logic — never a separate/approximate calculation that could disagree
with what actually gets pushed.

## Why

A TF155 eBay listing sold out overnight (qty 0), and re-pushing it the next day still showed 0
on eBay's Seller Hub — looked like a bug. Turned out to be entirely expected: the listing's eBay
Configurator has a `qty_cap` of 1, so even with 25 units genuinely in stock, only 1 is ever sent
(a deliberate "always show a trickle of stock" strategy some sellers use) — and eBay just took a
moment to reflect the just-synced value. Nothing was actually wrong, but there was no way to see
*why* the number was what it was without digging into a job's console log line
(`stock (25 capped 1)`). This column surfaces that same information right on the page.

## Per-platform behaviour (each mirrors its own sync job exactly)

- **eBay** (`attachEbayQtyToPush()`) — sums available stock (`quantity - reserved_quantity`)
  across the row's own eBay Configurator's `location_ids`, then caps by that configurator's
  `qty_cap`. Mirrors `EbayBatchSyncJob`'s `$qtyByProductLocation`/`$rawQty`/`$qty` computation.
  `null` when the row has no configurator assigned (nothing to compute).
- **Amazon** (`attachAmazonQtyToPush()`) — identical shape to eBay: sums the row's own Amazon
  Configurator's `location_ids`, capped by its `qty_cap`. Mirrors
  `SyncProductToAmazon::resolveQuantity()` / `AmazonBatchCreateJob`'s per-product qty math.
- **Shopify** (`attachShopifyQtyToPush()`) — genuinely different shape: Shopify's inventory
  model is natively multi-location (one quantity per Shopify location, no single "the" cap), so
  this shows the **total across every location this channel maps**
  (`channels_location_mappings`), with a per-location breakdown in the tooltip. Mirrors
  `ShopifyBatchSyncJob`'s own `$warehouseQtyMap` computation (same source table, same
  internal/non-scrap location filter). `null` when the channel has no location mappings at all.

All three are computed in a small, fixed number of batched queries per page load (one
`inventories_product_quantities` query covering every row's relevant locations at once), not
one query per row — same performance discipline as the existing `attachResolvedEbayCategories()`
on this same page.

## Code

- `plugins/webkul/channels/src/Filament/Pages/ManageListingsV2.php` — the three `attach*QtyToPush()`
  methods, called conditionally on `$channel->platform` right after the row query in
  `getProductRows()`. New select columns: `ec.location_ids`/`ec.qty_cap` (eBay),
  `ac.location_ids`/`ac.qty_cap` (Amazon) — both already-joined configurator tables, just needed
  the extra columns.
- `plugins/webkul/channels/resources/views/filament/pages/manage-listings-v2.blade.php` — one
  "Qty to Push" header shown for any of `$isEbay`/`$isAmazon`/`$isShopify`; the data cell
  branches by platform (eBay/Amazon share the same capped-single-value markup with a "(cap N)"
  note and tooltip; Shopify shows the total with a per-location tooltip instead).

## Tests

- `tests/Feature/EbayListingsQtyToPushTest.php` (eBay, shipped first) — 2 tests.
- `tests/Feature/ChannelListingsQtyToPushTest.php` (Amazon + Shopify) — 4 tests, verified against
  real local data (product 1 / ERR3340: 12 available at location 48, no cap on either platform
  → both correctly resolve to 12; Shopify channel 15 correctly sums to the same 12 via its own
  location mapping). 2 of the 4 skip locally for lack of edge-case fixture data (an Amazon
  mapping with no configurator, a Shopify channel with no location mappings) — same
  markTestSkipped-if-data-absent convention used throughout this codebase's tests.

All green: 10 tests, 27 assertions, 3 skipped (all for missing local edge-case data, not
failures), across both files plus the existing `ChannelPlatformTabsTest`/`ChannelHeaderCardTest`
regression check.
