Skip to content

Commit

Permalink
1554, introduce auto approve invoice && refactoring
Browse files Browse the repository at this point in the history
  • Loading branch information
Ivanov-Anton committed Sep 18, 2024
1 parent 39cfde9 commit e7f34a3
Show file tree
Hide file tree
Showing 16 changed files with 212 additions and 34 deletions.
14 changes: 6 additions & 8 deletions app/admin/billing/invoices.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,12 @@ def scoped_collection
end

member_action :approve, method: :post do
if resource.approvable?
resource.approve
flash[:notice] = 'Invoice approved'
redirect_back fallback_location: root_path
else
flash[:notice] = 'Invoice can' 't be approved'
redirect_back fallback_location: root_path
end
BillingInvoice::Approve.call(invoice: resource)
flash[:notice] = 'Invoice was successful approved'
rescue BillingInvoice::Approve::Error => e
flash[:error] = e.message
ensure
redirect_back fallback_location: root_path
end

member_action :regenerate_document, method: :post do
Expand Down
4 changes: 4 additions & 0 deletions app/forms/manual_invoice_form.rb
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,11 @@ def _save
end_time: end_time,
type_id: Billing::InvoiceType::MANUAL
)
Worker::FillInvoiceJob.perform_later(@model.id)
@model
rescue BillingInvoice::Create::Error => e
errors.add(:base, e.message)
rescue Worker::FillInvoiceJob => e
errors.add(:base, e.message)
end
end
14 changes: 0 additions & 14 deletions app/models/billing/invoice.rb
Original file line number Diff line number Diff line change
Expand Up @@ -158,14 +158,6 @@ def display_name
"Invoice #{id}"
end

# todo service
def approve
transaction do
update!(state_id: Billing::InvoiceState::APPROVED)
send_email
end
end

def approvable?
state.pending?
end
Expand Down Expand Up @@ -196,12 +188,6 @@ def subject
display_name
end

# FIX this copy paste
# todo service
def send_email
invoice_document&.send_invoice
end

private

def validate_dates
Expand Down
37 changes: 37 additions & 0 deletions app/services/billing_invoice/approve.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# frozen_string_literal: true

module BillingInvoice
class Approve < ApplicationService
parameter :invoice, required: true

Error = Class.new(ApplicationService::Error)

def call
Billing::Invoice.transaction do
raise_if_invalid!

approve_invoice!
send_email
end
end

private

def approve_invoice!
invoice.update!(state_id: Billing::InvoiceState::APPROVED)
rescue ActiveRecord::RecordInvalid => e
raise Error, e.message
end

def send_email
if invoice.invoice_document.present?
invoice.invoice_document.send_invoice
end
end

def raise_if_invalid!
raise Error, 'Invoice already approved' if invoice.state_id == Billing::InvoiceState::APPROVED
raise Error, "Invoice can't be approved" unless invoice.approvable?
end
end
end
3 changes: 2 additions & 1 deletion app/services/billing_invoice/create.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ class Create < ApplicationService
parameter :start_time, required: true
parameter :end_time, required: true

Error = Class.new(ApplicationService::Error)

delegate :contractor, to: :account

def call
Expand All @@ -22,7 +24,6 @@ def call
end_date: end_time
)
invoice.update! reference: build_reference(invoice)
Worker::FillInvoiceJob.perform_later(invoice.id)
invoice
end
rescue ActiveRecord::RecordInvalid => e
Expand Down
2 changes: 2 additions & 0 deletions app/services/billing_invoice/fill.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ module BillingInvoice
class Fill < ApplicationService
parameter :invoice, required: true

Error = Class.new(ApplicationService::Error)

def call
AdvisoryLock::Cdr.with_lock(:invoice, id: invoice.account_id) do
invoice.reload
Expand Down
17 changes: 14 additions & 3 deletions app/services/billing_invoice/generate.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ module BillingInvoice
class Generate < ApplicationService
parameter :account, required: true

Error = Class.new(ApplicationService::Error)

# @return [Billing::Invoice]
def call
AdvisoryLock::Cdr.with_lock(:invoice, id: account.id) do
Expand All @@ -19,15 +21,18 @@ def call
end_time: account.next_invoice_at,
type_id: account.next_invoice_type_id
)

BillingInvoice::Fill.call(invoice: invoice)
schedule_next_invoice!(invoice_params)
approve_invoice!(invoice) if YetiConfig.invoice&.auto_approve

invoice
end
rescue BillingInvoice::Create::Error => e
raise Error, e.message
rescue BillingInvoice::Fill::Error => e
raise Error, e.message
rescue ActiveRecord::RecordInvalid => e
message = e.record ? e.errors.full_messages.join(', ') : e.message
raise Error, message
raise Error, e.message
end

private
Expand Down Expand Up @@ -55,5 +60,11 @@ def schedule_next_invoice!(invoice_params)
next_invoice_type_id: invoice_params[:next_type_id]
)
end

def approve_invoice!(invoice)
BillingInvoice::Approve.call(invoice:)
rescue ActiveRecord::RecordInvalid => e
raise Error, e
end
end
end
4 changes: 4 additions & 0 deletions config/initializers/_config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,10 @@ def self.setting_files(config_root, _env)
end

optional(:customer_api_cdr_hide_fields).array(:string)

optional(:invoice).schema do
optional(:auto_approve).value(:bool)
end
end
end

Expand Down
2 changes: 2 additions & 0 deletions config/yeti_web.yml.ci
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,5 @@ cryptomus:

routing_simulation_default_interface: internal

invoice:
auto_approve: false
3 changes: 3 additions & 0 deletions config/yeti_web.yml.development
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,6 @@ api_log_tags:
- SOME_TAG_FOR_API_LOG

routing_simulation_default_interface: internal

invoice:
auto_approve: false
3 changes: 3 additions & 0 deletions config/yeti_web.yml.distr
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,6 @@ api_log_tags:
- SOME_TAG_FOR_API_LOG

routing_simulation_default_interface: internal

invoice:
auto_approve: false
5 changes: 4 additions & 1 deletion spec/config/yeti_web_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,10 @@
url_callback: a_kind_of(String),
url_return: a_kind_of(String)
},
routing_simulation_default_interface: a_kind_of(String)
routing_simulation_default_interface: a_kind_of(String),
invoice: {
auto_approve: boolean
}
}
end

Expand Down
37 changes: 37 additions & 0 deletions spec/features/billing/invoices/approve_invoice_feature_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# frozen_string_literal: true

RSpec.describe 'Approve invoide feature', type: :feature do
subject { click_action_item 'Approve' }

include_context :login_as_admin

context 'when valid data' do
let!(:invoice) { FactoryBot.create(:invoice, :manual, :pending, :with_vendor_account) }
let(:approved_state_id) { Billing::InvoiceState::APPROVED }

before { visit invoice_path(invoice) }

it 'should approve invoice', :js do
subject

expect(page).to have_flash_message 'Invoice was successful approved', type: :notice
expect(invoice.reload).to have_attributes(state_id: approved_state_id)
end
end

context 'when invalid data', :js do
let!(:invoice) { FactoryBot.create(:invoice, :pending, :with_vendor_account) }

before do
allow(BillingInvoice::Approve).to receive(:call).and_raise(BillingInvoice::Approve::Error, 'error')
visit invoice_path(invoice)
end

it 'should render error' do
subject

expect(page).to have_flash_message 'error', type: :error
expect(BillingInvoice::Approve).to have_received(:call)
end
end
end
53 changes: 53 additions & 0 deletions spec/services/billing_invoice/approve_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# frozen_string_literal: true

RSpec.describe BillingInvoice::Approve, type: :service do
subject { described_class.call(service_params) }

let(:service_params) { { invoice: } }

context 'when valid data' do
let!(:contractor) { FactoryBot.create(:customer) }
let!(:contact) { FactoryBot.create(:contact, contractor:) }
let!(:account) { FactoryBot.create(:account, contractor:, send_invoices_to: [contact.id]) }
let(:invoice_attrs) { { account:, contractor: } }
let!(:invoice) { FactoryBot.create(:invoice, :pending, invoice_attrs) }
let(:invoice_document_attrs) { { invoice:, filename: "#{invoice.id}_#{invoice.start_date}_#{invoice.end_date}" } }
let!(:invoice_document) { FactoryBot.create(:invoice_document, :filled, invoice_document_attrs) }

before { FactoryBot.create(:smtp_connection, global: true) }

it 'approves invoice' do
expect { subject }.to change { invoice.state_id }.to(Billing::InvoiceState::APPROVED)
end

it 'enqueues email worker' do
expect { subject }.to have_enqueued_job(Worker::SendEmailLogJob)
end
end

context 'when invoice already approved' do
let!(:invoice) { FactoryBot.build_stubbed(:invoice, :approved) }

it 'raises error' do
expect { subject }.to raise_error(BillingInvoice::Approve::Error, 'Invoice already approved')
end
end

context 'when invoice is not approvable' do
let!(:invoice) { FactoryBot.build_stubbed(:invoice, :new) }

it 'raises error' do
expect { subject }.to raise_error(BillingInvoice::Approve::Error, "Invoice can't be approved")
end
end

context 'when validation error' do
let!(:invoice) { FactoryBot.create(:invoice, :pending, :with_vendor_account) }

before { allow_any_instance_of(Billing::Invoice).to receive(:update!).and_raise(ActiveRecord::RecordInvalid) }

it 'raises error' do
expect { subject }.to raise_error(BillingInvoice::Approve::Error, 'Record invalid')
end
end
end
7 changes: 0 additions & 7 deletions spec/services/billing_invoice/create_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,6 @@
uuid: be_present
)
end

it 'enqueues Worker::FillInvoiceJob with invoice.id' do
subject
invoice = Billing::Invoice.last!

expect(Worker::FillInvoiceJob).to have_been_enqueued.with(invoice.id)
end
end

shared_examples :does_not_create_invoice do
Expand Down
41 changes: 41 additions & 0 deletions spec/services/billing_invoice/generate_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -203,4 +203,45 @@
)
end
end

context 'when "invoice.auto_approve" setting has "true" value' do
before { allow(YetiConfig.invoice).to receive(:auto_approve).and_return(true) }

it 'should create invoice with approved state' do
expect { subject }.to change(Billing::Invoice.approved, :count).by(1)
expect(YetiConfig.invoice).to have_received(:auto_approve).once
end
end

context 'when the "invoice.auto_approve" setting has "false" value' do
before { allow(YetiConfig.invoice).to receive(:auto_approve).and_return(false) }

it 'should create the "new" with approved state' do
expect { subject }.to change(Billing::Invoice.pending, :count).by(1)
end
end

context 'when BillingInvoice::Create service return error' do
before { allow(BillingInvoice::Create).to receive(:call).and_raise(BillingInvoice::Create::Error, 'error') }

it 'raises error' do
expect { subject }.to raise_error BillingInvoice::Generate::Error
end
end

context 'when BillingInvoice::Fill service return error' do
before { allow(BillingInvoice::Fill).to receive(:call).and_raise(BillingInvoice::Fill::Error, 'error') }

it 'raises error' do
expect { subject }.to raise_error BillingInvoice::Generate::Error
end
end

context 'when validation error generated' do
before { allow(BillingInvoice::Create).to receive(:call).and_raise(ActiveRecord::RecordInvalid) }

it 'raises error' do
expect { subject }.to raise_error BillingInvoice::Generate::Error
end
end
end

0 comments on commit e7f34a3

Please sign in to comment.