Skip to content

Commit

Permalink
Merge pull request #2456 from internetee/modify-monthly-invoice-gener…
Browse files Browse the repository at this point in the history
…ation

Refactored monthly invoice generation job
  • Loading branch information
vohmar authored Oct 12, 2022
2 parents 4a0dc8e + 3a3e072 commit 8244066
Show file tree
Hide file tree
Showing 21 changed files with 390 additions and 512 deletions.
56 changes: 3 additions & 53 deletions app/jobs/directo_invoice_forward_job.rb
Original file line number Diff line number Diff line change
@@ -1,15 +1,8 @@
class DirectoInvoiceForwardJob < ApplicationJob
def perform(monthly: false, dry: false)
data = nil
def perform(dry: false)
data = collect_receipts_data

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)
EisBilling::SendDataToDirecto.send_request(object_data: data, monthly: false, dry: dry)
end

def collect_receipts_data
Expand Down Expand Up @@ -38,47 +31,4 @@ def valid_invoice_conditions?(invoice)

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
3 changes: 1 addition & 2 deletions app/jobs/send_e_invoice_job.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,10 @@ class SendEInvoiceJob < ApplicationJob

def perform(invoice_id, payable: true)
logger.info "Started to process e-invoice for invoice_id #{invoice_id}"
invoice = Invoice.find_by(id: invoice_id)
invoice = Invoice.find(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)
rescue StandardError => e
log_error(invoice: invoice, error: e)
raise e
Expand Down
158 changes: 39 additions & 119 deletions app/jobs/send_monthly_invoices_job.rb
Original file line number Diff line number Diff line change
@@ -1,147 +1,67 @@
class SendMonthlyInvoicesJob < ApplicationJob # rubocop:disable Metrics/ClassLength
queue_as :default
discard_on StandardError

def perform(dry: false)
def perform(dry: false, months_ago: 1)
@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)
@month = Time.zone.now - months_ago.month
@directo_data = []

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
invoices = find_or_init_monthly_invoices
assign_invoice_numbers(invoices)
return if invoices.empty? || @dry

invoices.each do |inv|
inv.send_to_registrar! unless inv.sent?
send_e_invoice(inv.id)
@directo_data << inv.as_monthly_directo_json unless inv.in_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}")
return if @directo_data.empty?

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')
EisBilling::SendDataToDirecto.send_request(object_data: @directo_data,
monthly: true)
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)
def assign_invoice_numbers(invoices)
invoice_without_numbers = invoices.select { |i| i.number.nil? }
return if invoice_without_numbers.empty?

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)
result = EisBilling::GetMonthlyInvoiceNumbers.send_request(invoice_without_numbers.size)
response = JSON.parse(result.body)
handle_assign_numbers_response_errors(response)

false
end
numbers = response['invoice_numbers']
invoice_without_numbers.each_with_index do |inv, index|
inv.number = numbers[index]
next if inv.save

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)
Rails.logger.info 'There was an error creating monthly ' \
"invoice #{inv.number}: #{inv.errors.full_messages.first}"
end
end
# rubocop:enable Metrics/MethodLength

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!
def find_or_init_monthly_invoices(invoices: [])
Registrar.with_cash_accounts.find_each do |registrar|
invoice = registrar.find_or_init_monthly_invoice(month: @month)
invoices << invoice unless invoice.nil?
end
invoices
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
def send_e_invoice(invoice_id)
SendEInvoiceJob.set(wait: 30.seconds).perform_later(invoice_id, payable: false)
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
def handle_assign_numbers_response_errors(response)
raise 'INVOICE NUMBER LIMIT REACHED, COULD NOT GENERATE INVOICE' if response['code'] == '403'
raise 'PROBLEM WITH TOKEN' if response['error'] == 'out of range'
end
end
55 changes: 55 additions & 0 deletions app/models/concerns/invoice/book_keeping.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ module Invoice::BookKeeping
def as_directo_json
invoice = ActiveSupport::JSON.decode(ActiveSupport::JSON.encode(self))
invoice['customer'] = compose_directo_customer
invoice['date'] = issue_date.strftime('%Y-%m-%d')
invoice['issue_date'] = issue_date.strftime('%Y-%m-%d')
invoice['transaction_date'] = account_activity
.bank_transaction&.paid_at&.strftime('%Y-%m-%d')
Expand All @@ -13,6 +14,60 @@ def as_directo_json
invoice
end

def as_monthly_directo_json
invoice = as_json(only: %i[issue_date due_date created_at
vat_rate description number currency])
invoice['customer'] = compose_directo_customer
invoice['date'] = issue_date.strftime('%Y-%m-%d')
invoice['language'] = buyer.language == 'en' ? 'ENG' : ''
invoice['invoice_lines'] = compose_monthly_directo_lines

invoice
end

def to_e_invoice(payable: true)
generator = Invoice::EInvoiceGenerator.new(self, payable)
generator.generate
end

def do_not_send_e_invoice?
e_invoice_sent? || cancelled?
end

def e_invoice_sent?
e_invoice_sent_at.present?
end

private

def compose_monthly_directo_lines(lines: [])
metadata['items'].each do |item|
quantity = item['quantity']
duration = item['duration_in_years']
lines << item and next if !quantity || quantity&.negative?

divide_by_quantity_and_years(quantity, duration, item, lines)
end
lines.as_json
end

# rubocop:disable Metrics/MethodLength
def divide_by_quantity_and_years(quantity, duration, item, lines)
quantity.times do
single_item = item.except('duration_in_years').merge('quantity' => 1)
lines << single_item and next if duration < 1

duration.times do |dur|
single_item_dup = single_item.dup
single_item_dup['start_date'] = (issue_date + dur.year).end_of_month.strftime('%Y-%m-%d')
single_item_dup['end_date'] = (issue_date + (dur + 1).year).end_of_month.strftime('%Y-%m-%d')
single_item_dup['price'] = (item['price'].to_f / duration).round(2)
lines << single_item_dup
end
end
end
# rubocop:enable Metrics/MethodLength

def compose_directo_product
[{ 'product_id': Setting.directo_receipt_product_name, 'description': order,
'quantity': 1, 'price': ActionController::Base.helpers.number_with_precision(
Expand Down
Loading

0 comments on commit 8244066

Please sign in to comment.