mirror of
https://github.com/LaCasemate/fab-manager.git
synced 2024-12-01 12:24:28 +01:00
refactor code related to archiving
This commit is contained in:
parent
2b091acb75
commit
343b2f7d23
@ -1,6 +1,6 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'checksum'
|
||||
require 'integrity/checksum'
|
||||
require 'version'
|
||||
require 'zip'
|
||||
|
||||
@ -86,6 +86,6 @@ class AccountingPeriod < ApplicationRecord
|
||||
columns = AccountingPeriod.columns.map(&:name)
|
||||
.delete_if { |c| %w[id footprint created_at updated_at].include? c }
|
||||
|
||||
Checksum.text("#{columns.map { |c| self[c] }.join}#{previous_period ? previous_period.footprint : ''}")
|
||||
Integrity::Checksum.text("#{columns.map { |c| self[c] }.join}#{previous_period ? previous_period.footprint : ''}")
|
||||
end
|
||||
end
|
||||
|
@ -1,7 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'checksum'
|
||||
|
||||
# Setting values, kept history of modifications
|
||||
class HistoryValue < Footprintable
|
||||
belongs_to :setting
|
||||
|
@ -1,7 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'checksum'
|
||||
|
||||
# Invoice correspond to a single purchase made by an user. This purchase may
|
||||
# include reservation(s) and/or a subscription
|
||||
class Invoice < PaymentDocument
|
||||
|
@ -1,7 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'checksum'
|
||||
|
||||
# A single line inside an invoice. Can be a subscription or a reservation
|
||||
class InvoiceItem < Footprintable
|
||||
belongs_to :invoice
|
||||
|
@ -1,6 +1,6 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'checksum'
|
||||
require 'integrity/checksum'
|
||||
|
||||
# Provides helper methods to compute footprints
|
||||
class FootprintService
|
||||
@ -10,7 +10,7 @@ class FootprintService
|
||||
# @param item an instance of the provided class
|
||||
# @param sort_on the items in database by the provided criterion, to find the previous one
|
||||
def compute_footprint(klass, item, sort_on = 'id')
|
||||
Checksum.text(FootprintService.footprint_data(klass, item, sort_on))
|
||||
Integrity::Checksum.text(FootprintService.footprint_data(klass, item, sort_on))
|
||||
end
|
||||
|
||||
# Return the original data string used to compute the footprint
|
||||
@ -42,11 +42,13 @@ class FootprintService
|
||||
columns = FootprintService.footprint_columns(klass)
|
||||
current = FootprintService.footprint_data(klass, item)
|
||||
saved = FootprintDebug.find_by(footprint: item.footprint, klass: klass.name)
|
||||
others = FootprintDebug.where('klass = ? AND data LIKE ?', klass, "#{item.id}%")
|
||||
puts "Debug footprint for #{klass} [ id: #{item.id} ]"
|
||||
puts '-----------------------------------------'
|
||||
puts "columns: [ #{columns.join(', ')} ]"
|
||||
puts "current: #{current}"
|
||||
puts " saved: #{saved&.data}"
|
||||
puts "others possible matches: #{others.map(&:id)}"
|
||||
puts '-----------------------------------------'
|
||||
end
|
||||
|
||||
|
@ -1,14 +1,16 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'integrity/archive_helper'
|
||||
|
||||
# migrate the invoices from being attached to a user to invoicing_profiles which are GDPR compliant
|
||||
class MigrateInvoiceToInvoicingProfile < ActiveRecord::Migration[4.2]
|
||||
def up
|
||||
# first, check the footprints
|
||||
check_footprints
|
||||
Integrity::ArchiveHelper.check_footprints
|
||||
|
||||
# if everything is ok, proceed with migration
|
||||
# remove and save periods in memory
|
||||
periods = backup_and_remove_periods
|
||||
periods = Integrity::ArchiveHelper.backup_and_remove_periods
|
||||
# migrate invoices
|
||||
puts 'Migrating invoices. This may take a while...'
|
||||
Invoice.order(:id).all.each do |i|
|
||||
@ -24,14 +26,14 @@ class MigrateInvoiceToInvoicingProfile < ActiveRecord::Migration[4.2]
|
||||
InvoiceItem.order(:id).all.each(&:chain_record)
|
||||
Invoice.order(:id).all.each(&:chain_record)
|
||||
# write memory dump into database
|
||||
restore_periods(periods)
|
||||
Integrity::ArchiveHelper.restore_periods(periods)
|
||||
end
|
||||
|
||||
def down
|
||||
# here we don't check footprints to save processing time and because this is pointless when reverting the migrations
|
||||
|
||||
# remove and save periods in memory
|
||||
periods = backup_and_remove_periods
|
||||
periods = Integrity::ArchiveHelper.backup_and_remove_periods
|
||||
# reset invoices
|
||||
Invoice.order(:created_at).all.each do |i|
|
||||
i.update_column('user_id', i.invoicing_profile.user_id)
|
||||
@ -44,62 +46,6 @@ class MigrateInvoiceToInvoicingProfile < ActiveRecord::Migration[4.2]
|
||||
InvoiceItem.order(:id).all.each(&:chain_record)
|
||||
Invoice.order(:id).all.each(&:chain_record)
|
||||
# write memory dump into database
|
||||
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.count.positive?
|
||||
|
||||
puts 'Removing 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.all.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.all.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;")
|
||||
Integrity::ArchiveHelper.restore_periods(periods)
|
||||
end
|
||||
end
|
||||
|
@ -1,72 +1,17 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'integrity/archive_helper'
|
||||
|
||||
# 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
|
||||
Integrity::ArchiveHelper.check_footprints
|
||||
|
||||
# if everything is ok, proceed with migration
|
||||
# remove periods (backup their parameters in memory)
|
||||
periods = backup_and_remove_periods
|
||||
periods = Integrity::ArchiveHelper.backup_and_remove_periods(range_start: '2019-08-01', range_end: '2020-05-12')
|
||||
# recreate periods from memory dump
|
||||
restore_periods(periods)
|
||||
Integrity::ArchiveHelper.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' AND created_at < '2020-05-12'").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
|
||||
|
82
lib/integrity/archive_helper.rb
Normal file
82
lib/integrity/archive_helper.rb
Normal file
@ -0,0 +1,82 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Accounting integrity verifications
|
||||
module Integrity; end
|
||||
|
||||
# Provides various helpers methods for interacting with accounting archives
|
||||
class Integrity::ArchiveHelper
|
||||
class << self
|
||||
|
||||
## Checks the validity of all closed periods and raise an error otherwise
|
||||
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(range_start: nil, range_end: nil)
|
||||
range_periods = get_periods(range_start: range_start, range_end: range_end)
|
||||
return [] unless range_periods.count.positive?
|
||||
|
||||
puts 'Removing 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 = []
|
||||
range_periods.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
|
||||
range_periods.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
|
||||
|
||||
private
|
||||
|
||||
def get_periods(range_start: nil, range_end: nil)
|
||||
if range_start && range_end
|
||||
AccountingPeriod.where('created_at > ? AND created_at < ?', range_start, range_end)
|
||||
elsif range_start
|
||||
AccountingPeriod.where('created_at > ?', range_start)
|
||||
elsif range_end
|
||||
AccountingPeriod.where('created_at < ?', range_end)
|
||||
else
|
||||
AccountingPeriod.all
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -1,7 +1,10 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# provide checksums for archiving control
|
||||
class Checksum
|
||||
# Accounting integrity verifications
|
||||
module Integrity; end
|
||||
|
||||
# Provides checksums for archiving control
|
||||
class Integrity::Checksum
|
||||
class << self
|
||||
def code
|
||||
dir = Dir.pwd
|
@ -50,8 +50,8 @@ namespace :fablab do
|
||||
|
||||
desc 'generate current code checksum'
|
||||
task checksum: :environment do
|
||||
require 'checksum'
|
||||
puts Checksum.code
|
||||
require 'integrity/checksum'
|
||||
puts Integrity::Checksum.code
|
||||
end
|
||||
|
||||
desc 'delete users with accounts marked with is_active=false'
|
||||
|
@ -10,7 +10,6 @@ class Subscriptions::RenewAsUserTest < ActionDispatch::IntegrationTest
|
||||
|
||||
test 'user successfully renew a subscription after it has ended' do
|
||||
plan = Plan.find_by(group_id: @user.group.id, type: 'Plan', base_name: 'Mensuel')
|
||||
stripe_subscription = nil
|
||||
|
||||
VCR.use_cassette('subscriptions_user_renew_success', erb: true) do
|
||||
post '/api/stripe/confirm_payment',
|
||||
|
@ -159,9 +159,9 @@ class ActiveSupport::TestCase
|
||||
end
|
||||
|
||||
# Check archive matches
|
||||
require 'checksum'
|
||||
require 'integrity/checksum'
|
||||
sumfile = File.read("#{dest}/checksum.sha256").split("\t")
|
||||
assert_equal sumfile[0], Checksum.file("#{dest}/#{sumfile[1]}"), 'archive checksum does not match'
|
||||
assert_equal sumfile[0], Integrity::Checksum.file("#{dest}/#{sumfile[1]}"), 'archive checksum does not match'
|
||||
|
||||
archive = File.read("#{dest}/#{sumfile[1]}")
|
||||
archive_json = JSON.parse(archive)
|
||||
|
Loading…
Reference in New Issue
Block a user