|
1 | 1 | module ItemizableUpdateService |
2 | | - # @param itemizable [Itemizable] |
3 | | - # @param params [Hash] Parameters passed from the controller. Should include `line_item_attributes`. |
4 | | - # @param event_class [Class<Event>] the event class to publish the itemizable to. |
5 | | - def self.call(itemizable:, params: {}, event_class: nil) |
6 | | - original_storage_location = itemizable.storage_location |
7 | | - StorageLocation.transaction do |
8 | | - item_ids = params[:line_items_attributes]&.values&.map { |i| i[:item_id].to_i } || [] |
9 | | - inactive_item_names = Item.where(id: item_ids, active: false).pluck(:name) |
10 | | - if inactive_item_names.any? |
11 | | - raise "Update failed: The following items are currently inactive: #{inactive_item_names.join(", ")}. Please reactivate them before continuing." |
| 2 | + class << self |
| 3 | + # @param itemizable [Itemizable] |
| 4 | + # @param params [Hash] Parameters passed from the controller. Should include `line_item_attributes`. |
| 5 | + # @param event_class [Class<Event>] the event class to publish the itemizable to. |
| 6 | + def call(itemizable:, params: {}, event_class: nil) |
| 7 | + # we're updating just attributes on the itemizable, not line items |
| 8 | + if params[:line_items_attributes].blank? && SnapshotEvent.intervening(itemizable).present? |
| 9 | + itemizable.update!(params) |
| 10 | + itemizable.reload |
| 11 | + return |
12 | 12 | end |
13 | 13 |
|
14 | | - from_location = to_location = itemizable.storage_location |
15 | | - to_location = StorageLocation.find(params[:storage_location_id]) if params[:storage_location_id] |
| 14 | + original_storage_location = itemizable.storage_location |
| 15 | + StorageLocation.transaction do |
| 16 | + item_ids = params[:line_items_attributes]&.values&.map { |i| i[:item_id].to_i } || [] |
| 17 | + inactive_item_names = Item.where(id: item_ids, active: false).pluck(:name) |
| 18 | + if inactive_item_names.any? |
| 19 | + raise "Update failed: The following items are currently inactive: #{inactive_item_names.join(", ")}. Please reactivate them before continuing." |
| 20 | + end |
16 | 21 |
|
17 | | - verify_intervening_audit_on_storage_location_items(itemizable: itemizable, from_location_id: from_location.id, to_location_id: to_location.id) |
| 22 | + from_location = to_location = itemizable.storage_location |
| 23 | + to_location = StorageLocation.find(params[:storage_location_id]) if params[:storage_location_id] |
| 24 | + |
| 25 | + verify_intervening_audit_on_storage_location_items(itemizable: itemizable, from_location_id: from_location.id, to_location_id: to_location.id) |
18 | 26 |
|
19 | | - previous = nil |
20 | | - # TODO once event sourcing has been out for long enough, we can safely remove this |
21 | | - if Event.where(eventable: itemizable).none? || UpdateExistingEvent.where(eventable: itemizable).any? |
22 | 27 | previous = itemizable.line_items.map(&:dup) |
23 | | - end |
| 28 | + if inventory_changes?(previous, params[:line_items_attributes]) && SnapshotEvent.intervening(itemizable).present? |
| 29 | + raise "Cannot update #{itemizable.class.name.downcase} because there has been an intervening snapshot of the inventory." |
| 30 | + end |
24 | 31 |
|
25 | | - line_item_attrs = Array.wrap(params[:line_items_attributes]&.values) |
26 | | - line_item_attrs.each { |attr| attr.delete(:id) } |
| 32 | + line_item_attrs = Array.wrap(params[:line_items_attributes]&.values) |
| 33 | + line_item_attrs.each { |attr| attr.delete(:id) } |
27 | 34 |
|
28 | | - update_storage_location(itemizable: itemizable, params: params) |
29 | | - if previous |
30 | | - UpdateExistingEvent.publish(itemizable, previous, original_storage_location) |
31 | | - else |
32 | | - event_class&.publish(itemizable) |
| 35 | + update_storage_location(itemizable: itemizable, params: params) |
| 36 | + |
| 37 | + # TODO once event sourcing has been out for long enough, we can safely remove this |
| 38 | + if Event.where(eventable: itemizable).none? || UpdateExistingEvent.where(eventable: itemizable).any? |
| 39 | + UpdateExistingEvent.publish(itemizable, previous, original_storage_location) |
| 40 | + elsif inventory_changes?(previous, params[:line_items_attributes]) |
| 41 | + event_class&.publish(itemizable) |
| 42 | + end |
33 | 43 | end |
34 | 44 | end |
35 | | - end |
36 | 45 |
|
37 | | - # @param itemizable [Itemizable] |
38 | | - # @param params [Hash] Parameters passed from the controller. Should include `line_item_attributes`. |
39 | | - def self.update_storage_location(itemizable:, params:) |
40 | | - # Delete the line items -- they'll be replaced later |
41 | | - itemizable.line_items.delete_all |
42 | | - # Update the current model with the new parameters |
43 | | - itemizable.update!(params) |
44 | | - itemizable.reload |
45 | | - end |
| 46 | + # @param previous [Array<LineItem>] Previous line items before the update. |
| 47 | + # @param line_item_attributes [Hash] The new line item attributes from the params. |
| 48 | + # @return [Boolean] Returns true if the inventory has changed, false otherwise. |
| 49 | + def inventory_changes?(previous, line_item_attributes) |
| 50 | + previous_attrs = previous.to_h { |li| [li.item_id, li.quantity] } |
| 51 | + new_attrs = line_item_attributes.to_h do |_, li| |
| 52 | + [li[:item_id].to_i, li[:quantity].to_i] |
| 53 | + end |
| 54 | + previous_attrs != new_attrs |
| 55 | + end |
| 56 | + |
| 57 | + # @param itemizable [Itemizable] |
| 58 | + # @param params [Hash] Parameters passed from the controller. Should include `line_item_attributes`. |
| 59 | + def update_storage_location(itemizable:, params:) |
| 60 | + # Delete the line items -- they'll be replaced later |
| 61 | + itemizable.line_items.delete_all |
| 62 | + # Update the current model with the new parameters |
| 63 | + itemizable.update!(params) |
| 64 | + itemizable.reload |
| 65 | + end |
46 | 66 |
|
47 | | - # @param itemizable [Itemizable] |
48 | | - # @param from_location [StorageLocation] |
49 | | - # @param to_location [StorageLocation] |
50 | | - def self.verify_intervening_audit_on_storage_location_items(itemizable:, from_location_id:, to_location_id:) |
51 | | - return if from_location_id == to_location_id || !Audit.finalized_since?(itemizable, [from_location_id, to_location_id]) |
52 | | - |
53 | | - itemizable_type = itemizable.class.name.downcase |
54 | | - case itemizable_type |
55 | | - when "distribution" |
56 | | - raise "Cannot change the storage location because there has been an intervening audit of some items. " \ |
57 | | - "If you need to change the storage location, please reclaim this distribution and create a new distribution from the new storage location." |
58 | | - else |
59 | | - raise "Cannot change the storage location because there has been an intervening audit of some items. " \ |
60 | | - "If you need to change the storage location, please delete this #{itemizable_type} and create a new #{itemizable_type} with the new storage location." |
| 67 | + # @param itemizable [Itemizable] |
| 68 | + # @param from_location [StorageLocation] |
| 69 | + # @param to_location [StorageLocation] |
| 70 | + def verify_intervening_audit_on_storage_location_items(itemizable:, from_location_id:, to_location_id:) |
| 71 | + return if from_location_id == to_location_id || !Audit.finalized_since?(itemizable, [from_location_id, to_location_id]) |
| 72 | + |
| 73 | + itemizable_type = itemizable.class.name.downcase |
| 74 | + case itemizable_type |
| 75 | + when "distribution" |
| 76 | + raise "Cannot change the storage location because there has been an intervening audit of some items. " \ |
| 77 | + "If you need to change the storage location, please reclaim this distribution and create a new distribution from the new storage location." |
| 78 | + else |
| 79 | + raise "Cannot change the storage location because there has been an intervening audit of some items. " \ |
| 80 | + "If you need to change the storage location, please delete this #{itemizable_type} and create a new #{itemizable_type} with the new storage location." |
| 81 | + end |
61 | 82 | end |
62 | 83 | end |
63 | 84 | end |
0 commit comments