Merge branch 'master' into 2413-force-delete-lift-bug

This commit is contained in:
Thiago Youssef 2022-09-15 07:21:45 -03:00
commit 7328ea24c3
69 changed files with 2082 additions and 818 deletions

View file

@ -81,18 +81,24 @@ module Admin
payment_order.update(notes: 'Cancelled')
end
# rubocop:disable Metrics/MethodLength
def filter_by_status
case params[:status]
when 'Paid'
Invoice.includes(:account_activity, :buyer).where.not(account_activity: { id: nil })
when 'Unpaid'
Invoice.includes(:account_activity, :buyer).where(account_activity: { id: nil })
Invoice.includes(:account_activity, :buyer).where(account_activity: { id: nil },
cancelled_at: nil,
monthly_invoice: false)
when 'Cancelled'
Invoice.includes(:account_activity, :buyer).where.not(cancelled_at: nil)
when 'Monthly'
Invoice.where(monthly_invoice: true, cancelled_at: nil)
else
Invoice.includes(:account_activity, :buyer)
end
end
# rubocop:enable Metrics/MethodLength
def filter_by_receipt_date(invoices)
date_from_param = params[:q][:receipt_date_gteq] if params[:q][:receipt_date_gteq].present?

View file

@ -3,7 +3,6 @@ module EisBilling
protect_from_forgery with: :null_session
skip_authorization_check # Temporary solution
# skip_before_action :verify_authenticity_token # Temporary solution
before_action :persistent
before_action :authorized
INITIATOR = 'billing'.freeze
@ -49,11 +48,5 @@ module EisBilling
def logger
Rails.logger
end
def persistent
return true if Feature.billing_system_integrated?
render json: { message: "We don't work yet!" }, status: :unauthorized
end
end
end

View file

@ -1,8 +1,16 @@
module EisBilling
class LhvConnectTransactionsController < EisBilling::BaseController
def create
if params['_json'].nil? || params['_json'].empty?
render json: { message: 'MISSING PARAMS' }, status: :unprocessable_entity
return
end
bank_statement = BankStatement.create(bank_code: Setting.registry_bank_code,
iban: Setting.registry_iban)
params['_json'].each do |incoming_transaction|
process_transactions(incoming_transaction)
process_transactions(incoming_transaction, bank_statement)
end
render status: :ok, json: { message: 'RECEIVED', params: params }
@ -10,19 +18,13 @@ module EisBilling
private
def process_transactions(incoming_transaction)
def process_transactions(incoming_transaction, bank_statement)
logger.info 'Got incoming transactions'
logger.info incoming_transaction
bank_statement = BankStatement.new(bank_code: Setting.registry_bank_code,
iban: Setting.registry_iban)
bank_statement_transaction(bank_statement: bank_statement, incoming_transaction: incoming_transaction)
end
def bank_statement_transaction(bank_statement:, incoming_transaction:)
ActiveRecord::Base.transaction do
bank_statement.save!
transaction = create_transaction(incoming_transaction: incoming_transaction, bank_statement: bank_statement)
transaction = bank_statement.bank_transactions
.create!(transaction_attributes(incoming_transaction))
next if transaction.registrar.blank?
@ -32,17 +34,21 @@ module EisBilling
def create_invoice_if_missing(transaction)
Invoice.create_from_transaction!(transaction) unless transaction.autobindable?
transaction.autobind_invoice
invoice = transaction.autobind_invoice
return unless invoice.paid?
EisBilling::SendInvoiceStatus.send_info(invoice_number: invoice.number,
status: 'paid')
end
def create_transaction(incoming_transaction:, bank_statement:)
transaction_attributes = { sum: incoming_transaction['amount'],
currency: incoming_transaction['currency'],
paid_at: incoming_transaction['date'],
reference_no: incoming_transaction['payment_reference_number'],
description: incoming_transaction['payment_description'] }
bank_statement.bank_transactions.create!(transaction_attributes)
def transaction_attributes(incoming_transaction)
{
sum: incoming_transaction['amount'],
currency: incoming_transaction['currency'],
paid_at: incoming_transaction['date'],
reference_no: incoming_transaction['payment_reference_number'],
description: incoming_transaction['payment_description'],
}
end
end
end

View file

@ -5,6 +5,9 @@ module EisBilling
def update
payment_status = define_payment_status(params[:payment_state])
invoice = Invoice.find_by(number: params[:order_reference])
return if invoice.paid?
bank = create_bank_transfer(invoice: invoice, sum: params[:standing_amount], paid_at: params[:transaction_time])
create_payment_order(invoice: invoice, everypay_response: params, payment_status: payment_status)

View file

@ -163,6 +163,7 @@ module Repp
data = current_user.as_json(only: %i[id username roles])
data[:registrar_name] = registrar.name
data[:legaldoc_mandatory] = registrar.legaldoc_mandatory?
data[:address_processing] = Contact.address_processing?
data[:abilities] = Ability.new(current_user).permissions
data
end

View file

@ -193,7 +193,7 @@ module Repp
def contact_create_params(required: true)
create_params = %i[name email phone]
contact_params.require(create_params) if required
contact_params.slice(*create_params)
contact_params.slice(:id, *create_params)
end
def contact_ident_params(required: true)
@ -211,7 +211,7 @@ module Repp
end
def contact_params
params.require(:contact).permit(:name, :email, :phone, :legal_document,
params.require(:contact).permit(:id, :name, :email, :phone, :legal_document,
legal_document: %i[body type],
ident: [%i[ident ident_type ident_country_code]],
addr: [%i[country_code city street zip state]])

View file

@ -12,14 +12,14 @@ module Repp
desc 'Get all existing domains'
def index
authorize! :info, Epp::Domain
records = current_user.registrar.domains
records = current_user.registrar.domains.includes(:registrar, :registrant)
q = records.ransack(PartialSearchFormatter.format(search_params))
q.sorts = ['valid_to asc', 'created_at desc'] if q.sorts.empty?
# use distinct: false here due to ransack bug:
# https://github.com/activerecord-hackery/ransack/issues/429
domains = q.result(distinct: false)
limited_domains = domains.limit(limit).offset(offset).includes(:registrar, :registrant)
limited_domains = domains.limit(limit).offset(offset)
render_success(data: { new_domain: records.any? ? serialized_domains([records.last]) : [],
domains: serialized_domains(limited_domains.to_a.uniq),
@ -238,7 +238,7 @@ module Repp
def index_params
params.permit(:limit, :offset, :details, :simple, :q,
q: %i[s name_matches registrant_id_eq contacts_ident_eq
q: %i[s name_matches registrant_code_eq contacts_ident_eq
nameservers_hostname_eq valid_to_gteq valid_to_lteq
statuses_contains_array] + [s: []])
end

View file

@ -97,6 +97,7 @@ module Repp
account_activity_id_not_null
account_activity_id_null cancelled_at_null
cancelled_at_not_null number_gteq number_lteq
monthly_invoice_true monthly_invoice_false
total_gteq total_lteq s] + [s: []])
end

View file

@ -11,7 +11,7 @@ module Domains
outzone_date: domain.outzone_date,
purge_date: domain.purge_date)
return if domain.registrar.notifications.last.text.include? template
return if domain.registrar&.notifications&.last&.text&.include? template
domain.registrar.notifications.create!(text: template)
end
@ -23,7 +23,7 @@ module Domains
purge_date: domain.purge_date,
email: email)
return if domain.registrar.notifications.last.text.include? template
return if domain.registrar&.notifications&.last&.text&.include? template
domain.registrar.notifications.create!(text: template)
end

View file

@ -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

View file

@ -1,60 +1,31 @@
class DirectoInvoiceForwardJob < ApplicationJob
def perform(monthly: false, dry: false)
@dry = dry
(@month = Time.zone.now - 1.month) if monthly
data = nil
@client = new_directo_client
monthly ? send_monthly_invoices : send_receipts
if monthly
@month = Time.zone.now - 1.month
data = collect_monthly_data
else
data = collect_receipts_data
end
EisBilling::SendDataToDirecto.send_request(object_data: data, monthly: monthly, dry: dry)
end
def new_directo_client
DirectoApi::Client.new(ENV['directo_invoice_url'], Setting.directo_sales_agent,
Setting.directo_receipt_payment_term)
end
def send_receipts
def collect_receipts_data
unsent_invoices = Invoice.where(in_directo: false).non_cancelled
collected_data = []
Rails.logger.info("[DIRECTO] Trying to send #{unsent_invoices.count} prepayment invoices")
unsent_invoices.each do |invoice|
unless valid_invoice_conditions?(invoice)
Rails.logger.info "[DIRECTO] Invoice #{invoice.number} has been skipped"
next
end
@client.invoices.add_with_schema(invoice: invoice.as_directo_json, schema: 'prepayment')
collected_data << invoice.as_directo_json
end
sync_with_directo
end
def send_monthly_invoices
Registrar.where.not(test_registrar: true).find_each do |registrar|
next unless registrar.cash_account
@client = new_directo_client
send_invoice_for_registrar(registrar)
end
end
def send_invoice_for_registrar(registrar)
summary = registrar.monthly_summary(month: @month)
@client.invoices.add_with_schema(invoice: summary, schema: 'summary') unless summary.nil?
sync_with_directo if @client.invoices.count.positive?
end
def assign_monthly_numbers
raise 'Directo Counter is going to be out of period!' if directo_counter_exceedable?(@client.invoices.count)
min_directo = Setting.directo_monthly_number_min.presence.try(:to_i)
directo_number = [Setting.directo_monthly_number_last.presence.try(:to_i),
min_directo].compact.max || 0
@client.invoices.each do |inv|
directo_number += 1
inv.number = directo_number
end
collected_data
end
def valid_invoice_conditions?(invoice)
@ -68,29 +39,17 @@ class DirectoInvoiceForwardJob < ApplicationJob
true
end
def sync_with_directo
assign_monthly_numbers if @month
def collect_monthly_data
registrars_data = []
Rails.logger.info("[Directo] - attempting to send following XML:\n #{@client.invoices.as_xml}")
return if @dry
res = @client.invoices.deliver(ssl_verify: false)
process_directo_response(res.body, @client.invoices.as_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 process_directo_response(xml, req)
Rails.logger.info "[Directo] - Responded with body: #{xml}"
Nokogiri::XML(xml).css('Result').each do |res|
if @month
mark_invoice_as_sent(res: res, req: req)
else
inv = Invoice.find_by(number: res.attributes['docid'].value.to_i)
mark_invoice_as_sent(invoice: inv, res: res, req: req)
end
Registrar.where.not(test_registrar: true).find_each do |registrar|
registrars_data << {
registrar: registrar,
registrar_summery: registrar.monthly_summary(month: @month),
}
end
registrars_data
end
def mark_invoice_as_sent(invoice: nil, res:, req:)

View file

@ -0,0 +1,125 @@
class DirectoInvoiceForwardLegacyJob < ApplicationJob
def perform(monthly: false, dry: false)
@dry = dry
(@month = Time.zone.now - 1.month) if monthly
@client = new_directo_client
monthly ? send_monthly_invoices : send_receipts
end
def new_directo_client
DirectoApi::Client.new(ENV['directo_invoice_url'], Setting.directo_sales_agent,
Setting.directo_receipt_payment_term)
end
def send_receipts
unsent_invoices = Invoice.where(in_directo: false).non_cancelled
Rails.logger.info("[DIRECTO] Trying to send #{unsent_invoices.count} prepayment invoices")
unsent_invoices.each do |invoice|
unless valid_invoice_conditions?(invoice)
Rails.logger.info "[DIRECTO] Invoice #{invoice.number} has been skipped"
next
end
@client.invoices.add_with_schema(invoice: invoice.as_directo_json, schema: 'prepayment')
end
sync_with_directo
end
def send_monthly_invoices
Registrar.where.not(test_registrar: true).find_each do |registrar|
next unless registrar.cash_account
@client = new_directo_client
send_invoice_for_registrar(registrar)
end
end
def send_invoice_for_registrar(registrar)
summary = registrar.monthly_summary(month: @month)
@client.invoices.add_with_schema(invoice: summary, schema: 'summary') unless summary.nil?
sync_with_directo if @client.invoices.count.positive?
end
def assign_monthly_numbers
raise 'Directo Counter is going to be out of period!' if directo_counter_exceedable?(@client.invoices.count)
min_directo = Setting.directo_monthly_number_min.presence.try(:to_i)
directo_number = [Setting.directo_monthly_number_last.presence.try(:to_i),
min_directo].compact.max || 0
@client.invoices.each do |inv|
directo_number += 1
inv.number = directo_number
end
end
def valid_invoice_conditions?(invoice)
if invoice.account_activity.nil? || invoice.account_activity.bank_transaction.nil? ||
invoice.account_activity.bank_transaction.sum.nil? ||
invoice.account_activity.bank_transaction.sum != invoice.total
return false
end
true
end
def sync_with_directo
assign_monthly_numbers if @month
Rails.logger.info("[Directo] - attempting to send following XML:\n #{@client.invoices.as_xml}")
return if @dry
res = @client.invoices.deliver(ssl_verify: false)
process_directo_response(res.body, @client.invoices.as_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 process_directo_response(xml, req)
Rails.logger.info "[Directo] - Responded with body: #{xml}"
Nokogiri::XML(xml).css('Result').each do |res|
if @month
mark_invoice_as_sent(res: res, req: req)
else
inv = Invoice.find_by(number: res.attributes['docid'].value.to_i)
mark_invoice_as_sent(invoice: inv, res: res, req: req)
end
end
end
def mark_invoice_as_sent(invoice: nil, res:, req:)
directo_record = Directo.new(response: res.as_json.to_h,
request: req, invoice_number: res.attributes['docid'].value.to_i)
if invoice
directo_record.item = invoice
invoice.update(in_directo: true)
else
update_directo_number(num: directo_record.invoice_number)
end
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
def directo_counter_exceedable?(invoice_count)
min_directo = Setting.directo_monthly_number_min.presence.try(:to_i)
max_directo = Setting.directo_monthly_number_max.presence.try(:to_i)
last_directo = [Setting.directo_monthly_number_last.presence.try(:to_i),
min_directo].compact.max || 0
return true if max_directo && max_directo < (last_directo + invoice_count)
false
end
end

View file

@ -1,84 +0,0 @@
class DirectoInvoiceForwardTwoJob < ApplicationJob
def perform(monthly: false, dry: false)
data = nil
if monthly
@month = Time.zone.now - 1.month
data = collect_monthly_data
else
data = collect_receipts_data
end
EisBilling::SendDataToDirecto.send_request(object_data: data, monthly: monthly, dry: dry)
end
def collect_receipts_data
unsent_invoices = Invoice.where(in_directo: false).non_cancelled
collected_data = []
unsent_invoices.each do |invoice|
unless valid_invoice_conditions?(invoice)
Rails.logger.info "[DIRECTO] Invoice #{invoice.number} has been skipped"
next
end
collected_data << invoice.as_directo_json
end
collected_data
end
def valid_invoice_conditions?(invoice)
if invoice.account_activity.nil? || invoice.account_activity.bank_transaction.nil? ||
invoice.account_activity.bank_transaction.sum.nil? ||
invoice.account_activity.bank_transaction.sum != invoice.total
return false
end
true
end
def collect_monthly_data
registrars_data = []
Registrar.where.not(test_registrar: true).find_each do |registrar|
registrars_data << {
registrar: registrar,
registrar_summery: registrar.monthly_summary(month: @month),
}
end
registrars_data
end
def mark_invoice_as_sent(invoice: nil, res:, req:)
directo_record = Directo.new(response: res.as_json.to_h,
request: req, invoice_number: res.attributes['docid'].value.to_i)
if invoice
directo_record.item = invoice
invoice.update(in_directo: true)
else
update_directo_number(num: directo_record.invoice_number)
end
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
def directo_counter_exceedable?(invoice_count)
min_directo = Setting.directo_monthly_number_min.presence.try(:to_i)
max_directo = Setting.directo_monthly_number_max.presence.try(:to_i)
last_directo = [Setting.directo_monthly_number_last.presence.try(:to_i),
min_directo].compact.max || 0
return true if max_directo && max_directo < (last_directo + invoice_count)
false
end
end

View file

@ -6,7 +6,8 @@ class SendEInvoiceJob < ApplicationJob
invoice = Invoice.find_by(id: invoice_id)
return unless need_to_process_invoice?(invoice: invoice, payable: payable)
process(invoice: invoice, payable: payable)
send_invoice_to_eis_billing(invoice: invoice, payable: payable)
invoice.update(e_invoice_sent_at: Time.zone.now)
rescue StandardError => e
log_error(invoice: invoice, error: e)
raise e
@ -22,16 +23,9 @@ class SendEInvoiceJob < ApplicationJob
true
end
def process(invoice:, payable:)
invoice.to_e_invoice(payable: payable).deliver unless Rails.env.development?
invoice.update(e_invoice_sent_at: Time.zone.now)
log_success(invoice)
end
def log_success(invoice)
id = invoice.try(:id) || invoice
message = "E-Invoice for an invoice with ID # #{id} was sent successfully"
logger.info message
def send_invoice_to_eis_billing(invoice:, payable:)
result = EisBilling::SendEInvoice.send_request(invoice: invoice, payable: payable)
logger.info result.body
end
def log_error(invoice:, error:)

View file

@ -1,4 +1,4 @@
class SendEInvoiceTwoJob < ApplicationJob
class SendEInvoiceLegacyJob < ApplicationJob
discard_on HTTPClient::TimeoutError
def perform(invoice_id, payable: true)
@ -6,8 +6,7 @@ class SendEInvoiceTwoJob < ApplicationJob
invoice = Invoice.find_by(id: invoice_id)
return unless need_to_process_invoice?(invoice: invoice, payable: payable)
send_invoice_to_eis_billing(invoice: invoice, payable: payable)
invoice.update(e_invoice_sent_at: Time.zone.now)
process(invoice: invoice, payable: payable)
rescue StandardError => e
log_error(invoice: invoice, error: e)
raise e
@ -17,15 +16,23 @@ class SendEInvoiceTwoJob < ApplicationJob
def need_to_process_invoice?(invoice:, payable:)
logger.info "Checking if need to process e-invoice #{invoice}, payable: #{payable}"
unprocessable = invoice.do_not_send_e_invoice? && (invoice.monthly_invoice ? true : payable)
return false if invoice.blank?
return false if invoice.do_not_send_e_invoice? && payable
return false if unprocessable
true
end
def send_invoice_to_eis_billing(invoice:, payable:)
result = EisBilling::SendEInvoice.send_request(invoice: invoice, payable: payable)
logger.info result.body
def process(invoice:, payable:)
invoice.to_e_invoice(payable: payable).deliver unless Rails.env.development?
invoice.update(e_invoice_sent_at: Time.zone.now)
log_success(invoice)
end
def log_success(invoice)
id = invoice.try(:id) || invoice
message = "E-Invoice for an invoice with ID # #{id} was sent successfully"
logger.info message
end
def log_error(invoice:, error:)

View file

@ -0,0 +1,147 @@
class SendMonthlyInvoicesJob < ApplicationJob # rubocop:disable Metrics/ClassLength
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
# rubocop:disable Metrics/MethodLength
def send_monthly_invoices
Registrar.with_cash_accounts.find_each do |registrar|
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
send_email_to_registrar(invoice: invoice, registrar: registrar)
send_e_invoice(invoice.id)
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
# rubocop:enable Metrics/MethodLength
def send_email_to_registrar(invoice:, registrar:)
InvoiceMailer.invoice_email(invoice: invoice,
recipient: registrar.billing_email)
.deliver_now
end
def send_e_invoice(invoice_id)
SendEInvoiceLegacyJob.set(wait: 1.minute).perform_later(invoice_id, payable: false)
end
def create_invoice(summary, registrar)
invoice = registrar.init_monthly_invoice(normalize(summary))
invoice.number = assign_monthly_number
return unless invoice.save!
update_monthly_invoice_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_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_to_directo(res: res, req: req, invoice: inv)
end
end
def mark_invoice_as_sent_to_directo(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_monthly_invoice_number(num:)
return unless num.to_i > Setting.directo_monthly_number_last.to_i
Setting.directo_monthly_number_last = num.to_i
end
private
# rubocop:disable Metrics/MethodLength
def normalize(summary, lines: [])
sum = summary.dup
line_map = Hash.new 0
sum['invoice_lines'].each { |l| line_map[l] += 1 }
line_map.each_key do |count|
count['quantity'] = line_map[count] unless count['unit'].nil?
regex = /Domeenide ettemaks|Domains prepayment/
count['quantity'] = -1 if count['description'].match?(regex)
lines << count
end
sum['invoice_lines'] = summarize_lines(lines)
sum
end
# rubocop:enable Metrics/MethodLength
def summarize_lines(invoice_lines, lines: [])
line_map = Hash.new 0
invoice_lines.each do |l|
hash = l.with_indifferent_access.except(:start_date, :end_date)
line_map[hash] += 1
end
line_map.each_key do |count|
count['price'] = (line_map[count] * count['price'].to_f).round(3) unless count['price'].nil?
lines << count
end
lines
end
end

View file

@ -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

View file

@ -33,10 +33,10 @@ class BankTransaction < ApplicationRecord
return unless autobindable?
channel = manual ? 'admin_payment' : 'system_payment'
create_internal_payment_record(channel: channel, invoice: invoice, registrar: registrar)
create_internal_payment_record(invoice: invoice, registrar: registrar, channel: channel)
end
def create_internal_payment_record(channel: nil, invoice:, registrar:)
def create_internal_payment_record(invoice:, registrar:, channel: nil)
if channel.nil?
create_activity(invoice.buyer, invoice)
return
@ -47,9 +47,12 @@ class BankTransaction < ApplicationRecord
if create_activity(registrar, invoice)
payment_order.paid!
EisBilling::SendInvoiceStatus.send_info(invoice_number: invoice.number,
status: 'paid')
else
payment_order.update(notes: 'Failed to create activity', status: 'failed')
end
invoice
end
def bind_invoice(invoice_no, manual: false)
@ -62,8 +65,8 @@ class BankTransaction < ApplicationRecord
validate_invoice_data(invoice)
return if errors.any?
create_internal_payment_record(channel: (manual ? 'admin_payment' : nil), invoice: invoice,
registrar: invoice.buyer)
create_internal_payment_record(invoice: invoice, registrar: invoice.buyer,
channel: (manual ? 'admin_payment' : nil))
end
def validate_invoice_data(invoice)

View file

@ -4,13 +4,8 @@ module Billing
MULTI_REGEXP = /(\d{2,20})/
def self.generate
if Feature.billing_system_integrated?
result = EisBilling::GetReferenceNumber.send_request
JSON.parse(result.body)['reference_number']
else
base = Base.generate
"#{base}#{base.check_digit}"
end
result = EisBilling::GetReferenceNumber.send_request
JSON.parse(result.body)['reference_number']
end
def self.valid?(ref)

View file

@ -34,7 +34,13 @@ module Domain::ForceDelete
reason = explicit&.downcase
return reason if %w[invalid_email invalid_phone].include?(reason)
registrant.org? ? 'legal_person' : 'private_person'
if contact_emails_verification_failed.present?
'invalid_email'
elsif registrant.org?
'legal_person'
else
'private_person'
end
end
def force_delete_scheduled?

View file

@ -5,6 +5,10 @@ module EmailVerifable
scope :recently_not_validated, -> { where.not(id: ValidationEvent.validated_ids_by(name)) }
end
def email_verification_failed?
need_to_start_force_delete?
end
def validate_email_data(level:, count:)
validation_events.order(created_at: :desc).limit(count).all? do |event|
event.check_level == level.to_s && event.failed?

View file

@ -1,22 +1,27 @@
module Registrar::BookKeeping
module Registrar::BookKeeping # rubocop:disable Metrics/ModuleLength
extend ActiveSupport::Concern
DOMAIN_TO_PRODUCT = { 'ee': '01EE', 'com.ee': '02COM', 'pri.ee': '03PRI',
'fie.ee': '04FIE', 'med.ee': '05MED' }.freeze
included do
scope :with_cash_accounts, (lambda do
joins(:accounts).where('accounts.account_type = ? AND test_registrar != ?',
Account::CASH,
true)
end)
end
def monthly_summary(month:)
activities = monthly_activites(month)
return unless activities.any?
invoice = {
'number': 1,
'customer': compose_directo_customer,
'number': 1, 'customer': compose_directo_customer,
'language': language == 'en' ? 'ENG' : '', 'currency': activities.first.currency,
'date': month.end_of_month.strftime('%Y-%m-%d')
}.as_json
invoice['invoice_lines'] = prepare_invoice_lines(month: month, activities: activities)
invoice
end
@ -55,20 +60,25 @@ 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 = {
'product_id': DOMAIN_TO_PRODUCT[price.zone_name.to_sym],
'quantity': 1,
'unit': language == 'en' ? 'pc' : 'tk',
}
}.with_indifferent_access
finalize_invoice_line(line, price: price, duration: duration, activity: activity)
end
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)
@ -79,15 +89,16 @@ module Registrar::BookKeeping
def add_product_timeframe(line:, activity:, duration:)
create_time = activity.created_at
line['start_date'] = (create_time + (duration - 1).year).end_of_month.strftime('%Y-%m-%d')
line['end_date'] = (create_time + (duration - 1).year + 1).end_of_month.strftime('%Y-%m-%d')
line['end_date'] = (create_time + duration.year).end_of_month.strftime('%Y-%m-%d')
end
def description_in_language(price:, yearly:)
timeframe_string = yearly ? 'yearly' : 'monthly'
locale_string = "registrar.invoice_#{timeframe_string}_product_description"
length = yearly ? price.duration.in_years.to_i : price.duration.in_months.to_i
I18n.with_locale(language == 'en' ? 'en' : 'et') do
I18n.t(locale_string, tld: ".#{price.zone_name}", length: price.duration.in_years.to_i)
I18n.t(locale_string, tld: ".#{price.zone_name}", length: length)
end
end

View file

@ -24,6 +24,11 @@ class Contact < ApplicationRecord
alias_attribute :kind, :ident_type
alias_attribute :copy_from_id, :original_id # Old attribute name; for PaperTrail
scope :email_verification_failed, lambda {
joins('LEFT JOIN email_address_verifications emv ON contacts.email = emv.email')
.where('success = false and verified_at IS NOT NULL')
}
scope :with_different_company_name, (lambda do |company|
where("ident = ? AND ident_country_code = 'EE' AND name != ?",
company.registration_number,

View file

@ -725,6 +725,10 @@ class Domain < ApplicationRecord
DNS::DomainName.new(name)
end
def contact_emails_verification_failed
contacts.select(&:email_verification_failed?)&.map(&:email)&.uniq
end
def as_csv_row
[
name,

View file

@ -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
@ -59,39 +62,13 @@ class Invoice < ApplicationRecord
throw(:abort)
end
def invoice_number_from_billing
def set_invoice_number
result = EisBilling::GetInvoiceNumber.send_invoice
validate_invoice_number(result)
self.number = JSON.parse(result.body)['invoice_number'].to_i
end
def generate_invoice_number_legacy
last_no = Invoice.all
.where(number: Setting.invoice_number_min.to_i...Setting.invoice_number_max.to_i)
.order(number: :desc)
.limit(1)
.pick(:number)
if last_no && last_no >= Setting.invoice_number_min.to_i
self.number = last_no + 1
else
self.number = Setting.invoice_number_min.to_i
end
return if number <= Setting.invoice_number_max.to_i
billing_out_of_range_issue
end
def set_invoice_number
if Feature.billing_system_integrated?
invoice_number_from_billing
else
generate_invoice_number_legacy
end
end
def to_s
I18n.t('invoice_no', no: number)
end
@ -118,7 +95,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 +108,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

View file

@ -41,22 +41,16 @@ class Invoice
e_invoice_invoice_items = []
invoice.each do |invoice_item|
e_invoice_invoice_item = EInvoice::InvoiceItem.new.tap do |i|
i.description = invoice_item.description
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
end
e_invoice_invoice_item = generate_invoice_item(invoice, invoice_item)
e_invoice_invoice_items << e_invoice_invoice_item
end
e_invoice_name_item = e_invoice_invoice_items.shift if invoice.monthly_invoice
e_invoice_invoice = EInvoice::Invoice.new.tap do |i|
i.seller = seller
i.buyer = buyer
i.name = e_invoice_name_item&.description
i.items = e_invoice_invoice_items
i.number = invoice.number
i.date = invoice.issue_date
@ -72,9 +66,33 @@ class Invoice
i.currency = invoice.currency
i.delivery_channel = %i[internet_bank portal]
i.payable = payable
i.monthly_invoice = invoice.monthly_invoice
end
EInvoice::EInvoice.new(date: Time.zone.today, invoice: e_invoice_invoice)
end
private
def generate_invoice_item(invoice, item)
EInvoice::InvoiceItem.new.tap do |i|
i.description = item.description
i.unit = item.unit
i.price = item.price
i.quantity = item.quantity
if invoice.monthly_invoice && item.price && item.quantity
i.product_id = item.product_id
i.vat_rate = invoice.vat_rate
i.subtotal = (item.price * item.quantity).round(3)
i.vat_amount = i.subtotal * (i.vat_rate / 100)
i.total = i.subtotal + i.vat_amount
else
i.subtotal = item.subtotal
i.vat_rate = item.vat_rate
i.vat_amount = item.vat_amount
i.total = item.total
end
end
end
end
end

View file

@ -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

View file

@ -1,4 +1,4 @@
class Registrar < ApplicationRecord
class Registrar < ApplicationRecord # rubocop:disable Metrics/ClassLength
include Versions # version/registrar_version.rb
include Registrar::BookKeeping
include EmailVerifable
@ -56,9 +56,48 @@ class Registrar < ApplicationRecord
end
end
def issue_prepayment_invoice(amount, description = nil, payable: true)
vat_rate = ::Invoice::VatRateCalculator.new(registrar: self).calculate
# rubocop:disable Metrics/MethodLength
def init_monthly_invoice(summary)
Invoice.new(
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: self,
buyer_name: name,
buyer_reg_no: reg_no,
buyer_country_code: address_country_code,
buyer_state: address_state,
buyer_street: address_street,
buyer_city: address_city,
buyer_zip: address_zip,
buyer_phone: phone,
buyer_url: website,
buyer_email: email,
reference_no: reference_no,
vat_rate: calculate_vat_rate,
monthly_invoice: true,
metadata: { items: summary['invoice_lines'] },
total: 0
)
end
def issue_prepayment_invoice(amount, description = nil, payable: true)
invoice = invoices.create!(
issue_date: Time.zone.today,
due_date: (Time.zone.now + Setting.days_to_keep_invoices_active.days).to_date,
@ -91,7 +130,7 @@ class Registrar < ApplicationRecord
buyer_url: website,
buyer_email: email,
reference_no: reference_no,
vat_rate: vat_rate,
vat_rate: calculate_vat_rate,
items_attributes: [
{
description: 'prepayment',
@ -107,23 +146,17 @@ class Registrar < ApplicationRecord
.deliver_later(wait: 1.minute)
end
if Feature.billing_system_integrated?
add_invoice_instance = EisBilling::AddDeposits.new(invoice)
result = add_invoice_instance.send_invoice
add_invoice_instance = EisBilling::AddDeposits.new(invoice)
result = add_invoice_instance.send_invoice
link = JSON.parse(result.body)['everypay_link']
link = JSON.parse(result.body)['everypay_link']
invoice.update(payment_link: link)
end
if Feature.billing_system_integrated?
SendEInvoiceTwoJob.set(wait: 1.minute).perform_now(invoice.id, payable: payable)
else
SendEInvoiceJob.set(wait: 1.minute).perform_now(invoice.id, payable: payable)
end
invoice.update(payment_link: link)
SendEInvoiceJob.set(wait: 1.minute).perform_now(invoice.id, payable: payable)
invoice
end
# rubocop:enable Metrics/MethodLength
def cash_account
accounts.find_by(account_type: Account::CASH)
@ -265,4 +298,8 @@ class Registrar < ApplicationRecord
def vat_liable_in_foreign_country?
!vat_liable_locally?
end
def calculate_vat_rate
::Invoice::VatRateCalculator.new(registrar: self).calculate
end
end

View file

@ -52,6 +52,10 @@ class DomainPresenter
end
end
def contact_emails_verification_failed
domain.contact_emails_verification_failed.join(', ')
end
def remove_registry_lock_btn
return unless domain.locked_by_registrant?

View file

@ -21,6 +21,7 @@ module EisBilling
data[:custom_field1] = invoice.description
data[:custom_field2] = INITIATOR
data[:invoice_number] = invoice.number
data[:reference_number] = invoice.reference_no
data
end

View file

@ -12,7 +12,7 @@
<div class="col-md-3">
<div class="form-group">
<%= f.label t(:status) %>
<%= select_tag :status, options_for_select(%w(Paid Unpaid Cancelled),params[:status]),
<%= select_tag :status, options_for_select(%w(Paid Unpaid Cancelled Monthly),params[:status]),
{ multiple: false, include_blank: true, selected: params[:status], class: 'form-control selectize'} %>
</div>
</div>

View file

@ -31,6 +31,8 @@
%td= l invoice.receipt_date
- elsif invoice.cancelled?
%td.text-grey= t(:cancelled)
- elsif invoice.monthly_invoice
%td= l invoice.issue_date
- else
%td.text-danger= t(:unpaid)

View file

@ -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')
@ -24,6 +25,9 @@
.col-md-6= render 'registrar/invoices/partials/seller'
.col-md-6= render 'registrar/invoices/partials/buyer'
.row
.col-md-12= render 'registrar/invoices/partials/items'
- if @invoice.monthly_invoice
.col-md-12= render 'registrar/invoices/partials/monthly_invoice_items'
- else
.col-md-12= render 'registrar/invoices/partials/items'
.row
.col-md-12= render 'registrar/invoices/partials/payment_orders'

View file

@ -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

View file

@ -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}

View file

@ -0,0 +1,45 @@
<p>Lugupeetud domeeni <%= @domain.name %> registreerija/halduskontakt</p>
<p>Eesti Interneti Sihtasutusele (EIS) on saanud teatavaks, et domeeni <%= @domain.name %> kontaktandmed on puudulikud - eposti aadress <%= @domain.contact_emails_verification_failed %>.</p>
<p>Et see olukord on vastuolus .ee <a href='https://www.internet.ee/domains/ee-domain-regulation'>domeenireeglitega</a> algatas EIS <%= @delete_period_length %> päeva pikkuse kustutusmenetluse. Menetluse käigus on domeen <%= @expire_warning_period %> esimest päeva internetis kättesaadav.</p>
<p>Andmete parandamiseks pöörduge palun oma registripidaja <%= @registrar.name %> poole või isiklike ja oma ettevõtte andmete puhul <a href="https://registrant.internet.ee/">registreerija portaali</a>.</p>
<p>Kui kontaktandmed ei ole <%= @delete_period_length %> päeva jooksul parandatud, läheb domeen <%= @domain.name %> <%= @domain.force_delete_date %> domeenioksjonile <a href="https://auction.internet.ee">.ee oksjonikeskkonda</a>. Juhul kui domeenile <%= @domain.name %> ei tehta oksjonil 24h möödudes pakkumist, domeen vabaneb ja on registreerimiseks vabalt kättesaadav kõigile huvilistele. Muude võimalike oksjoni tulemuste kohta loe <a href="https://www.internet.ee/domeenid/domeenide-oksjonikeskkonna-kasutajatingimused#3-oksjonikeskkonna-enampakkumisel-osalemise-tingimused">siit</a>.</p>
<p>Lisaküsimuste korral võtke palun ühendust oma registripidajaga:</p>
<%= render 'mailers/shared/registrar/registrar.et.html', registrar: @registrar %>
<%= render 'mailers/shared/signatures/signature.et.html' %>
<hr>
<p>Dear registrant/administrative contact of .ee domain,</p>
<p>Estonian Internet Foundation has learned that contact data of the domain <%= @domain.name %> s invalid - email(s) <%= @domain.contact_emails_verification_failed %>.</p>
<p>Since this is a violation of <a href='https://www.internet.ee/domains/ee-domain-regulation'>Estonian domain regulations</a>, <%= @delete_period_length %>-day deletion process has started for the <%= @domain.name %> domain. For the first <%= @expire_warning_period %> days the domain will remain available on the Internet during the deletion process.</p>
<p>Please, contact your registrar <%= @registrar.name %> with updated contact data, or in case of your personal or business data use <a href="https://registrant.internet.ee/">.ee portal for registrants</a></p>
<p>If the data is not fixed within <%= @delete_period_length %> days, the domain <%= @domain.name %> will go to domain auction on <%= @domain.force_delete_date %> in the <a href="https://auction.internet.ee">.ee auction environment</a>. If no offer is made for the domain <%= @domain.name %> at auction within 24 hours, the domain will be released and made freely available for registration to anyone interested on a first-come, first-served basis. Read more about other potential auction results <a href="https://www.internet.ee/domains/auction-environment-user-agreement#3-terms-and-conditions-for-participation-in-the-auction-of-the-auction-environment">here</a>.</p>
<p>Should you have additional questions, please contact your registrar:</p>
<%= render 'mailers/shared/registrar/registrar.en.html', registrar: @registrar %>
<%= render 'mailers/shared/signatures/signature.en.html' %>
<hr>
<p>Уважаемый регистрант/административный контакт домена .ee</p>
<p>Целевому учреждению Eesti Internet (EIS) стало известно, что контактные данные домена <%= @registrant.reg_no %> неверны - электронная почта <%= @domain.contact_emails_verification_failed %>.</p>
<p>Так как это является нарушением <a href='https://www.internet.ee/domains/ee-domain-regulation'>Правил домена .ee</a>, <%= @delete_period_length %>-дневный процесс удаления начат для доменного имени <%= @domain.name %>. В течение первых <%= @expire_warning_period %> дней домен будет доступен в интернете.</p>
<p>Для уточнения контактных данных, пожалуйста, свяжитесь с регистратором <%= @registrar.name %>, либо воспользуйтесь <a href="https://registrant.internet.ee/">порталом для регистрантов</a></p>
<p>Если контактные данные не будут исправлены в течение <%= @delete_period_length %> дней, домен <%= @domain.name %> отправится <%= @domain.force_delete_date %> на доменный аукцион в <a href="https://auction.internet.ee">аукционной среде.ee</a>. Если в течение 24 часов в отношении домена <%= @domain.name %> е поступит предложений, домен освободится и станет доступным для всех желающих по принципу «кто раньше». О других возможных результатах аукциона читайте <a href="https://www.internet.ee/domains/auction-environment-user-agreement#3-terms-and-conditions-for-participation-in-the-auction-of-the-auction-environment">здесь</a>.</p>
<p>В случае возникновения дополнительных вопросов свяжитесь, пожалуйста, со своим регистратором:
<%= render 'mailers/shared/registrar/registrar.ru.html', registrar: @registrar %></p>
<%= render 'mailers/shared/signatures/signature.ru.html' %>

View file

@ -0,0 +1,45 @@
<p>Lugupeetud domeeni <%= @domain.name %> registreerija/halduskontakt</p>
<p>Eesti Interneti Sihtasutusele (EIS) on saanud teatavaks, et domeeni <%= @domain.name %> kontaktandmed on puudulikud - eposti aadress <%= @domain.contact_emails_verification_failed %></p>
<p>Et see olukord on vastuolus .ee <a href='https://www.internet.ee/domains/ee-domain-regulation'>domeenireeglitega</a> algatas EIS <%= @delete_period_length %> päeva pikkuse kustutusmenetluse. Menetluse käigus on domeen <%= @expire_warning_period %> esimest päeva internetis kättesaadav.</p>
<p>Andmete parandamiseks pöörduge palun oma registripidaja <%= @registrar.name %> poole või isiklike ja oma ettevõtte andmete puhul <a href="https://registrant.internet.ee/">registreerija portaali</a>.</p>
<p>Kui kontaktandmed ei ole <%= @delete_period_length %> päeva jooksul parandatud, läheb domeen <%= @domain.name %> <%= @domain.force_delete_date %> domeenioksjonile <a href="https://auction.internet.ee">.ee oksjonikeskkonda</a>. Juhul kui domeenile <%= @domain.name %> ei tehta oksjonil 24h möödudes pakkumist, domeen vabaneb ja on registreerimiseks vabalt kättesaadav kõigile huvilistele. Muude võimalike oksjoni tulemuste kohta loe <a href="https://www.internet.ee/domeenid/domeenide-oksjonikeskkonna-kasutajatingimused#3-oksjonikeskkonna-enampakkumisel-osalemise-tingimused">siit</a>.</p>
<p>Lisaküsimuste korral võtke palun ühendust oma registripidajaga:</p>
<%= render 'mailers/shared/registrar/registrar.et.html', registrar: @registrar %>
<%= render 'mailers/shared/signatures/signature.et.html' %>
<hr>
<p>Dear registrant/administrative contact of .ee domain,</p>
<p>Estonian Internet Foundation has learned that contact data of the domain <%= @domain.name %> s invalid - email(s) <%= @domain.contact_emails_verification_failed %>.</p>
<p>Since this is a violation of <a href='https://www.internet.ee/domains/ee-domain-regulation'>Estonian domain regulations</a>, <%= @delete_period_length %>-day deletion process has started for the <%= @domain.name %> domain. For the first <%= @expire_warning_period %> days the domain will remain available on the Internet during the deletion process.</p>
<p>Please, contact your registrar <%= @registrar.name %> with updated contact data, or in case of your personal or business data use <a href="https://registrant.internet.ee/">.ee portal for registrants</a></p>
<p>If the data is not fixed within <%= @delete_period_length %> days, the domain <%= @domain.name %> will go to domain auction on <%= @domain.force_delete_date %> in the <a href="https://auction.internet.ee">.ee auction environment</a>. If no offer is made for the domain <%= @domain.name %> at auction within 24 hours, the domain will be released and made freely available for registration to anyone interested on a first-come, first-served basis. Read more about other potential auction results <a href="https://www.internet.ee/domains/auction-environment-user-agreement#3-terms-and-conditions-for-participation-in-the-auction-of-the-auction-environment">here</a>.</p>
<p>Should you have additional questions, please contact your registrar:</p>
<%= render 'mailers/shared/registrar/registrar.en.html', registrar: @registrar %>
<%= render 'mailers/shared/signatures/signature.en.html' %>
<hr>
<p>Уважаемый регистрант/административный контакт домена .ee</p>
<p>Целевому учреждению Eesti Internet (EIS) стало известно, что контактные данные домена <%= @registrant.reg_no %> неверны - электронная почта <%= @domain.contact_emails_verification_failed %>.</p>
<p>Так как это является нарушением <a href='https://www.internet.ee/domains/ee-domain-regulation'>Правил домена .ee</a>, <%= @delete_period_length %>-дневный процесс удаления начат для доменного имени <%= @domain.name %>. В течение первых <%= @expire_warning_period %> дней домен будет доступен в интернете.</p>
<p>Для уточнения контактных данных, пожалуйста, свяжитесь с регистратором <%= @registrar.name %>, либо воспользуйтесь <a href="https://registrant.internet.ee/">порталом для регистрантов</a></p>
<p>Если контактные данные не будут исправлены в течение <%= @delete_period_length %> дней, домен <%= @domain.name %> отправится <%= @domain.force_delete_date %> на доменный аукцион в <a href="https://auction.internet.ee">аукционной среде.ee</a>. Если в течение 24 часов в отношении домена <%= @domain.name %> е поступит предложений, домен освободится и станет доступным для всех желающих по принципу «кто раньше». О других возможных результатах аукциона читайте <a href="https://www.internet.ee/domains/auction-environment-user-agreement#3-terms-and-conditions-for-participation-in-the-auction-of-the-auction-environment">здесь</a>.</p>
<p>В случае возникновения дополнительных вопросов свяжитесь, пожалуйста, со своим регистратором:
<%= render 'mailers/shared/registrar/registrar.ru.html', registrar: @registrar %></p>
<%= render 'mailers/shared/signatures/signature.ru.html' %>

View file

@ -19,6 +19,8 @@
%dd= l @invoice.receipt_date
- elsif @invoice.cancelled?
%dd.text-grey= t(:cancelled)
- elsif @invoice.monthly_invoice
%dd= l @invoice.issue_date
- else
%dd{class: 'text-danger'}= t(:unpaid)

View file

@ -0,0 +1,38 @@
%h4= t(:items)
%hr
.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)

View file

@ -13,7 +13,10 @@
.col-md-6= render 'registrar/invoices/partials/seller'
.col-md-6= render 'registrar/invoices/partials/buyer'
.row
.col-md-12= render 'registrar/invoices/partials/items'
- if @invoice.monthly_invoice
.col-md-12= render 'registrar/invoices/partials/monthly_invoice_items'
- else
.col-md-12= render 'registrar/invoices/partials/items'
- if @invoice.payable?
.row.semifooter