diff --git a/app/controllers/spree/billing/subscriptions_controller.rb b/app/controllers/spree/billing/subscriptions_controller.rb
index 488d304b0..3f1ce688e 100644
--- a/app/controllers/spree/billing/subscriptions_controller.rb
+++ b/app/controllers/spree/billing/subscriptions_controller.rb
@@ -1,8 +1,22 @@
module Spree
module Billing
class SubscriptionsController < Spree::Billing::BaseController
- before_action :load_customer
before_action :load_subscription, if: -> { member_action? }
+ before_action :load_variant, only: :create
+
+ helper_method :customer
+
+ def create
+ @subscription = @variant.subscriptions.build(subscription_params.merge(variant_id: @variant.id))
+
+ if @subscription.save
+ flash[:success] = flash_message_for(@subscription, :successfully_created)
+ else
+ flash[:error] = flash_message_for(@subscription, :not_created)
+ end
+
+ redirect_to billing_customer_subscriptions_url(customer)
+ end
protected
@@ -13,14 +27,14 @@ def collection
@collection = @search.result.page(page).per(per_page)
end
- def load_customer
- customer
- end
-
def load_subscription
@subscription = @object
end
+ def load_variant
+ @variant = SpreeCmCommissioner::VariantChecker.new(variant_params, current_vendor).find_or_create_variant
+ end
+
def customer
@customer ||= SpreeCmCommissioner::Customer.find(params[:customer_id])
end
@@ -39,6 +53,14 @@ def object_name
def collection_url(options = {})
billing_customer_subscriptions_url(options)
end
+
+ def variant_params
+ params.require(:variant).permit(:product_id, :sku, :price, option_value_ids: [])
+ end
+
+ def subscription_params
+ params.require(:spree_cm_commissioner_subscription).permit(:start_date, :customer_id, :status, :variant_id)
+ end
end
end
end
diff --git a/app/controllers/spree/billing/variants_controller.rb b/app/controllers/spree/billing/variants_controller.rb
index e240e2ed3..72ffd059e 100644
--- a/app/controllers/spree/billing/variants_controller.rb
+++ b/app/controllers/spree/billing/variants_controller.rb
@@ -4,6 +4,16 @@ class VariantsController < Spree::Billing::BaseController
belongs_to 'spree/product', find_by: :slug
before_action :load_data
+ def new
+ if request.xhr?
+ @variant = @product.variants.build
+
+ render :show, layout: false
+ else
+ redirect_to new_billing_product_variant_url(@product)
+ end
+ end
+
def collection
return @collection if @collection.present?
diff --git a/app/models/spree_cm_commissioner/subscription.rb b/app/models/spree_cm_commissioner/subscription.rb
index 159bda4cc..dc148ca1c 100644
--- a/app/models/spree_cm_commissioner/subscription.rb
+++ b/app/models/spree_cm_commissioner/subscription.rb
@@ -6,6 +6,7 @@ class Subscription < SpreeCmCommissioner::Base
belongs_to :variant, class_name: 'Spree::Variant'
belongs_to :customer, class_name: 'SpreeCmCommissioner::Customer'
+ belongs_to :product, class_name: 'Spree::Product'
has_many :orders, -> { order(:created_at) }, class_name: 'Spree::Order', dependent: :nullify
has_many :line_items, through: :orders, class_name: 'Spree::LineItem'
@@ -23,6 +24,8 @@ class Subscription < SpreeCmCommissioner::Base
after_create :create_order
after_commit :update_customer_active_subscriptions_count
+ accepts_nested_attributes_for :variant
+
def create_order
SpreeCmCommissioner::SubscribedOrderCreator.call(subscription: self)
end
diff --git a/app/models/spree_cm_commissioner/variant_decorator.rb b/app/models/spree_cm_commissioner/variant_decorator.rb
index 9a424fe77..896256a66 100644
--- a/app/models/spree_cm_commissioner/variant_decorator.rb
+++ b/app/models/spree_cm_commissioner/variant_decorator.rb
@@ -9,6 +9,8 @@ def self.prepended(base)
base.validate :validate_option_types
base.scope :subscribable, -> { active.joins(:product).where(product: { subscribable: true, status: :active }) }
+
+ base.has_many :subscriptions, class_name: 'SpreeCmCommissioner::Subscription', dependent: :destroy
end
def selected_option_value_ids
diff --git a/app/services/spree_cm_commissioner/sku_generator.rb b/app/services/spree_cm_commissioner/sku_generator.rb
new file mode 100644
index 000000000..7c6585185
--- /dev/null
+++ b/app/services/spree_cm_commissioner/sku_generator.rb
@@ -0,0 +1,29 @@
+module SpreeCmCommissioner
+ class SkuGenerator
+ def initialize(product, variant_params)
+ @product = product
+ @variant_params = variant_params
+ end
+
+ def generate_sku
+ return if @variant_params.nil? || @variant_params.blank?
+
+ option_values = @variant_params[:option_value_ids].map { |id| Spree::OptionValue.find(id) }
+ sku_parts = [product.name]
+
+ option_values.each do |option_value|
+ sku_parts << "#{option_value.option_type.name}-#{option_value.name}"
+ end
+
+ sku_parts << "price-#{@variant_params[:price]}"
+
+ sku_parts.join('-').gsub(' ', '-').downcase
+ end
+
+ private
+
+ def product
+ @product ||= Spree::Product.find(@variant_params[:product_id])
+ end
+ end
+end
diff --git a/app/services/spree_cm_commissioner/variant_checker.rb b/app/services/spree_cm_commissioner/variant_checker.rb
new file mode 100644
index 000000000..0fd7f39bc
--- /dev/null
+++ b/app/services/spree_cm_commissioner/variant_checker.rb
@@ -0,0 +1,32 @@
+module SpreeCmCommissioner
+ class VariantChecker
+ attr_reader :variant
+
+ def initialize(variant_params, current_vendor)
+ @product = Spree::Product.find_by(id: variant_params[:product_id])
+ @variant_params = variant_params
+ @current_vendor = current_vendor
+ end
+
+ def find_or_create_variant
+ find_variant_by_sku || create_variant
+ end
+
+ private
+
+ def find_variant_by_sku
+ @variant = @product.variants.where(sku: variant_sku).first
+ end
+
+ def create_variant
+ variant_creator = SpreeCmCommissioner::VariantCreator.new(@product, @variant_params, @current_vendor)
+ variant_creator.create_variant
+ @variant = variant_creator.variant
+ end
+
+ def variant_sku
+ sku_generator = SpreeCmCommissioner::SkuGenerator.new(@product, @variant_params)
+ sku_generator.generate_sku
+ end
+ end
+end
diff --git a/app/services/spree_cm_commissioner/variant_creator.rb b/app/services/spree_cm_commissioner/variant_creator.rb
new file mode 100644
index 000000000..b517110ae
--- /dev/null
+++ b/app/services/spree_cm_commissioner/variant_creator.rb
@@ -0,0 +1,37 @@
+module SpreeCmCommissioner
+ class VariantCreator
+ attr_reader :variant
+
+ def initialize(product, variant_params, current_vendor)
+ @product = product
+ @variant_params = variant_params
+ @current_vendor = current_vendor
+ end
+
+ def create_variant
+ @variant = @product.variants.build
+ generate_sku
+ set_variant_attributes
+ @variant.save
+ @variant
+ end
+
+ private
+
+ def generate_sku
+ sku_generator = SpreeCmCommissioner::SkuGenerator.new(@product, @variant_params)
+ @variant.sku = sku_generator.generate_sku
+ end
+
+ def set_variant_attributes
+ @variant.option_value_ids = @variant_params[:option_value_ids]
+ @variant.price = @variant_params[:price]
+ stock_item = @variant.stock_items.build(stock_location_id: stock_location, count_on_hand: 1, backorderable: false)
+ stock_item.save
+ end
+
+ def stock_location
+ @stock_location ||= Spree::StockLocation.find_by!(vendor_id: @current_vendor.id).id
+ end
+ end
+end
diff --git a/app/views/spree/billing/shared/_subscription_tabs.html.erb b/app/views/spree/billing/shared/_subscription_tabs.html.erb
index a22df5047..30f41a254 100644
--- a/app/views/spree/billing/shared/_subscription_tabs.html.erb
+++ b/app/views/spree/billing/shared/_subscription_tabs.html.erb
@@ -1,12 +1,12 @@
<% content_for :page_title do %>
- <%= page_header_back_button spree.billing_customer_subscriptions_path(@customer) %>
+ <%= page_header_back_button spree.billing_customer_subscriptions_path(customer) %>
<%= @subscription.variant.sku %>
<% end %>
<% content_for :page_tabs do %>
<%= link_to_with_icon 'tag.svg',
Spree.t(:edit),
- spree.edit_billing_customer_subscription_path(@customer, @subscription),
+ spree.edit_billing_customer_subscription_path(customer, @subscription),
class: "nav-link #{'active' if current == :account}" %>
<% end %>
@@ -14,7 +14,7 @@
<%= link_to_with_icon 'cart.svg',
Spree.t(:orders),
- spree.billing_orders_path(customer_id: @customer.id, subscription_id: @subscription.id),
+ spree.billing_orders_path(customer_id: customer.id, subscription_id: @subscription.id),
class: "nav-link #{'active' if current == :order}" %>
<% end %>
diff --git a/app/views/spree/billing/subscriptions/_form.html.erb b/app/views/spree/billing/subscriptions/_form.html.erb
index b8d6267f7..9ee37a22f 100644
--- a/app/views/spree/billing/subscriptions/_form.html.erb
+++ b/app/views/spree/billing/subscriptions/_form.html.erb
@@ -1,22 +1,90 @@
-
- <%= f.hidden_field :customer_id, value: @customer.id %>
- <%= f.field_container :variant_id do %>
- <%= f.label :variant, Spree.t(:variant) %>
- <%= f.collection_select(:variant_id, @customer.subscribable_variants, :id, :display_variant, { include_blank: false }, { class: 'select2' ,disabled: @subscription.persisted?}) %>
- <%= f.error_message_on :product_id %>
- <% end %>
- <%= f.field_container :start_date do %>
- <%= f.label :start_date, Spree.t(:start_date) %>
- <%= f.date_field :start_date, class: 'form-control', disabled: @subscription.persisted? %>
- <%= f.error_message_on :start_date %>
- <% end %>
- <% if @subscription.id.present? %>
- <%= f.field_container :status do %>
- <%= f.label :status, Spree.t(:status) %>
- <%= f.select :status, SpreeCmCommissioner::Subscription.statuses.keys, {}, class: 'select2' %>
- <%= f.error_message_on :status %>
+
+ <%= f.hidden_field :customer_id, value: customer.id %>
+
+
+ <% if @subscription.persisted? %>
+ <%= f.field_container :product do %>
+ <%= f.label :product, Spree.t(:product) %>
+ <%= f.text_field :product, value: f.object.variant.product.name, class: 'form-control', disabled: true %>
+ <%= f.error_message_on :product %>
+ <% end %>
+ <% else %>
+ <%= f.field_container :product do %>
+ <%= f.label :product, Spree:t(:product) %>
+ <%= f.select :product, options_for_select(Spree::Product.joins(:taxons)
+ .where(spree_taxons: { id: customer.taxon_id })
+ .map{ |product| [product.name, product.sku] }),
+ { include_blank: true },
+ { class: "select2-clear" } %>
+ <%= f.error_message_on :product %>
+ <% end %>
<% end %>
- <% end %>
+
+
+
+ <% if @subscription.persisted? %>
+ <%= f.field_container :variant do %>
+ <%= f.label :variant, Spree.t(:variant) %>
+ <%= f.text_field :variant, value: f.object.variant.sku, class: 'form-control', disabled: true %>
+ <%= f.error_message_on :product %>
+ <% end %>
+ <% else %>
+
+
+ <% end %>
+
+
+
+ <%= f.field_container :start_date do %>
+ <%= f.label :start_date, Spree.t(:start_date) %>
+ <%= f.date_field :start_date, class: 'form-control', disabled: @subscription.persisted? %>
+ <%= f.error_message_on :start_date %>
+ <% end %>
+
+
+
+ <% if @subscription.id.present? %>
+ <%= f.field_container :status do %>
+ <%= f.label :status, Spree.t(:status) %>
+ <%= f.select :status, SpreeCmCommissioner::Subscription.statuses.keys, {}, class: 'select2' %>
+ <%= f.error_message_on :status %>
+ <% end %>
+ <% end %>
+
+
+
\ No newline at end of file
diff --git a/app/views/spree/billing/variants/show.erb b/app/views/spree/billing/variants/show.erb
new file mode 100644
index 000000000..8bb02de74
--- /dev/null
+++ b/app/views/spree/billing/variants/show.erb
@@ -0,0 +1,33 @@
+ <%= fields_for @variant do |f| %>
+
+
+ <%= f.hidden_field :product_id, value: @product.id %>
+
+
+
+
+
+ <%= f.label :price, Spree.t(:price) %>
+ <%= f.text_field :price, placeholder: I18n.t('spree.billing.subscription_price_placeholder', currency: @variant.currency), value: number_to_currency(@variant.price, unit: ''), class: 'form-control' %>
+
+
+
+
+ <% @product.option_types.each do |option_type| %>
+
+ <%= label :new_variant, option_type.presentation %>
+ <% if option_type.name == 'color' %>
+ <%= f.collection_select 'option_value_ids', option_type.option_values, :id, :name,
+ { include_blank: true }, { name: 'variant[option_value_ids][]', class: 'select2-clear form-control', id: "option_value_ids-#{option_type.id}" } %>
+ <% else %>
+ <%= f.collection_select 'option_value_ids', option_type.option_values, :id, :presentation,
+ { include_blank: true }, { name: 'variant[option_value_ids][]', class: 'select2-clear form-control', id: "option_value_ids-#{option_type.id}" } %>
+ <% end %>
+
+ <% end %>
+
+
+
+ <% end %>
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 8fc0729cc..1e2c10ed8 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -265,6 +265,7 @@ en:
overdue: "Overdue"
failed: "Failed"
unknown: "Unknown"
+ subscription_price_placeholder: "Subscription Price In %{currency}"
upsupported_payment: "Unsupported event"
exceeded_available_quantity_on_date:
zero: "Rooms are not available on %{date}"
diff --git a/config/locales/km.yml b/config/locales/km.yml
index c47f338dc..efdc66a7c 100644
--- a/config/locales/km.yml
+++ b/config/locales/km.yml
@@ -247,6 +247,7 @@ km:
overdue: "ហួសថ្ងៃបង់"
failed: "បរាជ័យ"
unknown: "មិនស្គាល់"
+ subscription_price_placeholder: "តម្លៃជា %{currency}"
upsupported_payment: "មិនស្គាល់ការបង់ប្រាក់"
selected_item_not_available_on_date: "Selected item not available on %{date}"
auto_apply: "ដាក់ប្រើដោយស្វ័យប្រវត្តិ"
diff --git a/config/routes.rb b/config/routes.rb
index 9bee53313..64b892530 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -230,6 +230,10 @@
resources :homepage_section_relatable_options
end
+ namespace :billing do
+ resources :variants, only: %i[index show]
+ end
+
namespace :storefront do
resource :cart, controller: :cart, only: %i[show create destroy] do
patch :restart_checkout_flow
diff --git a/spec/services/spree_cm_commissioner/sku_generator_spec.rb b/spec/services/spree_cm_commissioner/sku_generator_spec.rb
new file mode 100644
index 000000000..ca212c56e
--- /dev/null
+++ b/spec/services/spree_cm_commissioner/sku_generator_spec.rb
@@ -0,0 +1,17 @@
+require 'spec_helper'
+
+RSpec.describe SpreeCmCommissioner::SkuGenerator do
+ let(:product) { create(:product, name: "Waste Collection") }
+ let(:option_types) { [create(:option_type, name: "Month"), create(:option_type, name: "Due Date"), create(:option_type, name: "Payment Method")] }
+ let(:option_values) { [create(:option_value, name: "1", option_type: option_types[0]), create(:option_value, name: "1", option_type: option_types[1]), create(:option_value, name: "pre-paid", option_type: option_types[2])] }
+ let(:variant_params) { { product_id: product.id, option_value_ids: option_values.map(&:id), price: 10.0 } }
+ let(:sku_generator) { described_class.new(product, variant_params) }
+
+ describe '.generate_sku' do
+ it 'returns the generated sku' do
+ generated_sku = sku_generator.generate_sku
+
+ expect(generated_sku).to eq "waste-collection-month-1-due-date-1-payment-method-pre-paid-price-10.0"
+ end
+ end
+end
diff --git a/spec/services/spree_cm_commissioner/variant_checker_spec.rb b/spec/services/spree_cm_commissioner/variant_checker_spec.rb
new file mode 100644
index 000000000..a858f13aa
--- /dev/null
+++ b/spec/services/spree_cm_commissioner/variant_checker_spec.rb
@@ -0,0 +1,33 @@
+require 'spec_helper'
+
+RSpec.describe SpreeCmCommissioner::VariantChecker do
+ let!(:product) { create(:product, name: "Island Waste Collection") }
+ let!(:option_types) { [create(:option_type, name: "Month"), create(:option_type, name: "Due Date"), create(:option_type, name: "Payment Method")] }
+ let!(:option_values) { [create(:option_value, name: "1", id: 1, option_type: option_types[0]), create(:option_value, name: "1", id: 2, option_type: option_types[1]), create(:option_value, name: "pre-paid", id: 3, option_type: option_types[2])] }
+ let!(:variant_params) { { product_id: product.id, option_value_ids: option_values.map(&:id), price: 10.0 } }
+ let!(:current_vendor) { create(:vendor) }
+
+ describe '#find_or_create_variant' do
+ context 'when the variant already exists' do
+ let!(:variant1) { SpreeCmCommissioner::VariantCreator.new(product, variant_params, current_vendor).create_variant }
+
+ it 'returns the existing variant' do
+ variant_checker = described_class.new(product, variant_params, current_vendor)
+ variant = variant_checker.find_or_create_variant
+ expect(variant).to be_a Spree::Variant
+ expect(variant).to eq(variant1)
+ end
+ end
+
+ context 'when the variant does not exist' do
+ it 'creates a new variant' do
+ variant_checker = described_class.new(product, variant_params, current_vendor)
+ allow(variant_checker).to receive(:find_variant_by_sku)
+ expect(variant_checker.send(:find_variant_by_sku)).to eq nil
+
+ variant = variant_checker.find_or_create_variant
+ expect(variant).to be_a Spree::Variant
+ end
+ end
+ end
+end
diff --git a/spec/services/spree_cm_commissioner/variant_creator_spec.rb b/spec/services/spree_cm_commissioner/variant_creator_spec.rb
new file mode 100644
index 000000000..495ad7ca6
--- /dev/null
+++ b/spec/services/spree_cm_commissioner/variant_creator_spec.rb
@@ -0,0 +1,22 @@
+require 'spec_helper'
+
+RSpec.describe SpreeCmCommissioner::VariantCreator do
+ describe '#create_variant' do
+ let(:product) { create(:product, name: "Waste Collection") }
+ let(:option_types) { [create(:option_type, name: "Month"), create(:option_type, name: "Due Date"), create(:option_type, name: "Payment Method")] }
+ let(:option_values) { [create(:option_value, name: "1", id: 1, option_type: option_types[0]), create(:option_value, name: "1", id: 2, option_type: option_types[1]), create(:option_value, name: "pre-paid", id: 3, option_type: option_types[2])] }
+ let(:variant_params) { { product_id: product.id, option_value_ids: option_values.map(&:id), price: 10.0 } }
+ let(:sku_generator) { described_class.new(product, variant_params) }
+ let(:current_vendor) { create(:vendor) }
+
+ it 'creates a variant with the expected attributes' do
+ variant_creator = SpreeCmCommissioner::VariantCreator.new(product, variant_params, current_vendor)
+ variant = variant_creator.create_variant
+
+ expect(variant.sku).to eq("waste-collection-month-1-due-date-1-payment-method-pre-paid-price-10.0")
+ expect(variant.option_value_ids).to eq([1, 2, 3])
+ expect(variant.price).to eq(10)
+ expect(variant.stock_items.first.count_on_hand).to eq(1)
+ end
+ end
+end