mirror of
https://github.com/internetee/registry.git
synced 2025-06-07 21:25:39 +02:00
Merge pull request #1547 from internetee/509-directo-to-gem
Directo: Reference BL from gem
This commit is contained in:
commit
8451aa7758
14 changed files with 443 additions and 263 deletions
2
Gemfile
2
Gemfile
|
@ -68,6 +68,8 @@ gem 'domain_name'
|
||||||
gem 'haml', '~> 5.0'
|
gem 'haml', '~> 5.0'
|
||||||
gem 'wkhtmltopdf-binary'
|
gem 'wkhtmltopdf-binary'
|
||||||
|
|
||||||
|
gem 'directo', github: 'internetee/directo', branch: 'master'
|
||||||
|
|
||||||
group :development do
|
group :development do
|
||||||
# deploy
|
# deploy
|
||||||
gem 'mina', '0.3.1' # for fast deployment
|
gem 'mina', '0.3.1' # for fast deployment
|
||||||
|
|
10
Gemfile.lock
10
Gemfile.lock
|
@ -7,6 +7,15 @@ GIT
|
||||||
activesupport
|
activesupport
|
||||||
savon
|
savon
|
||||||
|
|
||||||
|
GIT
|
||||||
|
remote: https://github.com/internetee/directo.git
|
||||||
|
revision: 8cb63d2fb91c640b264d5af05f4a6afbcfd46979
|
||||||
|
branch: master
|
||||||
|
specs:
|
||||||
|
directo (1.0.0)
|
||||||
|
money (~> 6.13)
|
||||||
|
nokogiri (~> 1.10)
|
||||||
|
|
||||||
GIT
|
GIT
|
||||||
remote: https://github.com/internetee/e_invoice.git
|
remote: https://github.com/internetee/e_invoice.git
|
||||||
revision: b374ffd7be77b559b30c7a0210dc0df5ac3ed723
|
revision: b374ffd7be77b559b30c7a0210dc0df5ac3ed723
|
||||||
|
@ -466,6 +475,7 @@ DEPENDENCIES
|
||||||
database_cleaner
|
database_cleaner
|
||||||
devise (~> 4.7)
|
devise (~> 4.7)
|
||||||
digidoc_client!
|
digidoc_client!
|
||||||
|
directo!
|
||||||
domain_name
|
domain_name
|
||||||
e_invoice!
|
e_invoice!
|
||||||
epp!
|
epp!
|
||||||
|
|
125
app/jobs/directo_invoice_forward_job.rb
Normal file
125
app/jobs/directo_invoice_forward_job.rb
Normal file
|
@ -0,0 +1,125 @@
|
||||||
|
class DirectoInvoiceForwardJob < Que::Job
|
||||||
|
def run(monthly: false, dry: false)
|
||||||
|
@dry = dry
|
||||||
|
(@month = Time.zone.now - 1.month) if monthly
|
||||||
|
api_url = ENV['directo_invoice_url']
|
||||||
|
sales_agent = Setting.directo_sales_agent
|
||||||
|
payment_term = Setting.directo_receipt_payment_term
|
||||||
|
@prepayment_product_id = Setting.directo_receipt_product_name
|
||||||
|
|
||||||
|
@client = DirectoApi::Client.new(api_url, sales_agent, payment_term)
|
||||||
|
monthly ? send_monthly_invoices : send_receipts
|
||||||
|
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|
|
||||||
|
fetch_monthly_summary(registrar: registrar)
|
||||||
|
end
|
||||||
|
|
||||||
|
return unless @client.invoices.count.positive?
|
||||||
|
|
||||||
|
sync_with_directo
|
||||||
|
end
|
||||||
|
|
||||||
|
def fetch_monthly_summary(registrar:)
|
||||||
|
return unless registrar.cash_account
|
||||||
|
|
||||||
|
summary = registrar.monthly_summary(month: @month)
|
||||||
|
@client.invoices.add_with_schema(invoice: summary, schema: 'summary') unless summary.nil?
|
||||||
|
end
|
||||||
|
|
||||||
|
def assign_monthly_numbers
|
||||||
|
if directo_counter_exceedable?(@client.invoices.count)
|
||||||
|
raise 'Directo Counter is going to be out of period!'
|
||||||
|
end
|
||||||
|
|
||||||
|
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
|
26
app/models/concerns/invoice/book_keeping.rb
Normal file
26
app/models/concerns/invoice/book_keeping.rb
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
module Concerns
|
||||||
|
module Invoice
|
||||||
|
module BookKeeping
|
||||||
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
|
def as_directo_json
|
||||||
|
invoice = ActiveSupport::JSON.decode(ActiveSupport::JSON.encode(self))
|
||||||
|
invoice['customer_code'] = buyer.accounting_customer_code
|
||||||
|
invoice['issue_date'] = issue_date.strftime('%Y-%m-%d')
|
||||||
|
invoice['transaction_date'] = account_activity
|
||||||
|
.bank_transaction&.paid_at&.strftime('%Y-%m-%d')
|
||||||
|
invoice['language'] = buyer.language == 'en' ? 'ENG' : ''
|
||||||
|
invoice['invoice_lines'] = compose_directo_product
|
||||||
|
|
||||||
|
invoice
|
||||||
|
end
|
||||||
|
|
||||||
|
def compose_directo_product
|
||||||
|
[{ 'product_id': Setting.directo_receipt_product_name, 'description': order,
|
||||||
|
'quantity': 1, 'price': ActionController::Base.helpers.number_with_precision(
|
||||||
|
subtotal, precision: 2, separator: '.'
|
||||||
|
) }].as_json
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
120
app/models/concerns/registrar/book_keeping.rb
Normal file
120
app/models/concerns/registrar/book_keeping.rb
Normal file
|
@ -0,0 +1,120 @@
|
||||||
|
module Concerns
|
||||||
|
module Registrar
|
||||||
|
module BookKeeping
|
||||||
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
|
DOMAIN_TO_PRODUCT = { 'ee': '01EE', 'com.ee': '02COM', 'pri.ee': '03PRI',
|
||||||
|
'fie.ee': '04FIE', 'med.ee': '05MED' }.freeze
|
||||||
|
|
||||||
|
def monthly_summary(month:)
|
||||||
|
activities = monthly_activites(month)
|
||||||
|
return unless activities.any?
|
||||||
|
|
||||||
|
invoice = {
|
||||||
|
'number': 1,
|
||||||
|
'customer_code': accounting_customer_code,
|
||||||
|
'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
|
||||||
|
|
||||||
|
def prepare_invoice_lines(month:, activities:)
|
||||||
|
lines = []
|
||||||
|
|
||||||
|
lines << { 'description': title_for_summary(month) }
|
||||||
|
activities.each do |activity|
|
||||||
|
fetch_invoice_lines(activity, lines)
|
||||||
|
end
|
||||||
|
lines << prepayment_for_all(lines)
|
||||||
|
|
||||||
|
lines.as_json
|
||||||
|
end
|
||||||
|
|
||||||
|
def title_for_summary(date)
|
||||||
|
I18n.with_locale(language == 'en' ? 'en' : 'et') do
|
||||||
|
I18n.t('registrar.monthly_summary_title', date: I18n.l(date, format: '%B %Y'))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def fetch_invoice_lines(activity, lines)
|
||||||
|
price = load_price(activity)
|
||||||
|
if price.duration.include? 'year'
|
||||||
|
price.duration.to_i.times do |duration|
|
||||||
|
lines << new_monthly_invoice_line(activity: activity, duration: duration + 1).as_json
|
||||||
|
end
|
||||||
|
else
|
||||||
|
lines << new_monthly_invoice_line(activity: activity).as_json
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def monthly_activites(month)
|
||||||
|
AccountActivity.where(account_id: account_ids)
|
||||||
|
.where(created_at: month.beginning_of_month..month.end_of_month)
|
||||||
|
.where(activity_type: [AccountActivity::CREATE, AccountActivity::RENEW])
|
||||||
|
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',
|
||||||
|
}
|
||||||
|
|
||||||
|
finalize_invoice_line(line, price: price, duration: duration, activity: activity)
|
||||||
|
end
|
||||||
|
|
||||||
|
def finalize_invoice_line(line, price:, activity:, duration:)
|
||||||
|
yearly = price.duration.include?('year')
|
||||||
|
|
||||||
|
line['price'] = yearly ? (price.price.amount / price.duration.to_i) : price.price.amount
|
||||||
|
line['description'] = description_in_language(price: price, yearly: yearly)
|
||||||
|
|
||||||
|
if duration.present?
|
||||||
|
add_product_timeframe(line: line, activity: activity, duration: duration) if duration > 1
|
||||||
|
end
|
||||||
|
|
||||||
|
line
|
||||||
|
end
|
||||||
|
|
||||||
|
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')
|
||||||
|
end
|
||||||
|
|
||||||
|
def description_in_language(price:, yearly:)
|
||||||
|
timeframe_string = yearly ? 'yearly' : 'monthly'
|
||||||
|
locale_string = "registrar.invoice_#{timeframe_string}_product_description"
|
||||||
|
|
||||||
|
I18n.with_locale(language == 'en' ? 'en' : 'et') do
|
||||||
|
I18n.t(locale_string, tld: ".#{price.zone_name}", length: price.duration.to_i)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def prepayment_for_all(lines)
|
||||||
|
total = 0
|
||||||
|
en = language == 'en'
|
||||||
|
lines.each { |l| total += l['quantity'].to_f * l['price'].to_f }
|
||||||
|
{
|
||||||
|
'product_id': Setting.directo_receipt_product_name,
|
||||||
|
'description': en ? 'Domains prepayment' : 'Domeenide ettemaks',
|
||||||
|
'quantity': -1,
|
||||||
|
'price': total,
|
||||||
|
'unit': en ? 'pc' : 'tk',
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def load_price(account_activity)
|
||||||
|
@pricelists ||= {}
|
||||||
|
return @pricelists[account_activity.price_id] if @pricelists.key? account_activity.price_id
|
||||||
|
|
||||||
|
@pricelists[account_activity.price_id] = account_activity.price
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,24 +0,0 @@
|
||||||
class Counter
|
|
||||||
def initialize value = 0
|
|
||||||
@value = value
|
|
||||||
end
|
|
||||||
attr_accessor :value
|
|
||||||
def method_missing *args, &blk
|
|
||||||
@value.send(*args, &blk)
|
|
||||||
end
|
|
||||||
def to_s
|
|
||||||
@value.to_s
|
|
||||||
end
|
|
||||||
|
|
||||||
def now
|
|
||||||
@value
|
|
||||||
end
|
|
||||||
|
|
||||||
# pre-increment ".+" when x not present
|
|
||||||
def next(x = 1)
|
|
||||||
@value += x
|
|
||||||
end
|
|
||||||
def prev(x = 1)
|
|
||||||
@value -= x
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,201 +1,3 @@
|
||||||
class Directo < ApplicationRecord
|
class Directo < ApplicationRecord
|
||||||
DOMAIN_TO_PRODUCT = {"ee" => "01EE", "com.ee" => "02COM", "pri.ee" => "03PRI", "fie.ee"=>"04FIE", "med.ee" => "05MED"}.freeze
|
|
||||||
belongs_to :item, polymorphic: true
|
belongs_to :item, polymorphic: true
|
||||||
|
|
||||||
def self.send_receipts
|
|
||||||
new_trans = Invoice.where(in_directo: false).non_cancelled
|
|
||||||
total = new_trans.count
|
|
||||||
counter = 0
|
|
||||||
Rails.logger.info("[DIRECTO] Will try to send #{total} invoices")
|
|
||||||
|
|
||||||
new_trans.find_in_batches(batch_size: 10).each do |group|
|
|
||||||
mappers = {} # need them as no direct connection between invoice
|
|
||||||
builder = Nokogiri::XML::Builder.new(encoding: "UTF-8") do |xml|
|
|
||||||
xml.invoices {
|
|
||||||
group.each do |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
|
|
||||||
Rails.logger.info("[DIRECTO] Invoice #{invoice.number} has been skipped")
|
|
||||||
next
|
|
||||||
end
|
|
||||||
counter += 1
|
|
||||||
|
|
||||||
num = invoice.number
|
|
||||||
paid_at = invoice.account_activity.bank_transaction&.paid_at&.strftime("%Y-%m-%d")
|
|
||||||
mappers[num] = invoice
|
|
||||||
xml.invoice(
|
|
||||||
"SalesAgent" => Setting.directo_sales_agent,
|
|
||||||
"Number" => num,
|
|
||||||
"InvoiceDate" => invoice.issue_date.strftime("%Y-%m-%d"),
|
|
||||||
'TransactionDate' => paid_at,
|
|
||||||
"PaymentTerm" => Setting.directo_receipt_payment_term,
|
|
||||||
"Currency" => invoice.currency,
|
|
||||||
"CustomerCode"=> invoice.buyer.accounting_customer_code
|
|
||||||
){
|
|
||||||
xml.line(
|
|
||||||
"ProductID" => Setting.directo_receipt_product_name,
|
|
||||||
"Quantity" => 1,
|
|
||||||
"UnitPriceWoVAT" => ActionController::Base.helpers.number_with_precision(invoice.subtotal, precision: 2, separator: "."),
|
|
||||||
"ProductName" => invoice.order
|
|
||||||
)
|
|
||||||
}
|
|
||||||
end
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
data = builder.to_xml.gsub("\n",'')
|
|
||||||
Rails.logger.info("[Directo] XML request: #{data}")
|
|
||||||
response = RestClient::Request.execute(url: ENV['directo_invoice_url'], method: :post, payload: {put: "1", what: "invoice", xmldata: data}, verify_ssl: false)
|
|
||||||
Rails.logger.info("[Directo] Directo responded with code: #{response.code}, body: #{response.body}")
|
|
||||||
dump_result_to_db(mappers: mappers, xml: response.to_s, data: data)
|
|
||||||
end
|
|
||||||
|
|
||||||
STDOUT << "#{Time.zone.now.utc} - Directo receipts sending finished. #{counter} of #{total} are sent\n"
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.dump_result_to_db(mappers:, xml:, data:)
|
|
||||||
Nokogiri::XML(xml).css("Result").each do |res|
|
|
||||||
obj = mappers[res.attributes["docid"].value.to_i]
|
|
||||||
obj.directo_records.create!(request: data,
|
|
||||||
response: res.as_json.to_h,
|
|
||||||
invoice_number: obj.number)
|
|
||||||
obj.update_columns(in_directo: true)
|
|
||||||
Rails.logger.info("[DIRECTO] Invoice #{res.attributes["docid"].value} was pushed and return is #{res.as_json.to_h.inspect}")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
def self.send_monthly_invoices(debug: false)
|
|
||||||
I18n.locale = :et unless Rails.env.test?
|
|
||||||
month = Time.now - 1.month
|
|
||||||
invoices_until = month.end_of_month
|
|
||||||
date_format = "%Y-%m-%d"
|
|
||||||
invoice_counter= Counter.new
|
|
||||||
|
|
||||||
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
|
|
||||||
if max_directo && (max_directo <= last_directo + Registrar.count)
|
|
||||||
raise 'Directo counter is out of period (max allowed number is smaller than last counter'\
|
|
||||||
'number plus Registrar\'s count)'
|
|
||||||
end
|
|
||||||
|
|
||||||
directo_next = last_directo
|
|
||||||
Registrar.where.not(test_registrar: true).find_each do |registrar|
|
|
||||||
unless registrar.cash_account
|
|
||||||
Rails.logger.info("[DIRECTO] Monthly invoice for registrar #{registrar.id} has been skipped as it doesn't has cash_account")
|
|
||||||
next
|
|
||||||
end
|
|
||||||
counter = Counter.new(1)
|
|
||||||
items = {}
|
|
||||||
registrar_activities = AccountActivity.where(account_id: registrar.account_ids).where("created_at BETWEEN ? AND ?",month.beginning_of_month, month.end_of_month)
|
|
||||||
|
|
||||||
# adding domains items
|
|
||||||
registrar_activities.where(activity_type: [AccountActivity::CREATE, AccountActivity::RENEW]).each do |activity|
|
|
||||||
price = load_price(activity)
|
|
||||||
|
|
||||||
if price.duration.include?('year')
|
|
||||||
price.duration.to_i.times do |i|
|
|
||||||
year = i+1
|
|
||||||
hash = {
|
|
||||||
"ProductID" => DOMAIN_TO_PRODUCT[price.zone_name],
|
|
||||||
"Unit" => "tk",
|
|
||||||
"ProductName" => ".#{price.zone_name} registreerimine: #{price.duration.to_i} aasta#{price.duration.to_i > 1 ? 't' : ''}",
|
|
||||||
"UnitPriceWoVAT" => price.price.amount / price.duration.to_i
|
|
||||||
}
|
|
||||||
hash["StartDate"] = (activity.created_at + (year-1).year).end_of_month.strftime(date_format) if year > 1
|
|
||||||
hash["EndDate"] = (activity.created_at + (year-1).year + 1).end_of_month.strftime(date_format) if year > 1
|
|
||||||
|
|
||||||
if items.has_key?(hash)
|
|
||||||
items[hash]["Quantity"] += 1
|
|
||||||
else
|
|
||||||
items[hash] = { "RN" => counter.next, "RR" => counter.now - i, "Quantity" => 1 }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
else
|
|
||||||
1.times do |i|
|
|
||||||
quantity = price.account_activities
|
|
||||||
.where(account_id: registrar.account_ids)
|
|
||||||
.where(created_at: month.beginning_of_month..month.end_of_month)
|
|
||||||
.where(activity_type: [AccountActivity::CREATE, AccountActivity::RENEW])
|
|
||||||
.count
|
|
||||||
|
|
||||||
hash = {
|
|
||||||
"ProductID" => DOMAIN_TO_PRODUCT[price.zone_name],
|
|
||||||
"Unit" => "tk",
|
|
||||||
"ProductName" => ".#{price.zone_name} registreerimine: #{price.duration.to_i} kuud",
|
|
||||||
"UnitPriceWoVAT" => price.price.amount,
|
|
||||||
}
|
|
||||||
|
|
||||||
if items.has_key?(hash)
|
|
||||||
#items[hash]["Quantity"] += 1
|
|
||||||
else
|
|
||||||
items[hash] = { "RN" => counter.next, "RR" => counter.now - i, "Quantity" => quantity }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
#adding prepaiments
|
|
||||||
if items.any?
|
|
||||||
total = 0
|
|
||||||
items.each{ |key, val| total += val["Quantity"] * key["UnitPriceWoVAT"] }
|
|
||||||
hash = {"ProductID" => Setting.directo_receipt_product_name, "Unit" => "tk", "ProductName" => "Domeenide ettemaks", "UnitPriceWoVAT"=>total}
|
|
||||||
items[hash] = {"RN"=>counter.next, "RR" => counter.now, "Quantity"=> -1}
|
|
||||||
end
|
|
||||||
|
|
||||||
# generating XML
|
|
||||||
if items.any?
|
|
||||||
directo_next += 1
|
|
||||||
invoice_counter.next
|
|
||||||
|
|
||||||
builder = Nokogiri::XML::Builder.new(encoding: "UTF-8") do |xml|
|
|
||||||
xml.invoices{
|
|
||||||
xml.invoice("Number" =>directo_next,
|
|
||||||
"InvoiceDate" =>invoices_until.strftime(date_format),
|
|
||||||
"PaymentTerm" =>Setting.directo_receipt_payment_term,
|
|
||||||
"CustomerCode"=>registrar.accounting_customer_code,
|
|
||||||
"Language" =>"",
|
|
||||||
"Currency" =>registrar_activities.first.currency,
|
|
||||||
"SalesAgent" =>Setting.directo_sales_agent){
|
|
||||||
xml.line("RN" => 1, "RR"=>1, "ProductName"=> "Domeenide registreerimine - #{I18n.l(invoices_until, format: "%B %Y").titleize}")
|
|
||||||
items.each do |line, val|
|
|
||||||
xml.line(val.merge(line))
|
|
||||||
end
|
|
||||||
}
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
data = builder.to_xml.gsub("\n",'')
|
|
||||||
Rails.logger.info("[Directo] XML request: #{data}")
|
|
||||||
|
|
||||||
if debug
|
|
||||||
STDOUT << "#{Time.zone.now.utc} - Directo xml had to be sent #{data}\n"
|
|
||||||
else
|
|
||||||
response = RestClient::Request.execute(url: ENV['directo_invoice_url'], method: :post, payload: {put: "1", what: "invoice", xmldata: data}, verify_ssl: false)
|
|
||||||
Rails.logger.info("[Directo] Directo responded with code: #{response.code}, body: #{response.body}")
|
|
||||||
response = response.to_s
|
|
||||||
|
|
||||||
Setting.directo_monthly_number_last = directo_next
|
|
||||||
Nokogiri::XML(response).css("Result").each do |res|
|
|
||||||
Directo.create!(request: data, response: res.as_json.to_h, invoice_number: directo_next)
|
|
||||||
Rails.logger.info("[DIRECTO] Invoice #{res.attributes["docid"].value} was pushed and return is #{res.as_json.to_h.inspect}")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
else
|
|
||||||
Rails.logger.info("[DIRECTO] Registrar #{registrar.id} has nothing to be sent to Directo")
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
STDOUT << "#{Time.zone.now.utc} - Directo invoices sending finished. #{invoice_counter.now} are sent\n"
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.load_price(account_activity)
|
|
||||||
@pricelists ||= {}
|
|
||||||
return @pricelists[account_activity.price_id] if @pricelists.has_key?(account_activity.price_id)
|
|
||||||
@pricelists[account_activity.price_id] = account_activity.price
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -2,6 +2,7 @@ class Invoice < ApplicationRecord
|
||||||
include Versions
|
include Versions
|
||||||
include Concerns::Invoice::Cancellable
|
include Concerns::Invoice::Cancellable
|
||||||
include Concerns::Invoice::Payable
|
include Concerns::Invoice::Payable
|
||||||
|
include Concerns::Invoice::BookKeeping
|
||||||
|
|
||||||
belongs_to :buyer, class_name: 'Registrar'
|
belongs_to :buyer, class_name: 'Registrar'
|
||||||
has_one :account_activity
|
has_one :account_activity
|
||||||
|
@ -71,7 +72,7 @@ class Invoice < ApplicationRecord
|
||||||
Country.new(buyer_country_code)
|
Country.new(buyer_country_code)
|
||||||
end
|
end
|
||||||
|
|
||||||
# order is used for directo/banklink description
|
# order is used for directo/banklink description
|
||||||
def order
|
def order
|
||||||
"Order nr. #{number}"
|
"Order nr. #{number}"
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
class Registrar < ApplicationRecord
|
class Registrar < ApplicationRecord
|
||||||
include Versions # version/registrar_version.rb
|
include Versions # version/registrar_version.rb
|
||||||
|
include Concerns::Registrar::BookKeeping
|
||||||
|
|
||||||
has_many :domains, dependent: :restrict_with_error
|
has_many :domains, dependent: :restrict_with_error
|
||||||
has_many :contacts, dependent: :restrict_with_error
|
has_many :contacts, dependent: :restrict_with_error
|
||||||
|
|
|
@ -1,4 +1,8 @@
|
||||||
en:
|
en:
|
||||||
|
registrar:
|
||||||
|
invoice_yearly_product_description: '%{tld} registration: %{length} year(s)'
|
||||||
|
invoice_monthly_product_description: '%{tld} registration: %{length} month(s)'
|
||||||
|
monthly_summary_title: 'Domain registrations - %{date}'
|
||||||
activerecord:
|
activerecord:
|
||||||
errors:
|
errors:
|
||||||
models:
|
models:
|
||||||
|
|
5
config/locales/registrars.et.yml
Normal file
5
config/locales/registrars.et.yml
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
et:
|
||||||
|
registrar:
|
||||||
|
invoice_yearly_product_description: '%{tld} registreerimine: %{length} aasta(t)'
|
||||||
|
invoice_monthly_product_description: '%{tld} registreerimine: %{length} kuu(d)'
|
||||||
|
monthly_summary_title: 'Domeenide registreerimine - %{date}'
|
146
test/jobs/directo_invoice_forward_job_test.rb
Normal file
146
test/jobs/directo_invoice_forward_job_test.rb
Normal file
|
@ -0,0 +1,146 @@
|
||||||
|
require "test_helper"
|
||||||
|
|
||||||
|
class DirectoInvoiceForwardJobTest < ActiveSupport::TestCase
|
||||||
|
setup do
|
||||||
|
@invoice = invoices(:one)
|
||||||
|
@user = registrars(:bestnames)
|
||||||
|
travel_to Time.zone.parse('2010-08-06')
|
||||||
|
end
|
||||||
|
|
||||||
|
def teardown
|
||||||
|
Setting.clear_cache
|
||||||
|
Setting.directo_monthly_number_min = 309901
|
||||||
|
Setting.directo_monthly_number_max = 309999
|
||||||
|
Setting.directo_monthly_number_last = 309901
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_xml_is_include_transaction_date
|
||||||
|
@invoice.update(total: @invoice.account_activity.bank_transaction.sum)
|
||||||
|
@invoice.account_activity.bank_transaction.update(paid_at: Time.zone.now)
|
||||||
|
|
||||||
|
response = <<-XML
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<results>
|
||||||
|
<Result Type="0" Desc="OK" docid="1" doctype="ARVE" submit="Invoices"/>
|
||||||
|
</results>
|
||||||
|
XML
|
||||||
|
|
||||||
|
stub_request(:post, ENV['directo_invoice_url']).with do |request|
|
||||||
|
request.body.include? 'TransactionDate'
|
||||||
|
end.to_return(status: 200, body: response)
|
||||||
|
|
||||||
|
assert_nothing_raised do
|
||||||
|
DirectoInvoiceForwardJob.run(monthly: false, dry: false)
|
||||||
|
end
|
||||||
|
|
||||||
|
assert_not_empty @invoice.directo_records.first.request
|
||||||
|
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 = 30991
|
||||||
|
|
||||||
|
assert_raises 'RuntimeError' do
|
||||||
|
DirectoInvoiceForwardJob.run(monthly: true, dry: false)
|
||||||
|
end
|
||||||
|
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 version="1.0" encoding="UTF-8"?>
|
||||||
|
<results>
|
||||||
|
<Result Type="0" Desc="OK" docid="309902" doctype="ARVE" submit="Invoices"/>
|
||||||
|
</results>
|
||||||
|
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
|
||||||
|
DirectoInvoiceForwardJob.run(monthly: true, dry: false)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_monthly_summary_is_delivered_in_english
|
||||||
|
activity = account_activities(:one)
|
||||||
|
price = billing_prices(:create_one_year)
|
||||||
|
activity.update(activity_type: 'create', price: price)
|
||||||
|
@user.update(language: 'en')
|
||||||
|
|
||||||
|
response = <<-XML
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<results>
|
||||||
|
<Result Type="0" Desc="OK" docid="309902" doctype="ARVE" submit="Invoices"/>
|
||||||
|
</results>
|
||||||
|
XML
|
||||||
|
|
||||||
|
stub_request(:post, ENV['directo_invoice_url']).with do |request|
|
||||||
|
body = CGI.unescape(request.body)
|
||||||
|
(body.include? 'test registration') &&
|
||||||
|
(body.include? 'Domains prepayment') &&
|
||||||
|
(body.include? '309902')
|
||||||
|
end.to_return(status: 200, body: response)
|
||||||
|
|
||||||
|
assert_difference 'Setting.directo_monthly_number_last' do
|
||||||
|
DirectoInvoiceForwardJob.run(monthly: true, dry: false)
|
||||||
|
end
|
||||||
|
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 version="1.0" encoding="UTF-8"?>
|
||||||
|
<results>
|
||||||
|
<Result Type="0" Desc="OK" docid="309902" doctype="ARVE" submit="Invoices"/>
|
||||||
|
</results>
|
||||||
|
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
|
||||||
|
DirectoInvoiceForwardJob.run(monthly: true, dry: false)
|
||||||
|
end
|
||||||
|
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 version="1.0" encoding="UTF-8"?>
|
||||||
|
<results>
|
||||||
|
<Result Type="0" Desc="OK" docid="309902" doctype="ARVE" submit="Invoices"/>
|
||||||
|
</results>
|
||||||
|
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
|
||||||
|
DirectoInvoiceForwardJob.run(monthly: true, dry: false)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,42 +1,4 @@
|
||||||
require 'test_helper'
|
require 'test_helper'
|
||||||
|
|
||||||
class DirectoTest < ActiveSupport::TestCase
|
class DirectoTest < ActiveSupport::TestCase
|
||||||
setup do
|
|
||||||
@invoice = invoices(:one)
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_monthly_invoices_max_range_raises_if_overlaps
|
|
||||||
|
|
||||||
Setting.directo_monthly_number_max = Setting.directo_monthly_number_last.to_i + Registrar.count - 1
|
|
||||||
error_message = 'Directo counter is out of period (max allowed number is smaller than last '\
|
|
||||||
'counternumber plus Registrar\'s count)'
|
|
||||||
|
|
||||||
error = assert_raises RuntimeError do
|
|
||||||
Directo.send_monthly_invoices
|
|
||||||
end
|
|
||||||
|
|
||||||
assert_equal error_message, error.message
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_xml_is_include_transaction_date
|
|
||||||
@invoice.update(total: @invoice.account_activity.bank_transaction.sum)
|
|
||||||
@invoice.account_activity.bank_transaction.update(paid_at: Time.zone.now)
|
|
||||||
|
|
||||||
response = <<-XML
|
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<results>
|
|
||||||
<Result Type="0" Desc="OK" docid="1" doctype="ARVE" submit="Invoices"/>
|
|
||||||
</results>
|
|
||||||
XML
|
|
||||||
|
|
||||||
stub_request(:post, ENV['directo_invoice_url']).with do |request|
|
|
||||||
request.body.include? 'TransactionDate'
|
|
||||||
end.to_return(status: 200, body: response)
|
|
||||||
|
|
||||||
assert_nothing_raised do
|
|
||||||
Directo.send_receipts
|
|
||||||
end
|
|
||||||
|
|
||||||
assert_not_empty @invoice.directo_records.first.request
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue