From cafa8056e244e2ff90e07de06f0d9394368b1fdc Mon Sep 17 00:00:00 2001 From: bedrock-adam Date: Sun, 23 Jun 2024 21:53:39 +1000 Subject: [PATCH 01/12] commit implementation and spec (wip) --- ...ate_accountify_aged_receivables_reports.rb | 16 +++++++ ...ountify_aged_receivables_report_periods.rb | 14 ++++++ db/schema.rb | 26 ++++++++++- lib/accountify/aged_receivables_report.rb | 44 +++++++++++++++++++ .../models/aged_receivables_report.rb | 11 +++++ .../aged_receivables_report_spec.rb | 40 +++++++++++++++++ 6 files changed, 150 insertions(+), 1 deletion(-) create mode 100644 db/migrate/20240620220408_create_accountify_aged_receivables_reports.rb create mode 100644 db/migrate/20240620220453_create_accountify_aged_receivables_report_periods.rb create mode 100644 lib/accountify/aged_receivables_report.rb create mode 100644 lib/accountify/models/aged_receivables_report.rb create mode 100644 spec/lib/accountify/aged_receivables_report_spec.rb diff --git a/db/migrate/20240620220408_create_accountify_aged_receivables_reports.rb b/db/migrate/20240620220408_create_accountify_aged_receivables_reports.rb new file mode 100644 index 0000000..079da1f --- /dev/null +++ b/db/migrate/20240620220408_create_accountify_aged_receivables_reports.rb @@ -0,0 +1,16 @@ +class CreateAccountifyAgedReceivablesReports < ActiveRecord::Migration[7.1] + def change + create_table :accountify_aged_receivables_reports do |t| + t.bigint :iam_tenant_id, null: false, index: true + + t.date :as_at_date, null: false + t.string :currency_code, null: false + t.integer :num_periods, null: false + t.integer :period_frequency, null: false + t.string :period_unit, null: false + t.string :ageing_by, null: false + + t.timestamps + end + end +end diff --git a/db/migrate/20240620220453_create_accountify_aged_receivables_report_periods.rb b/db/migrate/20240620220453_create_accountify_aged_receivables_report_periods.rb new file mode 100644 index 0000000..6bc7ecd --- /dev/null +++ b/db/migrate/20240620220453_create_accountify_aged_receivables_report_periods.rb @@ -0,0 +1,14 @@ +class CreateAccountifyAgedReceivablesReportPeriods < ActiveRecord::Migration[7.1] + def change + create_table :accountify_aged_receivables_report_periods do |t| + t.references :aged_receivables_report, + null: false, index: true, foreign_key: { to_table: :accountify_aged_receivables_reports } + + t.date :start_date, null: false + t.date :end_date, null: false + t.decimal :sub_total, precision: 12, scale: 2, null: false + + t.timestamps + end + end +end diff --git a/db/schema.rb b/db/schema.rb index e27aec3..a80c700 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,10 +10,33 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.1].define(version: 2024_05_30_211442) do +ActiveRecord::Schema[7.1].define(version: 2024_06_20_220453) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" + create_table "accountify_aged_receivables_report_periods", force: :cascade do |t| + t.bigint "aged_receivables_report_id", null: false + t.date "start_date", null: false + t.date "end_date", null: false + t.decimal "sub_total", precision: 12, scale: 2, null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["aged_receivables_report_id"], name: "idx_on_aged_receivables_report_id_7f32513b52" + end + + create_table "accountify_aged_receivables_reports", force: :cascade do |t| + t.bigint "iam_tenant_id", null: false + t.date "as_at_date", null: false + t.string "currency_code", null: false + t.integer "num_periods", null: false + t.integer "period_frequency", null: false + t.string "period_unit", null: false + t.string "ageing_by", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["iam_tenant_id"], name: "index_accountify_aged_receivables_reports_on_iam_tenant_id" + end + create_table "accountify_contacts", force: :cascade do |t| t.bigint "iam_tenant_id", null: false t.bigint "organisation_id", null: false @@ -95,6 +118,7 @@ t.index ["status", "created_at"], name: "index_outboxer_messages_on_status_and_created_at" end + add_foreign_key "accountify_aged_receivables_report_periods", "accountify_aged_receivables_reports", column: "aged_receivables_report_id" add_foreign_key "accountify_contacts", "accountify_organisations", column: "organisation_id" add_foreign_key "accountify_invoice_line_items", "accountify_invoices", column: "invoice_id" add_foreign_key "accountify_invoices", "accountify_contacts", column: "contact_id" diff --git a/lib/accountify/aged_receivables_report.rb b/lib/accountify/aged_receivables_report.rb new file mode 100644 index 0000000..a39377a --- /dev/null +++ b/lib/accountify/aged_receivables_report.rb @@ -0,0 +1,44 @@ +module Accountify + module AgedReceivablesReport + extend self + + def generate(iam_tenant_id:, + as_at_date: Date.today, currency_code: 'AUD', + num_periods: 4, period_frequency: 1, period_unit: :month, + ageing_by: :due_date) + report = Models::AgedReceivablesReport.create( + iam_tenant_id: iam_tenant_id, + as_at_date: as_at_date, + currency_code: currency_code, + num_periods: num_periods, + period_frequency: period_frequency, + period_unit: period_unit, + ageing_by: ageing_by) + + period_dates = (1..num_periods).map do |i| + case period_unit + when :month + as_at_date + i.months * period_frequency - 1.day + when :week + as_at_date + i.weeks * period_frequency - 1.day + when :day + as_at_date + i.day * period_frequency - 1.day + else + raise ArgumentError, "Unsupported period unit: #{period_unit}" + end + end + + ([as_at_date] + period_dates).each_cons(2) do |start_date, end_date| + sub_total = Models::Invoice + .where(iam_tenant_id: iam_tenant_id) + .where(currency_code: currency_code) + .where("#{ageing_by} >= ? AND #{ageing_by} <= ?", start_date, end_date) + .sum(:sub_total_amount) + + report.periods.create!(start_date: start_date, end_date: end_date, sub_total: sub_total) + end + + report + end + end +end diff --git a/lib/accountify/models/aged_receivables_report.rb b/lib/accountify/models/aged_receivables_report.rb new file mode 100644 index 0000000..8e8de10 --- /dev/null +++ b/lib/accountify/models/aged_receivables_report.rb @@ -0,0 +1,11 @@ +module Accountify + module Models + class AgedReceivablesReport < ActiveRecord::Base + self.table_name = 'accountify_aged_receivables_reports' + + class Period < ActiveRecord::Base; end + + has_many :periods + end + end +end diff --git a/spec/lib/accountify/aged_receivables_report_spec.rb b/spec/lib/accountify/aged_receivables_report_spec.rb new file mode 100644 index 0000000..55de1c9 --- /dev/null +++ b/spec/lib/accountify/aged_receivables_report_spec.rb @@ -0,0 +1,40 @@ +require 'rails_helper' + +module Accountify + RSpec.describe AgedReceivablesReport, type: :module do + let(:iam_tenant_id) { 4 } + let(:as_at_date) { Date.parse('2024-06-23') } + let(:currency_code) { 'AUD' } + let(:num_periods) { 4 } + let(:period_frequency) { 1 } + let(:period_unit) { :month } + let(:ageing_by) { :due_date } + + let(:aged_receivables_report) do + AgedReceivablesReport.generate( + iam_tenant_id: iam_tenant_id, + as_at_date: as_at_date, + currency_code: currency_code, + num_periods: num_periods, + period_frequency: period_frequency, + period_unit: period_unit, + ageing_by: ageing_by + ) + end + + describe '.generate' do + it 'calculates ageing periods correctly' do + expect(aged_receivables_report.periods.size).to eq(num_periods) + + expect( + aged_receivables_report.periods.map { |p| p.attributes.slice('start_date', 'end_date', 'sub_total') } + ).to eq([ + {'start_date' => Date.parse('2024-06-23'), 'end_date' => Date.parse('2024-07-22'), 'sub_total' => BigDecimal('0')}, + {'start_date' => Date.parse('2024-07-23'), 'end_date' => Date.parse('2024-08-22'), 'sub_total' => BigDecimal('0')}, + {'start_date' => Date.parse('2024-08-23'), 'end_date' => Date.parse('2024-09-22'), 'sub_total' => BigDecimal('0')}, + {'start_date' => Date.parse('2024-09-23'), 'end_date' => Date.parse('2024-10-22'), 'sub_total' => BigDecimal('0')} + ]) + end + end + end +end From 475c4620d25ec83d4ea6528b2f8bf756fbf5a161 Mon Sep 17 00:00:00 2001 From: bedrock-adam Date: Sun, 23 Jun 2024 22:02:11 +1000 Subject: [PATCH 02/12] with test passingn --- lib/accountify/aged_receivables_report.rb | 37 ++++++++++++------- .../aged_receivables_report_spec.rb | 32 ++++++++++++---- 2 files changed, 49 insertions(+), 20 deletions(-) diff --git a/lib/accountify/aged_receivables_report.rb b/lib/accountify/aged_receivables_report.rb index a39377a..15f1e8b 100644 --- a/lib/accountify/aged_receivables_report.rb +++ b/lib/accountify/aged_receivables_report.rb @@ -16,26 +16,37 @@ def generate(iam_tenant_id:, ageing_by: ageing_by) period_dates = (1..num_periods).map do |i| - case period_unit - when :month - as_at_date + i.months * period_frequency - 1.day - when :week - as_at_date + i.weeks * period_frequency - 1.day - when :day - as_at_date + i.day * period_frequency - 1.day - else - raise ArgumentError, "Unsupported period unit: #{period_unit}" - end + start_date = case period_unit + when :month + as_at_date + (i - 1) * period_frequency.months + when :week + as_at_date + (i - 1) * period_frequency.weeks + when :day + as_at_date + (i - 1) * period_frequency.days + end + + end_date = case period_unit + when :month + as_at_date + i * period_frequency.months - 1.day + when :week + as_at_date + i * period_frequency.weeks - 1.day + when :day + as_at_date + i * period_frequency.days - 1.day + end + { start_date: start_date, end_date: end_date } end - ([as_at_date] + period_dates).each_cons(2) do |start_date, end_date| + period_dates.each do |period| sub_total = Models::Invoice .where(iam_tenant_id: iam_tenant_id) .where(currency_code: currency_code) - .where("#{ageing_by} >= ? AND #{ageing_by} <= ?", start_date, end_date) + .where("#{ageing_by} >= ? AND #{ageing_by} <= ?", period[:start_date], period[:end_date]) .sum(:sub_total_amount) - report.periods.create!(start_date: start_date, end_date: end_date, sub_total: sub_total) + report.periods.create!( + start_date: period[:start_date], + end_date: period[:end_date], + sub_total: sub_total) end report diff --git a/spec/lib/accountify/aged_receivables_report_spec.rb b/spec/lib/accountify/aged_receivables_report_spec.rb index 55de1c9..a0c17ea 100644 --- a/spec/lib/accountify/aged_receivables_report_spec.rb +++ b/spec/lib/accountify/aged_receivables_report_spec.rb @@ -27,13 +27,31 @@ module Accountify expect(aged_receivables_report.periods.size).to eq(num_periods) expect( - aged_receivables_report.periods.map { |p| p.attributes.slice('start_date', 'end_date', 'sub_total') } - ).to eq([ - {'start_date' => Date.parse('2024-06-23'), 'end_date' => Date.parse('2024-07-22'), 'sub_total' => BigDecimal('0')}, - {'start_date' => Date.parse('2024-07-23'), 'end_date' => Date.parse('2024-08-22'), 'sub_total' => BigDecimal('0')}, - {'start_date' => Date.parse('2024-08-23'), 'end_date' => Date.parse('2024-09-22'), 'sub_total' => BigDecimal('0')}, - {'start_date' => Date.parse('2024-09-23'), 'end_date' => Date.parse('2024-10-22'), 'sub_total' => BigDecimal('0')} - ]) + aged_receivables_report.periods.map do |period| + period.attributes.slice('start_date', 'end_date', 'sub_total') + end + ).to eq( + [ + { + 'start_date' => Date.parse('2024-06-23'), + 'end_date' => Date.parse('2024-07-22'), + 'sub_total' => BigDecimal('0') + }, + { + 'start_date' => Date.parse('2024-07-23'), + 'end_date' => Date.parse('2024-08-22'), + 'sub_total' => BigDecimal('0')}, + { + 'start_date' => Date.parse('2024-08-23'), + 'end_date' => Date.parse('2024-09-22'), + 'sub_total' => BigDecimal('0')}, + { + 'start_date' => Date.parse('2024-09-23'), + 'end_date' => Date.parse('2024-10-22'), + 'sub_total' => BigDecimal('0') + } + ] + ) end end end From b7fc1a3a5adc0915722c894eb9c9b35e1ca86dce Mon Sep 17 00:00:00 2001 From: bedrock-adam Date: Sun, 23 Jun 2024 22:10:22 +1000 Subject: [PATCH 03/12] commit aged receivables with invoices --- .../aged_receivables_report_spec.rb | 116 +++++++++++++++++- 1 file changed, 112 insertions(+), 4 deletions(-) diff --git a/spec/lib/accountify/aged_receivables_report_spec.rb b/spec/lib/accountify/aged_receivables_report_spec.rb index a0c17ea..6ade60e 100644 --- a/spec/lib/accountify/aged_receivables_report_spec.rb +++ b/spec/lib/accountify/aged_receivables_report_spec.rb @@ -2,7 +2,115 @@ module Accountify RSpec.describe AgedReceivablesReport, type: :module do + let(:current_date) { Date.parse('2024-06-23') } + let(:iam_tenant_id) { 4 } + + let(:organisation) do + create(:accountify_organisation, iam_tenant_id: iam_tenant_id) + end + + let(:contact) do + create(:accountify_contact, + iam_tenant_id: iam_tenant_id, organisation_id: organisation.id) + end + + let!(:invoice_1) do + create(:accountify_invoice, + iam_tenant_id: iam_tenant_id, + organisation_id: organisation.id, + contact_id: contact.id, + currency_code: "AUD", + status: Invoice::Status::ISSUED, + due_date: current_date, + sub_total_amount: BigDecimal("200.00"), + sub_total_currency_code: "AUD", + line_items: [ + build(:accountify_invoice_line_item, + description: "Leather Boots", + unit_amount_amount: BigDecimal("100.0"), + unit_amount_currency_code: "AUD", + quantity: 1), + build(:accountify_invoice_line_item, + description: "White Pants", + unit_amount_amount: BigDecimal("100.0"), + unit_amount_currency_code: "AUD", + quantity: 1) ] + ) + end + + let!(:invoice_2) do + create(:accountify_invoice, + iam_tenant_id: iam_tenant_id, + organisation_id: organisation.id, + contact_id: contact.id, + currency_code: "AUD", + status: Invoice::Status::ISSUED, + due_date: current_date + 1.month, + sub_total_amount: BigDecimal("400.00"), + sub_total_currency_code: "AUD", + line_items: [ + build(:accountify_invoice_line_item, + description: "Leather Boots", + unit_amount_amount: BigDecimal("100.0"), + unit_amount_currency_code: "AUD", + quantity: 2), + build(:accountify_invoice_line_item, + description: "White Pants", + unit_amount_amount: BigDecimal("100.0"), + unit_amount_currency_code: "AUD", + quantity: 2) ] + ) + end + + let!(:invoice_3) do + create(:accountify_invoice, + iam_tenant_id: iam_tenant_id, + organisation_id: organisation.id, + contact_id: contact.id, + currency_code: "AUD", + status: Invoice::Status::ISSUED, + due_date: current_date + 2.months, + sub_total_amount: BigDecimal("600.00"), + sub_total_currency_code: "AUD", + line_items: [ + build(:accountify_invoice_line_item, + description: "Leather Boots", + unit_amount_amount: BigDecimal("100.0"), + unit_amount_currency_code: "AUD", + quantity: 3), + build(:accountify_invoice_line_item, + description: "White Pants", + unit_amount_amount: BigDecimal("100.0"), + unit_amount_currency_code: "AUD", + quantity: 3) ] + ) + end + + let!(:invoice_4) do + create(:accountify_invoice, + iam_tenant_id: iam_tenant_id, + organisation_id: organisation.id, + contact_id: contact.id, + currency_code: "AUD", + status: Invoice::Status::ISSUED, + due_date: current_date + 3.months, + sub_total_amount: BigDecimal("800.00"), + sub_total_currency_code: "AUD", + line_items: [ + build(:accountify_invoice_line_item, + description: "Leather Boots", + unit_amount_amount: BigDecimal("100.0"), + unit_amount_currency_code: "AUD", + quantity: 4), + build(:accountify_invoice_line_item, + description: "White Pants", + unit_amount_amount: BigDecimal("100.0"), + unit_amount_currency_code: "AUD", + quantity: 4) ] + ) + end + let(:as_at_date) { Date.parse('2024-06-23') } let(:currency_code) { 'AUD' } let(:num_periods) { 4 } @@ -35,20 +143,20 @@ module Accountify { 'start_date' => Date.parse('2024-06-23'), 'end_date' => Date.parse('2024-07-22'), - 'sub_total' => BigDecimal('0') + 'sub_total' => BigDecimal('200.0') }, { 'start_date' => Date.parse('2024-07-23'), 'end_date' => Date.parse('2024-08-22'), - 'sub_total' => BigDecimal('0')}, + 'sub_total' => BigDecimal('400.0')}, { 'start_date' => Date.parse('2024-08-23'), 'end_date' => Date.parse('2024-09-22'), - 'sub_total' => BigDecimal('0')}, + 'sub_total' => BigDecimal('600.0')}, { 'start_date' => Date.parse('2024-09-23'), 'end_date' => Date.parse('2024-10-22'), - 'sub_total' => BigDecimal('0') + 'sub_total' => BigDecimal('800.0') } ] ) From 7b8c53ec49bd0b0b5d7984bcc0d64cb117b0b67b Mon Sep 17 00:00:00 2001 From: bedrock-adam Date: Sun, 23 Jun 2024 22:22:04 +1000 Subject: [PATCH 04/12] pass spec --- lib/accountify/aged_receivables_report.rb | 81 ++++++++++++----------- 1 file changed, 43 insertions(+), 38 deletions(-) diff --git a/lib/accountify/aged_receivables_report.rb b/lib/accountify/aged_receivables_report.rb index 15f1e8b..f50c65a 100644 --- a/lib/accountify/aged_receivables_report.rb +++ b/lib/accountify/aged_receivables_report.rb @@ -6,47 +6,52 @@ def generate(iam_tenant_id:, as_at_date: Date.today, currency_code: 'AUD', num_periods: 4, period_frequency: 1, period_unit: :month, ageing_by: :due_date) - report = Models::AgedReceivablesReport.create( - iam_tenant_id: iam_tenant_id, - as_at_date: as_at_date, - currency_code: currency_code, - num_periods: num_periods, - period_frequency: period_frequency, - period_unit: period_unit, - ageing_by: ageing_by) - - period_dates = (1..num_periods).map do |i| - start_date = case period_unit - when :month - as_at_date + (i - 1) * period_frequency.months - when :week - as_at_date + (i - 1) * period_frequency.weeks - when :day - as_at_date + (i - 1) * period_frequency.days + report = nil + + ActiveRecord::Base.transaction(isolation: :repeatable_read) do + report = Models::AgedReceivablesReport.create( + iam_tenant_id: iam_tenant_id, + as_at_date: as_at_date, + currency_code: currency_code, + num_periods: num_periods, + period_frequency: period_frequency, + period_unit: period_unit, + ageing_by: ageing_by) + + period_dates = (1..num_periods).map do |i| + start_date = case period_unit + when :month + as_at_date + (i - 1) * period_frequency.months + when :week + as_at_date + (i - 1) * period_frequency.weeks + when :day + as_at_date + (i - 1) * period_frequency.days + end + + end_date = case period_unit + when :month + as_at_date + i * period_frequency.months - 1.day + when :week + as_at_date + i * period_frequency.weeks - 1.day + when :day + as_at_date + i * period_frequency.days - 1.day end - end_date = case period_unit - when :month - as_at_date + i * period_frequency.months - 1.day - when :week - as_at_date + i * period_frequency.weeks - 1.day - when :day - as_at_date + i * period_frequency.days - 1.day - end - { start_date: start_date, end_date: end_date } - end + { start_date: start_date, end_date: end_date } + end + + period_dates.each do |period| + sub_total = Models::Invoice + .where(iam_tenant_id: iam_tenant_id) + .where(currency_code: currency_code) + .where("#{ageing_by} >= ? AND #{ageing_by} <= ?", period[:start_date], period[:end_date]) + .sum(:sub_total_amount) - period_dates.each do |period| - sub_total = Models::Invoice - .where(iam_tenant_id: iam_tenant_id) - .where(currency_code: currency_code) - .where("#{ageing_by} >= ? AND #{ageing_by} <= ?", period[:start_date], period[:end_date]) - .sum(:sub_total_amount) - - report.periods.create!( - start_date: period[:start_date], - end_date: period[:end_date], - sub_total: sub_total) + report.periods.create!( + start_date: period[:start_date], + end_date: period[:end_date], + sub_total: sub_total) + end end report From 4e002ecfa4abb88a301476cea885899b8e3f7c99 Mon Sep 17 00:00:00 2001 From: bedrock-adam Date: Sun, 23 Jun 2024 22:50:34 +1000 Subject: [PATCH 05/12] fix build --- Gemfile | 17 ++--- Gemfile.lock | 5 ++ .../aged_receivables_report_spec.rb | 75 ++++--------------- spec/rails_helper.rb | 14 +++- 4 files changed, 38 insertions(+), 73 deletions(-) diff --git a/Gemfile b/Gemfile index e73d36d..54e5a8c 100644 --- a/Gemfile +++ b/Gemfile @@ -7,17 +7,8 @@ gem "rails" gem "sinatra" -# The original asset pipeline for Rails [https://github.com/rails/sprockets-rails] - gem "puma" -group :development, :test do - gem "pry-byebug" -end - -group :development do -end - gem "pg" gem "activerecord" @@ -25,7 +16,15 @@ gem 'outboxer', '1.0.0.pre.beta' gem "sidekiq" +group :development do +end + +group :test do + gem 'database_cleaner-active_record' +end + group :development, :test do + gem "pry-byebug" gem 'rspec-rails' gem 'factory_bot_rails' end diff --git a/Gemfile.lock b/Gemfile.lock index 75102eb..173348d 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -83,6 +83,10 @@ GEM concurrent-ruby (1.2.3) connection_pool (2.4.1) crass (1.0.6) + database_cleaner-active_record (2.1.0) + activerecord (>= 5.a) + database_cleaner-core (~> 2.0.0) + database_cleaner-core (2.0.1) date (3.3.4) diff-lcs (1.5.1) drb (2.2.1) @@ -258,6 +262,7 @@ PLATFORMS DEPENDENCIES activerecord + database_cleaner-active_record factory_bot_rails outboxer (= 1.0.0.pre.beta) pg diff --git a/spec/lib/accountify/aged_receivables_report_spec.rb b/spec/lib/accountify/aged_receivables_report_spec.rb index 6ade60e..df20b7f 100644 --- a/spec/lib/accountify/aged_receivables_report_spec.rb +++ b/spec/lib/accountify/aged_receivables_report_spec.rb @@ -23,20 +23,8 @@ module Accountify currency_code: "AUD", status: Invoice::Status::ISSUED, due_date: current_date, - sub_total_amount: BigDecimal("200.00"), - sub_total_currency_code: "AUD", - line_items: [ - build(:accountify_invoice_line_item, - description: "Leather Boots", - unit_amount_amount: BigDecimal("100.0"), - unit_amount_currency_code: "AUD", - quantity: 1), - build(:accountify_invoice_line_item, - description: "White Pants", - unit_amount_amount: BigDecimal("100.0"), - unit_amount_currency_code: "AUD", - quantity: 1) ] - ) + sub_total_amount: BigDecimal("100.00"), + sub_total_currency_code: "AUD") end let!(:invoice_2) do @@ -47,20 +35,8 @@ module Accountify currency_code: "AUD", status: Invoice::Status::ISSUED, due_date: current_date + 1.month, - sub_total_amount: BigDecimal("400.00"), - sub_total_currency_code: "AUD", - line_items: [ - build(:accountify_invoice_line_item, - description: "Leather Boots", - unit_amount_amount: BigDecimal("100.0"), - unit_amount_currency_code: "AUD", - quantity: 2), - build(:accountify_invoice_line_item, - description: "White Pants", - unit_amount_amount: BigDecimal("100.0"), - unit_amount_currency_code: "AUD", - quantity: 2) ] - ) + sub_total_amount: BigDecimal("200.00"), + sub_total_currency_code: "AUD") end let!(:invoice_3) do @@ -71,20 +47,8 @@ module Accountify currency_code: "AUD", status: Invoice::Status::ISSUED, due_date: current_date + 2.months, - sub_total_amount: BigDecimal("600.00"), - sub_total_currency_code: "AUD", - line_items: [ - build(:accountify_invoice_line_item, - description: "Leather Boots", - unit_amount_amount: BigDecimal("100.0"), - unit_amount_currency_code: "AUD", - quantity: 3), - build(:accountify_invoice_line_item, - description: "White Pants", - unit_amount_amount: BigDecimal("100.0"), - unit_amount_currency_code: "AUD", - quantity: 3) ] - ) + sub_total_amount: BigDecimal("300.00"), + sub_total_currency_code: "AUD") end let!(:invoice_4) do @@ -95,20 +59,8 @@ module Accountify currency_code: "AUD", status: Invoice::Status::ISSUED, due_date: current_date + 3.months, - sub_total_amount: BigDecimal("800.00"), - sub_total_currency_code: "AUD", - line_items: [ - build(:accountify_invoice_line_item, - description: "Leather Boots", - unit_amount_amount: BigDecimal("100.0"), - unit_amount_currency_code: "AUD", - quantity: 4), - build(:accountify_invoice_line_item, - description: "White Pants", - unit_amount_amount: BigDecimal("100.0"), - unit_amount_currency_code: "AUD", - quantity: 4) ] - ) + sub_total_amount: BigDecimal("400.00"), + sub_total_currency_code: "AUD") end let(:as_at_date) { Date.parse('2024-06-23') } @@ -126,8 +78,7 @@ module Accountify num_periods: num_periods, period_frequency: period_frequency, period_unit: period_unit, - ageing_by: ageing_by - ) + ageing_by: ageing_by) end describe '.generate' do @@ -143,20 +94,20 @@ module Accountify { 'start_date' => Date.parse('2024-06-23'), 'end_date' => Date.parse('2024-07-22'), - 'sub_total' => BigDecimal('200.0') + 'sub_total' => BigDecimal('100.0') }, { 'start_date' => Date.parse('2024-07-23'), 'end_date' => Date.parse('2024-08-22'), - 'sub_total' => BigDecimal('400.0')}, + 'sub_total' => BigDecimal('200.0')}, { 'start_date' => Date.parse('2024-08-23'), 'end_date' => Date.parse('2024-09-22'), - 'sub_total' => BigDecimal('600.0')}, + 'sub_total' => BigDecimal('300.0')}, { 'start_date' => Date.parse('2024-09-23'), 'end_date' => Date.parse('2024-10-22'), - 'sub_total' => BigDecimal('800.0') + 'sub_total' => BigDecimal('400.0') } ] ) diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb index b0e7f18..b4db3ac 100644 --- a/spec/rails_helper.rb +++ b/spec/rails_helper.rb @@ -1,7 +1,9 @@ +ENV['RAILS_ENV'] ||= 'test' + require 'spec_helper' require 'factory_bot_rails' +require 'database_cleaner/active_record' -ENV['RAILS_ENV'] ||= 'test' require_relative '../config/environment' abort("The Rails environment is running in production mode!") if Rails.env.production? @@ -19,7 +21,15 @@ RSpec.configure do |config| config.fixture_paths = [Rails.root.join('spec/fixtures')] - config.use_transactional_fixtures = true + config.use_transactional_fixtures = false + + config.before(:each) do + DatabaseCleaner.strategy = :truncation + end + + config.before(:each) do + DatabaseCleaner.clean + end config.infer_spec_type_from_file_location! From e546f9d660fc8566003e069fb1b4f2a7c9f10e30 Mon Sep 17 00:00:00 2001 From: bedrock-adam Date: Sun, 23 Jun 2024 23:03:52 +1000 Subject: [PATCH 06/12] add unique true based on iam_tenant_id --- ...ate_accountify_aged_receivables_reports.rb | 2 +- db/schema.rb | 2 +- lib/accountify/aged_receivables_report.rb | 47 ++++++++++++------- 3 files changed, 31 insertions(+), 20 deletions(-) diff --git a/db/migrate/20240620220408_create_accountify_aged_receivables_reports.rb b/db/migrate/20240620220408_create_accountify_aged_receivables_reports.rb index 079da1f..3dfc15b 100644 --- a/db/migrate/20240620220408_create_accountify_aged_receivables_reports.rb +++ b/db/migrate/20240620220408_create_accountify_aged_receivables_reports.rb @@ -1,7 +1,7 @@ class CreateAccountifyAgedReceivablesReports < ActiveRecord::Migration[7.1] def change create_table :accountify_aged_receivables_reports do |t| - t.bigint :iam_tenant_id, null: false, index: true + t.bigint :iam_tenant_id, null: false, index: { unique: true } t.date :as_at_date, null: false t.string :currency_code, null: false diff --git a/db/schema.rb b/db/schema.rb index a80c700..93adf3b 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -34,7 +34,7 @@ t.string "ageing_by", null: false t.datetime "created_at", null: false t.datetime "updated_at", null: false - t.index ["iam_tenant_id"], name: "index_accountify_aged_receivables_reports_on_iam_tenant_id" + t.index ["iam_tenant_id"], name: "index_accountify_aged_receivables_reports_on_iam_tenant_id", unique: true end create_table "accountify_contacts", force: :cascade do |t| diff --git a/lib/accountify/aged_receivables_report.rb b/lib/accountify/aged_receivables_report.rb index f50c65a..40599c9 100644 --- a/lib/accountify/aged_receivables_report.rb +++ b/lib/accountify/aged_receivables_report.rb @@ -9,6 +9,13 @@ def generate(iam_tenant_id:, report = nil ActiveRecord::Base.transaction(isolation: :repeatable_read) do + Models::AgedReceivablesReport + .where(iam_tenant_id: iam_tenant_id) + .each do |report| + report.periods.destroy_all + report.delete + end + report = Models::AgedReceivablesReport.create( iam_tenant_id: iam_tenant_id, as_at_date: as_at_date, @@ -19,23 +26,25 @@ def generate(iam_tenant_id:, ageing_by: ageing_by) period_dates = (1..num_periods).map do |i| - start_date = case period_unit - when :month - as_at_date + (i - 1) * period_frequency.months - when :week - as_at_date + (i - 1) * period_frequency.weeks - when :day - as_at_date + (i - 1) * period_frequency.days - end - - end_date = case period_unit - when :month - as_at_date + i * period_frequency.months - 1.day - when :week - as_at_date + i * period_frequency.weeks - 1.day - when :day - as_at_date + i * period_frequency.days - 1.day - end + start_date = + case period_unit + when :month + as_at_date + (i - 1) * period_frequency.months + when :week + as_at_date + (i - 1) * period_frequency.weeks + when :day + as_at_date + (i - 1) * period_frequency.days + end + + end_date = + case period_unit + when :month + as_at_date + i * period_frequency.months - 1.day + when :week + as_at_date + i * period_frequency.weeks - 1.day + when :day + as_at_date + i * period_frequency.days - 1.day + end { start_date: start_date, end_date: end_date } end @@ -44,7 +53,9 @@ def generate(iam_tenant_id:, sub_total = Models::Invoice .where(iam_tenant_id: iam_tenant_id) .where(currency_code: currency_code) - .where("#{ageing_by} >= ? AND #{ageing_by} <= ?", period[:start_date], period[:end_date]) + .where( + "#{ageing_by} >= ? AND #{ageing_by} <= ?", + period[:start_date], period[:end_date]) .sum(:sub_total_amount) report.periods.create!( From aa8f7e123ce42f99e90bb2cc9fcc4821b3c1cc0f Mon Sep 17 00:00:00 2001 From: bedrock-adam Date: Sun, 23 Jun 2024 23:06:22 +1000 Subject: [PATCH 07/12] formatting --- lib/accountify/aged_receivables_report.rb | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/lib/accountify/aged_receivables_report.rb b/lib/accountify/aged_receivables_report.rb index 40599c9..2fdf9cc 100644 --- a/lib/accountify/aged_receivables_report.rb +++ b/lib/accountify/aged_receivables_report.rb @@ -9,11 +9,9 @@ def generate(iam_tenant_id:, report = nil ActiveRecord::Base.transaction(isolation: :repeatable_read) do - Models::AgedReceivablesReport - .where(iam_tenant_id: iam_tenant_id) - .each do |report| - report.periods.destroy_all - report.delete + Models::AgedReceivablesReport.where(iam_tenant_id: iam_tenant_id).each do |report| + report.periods.destroy_all + report.delete end report = Models::AgedReceivablesReport.create( From 2af369255fa8b4b4851d77a6e8e6c90f2a217075 Mon Sep 17 00:00:00 2001 From: bedrock-adam Date: Fri, 28 Jun 2024 07:43:17 +1000 Subject: [PATCH 08/12] add event created job --- .../aged_receivables_report/generate_job.rb | 12 ++++++++++ app/jobs/event/created_job.rb | 16 +++++++++++++ lib/event/created_job.rb | 15 ------------ .../accountify_invoice_updated_spec.rb | 23 +++++++++++++++++++ 4 files changed, 51 insertions(+), 15 deletions(-) create mode 100644 app/jobs/accountify/aged_receivables_report/generate_job.rb create mode 100644 app/jobs/event/created_job.rb delete mode 100644 lib/event/created_job.rb create mode 100644 spec/jobs/event/created_job/accountify_invoice_updated_spec.rb diff --git a/app/jobs/accountify/aged_receivables_report/generate_job.rb b/app/jobs/accountify/aged_receivables_report/generate_job.rb new file mode 100644 index 0000000..e06e963 --- /dev/null +++ b/app/jobs/accountify/aged_receivables_report/generate_job.rb @@ -0,0 +1,12 @@ +module Accountify + module AgedReceivablesReport + class GenerateJob + include Sidekiq::Job + + def perform(args) + AgedReceivablesReport.generate({ + 'iam_tenant_id' => args['iam_tenant_id'] }) + end + end + end +end diff --git a/app/jobs/event/created_job.rb b/app/jobs/event/created_job.rb new file mode 100644 index 0000000..c718fde --- /dev/null +++ b/app/jobs/event/created_job.rb @@ -0,0 +1,16 @@ +module Event + class CreatedJob + include Sidekiq::Job + + sidekiq_options queue: 'events', retry: false + + def perform(args) + case args['type'] + when 'Accountify::Invoice::UpdatedEvent' + Accountify::AgedReceivablesReport::GenerateJob.perform_async({ + 'iam_tenant_id' => args['iam_tenant_id'] }) + end + end + end +end + diff --git a/lib/event/created_job.rb b/lib/event/created_job.rb deleted file mode 100644 index b9d0c84..0000000 --- a/lib/event/created_job.rb +++ /dev/null @@ -1,15 +0,0 @@ -module Event - class CreatedJob - include Sidekiq::Job - - sidekiq_options retry: false, backtrace: true - - def perform(args) - event = Models::Event - .where(iam_tenant_id: args['iam_tenant_id']) - .find(args['id']) - - logger.info "event #{event.id} processed" - end - end -end diff --git a/spec/jobs/event/created_job/accountify_invoice_updated_spec.rb b/spec/jobs/event/created_job/accountify_invoice_updated_spec.rb new file mode 100644 index 0000000..cff5f9d --- /dev/null +++ b/spec/jobs/event/created_job/accountify_invoice_updated_spec.rb @@ -0,0 +1,23 @@ +require 'rails_helper' + +module Event + RSpec.describe CreatedJob, type: :job do + let(:iam_tenant_id) { 555 } + + before do + Event::CreatedJob.new.perform({ + 'iam_tenant_id' => iam_tenant_id, + 'type' => 'Accountify::Invoice::UpdatedEvent' }) + end + + describe 'when Accountify::Invoice::UpdatedEvent' do + it 'performs Accountify::AgedReceivablesReport::GenerateJob async' do + expect(Accountify::AgedReceivablesReport::GenerateJob.jobs).to match([ + hash_including( + 'args' => [ + hash_including( + 'iam_tenant_id' => iam_tenant_id )])]) + end + end + end +end From 20c3c73e333730c7eb71756393b96610464203d4 Mon Sep 17 00:00:00 2001 From: bedrock-adam Date: Fri, 28 Jun 2024 07:48:39 +1000 Subject: [PATCH 09/12] add created job specs --- app/jobs/event/created_job.rb | 12 ++++++++++ .../accountify_invoice_deleted_event_spec.rb | 23 +++++++++++++++++++ .../accountify_invoice_issued_spec.rb | 23 +++++++++++++++++++ .../accountify_invoice_paid_spec.rb | 23 +++++++++++++++++++ .../accountify_invoice_voided_spec.rb | 23 +++++++++++++++++++ 5 files changed, 104 insertions(+) create mode 100644 spec/jobs/event/created_job/accountify_invoice_deleted_event_spec.rb create mode 100644 spec/jobs/event/created_job/accountify_invoice_issued_spec.rb create mode 100644 spec/jobs/event/created_job/accountify_invoice_paid_spec.rb create mode 100644 spec/jobs/event/created_job/accountify_invoice_voided_spec.rb diff --git a/app/jobs/event/created_job.rb b/app/jobs/event/created_job.rb index c718fde..c9d472e 100644 --- a/app/jobs/event/created_job.rb +++ b/app/jobs/event/created_job.rb @@ -6,9 +6,21 @@ class CreatedJob def perform(args) case args['type'] + when 'Accountify::Invoice::IssuedEvent' + Accountify::AgedReceivablesReport::GenerateJob.perform_async({ + 'iam_tenant_id' => args['iam_tenant_id'] }) when 'Accountify::Invoice::UpdatedEvent' Accountify::AgedReceivablesReport::GenerateJob.perform_async({ 'iam_tenant_id' => args['iam_tenant_id'] }) + when 'Accountify::Invoice::PaidEvent' + Accountify::AgedReceivablesReport::GenerateJob.perform_async({ + 'iam_tenant_id' => args['iam_tenant_id'] }) + when 'Accountify::Invoice::VoidedEvent' + Accountify::AgedReceivablesReport::GenerateJob.perform_async({ + 'iam_tenant_id' => args['iam_tenant_id'] }) + when 'Accountify::Invoice::DeletedEvent' + Accountify::AgedReceivablesReport::GenerateJob.perform_async({ + 'iam_tenant_id' => args['iam_tenant_id'] }) end end end diff --git a/spec/jobs/event/created_job/accountify_invoice_deleted_event_spec.rb b/spec/jobs/event/created_job/accountify_invoice_deleted_event_spec.rb new file mode 100644 index 0000000..77a161f --- /dev/null +++ b/spec/jobs/event/created_job/accountify_invoice_deleted_event_spec.rb @@ -0,0 +1,23 @@ +require 'rails_helper' + +module Event + RSpec.describe CreatedJob, type: :job do + let(:iam_tenant_id) { 555 } + + before do + Event::CreatedJob.new.perform({ + 'iam_tenant_id' => iam_tenant_id, + 'type' => 'Accountify::Invoice::DeletedEvent' }) + end + + describe 'when Accountify::Invoice::DeletedEvent' do + it 'performs Accountify::AgedReceivablesReport::GenerateJob async' do + expect(Accountify::AgedReceivablesReport::GenerateJob.jobs).to match([ + hash_including( + 'args' => [ + hash_including( + 'iam_tenant_id' => iam_tenant_id )])]) + end + end + end +end diff --git a/spec/jobs/event/created_job/accountify_invoice_issued_spec.rb b/spec/jobs/event/created_job/accountify_invoice_issued_spec.rb new file mode 100644 index 0000000..1316a76 --- /dev/null +++ b/spec/jobs/event/created_job/accountify_invoice_issued_spec.rb @@ -0,0 +1,23 @@ +require 'rails_helper' + +module Event + RSpec.describe CreatedJob, type: :job do + let(:iam_tenant_id) { 555 } + + before do + Event::CreatedJob.new.perform({ + 'iam_tenant_id' => iam_tenant_id, + 'type' => 'Accountify::Invoice::IssuedEvent' }) + end + + describe 'when Accountify::Invoice::IssuedEvent' do + it 'performs Accountify::AgedReceivablesReport::GenerateJob async' do + expect(Accountify::AgedReceivablesReport::GenerateJob.jobs).to match([ + hash_including( + 'args' => [ + hash_including( + 'iam_tenant_id' => iam_tenant_id )])]) + end + end + end +end diff --git a/spec/jobs/event/created_job/accountify_invoice_paid_spec.rb b/spec/jobs/event/created_job/accountify_invoice_paid_spec.rb new file mode 100644 index 0000000..41f2aec --- /dev/null +++ b/spec/jobs/event/created_job/accountify_invoice_paid_spec.rb @@ -0,0 +1,23 @@ +require 'rails_helper' + +module Event + RSpec.describe CreatedJob, type: :job do + let(:iam_tenant_id) { 555 } + + before do + Event::CreatedJob.new.perform({ + 'iam_tenant_id' => iam_tenant_id, + 'type' => 'Accountify::Invoice::PaidEvent' }) + end + + describe 'when Accountify::Invoice::PaidEvent' do + it 'performs Accountify::AgedReceivablesReport::GenerateJob async' do + expect(Accountify::AgedReceivablesReport::GenerateJob.jobs).to match([ + hash_including( + 'args' => [ + hash_including( + 'iam_tenant_id' => iam_tenant_id )])]) + end + end + end +end diff --git a/spec/jobs/event/created_job/accountify_invoice_voided_spec.rb b/spec/jobs/event/created_job/accountify_invoice_voided_spec.rb new file mode 100644 index 0000000..0c19e9c --- /dev/null +++ b/spec/jobs/event/created_job/accountify_invoice_voided_spec.rb @@ -0,0 +1,23 @@ +require 'rails_helper' + +module Event + RSpec.describe CreatedJob, type: :job do + let(:iam_tenant_id) { 555 } + + before do + Event::CreatedJob.new.perform({ + 'iam_tenant_id' => iam_tenant_id, + 'type' => 'Accountify::Invoice::VoidedEvent' }) + end + + describe 'when Accountify::Invoice::VoidedEvent' do + it 'performs Accountify::AgedReceivablesReport::GenerateJob async' do + expect(Accountify::AgedReceivablesReport::GenerateJob.jobs).to match([ + hash_including( + 'args' => [ + hash_including( + 'iam_tenant_id' => iam_tenant_id )])]) + end + end + end +end From 8746eeee84367c08a090daf2b1a855babc9ea91f Mon Sep 17 00:00:00 2001 From: bedrock-adam Date: Fri, 28 Jun 2024 08:06:26 +1000 Subject: [PATCH 10/12] move files --- .../invoice/deleted_event_spec.rb} | 0 .../invoice/issued_spec.rb} | 0 .../invoice/paid_spec.rb} | 0 .../invoice/updated_spec.rb} | 0 .../invoice/voided_spec.rb} | 0 5 files changed, 0 insertions(+), 0 deletions(-) rename spec/jobs/event/created_job/{accountify_invoice_deleted_event_spec.rb => accountify/invoice/deleted_event_spec.rb} (100%) rename spec/jobs/event/created_job/{accountify_invoice_issued_spec.rb => accountify/invoice/issued_spec.rb} (100%) rename spec/jobs/event/created_job/{accountify_invoice_paid_spec.rb => accountify/invoice/paid_spec.rb} (100%) rename spec/jobs/event/created_job/{accountify_invoice_updated_spec.rb => accountify/invoice/updated_spec.rb} (100%) rename spec/jobs/event/created_job/{accountify_invoice_voided_spec.rb => accountify/invoice/voided_spec.rb} (100%) diff --git a/spec/jobs/event/created_job/accountify_invoice_deleted_event_spec.rb b/spec/jobs/event/created_job/accountify/invoice/deleted_event_spec.rb similarity index 100% rename from spec/jobs/event/created_job/accountify_invoice_deleted_event_spec.rb rename to spec/jobs/event/created_job/accountify/invoice/deleted_event_spec.rb diff --git a/spec/jobs/event/created_job/accountify_invoice_issued_spec.rb b/spec/jobs/event/created_job/accountify/invoice/issued_spec.rb similarity index 100% rename from spec/jobs/event/created_job/accountify_invoice_issued_spec.rb rename to spec/jobs/event/created_job/accountify/invoice/issued_spec.rb diff --git a/spec/jobs/event/created_job/accountify_invoice_paid_spec.rb b/spec/jobs/event/created_job/accountify/invoice/paid_spec.rb similarity index 100% rename from spec/jobs/event/created_job/accountify_invoice_paid_spec.rb rename to spec/jobs/event/created_job/accountify/invoice/paid_spec.rb diff --git a/spec/jobs/event/created_job/accountify_invoice_updated_spec.rb b/spec/jobs/event/created_job/accountify/invoice/updated_spec.rb similarity index 100% rename from spec/jobs/event/created_job/accountify_invoice_updated_spec.rb rename to spec/jobs/event/created_job/accountify/invoice/updated_spec.rb diff --git a/spec/jobs/event/created_job/accountify_invoice_voided_spec.rb b/spec/jobs/event/created_job/accountify/invoice/voided_spec.rb similarity index 100% rename from spec/jobs/event/created_job/accountify_invoice_voided_spec.rb rename to spec/jobs/event/created_job/accountify/invoice/voided_spec.rb From 6b6dde8cdcc6d05d63697cad44d672a0ca374cad Mon Sep 17 00:00:00 2001 From: bedrock-adam Date: Sun, 30 Jun 2024 19:28:04 +1000 Subject: [PATCH 11/12] add specs --- app/jobs/event/created_job.rb | 1 - ...ate_accountify_aged_receivables_reports.rb | 2 +- ...ountify_aged_receivables_report_periods.rb | 3 +- db/schema.rb | 5 +- lib/accountify/aged_receivables_report.rb | 182 ++++++++++++---- .../aged_receivables_report/generate_spec.rb | 194 ++++++++++++++++++ .../aged_receivables_report_spec.rb | 117 ----------- 7 files changed, 340 insertions(+), 164 deletions(-) create mode 100644 spec/lib/accountify/aged_receivables_report/generate_spec.rb delete mode 100644 spec/lib/accountify/aged_receivables_report_spec.rb diff --git a/app/jobs/event/created_job.rb b/app/jobs/event/created_job.rb index c9d472e..a59b002 100644 --- a/app/jobs/event/created_job.rb +++ b/app/jobs/event/created_job.rb @@ -25,4 +25,3 @@ def perform(args) end end end - diff --git a/db/migrate/20240620220408_create_accountify_aged_receivables_reports.rb b/db/migrate/20240620220408_create_accountify_aged_receivables_reports.rb index 3dfc15b..ad5f112 100644 --- a/db/migrate/20240620220408_create_accountify_aged_receivables_reports.rb +++ b/db/migrate/20240620220408_create_accountify_aged_receivables_reports.rb @@ -6,7 +6,7 @@ def change t.date :as_at_date, null: false t.string :currency_code, null: false t.integer :num_periods, null: false - t.integer :period_frequency, null: false + t.integer :period_amount, null: false t.string :period_unit, null: false t.string :ageing_by, null: false diff --git a/db/migrate/20240620220453_create_accountify_aged_receivables_report_periods.rb b/db/migrate/20240620220453_create_accountify_aged_receivables_report_periods.rb index 6bc7ecd..7aa7c25 100644 --- a/db/migrate/20240620220453_create_accountify_aged_receivables_report_periods.rb +++ b/db/migrate/20240620220453_create_accountify_aged_receivables_report_periods.rb @@ -6,7 +6,8 @@ def change t.date :start_date, null: false t.date :end_date, null: false - t.decimal :sub_total, precision: 12, scale: 2, null: false + t.decimal :sub_total_amount, precision: 12, scale: 2, null: false + t.string :sub_total_currency_code, null: false t.timestamps end diff --git a/db/schema.rb b/db/schema.rb index 93adf3b..114e084 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -18,7 +18,8 @@ t.bigint "aged_receivables_report_id", null: false t.date "start_date", null: false t.date "end_date", null: false - t.decimal "sub_total", precision: 12, scale: 2, null: false + t.decimal "sub_total_amount", precision: 12, scale: 2, null: false + t.string "sub_total_currency_code", null: false t.datetime "created_at", null: false t.datetime "updated_at", null: false t.index ["aged_receivables_report_id"], name: "idx_on_aged_receivables_report_id_7f32513b52" @@ -29,7 +30,7 @@ t.date "as_at_date", null: false t.string "currency_code", null: false t.integer "num_periods", null: false - t.integer "period_frequency", null: false + t.integer "period_amount", null: false t.string "period_unit", null: false t.string "ageing_by", null: false t.datetime "created_at", null: false diff --git a/lib/accountify/aged_receivables_report.rb b/lib/accountify/aged_receivables_report.rb index 2fdf9cc..df72ef3 100644 --- a/lib/accountify/aged_receivables_report.rb +++ b/lib/accountify/aged_receivables_report.rb @@ -3,67 +3,165 @@ module AgedReceivablesReport extend self def generate(iam_tenant_id:, - as_at_date: Date.today, currency_code: 'AUD', - num_periods: 4, period_frequency: 1, period_unit: :month, - ageing_by: :due_date) - report = nil + as_at_date:, num_periods:, period_amount:, period_unit:, + ageing_by:, currency_code:) + validate( + iam_tenant_id: iam_tenant_id, + as_at_date: as_at_date, + num_periods: num_periods, + period_amount: period_amount, + period_unit: period_unit, + ageing_by: ageing_by, + currency_code: currency_code) + + period_ranges = generate_period_ranges( + as_at_date: as_at_date, + num_periods: num_periods, + period_amount: period_amount, + period_unit: period_unit) + + id = nil ActiveRecord::Base.transaction(isolation: :repeatable_read) do - Models::AgedReceivablesReport.where(iam_tenant_id: iam_tenant_id).each do |report| - report.periods.destroy_all - report.delete - end + current_utc_time = Time.current.utc report = Models::AgedReceivablesReport.create( iam_tenant_id: iam_tenant_id, as_at_date: as_at_date, currency_code: currency_code, num_periods: num_periods, - period_frequency: period_frequency, + period_amount: period_amount, period_unit: period_unit, - ageing_by: ageing_by) - - period_dates = (1..num_periods).map do |i| - start_date = - case period_unit - when :month - as_at_date + (i - 1) * period_frequency.months - when :week - as_at_date + (i - 1) * period_frequency.weeks - when :day - as_at_date + (i - 1) * period_frequency.days - end - - end_date = - case period_unit - when :month - as_at_date + i * period_frequency.months - 1.day - when :week - as_at_date + i * period_frequency.weeks - 1.day - when :day - as_at_date + i * period_frequency.days - 1.day - end - - { start_date: start_date, end_date: end_date } - end + ageing_by: ageing_by, + created_at: current_utc_time, + updated_at: current_utc_time) - period_dates.each do |period| - sub_total = Models::Invoice + period_ranges.each do |period_range| + sub_total_amount = Models::Invoice .where(iam_tenant_id: iam_tenant_id) - .where(currency_code: currency_code) + .where(sub_total_currency_code: currency_code) .where( "#{ageing_by} >= ? AND #{ageing_by} <= ?", - period[:start_date], period[:end_date]) + period_range[:start_date], period_range[:end_date]) .sum(:sub_total_amount) report.periods.create!( - start_date: period[:start_date], - end_date: period[:end_date], - sub_total: sub_total) + start_date: period_range[:start_date], + end_date: period_range[:end_date], + sub_total_amount: sub_total_amount, + sub_total_currency_code: currency_code) end + + id = report.id + end + + find_by_id(iam_tenant_id: iam_tenant_id, id: id) + end + + def find_by_id(iam_tenant_id:, id:) + report = nil + + ActiveRecord::Base.transaction(isolation: :repeatable_read) do + report = Models::AgedReceivablesReport + .includes(:periods) + .where(iam_tenant_id: iam_tenant_id) + .find_by!(id: id) + end + + { + id: report.id, + as_at_date: report.as_at_date, + currency_code: report.currency_code, + num_periods: report.num_periods, + period_amount: report.period_amount, + period_unit: report.period_unit.to_sym, + created_at: report.created_at, + updated_at: report.updated_at, + periods: report.periods.map do |period| + { + start_date: period.start_date, + end_date: period.end_date, + sub_total: { + amount: period.sub_total_amount, + currency_code: period.sub_total_currency_code + } + } + end + } + end + + def generate_period_ranges(as_at_date:, num_periods:, period_amount:, period_unit:) + (1..num_periods).map do |i| + start_date = + case period_unit + when :month + as_at_date + ((i - 1) * period_amount.months) + when :week + as_at_date + ((i - 1) * period_amount.weeks) + when :day + as_at_date + ((i - 1) * period_amount.days) + end + + end_date = + case period_unit + when :month + as_at_date + (i * period_amount.months) - 1.day + when :week + as_at_date + (i * period_amount.weeks) - 1.day + when :day + as_at_date + (i * period_amount.days) - 1.day + end + + { start_date: start_date, end_date: end_date } + end + end + + def validate(iam_tenant_id:, + as_at_date:, num_periods:, period_amount:, period_unit:, + ageing_by:, currency_code:) + if !as_at_date.is_a?(Date) + raise ArgumentError.new('as_at_date must be a valid date') + end + + if !num_periods.is_a?(Integer) + raise ArgumentError.new('num_periods must be an integer') + end + + if !period_amount.is_a?(Integer) + raise ArgumentError.new('period_amount must be an integer') end - report + if !period_unit.is_a?(Symbol) + raise ArgumentError.new('period_unit must be a symbol') + end + + if !ageing_by.is_a?(Symbol) + raise ArgumentError.new('ageing_by must be a symbol') + end + + if !currency_code.is_a?(String) + raise ArgumentError.new('currency_code must be a string') + end + + if !(1..24).include?(num_periods) + raise ArgumentError.new('num_periods must be within range 1..24 inclusive') + end + + if !(1..24).include?(period_amount) + raise ArgumentError.new('period_amount must be within range 1..24 inclusive') + end + + if ![:day, :week, :month].include?(period_unit) + raise ArgumentError.new('period_unit must be :day, :week, or :month') + end + + if ![:issue_date, :due_date].include?(ageing_by) + raise ArgumentError.new('ageing_by must be :issue_date or :due_date') + end + + if !['AUD'].include?(currency_code) + raise ArgumentError.new("currency_code must be 'AUD'") + end end end end diff --git a/spec/lib/accountify/aged_receivables_report/generate_spec.rb b/spec/lib/accountify/aged_receivables_report/generate_spec.rb new file mode 100644 index 0000000..b465ab7 --- /dev/null +++ b/spec/lib/accountify/aged_receivables_report/generate_spec.rb @@ -0,0 +1,194 @@ +require 'rails_helper' + +module Accountify + RSpec.describe AgedReceivablesReport, type: :module do + describe '.generate' do + let(:current_date) { Date.parse('2024-06-23') } + + let(:iam_tenant_id) { 4 } + + let(:organisation) do + create(:accountify_organisation, iam_tenant_id: iam_tenant_id) + end + + let(:contact) do + create(:accountify_contact, + iam_tenant_id: iam_tenant_id, organisation_id: organisation.id) + end + + let!(:invoice_1) do + create(:accountify_invoice, + iam_tenant_id: iam_tenant_id, + organisation_id: organisation.id, + contact_id: contact.id, + currency_code: "AUD", + status: Invoice::Status::ISSUED, + due_date: current_date, + sub_total_amount: BigDecimal("100.00"), + sub_total_currency_code: "AUD") + end + + let!(:invoice_2) do + create(:accountify_invoice, + iam_tenant_id: iam_tenant_id, + organisation_id: organisation.id, + contact_id: contact.id, + currency_code: "AUD", + status: Invoice::Status::ISSUED, + due_date: current_date + 1.month, + sub_total_amount: BigDecimal("200.00"), + sub_total_currency_code: "AUD") + end + + let!(:invoice_3) do + create(:accountify_invoice, + iam_tenant_id: iam_tenant_id, + organisation_id: organisation.id, + contact_id: contact.id, + currency_code: "AUD", + status: Invoice::Status::ISSUED, + due_date: current_date + 2.months, + sub_total_amount: BigDecimal("300.00"), + sub_total_currency_code: "AUD") + end + + let!(:invoice_4) do + create(:accountify_invoice, + iam_tenant_id: iam_tenant_id, + organisation_id: organisation.id, + contact_id: contact.id, + currency_code: "AUD", + status: Invoice::Status::ISSUED, + due_date: current_date + 3.months, + sub_total_amount: BigDecimal("400.00"), + sub_total_currency_code: "AUD") + end + + let(:as_at_date) { Date.parse('2024-06-23') } + let(:currency_code) { 'AUD' } + let(:num_periods) { 4 } + let(:period_amount) { 1 } + let(:period_unit) { :month } + let(:ageing_by) { :due_date } + + let(:report) do + AgedReceivablesReport.generate( + iam_tenant_id: iam_tenant_id, + as_at_date: as_at_date, + currency_code: currency_code, + num_periods: num_periods, + period_amount: period_amount, + period_unit: period_unit, + ageing_by: ageing_by) + end + + let(:report_model) do + Models::AgedReceivablesReport + .where(iam_tenant_id: iam_tenant_id) + .find_by!(id: report[:id]) + end + + it 'creates model' do + expect( + report_model.attributes.slice( + 'id', + 'as_at_date', + 'currency_code', + 'num_periods', + 'period_amount', + 'period_unit' + ).merge( + 'periods' => report_model.periods.map do |period| + period.slice( + 'start_date', + 'end_date', + 'sub_total_amount', + 'sub_total_currency_code') + end + ) + ).to eq( + 'id' => report_model.id, + 'as_at_date' => Date.parse('2024-06-23'), + 'currency_code' => 'AUD', + 'num_periods' => 4, + 'period_amount' => 1, + 'period_unit' => 'month', + 'periods' => [ + { + 'start_date' => Date.parse('2024-06-23'), + 'end_date' => Date.parse('2024-07-22'), + 'sub_total_amount' => 100.0, + 'sub_total_currency_code' => 'AUD' + }, + { + 'start_date' => Date.parse('2024-07-23'), + 'end_date' => Date.parse('2024-08-22'), + 'sub_total_amount' => 200.0, + 'sub_total_currency_code' => 'AUD' + }, + { + 'start_date' => Date.parse('2024-08-23'), + 'end_date' => Date.parse('2024-09-22'), + 'sub_total_amount' => 300.0, + 'sub_total_currency_code' => 'AUD' + }, + { + 'start_date' => Date.parse('2024-09-23'), + 'end_date' => Date.parse('2024-10-22'), + 'sub_total_amount' => 400.0, + 'sub_total_currency_code' => 'AUD' + } + ] + ) + end + + it 'returns report' do + expect(report).to include({ + id: be_a(Integer), + as_at_date: Date.parse('2024-06-23'), + currency_code: 'AUD', + num_periods: 4, + period_amount: 1, + period_unit: :month, + created_at: be_present, + updated_at: be_present, + periods: [ + { + start_date: Date.parse('2024-06-23'), + end_date: Date.parse('2024-07-22'), + sub_total: { + amount: BigDecimal('100.0'), + currency_code: 'AUD' + } + }, + { + start_date: Date.parse('2024-07-23'), + end_date: Date.parse('2024-08-22'), + sub_total: { + amount: BigDecimal('200.0'), + currency_code: 'AUD' + } + }, + { + start_date: Date.parse('2024-08-23'), + end_date: Date.parse('2024-09-22'), + sub_total: { + amount: BigDecimal('300.0'), + currency_code: 'AUD' + } + }, + { + start_date: Date.parse('2024-09-23'), + end_date: Date.parse('2024-10-22'), + sub_total: { + amount: BigDecimal('400.0'), + currency_code: 'AUD' + } + } + ] + }) + end + + end + end +end diff --git a/spec/lib/accountify/aged_receivables_report_spec.rb b/spec/lib/accountify/aged_receivables_report_spec.rb deleted file mode 100644 index df20b7f..0000000 --- a/spec/lib/accountify/aged_receivables_report_spec.rb +++ /dev/null @@ -1,117 +0,0 @@ -require 'rails_helper' - -module Accountify - RSpec.describe AgedReceivablesReport, type: :module do - let(:current_date) { Date.parse('2024-06-23') } - - let(:iam_tenant_id) { 4 } - - let(:organisation) do - create(:accountify_organisation, iam_tenant_id: iam_tenant_id) - end - - let(:contact) do - create(:accountify_contact, - iam_tenant_id: iam_tenant_id, organisation_id: organisation.id) - end - - let!(:invoice_1) do - create(:accountify_invoice, - iam_tenant_id: iam_tenant_id, - organisation_id: organisation.id, - contact_id: contact.id, - currency_code: "AUD", - status: Invoice::Status::ISSUED, - due_date: current_date, - sub_total_amount: BigDecimal("100.00"), - sub_total_currency_code: "AUD") - end - - let!(:invoice_2) do - create(:accountify_invoice, - iam_tenant_id: iam_tenant_id, - organisation_id: organisation.id, - contact_id: contact.id, - currency_code: "AUD", - status: Invoice::Status::ISSUED, - due_date: current_date + 1.month, - sub_total_amount: BigDecimal("200.00"), - sub_total_currency_code: "AUD") - end - - let!(:invoice_3) do - create(:accountify_invoice, - iam_tenant_id: iam_tenant_id, - organisation_id: organisation.id, - contact_id: contact.id, - currency_code: "AUD", - status: Invoice::Status::ISSUED, - due_date: current_date + 2.months, - sub_total_amount: BigDecimal("300.00"), - sub_total_currency_code: "AUD") - end - - let!(:invoice_4) do - create(:accountify_invoice, - iam_tenant_id: iam_tenant_id, - organisation_id: organisation.id, - contact_id: contact.id, - currency_code: "AUD", - status: Invoice::Status::ISSUED, - due_date: current_date + 3.months, - sub_total_amount: BigDecimal("400.00"), - sub_total_currency_code: "AUD") - end - - let(:as_at_date) { Date.parse('2024-06-23') } - let(:currency_code) { 'AUD' } - let(:num_periods) { 4 } - let(:period_frequency) { 1 } - let(:period_unit) { :month } - let(:ageing_by) { :due_date } - - let(:aged_receivables_report) do - AgedReceivablesReport.generate( - iam_tenant_id: iam_tenant_id, - as_at_date: as_at_date, - currency_code: currency_code, - num_periods: num_periods, - period_frequency: period_frequency, - period_unit: period_unit, - ageing_by: ageing_by) - end - - describe '.generate' do - it 'calculates ageing periods correctly' do - expect(aged_receivables_report.periods.size).to eq(num_periods) - - expect( - aged_receivables_report.periods.map do |period| - period.attributes.slice('start_date', 'end_date', 'sub_total') - end - ).to eq( - [ - { - 'start_date' => Date.parse('2024-06-23'), - 'end_date' => Date.parse('2024-07-22'), - 'sub_total' => BigDecimal('100.0') - }, - { - 'start_date' => Date.parse('2024-07-23'), - 'end_date' => Date.parse('2024-08-22'), - 'sub_total' => BigDecimal('200.0')}, - { - 'start_date' => Date.parse('2024-08-23'), - 'end_date' => Date.parse('2024-09-22'), - 'sub_total' => BigDecimal('300.0')}, - { - 'start_date' => Date.parse('2024-09-23'), - 'end_date' => Date.parse('2024-10-22'), - 'sub_total' => BigDecimal('400.0') - } - ] - ) - end - end - end -end From 749fcea07741c79b18b0dee78f1d2e8a3421c2e6 Mon Sep 17 00:00:00 2001 From: bedrock-adam Date: Sun, 30 Jun 2024 22:03:56 +1000 Subject: [PATCH 12/12] add optimisations --- lib/accountify/aged_receivables_report.rb | 80 ++++++++++++++++++++--- 1 file changed, 71 insertions(+), 9 deletions(-) diff --git a/lib/accountify/aged_receivables_report.rb b/lib/accountify/aged_receivables_report.rb index df72ef3..366201c 100644 --- a/lib/accountify/aged_receivables_report.rb +++ b/lib/accountify/aged_receivables_report.rb @@ -6,7 +6,6 @@ def generate(iam_tenant_id:, as_at_date:, num_periods:, period_amount:, period_unit:, ageing_by:, currency_code:) validate( - iam_tenant_id: iam_tenant_id, as_at_date: as_at_date, num_periods: num_periods, period_amount: period_amount, @@ -14,12 +13,6 @@ def generate(iam_tenant_id:, ageing_by: ageing_by, currency_code: currency_code) - period_ranges = generate_period_ranges( - as_at_date: as_at_date, - num_periods: num_periods, - period_amount: period_amount, - period_unit: period_unit) - id = nil ActiveRecord::Base.transaction(isolation: :repeatable_read) do @@ -36,6 +29,12 @@ def generate(iam_tenant_id:, created_at: current_utc_time, updated_at: current_utc_time) + period_ranges = generate_period_ranges( + as_at_date: as_at_date, + num_periods: num_periods, + period_amount: period_amount, + period_unit: period_unit) + period_ranges.each do |period_range| sub_total_amount = Models::Invoice .where(iam_tenant_id: iam_tenant_id) @@ -55,6 +54,70 @@ def generate(iam_tenant_id:, id = report.id end + find_by_id(iam_tenant_id: iam_tenant_id, id: id) + rescue ActiveRecord::RecordNotUnique + regenerate( + iam_tenant_id: iam_tenant_id, + as_at_date: as_at_date, + num_periods: num_period, + period_amount: period_amount, + period_unit: period_unit, + ageing_by: ageing_by, + currency_code: currency_code) + end + + def regenerate(iam_tenant_id:, + as_at_date:, num_periods:, period_amount:, period_unit:, + ageing_by:, currency_code:) + id = nil + + ActiveRecord::Base.transaction(transaction_isolation: :repeatable_read) do + current_utc_time = Time.current.utc + + report = Models::AgedReceivablesReport + .where(iam_tenant_id: iam_tenant_id) + .lock('FOR UPDATE NOWAIT') + .first! + + if report.generated_at <= generate_at + report.periods.delete_all + + report.update!( + as_at_date: as_at_date, + currency_code: currency_code, + num_periods: num_periods, + period_amount: period_amount, + period_unit: period_unit, + ageing_by: ageing_by, + created_at: current_utc_time, + updated_at: current_utc_time) + + period_ranges = generate_period_ranges( + as_at_date: as_at_date, + num_periods: num_periods, + period_amount: period_amount, + period_unit: period_unit) + + period_ranges.each do |period_range| + sub_total_amount = Models::Invoice + .where(iam_tenant_id: iam_tenant_id) + .where(sub_total_currency_code: currency_code) + .where( + "#{ageing_by} >= ? AND #{ageing_by} <= ?", + period_range[:start_date], period_range[:end_date]) + .sum(:sub_total_amount) + + report.periods.create!( + start_date: period_range[:start_date], + end_date: period_range[:end_date], + sub_total_amount: sub_total_amount, + sub_total_currency_code: currency_code) + end + end + + id = report.id + end + find_by_id(iam_tenant_id: iam_tenant_id, id: id) end @@ -116,8 +179,7 @@ def generate_period_ranges(as_at_date:, num_periods:, period_amount:, period_uni end end - def validate(iam_tenant_id:, - as_at_date:, num_periods:, period_amount:, period_unit:, + def validate(as_at_date:, num_periods:, period_amount:, period_unit:, ageing_by:, currency_code:) if !as_at_date.is_a?(Date) raise ArgumentError.new('as_at_date must be a valid date')