ba_spree 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (160) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +673 -0
  3. data/README.md +150 -0
  4. data/app/assets/config/manifest.js +1 -0
  5. data/app/assets/javascripts/ba_spree/admin_overrides/query_builder_controller.js +505 -0
  6. data/app/assets/javascripts/ba_spree/storefront/ba_spree_local_time.js +9 -0
  7. data/app/assets/stylesheets/spree/admin/custom.scss +40 -0
  8. data/app/controllers/ba_spree/checkout_controller_decorator.rb +111 -0
  9. data/app/controllers/ba_spree/store_controller_decorator.rb +55 -0
  10. data/app/controllers/concerns/ba_spree/theme_concern_decorator.rb +16 -0
  11. data/app/controllers/spree/admin/assets_controller_decorator.rb +48 -0
  12. data/app/controllers/spree/admin/products_controller_decorator.rb +25 -0
  13. data/app/controllers/spree/admin/promotion_rules_controller_decorator.rb +30 -0
  14. data/app/controllers/spree/admin/users_controller_decorator.rb +34 -0
  15. data/app/controllers/spree/contacts_controller_decorator.rb +23 -0
  16. data/app/controllers/spree/digital_links_controller.rb +61 -0
  17. data/app/controllers/spree/orders_controller_decorator.rb +24 -0
  18. data/app/decorators/ba_spree/product_pricing_decorator.rb +65 -0
  19. data/app/decorators/spree/current_decorator.rb +17 -0
  20. data/app/decorators/spree/money_decorator.rb +51 -0
  21. data/app/decorators/spree/order_email_subscriber_decorator.rb +64 -0
  22. data/app/decorators/spree/page_sections/featured_posts_decorator.rb +17 -0
  23. data/app/helpers/currency_rounding_helper.rb +10 -0
  24. data/app/helpers/spree/admin/filter_params_preservation.rb +130 -0
  25. data/app/helpers/spree/admin/navigation_helper_decorator.rb +23 -0
  26. data/app/helpers/spree/admin/promotion_rules_helper_decorator.rb +33 -0
  27. data/app/helpers/spree/admin/table_helper_decorator.rb +40 -0
  28. data/app/helpers/spree/base_helper_decorator.rb +63 -0
  29. data/app/helpers/spree/checkout_analytics_helper_decorator.rb +32 -0
  30. data/app/javascript/ba_spree/controllers/disable_when_hidden_controller.js +70 -0
  31. data/app/javascript/ba_spree/index.js +11 -0
  32. data/app/javascript/spree/admin/controllers/calendar_range_controller.js +61 -0
  33. data/app/javascript/spree/admin/controllers/slug_form_controller.js +17 -0
  34. data/app/models/ba_spree/address_decorator.rb +43 -0
  35. data/app/models/ba_spree/adjustment_decorator.rb +13 -0
  36. data/app/models/ba_spree/order_decorator.rb +95 -0
  37. data/app/models/ba_spree/order_updater_decorator.rb +33 -0
  38. data/app/models/ba_spree/payment_method_decorator.rb +10 -0
  39. data/app/models/ba_spree/product_decorator.rb +44 -0
  40. data/app/models/ba_spree/store_decorator.rb +23 -0
  41. data/app/models/ba_spree/variant_decorator.rb +49 -0
  42. data/app/models/concerns/spree/adjustment_source_decorator.rb +66 -0
  43. data/app/models/concerns/spree/product_property_metafields.rb +52 -0
  44. data/app/models/spree/adjustable/adjuster/promotion_decorator.rb +16 -0
  45. data/app/models/spree/admin/table/filter_decorator.rb +52 -0
  46. data/app/models/spree/admin/table/filter_group_decorator.rb +64 -0
  47. data/app/models/spree/asset_decorator.rb +102 -0
  48. data/app/models/spree/contact_decorator.rb +22 -0
  49. data/app/models/spree/digital_link_decorator.rb +9 -0
  50. data/app/models/spree/export_decorator.rb +34 -0
  51. data/app/models/spree/line_item_destroy_callback_decorator.rb +30 -0
  52. data/app/models/spree/metafield_definition_decorator.rb +34 -0
  53. data/app/models/spree/option_type_decorator.rb +22 -0
  54. data/app/models/spree/option_value_decorator.rb +19 -0
  55. data/app/models/spree/option_value_variant_decorator.rb +30 -0
  56. data/app/models/spree/order/address_book_decorator.rb +14 -0
  57. data/app/models/spree/order_merger_decorator.rb +29 -0
  58. data/app/models/spree/price_rules/customer_group_rule_decorator.rb +18 -0
  59. data/app/models/spree/price_rules/user_rule_decorator.rb +25 -0
  60. data/app/models/spree/price_rules/volume_rule_decorator.rb +25 -0
  61. data/app/models/spree/price_rules/zone_rule_decorator.rb +25 -0
  62. data/app/models/spree/pricing/batch_resolver.rb +96 -0
  63. data/app/models/spree/promotion_handler/cart_decorator.rb +51 -0
  64. data/app/models/spree/stock/packer_decorator.rb +61 -0
  65. data/app/models/spree/stock/prioritizer_decorator.rb +27 -0
  66. data/app/models/spree/tax_rate_decorator.rb +93 -0
  67. data/app/models/spree/taxon_decorator.rb +42 -0
  68. data/app/models/spree/user_bill_address_sync_decorator.rb +33 -0
  69. data/app/overrides/add_ba_spree_storefront_javascript_importmap_tags.rb +8 -0
  70. data/app/overrides/add_cache_to_product_listing_page.rb +41 -0
  71. data/app/overrides/add_class_to_login_link.rb +6 -0
  72. data/app/overrides/add_confirm_state_to_checkout_content_block.rb.rb +30 -0
  73. data/app/overrides/add_promotions_nav.rb +8 -0
  74. data/app/overrides/add_turbo_frame_to_admin_order_line_item_edit_link.rb +23 -0
  75. data/app/overrides/add_turbo_frame_to_promotion_action_edit_link.rb +20 -0
  76. data/app/overrides/add_turbo_stream_format_to_cart_path_in_cart_pane.rb +8 -0
  77. data/app/overrides/ba_spree_local_time_add_custom_javascript_import_map_tags.rb +8 -0
  78. data/app/overrides/disable_hidden_bill_address_fields_on_payment.rb +15 -0
  79. data/app/overrides/disable_turbo_on_user_registration_form.rb +18 -0
  80. data/app/overrides/fix_coupon_apply_button_layout.rb +7 -0
  81. data/app/overrides/hide_gift_cards_and_store_credits_from_account_nav.rb +11 -0
  82. data/app/overrides/hide_shipping_message_for_digital_only_orders.rb +15 -0
  83. data/app/overrides/localize_apply_button_text.rb +6 -0
  84. data/app/overrides/localize_newsletter_button_text.rb +6 -0
  85. data/app/overrides/replace_announcement_bar_text.rb +6 -0
  86. data/app/overrides/replace_button_edit_checkout.rb +10 -0
  87. data/app/overrides/replace_checkout_delivery_info.rb +6 -0
  88. data/app/overrides/replace_checkout_pay_button_with_dynamic_label.rb +14 -0
  89. data/app/overrides/replace_variant_not_available_button.rb +19 -0
  90. data/app/overrides/set_logo_width_height_in_admin_store_basic_form.rb +12 -0
  91. data/app/overrides/show_breadcrumbs_on_taxon_page.rb +30 -0
  92. data/app/overrides/show_individual_adjustments_in_order_details.rb +18 -0
  93. data/app/overrides/show_only_selected_payment_method_on_confirm.rb +29 -0
  94. data/app/overrides/spree/admin/preserve_filter_params_across_table_forms.rb +64 -0
  95. data/app/overrides/spree/admin/promotion_rules/_promotion_rule/set_turbo_frame_on_edit_link.html.erb.deface +11 -0
  96. data/app/overrides/spree/admin/stores/add_special_instruction_message_settings.rb +17 -0
  97. data/app/overrides/use_absolute_time_for_order_header_completed_at.rb +15 -0
  98. data/app/presenters/spree/mega_nav_presenter_decorator.rb +26 -0
  99. data/app/services/ba_spree/setup_service.rb +144 -0
  100. data/app/services/spree/assets/position_normalizer.rb +67 -0
  101. data/app/services/spree/products/duplicator_decorator.rb +39 -0
  102. data/app/services/spree/products/prepare_nested_attributes_decorator.rb +58 -0
  103. data/app/views/spree/addresses/edit.html.erb +64 -0
  104. data/app/views/spree/admin/orders/_order.html.erb +32 -0
  105. data/app/views/spree/admin/orders/_summary.html.erb +122 -0
  106. data/app/views/spree/admin/shared/_custom_head.html.erb +56 -0
  107. data/app/views/spree/admin/shared/sidebar/_promotions_nav.html.erb +55 -0
  108. data/app/views/spree/admin/stores/_special_instruction_message_settings.html.erb +14 -0
  109. data/app/views/spree/checkout/_accept_marketing_checkbox.html.erb +40 -0
  110. data/app/views/spree/checkout/_address.html.erb +135 -0
  111. data/app/views/spree/checkout/_confirm.html.erb +25 -0
  112. data/app/views/spree/checkout/_line_item.html.erb +26 -0
  113. data/app/views/spree/checkout/_payment_method_confirm.html.erb +8 -0
  114. data/app/views/spree/checkout/_payment_methods.html.erb +88 -0
  115. data/app/views/spree/checkout/_summary.html.erb +146 -0
  116. data/app/views/spree/shared/_purchased_items_table.html.erb +60 -0
  117. data/app/views/spree/shared/_purchased_items_table.text.erb +24 -0
  118. data/app/views/themes/default/spree/orders/_empty.html.erb +14 -0
  119. data/app/views/themes/default/spree/orders/_summary.html.erb +87 -0
  120. data/app/views/themes/default/spree/orders/edit.turbo_stream.erb +10 -0
  121. data/app/views/themes/default/spree/page_sections/_featured_taxons.html.erb +38 -0
  122. data/app/views/themes/default/spree/page_sections/_header.html.erb +124 -0
  123. data/app/views/themes/default/spree/page_sections/_newsletter.html.erb +4 -0
  124. data/app/views/themes/default/spree/page_sections/_post_details.html.erb +22 -0
  125. data/app/views/themes/default/spree/page_sections/_product_details.html.erb +88 -0
  126. data/app/views/themes/default/spree/page_sections/_product_grid.html.erb +58 -0
  127. data/app/views/themes/default/spree/page_sections/nav/_desktop.html.erb +94 -0
  128. data/app/views/themes/default/spree/page_sections/nav/_mobile.html.erb +148 -0
  129. data/app/views/themes/default/spree/posts/_post.html.erb +16 -0
  130. data/app/views/themes/default/spree/products/_description.html.erb +7 -0
  131. data/app/views/themes/default/spree/products/_details.html.erb +65 -0
  132. data/app/views/themes/default/spree/products/_json_ld_variant.html.erb +15 -0
  133. data/app/views/themes/default/spree/products/_price.html.erb +76 -0
  134. data/app/views/themes/default/spree/products/_swiper.html.erb +90 -0
  135. data/app/views/themes/default/spree/shared/_account_pane.html.erb +18 -0
  136. data/app/views/themes/default/spree/shared/_cart_icon.html.erb +19 -0
  137. data/app/views/themes/default/spree/shared/_line_item_options.html.erb +9 -0
  138. data/app/views/themes/default/spree/shared/_logo.html.erb +39 -0
  139. data/app/views/themes/default/spree/shared/_nav_taxon_square_image.html.erb +8 -0
  140. data/app/views/themes/default/spree/shared/_order_shipment.html.erb +79 -0
  141. data/app/views/themes/default/spree/shared/_search.html.erb +33 -0
  142. data/app/views/themes/default/spree/shared/icons/_cart.html.erb +6 -0
  143. data/app/views/themes/default/spree/shared/icons/_cart_48.html.erb +8 -0
  144. data/app/views/themes/default/spree/shared/icons/_heart.html.erb +15 -0
  145. data/config/importmap.rb +12 -0
  146. data/config/initializers/admin_navigation.rb +43 -0
  147. data/config/initializers/spree.rb +6 -0
  148. data/config/initializers/spree_admin_label_i18n_alias.rb +52 -0
  149. data/config/initializers/spree_admin_tables.rb +57 -0
  150. data/config/initializers/spree_permitted_attributes.rb +1 -0
  151. data/config/locales/en.yml +15 -0
  152. data/config/locales/ja.yml +18 -0
  153. data/config/locales/spree.en.yml +4 -0
  154. data/config/locales/spree.ja.yml +3493 -0
  155. data/lib/ba_spree/engine.rb +41 -0
  156. data/lib/ba_spree/spree_events_dedup_patch.rb +44 -0
  157. data/lib/ba_spree/version.rb +3 -0
  158. data/lib/ba_spree.rb +20 -0
  159. data/lib/generators/ba_spree/install/install_generator.rb +85 -0
  160. metadata +594 -0
@@ -0,0 +1,95 @@
1
+ module BaSpree
2
+ module OrderDecorator
3
+ ASSOCIATED_USER_ATTRIBUTES = [ :user_id, :email, :created_by_id, :bill_address_id, :ship_address_id ]
4
+
5
+ # @gem-override spree_core-5.3.6/app/models/spree/order.rb#associate_user!
6
+ # @see https://github.com/be-agile/giga-repeat/commit/d0dbe2b4524e22c1cf974bbd4829c587ef7cd254
7
+ # Associates the specified user with the order.
8
+ def associate_user!(user, override_email = true)
9
+ self.user = user
10
+ self.email = user.email if override_email
11
+ # we need to check if user is of admin user class to avoid mismatch type error
12
+ # in a scenario where we have separate classes for admin and regular users
13
+ self.created_by ||= user if user.is_a?(Spree.admin_user_class)
14
+ self.bill_address ||= user.bill_address
15
+ self.ship_address ||= user.ship_address
16
+
17
+ # 未保存(id=nil)の order では where(id: self) が `WHERE id IS NULL` となり、
18
+ # update_all が無関係なレコードを対象にしたり reload が RecordNotFound を投げる。
19
+ # まだ永続化されていない場合はメモリ上の代入のみで十分なので DB 反映はスキップする。
20
+ return self unless persisted?
21
+
22
+ changes = slice(*ASSOCIATED_USER_ATTRIBUTES)
23
+
24
+ # immediately persist the changes we just made, but don't use save
25
+ # since we might have an invalid address associated
26
+ ActiveRecord::Base.connected_to(role: :writing) do
27
+ self.class.unscoped.where(id: self).update_all(changes)
28
+ end
29
+
30
+ # Reload to ensure memory state matches DB state, then recalculate promotions
31
+ reload
32
+ updater.update
33
+
34
+ # Spree 5.3 追従: update_all はコールバックを bypass するため order.updated を手動 publish
35
+ publish_event('order.updated') if changes.present?
36
+ end
37
+
38
+ # @gem-override spree_core-5.3.6/app/models/spree/order.rb#disassociate_user!
39
+ # @see https://github.com/be-agile/giga-repeat/commit/d0dbe2b4524e22c1cf974bbd4829c587ef7cd254
40
+ def disassociate_user!
41
+ nullified_attributes = ASSOCIATED_USER_ATTRIBUTES.index_with(nil)
42
+
43
+ update_columns(nullified_attributes)
44
+
45
+ # Reload to ensure memory state matches DB state, then recalculate promotions
46
+ reload
47
+ updater.update
48
+ end
49
+
50
+ # @gem-override spree_core-5.3.6/app/models/spree/order.rb#after_cancel
51
+ # @see https://github.com/be-agile/giga-repeat/issues/1001
52
+ # Spree 5.3 追従: 本家追加の publish_order_canceled_event は super で取り込まれる
53
+ # 注文キャンセル時にpending状態のゲートウェイ決済(オーソリ)もキャンセルする
54
+ # Spreeコアは completed のみをキャンセルするが、オーソリのまま残すと以下の問題がある:
55
+ # - 決済プロバイダー側にオーソリが残る
56
+ # - 顧客のカード利用可能額が減少したまま
57
+ # - 後で誤って売上計上される可能性
58
+ def after_cancel
59
+ super
60
+
61
+ # pending 状態のゲートウェイ決済(オーソリ)をキャンセル
62
+ # store_credit は Spree コアで処理済みなので除外
63
+ payments.pending.not_store_credits.each do |payment|
64
+ # Gatewayベースの決済方法のみ対象(銀行振込や代引きは除外)
65
+ if payment.payment_method.is_a?(Spree::Gateway)
66
+ payment.cancel!
67
+ end
68
+ end
69
+ end
70
+
71
+ # @gem-override spree_core-5.3.6/app/models/spree/order.rb#canceled_by
72
+ # @see https://github.com/be-agile/giga-repeat/issues/1232
73
+ # Spree 5.3.6 本家のバグ修正: canceled_by 末尾の publish_event('order.canceled') は
74
+ # コメント("Manually publish update event")通り本来 'order.updated' を publish すべき箇所。
75
+ # 'order.canceled' のままだと、transaction 内の cancel! が after_cancel 経由で既に
76
+ # 'order.canceled' を publish しているのと合わせて二重 publish となり、
77
+ # OrderEmailSubscriber#send_cancel_email がガード無しのためキャンセルメールが2通送信される。
78
+ # update_columns はコールバックを bypass するため、本来意図された 'order.updated' を publish する。
79
+ def canceled_by(user, canceled_at = nil)
80
+ canceled_at ||= Time.current
81
+ changes = { canceler_id: user.id, canceled_at: canceled_at }
82
+
83
+ transaction do
84
+ update_columns(changes)
85
+ cancel!
86
+ end
87
+
88
+ # Manually publish update event since update_columns bypasses callbacks
89
+ # (cancel! 内の after_cancel が 'order.canceled' を publish 済みなので、ここは update のみ)
90
+ publish_event('order.updated')
91
+ end
92
+ end
93
+ end
94
+
95
+ Spree::Order.prepend(BaSpree::OrderDecorator)
@@ -0,0 +1,33 @@
1
+ module BaSpree
2
+ module OrderUpdaterDecorator
3
+ # @gem-override spree_core-5.3.6/app/models/spree/order_updater.rb#update_adjustment_total
4
+ # @see https://github.com/be-agile/giga-repeat/issues/909
5
+ # super 後に order-level adjustment に対する内税計算を追加する。
6
+ # Spree 5.3 追従: 本家の sum→pick 単一クエリ最適化は super 経由で取り込まれるため
7
+ # override 本体は無変更(super の結果を部分的に上書きする設計のため互換)。
8
+ def update_adjustment_total
9
+ super
10
+
11
+ order.adjustments.tax.where(adjustable_type: 'Spree::Order').destroy_all
12
+
13
+ rates = Spree::TaxRate.match(order.tax_zone)
14
+ rates.each do |rate|
15
+ rate.adjust_order_level_adjustments(order)
16
+ end
17
+
18
+ order.included_tax_total = line_items.sum(:included_tax_total) +
19
+ shipments.sum(:included_tax_total) +
20
+ adjustments.tax.eligible.where(adjustable_type: 'Spree::Order').sum(:amount)
21
+ # 0より小さいとvalidateエラーになるため
22
+ order.included_tax_total = 0 if order.included_tax_total.negative?
23
+
24
+ order.adjustment_total = line_items.sum(:adjustment_total) +
25
+ shipments.sum(:adjustment_total) +
26
+ adjustments.eligible.non_tax.sum(:amount)
27
+
28
+ update_order_total
29
+ end
30
+ end
31
+ end
32
+
33
+ Spree::OrderUpdater.prepend(BaSpree::OrderUpdaterDecorator)
@@ -0,0 +1,10 @@
1
+ module BaSpree
2
+ module PaymentMethodDecorator
3
+ def tax_category
4
+ return nil unless respond_to?(:preferred_tax_category_id)
5
+ Spree::TaxCategory.find_by(id: preferred_tax_category_id) if preferred_tax_category_id.present?
6
+ end
7
+ end
8
+ end
9
+
10
+ Spree::PaymentMethod.prepend(BaSpree::PaymentMethodDecorator)
@@ -0,0 +1,44 @@
1
+ module BaSpree
2
+ module ProductDecorator
3
+ def self.prepended(base)
4
+ # Spree 5.3 以前は、商品ごとのオプション並び順(spree_product_option_types.position)を正にするため
5
+ # `option_types` を reorder('spree_product_option_types.position') で上書きしていた。
6
+ # しかし through association の order/reorder に「他テーブルの列」を書くと、Rails が preload
7
+ # (WHERE id IN (...)) で読む経路で JOIN が外れ ORDER 句だけ残るため、
8
+ # 「Unknown column 'spree_product_option_types.position' in 'order clause'」で
9
+ # taxons#show(カテゴリページ)が 500 になる(5.3 で option_types を preload する経路が増え顕在化)。
10
+ #
11
+ # この罠は through の order に他テーブル列を書く限り原理的に回避できないため、上書きを削除し
12
+ # 5.3 core のデフォルト(`option_types, through: :product_option_types` + `product_option_types
13
+ # -> order(:position)`)に委ねる。これにより並び順は
14
+ # `spree_option_types.position`(グローバル順) → `spree_product_option_types.position`(商品ごと順)
15
+ # の複合になり、「商品ごと順」は第2ソートキーへ後退する。
16
+ # ※ org4 の複数 option_type 商品 355 件すべてで両者の並び順が一致することを確認済み(実害なし)。
17
+ # 将来「商品ごとにオプション表示順を変える」運用が必要になった場合は、option_types を直接
18
+ # 並べ替えるのではなく product_option_types.order(:position) 経由で option_type を引く形に
19
+ # 変更すること(preload 安全な書き方が必要)。
20
+ base.has_many :option_types_for_includes, -> { order(:id) },
21
+ through: :product_option_types,
22
+ source: :option_type,
23
+ class_name: 'Spree::OptionType'
24
+
25
+ # ProductProperty -> metafields 移行用の読み書きヘルパー(Spree 5.4 の Property 削除対応)
26
+ base.include Spree::ProductPropertyMetafields
27
+ end
28
+
29
+ def set_slug
30
+ self.slug = generate_slug
31
+ end
32
+
33
+ private
34
+ def generate_slug
35
+ if new_record?
36
+ uuid_for_friendly_id
37
+ else
38
+ self.changes["slug"]&.first || self.slug
39
+ end
40
+ end
41
+ end
42
+ end
43
+
44
+ Spree::Product.prepend(BaSpree::ProductDecorator)
@@ -0,0 +1,23 @@
1
+ module BaSpree
2
+ module StoreDecorator
3
+ def self.prepended(base)
4
+ base.has_rich_text :special_instruction_message
5
+
6
+ base.class_eval do
7
+ # @gem-override spree_core-5.3.6/app/models/spree/store.rb#default
8
+ # @see https://github.com/be-agile/giga-repeat/commit/b7c74b2a1f45555616c19f3788def6aa3f67a7a1
9
+ # 本家は default フラグが無ければ DB の最初の1件を返すが、ba_spree では
10
+ # 該当が無いとき新規 default インスタンスを返す(マルチテナントで未作成テナント対策)。
11
+ def self.default
12
+ if where(default: true).any?
13
+ where(default: true).first
14
+ else
15
+ new(default: true)
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+
23
+ Spree::Store.prepend BaSpree::StoreDecorator
@@ -0,0 +1,49 @@
1
+ module BaSpree
2
+ module VariantDecorator
3
+ # @gem-override spree_core-5.3.6/app/models/spree/variant.rb#set_option_value
4
+ # @see https://github.com/be-agile/giga-repeat/commit/108316dd2cab88a61933080614e15663517c7291
5
+ def set_option_value(opt_name, opt_value, opt_type_position = nil)
6
+ # no option values on master
7
+ return if is_master
8
+
9
+ option_type = Spree::OptionType.where(name: opt_name).first_or_initialize do |o|
10
+ o.name = o.presentation = opt_name
11
+ o.save!
12
+ end
13
+
14
+ current_value = find_option_value(opt_name)
15
+
16
+ if current_value.nil?
17
+ # then we have to check to make sure that the product has the option type
18
+ product_option_type = if (existing_prod_ot = product.product_option_types.find { |ot| ot.option_type_id == option_type.id })
19
+ existing_prod_ot
20
+ else
21
+ product_option_type = product.product_option_types.new
22
+ product_option_type.option_type = option_type
23
+ end
24
+ product_option_type.position = opt_type_position if opt_type_position
25
+ product_option_type.save! if product_option_type.new_record? || product_option_type.changed?
26
+ else
27
+ return if current_value.name == opt_value
28
+
29
+ option_values.delete(current_value)
30
+ end
31
+
32
+ option_value = option_type.option_values.where(name: opt_value).first_or_initialize do |o|
33
+ o.name = o.presentation = opt_value
34
+ o.save!
35
+ end
36
+
37
+ option_values << option_value
38
+ save
39
+ end
40
+
41
+ # @gem-override spree_core-5.3.6/app/models/spree/variant.rb#find_option_value
42
+ # @see https://github.com/be-agile/giga-repeat/commit/108316dd2cab88a61933080614e15663517c7291
43
+ def find_option_value(opt_name)
44
+ option_values.includes(:option_type).detect { |o| o.option_type.name == opt_name }
45
+ end
46
+ end
47
+ end
48
+
49
+ Spree::Variant.prepend(BaSpree::VariantDecorator)
@@ -0,0 +1,66 @@
1
+ module Spree
2
+ module AdjustmentSourceDecorator
3
+ # @gem-override spree_core-5.3.6/app/models/concerns/spree/adjustment_source.rb#create_unique_adjustment
4
+ # @see https://github.com/be-agile/giga-repeat/issues/1183
5
+ # 並行リクエスト時の重複 adjustment 作成を防ぐため、order を排他ロックしてから
6
+ # 既存チェックと作成を実行する。これがないと、ブラウザ複数タブから同一 order に
7
+ # 同時にカート操作されたとき、`already_adjusted?` の SELECT が両リクエストで
8
+ # false を返し、同じ source の adjustment が二重に INSERT されてしまう。
9
+ def create_unique_adjustment(order, adjustable)
10
+ with_order_lock(order) { super }
11
+ end
12
+
13
+ # @gem-override spree_core-5.3.6/app/models/concerns/spree/adjustment_source.rb#create_unique_adjustments
14
+ # @see https://github.com/be-agile/giga-repeat/issues/1183
15
+ def create_unique_adjustments(order, adjustables, &block)
16
+ with_order_lock(order) { super }
17
+ end
18
+
19
+ private
20
+
21
+ # @gem-override spree_core-5.3.6/app/models/concerns/spree/adjustment_source.rb#already_adjusted?
22
+ # @see https://github.com/be-agile/giga-repeat/issues/1183
23
+ # `already_adjusted?` を locking read (FOR UPDATE) で実行することで、
24
+ # MySQL REPEATABLE READ 下の snapshot 問題を回避する。
25
+ # 通常 SELECT は transaction 開始時の snapshot を見るため、先行 transaction の
26
+ # adjustment INSERT が後続 transaction から見えないケースがあるが、
27
+ # locking read は current read で最新状態を見るため、先行の commit 後の
28
+ # adjustment が必ず見える。
29
+ def already_adjusted?(adjustable)
30
+ adjustments.lock.where(adjustable: adjustable).exists?
31
+ end
32
+
33
+ # @gem-override spree_core-5.3.6/app/models/concerns/spree/adjustment_source.rb#already_adjusted_ids
34
+ # @see https://github.com/be-agile/giga-repeat/issues/1183
35
+ # 同上の理由で locking read で実行する。
36
+ def already_adjusted_ids(order)
37
+ adjustments.lock.where(order: order).pluck(:adjustable_id)
38
+ end
39
+
40
+ # order インスタンス自体に dirty な属性 (例: last_ip_address) がある状態で
41
+ # `order.with_lock` を呼ぶと Rails 8 では RuntimeError になるため、
42
+ # 別インスタンスで `SELECT ... FOR UPDATE` を発行してロックを取得する。
43
+ # トランザクション内で行リレベルロックを取れば、後続リクエストは
44
+ # 先行トランザクションの commit を待つので race condition は防げる。
45
+ def with_order_lock(order)
46
+ order.class.transaction do
47
+ order.class.lock.where(id: order.id).pluck(:id)
48
+ yield
49
+ end
50
+ end
51
+ end
52
+ end
53
+
54
+ # Spree::AdjustmentSource を include している promotion action 系の各クラスに対して
55
+ # decorator を prepend する。concern module 自体に prepend するのではなく include 元クラスに
56
+ # 直接適用することで、Ruby のメソッド解決順が確実に decorator を経由するようにする。
57
+ # TaxRate は AdjustmentSource を include しているが create_unique_adjustment を経由しないため対象外。
58
+ [
59
+ Spree::Promotion::Actions::CreateAdjustment,
60
+ Spree::Promotion::Actions::CreateItemAdjustments,
61
+ Spree::Promotion::Actions::FreeShipping,
62
+ defined?(Spree::Promotion::Actions::CreateVolumeDiscountAdjustments) ? Spree::Promotion::Actions::CreateVolumeDiscountAdjustments : nil,
63
+ defined?(Spree::Promotion::Actions::CreateVolumeBasedOptionFee) ? Spree::Promotion::Actions::CreateVolumeBasedOptionFee : nil
64
+ ].compact.each do |klass|
65
+ klass.prepend(Spree::AdjustmentSourceDecorator)
66
+ end
@@ -0,0 +1,52 @@
1
+ module Spree
2
+ # Spree 5.4 で Spree::Property が削除されるため、ProductProperty で
3
+ # 保持していた商品の付加情報(gravity_forms / 地図形式 / 提供形態 / short_description 等)を
4
+ # Spree 5.2 の metafields に移行する。その読み書きを一箇所に集約する薄いヘルパー。
5
+ #
6
+ # 移行 rake(migrate_product_properties_to_metafields)は旧 ProductProperty を
7
+ # namespace="properties", key=property.name, value=product_property.value
8
+ # で metafield にコピー済み。したがって本ヘルパーは namespace="properties" 規約のもとで
9
+ # 本家の get_metafield / set_metafield を薄くラップするだけにとどめ、本家 API を再実装しない。
10
+ #
11
+ # 日本語 key("地図形式" 等)は ba_spree/metafield_definition_decorator.rb が
12
+ # 本家 normalizes(parameterize) を無効化済みのため、そのまま通る(再 normalize 不要)。
13
+ module ProductPropertyMetafields
14
+ extend ActiveSupport::Concern
15
+
16
+ PROPERTY_NAMESPACE = 'properties'.freeze
17
+
18
+ # 旧 product.property(name) 相当。指定プロパティ名の metafield 値(文字列)を返す。無ければ nil。
19
+ def property_metafield_value(name)
20
+ get_metafield(property_metafield_key(name))&.value
21
+ end
22
+
23
+ # 旧 product_properties.create!/update 相当。指定プロパティ名の metafield に値を設定する。
24
+ # MetafieldDefinition は本家 set_metafield 内で find_or_create される。
25
+ def set_property_metafield(name, value)
26
+ set_metafield(property_metafield_key(name), value)
27
+ end
28
+
29
+ # 旧 product.has_property? 相当。
30
+ def property_metafield?(name)
31
+ has_metafield?(property_metafield_key(name))
32
+ end
33
+
34
+ # PDP 表示用。namespace="properties" かつフロント表示対象(available_on_front_end)の
35
+ # metafield を、定義の作成順で返す。旧
36
+ # product_properties.joins(:property).merge(Spree::Property.available_on_front_end)
37
+ # .sort_by_property_position
38
+ # の置き換え。各要素は Spree::Metafield で、name(=表示名) / value / id を持つ。
39
+ def front_end_property_metafields
40
+ public_metafields
41
+ .select { |mf| mf.metafield_definition&.namespace == PROPERTY_NAMESPACE }
42
+ .sort_by { |mf| mf.metafield_definition_id }
43
+ end
44
+
45
+ private
46
+
47
+ # "提供形態" -> "properties.提供形態"
48
+ def property_metafield_key(name)
49
+ "#{PROPERTY_NAMESPACE}.#{name}"
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,16 @@
1
+ module Spree
2
+ module Adjustable
3
+ module Adjuster
4
+ module PromotionDecorator
5
+ # @gem-override spree_core-5.3.6/app/models/spree/adjustable/adjuster/promotion.rb#choose_best_promo_adjustment
6
+ # @see https://github.com/be-agile/giga-repeat/commit/3d5683b6c3e6875cfd808368f0ab21c020270ebb
7
+ # 本家は競合プロモーション adjustment のうち最良のもの以外を eligible: false にするが、
8
+ # ba_spree では複数プロモーションを併用させるため、この絞り込みを無効化する。
9
+ def choose_best_promo_adjustment
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
15
+
16
+ Spree::Adjustable::Adjuster::Promotion.prepend(Spree::Adjustable::Adjuster::PromotionDecorator)
@@ -0,0 +1,52 @@
1
+ module Spree
2
+ module Admin
3
+ class Table
4
+ # 本家 Spree::Admin::Table::Filter の OPERATORS は label が英語ハードコード
5
+ # ("equals" 等)で、フィルタービルダーの演算子セレクトが日本語にならない
6
+ # 原因の一つ(Ruby 側)。operators_for_select はこの label を
7
+ # data-query-builder-operators-value として JS に渡すため、ここを i18n
8
+ # (admin.tables.operators.*) から引くよう上書きする。
9
+ #
10
+ # 注意: OPERATORS 定数を起動時に書き換えるとロケールが起動時で固定されて
11
+ # しまうため、リクエストごとに評価される operators_for_select 側で翻訳する。
12
+ module FilterDecorator
13
+ module ClassMethods
14
+ # OPERATORS の各 operator キー -> i18n キー(admin.tables.operators 配下)。
15
+ # キーと i18n キーの対応は本家ビュー(_filter_row 等)が使う
16
+ # admin.tables.operators.* に合わせる。
17
+ OPERATOR_I18N_KEYS = {
18
+ eq: :equals,
19
+ not_eq: :not_equals,
20
+ cont: :contains,
21
+ not_cont: :does_not_contain,
22
+ start: :starts_with,
23
+ end: :ends_with,
24
+ gt: :greater_than,
25
+ gteq: :greater_than_or_equal,
26
+ lt: :less_than,
27
+ lteq: :less_than_or_equal,
28
+ in: :is_any_of,
29
+ not_in: :is_none_of,
30
+ null: :is_empty,
31
+ not_null: :is_not_empty
32
+ }.freeze
33
+
34
+ def operators_for_select
35
+ super.map do |operator|
36
+ i18n_key = OPERATOR_I18N_KEYS[operator[:value].to_sym]
37
+ next operator unless i18n_key
38
+
39
+ operator.merge(label: Spree.t("admin.tables.operators.#{i18n_key}"))
40
+ end
41
+ end
42
+ end
43
+
44
+ def self.prepended(base)
45
+ base.singleton_class.prepend(ClassMethods)
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+
52
+ Spree::Admin::Table::Filter.prepend(Spree::Admin::Table::FilterDecorator)
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Spree
4
+ module Admin
5
+ class Table
6
+ # フィルタービルダーの「または」グループが ransack に届かず、絞り込みとして機能していない。
7
+ #
8
+ # 本家 (spree_admin 5.3.6) の build_or_grouping には2つの問題がある:
9
+ #
10
+ # 1. p (述語) と v (値) を a (属性) の内側に入れている。
11
+ # ransack は a と兄弟の階層に p / v を期待するため、grouping ごと黙って無視される
12
+ # (= 全件が返る)。
13
+ # 2. 述語を operator.to_s.sub('_', '') で作っており、最初の _ を落としてしまう。
14
+ # not_eq → "noteq"、not_cont → "notcont" のように不正な述語になる。
15
+ #
16
+ # @see https://github.com/be-agile/giga-repeat/issues/1262
17
+ module FilterGroupDecorator
18
+ private
19
+
20
+ # @gem-override spree_admin-5.3.6/app/models/spree/admin/table/filter_group.rb#build_or_grouping
21
+ # @see https://github.com/be-agile/giga-repeat/issues/1262
22
+ def build_or_grouping(group)
23
+ conditions = {}
24
+
25
+ group.filters.each_with_index do |filter, index|
26
+ param = filter.to_ransack_param
27
+ next if param.empty?
28
+
29
+ conditions[index.to_s] = {
30
+ a: { '0' => { name: filter.field } },
31
+ p: ransack_predicate_for(filter),
32
+ v: ransack_values_for(param.values.first)
33
+ }
34
+ end
35
+
36
+ {
37
+ m: 'or',
38
+ c: conditions
39
+ }
40
+ end
41
+
42
+ # ransack の述語名 (先頭の _ を除いた OPERATORS の predicate)
43
+ # @return [String]
44
+ def ransack_predicate_for(filter)
45
+ predicate = Spree::Admin::Table::Filter::OPERATORS.dig(filter.operator, :predicate)
46
+ predicate.to_s.delete_prefix('_')
47
+ end
48
+
49
+ # ransack の値。in / not_in のように配列を取る述語では、値をまとめて1つの v に入れると
50
+ # マッチしなくなる (0件になる) ため、要素ごとに v のエントリを作る。
51
+ # @return [Hash]
52
+ def ransack_values_for(value)
53
+ values = value.is_a?(Array) ? value : [ value ]
54
+ # 値が未入力 (空配列) の場合は空の v を作らない。nil を渡せば ransack がその条件を無視する
55
+ values = [ nil ] if values.empty?
56
+
57
+ values.each_with_index.to_h { |item, index| [ index.to_s, { value: item } ] }
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
63
+
64
+ Spree::Admin::Table::FilterGroup.prepend(Spree::Admin::Table::FilterGroupDecorator)
@@ -0,0 +1,102 @@
1
+ module Spree
2
+ # @gem-override spree_core-5.3.6/app/models/spree/asset.rb
3
+ # reason: Spree 5.3 で Asset#attachment に named variant の事前生成ブロック
4
+ # (mini/small/medium/large/xlarge/og_image を format: webp + preprocessed: true で定義)が
5
+ # 追加された。preprocessed: true は画像 attach 時に 1 画像あたり 6 本の
6
+ # ActiveStorage::TransformJob をキューし、さらに表示時に未生成 variant を
7
+ # /rails/active_storage/representations/... でオンザフライ webp 変換する。
8
+ #
9
+ # 動産社等のローカル / stg では画像 blob の実体が本番 S3 にしか無く、変換が
10
+ # 元画像を取得できずに 1 リクエストあたり数百ms〜1秒の遅延・失敗となる。画像の多い
11
+ # ページではこれが積み重なり、スモークテスト / ブラウザテストがタイムアウトする
12
+ # (Issue #1210 のコメント参照)。
13
+ #
14
+ # そこで「variant の定義自体は残す(ビューの image.variant(:large) 等が
15
+ # ArgumentError にならないように)が、事前生成 preprocessed を環境フラグで
16
+ # false に倒す」ことで、attach 時の TransformJob 大量発行を止める。
17
+ # 本番(フラグ未設定)では 5.3 デフォルトの preprocessed: true を維持する。
18
+ #
19
+ # 制御: ENV['DISABLE_IMAGE_VARIANT_PREPROCESSING'] が truthy のとき preprocessed を無効化。
20
+ # development / test ではデフォルトで無効化する(画像実体が無い環境での遅延回避)。
21
+ #
22
+ # ⚠️ stg は RAILS_ENV=production で動作するため、env 名だけでは本番と区別できない。
23
+ # stg で画像処理を止めたい場合は stg のデプロイ環境変数に
24
+ # DISABLE_IMAGE_VARIANT_PREPROCESSING=true を明示設定すること。
25
+ # 本番環境では未設定のままにし、5.3 デフォルト(preprocessed: true)を維持する。
26
+ #
27
+ # @see https://github.com/be-agile/giga-repeat/issues/1210
28
+ module AssetDecorator
29
+ def self.prepended(base)
30
+ # acts_as_list の採番 (bottom_position_in_list + 1) はロックを取らないため、
31
+ # 管理画面の一括アップロードのように同一 viewable へ並列に INSERT すると
32
+ # 同じ position を持つレコードが同時に作られる。その状態になると並び替えが
33
+ # 効かなくなるため、採番より先に viewable の行を排他ロックして直列化する。
34
+ # acts_as_list の before_create :avoid_collision より先に走らせる必要がある。
35
+ # @see https://github.com/be-agile/giga-repeat/issues/1292
36
+ base.before_create :lock_position_scope, prepend: true
37
+
38
+ base.has_one_attached :attachment, service: Spree.public_storage_service_name do |attachable|
39
+ # 本家同様、キー順(format, resize_to_fill, saver)を維持して variation digest を一致させる。
40
+ Spree::Config.product_image_variant_sizes.each do |name, (width, height)|
41
+ attachable.variant name,
42
+ format: 'webp',
43
+ resize_to_fill: [ width, height ],
44
+ saver: Spree::Asset::WEBP_SAVER_OPTIONS,
45
+ preprocessed: !Spree::Asset.image_variant_preprocessing_disabled?
46
+ end
47
+ end
48
+ end
49
+
50
+ # viewable の行に排他ロックを取ったうえで position を採番し、同一 viewable への
51
+ # 並列 INSERT を直列化する。ロックはこの save のトランザクション終了時に解放される。
52
+ #
53
+ # 採番をここで行うのは、acts_as_list の採番が通常の SELECT (consistent read) で
54
+ # 最大 position を読むためである。MySQL の REPEATABLE READ では、行ロックの取得を
55
+ # 待っている間に他トランザクションが commit した行が consistent read から見えず、
56
+ # ロックを取っても同じ position を採番してしまう。ロック付き読み取り (FOR UPDATE) は
57
+ # 常に最新の確定値を読むため、こちらで採番する。
58
+ #
59
+ # viewable が未確定 (新規商品作成時のセッション退避アップロード等) の場合は
60
+ # ロック対象の行が無いため何もしない。この経路の position は
61
+ # Spree::Admin::ProductsControllerDecorator が付け替え後に詰め直す。
62
+ def lock_position_scope
63
+ return if viewable_id.blank? || viewable_type.blank?
64
+
65
+ lock_viewable_row
66
+ return if self[:position].present?
67
+
68
+ bottom = Spree::Asset.unscoped
69
+ .where(viewable_type: viewable_type, viewable_id: viewable_id)
70
+ .lock
71
+ .maximum(:position)
72
+ self[:position] = bottom.to_i + 1
73
+ end
74
+
75
+ # 同一 viewable の position を操作する処理を直列化するための行ロック。
76
+ # 採番(lock_position_scope)と並び替え(Spree::Admin::AssetsControllerDecorator)の
77
+ # 双方が同じ行を取り合うことで、両者が混ざらないようにする。
78
+ def lock_viewable_row
79
+ return if viewable_id.blank? || viewable_type.blank?
80
+
81
+ viewable_class = viewable_type.safe_constantize
82
+ return if viewable_class.nil?
83
+
84
+ viewable_class.unscoped.lock.where(id: viewable_id).pick(:id)
85
+ end
86
+
87
+ module ClassMethods
88
+ # 画像 variant の事前生成(preprocessed)を無効化すべき環境かどうか。
89
+ # ENV['DISABLE_IMAGE_VARIANT_PREPROCESSING'] を最優先で見る(本番でも明示的に切り替え可能)。
90
+ # 未設定なら development / test で自動的に無効化する。
91
+ def image_variant_preprocessing_disabled?
92
+ flag = ENV['DISABLE_IMAGE_VARIANT_PREPROCESSING']
93
+ return ActiveModel::Type::Boolean.new.cast(flag) unless flag.nil?
94
+
95
+ Rails.env.development? || Rails.env.test?
96
+ end
97
+ end
98
+ end
99
+ end
100
+
101
+ Spree::Asset.singleton_class.prepend(Spree::AssetDecorator::ClassMethods)
102
+ Spree::Asset.prepend(Spree::AssetDecorator)
@@ -0,0 +1,22 @@
1
+ module Spree
2
+ module ContactDecorator
3
+ def self.prepended(base)
4
+ base.attribute :mail_from_address, validate: true
5
+ end
6
+
7
+ # @gem-override spree_storefront-5.3.6/app/models/spree/contact.rb#headers
8
+ # @see https://github.com/be-agile/giga-repeat/issues/1092
9
+ # 本家は from を問い合わせ者本人のメールにするが、SES 未検証アドレスで 500 になるため
10
+ # from を mail_from_address(検証済み送信元)に変え、本人は reply_to に回す。
11
+ def headers
12
+ {
13
+ subject: 'Contact Us',
14
+ to: customer_support_email.to_s,
15
+ from: mail_from_address,
16
+ reply_to: %("#{name}" <#{email}>)
17
+ }
18
+ end
19
+ end
20
+ end
21
+
22
+ Spree::Contact.prepend Spree::ContactDecorator
@@ -0,0 +1,9 @@
1
+ module Spree
2
+ module DigitalLinkDecorator
3
+ def downloadable?
4
+ line_item.order.paid?
5
+ end
6
+ end
7
+ end
8
+
9
+ Spree::DigitalLink.prepend Spree::DigitalLinkDecorator