diff --git a/Gemfile.lock b/Gemfile.lock index 4144c9ed3..a49c9becb 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -18,7 +18,7 @@ GIT GIT remote: https://github.com/internetee/e_invoice.git - revision: b374ffd7be77b559b30c7a0210dc0df5ac3ed723 + revision: 5f8d0029bf1affdbf2bd6e3d1ce87d34066add4d branch: master specs: e_invoice (0.1.0) @@ -238,7 +238,7 @@ GEM http-cookie (1.0.3) domain_name (~> 0.5) httpclient (2.8.3) - httpi (2.4.4) + httpi (2.4.5) rack socksify i18n (1.8.5) @@ -467,7 +467,8 @@ GEM i18n warden (1.2.8) rack (>= 2.0.6) - wasabi (3.5.0) + wasabi (3.6.1) + addressable httpi (~> 2.0) nokogiri (>= 1.4.2) webdrivers (4.4.1) diff --git a/app/jobs/send_e_invoice_job.rb b/app/jobs/send_e_invoice_job.rb index e281db14d..91e068b9f 100644 --- a/app/jobs/send_e_invoice_job.rb +++ b/app/jobs/send_e_invoice_job.rb @@ -1,8 +1,8 @@ class SendEInvoiceJob < Que::Job - def run(invoice_id) - invoice = run_condition(Invoice.find_by(id: invoice_id)) + def run(invoice_id, payable: true) + invoice = run_condition(Invoice.find_by(id: invoice_id), payable: payable) - invoice.to_e_invoice.deliver + invoice.to_e_invoice(payable: payable).deliver ActiveRecord::Base.transaction do invoice.update(e_invoice_sent_at: Time.zone.now) log_success(invoice) @@ -15,9 +15,9 @@ class SendEInvoiceJob < Que::Job private - def run_condition(invoice) + def run_condition(invoice, payable: true) destroy unless invoice - destroy if invoice.do_not_send_e_invoice? + destroy if invoice.do_not_send_e_invoice? && payable invoice end diff --git a/app/models/bank_transaction.rb b/app/models/bank_transaction.rb index a610b41f8..24bf51e0c 100644 --- a/app/models/bank_transaction.rb +++ b/app/models/bank_transaction.rb @@ -31,16 +31,18 @@ class BankTransaction < ApplicationRecord @registrar ||= Invoice.find_by(reference_no: parsed_ref_number)&.buyer end + def autobindable? + !binded? && registrar && invoice.payable? ? true : false + rescue NoMethodError + false + end + # For successful binding, reference number, invoice id and sum must match with the invoice def autobind_invoice(manual: false) - return if binded? - return unless registrar - return unless invoice - return unless invoice.payable? + return unless autobindable? channel = manual ? 'admin_payment' : 'system_payment' - create_internal_payment_record(channel: channel, invoice: invoice, - registrar: registrar) + create_internal_payment_record(channel: channel, invoice: invoice, registrar: registrar) end def create_internal_payment_record(channel: nil, invoice:, registrar:) @@ -89,12 +91,11 @@ class BankTransaction < ApplicationRecord end def create_activity(registrar, invoice) - activity = AccountActivity.new( - account: registrar.cash_account, bank_transaction: self, - invoice: invoice, sum: invoice.subtotal, - currency: currency, description: description, - activity_type: AccountActivity::ADD_CREDIT - ) + activity = AccountActivity.new(account: registrar.cash_account, bank_transaction: self, + invoice: invoice, sum: invoice.subtotal, + currency: currency, description: description, + activity_type: AccountActivity::ADD_CREDIT) + if activity.save reset_pending_registrar_balance_reload true @@ -103,6 +104,10 @@ class BankTransaction < ApplicationRecord end end + def parsed_ref_number + reference_no || ref_number_from_description + end + private def reset_pending_registrar_balance_reload @@ -112,17 +117,12 @@ class BankTransaction < ApplicationRecord registrar.save! end - def parsed_ref_number - reference_no || ref_number_from_description - end - def ref_number_from_description - (Billing::ReferenceNo::MULTI_REGEXP.match(description) || []).captures.each do |match| - break match if match.length == 7 || valid_ref_no?(match) - end + matches = description.to_s.scan(Billing::ReferenceNo::MULTI_REGEXP).flatten + matches.detect { |m| break m if m.length == 7 || valid_ref_no?(m) } end def valid_ref_no?(match) - return true if Billing::ReferenceNo.valid?(match) && Registrar.find_by(reference_no: match).any? + return true if Billing::ReferenceNo.valid?(match) && Registrar.find_by(reference_no: match) end end diff --git a/app/models/invoice.rb b/app/models/invoice.rb index a130a90ff..8e82bbea6 100644 --- a/app/models/invoice.rb +++ b/app/models/invoice.rb @@ -99,8 +99,8 @@ class Invoice < ApplicationRecord generator.as_pdf end - def to_e_invoice - generator = Invoice::EInvoiceGenerator.new(self) + def to_e_invoice(payable: true) + generator = Invoice::EInvoiceGenerator.new(self, payable: payable) generator.generate end @@ -112,6 +112,15 @@ class Invoice < ApplicationRecord e_invoice_sent_at.present? end + def self.create_from_transaction!(transaction) + registrar_user = Registrar.find_by(reference_no: transaction.parsed_ref_number) + return unless registrar_user + + vat = VatRateCalculator.new(registrar: registrar_user).calculate + net = (transaction.sum / (1 + (vat / 100))) + registrar_user.issue_prepayment_invoice(net, 'Direct top-up via bank transfer', payable: false) + end + private def apply_default_buyer_vat_no @@ -119,6 +128,6 @@ class Invoice < ApplicationRecord end def calculate_total - self.total = subtotal + vat_amount + self.total = (subtotal + vat_amount).round(3) end end diff --git a/app/models/invoice/e_invoice_generator.rb b/app/models/invoice/e_invoice_generator.rb index 9a2ab2e01..d2963b93e 100644 --- a/app/models/invoice/e_invoice_generator.rb +++ b/app/models/invoice/e_invoice_generator.rb @@ -1,9 +1,11 @@ class Invoice class EInvoiceGenerator attr_reader :invoice + attr_reader :payable - def initialize(invoice) + def initialize(invoice, payable) @invoice = invoice + @payable = payable end def generate @@ -70,9 +72,10 @@ class Invoice i.total = invoice.total i.currency = invoice.currency i.delivery_channel = %i[internet_bank portal] + i.payable = payable end EInvoice::EInvoice.new(date: Time.zone.today, invoice: e_invoice_invoice) end end -end \ No newline at end of file +end diff --git a/app/models/invoice_item.rb b/app/models/invoice_item.rb index ec0c77767..61339f5cb 100644 --- a/app/models/invoice_item.rb +++ b/app/models/invoice_item.rb @@ -5,7 +5,7 @@ class InvoiceItem < ApplicationRecord delegate :vat_rate, to: :invoice def item_sum_without_vat - (price * quantity).round(2) + (price * quantity).round(3) end alias_method :subtotal, :item_sum_without_vat @@ -14,6 +14,6 @@ class InvoiceItem < ApplicationRecord end def total - subtotal + vat_amount + (subtotal + vat_amount) end -end \ No newline at end of file +end diff --git a/app/models/registrar.rb b/app/models/registrar.rb index 470d768b7..e5020d83f 100644 --- a/app/models/registrar.rb +++ b/app/models/registrar.rb @@ -54,7 +54,7 @@ class Registrar < ApplicationRecord end end - def issue_prepayment_invoice(amount, description = nil) + def issue_prepayment_invoice(amount, description = nil, payable: true) vat_rate = ::Invoice::VatRateCalculator.new(registrar: self).calculate invoice = invoices.create!( @@ -99,7 +99,12 @@ class Registrar < ApplicationRecord } ] ) - SendEInvoiceJob.enqueue(invoice.id) + + unless payable + InvoiceMailer.invoice_email(invoice: invoice, recipient: billing_email).deliver_now + end + + SendEInvoiceJob.enqueue(invoice.id, payable: payable) invoice end diff --git a/db/migrate/20200910085157_change_invoice_item_price_scale_to_three_places.rb b/db/migrate/20200910085157_change_invoice_item_price_scale_to_three_places.rb new file mode 100644 index 000000000..f1f41343b --- /dev/null +++ b/db/migrate/20200910085157_change_invoice_item_price_scale_to_three_places.rb @@ -0,0 +1,5 @@ +class ChangeInvoiceItemPriceScaleToThreePlaces < ActiveRecord::Migration[6.0] + def change + change_column :invoice_items, :price, :decimal, precision: 10, scale: 3 + end +end diff --git a/db/structure.sql b/db/structure.sql index 1e32bf318..912946c60 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -962,7 +962,7 @@ CREATE TABLE public.invoice_items ( description character varying NOT NULL, unit character varying NOT NULL, quantity integer NOT NULL, - price numeric(10,2) NOT NULL, + price numeric(10,3) NOT NULL, created_at timestamp without time zone, updated_at timestamp without time zone, creator_str character varying, @@ -4850,6 +4850,7 @@ INSERT INTO "schema_migrations" (version) VALUES ('20200811074839'), ('20200812090409'), ('20200812125810'), -('20200908131554'); +('20200908131554'), +('20200910085157'); diff --git a/lib/tasks/invoices/process_payments.rake b/lib/tasks/invoices/process_payments.rake index 340aba187..3e02a8838 100644 --- a/lib/tasks/invoices/process_payments.rake +++ b/lib/tasks/invoices/process_payments.rake @@ -36,6 +36,8 @@ namespace :invoices do reference_no: incoming_transaction.payment_reference_number, description: incoming_transaction.payment_description } transaction = bank_statement.bank_transactions.create!(transaction_attributes) + Invoice.create_from_transaction!(transaction) unless transaction.autobindable? + transaction.autobind_invoice end end diff --git a/test/models/bank_transaction_test.rb b/test/models/bank_transaction_test.rb index 944b47573..9a9b02a74 100644 --- a/test/models/bank_transaction_test.rb +++ b/test/models/bank_transaction_test.rb @@ -156,6 +156,24 @@ class BankTransactionTest < ActiveSupport::TestCase assert transaction.errors.full_messages.include?('Cannot bind cancelled invoice') end + def test_assumes_7_digit_number_is_reference_no_in_desc + statement = BankTransaction.new + statement.description = 'number 1234567 defo valid' + assert_equal '1234567', statement.parsed_ref_number + end + + def test_determines_correct_ref_no_from_description + statement = BankTransaction.new + ref_no = registrars(:bestnames).reference_no + statement.description = "invoice 123 125 55 4521 #{ref_no} 7541 defo valid" + assert_equal ref_no.to_s, statement.parsed_ref_number + end + + def test_parsed_ref_no_returns_nil_if_ref_not_found + statement = BankTransaction.new + statement.description = "all invalid 12 123 55 77777 --" + assert_nil statement.parsed_ref_number + end private def create_payable_invoice(attributes) diff --git a/test/models/invoice_test.rb b/test/models/invoice_test.rb index 9c1c45610..150e8032c 100644 --- a/test/models/invoice_test.rb +++ b/test/models/invoice_test.rb @@ -1,6 +1,8 @@ require 'test_helper' class InvoiceTest < ActiveSupport::TestCase + include ActionMailer::TestHelper + setup do @invoice = invoices(:one) end @@ -109,4 +111,33 @@ class InvoiceTest < ActiveSupport::TestCase seller_zip: nil) assert_equal 'street, city, state', invoice.seller_address end + + def test_creates_invoice_with_bank_transaction_total + registrar = registrars(:bestnames) + transaction = bank_transactions(:one).dup + transaction.reference_no = registrar.reference_no + transaction.sum = 250 + + invoice = Invoice.create_from_transaction!(transaction) + assert_equal 250, invoice.total + + transaction.sum = 146.88 + invoice = Invoice.create_from_transaction!(transaction) + assert_equal 146.88, invoice.total + + transaction.sum = 0.99 + invoice = Invoice.create_from_transaction!(transaction) + assert_equal 0.99, invoice.total + end + + def test_emails_invoice_after_creating_topup_invoice + registrar = registrars(:bestnames) + transaction = bank_transactions(:one).dup + transaction.reference_no = registrar.reference_no + transaction.sum = 250 + + assert_emails 1 do + Invoice.create_from_transaction!(transaction) + end + end end diff --git a/test/tasks/invoices/process_payments_test.rb b/test/tasks/invoices/process_payments_test.rb index bd447be29..eeaf411cc 100644 --- a/test/tasks/invoices/process_payments_test.rb +++ b/test/tasks/invoices/process_payments_test.rb @@ -82,6 +82,25 @@ class ProcessPaymentsTaskTest < ActiveSupport::TestCase assert payment_order.failed? end + def test_credits_registrar_account_without_invoice_beforehand + registrar = registrars(:bestnames) + + assert_changes -> { registrar.accounts.first.balance } do + run_task + end + + assert_changes -> { registrar.invoices.count } do + run_task + end + end + + def test_topup_creates_invoice_with_total_of_transactioned_amount + registrar = registrars(:bestnames) + run_task + + assert_equal 0.1, registrar.invoices.last.total + end + def test_output assert_output "Transactions processed: 1\n" do run_task