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,64 @@
1
+ module Spree
2
+ # Spree 5.3.6 の注文確認 / 店舗オーナー通知メールが、本番のインスタンス複数台 + Sidekiq 非同期
3
+ # 環境で2通送信される race condition への対策。
4
+ #
5
+ # 本家 OrderEmailSubscriber#send_confirmation_email は
6
+ # ① return if order.confirmation_delivered? (チェック)
7
+ # ② OrderMailer.confirm_email(...).deliver_later (送信)
8
+ # ③ order.update_column(:confirmation_delivered, true) (フラグ更新)
9
+ # の順で、①〜③が非アトミック。さらに本家は complete 遷移時に order.completed を2回 publish するため
10
+ # (finalize! 内 + after_transition)、2インスタンスが①を同時に false で通過すると②を両方実行し2通になる。
11
+ #
12
+ # 「flag=false の行を true に更新できた1プロセスだけが送信権を得る」条件付き UPDATE 一発に変更し、
13
+ # MySQL の行ロックで直列化することで、同時実行でも送信を1通に絞る。
14
+ # 確認メール / 店舗オーナー通知の両方が同型の race を持つため、両方に適用する。
15
+ #
16
+ # @see https://github.com/be-agile/giga-repeat/issues/1232
17
+ module OrderEmailSubscriberDecorator
18
+ private
19
+
20
+ # @gem-override spree_emails-5.3.6/app/subscribers/spree/order_email_subscriber.rb#send_confirmation_email
21
+ # @see https://github.com/be-agile/giga-repeat/issues/1232
22
+ def send_confirmation_email(event)
23
+ order = find_order(event)
24
+ return unless order
25
+ return unless order.store.prefers_send_consumer_transactional_emails?
26
+
27
+ # アトミックに false→true を更新できた1プロセスだけが送信する(多重送信防止)
28
+ return if claim_delivery_flag(order, :confirmation_delivered).zero?
29
+
30
+ OrderMailer.confirm_email(order.id).deliver_later
31
+
32
+ send_store_owner_notification(order) if order.store.new_order_notifications_email.present?
33
+ end
34
+
35
+ # @gem-override spree_emails-5.3.6/app/subscribers/spree/order_email_subscriber.rb#send_store_owner_notification
36
+ # @see https://github.com/be-agile/giga-repeat/issues/1232
37
+ def send_store_owner_notification(order)
38
+ return if order.store.new_order_notifications_email.blank?
39
+ return if claim_delivery_flag(order, :store_owner_notification_delivered).zero?
40
+
41
+ OrderMailer.store_owner_notification_email(order.id).deliver_later
42
+ end
43
+
44
+ # id 指定 + 当該フラグが「未送信」の行を true に更新し、更新できた行数を返す。
45
+ # 同時実行時は MySQL の行ロックで直列化され、勝者のみ 1、それ以外は 0 を得る。
46
+ #
47
+ # 「未送信」は false だけでなく NULL も含める必要がある。
48
+ # store_owner_notification_delivered は upstream スキーマ上 default 未指定 (= NULL) で、
49
+ # 新規注文ではこのカラムが NULL のまま作られる。`column => false` だけだと MySQL の
50
+ # `NULL = FALSE` が真にならず更新0行となり、店舗オーナー通知が永久に送られない (#1241)。
51
+ def claim_delivery_flag(order, column)
52
+ Spree::Order
53
+ .where(id: order.id)
54
+ .where(arel_table[column].eq(false).or(arel_table[column].eq(nil)))
55
+ .update_all(column => true)
56
+ end
57
+
58
+ def arel_table
59
+ Spree::Order.arel_table
60
+ end
61
+ end
62
+ end
63
+
64
+ Spree::OrderEmailSubscriber.prepend(Spree::OrderEmailSubscriberDecorator)
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Spree
4
+ module PageSections
5
+ module FeaturedPostsDecorator
6
+ def retrieve_posts
7
+ Spree::Post.where(store: store)
8
+ .where("published_at < ?", Time.zone.now.utc)
9
+ .order(id: :desc)
10
+ .includes(:post_category)
11
+ .limit(preferred_max_posts_to_show)
12
+ end
13
+ end
14
+ end
15
+ end
16
+
17
+ ::Spree::PageSections::FeaturedPosts.prepend(Spree::PageSections::FeaturedPostsDecorator)
@@ -0,0 +1,10 @@
1
+ module CurrencyRoundingHelper
2
+ def round_by_currency(amount, currency)
3
+ decimal_places = currency_decimal_places(currency)
4
+ BigDecimal(amount.to_s).round(decimal_places, BigDecimal::ROUND_HALF_UP)
5
+ end
6
+
7
+ def currency_decimal_places(currency)
8
+ Money::Currency.find(currency).decimal_places
9
+ end
10
+ end
@@ -0,0 +1,130 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Spree
4
+ module Admin
5
+ # 一覧テーブル (spree/admin/tables/_table) の絞り込み条件は、操作するたびに URL から消えてしまう。
6
+ #
7
+ # 1. ツールバーの「検索」「期間」「フィルタービルダー」は独立した GET フォームで、
8
+ # GET フォームは送信時に action URL のクエリ文字列を捨てて自分の入力だけを付け直すため、
9
+ # どれか一つを適用すると他のフォームの条件が消える。
10
+ # 2. 並び替え・表示件数のリンクは url_for / query_parameters.slice(:q) で組まれており、
11
+ # query_state が落ちる (q は残るので結果は絞り込まれたままだが、
12
+ # フィルターの件数バッジとドロワーが空になり「効いているのに見えないフィルタ」状態になる)。
13
+ #
14
+ # 絞り込み条件を引き継ぐための共通処理をここにまとめる。
15
+ #
16
+ # なおコントローラの TableConcern#process_table_query_state が query_state を params[:q] に
17
+ # マージし直すため、query_state さえ引き継げば ransack のパラメータは再生成される。
18
+ # @see https://github.com/be-agile/giga-repeat/issues/1262
19
+ module FilterParamsPreservation
20
+ # 絞り込み条件ではないため引き継がないパラメータ
21
+ # (page: 条件を変えたら1ページ目に戻すのが自然、commit: 送信ボタンのラベル)
22
+ NON_FILTER_PARAMS = %w[page commit utf8 authenticity_token _method].freeze
23
+
24
+ # 現在の URL にある絞り込みパラメータのうち、そのフォーム自身が送信しないものを
25
+ # hidden input として出力する。
26
+ #
27
+ # @param except_q [Array<String, Symbol>] フォーム自身が送信する q[...] のキー
28
+ # @param owns_query_state [Boolean] フィルタービルダーのフォームなら true。
29
+ # query_state と、そこから生成される q[...] を除外する
30
+ # (フィルターを削除したときに古い条件が残らないようにするため)
31
+ # @param table [Spree::Admin::Table, nil] owns_query_state が true のときに必要
32
+ # @return [String] hidden input の HTML
33
+ def preserved_filter_params_inputs(except_q: [], owns_query_state: false, table: nil)
34
+ preserved = request.query_parameters.deep_dup
35
+ NON_FILTER_PARAMS.each { |key| preserved.delete(key) }
36
+
37
+ excluded_q_keys = except_q.map(&:to_s)
38
+ if owns_query_state
39
+ preserved.delete('query_state')
40
+ excluded_q_keys += query_state_ransack_keys(table)
41
+ end
42
+
43
+ if preserved['q'].is_a?(Hash)
44
+ remaining_q = sanitize_ransack_params(preserved['q']).except(*excluded_q_keys)
45
+ remaining_q.present? ? preserved['q'] = remaining_q : preserved.delete('q')
46
+ end
47
+
48
+ hidden_inputs_for_params(preserved)
49
+ end
50
+
51
+ # ページネーションのリンク用パラメータ。
52
+ # 本家は request.query_parameters をそのまま使うため、OR グループの壊れた ransack キーが
53
+ # そのまま (しかも名前が変質しながら) URL に載り続ける。
54
+ #
55
+ # @param page [Integer] 遷移先のページ
56
+ # @return [Hash]
57
+ def preserved_pagination_params(page)
58
+ preserved = request.query_parameters.deep_dup
59
+ preserved['q'] = preserved_ransack_params if preserved['q'].is_a?(Hash)
60
+ preserved.merge(page: page)
61
+ end
62
+
63
+ # URL に載っている ransack のパラメータ (壊れたキーは除く)。
64
+ #
65
+ # コントローラの process_table_query_state が query_state 由来の条件を params[:q] に
66
+ # マージするため、params[:q] を使うと OR グループの grouping が丸ごと展開されてしまう。
67
+ # リンクを組むときは URL 生の q を使う (条件は query_state から再生成される)。
68
+ #
69
+ # @return [Hash]
70
+ def preserved_ransack_params
71
+ sanitize_ransack_params(request.query_parameters['q']) || {}
72
+ end
73
+
74
+ private
75
+
76
+ # 現在の query_state を引き継ぐための url_for 用パラメータ
77
+ # (値が nil なら url_for が落としてくれる)
78
+ # @return [Hash]
79
+ def preserved_query_state_param
80
+ { query_state: params[:query_state].presence }
81
+ end
82
+
83
+ # ransack として意味をなさない壊れたキーを捨てる。
84
+ #
85
+ # フィルタービルダーで「または」グループを使うと、gem の JS が q[g[0][m]] という
86
+ # Rack が正しくパースできない名前の input を生成する:
87
+ # Rack::Utils.parse_nested_query("q[g[0][m]]=or") #=> {"q"=>{"g[0"=>{"m"=>{"]"=>"or"}}}}
88
+ # これをそのまま引き継ぐと名前が変質したまま増殖していくので捨てる。
89
+ # OR の条件は query_state から正しく再生成されるため、捨てても絞り込み結果は変わらない。
90
+ #
91
+ # @param q_params [Hash, nil]
92
+ # @return [Hash, nil]
93
+ def sanitize_ransack_params(q_params)
94
+ return q_params unless q_params.is_a?(Hash)
95
+
96
+ q_params.reject { |key, _value| key.to_s.match?(/[\[\]]/) }
97
+ end
98
+
99
+ # 現在の query_state から生成される ransack のキー
100
+ # (コントローラの process_table_query_state と同じ変換を行う)
101
+ # @return [Array<String>]
102
+ def query_state_ransack_keys(table)
103
+ return [] if table.blank? || params[:query_state].blank?
104
+
105
+ query_builder = Spree::Admin::Table::QueryBuilder.new(table)
106
+ query_builder.load_from_json(params[:query_state])
107
+ query_builder.to_ransack_params.keys.map(&:to_s)
108
+ end
109
+
110
+ # ネストしたパラメータを hidden input に展開する
111
+ # id は付けない (画面上の入力欄と id が重複するのを避けるため)
112
+ def hidden_inputs_for_params(params_hash, prefix = nil)
113
+ safe_join(
114
+ params_hash.map do |key, value|
115
+ name = prefix ? "#{prefix}[#{key}]" : key.to_s
116
+
117
+ case value
118
+ when Hash
119
+ hidden_inputs_for_params(value, name)
120
+ when Array
121
+ safe_join(value.map { |item| hidden_field_tag("#{name}[]", item, id: nil) })
122
+ else
123
+ hidden_field_tag(name, value, id: nil)
124
+ end
125
+ end
126
+ )
127
+ end
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Spree
4
+ module Admin
5
+ # 表示件数 (per page) のリンクに query_state を引き継ぐ。
6
+ # @see Spree::Admin::FilterParamsPreservation
7
+ # @see https://github.com/be-agile/giga-repeat/issues/1262
8
+ module NavigationHelperDecorator
9
+ include Spree::Admin::FilterParamsPreservation
10
+
11
+ # 本家は request.query_parameters.slice(:q) しか引き継がないため query_state が落ちる。
12
+ # q は残るので結果は絞り込まれたままだが、フィルターの件数バッジとドロワーが空になってしまう。
13
+ # あわせて、OR グループで壊れた ransack キーが URL に載らないようにする。
14
+ def per_page_dropdown_params(per_page)
15
+ dropdown_params = super
16
+ dropdown_params[:q] = preserved_ransack_params if dropdown_params[:q].present?
17
+ dropdown_params.merge(preserved_query_state_param)
18
+ end
19
+ end
20
+ end
21
+ end
22
+
23
+ Spree::Admin::NavigationHelper.prepend(Spree::Admin::NavigationHelperDecorator)
@@ -0,0 +1,33 @@
1
+ module Spree
2
+ module Admin
3
+ module PromotionRulesHelperDecorator
4
+ # オプション値プロモーションルールのアクティブなオプションを取得するメソッドをオーバーライド
5
+ # @gem-override spree_admin-5.3.6/app/helpers/spree/admin/promotion_rules_helper.rb#active_options_for_option_value_promotion_rule
6
+ # reason: 実装当時は本家未実装だったため独自実装した。その後 spree_admin 5.1.6 で本家にも
7
+ # 同名メソッドが追加され、現在は本家実装を super なしで上書きしている状態 (commit a8034366)
8
+ # @see https://github.com/be-agile/giga-repeat/commit/a803436627949044869c3dfdae26f8909ecaf617
9
+ def active_options_for_option_value_promotion_rule(promotion_rule)
10
+ eligible_values = promotion_rule.preferred_eligible_values || []
11
+ return [] if eligible_values.empty?
12
+
13
+ # eligible_valuesはoption_value_idのリストなので、idではなくoption_value_idで検索
14
+ option_values = Spree::OptionValue.joins(:option_type)
15
+ .where(id: eligible_values)
16
+ .select('spree_option_values.id,
17
+ spree_option_values.presentation AS value_presentation,
18
+ spree_option_types.presentation AS type_presentation')
19
+
20
+ # tom-select用のJSON形式にフォーマット
21
+ option_values.map do |ov|
22
+ {
23
+ id: ov.id,
24
+ name: "#{ov.type_presentation}: #{ov.value_presentation}"
25
+ }
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+
32
+ # Decoratorを適用
33
+ Spree::Admin::PromotionRulesHelper.prepend(Spree::Admin::PromotionRulesHelperDecorator)
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Spree
4
+ module Admin
5
+ # 一覧テーブルの絞り込み条件を引き継ぐ。
6
+ # ビューからは preserved_filter_params_inputs を使う (Deface で各フォームに差し込んでいる)。
7
+ # @see Spree::Admin::FilterParamsPreservation
8
+ # @see https://github.com/be-agile/giga-repeat/issues/1262
9
+ module TableHelperDecorator
10
+ include Spree::Admin::FilterParamsPreservation
11
+
12
+ private
13
+
14
+ # 並び替えリンクに query_state を引き継ぐ。
15
+ #
16
+ # 本家は url_for(q: ...) でリンクを組むため query_state が落ちる。q は残るので結果は
17
+ # 絞り込まれたままだが、フィルターの件数バッジとドロワーが空になってしまう。
18
+ #
19
+ # 引数の current_q (= params[:q]) はコントローラが query_state 由来の条件をマージした後の値で、
20
+ # OR グループの grouping が丸ごと展開されてリンクが肥大する。URL 生の q を使えば
21
+ # 表示件数・ページネーションのリンクと同じ内容になり、条件は query_state から再生成される。
22
+ #
23
+ # @gem-override spree_admin-5.3.6/app/helpers/spree/admin/table_helper.rb#sort_dropdown_item
24
+ # @see https://github.com/be-agile/giga-repeat/issues/1262
25
+ def sort_dropdown_item(label, sort_value, is_active, _current_q)
26
+ sort_params = preserved_query_state_param.merge(q: preserved_ransack_params.merge(s: sort_value))
27
+
28
+ link_to(
29
+ url_for(sort_params),
30
+ class: "dropdown-item flex items-center justify-between #{'active' if is_active}",
31
+ data: { turbo_action: 'advance' }
32
+ ) do
33
+ safe_join([ content_tag(:span, label), is_active ? icon('check') : '' ])
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+
40
+ Spree::Admin::TableHelper.prepend(Spree::Admin::TableHelperDecorator)
@@ -0,0 +1,63 @@
1
+ module Spree
2
+ module BaseHelperDecorator
3
+ CHILD_TAXONS_SECTION_DEFAULT_PREFERENCES = {
4
+ heading_size: 'medium',
5
+ heading_alignment: 'center'
6
+ }.freeze
7
+
8
+ # 子カテゴリを「注目カテゴリ」セクションとして描画する。
9
+ # taxon ページの商品グリッド (_product_grid.html.erb) から呼ばれる。
10
+ def render_child_taxons_section(taxon, preferences: CHILD_TAXONS_SECTION_DEFAULT_PREFERENCES)
11
+ return if taxon.blank?
12
+
13
+ children = taxon.children
14
+ return if children.blank?
15
+
16
+ section = Spree::PageSections::FeaturedTaxons.build
17
+ (preferences || {}).each do |key, value|
18
+ section.send("preferred_#{key}=", value)
19
+ end
20
+
21
+ section.links = children.map do |child|
22
+ Spree::PageLink.build(label: child.name, linkable: child)
23
+ end
24
+
25
+ render('spree/page_sections/featured_taxons', section: ChildTaxonsSection.new(section))
26
+ end
27
+
28
+ # 永続化されていない PageSection をラップし、フラグメントキャッシュのキーを
29
+ # 子カテゴリの内容から導出する。
30
+ # 未保存の AR レコードは cache_key が常に ".../new" になり、カテゴリを跨いで
31
+ # キャッシュが衝突するため。
32
+ class ChildTaxonsSection < SimpleDelegator
33
+ def cache_key
34
+ child_taxon_ids = links.map(&:linkable_id)
35
+ max_updated_at = links.filter_map(&:linkable).map(&:updated_at).max.to_i
36
+ "spree/page_sections/child_taxons/#{child_taxon_ids.join('-')}-#{max_updated_at}"
37
+ end
38
+
39
+ # ActiveSupport::Cache は cache_key より先にこちらを見るため、
40
+ # 委譲先の実装が使われないよう合わせて上書きする
41
+ def cache_key_with_version
42
+ cache_key
43
+ end
44
+ end
45
+
46
+ def sort_adjustments_with_flat_percent_last(adjustments)
47
+ adjustments.sort_by do |adj|
48
+ if adj.adjustable_type == 'Spree::Order' && adj.source_type == 'Spree::PaymentMethod'
49
+ 3
50
+ elsif adj.source_type == 'Spree::PromotionAction' &&
51
+ adj.source&.calculator&.is_a?(Spree::Calculator::FlatPercentOrderTotal)
52
+ 2
53
+ elsif adj.source_type == 'Spree::PromotionAction'
54
+ 1
55
+ else
56
+ 0
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
62
+
63
+ Spree::BaseHelper.prepend Spree::BaseHelperDecorator
@@ -0,0 +1,32 @@
1
+ module Spree
2
+ module CheckoutAnalyticsHelperDecorator
3
+ # @gem-override spree_storefront-5.3.6/app/helpers/spree/checkout_analytics_helper.rb#clear_checkout_completed_session
4
+ # reason: 本家のセッションクリア (super) に加え、ゲスト注文の token cookie・signed cookie・
5
+ # return_to セッションも削除して不要なリダイレクトを防ぐ (commit 5df2e778)
6
+ # @see https://github.com/be-agile/giga-repeat/commit/5df2e7780bcd683db4dfb369edc26c72f73d1d5b
7
+ def clear_checkout_completed_session
8
+ super
9
+ # Clear guest order tokens from cookies
10
+ cookies.delete(:token) if cookies[:token].present?
11
+ cookies.delete(:guest_token) if cookies[:guest_token].present?
12
+ cookies.delete(:order_token) if cookies[:order_token].present?
13
+
14
+ # Clear permanent signed tokens
15
+ cookies.permanent.signed[:token] = nil if cookies.permanent.signed[:token].present?
16
+ cookies.permanent.signed[:guest_token] = nil if cookies.permanent.signed[:guest_token].present?
17
+ cookies.permanent.signed[:order_token] = nil if cookies.permanent.signed[:order_token].present?
18
+
19
+ # Clear session-based stored locations that cause unwanted redirects
20
+ session.delete("user_return_to") if session["user_return_to"]
21
+ session.delete("#{resource_name}_return_to") if session["#{resource_name}_return_to"]
22
+ end
23
+
24
+ private
25
+
26
+ def resource_name
27
+ :user
28
+ end
29
+ end
30
+ end
31
+
32
+ Spree::CheckoutAnalyticsHelper.prepend(Spree::CheckoutAnalyticsHelperDecorator)
@@ -0,0 +1,70 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ // 非表示(hidden クラス等で display:none)になっている間、配下の入力欄を disabled にする。
4
+ //
5
+ // 目的: チェックアウトの payment 画面では、請求先住所(bill_address)フォームが
6
+ // 「配送先と同じ(use_shipping)」のとき hidden クラスで隠されるが、中の
7
+ // firstname_kana / lastname_kana などには required が付いたまま残る。
8
+ // この状態で「保存して続ける」を押すと、ブラウザの HTML5 バリデーションが
9
+ // 空の required フィールドを検証しようとし、対象が非表示で focus できないため
10
+ // "An invalid form control ... is not focusable"
11
+ // を出して submit を完全に中止する(POST が一切飛ばない)。
12
+ //
13
+ // 非表示の input を disabled にすると、HTML5 バリデーションの対象から外れ、
14
+ // かつフォーム送信にも含まれなくなるため、この問題を回避できる。
15
+ // 表示に戻った際は disabled を解除し、required 等の挙動を元通りにする。
16
+ // @see https://github.com/be-agile/giga-repeat/issues/1210
17
+ export default class extends Controller {
18
+ connect() {
19
+ this.observer = new MutationObserver(() => this.sync())
20
+ this.observer.observe(this.element, {
21
+ attributes: true,
22
+ attributeFilter: ["class", "style"]
23
+ })
24
+ this.sync()
25
+ }
26
+
27
+ disconnect() {
28
+ if (this.observer) {
29
+ this.observer.disconnect()
30
+ this.observer = null
31
+ }
32
+ // コントローラ離脱時に disabled を残さない(送信から欠落させない)
33
+ this.enableControls()
34
+ }
35
+
36
+ sync() {
37
+ if (this.isHidden()) {
38
+ this.disableControls()
39
+ } else {
40
+ this.enableControls()
41
+ }
42
+ }
43
+
44
+ // offsetParent が null = display:none(hidden クラス含む)で実際に非表示
45
+ isHidden() {
46
+ return this.element.offsetParent === null
47
+ }
48
+
49
+ disableControls() {
50
+ this.controls().forEach((el) => {
51
+ if (!el.disabled) {
52
+ el.dataset.disabledByHidden = "true"
53
+ el.disabled = true
54
+ }
55
+ })
56
+ }
57
+
58
+ enableControls() {
59
+ this.controls().forEach((el) => {
60
+ if (el.dataset.disabledByHidden === "true") {
61
+ el.disabled = false
62
+ delete el.dataset.disabledByHidden
63
+ }
64
+ })
65
+ }
66
+
67
+ controls() {
68
+ return this.element.querySelectorAll("input, select, textarea")
69
+ }
70
+ }
@@ -0,0 +1,11 @@
1
+ import { lazyLoadControllersFromManifest } from "spree/storefront/helpers/lazy_load_controllers_with_manifest"
2
+
3
+ const controllers = ["disable-when-hidden"]
4
+ const manifest = {
5
+ "disable-when-hidden": "ba_spree/controllers/disable_when_hidden_controller"
6
+ }
7
+
8
+ document.addEventListener('turbo:load', function () {
9
+ const application = window.Stimulus
10
+ lazyLoadControllersFromManifest(controllers, null, application, manifest)
11
+ })
@@ -0,0 +1,61 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+ import { easepick } from '@easepick/core';
3
+ import { RangePlugin } from '@easepick/range-plugin';
4
+ import { PresetPlugin } from '@easepick/preset-plugin';
5
+
6
+ export default class extends Controller {
7
+ static targets = [ "picker", "dateFrom", "dateTo", "label" ]
8
+ connect() {
9
+ this.picker = new easepick.create({
10
+ element: this.pickerTarget,
11
+ lang: 'ja-JP',
12
+ inline: true,
13
+ css: [
14
+ 'https://cdn.jsdelivr.net/npm/@easepick/core@1.2.0/dist/index.css',
15
+ 'https://cdn.jsdelivr.net/npm/@easepick/range-plugin@1.2.0/dist/index.css',
16
+ 'https://cdn.jsdelivr.net/npm/@easepick/preset-plugin@1.2.0/dist/index.css',
17
+ ],
18
+ doc: document,
19
+ plugins: [RangePlugin, PresetPlugin],
20
+ RangePlugin: {
21
+ tooltip: true,
22
+ locale: {
23
+ one: '日',
24
+ other: '日間'
25
+ },
26
+ startDate: this.dateFromTarget.value,
27
+ endDate: this.dateToTarget.value,
28
+ },
29
+ PresetPlugin: {
30
+ customLabels: [
31
+ '今日',
32
+ '昨日',
33
+ '直近7日間',
34
+ '直近30日間',
35
+ '今月',
36
+ '先月'
37
+ ],
38
+ position: 'left'
39
+ }
40
+ })
41
+
42
+ this.picker.on('render', () => {
43
+ this.picker.ui.container.style.boxShadow = 'none';
44
+ });
45
+
46
+ this.picker.on('select', (event) => {
47
+ const data = event.detail;
48
+ this.dateFromTarget.value = data.start
49
+ this.dateToTarget.value = data.end
50
+
51
+ // https://stackoverflow.com/questions/68624668/how-can-i-submit-a-form-on-input-change-with-turbo-streams
52
+ this.pickerTarget.closest('form').requestSubmit()
53
+
54
+ this.labelTarget.innerHTML = this.pickerTarget.innerHTML
55
+ })
56
+ }
57
+
58
+ open() {
59
+ this.picker.show()
60
+ }
61
+ }
@@ -0,0 +1,17 @@
1
+ import { Controller } from '@hotwired/stimulus'
2
+
3
+ export default class extends Controller {
4
+ static targets = [
5
+ 'name',
6
+ 'url',
7
+ ]
8
+
9
+ updateUrlFromName() {
10
+ const name = this.nameTarget.value
11
+ const url = name.toLowerCase().replace(/[^a-z0-9\u4e00-\u9faf\u3040-\u309f\u30a0-\u30ff\uff00-\uff9fa-z]+/gi, '-').replace(/(^-|-$)+/g, '');
12
+ this.urlTarget.value = url
13
+
14
+ // TODO: do ajax call and check if url is available
15
+ // if not, append a number to the end to make it unique
16
+ }
17
+ }
@@ -0,0 +1,43 @@
1
+ module BaSpree
2
+ module AddressDecorator
3
+ def self.prepended(base)
4
+ # NOTE: Spree::Address は Spree 標準で `after_create :set_user_attributes` が設定されており、
5
+ # 配送先・請求先の区別なく Address 作成時に User の名前等が上書きされていたが、
6
+ # 「プレゼント送付先(配送先)の名前で User の本名が上書きされる」問題があったため、
7
+ # Spree 標準の callback そのものを解除する。
8
+ # 代わりに User#bill_address_id 変更時のコールバック(Spree::UserBillAddressSyncDecorator)で
9
+ # 請求先からのみ User 情報を同期するように変更した。
10
+ # raise: false は必須。prepend されたモジュールが再度 prepend されると Ruby は
11
+ # ancestors を変えないままこの prepended フックだけを再実行するため、2回目以降は
12
+ # 解除済みの callback を探して ArgumentError になる。development では
13
+ # engine の activate が to_prepare からリロードのたびに decorator を load し直すので、
14
+ # ガードがないとファイルを1つ編集しただけでリロードが落ちる。
15
+ # @see https://github.com/be-agile/giga-repeat/issues/1307
16
+ base.skip_callback :create, :after, :set_user_attributes, raise: false
17
+
18
+ base.after_initialize :set_default_values, if: -> { new_record? && user.present? }
19
+ end
20
+
21
+ # @gem-override spree_core-5.3.6/app/models/spree/address.rb#show_company_address_field?
22
+ # @see https://github.com/be-agile/giga-repeat/issues/1165
23
+ # 本家は Store#prefers_company_field_enabled? を見るが、ba_spree では常に会社名欄を表示する。
24
+ # 日本の EC では法人向け配送のため会社名欄が求められることが多いため。
25
+ def show_company_address_field?
26
+ true
27
+ end
28
+
29
+ private
30
+
31
+ # @gem-override spree_core-5.3.6/app/models/spree/address.rb#set_default_values
32
+ # @see https://github.com/be-agile/giga-repeat/issues/1165
33
+ # 本家の firstname / lastname / phone 補完に加えて company も User から補完する。
34
+ def set_default_values
35
+ self.firstname ||= user.first_name
36
+ self.lastname ||= user.last_name
37
+ self.phone ||= user.phone
38
+ self.company ||= user.company
39
+ end
40
+ end
41
+ end
42
+
43
+ Spree::Address.prepend BaSpree::AddressDecorator
@@ -0,0 +1,13 @@
1
+ module BaSpree
2
+ module AdjustmentDecorator
3
+ def self.prepended(base)
4
+ base.scope :payment_method, -> { where(source_type: 'Spree::PaymentMethod') }
5
+ base.scope :non_payment_method, -> { where.not(source_type: 'Spree::PaymentMethod') }
6
+ base.scope :non_shipping, -> { where.not(adjustable_type: 'Spree::Shipment') }
7
+ base.scope :promotion, -> { where(source_type: 'Spree::PromotionAction') }
8
+ base.scope :non_promotion, -> { where.not(source_type: 'Spree::PromotionAction') }
9
+ end
10
+ end
11
+ end
12
+
13
+ Spree::Adjustment.prepend BaSpree::AdjustmentDecorator