Class: Spree::Fulfillments::Create
- Inherits:
-
Object
- Object
- Spree::Fulfillments::Create
- Includes:
- ServiceModule::Base
- Defined in:
- app/services/spree/fulfillments/create.rb
Overview
Manually creates a fulfillment (Spree::Shipment) on a completed order, bypassing order routing. Moves the requested quantities of each line item's not-yet-shipped inventory units out of their current shipments into the new fulfillment, mirroring externally-managed fulfillment (3PL, courier API, drop-shipping) back into Spree.
Stock bookkeeping follows the split/transfer semantics: when the source
and target stock locations differ, moved quantities are restocked at the
source and unstocked at the target. Source shipments left empty are
destroyed and their cost and selected delivery method carry over to the
new fulfillment, unless the caller provides its own cost /
delivery_method.
The resulting cost is frozen only for fulfillments registered with status: 'shipped' (rate refresh skips shipped shipments). Pending/ready fulfillments participate in the standard rate machinery — the order updater re-prices them from the delivery method calculators on the next recalculation, exactly like shipments created via split/transfer.
Instance Method Summary collapse
-
#call(order:, stock_location:, items: nil, tracking: nil, delivery_method: nil, cost: nil, status: nil, metadata: nil) ⇒ Spree::ServiceModule::Result
The created shipment on success.
Methods included from ServiceModule::Base
Instance Method Details
#call(order:, stock_location:, items: nil, tracking: nil, delivery_method: nil, cost: nil, status: nil, metadata: nil) ⇒ Spree::ServiceModule::Result
Returns the created shipment on success.
39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 |
# File 'app/services/spree/fulfillments/create.rb', line 39 def call(order:, stock_location:, items: nil, tracking: nil, delivery_method: nil, cost: nil, status: nil, metadata: nil) return failure(nil, Spree.t('fulfillments.errors.invalid_status')) unless status.nil? || status == 'shipped' cost = parse_cost(cost) return cost if cost.is_a?(Spree::ServiceModule::Result) fulfillment = nil # The order row is locked before reading fulfillable units so # concurrent creations (e.g. duplicate carrier webhooks) validate and # move units against a serialized snapshot. The API layer already # serializes via with_order_lock; this covers direct service callers. ActiveRecord::Base.transaction do order.lock! return failure(nil, Spree.t('fulfillments.errors.order_not_completed')) unless order.completed? return failure(nil, Spree.t('fulfillments.errors.order_canceled')) if order.canceled? units_by_line_item = fulfillable_units(order) requested = normalize_items(order, items, units_by_line_item) return requested if requested.is_a?(Spree::ServiceModule::Result) fulfillment = order.shipments.new( stock_location: stock_location, address_id: order.ship_address_id, tracking: tracking ) fulfillment. = if .present? fulfillment.save! source_shipments = move_units(order, fulfillment, requested, units_by_line_item) inherited = destroy_drained_shipments(source_shipments, capture_delivery_method: delivery_method.nil?) attach_cost_and_rate(fulfillment, delivery_method, cost, inherited) if status == 'shipped' mark_shipped(fulfillment) else fulfillment.update!(order) end order.reload.update_with_updater! end success(fulfillment.reload) end |