From af153329064bc5295a5f5f91033180146f1cf2e1 Mon Sep 17 00:00:00 2001 From: Yuri Skurikhin Date: Thu, 10 Sep 2015 13:34:20 +0300 Subject: [PATCH 1/3] invoice - chack overlap dates --- app/models/invoice.rb | 20 +++++++-- ...50910054909_change_invoice_date_columns.rb | 6 +++ db/schema.rb | 6 +-- spec/factories.rb | 1 + spec/models/invoice_spec.rb | 43 ++++++++++++++++++- 5 files changed, 68 insertions(+), 8 deletions(-) create mode 100644 db/migrate/20150910054909_change_invoice_date_columns.rb diff --git a/app/models/invoice.rb b/app/models/invoice.rb index 86c47a78..d87980c1 100644 --- a/app/models/invoice.rb +++ b/app/models/invoice.rb @@ -5,8 +5,8 @@ # id :integer not null, primary key # organization_id :integer not null # customer_id :integer not null -# starts_at :datetime -# ends_at :datetime not null +# starts_at :date +# ends_at :date not null # currency :string default("USD"), not null # amount_cents :integer default(0), not null # sent_at :datetime @@ -32,18 +32,32 @@ class Invoice < ActiveRecord::Base validates :ends_at, presence: true validates :amount, presence: true validates :currency, presence: true + validates :customer_name, presence: true validates :amount, numericality: { greater_than: 0, less_than_or_equal_to: Dictionaries.money_max } validates :currency, inclusion: { in: Dictionaries.currencies, message: "%{value} is not a valid currency" } + validate :check_overlap_dates_for_customer, if: 'customer_name.present?' - scope :ordered, -> { order('created_at DESC') } + scope :ordered, -> { order('created_at DESC') } + scope :all_except, -> (invoice) { where.not(id: invoice) } after_save :set_currency private + def check_overlap_dates_for_customer + self.customer.invoices.all_except(self.id).where(organization: self.organization).each do |i| + self_start = self.starts_at ? self.starts_at : self.ends_at + i_start = i.starts_at ? i.starts_at : i.ends_at + if (self_start..self.ends_at).overlaps? (i_start..i.ends_at) + errors.add(:starts_at, 'Overlap date for this customer') if self.starts_at + errors.add(:ends_at, 'Overlap date for this customer') + end + end + end + def set_currency invoice_items.each{ |i| i.update(currency: currency) } end diff --git a/db/migrate/20150910054909_change_invoice_date_columns.rb b/db/migrate/20150910054909_change_invoice_date_columns.rb new file mode 100644 index 00000000..ee40b6ef --- /dev/null +++ b/db/migrate/20150910054909_change_invoice_date_columns.rb @@ -0,0 +1,6 @@ +class ChangeInvoiceDateColumns < ActiveRecord::Migration + def change + change_column :invoices, :starts_at, :date + change_column :invoices, :ends_at, :date + end +end diff --git a/db/schema.rb b/db/schema.rb index f533d359..6dccc3ce 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20150909054901) do +ActiveRecord::Schema.define(version: 20150910054909) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -94,8 +94,8 @@ create_table "invoices", force: :cascade do |t| t.integer "organization_id", null: false t.integer "customer_id", null: false - t.datetime "starts_at" - t.datetime "ends_at", null: false + t.date "starts_at" + t.date "ends_at", null: false t.string "currency", default: "USD", null: false t.integer "amount_cents", default: 0, null: false t.datetime "sent_at" diff --git a/spec/factories.rb b/spec/factories.rb index c048ab86..b012a44d 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -146,6 +146,7 @@ factory :invoice do organization customer + customer_name ends_at { Time.now } currency 'RUB' amount 500 diff --git a/spec/models/invoice_spec.rb b/spec/models/invoice_spec.rb index 0a124392..3a9b3536 100644 --- a/spec/models/invoice_spec.rb +++ b/spec/models/invoice_spec.rb @@ -5,8 +5,8 @@ # id :integer not null, primary key # organization_id :integer not null # customer_id :integer not null -# starts_at :datetime -# ends_at :datetime not null +# starts_at :date +# ends_at :date not null # currency :string default("USD"), not null # amount_cents :integer default(0), not null # sent_at :datetime @@ -28,8 +28,47 @@ it { is_expected.to validate_presence_of(:organization) } it { is_expected.to validate_presence_of(:ends_at) } it { is_expected.to validate_presence_of(:currency) } + it { is_expected.to validate_presence_of(:customer_name) } it { is_expected.to validate_numericality_of(:amount). is_greater_than(0).is_less_than_or_equal_to(Dictionaries.money_max) } it { is_expected.to validate_inclusion_of(:currency).in_array(Dictionaries.currencies) } + + context 'check customer date range crossing' do + let!(:org) { create :organization } + let!(:customer) { create :customer } + let!(:invoice) { create :invoice, customer_name: customer.name, + organization: org, starts_at: Date.today - 10.days, ends_at: Date.today } + let(:invoice1) { build :invoice, customer_name: customer.name, + organization: org, starts_at: Date.today - 9.days, ends_at: Date.today + 1.days } + let(:invoice2) { build :invoice, customer_name: customer.name, + organization: org, starts_at: Date.today - 11.days, ends_at: Date.today } + let(:invoice3) { build :invoice, customer_name: customer.name, + organization: org, starts_at: Date.today - 9.days, ends_at: Date.today } + let(:invoice4) { build :invoice, customer_name: customer.name, + organization: org, starts_at: Date.today - 11.days, ends_at: Date.today + 1.days } + let(:invoice5) { build :invoice, customer_name: customer.name, + organization: org, starts_at: Date.today + 2.days, ends_at: Date.today + 10.days } + + it 'Show error on starts_at' do + invoice1.valid? + expect(invoice1.errors[:ends_at]).to include('Overlap date for this customer') + end + it 'Show error on ends_at' do + invoice2.valid? + expect(invoice2.errors[:ends_at]).to include('Overlap date for this customer') + end + it 'Show errors on starts_at and ends_at' do + invoice3.valid? + expect(invoice3.errors[:ends_at]).to include('Overlap date for this customer') + end + it 'Show errors on starts_at and ends_at' do + invoice4.valid? + expect(invoice4.errors[:ends_at]).to include('Overlap date for this customer') + end + it 'Dont show errors on starts_at and ends_at' do + invoice5.valid? + expect(invoice5.errors[:ends_at]).to_not include('Overlap date for this customer') + end + end end end From 021879c0d217cf2a0190d9e1bcf3be1dbe757055 Mon Sep 17 00:00:00 2001 From: Yuri Skurikhin Date: Thu, 10 Sep 2015 15:49:35 +0300 Subject: [PATCH 2/3] fix prc --- Gemfile | 1 + Gemfile.lock | 3 +++ app/models/invoice.rb | 13 +------------ spec/models/invoice_spec.rb | 12 ++++++------ 4 files changed, 11 insertions(+), 18 deletions(-) diff --git a/Gemfile b/Gemfile index b2a07e7c..7074f669 100644 --- a/Gemfile +++ b/Gemfile @@ -45,6 +45,7 @@ gem 'russian_central_bank' gem 'gon' gem 'whenever', require: false gem 'cocoon' +gem 'validates_overlap' group :development, :test do gem 'rspec-rails' diff --git a/Gemfile.lock b/Gemfile.lock index 66fccd8e..4eb7a83c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -359,6 +359,8 @@ GEM raindrops (~> 0.7) uuid (2.3.7) macaddr (~> 1.0) + validates_overlap (0.5.2) + rails (>= 3.0.0) warden (1.2.3) rack (>= 1.0) wasabi (3.5.0) @@ -425,6 +427,7 @@ DEPENDENCIES timecop uglifier (>= 1.3.0) unicorn + validates_overlap whenever BUNDLED WITH diff --git a/app/models/invoice.rb b/app/models/invoice.rb index d87980c1..2e3b3689 100644 --- a/app/models/invoice.rb +++ b/app/models/invoice.rb @@ -38,7 +38,7 @@ class Invoice < ActiveRecord::Base less_than_or_equal_to: Dictionaries.money_max } validates :currency, inclusion: { in: Dictionaries.currencies, message: "%{value} is not a valid currency" } - validate :check_overlap_dates_for_customer, if: 'customer_name.present?' + validates :starts_at, :ends_at, overlap: { scope: 'customer_id' } scope :ordered, -> { order('created_at DESC') } scope :all_except, -> (invoice) { where.not(id: invoice) } @@ -47,17 +47,6 @@ class Invoice < ActiveRecord::Base private - def check_overlap_dates_for_customer - self.customer.invoices.all_except(self.id).where(organization: self.organization).each do |i| - self_start = self.starts_at ? self.starts_at : self.ends_at - i_start = i.starts_at ? i.starts_at : i.ends_at - if (self_start..self.ends_at).overlaps? (i_start..i.ends_at) - errors.add(:starts_at, 'Overlap date for this customer') if self.starts_at - errors.add(:ends_at, 'Overlap date for this customer') - end - end - end - def set_currency invoice_items.each{ |i| i.update(currency: currency) } end diff --git a/spec/models/invoice_spec.rb b/spec/models/invoice_spec.rb index 3a9b3536..cdbe5d2a 100644 --- a/spec/models/invoice_spec.rb +++ b/spec/models/invoice_spec.rb @@ -33,7 +33,7 @@ is_greater_than(0).is_less_than_or_equal_to(Dictionaries.money_max) } it { is_expected.to validate_inclusion_of(:currency).in_array(Dictionaries.currencies) } - context 'check customer date range crossing' do + context 'check customer date range overlaping' do let!(:org) { create :organization } let!(:customer) { create :customer } let!(:invoice) { create :invoice, customer_name: customer.name, @@ -51,23 +51,23 @@ it 'Show error on starts_at' do invoice1.valid? - expect(invoice1.errors[:ends_at]).to include('Overlap date for this customer') + expect(invoice1.errors[:starts_at]).to include('overlaps with another record') end it 'Show error on ends_at' do invoice2.valid? - expect(invoice2.errors[:ends_at]).to include('Overlap date for this customer') + expect(invoice2.errors[:starts_at]).to include('overlaps with another record') end it 'Show errors on starts_at and ends_at' do invoice3.valid? - expect(invoice3.errors[:ends_at]).to include('Overlap date for this customer') + expect(invoice3.errors[:starts_at]).to include('overlaps with another record') end it 'Show errors on starts_at and ends_at' do invoice4.valid? - expect(invoice4.errors[:ends_at]).to include('Overlap date for this customer') + expect(invoice4.errors[:starts_at]).to include('overlaps with another record') end it 'Dont show errors on starts_at and ends_at' do invoice5.valid? - expect(invoice5.errors[:ends_at]).to_not include('Overlap date for this customer') + expect(invoice5.errors[:starts_at]).to_not include('overlaps with another record') end end end From 06dca7aa2d05d5297ba219b583c82bfcf91effde Mon Sep 17 00:00:00 2001 From: Yuri Skurikhin Date: Fri, 11 Sep 2015 13:38:29 +0300 Subject: [PATCH 3/3] fix prc --- app/models/invoice.rb | 5 ++--- spec/models/invoice_spec.rb | 10 +++++----- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/app/models/invoice.rb b/app/models/invoice.rb index 2e3b3689..1fdf9003 100644 --- a/app/models/invoice.rb +++ b/app/models/invoice.rb @@ -38,10 +38,9 @@ class Invoice < ActiveRecord::Base less_than_or_equal_to: Dictionaries.money_max } validates :currency, inclusion: { in: Dictionaries.currencies, message: "%{value} is not a valid currency" } - validates :starts_at, :ends_at, overlap: { scope: 'customer_id' } + validates :starts_at, :ends_at, overlap: { scope: 'customer_id', message_content: 'overlaps with another Invoice' } - scope :ordered, -> { order('created_at DESC') } - scope :all_except, -> (invoice) { where.not(id: invoice) } + scope :ordered, -> { order('created_at DESC') } after_save :set_currency diff --git a/spec/models/invoice_spec.rb b/spec/models/invoice_spec.rb index cdbe5d2a..14fbf35a 100644 --- a/spec/models/invoice_spec.rb +++ b/spec/models/invoice_spec.rb @@ -51,23 +51,23 @@ it 'Show error on starts_at' do invoice1.valid? - expect(invoice1.errors[:starts_at]).to include('overlaps with another record') + expect(invoice1.errors[:starts_at]).to include('overlaps with another Invoice') end it 'Show error on ends_at' do invoice2.valid? - expect(invoice2.errors[:starts_at]).to include('overlaps with another record') + expect(invoice2.errors[:starts_at]).to include('overlaps with another Invoice') end it 'Show errors on starts_at and ends_at' do invoice3.valid? - expect(invoice3.errors[:starts_at]).to include('overlaps with another record') + expect(invoice3.errors[:starts_at]).to include('overlaps with another Invoice') end it 'Show errors on starts_at and ends_at' do invoice4.valid? - expect(invoice4.errors[:starts_at]).to include('overlaps with another record') + expect(invoice4.errors[:starts_at]).to include('overlaps with another Invoice') end it 'Dont show errors on starts_at and ends_at' do invoice5.valid? - expect(invoice5.errors[:starts_at]).to_not include('overlaps with another record') + expect(invoice5.errors[:starts_at]).to_not include('overlaps with another Invoice') end end end