Bulk Import — inventory + storage in one upload

Migrating from another WMS, or landing a backlog of pre-EMS shipments, used to mean running two CSV imports back-to-back — one for inventory, one for storage. v3.5.5 collapsed them into a single template. One row per (product × AWB) declares what arrived and (optionally) where it now lives. EMS lands the migration shipment, the products, the per-box records, the skid placements, and the storage zone stamps in one transaction.

One CSV, not two

Before v3.5.5, bulk-import was a two-step:

  1. Inventory Bulk Import — created a migration shipment per AWB with rolled-up item totals (no real boxes).
  2. Storage Bulk Import — separately attached skids + zone/slot placements, only after step 1 had completed for the same AWB.

The two-step had three problems: it doubled the operator's CSV authoring time, it left a window where inventoried units existed without a storage placement (and vice versa), and it never materialised per-box records — so production scan, damage tracking, and the customer portal's box detail were all blind on bulk-imported inventory.

The unified import fixes all three. The button on the Inventory page and the Storage page both open the same modal; you upload one CSV; EMS handles the two-job hand-off internally.

What gets created

For each unique (Consignee, AWB) in the CSV, EMS creates (or updates) a migration shipment at status inventoried. For each row, EMS finds or creates the matching product (keyed on brand + name + flavour + mL + mg) and writes the per-box records on the shipment with status: 'received'. If the row carries Skid Number + Zone, EMS also creates the skid with the listed boxes attached and stamps each box's currentLocation with the zone/slot.


End-to-end workflow

  1. Open Inventory (or Storage) → click Bulk Import.
  2. Click Download Template. Edit in Excel or Google Sheets.
  3. Save as CSV (UTF-8). Drag onto the modal or click Upload Completed CSV.
  4. Review the preview — every row is validated against the catalogue + zone config; errors are flagged inline so you can fix them before commit.
  5. Click Import. EMS commits everything in one pass and reports counts (shipments created/updated, products created/reused, boxes created/merged, skids created/updated).
  6. Verify placement in Receiving → Put-Aways — every bulk-imported skid lands in the queue for manual sign-off (see below).

Template columns

The template downloads with header comments + example rows. The columns are:

Required

Optional (storage placement)

Box Numbers format

Three accepted forms; combine freely with commas:

Box numbers are case-sensitive strings, so BATCH1-44 stays as BATCH1-44 (not expanded). Each parsed number becomes one shipment.boxes[] entry that downstream modules — production scan, damage tracking, box-level reports, the customer portal — can address by name.


How merges work

Mixed boxes

Two rows on the same AWB sharing a box number → the box becomes a mixed box carrying both products. Quantities sum, items dedupe by productId, and the items list on the box reflects both rows. Example:

Example: a mixed box

Row 1 — Orbito · Blueberry Raspberry · 30mL · 20mg · boxes 1-50 · 5,000 units
Row 2 — Linvo · White Peach · 30mL · 10mg · boxes 25-30 · 600 units
Result: Boxes 25 through 30 each carry both products. Box 26 (for example) has Blueberry Raspberry × 100 units AND White Peach × 100 units — production scan resolves it as a mixed box and asks the operator to pick which flavour they're stamping.

Multi-product skids

Two rows sharing Consignee + AWB + Skid Number → one skid carrying both products. Same skid label, same zone/slot, items merge by productId. Useful when you've packed several flavours onto one physical skid at your warehouse.

Re-runs are safe

The import is idempotent. Re-running the same CSV does not double-count anything:


Auto-receive — units land as Available

Bulk-imported boxes get status: 'received' + a receivedAt timestamp matching the row's Import Date. That means the inventory rollup counts them as Available immediately — not stranded in the "Expected" or "In Transit" buckets the way they were before v3.5.5.040.

Practical effect: as soon as the import finishes, the units appear on the Inventory page under the right consignee/brand/variant, customers can request POs against them via the portal, and the shipment's milestone shows Receiving as complete. No separate "mark as received" step.

Before vs after

Pre-3.5.5.040: bulk-imported shipments showed units in the Expected column and the milestone bar stuck on "Receiving — Step 3 of 3". You'd open the shipment hoping for a Receive button and find nothing. Post-.040: units appear under Available; the milestone bar shows Receiving complete; the shipment moves cleanly to Inventoried status.


Put-Aways verification

Every bulk-imported skid is created with putAwayStatus: 'pending', so it appears in Receiving → Put-Aways for manual placement verification — the same model used for live receiving.

The Put-Aways tab shows each pending skid with its label, AWB, consignee, box count, units, planned zone/slot, and age. A floor supervisor walks the warehouse, confirms each skid is physically where the CSV said it should be, and clicks Confirm placement (or scans the location QR if you're using printed location labels). The skid flips to stored and disappears from the queue.

If the physical location doesn't match the CSV, the supervisor can reassign on the spot — the put-away modal lets you scan or type a different slot, and EMS logs both the original planned location and the new actual one for audit.


Common errors

Errors are reported per-row in the preview before commit. Fix them in the CSV and re-upload — nothing's written to the system until you click Import.


Best practices