solidus_subscriptions-alpha 0.0.4 → 0.0.5
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.
- checksums.yaml +4 -4
- data/LICENSE +26 -26
- data/README.md +130 -130
- data/Rakefile +28 -28
- data/app/assets/javascripts/spree/backend/solidus_subscriptions.js +1 -1
- data/app/assets/javascripts/spree/frontend/solidus_subscriptions.js +1 -1
- data/app/assets/stylesheets/spree/backend/solidus_subscriptions.css +4 -4
- data/app/assets/stylesheets/spree/frontend/solidus_subscriptions.css +4 -4
- data/app/controllers/solidus_subscriptions/api/v1/line_items_controller.rb +35 -35
- data/app/controllers/solidus_subscriptions/api/v1/subscriptions_controller.rb +44 -44
- data/app/controllers/spree/admin/subscriptions_controller.rb +66 -66
- data/app/decorators/spree/controllers/api/line_items/create_subscription_line_items.rb +28 -28
- data/app/decorators/spree/controllers/orders/create_subscription_line_items.rb +33 -33
- data/app/decorators/spree/line_items/subscription_line_items_association.rb +22 -22
- data/app/decorators/spree/orders/after_create.rb +15 -15
- data/app/decorators/spree/orders/finalize_creates_subscriptions.rb +19 -19
- data/app/decorators/spree/orders/subscription_line_items_association.rb +15 -15
- data/app/decorators/spree/products/subscribable_decorator.rb +11 -11
- data/app/decorators/spree/users/have_many_subscriptions.rb +18 -18
- data/app/decorators/spree/variant_pretty_name.rb +13 -13
- data/app/jobs/solidus_subscriptions/process_installments_job.rb +22 -22
- data/app/models/solidus_subscriptions/checkout.rb +141 -141
- data/app/models/solidus_subscriptions/dispatcher.rb +32 -32
- data/app/models/solidus_subscriptions/failure_dispatcher.rb +19 -19
- data/app/models/solidus_subscriptions/installment.rb +126 -126
- data/app/models/solidus_subscriptions/installment_detail.rb +23 -23
- data/app/models/solidus_subscriptions/interval.rb +24 -24
- data/app/models/solidus_subscriptions/line_item.rb +98 -98
- data/app/models/solidus_subscriptions/line_item_builder.rb +44 -44
- data/app/models/solidus_subscriptions/order_builder.rb +40 -40
- data/app/models/solidus_subscriptions/out_of_stock_dispatcher.rb +19 -19
- data/app/models/solidus_subscriptions/payment_failed_dispatcher.rb +23 -23
- data/app/models/solidus_subscriptions/subscription.rb +217 -217
- data/app/models/solidus_subscriptions/subscription_generator.rb +60 -60
- data/app/models/solidus_subscriptions/subscription_line_item_builder.rb +21 -21
- data/app/models/solidus_subscriptions/subscription_order_promotion_rule.rb +25 -25
- data/app/models/solidus_subscriptions/subscription_promotion_rule.rb +38 -38
- data/app/models/solidus_subscriptions/success_dispatcher.rb +16 -16
- data/app/models/solidus_subscriptions/unsubscribable_error.rb +17 -17
- data/app/models/solidus_subscriptions/user_mismatch_error.rb +15 -15
- data/app/overrides/views/admin_subscribable_checkbox.rb +13 -13
- data/app/overrides/views/admin_subscriptions_menu_link.rb +8 -8
- data/app/overrides/views/subscription_line_item_fields.rb +6 -6
- data/app/views/spree/admin/products/_subscribable_checkbox.html.erb +8 -8
- data/app/views/spree/admin/promotions/rules/_subscription_order_promotion_rule.html.erb +0 -0
- data/app/views/spree/admin/promotions/rules/_subscription_promotion_rule.html.erb +0 -0
- data/app/views/spree/admin/shared/_no_objects_found.html.erb +4 -4
- data/app/views/spree/admin/shared/_subscription_tab.html.erb +3 -3
- data/app/views/spree/admin/solidus_subscriptions/subscriptions/_subscription.html.erb +66 -66
- data/app/views/spree/admin/subscriptions/_form.html.erb +81 -81
- data/app/views/spree/admin/subscriptions/_legacy_form.html.erb +81 -81
- data/app/views/spree/admin/subscriptions/_legacy_sidebar.html.erb +28 -28
- data/app/views/spree/admin/subscriptions/edit.html.erb +21 -21
- data/app/views/spree/admin/subscriptions/index.html.erb +119 -119
- data/app/views/spree/admin/subscriptions/new.html.erb +9 -9
- data/app/views/spree/admin/variants/_subscribable_checkbox.html.erb +6 -6
- data/app/views/spree/frontend/products/_subscription_line_item_fields.html.erb +30 -30
- data/config/locales/en.yml +91 -91
- data/config/routes.rb +25 -25
- data/db/migrate/20160825164850_create_solidus_subscriptions_subscriptions.rb +11 -11
- data/db/migrate/20160825173548_create_solidus_subscriptions_line_items.rb +17 -17
- data/db/migrate/20160825202248_create_solidus_subscriptions_installments.rb +23 -23
- data/db/migrate/20160825211202_create_solidus_subscriptions_installment_details.rb +22 -22
- data/db/migrate/20160825214240_add_subscribable_to_spree_variants.rb +5 -5
- data/db/migrate/20160829201653_change_subscription_line_items_installments_to_max_installments.rb +5 -5
- data/db/migrate/20160902220242_remove_state_from_solidus_susbscriptions_installment_details.rb +5 -5
- data/db/migrate/20160902220604_add_successful_to_solidus_subscriptions_installment_details.rb +5 -5
- data/db/migrate/20160902221218_add_message_to_solidus_subscriptions_installment_details.rb +5 -5
- data/db/migrate/20160922164101_add_interval_length_and_units_to_subscription_line_items.rb +8 -8
- data/db/migrate/20161006191003_add_skip_count_to_solidus_subscriptions_subscriptions.rb +5 -5
- data/db/migrate/20161006191127_add_successive_skip_count_to_solidus_subscriptions_subscriptions.rb +5 -5
- data/db/migrate/20161014212649_allow_spree_line_item_id_to_be_null.rb +5 -5
- data/db/migrate/20161017155749_add_order_id_to_solidus_subscriptions_installment_details.rb +6 -6
- data/db/migrate/20161017175509_remove_order_id_from_solidus_subscriptions_installments.rb +6 -6
- data/db/migrate/20161017201944_add_subscription_order_to_spree_orders.rb +5 -5
- data/db/migrate/20161221155142_add_store_to_solidus_subscriptions_subscriptions.rb +6 -6
- data/db/migrate/20161223152905_add_address_id_to_solidus_subscriptions_subscriptions.rb +7 -7
- data/db/migrate/20170106224713_change_line_item_max_installments_to_end_date.rb +6 -6
- data/db/migrate/20170111224458_change_subscription_actionable_date_to_datetime.rb +5 -5
- data/db/migrate/20170111232801_change_inteval_actionable_date_to_datetime.rb +5 -5
- data/db/migrate/20170112012407_add_config_options_to_subscriptions.rb +7 -7
- data/lib/generators/solidus_subscriptions/install/install_generator.rb +30 -30
- data/lib/solidus_subscriptions.rb +6 -6
- data/lib/solidus_subscriptions/ability.rb +19 -19
- data/lib/solidus_subscriptions/config.rb +97 -97
- data/lib/solidus_subscriptions/engine.rb +56 -56
- data/lib/solidus_subscriptions/permitted_attributes.rb +36 -36
- data/lib/solidus_subscriptions/processor.rb +108 -108
- data/lib/solidus_subscriptions/testing_support/factories.rb +5 -5
- data/lib/solidus_subscriptions/testing_support/factories/installment_detail_factory.rb +7 -7
- data/lib/solidus_subscriptions/testing_support/factories/installment_factory.rb +21 -21
- data/lib/solidus_subscriptions/testing_support/factories/line_item_factory.rb +18 -18
- data/lib/solidus_subscriptions/testing_support/factories/spree/line_item_factory.rb +17 -17
- data/lib/solidus_subscriptions/testing_support/factories/spree/order_factory.rb +18 -18
- data/lib/solidus_subscriptions/testing_support/factories/spree_modification_factory.rb +8 -8
- data/lib/solidus_subscriptions/testing_support/factories/subscription_factory.rb +43 -43
- data/lib/solidus_subscriptions/version.rb +3 -3
- data/lib/solidus_subscriptions/version.rb~ +3 -0
- data/lib/tasks/process_subscriptions.rake +6 -6
- metadata +4 -3
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
# A handler for behaviour that should happen after installments are marked as
|
|
2
|
-
# failures
|
|
3
|
-
module SolidusSubscriptions
|
|
4
|
-
class FailureDispatcher < Dispatcher
|
|
5
|
-
def dispatch
|
|
6
|
-
order.touch :completed_at
|
|
7
|
-
order.cancel!
|
|
8
|
-
installments.each { |i| i.failed!(order) }
|
|
9
|
-
super
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
def message
|
|
13
|
-
"
|
|
14
|
-
Something went wrong processing installments: #{installments.map(&:id).join(', ')}.
|
|
15
|
-
They have been marked for reprocessing.
|
|
16
|
-
"
|
|
17
|
-
end
|
|
18
|
-
end
|
|
19
|
-
end
|
|
1
|
+
# A handler for behaviour that should happen after installments are marked as
|
|
2
|
+
# failures
|
|
3
|
+
module SolidusSubscriptions
|
|
4
|
+
class FailureDispatcher < Dispatcher
|
|
5
|
+
def dispatch
|
|
6
|
+
order.touch :completed_at
|
|
7
|
+
order.cancel!
|
|
8
|
+
installments.each { |i| i.failed!(order) }
|
|
9
|
+
super
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def message
|
|
13
|
+
"
|
|
14
|
+
Something went wrong processing installments: #{installments.map(&:id).join(', ')}.
|
|
15
|
+
They have been marked for reprocessing.
|
|
16
|
+
"
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -1,126 +1,126 @@
|
|
|
1
|
-
# This class represents a single iteration of a subscription. It is fulfulled
|
|
2
|
-
# by a conmpleted order and maintains an association which tracks all attempts
|
|
3
|
-
# successful or othewise at fulfulling this installment
|
|
4
|
-
module SolidusSubscriptions
|
|
5
|
-
class Installment < ActiveRecord::Base
|
|
6
|
-
has_many :details, class_name: 'SolidusSubscriptions::InstallmentDetail'
|
|
7
|
-
belongs_to(
|
|
8
|
-
:subscription,
|
|
9
|
-
class_name: 'SolidusSubscriptions::Subscription',
|
|
10
|
-
inverse_of: :installments
|
|
11
|
-
)
|
|
12
|
-
|
|
13
|
-
validates :subscription, presence: true
|
|
14
|
-
|
|
15
|
-
scope :fulfilled, (lambda do
|
|
16
|
-
joins(:details).where(InstallmentDetail.table_name => { success: true }).distinct
|
|
17
|
-
end)
|
|
18
|
-
|
|
19
|
-
scope :unfulfilled, (lambda do
|
|
20
|
-
fulfilled_ids = fulfilled.pluck(:id)
|
|
21
|
-
where.not(id: fulfilled_ids).distinct
|
|
22
|
-
end)
|
|
23
|
-
|
|
24
|
-
scope :actionable, (lambda do
|
|
25
|
-
unfulfilled.where("#{table_name}.actionable_date <= ?", Time.zone.now)
|
|
26
|
-
end)
|
|
27
|
-
|
|
28
|
-
# Get the builder for the subscription_line_item. This will be an
|
|
29
|
-
# object that can generate the appropriate line item for the subscribable
|
|
30
|
-
# object
|
|
31
|
-
#
|
|
32
|
-
# @return [SolidusSubscriptions::LineItemBuilder]
|
|
33
|
-
def line_item_builder
|
|
34
|
-
subscription.line_item_builder
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
# Mark this installment as out of stock.
|
|
38
|
-
#
|
|
39
|
-
# @return [SolidusSubscriptions::InstallmentDetail] The record of the failed
|
|
40
|
-
# processing attempt
|
|
41
|
-
def out_of_stock
|
|
42
|
-
advance_actionable_date!
|
|
43
|
-
|
|
44
|
-
details.create!(
|
|
45
|
-
success: false,
|
|
46
|
-
message: I18n.t('solidus_subscriptions.installment_details.out_of_stock')
|
|
47
|
-
)
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
# Mark this installment as a success
|
|
51
|
-
#
|
|
52
|
-
# @param order [Spree::Order] The order generated for this processing
|
|
53
|
-
# attempt
|
|
54
|
-
#
|
|
55
|
-
# @return [SolidusSubscriptions::InstallmentDetail] The record of the
|
|
56
|
-
# successful processing attempt
|
|
57
|
-
def success!(order)
|
|
58
|
-
update!(actionable_date: nil)
|
|
59
|
-
|
|
60
|
-
details.create!(
|
|
61
|
-
success: true,
|
|
62
|
-
order: order,
|
|
63
|
-
message: I18n.t('solidus_subscriptions.installment_details.success')
|
|
64
|
-
)
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
# Mark this installment as a failure
|
|
68
|
-
#
|
|
69
|
-
# @param order [Spree::Order] The order generated for this processing
|
|
70
|
-
# attempt
|
|
71
|
-
#
|
|
72
|
-
# @return [SolidusSubscriptions::InstallmentDetail] The record of the
|
|
73
|
-
# failed processing attempt
|
|
74
|
-
def failed!(order)
|
|
75
|
-
advance_actionable_date!
|
|
76
|
-
|
|
77
|
-
details.create!(
|
|
78
|
-
success: false,
|
|
79
|
-
order: order,
|
|
80
|
-
message: I18n.t('solidus_subscriptions.installment_details.failed')
|
|
81
|
-
)
|
|
82
|
-
end
|
|
83
|
-
|
|
84
|
-
# Does this installment still need to be fulfilled by a completed order
|
|
85
|
-
#
|
|
86
|
-
# @return [Boolean]
|
|
87
|
-
def unfulfilled?
|
|
88
|
-
!fulfilled?
|
|
89
|
-
end
|
|
90
|
-
|
|
91
|
-
# Had this installment been fulfilled by a completed order
|
|
92
|
-
#
|
|
93
|
-
# @return [Boolean]
|
|
94
|
-
def fulfilled?
|
|
95
|
-
details.where(success: true).exists?
|
|
96
|
-
end
|
|
97
|
-
|
|
98
|
-
# Mark this installment as having a failed payment
|
|
99
|
-
#
|
|
100
|
-
# @param order [Spree::Order] The order generated for this processing
|
|
101
|
-
# attempt
|
|
102
|
-
#
|
|
103
|
-
# @return [SolidusSubscriptions::InstallmentDetail] The record of the
|
|
104
|
-
# failed processing attempt
|
|
105
|
-
def payment_failed!(order)
|
|
106
|
-
advance_actionable_date!
|
|
107
|
-
|
|
108
|
-
details.create!(
|
|
109
|
-
success: false,
|
|
110
|
-
order: order,
|
|
111
|
-
message: I18n.t('solidus_subscriptions.installment_details.payment_failed')
|
|
112
|
-
)
|
|
113
|
-
end
|
|
114
|
-
|
|
115
|
-
private
|
|
116
|
-
|
|
117
|
-
def advance_actionable_date!
|
|
118
|
-
update!(actionable_date: next_actionable_date)
|
|
119
|
-
end
|
|
120
|
-
|
|
121
|
-
def next_actionable_date
|
|
122
|
-
return if Config.reprocessing_interval.nil?
|
|
123
|
-
(DateTime.current + Config.reprocessing_interval).beginning_of_minute
|
|
124
|
-
end
|
|
125
|
-
end
|
|
126
|
-
end
|
|
1
|
+
# This class represents a single iteration of a subscription. It is fulfulled
|
|
2
|
+
# by a conmpleted order and maintains an association which tracks all attempts
|
|
3
|
+
# successful or othewise at fulfulling this installment
|
|
4
|
+
module SolidusSubscriptions
|
|
5
|
+
class Installment < ActiveRecord::Base
|
|
6
|
+
has_many :details, class_name: 'SolidusSubscriptions::InstallmentDetail'
|
|
7
|
+
belongs_to(
|
|
8
|
+
:subscription,
|
|
9
|
+
class_name: 'SolidusSubscriptions::Subscription',
|
|
10
|
+
inverse_of: :installments
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
validates :subscription, presence: true
|
|
14
|
+
|
|
15
|
+
scope :fulfilled, (lambda do
|
|
16
|
+
joins(:details).where(InstallmentDetail.table_name => { success: true }).distinct
|
|
17
|
+
end)
|
|
18
|
+
|
|
19
|
+
scope :unfulfilled, (lambda do
|
|
20
|
+
fulfilled_ids = fulfilled.pluck(:id)
|
|
21
|
+
where.not(id: fulfilled_ids).distinct
|
|
22
|
+
end)
|
|
23
|
+
|
|
24
|
+
scope :actionable, (lambda do
|
|
25
|
+
unfulfilled.where("#{table_name}.actionable_date <= ?", Time.zone.now)
|
|
26
|
+
end)
|
|
27
|
+
|
|
28
|
+
# Get the builder for the subscription_line_item. This will be an
|
|
29
|
+
# object that can generate the appropriate line item for the subscribable
|
|
30
|
+
# object
|
|
31
|
+
#
|
|
32
|
+
# @return [SolidusSubscriptions::LineItemBuilder]
|
|
33
|
+
def line_item_builder
|
|
34
|
+
subscription.line_item_builder
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Mark this installment as out of stock.
|
|
38
|
+
#
|
|
39
|
+
# @return [SolidusSubscriptions::InstallmentDetail] The record of the failed
|
|
40
|
+
# processing attempt
|
|
41
|
+
def out_of_stock
|
|
42
|
+
advance_actionable_date!
|
|
43
|
+
|
|
44
|
+
details.create!(
|
|
45
|
+
success: false,
|
|
46
|
+
message: I18n.t('solidus_subscriptions.installment_details.out_of_stock')
|
|
47
|
+
)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Mark this installment as a success
|
|
51
|
+
#
|
|
52
|
+
# @param order [Spree::Order] The order generated for this processing
|
|
53
|
+
# attempt
|
|
54
|
+
#
|
|
55
|
+
# @return [SolidusSubscriptions::InstallmentDetail] The record of the
|
|
56
|
+
# successful processing attempt
|
|
57
|
+
def success!(order)
|
|
58
|
+
update!(actionable_date: nil)
|
|
59
|
+
|
|
60
|
+
details.create!(
|
|
61
|
+
success: true,
|
|
62
|
+
order: order,
|
|
63
|
+
message: I18n.t('solidus_subscriptions.installment_details.success')
|
|
64
|
+
)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Mark this installment as a failure
|
|
68
|
+
#
|
|
69
|
+
# @param order [Spree::Order] The order generated for this processing
|
|
70
|
+
# attempt
|
|
71
|
+
#
|
|
72
|
+
# @return [SolidusSubscriptions::InstallmentDetail] The record of the
|
|
73
|
+
# failed processing attempt
|
|
74
|
+
def failed!(order)
|
|
75
|
+
advance_actionable_date!
|
|
76
|
+
|
|
77
|
+
details.create!(
|
|
78
|
+
success: false,
|
|
79
|
+
order: order,
|
|
80
|
+
message: I18n.t('solidus_subscriptions.installment_details.failed')
|
|
81
|
+
)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Does this installment still need to be fulfilled by a completed order
|
|
85
|
+
#
|
|
86
|
+
# @return [Boolean]
|
|
87
|
+
def unfulfilled?
|
|
88
|
+
!fulfilled?
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Had this installment been fulfilled by a completed order
|
|
92
|
+
#
|
|
93
|
+
# @return [Boolean]
|
|
94
|
+
def fulfilled?
|
|
95
|
+
details.where(success: true).exists?
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Mark this installment as having a failed payment
|
|
99
|
+
#
|
|
100
|
+
# @param order [Spree::Order] The order generated for this processing
|
|
101
|
+
# attempt
|
|
102
|
+
#
|
|
103
|
+
# @return [SolidusSubscriptions::InstallmentDetail] The record of the
|
|
104
|
+
# failed processing attempt
|
|
105
|
+
def payment_failed!(order)
|
|
106
|
+
advance_actionable_date!
|
|
107
|
+
|
|
108
|
+
details.create!(
|
|
109
|
+
success: false,
|
|
110
|
+
order: order,
|
|
111
|
+
message: I18n.t('solidus_subscriptions.installment_details.payment_failed')
|
|
112
|
+
)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
private
|
|
116
|
+
|
|
117
|
+
def advance_actionable_date!
|
|
118
|
+
update!(actionable_date: next_actionable_date)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def next_actionable_date
|
|
122
|
+
return if Config.reprocessing_interval.nil?
|
|
123
|
+
(DateTime.current + Config.reprocessing_interval).beginning_of_minute
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
@@ -1,23 +1,23 @@
|
|
|
1
|
-
# This class represents a single attempt to fulfill an installment. It will
|
|
2
|
-
# indicate the result of that attept.
|
|
3
|
-
module SolidusSubscriptions
|
|
4
|
-
class InstallmentDetail < ActiveRecord::Base
|
|
5
|
-
belongs_to(
|
|
6
|
-
:installment,
|
|
7
|
-
class_name: 'SolidusSubscriptions::Installment',
|
|
8
|
-
inverse_of: :details
|
|
9
|
-
)
|
|
10
|
-
|
|
11
|
-
belongs_to(:order, class_name: 'Spree::Order')
|
|
12
|
-
|
|
13
|
-
validates :installment, presence: true
|
|
14
|
-
alias_attribute :successful, :success
|
|
15
|
-
|
|
16
|
-
# Was the attempt at fulfilling this installment a failure?
|
|
17
|
-
#
|
|
18
|
-
# @return [Boolean]
|
|
19
|
-
def failed?
|
|
20
|
-
!success
|
|
21
|
-
end
|
|
22
|
-
end
|
|
23
|
-
end
|
|
1
|
+
# This class represents a single attempt to fulfill an installment. It will
|
|
2
|
+
# indicate the result of that attept.
|
|
3
|
+
module SolidusSubscriptions
|
|
4
|
+
class InstallmentDetail < ActiveRecord::Base
|
|
5
|
+
belongs_to(
|
|
6
|
+
:installment,
|
|
7
|
+
class_name: 'SolidusSubscriptions::Installment',
|
|
8
|
+
inverse_of: :details
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
belongs_to(:order, class_name: 'Spree::Order')
|
|
12
|
+
|
|
13
|
+
validates :installment, presence: true
|
|
14
|
+
alias_attribute :successful, :success
|
|
15
|
+
|
|
16
|
+
# Was the attempt at fulfilling this installment a failure?
|
|
17
|
+
#
|
|
18
|
+
# @return [Boolean]
|
|
19
|
+
def failed?
|
|
20
|
+
!success
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -1,24 +1,24 @@
|
|
|
1
|
-
# This module is intended to be included into any active record
|
|
2
|
-
# modle which needs to be aware of how intervals and stored and
|
|
3
|
-
# calculated in the db.
|
|
4
|
-
#
|
|
5
|
-
# Base models must have the following fields: interval_length (integer) and interval_units (integer)
|
|
6
|
-
module SolidusSubscriptions
|
|
7
|
-
module Interval
|
|
8
|
-
def self.included(base)
|
|
9
|
-
base.enum interval_units: {
|
|
10
|
-
day: 0,
|
|
11
|
-
week: 1,
|
|
12
|
-
month: 2,
|
|
13
|
-
year: 3
|
|
14
|
-
}
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
# Calculates the number of seconds in the interval.
|
|
18
|
-
#
|
|
19
|
-
# @return [Integer] The number of seconds.
|
|
20
|
-
def interval
|
|
21
|
-
ActiveSupport::Duration.new(interval_length, { interval_units.pluralize.to_sym => interval_length })
|
|
22
|
-
end
|
|
23
|
-
end
|
|
24
|
-
end
|
|
1
|
+
# This module is intended to be included into any active record
|
|
2
|
+
# modle which needs to be aware of how intervals and stored and
|
|
3
|
+
# calculated in the db.
|
|
4
|
+
#
|
|
5
|
+
# Base models must have the following fields: interval_length (integer) and interval_units (integer)
|
|
6
|
+
module SolidusSubscriptions
|
|
7
|
+
module Interval
|
|
8
|
+
def self.included(base)
|
|
9
|
+
base.enum interval_units: {
|
|
10
|
+
day: 0,
|
|
11
|
+
week: 1,
|
|
12
|
+
month: 2,
|
|
13
|
+
year: 3
|
|
14
|
+
}
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Calculates the number of seconds in the interval.
|
|
18
|
+
#
|
|
19
|
+
# @return [Integer] The number of seconds.
|
|
20
|
+
def interval
|
|
21
|
+
ActiveSupport::Duration.new(interval_length, { interval_units.pluralize.to_sym => interval_length })
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -1,98 +1,98 @@
|
|
|
1
|
-
# The LineItem class is responsible for associating Line items to subscriptions. # It tracks the following values:
|
|
2
|
-
#
|
|
3
|
-
# [Spree::LineItem] :spree_line_item The spree object which created this instance
|
|
4
|
-
#
|
|
5
|
-
# [SolidusSubscription::Subscription] :subscription The object responsible for
|
|
6
|
-
# grouping all information needed to create new subscription orders together
|
|
7
|
-
#
|
|
8
|
-
# [Integer] :subscribable_id The id of the object to be added to new subscription
|
|
9
|
-
# orders when they are placed
|
|
10
|
-
#
|
|
11
|
-
# [Integer] :quantity How many units of the subscribable should be included in
|
|
12
|
-
# future orders
|
|
13
|
-
#
|
|
14
|
-
# [Integer] :interval How often subscription orders should be placed
|
|
15
|
-
#
|
|
16
|
-
# [Integer] :installments How many subscription orders should be placed
|
|
17
|
-
module SolidusSubscriptions
|
|
18
|
-
class LineItem < ActiveRecord::Base
|
|
19
|
-
include Interval
|
|
20
|
-
|
|
21
|
-
belongs_to :spree_line_item, class_name: 'Spree::LineItem', inverse_of: :subscription_line_items
|
|
22
|
-
has_one :order, through: :spree_line_item, class_name: 'Spree::Order'
|
|
23
|
-
belongs_to(
|
|
24
|
-
:subscription,
|
|
25
|
-
class_name: 'SolidusSubscriptions::Subscription',
|
|
26
|
-
inverse_of: :line_items
|
|
27
|
-
)
|
|
28
|
-
|
|
29
|
-
validates :subscribable_id, presence: :true
|
|
30
|
-
validates :quantity, numericality: { greater_than: 0 }
|
|
31
|
-
validates :interval_length, numericality: { greater_than: 0 }, unless: -> { subscription }
|
|
32
|
-
|
|
33
|
-
before_update :update_actionable_date_if_interval_changed
|
|
34
|
-
|
|
35
|
-
def next_actionable_date
|
|
36
|
-
dummy_subscription.next_actionable_date
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
def as_json(**options)
|
|
40
|
-
options[:methods] ||= [:dummy_line_item, :next_actionable_date]
|
|
41
|
-
super(options)
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
# Get a placeholder line item for calculating the values of future
|
|
45
|
-
# subscription orders. It is frozen and cannot be saved
|
|
46
|
-
def dummy_line_item
|
|
47
|
-
li = LineItemBuilder.new([self]).spree_line_items.first
|
|
48
|
-
return unless li
|
|
49
|
-
|
|
50
|
-
li.order = dummy_order
|
|
51
|
-
li.validate
|
|
52
|
-
li.freeze
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
def interval
|
|
56
|
-
subscription.try!(:interval) || super
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
private
|
|
60
|
-
|
|
61
|
-
# Get a placeholder order for calculating the values of future
|
|
62
|
-
# subscription orders. It is a frozen duplicate of the current order and
|
|
63
|
-
# cannot be saved
|
|
64
|
-
def dummy_order
|
|
65
|
-
order = spree_line_item ? spree_line_item.order.dup : Spree::Order.create
|
|
66
|
-
order.ship_address = subscription.shipping_address || subscription.user.ship_address if subscription
|
|
67
|
-
|
|
68
|
-
order.freeze
|
|
69
|
-
end
|
|
70
|
-
|
|
71
|
-
# A place holder for calculating dynamic values needed to display in the cart
|
|
72
|
-
# it is frozen and cannot be saved
|
|
73
|
-
def dummy_subscription
|
|
74
|
-
Subscription.new(line_items: [dup], interval_length: interval_length, interval_units: interval_units).freeze
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
def update_actionable_date_if_interval_changed
|
|
78
|
-
if persisted? && subscription && (interval_length_changed? || interval_units_changed?)
|
|
79
|
-
base_date = if subscription.installments.any?
|
|
80
|
-
subscription.installments.last.created_at
|
|
81
|
-
else
|
|
82
|
-
subscription.created_at
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
new_date = interval.since(base_date)
|
|
86
|
-
|
|
87
|
-
if new_date < Time.zone.now
|
|
88
|
-
# if the chosen base time plus the new interval is in the past, set
|
|
89
|
-
# the actionable_date to be now to avoid confusion and possible
|
|
90
|
-
# mis-processing.
|
|
91
|
-
new_date = Time.zone.now
|
|
92
|
-
end
|
|
93
|
-
|
|
94
|
-
subscription.actionable_date = new_date
|
|
95
|
-
end
|
|
96
|
-
end
|
|
97
|
-
end
|
|
98
|
-
end
|
|
1
|
+
# The LineItem class is responsible for associating Line items to subscriptions. # It tracks the following values:
|
|
2
|
+
#
|
|
3
|
+
# [Spree::LineItem] :spree_line_item The spree object which created this instance
|
|
4
|
+
#
|
|
5
|
+
# [SolidusSubscription::Subscription] :subscription The object responsible for
|
|
6
|
+
# grouping all information needed to create new subscription orders together
|
|
7
|
+
#
|
|
8
|
+
# [Integer] :subscribable_id The id of the object to be added to new subscription
|
|
9
|
+
# orders when they are placed
|
|
10
|
+
#
|
|
11
|
+
# [Integer] :quantity How many units of the subscribable should be included in
|
|
12
|
+
# future orders
|
|
13
|
+
#
|
|
14
|
+
# [Integer] :interval How often subscription orders should be placed
|
|
15
|
+
#
|
|
16
|
+
# [Integer] :installments How many subscription orders should be placed
|
|
17
|
+
module SolidusSubscriptions
|
|
18
|
+
class LineItem < ActiveRecord::Base
|
|
19
|
+
include Interval
|
|
20
|
+
|
|
21
|
+
belongs_to :spree_line_item, class_name: 'Spree::LineItem', inverse_of: :subscription_line_items
|
|
22
|
+
has_one :order, through: :spree_line_item, class_name: 'Spree::Order'
|
|
23
|
+
belongs_to(
|
|
24
|
+
:subscription,
|
|
25
|
+
class_name: 'SolidusSubscriptions::Subscription',
|
|
26
|
+
inverse_of: :line_items
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
validates :subscribable_id, presence: :true
|
|
30
|
+
validates :quantity, numericality: { greater_than: 0 }
|
|
31
|
+
validates :interval_length, numericality: { greater_than: 0 }, unless: -> { subscription }
|
|
32
|
+
|
|
33
|
+
before_update :update_actionable_date_if_interval_changed
|
|
34
|
+
|
|
35
|
+
def next_actionable_date
|
|
36
|
+
dummy_subscription.next_actionable_date
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def as_json(**options)
|
|
40
|
+
options[:methods] ||= [:dummy_line_item, :next_actionable_date]
|
|
41
|
+
super(options)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Get a placeholder line item for calculating the values of future
|
|
45
|
+
# subscription orders. It is frozen and cannot be saved
|
|
46
|
+
def dummy_line_item
|
|
47
|
+
li = LineItemBuilder.new([self]).spree_line_items.first
|
|
48
|
+
return unless li
|
|
49
|
+
|
|
50
|
+
li.order = dummy_order
|
|
51
|
+
li.validate
|
|
52
|
+
li.freeze
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def interval
|
|
56
|
+
subscription.try!(:interval) || super
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
private
|
|
60
|
+
|
|
61
|
+
# Get a placeholder order for calculating the values of future
|
|
62
|
+
# subscription orders. It is a frozen duplicate of the current order and
|
|
63
|
+
# cannot be saved
|
|
64
|
+
def dummy_order
|
|
65
|
+
order = spree_line_item ? spree_line_item.order.dup : Spree::Order.create
|
|
66
|
+
order.ship_address = subscription.shipping_address || subscription.user.ship_address if subscription
|
|
67
|
+
|
|
68
|
+
order.freeze
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# A place holder for calculating dynamic values needed to display in the cart
|
|
72
|
+
# it is frozen and cannot be saved
|
|
73
|
+
def dummy_subscription
|
|
74
|
+
Subscription.new(line_items: [dup], interval_length: interval_length, interval_units: interval_units).freeze
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def update_actionable_date_if_interval_changed
|
|
78
|
+
if persisted? && subscription && (interval_length_changed? || interval_units_changed?)
|
|
79
|
+
base_date = if subscription.installments.any?
|
|
80
|
+
subscription.installments.last.created_at
|
|
81
|
+
else
|
|
82
|
+
subscription.created_at
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
new_date = interval.since(base_date)
|
|
86
|
+
|
|
87
|
+
if new_date < Time.zone.now
|
|
88
|
+
# if the chosen base time plus the new interval is in the past, set
|
|
89
|
+
# the actionable_date to be now to avoid confusion and possible
|
|
90
|
+
# mis-processing.
|
|
91
|
+
new_date = Time.zone.now
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
subscription.actionable_date = new_date
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|