summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/controllers/spree/admin/subscription_events_controller.rb35
-rw-r--r--app/models/solidus_subscriptions/line_item.rb35
-rw-r--r--app/models/solidus_subscriptions/subscription.rb30
-rw-r--r--app/models/solidus_subscriptions/subscription_event.rb22
-rw-r--r--app/views/spree/admin/shared/_subscription_tabs.html.erb3
-rw-r--r--app/views/spree/admin/subscription_events/_state_pill.html.erb8
-rw-r--r--app/views/spree/admin/subscription_events/index.html.erb42
-rw-r--r--config/locales/en.yml4
-rw-r--r--config/routes.rb1
-rw-r--r--db/migrate/20200730101242_create_solidus_subscriptions_subscription_events.rb17
-rw-r--r--lib/solidus_subscriptions/testing_support/factories/subscription_event_factory.rb6
-rw-r--r--spec/models/solidus_subscriptions/line_item_spec.rb42
-rw-r--r--spec/models/solidus_subscriptions/subscription_event_spec.rb15
-rw-r--r--spec/models/solidus_subscriptions/subscription_spec.rb95
14 files changed, 355 insertions, 0 deletions
diff --git a/app/controllers/spree/admin/subscription_events_controller.rb b/app/controllers/spree/admin/subscription_events_controller.rb
new file mode 100644
index 0000000..ad86887
--- /dev/null
+++ b/app/controllers/spree/admin/subscription_events_controller.rb
@@ -0,0 +1,35 @@
+module Spree
+ module Admin
+ class SubscriptionEventsController < ResourceController
+ belongs_to 'subscription', model_class: SolidusSubscriptions::Subscription
+
+ skip_before_action :load_resource, only: :index
+
+ def index
+ @search = collection.ransack((params[:q] || {}).reverse_merge(s: 'created_at desc'))
+
+ @subscription_events = @search.result(distinct: true).
+ page(params[:page]).
+ per(params[:per_page] || 20)
+ end
+
+ private
+
+ def model_class
+ ::SolidusSubscriptions::SubscriptionEvent
+ end
+
+ def find_resource
+ parent.events.find(params[:id])
+ end
+
+ def build_resource
+ parent.events.build
+ end
+
+ def collection
+ parent.events
+ end
+ end
+ end
+end
diff --git a/app/models/solidus_subscriptions/line_item.rb b/app/models/solidus_subscriptions/line_item.rb
index 9ef045b..0477877 100644
--- a/app/models/solidus_subscriptions/line_item.rb
+++ b/app/models/solidus_subscriptions/line_item.rb
@@ -36,6 +36,10 @@ module SolidusSubscriptions
validates :quantity, numericality: { greater_than: 0 }
validates :interval_length, numericality: { greater_than: 0 }, unless: -> { subscription }
+ after_create :track_creation_event
+ after_update :track_update_event
+ after_destroy :track_destroy_event
+
def as_json(**options)
options[:methods] ||= [:dummy_line_item]
super(options)
@@ -64,5 +68,36 @@ module SolidusSubscriptions
order.freeze
end
+
+ def as_json_for_event
+ as_json.with_indifferent_access.except(
+ :dummy_line_item,
+ :interval_units,
+ :interval_length,
+ :end_date,
+ :spree_line_item_id,
+ :subscription_id,
+ :created_at,
+ :updated_at,
+ )
+ end
+
+ def track_creation_event
+ return unless subscription
+
+ subscription.events.create!(event_type: 'line_item_created', details: as_json_for_event)
+ end
+
+ def track_update_event
+ return unless subscription
+
+ subscription.events.create!(event_type: 'line_item_updated', details: as_json_for_event)
+ end
+
+ def track_destroy_event
+ return unless subscription
+
+ subscription.events.create!(event_type: 'line_item_destroyed', details: as_json_for_event)
+ end
end
end
diff --git a/app/models/solidus_subscriptions/subscription.rb b/app/models/solidus_subscriptions/subscription.rb
index 33a58f4..d19d624 100644
--- a/app/models/solidus_subscriptions/subscription.rb
+++ b/app/models/solidus_subscriptions/subscription.rb
@@ -10,6 +10,7 @@ module SolidusSubscriptions
belongs_to :user, class_name: "::#{::Spree.user_class}"
has_many :line_items, class_name: 'SolidusSubscriptions::LineItem', inverse_of: :subscription
has_many :installments, class_name: 'SolidusSubscriptions::Installment'
+ has_many :events, class_name: 'SolidusSubscriptions::SubscriptionEvent'
belongs_to :store, class_name: '::Spree::Store'
belongs_to :shipping_address, class_name: '::Spree::Address', optional: true
belongs_to :billing_address, class_name: '::Spree::Address', optional: true
@@ -28,6 +29,8 @@ module SolidusSubscriptions
before_validation :set_payment_method
before_update :update_actionable_date_if_interval_changed
+ after_create :track_creation_event
+ after_update :track_update_event
# Find all subscriptions that are "actionable"; that is, ones that have an
# actionable_date in the past and are not invalid or canceled.
@@ -108,6 +111,7 @@ module SolidusSubscriptions
end
after_transition to: :active, do: :advance_actionable_date
+ after_transition do: :track_transition_event
end
# This method determines if a subscription may be canceled. Canceled
@@ -172,6 +176,9 @@ module SolidusSubscriptions
# subscription will be eligible to be processed.
def advance_actionable_date
update! actionable_date: next_actionable_date
+
+ events.create!(event_type: 'subscription_skipped')
+
actionable_date
end
@@ -259,5 +266,28 @@ module SolidusSubscriptions
self.payment_method = payment_source.payment_method
end
end
+
+ def as_json_for_event
+ as_json
+ end
+
+ def track_creation_event
+ events.create!(event_type: 'subscription_created', details: as_json_for_event)
+ end
+
+ def track_update_event
+ events.create!(event_type: 'subscription_updated', details: as_json_for_event)
+ end
+
+ def track_transition_event
+ event_type = {
+ active: 'subscription_activated',
+ canceled: 'subscription_canceled',
+ pending_cancellation: 'subscription_cancellation_requested',
+ inactive: 'subscription_deactivated',
+ }[state.to_sym]
+
+ events.create!(event_type: event_type, details: as_json_for_event)
+ end
end
end
diff --git a/app/models/solidus_subscriptions/subscription_event.rb b/app/models/solidus_subscriptions/subscription_event.rb
new file mode 100644
index 0000000..93552fd
--- /dev/null
+++ b/app/models/solidus_subscriptions/subscription_event.rb
@@ -0,0 +1,22 @@
+module SolidusSubscriptions
+ class SubscriptionEvent < ApplicationRecord
+ belongs_to :subscription, class_name: 'SolidusSubscriptions::Subscription', inverse_of: :events
+
+ after_initialize do
+ self.details ||= {}
+ end
+
+ after_create :emit_event
+
+ private
+
+ def emit_event
+ return unless defined?(::Spree::Event)
+
+ ::Spree::Event.fire(
+ "solidus_subscriptions.#{event_type}",
+ details.deep_symbolize_keys.merge(subscription: subscription),
+ )
+ end
+ end
+end
diff --git a/app/views/spree/admin/shared/_subscription_tabs.html.erb b/app/views/spree/admin/shared/_subscription_tabs.html.erb
index 7c00486..f1d8e0f 100644
--- a/app/views/spree/admin/shared/_subscription_tabs.html.erb
+++ b/app/views/spree/admin/shared/_subscription_tabs.html.erb
@@ -7,6 +7,9 @@
<li<%== ' class="active"' if current == :installments %>>
<%= link_to t("spree.admin.subscriptions.edit.installments"), spree.admin_subscription_installments_path(subscription) %>
</li>
+ <li<%== ' class="active"' if current == :events %>>
+ <%= link_to t("spree.admin.subscriptions.edit.events"), spree.admin_subscription_subscription_events_path(subscription) %>
+ </li>
</ul>
</nav>
<% end %>
diff --git a/app/views/spree/admin/subscription_events/_state_pill.html.erb b/app/views/spree/admin/subscription_events/_state_pill.html.erb
new file mode 100644
index 0000000..08a86a3
--- /dev/null
+++ b/app/views/spree/admin/subscription_events/_state_pill.html.erb
@@ -0,0 +1,8 @@
+<% state_class = {
+ fulfilled: 'active',
+ unfulfilled: 'error',
+}[installment.state.to_sym] %>
+
+<span class="pill pill-<%= state_class %>">
+ <%= SolidusSubscriptions::Installment.human_attribute_name("state.#{installment.state}") %>
+</span>
diff --git a/app/views/spree/admin/subscription_events/index.html.erb b/app/views/spree/admin/subscription_events/index.html.erb
new file mode 100644
index 0000000..547088e
--- /dev/null
+++ b/app/views/spree/admin/subscription_events/index.html.erb
@@ -0,0 +1,42 @@
+<% content_for(:page_title, t('.title')) %>
+
+<%= render 'spree/admin/shared/subscription_breadcrumbs', subscription: @subscription %>
+<%= render 'spree/admin/shared/subscription_sidebar', subscription: @subscription %>
+<%= render 'spree/admin/shared/subscription_tabs', current: :events, subscription: @subscription %>
+<%= render 'spree/admin/shared/subscription_actions', subscription: @subscription %>
+
+<fieldset class="no-border-bottom">
+ <legend><%= t('spree.admin.subscription_events.index.title') %></legend>
+
+ <%= paginate @subscription_events, theme: 'solidus_admin' %>
+
+ <table id="listing_subscription_events" class="index">
+ <thead>
+ <tr data-hook="admin_subscription_events_index_headers">
+ <th><%= SolidusSubscriptions::SubscriptionEvent.human_attribute_name(:event_type) %></th>
+ <th><%= SolidusSubscriptions::SubscriptionEvent.human_attribute_name(:details) %></th>
+ <th><%= sort_link(@search, :created_at, SolidusSubscriptions::SubscriptionEvent.human_attribute_name(:created_at)) %></th>
+ </tr>
+ </thead>
+
+ <tbody>
+ <% @subscription_events.each do |event| %>
+ <tr>
+ <td><%= event.event_type %></td>
+ <td>
+ <% if event.details.is_a?(Hash) %>
+ <% event.details.each_pair do |key, value| %>
+ <strong><%= key %></strong>: <%= value %><br>
+ <% end %>
+ <% else %>
+ <%= event.details %>
+ <% end %>
+ </td>
+ <td><%= l event.created_at %></td>
+ </tr>
+ <% end %>
+ </tbody>
+ </table>
+
+ <%= paginate @subscription_events, theme: 'solidus_admin' %>
+</fieldset>
diff --git a/config/locales/en.yml b/config/locales/en.yml
index e132d3f..06efcd6 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -40,6 +40,7 @@ en:
sidebar: Status
details: Details
installments: Installments
+ events: Events
payment: Payment
new:
back: Back to Subscriptions List
@@ -52,6 +53,9 @@ en:
installments:
index:
title: Installments
+ subscription_events:
+ index:
+ title: Events
promotion_rule_types:
subscription_promotion_rule:
name: Subscription
diff --git a/config/routes.rb b/config/routes.rb
index d8878c2..eb4c8f3 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -23,6 +23,7 @@ Spree::Core::Engine.routes.draw do
post :activate, on: :member
post :skip, on: :member
resources :installments, only: [:index, :show]
+ resources :subscription_events, only: :index
end
end
end
diff --git a/db/migrate/20200730101242_create_solidus_subscriptions_subscription_events.rb b/db/migrate/20200730101242_create_solidus_subscriptions_subscription_events.rb
new file mode 100644
index 0000000..62de91e
--- /dev/null
+++ b/db/migrate/20200730101242_create_solidus_subscriptions_subscription_events.rb
@@ -0,0 +1,17 @@
+class CreateSolidusSubscriptionsSubscriptionEvents < ActiveRecord::Migration[5.2]
+ def change
+ create_table :solidus_subscriptions_subscription_events do |t|
+ t.belongs_to(
+ :subscription,
+ null: false,
+ foreign_key: { to_table: :solidus_subscriptions_subscriptions },
+ index: { name: :idx_solidus_subscription_events_on_subscription_id },
+ type: :integer,
+ )
+ t.string :event_type, null: false
+ t.json :details, null: false
+
+ t.timestamps
+ end
+ end
+end
diff --git a/lib/solidus_subscriptions/testing_support/factories/subscription_event_factory.rb b/lib/solidus_subscriptions/testing_support/factories/subscription_event_factory.rb
new file mode 100644
index 0000000..0158e2e
--- /dev/null
+++ b/lib/solidus_subscriptions/testing_support/factories/subscription_event_factory.rb
@@ -0,0 +1,6 @@
+FactoryBot.define do
+ factory :subscription_event, class: 'SolidusSubscriptions::SubscriptionEvent' do
+ subscription
+ event_type { 'test_event' }
+ end
+end
diff --git a/spec/models/solidus_subscriptions/line_item_spec.rb b/spec/models/solidus_subscriptions/line_item_spec.rb
index 42349fc..a0b8845 100644
--- a/spec/models/solidus_subscriptions/line_item_spec.rb
+++ b/spec/models/solidus_subscriptions/line_item_spec.rb
@@ -10,6 +10,48 @@ RSpec.describe SolidusSubscriptions::LineItem, type: :model do
it { is_expected.to validate_numericality_of(:quantity).is_greater_than(0) }
it { is_expected.to validate_numericality_of(:interval_length).is_greater_than(0) }
+ describe '#save!' do
+ context 'when the line item is new' do
+ it 'tracks a line_item_created event' do
+ line_item = build(:subscription_line_item, :with_subscription)
+
+ line_item.save!
+
+ expect(line_item.subscription.events.last).to have_attributes(
+ event_type: 'line_item_created',
+ details: a_hash_including('id' => line_item.id),
+ )
+ end
+ end
+
+ context 'when the line item is persisted' do
+ it 'tracks a line_item_updated event' do
+ line_item = create(:subscription_line_item, :with_subscription)
+
+ line_item.quantity = 2
+ line_item.save!
+
+ expect(line_item.subscription.events.last).to have_attributes(
+ event_type: 'line_item_updated',
+ details: a_hash_including('id' => line_item.id),
+ )
+ end
+ end
+ end
+
+ describe '#destroy!' do
+ it 'tracks a line_item_destroyed event' do
+ line_item = create(:subscription_line_item, :with_subscription)
+
+ line_item.destroy!
+
+ expect(line_item.subscription.events.last).to have_attributes(
+ event_type: 'line_item_destroyed',
+ details: a_hash_including('id' => line_item.id),
+ )
+ end
+ end
+
describe "#interval" do
let(:line_item) { create :subscription_line_item, :with_subscription }
before do
diff --git a/spec/models/solidus_subscriptions/subscription_event_spec.rb b/spec/models/solidus_subscriptions/subscription_event_spec.rb
new file mode 100644
index 0000000..d4602f3
--- /dev/null
+++ b/spec/models/solidus_subscriptions/subscription_event_spec.rb
@@ -0,0 +1,15 @@
+require 'spec_helper'
+
+RSpec.describe SolidusSubscriptions::SubscriptionEvent do
+ describe '#save' do
+ it 'emits a Solidus event' do
+ event_klass = class_spy('Spree::Event')
+ stub_const('Spree::Event', event_klass)
+
+ subscription = create(:subscription)
+ subscription_event = create(:subscription_event, subscription: subscription, event_type: 'test_event', details: { foo: 'bar' })
+
+ expect(event_klass).to have_received(:fire).with('solidus_subscriptions.test_event', subscription: subscription_event.subscription, foo: 'bar')
+ end
+ end
+end
diff --git a/spec/models/solidus_subscriptions/subscription_spec.rb b/spec/models/solidus_subscriptions/subscription_spec.rb
index 09ea53f..ffda96a 100644
--- a/spec/models/solidus_subscriptions/subscription_spec.rb
+++ b/spec/models/solidus_subscriptions/subscription_spec.rb
@@ -15,6 +15,35 @@ RSpec.describe SolidusSubscriptions::Subscription, type: :model do
it { is_expected.to accept_nested_attributes_for :line_items }
+ describe '#save' do
+ context 'when the subscription is new' do
+ it 'tracks a subscription_created event' do
+ subscription = build(:subscription)
+
+ subscription.save!
+
+ expect(subscription.events.last).to have_attributes(
+ event_type: 'subscription_created',
+ details: a_hash_including('id' => subscription.id),
+ )
+ end
+ end
+
+ context 'when the line item is persisted' do
+ it 'tracks a subscription_updated event' do
+ subscription = create(:subscription)
+
+ subscription.end_date = Time.zone.tomorrow
+ subscription.save!
+
+ expect(subscription.events.last).to have_attributes(
+ event_type: 'subscription_updated',
+ details: a_hash_including('id' => subscription.id),
+ )
+ end
+ end
+ end
+
describe '#cancel' do
subject { subscription.cancel }
@@ -31,6 +60,11 @@ RSpec.describe SolidusSubscriptions::Subscription, type: :model do
subject
expect(subscription.canceled?).to be_truthy
end
+
+ it 'creates a subscription_canceled event' do
+ subject
+ expect(subscription.events.last).to have_attributes(event_type: 'subscription_canceled')
+ end
end
context 'the subscription cannot be canceled' do
@@ -40,6 +74,11 @@ RSpec.describe SolidusSubscriptions::Subscription, type: :model do
subject
expect(subscription.pending_cancellation?).to be_truthy
end
+
+ it 'creates a subscription_cancellation_requested event' do
+ subject
+ expect(subscription.events.last).to have_attributes(event_type: 'subscription_cancellation_requested')
+ end
end
end
@@ -116,10 +155,61 @@ RSpec.describe SolidusSubscriptions::Subscription, type: :model do
subject
expect(subscription.inactive?).to be_truthy
end
+
+ it 'creates a subscription_deactivated event' do
+ subject
+ expect(subscription.events.last).to have_attributes(event_type: 'subscription_deactivated')
+ end
end
context 'the subscription cannot be deactivated' do
it { is_expected.to be_falsy }
+
+ it 'does not create an event' do
+ expect { subject }.not_to change(subscription.events, :count)
+ end
+ end
+ end
+
+ describe '#activate' do
+ context 'when the subscription can be activated' do
+ it 'activates the subscription' do
+ subscription = create(:subscription,
+ actionable_date: Time.zone.today,
+ end_date: Time.zone.yesterday,)
+ subscription.deactivate!
+
+ subscription.activate
+
+ expect(subscription.state).to eq('active')
+ end
+
+ it 'creates a subscription_activated event' do
+ subscription = create(:subscription,
+ actionable_date: Time.zone.today,
+ end_date: Time.zone.yesterday,)
+ subscription.deactivate!
+
+ subscription.activate
+
+ expect(subscription.events.last).to have_attributes(event_type: 'subscription_activated')
+ end
+ end
+
+ context 'the subscription cannot be activated' do
+ it 'returns false' do
+ subscription = create(:subscription, actionable_date: Time.zone.today)
+
+ expect(subscription.activate).to eq(false)
+ end
+
+ it 'does not create an event' do
+ subscription = create(:subscription, actionable_date: Time.zone.today)
+
+ expect {
+ subscription.activate
+ }.not_to change(subscription.events, :count)
+ end
end
end
@@ -165,6 +255,11 @@ RSpec.describe SolidusSubscriptions::Subscription, type: :model do
actionable_date: expected_date
)
end
+
+ it 'creates a subscription_skipped event' do
+ subject
+ expect(subscription.events.last).to have_attributes(event_type: 'subscription_skipped')
+ end
end
describe ".actionable" do