2021-05-21 16:46:41 +02:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2021-05-24 11:34:35 +02:00
|
|
|
require 'integrity/archive_helper'
|
|
|
|
|
2021-05-24 11:19:59 +02:00
|
|
|
# This take will ensure data integrity for invoices.
|
|
|
|
# A bug introduced with v4.3.0 has made invoices without saving the associated Reservation (Invoice.invoiced_id is null).
|
|
|
|
# This issue is concerning slots restricted to subscribers, when the restriction is manually overridden by an admin.
|
|
|
|
namespace :fablab do
|
|
|
|
desc 'Remove the invoices w/o reservation or regenerate the reservation'
|
|
|
|
task fix_invoices: :environment do |_task, _args|
|
2021-05-24 16:34:27 +02:00
|
|
|
next unless Invoice.where(invoiced_id: nil).count.positive?
|
2021-05-21 16:46:41 +02:00
|
|
|
|
2021-05-24 11:19:59 +02:00
|
|
|
include ActionView::Helpers::NumberHelper
|
|
|
|
|
2021-05-21 16:46:41 +02:00
|
|
|
# check the footprints and save the archives
|
2021-05-24 11:34:35 +02:00
|
|
|
Integrity::ArchiveHelper.check_footprints
|
2021-05-24 11:19:59 +02:00
|
|
|
ActiveRecord::Base.transaction do
|
2021-05-24 11:34:35 +02:00
|
|
|
periods = Integrity::ArchiveHelper.backup_and_remove_periods
|
2021-05-24 11:19:59 +02:00
|
|
|
|
|
|
|
# fix invoices data
|
|
|
|
Invoice.where(invoiced_id: nil).each do |invoice|
|
|
|
|
ii = invoice.invoice_items.where(subscription_id: nil).first
|
|
|
|
puts '=============================================='
|
|
|
|
puts "Invoice #{invoice.id} (# #{invoice.reference})"
|
|
|
|
puts "Total: #{number_to_currency(invoice.total / 100.0)}"
|
|
|
|
puts "Subject: #{ii.description}."
|
|
|
|
puts "Customer: #{invoice.invoicing_profile.full_name} (#{invoice.invoicing_profile.email})"
|
|
|
|
puts "Operator: #{invoice.operator_profile&.user&.profile&.full_name} (#{invoice.operator_profile&.user&.email})"
|
|
|
|
puts "Date: #{invoice.created_at}"
|
|
|
|
|
2021-05-24 16:34:27 +02:00
|
|
|
print 'Delete [d], create the missing reservation [c] OR keep as error[e] ? > '
|
2021-05-24 11:19:59 +02:00
|
|
|
confirm = STDIN.gets.chomp
|
|
|
|
if confirm == 'd'
|
|
|
|
puts "Destroying #{invoice.id}..."
|
|
|
|
invoice.destroy
|
|
|
|
elsif confirm == 'c'
|
2021-05-24 16:34:27 +02:00
|
|
|
if invoice.invoiced_type != 'Reservation'
|
|
|
|
STDERR.puts "WARNING: Invoice #{invoice.id} is about #{invoice.invoiced_type}. Please handle manually."
|
|
|
|
STDERR.puts 'Ignoring...'
|
|
|
|
next
|
|
|
|
end
|
|
|
|
|
2021-05-24 11:19:59 +02:00
|
|
|
reservable = find_reservable(ii)
|
|
|
|
if reservable
|
|
|
|
if reservable.is_a? Event
|
|
|
|
STDERR.puts "WARNING: invoice #{invoice.id} is linked to Event #{reservable.id}. This is unsupported, please handle manually."
|
|
|
|
STDERR.puts 'Ignoring...'
|
|
|
|
next
|
|
|
|
end
|
|
|
|
reservation = ::Reservation.create!(
|
|
|
|
reservable_id: reservable.id,
|
|
|
|
reservable_type: reservable.class.name,
|
2022-07-13 16:28:43 +02:00
|
|
|
slots_reservations_attributes: slots_reservations_attributes(invoice, reservable),
|
2021-05-24 11:19:59 +02:00
|
|
|
statistic_profile_id: StatisticProfile.find_by(user: invoice.user).id
|
|
|
|
)
|
|
|
|
invoice.update_attributes(invoiced: reservation)
|
|
|
|
else
|
|
|
|
STDERR.puts "WARNING: Unable to guess the reservable for invoice #{invoice.id}, please handle manually."
|
|
|
|
STDERR.puts 'Ignoring...'
|
|
|
|
end
|
2021-05-24 16:34:27 +02:00
|
|
|
elsif confirm == 'e'
|
|
|
|
invoice.update_attributes(invoiced_type: 'Error')
|
2021-05-24 11:19:59 +02:00
|
|
|
else
|
|
|
|
puts "Operation #{confirm} unknown. Ignoring invoice #{invoice.id}..."
|
|
|
|
end
|
2021-05-21 16:46:41 +02:00
|
|
|
end
|
|
|
|
|
2021-05-24 11:19:59 +02:00
|
|
|
# chain records
|
|
|
|
puts 'Chaining all record. This may take a while...'
|
|
|
|
InvoiceItem.order(:id).all.each(&:chain_record)
|
|
|
|
Invoice.order(:id).all.each(&:chain_record)
|
2021-05-21 16:46:41 +02:00
|
|
|
|
2021-05-24 11:19:59 +02:00
|
|
|
# re-create all archives from the memory dump
|
2021-05-24 11:34:35 +02:00
|
|
|
Integrity::ArchiveHelper.restore_periods(periods)
|
2021-05-24 11:19:59 +02:00
|
|
|
end
|
2021-05-21 16:46:41 +02:00
|
|
|
end
|
|
|
|
|
|
|
|
private
|
|
|
|
|
|
|
|
def find_reservable(invoice_item)
|
|
|
|
descr = /^([a-zA-Z\u00C0-\u017F]+\s+)+/.match(invoice_item.description)[0].strip[/(.*)\s/, 1]
|
|
|
|
reservable = InvoiceItem.where('description LIKE ?', "#{descr}%")
|
|
|
|
.map(&:invoice)
|
|
|
|
.filter { |i| !i.invoiced_id.nil? }
|
|
|
|
.map(&:invoiced)
|
|
|
|
.map(&:reservable)
|
|
|
|
.first
|
|
|
|
reservable ||= [Machine, Training, Space].map { |c| c.where('name LIKE ?', "#{descr}%") }
|
|
|
|
.filter { |r| r.count.positive? }
|
|
|
|
.first
|
2021-05-24 11:19:59 +02:00
|
|
|
&.first
|
2021-05-21 16:46:41 +02:00
|
|
|
|
|
|
|
reservable || Event.where('title LIKE ?', "#{descr}%").first
|
|
|
|
end
|
|
|
|
|
|
|
|
def find_slots(invoice)
|
|
|
|
invoice.invoice_items.map do |ii|
|
2021-05-24 11:19:59 +02:00
|
|
|
description = ii.description
|
|
|
|
# DateTime.parse only works with english dates, so translate the month name
|
|
|
|
month_idx = I18n.t('date.month_names').find_index { |month| month && description.include?(month) }
|
2021-05-26 11:49:52 +02:00
|
|
|
unless month_idx.nil?
|
|
|
|
description.gsub!(/#{I18n.t('date.month_names')[month_idx]}/, I18n.t('date.month_names', locale: :en)[month_idx])
|
|
|
|
end
|
2021-05-24 11:19:59 +02:00
|
|
|
start = DateTime.parse(description)
|
|
|
|
end_time = DateTime.parse(/- (.+)$/.match(description)[1])
|
|
|
|
[start, DateTime.new(start.year, start.month, start.day, end_time.hour, end_time.min, end_time.sec, DateTime.current.zone)]
|
2021-05-21 16:46:41 +02:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def find_availability(reservable, slot)
|
|
|
|
return if reservable.is_a? Event
|
|
|
|
|
|
|
|
availability = reservable.availabilities.where('start_at <= ? AND end_at >= ?', slot[0], slot[1]).first
|
|
|
|
unless availability
|
2021-05-21 18:21:24 +02:00
|
|
|
STDERR.puts "WARNING: Unable to find an availability for #{reservable.class.name} #{reservable.id}, at #{slot[0]}, creating..."
|
|
|
|
availability = reservable.availabilities.create!(start_at: slot[0], end_at: slot[1])
|
2021-05-21 16:46:41 +02:00
|
|
|
end
|
|
|
|
availability
|
|
|
|
end
|
|
|
|
|
2022-07-13 16:28:43 +02:00
|
|
|
def slots_reservations_attributes(invoice, reservable)
|
|
|
|
find_slots(invoice).map do |slot_dates|
|
|
|
|
availability = find_availability(reservable, slot_dates)
|
|
|
|
slot = Slot.find_by(start_at: slot_dates[0], end_at: slot_dates[1], availability_id: availability&.id)
|
2021-05-21 16:46:41 +02:00
|
|
|
{
|
2022-07-13 16:28:43 +02:00
|
|
|
slot_id: slot&.id,
|
2021-05-21 16:46:41 +02:00
|
|
|
offered: invoice.total.zero?
|
|
|
|
}
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|