spree_np_atobarai 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 (38) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +23 -0
  3. data/Gemfile +3 -0
  4. data/LICENSE +673 -0
  5. data/README.md +71 -0
  6. data/app/controllers/spree/admin/np_orders_controller.rb +36 -0
  7. data/app/controllers/spree_np_atobarai/checkout_controller_decorator.rb +26 -0
  8. data/app/models/spree/payment_method/np_atobarai.rb +94 -0
  9. data/app/models/spree_np_atobarai/order_decorator.rb +114 -0
  10. data/app/models/spree_np_atobarai/payment_decorator.rb +23 -0
  11. data/app/overrides/add_invoice_sending_method_to_account_orders.rb +27 -0
  12. data/app/overrides/add_invoice_sending_method_to_checkout_summary.rb +13 -0
  13. data/app/overrides/add_invoice_sending_method_to_payment.rb +11 -0
  14. data/app/overrides/add_invoice_sending_method_to_purchased_items_table.rb +22 -0
  15. data/app/overrides/hide_preference_fields_for_payment_method_np_atobarai.rb +10 -0
  16. data/app/overrides/set_orders_active_for_np_orders.rb +10 -0
  17. data/app/services/spree_np_atobarai/order_csv_service.rb +123 -0
  18. data/app/views/spree/admin/np_orders/index.html.erb +29 -0
  19. data/app/views/spree/admin/payment_methods/custom_form_fields/_np_atobarai_form_fields.html.erb +32 -0
  20. data/app/views/spree/checkout/payment/_npatobarai.html.erb +12 -0
  21. data/app/views/spree_np_atobarai/_np_atobarai_invoice_options.html.erb +24 -0
  22. data/app/views/spree_np_atobarai/admin/_orders_nav_item.html.erb +4 -0
  23. data/app/views/spree_np_atobarai/admin/np_orders/_list.html.erb +29 -0
  24. data/app/views/spree_np_atobarai/admin/np_orders/_order.html.erb +31 -0
  25. data/bin/rails +8 -0
  26. data/config/initializers/admin_navigation.rb +20 -0
  27. data/config/locales/activerecord.en.yml +9 -0
  28. data/config/locales/activerecord.ja.yml +9 -0
  29. data/config/locales/en.yml +21 -0
  30. data/config/locales/ja.yml +25 -0
  31. data/config/routes.rb +9 -0
  32. data/db/migrate/20250401033922_add_column_invoice_sending_to_payment.rb +5 -0
  33. data/lib/generators/spree_np_atobarai/install_generator.rb +19 -0
  34. data/lib/spree_np_atobarai/engine.rb +34 -0
  35. data/lib/spree_np_atobarai/version.rb +7 -0
  36. data/lib/spree_np_atobarai.rb +5 -0
  37. data/spree_np_atobarai.gemspec +29 -0
  38. metadata +151 -0
data/README.md ADDED
@@ -0,0 +1,71 @@
1
+ # SpreeNpAtobarai
2
+
3
+ `spree_np_atobarai` is a Spree extension that integrates the [NP Atobarai](https://np-atobarai.jp/) payment service into your Spree store. It provides functionalities for managing orders using this payment method, exporting order data in CSV format, and selecting invoice sending methods.
4
+
5
+ ## Features
6
+
7
+ 1. **New Payment Method Type**
8
+ - Adds `Spree::PaymentMethod::NpAtobarai`, allowing customers to pay using the NP Atobarai service.
9
+
10
+ 2. **Order Management Screen**
11
+ - Provides an admin interface for managing orders that use the NP Atobarai payment method.
12
+
13
+ 3. **CSV Export for NP Atobarai**
14
+ - Allows exporting NP orders in CSV format for upload to the NP Atobarai service.
15
+
16
+ 4. **Invoice Sending Methods Selection**
17
+ - Allows selecting `invoice_sending_methods` when using the `NP Atobarai` payment method.
18
+
19
+ ## Installation
20
+
21
+ 1. Add spree_np_atobarai to your Gemfile:
22
+
23
+ ```ruby
24
+ gem 'spree_np_atobarai'
25
+ ```
26
+
27
+ 2. Install the gem using Bundler:
28
+
29
+ ```shell
30
+ bundle install
31
+ ```
32
+
33
+ 3. Copy & run migrations:
34
+
35
+ ```shell
36
+ bundle exec rails g spree_np_atobarai:install
37
+ ```
38
+
39
+ ## Usage
40
+
41
+ ### Creating the Payment Method
42
+
43
+ After installing the extension, create a new NP Atobarai payment method using the Rails console:
44
+
45
+ ```ruby
46
+ Spree::PaymentMethod::NpAtobarai.find_or_create_by(
47
+ name: "Np Atobarai 1",
48
+ description: "Np Atobarai payment method",
49
+ active: true,
50
+ display_on: "both",
51
+ auto_capture: true
52
+ )
53
+ ```
54
+
55
+ This will register the NP Atobarai payment method in your Spree store and make it available for use.
56
+
57
+ ### Managing NP Orders
58
+ Once installed, an NP Order management screen will be available in the Spree admin panel. Here, you can view and manage all orders that use the NP Atobarai payment method.
59
+
60
+ ### Exporting NP Orders to CSV
61
+ To generate a CSV file for NP orders, navigate to the NP Order section in the admin panel and click the Export CSV button.
62
+
63
+ ## License
64
+
65
+ This extension is available as open source under the terms of either:
66
+ * [GNU Affero General Public License v3.0 or later (AGPL-3.0-or-later)](https://www.gnu.org/licenses/agpl-3.0.en.html)
67
+ * [BSD 3-Clause License](https://opensource.org/licenses/BSD-3-Clause)
68
+
69
+ ## Credits
70
+
71
+ [be agile Co., Ltd.](https://be-agile.jp/)
@@ -0,0 +1,36 @@
1
+ module Spree
2
+ module Admin
3
+ class NpOrdersController < Spree::Admin::OrdersController
4
+ include Spree::Admin::OrderConcern
5
+
6
+ respond_to :html
7
+
8
+ def index
9
+ super
10
+ # Spree 5.3 追従: 本家の index は空メソッドになり、@orders → @collection(controller_name 由来の
11
+ # @np_orders)へ移行。np_atobarai 限定は scope override で適用済みなので、ビュー(@orders/@search 前提)
12
+ # 向けに @collection を @orders へエイリアスする。
13
+ @orders = @collection
14
+ end
15
+
16
+ def csv_export
17
+ send_data SpreeNpAtobarai::OrderCSVService.new(params[:order_ids]).to_csv,
18
+ filename: "orders-#{Time.current.strftime('%Y%m%d%H%M%S')}.csv",
19
+ type: "text/csv",
20
+ disposition: "attachment"
21
+ end
22
+
23
+ private
24
+
25
+ def scope
26
+ # Spree 5.3 追従: collection(ransack/ページネーション)の基盤 scope に np_atobarai 限定を適用する。
27
+ # 本家 OrdersController#scope は index 時に .complete を付けるため、それに合わせる。
28
+ current_store.orders.accessible_by(current_ability, :index).complete.np_atobarai_orders
29
+ end
30
+
31
+ def model_class
32
+ Spree::Order
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,26 @@
1
+ module SpreeNpAtobarai
2
+ module CheckoutControllerDecorator
3
+ def self.prepended(base)
4
+ base.before_action :validate_invoice_method_for_np_atobarai, only: [ :update ], if: -> { params[:state] == 'payment' }
5
+ end
6
+
7
+ private
8
+
9
+ def validate_invoice_method_for_np_atobarai
10
+ payments_attribute = params.dig(:order, :payments_attributes)&.last
11
+ return if payments_attribute.blank?
12
+
13
+ payment_method_id = payments_attribute[:payment_method_id]
14
+ invoice_sending = payments_attribute[:invoice_sending]
15
+
16
+ payment = Spree::Payment.new(payment_method_id: payment_method_id, invoice_sending: invoice_sending)
17
+ payment.valid?
18
+ return if payment.errors[:invoice_sending].blank?
19
+
20
+ flash[:error] = Spree.t('please_select_invoice_sending_method')
21
+ redirect_to(checkout_state_path(@order.token, @order.state))
22
+ end
23
+ end
24
+ end
25
+
26
+ Spree::CheckoutController.prepend SpreeNpAtobarai::CheckoutControllerDecorator
@@ -0,0 +1,94 @@
1
+ class Spree::PaymentMethod::NpAtobarai < Spree::PaymentMethod
2
+ INVOICE_SENDING_OPTIONS = [ :postal, :email ]
3
+
4
+ preference :np_atobarai_fee, :float, default: 330
5
+ preference :np_atobarai_no_fee_same_month, :boolean, default: true
6
+ preference :tax_category_id, :integer
7
+
8
+ def actions
9
+ %w[capture void]
10
+ end
11
+
12
+ # Indicates whether its possible to capture the payment
13
+ def can_capture?(payment)
14
+ [ 'checkout', 'pending' ].include?(payment.state)
15
+ end
16
+
17
+ # Indicates whether its possible to void the payment.
18
+ def can_void?(payment)
19
+ payment.state != 'void'
20
+ end
21
+
22
+ def custom_form_fields_partial_name
23
+ 'np_atobarai_form_fields'
24
+ end
25
+
26
+ def capture(*)
27
+ ActiveMerchant::Billing::Response.new(true, "", {}, {})
28
+ end
29
+
30
+ def cancel(*)
31
+ ActiveMerchant::Billing::Response.new(true, "", {}, {})
32
+ end
33
+
34
+ def void(*)
35
+ ActiveMerchant::Billing::Response.new(true, "", {}, {})
36
+ end
37
+
38
+ def source_required?
39
+ false
40
+ end
41
+
42
+ def np_atobarai_fee_adjustment_applicable?(order, payment)
43
+ return false if preferred_np_atobarai_fee.blank? || order.blank? || payment&.invoice_sending_email?
44
+
45
+ return true if preferred_np_atobarai_no_fee_same_month.blank?
46
+
47
+ !has_np_atobarai_orders_this_month?(order.billing_address&.phone)
48
+ end
49
+
50
+ def create_np_atobarai_fee_adjustment!(order)
51
+ adjustment = order.adjustments.new
52
+ adjustment.source = self
53
+ adjustment.order = order
54
+ adjustment.amount = preferred_np_atobarai_fee
55
+ adjustment.label = np_atobarai_fee_label
56
+ adjustment.save!
57
+ adjustment
58
+ end
59
+
60
+ class << self
61
+ def destroy_np_atobarai_adjustments(order)
62
+ order.adjustments.where(label: np_atobarai_fee_label).destroy_all
63
+ end
64
+
65
+ def np_atobarai_fee_label
66
+ Spree.t('np_atobarai_fee_adjustment')
67
+ end
68
+
69
+ def invoice_sending_options
70
+ Spree::Payment.invoice_sendings.slice(*INVOICE_SENDING_OPTIONS)
71
+ end
72
+ end
73
+
74
+ delegate :np_atobarai_fee_label, to: :class
75
+
76
+ private
77
+
78
+ def has_np_atobarai_orders_this_month?(phone_number)
79
+ return false if phone_number.blank?
80
+
81
+ start_of_month = Time.current.beginning_of_month
82
+ end_of_month = Time.current.end_of_month
83
+ payment_method_ids = Spree::PaymentMethod::NpAtobarai.ids
84
+ return false if payment_method_ids.blank?
85
+
86
+ Spree::Order.complete
87
+ .where(state: 'complete')
88
+ .completed_between(start_of_month, end_of_month)
89
+ .joins(:bill_address, :payments)
90
+ .where("REPLACE(spree_addresses.phone, '-', '') = ?", phone_number.delete('-'))
91
+ .where(spree_payments: { payment_method_id: payment_method_ids, invoice_sending: Spree::Payment.invoice_sendings[:postal] })
92
+ .merge(Spree::Payment.valid).exists?
93
+ end
94
+ end
@@ -0,0 +1,114 @@
1
+ module SpreeNpAtobarai
2
+ module OrderDecorator
3
+ def self.prepended(base)
4
+ base.set_callback :updating_from_params, :before, :adjust_np_atobarai_fee, prepend: true
5
+ base.set_callback :updating_from_params, :after, :update_np_atobarai_invoice_sending, prepend: true
6
+
7
+ base.scope :np_atobarai_orders, -> {
8
+ joins(:payments)
9
+ .where(spree_payments: { payment_method_id: Spree::PaymentMethod::NpAtobarai.ids })
10
+ .merge(Spree::Payment.unscoped.valid).distinct
11
+ }
12
+ end
13
+
14
+ def human_invoice_sending_name
15
+ payments.last&.human_invoice_sending_name
16
+ end
17
+
18
+ def np_atobarai_order?
19
+ Spree::Order.np_atobarai_orders.exists?(id: id)
20
+ end
21
+
22
+ private
23
+
24
+ def adjust_np_atobarai_fee
25
+ return if payments_attributes.blank?
26
+
27
+ destroy_np_atobarai_adjustments
28
+ payment_method = find_np_atobarai_payment_method
29
+ create_np_atobarai_fee_adjustment!(payment_method)
30
+ updater.update
31
+ end
32
+
33
+ def find_np_atobarai_payment_method
34
+ payment_method_ids = payments_attributes.pluck('payment_method_id')
35
+ # Spree 5.3 で Order#available_payment_methods は deprecated になり、かつ gem 内部で
36
+ # collect_payment_methods(store) を arity 0 のメソッドへ 1 引数で呼ぶ不整合があるため
37
+ # ArgumentError (wrong number of arguments given 1 expected 0) で必ず落ちる。
38
+ # 5.3 推奨の collect_frontend_payment_methods(配列を返す)へ置き換える。
39
+ available_method_ids = collect_frontend_payment_methods.map(&:id)
40
+
41
+ Spree::PaymentMethod
42
+ .where(id: payment_method_ids)
43
+ .where(id: available_method_ids)
44
+ .find_by(type: 'Spree::PaymentMethod::NpAtobarai')
45
+ end
46
+
47
+ def destroy_np_atobarai_adjustments
48
+ Spree::PaymentMethod::NpAtobarai.destroy_np_atobarai_adjustments(self)
49
+ end
50
+
51
+ def create_np_atobarai_fee_adjustment!(payment_method)
52
+ return if payment_method.blank?
53
+
54
+ # payments_attributesからinvoice_sendingを取得
55
+ invoice_sending = payments_attributes.dig(0, 'invoice_sending')
56
+
57
+ # メール送付の場合は手数料なし
58
+ return if invoice_sending == 'email'
59
+
60
+ # 郵送の場合、または未指定の場合に手数料を追加
61
+ # 同月内の既存注文チェックは、np_atobarai_fee_adjustment_applicableで行われる
62
+ return if preferred_np_atobarai_no_fee_same_month && has_np_atobarai_orders_this_month?(billing_address&.phone)
63
+
64
+ payment_method.create_np_atobarai_fee_adjustment!(self)
65
+ end
66
+
67
+ def has_np_atobarai_orders_this_month?(phone_number)
68
+ return false if phone_number.blank?
69
+
70
+ start_of_month = Time.current.beginning_of_month
71
+ end_of_month = Time.current.end_of_month
72
+ payment_method_ids = Spree::PaymentMethod::NpAtobarai.ids
73
+ return false if payment_method_ids.blank?
74
+
75
+ Spree::Order.complete
76
+ .where(state: 'complete')
77
+ .completed_between(start_of_month, end_of_month)
78
+ .joins(:bill_address, :payments)
79
+ .where("REPLACE(spree_addresses.phone, '-', '') = ?", phone_number.delete('-'))
80
+ .where(spree_payments: { payment_method_id: payment_method_ids, invoice_sending: Spree::Payment.invoice_sendings[:postal] })
81
+ .merge(Spree::Payment.valid).exists?
82
+ end
83
+
84
+ def preferred_np_atobarai_no_fee_same_month
85
+ payment_method = find_np_atobarai_payment_method
86
+ payment_method&.preferred_np_atobarai_no_fee_same_month
87
+ end
88
+
89
+ def update_np_atobarai_invoice_sending
90
+ return if payments_attributes.blank?
91
+
92
+ payment_method = find_np_atobarai_payment_method
93
+ return if payment_method.blank?
94
+
95
+ invoice_sending = payments_attributes.dig(0, 'invoice_sending')
96
+ return if invoice_sending.blank?
97
+
98
+ payment = payments.where(payment_method_id: payment_method.id).where.not(state: Spree::Payment::INVALID_STATES).last
99
+ return if payment.blank?
100
+
101
+ payment.update_columns(invoice_sending: invoice_sending) if payment.invoice_sending != invoice_sending
102
+ end
103
+
104
+ # ba_spree_cash_on_delivery の同名 private メソッドと実装が同一。engine ごとにモジュールを
105
+ # 分離した結果、両方が Spree::Order の ancestors に載り外側だけが呼ばれるが、実装が同じなので
106
+ # どちらが外側でも挙動は変わらない。
107
+ # @see https://github.com/be-agile/giga-repeat/issues/1307
108
+ def payments_attributes
109
+ @updating_params&.dig('order', 'payments_attributes') || []
110
+ end
111
+ end
112
+ end
113
+
114
+ Spree::Order.prepend SpreeNpAtobarai::OrderDecorator
@@ -0,0 +1,23 @@
1
+ module SpreeNpAtobarai
2
+ module PaymentDecorator
3
+ def self.prepended(base)
4
+ base.attribute :invoice_sending, :string
5
+ base.enum :invoice_sending, { postal: 'postal', email: 'email' }, prefix: :invoice_sending
6
+
7
+ base.validates :invoice_sending, presence: true,
8
+ if: ->(payment) { payment.payment_method.is_a?(Spree::PaymentMethod::NpAtobarai) }
9
+
10
+ base.define_singleton_method(:human_invoice_sending_name) do |invoice_sending_type|
11
+ return if invoice_sending_type.blank?
12
+
13
+ I18n.t("activerecord.attributes.spree/payment.invoice_sendings.#{invoice_sending_type}")
14
+ end
15
+ end
16
+
17
+ def human_invoice_sending_name
18
+ self.class.human_invoice_sending_name(invoice_sending)
19
+ end
20
+ end
21
+ end
22
+
23
+ Spree::Payment.prepend(SpreeNpAtobarai::PaymentDecorator) unless Spree::Payment.included_modules.include?(SpreeNpAtobarai::PaymentDecorator)
@@ -0,0 +1,27 @@
1
+ # This Deface override adds a new column for "Invoice Sending Method"
2
+ # to the orders table on the `account/orders` page.
3
+ Deface::Override.new(
4
+ virtual_path: 'spree/account/_orders',
5
+ name: 'add_invoice_sending_method_to_account_orders',
6
+ insert_after: 'table tr > th:nth-of-type(2)',
7
+ text: <<-ERB
8
+ <% if @orders.np_atobarai_orders.any? %>
9
+ <th class="font-normal px-4">
10
+ <%= Spree::Payment.human_attribute_name :invoice_sending %>
11
+ </th>
12
+ <% end %>
13
+ ERB
14
+ )
15
+
16
+ Deface::Override.new(
17
+ virtual_path: 'spree/account/_order',
18
+ name: 'add_invoice_sending_method_to_account_orders_rows',
19
+ insert_after: 'tr > td:nth-of-type(2)',
20
+ text: <<-ERB
21
+ <% if @orders.np_atobarai_orders.any? %>
22
+ <td class="md:w-auto pr-2 md:px-4 md:border-y border-accent">
23
+ <%= order.human_invoice_sending_name %>
24
+ </td>
25
+ <% end %>
26
+ ERB
27
+ )
@@ -0,0 +1,13 @@
1
+ Deface::Override.new(
2
+ virtual_path: 'spree/checkout/_summary',
3
+ name: 'add_invoice_sending_method_to_checkout_summary',
4
+ insert_top: "div[data-hook='order_summary']",
5
+ text: <<-ERB
6
+ <% if (order.confirm? || order.complete?) && order.np_atobarai_order? %>
7
+ <div class="flex justify-between items-center mb-2">
8
+ <span><%= Spree::Payment.human_attribute_name :invoice_sending %>:</span>
9
+ <span><%= order.human_invoice_sending_name %></span>
10
+ </div>
11
+ <% end %>
12
+ ERB
13
+ )
@@ -0,0 +1,11 @@
1
+ Deface::Override.new(
2
+ virtual_path: 'spree/admin/payments/_payment',
3
+ name: 'add_invoice_sending_method_to_payment',
4
+ insert_after: "strong:contains('payment_method_name(payment)')",
5
+ text: <<-HTML
6
+ <% if payment.payment_method.is_a?(Spree::PaymentMethod::NpAtobarai) && payment.invoice_sending.present? %>
7
+ <p class="mb-1 mt-2"><%= Spree::Payment.human_attribute_name :invoice_sending %>:</p>
8
+ <strong><%= payment.human_invoice_sending_name %></strong>
9
+ <% end %>
10
+ HTML
11
+ )
@@ -0,0 +1,22 @@
1
+ Deface::Override.new(
2
+ virtual_path: 'spree/shared/_purchased_items_table',
3
+ name: 'add_invoice_sending_method_to_purchased_items_table',
4
+ insert_before: "erb[loud]:contains('spree/shared/purchased_items_table/total')",
5
+ text: <<-HTML
6
+ <% if order.np_atobarai_order? %>
7
+ <tr>
8
+ <td></td>
9
+ <td>
10
+ <p class="f-fallback purchase_total purchase_total--label">
11
+ <%= Spree::Payment.human_attribute_name :invoice_sending %>:
12
+ </p>
13
+ </td>
14
+ <td class="purchase_total-col">
15
+ <p class="f-fallback purchase_total">
16
+ <%= order.human_invoice_sending_name %>
17
+ </p>
18
+ </td>
19
+ </tr>
20
+ <% end %>
21
+ HTML
22
+ )
@@ -0,0 +1,10 @@
1
+ Deface::Override.new(
2
+ virtual_path: 'spree/admin/payment_methods/_form',
3
+ name: 'hide_preference_fields_for_payment_method_np_atobarai',
4
+ replace: "erb[loud]:contains('preference_fields(@object, f)')",
5
+ text: <<-HTML
6
+ <% unless @object.is_a?(Spree::PaymentMethod::NpAtobarai) %>
7
+ <%= preference_fields(@object, f) unless preference_fields(@object, f).empty? %>
8
+ <% end %>
9
+ HTML
10
+ )
@@ -0,0 +1,10 @@
1
+ Deface::Override.new(
2
+ virtual_path: 'spree/admin/shared/sidebar/_orders_nav',
3
+ name: 'set_orders_active_for_np_orders',
4
+ insert_after: "erb[silent]:contains('orders_active =')",
5
+ text: <<-ERB
6
+ <% if controller_name == 'np_orders' %>
7
+ <% orders_active = true %>
8
+ <% end %>
9
+ ERB
10
+ )
@@ -0,0 +1,123 @@
1
+ module SpreeNpAtobarai
2
+ class OrderCSVService
3
+ CSV_HEADER = [
4
+ "加盟店取引ID", "加盟店取引受注日", "企業名", "部署名", "担当者名", "購入企業ID",
5
+ "郵便番号", "住所", "電話番号1", "電話番号2",
6
+ "メールアドレス(To)", "メールアドレス(Cc)1", "メールアドレス(Cc)2", "メールアドレス(Cc)3", "メールアドレス(Cc)4",
7
+ "配送先企業名", "配送先部署名", "配送先担当者名", "配送先担当者名(カナ)",
8
+ "配送先郵便番号", "配送先住所", "配送先電話番号",
9
+ "決済方法", "FAX番号", "請求書送付方法", "コンビニ払い設定",
10
+ "取引金額", "明細", "元加盟店取引受注日", "単価", "数量", "金額", "NP購入企業ID", "請求書税区分"
11
+ ].freeze
12
+
13
+ def initialize(order_ids)
14
+ @order_ids = order_ids.map(&:to_i)
15
+ @orders = Spree::Order.where(id: order_ids).includes(:line_items, :adjustments, :ship_address, :bill_address, payments: :payment_method)
16
+ end
17
+
18
+ def to_csv
19
+ options = {
20
+ encoding: "Shift_JIS",
21
+ undef: :replace, # 未定義文字を置換
22
+ replace: '?', # 未定義文字の置換文字
23
+ invalid: :replace # 不正なバイトシーケンスを置換
24
+ }
25
+ CSV.generate do |csv|
26
+ csv << CSV_HEADER
27
+
28
+ @order_ids.each do |id|
29
+ order = @orders.find { |o| o.id == id }
30
+ next unless order
31
+
32
+ rows = build_order_rows(order)
33
+ rows.each { |row| csv << row }
34
+ end
35
+ end.encode("Shift_JIS", **options)
36
+ end
37
+
38
+ private
39
+
40
+ def build_order_rows(order)
41
+ detail_rows = build_detail_rows(order)
42
+ rows = []
43
+
44
+ # Row 1: full info + first detail row
45
+ rows << build_main_row(order, detail_rows.shift)
46
+
47
+ # From row 2 onwards: add detail rows with nil values for the first 27 columns
48
+ detail_rows.each do |detail_row|
49
+ base = Array.new(27, nil)
50
+ rows << base + detail_row + [ nil, nil ]
51
+ end
52
+
53
+ rows
54
+ end
55
+
56
+ def build_main_row(order, detail_row)
57
+ bill = order.bill_address
58
+ ship = order.ship_address
59
+ same_address = bill == ship
60
+
61
+ [
62
+ order.number,
63
+ order.created_at.strftime('%Y/%m/%d'), # Format to YYYY/MM/DD
64
+ bill&.company.presence || "#{bill&.last_name} #{bill&.first_name}",
65
+ nil, nil, nil,
66
+ bill&.zipcode,
67
+ "#{bill&.state&.name}#{bill&.city}#{bill&.address1}#{bill&.address2}",
68
+ bill&.phone,
69
+ nil,
70
+ order.email,
71
+ nil, nil, nil, nil, # Cc emails
72
+ (!same_address ? (ship&.company.presence || "#{ship&.last_name} #{ship&.first_name}") : nil),
73
+ nil, nil, nil,
74
+ (!same_address ? ship&.zipcode : nil),
75
+ (!same_address ? "#{ship&.state&.name}#{ship&.city}#{ship&.address1}#{ship&.address2}" : nil),
76
+ (!same_address ? ship&.phone : nil),
77
+ 2,
78
+ nil,
79
+ latest_np_payment(order)&.invoice_sending_postal? ? 1 : 2,
80
+ nil,
81
+ order.total.to_i
82
+ ] + detail_row + [ nil, nil ]
83
+ end
84
+
85
+ # Build detail rows for the order
86
+ # Each detail row contains the following columns:
87
+ # - 明細
88
+ # - 元加盟店取引受注日 : always nil
89
+ # - 単価
90
+ # - 数量
91
+ # - 金額
92
+ def build_detail_rows(order)
93
+ rows = []
94
+ set_row = lambda do |name, price, quantity|
95
+ rows << [ name, nil, price.to_i, quantity, price.to_i * quantity ]
96
+ end
97
+
98
+ order.line_items.each do |li|
99
+ set_row.call(li.product.name, li.price, li.quantity)
100
+ end
101
+
102
+ order.all_adjustments.eligible.where.not(source_type: [ "Spree::TaxRate" ]).each do |adj|
103
+ amount = adj.amount
104
+ next if amount == 0
105
+
106
+ set_row.call(adj.label, amount, 1)
107
+ end
108
+
109
+ if order.shipment_total.to_i > 0
110
+ set_row.call("送料", order.shipment_total, 1)
111
+ end
112
+
113
+ rows
114
+ end
115
+
116
+ def latest_np_payment(order)
117
+ order.payments.valid
118
+ .joins(:payment_method)
119
+ .where(spree_payment_methods: { type: "Spree::PaymentMethod::NpAtobarai" })
120
+ .last
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,29 @@
1
+ <% content_for :page_title do %>
2
+ <% if controller_name == 'checkouts' %>
3
+ <%= Spree.t(:draft_orders) %>
4
+ <% else %>
5
+ <%= Spree.t(:orders) %>
6
+ <% end %>
7
+ <% end %>
8
+
9
+ <% content_for :page_actions do %>
10
+ <%= button_to Spree.t("admin.np_atobarai.btns.btn_export"), csv_export_admin_np_orders_path,
11
+ method: :post,
12
+ params: { order_ids: @orders.map(&:id) },
13
+ class: "btn btn-primary",
14
+ data: { turbo: false } %>
15
+ <% end %>
16
+
17
+ <div class="card-lg np-orders-container">
18
+ <%= render 'spree/admin/orders/filters' %>
19
+ <%= render 'spree_np_atobarai/admin/np_orders/list' %>
20
+ </div>
21
+
22
+ <script>
23
+ document.addEventListener("turbo:load", function() {
24
+ const form = document.querySelector(".np-orders-container form.filter-wrap")
25
+ if (form) {
26
+ form.removeAttribute("action")
27
+ }
28
+ })
29
+ </script>
@@ -0,0 +1,32 @@
1
+ <div class="form-group">
2
+ <%= f.label :preferred_np_atobarai_fee, Spree.t('np_atobarai.fee_label') %>
3
+ <%= f.text_field :preferred_np_atobarai_fee, class: 'form-control' %>
4
+ <span class="text-muted form-text mt-2">
5
+ <%= Spree.t('np_atobarai.fee_hint') %>
6
+ </span>
7
+ </div>
8
+
9
+ <div class="form-group">
10
+ <div class="custom-control custom-checkbox">
11
+ <%= f.check_box :preferred_np_atobarai_no_fee_same_month, class: 'custom-control-input' %>
12
+ <%= f.label :preferred_np_atobarai_no_fee_same_month, Spree.t('np_atobarai.no_fee_same_month_label'), class: 'custom-control-label' %>
13
+ </div>
14
+ <span class="text-muted form-text mt-2">
15
+ <%= Spree.t('np_atobarai.no_fee_same_month_hint') %>
16
+ </span>
17
+ </div>
18
+
19
+ <div class="form-group">
20
+ <%= f.label :preferred_tax_category_id, Spree.t(:tax_category) %>
21
+ <%= f.collection_select(
22
+ :preferred_tax_category_id,
23
+ Spree::TaxCategory.all,
24
+ :id,
25
+ :name,
26
+ { include_blank: Spree.t(:none) },
27
+ { class: 'custom-select' }
28
+ ) %>
29
+ <span class="text-muted form-text mt-2">
30
+ 内税計算を適用する税カテゴリーを選択してください
31
+ </span>
32
+ </div>
@@ -0,0 +1,12 @@
1
+ <% current_payment = @order.payments.last %>
2
+
3
+ <%= render 'spree_np_atobarai/np_atobarai_invoice_options', current_payment: current_payment, payment_method: payment_method, order: @order %>
4
+
5
+ <div class="payment-NpAtobarai">
6
+ <p><%= Spree.t('storefront.checkout.np_atobarai.description').html_safe %></p>
7
+ <% if payment_method&.np_atobarai_fee_adjustment_applicable?(@order, current_payment) %>
8
+ <p>
9
+ <strong><%= Spree.t('storefront.checkout.np_atobarai.extra', extra: Spree::Money.new(payment_method.preferred_np_atobarai_fee).to_s) %></strong>
10
+ </p>
11
+ <% end %>
12
+ </div>