Refactor and improve invoices

- `runner 'Invoice.cancel_overdue_invoices'` in `schedule.rb` is
changed to `rake 'invoices:cancel_overdue'`.
- `invoices.payment_term` database column is removed and its value is
hardcoded in UI.
- `invoices.paid_at` is removed as unused
- `invoices.due_date` column's type is now `date`.
- `Invoice#invoice_items` renamed to `Invoice#items` and `Invoice`
interface to get a list of items is unified.
- Default date format in UI.
- Default translations are used.
- Tests improved.
- Specs converted to tests and removed along with factories.
- Database structure improved.
This commit is contained in:
Artur Beljajev 2018-10-17 12:21:04 +03:00
parent d86ec026e3
commit a97728c0f3
65 changed files with 758 additions and 341 deletions

View file

@ -33,13 +33,8 @@ module Admin
end end
def cancel def cancel
if @invoice.cancel @invoice.cancel
flash[:notice] = t(:record_updated) redirect_to [:admin, @invoice], notice: t('.cancelled')
redirect_to([:admin, @invoice])
else
flash.now[:alert] = t(:failed_to_update_record)
render :show
end
end end
def forward def forward

View file

@ -6,8 +6,7 @@ class Registrar
def index def index
params[:q] ||= {} params[:q] ||= {}
invoices = current_registrar_user.registrar.invoices invoices = current_registrar_user.registrar.invoices.includes(:items, :account_activity)
.includes(:invoice_items, :account_activity)
normalize_search_parameters do normalize_search_parameters do
@q = invoices.search(params[:q]) @q = invoices.search(params[:q])
@ -35,13 +34,8 @@ class Registrar
end end
def cancel def cancel
if @invoice.cancel @invoice.cancel
flash[:notice] = t(:record_updated) redirect_to [:registrar, @invoice], notice: t('.cancelled')
redirect_to([:registrar, @invoice])
else
flash.now[:alert] = t(:failed_to_update_record)
render :show
end
end end
def download_pdf def download_pdf
@ -58,18 +52,7 @@ class Registrar
def normalize_search_parameters def normalize_search_parameters
params[:q][:total_gteq].gsub!(',', '.') if params[:q][:total_gteq] params[:q][:total_gteq].gsub!(',', '.') if params[:q][:total_gteq]
params[:q][:total_lteq].gsub!(',', '.') if params[:q][:total_lteq] params[:q][:total_lteq].gsub!(',', '.') if params[:q][:total_lteq]
ca_cache = params[:q][:due_date_lteq]
begin
end_time = params[:q][:due_date_lteq].try(:to_date)
params[:q][:due_date_lteq] = end_time.try(:end_of_day)
rescue
logger.warn('Invalid date')
end
yield yield
params[:q][:due_date_lteq] = ca_cache
end end
end end
end end

View file

@ -29,7 +29,7 @@ class Registrar
if @payment.valid_response_from_intermediary? && @payment.settled_payment? if @payment.valid_response_from_intermediary? && @payment.settled_payment?
@payment.complete_transaction @payment.complete_transaction
if invoice.binded? if invoice.paid?
flash[:notice] = t(:pending_applied) flash[:notice] = t(:pending_applied)
else else
flash[:alert] = t(:something_wrong) flash[:alert] = t(:something_wrong)

View file

@ -41,9 +41,7 @@ class BankTransaction < ActiveRecord::Base
return unless registrar return unless registrar
return unless invoice_num return unless invoice_num
return unless invoice return unless invoice
return unless invoice.payable?
return if invoice.binded?
return if invoice.cancelled?
return if invoice.total != sum return if invoice.total != sum
create_activity(registrar, invoice) create_activity(registrar, invoice)
@ -62,7 +60,7 @@ class BankTransaction < ActiveRecord::Base
return return
end end
if invoice.binded? if invoice.paid?
errors.add(:base, I18n.t('invoice_is_already_binded')) errors.add(:base, I18n.t('invoice_is_already_binded'))
return return
end end

View file

@ -0,0 +1,28 @@
module Concerns
module Invoice
module Cancellable
extend ActiveSupport::Concern
included do
scope :non_cancelled, -> { where(cancelled_at: nil) }
end
def cancellable?
unpaid? && not_cancelled?
end
def cancel
raise 'Invoice cannot be cancelled' unless cancellable?
update!(cancelled_at: Time.zone.now)
end
def cancelled?
cancelled_at
end
def not_cancelled?
!cancelled?
end
end
end
end

View file

@ -0,0 +1,28 @@
module Concerns
module Invoice
module Payable
extend ActiveSupport::Concern
included do
scope :unpaid, -> { where('id NOT IN (SELECT invoice_id FROM account_activities WHERE' \
' invoice_id IS NOT NULL)') }
end
def payable?
unpaid? && not_cancelled?
end
def paid?
account_activity
end
def receipt_date
account_activity.created_at.to_date
end
def unpaid?
!paid?
end
end
end
end

View file

@ -3,7 +3,7 @@ class Directo < ActiveRecord::Base
belongs_to :item, polymorphic: true belongs_to :item, polymorphic: true
def self.send_receipts def self.send_receipts
new_trans = Invoice.where(in_directo: false).where(cancelled_at: nil) new_trans = Invoice.where(in_directo: false).non_cancelled
total = new_trans.count total = new_trans.count
counter = 0 counter = 0
Rails.logger.info("[DIRECTO] Will try to send #{total} invoices") Rails.logger.info("[DIRECTO] Will try to send #{total} invoices")
@ -26,7 +26,7 @@ class Directo < ActiveRecord::Base
xml.invoice( xml.invoice(
"SalesAgent" => Setting.directo_sales_agent, "SalesAgent" => Setting.directo_sales_agent,
"Number" => num, "Number" => num,
"InvoiceDate" => invoice.created_at.strftime("%Y-%m-%d"), "InvoiceDate" => invoice.issue_date.strftime("%Y-%m-%d"),
"PaymentTerm" => Setting.directo_receipt_payment_term, "PaymentTerm" => Setting.directo_receipt_payment_term,
"Currency" => invoice.currency, "Currency" => invoice.currency,
"CustomerCode"=> invoice.buyer.accounting_customer_code "CustomerCode"=> invoice.buyer.accounting_customer_code

View file

@ -1,16 +1,16 @@
class Invoice < ActiveRecord::Base class Invoice < ActiveRecord::Base
include Versions include Versions
include Concerns::Invoice::Cancellable
include Concerns::Invoice::Payable
belongs_to :seller, class_name: 'Registrar' belongs_to :seller, class_name: 'Registrar'
belongs_to :buyer, class_name: 'Registrar' belongs_to :buyer, class_name: 'Registrar'
has_one :account_activity has_one :account_activity
has_many :invoice_items has_many :items, class_name: 'InvoiceItem', dependent: :destroy
has_many :directo_records, as: :item, class_name: 'Directo' has_many :directo_records, as: :item, class_name: 'Directo'
accepts_nested_attributes_for :invoice_items accepts_nested_attributes_for :items
scope :unbinded, lambda {
where('id NOT IN (SELECT invoice_id FROM account_activities where invoice_id IS NOT NULL)')
}
scope :all_columns, ->{select("invoices.*")} scope :all_columns, ->{select("invoices.*")}
scope :sort_due_date_column, ->{all_columns.select("CASE WHEN invoices.cancelled_at is not null THEN scope :sort_due_date_column, ->{all_columns.select("CASE WHEN invoices.cancelled_at is not null THEN
(invoices.cancelled_at + interval '100 year') ELSE (invoices.cancelled_at + interval '100 year') ELSE
@ -24,11 +24,14 @@ class Invoice < ActiveRecord::Base
scope :sort_by_sort_receipt_date_asc, ->{sort_receipt_date_column.order("sort_receipt_date ASC")} scope :sort_by_sort_receipt_date_asc, ->{sort_receipt_date_column.order("sort_receipt_date ASC")}
scope :sort_by_sort_receipt_date_desc, ->{sort_receipt_date_column.order("sort_receipt_date DESC")} scope :sort_by_sort_receipt_date_desc, ->{sort_receipt_date_column.order("sort_receipt_date DESC")}
scope :overdue, -> { unpaid.non_cancelled.where('due_date < ?', Time.zone.today) }
attr_accessor :billing_email attr_accessor :billing_email
validates :billing_email, email_format: { message: :invalid }, allow_blank: true validates :billing_email, email_format: { message: :invalid }, allow_blank: true
validates :issue_date, presence: true
validates :due_date, :currency, :seller_name, validates :due_date, :currency, :seller_name,
:seller_iban, :buyer_name, :invoice_items, presence: true :seller_iban, :buyer_name, :items, presence: true
validates :vat_rate, numericality: { greater_than_or_equal_to: 0, less_than: 100 }, validates :vat_rate, numericality: { greater_than_or_equal_to: 0, less_than: 100 },
allow_nil: true allow_nil: true
@ -56,35 +59,6 @@ class Invoice < ActiveRecord::Base
false false
end end
class << self
def cancel_overdue_invoices
STDOUT << "#{Time.zone.now.utc} - Cancelling overdue invoices\n" unless Rails.env.test?
cr_at = Time.zone.now - Setting.days_to_keep_overdue_invoices_active.days
invoices = Invoice.unbinded.where(
'due_date < ? AND cancelled_at IS NULL', cr_at
)
unless Rails.env.test?
invoices.each do |m|
STDOUT << "#{Time.zone.now.utc} Invoice.cancel_overdue_invoices: ##{m.id}\n"
end
end
count = invoices.update_all(cancelled_at: Time.zone.now)
STDOUT << "#{Time.zone.now.utc} - Successfully cancelled #{count} overdue invoices\n" unless Rails.env.test?
end
end
def binded?
account_activity.present?
end
def receipt_date
account_activity.try(:created_at)
end
def to_s def to_s
I18n.t('invoice_no', no: number) I18n.t('invoice_no', no: number)
end end
@ -119,25 +93,6 @@ class Invoice < ActiveRecord::Base
"invoice-#{number}.pdf" "invoice-#{number}.pdf"
end end
def cancel
if binded?
errors.add(:base, I18n.t('cannot_cancel_paid_invoice'))
return false
end
if cancelled?
errors.add(:base, I18n.t('cannot_cancel_cancelled_invoice'))
return false
end
self.cancelled_at = Time.zone.now
save
end
def cancelled?
cancelled_at.present?
end
def forward(html) def forward(html)
return false unless valid? return false unless valid?
return false unless billing_email.present? return false unless billing_email.present?
@ -146,12 +101,8 @@ class Invoice < ActiveRecord::Base
true true
end end
def items
invoice_items
end
def subtotal def subtotal
invoice_items.map(&:item_sum_without_vat).reduce(:+) items.map(&:item_sum_without_vat).reduce(:+)
end end
def vat_amount def vat_amount
@ -164,6 +115,10 @@ class Invoice < ActiveRecord::Base
read_attribute(:total) read_attribute(:total)
end end
def each
items.each { |item| yield item }
end
private private
def apply_default_vat_rate def apply_default_vat_rate

View file

@ -3,6 +3,6 @@ class InvoiceItem < ActiveRecord::Base
belongs_to :invoice belongs_to :invoice
def item_sum_without_vat def item_sum_without_vat
(amount * price).round(2) (price * quantity).round(2)
end end
end end

View file

@ -51,8 +51,8 @@ class Registrar < ActiveRecord::Base
def issue_prepayment_invoice(amount, description = nil) def issue_prepayment_invoice(amount, description = nil)
invoices.create( invoices.create(
due_date: (Time.zone.now.to_date + Setting.days_to_keep_invoices_active.days).end_of_day, issue_date: Time.zone.today,
payment_term: 'prepayment', due_date: (Time.zone.now + Setting.days_to_keep_invoices_active.days).to_date,
description: description, description: description,
currency: 'EUR', currency: 'EUR',
seller_name: Setting.registry_juridical_name, seller_name: Setting.registry_juridical_name,
@ -82,11 +82,11 @@ class Registrar < ActiveRecord::Base
buyer_url: website, buyer_url: website,
buyer_email: email, buyer_email: email,
reference_no: reference_no, reference_no: reference_no,
invoice_items_attributes: [ items_attributes: [
{ {
description: 'prepayment', description: 'prepayment',
unit: 'piece', unit: 'piece',
amount: 1, quantity: 1,
price: amount price: amount
} }
] ]

View file

@ -0,0 +1,33 @@
class OverdueInvoiceCanceller
attr_reader :invoices
attr_reader :delay
def initialize(invoices: Invoice.overdue, delay: self.class.delay)
@invoices = invoices
@delay = delay
end
def self.default_delay
30.days
end
def self.delay
Setting.days_to_keep_overdue_invoices_active&.days || default_delay
end
def cancel
invoices.each do |invoice|
next unless cancellable?(invoice)
invoice.cancel
yield invoice if block_given?
end
end
private
def cancellable?(invoice)
due_date_with_delay = invoice.due_date + delay
due_date_with_delay.past?
end
end

View file

@ -61,7 +61,7 @@
%th{class: 'col-xs-2'} %th{class: 'col-xs-2'}
= sort_link(@q, 'activity_type') = sort_link(@q, 'activity_type')
%th{class: 'col-xs-3'} %th{class: 'col-xs-3'}
= sort_link(@q, 'created_at', t(:receipt_date)) = sort_link(@q, 'created_at', AccountActivity.human_attribute_name(:created_at))
%th{class: 'col-xs-2'} %th{class: 'col-xs-2'}
= sort_link(@q, 'sum') = sort_link(@q, 'sum')
%tbody %tbody

View file

@ -16,18 +16,18 @@
%th{class: 'col-xs-3'} %th{class: 'col-xs-3'}
= sort_link(@q, :sort_receipt_date, "Receipt date") = sort_link(@q, :sort_receipt_date, "Receipt date")
%tbody %tbody
- @invoices.each do |x| - @invoices.each do |invoice|
%tr %tr
%td= link_to(x, [:admin, x]) %td= link_to(invoice, [:admin, invoice])
%td= link_to(x.buyer_name, admin_registrar_path(x.buyer_id)) %td= link_to(invoice.buyer_name, admin_registrar_path(invoice.buyer_id))
- if x.cancelled? - if invoice.cancelled?
%td.text-grey= t(:cancelled) %td.text-grey= t(:cancelled)
- else - else
%td= l(x.due_date, format: :date_long) %td= l invoice.due_date
- if x.binded? - if invoice.paid?
%td= l(x.receipt_date, format: :date_long) %td= l invoice.receipt_date
- elsif x.cancelled? - elsif invoice.cancelled?
%td.text-grey= t(:cancelled) %td.text-grey= t(:cancelled)
- else - else
%td.text-danger= t(:unpaid) %td.text-danger= t(:unpaid)

View file

@ -4,11 +4,11 @@
= @invoice = @invoice
.col-sm-8 .col-sm-8
%h1.text-right.text-center-xs %h1.text-right.text-center-xs
- unless @invoice.binded? - if @invoice.unpaid?
= link_to(t(:payment_received), new_admin_bank_statement_path(invoice_id: @invoice.id), class: 'btn btn-default') = link_to(t(:payment_received), new_admin_bank_statement_path(invoice_id: @invoice.id), class: 'btn btn-default')
= link_to(t(:download), admin_invoice_download_pdf_path(@invoice), class: 'btn btn-default') = link_to(t(:download), admin_invoice_download_pdf_path(@invoice), class: 'btn btn-default')
= link_to(t(:forward), admin_invoice_forward_path(@invoice), class: 'btn btn-default') = link_to(t(:forward), admin_invoice_forward_path(@invoice), class: 'btn btn-default')
- if !@invoice.cancelled? && !@invoice.binded? - if @invoice.cancellable?
= link_to(t(:cancel), cancel_admin_invoice_path(@invoice), method: :patch, class: 'btn btn-warning') = link_to(t(:cancel), cancel_admin_invoice_path(@invoice), method: :patch, class: 'btn btn-warning')
= link_to(t(:back), admin_invoices_path, class: 'btn btn-default') = link_to(t(:back), admin_invoices_path, class: 'btn btn-default')
%hr %hr

View file

@ -44,7 +44,7 @@
%th{class: 'col-xs-2'} %th{class: 'col-xs-2'}
= sort_link(@q, 'activity_type') = sort_link(@q, 'activity_type')
%th{class: 'col-xs-3'} %th{class: 'col-xs-3'}
= sort_link(@q, 'created_at', t(:receipt_date)) = sort_link(@q, 'created_at', AccountActivity.human_attribute_name(:created_at))
%th{class: 'col-xs-2'} %th{class: 'col-xs-2'}
= sort_link(@q, 'sum') = sort_link(@q, 'sum')
%tbody %tbody

View file

@ -52,22 +52,22 @@
%thead %thead
%tr %tr
%th{class: 'col-xs-3'}= t(:invoice) %th{class: 'col-xs-3'}= t(:invoice)
%th{class: 'col-xs-3'}= t(:receipt_date) %th{class: 'col-xs-3'}= Invoice.human_attribute_name :receipt_date
%th{class: 'col-xs-3'}= t(:due_date) %th{class: 'col-xs-3'}= t(:due_date)
%th{class: 'col-xs-3'}= t(:total) %th{class: 'col-xs-3'}= t(:total)
%tbody %tbody
- @invoices.each do |x| - @invoices.each do |invoice|
%tr %tr.invoice
%td= link_to(x, [:registrar, x]) %td= link_to(invoice, [:registrar, invoice])
- if x.receipt_date - if invoice.paid?
%td= l(x.receipt_date, format: :date_long) %td= l invoice.receipt_date
- elsif x.cancelled? - elsif invoice.cancelled?
%td.text-grey= t(:cancelled) %td.text-grey= t(:cancelled)
- else - else
%td{class: 'text-danger'}= t(:unpaid) %td{class: 'text-danger'}= t(:unpaid)
%td= l(x.due_date, format: :date_long) %td= l invoice.due_date
%td= currency(x.total) %td= currency(invoice.total)
.row .row
.col-md-12 .col-md-12
= paginate @invoices = paginate @invoices

View file

@ -2,28 +2,28 @@
%hr %hr
%dl.dl-horizontal %dl.dl-horizontal
%dt= t(:issue_date) %dt= t(:issue_date)
%dd= l(@invoice.created_at, format: :date_long) %dd= l @invoice.issue_date
- if @invoice.cancelled? - if @invoice.cancelled?
%dt= t(:cancel_date) %dt= Invoice.human_attribute_name :cancelled_at
%dd= l(@invoice.cancelled_at, format: :date_long) %dd= l @invoice.cancelled_at
%dt= t(:due_date) %dt= t(:due_date)
- if @invoice.cancelled? - if @invoice.cancelled?
%dd.text-grey= t(:cancelled) %dd.text-grey= t(:cancelled)
- else - else
%dd= l(@invoice.due_date, format: :date_long) %dd= l @invoice.due_date
%dt= t(:receipt_date) %dt= Invoice.human_attribute_name :receipt_date
- if @invoice.binded? - if @invoice.paid?
%dd= l(@invoice.receipt_date, format: :date_long) %dd= l @invoice.receipt_date
- elsif @invoice.cancelled? - elsif @invoice.cancelled?
%dd.text-grey= t(:cancelled) %dd.text-grey= t(:cancelled)
- else - else
%dd{class: 'text-danger'}= t(:unpaid) %dd{class: 'text-danger'}= t(:unpaid)
%dt= t(:payment_term) %dt= t(:payment_term)
%dd= t(@invoice.payment_term) %dd Prepayment
%dt= t(:invoice_number) %dt= t(:invoice_number)
%dd= @invoice.number %dd= @invoice.number

View file

@ -6,17 +6,17 @@
%tr %tr
%th{class: 'col-xs-4'}= t(:description) %th{class: 'col-xs-4'}= t(:description)
%th{class: 'col-xs-2'}= t(:unit) %th{class: 'col-xs-2'}= t(:unit)
%th{class: 'col-xs-2'}= t(:amount) %th{class: 'col-xs-2'}= InvoiceItem.human_attribute_name :quantity
%th{class: 'col-xs-2'}= t(:price) %th{class: 'col-xs-2'}= t(:price)
%th{class: 'col-xs-2'}= t(:total) %th{class: 'col-xs-2'}= t(:total)
%tbody %tbody
- @invoice.items.each do |x| - @invoice.each do |invoice_item|
%tr %tr
%td= t(x.description) %td= invoice_item.description
%td= x.unit %td= invoice_item.unit
%td= currency(x.amount) %td= invoice_item.quantity
%td= currency(x.price) %td= currency(invoice_item.price)
%td= currency(x.item_sum_without_vat) %td= currency(invoice_item.item_sum_without_vat)
%tfoot %tfoot
%tr %tr
%th{colspan: 3} %th{colspan: 3}

View file

@ -5,7 +5,7 @@
/ %dd= t(@invoice.document_name) / %dd= t(@invoice.document_name)
%dt= t(:issue_date) %dt= t(:issue_date)
%dd= l(@invoice.created_at) %dd= l(@invoice.date)
%dt= t(:due_date) %dt= t(:due_date)
%dd= l(@invoice.due_date) %dd= l(@invoice.due_date)

View file

@ -149,21 +149,21 @@
%hr %hr
%dl.dl-horizontal %dl.dl-horizontal
%dt= t(:issue_date) %dt= t(:issue_date)
%dd= l(@invoice.created_at, format: :date_long) %dd= l @invoice.issue_date
- if @invoice.cancelled? - if @invoice.cancelled?
%dt= t(:cancel_date) %dt= Invoice.human_attribute_name :cancelled_at
%dd= l(@invoice.cancelled_at, format: :date_long) %dd= l @invoice.cancelled_at,
%dt= t(:due_date) %dt= t(:due_date)
- if @invoice.cancelled? - if @invoice.cancelled?
%dd= t(:cancelled) %dd= t(:cancelled)
- else - else
%dd= l(@invoice.due_date, format: :date_long) %dd= l @invoice.due_date
%dt= t(:receipt_date) %dt= Invoice.human_attribute_name :receipt_date
- if @invoice.binded? - if @invoice.paid?
%dd= l(@invoice.receipt_date, format: :date_long) %dd= l @invoice.receipt_date
- elsif @invoice.cancelled? - elsif @invoice.cancelled?
%dd= t(:cancelled) %dd= t(:cancelled)
- else - else
@ -173,7 +173,7 @@
%dd= @invoice.seller_contact_name %dd= @invoice.seller_contact_name
%dt= t(:payment_term) %dt= t(:payment_term)
%dd= t(@invoice.payment_term) %dd Prepayment
%dt= t(:invoice_number) %dt= t(:invoice_number)
%dd= @invoice.number %dd= @invoice.number
@ -224,17 +224,17 @@
%tr %tr
%th{class: 'col-xs-4'}= t(:description) %th{class: 'col-xs-4'}= t(:description)
%th{class: 'col-xs-2'}= t(:unit) %th{class: 'col-xs-2'}= t(:unit)
%th{class: 'col-xs-1'}= t(:amount) %th{class: 'col-xs-1'}= InvoiceItem.human_attribute_name :quantity
%th{class: 'col-xs-3'}= t(:price) %th{class: 'col-xs-3'}= t(:price)
%th{class: 'col-xs-2'}= t(:total) %th{class: 'col-xs-2'}= t(:total)
%tbody %tbody
- @invoice.items.each do |x| - @invoice.each do |invoice_item|
%tr %tr
%td= t(x.description) %td= invoice_item.description
%td= x.unit %td= invoice_item.unit
%td= currency(x.amount) %td= invoice_item.quantity
%td= currency(x.price) %td= currency(invoice_item.price)
%td= "#{currency(x.item_sum_without_vat)} #{@invoice.currency}" %td= "#{currency(invoice_item.item_sum_without_vat)} #{@invoice.currency}"
%tfoot %tfoot
%tr %tr
%th{colspan: 3} %th{colspan: 3}

View file

@ -1,7 +1,7 @@
- content_for :actions do - content_for :actions do
= link_to(t(:download), download_pdf_registrar_invoice_path(@invoice), class: 'btn btn-default') = link_to(t(:download), download_pdf_registrar_invoice_path(@invoice), class: 'btn btn-default')
= link_to(t(:forward), forward_registrar_invoice_path(@invoice), class: 'btn btn-default') = link_to(t(:forward), forward_registrar_invoice_path(@invoice), class: 'btn btn-default')
- if !@invoice.cancelled? && !@invoice.binded? - if @invoice.cancellable?
= link_to(t(:cancel), cancel_registrar_invoice_path(@invoice), method: :patch, class: 'btn btn-warning') = link_to(t(:cancel), cancel_registrar_invoice_path(@invoice), method: :patch, class: 'btn btn-warning')
= link_to(t(:back), registrar_invoices_path, class: 'btn btn-default') = link_to(t(:back), registrar_invoices_path, class: 'btn btn-default')
= render 'shared/title', name: @invoice.to_s = render 'shared/title', name: @invoice.to_s
@ -15,6 +15,6 @@
.row .row
.col-md-12= render 'registrar/invoices/partials/items' .col-md-12= render 'registrar/invoices/partials/items'
- if !@invoice.cancelled? && !@invoice.binded? - if @invoice.payable?
.row.semifooter .row.semifooter
.col-md-6-offset-6.text-right= render 'registrar/invoices/partials/banklinks', locals: { payment_channels: PaymentOrders::PAYMENT_METHODS } .col-md-6-offset-6.text-right= render 'registrar/invoices/partials/banklinks', locals: { payment_channels: PaymentOrders::PAYMENT_METHODS }

View file

@ -40,7 +40,6 @@ if con.present? && con.table_exists?('settings')
Setting.save_default(:directo_monthly_number_max, 309999) Setting.save_default(:directo_monthly_number_max, 309999)
Setting.save_default(:directo_monthly_number_last, 309901) Setting.save_default(:directo_monthly_number_last, 309901)
Setting.save_default(:days_to_keep_invoices_active, 30) Setting.save_default(:days_to_keep_invoices_active, 30)
Setting.save_default(:days_to_keep_overdue_invoices_active, 30)
Setting.save_default(:minimum_deposit, 0.0) Setting.save_default(:minimum_deposit, 0.0)
Setting.save_default(:directo_receipt_payment_term, "R") Setting.save_default(:directo_receipt_payment_term, "R")
Setting.save_default(:directo_receipt_product_name, "ETTEM06") Setting.save_default(:directo_receipt_product_name, "ETTEM06")

View file

@ -0,0 +1,5 @@
en:
activerecord:
attributes:
account_activity:
created_at: Receipt date

View file

@ -0,0 +1,5 @@
en:
admin:
invoices:
cancel:
cancelled: Invoice has been cancelled

View file

@ -523,12 +523,10 @@ en:
domain_not_found: 'Domain was not found' domain_not_found: 'Domain was not found'
new_contact: 'New contact' new_contact: 'New contact'
add_deposit: 'Add deposit' add_deposit: 'Add deposit'
amount: 'Amount'
please_pay_the_following_invoice: 'Please pay the following invoice' please_pay_the_following_invoice: 'Please pay the following invoice'
invoice_no: 'Invoice no. %{no}' invoice_no: 'Invoice no. %{no}'
invoice_number: Invoice no. invoice_number: Invoice no.
seller: 'Seller' seller: 'Seller'
prepayment: 'Prepayment'
unpaid: 'Unpaid' unpaid: 'Unpaid'
your_current_account_balance_is: 'Your current account balance is %{balance} %{currency}' your_current_account_balance_is: 'Your current account balance is %{balance} %{currency}'
billing: 'Billing' billing: 'Billing'
@ -588,7 +586,6 @@ en:
refers to the computer that saves and forwards notices related to the Domain Names and their corresponding IP addresses in the Internet. refers to the computer that saves and forwards notices related to the Domain Names and their corresponding IP addresses in the Internet.
account_activity: 'Account activity' account_activity: 'Account activity'
account_activities: 'Account activities' account_activities: 'Account activities'
receipt_date: 'Receipt date'
manual_binding: 'Manual binding' manual_binding: 'Manual binding'
transaction_is_already_binded: 'Transaction is already binded' transaction_is_already_binded: 'Transaction is already binded'
invoice_was_not_found: 'Invoice was not found' invoice_was_not_found: 'Invoice was not found'
@ -614,9 +611,6 @@ en:
certificates: 'Certificates' certificates: 'Certificates'
cancel: 'Cancel' cancel: 'Cancel'
cancelled: 'Cancelled' cancelled: 'Cancelled'
cancel_date: 'Cancel date'
cannot_cancel_paid_invoice: 'Cannot cancel paid invoice'
cannot_cancel_cancelled_invoice: 'Cannot cancel cancelled invoice'
cannot_bind_cancelled_invoice: 'Cannot bind cancelled invoice' cannot_bind_cancelled_invoice: 'Cannot bind cancelled invoice'
minimum_invoice_no: 'Miminum invoice no' minimum_invoice_no: 'Miminum invoice no'
maximum_invoice_no: 'Maximum invoice no' maximum_invoice_no: 'Maximum invoice no'

View file

@ -9,3 +9,6 @@ en:
payment_complete: Credit Card payment Complete payment_complete: Credit Card payment Complete
index: index:
reset_btn: Reset reset_btn: Reset
cancel:
cancelled: Invoice has been cancelled

View file

@ -22,7 +22,7 @@ if @cron_group == 'registry'
end end
every :day, at: '12:10am' do every :day, at: '12:10am' do
runner 'Invoice.cancel_overdue_invoices' rake 'invoices:cancel_overdue'
end end
# TODO # TODO

View file

@ -0,0 +1,5 @@
class RenameInvoiceItemsAmountToQuantity < ActiveRecord::Migration
def change
rename_column :invoice_items, :amount, :quantity
end
end

View file

@ -0,0 +1,5 @@
class AddInvoiceItemsInvoiceIdFk < ActiveRecord::Migration
def change
add_foreign_key :invoice_items, :invoices, name: 'invoice_items_invoice_id_fk'
end
end

View file

@ -0,0 +1,5 @@
class ChangeInvoiceItemsInvoiceIdToNotNull < ActiveRecord::Migration
def change
change_column_null :invoice_items, :invoice_id, false
end
end

View file

@ -0,0 +1,5 @@
class ChangeInvoiceItemsQuantityToNotNull < ActiveRecord::Migration
def change
change_column_null :invoice_items, :quantity, false
end
end

View file

@ -0,0 +1,5 @@
class ChangeInvoiceItemsUnitToNotNull < ActiveRecord::Migration
def change
change_column_null :invoice_items, :unit, false
end
end

View file

@ -0,0 +1,5 @@
class ChangeInvoiceItemsPriceToNotNull < ActiveRecord::Migration
def change
change_column_null :invoice_items, :price, false
end
end

View file

@ -0,0 +1,5 @@
class ChangeInvoicesDueDateToDate < ActiveRecord::Migration
def change
change_column :invoices, :due_date, :date, null: false
end
end

View file

@ -0,0 +1,5 @@
class AddInvoicesIssueDate < ActiveRecord::Migration
def change
add_column :invoices, :issue_date, :date
end
end

View file

@ -0,0 +1,5 @@
class RemoveInvoicesPaidAt < ActiveRecord::Migration
def change
remove_column :invoices, :paid_at
end
end

View file

@ -0,0 +1,5 @@
class RemoveInvoicesPaymentTerm < ActiveRecord::Migration
def change
remove_column :invoices, :payment_term
end
end

View file

@ -1039,11 +1039,11 @@ ALTER SEQUENCE public.epp_sessions_id_seq OWNED BY public.epp_sessions.id;
CREATE TABLE public.invoice_items ( CREATE TABLE public.invoice_items (
id integer NOT NULL, id integer NOT NULL,
invoice_id integer, invoice_id integer NOT NULL,
description character varying NOT NULL, description character varying NOT NULL,
unit character varying, unit character varying NOT NULL,
amount integer, quantity integer NOT NULL,
price numeric(10,2), price numeric(10,2) NOT NULL,
created_at timestamp without time zone, created_at timestamp without time zone,
updated_at timestamp without time zone, updated_at timestamp without time zone,
creator_str character varying, creator_str character varying,
@ -1078,13 +1078,11 @@ CREATE TABLE public.invoices (
id integer NOT NULL, id integer NOT NULL,
created_at timestamp without time zone NOT NULL, created_at timestamp without time zone NOT NULL,
updated_at timestamp without time zone NOT NULL, updated_at timestamp without time zone NOT NULL,
due_date timestamp without time zone NOT NULL, due_date date NOT NULL,
payment_term character varying,
currency character varying NOT NULL, currency character varying NOT NULL,
description character varying, description character varying,
reference_no character varying NOT NULL, reference_no character varying NOT NULL,
vat_rate numeric(4,3), vat_rate numeric(4,3),
paid_at timestamp without time zone,
seller_id integer, seller_id integer,
seller_name character varying NOT NULL, seller_name character varying NOT NULL,
seller_reg_no character varying, seller_reg_no character varying,
@ -1118,7 +1116,8 @@ CREATE TABLE public.invoices (
cancelled_at timestamp without time zone, cancelled_at timestamp without time zone,
total numeric(10,2) NOT NULL, total numeric(10,2) NOT NULL,
in_directo boolean DEFAULT false, in_directo boolean DEFAULT false,
buyer_vat_no character varying buyer_vat_no character varying,
issue_date date
); );
@ -4234,6 +4233,14 @@ ALTER TABLE ONLY public.account_activities
ADD CONSTRAINT fk_rails_d2cc3c2fa9 FOREIGN KEY (price_id) REFERENCES public.prices(id); ADD CONSTRAINT fk_rails_d2cc3c2fa9 FOREIGN KEY (price_id) REFERENCES public.prices(id);
--
-- Name: invoice_items_invoice_id_fk; Type: FK CONSTRAINT; Schema: public; Owner: -
--
ALTER TABLE ONLY public.invoice_items
ADD CONSTRAINT invoice_items_invoice_id_fk FOREIGN KEY (invoice_id) REFERENCES public.invoices(id);
-- --
-- Name: messages_registrar_id_fk; Type: FK CONSTRAINT; Schema: public; Owner: - -- Name: messages_registrar_id_fk; Type: FK CONSTRAINT; Schema: public; Owner: -
-- --
@ -4926,6 +4933,22 @@ INSERT INTO schema_migrations (version) VALUES ('20181001090536');
INSERT INTO schema_migrations (version) VALUES ('20181002090319'); INSERT INTO schema_migrations (version) VALUES ('20181002090319');
INSERT INTO schema_migrations (version) VALUES ('20181017092829');
INSERT INTO schema_migrations (version) VALUES ('20181017153658');
INSERT INTO schema_migrations (version) VALUES ('20181017153812');
INSERT INTO schema_migrations (version) VALUES ('20181017153935');
INSERT INTO schema_migrations (version) VALUES ('20181017154038');
INSERT INTO schema_migrations (version) VALUES ('20181017154143');
INSERT INTO schema_migrations (version) VALUES ('20181017205123');
INSERT INTO schema_migrations (version) VALUES ('20181022100114');
INSERT INTO schema_migrations (version) VALUES ('20181108154921'); INSERT INTO schema_migrations (version) VALUES ('20181108154921');
INSERT INTO schema_migrations (version) VALUES ('20181129150515'); INSERT INTO schema_migrations (version) VALUES ('20181129150515');
@ -4962,3 +4985,7 @@ INSERT INTO schema_migrations (version) VALUES ('20190102115333');
INSERT INTO schema_migrations (version) VALUES ('20190102144032'); INSERT INTO schema_migrations (version) VALUES ('20190102144032');
INSERT INTO schema_migrations (version) VALUES ('20190311111718');
INSERT INTO schema_migrations (version) VALUES ('20190312211614');

View file

@ -0,0 +1,16 @@
namespace :data_migrations do
task populate_invoice_issue_date: [:environment] do
processed_invoice_count = 0
Invoice.transaction do
Invoice.find_each do |invoice|
invoice_issue_date = invoice.created_at.to_date
invoice.update!(issue_date: invoice_issue_date)
processed_invoice_count += 1
end
end
puts "Invoices processed: #{processed_invoice_count}"
end
end

View file

@ -0,0 +1,13 @@
namespace :invoices do
task cancel_overdue: :environment do
cancelled_invoice_count = 0
canceller = OverdueInvoiceCanceller.new
canceller.cancel do |invoice|
puts "Invoice ##{invoice.id} is cancelled"
cancelled_invoice_count += 1
end
puts "Cancelled total: #{cancelled_invoice_count}"
end
end

View file

@ -1,18 +0,0 @@
FactoryBot.define do
factory :invoice do
buyer_name 'Registrar 1'
currency { 'EUR' }
due_date { Time.zone.now.to_date + 1.day }
seller_iban { '123' }
seller_name { 'EIS' }
seller_city { 'Tallinn' }
seller_street { 'Paldiski mnt. 123' }
vat_rate 0.2
buyer { FactoryBot.create(:registrar) }
sequence(:reference_no) { |n| "1234#{n}" }
after :build do |invoice|
invoice.invoice_items << FactoryBot.create_pair(:invoice_item)
end
end
end

View file

@ -1,8 +0,0 @@
FactoryBot.define do
factory :invoice_item do
description { 'add money' }
unit 1
amount 1
price 150
end
end

View file

@ -1,69 +0,0 @@
require 'rails_helper'
describe Invoice do
context 'with invalid attribute' do
before :all do
@invoice = Invoice.new
end
it 'should not be valid' do
@invoice.valid?
@invoice.errors.full_messages.should match_array([
"Buyer name is missing",
"Currency is missing",
"Due date is missing",
"Invoice items is missing",
"Seller iban is missing",
"Seller name is missing",
])
end
it 'should not have any versions' do
@invoice.versions.should == []
end
end
context 'with valid attributes' do
before :all do
@invoice = create(:invoice)
end
it 'should be valid' do
@invoice.valid?
@invoice.errors.full_messages.should match_array([])
end
it 'should be valid twice' do
@invoice = create(:invoice)
@invoice.valid?
@invoice.errors.full_messages.should match_array([])
end
it 'should be valid twice' do
@invoice = create(:invoice)
@invoice.valid?
@invoice.errors.full_messages.should match_array([])
end
it 'should return correct addresses' do
@invoice = create(:invoice)
@invoice.seller_address.should == 'Paldiski mnt. 123, Tallinn'
end
it 'should cancel overdue invoices' do
create(:invoice, created_at: Time.zone.now - 35.days, due_date: Time.zone.now - 30.days)
Invoice.cancel_overdue_invoices
Invoice.where(cancelled_at: nil).count.should == 1
end
it 'should have one version' do
with_versioning do
@invoice.versions.should == []
@invoice.buyer_name = 'New name'
@invoice.save
@invoice.errors.full_messages.should match_array([])
@invoice.versions.size.should == 1
end
end
end
end

View file

@ -1,7 +1,5 @@
DEFAULTS: &DEFAULTS
account_id: 1
one: one:
<<: *DEFAULTS account: cash
invoice: paid invoice: one
bank_transaction: one bank_transaction: one
created_at: <%= Time.zone.parse('2010-07-05 10:00') %>

View file

@ -4,6 +4,12 @@ cash:
currency: EUR currency: EUR
registrar: bestnames registrar: bestnames
two:
account_type: cash
balance: 100
currency: EUR
registrar: goodnames
not_in_use_cash: not_in_use_cash:
account_type: cash account_type: cash
balance: 0 balance: 0

View file

@ -1,13 +1,6 @@
one: one:
description: Acme services description: Acme services
price: 5 price: 5
amount: 1 quantity: 1
unit: pc unit: pc
invoice: valid invoice: one
two:
description: Acme services
price: 5
amount: 2
unit: pc
invoice: valid

View file

@ -1,5 +1,5 @@
DEFAULTS: &DEFAULTS one:
created_at: <%= Date.parse '2010-07-05' %> issue_date: <%= Date.parse '2010-07-05' %>
due_date: <%= Date.parse '2010-07-06' %> due_date: <%= Date.parse '2010-07-06' %>
currency: EUR currency: EUR
seller_name: John Doe seller_name: John Doe
@ -10,30 +10,15 @@ DEFAULTS: &DEFAULTS
total: 16.50 total: 16.50
reference_no: 13 reference_no: 13
valid:
<<: *DEFAULTS
exported:
<<: *DEFAULTS
in_directo: true
cancelled:
<<: *DEFAULTS
cancelled_at: <%= Date.parse '2010-07-05' %>
paid:
<<: *DEFAULTS
total: 1
outstanding:
<<: *DEFAULTS
due_date: <%= Date.parse '2010-07-04' %>
overdue:
<<: *DEFAULTS
due_date: <%= Date.parse '2010-07-03' %>
for_payments_test: for_payments_test:
<<: *DEFAULTS
total: 12.00
number: 1 number: 1
issue_date: <%= Date.parse '2010-07-05' %>
due_date: <%= Date.parse '2010-07-06' %>
currency: EUR
seller_name: John Doe
seller_iban: 1234
buyer: bestnames
buyer_name: Jane Doe
vat_rate: 0.1
reference_no: 13
total: 12.00

View file

@ -35,7 +35,7 @@ class RegenerateRegistrarReferenceNumbersTaskTest < ActiveSupport::TestCase
def test_keeps_iso_reference_number_on_the_invoice_unchanged def test_keeps_iso_reference_number_on_the_invoice_unchanged
registrar = registrars(:bestnames) registrar = registrars(:bestnames)
registrar.update_column(:reference_no, 'RF1111') registrar.update_column(:reference_no, 'RF1111')
invoice = registrar.invoices.first invoice = invoices(:one)
invoice.update!(reference_no: 'RF2222') invoice.update!(reference_no: 'RF2222')
capture_io { run_task } capture_io { run_task }

View file

@ -0,0 +1,11 @@
require 'test_helper'
class AccountActivityTest < ActiveSupport::TestCase
setup do
@account_activity = account_activities(:one)
end
def test_fixture_is_valid
assert @account_activity.valid?
end
end

View file

@ -2,7 +2,7 @@ require 'test_helper'
class BankTransactionTest < ActiveSupport::TestCase class BankTransactionTest < ActiveSupport::TestCase
def test_matches_against_invoice_reference_number def test_matches_against_invoice_reference_number
invoices(:valid).update!(number: '2222', total: 10, reference_no: '1111') invoices(:one).update!(account_activity: nil, number: '2222', total: 10, reference_no: '1111')
transaction = BankTransaction.new(description: 'invoice #2222', sum: 10, reference_no: '1111') transaction = BankTransaction.new(description: 'invoice #2222', sum: 10, reference_no: '1111')
assert_difference 'AccountActivity.count' do assert_difference 'AccountActivity.count' do
@ -20,7 +20,7 @@ class BankTransactionTest < ActiveSupport::TestCase
end end
def test_underpayment_is_not_matched_with_invoice def test_underpayment_is_not_matched_with_invoice
invoices(:valid).update!(number: '2222', total: 10) invoices(:one).update!(account_activity: nil, number: '2222', total: 10)
transaction = BankTransaction.new(sum: 9) transaction = BankTransaction.new(sum: 9)
assert_no_difference 'AccountActivity.count' do assert_no_difference 'AccountActivity.count' do
@ -30,7 +30,7 @@ class BankTransactionTest < ActiveSupport::TestCase
end end
def test_overpayment_is_not_matched_with_invoice def test_overpayment_is_not_matched_with_invoice
invoices(:valid).update!(number: '2222', total: 10) invoices(:one).update!(account_activity: nil, number: '2222', total: 10)
transaction = BankTransaction.new(sum: 11) transaction = BankTransaction.new(sum: 11)
assert_no_difference 'AccountActivity.count' do assert_no_difference 'AccountActivity.count' do
@ -40,7 +40,7 @@ class BankTransactionTest < ActiveSupport::TestCase
end end
def test_cancelled_invoice_is_not_matched def test_cancelled_invoice_is_not_matched
invoices(:valid).update!(number: '2222', total: 10, cancelled_at: '2010-07-05') invoices(:one).update!(account_activity: nil, number: '2222', total: 10, cancelled_at: '2010-07-05')
transaction = BankTransaction.new(sum: 10) transaction = BankTransaction.new(sum: 10)
assert_no_difference 'AccountActivity.count' do assert_no_difference 'AccountActivity.count' do

View file

@ -0,0 +1,69 @@
require 'test_helper'
class CancellableInvoiceTest < ActiveSupport::TestCase
setup do
@invoice = invoices(:one)
end
def test_non_cancelled_scope_returns_non_cancelled_invoices
@invoice.update!(cancelled_at: nil)
assert Invoice.non_cancelled.include?(@invoice), 'Should return cancelled invoice'
end
def test_non_cancelled_scope_does_not_return_cancelled_invoices
@invoice.update!(cancelled_at: '2010-07-05')
assert_not Invoice.non_cancelled.include?(@invoice), 'Should not return cancelled invoice'
end
def test_cancellable_when_unpaid_and_not_yet_cancelled
@invoice.account_activity = nil
@invoice.cancelled_at = nil
assert @invoice.cancellable?
end
def test_not_cancellable_when_paid
assert @invoice.paid?
assert_not @invoice.cancellable?
end
def test_not_cancellable_when_already_cancelled
@invoice.cancelled_at = '2010-07-05'
assert_not @invoice.cancellable?
end
def test_cancels_an_invoice
travel_to Time.zone.parse('2010-07-05 08:00')
@invoice.account_activity = nil
assert @invoice.cancellable?
assert_nil @invoice.cancelled_at
@invoice.cancel
@invoice.reload
assert @invoice.cancelled?
assert_equal Time.zone.parse('2010-07-05 08:00'), @invoice.cancelled_at
end
def test_throws_an_exception_when_trying_to_cancel_already_cancelled_invoice
@invoice.cancelled_at = '2010-07-05'
e = assert_raise do
@invoice.cancel
end
assert_equal 'Invoice cannot be cancelled', e.message
end
def test_not_cancelled
@invoice.cancelled_at = nil
assert @invoice.not_cancelled?
assert_not @invoice.cancelled?
end
def test_cancelled
@invoice.cancelled_at = '2010-07-05'
assert @invoice.cancelled?
assert_not @invoice.not_cancelled?
end
end

View file

@ -0,0 +1,53 @@
require 'test_helper'
class InvoiceTest < ActiveSupport::TestCase
setup do
@invoice = invoices(:one)
end
def test_unpaid_scope_returns_unpaid_invoices
@invoice.account_activity = nil
assert Invoice.unpaid.include?(@invoice), 'Should return unpaid invoice'
end
def test_unpaid_scope_does_not_return_paid_invoices
assert @invoice.paid?
assert_not Invoice.unpaid.include?(@invoice), 'Should not return paid invoice'
end
def test_paid_when_there_is_an_account_activity
assert @invoice.account_activity
assert @invoice.paid?
assert_not @invoice.unpaid?
end
def test_unpaid_when_there_is_no_account_activity
@invoice.account_activity = nil
assert @invoice.unpaid?
assert_not @invoice.paid?
end
def test_payable_when_unpaid_and_not_cancelled
@invoice.account_activity = nil
@invoice.cancelled_at = nil
assert @invoice.payable?
end
def test_not_payable_when_already_paid
assert @invoice.paid?
assert_not @invoice.payable?
end
def test_not_payable_when_cancelled
@invoice.cancelled_at = '2010-07-05'
assert_not @invoice.payable?
end
def test_returns_receipt_date
assert_equal Time.zone.parse('2010-07-05 10:00'), @invoice.account_activity.created_at
assert_equal Date.parse('2010-07-05'), @invoice.receipt_date
end
end

View file

@ -0,0 +1,8 @@
require 'test_helper'
class InvoiceItemTest < ActiveSupport::TestCase
def test_calculates_sum_without_vat
invoice_item = InvoiceItem.new(price: 5, quantity: 2)
assert_equal 10, invoice_item.item_sum_without_vat
end
end

View file

@ -2,11 +2,40 @@ require 'test_helper'
class InvoiceTest < ActiveSupport::TestCase class InvoiceTest < ActiveSupport::TestCase
setup do setup do
@invoice = invoices(:valid) @invoice = invoices(:one)
end end
def test_valid def test_fixture_is_valid
assert @invoice.valid? assert @invoice.valid?, proc { @invoice.errors.full_messages }
end
def test_overdue_scope_returns_unpaid_uncancelled_invoices_with_past_due_date
travel_to Time.zone.parse('2010-07-05')
@invoice.update!(account_activity: nil, cancelled_at: nil, due_date: '2010-07-04')
assert Invoice.overdue.include?(@invoice), 'Should return overdue invoice'
end
def test_overdue_scope_does_not_return_paid_invoices
assert @invoice.paid?
assert_not Invoice.overdue.include?(@invoice), 'Should not return paid invoice'
end
def test_overdue_scope_does_not_return_cancelled_invoices
@invoice.update!(cancelled_at: '2010-07-05')
assert_not Invoice.overdue.include?(@invoice), 'Should not return cancelled invoice'
end
def test_overdue_scope_does_not_return_invoices_with_due_due_of_today_or_in_the_future
travel_to Time.zone.parse('2010-07-05')
@invoice.update!(due_date: '2010-07-05')
assert_not Invoice.overdue.include?(@invoice), 'Should not return non-overdue invoice'
end
def test_invalid_without_issue_date
@invoice.issue_date = nil
assert @invoice.invalid?
end end
def test_optional_vat_rate def test_optional_vat_rate
@ -30,7 +59,7 @@ class InvoiceTest < ActiveSupport::TestCase
def test_serializes_and_deserializes_vat_rate def test_serializes_and_deserializes_vat_rate
invoice = @invoice.dup invoice = @invoice.dup
invoice.invoice_items = @invoice.invoice_items invoice.items = @invoice.items
invoice.vat_rate = BigDecimal('25.5') invoice.vat_rate = BigDecimal('25.5')
invoice.save! invoice.save!
invoice.reload invoice.reload
@ -42,7 +71,7 @@ class InvoiceTest < ActiveSupport::TestCase
invoice = @invoice.dup invoice = @invoice.dup
invoice.vat_rate = nil invoice.vat_rate = nil
invoice.buyer = registrar invoice.buyer = registrar
invoice.invoice_items = @invoice.invoice_items invoice.items = @invoice.items
registrar.stub(:effective_vat_rate, BigDecimal(55)) do registrar.stub(:effective_vat_rate, BigDecimal(55)) do
invoice.save! invoice.save!
@ -59,7 +88,9 @@ class InvoiceTest < ActiveSupport::TestCase
end end
def test_calculates_vat_amount def test_calculates_vat_amount
assert_equal BigDecimal('1.5'), @invoice.vat_amount invoice_item = InvoiceItem.new(price: 25, quantity: 2)
invoice = Invoice.new(vat_rate: 10, items: [invoice_item, invoice_item.dup])
assert_equal 10, invoice.vat_amount
end end
def test_vat_amount_is_zero_when_vat_rate_is_blank def test_vat_amount_is_zero_when_vat_rate_is_blank
@ -69,7 +100,7 @@ class InvoiceTest < ActiveSupport::TestCase
def test_calculates_subtotal def test_calculates_subtotal
line_item = InvoiceItem.new line_item = InvoiceItem.new
invoice = Invoice.new(invoice_items: [line_item, line_item]) invoice = Invoice.new(items: [line_item, line_item])
line_item.stub(:item_sum_without_vat, BigDecimal('2.5')) do line_item.stub(:item_sum_without_vat, BigDecimal('2.5')) do
assert_equal BigDecimal(5), invoice.subtotal assert_equal BigDecimal(5), invoice.subtotal
@ -84,7 +115,7 @@ class InvoiceTest < ActiveSupport::TestCase
line_item = InvoiceItem.new line_item = InvoiceItem.new
invoice = Invoice.new invoice = Invoice.new
invoice.vat_rate = 10 invoice.vat_rate = 10
invoice.invoice_items = [line_item, line_item] invoice.items = [line_item, line_item]
line_item.stub(:item_sum_without_vat, BigDecimal('2.5')) do line_item.stub(:item_sum_without_vat, BigDecimal('2.5')) do
assert_equal BigDecimal('5.50'), invoice.total assert_equal BigDecimal('5.50'), invoice.total
@ -102,8 +133,25 @@ class InvoiceTest < ActiveSupport::TestCase
invoice = @invoice.dup invoice = @invoice.dup
invoice.buyer_vat_no = nil invoice.buyer_vat_no = nil
invoice.buyer = registrar invoice.buyer = registrar
invoice.invoice_items = @invoice.invoice_items invoice.items = @invoice.items
invoice.save! invoice.save!
assert_equal 'US1234', invoice.buyer_vat_no assert_equal 'US1234', invoice.buyer_vat_no
end end
def test_invalid_without_invoice_items
@invoice.items.clear
assert @invoice.invalid?
end
def test_iterates_over_invoice_items
invoice = Invoice.new(items: [InvoiceItem.new(description: 'test')])
iteration_count = 0
invoice.each do |invoice_item|
assert_equal 'test', invoice_item.description
iteration_count += 1
end
assert_equal 1, iteration_count
end
end end

View file

@ -10,8 +10,8 @@ class BankLinkTest < ActiveSupport::TestCase
@invoice = invoices(:for_payments_test) @invoice = invoices(:for_payments_test)
invoice_item = invoice_items(:one) invoice_item = invoice_items(:one)
@invoice.invoice_items << invoice_item @invoice.items << invoice_item
@invoice.invoice_items << invoice_item @invoice.items << invoice_item
travel_to '2018-04-01 00:30 +0300' travel_to '2018-04-01 00:30 +0300'
create_new_bank_link create_new_bank_link

View file

@ -7,8 +7,8 @@ class EveryPayTest < ActiveSupport::TestCase
@invoice = invoices(:for_payments_test) @invoice = invoices(:for_payments_test)
invoice_item = invoice_items(:one) invoice_item = invoice_items(:one)
@invoice.invoice_items << invoice_item @invoice.items << invoice_item
@invoice.invoice_items << invoice_item @invoice.items << invoice_item
params = { params = {
response: response:

View file

@ -83,4 +83,17 @@ class RegistrarTest < ActiveSupport::TestCase
registrars(:goodnames).update!(reference_no: '1234') registrars(:goodnames).update!(reference_no: '1234')
end end
end end
def test_issues_new_invoice
travel_to Time.zone.parse('2010-07-05')
@original_days_to_keep_invoices_active_setting = Setting.days_to_keep_invoices_active
Setting.days_to_keep_invoices_active = 10
invoice = @registrar.issue_prepayment_invoice(100)
assert_equal Date.parse('2010-07-05'), invoice.issue_date
assert_equal Date.parse('2010-07-15'), invoice.due_date
Setting.days_to_keep_invoices_active = @original_days_to_keep_invoices_active_setting
end
end end

View file

@ -0,0 +1,62 @@
require 'test_helper'
class OverdueInvoiceCancellerTest < ActiveSupport::TestCase
setup do
@invoice = invoices(:one)
end
def test_default_delay
assert_equal 30.days, OverdueInvoiceCanceller.default_delay
end
def test_uses_default_delay_when_not_configured
Setting.days_to_keep_overdue_invoices_active = nil
canceller = OverdueInvoiceCanceller.new
assert_equal OverdueInvoiceCanceller.default_delay, canceller.delay
end
def test_uses_configured_delay
Setting.days_to_keep_overdue_invoices_active = 1
canceller = OverdueInvoiceCanceller.new
assert_equal 1.day, canceller.delay
end
def test_cancels_overdue_invoices
travel_to Time.zone.parse('2010-07-05')
@invoice.update!(account_activity: nil, cancelled_at: nil, due_date: '2010-07-03')
assert @invoice.cancellable?
canceller = OverdueInvoiceCanceller.new(delay: 1.day)
canceller.cancel
@invoice.reload
assert @invoice.cancelled?
end
def test_yields_cancelled_invoices
travel_to Time.zone.parse('2010-07-05')
@invoice.update!(account_activity: nil, cancelled_at: nil, due_date: '2010-07-03')
assert @invoice.cancellable?
canceller = OverdueInvoiceCanceller.new(delay: 1.day)
iteration_count = 0
canceller.cancel do |invoice|
assert_equal @invoice, invoice
iteration_count += 1
end
assert_equal 1, iteration_count
end
def test_keeps_not_overdue_invoices_intact
travel_to Time.zone.parse('2010-07-05')
@invoice.update!(account_activity: nil, cancelled_at: nil, due_date: '2010-07-04')
assert @invoice.cancellable?
canceller = OverdueInvoiceCanceller.new(delay: 1.day)
canceller.cancel
@invoice.reload
assert @invoice.not_cancelled?
end
end

View file

@ -0,0 +1,20 @@
require 'test_helper'
class AdminAreaInvoicesTest < ApplicationSystemTestCase
setup do
sign_in users(:admin)
@invoice = invoices(:one)
end
def test_cancels_an_invoice
@invoice.account_activity = nil
assert @invoice.cancellable?
visit admin_invoice_url(@invoice)
click_on 'Cancel'
@invoice.reload
assert @invoice.cancelled?
assert_text 'Invoice has been cancelled'
end
end

View file

@ -1,12 +1,12 @@
require 'test_helper' require 'test_helper'
class ListInvoicesTest < ApplicationSystemTestCase class ListInvoicesTest < ApplicationSystemTestCase
def setup setup do
super
@user = users(:api_bestnames) @user = users(:api_bestnames)
@registrar_invoices = @user.registrar.invoices
sign_in @user sign_in @user
@invoice = invoices(:one)
eliminate_effect_of_other_invoices
end end
def test_show_balance def test_show_balance
@ -14,15 +14,31 @@ class ListInvoicesTest < ApplicationSystemTestCase
assert_text "Your current account balance is 100,00 EUR" assert_text "Your current account balance is 100,00 EUR"
end end
def test_show_multiple_invoices def test_show_invoices_of_current_registrar
@invoices = invoices registrar = registrars(:bestnames)
@registrar_invoices = [] @user.update!(registrar: registrar)
@invoices.each do |invoice| @invoice.update!(seller: registrar)
@registrar_invoices << invoice
visit registrar_invoices_url
assert_css '.invoice'
end end
visit registrar_invoices_path def test_do_not_show_invoices_of_other_registrars
assert_text "Unpaid", count: 5 registrar = registrars(:goodnames)
assert_text "Invoice no.", count: 7 @user.update!(registrar: registrar)
@invoice.update!(seller: registrar)
visit registrar_invoices_url
assert_no_css '.invoice'
end
private
def eliminate_effect_of_other_invoices
Invoice.connection.disable_referential_integrity do
Invoice.delete_all("id != #{@invoice.id}")
end
end end
end end

View file

@ -12,8 +12,8 @@ class PaymentCallbackTest < ApplicationSystemTestCase
@invoice = invoices(:for_payments_test) @invoice = invoices(:for_payments_test)
invoice_item = invoice_items(:one) invoice_item = invoice_items(:one)
@invoice.invoice_items << invoice_item @invoice.items << invoice_item
@invoice.invoice_items << invoice_item @invoice.items << invoice_item
@user.registrar.invoices << @invoice @user.registrar.invoices << @invoice
end end

View file

@ -12,8 +12,8 @@ class PaymentReturnTest < ApplicationSystemTestCase
@invoice = invoices(:for_payments_test) @invoice = invoices(:for_payments_test)
invoice_item = invoice_items(:one) invoice_item = invoice_items(:one)
@invoice.invoice_items << invoice_item @invoice.items << invoice_item
@invoice.invoice_items << invoice_item @invoice.items << invoice_item
@user.registrar.invoices << @invoice @user.registrar.invoices << @invoice
end end

View file

@ -0,0 +1,20 @@
require 'test_helper'
class RegistrarAreaInvoicesTest < ApplicationSystemTestCase
setup do
sign_in users(:api_bestnames)
@invoice = invoices(:one)
end
def test_cancels_an_invoice
@invoice.account_activity = nil
assert @invoice.cancellable?
visit registrar_invoice_url(@invoice)
click_on 'Cancel'
@invoice.reload
assert @invoice.cancelled?
assert_text 'Invoice has been cancelled'
end
end

View file

@ -0,0 +1,37 @@
require 'test_helper'
class PopulateInvoiceIssueDateTaskTest < ActiveSupport::TestCase
setup do
@invoice = invoices(:one)
end
def test_populates_invoice_issue_date
eliminate_effect_of_other_invoices
@invoice.update_columns(issue_date: nil, created_at: Time.zone.parse('2010-07-05'))
assert_nil @invoice.read_attribute(:issue_date)
capture_io { run_task }
@invoice.reload
assert_equal Date.parse('2010-07-05'), @invoice.issue_date
end
def test_outputs_results
eliminate_effect_of_other_invoices
@invoice.update_columns(issue_date: nil, created_at: Time.zone.parse('2010-07-05'))
assert_output("Invoices processed: 1\n") { run_task }
end
private
def eliminate_effect_of_other_invoices
Invoice.connection.disable_referential_integrity do
Invoice.delete_all("id != #{@invoice.id}")
end
end
def run_task
Rake::Task['data_migrations:populate_invoice_issue_date'].execute
end
end

View file

@ -0,0 +1,41 @@
require 'test_helper'
class CancelOverdueInvoicesTaskTest < ActiveSupport::TestCase
setup do
@invoice = invoices(:one)
eliminate_effect_of_other_invoices
end
def test_cancels_overdue_invoices
@invoice.update!(account_activity: nil, cancelled_at: nil, due_date: '2010-07-05')
assert @invoice.cancellable?
capture_io do
run_task
end
@invoice.reload
assert @invoice.cancelled?
end
def test_output
@invoice.update!(account_activity: nil, cancelled_at: nil, due_date: '2010-07-05')
assert @invoice.cancellable?
assert_output "Invoice ##{@invoice.id} is cancelled\nCancelled total: 1\n" do
run_task
end
end
private
def eliminate_effect_of_other_invoices
Invoice.connection.disable_referential_integrity do
Invoice.delete_all("id != #{@invoice.id}")
end
end
def run_task
Rake::Task['invoices:cancel_overdue'].execute
end
end