Class: Catalog::SwitchItemizableCatalog
- Inherits:
-
BaseService
- Object
- BaseService
- Catalog::SwitchItemizableCatalog
- Defined in:
- app/services/catalog/switch_itemizable_catalog.rb
Overview
The single sanctioned, atomic path for "flipping" an itemizable — an Order,
Quote, or RoomConfiguration (cart) — onto a different catalog/currency (e.g. a
US cart that becomes a Canadian order, SO727885, 2026-07).
The flip is a multi-step mutation — the customer, the currency, every line
item's catalog item, the inventory reservations behind those lines, and any
authorized payment — and it MUST be all-or-nothing. Done piecemeal (or via a
save-time callback) it can leave an order half-converted: CAD currency with a
US line, or US-warehouse stock committed under a Canadian order. This service
performs the whole thing inside a single transaction, so a failure anywhere
(an item with no equivalent in the target catalog, a live authorization in
the wrong currency, an incoherent save) rolls the itemizable back to its
original, coherent state.
Coherence is enforced declaratively by
LineItem#catalog_item_matches_resource_catalog (through
Models::Pickable#validates_associated), which keys off the customer's
catalog. So a real flip has to move the customer too — pass +target_customer+
(the incident: a cart reassigned from a guest to a Canadian customer). Pass
+target_catalog+ instead only when the customer already resolves to it.
Sibling of Catalog::AssignCatalogToCustomer (which drives the customer-keeps-
identity, catalog-changes cascade).
Defined Under Namespace
Classes: Result
Instance Attribute Summary
Attributes inherited from BaseService
Instance Method Summary collapse
-
#process(itemizable, target_catalog: nil, target_customer: nil) ⇒ Result
Atomically re-bases +itemizable+ onto a target catalog: (optionally) reassigns the customer, sets the currency, remaps every goods line to the equivalent item in the target catalog (moving its stock reservation), and refuses when a live authorization in the outgoing currency would be left mismatched.
Methods inherited from BaseService
#initialize, #log_debug, #log_error, #log_info, #log_warning, #logger, #tagged_logger
Constructor Details
This class inherits a constructor from BaseService
Instance Method Details
#process(itemizable, target_catalog: nil, target_customer: nil) ⇒ Result
Atomically re-bases +itemizable+ onto a target catalog: (optionally)
reassigns the customer, sets the currency, remaps every goods line to the
equivalent item in the target catalog (moving its stock reservation), and
refuses when a live authorization in the outgoing currency would be left
mismatched. Any failure aborts the whole transaction — no half-flipped
state.
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 85 86 87 |
# File 'app/services/catalog/switch_itemizable_catalog.rb', line 55 def process(itemizable, target_catalog: nil, target_customer: nil) target_catalog ||= target_customer&.catalog raise ArgumentError, 'target_catalog or target_customer is required' if target_catalog.nil? # Fail fast rather than silently flip only the catalog while leaving the old # customer attached — Quote/RoomConfiguration reassign via their opportunity. raise ArgumentError, "#{itemizable.class.name} does not support target_customer reassignment" if target_customer && !itemizable.respond_to?(:customer=) return Result.new(messages: ['Already on the target catalog'], itemizable:) if !target_customer && on_target?(itemizable, target_catalog) errors = precondition_errors(itemizable, target_catalog) return Result.new(messages: errors, itemizable:) if errors.present? itemizable.transaction do itemizable.customer = target_customer if target_customer # strict_mode: raise (→ rollback) rather than silently drop a line with no # equivalent in the target catalog. Remapper sets the currency from the # target catalog and moves inventory commits to the new store items. CatalogItem::Remapper.new(itemizable, target_catalog:, strict_mode: true).fix_catalog # Remapper skips its own save! (and reload) when the itemizable is already # coherent — e.g. a same-catalog customer reassignment — so persist any # still-pending change (the customer swap) that would otherwise be lost. itemizable.save! if itemizable.changed? end Result.new(switched: true, messages: ["Switched #{itemizable.class.name} ##{itemizable.id} to catalog #{target_catalog.id}"], itemizable:) rescue CatalogItem::Remapper::CatalogItemMissing, ActiveRecord::RecordInvalid => e log_warning("SwitchItemizableCatalog aborted for #{itemizable.class.name} ##{itemizable.id}: #{e.}") # The transaction rolled back, but the in-memory object still holds the # attempted currency/customer/line changes — refresh it so callers see the # true (unchanged) state. itemizable.reload if itemizable.persisted? Result.new(switched: false, messages: Array(e.), itemizable:) end |