1
0
mirror of https://github.com/LaCasemate/fab-manager.git synced 2025-02-19 13:54:25 +01:00

migrate database to object[] to store multiple boughts items

This commit is contained in:
Sylvain 2021-05-25 17:28:35 +02:00
parent c09fd8192d
commit 6b90e73195
14 changed files with 233 additions and 139 deletions

View File

@ -0,0 +1,8 @@
# frozen_string_literal: true
# Raised when an invalid invoice is encountered in database
class InvalidInvoiceError < StandardError
def initialize(msg = nil)
super(msg || 'Please run rails `fablab:fix_invoices`')
end
end

View File

@ -6,7 +6,6 @@ class Invoice < PaymentDocument
include NotifyWith::NotificationAttachedObject
require 'fileutils'
scope :only_invoice, -> { where(type: nil) }
belongs_to :invoiced, polymorphic: true
has_many :invoice_items, dependent: :destroy
accepts_nested_attributes_for :invoice_items
@ -15,10 +14,6 @@ class Invoice < PaymentDocument
belongs_to :wallet_transaction
belongs_to :coupon
belongs_to :subscription, foreign_type: 'Subscription', foreign_key: 'invoiced_id'
belongs_to :reservation, foreign_type: 'Reservation', foreign_key: 'invoiced_id'
belongs_to :offer_day, foreign_type: 'OfferDay', foreign_key: 'invoiced_id'
has_one :avoir, class_name: 'Invoice', foreign_key: :invoice_id, dependent: :destroy
has_one :payment_schedule_item
has_one :payment_gateway_object, as: :item

View File

@ -8,6 +8,12 @@ class InvoiceItem < Footprintable
has_one :invoice_item # associates invoice_items of an invoice to invoice_items of an Avoir
has_one :payment_gateway_object, as: :item
belongs_to :object, polymorphic: true
belongs_to :reservation, foreign_type: 'Reservation', foreign_key: 'object_id'
belongs_to :subscription, foreign_type: 'Subscription', foreign_key: 'object_id'
belongs_to :wallet_transaction, foreign_type: 'WalletTransaction', foreign_key: 'object_id'
belongs_to :offer_day, foreign_type: 'OfferDay', foreign_key: 'object_id'
after_create :chain_record
after_update :log_changes

View File

@ -4,6 +4,6 @@
class OfferDay < ApplicationRecord
include NotifyWith::NotificationAttachedObject
has_many :invoices, as: :invoiced, dependent: :destroy
has_many :invoice_items, as: :object, dependent: :destroy
belongs_to :subscription
end

View File

@ -5,19 +5,17 @@
class PaymentSchedule < PaymentDocument
require 'fileutils'
belongs_to :scheduled, polymorphic: true
belongs_to :wallet_transaction
belongs_to :coupon
belongs_to :invoicing_profile
belongs_to :statistic_profile
belongs_to :operator_profile, foreign_key: :operator_profile_id, class_name: 'InvoicingProfile'
belongs_to :subscription, foreign_type: 'Subscription', foreign_key: 'scheduled_id'
belongs_to :reservation, foreign_type: 'Reservation', foreign_key: 'scheduled_id'
has_many :payment_schedule_items
has_many :payment_gateway_objects, as: :item
has_one :wallet_transaction, as: :transactable
before_create :add_environment
after_create :update_reference, :chain_record
after_commit :generate_and_send_document, on: [:create], if: :persisted?

View File

@ -0,0 +1,13 @@
# frozen_string_literal: true
# Links an object bought and a payment schedule used to pay this object
class PaymentScheduleObject < Footprintable
belongs_to :object, polymorphic: true
belongs_to :reservation, foreign_type: 'Reservation', foreign_key: 'object_id'
belongs_to :subscription, foreign_type: 'Subscription', foreign_key: 'object_id'
belongs_to :wallet_transaction, foreign_type: 'WalletTransaction', foreign_key: 'object_id'
belongs_to :offer_day, foreign_type: 'OfferDay', foreign_key: 'object_id'
belongs_to :payment_schedule
after_create :chain_record
end

View File

@ -17,7 +17,7 @@ class Reservation < ApplicationRecord
has_many :tickets
accepts_nested_attributes_for :tickets, allow_destroy: false
has_one :invoice, -> { where(type: nil) }, as: :invoiced, dependent: :destroy
has_many :invoice_items, as: :object, dependent: :destroy
has_one :payment_schedule, as: :scheduled, dependent: :destroy
validates_presence_of :reservable_id, :reservable_type

View File

@ -9,7 +9,7 @@ class Subscription < ApplicationRecord
has_one :payment_schedule, as: :scheduled, dependent: :destroy
has_one :payment_gateway_object, as: :item
has_many :invoices, as: :invoiced, dependent: :destroy
has_many :invoice_items, as: :object, dependent: :destroy
has_many :offer_days, dependent: :destroy
validates_presence_of :plan_id

View File

@ -7,9 +7,11 @@ class WalletTransaction < ApplicationRecord
belongs_to :invoicing_profile
belongs_to :wallet
belongs_to :reservation
belongs_to :transactable, polymorphic: true
# what was paid with the wallet
has_one :invoice
has_one :payment_schedule
# how the wallet was credited
has_one :invoice_item, as: :object, dependent: :destroy
validates_inclusion_of :transaction_type, in: %w[credit debit]
validates :invoicing_profile, :wallet, presence: true

View File

@ -1,99 +0,0 @@
# frozen_string_literal: true
require 'integrity/archive_helper'
# This migration will ensure data integrity for invoices.
# A bug introduced with v4.7.0 has made invoices without invoiced_id for Reservations.
# This issue is concerning slots restricted to subscribers, when the restriction was manually overridden by an admin.
class FixInvoicesWithoutInvoicedId < ActiveRecord::Migration[5.2]
def up
return unless Invoice.where(invoiced_id: nil).count.positive?
# check the footprints and save the archives
Integrity::ArchiveHelper.check_footprints
periods = Integrity::ArchiveHelper.backup_and_remove_periods
# fix invoices data
Invoice.where(invoiced_id: nil).each do |invoice|
if invoice.invoiced_type != 'Reservation'
STDERR.puts "WARNING: Invoice #{invoice.id} is not about a reservation, ignoring..."
next
end
ii = invoice.invoice_items.where(subscription_id: nil).first
reservable = find_reservable(ii)
if reservable
if reservable.is_a? Event
STDERR.puts "WARNING: invoice #{invoice.id} may be linked to the Event #{reservable.id}. This is unsupported, ignoring..."
next
end
::Reservation.create!(
reservable_id: reservable.id,
reservable_type: reservable.class.name,
slots_attributes: slots_attributes(invoice, reservable),
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}, ignoring..."
end
end
# 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)
# re-create all archives from the memory dump
Integrity::ArchiveHelper.restore_periods(periods)
end
def down; 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
&.first
reservable || Event.where('title LIKE ?', "#{descr}%").first
end
def find_slots(invoice)
invoice.invoice_items.map do |ii|
start = DateTime.parse(ii.description)
end_time = DateTime.parse(/- (.+)$/.match(ii.description)[1])
[start, DateTime.new(start.year, start.month, start.day, end_time.hour, end_time.min, end_time.sec, end_time.zone)]
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
STDERR.puts "WARNING: Unable to find an availability for #{reservable.class.name} #{reservable.id}, at #{slot[0]}..."
end
availability
end
def slots_attributes(invoice, reservable)
find_slots(invoice).map do |slot|
{
start_at: slot[0],
end_at: slot[1],
availability_id: find_availability(reservable, slot).id,
offered: invoice.total.zero?
}
end
end
end

View File

@ -11,7 +11,10 @@ require 'integrity/archive_helper'
# extensible and will allow to invoice more types of objects the future.
class AddObjectToInvoiceItem < ActiveRecord::Migration[5.2]
def up
# first, check the footprints
# first check that there's no bogus invoices
raise InvalidInvoiceError if Invoice.where(invoiced_id: nil).where.not(invoiced_type: 'Error').count.positive?
# check the footprints
Integrity::ArchiveHelper.check_footprints
# if everything is ok, proceed with migration: remove and save periods in memory
@ -28,14 +31,27 @@ class AddObjectToInvoiceItem < ActiveRecord::Migration[5.2]
)
end
Invoice.where(invoiced_type: 'Reservation').each do |invoice|
invoice.invoice_items.first.update_attributes(
invoice.invoice_items.where(subscription_id: nil).first.update_attributes(
object_id: invoice.invoiced_id,
object_type: invoice.invoiced_type,
main: true
)
invoice.invoice_items.where(subscription_id: nil)[1..-1].each do |ii|
ii.update_attributes(
object_id: invoice.invoiced_id,
object_type: invoice.invoiced_type
)
end
subscription_item = invoice.invoice_items.where.not(subscription_id: nil).first
next unless subscription_item
subscription_item.update_attributes(
object_id: subscription_item.subscription_id,
object_type: 'Subscription'
)
end
remove_column :invoice_items, :subscription_id
remove_reference :invoices, :invoiced
remove_reference :invoices, :invoiced, polymorphic: true
# chain records
puts 'Chaining all record. This may take a while...'
@ -56,8 +72,19 @@ class AddObjectToInvoiceItem < ActiveRecord::Migration[5.2]
add_column :invoice_items, :subscription_id, :integer
add_reference :invoices, :invoiced, polymorphic: true
# migrate data
InvoiceItem.where(main: true).each do |ii|
ii.invoice.update_attributes(
invoiced_id: ii.object_id,
invoiced_type: ii.object_type
)
end
InvoiceItem.where(object_type: 'Subscription').each do |ii|
ii.update_attributes(
subscription_id: ii.object_id
)
end
remove_column :invoice_items, :main
remove_reference :invoice_items, :object
remove_reference :invoice_items, :object, polymorphic: true
# chain records
puts 'Chaining all record. This may take a while...'

View File

@ -0,0 +1,68 @@
# frozen_string_literal: true
# Following the scheme of the previous migration (20210521085710_add_object_to_invoice_item.rb)
# we'll save the bought objects associated to a payment schedule into this data table.
class CreatePaymentScheduleObjects < ActiveRecord::Migration[5.2]
def up
create_table :payment_schedule_objects do |t|
t.references :object, polymorphic: true
t.belongs_to :payment_schedule, foreign_key: true
t.boolean :main
t.string :footprint
t.timestamps
end
# migrate data
PaymentSchedule.all.each do |payment_schedule|
PaymentScheduleObject.create!(
payment_schedule: payment_schedule,
object_id: payment_schedule.scheduled_id,
object_type: payment_schedule.scheduled_type,
main: true
)
end
PaymentSchedule.where(scheduled_type: 'Reservation').each do |payment_schedule|
PaymentScheduleObject.create!(
payment_schedule: payment_schedule,
object_id: payment_schedule.payment_schedule_items.first.details['subscription_id'],
object_type: 'Subscription'
)
end
remove_column :payment_schedules, :scheduled_id
remove_column :payment_schedules, :scheduled_type
PaymentScheduleItem.update_all("details = details - 'subscription_id'")
# chain records
puts 'Chaining all record. This may take a while...'
PaymentScheduleItem.order(:id).all.each(&:chain_record)
PaymentSchedule.order(:id).all.each(&:chain_record)
end
def down
add_column :payment_schedules, :scheduled_id, :integer
add_column :payment_schedules, :scheduled_type, :string
# migrate data
PaymentScheduleObject.where(main: true).each do |pso|
pso.payment_schedule.update_attributes(
scheduled_id: pso.object_id,
scheduled_type: pso.object_type
)
end
PaymentScheduleObject.where(object_type: 'Subscription').each do |pso|
pso.payment_schedule.payment_schedule_items.each do |psi|
psi.details['subscription_id'] = pso.object_id
pdi.save!
end
end
drop_table :payment_schedule_objects
# chain records
puts 'Chaining all record. This may take a while...'
PaymentScheduleItem.order(:id).all.each(&:chain_record)
PaymentSchedule.order(:id).all.each(&:chain_record)
end
end

View File

@ -0,0 +1,12 @@
# frozen_string_literal: true
# Following the scheme of the previous migrations, we cannot store anymore a single object as "the bought item"
# because wa want to be able to buy multiple items at the same time.
# Previously WalletTransaction was saving the item bought using the wallet in transactable columns (polymorphic).
# This was limiting to one item only, was redundant with (Invoice|PaymentSchedule).wallet_transaction_id, and anyway
# this data was not used anywhere in the application so we remove it.
class RemoveTransactableFromWalletTransaction < ActiveRecord::Migration[5.2]
def change
remove_reference :wallet_transactions, :transactable, polymorphic: true
end
end

View File

@ -1014,9 +1014,11 @@ CREATE TABLE public.invoice_items (
created_at timestamp without time zone,
updated_at timestamp without time zone,
description text,
subscription_id integer,
invoice_item_id integer,
footprint character varying
footprint character varying,
object_type character varying,
object_id bigint,
main boolean
);
@ -1045,8 +1047,6 @@ ALTER SEQUENCE public.invoice_items_id_seq OWNED BY public.invoice_items.id;
CREATE TABLE public.invoices (
id integer NOT NULL,
invoiced_type character varying,
invoiced_id integer,
total integer,
created_at timestamp without time zone,
updated_at timestamp without time zone,
@ -1529,14 +1529,47 @@ CREATE SEQUENCE public.payment_schedule_items_id_seq
ALTER SEQUENCE public.payment_schedule_items_id_seq OWNED BY public.payment_schedule_items.id;
--
-- Name: payment_schedule_objects; Type: TABLE; Schema: public; Owner: -
--
CREATE TABLE public.payment_schedule_objects (
id bigint NOT NULL,
object_type character varying,
object_id bigint,
payment_schedule_id bigint,
main boolean,
footprint character varying,
created_at timestamp without time zone NOT NULL,
updated_at timestamp without time zone NOT NULL
);
--
-- Name: payment_schedule_objects_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--
CREATE SEQUENCE public.payment_schedule_objects_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
--
-- Name: payment_schedule_objects_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
--
ALTER SEQUENCE public.payment_schedule_objects_id_seq OWNED BY public.payment_schedule_objects.id;
--
-- Name: payment_schedules; Type: TABLE; Schema: public; Owner: -
--
CREATE TABLE public.payment_schedules (
id bigint NOT NULL,
scheduled_type character varying,
scheduled_id bigint,
total integer,
reference character varying,
payment_method character varying,
@ -2969,8 +3002,6 @@ CREATE TABLE public.users_roles (
CREATE TABLE public.wallet_transactions (
id integer NOT NULL,
wallet_id integer,
transactable_type character varying,
transactable_id integer,
transaction_type character varying,
amount integer,
created_at timestamp without time zone NOT NULL,
@ -3317,6 +3348,13 @@ ALTER TABLE ONLY public.payment_gateway_objects ALTER COLUMN id SET DEFAULT next
ALTER TABLE ONLY public.payment_schedule_items ALTER COLUMN id SET DEFAULT nextval('public.payment_schedule_items_id_seq'::regclass);
--
-- Name: payment_schedule_objects id; Type: DEFAULT; Schema: public; Owner: -
--
ALTER TABLE ONLY public.payment_schedule_objects ALTER COLUMN id SET DEFAULT nextval('public.payment_schedule_objects_id_seq'::regclass);
--
-- Name: payment_schedules id; Type: DEFAULT; Schema: public; Owner: -
--
@ -3954,6 +3992,14 @@ ALTER TABLE ONLY public.payment_schedule_items
ADD CONSTRAINT payment_schedule_items_pkey PRIMARY KEY (id);
--
-- Name: payment_schedule_objects payment_schedule_objects_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--
ALTER TABLE ONLY public.payment_schedule_objects
ADD CONSTRAINT payment_schedule_objects_pkey PRIMARY KEY (id);
--
-- Name: payment_schedules payment_schedules_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--
@ -4474,6 +4520,13 @@ CREATE INDEX index_i_calendar_events_on_i_calendar_id ON public.i_calendar_event
CREATE INDEX index_invoice_items_on_invoice_id ON public.invoice_items USING btree (invoice_id);
--
-- Name: index_invoice_items_on_object_type_and_object_id; Type: INDEX; Schema: public; Owner: -
--
CREATE INDEX index_invoice_items_on_object_type_and_object_id ON public.invoice_items USING btree (object_type, object_id);
--
-- Name: index_invoices_on_coupon_id; Type: INDEX; Schema: public; Owner: -
--
@ -4600,6 +4653,20 @@ CREATE INDEX index_payment_schedule_items_on_invoice_id ON public.payment_schedu
CREATE INDEX index_payment_schedule_items_on_payment_schedule_id ON public.payment_schedule_items USING btree (payment_schedule_id);
--
-- Name: index_payment_schedule_objects_on_object_type_and_object_id; Type: INDEX; Schema: public; Owner: -
--
CREATE INDEX index_payment_schedule_objects_on_object_type_and_object_id ON public.payment_schedule_objects USING btree (object_type, object_id);
--
-- Name: index_payment_schedule_objects_on_payment_schedule_id; Type: INDEX; Schema: public; Owner: -
--
CREATE INDEX index_payment_schedule_objects_on_payment_schedule_id ON public.payment_schedule_objects USING btree (payment_schedule_id);
--
-- Name: index_payment_schedules_on_coupon_id; Type: INDEX; Schema: public; Owner: -
--
@ -4621,13 +4688,6 @@ CREATE INDEX index_payment_schedules_on_invoicing_profile_id ON public.payment_s
CREATE INDEX index_payment_schedules_on_operator_profile_id ON public.payment_schedules USING btree (operator_profile_id);
--
-- Name: index_payment_schedules_on_scheduled_type_and_scheduled_id; Type: INDEX; Schema: public; Owner: -
--
CREATE INDEX index_payment_schedules_on_scheduled_type_and_scheduled_id ON public.payment_schedules USING btree (scheduled_type, scheduled_id);
--
-- Name: index_payment_schedules_on_statistic_profile_id; Type: INDEX; Schema: public; Owner: -
--
@ -5118,13 +5178,6 @@ CREATE INDEX index_users_roles_on_user_id_and_role_id ON public.users_roles USIN
CREATE INDEX index_wallet_transactions_on_invoicing_profile_id ON public.wallet_transactions USING btree (invoicing_profile_id);
--
-- Name: index_wallet_transactions_on_transactable; Type: INDEX; Schema: public; Owner: -
--
CREATE INDEX index_wallet_transactions_on_transactable ON public.wallet_transactions USING btree (transactable_type, transactable_id);
--
-- Name: index_wallet_transactions_on_wallet_id; Type: INDEX; Schema: public; Owner: -
--
@ -5383,6 +5436,14 @@ ALTER TABLE ONLY public.payment_schedules
ADD CONSTRAINT fk_rails_552bc65163 FOREIGN KEY (coupon_id) REFERENCES public.coupons(id);
--
-- Name: payment_schedule_objects fk_rails_56f6b6d2d2; Type: FK CONSTRAINT; Schema: public; Owner: -
--
ALTER TABLE ONLY public.payment_schedule_objects
ADD CONSTRAINT fk_rails_56f6b6d2d2 FOREIGN KEY (payment_schedule_id) REFERENCES public.payment_schedules(id);
--
-- Name: tickets fk_rails_65422fe751; Type: FK CONSTRAINT; Schema: public; Owner: -
--
@ -5943,6 +6004,9 @@ INSERT INTO "schema_migrations" (version) VALUES
('20201027101809'),
('20201112092002'),
('20210416073410'),
('20210416083610');
('20210416083610'),
('20210521085710'),
('20210525134018'),
('20210525150942');