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| %> +
+ <%= f.label :status, class: 'col-sm-2 control-label' %> + +
+ <%= f.select :status, options_for_select(statuses, search.status), { include_blank: t('.all') }, + class: 'form-control' %> +
+
+ +
+
+ <%= f.submit t('.search_btn'), class: 'btn btn-primary', name: nil %> + <%= link_to t('.reset_btn'), admin_prices_path, + class: 'btn btn-default price-search-form-search-btn' %> +
+
+ +<% 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