solidus_subscriptions-alpha 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (97) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +26 -0
  3. data/README.md +128 -0
  4. data/Rakefile +28 -0
  5. data/app/assets/javascripts/spree/backend/solidus_subscriptions.js +2 -0
  6. data/app/assets/javascripts/spree/frontend/solidus_subscriptions.js +2 -0
  7. data/app/assets/stylesheets/spree/backend/solidus_subscriptions.css +4 -0
  8. data/app/assets/stylesheets/spree/frontend/solidus_subscriptions.css +4 -0
  9. data/app/controllers/solidus_subscriptions/api/v1/line_items_controller.rb +35 -0
  10. data/app/controllers/solidus_subscriptions/api/v1/subscriptions_controller.rb +44 -0
  11. data/app/controllers/spree/admin/subscriptions_controller.rb +66 -0
  12. data/app/decorators/spree/controllers/api/line_items/create_subscription_line_items.rb +28 -0
  13. data/app/decorators/spree/controllers/orders/create_subscription_line_items.rb +33 -0
  14. data/app/decorators/spree/line_items/subscription_line_items_association.rb +22 -0
  15. data/app/decorators/spree/orders/after_create.rb +15 -0
  16. data/app/decorators/spree/orders/finalize_creates_subscriptions.rb +19 -0
  17. data/app/decorators/spree/orders/subscription_line_items_association.rb +15 -0
  18. data/app/decorators/spree/users/have_many_subscriptions.rb +18 -0
  19. data/app/decorators/spree/variant_pretty_name.rb +13 -0
  20. data/app/jobs/solidus_subscriptions/process_installments_job.rb +22 -0
  21. data/app/models/solidus_subscriptions/checkout.rb +137 -0
  22. data/app/models/solidus_subscriptions/dispatcher.rb +32 -0
  23. data/app/models/solidus_subscriptions/failure_dispatcher.rb +19 -0
  24. data/app/models/solidus_subscriptions/installment.rb +126 -0
  25. data/app/models/solidus_subscriptions/installment_detail.rb +23 -0
  26. data/app/models/solidus_subscriptions/interval.rb +24 -0
  27. data/app/models/solidus_subscriptions/line_item.rb +98 -0
  28. data/app/models/solidus_subscriptions/line_item_builder.rb +44 -0
  29. data/app/models/solidus_subscriptions/order_builder.rb +40 -0
  30. data/app/models/solidus_subscriptions/out_of_stock_dispatcher.rb +19 -0
  31. data/app/models/solidus_subscriptions/payment_failed_dispatcher.rb +23 -0
  32. data/app/models/solidus_subscriptions/subscription.rb +217 -0
  33. data/app/models/solidus_subscriptions/subscription_generator.rb +60 -0
  34. data/app/models/solidus_subscriptions/subscription_line_item_builder.rb +21 -0
  35. data/app/models/solidus_subscriptions/subscription_order_promotion_rule.rb +25 -0
  36. data/app/models/solidus_subscriptions/subscription_promotion_rule.rb +38 -0
  37. data/app/models/solidus_subscriptions/success_dispatcher.rb +16 -0
  38. data/app/models/solidus_subscriptions/unsubscribable_error.rb +17 -0
  39. data/app/models/solidus_subscriptions/user_mismatch_error.rb +15 -0
  40. data/app/overrides/views/admin_subscribable_checkbox.rb +6 -0
  41. data/app/overrides/views/admin_subscriptions_menu_link.rb +8 -0
  42. data/app/overrides/views/subscription_line_item_fields.rb +6 -0
  43. data/app/views/spree/admin/promotions/rules/_subscription_order_promotion_rule.html.erb +0 -0
  44. data/app/views/spree/admin/promotions/rules/_subscription_promotion_rule.html.erb +0 -0
  45. data/app/views/spree/admin/shared/_no_objects_found.html.erb +4 -0
  46. data/app/views/spree/admin/shared/_subscription_tab.html.erb +3 -0
  47. data/app/views/spree/admin/solidus_subscriptions/subscriptions/_subscription.html.erb +66 -0
  48. data/app/views/spree/admin/subscriptions/_form.html.erb +81 -0
  49. data/app/views/spree/admin/subscriptions/_legacy_form.html.erb +81 -0
  50. data/app/views/spree/admin/subscriptions/_legacy_sidebar.html.erb +28 -0
  51. data/app/views/spree/admin/subscriptions/edit.html.erb +21 -0
  52. data/app/views/spree/admin/subscriptions/index.html.erb +119 -0
  53. data/app/views/spree/admin/subscriptions/new.html.erb +9 -0
  54. data/app/views/spree/admin/variants/_subscribable_checkbox.html.erb +6 -0
  55. data/app/views/spree/frontend/products/_subscription_line_item_fields.html.erb +30 -0
  56. data/config/locales/en.yml +91 -0
  57. data/config/routes.rb +25 -0
  58. data/db/migrate/20160825164850_create_solidus_subscriptions_subscriptions.rb +11 -0
  59. data/db/migrate/20160825173548_create_solidus_subscriptions_line_items.rb +17 -0
  60. data/db/migrate/20160825202248_create_solidus_subscriptions_installments.rb +23 -0
  61. data/db/migrate/20160825211202_create_solidus_subscriptions_installment_details.rb +22 -0
  62. data/db/migrate/20160825214240_add_subscribable_to_spree_variants.rb +5 -0
  63. data/db/migrate/20160829201653_change_subscription_line_items_installments_to_max_installments.rb +5 -0
  64. data/db/migrate/20160902220242_remove_state_from_solidus_susbscriptions_installment_details.rb +5 -0
  65. data/db/migrate/20160902220604_add_successful_to_solidus_subscriptions_installment_details.rb +5 -0
  66. data/db/migrate/20160902221218_add_message_to_solidus_subscriptions_installment_details.rb +5 -0
  67. data/db/migrate/20160922164101_add_interval_length_and_units_to_subscription_line_items.rb +8 -0
  68. data/db/migrate/20161006191003_add_skip_count_to_solidus_subscriptions_subscriptions.rb +5 -0
  69. data/db/migrate/20161006191127_add_successive_skip_count_to_solidus_subscriptions_subscriptions.rb +5 -0
  70. data/db/migrate/20161014212649_allow_spree_line_item_id_to_be_null.rb +5 -0
  71. data/db/migrate/20161017155749_add_order_id_to_solidus_subscriptions_installment_details.rb +6 -0
  72. data/db/migrate/20161017175509_remove_order_id_from_solidus_subscriptions_installments.rb +6 -0
  73. data/db/migrate/20161017201944_add_subscription_order_to_spree_orders.rb +5 -0
  74. data/db/migrate/20161221155142_add_store_to_solidus_subscriptions_subscriptions.rb +6 -0
  75. data/db/migrate/20161223152905_add_address_id_to_solidus_subscriptions_subscriptions.rb +7 -0
  76. data/db/migrate/20170106224713_change_line_item_max_installments_to_end_date.rb +6 -0
  77. data/db/migrate/20170111224458_change_subscription_actionable_date_to_datetime.rb +5 -0
  78. data/db/migrate/20170111232801_change_inteval_actionable_date_to_datetime.rb +5 -0
  79. data/db/migrate/20170112012407_add_config_options_to_subscriptions.rb +7 -0
  80. data/lib/generators/solidus_subscriptions/install/install_generator.rb +30 -0
  81. data/lib/solidus_subscriptions.rb +6 -0
  82. data/lib/solidus_subscriptions/ability.rb +19 -0
  83. data/lib/solidus_subscriptions/config.rb +97 -0
  84. data/lib/solidus_subscriptions/engine.rb +56 -0
  85. data/lib/solidus_subscriptions/permitted_attributes.rb +36 -0
  86. data/lib/solidus_subscriptions/processor.rb +108 -0
  87. data/lib/solidus_subscriptions/testing_support/factories.rb +5 -0
  88. data/lib/solidus_subscriptions/testing_support/factories/installment_detail_factory.rb +7 -0
  89. data/lib/solidus_subscriptions/testing_support/factories/installment_factory.rb +21 -0
  90. data/lib/solidus_subscriptions/testing_support/factories/line_item_factory.rb +18 -0
  91. data/lib/solidus_subscriptions/testing_support/factories/spree/line_item_factory.rb +17 -0
  92. data/lib/solidus_subscriptions/testing_support/factories/spree/order_factory.rb +18 -0
  93. data/lib/solidus_subscriptions/testing_support/factories/spree_modification_factory.rb +8 -0
  94. data/lib/solidus_subscriptions/testing_support/factories/subscription_factory.rb +43 -0
  95. data/lib/solidus_subscriptions/version.rb +3 -0
  96. data/lib/tasks/process_subscriptions.rake +6 -0
  97. metadata +460 -0
@@ -0,0 +1,60 @@
1
+ # This module is responsible for taking SolidusSubscriptions::LineItem
2
+ # objects and creating SolidusSubscriptions::Subscription Objects
3
+ module SolidusSubscriptions
4
+ module SubscriptionGenerator
5
+ extend self
6
+
7
+ SubscriptionConfiguration = Struct.new(:interval_length, :interval_units, :end_date)
8
+
9
+ # Create and persist a subscription for a collection of subscription
10
+ # line items
11
+ #
12
+ # @param subscription_line_items [Array<SolidusSubscriptions::LineItem>] The
13
+ # subscription_line_items to be activated
14
+ #
15
+ # @return [SolidusSubscriptions::Subscription]
16
+ def activate(subscription_line_items)
17
+ return if subscription_line_items.empty?
18
+
19
+ order = subscription_line_items.first.order
20
+ configuration = subscription_configuration(subscription_line_items.first)
21
+
22
+ subscription_attributes = {
23
+ user: order.user,
24
+ line_items: subscription_line_items,
25
+ store: order.store,
26
+ shipping_address: order.ship_address,
27
+ **configuration.to_h
28
+ }
29
+
30
+ Subscription.create!(subscription_attributes) do |sub|
31
+ sub.actionable_date = sub.next_actionable_date
32
+ end
33
+ end
34
+
35
+ # Group a collection of line items by common subscription configuration
36
+ # options. Grouped subscription_line_items can belong to a single
37
+ # subscription.
38
+ #
39
+ # @param subscription_line_items [Array<SolidusSubscriptions::LineItem>] The
40
+ # subscription_line_items to be grouped.
41
+ #
42
+ # @return [Array<Array<SolidusSubscriptions::LineItem>>]
43
+ def group(subscription_line_items)
44
+ subscription_line_items.group_by do |li|
45
+ subscription_configuration(li)
46
+ end.
47
+ values
48
+ end
49
+
50
+ private
51
+
52
+ def subscription_configuration(subscription_line_item)
53
+ SubscriptionConfiguration.new(
54
+ subscription_line_item.interval_length,
55
+ subscription_line_item.interval_units,
56
+ subscription_line_item.end_date
57
+ )
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,21 @@
1
+ module SolidusSubscriptions
2
+ module SubscriptionLineItemBuilder
3
+ private
4
+
5
+ def create_subscription_line_item(line_item)
6
+ SolidusSubscriptions::LineItem.create!(
7
+ subscription_params.merge(spree_line_item: line_item)
8
+ )
9
+
10
+ # Rerun the promotion handler to pickup subscription promotions
11
+ Spree::PromotionHandler::Cart.new(line_item.order).activate
12
+ line_item.order.update!
13
+ end
14
+
15
+ def subscription_params
16
+ params.require(:subscription_line_item).permit(
17
+ SolidusSubscriptions::Config.subscription_line_item_attributes
18
+ )
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,25 @@
1
+ module SolidusSubscriptions
2
+ class SubscriptionOrderPromotionRule < Spree::PromotionRule
3
+ # Promotion can be applied to an entire order. Will only be true
4
+ # for Spree::Order
5
+ #
6
+ # @param promotable [Object] Any object which could have this
7
+ # promotion rule applied to it.
8
+ #
9
+ # @return [Boolean]
10
+ def applicable?(promotable)
11
+ promotable.is_a? Spree::Order
12
+ end
13
+
14
+ # An order is eligible if it fulfills a subscription Installment. Will only
15
+ # return true if the order fulfills one or more Installments
16
+ #
17
+ # @param order [Spree::Order] The order which could have this rule applied
18
+ # to it.
19
+ #
20
+ # @return [Boolean]
21
+ def eligible?(order, **_options)
22
+ order.subscription_order?
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,38 @@
1
+ module SolidusSubscriptions
2
+ class SubscriptionPromotionRule < Spree::PromotionRule
3
+ # Promotion can be applied to an entire order. Will only be true
4
+ # for Spree::Order
5
+ #
6
+ # @param promotable [Object] Any object which could have this
7
+ # promotion rule applied to it.
8
+ #
9
+ # @return [Boolean]
10
+ def applicable?(promotable)
11
+ promotable.is_a? Spree::Order
12
+ end
13
+
14
+ # An order is eligible if it contains a line item with an associates
15
+ # subscription_line_item. This rule applies to order and so its eligibility
16
+ # will always be considered against an order. Will only return true for
17
+ # orders containing Spree::Line item with associated subscription_line_items
18
+ #
19
+ # @param order [Spree::Order] The order which could have this rule applied
20
+ # to it.
21
+ #
22
+ # @return [Boolean]
23
+ def eligible?(order, **_options)
24
+ order.subscription_line_items.any?
25
+ end
26
+
27
+ # Certain actions create adjustments on line items. In this case, only
28
+ # line items with associated subscription_line_itms are eligible to be
29
+ # adjusted. Will only return true # if :line_item has an associated
30
+ # subscription.
31
+ #
32
+ # @param line_item [Spree::LineItem] The line item which could be adjusted
33
+ # by the promotion.
34
+ def actionable?(line_item)
35
+ line_item.subscription_line_items.present?
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,16 @@
1
+ # This service class is intented to provide callback behaviour to handle
2
+ # an installment successfully being processed
3
+ module SolidusSubscriptions
4
+ class SuccessDispatcher < Dispatcher
5
+ def dispatch
6
+ installments.each { |i| i.success!(order) }
7
+ super
8
+ end
9
+
10
+ private
11
+
12
+ def message
13
+ "Successfully processed installments: #{installments.map(&:id).join(', ')}"
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,17 @@
1
+ # This error should be raised if a user attempts to subscribe to a item which
2
+ # is not subscribable
3
+ module SolidusSubscriptions
4
+ class UnsubscribableError < StandardError
5
+ def initialize(subscribable)
6
+ @subscribable = subscribable
7
+ super
8
+ end
9
+
10
+ def to_s
11
+ <<-MSG.squish
12
+ #{@subscribable.class} with id: #{@subscribable.id} cannot be
13
+ subscribed to.
14
+ MSG
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,15 @@
1
+ module SolidusSubscriptions
2
+ class UserMismatchError < StandardError
3
+ def initialize(installments)
4
+ @installments = installments
5
+ end
6
+
7
+ def to_s
8
+ <<-MSG.squish
9
+ Installments must have the same user to be processed as a consolidated
10
+ installment. Could not process installments:
11
+ #{@installments.map(&:id).join(', ')}
12
+ MSG
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,6 @@
1
+ Deface::Override.new(
2
+ virtual_path: "spree/admin/variants/_form",
3
+ name: "solidus_subscriptions_subscribable_checkbox",
4
+ insert_after: "[data-hook='track_inventory']",
5
+ partial: "spree/admin/variants/subscribable_checkbox"
6
+ )
@@ -0,0 +1,8 @@
1
+ if !Spree::Backend::Config.respond_to?(:menu_items)
2
+ Deface::Override.new(
3
+ virtual_path: 'spree/admin/shared/_menu',
4
+ name: :add_subcriptions_admin_link,
5
+ insert_bottom: "[data-hook='admin_tabs']",
6
+ partial: 'spree/admin/shared/subscription_tab'
7
+ )
8
+ end
@@ -0,0 +1,6 @@
1
+ Deface::Override.new(
2
+ virtual_path: "spree/products/_cart_form",
3
+ name: "subscription_line_item_fields",
4
+ insert_after: "[data-hook='inside_product_cart_form']",
5
+ partial: "spree/frontend/products/subscription_line_item_fields"
6
+ )
@@ -0,0 +1,4 @@
1
+ <%= Spree.t(:no_resource, resource: resource.model_name.human.pluralize) %>
2
+ <% if can? :create, resource %>
3
+ <%= link_to Spree.t(:create_one), new_resource_url %>
4
+ <% end %>
@@ -0,0 +1,3 @@
1
+ <% if can? :admin, SolidusSubscriptions::Subscription %>
2
+ <%= tab :subscriptions, icon: 'repeat' %>
3
+ <% end %>
@@ -0,0 +1,66 @@
1
+ <%= content_tag :tr, class: cycle('odd', 'even') do %>
2
+ <%= content_tag :td, l(subscription.created_at.to_date), class: 'align-center' %>
3
+
4
+ <%= content_tag :td, class: 'align-center' do %>
5
+ <%= link_to subscription.user.email, admin_user_path(subscription.user) %>
6
+ <% end %>
7
+
8
+ <%= content_tag :td, class: 'align-center' do %>
9
+ <%= subscription.actionable_date ? l(subscription.actionable_date) : '-' %>
10
+ <% end %>
11
+
12
+ <%= content_tag :td, class: 'align-center' do %>
13
+ <%= subscription.interval.inspect %>
14
+ <% end %>
15
+
16
+ <%= content_tag :td, class: 'align-center' do %>
17
+ <%= content_tag :span, subscription.human_state_name, class: "state #{ subscription.state }" %>
18
+ <% end %>
19
+
20
+ <%= content_tag :td, class: 'align-center' do %>
21
+ <%= content_tag :span, class: "state #{ subscription.processing_state }" do %>
22
+ <%= subscription.class.human_attribute_name("processing_state.#{ subscription.processing_state }") %>
23
+ <% end %>
24
+ <% end %>
25
+
26
+ <td class="actions align-center">
27
+ <% if subscription.state_events.include?(:cancel) %>
28
+ <%=
29
+ link_to_with_icon(
30
+ :stop,
31
+ t('.cancel'),
32
+ spree.cancel_admin_subscription_path(subscription),
33
+ no_text: true,
34
+ method: :delete,
35
+ data: { confirm: t('.cancel_alert') }
36
+ )
37
+ %>
38
+ <% end %>
39
+
40
+ <% if subscription.state_events.include?(:activate) %>
41
+ <%=
42
+ link_to_with_icon(
43
+ :play,
44
+ t('.activate'),
45
+ spree.activate_admin_subscription_path(subscription),
46
+ no_text: true,
47
+ method: :post
48
+ )
49
+ %>
50
+ <% end %>
51
+
52
+ <% if subscription.active? %>
53
+ <%=
54
+ link_to_with_icon(
55
+ :'fast-forward',
56
+ t('.skip'),
57
+ spree.skip_admin_subscription_path(subscription),
58
+ no_text: true,
59
+ method: :post
60
+ )
61
+ %>
62
+ <% end %>
63
+
64
+ <%= link_to_edit(subscription, no_text: true) %>
65
+ </td>
66
+ <% end %>
@@ -0,0 +1,81 @@
1
+ <% new ||= false %>
2
+
3
+ <fieldset class="no-border-top">
4
+ <fieldset class="index no-border-bottom">
5
+ <legend><%= t('.subscription') %></legend>
6
+
7
+ <% if new %>
8
+ <div class="row">
9
+ <div class="field twelve columns">
10
+ <%= f.label :user_id, Spree.t(:user), class: "required" %>
11
+ <%= f.collection_select :user_id, Spree::User.all, :id, :email, {}, class: "select2 fullwidth" %>
12
+ </div>
13
+
14
+ </div>
15
+ <% end %>
16
+ </fieldset>
17
+
18
+ <div class="row">
19
+ <%= content_tag :div, class: "field alpha three columns" do %>
20
+ <%= f.label :actionable_date %>
21
+ <%= f.text_field :actionable_date, class: "fullwidth datepicker" %>
22
+ <% end %>
23
+
24
+ <div class='field three columns'>
25
+ <%= f.label :inverval_length %>
26
+ <%= f.number_field :interval_length, class: "fullwidth" %>
27
+ </div>
28
+
29
+ <div class='field three columns'>
30
+ <%= f.label :interval_units %>
31
+ <%
32
+ unit_values = SolidusSubscriptions::LineItem.interval_units.keys
33
+ units = unit_values.map do |unit|
34
+ [
35
+ unit,
36
+ SolidusSubscriptions::LineItem.human_attribute_name("interval_units.#{ unit }")
37
+ ]
38
+ end
39
+ %>
40
+
41
+ <div>
42
+ <% units.each_with_index do |(value, name), i| %>
43
+ <div>
44
+ <%= f.label :interval_units, for: "interval_units_#{ value }", class: 'fullwidth' do %>
45
+ <%= f.radio_button :interval_units, value, id: "interval_units_#{ value }" %>
46
+ <%= name %>
47
+ <% end %>
48
+ </div>
49
+ <% end %>
50
+ </div>
51
+ </div>
52
+
53
+ <div class="field omega three columns">
54
+ <%= f.label :end_date %>
55
+ <%= f.date_field :end_date, class: "required fullwidth" %>
56
+ </div>
57
+ </div>
58
+
59
+ <%= f.fields_for :line_items do |lf| %>
60
+ <fieldset class='no-border-bottom'>
61
+ <legend><%= t('.subscription_line_item') %></legend>
62
+
63
+ <div class="row">
64
+ <div class='field alpha two columns'>
65
+ <%= lf.label :quantity %>
66
+ <%= lf.number_field :quantity, min: 1, class: "fullwidth" %>
67
+ </div>
68
+
69
+ <div class="field columns ten omega">
70
+ <%= lf.label :subscribable_id %>
71
+ <%= lf.collection_select :subscribable_id, Spree::Variant.where(subscribable: true), :id, :pretty_name, {}, { class: "fullwidth select2" } %>
72
+ </div>
73
+ </div>
74
+
75
+ </fieldset>
76
+ <% end %>
77
+
78
+ <div class="filter-actions" data-hook='buttons'>
79
+ <%= f.button type: :submit %>
80
+ </div>
81
+ </fieldset>
@@ -0,0 +1,81 @@
1
+ <% new ||= false %>
2
+
3
+ <fieldset class="no-border-top">
4
+ <fieldset class="index no-border-bottom">
5
+ <legend><%= t('.subscription') %></legend>
6
+
7
+ <% if new %>
8
+ <div class="row">
9
+ <div class="field twelve columns">
10
+ <%= f.label :user_id, Spree.t(:user), class: "required" %>
11
+ <%= f.collection_select :user_id, Spree::User.all, :id, :email, {}, class: "select2 fullwidth" %>
12
+ </div>
13
+
14
+ </div>
15
+ <% end %>
16
+ </fieldset>
17
+
18
+ <%= f.fields_for :line_item, @line_item do |lf| %>
19
+ <div class="row">
20
+ <%= content_tag :div, class: "field alpha three columns" do %>
21
+ <%= f.label :actionable_date %>
22
+ <%= f.text_field :actionable_date, class: "fullwidth datepicker" %>
23
+ <% end %>
24
+
25
+ <div class='field three columns'>
26
+ <%= lf.label :inverval_length %>
27
+ <%= lf.number_field :interval_length, class: "fullwidth" %>
28
+ </div>
29
+
30
+ <div class='field three columns'>
31
+ <%= lf.label :interval_units %>
32
+ <%
33
+ unit_values = SolidusSubscriptions::LineItem.interval_units.keys
34
+ units = unit_values.map do |unit|
35
+ [
36
+ unit,
37
+ SolidusSubscriptions::LineItem.human_attribute_name("interval_units.#{ unit }")
38
+ ]
39
+ end
40
+ %>
41
+
42
+ <div>
43
+ <% units.each_with_index do |(value, name), i| %>
44
+ <div>
45
+ <%= lf.label :interval_units, for: "interval_units_#{ value }", class: 'fullwidth' do %>
46
+ <%= lf.radio_button :interval_units, value, id: "interval_units_#{ value }" %>
47
+ <%= name %>
48
+ <% end %>
49
+ </div>
50
+ <% end %>
51
+ </div>
52
+ </div>
53
+
54
+ <div class="field omega three columns">
55
+ <%= lf.label :end_date %>
56
+ <%= lf.date_field :end_date, class: "required", class: "fullwidth" %>
57
+ </div>
58
+ </div>
59
+
60
+ <fieldset class='no-border-bottom'>
61
+ <legend><%= t('.subscription_line_item') %></legend>
62
+
63
+ <div class="row">
64
+ <div class='field alpha two columns'>
65
+ <%= lf.label :quantity %>
66
+ <%= lf.number_field :quantity, min: 1, class: "fullwidth" %>
67
+ </div>
68
+
69
+ <div class="field columns ten omega">
70
+ <%= lf.label :subscribable_id %>
71
+ <%= lf.collection_select :subscribable_id, Spree::Variant.where(subscribable: true), :id, :pretty_name, {}, { class: "fullwidth select2" } %>
72
+ </div>
73
+ </div>
74
+
75
+ </fieldset>
76
+ <% end %>
77
+
78
+ <div class="filter-actions" data-hook='buttons'>
79
+ <%= f.button type: :submit %>
80
+ </div>
81
+ </fieldset>