diff --git a/Gemfile.lock b/Gemfile.lock
index ea08f79a2..8356cec3a 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -165,7 +165,7 @@ GEM
daemons-rails (1.2.1)
daemons
multi_json (~> 1.0)
- database_cleaner (1.5.3)
+ database_cleaner (1.6.1)
deep_cloneable (2.1.1)
activerecord (>= 3.1.0, < 5.0.0)
descendants_tracker (0.0.4)
@@ -215,7 +215,7 @@ GEM
haml (>= 4.0.6, < 5.0)
html2haml (>= 1.0.1)
railties (>= 4.0.1)
- hashdiff (0.3.2)
+ hashdiff (0.3.4)
hashie (3.5.5)
hashie-forbidden_attributes (0.1.1)
hashie (>= 3.0)
@@ -478,7 +478,7 @@ GEM
wasabi (3.5.0)
httpi (~> 2.0)
nokogiri (>= 1.4.2)
- webmock (2.3.2)
+ webmock (3.0.1)
addressable (>= 2.3.6)
crack (>= 0.3.2)
hashdiff
diff --git a/app/controllers/admin/billing/prices_controller.rb b/app/controllers/admin/billing/prices_controller.rb
index f66d3351b..a9862b113 100644
--- a/app/controllers/admin/billing/prices_controller.rb
+++ b/app/controllers/admin/billing/prices_controller.rb
@@ -2,13 +2,30 @@ module Admin
module Billing
class PricesController < AdminController
authorize_resource(class: 'Billing::Price')
- before_action :load_price, only: %i[edit update destroy]
+ before_action :load_price, only: %i[edit update expire]
helper_method :zones
helper_method :operation_categories
helper_method :durations
+ helper_method :statuses
+
+ def self.default_status
+ 'effective'
+ end
def index
- @q = ::Billing::Price.search(params[:q])
+ @search = OpenStruct.new(search_params)
+
+ unless @search.status
+ @search.status = self.class.default_status
+ end
+
+ prices = ::Billing::Price.all
+
+ if @search.status.present?
+ prices = ::Billing::Price.send(@search.status)
+ end
+
+ @q = prices.search(params[:q])
@q.sorts = ['zone_id asc', 'duration asc', 'operation_category asc',
'valid_from desc', 'valid_to asc'] if @q.sorts.empty?
@prices = @q.result.page(params[:page])
@@ -41,9 +58,10 @@ module Admin
end
end
- def destroy
- @price.destroy!
- flash[:notice] = t('.destroyed')
+ def expire
+ @price.expire
+ @price.save!
+ flash[:notice] = t('.expired')
redirect_to_index
end
@@ -66,6 +84,13 @@ module Admin
params.require(:price).permit(*allowed_params)
end
+ def search_params
+ allowed_params = %i[
+ status
+ ]
+ params.fetch(:search, {}).permit(*allowed_params)
+ end
+
def redirect_to_index
redirect_to admin_prices_url
end
@@ -82,6 +107,10 @@ module Admin
durations = ::Billing::Price::durations
durations.collect { |duration| [duration.sub('mon', 'month'), duration] }
end
+
+ def statuses
+ ::Billing::Price.statuses.map { |status| [status.capitalize, status] }
+ end
end
end
end
diff --git a/app/controllers/admin/dns/zones_controller.rb b/app/controllers/admin/dns/zones_controller.rb
index 10e432520..9d5041fb5 100644
--- a/app/controllers/admin/dns/zones_controller.rb
+++ b/app/controllers/admin/dns/zones_controller.rb
@@ -36,12 +36,6 @@ module Admin
end
end
- def destroy
- @zone.destroy!
- flash[:notice] = t('.destroyed')
- redirect_to_index
- end
-
private
def load_zone
diff --git a/app/models/billing/price.rb b/app/models/billing/price.rb
index c12cffffe..2475363b4 100644
--- a/app/models/billing/price.rb
+++ b/app/models/billing/price.rb
@@ -1,6 +1,7 @@
module Billing
class Price < ActiveRecord::Base
include Versions
+ include Concerns::Billing::Price::Expirable
has_paper_trail class_name: '::PriceVersion'
self.auto_html5_validation = false
@@ -11,6 +12,8 @@ module Billing
validates :operation_category, inclusion: { in: Proc.new { |price| price.class.operation_categories } }
validates :duration, inclusion: { in: Proc.new { |price| price.class.durations } }
+ alias_attribute :effect_time, :valid_from
+ alias_attribute :expire_time, :valid_to
monetize :price_cents, allow_nil: true, numericality: { greater_than_or_equal_to: 0 }
after_initialize :init_values
@@ -36,6 +39,21 @@ module Billing
]
end
+ def self.statuses
+ %w[upcoming effective expired]
+ end
+
+ def self.upcoming
+ where("#{attribute_alias(:effect_time)} > ?", Time.zone.now)
+ end
+
+ def self.effective
+ condition = "#{attribute_alias(:effect_time)} <= :now " \
+ " AND (#{attribute_alias(:expire_time)} >= :now" \
+ " OR #{attribute_alias(:expire_time)} IS NULL)"
+ where(condition, now: Time.zone.now)
+ end
+
def self.valid
where('valid_from <= ? AND (valid_to >= ? OR valid_to IS NULL)', Time.zone.now.end_of_day,
Time.zone.now.beginning_of_day)
diff --git a/app/models/concerns/billing/price/expirable.rb b/app/models/concerns/billing/price/expirable.rb
new file mode 100644
index 000000000..c0e05832a
--- /dev/null
+++ b/app/models/concerns/billing/price/expirable.rb
@@ -0,0 +1,17 @@
+module Concerns::Billing::Price::Expirable
+ extend ActiveSupport::Concern
+
+ class_methods do
+ def expired
+ where("#{attribute_alias(:expire_time)} < ?", Time.zone.now)
+ end
+ end
+
+ def expire
+ self[:valid_to] = Time.zone.now - 1
+ end
+
+ def expired?
+ expire_time.past?
+ end
+end
diff --git a/app/views/admin/billing/prices/_price.html.erb b/app/views/admin/billing/prices/_price.html.erb
index f2cf2027e..199f53e81 100644
--- a/app/views/admin/billing/prices/_price.html.erb
+++ b/app/views/admin/billing/prices/_price.html.erb
@@ -1,5 +1,5 @@
-
- <%= link_to price.zone_name, edit_admin_price_path(price), id: 'admin-edit-price-btn' %> |
+
+ <%= link_to price.zone_name, edit_admin_price_path(price), class: 'edit-price-btn' %> |
<%= price.duration.sub('mons', 'months') %> |
<%= price.operation_category %> |
<%= number_to_currency price.price %> |
diff --git a/app/views/admin/billing/prices/_search_form.html.erb b/app/views/admin/billing/prices/_search_form.html.erb
new file mode 100644
index 000000000..898cf9602
--- /dev/null
+++ b/app/views/admin/billing/prices/_search_form.html.erb
@@ -0,0 +1,19 @@
+<%= form_for :search, url: admin_prices_path, method: :get, html: { class: 'form-horizontal' } do |f| %>
+
+
+
+
+<% end %>
diff --git a/app/views/admin/billing/prices/edit.html.erb b/app/views/admin/billing/prices/edit.html.erb
index 75d82814f..b447814a2 100644
--- a/app/views/admin/billing/prices/edit.html.erb
+++ b/app/views/admin/billing/prices/edit.html.erb
@@ -9,9 +9,9 @@
- <%= link_to(t('.delete_btn'), admin_price_path(@price),
- method: :delete,
- data: { confirm: t('.delete_btn_confirm') },
+ <%= link_to(t('.expire_btn'), expire_admin_price_path(@price),
+ method: :patch,
+ data: { confirm: t('.expire_btn_confirm') },
class: 'btn btn-danger') %>
diff --git a/app/views/admin/billing/prices/index.html.erb b/app/views/admin/billing/prices/index.html.erb
index c675b8bde..31d74dc96 100644
--- a/app/views/admin/billing/prices/index.html.erb
+++ b/app/views/admin/billing/prices/index.html.erb
@@ -10,6 +10,8 @@
+<%= render 'search_form', search: @search %>
+
<% if @prices.present? %>
diff --git a/app/views/admin/dns/zones/edit.html.erb b/app/views/admin/dns/zones/edit.html.erb
index 645a77197..fc37f984b 100644
--- a/app/views/admin/dns/zones/edit.html.erb
+++ b/app/views/admin/dns/zones/edit.html.erb
@@ -3,18 +3,7 @@
<%= render 'form', zone: @zone %>
diff --git a/config/locales/admin/billing/prices.en.yml b/config/locales/admin/billing/prices.en.yml
index 277b708da..65e658fbb 100644
--- a/config/locales/admin/billing/prices.en.yml
+++ b/config/locales/admin/billing/prices.en.yml
@@ -15,15 +15,20 @@ en:
edit:
title: Edit price
- delete_btn: Delete
- delete_btn_confirm: Are you sure you want to delete price?
+ expire_btn: Expire
+ expire_btn_confirm: Are you sure you want to expire price?
update:
updated: Price has been updated
- destroy:
- destroyed: Price has been deleted
+ expire:
+ expired: Price has been expired
form:
create_btn: Create price
update_btn: Update price
+
+ search_form:
+ all: All
+ search_btn: Search
+ reset_btn: Reset
diff --git a/config/locales/admin/dns/zones.en.yml b/config/locales/admin/dns/zones.en.yml
index 08897a93b..6397c61ff 100644
--- a/config/locales/admin/dns/zones.en.yml
+++ b/config/locales/admin/dns/zones.en.yml
@@ -15,15 +15,10 @@ en:
edit:
title: Edit zone
- delete_btn: Delete
- delete_btn_confirm: Are you sure you want to delete zone?
update:
updated: Zone has been updated
- destroy:
- destroyed: Zone has been deleted
-
form:
create_btn: Create zone
update_btn: Update zone
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 3f70d8828..2627b112a 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -23,7 +23,7 @@ en:
long: "%A, %e. %B %Y, %H:%M"
short: "%d.%m.%y, %H:%M"
shorts: "%d.%m.%y, %H:%M:%S"
- date: "%d.%m.%y"
+ date: "%Y-%m-%d"
date_long: "%d. %B %Y"
ydate: "%Y.%m.%d"
date:
diff --git a/config/routes.rb b/config/routes.rb
index b808f1a24..e9fb61989 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -163,10 +163,16 @@ Rails.application.routes.draw do
namespace :admin do
resources :keyrelays
resources :zonefiles
- resources :zones, controller: 'dns/zones', except: %i[show]
+ resources :zones, controller: 'dns/zones', except: %i[show destroy]
resources :legal_documents
resources :keyrelays
- resources :prices, controller: 'billing/prices', except: %i[show]
+
+ resources :prices, controller: 'billing/prices', except: %i[show destroy] do
+ member do
+ patch :expire
+ end
+ end
+
resources :mail_templates
resources :account_activities
diff --git a/spec/factories/billing/price.rb b/spec/factories/billing/price.rb
index 52860bd14..b7d49653d 100644
--- a/spec/factories/billing/price.rb
+++ b/spec/factories/billing/price.rb
@@ -6,5 +6,13 @@ FactoryGirl.define do
duration '1 year'
operation_category Billing::Price.operation_categories.first
zone
+
+ factory :effective_price do
+ expire_time { Time.zone.now + 1.day }
+ end
+
+ factory :expired_price do
+ expire_time { Time.zone.now - 1.day }
+ end
end
end
diff --git a/spec/features/admin/billing/prices/delete_spec.rb b/spec/features/admin/billing/prices/delete_spec.rb
deleted file mode 100644
index 37c8f4003..000000000
--- a/spec/features/admin/billing/prices/delete_spec.rb
+++ /dev/null
@@ -1,23 +0,0 @@
-require 'rails_helper'
-
-RSpec.feature 'Deleting price in admin area', settings: false do
- given!(:price) { create(:price) }
-
- background do
- sign_in_to_admin_area
- end
-
- scenario 'deletes price' do
- visit admin_prices_url
- open_form
- click_link_or_button t('admin.billing.prices.edit.delete_btn')
-
- expect(page).to have_text(t('admin.billing.prices.destroy.destroyed'))
- end
-
- private
-
- def open_form
- click_link_or_button 'admin-edit-price-btn'
- end
-end
diff --git a/spec/features/admin/billing/prices/edit_spec.rb b/spec/features/admin/billing/prices/edit_spec.rb
index 6c567b5ca..8ef87b9a0 100644
--- a/spec/features/admin/billing/prices/edit_spec.rb
+++ b/spec/features/admin/billing/prices/edit_spec.rb
@@ -1,24 +1,22 @@
require 'rails_helper'
RSpec.feature 'Editing price in admin area', settings: false do
- given!(:price) { create(:price) }
+ given!(:price) { create(:effective_price) }
background do
sign_in_to_admin_area
end
scenario 'updates price' do
- visit admin_prices_url
+ visit admin_prices_path
open_form
submit_form
expect(page).to have_text(t('admin.billing.prices.update.updated'))
end
- private
-
def open_form
- click_link_or_button 'admin-edit-price-btn'
+ find('.edit-price-btn').click
end
def submit_form
diff --git a/spec/features/admin/billing/prices/expire_spec.rb b/spec/features/admin/billing/prices/expire_spec.rb
new file mode 100644
index 000000000..9a835f09c
--- /dev/null
+++ b/spec/features/admin/billing/prices/expire_spec.rb
@@ -0,0 +1,25 @@
+require 'rails_helper'
+
+RSpec.feature 'Expiring price in admin area', settings: false do
+ given!(:price) { create(:effective_price) }
+
+ background do
+ sign_in_to_admin_area
+ end
+
+ scenario 'expires price' do
+ visit admin_prices_path
+ open_edit_form
+ expire
+
+ expect(page).to have_text(t('admin.billing.prices.expire.expired'))
+ end
+
+ def open_edit_form
+ find('.edit-price-btn').click
+ end
+
+ def expire
+ click_link_or_button t('admin.billing.prices.edit.expire_btn')
+ end
+end
diff --git a/spec/features/admin/billing/prices/list_spec.rb b/spec/features/admin/billing/prices/list_spec.rb
new file mode 100644
index 000000000..95ebcb975
--- /dev/null
+++ b/spec/features/admin/billing/prices/list_spec.rb
@@ -0,0 +1,33 @@
+require 'rails_helper'
+
+RSpec.feature 'Viewing prices in admin area', settings: false do
+ given!(:effective_price) { create(:effective_price) }
+ given!(:expired_price) { create(:expired_price) }
+
+ background do
+ sign_in_to_admin_area
+ end
+
+ describe 'search' do
+ context 'when status is not selected' do
+ scenario 'shows effective prices' do
+ visit admin_prices_path
+ expect(page).to have_css('.price', count: 1)
+ end
+ end
+
+ context 'when status is given' do
+ scenario 'filters by given status' do
+ visit admin_prices_path
+ select Admin::Billing::PricesController.default_status.capitalize, from: 'search_status'
+ submit_search_form
+
+ expect(page).to have_css('.price', count: 1)
+ end
+ end
+
+ def submit_search_form
+ find('.price-search-form-search-btn').click
+ end
+ end
+end
diff --git a/spec/features/admin/billing/prices/new_spec.rb b/spec/features/admin/billing/prices/new_spec.rb
index ddbbac57b..27c56ffb6 100644
--- a/spec/features/admin/billing/prices/new_spec.rb
+++ b/spec/features/admin/billing/prices/new_spec.rb
@@ -16,8 +16,6 @@ RSpec.feature 'New price in admin area', settings: false do
expect(page).to have_text(t('admin.billing.prices.create.created'))
end
- private
-
def open_list
click_link_or_button t('admin.menu.prices')
end
diff --git a/spec/features/admin/dns/zones/delete_spec.rb b/spec/features/admin/dns/zones/delete_spec.rb
deleted file mode 100644
index 4cf0e5f28..000000000
--- a/spec/features/admin/dns/zones/delete_spec.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-require 'rails_helper'
-
-RSpec.feature 'Deleting zone in admin area', settings: false do
- given!(:zone) { create(:zone) }
-
- background do
- sign_in_to_admin_area
- end
-
- scenario 'deletes zone' do
- visit edit_admin_zone_url(zone)
- click_link_or_button t('admin.dns.zones.edit.delete_btn')
-
- expect(page).to have_text(t('admin.dns.zones.destroy.destroyed'))
- end
-end
diff --git a/spec/models/billing/price_spec.rb b/spec/models/billing/price_spec.rb
index f9d6c8fc8..7ee2277cc 100644
--- a/spec/models/billing/price_spec.rb
+++ b/spec/models/billing/price_spec.rb
@@ -3,6 +3,8 @@ require 'rails_helper'
RSpec.describe Billing::Price do
it { is_expected.to monetize(:price) }
it { is_expected.to be_versioned }
+ it { is_expected.to alias_attribute(:effect_time, :valid_from) }
+ it { is_expected.to alias_attribute(:expire_time, :valid_to) }
it 'should have one version' do
with_versioning do
@@ -13,14 +15,14 @@ RSpec.describe Billing::Price do
end
describe '::operation_categories', db: false do
- it 'returns available operation categories' do
+ it 'returns operation categories' do
categories = %w[create renew]
expect(described_class.operation_categories).to eq(categories)
end
end
describe '::durations', db: false do
- it 'returns available durations' do
+ it 'returns durations' do
durations = [
'3 mons',
'6 mons',
@@ -41,6 +43,40 @@ RSpec.describe Billing::Price do
end
end
+ describe '::statuses', db: false do
+ it 'returns statuses' do
+ expect(described_class.statuses).to eq(%w[upcoming effective expired])
+ end
+ end
+
+ describe '::upcoming' do
+ before :example do
+ travel_to Time.zone.parse('05.07.2010 00:00')
+
+ create(:price, id: 1, effect_time: Time.zone.parse('05.07.2010 00:00'))
+ create(:price, id: 2, effect_time: Time.zone.parse('05.07.2010 00:01'))
+ end
+
+ it 'returns upcoming' do
+ expect(described_class.upcoming.ids).to eq([2])
+ end
+ end
+
+ describe '::effective' do
+ before :example do
+ travel_to Time.zone.parse('05.07.2010 00:00')
+
+ create(:price, id: 1, effect_time: '05.07.2010 00:01', expire_time: '05.07.2010 00:02')
+ create(:price, id: 2, effect_time: '05.07.2010 00:00', expire_time: '05.07.2010 00:01')
+ create(:price, id: 3, effect_time: '05.07.2010 00:00', expire_time: nil)
+ create(:price, id: 4, effect_time: '04.07.2010', expire_time: '04.07.2010 23:59')
+ end
+
+ it 'returns effective' do
+ expect(described_class.effective.ids).to eq([2, 3])
+ end
+ end
+
describe 'zone validation', db: false do
subject(:price) { described_class.new }
diff --git a/spec/models/concerns/billing/price/expirable_spec.rb b/spec/models/concerns/billing/price/expirable_spec.rb
new file mode 100644
index 000000000..2180af432
--- /dev/null
+++ b/spec/models/concerns/billing/price/expirable_spec.rb
@@ -0,0 +1,55 @@
+require 'rails_helper'
+
+RSpec.describe Billing::Price do
+ describe '::expired' do
+ before :example do
+ travel_to Time.zone.parse('05.07.2010 00:00')
+
+ create(:price, id: 1, expire_time: Time.zone.parse('04.07.2010 23:59'))
+ create(:price, id: 2, expire_time: Time.zone.parse('05.07.2010 00:00'))
+ create(:price, id: 3, expire_time: Time.zone.parse('05.07.2010 00:01'))
+ end
+
+ it 'returns prices with expire time in the past ' do
+ expect(described_class.expired.ids).to eq([1])
+ end
+ end
+
+ describe '#expire', db: false do
+ let(:price) { described_class.new(expire_time: Time.zone.parse('06.07.2010')) }
+
+ before :example do
+ travel_to Time.zone.parse('05.07.2010 00:00')
+ end
+
+ it 'expires price' do
+ expect { price.expire }.to change { price.expired? }.from(false).to(true)
+ end
+ end
+
+ describe '#expired?', db: false do
+ subject(:expired) { domain.expired? }
+
+ before :example do
+ travel_to Time.zone.parse('05.07.2010 00:00')
+ end
+
+ context 'when expire time is in the past' do
+ let(:domain) { described_class.new(expire_time: Time.zone.parse('04.07.2010 23:59')) }
+
+ specify { expect(expired).to be true }
+ end
+
+ context 'when expire time is now' do
+ let(:domain) { described_class.new(expire_time: Time.zone.parse('05.07.2010 00:00')) }
+
+ specify { expect(expired).to be false }
+ end
+
+ context 'when expire time is in the future' do
+ let(:domain) { described_class.new(expire_time: Time.zone.parse('05.07.2010 00:01')) }
+
+ specify { expect(expired).to be false }
+ end
+ end
+end
diff --git a/spec/requests/admin/billing/prices/destroy_spec.rb b/spec/requests/admin/billing/prices/destroy_spec.rb
deleted file mode 100644
index 5a34b8dcb..000000000
--- a/spec/requests/admin/billing/prices/destroy_spec.rb
+++ /dev/null
@@ -1,18 +0,0 @@
-require 'rails_helper'
-
-RSpec.describe 'admin price destroy', settings: false do
- let!(:price) { create(:price) }
-
- before :example do
- sign_in_to_admin_area
- end
-
- it 'deletes price' do
- expect { delete admin_price_path(price) }.to change { Billing::Price.count }.from(1).to(0)
- end
-
- it 'redirects to :index' do
- delete admin_price_path(price)
- expect(response).to redirect_to admin_prices_url
- end
-end
diff --git a/spec/requests/admin/billing/prices/expire_spec.rb b/spec/requests/admin/billing/prices/expire_spec.rb
new file mode 100644
index 000000000..79612bbc9
--- /dev/null
+++ b/spec/requests/admin/billing/prices/expire_spec.rb
@@ -0,0 +1,22 @@
+require 'rails_helper'
+
+RSpec.describe 'admin price expire', settings: false do
+ before :example do
+ sign_in_to_admin_area
+ end
+
+ it 'expires price' do
+ price = create(:effective_price)
+
+ expect { patch expire_admin_price_path(price); price.reload }
+ .to change { price.expired? }.from(false).to(true)
+ end
+
+ it 'redirects to :index' do
+ price = create(:effective_price)
+
+ patch expire_admin_price_path(price)
+
+ expect(response).to redirect_to admin_prices_url
+ end
+end
diff --git a/spec/requests/admin/dns/zones/destroy_spec.rb b/spec/requests/admin/dns/zones/destroy_spec.rb
deleted file mode 100644
index 4d2c714bc..000000000
--- a/spec/requests/admin/dns/zones/destroy_spec.rb
+++ /dev/null
@@ -1,18 +0,0 @@
-require 'rails_helper'
-
-RSpec.describe 'admin zone destroy', settings: false do
- let!(:zone) { create(:zone) }
-
- before :example do
- sign_in_to_admin_area
- end
-
- it 'deletes zone' do
- expect { delete admin_zone_path(zone) }.to change { DNS::Zone.count }.from(1).to(0)
- end
-
- it 'redirects to :index' do
- delete admin_zone_path(zone)
- expect(response).to redirect_to admin_zones_url
- end
-end