From 02448e9e6c6ec10da642ec7d33f8020f6c429953 Mon Sep 17 00:00:00 2001 From: Denis Talakevich Date: Thu, 18 Feb 2021 14:21:43 +0200 Subject: [PATCH] add invoice reference fixes #870 --- app/admin/billing/accounts.rb | 5 ++ app/admin/billing/invoices.rb | 3 ++ app/forms/account_form.rb | 4 +- app/lib/invoice_ref_template.rb | 27 ++++++++++ app/models/account.rb | 1 + app/services/billing_invoice/create.rb | 7 +++ .../billing_invoice/generate_document.rb | 3 ++ ...218092245_add_account_invoice_reference.rb | 6 +++ .../20210218095038_add_invoice_reference.rb | 15 ++++++ db/secondbase/structure.sql | 13 ++++- db/structure.sql | 5 +- spec/factories/billing/invoice.rb | 4 ++ .../billing/accounts/edit_account_spec.rb | 21 ++++++++ .../billing/accounts/new_account_spec.rb | 54 +++++++++++++++++-- spec/lib/invoice_ref_template_spec.rb | 47 ++++++++++++++++ spec/services/billing_invoice/create_spec.rb | 30 ++++++++++- 16 files changed, 237 insertions(+), 8 deletions(-) create mode 100644 app/lib/invoice_ref_template.rb create mode 100644 db/migrate/20210218092245_add_account_invoice_reference.rb create mode 100644 db/secondbase/migrate/20210218095038_add_invoice_reference.rb create mode 100644 spec/lib/invoice_ref_template_spec.rb diff --git a/app/admin/billing/accounts.rb b/app/admin/billing/accounts.rb index 733649ba6..888eab88a 100644 --- a/app/admin/billing/accounts.rb +++ b/app/admin/billing/accounts.rb @@ -57,6 +57,7 @@ def find_resource :customer_invoice_period_id, :vendor_invoice_period_id, :autogenerate_vendor_invoices, :autogenerate_customer_invoices, :vendor_invoice_template_id, :customer_invoice_template_id, :timezone_id, + :customer_invoice_ref_template, :vendor_invoice_ref_template, send_invoices_to: [], send_balance_notifications_to: [] includes :customer_invoice_period, :vendor_invoice_period, :contractor, :timezone, @@ -177,6 +178,8 @@ def find_resource end end row :timezone + row :customer_invoice_ref_template + row :vendor_invoice_ref_template end panel 'Last Payments' do @@ -242,6 +245,8 @@ def find_resource f.input :send_invoices_to, as: :select, input_html: { class: 'chosen', multiple: true }, collection: Billing::Contact.collection f.input :send_balance_notifications_to, as: :select, input_html: { class: 'chosen', multiple: true }, collection: Billing::Contact.collection f.input :timezone_id, as: :select, input_html: { class: 'chosen' }, collection: System::Timezone.all + f.input :customer_invoice_ref_template + f.input :vendor_invoice_ref_template f.input :uuid, as: :string end f.actions diff --git a/app/admin/billing/invoices.rb b/app/admin/billing/invoices.rb index 732302e38..b97b3a7e4 100644 --- a/app/admin/billing/invoices.rb +++ b/app/admin/billing/invoices.rb @@ -118,6 +118,7 @@ def build_new_resource selectable_column id_column actions + column :reference column :contractor, footer: lambda { strong do 'Total:' @@ -164,6 +165,7 @@ def build_new_resource end filter :id + filter :reference filter :contractor, input_html: { class: 'chosen-ajax', 'data-path': '/contractors/search' }, collection: proc { @@ -193,6 +195,7 @@ def build_new_resource tab 'Invoice' do attributes_table_for s do row :id + row :reference row :contractor_id row :account row :state diff --git a/app/forms/account_form.rb b/app/forms/account_form.rb index 17a36f48b..91f610827 100644 --- a/app/forms/account_form.rb +++ b/app/forms/account_form.rb @@ -27,7 +27,9 @@ def self.policy_class :send_invoices_to, :send_balance_notifications_to, :timezone_id, - :uuid + :uuid, + :customer_invoice_ref_template, + :vendor_invoice_ref_template before_save :apply_vendor_invoice_period before_save :apply_customer_invoice_period diff --git a/app/lib/invoice_ref_template.rb b/app/lib/invoice_ref_template.rb new file mode 100644 index 000000000..bbcb86b45 --- /dev/null +++ b/app/lib/invoice_ref_template.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +class InvoiceRefTemplate + TEMPLATES = { + '$id': proc { invoice.id } + }.freeze + + def self.call(invoice, template) + new(invoice, template).call + end + + attr_reader :invoice, :template + + def initialize(invoice, template) + @invoice = invoice + @template = template + end + + def call + result = template + TEMPLATES.each do |key, value_block| + value = instance_exec(&value_block).to_s + result = result.gsub(key.to_s, value) + end + result + end +end diff --git a/app/models/account.rb b/app/models/account.rb index 7c90b1a21..e16cefac4 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -105,6 +105,7 @@ class Account < Yeti::ActiveRecord validates :vat, numericality: { greater_than_or_equal_to: 0, less_than_or_equal_to: 100, allow_nil: false } # this is percents validates :destination_rate_limit, numericality: { greater_than_or_equal_to: 0, allow_nil: true } validates :max_call_duration, numericality: { greater_than: 0, allow_nil: true } + validates :customer_invoice_ref_template, :vendor_invoice_ref_template, presence: true after_initialize do if new_record? diff --git a/app/services/billing_invoice/create.rb b/app/services/billing_invoice/create.rb index 64ae49d57..526fa84b3 100644 --- a/app/services/billing_invoice/create.rb +++ b/app/services/billing_invoice/create.rb @@ -23,6 +23,7 @@ def call start_date: start_time, end_date: end_time ) + invoice.update! reference: build_reference(invoice) Worker::FillInvoiceJob.perform_later(invoice.id) invoice end @@ -33,6 +34,12 @@ def call private + def build_reference(invoice) + template_name = is_vendor ? :vendor_invoice_ref_template : :customer_invoice_ref_template + template = account.public_send(template_name) + InvoiceRefTemplate.call(invoice, template) + end + def validate! start_date = start_time.in_time_zone(Time.zone) end_date = end_time.in_time_zone(Time.zone) diff --git a/app/services/billing_invoice/generate_document.rb b/app/services/billing_invoice/generate_document.rb index bc0ccf45d..ac00ab0a4 100644 --- a/app/services/billing_invoice/generate_document.rb +++ b/app/services/billing_invoice/generate_document.rb @@ -29,6 +29,7 @@ class GenerateDocument < ApplicationService # INV_DST_AMOUNT, # INV_DST_FIRST_CALL_AT, # INV_DST_LAST_CALL_AT + # INV_REF class TemplateUndefined < Error def initialize(invoice_id) @@ -50,6 +51,7 @@ def self.replaces_list contractor_address contractor_phones inv_id + inv_ref inv_created_at inv_start_date inv_end_date @@ -125,6 +127,7 @@ def replaces contractor_address: invoice.contractor.address, contractor_phones: invoice.contractor.phones, inv_id: invoice.id, + inv_ref: invoice.reference, inv_created_at: invoice.created_at, inv_start_date: invoice.start_date, inv_end_date: invoice.end_date, diff --git a/db/migrate/20210218092245_add_account_invoice_reference.rb b/db/migrate/20210218092245_add_account_invoice_reference.rb new file mode 100644 index 000000000..a9ce3de74 --- /dev/null +++ b/db/migrate/20210218092245_add_account_invoice_reference.rb @@ -0,0 +1,6 @@ +class AddAccountInvoiceReference < ActiveRecord::Migration[5.2] + def change + add_column :accounts, :customer_invoice_ref_template, :string, default: '$id', null: false + add_column :accounts, :vendor_invoice_ref_template, :string, default: '$id', null: false + end +end diff --git a/db/secondbase/migrate/20210218095038_add_invoice_reference.rb b/db/secondbase/migrate/20210218095038_add_invoice_reference.rb new file mode 100644 index 000000000..b25d78ec3 --- /dev/null +++ b/db/secondbase/migrate/20210218095038_add_invoice_reference.rb @@ -0,0 +1,15 @@ +class AddInvoiceReference < ActiveRecord::Migration[5.2] + def up + add_column 'billing.invoices', :reference, :string + + execute <<-SQL + UPDATE billing.invoices SET reference = id::varchar + SQL + + add_index 'billing.invoices', :reference + end + + def down + remove_column 'billing.invoices', :reference + end +end diff --git a/db/secondbase/structure.sql b/db/secondbase/structure.sql index aaaa923dc..9af5bcfad 100644 --- a/db/secondbase/structure.sql +++ b/db/secondbase/structure.sql @@ -2132,7 +2132,8 @@ CREATE TABLE billing.invoices ( last_successful_call_at timestamp with time zone, successful_calls_count bigint, type_id smallint NOT NULL, - billing_duration bigint NOT NULL + billing_duration bigint NOT NULL, + reference character varying ); @@ -4070,6 +4071,13 @@ CREATE INDEX auth_log_id_idx ON ONLY auth_log.auth_log USING btree (id); CREATE INDEX auth_log_request_time_idx ON ONLY auth_log.auth_log USING btree (request_time); +-- +-- Name: index_billing.invoices_on_reference; Type: INDEX; Schema: billing; Owner: - +-- + +CREATE INDEX "index_billing.invoices_on_reference" ON billing.invoices USING btree (reference); + + -- -- Name: invoice_destinations_invoice_id_idx; Type: INDEX; Schema: billing; Owner: - -- @@ -4399,6 +4407,7 @@ INSERT INTO "public"."schema_migrations" (version) VALUES ('20200803201602'), ('20201128134302'), ('20210116150950'), -('20210212102105'); +('20210212102105'), +('20210218095038'); diff --git a/db/structure.sql b/db/structure.sql index 02ad34c36..71879d51c 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -910,6 +910,8 @@ CREATE TABLE billing.accounts ( total_capacity smallint, destination_rate_limit numeric, max_call_duration integer, + customer_invoice_ref_template character varying DEFAULT '$id'::character varying NOT NULL, + vendor_invoice_ref_template character varying DEFAULT '$id'::character varying NOT NULL, CONSTRAINT positive_max_call_duration CHECK ((max_call_duration > 0)), CONSTRAINT positive_origination_capacity CHECK ((origination_capacity > 0)), CONSTRAINT positive_termination_capacity CHECK ((termination_capacity > 0)), @@ -23004,6 +23006,7 @@ INSERT INTO "public"."schema_migrations" (version) VALUES ('20201103092516'), ('20201111201356'), ('20201226161344'), -('20210116152459'); +('20210116152459'), +('20210218092245'); diff --git a/spec/factories/billing/invoice.rb b/spec/factories/billing/invoice.rb index 12b1a56a7..d9d4b67c2 100644 --- a/spec/factories/billing/invoice.rb +++ b/spec/factories/billing/invoice.rb @@ -46,5 +46,9 @@ trait :with_vendor_account do account { FactoryBot.create(:account, contractor: FactoryBot.create(:vendor)) } end + + after(:create) do |record| + record.update!(reference: record.id.to_s) if record.reference.blank? + end end end diff --git a/spec/features/billing/accounts/edit_account_spec.rb b/spec/features/billing/accounts/edit_account_spec.rb index 448acc146..b45debeb8 100644 --- a/spec/features/billing/accounts/edit_account_spec.rb +++ b/spec/features/billing/accounts/edit_account_spec.rb @@ -31,4 +31,25 @@ ) end end + + context 'with invoice ref templates change' do + before do + fill_in 'Customer invoice ref template', with: 'cust-$id' + fill_in 'Vendor invoice ref template', with: 'vend-$id' + end + + it 'updates account' do + expect { + subject + expect(page).to have_flash_message('Account was successfully updated.', type: :notice) + }.to change { Account.count }.by(0) + + expect(page).to have_current_path account_path(account.id) + + expect(account.reload).to have_attributes( + customer_invoice_ref_template: 'cust-$id', + vendor_invoice_ref_template: 'vend-$id' + ) + end + end end diff --git a/spec/features/billing/accounts/new_account_spec.rb b/spec/features/billing/accounts/new_account_spec.rb index 3e7888e70..e0f28c2a0 100644 --- a/spec/features/billing/accounts/new_account_spec.rb +++ b/spec/features/billing/accounts/new_account_spec.rb @@ -74,7 +74,51 @@ next_vendor_invoice_type_id: nil, customer_invoice_period_id: nil, next_customer_invoice_at: nil, - next_customer_invoice_type_id: nil + next_customer_invoice_type_id: nil, + customer_invoice_ref_template: '$id', + vendor_invoice_ref_template: '$id' + ) + end + end + + context 'with invoice ref templates' do + let(:form_params) do + super().merge customer_invoice_ref_template: 'cust-$id', + vendor_invoice_ref_template: 'vend-$id' + end + before do + fill_in 'Customer invoice ref template', with: form_params[:customer_invoice_ref_template] + fill_in 'Vendor invoice ref template', with: form_params[:vendor_invoice_ref_template] + end + + it 'creates new account successfully' do + subject + + expect(page).to have_flash_message('Account was successfully created.', type: :notice) + account = Account.last! + expect(page).to have_current_path account_path(account.id) + + expect(account).to have_attributes( + name: form_params[:name], + contractor: form_params[:contractor], + max_balance: form_params[:max_balance], + min_balance: form_params[:min_balance], + balance_low_threshold: form_params[:balance_low_threshold], + balance_high_threshold: form_params[:balance_high_threshold], + destination_rate_limit: form_params[:destination_rate_limit], + max_call_duration: form_params[:max_call_duration], + origination_capacity: form_params[:origination_capacity], + termination_capacity: form_params[:termination_capacity], + total_capacity: form_params[:total_capacity], + timezone: form_params[:timezone], + vendor_invoice_period_id: nil, + next_vendor_invoice_at: nil, + next_vendor_invoice_type_id: nil, + customer_invoice_period_id: nil, + next_customer_invoice_at: nil, + next_customer_invoice_type_id: nil, + customer_invoice_ref_template: form_params[:customer_invoice_ref_template], + vendor_invoice_ref_template: form_params[:vendor_invoice_ref_template] ) end end @@ -118,7 +162,9 @@ next_vendor_invoice_type_id: nil, customer_invoice_period_id: Billing::InvoicePeriod::WEEKLY_ID, next_customer_invoice_at: account_time_zone.parse('2020-01-06 00:00:00'), - next_customer_invoice_type_id: Billing::InvoiceType::AUTO_FULL + next_customer_invoice_type_id: Billing::InvoiceType::AUTO_FULL, + customer_invoice_ref_template: '$id', + vendor_invoice_ref_template: '$id' ) end end @@ -163,7 +209,9 @@ next_vendor_invoice_type_id: Billing::InvoiceType::AUTO_PARTIAL, customer_invoice_period_id: nil, next_customer_invoice_at: nil, - next_customer_invoice_type_id: nil + next_customer_invoice_type_id: nil, + customer_invoice_ref_template: '$id', + vendor_invoice_ref_template: '$id' ) end end diff --git a/spec/lib/invoice_ref_template_spec.rb b/spec/lib/invoice_ref_template_spec.rb new file mode 100644 index 000000000..8a96707e6 --- /dev/null +++ b/spec/lib/invoice_ref_template_spec.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +RSpec.describe InvoiceRefTemplate, '.call' do + subject do + InvoiceRefTemplate.call(invoice, template) + end + + let!(:contractor) { FactoryBot.create(:vendor) } + let!(:account) { FactoryBot.create(:account, account_attrs) } + let(:account_attrs) do + { contractor: contractor } + end + let(:invoice) { FactoryBot.create(:invoice, :vendor, :manual, invoice_attrs) } + let(:invoice_attrs) do + { account: account } + end + + context 'when template is "$id"' do + let(:template) { '$id' } + + it { is_expected.to eq invoice.id.to_s } + end + + context 'when template is "test_$id"' do + let(:template) { 'test_$id' } + + it { is_expected.to eq "test_#{invoice.id}" } + end + + context 'when template is "$id_test"' do + let(:template) { '$id_test' } + + it { is_expected.to eq "#{invoice.id}_test" } + end + + context 'when template is "$id_test_$id"' do + let(:template) { '$id_test_$id' } + + it { is_expected.to eq "#{invoice.id}_test_#{invoice.id}" } + end + + context 'when template is "test"' do + let(:template) { 'test' } + + it { is_expected.to eq 'test' } + end +end diff --git a/spec/services/billing_invoice/create_spec.rb b/spec/services/billing_invoice/create_spec.rb index 860423dad..51d0d3b35 100644 --- a/spec/services/billing_invoice/create_spec.rb +++ b/spec/services/billing_invoice/create_spec.rb @@ -6,7 +6,14 @@ end shared_examples :creates_an_invoice do + let(:expected_reference) { Billing::Invoice.last!.id.to_s } + it 'creates an invoice with correct params' do + expect(InvoiceRefTemplate).to receive(:call).once.with( + a_kind_of(Billing::Invoice), + service_params[:is_vendor] ? account.vendor_invoice_ref_template : account.customer_invoice_ref_template + ).and_call_original + expect { subject }.to change { Billing::Invoice.count }.by(1) invoice = Billing::Invoice.last! @@ -26,7 +33,8 @@ first_successful_call_at: nil, last_call_at: nil, last_successful_call_at: nil, - successful_calls_count: nil + successful_calls_count: nil, + reference: expected_reference ) end @@ -104,10 +112,30 @@ include_examples :creates_an_invoice end + context 'when vendor_invoice_ref_template is different' do + let(:account_attrs) do + super().merge vendor_invoice_ref_template: 'rspec_$id' + end + + include_examples :creates_an_invoice do + let(:expected_reference) { "rspec_#{Billing::Invoice.last!.id}" } + end + end + context 'with is_vendor=false' do let(:service_params) { super().merge is_vendor: false } include_examples :creates_an_invoice + + context 'when customer_invoice_ref_template is different' do + let(:account_attrs) do + super().merge customer_invoice_ref_template: 'rspec_$id' + end + + include_examples :creates_an_invoice do + let(:expected_reference) { "rspec_#{Billing::Invoice.last!.id}" } + end + end end context 'without type_id' do