From e5cef6e6bf68e14ea86ca4807e1773a86e7e50e2 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Tue, 21 Jul 2020 17:25:19 +0200 Subject: [PATCH 01/28] [bug] unable to export reservations --- CHANGELOG.md | 2 ++ app/views/exports/users_reservations.xlsx.axlsx | 8 ++++---- config/locales/en.yml | 1 + config/locales/fr.yml | 1 + 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a2d5be27..15226c781 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # Changelog Fab-manager +- Fix a bug: unable to export reservations + ## v4.5.3 2020 July 21 - Documentation of the easy upgrade procedure diff --git a/app/views/exports/users_reservations.xlsx.axlsx b/app/views/exports/users_reservations.xlsx.axlsx index 2c0f8cadd..d2127da4f 100644 --- a/app/views/exports/users_reservations.xlsx.axlsx +++ b/app/views/exports/users_reservations.xlsx.axlsx @@ -15,14 +15,14 @@ wb.add_worksheet(name: t('export_reservations.reservations')) do |sheet| # data rows @reservations.each do |resrv| data = [ - resrv.user.id, - resrv.user.profile.full_name, - resrv.user.email, + resrv.user&.id, + resrv.user&.profile&.full_name || t('export_reservations.deleted_user'), + resrv.user&.email, resrv.created_at.to_date, resrv.reservable_type, (resrv.reservable.nil? ? '' : resrv.reservable.name), (resrv.reservable_type == 'Event') ? resrv.total_booked_seats: resrv.slots.count, - (resrv.invoice.paid_with_stripe?) ? t('export_reservations.local_payment') : t('export_reservations.online_payment') + (resrv.invoice&.paid_with_stripe?) ? t('export_reservations.online_payment') : t('export_reservations.local_payment') ] styles = [nil, nil, nil, date, nil, nil, nil, nil] types = [:integer, :string, :string, :date, :string, :string, :integer, :string] diff --git a/config/locales/en.yml b/config/locales/en.yml index e77f9301f..7073772f7 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -188,6 +188,7 @@ en: payment_method: "Payment method" local_payment: "Payment at the reception" online_payment: "Online payment" + deleted_user: "Deleted user" #subscriptions list export to EXCEL format export_subscriptions: subscriptions: "Subscriptions" diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 1e6128a0f..e1fc17e14 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -188,6 +188,7 @@ fr: payment_method: "Méthode de paiement" local_payment: "Paiement à l'accueil" online_payment: "Paiement en ligne" + deleted_user: "Utilisateur supprimé" #subscriptions list export to EXCEL format export_subscriptions: subscriptions: "Abonnements" From 62e6764d8ebb3be492182eee47612b193a74a927 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Tue, 21 Jul 2020 17:53:58 +0200 Subject: [PATCH 02/28] [bug] unable to receive emails in development --- CHANGELOG.md | 1 + config/secrets.yml | 2 ++ 2 files changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 15226c781..fa9746f8e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # Changelog Fab-manager - Fix a bug: unable to export reservations +- Fix a bug: unable to receive mails in development ## v4.5.3 2020 July 21 diff --git a/config/secrets.yml b/config/secrets.yml index bdf7ff39f..7c544267f 100644 --- a/config/secrets.yml +++ b/config/secrets.yml @@ -14,6 +14,8 @@ development: secret_key_base: <%= ENV["SECRET_KEY_BASE"] %> default_host: <%= ENV["DEFAULT_HOST"] %> default_protocol: <%= ENV["DEFAULT_PROTOCOL"] %> + smtp_address: <%= ENV["SMTP_ADDRESS"] %> + smtp_port: <%= ENV["SMTP_PORT"] %> time_zone: <%= ENV["TIME_ZONE"] %> week_starting_day: <%= ENV["WEEK_STARTING_DAY"] %> d3_date_format: <%= ENV["D3_DATE_FORMAT"].dump %> # .dump is needed as the value may start by a '%', see https://github.com/tenderlove/psych/issues/75 From d9be51b01bd71e94d0e10cc0637a4d3586e3b836 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Tue, 21 Jul 2020 18:04:20 +0200 Subject: [PATCH 03/28] [admin form] show if phone is required --- CHANGELOG.md | 1 + app/assets/javascripts/controllers/admin/members.js.erb | 6 +++++- app/assets/javascripts/router.js.erb | 3 +++ app/assets/templates/shared/_admin_form.html | 5 +++-- 4 files changed, 12 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fa9746f8e..97a7c1667 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # Changelog Fab-manager +- Display an asterisk on the phone input field, in the admin creation form, if the phone is configured as required - Fix a bug: unable to export reservations - Fix a bug: unable to receive mails in development diff --git a/app/assets/javascripts/controllers/admin/members.js.erb b/app/assets/javascripts/controllers/admin/members.js.erb index 30c3a96b0..b4dbd8333 100644 --- a/app/assets/javascripts/controllers/admin/members.js.erb +++ b/app/assets/javascripts/controllers/admin/members.js.erb @@ -1070,7 +1070,8 @@ Application.Controllers.controller('ImportMembersResultController', ['$scope', ' /** * Controller used in the admin's creation page (admin view) */ -Application.Controllers.controller('NewAdminController', ['$state', '$scope', 'Admin', 'growl', '_t', function ($state, $scope, Admin, growl, _t) { +Application.Controllers.controller('NewAdminController', ['$state', '$scope', 'Admin', 'growl', '_t', 'phoneRequiredPromise', + function ($state, $scope, Admin, growl, _t, phoneRequiredPromise) { // default admin profile let getGender; $scope.admin = { @@ -1090,6 +1091,9 @@ Application.Controllers.controller('NewAdminController', ['$state', '$scope', 'A } }; + // is the phone number required in _admin_form? + $scope.phoneRequired = (phoneRequiredPromise.setting.value === 'true'); + /** * Shows the birth day datepicker * @param $event {Object} jQuery event object diff --git a/app/assets/javascripts/router.js.erb b/app/assets/javascripts/router.js.erb index 563920f9f..52bb3a9bc 100644 --- a/app/assets/javascripts/router.js.erb +++ b/app/assets/javascripts/router.js.erb @@ -947,6 +947,9 @@ angular.module('application.router', ['ui.router']) templateUrl: '<%= asset_path "admin/admins/new.html" %>', controller: 'NewAdminController' } + }, + resolve: { + phoneRequiredPromise: ['Setting', function (Setting) { return Setting.get({ name: 'phone_required' }).$promise; }] } }) .state('app.admin.managers_new', { diff --git a/app/assets/templates/shared/_admin_form.html b/app/assets/templates/shared/_admin_form.html index 53fb425cf..458a79a50 100644 --- a/app/assets/templates/shared/_admin_form.html +++ b/app/assets/templates/shared/_admin_form.html @@ -110,12 +110,13 @@
- + + placeholder="{{ 'app.admin.admins_new.phone_number' | translate }}" + ng-required="phoneRequired">
From 1fd70fb1dab3f6b7a9301eec021781d59da51b92 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Tue, 21 Jul 2020 18:19:23 +0200 Subject: [PATCH 04/28] New translations en.yml (Zulu) --- config/locales/zu.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/config/locales/zu.yml b/config/locales/zu.yml index d05778549..f2e0eec9c 100644 --- a/config/locales/zu.yml +++ b/config/locales/zu.yml @@ -188,6 +188,7 @@ zu: payment_method: "crwdns3501:0crwdne3501:0" local_payment: "crwdns3503:0crwdne3503:0" online_payment: "crwdns3505:0crwdne3505:0" + deleted_user: "crwdns20886:0crwdne20886:0" #subscriptions list export to EXCEL format export_subscriptions: subscriptions: "crwdns3507:0crwdne3507:0" From 346cbb570f45e3023f27f54af042a8e9b06e7391 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Tue, 21 Jul 2020 18:19:27 +0200 Subject: [PATCH 05/28] New translations en.yml (Portuguese) --- config/locales/pt.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/config/locales/pt.yml b/config/locales/pt.yml index 52728f472..7c7bfbefd 100755 --- a/config/locales/pt.yml +++ b/config/locales/pt.yml @@ -188,6 +188,7 @@ pt: payment_method: "Método de pagamento" local_payment: "Pagamento na recepção" online_payment: "Pagamento online" + deleted_user: "Deleted user" #subscriptions list export to EXCEL format export_subscriptions: subscriptions: "Assinaturas" From 075f4a5c0be649089dee4cd57ad38f497576e67b Mon Sep 17 00:00:00 2001 From: Sylvain Date: Tue, 21 Jul 2020 18:19:31 +0200 Subject: [PATCH 06/28] New translations en.yml (French) --- config/locales/fr.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 1e6128a0f..c0a3a4ae7 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -188,6 +188,7 @@ fr: payment_method: "Méthode de paiement" local_payment: "Paiement à l'accueil" online_payment: "Paiement en ligne" + deleted_user: "Deleted user" #subscriptions list export to EXCEL format export_subscriptions: subscriptions: "Abonnements" From 9cd55b1a07660337f82fa4847269514dc44421c5 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Tue, 21 Jul 2020 18:19:33 +0200 Subject: [PATCH 07/28] New translations en.yml (Spanish) --- config/locales/es.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/config/locales/es.yml b/config/locales/es.yml index ef2cf90da..128049130 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -188,6 +188,7 @@ es: payment_method: "Método de pago" local_payment: "Pago en recepción" online_payment: "Pago online" + deleted_user: "Deleted user" #subscriptions list export to EXCEL format export_subscriptions: subscriptions: "Suscripciones" From f0858ffa116398412fbb5c4daaddd38ce07f520f Mon Sep 17 00:00:00 2001 From: Sylvain Date: Tue, 21 Jul 2020 18:21:06 +0200 Subject: [PATCH 08/28] New translations en.yml (Portuguese) --- config/locales/pt.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/locales/pt.yml b/config/locales/pt.yml index 7c7bfbefd..63941d13f 100755 --- a/config/locales/pt.yml +++ b/config/locales/pt.yml @@ -188,7 +188,7 @@ pt: payment_method: "Método de pagamento" local_payment: "Pagamento na recepção" online_payment: "Pagamento online" - deleted_user: "Deleted user" + deleted_user: "Usuário deletado" #subscriptions list export to EXCEL format export_subscriptions: subscriptions: "Assinaturas" From c0de645f67cb4e0bd161ba7376c3154b6ba9b3c8 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Tue, 21 Jul 2020 18:21:09 +0200 Subject: [PATCH 09/28] New translations en.yml (Spanish) --- config/locales/es.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/locales/es.yml b/config/locales/es.yml index 128049130..d409ca5be 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -188,7 +188,7 @@ es: payment_method: "Método de pago" local_payment: "Pago en recepción" online_payment: "Pago online" - deleted_user: "Deleted user" + deleted_user: "Usuario eliminado" #subscriptions list export to EXCEL format export_subscriptions: subscriptions: "Suscripciones" From 3a9c221cece8feee001b7c2ecec6e0b2e07ac544 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Tue, 21 Jul 2020 18:21:13 +0200 Subject: [PATCH 10/28] New translations en.yml (French) --- config/locales/fr.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/locales/fr.yml b/config/locales/fr.yml index c0a3a4ae7..e1fc17e14 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -188,7 +188,7 @@ fr: payment_method: "Méthode de paiement" local_payment: "Paiement à l'accueil" online_payment: "Paiement en ligne" - deleted_user: "Deleted user" + deleted_user: "Utilisateur supprimé" #subscriptions list export to EXCEL format export_subscriptions: subscriptions: "Abonnements" From 7bf06ff23e255925ea34352e673e64c78889b45e Mon Sep 17 00:00:00 2001 From: Sylvain Date: Tue, 21 Jul 2020 19:25:21 +0200 Subject: [PATCH 11/28] debug footprints --- CHANGELOG.md | 2 + app/models/footprint_debug.rb | 2 + app/models/history_value.rb | 9 + app/models/invoice.rb | 9 + app/models/invoice_item.rb | 9 + app/services/footprint_service.rb | 40 +- .../20200721162939_create_footprint_debugs.rb | 15 + db/structure.sql | 51 +- lib/tasks/db.rake | 29 + lib/tasks/fablab/maintenance.rake | 13 + test/fixtures/footprint_debugs.yml | 645 ++++++++++++++++++ 11 files changed, 817 insertions(+), 7 deletions(-) create mode 100644 app/models/footprint_debug.rb create mode 100644 db/migrate/20200721162939_create_footprint_debugs.rb create mode 100644 lib/tasks/db.rake create mode 100644 test/fixtures/footprint_debugs.yml diff --git a/CHANGELOG.md b/CHANGELOG.md index 97a7c1667..21a39cbdc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,10 @@ # Changelog Fab-manager - Display an asterisk on the phone input field, in the admin creation form, if the phone is configured as required +- Keep the history of footprints data for verification purposes - Fix a bug: unable to export reservations - Fix a bug: unable to receive mails in development +- [TODO DEPLOY] `rails fablab:maintenance:save_footprint_data` ## v4.5.3 2020 July 21 diff --git a/app/models/footprint_debug.rb b/app/models/footprint_debug.rb new file mode 100644 index 000000000..9d7c4329a --- /dev/null +++ b/app/models/footprint_debug.rb @@ -0,0 +1,2 @@ +class FootprintDebug < ApplicationRecord +end diff --git a/app/models/history_value.rb b/app/models/history_value.rb index c4844433c..b62cf843f 100644 --- a/app/models/history_value.rb +++ b/app/models/history_value.rb @@ -12,12 +12,21 @@ class HistoryValue < ApplicationRecord def chain_record self.footprint = compute_footprint save! + FootprintDebug.create!( + footprint: footprint, + data: FootprintService.footprint_data(HistoryValue, self, 'created_at'), + klass: HistoryValue.name + ) end def check_footprint footprint == compute_footprint end + def debug_footprint + FootprintService.debug_footprint(HistoryValue, self) + end + def user invoicing_profile.user end diff --git a/app/models/invoice.rb b/app/models/invoice.rb index 49374aa77..efe5cb030 100644 --- a/app/models/invoice.rb +++ b/app/models/invoice.rb @@ -174,12 +174,21 @@ class Invoice < ApplicationRecord def chain_record self.footprint = compute_footprint save! + FootprintDebug.create!( + footprint: footprint, + data: FootprintService.footprint_data(Invoice, self), + klass: Invoice.name + ) end def check_footprint invoice_items.map(&:check_footprint).all? && footprint == compute_footprint end + def debug_footprint + FootprintService.debug_footprint(Invoice, self) + end + def set_wallet_transaction(amount, transaction_id) raise InvalidFootprintError unless check_footprint diff --git a/app/models/invoice_item.rb b/app/models/invoice_item.rb index 54b4f45a6..28590005e 100644 --- a/app/models/invoice_item.rb +++ b/app/models/invoice_item.rb @@ -15,12 +15,21 @@ class InvoiceItem < ApplicationRecord def chain_record self.footprint = compute_footprint save! + FootprintDebug.create!( + footprint: footprint, + data: FootprintService.footprint_data(InvoiceItem, self), + klass: InvoiceItem.name + ) end def check_footprint footprint == compute_footprint end + def debug_footprint + FootprintService.debug_footprint(InvoiceItem, self) + end + def amount_after_coupon # deduct coupon discount coupon_service = CouponService.new diff --git a/app/services/footprint_service.rb b/app/services/footprint_service.rb index c5c584b1b..2ada8a376 100644 --- a/app/services/footprint_service.rb +++ b/app/services/footprint_service.rb @@ -3,19 +3,47 @@ # Provides helper methods to compute footprints class FootprintService # Compute the footprint - # @param class_name Invoice|InvoiceItem|HistoryValue + # @param klass Invoice|InvoiceItem|HistoryValue # @param item an instance of the provided class # @param sort the items in database by the provided criterion, to find the previous one def self.compute_footprint(klass, item, sort_on = 'id') + Checksum.text(FootprintService.footprint_data(klass, item, sort_on)) + end + + # Return the original data string used to compute the footprint + # @param klass Invoice|InvoiceItem|HistoryValue + # @param item an instance of the provided class + # @param sort the items in database by the provided criterion, to find the previous one + def self.footprint_data(klass, item, sort_on = 'id') raise TypeError unless item.is_a? klass previous = klass.where("#{sort_on} < ?", item[sort_on]) - .order("#{sort_on} DESC") - .limit(1) + .order("#{sort_on} DESC") + .limit(1) - columns = klass.columns.map(&:name) - .delete_if { |c| %w[footprint updated_at].include? c } + columns = FootprintService.footprint_columns(klass) - Checksum.text("#{columns.map { |c| item[c] }.join}#{previous.first ? previous.first.footprint : ''}") + "#{columns.map { |c| item[c] }.join}#{previous.first ? previous.first.footprint : ''}" + end + + # Return an ordered array of the columns used in the footprint computation + # @param klass Invoice|InvoiceItem|HistoryValue + def self.footprint_columns(klass) + klass.columns.map(&:name).delete_if { |c| %w[footprint updated_at].include? c } + end + + # Logs a debugging message to help finding why a footprint is invalid + # @param klass Invoice|InvoiceItem|HistoryValue + # @param item an instance of the provided class + def self.debug_footprint(klass, item) + columns = FootprintService.footprint_columns(klass) + current = FootprintService.footprint_data(klass, item) + saved = FootprintDebug.find_by(footprint: item.footprint, klass: klass) + puts "Debug footprint for Invoice [ id: #{item.id} ]" + puts '-----------------------------------------' + puts "columns: [ #{columns.join(', ')} ]" + puts "current footprint: #{current}" + puts " saved footprint: #{saved&.data}" + puts '-----------------------------------------' end end diff --git a/db/migrate/20200721162939_create_footprint_debugs.rb b/db/migrate/20200721162939_create_footprint_debugs.rb new file mode 100644 index 000000000..7cfdf5617 --- /dev/null +++ b/db/migrate/20200721162939_create_footprint_debugs.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +# This table saves the original data used to create footprints, this allows +# to debug invalid footprints +class CreateFootprintDebugs < ActiveRecord::Migration[5.2] + def change + create_table :footprint_debugs do |t| + t.string :footprint + t.string :data + t.string :klass + + t.timestamps + end + end +end diff --git a/db/structure.sql b/db/structure.sql index eb2a7d80a..6ca27d22d 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -763,6 +763,39 @@ CREATE SEQUENCE public.exports_id_seq ALTER SEQUENCE public.exports_id_seq OWNED BY public.exports.id; +-- +-- Name: footprint_debugs; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.footprint_debugs ( + id bigint NOT NULL, + footprint character varying, + data character varying, + klass character varying, + created_at timestamp without time zone NOT NULL, + updated_at timestamp without time zone NOT NULL +); + + +-- +-- Name: footprint_debugs_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.footprint_debugs_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: footprint_debugs_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public.footprint_debugs_id_seq OWNED BY public.footprint_debugs.id; + + -- -- Name: friendly_id_slugs; Type: TABLE; Schema: public; Owner: - -- @@ -3020,6 +3053,13 @@ ALTER TABLE ONLY public.events_event_themes ALTER COLUMN id SET DEFAULT nextval( ALTER TABLE ONLY public.exports ALTER COLUMN id SET DEFAULT nextval('public.exports_id_seq'::regclass); +-- +-- Name: footprint_debugs id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.footprint_debugs ALTER COLUMN id SET DEFAULT nextval('public.footprint_debugs_id_seq'::regclass); + + -- -- Name: friendly_id_slugs id; Type: DEFAULT; Schema: public; Owner: - -- @@ -3607,6 +3647,14 @@ ALTER TABLE ONLY public.exports ADD CONSTRAINT exports_pkey PRIMARY KEY (id); +-- +-- Name: footprint_debugs footprint_debugs_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.footprint_debugs + ADD CONSTRAINT footprint_debugs_pkey PRIMARY KEY (id); + + -- -- Name: friendly_id_slugs friendly_id_slugs_pkey; Type: CONSTRAINT; Schema: public; Owner: - -- @@ -5598,6 +5646,7 @@ INSERT INTO "schema_migrations" (version) VALUES ('20200622135401'), ('20200623134900'), ('20200623141305'), -('20200629123011'); +('20200629123011'), +('20200721162939'); diff --git a/lib/tasks/db.rake b/lib/tasks/db.rake new file mode 100644 index 000000000..f2484654f --- /dev/null +++ b/lib/tasks/db.rake @@ -0,0 +1,29 @@ +# frozen_string_literal: false + +namespace :db do + desc 'Convert development DB to Rails test fixtures' + task to_fixtures: :environment do + TABLES_TO_SKIP = %w[ar_internal_metadata delayed_jobs schema_info schema_migrations].freeze + + begin + ActiveRecord::Base.establish_connection + ActiveRecord::Base.connection.tables.each do |table_name| + next if TABLES_TO_SKIP.include?(table_name) + + conter = '000' + file_path = "#{Rails.root}/test/fixtures/#{table_name}.yml" + File.open(file_path, 'w') do |file| + rows = ActiveRecord::Base.connection.select_all("SELECT * FROM #{table_name}") + data = rows.each_with_object({}) do |record, hash| + suffix = record['id'].blank? ? conter.succ! : record['id'] + hash["#{table_name.singularize}_#{suffix}"] = record + end + puts "Writing table '#{table_name}' to '#{file_path}'" + file.write(data.to_yaml) + end + end + ensure + ActiveRecord::Base.connection&.close + end + end +end diff --git a/lib/tasks/fablab/maintenance.rake b/lib/tasks/fablab/maintenance.rake index af2d7eb72..8192de46e 100644 --- a/lib/tasks/fablab/maintenance.rake +++ b/lib/tasks/fablab/maintenance.rake @@ -91,5 +91,18 @@ namespace :fablab do Sidekiq::Queue.new('default').clear Sidekiq::DeadSet.new.clear end + + desc 'save the footprint original data' + task save_footprint_data: :environment do + [Invoice, InvoiceItem, HistoryValue].each do |klass| + klass.all.each do |item| + FootprintDebug.create!( + footprint: item.footprint, + data: FootprintService.footprint_data(klass, item, klass == 'HistoryValue' ? 'created_at' : 'id'), + klass: klass + ) + end + end + end end end diff --git a/test/fixtures/footprint_debugs.yml b/test/fixtures/footprint_debugs.yml new file mode 100644 index 000000000..305b2379a --- /dev/null +++ b/test/fixtures/footprint_debugs.yml @@ -0,0 +1,645 @@ +--- +footprint_debug_7: + id: 7 + footprint: 8291cfc4cc4cf57eb63cb079ac952cd0ad8ca7b3e7f663f04d4c18a3c5c4ca68 + data: 1Subscription1in_17wpf92sOmf47Nz9itj6vmJw100002012-03-12 12:03:31 +01001604001/VLtest333 + klass: Invoice + created_at: '2020-07-21 17:15:14.138138' + updated_at: '2020-07-21 17:15:14.138138' +footprint_debug_8: + id: 8 + footprint: 1d8d62a39bd6e230043e60ffd0429c14414f5703a60b9cba71a81f6a9dcaf894 + data: 2Subscription220002012-03-12 14:40:22 +01001604002test4148291cfc4cc4cf57eb63cb079ac952cd0ad8ca7b3e7f663f04d4c18a3c5c4ca68 + klass: Invoice + created_at: '2020-07-21 17:15:14.144617' + updated_at: '2020-07-21 17:15:14.144617' +footprint_debug_9: + id: 9 + footprint: 8fb2c1105d24ac19096d5673b02ddea11c0c8578d818fad320c85c6005dda69b + data: 3Subscription330002015-06-10 13:20:01 +02001203001test7171d8d62a39bd6e230043e60ffd0429c14414f5703a60b9cba71a81f6a9dcaf894 + klass: Invoice + created_at: '2020-07-21 17:15:14.149373' + updated_at: '2020-07-21 17:15:14.149373' +footprint_debug_10: + id: 10 + footprint: 4ed7cf862affb0b6e0be53cfd97da3373b51c555ef2f06c7fe2b354940f10b53 + data: 4Reservation102016-04-05 10:35:52 +02001203002test7178fb2c1105d24ac19096d5673b02ddea11c0c8578d818fad320c85c6005dda69b + klass: Invoice + created_at: '2020-07-21 17:15:14.154327' + updated_at: '2020-07-21 17:15:14.154327' +footprint_debug_11: + id: 11 + footprint: 9aead7f5eb9411778d072279a058be0fe2da76eb12f6a22100cbec1f2b3f6e9d + data: 5Reservation215002016-04-05 10:36:46 +02001506031test3134ed7cf862affb0b6e0be53cfd97da3373b51c555ef2f06c7fe2b354940f10b53 + klass: Invoice + created_at: '2020-07-21 17:15:14.159216' + updated_at: '2020-07-21 17:15:14.159216' +footprint_debug_12: + id: 12 + footprint: 931e8b6f5579f2e7eda625bdf5962da89892957abb25480603ae985e6e108059 + data: 11sub_8DGB4ErIc2asOv100002012-03-12 12:03:31 +0100Sleede - standard, association + - month1 + klass: InvoiceItem + created_at: '2020-07-21 17:15:14.185651' + updated_at: '2020-07-21 17:15:14.185651' +footprint_debug_13: + id: 13 + footprint: abbf95b8d506ed6207343edbf8ee63893cd02508de6d25347999d7dbc755c6fb + data: 2220002012-03-12 14:40:22 +0100Mensuel tarif réduit - étudiant, - de 25 ans, + enseignant, demandeur d'emploi - month2931e8b6f5579f2e7eda625bdf5962da89892957abb25480603ae985e6e108059 + klass: InvoiceItem + created_at: '2020-07-21 17:15:14.192656' + updated_at: '2020-07-21 17:15:14.192656' +footprint_debug_14: + id: 14 + footprint: caf3b7e54e0191d8f262e01acc4e66f29f9d27abe52e595fb3b71724a32cfe90 + data: 3330002015-06-10 13:20:01 +0200Mensuel - standard, association - month3abbf95b8d506ed6207343edbf8ee63893cd02508de6d25347999d7dbc755c6fb + klass: InvoiceItem + created_at: '2020-07-21 17:15:14.197393' + updated_at: '2020-07-21 17:15:14.197393' +footprint_debug_15: + id: 15 + footprint: 71d095aa4d49ebac39f7efdc5c9de2354ea8efeec7c35a6708143fb66dd66b0b + data: 4402016-04-05 10:35:52 +0200Formation Laser / Vinyle April 11, 2012 08:00 + - 12:00 PMcaf3b7e54e0191d8f262e01acc4e66f29f9d27abe52e595fb3b71724a32cfe90 + klass: InvoiceItem + created_at: '2020-07-21 17:15:14.202635' + updated_at: '2020-07-21 17:15:14.202635' +footprint_debug_16: + id: 16 + footprint: d4d856ee04f27b5e35f43056b46f139df73ffdfaad1716636f569164c52c5e5b + data: 5515002016-04-05 10:36:46 +0200Imprimante 3D June 15, 2015 12:00 - 01:00 PM71d095aa4d49ebac39f7efdc5c9de2354ea8efeec7c35a6708143fb66dd66b0b + klass: InvoiceItem + created_at: '2020-07-21 17:15:14.207404' + updated_at: '2020-07-21 17:15:14.207404' +footprint_debug_17: + id: 17 + footprint: 75245583332fa0add0a16e28985d7d59ed0e799e76f1b16be30f5576044fba7f + data: '11

Fab-manager est + outil de gestion des atelier de fabrication numérique, permettant de réserver + des machines de découpe, des imprimantes 3D, etc. tout en gérant simplement les + aspect financier, comptable et statistiques de votre espace.

Fab-manager est un projet libre: ouvert à tous, il offre la + possibilité de contribuer soi-même au code, de télécharger le logiciel, de l''étudier + et de le redistribuer. Vous n''êtes pas technicien ? Vous pouvez quand même participer + à traduire Fab-manager dans votre + langue.

Fab-manager favorise le partage de connaissances grâce au réseau + OpenLab: les projets que vous documentez sont partagés avec l''ensemble du réseau + des Fab-managers.

2018-12-17 12:23:01 +01001' + klass: HistoryValue + created_at: '2020-07-21 17:15:14.227681' + updated_at: '2020-07-21 17:15:14.227681' +footprint_debug_18: + id: 18 + footprint: a9a343e802ff6cd39abcd56bc6079e3ff82a6d0b566ead706eb80de4f0d03f1b + data: 22Imaginer, Fabriquer,
Partager au Fab Lab
de La Casemate2018-12-17 + 12:23:01 +0100175245583332fa0add0a16e28985d7d59ed0e799e76f1b16be30f5576044fba7f + klass: HistoryValue + created_at: '2020-07-21 17:15:14.231746' + updated_at: '2020-07-21 17:15:14.231746' +footprint_debug_19: + id: 19 + footprint: 99092a004db5bc28acad559aaec377ed2dea977d64a9cf14cf0ad8114a5d52d7 + data: 33
Contact commercial :
contact@fab-manager.com
Support + technique :
Forum
Feedback
GitHub


+

Visitez le site de Fab-manager

2018-12-17 + 12:23:01 +01001a9a343e802ff6cd39abcd56bc6079e3ff82a6d0b566ead706eb80de4f0d03f1b + klass: HistoryValue + created_at: '2020-07-21 17:15:14.236192' + updated_at: '2020-07-21 17:15:14.236192' +footprint_debug_20: + id: 20 + footprint: c9c1ae97e6f6cf1363f5d942419534c8f729752fa6c0d389b441caad9a7dbbc3 + data: 44fab_manager2018-12-17 12:23:01 +0100199092a004db5bc28acad559aaec377ed2dea977d64a9cf14cf0ad8114a5d52d7 + klass: HistoryValue + created_at: '2020-07-21 17:15:14.241427' + updated_at: '2020-07-21 17:15:14.241427' +footprint_debug_21: + id: 21 + footprint: 91e2d05f9ade92056bbfb95e33944219f8100e6b2714ef1c26aa72bf24250db2 + data: 55Tout achat d'heure machine est définitif. Aucune annulation ne pourra être + effectuée, néanmoins au plus tard 24h avant le créneau fixé, vous pouvez en modifier + la date et l'horaire à votre convenance et en fonction du calendrier proposé. + Passé ce délais, aucun changement ne pourra être effectué.2018-12-17 12:23:01 + +01001c9c1ae97e6f6cf1363f5d942419534c8f729752fa6c0d389b441caad9a7dbbc3 + klass: HistoryValue + created_at: '2020-07-21 17:15:14.252785' + updated_at: '2020-07-21 17:15:14.252785' +footprint_debug_22: + id: 22 + footprint: 34af06c6a915d23c1c208de9c23f123f8de079dd8b64cad58f3e2c31a6955d0b + data: 66Toute réservation de formation est définitive. Aucune annulation ne pourra + être effectuée, néanmoins au plus tard 24h avant le créneau fixé, vous pouvez + en modifier la date et l'horaire à votre convenance et en fonction du calendrier + proposé. Passé ce délais, aucun changement ne pourra être effectué.2018-12-17 + 12:23:01 +0100191e2d05f9ade92056bbfb95e33944219f8100e6b2714ef1c26aa72bf24250db2 + klass: HistoryValue + created_at: '2020-07-21 17:15:14.258307' + updated_at: '2020-07-21 17:15:14.258307' +footprint_debug_23: + id: 23 + footprint: 69d6681ae22d81339f62b5fce96f43f01459e319de318763202d744c826657c3 + data: '77

Règle sur la date de début des abonnements

  • Si vous êtes un nouvel utilisateur + - i.e aucune formation d''enregistrée sur le site - votre abonnement débutera + à la date de réservation de votre première formation.
  • Si vous avez déjà une formation + ou plus de validée, votre abonnement débutera à la date de votre achat d''abonnement.
  • +

Merci de bien prendre ses informations en compte, et merci de votre compréhension. + L''équipe du Fab Lab.

2018-12-17 12:23:01 +0100134af06c6a915d23c1c208de9c23f123f8de079dd8b64cad58f3e2c31a6955d0b' + klass: HistoryValue + created_at: '2020-07-21 17:15:14.263926' + updated_at: '2020-07-21 17:15:14.263926' +footprint_debug_24: + id: 24 + footprint: eddfaaafe4dde25a3d3e0c2edbcfb72c44c39ad5022557241b32264db9518dca + data: 99iVBORw0KGgoAAAANSUhEUgAAAG0AAABZCAYAAAA0E6rtAAAACXBIWXMAAAsTAAALEwEAmpwYAAA57WlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4KPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42LWMxMzggNzkuMTU5ODI0LCAyMDE2LzA5LzE0LTAxOjA5OjAxICAgICAgICAiPgogICA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPgogICAgICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgogICAgICAgICAgICB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iCiAgICAgICAgICAgIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIKICAgICAgICAgICAgeG1sbnM6cGhvdG9zaG9wPSJodHRwOi8vbnMuYWRvYmUuY29tL3Bob3Rvc2hvcC8xLjAvIgogICAgICAgICAgICB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIKICAgICAgICAgICAgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIKICAgICAgICAgICAgeG1sbnM6dGlmZj0iaHR0cDovL25zLmFkb2JlLmNvbS90aWZmLzEuMC8iCiAgICAgICAgICAgIHhtbG5zOmV4aWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20vZXhpZi8xLjAvIj4KICAgICAgICAgPHhtcDpDcmVhdG9yVG9vbD5BZG9iZSBQaG90b3Nob3AgQ0MgMjAxNyAoV2luZG93cyk8L3htcDpDcmVhdG9yVG9vbD4KICAgICAgICAgPHhtcDpDcmVhdGVEYXRlPjIwMTctMDEtMDNUMTE6MTg6MTgrMDE6MDA8L3htcDpDcmVhdGVEYXRlPgogICAgICAgICA8eG1wOk1vZGlmeURhdGU+MjAxNy0wNi0wNlQxNTo1NjoxMiswMjowMDwveG1wOk1vZGlmeURhdGU+CiAgICAgICAgIDx4bXA6TWV0YWRhdGFEYXRlPjIwMTctMDYtMDZUMTU6NTY6MTIrMDI6MDA8L3htcDpNZXRhZGF0YURhdGU+CiAgICAgICAgIDxkYzpmb3JtYXQ+aW1hZ2UvcG5nPC9kYzpmb3JtYXQ+CiAgICAgICAgIDxwaG90b3Nob3A6Q29sb3JNb2RlPjM8L3Bob3Rvc2hvcDpDb2xvck1vZGU+CiAgICAgICAgIDx4bXBNTTpJbnN0YW5jZUlEPnhtcC5paWQ6MmYwMTE5MTMtODI5NS0zOTQ0LWJmZjYtMTY5ZTNhZTQ5OThlPC94bXBNTTpJbnN0YW5jZUlEPgogICAgICAgICA8eG1wTU06RG9jdW1lbnRJRD5hZG9iZTpkb2NpZDpwaG90b3Nob3A6ZGU3ZGE1MmYtNGFiZi0xMWU3LTljODAtYWJjY2ZlM2JkNzdmPC94bXBNTTpEb2N1bWVudElEPgogICAgICAgICA8eG1wTU06T3JpZ2luYWxEb2N1bWVudElEPnhtcC5kaWQ6YTE5NTAzOTAtOGQwOS0zMzQ3LWFkNGQtMzkyNDQ2YjRiNWJiPC94bXBNTTpPcmlnaW5hbERvY3VtZW50SUQ+CiAgICAgICAgIDx4bXBNTTpIaXN0b3J5PgogICAgICAgICAgICA8cmRmOlNlcT4KICAgICAgICAgICAgICAgPHJkZjpsaSByZGY6cGFyc2VUeXBlPSJSZXNvdXJjZSI+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDphY3Rpb24+Y3JlYXRlZDwvc3RFdnQ6YWN0aW9uPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6aW5zdGFuY2VJRD54bXAuaWlkOmExOTUwMzkwLThkMDktMzM0Ny1hZDRkLTM5MjQ0NmI0YjViYjwvc3RFdnQ6aW5zdGFuY2VJRD4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OndoZW4+MjAxNy0wMS0wM1QxMToxODoxOCswMTowMDwvc3RFdnQ6d2hlbj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OnNvZnR3YXJlQWdlbnQ+QWRvYmUgUGhvdG9zaG9wIENDIDIwMTcgKFdpbmRvd3MpPC9zdEV2dDpzb2Z0d2FyZUFnZW50PgogICAgICAgICAgICAgICA8L3JkZjpsaT4KICAgICAgICAgICAgICAgPHJkZjpsaSByZGY6cGFyc2VUeXBlPSJSZXNvdXJjZSI+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDphY3Rpb24+c2F2ZWQ8L3N0RXZ0OmFjdGlvbj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0Omluc3RhbmNlSUQ+eG1wLmlpZDoyZjAxMTkxMy04Mjk1LTM5NDQtYmZmNi0xNjllM2FlNDk5OGU8L3N0RXZ0Omluc3RhbmNlSUQ+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDp3aGVuPjIwMTctMDYtMDZUMTU6NTY6MTIrMDI6MDA8L3N0RXZ0OndoZW4+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDpzb2Z0d2FyZUFnZW50PkFkb2JlIFBob3Rvc2hvcCBDQyAyMDE3IChXaW5kb3dzKTwvc3RFdnQ6c29mdHdhcmVBZ2VudD4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmNoYW5nZWQ+Lzwvc3RFdnQ6Y2hhbmdlZD4KICAgICAgICAgICAgICAgPC9yZGY6bGk+CiAgICAgICAgICAgIDwvcmRmOlNlcT4KICAgICAgICAgPC94bXBNTTpIaXN0b3J5PgogICAgICAgICA8dGlmZjpPcmllbnRhdGlvbj4xPC90aWZmOk9yaWVudGF0aW9uPgogICAgICAgICA8dGlmZjpYUmVzb2x1dGlvbj43MjAwMDAvMTAwMDA8L3RpZmY6WFJlc29sdXRpb24+CiAgICAgICAgIDx0aWZmOllSZXNvbHV0aW9uPjcyMDAwMC8xMDAwMDwvdGlmZjpZUmVzb2x1dGlvbj4KICAgICAgICAgPHRpZmY6UmVzb2x1dGlvblVuaXQ+MjwvdGlmZjpSZXNvbHV0aW9uVW5pdD4KICAgICAgICAgPGV4aWY6Q29sb3JTcGFjZT42NTUzNTwvZXhpZjpDb2xvclNwYWNlPgogICAgICAgICA8ZXhpZjpQaXhlbFhEaW1lbnNpb24+MTA5PC9leGlmOlBpeGVsWERpbWVuc2lvbj4KICAgICAgICAgPGV4aWY6UGl4ZWxZRGltZW5zaW9uPjg5PC9leGlmOlBpeGVsWURpbWVuc2lvbj4KICAgICAgPC9yZGY6RGVzY3JpcHRpb24+CiAgIDwvcmRmOlJERj4KPC94OnhtcG1ldGE+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgCjw/eHBhY2tldCBlbmQ9InciPz7jSvdMAAAAIGNIUk0AAHolAACAgwAA+f8AAIDpAAB1MAAA6mAAADqYAAAXb5JfxUYAACL8SURBVHja7J15nB5Ftfe/VdXLsy8zk5lM9hASsrDKImgiuyCL7ILgq4KKu9flivuCiKjoFeWqCCoKQpTLIsiOrBqI5Bo2IQsJ2ZPJZPZn7ae7q94/+kkyQzIhExKSwVufT08mz3RX91Onzjm/86tTp4Vh+9oKYCFQA/YlThaHB+jlBmxuIcdyesmQQVAixELjYOPRSo25KDoJMdQ4mjTLKNFInBUENOGzEoc4vphEYnwVe4KNPdmgJoMeKxAjDSYnEAnABgSgo0cxZQNdINYJ9CqfcJmAxQaWpulZW0GZEI9RJHgcRYDmEKCIxQbKOLhoYF9COrDoJsQGRpNhPj20U+Xs+g0Ha712jKXpRhwdsrObhcAIQ61apeZ7xJXiQa+IxRvQNo5yWP/dABpDI8nRAn/mQcTfHmJN82GqTTjGECLw61fp+tn9ezObfhcIQAIKGwlIA2JlmRELfbz5LvajFeSTEJR0/ezh3qw3QmA1DApDDIFGjsnSOFMgjs/iHA/B2Co1DB4QIgDhOsjGHCqbRyQzyKYUoiEGjgVKQqjBCzBdFXRHCV3uRff1YDp60V5FAOMlanwM6wRD+kt98MxEggcM4pEqxblgSgEG+/+EtvUWYEigaMbZL0fiPQXEOQ5iH6gR0IfARubSqGlNWJP2wZk+HWvGOKzRLagRo5DZEchUGlSsrlEbm4awgi4VCHs7CNvbCFevI1i4Ev/FBfgvLyRc1AHdRWkIDk5iHSywLwlIPyHRs9OEd4Nsq9Qnyv8JDRAYAiRdJGaOJf4xkCeFeHlDDYRCjmoh/tYZuMcdQezQI7D2HovKtQDudt5BgkoiM0lkZiT22H3hYOA0AI+wux1/6XJq85/Ge+gf+P94kWDdOssEwTEK65gmsotq6D/a+L8PKS4zmH93oQkE4jBJ8uIa6mxFNaspI5wksbcfSuykY3CPOgJ3/0MRTnoX3N9F5ceiDhlL7JBZmA+WqS2aT23OfKoPPob3xP8SdHbto1DfypA6uwP1q4DwJkGpSw/Q5je50Mymn3arwP2YwP64xB9h6IV0lsQJx5M4+1RiJ5yIyo1+Q7+kcBK4+83E3W8myQs/iDdvDuU7/oJ359/xly6fYVA/s4m9O0bsexA+atD/HkKTCAycKkhfKggPgh6MZRM76ShSn7yQ+DEnIqzUbv/C0s0Qn/ku4jPfhf+J5yjdfAvl39+Jv/SV42ycQxXJHxjCnwSYavhmFppANKTJfUUgPwrVNBRxDtqX9Fc/TuKkM5CJhj3yy9uTDiD3jf1JnnsWpRtnU7zu1my4fv33KqQOzGB/thtvXfhGoLUhNvXt7TyxF+iox1rN2CgUVTzGkZoxntj1BnGBps9V2RiZL15E/r9/TOzQYxB2fA/3EALV2ErsmHfiHn8IplKg8vyiGSn0kXncuY3U2rfl5Txl0e0mUGbnAxmJAAFhEBDqEFtKloY1xI4yIhYumvCoEWR/1Y4/Bbpx9p1K/spvEj/xrGEbuBpdpfw/f6D30p/AgsWLIPFegXxGI9HKEE6OU7MCTBAgAoHZIKkJK3IObxAjskNQSeMjiJ8myNzVSW2KoJvE2Scw4sFbhrXAAISMkTz3w7Q8fhfOOafuU6Z0bwU9xcOmImwqCYdSyqGYcigmHTxpIcwbGy4MSWhhHR9Kkh80iFsExbShRObLn6LpltnYrfvwZmlqxCSabrmdkX/4r5FWxvzF0DFNoVGhxvLrR6CR5o2P7+RQBGYBY8mdHyN7vaHqGAHZy/+D/BVXIEWSN2NLXfAZmu/5w5TYhL2vC4OOFnwNYvdyKEMS2jhyZ7SQusGniHENuau/QvarlwMOb+YWm/luRj5229uzp59wDZWiFRaKaM+L7M5uEKD6Gv259C2PiJASeOSOtkjdEFJMQUj+qq+Q+dSX+HdpMtdM6pQTp1LodorPPfOwPXok0tiYDVWEkOwKAnNQ9PivbTAcCkGKJAHuZIV7hyCYoSmT+95nyX7lO/CmWOgYIgjrai/odUvfW3nuX/es/NS3CMsVZDy2i4LoQdbTzDaEFkawI6lwrxaEMzS9ZD73IbJfuYx/1yYbmpOmpprXfvVj+N2dWA2NoHcxGDED6WxLDs5yILHQxL4i4YSQLhJnHkfuh1fsqifDX72U2spX0NUyQu6BxK0UiESy1nPzbftUVjx/pJMbmzIm2NUWxxdKrreMtcRgSgYQSwbRMos4FvmTQ8QfoCdnHziREbfPxp64705/qtqaJXRefTWV+/9B2N0LYW23I7Stz2SBCUKjC9WSULKG2uX+QRhDKISpSOQC41X+685y1/1i8Vbva7BIjlTk74Dq4SJpaJz9MxKnnr/zp9GGlaz9wMUU7nsUiYtw3T3aVwoAx0I4kjd6CU4JWerwq+dZFTKvYjsMNgKX+MUQHG4okfrURbtEYAB9f76N4n1zUHYamYkzYCRej+wMr68fM4jENqaomNfoV2ylv9cp5MDo5EjLvcSyBtzHINAo4ocJ5Ec03TgHTiPzuc/vstkTrFqFEDYi4cBGdkEAAYR9JRiyzzAI6SKzMbAE1DS6t4LB385+DEI50QSS/QZaCkw5RJfLbE5R2s7nQYDlIJIOwtpxDRVA1egJVhp/0wchggCJjbrIEIwRlkvqSx/Hatlr1+m8bcOrQIephsikQ8Pn3os9ZgymUt1un4NjUZk3n/K9T2LKIDMxcp84HWevvTA1f/PEGOR64SjKT/2T4v3zEI5A1H2rKdawxjWQOe88VKYR43mDDq0xGlOrEvb14Le3E7zchr9gFUFvB9LNIlOxbT/Ha3gUayzlTf/rxaGN/CwXeXZIH/FTjyHx7tPfAGv9KpNYDRCNSRo++0mcsTOG3Fv3LddS/MsTaK+GlWim4Qufwd3rwO2+3rn/dor3zIMgjDLAtEFXa6jRjTR+8Quo1FBW3jVhqQdvwbMU7ryT7qtuRXcWkI2pHdY46UNd16IRU6iLNbVGmUqTvvgDqETT7nH3xmBK3g7CUVMfkACZTSAT+aEp/9ixqKwLFT3QP4UGM+RMBIlKNpA45BhaLvspY/7831gjmwg7SzuMkKVTZw5HAz0k9k/C8YYSsVPeRuydJw/PCNiYekCqUePzqHzj0JiI1mbU6Aw60FtahNfJ6qeOPYPmn38LmXTR5doOgST5QVKcS5KzSAHue0K8FmGniJ95GkLGGbbNENEDrQ1Id2g0k0ynsEa1RIBjFyy9ZM98D+kzj0FXylHi7VCF9i08vo/HZcTHS9RpITXcd+xP/Jhjhq28hGWB0Qgs7JZWhprlIe089qgJGMIdoKi253yX5CnHI6RAe0NPH5LLkaxE0Ik+XKL3FShi7zoW1Th2eKgTQb8jmrUmrGG0Bhys1h35HhJr1KhI04YotMI9f2Ltpy+m+MS92zwvtv8UrLEtmFow5Kez3o6giyRFnONCaqhxrbhHHjYsNKo8/3G6r7kWQT3HXwiEpfBeXooUNiQdrFE7lmNpj21EYDCBQQxBUUvz/sb6/76O8kOPM/7hydijJ2+9/9bx2A3NBCvWA0NzQ5ZNFRvVDMmjwMc+eCrufocMC6H5y1+m+7pbEFiR4DYGs3YMEUpUc6Lum7amowYqRVAWwtly0KwxLRBzMaEZElZQiTQ2ktqiDryXFg4qNJHOI7PpTdZhSEJbDxRxDncIJxlsYkcfinCzw8N3SRclsoikQiirP0mH7q4iGzKohtzWLw41tVULUblmrObxWw7MqBasxhxBW9+QfOJG3CKVA54/uAGWDiLnbERMQ4rZZBoLgTvTUBMym8I55G3DCHEIEDIyjf0PEbESVtMIVHqQvQKhj7fkecLezkHM11hUfgSEwet7vm3GojsYpy1B4MB0Q4ic0oi115hhBhWJOMJXH2hUtgGZ2HoqutEa75VFhIXurZugxjFYo0dgCIb+PPV/RWzwUMNoD9PjsSOBmowTHxPCFIHG2XsKVkvL8AnFwiph2EvY3UfY0UfY0YvuKNWZDIPM5hCxxNYvDgJqS1YQ9vYMRmZh792MQA/JdulKkRANeY01tnXQ84INqwg6NkSmd4hRhaVx9pKYyQaNPX06kBo2QnMmTiH/sXMQSFAKYSmCnh6qDz1LWOzBGjcCYW09tU8XCtReakMfVRgcQU6sx3hDCLDdyVPIHHcImXPPw52y/6DnVebNx1vyCiLmDvl7Wy5MNmiIuVgzxg0ryxg/6EhG//LIAZ95K55n9bMfwKxdgzWmedBrw+4NhGs60cXBVxDsMeMR0sEMIVbLn/8pcud/EoEaHPWuep7OS68GD2R+6Ns7LIE1CXxUYx61DXUeNi0UmFqIVAmsEYMTxUHXOsKuTvQ2SGmroRUh7aFBuyjxbWu6TfX5x+m+4Qb6fv8QYUcB1Zip85lD9GkCMRZCZDaP1Txq+AvNGEwYoNJZrOZt+ZR1BB3d6J6+wYU2ajQiaUN1Z/CPBl0pEXR1oSv1BdkdzOKSQItBIxJpZLaJN0ULfGQ6g9U4cnCF7OpGBx5h2+BCs8eNxhqXwXh6J6StKBJvPYWxv72TiU/fQeygqQTdxR0ipCWYHBhkPolIpIa/wCwbbJBjsqjGEYMLrVCK6pl0dhNt4NpaEJtBjWseEuwPC52EPavraRKD8I7T30brr3+APTJP2FvdIU1LAIjGGMKODSv5aK9E0LGaoGstQddadLWXYP0ajPaxxzUiM4MzO2FvoY4iu9F+aZCQK4EzYWI9v2T7Wu+t17PypLNo/+F3MbXBkWn8LUeSOvNoTFhlqE7NAhEDEDEbtoF49sRWnvswG778faRtRZBfSoK+AuGaEvZZYxFi8I0h4SvrolpN61ahC33Ihq2DFnfCZAwewmwfNK+tXkXfU09TeWYRqeOOJXHwrMEFd/hB9F5zJxgNQg1FaHVJKclwy80PO9dTmvt0P8I4ylMTMoHdOmob36eGsAQ2MUTOxlRqgw/Q1Imb+t2e4ZFuHIVCeDF0W8c2z7VbxiBsB2PMkDIPLKJ8sF2fj74rGCwVQ4k8Ir2ZMDYVD7BQmYZtUEiShku+SPYjFyESCVR+cJTpTBiNSibQpe30ayb6IdRrs8DCjYOREIaR6pjtFpqpgcFU/br81DCT3MDDhBqZsLBGDI6EhbSI7bP/9g1QcxPWyBaqS1cN/cFeaz+C3LEtUhIogcB0VjB+ZZjHaECgkQ1x1OiRO6VLlW/CbmzGUNuhmHGXAGSQXQKB7ipiSn2I3PCG/cYYZFMG2ZjfKf1JuxHV0Dj0FEVtQOvtsxQYjOdvaR6FQNhqiyUeaTBtIDH1am7DX9XAampCpXZWzS2J2qfOFG1HDo4QUTXLUJchvj1pBCIKE6s+eB54teio1jAVDxNuucfbAr0SFLq3G92+DsbvP9xlhtU4EpV+rdX3GuDVPUScbW0/t/ceHS0uB6+tOUGhBx/IHjWN2MFveQ34G2CqNexpeUZ89zOoZH5TurlwHfz2drp+dBP+whWI1OaQw5LoZQZB2N1LsKYN99BhKjABhAaBwR41EmFtS9NC2r/2DQqz78Ea10rTd79Mcuaxg57tjh2NwsaEr61quXe/l/gBh5A69l1YuW37VV0ro6mhGjOkTz0faQ806brSS99vH6ZWW4LoV1JRhuilIKHiESxYNbyNY2jAsVATm7d9Xq1Aae4c+pa9SOHxv1F7+ZVtx1Nj9kKS2a7Ug/ihR5E7+yNY+dfOAAg71gFBpMFbCbl0TxfGq2wRb0qbYLlBLjMI/IULgWGMIEODSMewWradBh5WC1AOcEhgJXLgl7eN1lonIFsymNeTL7KVVnn2JTSVwUMDIbaaZyI3UF4hYbFA4i9eTNCxfhgjR42Mx5CZbYMQXSyie3sQ2JggJOjr2bbQmpqxJzXCTqwDWXvlOUp3/Q2BM+RQTU5CU8EsAEW4uJ1g2Z5gIuvbLG33NRgRNXAiao1KZbBGtLyGeQwI+qL6yegQXShs+z5OEmevcdHmffH6N/CHpfW0X3oFtUUrkMSjPrcmOcup04sDTaelCYnh/x2cz+quIrWnnyJ26KzdDioMBl3qBHzC0paaIJNZwkpfXbxis3lMJBGuwphSfdfmwI5VMk/QthJT9BDKRuuQsHMDobceoRIY71VLJVIhbFBNjRijCUsdqEwTutT72hNPa0wQYmo1dM1Dd/ZQfWY+vbNvo/zos8hsGtPrY3RAWOpE2HF0uRjdNpFAFzfU/ejAiWL9BZiCN2c8saVV/EnVv84l/aFeRCy725RMJGxMxaf9a9/HyuejwHNAMArCtqitWIWI2wgZfShTDkHHBtq//H1UPD4Q7W0q328RtK0HbSNSCqldyg/PZ90HPgVSsWkDmu7nV6TAW7CEcH2J9k9/AxlLYvxgcI6pzi+bMMT4NUylTNhXIGzvJVjbhfE8VDYFjkIkIVjZTdvFlyDt2KZnFpYiLJXwV61GpAdaHCtBDB+7zYdHJPYk/58v4T03j9hbj9t9mmZL8DWle56sLyaqfghKbxoVYcWQ2XidUQijPRidvZTueqK+AaN+mTH9rosyk2UuAcIgqgJ/URu1hSuJygRsTAPffE8BEHMRCAq3PcLGoh1b8Zb1Y2Pypd4sSSEQMtprIGLxaOMjISQUus+jcOtjRF9gI5GqAYVMJxCuNQBdytMQHEIFg/+QxiVc1Yb3xNO736UpkLk4Mp2MvrAQUSFMqVC5JDIWxwQ+ulAl7OyDqo9wBbpSw2iDyidRDal6ERmDTCdRzVmEFcPoWn23igBHQCzafSMtB7slH2kBINMxrOY0uDamWsaYANWYQsZigEbGHVRTCpVPRkNtWch8GmEpIKgvGTmoXBLVlAIp0F4t2sDvgu4toTuLCEchU7E6KImWmGTMjfq15RbhgKWooIBWvLlrsV4w6P2q9z1M6sL3oZp2T0qdCTWmBo1ffB+pE4+jcO9f6fn1zdijR5K78HycGTPQxQK9N95M3y33k3nPiWTOPhNrZAvewkX0XHcz3rxFgMFqaSD7kbNIHPkORDZNsGIVfTffQuGORxGkaPz2R3An7EPxgfuIH3gQsbcehi6X6P3NDRRuepCwFC2bZC86jfQpJ6OamwnWt1G47S6Ktz8G5YCwXME9YC/yn76I2L77UfnH01Tm/4PUrOMJyxW6f3493qLF2M2t5D52LolZMxGOTfWZZ+j6xfXUFi8jMetwGj56IUF7O96SF0keeTS15a/Q9aPfYSoBwlWb8cjFwPuBS5Aso+WyVTSaFdYIU/jT9eaNaG3fucS8qBrMgmyrWdgw2ixsGG0WZFrMS26r6b3jZmOMMTr0THHuA6a67MUB1/qda03PX24wYblnwOeVF+aaxXtNM4vG7GUKj9+9xT21qZpVHzjPvOgkTe+9s+ufhgPPCQpm+QknmBfAdPzqB1v2oaum7VtfMC+SMC9Pnm7K/3py4N9DzxhjTFBoN0v2P8QscBpM7103bdFP+aW/mZdSObPi9FP7PZ9vjDGm957ZZkHDKLMg0WIWNtbHJj9qqbwWuAH4AxqDd6vEXW+CEpU77sLoym4EkGYzDycdkm99J+6E6QPNREMr2VP+HzI+EDTF9j2U2BHTSZ04k9Q7TgRgw/e+w8rz3k3QvhKBS8MnPow1Kk/YU+yHIPqHEyncQ6YSP3Bf8hd/CIDCPXew+sLzqMz7O0K4ZM46G2v8SHIfOpf4jCMA6P3z72m/8kuE3Z11VqOECTyyn76AzKnno70KHVddTvsVX0WXC8SnzST3yfcSdm/Y7BmrFbyXn6E8Z25k9tWr0OPGVSIbKFJ5rp3EXy3iF1TvfYrqw/cSP37PqElsdEDxgTsxlQqpk05HxiK/o8s99Mz+He4+00nOfOfmdbCmPKVbH2fFqccjx0q8p9bir15LbfkSrOZxWA2tqFQefL/eTx+9N96ASMfJnnM+wo7jjBlF39oS6z7wccj71J5dgvfMchJvfxvxQ2ci3RzutCm4hx0IgLfiGdZ9+At4nZ1YmVE0fPQ/wNdYTXlih0VEvL/iJYp/foCgdx2Zs84hNuUgEoe/BX/B8k0gputHP2LDN36CdJJRUZxXhWpW/zcbRXgpvFbgvkv39TQUrr0RZ9bRqNjur6vvL3mBdR/7AkZUmXjAATiT9gOg9Mj9rPjw5xhx8Xn9hGYgCPHXrSd58nFkLziT+E9mINwEwkS5/cb3Iwdfj86rzz7Nus99HXf6GFJHH4XVOgkcG699Bc7aKTSccSHuJftitzYjdKLeRw3pJlDJSNNrS14h7OzBQrKxDrvRGpXJYzdGfKiz9wFMePTBepAejb7Kj0Am6wVhhCFY04mhiointpqGYK2pbx2NQKZAUHrCkLxdkvxw9a4nqNxzF6mzPrjbhabDABlkMckYRg7kGyWgYukBdJbWJRq/fiEtl14V8Xxz51L913OkTj4Ru3X8wLUcAMug3AaUk4vQHRC2ryd19BGMf+g2BEn8dWsp/Plx3GlTiU3dd3OVcLHpxph62DCQp9x8n6B7A7VFi6M4zitjAijNfaKO9KNUD5Fyog1ocuslmaxyv3gjihA0IcHvbNxTTK04svCDXxI78lispt27cV4ohbAkRkqQ/TgfR/WPpqIhqgaopgSxww+KzNaSf7LqrHOorl3N5PmPR0Izr2LWpYpKKgm5SfuMXSVx4iEIkmivm7UfeT999zzM6F9fRWzqviAVulwiLEZZys6kvbAbRlHrWoNKJ+vPLQl7ewna26PJt2EVbZ/6JNVnFiHTaax9svgvtpM++YStyXjr8bvZHMCj69GFpjbHEP5GkKU273kKV/9096xnqn5JRsqKCCshELLfRNv4ez+mXNgWxtforohqslv3pvm/LmfSk38lftA76jRYBhx7E/4Qth3Fg0oibGcj6YUp1muHOVlyF32QcTf/htz7ImCicjnCzm4qf38qWneb+BbG3P5bxv3pt2TOOK8OFgRhZzelx54CDO7Uwxh1zXWM/NkVjP79dUx66lnSZ59C0N3Vb4Ja25ScTFKk/5GiRJICFoVrwJovSFK86kYqD9z2xquXEgPM1yYqyurHtjuy3/RjEwVkSgF9f7qH2urFyGSW3Lnvx5kwmcqzj0WnN2Sx9s5BGCFUHUSZvsZodFDZNDqF/3mY2pqXEEKSPfN9pM8+h6BtKRCiRo3A2ruVnl/MpvjQXwBIHvlOsud8AFnffC8sCcqh77e30fWbn2IIiB92BI2f/k8yZ5yFtLLInI1w+1kKodlWmpYlB5GoJFwdoC+H2A2mrzvZ/c3vYU2fjj122s52VltMKiElQkHxrnsI17eBBn/NKnSlAEFI9zW/xm4ehXAdKvOfQZHAe3YBXb+4EnRUR6T2wkoqj73Amr6LSZ04C5lK0XfbA4QdbSSPextGC4J1PfTedDve84uorVqFoYa/uo31l3wNK99A9fkF+AvXsPqci8icfzpWvpnSI4/jvfACyWOPQOYbML09+G0dtH3+y6ROuw+7cS+8Jc+SOvVUMieeC5aNdF3CoED7F6+k/Pd/kHjrW5HJDEHHGsqPPk35wXnY00ay4YqvoIOAytynkW5i8Km8fFvjiY2k4ccS+fmQDSTOO5kRN92MkImdJrP2y75ExzevQeWS0TKE2WwfTdmvE6jRYqBI2ogQdNVnY50qYSlEwsFUw6g0YD1HVDg2QkFYKEeEsqgv+BiF0VGgI90kRvsYv4IQDjKbxAQaXSxFjsJKIFMxdF+xXqPEQddq0S1EpPlSO8iWOA1f/TAEFrV/voxsTdP4+c9gt+5F5fk5rD75IoK1HQhHoatVhO0ilIXxPUxYQ8bTYAl0sRy5gLiDjDtbTcEzxiy3xmzTr4T4mMsqcGAvjcdU/vgAPWO/Sv6HV+00oSVmzUJlfkfQU0DlMnUAFQ0yrkRsrIcpRISSZVQSe2PRMpQAaRDOq/yaFdkRZdcZeQ3SsSKQEdZz/KWIci+0E91DgZAClU1u+jvKIHPJaEKEGpWOgZKYIARhMH0BamSK7PvOw87vPXDdrNxO19XX4q9uR2UTYAuUa0fPYwJEzEE6iQjWh0Swv27et5ozKaCmxX3i3a/BS6xD8VHi095L7s8dVKcYKuSu/CLZ//zmzrKPdP/6l3Rc9lNqK9fsoS81NggVR+biAwdTgKmEiEyczLknkjzhSNToFowf4C9+hcIf76Z875MQcxAxueML3yIyE77WNzeV+z6xXSP0fuCXZE/oIz3bo5jHgoZrvkn6Q5/bacNSXTSfwh13UV34L4wX1FeJ9wB5iWhtpzLn+XnBmsI6kbLsAT5YCvBCdLGETMdQTXlMEBKu78TUAmQiCTFrR7ONpTCEEK7ywuDBlkrlgZFBubxdw3I+cBOwlvx5htTskCImLsj//FtkLvzMTp7TIa9d1fmNZUEFkp5bbzp29Tnve8TKjxJbVOoUQCnE1MJ64BTBXJFS0drgDmaHGyEQgUEERVMJPEYbxYigun27LQ4EzgCWU/1XDbkiQfp0E/hU730U4h6xt82CnfTWWoHcww4BoK0pU64O17Svqzw1B/nq2iQCCCUiUJu2XQkUuGJIu2G2puVCgzA+gQ7JIEjqYOjvT+ui53cBfZ+QxEN8Q+8Xr6T761/GMMw3bwzmcbvX0/n1C38cdiyZH5t1OJpgtz/TkIS2cQHep/TLGuEFhnTFkKDv8p/Rcd578dsWvakE5j31MG1HH31lde5jl9gjWhAVb48w2tbQzRdoFJrSn1zEhpDMtYL8pNKf7sV/cRn5H3+b+DvPGN7a1bOW8s+uZv1Vv7r8X93dX1eZOC/MeBtedy+j3Qx5qfD17nuh8nb5tAPqPq2daNtCCguDZCSFZTXcRzRqmiAxMWxfR/Xuv6JLa7EOmIZMZIeVsExQpXzPbHo//Y2w+/d//LpXLX9nDVDxAiqdXZRKZZpiCZKWTfgq6L+puGv/5rJLfNoOCS2JhUGRpYZHot0jvFsgpCKxn6nW3MoTc/Ae+RuyycKeOAFh7dlVE4xfpPrEA/R8+7v0fvMXK/1liz9ZQf8yJHpVtKibJAE0OnESw11oVZJ46Iqi+JDAesngTFXER4ZrV1K5/SH8Bc8hRyaxx4ypF5Tfg8yg10d1zoP0XPoD+i79Od685x7zdPihPvwHXQwa6OwH4vUeIrSd9Ub5KKcDcaemMA9in5TkPkHg5yq3PkT1wXnETjqc5AVnEjv6eGRyxG5lN4K2pVQfeJDy7ffiPfY8YV93l4+62iCvFjidhvIebRl28hvuBVBba5Bf08i7DfJjhvzZqq+SqPzxPqp3P4kz6yDix78D97iZODPe8obV/g83rKA670m8R+bhPT4H/5ml6LCqDfZfbNyfpOl5fD0WNi57erN2TbcC4ClB8ekY1h/KJD8icU4QxVqmet/f8O5/EjVxNO7b98eddRj2/m/BmjoRKzuSjXkTr5fP1OV2guVr8RctwPvfp6k98jz+Sy+j+3rrX9v6myR5HRRvh7DkoJEMj2btOiMksNFhkupDCvNQgDzYJ/4+cE7XhgnmlXWEr6yicuNDkIlj7TcKe/J0nAOnYc0Yj9U6CjWiFZHMItwYwtroIDamWweYwIeahy72EHasJVi3jmDRaoIXFuMvWYi/YBV6bRHMxvjKqQpSj2iq14N3t8Kp6k0OZ/gUvrF29Q0MAgeNS+2fUP1nF8mfWYiZGuckTfxIi7BV95WozVlIbc6LlACZiqNGNCEzeUQsCVkXkXEQrh2tuYUaPB/dV4MeD+OVCPu6CDu7MX1lNuaVSxQGx2hiLxrCu4D7AorzJDVPYe006u1NJ7T+YAVCJHpZnuKyblI3xtETejFvl7gH28QOAPYP0E2yGBAW1xOwlmjT05av+RNsep1cffAjLbRJEKJWB+jnArz5DqW5Ls7fl1Hts4FcXVDD+WXP1u66sUGSpLC8jXB5EnWTpCh68MeOJLZ3F4mpFtbeCsYDLQKSIFyiTBEZrS4JH4xnoCgwayUsA/PyagqL4nhLxhFrW4HApUiCDAaFIOTN0P7/ANXjuuhKlYnHAAAAAElFTkSuQmCC2018-12-17 + 12:23:01 +0100169d6681ae22d81339f62b5fce96f43f01459e319de318763202d744c826657c3 + klass: HistoryValue + created_at: '2020-07-21 17:15:14.270769' + updated_at: '2020-07-21 17:15:14.270769' +footprint_debug_25: + id: 25 + footprint: ed23a2eb1903befc977621bc3c3b19aad831fe550ebaa99e9299238b3d93c275 + data: 1010YYMMmmmX[/VL]R[/A]2018-12-17 12:23:01 +01001eddfaaafe4dde25a3d3e0c2edbcfb72c44c39ad5022557241b32264db9518dca + klass: HistoryValue + created_at: '2020-07-21 17:15:14.281378' + updated_at: '2020-07-21 17:15:14.281378' +footprint_debug_26: + id: 26 + footprint: e5ec7e2f724da59e251202a68b4bab101979b8328c9ad195e3944f42a8a2bff1 + data: 1111true2018-12-17 12:23:01 +01001ed23a2eb1903befc977621bc3c3b19aad831fe550ebaa99e9299238b3d93c275 + klass: HistoryValue + created_at: '2020-07-21 17:15:14.286715' + updated_at: '2020-07-21 17:15:14.286715' +footprint_debug_27: + id: 27 + footprint: 1291396a56454be64857a103fdcc9a0bcbf98ae9f5c76da5634fc209c900bd24 + data: 1212INMEDFABLAB2018-12-17 12:23:01 +01001e5ec7e2f724da59e251202a68b4bab101979b8328c9ad195e3944f42a8a2bff1 + klass: HistoryValue + created_at: '2020-07-21 17:15:14.291741' + updated_at: '2020-07-21 17:15:14.291741' +footprint_debug_28: + id: 28 + footprint: 871452c7d3d9102e3af11992402549f06a9c32bf3892589ec034f651a9924f21 + data: 1313nnnnnn-MM-YY2018-12-17 12:23:01 +010011291396a56454be64857a103fdcc9a0bcbf98ae9f5c76da5634fc209c900bd24 + klass: HistoryValue + created_at: '2020-07-21 17:15:14.296254' + updated_at: '2020-07-21 17:15:14.296254' +footprint_debug_29: + id: 29 + footprint: 5465cf9395ca87683e977f52754e2eddf1535383b1f7a345fe85d5f80d99731f + data: 1414false2018-12-17 12:23:01 +01001871452c7d3d9102e3af11992402549f06a9c32bf3892589ec034f651a9924f21 + klass: HistoryValue + created_at: '2020-07-21 17:15:14.300673' + updated_at: '2020-07-21 17:15:14.300673' +footprint_debug_30: + id: 30 + footprint: 8d83e8e047857f7add3a6bf6dae8ed477df1792c049b2686ef034d5c5f982950 + data: 151520.02018-12-17 12:23:01 +010015465cf9395ca87683e977f52754e2eddf1535383b1f7a345fe85d5f80d99731f + klass: HistoryValue + created_at: '2020-07-21 17:15:14.304827' + updated_at: '2020-07-21 17:15:14.304827' +footprint_debug_31: + id: 31 + footprint: 7fe2e034676632260cdb75f6b69c504430191599f1fe0cc9469d7d9a00362e3e + data: 1616Notre association n'est pas assujettie à la TVA2018-12-17 12:23:01 +010018d83e8e047857f7add3a6bf6dae8ed477df1792c049b2686ef034d5c5f982950 + klass: HistoryValue + created_at: '2020-07-21 17:15:14.309387' + updated_at: '2020-07-21 17:15:14.309387' +footprint_debug_32: + id: 32 + footprint: 7e6ffbdf7e2421f14b975d9c3a91b7dd4f495e2350671887a651fcde798d2c86 + data: '1717Fab-manager
41 rue du Colonel Moutarde, 21000 DIJON France
Tél.: + +33 1 23 45 67 98
Fax. : +33 1 23 45 67 98
SIRET : 237 082 474 00006 + - APE 913 E2018-12-17 12:23:01 +010017fe2e034676632260cdb75f6b69c504430191599f1fe0cc9469d7d9a00362e3e' + klass: HistoryValue + created_at: '2020-07-21 17:15:14.314012' + updated_at: '2020-07-21 17:15:14.314012' +footprint_debug_33: + id: 33 + footprint: 5d3504df707a090b0cd720085dbf5768804c1fdb79666c137f1dd30727ddf3a8 + data: 18181970-01-01 08:00:002018-12-17 12:23:01 +010017e6ffbdf7e2421f14b975d9c3a91b7dd4f495e2350671887a651fcde798d2c86 + klass: HistoryValue + created_at: '2020-07-21 17:15:14.318306' + updated_at: '2020-07-21 17:15:14.318306' +footprint_debug_34: + id: 34 + footprint: b6b8b1039a886030e33fa6b4e2a36b00a1b7d39768a4717039a74da126440f16 + data: 19191970-01-01 23:59:592018-12-17 12:23:01 +010015d3504df707a090b0cd720085dbf5768804c1fdb79666c137f1dd30727ddf3a8 + klass: HistoryValue + created_at: '2020-07-21 17:15:14.322734' + updated_at: '2020-07-21 17:15:14.322734' +footprint_debug_35: + id: 35 + footprint: 84655b10bcdb2328008324d2a84eda4c82e997efdd2548c142e749671bff813b + data: 2020true2018-12-17 12:23:01 +01001b6b8b1039a886030e33fa6b4e2a36b00a1b7d39768a4717039a74da126440f16 + klass: HistoryValue + created_at: '2020-07-21 17:15:14.32734' + updated_at: '2020-07-21 17:15:14.32734' +footprint_debug_36: + id: 36 + footprint: 56911ae1d9620890c8cd9a437d897e70dab8dbfca29ac9bda47d46ea88fcf041 + data: 2121242018-12-17 12:23:01 +0100184655b10bcdb2328008324d2a84eda4c82e997efdd2548c142e749671bff813b + klass: HistoryValue + created_at: '2020-07-21 17:15:14.331955' + updated_at: '2020-07-21 17:15:14.331955' +footprint_debug_37: + id: 37 + footprint: f1d81764e08fc76a98e05e20a9f8133d326618f0603b62a34ed718e58c42b17f + data: 2222false2018-12-17 12:23:01 +0100156911ae1d9620890c8cd9a437d897e70dab8dbfca29ac9bda47d46ea88fcf041 + klass: HistoryValue + created_at: '2020-07-21 17:15:14.336879' + updated_at: '2020-07-21 17:15:14.336879' +footprint_debug_38: + id: 38 + footprint: aaa4c192b537b06539f93f9121db2a741fc2afd33684a1442f41b67c3e79909c + data: 2323242018-12-17 12:23:01 +01001f1d81764e08fc76a98e05e20a9f8133d326618f0603b62a34ed718e58c42b17f + klass: HistoryValue + created_at: '2020-07-21 17:15:14.341659' + updated_at: '2020-07-21 17:15:14.341659' +footprint_debug_39: + id: 39 + footprint: 90b7f212f67fb46e19c9aed26b5a9d013dc5435c1204acd573415e19f3a83a69 + data: 2424#cb11172018-12-17 12:23:01 +01001aaa4c192b537b06539f93f9121db2a741fc2afd33684a1442f41b67c3e79909c + klass: HistoryValue + created_at: '2020-07-21 17:15:14.346427' + updated_at: '2020-07-21 17:15:14.346427' +footprint_debug_40: + id: 40 + footprint: c04293a2f238379d56d2aedf9416bc4c30a01d4e0f270650ae60751fda3581b7 + data: 2525#ffdd002018-12-17 12:23:01 +0100190b7f212f67fb46e19c9aed26b5a9d013dc5435c1204acd573415e19f3a83a69 + klass: HistoryValue + created_at: '2020-07-21 17:15:14.351153' + updated_at: '2020-07-21 17:15:14.351153' +footprint_debug_41: + id: 41 + footprint: 4413b595bfbb0ec9ac2637997b1731eb06e516dea606e6a2f1acc4b9bf323f98 + data: 2626Avant de réserver une formation, nous vous conseillons de consulter nos + offres d'abonnement qui proposent des conditions avantageuses sur le prix des + formations et les créneaux machines.2018-12-17 12:23:01 +01001c04293a2f238379d56d2aedf9416bc4c30a01d4e0f270650ae60751fda3581b7 + klass: HistoryValue + created_at: '2020-07-21 17:15:14.355548' + updated_at: '2020-07-21 17:15:14.355548' +footprint_debug_42: + id: 42 + footprint: 15c5f76e7bad2d520d9a8bb278660fd15e6238c27166bfff9aa155c7c4124c3f + data: 2727Fab Lab de La Casemate2018-12-17 12:23:01 +010014413b595bfbb0ec9ac2637997b1731eb06e516dea606e6a2f1acc4b9bf323f98 + klass: HistoryValue + created_at: '2020-07-21 17:15:14.359742' + updated_at: '2020-07-21 17:15:14.359742' +footprint_debug_43: + id: 43 + footprint: 3462a9ee7393704f8d643726e7fa1ed4842e52955638ee3867e55da64df75542 + data: 2828male2018-12-17 12:23:01 +0100115c5f76e7bad2d520d9a8bb278660fd15e6238c27166bfff9aa155c7c4124c3f + klass: HistoryValue + created_at: '2020-07-21 17:15:14.363983' + updated_at: '2020-07-21 17:15:14.363983' +footprint_debug_44: + id: 44 + footprint: '0995b733e10693beffef7898aa6b39d7cb8c41fe7c855a67a55655bb5cfa9ef3' + data: 29292018-12-17 12:23:01 +010013462a9ee7393704f8d643726e7fa1ed4842e52955638ee3867e55da64df75542 + klass: HistoryValue + created_at: '2020-07-21 17:15:14.368192' + updated_at: '2020-07-21 17:15:14.368192' +footprint_debug_45: + id: 45 + footprint: 868d09a6f3b6a461b2476b61f3e6ee0ae696332485d2c1f41fbfe9d142e2f1c9 + data: 303032018-12-17 12:23:01 +010010995b733e10693beffef7898aa6b39d7cb8c41fe7c855a67a55655bb5cfa9ef3 + klass: HistoryValue + created_at: '2020-07-21 17:15:14.372768' + updated_at: '2020-07-21 17:15:14.372768' +footprint_debug_46: + id: 46 + footprint: 99658592bc30b15633d1cad934bcfdf37f97700e5283856070855f78a6b926ef + data: 313112018-12-17 12:23:01 +01001868d09a6f3b6a461b2476b61f3e6ee0ae696332485d2c1f41fbfe9d142e2f1c9 + klass: HistoryValue + created_at: '2020-07-21 17:15:14.377733' + updated_at: '2020-07-21 17:15:14.377733' +footprint_debug_47: + id: 47 + footprint: 1f3fe91aa7ef13d9f014a92375acd0957586af263d9be009845670c732dc8f64 + data: 3232f2018-12-17 12:23:01 +0100199658592bc30b15633d1cad934bcfdf37f97700e5283856070855f78a6b926ef + klass: HistoryValue + created_at: '2020-07-21 17:15:14.38263' + updated_at: '2020-07-21 17:15:14.38263' +footprint_debug_48: + id: 48 + footprint: cb3e208a107ee13f4ef7ebb88cd12212acf442ef9d96f1623012c1827e891bac + data: 3333default2018-12-17 12:23:01 +010011f3fe91aa7ef13d9f014a92375acd0957586af263d9be009845670c732dc8f64 + klass: HistoryValue + created_at: '2020-07-21 17:15:14.387081' + updated_at: '2020-07-21 17:15:14.387081' +footprint_debug_49: + id: 49 + footprint: 1d5b3e6fd1c8104a80a45b33bff31fdde6b02f95592263a7608b7ff3d5618f6d + data: 3434A propos de Fab-manager2018-12-31 11:22:25 +01001cb3e208a107ee13f4ef7ebb88cd12212acf442ef9d96f1623012c1827e891bac + klass: HistoryValue + created_at: '2020-07-21 17:15:14.391126' + updated_at: '2020-07-21 17:15:14.391126' +footprint_debug_50: + id: 50 + footprint: 5d0c759db1c4aad4bb9ad6f60eb7f7fb2f98f47edff7e69a03d0bf72aa2105f1 + data: 3535* Tarif réduit si vous avez moins de 25 ans, que vous êtes étudiant ou + demandeur d'emploi.2018-12-31 11:22:25 +010011d5b3e6fd1c8104a80a45b33bff31fdde6b02f95592263a7608b7ff3d5618f6d + klass: HistoryValue + created_at: '2020-07-21 17:15:14.395007' + updated_at: '2020-07-21 17:15:14.395007' +footprint_debug_51: + id: 51 + footprint: ad0f2b743469450d84f688642b723a460dc42dbea43d1addd42fb07b4368bb34 + data: 3636t2018-12-31 11:22:25 +010015d0c759db1c4aad4bb9ad6f60eb7f7fb2f98f47edff7e69a03d0bf72aa2105f1 + klass: HistoryValue + created_at: '2020-07-21 17:15:14.398953' + updated_at: '2020-07-21 17:15:14.398953' +footprint_debug_52: + id: 52 + footprint: 804902f2126fab8cce46e299aba05c0797be45ae3581091e82b9ca2f531338bf + data: 3737242018-12-31 11:22:25 +01001ad0f2b743469450d84f688642b723a460dc42dbea43d1addd42fb07b4368bb34 + klass: HistoryValue + created_at: '2020-07-21 17:15:14.402822' + updated_at: '2020-07-21 17:15:14.402822' +footprint_debug_53: + id: 53 + footprint: '085164a7288540c9beb0a6243856016fc36aae54bfb7d5d41af354650277d1ea' + data: '3838

La présente politique de confidentialité définit et vous informe de + la manière dont _________ utilise et protège les informations que vous nous transmettez, + le cas échéant, lorsque vous utilisez le présent site accessible à partir de l’URL + suivante : _________ (ci-après le « Site »).

Veuillez noter que cette politique + de confidentialité est susceptible d’être modifiée ou complétée à tout moment + par _________, notamment en vue de se conformer à toute évolution législative, + réglementaire, jurisprudentielle ou technologique. Dans un tel cas, la date de + sa mise à jour sera clairement identifiée en tête de la présente politique et + l''Utilisateur sera informé par courriel. Ces modifications engagent l’Utilisateur + dès leur mise en ligne. Il convient par conséquent que l’Utilisateur consulte + régulièrement la présente politique de confidentialité et d’utilisation des cookies + afin de prendre connaissance de ses éventuelles modifications.

2018-12-31 11:22:25 + +01001804902f2126fab8cce46e299aba05c0797be45ae3581091e82b9ca2f531338bf' + klass: HistoryValue + created_at: '2020-07-21 17:15:14.407478' + updated_at: '2020-07-21 17:15:14.407478' +footprint_debug_54: + id: 54 + footprint: + data: 39395302019-09-20 13:02:32 +02001085164a7288540c9beb0a6243856016fc36aae54bfb7d5d41af354650277d1ea + klass: HistoryValue + created_at: '2020-07-21 17:15:14.41201' + updated_at: '2020-07-21 17:15:14.41201' +footprint_debug_55: + id: 55 + footprint: + data: 404058012019-09-20 13:02:32 +02001 + klass: HistoryValue + created_at: '2020-07-21 17:15:14.416333' + updated_at: '2020-07-21 17:15:14.416333' +footprint_debug_56: + id: 56 + footprint: + data: 4141Client card2019-09-20 13:02:32 +02001 + klass: HistoryValue + created_at: '2020-07-21 17:15:14.428825' + updated_at: '2020-07-21 17:15:14.428825' +footprint_debug_57: + id: 57 + footprint: + data: 424258022019-09-20 13:02:32 +02001 + klass: HistoryValue + created_at: '2020-07-21 17:15:14.433717' + updated_at: '2020-07-21 17:15:14.433717' +footprint_debug_58: + id: 58 + footprint: + data: 4343Client wallet2019-09-20 13:02:32 +02001 + klass: HistoryValue + created_at: '2020-07-21 17:15:14.438971' + updated_at: '2020-07-21 17:15:14.438971' +footprint_debug_59: + id: 59 + footprint: + data: 444458032019-09-20 13:02:32 +02001 + klass: HistoryValue + created_at: '2020-07-21 17:15:14.443855' + updated_at: '2020-07-21 17:15:14.443855' +footprint_debug_60: + id: 60 + footprint: + data: 4545Client other2019-09-20 13:02:32 +02001 + klass: HistoryValue + created_at: '2020-07-21 17:15:14.448199' + updated_at: '2020-07-21 17:15:14.448199' +footprint_debug_61: + id: 61 + footprint: + data: 464640912019-09-20 13:02:32 +02001 + klass: HistoryValue + created_at: '2020-07-21 17:15:14.452333' + updated_at: '2020-07-21 17:15:14.452333' +footprint_debug_62: + id: 62 + footprint: + data: 4747Wallet credit2019-09-20 13:02:32 +02001 + klass: HistoryValue + created_at: '2020-07-21 17:15:14.456568' + updated_at: '2020-07-21 17:15:14.456568' +footprint_debug_63: + id: 63 + footprint: + data: 48484452019-09-20 13:02:32 +02001 + klass: HistoryValue + created_at: '2020-07-21 17:15:14.460895' + updated_at: '2020-07-21 17:15:14.460895' +footprint_debug_64: + id: 64 + footprint: + data: 4949VAT2019-09-20 13:02:32 +02001 + klass: HistoryValue + created_at: '2020-07-21 17:15:14.465768' + updated_at: '2020-07-21 17:15:14.465768' +footprint_debug_65: + id: 65 + footprint: + data: 505070612019-09-20 13:02:32 +02001 + klass: HistoryValue + created_at: '2020-07-21 17:15:14.470277' + updated_at: '2020-07-21 17:15:14.470277' +footprint_debug_66: + id: 66 + footprint: + data: 5151Subscription2019-09-20 13:02:32 +02001 + klass: HistoryValue + created_at: '2020-07-21 17:15:14.474733' + updated_at: '2020-07-21 17:15:14.474733' +footprint_debug_67: + id: 67 + footprint: + data: 525270622019-09-20 13:02:32 +02001 + klass: HistoryValue + created_at: '2020-07-21 17:15:14.479249' + updated_at: '2020-07-21 17:15:14.479249' +footprint_debug_68: + id: 68 + footprint: + data: 5353Machine reservation2019-09-20 13:02:32 +02001 + klass: HistoryValue + created_at: '2020-07-21 17:15:14.483978' + updated_at: '2020-07-21 17:15:14.483978' +footprint_debug_69: + id: 69 + footprint: + data: 545470632019-09-20 13:02:32 +02001 + klass: HistoryValue + created_at: '2020-07-21 17:15:14.488467' + updated_at: '2020-07-21 17:15:14.488467' +footprint_debug_70: + id: 70 + footprint: + data: 5555Training reservation2019-09-20 13:02:32 +02001 + klass: HistoryValue + created_at: '2020-07-21 17:15:14.492972' + updated_at: '2020-07-21 17:15:14.492972' +footprint_debug_71: + id: 71 + footprint: + data: 565670642019-09-20 13:02:32 +02001 + klass: HistoryValue + created_at: '2020-07-21 17:15:14.497436' + updated_at: '2020-07-21 17:15:14.497436' +footprint_debug_72: + id: 72 + footprint: + data: 5757Event reservation2019-09-20 13:02:32 +02001 + klass: HistoryValue + created_at: '2020-07-21 17:15:14.501746' + updated_at: '2020-07-21 17:15:14.501746' +footprint_debug_73: + id: 73 + footprint: + data: 585870652019-09-20 13:02:32 +02001 + klass: HistoryValue + created_at: '2020-07-21 17:15:14.506227' + updated_at: '2020-07-21 17:15:14.506227' +footprint_debug_74: + id: 74 + footprint: + data: 5959Space reservation2019-09-20 13:02:32 +02001 + klass: HistoryValue + created_at: '2020-07-21 17:15:14.510588' + updated_at: '2020-07-21 17:15:14.510588' +footprint_debug_75: + id: 75 + footprint: + data: |- + 6060
+
+
News
+
+
+
+
Projects
+
+
+
Last tweet
+
Last members
+
+
+
+
+
Next events
+
+
+
2020-03-25 10:24:09 +01001 + klass: HistoryValue + created_at: '2020-07-21 17:15:14.514679' + updated_at: '2020-07-21 17:15:14.514679' +footprint_debug_76: + id: 76 + footprint: + data: 6161602020-05-22 17:22:08 +02001 + klass: HistoryValue + created_at: '2020-07-21 17:15:14.519091' + updated_at: '2020-07-21 17:15:14.519091' +footprint_debug_77: + id: 77 + footprint: + data: 6262true2020-06-01 13:12:21 +02001 + klass: HistoryValue + created_at: '2020-07-21 17:15:14.523373' + updated_at: '2020-07-21 17:15:14.523373' +footprint_debug_78: + id: 78 + footprint: + data: 6363true2020-06-01 13:12:21 +02001 + klass: HistoryValue + created_at: '2020-07-21 17:15:14.527996' + updated_at: '2020-07-21 17:15:14.527996' +footprint_debug_79: + id: 79 + footprint: + data: 6464once2020-06-01 13:12:21 +02001 + klass: HistoryValue + created_at: '2020-07-21 17:15:14.533115' + updated_at: '2020-07-21 17:15:14.533115' +footprint_debug_80: + id: 80 + footprint: + data: 6565noreply@fab-manager.com2020-06-01 13:12:21 +02001 + klass: HistoryValue + created_at: '2020-07-21 17:15:14.538187' + updated_at: '2020-07-21 17:15:14.538187' +footprint_debug_81: + id: 81 + footprint: + data: 6666t2020-06-08 19:12:16 +02001 + klass: HistoryValue + created_at: '2020-07-21 17:15:14.542746' + updated_at: '2020-07-21 17:15:14.542746' +footprint_debug_82: + id: 82 + footprint: + data: 6767pk_test_aScrMu3y4AocfCN5XLJjGzmQ2020-06-08 19:12:16 +02001 + klass: HistoryValue + created_at: '2020-07-21 17:15:14.547235' + updated_at: '2020-07-21 17:15:14.547235' +footprint_debug_83: + id: 83 + footprint: + data: 6868sk_test_mGokO9TGtrVxMOyK4yZiktBE2020-06-08 19:12:16 +02001 + klass: HistoryValue + created_at: '2020-07-21 17:15:14.551532' + updated_at: '2020-07-21 17:15:14.551532' +footprint_debug_84: + id: 84 + footprint: + data: 6969usd2020-06-08 19:12:16 +02001 + klass: HistoryValue + created_at: '2020-07-21 17:15:14.555503' + updated_at: '2020-07-21 17:15:14.555503' +footprint_debug_85: + id: 85 + footprint: + data: 7070FabManager_invoice2020-06-15 12:04:06 +02001 + klass: HistoryValue + created_at: '2020-07-21 17:15:14.560357' + updated_at: '2020-07-21 17:15:14.560357' +footprint_debug_86: + id: 86 + footprint: + data: 7171f2020-06-15 12:04:06 +02001 + klass: HistoryValue + created_at: '2020-07-21 17:15:14.565096' + updated_at: '2020-07-21 17:15:14.565096' +footprint_debug_87: + id: 87 + footprint: + data: 7272t2020-06-15 12:04:06 +02001 + klass: HistoryValue + created_at: '2020-07-21 17:15:14.569921' + updated_at: '2020-07-21 17:15:14.569921' +footprint_debug_88: + id: 88 + footprint: + data: 7373t2020-06-17 12:48:19 +02001 + klass: HistoryValue + created_at: '2020-07-21 17:15:14.574885' + updated_at: '2020-07-21 17:15:14.574885' From 14b0b2ac30c7f8281ede61e51e75fccf3de6c365 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Tue, 21 Jul 2020 19:28:30 +0200 Subject: [PATCH 12/28] class documentation --- app/models/footprint_debug.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/models/footprint_debug.rb b/app/models/footprint_debug.rb index 9d7c4329a..d79fc095c 100644 --- a/app/models/footprint_debug.rb +++ b/app/models/footprint_debug.rb @@ -1,2 +1,5 @@ +# frozen_string_literal: true + +# When a footprint is generated, the associated data is kept to allow further verifications class FootprintDebug < ApplicationRecord end From 19fb816d36b40e4762054280f078601404716bc1 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Wed, 22 Jul 2020 09:45:20 +0200 Subject: [PATCH 13/28] Enhanced rake task to create fixtures for test cases --- CHANGELOG.md | 1 + app/services/footprint_service.rb | 2 +- db/structure.sql | 57 ++++++++++++++++++++----------- lib/tasks/db.rake | 5 +-- lib/tasks/fablab/maintenance.rake | 9 ----- test/fixtures/README.md | 10 ++++++ 6 files changed, 53 insertions(+), 31 deletions(-) create mode 100644 test/fixtures/README.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 21a39cbdc..7812391e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ - Display an asterisk on the phone input field, in the admin creation form, if the phone is configured as required - Keep the history of footprints data for verification purposes +- Enhanced rake task to create fixtures for test cases - Fix a bug: unable to export reservations - Fix a bug: unable to receive mails in development - [TODO DEPLOY] `rails fablab:maintenance:save_footprint_data` diff --git a/app/services/footprint_service.rb b/app/services/footprint_service.rb index 2ada8a376..425395bf0 100644 --- a/app/services/footprint_service.rb +++ b/app/services/footprint_service.rb @@ -39,7 +39,7 @@ class FootprintService columns = FootprintService.footprint_columns(klass) current = FootprintService.footprint_data(klass, item) saved = FootprintDebug.find_by(footprint: item.footprint, klass: klass) - puts "Debug footprint for Invoice [ id: #{item.id} ]" + puts "Debug footprint for #{klass} [ id: #{item.id} ]" puts '-----------------------------------------' puts "columns: [ #{columns.join(', ')} ]" puts "current footprint: #{current}" diff --git a/db/structure.sql b/db/structure.sql index 6ca27d22d..d94aa5d6e 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -108,8 +108,8 @@ SET default_tablespace = ''; CREATE TABLE public.abuses ( id integer NOT NULL, - signaled_type character varying, signaled_id integer, + signaled_type character varying, first_name character varying, last_name character varying, email character varying, @@ -187,8 +187,8 @@ CREATE TABLE public.addresses ( locality character varying, country character varying, postal_code character varying, - placeable_type character varying, placeable_id integer, + placeable_type character varying, created_at timestamp without time zone, updated_at timestamp without time zone ); @@ -263,8 +263,8 @@ CREATE TABLE public.ar_internal_metadata ( CREATE TABLE public.assets ( id integer NOT NULL, - viewable_type character varying, viewable_id integer, + viewable_type character varying, attachment character varying, type character varying, created_at timestamp without time zone, @@ -504,8 +504,8 @@ ALTER SEQUENCE public.coupons_id_seq OWNED BY public.coupons.id; CREATE TABLE public.credits ( id integer NOT NULL, - creditable_type character varying, creditable_id integer, + creditable_type character varying, plan_id integer, hours integer, created_at timestamp without time zone, @@ -1046,8 +1046,8 @@ 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, + invoiced_type character varying, stp_invoice_id character varying, total integer, created_at timestamp without time zone, @@ -1226,15 +1226,15 @@ ALTER SEQUENCE public.machines_id_seq OWNED BY public.machines.id; CREATE TABLE public.notifications ( id integer NOT NULL, receiver_id integer, - attached_object_type character varying, attached_object_id integer, + attached_object_type character varying, notification_type_id integer, is_read boolean DEFAULT false, created_at timestamp without time zone, updated_at timestamp without time zone, receiver_type character varying, is_send boolean DEFAULT false, - meta_data jsonb DEFAULT '"{}"'::jsonb + meta_data jsonb DEFAULT '{}'::jsonb ); @@ -1575,8 +1575,8 @@ CREATE TABLE public.prices ( id integer NOT NULL, group_id integer, plan_id integer, - priceable_type character varying, priceable_id integer, + priceable_type character varying, amount integer, created_at timestamp without time zone NOT NULL, updated_at timestamp without time zone NOT NULL @@ -1891,8 +1891,8 @@ CREATE TABLE public.reservations ( message text, created_at timestamp without time zone, updated_at timestamp without time zone, - reservable_type character varying, reservable_id integer, + reservable_type character varying, nb_reserve_places integer, statistic_profile_id integer ); @@ -1924,8 +1924,8 @@ ALTER SEQUENCE public.reservations_id_seq OWNED BY public.reservations.id; CREATE TABLE public.roles ( id integer NOT NULL, name character varying, - resource_type character varying, resource_id integer, + resource_type character varying, created_at timestamp without time zone, updated_at timestamp without time zone ); @@ -2859,8 +2859,8 @@ CREATE TABLE public.users_roles ( CREATE TABLE public.wallet_transactions ( id integer NOT NULL, wallet_id integer, - transactable_type character varying, transactable_id integer, + transactable_type character varying, transaction_type character varying, amount integer, created_at timestamp without time zone NOT NULL, @@ -3919,14 +3919,6 @@ ALTER TABLE ONLY public.roles ADD CONSTRAINT roles_pkey PRIMARY KEY (id); --- --- Name: schema_migrations schema_migrations_pkey; Type: CONSTRAINT; Schema: public; Owner: - --- - -ALTER TABLE ONLY public.schema_migrations - ADD CONSTRAINT schema_migrations_pkey PRIMARY KEY (version); - - -- -- Name: settings settings_pkey; Type: CONSTRAINT; Schema: public; Owner: - -- @@ -4942,6 +4934,29 @@ CREATE INDEX profiles_lower_unaccent_last_name_trgm_idx ON public.profiles USING CREATE INDEX projects_search_vector_idx ON public.projects USING gin (search_vector); +-- +-- Name: unique_schema_migrations; Type: INDEX; Schema: public; Owner: - +-- + +CREATE UNIQUE INDEX unique_schema_migrations ON public.schema_migrations USING btree (version); + + +-- +-- Name: accounting_periods accounting_periods_del_protect; Type: RULE; Schema: public; Owner: - +-- + +CREATE RULE accounting_periods_del_protect AS + ON DELETE TO public.accounting_periods DO INSTEAD NOTHING; + + +-- +-- Name: accounting_periods accounting_periods_upd_protect; Type: RULE; Schema: public; Owner: - +-- + +CREATE RULE accounting_periods_upd_protect AS + ON UPDATE TO public.accounting_periods DO INSTEAD NOTHING; + + -- -- Name: projects projects_search_content_trigger; Type: TRIGGER; Schema: public; Owner: - -- @@ -5428,6 +5443,7 @@ INSERT INTO "schema_migrations" (version) VALUES ('20140605125131'), ('20140605142133'), ('20140605151442'), +('20140606133116'), ('20140609092700'), ('20140609092827'), ('20140610153123'), @@ -5496,12 +5512,14 @@ INSERT INTO "schema_migrations" (version) VALUES ('20150507075620'), ('20150512123546'), ('20150520132030'), +('20150520133409'), ('20150526130729'), ('20150527153312'), ('20150529113555'), ('20150601125944'), ('20150603104502'), ('20150603104658'), +('20150603133050'), ('20150604081757'), ('20150604131525'), ('20150608142234'), @@ -5583,6 +5601,7 @@ INSERT INTO "schema_migrations" (version) VALUES ('20160905142700'), ('20160906094739'), ('20160906094847'), +('20160906145713'), ('20160915105234'), ('20161123104604'), ('20170109085345'), diff --git a/lib/tasks/db.rake b/lib/tasks/db.rake index f2484654f..ab8be6329 100644 --- a/lib/tasks/db.rake +++ b/lib/tasks/db.rake @@ -2,16 +2,17 @@ namespace :db do desc 'Convert development DB to Rails test fixtures' - task to_fixtures: :environment do + task :to_fixtures, [:table] => :environment do |_task, args| TABLES_TO_SKIP = %w[ar_internal_metadata delayed_jobs schema_info schema_migrations].freeze begin ActiveRecord::Base.establish_connection ActiveRecord::Base.connection.tables.each do |table_name| next if TABLES_TO_SKIP.include?(table_name) + next if args.table && args.table != table_name conter = '000' - file_path = "#{Rails.root}/test/fixtures/#{table_name}.yml" + file_path = "#{Rails.root}/test/fixtures/test/#{table_name}.yml" File.open(file_path, 'w') do |file| rows = ActiveRecord::Base.connection.select_all("SELECT * FROM #{table_name}") data = rows.each_with_object({}) do |record, hash| diff --git a/lib/tasks/fablab/maintenance.rake b/lib/tasks/fablab/maintenance.rake index 8192de46e..209636dbb 100644 --- a/lib/tasks/fablab/maintenance.rake +++ b/lib/tasks/fablab/maintenance.rake @@ -35,15 +35,6 @@ namespace :fablab do end end - desc 'generate fixtures from db' - task generate_fixtures: :environment do - Rails.application.eager_load! - ActiveRecord::Base.descendants.reject { |c| [ActiveRecord::SchemaMigration, PartnerPlan].include? c }.each do |ar_base| - p "========== #{ar_base} ==============" - ar_base.dump_fixtures - end - end - desc 'generate current code checksum' task checksum: :environment do require 'checksum' diff --git a/test/fixtures/README.md b/test/fixtures/README.md new file mode 100644 index 000000000..77bafc4b8 --- /dev/null +++ b/test/fixtures/README.md @@ -0,0 +1,10 @@ +# Test fixtures + +Fixtures are test data. +Every time a new test is run, the database is filled with these data. + +You can create fixtures manually or using the following task, to dump your current table/database to the YAML fixture files: +```bash +rails db:to_fixtures[table] +``` +The parameter `table` is optional. If not specified, the whole database will be dumped. \ No newline at end of file From 15b9c7b4b93c0be93cf01e4fc8a0dd73dca9bb51 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Wed, 22 Jul 2020 11:16:43 +0200 Subject: [PATCH 14/28] test export availabilites --- Gemfile | 1 + Gemfile.lock | 4 ++ db/structure.sql | 57 ++++++------------ test/fixtures/trainings_availabilities.yml | 7 +++ .../exports/accounting_export_test.rb | 2 +- .../exports/availabilites_export_test.rb | 60 +++++++++++++++++++ 6 files changed, 92 insertions(+), 39 deletions(-) create mode 100644 test/integration/exports/availabilites_export_test.rb diff --git a/Gemfile b/Gemfile index 04bdd6a96..d10c643df 100644 --- a/Gemfile +++ b/Gemfile @@ -61,6 +61,7 @@ group :test do gem 'pdf-reader' gem 'vcr', '3.0.1' gem 'webmock' + gem 'rubyXL' end group :production, :staging do diff --git a/Gemfile.lock b/Gemfile.lock index 71529414b..0fbd391c4 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -366,6 +366,9 @@ GEM ruby-rc4 (0.1.5) ruby-vips (2.0.17) ffi (~> 1.9) + rubyXL (3.4.14) + nokogiri (>= 1.10.8) + rubyzip (>= 1.3.0) rubyzip (1.3.0) safe_yaml (1.0.5) sass (3.4.25) @@ -518,6 +521,7 @@ DEPENDENCIES responders (~> 2.0) rolify rubocop (~> 0.61.1) + rubyXL rubyzip (>= 1.3.0) sass-rails (~> 5.0, >= 5.0.6) sdoc (~> 0.4.0) diff --git a/db/structure.sql b/db/structure.sql index d94aa5d6e..6ca27d22d 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -108,8 +108,8 @@ SET default_tablespace = ''; CREATE TABLE public.abuses ( id integer NOT NULL, - signaled_id integer, signaled_type character varying, + signaled_id integer, first_name character varying, last_name character varying, email character varying, @@ -187,8 +187,8 @@ CREATE TABLE public.addresses ( locality character varying, country character varying, postal_code character varying, - placeable_id integer, placeable_type character varying, + placeable_id integer, created_at timestamp without time zone, updated_at timestamp without time zone ); @@ -263,8 +263,8 @@ CREATE TABLE public.ar_internal_metadata ( CREATE TABLE public.assets ( id integer NOT NULL, - viewable_id integer, viewable_type character varying, + viewable_id integer, attachment character varying, type character varying, created_at timestamp without time zone, @@ -504,8 +504,8 @@ ALTER SEQUENCE public.coupons_id_seq OWNED BY public.coupons.id; CREATE TABLE public.credits ( id integer NOT NULL, - creditable_id integer, creditable_type character varying, + creditable_id integer, plan_id integer, hours integer, created_at timestamp without time zone, @@ -1046,8 +1046,8 @@ ALTER SEQUENCE public.invoice_items_id_seq OWNED BY public.invoice_items.id; CREATE TABLE public.invoices ( id integer NOT NULL, - invoiced_id integer, invoiced_type character varying, + invoiced_id integer, stp_invoice_id character varying, total integer, created_at timestamp without time zone, @@ -1226,15 +1226,15 @@ ALTER SEQUENCE public.machines_id_seq OWNED BY public.machines.id; CREATE TABLE public.notifications ( id integer NOT NULL, receiver_id integer, - attached_object_id integer, attached_object_type character varying, + attached_object_id integer, notification_type_id integer, is_read boolean DEFAULT false, created_at timestamp without time zone, updated_at timestamp without time zone, receiver_type character varying, is_send boolean DEFAULT false, - meta_data jsonb DEFAULT '{}'::jsonb + meta_data jsonb DEFAULT '"{}"'::jsonb ); @@ -1575,8 +1575,8 @@ CREATE TABLE public.prices ( id integer NOT NULL, group_id integer, plan_id integer, - priceable_id integer, priceable_type character varying, + priceable_id integer, amount integer, created_at timestamp without time zone NOT NULL, updated_at timestamp without time zone NOT NULL @@ -1891,8 +1891,8 @@ CREATE TABLE public.reservations ( message text, created_at timestamp without time zone, updated_at timestamp without time zone, - reservable_id integer, reservable_type character varying, + reservable_id integer, nb_reserve_places integer, statistic_profile_id integer ); @@ -1924,8 +1924,8 @@ ALTER SEQUENCE public.reservations_id_seq OWNED BY public.reservations.id; CREATE TABLE public.roles ( id integer NOT NULL, name character varying, - resource_id integer, resource_type character varying, + resource_id integer, created_at timestamp without time zone, updated_at timestamp without time zone ); @@ -2859,8 +2859,8 @@ CREATE TABLE public.users_roles ( CREATE TABLE public.wallet_transactions ( id integer NOT NULL, wallet_id integer, - transactable_id integer, transactable_type character varying, + transactable_id integer, transaction_type character varying, amount integer, created_at timestamp without time zone NOT NULL, @@ -3919,6 +3919,14 @@ ALTER TABLE ONLY public.roles ADD CONSTRAINT roles_pkey PRIMARY KEY (id); +-- +-- Name: schema_migrations schema_migrations_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.schema_migrations + ADD CONSTRAINT schema_migrations_pkey PRIMARY KEY (version); + + -- -- Name: settings settings_pkey; Type: CONSTRAINT; Schema: public; Owner: - -- @@ -4934,29 +4942,6 @@ CREATE INDEX profiles_lower_unaccent_last_name_trgm_idx ON public.profiles USING CREATE INDEX projects_search_vector_idx ON public.projects USING gin (search_vector); --- --- Name: unique_schema_migrations; Type: INDEX; Schema: public; Owner: - --- - -CREATE UNIQUE INDEX unique_schema_migrations ON public.schema_migrations USING btree (version); - - --- --- Name: accounting_periods accounting_periods_del_protect; Type: RULE; Schema: public; Owner: - --- - -CREATE RULE accounting_periods_del_protect AS - ON DELETE TO public.accounting_periods DO INSTEAD NOTHING; - - --- --- Name: accounting_periods accounting_periods_upd_protect; Type: RULE; Schema: public; Owner: - --- - -CREATE RULE accounting_periods_upd_protect AS - ON UPDATE TO public.accounting_periods DO INSTEAD NOTHING; - - -- -- Name: projects projects_search_content_trigger; Type: TRIGGER; Schema: public; Owner: - -- @@ -5443,7 +5428,6 @@ INSERT INTO "schema_migrations" (version) VALUES ('20140605125131'), ('20140605142133'), ('20140605151442'), -('20140606133116'), ('20140609092700'), ('20140609092827'), ('20140610153123'), @@ -5512,14 +5496,12 @@ INSERT INTO "schema_migrations" (version) VALUES ('20150507075620'), ('20150512123546'), ('20150520132030'), -('20150520133409'), ('20150526130729'), ('20150527153312'), ('20150529113555'), ('20150601125944'), ('20150603104502'), ('20150603104658'), -('20150603133050'), ('20150604081757'), ('20150604131525'), ('20150608142234'), @@ -5601,7 +5583,6 @@ INSERT INTO "schema_migrations" (version) VALUES ('20160905142700'), ('20160906094739'), ('20160906094847'), -('20160906145713'), ('20160915105234'), ('20161123104604'), ('20170109085345'), diff --git a/test/fixtures/trainings_availabilities.yml b/test/fixtures/trainings_availabilities.yml index 0f2b65ec7..0827e52de 100644 --- a/test/fixtures/trainings_availabilities.yml +++ b/test/fixtures/trainings_availabilities.yml @@ -19,3 +19,10 @@ trainings_availability_3: availability_id: 8 created_at: 2016-04-04 15:26:49.574308000 Z updated_at: 2016-04-04 15:26:49.574308000 Z + +trainings_availability_4: + id: 4 + training_id: 3 + availability_id: 12 + created_at: 2020-07-22 10:09:41.841162000 Z + updated_at: 2020-07-22 10:09:41.841162000 Z diff --git a/test/integration/exports/accounting_export_test.rb b/test/integration/exports/accounting_export_test.rb index dfc308b45..29a3c6632 100644 --- a/test/integration/exports/accounting_export_test.rb +++ b/test/integration/exports/accounting_export_test.rb @@ -8,7 +8,7 @@ class Exports::AccountingExportTest < ActionDispatch::IntegrationTest login_as(admin, scope: :user) end - test 'creation modification reservation and re-modification scenario' do + test 'export accounting period to ACD software' do # First, we create a new export post '/api/accounting/export', params: { diff --git a/test/integration/exports/availabilites_export_test.rb b/test/integration/exports/availabilites_export_test.rb new file mode 100644 index 000000000..25692ffe8 --- /dev/null +++ b/test/integration/exports/availabilites_export_test.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true + +require 'test_helper' +require 'rubyXL' + +module Exports; end + +class Exports::AccountingExportTest < ActionDispatch::IntegrationTest + setup do + admin = User.with_role(:admin).first + login_as(admin, scope: :user) + end + + test 'export availabilities to Excel' do + # First, we create a new export + get '/api/availabilities/export_index.xlsx' + + # Check response format & status + assert_equal 200, response.status, response.body + assert_equal Mime[:json], response.content_type + + # Check the export was created correctly + res = json_response(response.body) + e = Export.where(id: res[:export_id]).first + assert_not_nil e, 'Export was not created in database' + + # Run the worker + worker = AvailabilitiesExportWorker.new + worker.perform(e.id) + + # notification + assert_not_empty Notification.where(attached_object: e) + + # resulting XLSX file + assert FileTest.exist?(e.file), 'XLSX file was not generated' + workbook = RubyXL::Parser.parse(e.file) + + # test worksheets + assert_not_nil workbook[I18n.t('export_availabilities.machines')] + assert_not_nil workbook[I18n.t('export_availabilities.trainings')] + assert_not_nil workbook[I18n.t('export_availabilities.events')] + if Setting.get('spaces_module') + assert_not_nil workbook[I18n.t('export_availabilities.spaces')] + else + assert_nil workbook[I18n.t('export_availabilities.spaces')] + end + + # test data + availability = Availability.find(13) + machines = workbook[I18n.t('export_availabilities.machines')] + assert_equal availability.start_at.to_date, machines.sheet_data[1][0].value.to_date + assert_equal I18n.l(availability.start_at, format: '%A').capitalize, machines.sheet_data[1][1].value + assert_match(/^#{availability.start_at.strftime('%H:%M')} - /, machines.sheet_data[1][2].value) + assert_includes availability.machines.map(&:name), machines.sheet_data[1][3].value + + # Clean XLSX file + require 'fileutils' + FileUtils.rm(e.file) + end +end \ No newline at end of file From 807481db98695c03fa91d33669bb2e08bc7aa1b2 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Wed, 22 Jul 2020 12:23:01 +0200 Subject: [PATCH 15/28] test reservations export --- .../exports/availabilites_export_test.rb | 2 +- .../exports/reservations_export_test.rb | 52 +++++++++++++++++++ 2 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 test/integration/exports/reservations_export_test.rb diff --git a/test/integration/exports/availabilites_export_test.rb b/test/integration/exports/availabilites_export_test.rb index 25692ffe8..d254509eb 100644 --- a/test/integration/exports/availabilites_export_test.rb +++ b/test/integration/exports/availabilites_export_test.rb @@ -5,7 +5,7 @@ require 'rubyXL' module Exports; end -class Exports::AccountingExportTest < ActionDispatch::IntegrationTest +class Exports::AvailabilitiesExportTest < ActionDispatch::IntegrationTest setup do admin = User.with_role(:admin).first login_as(admin, scope: :user) diff --git a/test/integration/exports/reservations_export_test.rb b/test/integration/exports/reservations_export_test.rb new file mode 100644 index 000000000..942b966b9 --- /dev/null +++ b/test/integration/exports/reservations_export_test.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +require 'test_helper' +require 'rubyXL' + +module Exports; end + +class Exports::ReservationsExportTest < ActionDispatch::IntegrationTest + setup do + admin = User.with_role(:admin).first + login_as(admin, scope: :user) + end + + test 'export reservations to Excel' do + # First, we create a new export + get '/api/members/export_reservations.xlsx' + + # Check response format & status + assert_equal 200, response.status, response.body + assert_equal Mime[:json], response.content_type + + # Check the export was created correctly + res = json_response(response.body) + e = Export.where(id: res[:export_id]).first + assert_not_nil e, 'Export was not created in database' + + # Run the worker + worker = UsersExportWorker.new + worker.perform(e.id) + + # notification + assert_not_empty Notification.where(attached_object: e) + + # resulting XLSX file + assert FileTest.exist?(e.file), 'XLSX file was not generated' + workbook = RubyXL::Parser.parse(e.file) + + # test worksheet + assert_not_nil workbook[I18n.t('export_reservations.reservations')] + + # test data + reservation = Reservation.find(1) + wb = workbook[I18n.t('export_reservations.reservations')] + assert_equal reservation.user.id, wb.sheet_data[1][0].value + assert_equal reservation.created_at.to_date, wb.sheet_data[1][3].value.to_date + assert_equal reservation.reservable_type, wb.sheet_data[1][4].value + + # Clean XLSX file + require 'fileutils' + FileUtils.rm(e.file) + end +end \ No newline at end of file From 190e815f736fbc116cc874f45aa5709d53abd086 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Wed, 22 Jul 2020 12:29:54 +0200 Subject: [PATCH 16/28] [bug] unable to export subscriptions --- CHANGELOG.md | 1 + app/views/exports/users_subscriptions.xlsx.axlsx | 6 +++--- config/locales/en.yml | 1 + config/locales/fr.yml | 1 + 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7812391e6..0e3f2cde9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ - Keep the history of footprints data for verification purposes - Enhanced rake task to create fixtures for test cases - Fix a bug: unable to export reservations +- Fix a bug: unable to export subscriptions - Fix a bug: unable to receive mails in development - [TODO DEPLOY] `rails fablab:maintenance:save_footprint_data` diff --git a/app/views/exports/users_subscriptions.xlsx.axlsx b/app/views/exports/users_subscriptions.xlsx.axlsx index 4b7257c9f..bdcf01590 100644 --- a/app/views/exports/users_subscriptions.xlsx.axlsx +++ b/app/views/exports/users_subscriptions.xlsx.axlsx @@ -15,9 +15,9 @@ wb.add_worksheet(name: t('export_subscriptions.subscriptions')) do |sheet| # data rows @subscriptions.each do |sub| data = [ - sub.user.id, - sub.user.profile.full_name, - sub.user.email, + sub.user&.id, + sub.user&.profile&.full_name || t('export_subscriptions.deleted_user'), + sub.user&.email, sub.plan.human_readable_name(group: true), t("duration.#{sub.plan.interval}", count: sub.plan.interval_count), sub.created_at.to_date, diff --git a/config/locales/en.yml b/config/locales/en.yml index 7073772f7..498cb5f90 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -203,6 +203,7 @@ en: payment_method: "Payment method" local_payment: "Payment at the reception" online_payment: "Online payment" + deleted_user: "Deleted user" #reservation slots export, by type, to EXCEL format export_availabilities: machines: "Machines" diff --git a/config/locales/fr.yml b/config/locales/fr.yml index e1fc17e14..cdb54732a 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -203,6 +203,7 @@ fr: payment_method: "Méthode de paiement" local_payment: "Paiement à l'accueil" online_payment: "Paiement en ligne" + deleted_user: "Utilisateur supprimé" #reservation slots export, by type, to EXCEL format export_availabilities: machines: "Machines" From 33c3eb06e787caeb01adbceb7c5d273851013f64 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Wed, 22 Jul 2020 12:34:11 +0200 Subject: [PATCH 17/28] test subscriptions export --- .../exports/subscriptions_export_test.rb | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 test/integration/exports/subscriptions_export_test.rb diff --git a/test/integration/exports/subscriptions_export_test.rb b/test/integration/exports/subscriptions_export_test.rb new file mode 100644 index 000000000..9129074b3 --- /dev/null +++ b/test/integration/exports/subscriptions_export_test.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +require 'test_helper' +require 'rubyXL' + +module Exports; end + +class Exports::SubscriptionsExportTest < ActionDispatch::IntegrationTest + setup do + admin = User.with_role(:admin).first + login_as(admin, scope: :user) + end + + test 'export subscriptions to Excel' do + # First, we create a new export + get '/api/members/export_subscriptions.xlsx' + + # Check response format & status + assert_equal 200, response.status, response.body + assert_equal Mime[:json], response.content_type + + # Check the export was created correctly + res = json_response(response.body) + e = Export.where(id: res[:export_id]).first + assert_not_nil e, 'Export was not created in database' + + # Run the worker + worker = UsersExportWorker.new + worker.perform(e.id) + + # notification + assert_not_empty Notification.where(attached_object: e) + + # resulting XLSX file + assert FileTest.exist?(e.file), 'XLSX file was not generated' + workbook = RubyXL::Parser.parse(e.file) + + # test worksheet + assert_not_nil workbook[I18n.t('export_subscriptions.subscriptions')] + + # test data + subscription = Subscription.find(1) + wb = workbook[I18n.t('export_subscriptions.subscriptions')] + assert_equal subscription.user.id, wb.sheet_data[1][0].value + assert_equal subscription.plan.human_readable_name(group: true), wb.sheet_data[1][3].value + assert_equal subscription.created_at.to_date, wb.sheet_data[1][5].value.to_date + + # Clean XLSX file + require 'fileutils' + FileUtils.rm(e.file) + end +end \ No newline at end of file From 4bd26dc55894a98f6b674123ccc6f31c33be1957 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Wed, 22 Jul 2020 13:01:52 +0200 Subject: [PATCH 18/28] test members export --- test/fixtures/users.yml | 7 +++ .../exports/members_export_test.rb | 51 +++++++++++++++++++ 2 files changed, 58 insertions(+) create mode 100644 test/integration/exports/members_export_test.rb diff --git a/test/fixtures/users.yml b/test/fixtures/users.yml index e717303a5..c5aacaf8b 100644 --- a/test/fixtures/users.yml +++ b/test/fixtures/users.yml @@ -30,6 +30,7 @@ user_2: uid: auth_token: merged_at: + is_allow_newsletter: true user_4: id: 4 @@ -62,6 +63,7 @@ user_4: uid: auth_token: merged_at: + is_allow_newsletter: false user_6: id: 6 @@ -94,6 +96,7 @@ user_6: uid: auth_token: merged_at: + is_allow_newsletter: true user_5: id: 5 @@ -126,6 +129,7 @@ user_5: uid: auth_token: merged_at: + is_allow_newsletter: true user_3: id: 3 @@ -158,6 +162,7 @@ user_3: uid: auth_token: merged_at: + is_allow_newsletter: false user_1: id: 1 @@ -190,6 +195,7 @@ user_1: uid: auth_token: merged_at: + is_allow_newsletter: true user_7: @@ -223,3 +229,4 @@ user_7: uid: auth_token: merged_at: + is_allow_newsletter: false diff --git a/test/integration/exports/members_export_test.rb b/test/integration/exports/members_export_test.rb new file mode 100644 index 000000000..51f359bb6 --- /dev/null +++ b/test/integration/exports/members_export_test.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +require 'test_helper' +require 'rubyXL' + +module Exports; end + +class Exports::MembersExportTest < ActionDispatch::IntegrationTest + setup do + admin = User.with_role(:admin).first + login_as(admin, scope: :user) + end + + test 'export members to Excel' do + # First, we create a new export + get '/api/members/export_members.xlsx' + + # Check response format & status + assert_equal 200, response.status, response.body + assert_equal Mime[:json], response.content_type + + # Check the export was created correctly + res = json_response(response.body) + e = Export.where(id: res[:export_id]).first + assert_not_nil e, 'Export was not created in database' + + # Run the worker + worker = UsersExportWorker.new + worker.perform(e.id) + + # notification + assert_not_empty Notification.where(attached_object: e) + + # resulting XLSX file + assert FileTest.exist?(e.file), 'XLSX file was not generated' + workbook = RubyXL::Parser.parse(e.file) + + # test worksheet + assert_not_nil workbook[I18n.t('export_members.members')] + + # test data + member = User.members.first + wb = workbook[I18n.t('export_members.members')] + assert_equal member.id, wb.sheet_data[1][0].value + assert_equal (member.is_allow_newsletter ? 1 : nil), wb.sheet_data[1][4].value + + # Clean XLSX file + require 'fileutils' + FileUtils.rm(e.file) + end +end \ No newline at end of file From d78162d3182f8a0a834a784b5aba8fcfbca25f9c Mon Sep 17 00:00:00 2001 From: Sylvain Date: Wed, 22 Jul 2020 13:02:21 +0200 Subject: [PATCH 19/28] updated changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e3f2cde9..d2d8f4451 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ - Display an asterisk on the phone input field, in the admin creation form, if the phone is configured as required - Keep the history of footprints data for verification purposes - Enhanced rake task to create fixtures for test cases +- Automated tests for exports - Fix a bug: unable to export reservations - Fix a bug: unable to export subscriptions - Fix a bug: unable to receive mails in development From 3b9ddebc0dfe8ccee49e172ea31534ef2b9e1ff7 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Wed, 29 Jul 2020 14:36:22 +0200 Subject: [PATCH 20/28] [security] updated json to 2.3.1 to fix CVE-2020-10663 --- CHANGELOG.md | 1 + Gemfile | 3 +-- Gemfile.lock | 8 ++------ 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d2d8f4451..900bb5954 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ - Fix a bug: unable to export reservations - Fix a bug: unable to export subscriptions - Fix a bug: unable to receive mails in development +- Fix a security issue: updated json to 2.3.1 to fix [CVE-2020-10663](https://nvd.nist.gov/vuln/detail/CVE-2020-10663) - [TODO DEPLOY] `rails fablab:maintenance:save_footprint_data` ## v4.5.3 2020 July 21 diff --git a/Gemfile b/Gemfile index d10c643df..e9ffbd018 100644 --- a/Gemfile +++ b/Gemfile @@ -24,8 +24,7 @@ gem 'jquery-rails' # Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder gem 'jbuilder', '~> 2.5' gem 'jbuilder_cache_multi' -# bundle exec rake doc:rails generates the API under doc/api. -gem 'sdoc', '~> 0.4.0', group: :doc # TODO, remove unused ? +gem "json", ">= 2.3.0" gem 'forgery' gem 'responders', '~> 2.0' diff --git a/Gemfile.lock b/Gemfile.lock index 0fbd391c4..e6ddd3b0b 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -206,7 +206,7 @@ GEM rails-dom-testing (>= 1, < 3) railties (>= 4.2.0) thor (>= 0.14, < 2.0) - json (1.8.6) + json (2.3.1) jwt (2.2.1) kaminari (1.2.1) activesupport (>= 4.1.0) @@ -344,7 +344,6 @@ GEM rb-inotify (0.10.1) ffi (~> 1.0) rb-readline (0.5.5) - rdoc (4.3.0) recurrence (1.3.0) activesupport i18n @@ -380,9 +379,6 @@ GEM tilt (>= 1.1, < 3) sassc (2.2.1) ffi (~> 1.9) - sdoc (0.4.2) - json (~> 1.7, >= 1.7.7) - rdoc (~> 4.0) seed_dump (3.3.1) activerecord (>= 4) activesupport (>= 4) @@ -493,6 +489,7 @@ DEPENDENCIES jbuilder (~> 2.5) jbuilder_cache_multi jquery-rails + json (>= 2.3.0) kaminari listen (~> 3.0.5) message_format @@ -524,7 +521,6 @@ DEPENDENCIES rubyXL rubyzip (>= 1.3.0) sass-rails (~> 5.0, >= 5.0.6) - sdoc (~> 0.4.0) seed_dump sha3 sidekiq (>= 6.0.7) From 1e1658cd67cb2a4ee3f3fb65fbe9fa1865a41236 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Wed, 29 Jul 2020 14:37:50 +0200 Subject: [PATCH 21/28] New translations en.yml (Zulu) --- config/locales/zu.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/config/locales/zu.yml b/config/locales/zu.yml index f2e0eec9c..df91f08ec 100644 --- a/config/locales/zu.yml +++ b/config/locales/zu.yml @@ -203,6 +203,7 @@ zu: payment_method: "crwdns3525:0crwdne3525:0" local_payment: "crwdns3527:0crwdne3527:0" online_payment: "crwdns3529:0crwdne3529:0" + deleted_user: "crwdns20888:0crwdne20888:0" #reservation slots export, by type, to EXCEL format export_availabilities: machines: "crwdns3531:0crwdne3531:0" From e7e96a528901f3c99b13f4316025396a0f5eb33e Mon Sep 17 00:00:00 2001 From: Sylvain Date: Wed, 29 Jul 2020 14:37:57 +0200 Subject: [PATCH 22/28] New translations en.yml (Portuguese) --- config/locales/pt.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/config/locales/pt.yml b/config/locales/pt.yml index 63941d13f..a093cb174 100755 --- a/config/locales/pt.yml +++ b/config/locales/pt.yml @@ -203,6 +203,7 @@ pt: payment_method: "Método de pagamento" local_payment: "Pagamento na recepção" online_payment: "Pagamento online" + deleted_user: "Deleted user" #reservation slots export, by type, to EXCEL format export_availabilities: machines: "Máquinas" From 94354dca8204165bb36fe6d8314b11bf86928437 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Wed, 29 Jul 2020 14:38:02 +0200 Subject: [PATCH 23/28] New translations en.yml (Spanish) --- config/locales/es.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/config/locales/es.yml b/config/locales/es.yml index d409ca5be..20b756e72 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -203,6 +203,7 @@ es: payment_method: "Método de pago" local_payment: "Pago en recepción" online_payment: "Pago en línea" + deleted_user: "Deleted user" #reservation slots export, by type, to EXCEL format export_availabilities: machines: "Máquinas" From e2d701719d91c13a8db75679207d5597c6f10099 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Wed, 29 Jul 2020 14:38:06 +0200 Subject: [PATCH 24/28] New translations en.yml (French) --- config/locales/fr.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/config/locales/fr.yml b/config/locales/fr.yml index e1fc17e14..68a9d57a0 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -203,6 +203,7 @@ fr: payment_method: "Méthode de paiement" local_payment: "Paiement à l'accueil" online_payment: "Paiement en ligne" + deleted_user: "Deleted user" #reservation slots export, by type, to EXCEL format export_availabilities: machines: "Machines" From 7e2a409b949f9caadd3aeff6d53a1c5187ac46bb Mon Sep 17 00:00:00 2001 From: Sylvain Date: Wed, 29 Jul 2020 14:40:14 +0200 Subject: [PATCH 25/28] New translations en.yml (Portuguese) --- config/locales/pt.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/locales/pt.yml b/config/locales/pt.yml index a093cb174..841ff5721 100755 --- a/config/locales/pt.yml +++ b/config/locales/pt.yml @@ -203,7 +203,7 @@ pt: payment_method: "Método de pagamento" local_payment: "Pagamento na recepção" online_payment: "Pagamento online" - deleted_user: "Deleted user" + deleted_user: "Usuário deletado" #reservation slots export, by type, to EXCEL format export_availabilities: machines: "Máquinas" From 98cf40a93c7d7e004a1b41f2f75d83fdbb9cba78 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Wed, 29 Jul 2020 14:40:17 +0200 Subject: [PATCH 26/28] New translations en.yml (Spanish) --- config/locales/es.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/locales/es.yml b/config/locales/es.yml index 20b756e72..55813092e 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -203,7 +203,7 @@ es: payment_method: "Método de pago" local_payment: "Pago en recepción" online_payment: "Pago en línea" - deleted_user: "Deleted user" + deleted_user: "Usario eliminado" #reservation slots export, by type, to EXCEL format export_availabilities: machines: "Máquinas" From 18c82f2d1b47f7df45214211fa7d95c96b13a5f5 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Wed, 29 Jul 2020 14:40:21 +0200 Subject: [PATCH 27/28] New translations en.yml (French) --- config/locales/fr.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 68a9d57a0..cdb54732a 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -203,7 +203,7 @@ fr: payment_method: "Méthode de paiement" local_payment: "Paiement à l'accueil" online_payment: "Paiement en ligne" - deleted_user: "Deleted user" + deleted_user: "Utilisateur supprimé" #reservation slots export, by type, to EXCEL format export_availabilities: machines: "Machines" From 8278fd19df4bd23020c250efba98a36fed7da1de Mon Sep 17 00:00:00 2001 From: Sylvain Date: Wed, 29 Jul 2020 15:17:09 +0200 Subject: [PATCH 28/28] Version 4.5.4 --- CHANGELOG.md | 3 +++ package.json | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 900bb5954..55f76ddf7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # Changelog Fab-manager +## v4.5.4 2020 July 29 + - Display an asterisk on the phone input field, in the admin creation form, if the phone is configured as required - Keep the history of footprints data for verification purposes - Enhanced rake task to create fixtures for test cases @@ -8,6 +10,7 @@ - Fix a bug: unable to export subscriptions - Fix a bug: unable to receive mails in development - Fix a security issue: updated json to 2.3.1 to fix [CVE-2020-10663](https://nvd.nist.gov/vuln/detail/CVE-2020-10663) +- [TODO DEPLOY] `rails db:migrate` - [TODO DEPLOY] `rails fablab:maintenance:save_footprint_data` ## v4.5.3 2020 July 21 diff --git a/package.json b/package.json index 647d12ba6..0175fa9aa 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fab-manager", - "version": "4.5.3", + "version": "4.5.4", "description": "Fab-manager is the FabLab management solution. It provides a comprehensive, web-based, open-source tool to simplify your administrative tasks and your marker's projects.", "keywords": [ "fablab",