From 06502cde330e1d20bc54d8ddb8b9d17cca8a933a Mon Sep 17 00:00:00 2001
From: Sylvain <sylvain@sbx.pm>
Date: Mon, 11 May 2020 10:10:52 +0200
Subject: [PATCH] [bug] accounting periods totals are wrong for periods closed
 after 2019-08-01

---
 CHANGELOG.md                                  |  2 +
 app/models/accounting_period.rb               |  2 +-
 app/services/vat_history_service.rb           |  2 +-
 ...30_migrate_invoice_to_invoicing_profile.rb |  2 -
 .../20200511075933_fix_accounting_periods.rb  | 72 +++++++++++++++++++
 db/schema.rb                                  | 24 +++----
 6 files changed, 88 insertions(+), 16 deletions(-)
 create mode 100644 db/migrate/20200511075933_fix_accounting_periods.rb

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2ab78ffee..8f792d961 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,6 +8,7 @@
 - Ask for confirmation before booking a slot for a member without the required tag
 - Corrected the documentation about BOOK_SLOT_AT_SAME_TIME
 - Auto-adjusts text colors based on the selected theme colors
+- Fix a bug: accounting periods totals are wrong for periods closed after 2019-08-01
 - Fix a bug: unable to change group if the previous was deactivated
 - Fix a bug: unable to create events or trainings that are not multiples of SLOT_DURATION
 - Fix a bug: unable to delete an unreserved event
@@ -19,6 +20,7 @@
 - Fix a bug: background image of the profile is not shown and wrong menu hover color
 - Fix a bug: do not show disabled groups and plans during availability creation
 - Fix a security issue: updated jquery to fix [CVE-2020-11023](https://nvd.nist.gov/vuln/detail/CVE-2020-11023)
+- [TODO DEPLOY] `rails db:migrate`
 
 ## v4.3.4 2020 April 14
 
diff --git a/app/models/accounting_period.rb b/app/models/accounting_period.rb
index 5402781f9..4a5824221 100644
--- a/app/models/accounting_period.rb
+++ b/app/models/accounting_period.rb
@@ -30,7 +30,7 @@ class AccountingPeriod < ApplicationRecord
   def invoices_with_vat(invoices)
     vat_service = VatHistoryService.new
     invoices.map do |i|
-      { invoice: i, vat_rate: vat_service.invoice_vat(i) }
+      { invoice: i, vat_rate: vat_service.invoice_vat(i) / 100.0 }
     end
   end
 
diff --git a/app/services/vat_history_service.rb b/app/services/vat_history_service.rb
index 8ced7b37c..4c03cfe71 100644
--- a/app/services/vat_history_service.rb
+++ b/app/services/vat_history_service.rb
@@ -11,7 +11,7 @@ class VatHistoryService
     end
   end
 
-  # return the VAT rate foe the given date
+  # return the VAT rate for the given date
   def vat_rate(date)
     @vat_rates = vat_history if @vat_rates.nil?
 
diff --git a/db/migrate/20190522115230_migrate_invoice_to_invoicing_profile.rb b/db/migrate/20190522115230_migrate_invoice_to_invoicing_profile.rb
index 3fb3ccd63..b99519b52 100644
--- a/db/migrate/20190522115230_migrate_invoice_to_invoicing_profile.rb
+++ b/db/migrate/20190522115230_migrate_invoice_to_invoicing_profile.rb
@@ -1,8 +1,6 @@
 # frozen_string_literal: true
 
 # migrate the invoices from being attached to a user to invoicing_profiles which are GDPR compliant
-# frozen_string_literal:true
-
 class MigrateInvoiceToInvoicingProfile < ActiveRecord::Migration[4.2]
   def up
     # first, check the footprints
diff --git a/db/migrate/20200511075933_fix_accounting_periods.rb b/db/migrate/20200511075933_fix_accounting_periods.rb
new file mode 100644
index 000000000..87f21a6fc
--- /dev/null
+++ b/db/migrate/20200511075933_fix_accounting_periods.rb
@@ -0,0 +1,72 @@
+# frozen_string_literal: true
+
+# regenerate the accounting periods affected by the current bug (period totals are wrong due to wrong VAT computation)
+class FixAccountingPeriods < ActiveRecord::Migration[5.2]
+  def change
+    # first, check the footprints
+    check_footprints
+
+    # if everything is ok, proceed with migration
+    # remove periods (backup their parameters in memory)
+    periods = backup_and_remove_periods
+    # recreate periods from memory dump
+    restore_periods(periods)
+  end
+
+  def check_footprints
+    if AccountingPeriod.count.positive?
+      last_period = AccountingPeriod.order(start_at: :desc).first
+      puts "Checking invoices footprints from #{last_period.end_at}. This may take a while..."
+      Invoice.where('created_at > ?', last_period.end_at).order(:id).each do |i|
+        raise "Invalid footprint for invoice #{i.id}" unless i.check_footprint
+      end
+    else
+      puts 'Checking all invoices footprints. This may take a while...'
+      Invoice.order(:id).all.each do |i|
+        raise "Invalid footprint for invoice #{i.id}" unless i.check_footprint
+      end
+    end
+  end
+
+  # will return an array of hash containing the removed periods data
+  def backup_and_remove_periods
+    return [] unless AccountingPeriod.where("created_at > '2019-08-01'").count.positive?
+
+    puts 'Removing erroneous accounting archives...'
+    # 1. remove protection for AccountingPeriods
+    execute("DROP RULE IF EXISTS accounting_periods_del_protect ON #{AccountingPeriod.arel_table.name};")
+    # 2. backup AccountingPeriods in memory
+    periods = []
+    AccountingPeriod.where("created_at > '2019-08-01'").each do |p|
+      periods.push(
+        start_at: p.start_at,
+        end_at: p.end_at,
+        closed_at: p.closed_at,
+        closed_by: p.closed_by
+      )
+    end
+    # 3. Delete periods from database
+    AccountingPeriod.where("created_at > '2019-08-01'").each do |ap|
+      execute("DELETE FROM accounting_periods WHERE ID=#{ap.id};")
+    end
+    periods
+  end
+
+  def restore_periods(periods)
+    return unless periods.size.positive?
+
+    # 1. recreate AccountingPeriods
+    puts 'Recreating accounting archives. This may take a while...'
+    periods.each do |p|
+      AccountingPeriod.create!(
+        start_at: p[:start_at],
+        end_at: p[:end_at],
+        closed_at: p[:closed_at],
+        closed_by: p[:closed_by]
+      )
+    end
+    # 2. reset protection for AccountingPeriods
+    execute("CREATE RULE accounting_periods_del_protect AS ON DELETE TO #{AccountingPeriod.arel_table.name} DO INSTEAD NOTHING;")
+  end
+
+end
diff --git a/db/schema.rb b/db/schema.rb
index cbd50fd4a..b86050026 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
 #
 # It's strongly recommended that you check this file into your version control system.
 
-ActiveRecord::Schema.define(version: 2020_04_15_141809) do
+ActiveRecord::Schema.define(version: 2020_05_11_075933) do
 
   # These are extensions that must be enabled in order to support this database
   enable_extension "pg_trgm"
@@ -18,8 +18,8 @@ ActiveRecord::Schema.define(version: 2020_04_15_141809) do
   enable_extension "unaccent"
 
   create_table "abuses", id: :serial, force: :cascade do |t|
-    t.string "signaled_type"
     t.integer "signaled_id"
+    t.string "signaled_type"
     t.string "first_name"
     t.string "last_name"
     t.string "email"
@@ -48,8 +48,8 @@ ActiveRecord::Schema.define(version: 2020_04_15_141809) do
     t.string "locality"
     t.string "country"
     t.string "postal_code"
-    t.string "placeable_type"
     t.integer "placeable_id"
+    t.string "placeable_type"
     t.datetime "created_at"
     t.datetime "updated_at"
   end
@@ -63,8 +63,8 @@ ActiveRecord::Schema.define(version: 2020_04_15_141809) do
   end
 
   create_table "assets", id: :serial, force: :cascade do |t|
-    t.string "viewable_type"
     t.integer "viewable_id"
+    t.string "viewable_type"
     t.string "attachment"
     t.string "type"
     t.datetime "created_at"
@@ -132,8 +132,8 @@ ActiveRecord::Schema.define(version: 2020_04_15_141809) do
   end
 
   create_table "credits", id: :serial, force: :cascade do |t|
-    t.string "creditable_type"
     t.integer "creditable_id"
+    t.string "creditable_type"
     t.integer "plan_id"
     t.integer "hours"
     t.datetime "created_at"
@@ -285,8 +285,8 @@ ActiveRecord::Schema.define(version: 2020_04_15_141809) do
   end
 
   create_table "invoices", id: :serial, force: :cascade do |t|
-    t.string "invoiced_type"
     t.integer "invoiced_id"
+    t.string "invoiced_type"
     t.string "stp_invoice_id"
     t.integer "total"
     t.datetime "created_at"
@@ -349,15 +349,15 @@ ActiveRecord::Schema.define(version: 2020_04_15_141809) do
 
   create_table "notifications", id: :serial, force: :cascade do |t|
     t.integer "receiver_id"
-    t.string "attached_object_type"
     t.integer "attached_object_id"
+    t.string "attached_object_type"
     t.integer "notification_type_id"
     t.boolean "is_read", default: false
     t.datetime "created_at"
     t.datetime "updated_at"
     t.string "receiver_type"
     t.boolean "is_send", default: false
-    t.jsonb "meta_data", default: "{}"
+    t.jsonb "meta_data", default: {}
     t.index ["notification_type_id"], name: "index_notifications_on_notification_type_id"
     t.index ["receiver_id"], name: "index_notifications_on_receiver_id"
   end
@@ -457,8 +457,8 @@ ActiveRecord::Schema.define(version: 2020_04_15_141809) do
   create_table "prices", id: :serial, force: :cascade do |t|
     t.integer "group_id"
     t.integer "plan_id"
-    t.string "priceable_type"
     t.integer "priceable_id"
+    t.string "priceable_type"
     t.integer "amount"
     t.datetime "created_at", null: false
     t.datetime "updated_at", null: false
@@ -565,8 +565,8 @@ ActiveRecord::Schema.define(version: 2020_04_15_141809) do
     t.text "message"
     t.datetime "created_at"
     t.datetime "updated_at"
-    t.string "reservable_type"
     t.integer "reservable_id"
+    t.string "reservable_type"
     t.integer "nb_reserve_places"
     t.integer "statistic_profile_id"
     t.index ["reservable_type", "reservable_id"], name: "index_reservations_on_reservable_type_and_reservable_id"
@@ -575,8 +575,8 @@ ActiveRecord::Schema.define(version: 2020_04_15_141809) do
 
   create_table "roles", id: :serial, force: :cascade do |t|
     t.string "name"
-    t.string "resource_type"
     t.integer "resource_id"
+    t.string "resource_type"
     t.datetime "created_at"
     t.datetime "updated_at"
     t.index ["name", "resource_type", "resource_id"], name: "index_roles_on_name_and_resource_type_and_resource_id"
@@ -867,8 +867,8 @@ ActiveRecord::Schema.define(version: 2020_04_15_141809) do
 
   create_table "wallet_transactions", id: :serial, force: :cascade do |t|
     t.integer "wallet_id"
-    t.string "transactable_type"
     t.integer "transactable_id"
+    t.string "transactable_type"
     t.string "transaction_type"
     t.integer "amount"
     t.datetime "created_at", null: false