Merge pull request #1676 from internetee/1101-topping-up-credit-account-without-an-invoice

Process payments: Allow direct top up via bank transfer
This commit is contained in:
Timo Võhmar 2020-09-11 11:48:44 +03:00 committed by GitHub
commit 90b6a0a693
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 134 additions and 40 deletions

View file

@ -18,7 +18,7 @@ GIT
GIT GIT
remote: https://github.com/internetee/e_invoice.git remote: https://github.com/internetee/e_invoice.git
revision: b374ffd7be77b559b30c7a0210dc0df5ac3ed723 revision: 5f8d0029bf1affdbf2bd6e3d1ce87d34066add4d
branch: master branch: master
specs: specs:
e_invoice (0.1.0) e_invoice (0.1.0)
@ -238,7 +238,7 @@ GEM
http-cookie (1.0.3) http-cookie (1.0.3)
domain_name (~> 0.5) domain_name (~> 0.5)
httpclient (2.8.3) httpclient (2.8.3)
httpi (2.4.4) httpi (2.4.5)
rack rack
socksify socksify
i18n (1.8.5) i18n (1.8.5)
@ -467,7 +467,8 @@ GEM
i18n i18n
warden (1.2.8) warden (1.2.8)
rack (>= 2.0.6) rack (>= 2.0.6)
wasabi (3.5.0) wasabi (3.6.1)
addressable
httpi (~> 2.0) httpi (~> 2.0)
nokogiri (>= 1.4.2) nokogiri (>= 1.4.2)
webdrivers (4.4.1) webdrivers (4.4.1)

View file

@ -1,8 +1,8 @@
class SendEInvoiceJob < Que::Job class SendEInvoiceJob < Que::Job
def run(invoice_id) def run(invoice_id, payable: true)
invoice = run_condition(Invoice.find_by(id: invoice_id)) 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 ActiveRecord::Base.transaction do
invoice.update(e_invoice_sent_at: Time.zone.now) invoice.update(e_invoice_sent_at: Time.zone.now)
log_success(invoice) log_success(invoice)
@ -15,9 +15,9 @@ class SendEInvoiceJob < Que::Job
private private
def run_condition(invoice) def run_condition(invoice, payable: true)
destroy unless invoice destroy unless invoice
destroy if invoice.do_not_send_e_invoice? destroy if invoice.do_not_send_e_invoice? && payable
invoice invoice
end end

View file

@ -31,16 +31,18 @@ class BankTransaction < ApplicationRecord
@registrar ||= Invoice.find_by(reference_no: parsed_ref_number)&.buyer @registrar ||= Invoice.find_by(reference_no: parsed_ref_number)&.buyer
end 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 # For successful binding, reference number, invoice id and sum must match with the invoice
def autobind_invoice(manual: false) def autobind_invoice(manual: false)
return if binded? return unless autobindable?
return unless registrar
return unless invoice
return unless invoice.payable?
channel = manual ? 'admin_payment' : 'system_payment' channel = manual ? 'admin_payment' : 'system_payment'
create_internal_payment_record(channel: channel, invoice: invoice, create_internal_payment_record(channel: channel, invoice: invoice, registrar: registrar)
registrar: registrar)
end end
def create_internal_payment_record(channel: nil, invoice:, registrar:) def create_internal_payment_record(channel: nil, invoice:, registrar:)
@ -89,12 +91,11 @@ class BankTransaction < ApplicationRecord
end end
def create_activity(registrar, invoice) def create_activity(registrar, invoice)
activity = AccountActivity.new( activity = AccountActivity.new(account: registrar.cash_account, bank_transaction: self,
account: registrar.cash_account, bank_transaction: self, invoice: invoice, sum: invoice.subtotal,
invoice: invoice, sum: invoice.subtotal, currency: currency, description: description,
currency: currency, description: description, activity_type: AccountActivity::ADD_CREDIT)
activity_type: AccountActivity::ADD_CREDIT
)
if activity.save if activity.save
reset_pending_registrar_balance_reload reset_pending_registrar_balance_reload
true true
@ -103,6 +104,10 @@ class BankTransaction < ApplicationRecord
end end
end end
def parsed_ref_number
reference_no || ref_number_from_description
end
private private
def reset_pending_registrar_balance_reload def reset_pending_registrar_balance_reload
@ -112,17 +117,12 @@ class BankTransaction < ApplicationRecord
registrar.save! registrar.save!
end end
def parsed_ref_number
reference_no || ref_number_from_description
end
def ref_number_from_description def ref_number_from_description
(Billing::ReferenceNo::MULTI_REGEXP.match(description) || []).captures.each do |match| matches = description.to_s.scan(Billing::ReferenceNo::MULTI_REGEXP).flatten
break match if match.length == 7 || valid_ref_no?(match) matches.detect { |m| break m if m.length == 7 || valid_ref_no?(m) }
end
end end
def valid_ref_no?(match) 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
end end

View file

@ -99,8 +99,8 @@ class Invoice < ApplicationRecord
generator.as_pdf generator.as_pdf
end end
def to_e_invoice def to_e_invoice(payable: true)
generator = Invoice::EInvoiceGenerator.new(self) generator = Invoice::EInvoiceGenerator.new(self, payable: payable)
generator.generate generator.generate
end end
@ -112,6 +112,15 @@ class Invoice < ApplicationRecord
e_invoice_sent_at.present? e_invoice_sent_at.present?
end 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 private
def apply_default_buyer_vat_no def apply_default_buyer_vat_no
@ -119,6 +128,6 @@ class Invoice < ApplicationRecord
end end
def calculate_total def calculate_total
self.total = subtotal + vat_amount self.total = (subtotal + vat_amount).round(3)
end end
end end

View file

@ -1,9 +1,11 @@
class Invoice class Invoice
class EInvoiceGenerator class EInvoiceGenerator
attr_reader :invoice attr_reader :invoice
attr_reader :payable
def initialize(invoice) def initialize(invoice, payable)
@invoice = invoice @invoice = invoice
@payable = payable
end end
def generate def generate
@ -70,9 +72,10 @@ class Invoice
i.total = invoice.total i.total = invoice.total
i.currency = invoice.currency i.currency = invoice.currency
i.delivery_channel = %i[internet_bank portal] i.delivery_channel = %i[internet_bank portal]
i.payable = payable
end end
EInvoice::EInvoice.new(date: Time.zone.today, invoice: e_invoice_invoice) EInvoice::EInvoice.new(date: Time.zone.today, invoice: e_invoice_invoice)
end end
end end
end end

View file

@ -5,7 +5,7 @@ class InvoiceItem < ApplicationRecord
delegate :vat_rate, to: :invoice delegate :vat_rate, to: :invoice
def item_sum_without_vat def item_sum_without_vat
(price * quantity).round(2) (price * quantity).round(3)
end end
alias_method :subtotal, :item_sum_without_vat alias_method :subtotal, :item_sum_without_vat
@ -14,6 +14,6 @@ class InvoiceItem < ApplicationRecord
end end
def total def total
subtotal + vat_amount (subtotal + vat_amount)
end end
end end

View file

@ -54,7 +54,7 @@ class Registrar < ApplicationRecord
end end
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 vat_rate = ::Invoice::VatRateCalculator.new(registrar: self).calculate
invoice = invoices.create!( 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 invoice
end end

View file

@ -0,0 +1,5 @@
class ChangeInvoiceItemPriceScaleToThreePlaces < ActiveRecord::Migration[6.0]
def change
change_column :invoice_items, :price, :decimal, precision: 10, scale: 3
end
end

View file

@ -962,7 +962,7 @@ CREATE TABLE public.invoice_items (
description character varying NOT NULL, description character varying NOT NULL,
unit character varying NOT NULL, unit character varying NOT NULL,
quantity integer NOT NULL, quantity integer NOT NULL,
price numeric(10,2) NOT NULL, price numeric(10,3) NOT NULL,
created_at timestamp without time zone, created_at timestamp without time zone,
updated_at timestamp without time zone, updated_at timestamp without time zone,
creator_str character varying, creator_str character varying,
@ -4850,6 +4850,7 @@ INSERT INTO "schema_migrations" (version) VALUES
('20200811074839'), ('20200811074839'),
('20200812090409'), ('20200812090409'),
('20200812125810'), ('20200812125810'),
('20200908131554'); ('20200908131554'),
('20200910085157');

View file

@ -36,6 +36,8 @@ namespace :invoices do
reference_no: incoming_transaction.payment_reference_number, reference_no: incoming_transaction.payment_reference_number,
description: incoming_transaction.payment_description } description: incoming_transaction.payment_description }
transaction = bank_statement.bank_transactions.create!(transaction_attributes) transaction = bank_statement.bank_transactions.create!(transaction_attributes)
Invoice.create_from_transaction!(transaction) unless transaction.autobindable?
transaction.autobind_invoice transaction.autobind_invoice
end end
end end

View file

@ -156,6 +156,24 @@ class BankTransactionTest < ActiveSupport::TestCase
assert transaction.errors.full_messages.include?('Cannot bind cancelled invoice') assert transaction.errors.full_messages.include?('Cannot bind cancelled invoice')
end 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 private
def create_payable_invoice(attributes) def create_payable_invoice(attributes)

View file

@ -1,6 +1,8 @@
require 'test_helper' require 'test_helper'
class InvoiceTest < ActiveSupport::TestCase class InvoiceTest < ActiveSupport::TestCase
include ActionMailer::TestHelper
setup do setup do
@invoice = invoices(:one) @invoice = invoices(:one)
end end
@ -109,4 +111,33 @@ class InvoiceTest < ActiveSupport::TestCase
seller_zip: nil) seller_zip: nil)
assert_equal 'street, city, state', invoice.seller_address assert_equal 'street, city, state', invoice.seller_address
end 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 end

View file

@ -82,6 +82,25 @@ class ProcessPaymentsTaskTest < ActiveSupport::TestCase
assert payment_order.failed? assert payment_order.failed?
end 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 def test_output
assert_output "Transactions processed: 1\n" do assert_output "Transactions processed: 1\n" do
run_task run_task