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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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,
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');

View file

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

View file

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

View file

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

View file

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