solidus_subscriptions-alpha 0.0.1

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 (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>