Access to add and change pages is restricted. See: https://cwiki.apache.org/confluence/display/OFBIZ/Wiki+access

The situation

A 3PL operator runs a warehouse and ships goods on behalf of merchant clients. The 3PL:

  • does not sell the goods (the merchant already sold them),
  • does not invoice the buyer or take payment,
  • only needs to receive, store, pick, pack, ship, and report tracking.

The trap (why the obvious path fails)

OFBiz's out-of-the-box "ship an order" path (quickShipEntireOrder) assumes a sales order. As soon as a SALES_SHIPMENT advances to SHIPMENT_PICKED (and again at SHIPMENT_PACKED, which also marks it ready), OFBiz automatically creates a customer invoice (SALES_INVOICE) — and from there it can auto-create a payment and march the order through to-cash.

That is correct for a store. It is wrong for a 3PL. The invoice trigger is an ECA that fires for any shipment of type SALES_SHIPMENT, and — unlike auto-payment — there is no config property to turn it off.

The rule

Don't use the sales types. Model fulfillment with dedicated non-sales types.

Use thisNot this
OrderType = FULFILLMENT_ORDER (standalone)SALES_ORDER
ShipmentType = FULFIL_SHIPMENTSALES_SHIPMENT

FULFILLMENT_ORDER must be standalone — do not declare it as a child of SALES_ORDER, or sales-typed logic re-attaches through parent-type checks.

Why it works

OFBiz's billing fires from ECAs gated on the sales types. Give it a non-sales type and the triggers simply never match:

AutomationFires when…With FULFILLMENT_ORDER?
Auto-invoice on shipshipment is SALES_SHIPMENTNever fires
Auto-paymentorder is SALES_ORDERNever fires
GL postingparty is an internal organizationSelf-skips

Result: zero order-to-cash — no invoice, no payment, no accounting postings. By construction, not by patching.

The cost, and how to pay it

The stock warehouse pipeline is sales-coupled: findOrdersToPickMove expects a SALES_ORDER, and quickShipEntireOrder branches its logic on orderTypeId == SALES_ORDER (a FULFILLMENT_ORDER falls into the purchase-order branch and never produces a usable shipment — the shipment is only typed SALES_SHIPMENT when the order is a SALES_ORDER). So you can't reuse the one-call shipping service; you drive the lower-level services yourself, in this order:

  1. Receive  receiveInventoryProduct (stocks the shelf; owner = the merchant).
  2. Intake  storeOrder as FULFILLMENT_ORDER, then approve it.
  3. Reserve  reserveStoreInventory.
  4. Pick — observe the reservation.
  5. Track  createShipmentRouteSegment before shipping (it is rejected once a shipment is shipped).
  6. Ship  createShipment (FULFIL_SHIPMENT) → issue each item → updateShipment INPUT → PACKED → SHIPPED (you cannot jump straight to SHIPPED).
  7. Poll — read the order, shipment, and tracking back.

One gap to know: OFBiz has no complete non-sales "issue inventory to shipment" service — the complete one is sales-only. Hand-roll the issuance with createItemIssuance + createInventoryItemDetail, and set availableToPromiseDiff = 0 (the reserve step already lowered ATP — don't subtract it twice, or ATP goes negative).

The tenancy model (many merchants, one warehouse)

All on stock entities — no new tables:

3PL conceptOFBiz entity
Operator (the 3PL)PartyGroup
Warehouseone Facility (type WAREHOUSE), owned by the operator
Merchant clientPartyGroup
Merchant's account at the 3PLProductStore, with payToPartyId = the merchant
Which warehouse serves that accountProductStoreFacility
API access grantProductStoreRole (role FULFILL_API_CLIENT)
Order isolationOrderHeader.productStoreId
Commingled stockInventoryItem.ownerPartyId = the merchant

So orders stay isolated per store, while every merchant's inventory sits in the same warehouse, told apart only by owner. Onboarding a new client is just repeating this block of data — no code.

Accounting preferences you still need

Even though nothing gets invoiced, two stock behaviors still read accounting prefs, so give each merchant one PartyAcctgPreference:

  • COGS at issuance — issuing inventory computes cost using the merchant's cogsMethodId (e.g. COGS_AVG_COST).
  • BI facts — the bi plugin loads order facts on approval and needs baseCurrencyUomId.

Set both on the PartyAcctgPreference, or disable the bi plugin if you don't want fact-loading.

In one line

A 3PL ships; it doesn't sell. Type the order as FULFILLMENT_ORDER and OFBiz stops trying to bill — everything else is just driving the warehouse services in order.

  • No labels