← All manuals Operator Guide for this screen IM2

Create New Inventory Item — Technical Spec

Create New Inventory Item
Technical Spec Ver 3 Updated 2026-09-06 Matches the live screen

1. Routes

Handlers in app/im2.py, template newitem.html.

MethodPathPurposePermission
GET/newThe formaction item_create
POST/api/items/newCreate the IM2 row + push to QBaction item_create
GET/api/items/pendingItems with qb_status in ('pending','error','sent')signed in
POST/api/items/retry-qbRe-push pending + error rows to QBaction item_qb_sync

ACTION_DEFS registers item_create as "Create New Item — The /new screen and saving a new item." Permissions are data (role_action_perms, defaults in ACTION_DEFAULTS), editable on /perms.

2. GET context

next_sku(cur) = max(sku::bigint) where sku ~ '^[0-9]+$' + 1, floor 50001 — computed for display only, outside any lock. Context also carries setting_uoms(cfg), income_gl, expense_gl, default_count_group, list values for gen_loc / item_group / manufacturer / count_group, and pending (count of qb_status in ('pending','error','sent') on GET; the count returned by the create and retry endpoints counts ('pending','error') only).

Fixed bug retained in comments: the lead-time field used to be pre-filled from the PO Workbench syscon default_lead_time_days; it now renders blank and that syscon is left to po.py default_lead_time().

3. POST /api/items/new

Form fields: item_name, description_po, uom, primary_bin, secondary_bin, gen_loc, item_group, manufacturer, mfr_part_no, min_qty, max_qty, count_group, notes, primary_vendor, purchase_cost, lead_time_days.

Validation, in order:

  1. require_action(user, "item_create").
  2. item_name and description_po both non-blank → else 400.
  3. uom must be in setting_uoms(settings()) → else 400 (UOMS is EA/FT today).
  4. num() on purchase_cost, min_qty, max_qty, lead_time_days: blank → NULL, non-numeric → 400.
  5. Min/Max rules (Dave, 2026-09-06): either value below 0 → 400 Min qty cannot be negative / Max qty cannot be negative; both present and min > max → 400 Min qty (n) cannot be more than Max qty (n). Either may be blank.
  6. pg_advisory_xact_lock(SKU_LOCK), then a case-insensitive item_name uniqueness check → 400 if taken.
  7. next_sku(cur) inside the lock — the authoritative SKU, which may differ from the one rendered.
  8. INSERT INTO items with qty_on_hand = 0, description_so = description_po, qb_status='pending', qb_active=false, income_gl/expense_gl from settings, count_group defaulting to cfg['default_count_group'], created_by/updated_by = username, timestamps now(). Bin/UOM normalisation (uppercase/trim) is the DB trigger items_normalize_trg.
  9. audit_log row: field_name='item', new_value=item_name, source='new item screen'.

The IM2 transaction commits before QuickBooks is contacted.

4. QuickBooks push

_push_new_item_to_qb(sku, item_name, description, cost, vendor, reorder_point)qb.QBO().create_inventory_item(...): Type='Inventory', TrackQtyOnHand=True, QtyOnHand=0, InvStartDate=today, Sku, Name, Description+PurchaseDesc, PurchaseCost (if given), ReorderPoint = min_qty, income/expense/asset account refs from settings (qb_income_account_id, qb_expense_account_id, qb_asset_account_id), preferred vendor resolved by vendor_id(vendor).

POST /api/items/retry-qb re-pushes every row with qb_status in ('pending','error') and returns created/failed/pending; 400 when nothing is waiting. qb_status='sent' rows are not re-sent — the new-item QB sync sheet endpoint /api/items/new-sync was removed 2026-09-06 (Dave: "we're not using sync sheets"), and re-sending 'sent' rows was what could create a duplicate QB item.

Note GET /api/items/pending still lists qb_status in ('pending','error','sent'), so any historical 'sent' row remains visible on the screen even though Retry no longer touches it.

5. What syncs back

scripts/qb_refresh.py mirrors QB-owned fields (name, description, cost, active flag, qb_item_id) onto items and archives QB deactivations. Item name is read-only in IM2 by design — a rename happens in QB and arrives on the next refresh. IM2 never writes QB except through this screen's create call.

6. Known limits / gaps

Client-side validation (added 2026-09-06)

Dave, 2026-09-06: "minimize the checks at save that could fail." newitem.html now mirrors every server-side rule on blur: checkNumber('purchase_cost'), checkMinMax() (numeric, non-negative, min <= max) and checkBin() for both bin boxes (upper-cased in place, ^[A-Z0-9][A-Z0-9 ./-]*$). Messages render in a .fmsg div per field and the field gets .bad; validateAll() gates the submit handler with "Fix the fields marked in red first". The server checks in create_item are unchanged and still authoritative — this only stops the round trip. Remaining save-time failures are by nature server-only: duplicate item name and whatever QuickBooks rejects. The bin pattern is client-only; there is no server bin format rule, so it is deliberately permissive.