From 74bc38c2b757ead7b0716772627519bc9b2adb26 Mon Sep 17 00:00:00 2001 From: Thea Choem <29684683+theachoem@users.noreply.github.com> Date: Thu, 23 May 2024 18:09:07 +0700 Subject: [PATCH] close #162 allow payout to be optional for product --- .../payout_profile_products_controller.rb | 10 +++++ app/models/spree/payout_profile.rb | 19 +++++++++ app/models/spree/payout_profiles/payway_v2.rb | 5 +++ app/models/vpago/line_item_decorator.rb | 5 ++- app/models/vpago/product_decorator.rb | 9 ++++- .../payout_profile_products.html.erb.deface | 4 +- .../payout_profile_products/_form.html.erb | 27 ++++++++++++- .../_payout_profile_info_card.html.erb | 24 +++++++++++ .../_payout_profile_status.html.erb | 11 +++++ .../payout_profile_products/edit.html.erb | 18 +++++++++ .../payout_profile_products/new.html.erb | 17 +++++++- .../admin/payout_profiles/_filter.html.erb | 19 +++++++++ .../admin/payout_profiles/_form.html.erb | 2 +- .../admin/payout_profiles/index.html.erb | 8 +++- ..._optional_spere_payout_profile_products.rb | 5 +++ spec/models/spree/line_item_spec.rb | 40 ++++++------------- 16 files changed, 185 insertions(+), 38 deletions(-) create mode 100644 app/views/spree/admin/payout_profile_products/_payout_profile_info_card.html.erb create mode 100644 app/views/spree/admin/payout_profile_products/_payout_profile_status.html.erb create mode 100644 app/views/spree/admin/payout_profile_products/edit.html.erb create mode 100644 app/views/spree/admin/payout_profiles/_filter.html.erb create mode 100644 db/migrate/20240523070757_add_optional_spere_payout_profile_products.rb diff --git a/app/controllers/spree/admin/payout_profile_products_controller.rb b/app/controllers/spree/admin/payout_profile_products_controller.rb index 901c7046..8878b67b 100644 --- a/app/controllers/spree/admin/payout_profile_products_controller.rb +++ b/app/controllers/spree/admin/payout_profile_products_controller.rb @@ -1,8 +1,18 @@ module Spree module Admin class PayoutProfileProductsController < Spree::Admin::ResourceController + before_action :redirect_to_object, only: [:index] + belongs_to 'spree/product', find_by: :slug + def redirect_to_object + if parent.payout_profile_products.any? + redirect_to edit_object_url(parent.payout_profile_products.first) + else + redirect_to new_object_url + end + end + def model_class Spree::PayoutProfileProduct end diff --git a/app/models/spree/payout_profile.rb b/app/models/spree/payout_profile.rb index ac3e1756..e2bbd1fb 100644 --- a/app/models/spree/payout_profile.rb +++ b/app/models/spree/payout_profile.rb @@ -19,16 +19,35 @@ class PayoutProfile < Base before_save :ensure_default_exists_and_clear_vendor before_destroy :confirm_destroyable + self.whitelisted_ransackable_attributes = %w[name vendor_id] + def self.default Rails.cache.fetch("default_payout_account/#{self.name.underscore}") do find_by(type: self.name, default: true) end end + def bank_name + 'None' + end + + def display_name + display_name = name + + bank_info = [bank_name, bank_account_number].compact.join(" - ") + display_name += " (#{bank_info})" unless bank_info.empty? + + display_name + end + def verified? verified_at.present? end + def receivable? + verified? && active? + end + def registered_in_bank? true end diff --git a/app/models/spree/payout_profiles/payway_v2.rb b/app/models/spree/payout_profiles/payway_v2.rb index e241ccc0..708fea6a 100644 --- a/app/models/spree/payout_profiles/payway_v2.rb +++ b/app/models/spree/payout_profiles/payway_v2.rb @@ -30,6 +30,11 @@ def allow_to_verify_with_bank? preferred_api_key.present? && preferred_rsa_public_key.present? end + + # override + def bank_name + 'ABA' + end end end end diff --git a/app/models/vpago/line_item_decorator.rb b/app/models/vpago/line_item_decorator.rb index 1610d79f..a01dbf4c 100644 --- a/app/models/vpago/line_item_decorator.rb +++ b/app/models/vpago/line_item_decorator.rb @@ -4,10 +4,13 @@ def self.prepended(base) base.has_many :payout_profiles, class_name: 'Spree::PayoutProfile', through: :product base.has_many :active_payout_profiles, class_name: 'Spree::PayoutProfile', through: :product base.has_many :active_payway_payout_profiles, class_name: 'Spree::PayoutProfile', through: :product + + base.has_many :required_active_payout_profiles, class_name: 'Spree::PayoutProfile', through: :product end + # considred required when there are any required profiles. def required_payway_payout? - active_payway_payout_profiles.any? + required_active_payout_profiles.payway.exists? end end end diff --git a/app/models/vpago/product_decorator.rb b/app/models/vpago/product_decorator.rb index 287d8666..f6a5d6a8 100644 --- a/app/models/vpago/product_decorator.rb +++ b/app/models/vpago/product_decorator.rb @@ -1,11 +1,18 @@ module Vpago module ProductDecorator def self.prepended(base) + base.scope :required, -> { where(optional: false) } + base.scope :optional, -> { where(optional: true) } + base.has_many :payout_profile_products, class_name: 'Spree::PayoutProfileProduct', inverse_of: :product base.has_many :payout_profiles, class_name: 'Spree::PayoutProfile', through: :payout_profile_products, source: :payout_profile - + base.has_many :active_payout_profiles, -> { verified.active }, class_name: 'Spree::PayoutProfile', through: :payout_profile_products, source: :payout_profile base.has_many :active_payway_payout_profiles, -> { payway.verified.active }, class_name: 'Spree::PayoutProfile', through: :payout_profile_products, source: :payout_profile + + # required + base.has_many :required_payout_profile_products, -> { required }, class_name: 'Spree::PayoutProfileProduct', inverse_of: :product + base.has_many :required_active_payout_profiles, -> { verified.active }, class_name: 'Spree::PayoutProfile', through: :payout_profile_products, source: :payout_profile end end end diff --git a/app/overrides/spree/admin/shared/_product_tabs/payout_profile_products.html.erb.deface b/app/overrides/spree/admin/shared/_product_tabs/payout_profile_products.html.erb.deface index 43606497..b7aef11a 100644 --- a/app/overrides/spree/admin/shared/_product_tabs/payout_profile_products.html.erb.deface +++ b/app/overrides/spree/admin/shared/_product_tabs/payout_profile_products.html.erb.deface @@ -2,7 +2,7 @@ <%= content_tag :li, class: 'nav-item' do %> <%= link_to_with_icon 'wallet2.svg', - Spree.t(:payout_profile_products), + Spree.t(:payout), admin_product_payout_profile_products_url(@product), class: "nav-link #{'active' if current == :payout_profile_products}" %> -<% end if can?(:admin, Spree::PayoutProfileProduct) %> \ No newline at end of file +<% end if can?(:admin, Spree::PayoutProfileProduct) %> diff --git a/app/views/spree/admin/payout_profile_products/_form.html.erb b/app/views/spree/admin/payout_profile_products/_form.html.erb index f45ec26a..90d99702 100644 --- a/app/views/spree/admin/payout_profile_products/_form.html.erb +++ b/app/views/spree/admin/payout_profile_products/_form.html.erb @@ -1,5 +1,28 @@ <%= f.field_container :payout_profile_id do %> <%= f.label :payout_profile_id, Spree.t(:payout_profile) %> - <%= f.collection_select :payout_profile_id, @product.vendor.payout_profiles, :id, :name, { prompt: Spree.t('match_choices.none') }, class: 'form-control select2' %> + <%= f.collection_select :payout_profile_id, @product.vendor.payout_profiles, :id, :display_name, { prompt: Spree.t('match_choices.none') }, class: 'form-control select2' %> <%= f.error_message_on :payout_profile_id %> -<% end %> \ No newline at end of file +<% end %> + +<%= render partial: 'payout_profile_info_card', locals: { payout_profile: f.object.payout_profile } if f.object.payout_profile.present? %> + +<%= f.field_container :optional, class: ["form-group card card-body"] do %> + <% default = object.class.columns_hash['optional'].default %> + + <%= Spree.t(:optional) %> +
+ <%= f.radio_button :optional, true, class: 'form-check-input' %> + <%= f.label :optional_true, Spree.t(:say_yes) + (default == 'true' ? ' (default)' : ''), class: 'form-check-label' %> + + <%= "User can buy this product with other payment methods beside #{f.object.payout_profile.bank_name}, but amount will be sent to #{Spree::Store.default.name} account" %> + +
+ +
+ <%= f.radio_button :optional, false, class: 'form-check-input' %> + <%= f.label :optional_false, Spree.t(:say_no) + (default == 'false' ? ' (default)' : ''), class: 'form-check-label' %> + + <%= "User can only buy this product with #{f.object.payout_profile.bank_name} payment methods" %> + +
+<% end if f.object.payout_profile.present? %> diff --git a/app/views/spree/admin/payout_profile_products/_payout_profile_info_card.html.erb b/app/views/spree/admin/payout_profile_products/_payout_profile_info_card.html.erb new file mode 100644 index 00000000..f37c450b --- /dev/null +++ b/app/views/spree/admin/payout_profile_products/_payout_profile_info_card.html.erb @@ -0,0 +1,24 @@ + diff --git a/app/views/spree/admin/payout_profile_products/_payout_profile_status.html.erb b/app/views/spree/admin/payout_profile_products/_payout_profile_status.html.erb new file mode 100644 index 00000000..7b4ff5ed --- /dev/null +++ b/app/views/spree/admin/payout_profile_products/_payout_profile_status.html.erb @@ -0,0 +1,11 @@ +<% if payout_profile.present? && !payout_profile.receivable? %> +
+ Profile is not verified or is inactive. Please verify and mark it as active to receive payout amount. + <%= button_link_to Spree.t(:verify), spree.edit_admin_payout_profile_path(payout_profile), class: "btn-warning", icon: 'send.svg' %> +
+<% elsif payout_profile&.receivable? %> +
+ <%= svg_icon name: "tick.svg", classes: 'mr-2', width: '16', height: '16' %> + This product is now eligible for payout +
+<% end %> diff --git a/app/views/spree/admin/payout_profile_products/edit.html.erb b/app/views/spree/admin/payout_profile_products/edit.html.erb new file mode 100644 index 00000000..961e2571 --- /dev/null +++ b/app/views/spree/admin/payout_profile_products/edit.html.erb @@ -0,0 +1,18 @@ +<%= render partial: 'spree/admin/shared/product_tabs', locals: { current: :payout_profile_products } %> +<%= render partial: 'spree/admin/shared/error_messages', locals: { target: @payout_profile_product } %> + +<%= render partial: 'payout_profile_status', locals: { payout_profile: @payout_profile_product.payout_profile } %> + +<% content_for :page_actions do %> + <%= button_link_to Spree.t(:add_profiles), admin_payout_profiles_url(q: { vendor_id_eq: @product.vendor_id }), icon: 'arrow-right-circle.svg' %> +<% end %> + +<%= form_for @payout_profile_product, url: object_url do |f| %> +
+ <%= render partial: 'form', locals: { f: f } %> +
+ <%= button Spree.t('actions.update'), 'save.svg', 'submit', { class: 'btn-success', data: { disable_with: "#{ Spree.t(:saving) }..." } } %> + <%= link_to_with_icon 'delete.svg', Spree.t(:delete), object_url(@payout_profile_product), { class: 'btn btn-danger float-right', data: { confirm: Spree.t(:are_you_sure), method: :delete } } %> +
+
+<% end %> diff --git a/app/views/spree/admin/payout_profile_products/new.html.erb b/app/views/spree/admin/payout_profile_products/new.html.erb index 68a7fd4c..4dd84d3e 100644 --- a/app/views/spree/admin/payout_profile_products/new.html.erb +++ b/app/views/spree/admin/payout_profile_products/new.html.erb @@ -1,9 +1,22 @@ <%= render partial: 'spree/admin/shared/product_tabs', locals: { current: :payout_profile_products } %> <%= render partial: 'spree/admin/shared/error_messages', locals: { target: @payout_profile_product } %> +<% content_for :page_actions do %> + <%= button_link_to Spree.t(:add_profiles) + " (#{@product.vendor&.name})", admin_payout_profiles_url(q: { vendor_id_eq: @product.vendor_id }), icon: 'arrow-right-circle.svg' %> +<% end if @product.vendor.payout_profiles.any? %> + +<% if @product.vendor.payout_profiles.none? %> +
+ To set up payouts for this product, make sure the vendor has a payout account linked + <%= button_link_to Spree.t(:add_profiles) + " (#{@product.vendor&.name})", new_admin_payout_profile_url(vendor_id: @product.vendor_id), class: "btn-warning", icon: 'arrow-right-circle.svg' %> +
+<% end %> + <%= form_for @payout_profile_product, url: collection_url do |f| %>
<%= render partial: 'form', locals: { f: f } %> - <%= render partial: 'spree/admin/shared/new_resource_links' %> +
+ <%= button Spree.t('actions.create'), 'save.svg', 'submit', {class: 'btn-success', data: { disable_with: "#{ Spree.t(:saving) }..." }} %> +
-<% end %> +<% end if @product.vendor.payout_profiles.any? %> diff --git a/app/views/spree/admin/payout_profiles/_filter.html.erb b/app/views/spree/admin/payout_profiles/_filter.html.erb new file mode 100644 index 00000000..cb78786b --- /dev/null +++ b/app/views/spree/admin/payout_profiles/_filter.html.erb @@ -0,0 +1,19 @@ +<%= search_form_for [:admin, @search] do |f| %> +
+
+
+ <%= f.label :name_cont, Spree.t(:name) %> + <%= f.text_field :name_cont, size: 15, class: "form-control js-quick-search-target js-filterable" %> +
+
+
+
+ <%= f.label :vendor_id_eq, Spree.t(:vendor) %> + <%= f.select :vendor_id_eq, Spree::Vendor.all.map{|t| [t.name , t.id]}, { include_blank: true}, class: 'form-control js-filterable' %> +
+
+
+
+ <%= button Spree.t(:search), 'search.svg' %> +
+<% end %> diff --git a/app/views/spree/admin/payout_profiles/_form.html.erb b/app/views/spree/admin/payout_profiles/_form.html.erb index 769d4ab7..2e8ecdea 100644 --- a/app/views/spree/admin/payout_profiles/_form.html.erb +++ b/app/views/spree/admin/payout_profiles/_form.html.erb @@ -13,7 +13,7 @@ <%= f.field_container :vendor do %> <%= f.label :vendor, Spree.t(:vendors) %> - <%= f.collection_select :vendor_id, Spree::Vendor.all, :id, :name, { prompt: Spree.t('match_choices.none') }, class: 'form-control select2' %> + <%= f.collection_select :vendor_id, Spree::Vendor.all, :id, :name, { prompt: Spree.t('match_choices.none'), selected: f.object.vendor_id || params[:vendor_id] }, class: 'form-control select2' %> <%= f.error_message_on :vendor_id %> <% end unless f.object.default? %> diff --git a/app/views/spree/admin/payout_profiles/index.html.erb b/app/views/spree/admin/payout_profiles/index.html.erb index 49aa1985..5c3ed051 100644 --- a/app/views/spree/admin/payout_profiles/index.html.erb +++ b/app/views/spree/admin/payout_profiles/index.html.erb @@ -3,9 +3,13 @@ <% end %> <% content_for :page_actions do %> - <%= button_link_to Spree.t(:new), new_object_url, class: "btn-success", icon: 'add.svg' %> + <%= button_link_to Spree.t(:new), new_object_url(vendor_id: params.dig(:q, :vendor_id_eq)), class: "btn-success", icon: 'add.svg' %> <% end if can? :create, Spree::PayoutProfile %> +<% content_for :table_filter do %> + <% render partial: 'filter' %> +<% end %> + <% if @payout_profiles.any? %>
@@ -52,7 +56,7 @@ <% else %>
<%= Spree.t(:no_resource_found, resource: plural_resource_name(Spree::PayoutProfile)) %>, - <%= link_to(Spree.t(:add_one), new_object_url) if can? :create, Spree::PayoutProfile %>! + <%= link_to(Spree.t(:add_one), new_object_url(vendor_id: params.dig(:q, :vendor_id_eq))) if can? :create, Spree::PayoutProfile %>!
<% end %> diff --git a/db/migrate/20240523070757_add_optional_spere_payout_profile_products.rb b/db/migrate/20240523070757_add_optional_spere_payout_profile_products.rb new file mode 100644 index 00000000..8ba22e03 --- /dev/null +++ b/db/migrate/20240523070757_add_optional_spere_payout_profile_products.rb @@ -0,0 +1,5 @@ +class AddOptionalSperePayoutProfileProducts < ActiveRecord::Migration[7.0] + def change + add_column :spree_payout_profile_products, :optional, :boolean, null: false, default: false, if_not_exists: true + end +end diff --git a/spec/models/spree/line_item_spec.rb b/spec/models/spree/line_item_spec.rb index 474c557b..41274641 100644 --- a/spec/models/spree/line_item_spec.rb +++ b/spec/models/spree/line_item_spec.rb @@ -2,44 +2,30 @@ RSpec.describe Spree::LineItem, type: :model do it { should have_many(:payout_profiles).class_name('Spree::PayoutProfile').through(:product) } + it { should have_many(:required_active_payout_profiles).class_name('Spree::PayoutProfile').through(:product) } describe '#required_payway_payout?' do - let(:payout_profiles) { [payout_product] } - let(:product) { create(:product, payout_profiles: payout_profiles) } - let(:line_item) { create(:line_item, product: product) } + context 'when there are required_active_payout_profiles in product' do + let(:payout_product) { build(:payway_payout_profile, active: true, verified_at: DateTime.current)} + let(:product) { create(:product, required_active_payout_profiles: [payout_product]) } + let(:line_item) { create(:line_item, product: product) } - context 'when have active & verified payout profile in product' do - let(:payout_product) { create(:payway_payout_profile, active: true, verified_at: DateTime.current)} - it 'return true' do - expect(line_item.active_payway_payout_profiles).to eq [payout_product] + expect(product.required_active_payout_profiles.payway.exists?).to be true + expect(line_item.required_active_payout_profiles.payway.exists?).to be true + expect(line_item.required_payway_payout?).to be true end end - context 'when have verified, but not active payout profile in product' do - let(:payout_product) { create(:payway_payout_profile, active: false, verified_at: DateTime.current)} - - it 'return false' do - expect(line_item.active_payway_payout_profiles).to eq [] - expect(line_item.required_payway_payout?).to be false - end - end + context 'when there are no required_active_payout_profiles in product' do + let(:product) { create(:product, required_active_payout_profiles: []) } + let(:line_item) { create(:line_item, product: product) } - context 'when have active, but not verified payout profile in product' do - let(:payout_product) { create(:payway_payout_profile, active: false, verified_at: DateTime.current)} - it 'return false' do - expect(line_item.active_payway_payout_profiles).to eq [] - expect(line_item.required_payway_payout?).to be false - end - end + expect(product.required_active_payout_profiles.payway.exists?).to be false + expect(line_item.required_active_payout_profiles.payway.exists?).to be false - context 'when does not have active & verified payout profile in product' do - let(:payout_profiles) { [] } - - it 'return false' do - expect(line_item.active_payway_payout_profiles).to eq [] expect(line_item.required_payway_payout?).to be false end end