diff --git a/app/jobs/delete_monthly_invoices_job.rb b/app/jobs/delete_monthly_invoices_job.rb
new file mode 100644
index 000000000..8a38d0d14
--- /dev/null
+++ b/app/jobs/delete_monthly_invoices_job.rb
@@ -0,0 +1,10 @@
+class DeleteMonthlyInvoicesJob < ApplicationJob
+ queue_as :default
+
+ def perform
+ @month = Time.zone.now - 1.month
+ invoices = Invoice.where(monthly_invoice: true, issue_date: @month.end_of_month.to_date,
+ in_directo: false, e_invoice_sent_at: nil)
+ invoices.delete_all
+ end
+end
\ No newline at end of file
diff --git a/app/jobs/send_e_invoice_job.rb b/app/jobs/send_e_invoice_job.rb
index 33a2745c6..4e77926b0 100644
--- a/app/jobs/send_e_invoice_job.rb
+++ b/app/jobs/send_e_invoice_job.rb
@@ -17,7 +17,7 @@ class SendEInvoiceJob < ApplicationJob
def need_to_process_invoice?(invoice:, payable:)
logger.info "Checking if need to process e-invoice #{invoice}, payable: #{payable}"
return false if invoice.blank?
- return false if invoice.do_not_send_e_invoice? && payable
+ return false if invoice.do_not_send_e_invoice? && (invoice.monthly_invoice ? true : payable)
true
end
diff --git a/app/jobs/send_monthly_invoices_job.rb b/app/jobs/send_monthly_invoices_job.rb
new file mode 100644
index 000000000..ddb4bd80c
--- /dev/null
+++ b/app/jobs/send_monthly_invoices_job.rb
@@ -0,0 +1,155 @@
+class SendMonthlyInvoicesJob < ApplicationJob
+ queue_as :default
+
+ def perform(dry: false)
+ @dry = dry
+ @month = Time.zone.now - 1.month
+ @directo_client = new_directo_client
+ @min_directo_num = Setting.directo_monthly_number_min.presence.try(:to_i)
+ @max_directo_num = Setting.directo_monthly_number_max.presence.try(:to_i)
+
+ send_monthly_invoices
+ end
+
+ def new_directo_client
+ DirectoApi::Client.new(ENV['directo_invoice_url'], Setting.directo_sales_agent,
+ Setting.directo_receipt_payment_term)
+ end
+
+ def send_monthly_invoices
+ Registrar.where.not(test_registrar: true).find_each do |registrar|
+ next unless registrar.cash_account
+
+ summary = registrar.monthly_summary(month: @month)
+ next if summary.nil?
+
+ invoice = registrar.monthly_invoice(month: @month) || create_invoice(summary, registrar)
+ next if invoice.nil? || @dry
+
+ InvoiceMailer.invoice_email(invoice: invoice,
+ recipient: registrar.billing_email)
+ .deliver_now
+
+ SendEInvoiceJob.set(wait: 1.minute).perform_now(invoice.id, payable: false)
+
+ next if invoice.in_directo
+
+ Rails.logger.info("[DIRECTO] Trying to send monthly invoice #{invoice.number}")
+ @directo_client = new_directo_client
+ directo_invoices = @directo_client.invoices.add_with_schema(invoice: summary,
+ schema: 'summary')
+ next unless directo_invoices.size.positive?
+
+ directo_invoices.last.number = invoice.number
+ sync_with_directo
+ end
+ end
+
+ def create_invoice(summary, registrar)
+ vat_rate = ::Invoice::VatRateCalculator.new(registrar: registrar).calculate
+ invoice = Invoice.new(
+ number: assign_monthly_number,
+ issue_date: summary['date'].to_date,
+ due_date: summary['date'].to_date,
+ currency: 'EUR',
+ description: I18n.t('invoice.monthly_invoice_description'),
+ seller_name: Setting.registry_juridical_name,
+ seller_reg_no: Setting.registry_reg_no,
+ seller_iban: Setting.registry_iban,
+ seller_bank: Setting.registry_bank,
+ seller_swift: Setting.registry_swift,
+ seller_vat_no: Setting.registry_vat_no,
+ seller_country_code: Setting.registry_country_code,
+ seller_state: Setting.registry_state,
+ seller_street: Setting.registry_street,
+ seller_city: Setting.registry_city,
+ seller_zip: Setting.registry_zip,
+ seller_phone: Setting.registry_phone,
+ seller_url: Setting.registry_url,
+ seller_email: Setting.registry_email,
+ seller_contact_name: Setting.registry_invoice_contact,
+ buyer: registrar,
+ buyer_name: registrar.name,
+ buyer_reg_no: registrar.reg_no,
+ buyer_country_code: registrar.address_country_code,
+ buyer_state: registrar.address_state,
+ buyer_street: registrar.address_street,
+ buyer_city: registrar.address_city,
+ buyer_zip: registrar.address_zip,
+ buyer_phone: registrar.phone,
+ buyer_url: registrar.website,
+ buyer_email: registrar.email,
+ reference_no: registrar.reference_no,
+ vat_rate: vat_rate,
+ monthly_invoice: true,
+ metadata: { items: summary['invoice_lines'] },
+ total: 0
+ )
+ return unless invoice.save!
+
+ update_directo_number(num: invoice.number)
+ invoice
+ end
+
+ def sync_with_directo
+ invoices_xml = @directo_client.invoices.as_xml
+
+ Rails.logger.info("[Directo] - attempting to send following XML:\n #{invoices_xml}")
+
+ res = @directo_client.invoices.deliver(ssl_verify: false)
+ process_directo_response(res.body, invoices_xml)
+ rescue SocketError, Errno::ECONNREFUSED, Timeout::Error, Errno::EINVAL, Errno::ECONNRESET,
+ EOFError, Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError, Net::ProtocolError
+ Rails.logger.info('[Directo] Failed to communicate via API')
+ end
+
+ def assign_monthly_numbers
+ invoices_count = @directo_client.invoices.count
+ last_directo_num = [Setting.directo_monthly_number_last.presence.try(:to_i),
+ @min_directo_num].compact.max || 0
+ raise 'Directo Counter is out of period!' if directo_counter_exceedable?(invoices_count,
+ last_directo_num)
+
+ @directo_client.invoices.each do |inv|
+ last_directo_num += 1
+ inv.number = last_directo_num
+ end
+ end
+
+ def assign_monthly_number
+ last_directo_num = [Setting.directo_monthly_number_last.presence.try(:to_i),
+ @min_directo_num].compact.max || 0
+ raise 'Directo Counter is out of period!' if directo_counter_exceedable?(1, last_directo_num)
+
+ last_directo_num + 1
+ end
+
+ def directo_counter_exceedable?(invoices_count, last_directo_num)
+ return true if @max_directo_num && @max_directo_num < (last_directo_num + invoices_count)
+
+ false
+ end
+
+ def process_directo_response(body, req)
+ Rails.logger.info "[Directo] - Responded with body: #{body}"
+ Nokogiri::XML(body).css('Result').each do |res|
+ inv = Invoice.find_by(number: res.attributes['docid'].value.to_i)
+ mark_invoice_as_sent(res: res, req: req, invoice: inv)
+ end
+ end
+
+ def mark_invoice_as_sent(res:, req:, invoice: nil)
+ directo_record = Directo.new(response: res.as_json.to_h,
+ request: req, invoice_number: res.attributes['docid'].value.to_i)
+ directo_record.item = invoice
+ invoice.update(in_directo: true)
+
+ directo_record.save!
+ end
+
+ def update_directo_number(num:)
+ return unless num.to_i > Setting.directo_monthly_number_last.to_i
+
+ Setting.directo_monthly_number_last = num.to_i
+ end
+end
diff --git a/app/mailers/invoice_mailer.rb b/app/mailers/invoice_mailer.rb
index a9d544d63..95b7fefd6 100644
--- a/app/mailers/invoice_mailer.rb
+++ b/app/mailers/invoice_mailer.rb
@@ -4,6 +4,7 @@ class InvoiceMailer < ApplicationMailer
subject = default_i18n_subject(invoice_number: invoice.number)
subject << I18n.t('invoice.already_paid') if paid
+ subject << I18n.t('invoice.monthly_invoice') if invoice.monthly_invoice
attachments["invoice-#{invoice.number}.pdf"] = invoice.as_pdf
mail(to: recipient, subject: subject)
end
diff --git a/app/models/concerns/registrar/book_keeping.rb b/app/models/concerns/registrar/book_keeping.rb
index 76535ce12..4431df9b8 100644
--- a/app/models/concerns/registrar/book_keeping.rb
+++ b/app/models/concerns/registrar/book_keeping.rb
@@ -55,6 +55,11 @@ module Registrar::BookKeeping
.where(activity_type: [AccountActivity::CREATE, AccountActivity::RENEW])
end
+ def monthly_invoice(month:)
+ invoices.where(monthly_invoice: true, issue_date: month.end_of_month.to_date,
+ cancelled_at: nil).first
+ end
+
def new_monthly_invoice_line(activity:, duration: nil)
price = load_price(activity)
line = {
@@ -68,7 +73,7 @@ module Registrar::BookKeeping
def finalize_invoice_line(line, price:, activity:, duration:)
yearly = price.duration.in_years.to_i >= 1
- line['price'] = yearly ? (price.price.amount / price.duration.in_years.to_i) : price.price.amount
+ line['price'] = yearly ? (price.price.amount / price.duration.in_years.to_i).to_f : price.price.amount.to_f
line['description'] = description_in_language(price: price, yearly: yearly)
add_product_timeframe(line: line, activity: activity, duration: duration) if duration.present? && (duration > 1)
diff --git a/app/models/invoice.rb b/app/models/invoice.rb
index b7e60abfb..327a107cd 100644
--- a/app/models/invoice.rb
+++ b/app/models/invoice.rb
@@ -32,11 +32,14 @@ class Invoice < ApplicationRecord
# rubocop:enable Layout/LineLength
# rubocop:enable Style/MultilineBlockLayout
validates :due_date, :currency, :seller_name,
- :seller_iban, :buyer_name, :items, presence: true
+ :seller_iban, :buyer_name, presence: true
+ validates :items, presence: true, unless: -> { monthly_invoice }
before_create :set_invoice_number
before_create :calculate_total, unless: :total?
before_create :apply_default_buyer_vat_no, unless: :buyer_vat_no?
+ skip_callback :create, :before, :set_invoice_number, if: -> { monthly_invoice }
+ skip_callback :create, :before, :calculate_total, if: -> { monthly_invoice }
attribute :vat_rate, ::Type::VatRate.new
@@ -118,7 +121,7 @@ class Invoice < ApplicationRecord
end
def subtotal
- items.map(&:item_sum_without_vat).reduce(:+)
+ items.map(&:item_sum_without_vat).reduce(:+) || 0
end
def vat_amount
@@ -131,7 +134,11 @@ class Invoice < ApplicationRecord
end
def each(&block)
- items.each(&block)
+ if monthly_invoice
+ metadata['items'].map { |el| OpenStruct.new(el) }.each(&block)
+ else
+ items.each(&block)
+ end
end
def as_pdf
diff --git a/app/models/invoice/e_invoice_generator.rb b/app/models/invoice/e_invoice_generator.rb
index 2361656a7..28373b1fb 100644
--- a/app/models/invoice/e_invoice_generator.rb
+++ b/app/models/invoice/e_invoice_generator.rb
@@ -46,10 +46,17 @@ class Invoice
i.price = invoice_item.price
i.quantity = invoice_item.quantity
i.unit = invoice_item.unit
- i.subtotal = invoice_item.subtotal
- i.vat_rate = invoice_item.vat_rate
- i.vat_amount = invoice_item.vat_amount
- i.total = invoice_item.total
+ if invoice.monthly_invoice
+ i.subtotal = 0
+ i.vat_rate = 0
+ i.vat_amount = 0
+ i.total = 0
+ else
+ i.subtotal = invoice_item.subtotal
+ i.vat_rate = invoice_item.vat_rate
+ i.vat_amount = invoice_item.vat_amount
+ i.total = invoice_item.total
+ end
end
e_invoice_invoice_items << e_invoice_invoice_item
end
@@ -66,9 +73,15 @@ class Invoice
i.beneficiary_name = invoice.seller_name
i.beneficiary_account_number = invoice.seller_iban
i.payer_name = invoice.buyer_name
- i.subtotal = invoice.subtotal
- i.vat_amount = invoice.vat_amount
- i.total = invoice.total
+ if invoice.monthly_invoice
+ i.subtotal = 0
+ i.vat_amount = 0
+ i.total = 0
+ else
+ i.subtotal = invoice.subtotal
+ i.vat_amount = invoice.vat_amount
+ i.total = invoice.total
+ end
i.currency = invoice.currency
i.delivery_channel = %i[internet_bank portal]
i.payable = payable
diff --git a/app/models/invoice/pdf_generator.rb b/app/models/invoice/pdf_generator.rb
index 14fb99814..7762456c4 100644
--- a/app/models/invoice/pdf_generator.rb
+++ b/app/models/invoice/pdf_generator.rb
@@ -14,7 +14,8 @@ class Invoice
private
def invoice_html
- ApplicationController.render(template: 'invoice/pdf', assigns: { invoice: invoice })
+ template = invoice.monthly_invoice ? 'invoice/monthly_pdf' : 'invoice/pdf'
+ ApplicationController.render(template: template, assigns: { invoice: invoice })
end
end
end
diff --git a/app/views/admin/invoices/show.haml b/app/views/admin/invoices/show.haml
index b121c8337..f64bd9cc4 100644
--- a/app/views/admin/invoices/show.haml
+++ b/app/views/admin/invoices/show.haml
@@ -4,11 +4,12 @@
= @invoice
.col-sm-8
%h1.text-right.text-center-xs
- - if @invoice.unpaid?
- = link_to(t(:payment_received), new_admin_bank_statement_path(invoice_id: @invoice.id), class: 'btn btn-default')
+ - unless @invoice.monthly_invoice
+ - if @invoice.unpaid?
+ = link_to(t(:payment_received), new_admin_bank_statement_path(invoice_id: @invoice.id), class: 'btn btn-default')
- - if @invoice.paid? and !@invoice.cancelled?
- = link_to(t(:cancel_payment), cancel_paid_admin_invoices_path(invoice_id: @invoice.id), method: 'post', data: { confirm: t(:are_you_sure) }, class: 'btn btn-warning')
+ - if @invoice.paid? && !@invoice.cancelled?
+ = link_to(t(:cancel_payment), cancel_paid_admin_invoices_path(invoice_id: @invoice.id), method: 'post', data: { confirm: t(:are_you_sure) }, class: 'btn btn-warning')
= link_to(t('.download_btn'), download_admin_invoice_path(@invoice), class: 'btn btn-default')
= link_to(t('.deliver_btn'), new_admin_invoice_delivery_path(@invoice), class: 'btn btn-default')
diff --git a/app/views/invoice/monthly_pdf.haml b/app/views/invoice/monthly_pdf.haml
new file mode 100644
index 000000000..c7e179d03
--- /dev/null
+++ b/app/views/invoice/monthly_pdf.haml
@@ -0,0 +1,277 @@
+%html{lang: I18n.locale.to_s}
+ %head
+ %meta{charset: "utf-8"}
+ :css
+ .container {
+ margin: auto;
+ font-size: 12px;
+ }
+
+ .col-md-12 {
+
+ }
+
+ .col-md-6 {
+ width: 49%;
+ display: inline-block;
+ }
+
+ .col-xs-4 {
+ width: 33%;
+ }
+
+ .col-xs-2 {
+ width: 16%;
+ }
+
+ .col-md-3 {
+ width: 24%;
+ display: inline-block;
+ }
+
+ .left {
+ float: left;
+ }
+
+ .left {
+ padding-right: 5px;
+ }
+
+ .right {
+ float: right;
+ }
+
+ .text-right {
+ text-align: right;
+ }
+
+ dt {
+ float: left;
+ width: 100px;
+ clear: left;
+ text-align: right;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ font-weight: bold;
+ line-height: 1.42857;
+ }
+
+ dd {
+ margin-left: 120px;
+ line-height: 1.42857;
+ }
+
+ table {
+ width: 100%;
+ border-collapse: collapse;
+ font-size: 12px;
+ }
+
+ th {
+ text-align: left;
+ border: 0px;
+ border-top: 1px solid #DDD;
+ padding: 6px;
+ }
+
+ thead th {
+ border-bottom: 2px solid #DDD;
+ border-top: 0px;
+ }
+
+ td {
+ border-top: 1px solid #DDD;
+ }
+
+ td {
+ padding: 6px;
+ }
+
+ .no-border {
+ border: 0px;
+ }
+
+ hr {
+ height: 1px;
+ border: 0;
+ color: #DDD;
+ background-color: #DDD;
+ }
+
+ .clear {
+ clear: both;
+ }
+
+ .pull-down {
+ margin-top: 50px;
+ }
+
+ #header {
+ position: relative;
+ min-height: 100px;
+ }
+
+ img {
+ width: 106px;
+ height: 102px;
+ }
+
+ #header-content {
+ position: absolute;
+ bottom: 0;
+ }
+
+ #footer {
+ position: absolute;
+ bottom: 0px;
+ width: 99%;
+ }
+
+ h1 {
+ margin-bottom: 5px;
+ }
+ %body
+ .container
+ #header.row
+ .col-sm-6.left
+ #header-content
+ %h1
+ = @invoice
+ .col-sm-6.right
+ %img{src: "#{Rails.root}/public/eis-logo-black-et.png"}
+ .clear
+ %hr
+ .row
+ .col-md-6.left
+ %h4
+ Details
+ %hr
+ %dl.dl-horizontal
+ %dt= t(:issue_date)
+ %dd= l @invoice.issue_date
+
+ - if @invoice.cancelled?
+ %dt= Invoice.human_attribute_name :cancelled_at
+ %dd= l @invoice.cancelled_at
+
+ %dt= t(:due_date)
+ - if @invoice.cancelled?
+ %dd= t(:cancelled)
+ - else
+ %dd= l @invoice.due_date
+
+ %dt= t(:issuer)
+ %dd= @invoice.seller_contact_name
+
+ - if @invoice.description.present?
+ %dt= t(:description)
+ %dd=@invoice.description
+
+ %dt= Invoice.human_attribute_name :reference_no
+ %dd= @invoice.reference_no
+
+ .col-md-6.right
+ %h4= t(:client)
+ %hr
+ %dl.dl-horizontal
+ %dt= t(:name)
+ %dd= @invoice.buyer_name
+
+ %dt= t(:reg_no)
+ %dd= @invoice.buyer_reg_no
+
+ - if @invoice.buyer_address.present?
+ %dt= Invoice.human_attribute_name :address
+ %dd= @invoice.buyer_address
+
+ - if @invoice.buyer_country.present?
+ %dt= t(:country)
+ %dd= @invoice.buyer_country
+
+ - if @invoice.buyer_phone.present?
+ %dt= t(:phone)
+ %dd= @invoice.buyer_phone
+
+ - if @invoice.buyer_url.present?
+ %dt= t(:url)
+ %dd= @invoice.buyer_url
+
+ - if @invoice.buyer_email.present?
+ %dt= t(:email)
+ %dd= @invoice.buyer_email
+
+ .clear
+ .row.pull-down
+ .col-md-12
+ .table-responsive
+ %table.table.table-hover.table-condensed
+ %thead
+ %tr
+ %th{class: 'col-xs-1'}= t(:code)
+ %th{class: 'col-xs-1'}= InvoiceItem.human_attribute_name :quantity
+ %th{class: 'col-xs-1'}= t(:unit)
+ %th{class: 'col-xs-5'}= t(:description)
+ %th{class: 'col-xs-2'}= t(:price)
+ %th{class: 'col-xs-2'}= t(:total)
+ %tbody
+ - @invoice.each do |invoice_item|
+ %tr
+ %td= invoice_item.product_id
+ %td= invoice_item.quantity
+ %td= invoice_item.unit
+ %td= invoice_item.description
+ - if invoice_item.price && invoice_item.quantity
+ %td= currency(invoice_item.price)
+ %td= "#{currency((invoice_item.price * invoice_item.quantity).round(3))} #{@invoice.currency}"
+ - else
+ %td= ''
+ %td= ''
+ %tfoot
+ %tr
+ %th{colspan: 4}
+ %th= Invoice.human_attribute_name :subtotal
+ %td= number_to_currency(0)
+ %tr
+ %th.no-border{colspan: 4}
+ %th= "VAT #{number_to_percentage(@invoice.vat_rate, precision: 1)}"
+ %td= number_to_currency(0)
+ %tr
+ %th.no-border{colspan: 4}
+ %th= t(:total)
+ %td= number_to_currency(0)
+
+ #footer
+ %hr
+ .row
+ .col-md-3.left
+ = @invoice.seller_name
+ %br
+ = @invoice.seller_address
+ %br
+ = @invoice.seller_country
+ %br
+ = "#{t('reg_no')} #{@invoice.seller_reg_no}"
+ %br
+ = "#{Registrar.human_attribute_name :vat_no} #{@invoice.seller_vat_no}"
+
+ .col-md-3.left
+ = @invoice.seller_phone
+ %br
+ = @invoice.seller_email
+ %br
+ = @invoice.seller_url
+
+ .col-md-3.text-right.left
+ = t(:bank)
+ %br
+ = t(:iban)
+ %br
+ = t(:swift)
+
+ .col-md-3.left
+ = @invoice.seller_bank
+ %br
+ = @invoice.seller_iban
+ %br
+ = @invoice.seller_swift
\ No newline at end of file
diff --git a/app/views/invoice/pdf.haml b/app/views/invoice/pdf.haml
index 9f10acdad..dc3d4370a 100644
--- a/app/views/invoice/pdf.haml
+++ b/app/views/invoice/pdf.haml
@@ -234,7 +234,7 @@
%td= invoice_item.unit
%td= invoice_item.quantity
%td= currency(invoice_item.price)
- %td= "#{currency(invoice_item.item_sum_without_vat)} #{@invoice.currency}"
+ %td= "#{currency(invoice_item.item_sum_without_vat)} #{@invoice.currency}"
%tfoot
%tr
%th{colspan: 3}
diff --git a/config/locales/en.yml b/config/locales/en.yml
index ec8953a84..9021eff60 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -484,6 +484,8 @@ en:
invoice:
title: 'Invoice'
already_paid: " (already paid)"
+ monthly_invoice: " (monthly invoice)"
+ monthly_invoice_description: 'Monthly invoice'
bank_statements: 'Bank statements'
back_to_bank_statements: 'Back to bank statements'
back_to_bank_statement: 'Back to bank statement'
diff --git a/config/locales/et.yml b/config/locales/et.yml
index ad04db007..84672ed3c 100644
--- a/config/locales/et.yml
+++ b/config/locales/et.yml
@@ -9,3 +9,5 @@ et:
invoice:
title: 'Arve'
already_paid: " (juba makstud)"
+ monthly_invoice: " (kuuaruanne)"
+ monthly_invoice_description: 'Kuuaruanne'
diff --git a/db/migrate/20220818075833_add_monthly_invoice_type_columns.rb b/db/migrate/20220818075833_add_monthly_invoice_type_columns.rb
new file mode 100644
index 000000000..33c98124c
--- /dev/null
+++ b/db/migrate/20220818075833_add_monthly_invoice_type_columns.rb
@@ -0,0 +1,6 @@
+class AddMonthlyInvoiceTypeColumns < ActiveRecord::Migration[6.1]
+ def change
+ add_column :invoices, :monthly_invoice, :boolean, default: false
+ add_column :invoices, :metadata, :jsonb
+ end
+end
diff --git a/db/structure.sql b/db/structure.sql
index a96474d53..6e4c145f1 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -955,14 +955,14 @@ CREATE TABLE public.domains (
pending_json jsonb,
force_delete_date date,
statuses character varying[],
+ status_notes public.hstore,
upid integer,
up_date timestamp without time zone,
uuid uuid DEFAULT public.gen_random_uuid() NOT NULL,
locked_by_registrant_at timestamp without time zone,
force_delete_start timestamp without time zone,
force_delete_data public.hstore,
- json_statuses_history jsonb,
- status_notes public.hstore
+ json_statuses_history jsonb
);
@@ -985,6 +985,98 @@ CREATE SEQUENCE public.domains_id_seq
ALTER SEQUENCE public.domains_id_seq OWNED BY public.domains.id;
+--
+-- Name: email_address_verifications; Type: TABLE; Schema: public; Owner: -
+--
+
+CREATE TABLE public.email_address_verifications (
+ id bigint NOT NULL,
+ email public.citext NOT NULL,
+ verified_at timestamp without time zone,
+ success boolean DEFAULT false NOT NULL,
+ domain public.citext NOT NULL
+);
+
+
+--
+-- Name: email_address_verifications_id_seq; Type: SEQUENCE; Schema: public; Owner: -
+--
+
+CREATE SEQUENCE public.email_address_verifications_id_seq
+ START WITH 1
+ INCREMENT BY 1
+ NO MINVALUE
+ NO MAXVALUE
+ CACHE 1;
+
+
+--
+-- Name: email_address_verifications_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
+--
+
+ALTER SEQUENCE public.email_address_verifications_id_seq OWNED BY public.email_address_verifications.id;
+
+
+--
+-- Name: email_addresses_validations; Type: TABLE; Schema: public; Owner: -
+--
+
+CREATE TABLE public.email_addresses_validations (
+ id bigint NOT NULL,
+ email character varying NOT NULL,
+ validated_at timestamp without time zone
+);
+
+
+--
+-- Name: email_addresses_validations_id_seq; Type: SEQUENCE; Schema: public; Owner: -
+--
+
+CREATE SEQUENCE public.email_addresses_validations_id_seq
+ START WITH 1
+ INCREMENT BY 1
+ NO MINVALUE
+ NO MAXVALUE
+ CACHE 1;
+
+
+--
+-- Name: email_addresses_validations_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
+--
+
+ALTER SEQUENCE public.email_addresses_validations_id_seq OWNED BY public.email_addresses_validations.id;
+
+
+--
+-- Name: email_addresses_verifications; Type: TABLE; Schema: public; Owner: -
+--
+
+CREATE TABLE public.email_addresses_verifications (
+ id bigint NOT NULL,
+ email character varying NOT NULL,
+ validated_at timestamp without time zone
+);
+
+
+--
+-- Name: email_addresses_verifications_id_seq; Type: SEQUENCE; Schema: public; Owner: -
+--
+
+CREATE SEQUENCE public.email_addresses_verifications_id_seq
+ START WITH 1
+ INCREMENT BY 1
+ NO MINVALUE
+ NO MAXVALUE
+ CACHE 1;
+
+
+--
+-- Name: email_addresses_verifications_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
+--
+
+ALTER SEQUENCE public.email_addresses_verifications_id_seq OWNED BY public.email_addresses_verifications.id;
+
+
--
-- Name: epp_sessions; Type: TABLE; Schema: public; Owner: -
--
@@ -1104,6 +1196,8 @@ CREATE TABLE public.invoices (
issue_date date NOT NULL,
e_invoice_sent_at timestamp without time zone,
payment_link character varying,
+ monthly_invoice boolean DEFAULT false,
+ metadata jsonb,
CONSTRAINT invoices_due_date_is_not_before_issue_date CHECK ((due_date >= issue_date))
);
@@ -2190,6 +2284,74 @@ CREATE SEQUENCE public.payment_orders_id_seq
ALTER SEQUENCE public.payment_orders_id_seq OWNED BY public.payment_orders.id;
+--
+-- Name: pghero_query_stats; Type: TABLE; Schema: public; Owner: -
+--
+
+CREATE TABLE public.pghero_query_stats (
+ id bigint NOT NULL,
+ database text,
+ "user" text,
+ query text,
+ query_hash bigint,
+ total_time double precision,
+ calls bigint,
+ captured_at timestamp without time zone
+);
+
+
+--
+-- Name: pghero_query_stats_id_seq; Type: SEQUENCE; Schema: public; Owner: -
+--
+
+CREATE SEQUENCE public.pghero_query_stats_id_seq
+ START WITH 1
+ INCREMENT BY 1
+ NO MINVALUE
+ NO MAXVALUE
+ CACHE 1;
+
+
+--
+-- Name: pghero_query_stats_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
+--
+
+ALTER SEQUENCE public.pghero_query_stats_id_seq OWNED BY public.pghero_query_stats.id;
+
+
+--
+-- Name: pghero_space_stats; Type: TABLE; Schema: public; Owner: -
+--
+
+CREATE TABLE public.pghero_space_stats (
+ id bigint NOT NULL,
+ database text,
+ schema text,
+ relation text,
+ size bigint,
+ captured_at timestamp without time zone
+);
+
+
+--
+-- Name: pghero_space_stats_id_seq; Type: SEQUENCE; Schema: public; Owner: -
+--
+
+CREATE SEQUENCE public.pghero_space_stats_id_seq
+ START WITH 1
+ INCREMENT BY 1
+ NO MINVALUE
+ NO MAXVALUE
+ CACHE 1;
+
+
+--
+-- Name: pghero_space_stats_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
+--
+
+ALTER SEQUENCE public.pghero_space_stats_id_seq OWNED BY public.pghero_space_stats.id;
+
+
--
-- Name: prices; Type: TABLE; Schema: public; Owner: -
--
@@ -2228,6 +2390,48 @@ CREATE SEQUENCE public.prices_id_seq
ALTER SEQUENCE public.prices_id_seq OWNED BY public.prices.id;
+--
+-- Name: que_jobs; Type: TABLE; Schema: public; Owner: -
+--
+
+CREATE TABLE public.que_jobs (
+ priority smallint DEFAULT 100 NOT NULL,
+ run_at timestamp with time zone DEFAULT now() NOT NULL,
+ job_id bigint NOT NULL,
+ job_class text NOT NULL,
+ args json DEFAULT '[]'::json NOT NULL,
+ error_count integer DEFAULT 0 NOT NULL,
+ last_error text,
+ queue text DEFAULT ''::text NOT NULL
+);
+
+
+--
+-- Name: TABLE que_jobs; Type: COMMENT; Schema: public; Owner: -
+--
+
+COMMENT ON TABLE public.que_jobs IS '3';
+
+
+--
+-- Name: que_jobs_job_id_seq; Type: SEQUENCE; Schema: public; Owner: -
+--
+
+CREATE SEQUENCE public.que_jobs_job_id_seq
+ START WITH 1
+ INCREMENT BY 1
+ NO MINVALUE
+ NO MAXVALUE
+ CACHE 1;
+
+
+--
+-- Name: que_jobs_job_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
+--
+
+ALTER SEQUENCE public.que_jobs_job_id_seq OWNED BY public.que_jobs.job_id;
+
+
--
-- Name: registrant_verifications; Type: TABLE; Schema: public; Owner: -
--
@@ -2508,8 +2712,7 @@ CREATE TABLE public.validation_events (
validation_eventable_type character varying,
validation_eventable_id bigint,
created_at timestamp(6) without time zone NOT NULL,
- updated_at timestamp(6) without time zone NOT NULL,
- event_type public.validation_type
+ updated_at timestamp(6) without time zone NOT NULL
);
@@ -2813,6 +3016,27 @@ ALTER TABLE ONLY public.domain_transfers ALTER COLUMN id SET DEFAULT nextval('pu
ALTER TABLE ONLY public.domains ALTER COLUMN id SET DEFAULT nextval('public.domains_id_seq'::regclass);
+--
+-- Name: email_address_verifications id; Type: DEFAULT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY public.email_address_verifications ALTER COLUMN id SET DEFAULT nextval('public.email_address_verifications_id_seq'::regclass);
+
+
+--
+-- Name: email_addresses_validations id; Type: DEFAULT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY public.email_addresses_validations ALTER COLUMN id SET DEFAULT nextval('public.email_addresses_validations_id_seq'::regclass);
+
+
+--
+-- Name: email_addresses_verifications id; Type: DEFAULT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY public.email_addresses_verifications ALTER COLUMN id SET DEFAULT nextval('public.email_addresses_verifications_id_seq'::regclass);
+
+
--
-- Name: epp_sessions id; Type: DEFAULT; Schema: public; Owner: -
--
@@ -3030,6 +3254,20 @@ ALTER TABLE ONLY public.notifications ALTER COLUMN id SET DEFAULT nextval('publi
ALTER TABLE ONLY public.payment_orders ALTER COLUMN id SET DEFAULT nextval('public.payment_orders_id_seq'::regclass);
+--
+-- Name: pghero_query_stats id; Type: DEFAULT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY public.pghero_query_stats ALTER COLUMN id SET DEFAULT nextval('public.pghero_query_stats_id_seq'::regclass);
+
+
+--
+-- Name: pghero_space_stats id; Type: DEFAULT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY public.pghero_space_stats ALTER COLUMN id SET DEFAULT nextval('public.pghero_space_stats_id_seq'::regclass);
+
+
--
-- Name: prices id; Type: DEFAULT; Schema: public; Owner: -
--
@@ -3037,6 +3275,13 @@ ALTER TABLE ONLY public.payment_orders ALTER COLUMN id SET DEFAULT nextval('publ
ALTER TABLE ONLY public.prices ALTER COLUMN id SET DEFAULT nextval('public.prices_id_seq'::regclass);
+--
+-- Name: que_jobs job_id; Type: DEFAULT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY public.que_jobs ALTER COLUMN job_id SET DEFAULT nextval('public.que_jobs_job_id_seq'::regclass);
+
+
--
-- Name: registrant_verifications id; Type: DEFAULT; Schema: public; Owner: -
--
@@ -3274,6 +3519,30 @@ ALTER TABLE ONLY public.domains
ADD CONSTRAINT domains_pkey PRIMARY KEY (id);
+--
+-- Name: email_address_verifications email_address_verifications_pkey; Type: CONSTRAINT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY public.email_address_verifications
+ ADD CONSTRAINT email_address_verifications_pkey PRIMARY KEY (id);
+
+
+--
+-- Name: email_addresses_validations email_addresses_validations_pkey; Type: CONSTRAINT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY public.email_addresses_validations
+ ADD CONSTRAINT email_addresses_validations_pkey PRIMARY KEY (id);
+
+
+--
+-- Name: email_addresses_verifications email_addresses_verifications_pkey; Type: CONSTRAINT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY public.email_addresses_verifications
+ ADD CONSTRAINT email_addresses_verifications_pkey PRIMARY KEY (id);
+
+
--
-- Name: epp_sessions epp_sessions_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--
@@ -3522,6 +3791,22 @@ ALTER TABLE ONLY public.payment_orders
ADD CONSTRAINT payment_orders_pkey PRIMARY KEY (id);
+--
+-- Name: pghero_query_stats pghero_query_stats_pkey; Type: CONSTRAINT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY public.pghero_query_stats
+ ADD CONSTRAINT pghero_query_stats_pkey PRIMARY KEY (id);
+
+
+--
+-- Name: pghero_space_stats pghero_space_stats_pkey; Type: CONSTRAINT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY public.pghero_space_stats
+ ADD CONSTRAINT pghero_space_stats_pkey PRIMARY KEY (id);
+
+
--
-- Name: prices prices_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--
@@ -3530,6 +3815,14 @@ ALTER TABLE ONLY public.prices
ADD CONSTRAINT prices_pkey PRIMARY KEY (id);
+--
+-- Name: que_jobs que_jobs_pkey; Type: CONSTRAINT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY public.que_jobs
+ ADD CONSTRAINT que_jobs_pkey PRIMARY KEY (queue, priority, run_at, job_id);
+
+
--
-- Name: registrant_verifications registrant_verifications_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--
@@ -3941,6 +4234,13 @@ CREATE INDEX index_domains_on_registrar_id ON public.domains USING btree (regist
CREATE INDEX index_domains_on_statuses ON public.domains USING gin (statuses);
+--
+-- Name: index_email_address_verifications_on_domain; Type: INDEX; Schema: public; Owner: -
+--
+
+CREATE INDEX index_email_address_verifications_on_domain ON public.email_address_verifications USING btree (domain);
+
+
--
-- Name: index_epp_sessions_on_updated_at; Type: INDEX; Schema: public; Owner: -
--
@@ -4277,6 +4577,20 @@ CREATE INDEX index_notifications_on_registrar_id ON public.notifications USING b
CREATE INDEX index_payment_orders_on_invoice_id ON public.payment_orders USING btree (invoice_id);
+--
+-- Name: index_pghero_query_stats_on_database_and_captured_at; Type: INDEX; Schema: public; Owner: -
+--
+
+CREATE INDEX index_pghero_query_stats_on_database_and_captured_at ON public.pghero_query_stats USING btree (database, captured_at);
+
+
+--
+-- Name: index_pghero_space_stats_on_database_and_captured_at; Type: INDEX; Schema: public; Owner: -
+--
+
+CREATE INDEX index_pghero_space_stats_on_database_and_captured_at ON public.pghero_space_stats USING btree (database, captured_at);
+
+
--
-- Name: index_prices_on_zone_id; Type: INDEX; Schema: public; Owner: -
--
@@ -4333,13 +4647,6 @@ CREATE INDEX index_users_on_registrar_id ON public.users USING btree (registrar_
CREATE INDEX index_validation_events_on_event_data ON public.validation_events USING gin (event_data);
---
--- Name: index_validation_events_on_event_type; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX index_validation_events_on_event_type ON public.validation_events USING btree (event_type);
-
-
--
-- Name: index_validation_events_on_validation_eventable; Type: INDEX; Schema: public; Owner: -
--
@@ -5085,9 +5392,11 @@ INSERT INTO "schema_migrations" (version) VALUES
('20210708131814'),
('20210729131100'),
('20210729134625'),
-('20210827185249'),
-('20211029073644'),
+('20211028122103'),
+('20211028125245'),
+('20211029082225'),
('20211124071418'),
+('20211124084308'),
('20211125181033'),
('20211125184334'),
('20211126085139'),
@@ -5098,12 +5407,12 @@ INSERT INTO "schema_migrations" (version) VALUES
('20220124105717'),
('20220228093211'),
('20220316140727'),
-('20220406085500'),
('20220412130856'),
('20220413073315'),
('20220413084536'),
('20220413084748'),
('20220504090512'),
-('20220524130709');
+('20220524130709'),
+('20220818075833');
diff --git a/lib/serializers/repp/invoice.rb b/lib/serializers/repp/invoice.rb
index 3b8efbd58..686eaac99 100644
--- a/lib/serializers/repp/invoice.rb
+++ b/lib/serializers/repp/invoice.rb
@@ -23,7 +23,8 @@ module Serializers
created_at: obj.created_at, updated_at: obj.updated_at,
due_date: obj.due_date, currency: obj.currency,
seller: seller, buyer: buyer, items: items,
- recipient: obj.buyer.billing_email
+ recipient: obj.buyer.billing_email,
+ monthly_invoice: obj.monthly_invoice
}
end
@@ -54,11 +55,15 @@ module Serializers
end
def items
- invoice.items.map do |item|
- { description: item.description, unit: item.unit,
- quantity: item.quantity, price: item.price,
- sum_without_vat: item.item_sum_without_vat,
- vat_amount: item.vat_amount, total: item.total }
+ if invoice.monthly_invoice
+ invoice.metadata['items']
+ else
+ invoice.items.map do |item|
+ { description: item.description, unit: item.unit,
+ quantity: item.quantity, price: item.price,
+ sum_without_vat: item.item_sum_without_vat,
+ vat_amount: item.vat_amount, total: item.total }
+ end
end
end
@@ -75,6 +80,7 @@ module Serializers
due_date: invoice.due_date,
total: invoice.total,
recipient: invoice.buyer.billing_email,
+ monthly_invoice: invoice.monthly_invoice,
}
end
# rubocop:enable Metrics/MethodLength
diff --git a/test/jobs/directo_invoice_forward_job_test.rb b/test/jobs/directo_invoice_forward_job_test.rb
index 33a05f644..11ed657f6 100644
--- a/test/jobs/directo_invoice_forward_job_test.rb
+++ b/test/jobs/directo_invoice_forward_job_test.rb
@@ -8,9 +8,9 @@ class DirectoInvoiceForwardJobTest < ActiveSupport::TestCase
end
def teardown
- Setting.directo_monthly_number_min = 309901
- Setting.directo_monthly_number_max = 309999
- Setting.directo_monthly_number_last = 309901
+ Setting.directo_monthly_number_min = 309_901
+ Setting.directo_monthly_number_max = 309_999
+ Setting.directo_monthly_number_last = 309_901
end
def test_directo_json_sends_customer_as_hash
@@ -49,7 +49,7 @@ class DirectoInvoiceForwardJobTest < ActiveSupport::TestCase
price = billing_prices(:create_one_year)
activity.update!(activity_type: 'create', price: price)
- Setting.directo_monthly_number_max = 30991
+ Setting.directo_monthly_number_max = 30_991
assert_raises 'RuntimeError' do
DirectoInvoiceForwardJob.perform_now(monthly: true, dry: false)
diff --git a/test/jobs/send_monthly_invoices_job_test.rb b/test/jobs/send_monthly_invoices_job_test.rb
new file mode 100644
index 000000000..38c6252f4
--- /dev/null
+++ b/test/jobs/send_monthly_invoices_job_test.rb
@@ -0,0 +1,265 @@
+require 'test_helper'
+
+class SendMonthlyInvoicesJobTest < ActiveSupport::TestCase
+ include ActionMailer::TestHelper
+
+ setup do
+ @user = registrars(:bestnames)
+ @date = Time.zone.parse('2010-08-06')
+ travel_to @date
+ ActionMailer::Base.deliveries.clear
+ EInvoice.provider = EInvoice::Providers::TestProvider.new
+ EInvoice::Providers::TestProvider.deliveries.clear
+ end
+
+ def teardown
+ Setting.directo_monthly_number_min = 309_901
+ Setting.directo_monthly_number_max = 309_999
+ Setting.directo_monthly_number_last = 309_901
+ EInvoice.provider = EInvoice::Providers::TestProvider.new
+ EInvoice::Providers::TestProvider.deliveries.clear
+ end
+
+ def test_fails_if_directo_bounds_exceedable
+ activity = account_activities(:one)
+ price = billing_prices(:create_one_year)
+ activity.update!(activity_type: 'create', price: price)
+
+ Setting.directo_monthly_number_max = 30_991
+
+ assert_no_difference 'Directo.count' do
+ assert_raises 'RuntimeError' do
+ SendMonthlyInvoicesJob.perform_now
+ end
+ end
+
+ assert_nil Invoice.find_by_monthly_invoice(true)
+ assert_emails 0
+ assert_equal 0, EInvoice::Providers::TestProvider.deliveries.count
+ end
+
+ def test_monthly_summary_is_not_delivered_if_dry
+ activity = account_activities(:one)
+ price = billing_prices(:create_one_year)
+ activity.update!(activity_type: 'create', price: price)
+ @user.update(language: 'et')
+
+ assert_difference 'Setting.directo_monthly_number_last' do
+ assert_no_difference 'Directo.count' do
+ SendMonthlyInvoicesJob.perform_now(dry: true)
+ end
+ end
+
+ invoice = Invoice.last
+ assert_equal 309_902, invoice.number
+ refute invoice.in_directo
+ assert invoice.e_invoice_sent_at.blank?
+
+ assert_emails 0
+ assert_equal 0, EInvoice::Providers::TestProvider.deliveries.count
+ end
+
+ def test_monthly_summary_is_delivered_if_invoice_already_exists
+ @monthly_invoice = invoices(:one)
+ @monthly_invoice.update(number: 309_902, monthly_invoice: true,
+ issue_date: @date.last_month.end_of_month,
+ due_date: @date.last_month.end_of_month,
+ metadata: metadata,
+ in_directo: false,
+ e_invoice_sent_at: nil)
+
+ activity = account_activities(:one)
+ price = billing_prices(:create_one_year)
+ activity.update!(activity_type: 'create', price: price)
+ @user.update(language: 'et')
+
+ response = <<-XML
+
+
+
+
+ XML
+
+ stub_request(:post, ENV['directo_invoice_url']).with do |request|
+ body = CGI.unescape(request.body)
+
+ (body.include? '.test registreerimine: 1 aasta(t)') &&
+ (body.include? 'Domeenide ettemaks') &&
+ (body.include? '309902')
+ end.to_return(status: 200, body: response)
+
+ assert_no_difference 'Setting.directo_monthly_number_last' do
+ assert_difference('Directo.count', 1) do
+ SendMonthlyInvoicesJob.perform_now
+ end
+ end
+
+ invoice = Invoice.last
+ assert_equal 309_902, invoice.number
+ assert invoice.in_directo
+ assert_not invoice.e_invoice_sent_at.blank?
+
+ assert_emails 1
+ email = ActionMailer::Base.deliveries.last
+ assert_equal ['billing@bestnames.test'], email.to
+ assert_equal 'Invoice no. 309902 (monthly invoice)', email.subject
+ assert email.attachments['invoice-309902.pdf']
+
+ assert_equal 1, EInvoice::Providers::TestProvider.deliveries.count
+ end
+
+ def test_monthly_summary_is_delivered_in_estonian
+ activity = account_activities(:one)
+ price = billing_prices(:create_one_year)
+ activity.update!(activity_type: 'create', price: price)
+ @user.update(language: 'et')
+
+ response = <<-XML
+
+
+
+
+ XML
+
+ stub_request(:post, ENV['directo_invoice_url']).with do |request|
+ body = CGI.unescape(request.body)
+
+ (body.include? '.test registreerimine: 1 aasta(t)') &&
+ (body.include? 'Domeenide ettemaks') &&
+ (body.include? '309902')
+ end.to_return(status: 200, body: response)
+
+ assert_difference 'Setting.directo_monthly_number_last' do
+ assert_difference('Directo.count', 1) do
+ SendMonthlyInvoicesJob.perform_now
+ end
+ end
+
+ invoice = Invoice.last
+ assert_equal 309_902, invoice.number
+ assert invoice.in_directo
+ assert_not invoice.e_invoice_sent_at.blank?
+
+ assert_emails 1
+ email = ActionMailer::Base.deliveries.last
+ assert_equal ['billing@bestnames.test'], email.to
+ assert_equal 'Invoice no. 309902 (monthly invoice)', email.subject
+ assert email.attachments['invoice-309902.pdf']
+
+ assert_equal 1, EInvoice::Providers::TestProvider.deliveries.count
+ end
+
+ def test_multi_year_purchases_have_duration_assigned
+ activity = account_activities(:one)
+ price = billing_prices(:create_one_year)
+ price.update(duration: 3.years)
+ activity.update(activity_type: 'create', price: price)
+
+ response = <<-XML
+
+
+
+
+ XML
+
+ stub_request(:post, ENV['directo_invoice_url']).with do |request|
+ body = CGI.unescape(request.body)
+ (body.include? 'StartDate') && (body.include? 'EndDate')
+ end.to_return(status: 200, body: response)
+
+ assert_difference 'Setting.directo_monthly_number_last' do
+ SendMonthlyInvoicesJob.perform_now
+ end
+
+ invoice = Invoice.last
+ assert_equal 309_902, invoice.number
+ assert invoice.in_directo
+ assert_not invoice.e_invoice_sent_at.blank?
+ end
+
+ def test_monthly_duration_products_are_present_in_summary
+ activity = account_activities(:one)
+ price = billing_prices(:create_one_month)
+ activity.update(activity_type: 'create', price: price)
+
+ response = <<-XML
+
+
+
+
+ XML
+
+ stub_request(:post, ENV['directo_invoice_url']).with do |request|
+ body = CGI.unescape(request.body)
+ body.include? 'month(s)'
+ end.to_return(status: 200, body: response)
+
+ assert_difference 'Setting.directo_monthly_number_last' do
+ SendMonthlyInvoicesJob.perform_now
+ end
+
+ invoice = Invoice.last
+ assert_equal 309_902, invoice.number
+ assert invoice.in_directo
+ assert_not invoice.e_invoice_sent_at.blank?
+ end
+
+ def test_sends_each_monthly_invoice_separately
+ WebMock.reset!
+
+ activity = account_activities(:one)
+ price = billing_prices(:create_one_year)
+ price.update(duration: 3.years)
+ activity.update(activity_type: 'create', price: price)
+
+ # Creating account activity for second action
+ another_activity = activity.dup
+ another_activity.account = accounts(:two)
+
+ AccountActivity.skip_callback(:create, :after, :update_balance)
+ another_activity.created_at = Time.zone.parse('2010-07-05 10:00')
+ another_activity.save
+ AccountActivity.set_callback(:create, :after, :update_balance)
+
+ response = <<-XML
+
+
+
+
+ XML
+
+ first_registrar_stub = stub_request(:post, ENV['directo_invoice_url']).with do |request|
+ body = CGI.unescape(request.body)
+ (body.include? 'StartDate') && (body.include? 'EndDate') && (body.include? 'bestnames')
+ end.to_return(status: 200, body: response)
+
+ second_registrar_stub = stub_request(:post, ENV['directo_invoice_url']).with do |request|
+ body = CGI.unescape(request.body)
+ (body.include? 'StartDate') && (body.include? 'EndDate') && (body.include? 'goodnames')
+ end.to_return(status: 200, body: response)
+
+ assert_difference('Invoice.count', 2) do
+ assert_difference('Directo.count', 2) do
+ SendMonthlyInvoicesJob.perform_now
+ end
+ end
+
+ assert_requested first_registrar_stub
+ assert_requested second_registrar_stub
+
+ assert_emails 2
+ assert_equal 2, EInvoice::Providers::TestProvider.deliveries.count
+ end
+
+ private
+
+ def metadata
+ {
+ "items" => [
+ { "description" => "Domeenide registreerimine - Juuli 2010" },
+ { "product_id" => nil, "quantity" => 1, "unit" => "tk", "price" => 10.0, "description" => ".test registreerimine: 1 aasta(t)" },
+ { "product_id" => "ETTEM06", "description" => "Domeenide ettemaks", "quantity" => -1, "price" => 10.0, "unit" => "tk" },
+ ],
+ }
+ end
+end
\ No newline at end of file