diff --git a/migration/20.03/2003-postScript.sql b/migration/20.03/2003-postScript.sql
new file mode 100644
index 0000000000000000000000000000000000000000..26c49ff479cae857fd27f2694631bdeef3c4b273
--- /dev/null
+++ b/migration/20.03/2003-postScript.sql
@@ -0,0 +1,182 @@
+-- *************************************************************************--
+--                                                                          --
+--                                                                          --
+-- Model migration script - 19.04 to 20.01 (Run this file after migrate.sh) --
+--                                                                          --
+--                                                                          --
+-- *************************************************************************--
+
+DROP VIEW IF EXISTS res_view_letterbox;
+DROP VIEW IF EXISTS view_contacts;
+
+DROP TABLE IF EXISTS cases;
+DROP TABLE IF EXISTS cases_res;
+
+DELETE FROM contacts_groups_lists WHERE contact_id IS NULL;
+
+DROP TABLE IF EXISTS contacts_res;
+DROP TABLE IF EXISTS contact_addresses;
+DROP TABLE IF EXISTS contact_communication;
+DROP TABLE IF EXISTS contact_purposes;
+DROP TABLE IF EXISTS contact_types;
+DROP TABLE IF EXISTS contacts_v2;
+
+UPDATE CONTACTS SET civility = null WHERE civility = '';
+UPDATE CONTACTS SET firstname = null WHERE firstname = '';
+UPDATE CONTACTS SET lastname = null WHERE lastname = '';
+UPDATE CONTACTS SET company = null WHERE company = '';
+UPDATE CONTACTS SET department = null WHERE department = '';
+UPDATE CONTACTS SET function = null WHERE function = '';
+UPDATE CONTACTS SET address_number = null WHERE address_number = '';
+UPDATE CONTACTS SET address_street = null WHERE address_street = '';
+UPDATE CONTACTS SET address_additional1 = null WHERE address_additional1 = '';
+UPDATE CONTACTS SET address_additional2 = null WHERE address_additional2 = '';
+UPDATE CONTACTS SET address_postcode = null WHERE address_postcode = '';
+UPDATE CONTACTS SET address_town = null WHERE address_town = '';
+UPDATE CONTACTS SET address_country = null WHERE address_country = '';
+UPDATE CONTACTS SET email = null WHERE email = '';
+UPDATE CONTACTS SET phone = null WHERE phone = '';
+UPDATE CONTACTS SET notes = null WHERE notes = '';
+
+ALTER TABLE acknowledgement_receipts ALTER COLUMN contact_id set not null;
+ALTER TABLE acknowledgement_receipts DROP COLUMN IF EXISTS contact_address_id;
+ALTER TABLE contacts_groups_lists ALTER COLUMN contact_id set not null;
+ALTER TABLE contacts_groups_lists DROP COLUMN IF EXISTS contact_addresses_id;
+ALTER TABLE res_attachments DROP COLUMN IF EXISTS dest_contact_id;
+ALTER TABLE res_attachments DROP COLUMN IF EXISTS dest_address_id;
+ALTER TABLE res_attachments DROP COLUMN IF EXISTS dest_user;
+ALTER TABLE contacts_filling DROP COLUMN IF EXISTS rating_columns;
+
+ALTER TABLE res_letterbox DROP COLUMN IF EXISTS exp_contact_id;
+ALTER TABLE res_letterbox DROP COLUMN IF EXISTS exp_user_id;
+ALTER TABLE res_letterbox DROP COLUMN IF EXISTS dest_contact_id;
+ALTER TABLE res_letterbox DROP COLUMN IF EXISTS dest_user_id;
+ALTER TABLE res_letterbox DROP COLUMN IF EXISTS address_id;
+ALTER TABLE res_letterbox DROP COLUMN IF EXISTS is_multicontacts;
+
+DROP TABLE IF EXISTS fp_fileplan;
+DROP TABLE IF EXISTS fp_fileplan_positions;
+DROP TABLE IF EXISTS fp_res_fileplan_positions;
+
+DROP TABLE IF EXISTS folder_tmp;
+ALTER TABLE res_letterbox DROP COLUMN IF EXISTS folders_system_id;
+
+DROP TABLE IF EXISTS groupbasket_status;
+DROP TABLE IF EXISTS indexingmodels;
+
+DROP TABLE IF EXISTS mlb_coll_ext;
+DROP TABLE IF EXISTS res_version_attachments;
+DROP TABLE IF EXISTS adr_attachments_version;
+ALTER TABLE shippings DROP COLUMN IF EXISTS is_version;
+
+ALTER TABLE priorities DROP COLUMN IF EXISTS default_priority;
+
+DROP TABLE IF EXISTS doctypes_indexes;
+
+DROP TABLE IF EXISTS listmodels;
+
+ALTER TABLE res_letterbox DROP COLUMN IF EXISTS custom_t1;
+ALTER TABLE res_letterbox DROP COLUMN IF EXISTS custom_t2;
+ALTER TABLE res_letterbox DROP COLUMN IF EXISTS custom_t3;
+ALTER TABLE res_letterbox DROP COLUMN IF EXISTS custom_t4;
+ALTER TABLE res_letterbox DROP COLUMN IF EXISTS custom_t5;
+ALTER TABLE res_letterbox DROP COLUMN IF EXISTS custom_t6;
+ALTER TABLE res_letterbox DROP COLUMN IF EXISTS custom_t7;
+ALTER TABLE res_letterbox DROP COLUMN IF EXISTS custom_t8;
+ALTER TABLE res_letterbox DROP COLUMN IF EXISTS custom_t9;
+ALTER TABLE res_letterbox DROP COLUMN IF EXISTS custom_t10;
+ALTER TABLE res_letterbox DROP COLUMN IF EXISTS custom_t11;
+ALTER TABLE res_letterbox DROP COLUMN IF EXISTS custom_t12;
+ALTER TABLE res_letterbox DROP COLUMN IF EXISTS custom_t13;
+ALTER TABLE res_letterbox DROP COLUMN IF EXISTS custom_t14;
+ALTER TABLE res_letterbox DROP COLUMN IF EXISTS custom_t15;
+ALTER TABLE res_letterbox DROP COLUMN IF EXISTS custom_d1;
+ALTER TABLE res_letterbox DROP COLUMN IF EXISTS custom_d2;
+ALTER TABLE res_letterbox DROP COLUMN IF EXISTS custom_d3;
+ALTER TABLE res_letterbox DROP COLUMN IF EXISTS custom_d4;
+ALTER TABLE res_letterbox DROP COLUMN IF EXISTS custom_d5;
+ALTER TABLE res_letterbox DROP COLUMN IF EXISTS custom_d6;
+ALTER TABLE res_letterbox DROP COLUMN IF EXISTS custom_d7;
+ALTER TABLE res_letterbox DROP COLUMN IF EXISTS custom_d8;
+ALTER TABLE res_letterbox DROP COLUMN IF EXISTS custom_d9;
+ALTER TABLE res_letterbox DROP COLUMN IF EXISTS custom_d10;
+ALTER TABLE res_letterbox DROP COLUMN IF EXISTS custom_n1;
+ALTER TABLE res_letterbox DROP COLUMN IF EXISTS custom_n2;
+ALTER TABLE res_letterbox DROP COLUMN IF EXISTS custom_n3;
+ALTER TABLE res_letterbox DROP COLUMN IF EXISTS custom_n4;
+ALTER TABLE res_letterbox DROP COLUMN IF EXISTS custom_n5;
+ALTER TABLE res_letterbox DROP COLUMN IF EXISTS custom_f1;
+ALTER TABLE res_letterbox DROP COLUMN IF EXISTS custom_f2;
+ALTER TABLE res_letterbox DROP COLUMN IF EXISTS custom_f3;
+ALTER TABLE res_letterbox DROP COLUMN IF EXISTS custom_f4;
+ALTER TABLE res_letterbox DROP COLUMN IF EXISTS custom_f5;
+ALTER TABLE res_letterbox DROP COLUMN IF EXISTS scan_date;
+ALTER TABLE res_letterbox DROP COLUMN IF EXISTS scan_user;
+ALTER TABLE res_letterbox DROP COLUMN IF EXISTS scan_location;
+ALTER TABLE res_letterbox DROP COLUMN IF EXISTS scan_wkstation;
+ALTER TABLE res_letterbox DROP COLUMN IF EXISTS scan_batch;
+ALTER TABLE res_letterbox DROP COLUMN IF EXISTS scan_postmark;
+ALTER TABLE res_letterbox DROP COLUMN IF EXISTS description;
+ALTER TABLE res_letterbox DROP COLUMN IF EXISTS author;
+ALTER TABLE res_letterbox DROP COLUMN IF EXISTS reference_number;
+ALTER TABLE res_letterbox DROP COLUMN IF EXISTS external_reference;
+
+
+/* RE CREATE VIEWS */
+CREATE OR REPLACE VIEW res_view_letterbox AS
+SELECT r.res_id,
+       r.type_id,
+       r.policy_id,
+       r.cycle_id,
+       d.description AS type_label,
+       d.doctypes_first_level_id,
+       dfl.doctypes_first_level_label,
+       dfl.css_style AS doctype_first_level_style,
+       d.doctypes_second_level_id,
+       dsl.doctypes_second_level_label,
+       dsl.css_style AS doctype_second_level_style,
+       r.format,
+       r.typist,
+       r.creation_date,
+       r.modification_date,
+       r.docserver_id,
+       r.path,
+       r.filename,
+       r.fingerprint,
+       r.filesize,
+       r.status,
+       r.work_batch,
+       r.doc_date,
+       r.external_id,
+       r.external_link,
+       r.departure_date,
+       r.opinion_limit_date,
+       r.department_number_id,
+       r.barcode,
+       r.external_signatory_book_id,
+       r.initiator,
+       r.destination,
+       r.dest_user,
+       r.confidentiality,
+       r.category_id,
+       r.alt_identifier,
+       r.admission_date,
+       r.process_limit_date,
+       r.closing_date,
+       r.alarm1_date,
+       r.alarm2_date,
+       r.flag_alarm1,
+       r.flag_alarm2,
+       r.subject,
+       r.priority,
+       r.locker_user_id,
+       r.locker_time,
+       r.custom_fields,
+       en.entity_label,
+       en.entity_type AS entitytype
+FROM doctypes d,
+     doctypes_first_level dfl,
+     doctypes_second_level dsl,
+     res_letterbox r
+    LEFT JOIN entities en ON r.destination::text = en.entity_id::text
+WHERE r.type_id = d.type_id AND d.doctypes_first_level_id = dfl.doctypes_first_level_id AND d.doctypes_second_level_id = dsl.doctypes_second_level_id;
diff --git a/migration/20.03/2003.sql b/migration/20.03/2003.sql
new file mode 100644
index 0000000000000000000000000000000000000000..2f334c720b106f670214aab9dca3b9635859ef53
--- /dev/null
+++ b/migration/20.03/2003.sql
@@ -0,0 +1,774 @@
+-- *************************************************************************--
+--                                                                          --
+--                                                                          --
+-- Model migration script - 19.04 to 20.03                                  --
+--                                                                          --
+--                                                                          --
+-- *************************************************************************--
+UPDATE parameters SET param_value_string = '20.03' WHERE id = 'database_version';
+
+
+/* VIEWS */
+DROP VIEW IF EXISTS res_view_letterbox;
+DROP VIEW IF EXISTS res_view_attachments;
+DROP VIEW IF EXISTS view_folders;
+
+/*USERS*/
+ALTER TABLE users DROP COLUMN IF EXISTS reset_token;
+ALTER TABLE users DROP COLUMN IF EXISTS change_password;
+ALTER TABLE users ADD COLUMN reset_token text;
+ALTER TABLE users DROP COLUMN IF EXISTS preferences;
+ALTER TABLE users ADD COLUMN preferences jsonb NOT NULL DEFAULT '{"documentEdition" : "java"}';
+
+/* FULL TEXT */
+DELETE FROM docservers where docserver_type_id = 'FULLTEXT';
+INSERT INTO docservers (docserver_id, docserver_type_id, device_label, is_readonly, size_limit_number, actual_size_number, path_template, creation_date, coll_id)
+VALUES ('FULLTEXT_DOCUMENT', 'FULLTEXT', 'Full text indexes for documents', 'N', 50000000000, 0, '/opt/maarch/docservers/indexes/documents/', '2019-11-01 12:00:00.123456', 'letterbox_coll');
+INSERT INTO docservers (docserver_id, docserver_type_id, device_label, is_readonly, size_limit_number, actual_size_number, path_template, creation_date, coll_id)
+VALUES ('FULLTEXT_ATTACHMENT', 'FULLTEXT', 'Full text indexes for attachments', 'N', 50000000000, 0, '/opt/maarch/docservers/indexes/attachments/', '2019-11-01 12:00:00.123456', 'attachments_coll');
+UPDATE docserver_types SET fingerprint_mode = NULL WHERE docserver_type_id = 'FULLTEXT';
+UPDATE res_letterbox SET fulltext_result = 'SUCCESS' WHERE fulltext_result = '1' OR fulltext_result = '2';
+UPDATE res_letterbox SET fulltext_result = 'ERROR' WHERE fulltext_result = '-1' OR fulltext_result = '-2';
+UPDATE res_attachments SET fulltext_result = 'SUCCESS' WHERE fulltext_result = '1' OR fulltext_result = '2';
+UPDATE res_attachments SET fulltext_result = 'ERROR' WHERE fulltext_result = '-1' OR fulltext_result = '-2';
+
+
+/* GROUPS INDEXING */
+ALTER TABLE usergroups ALTER COLUMN group_desc DROP DEFAULT;
+ALTER TABLE usergroups DROP COLUMN IF EXISTS can_index;
+ALTER TABLE usergroups ADD COLUMN can_index boolean NOT NULL DEFAULT FALSE;
+ALTER TABLE usergroups DROP COLUMN IF EXISTS indexation_parameters;
+ALTER TABLE usergroups ADD COLUMN indexation_parameters jsonb NOT NULL DEFAULT '{"actions" : [], "entities" : [], "keywords" : []}';
+
+
+/* BASKETS LIST EVENT */
+ALTER TABLE groupbasket DROP COLUMN IF EXISTS list_event;
+ALTER TABLE groupbasket ADD COLUMN list_event character varying(255);
+UPDATE groupbasket SET list_event = 'processDocument'
+FROM (
+       SELECT basket_id, group_id
+       FROM actions_groupbaskets ag
+         LEFT JOIN actions a ON ag.id_action = a.id
+       WHERE ag.default_action_list = 'Y' AND a.action_page in ('validate_mail', 'process')
+     ) AS subquery
+WHERE groupbasket.basket_id = subquery.basket_id AND groupbasket.group_id = subquery.group_id;
+UPDATE groupbasket SET list_event = 'viewDoc'
+FROM (
+       SELECT basket_id, group_id
+       FROM actions_groupbaskets ag
+         LEFT JOIN actions a ON ag.id_action = a.id
+       WHERE ag.default_action_list = 'Y' AND a.component = 'viewDoc'
+     ) AS subquery
+WHERE groupbasket.basket_id = subquery.basket_id AND groupbasket.group_id = subquery.group_id;
+UPDATE groupbasket SET list_event = 'signatureBookAction'
+FROM (
+       SELECT basket_id, group_id
+       FROM actions_groupbaskets ag
+         LEFT JOIN actions a ON ag.id_action = a.id
+       WHERE ag.default_action_list = 'Y' AND a.action_page in ('visa_mail')
+     ) AS subquery
+WHERE groupbasket.basket_id = subquery.basket_id AND groupbasket.group_id = subquery.group_id;
+ALTER TABLE groupbasket DROP COLUMN IF EXISTS list_event_data;
+ALTER TABLE groupbasket ADD COLUMN list_event_data jsonb;
+
+update groupbasket set list_event_data = '{"canUpdate":true,"defaultTab":"info"}'
+where group_id in (
+    select group_id
+    from actions_groupbaskets
+    where id_action in (
+        select id
+        from actions
+        where action_page = 'validate_mail'
+    ) and groupbasket.basket_id = actions_groupbaskets.basket_id
+);
+
+-- /!\ Do not move : update actions AFTER all updates on groupbasket
+DELETE FROM actions WHERE component = 'viewDoc' OR action_page in ('view', 'validate_mail', 'process', 'visa_mail');
+
+UPDATE actions SET component = 'rejectVisaBackToPreviousAction' WHERE action_page = 'rejection_visa_previous';
+UPDATE actions SET component = 'redirectInitiatorEntityAction' WHERE action_page = 'redirect_visa_entity';
+UPDATE actions SET component = 'rejectVisaBackToPreviousAction' WHERE action_page = 'rejection_visa_previous';
+UPDATE actions SET component = 'rejectVisaBackToRedactorAction' WHERE action_page = 'rejection_visa_redactor';
+UPDATE actions SET component = 'interruptVisaAction' WHERE action_page = 'interrupt_visa';
+UPDATE actions SET component = 'sendSignatureBookAction' WHERE action_page IN ('send_to_visa', 'send_signed_docs');
+UPDATE actions SET component = 'continueCircuitAction' WHERE action_page = 'visa_workflow';
+
+/* FOLDERS */
+DO $$ BEGIN
+  IF (SELECT count(attname) FROM pg_attribute WHERE attrelid = (SELECT oid FROM pg_class WHERE relname = 'folders') AND attname = 'folders_system_id') THEN
+    ALTER TABLE folders RENAME TO folder_tmp;
+    ALTER TABLE folder_tmp RENAME CONSTRAINT folders_pkey to folders_tmp_pkey;
+  END IF;
+END$$;
+
+DROP TABLE IF EXISTS folders;
+CREATE TABLE folders
+(
+  id serial NOT NULL,
+  label character varying(255) NOT NULL,
+  public boolean NOT NULL,   
+  user_id INTEGER NOT NULL,
+  parent_id INTEGER,
+  level INTEGER NOT NULL,
+  CONSTRAINT folders_pkey PRIMARY KEY (id)
+)
+WITH (OIDS=FALSE);
+
+DROP TABLE IF EXISTS resources_folders;
+CREATE TABLE resources_folders
+(
+  id serial NOT NULL,
+  folder_id INTEGER NOT NULL,
+  res_id INTEGER NOT NULL,
+  CONSTRAINT resources_folders_pkey PRIMARY KEY (id),
+  CONSTRAINT resources_folders_unique_key UNIQUE (folder_id, res_id)
+)
+WITH (OIDS=FALSE);
+
+DROP TABLE IF EXISTS entities_folders;
+CREATE TABLE entities_folders
+(
+  id serial NOT NULL,
+  folder_id INTEGER NOT NULL,
+  entity_id INTEGER NOT NULL,
+  edition boolean NOT NULL,
+  CONSTRAINT entities_folders_pkey PRIMARY KEY (id),
+  CONSTRAINT entities_folders_unique_key UNIQUE (folder_id, entity_id)
+)
+WITH (OIDS=FALSE);
+
+DROP TABLE IF EXISTS users_pinned_folders;
+CREATE TABLE users_pinned_folders
+(
+  id serial NOT NULL,
+  folder_id INTEGER NOT NULL,
+  user_id INTEGER NOT NULL,
+  CONSTRAINT users_pinned_folders_pkey PRIMARY KEY (id),
+  CONSTRAINT users_pinned_folders_unique_key UNIQUE (folder_id, user_id)
+)
+WITH (OIDS=FALSE);
+
+
+/* CUSTOM FIELDS */
+DROP TABLE IF EXISTS custom_fields;
+CREATE TABLE custom_fields
+(
+  id serial NOT NULL,
+  label character varying(256) NOT NULL,
+  type character varying(256) NOT NULL,
+  values jsonb,
+  CONSTRAINT custom_fields_pkey PRIMARY KEY (id),
+  CONSTRAINT custom_fields_unique_key UNIQUE (label)
+)
+WITH (OIDS=FALSE);
+
+/* CONTACTS CUSTOM FIELDS */
+DROP TABLE IF EXISTS contacts_custom_fields_list;
+CREATE TABLE contacts_custom_fields_list
+(
+  id serial NOT NULL,
+  label character varying(256) NOT NULL,
+  type character varying(256) NOT NULL,
+  values jsonb,
+  CONSTRAINT contacts_custom_fields_list_pkey PRIMARY KEY (id),
+  CONSTRAINT contacts_custom_fields_list_unique_key UNIQUE (label)
+)
+WITH (OIDS=FALSE);
+
+
+/* INDEXING MODELS */
+DROP TABLE IF EXISTS indexing_models;
+CREATE TABLE indexing_models
+(
+  id SERIAL NOT NULL,
+  label character varying(256) NOT NULL,
+  category character varying(256) NOT NULL,
+  "default" BOOLEAN NOT NULL,
+  owner INTEGER NOT NULL,
+  private BOOLEAN NOT NULL,
+  master INTEGER DEFAULT NULL,
+  enabled BOOLEAN DEFAULT TRUE NOT NULL,
+  CONSTRAINT indexing_models_pkey PRIMARY KEY (id)
+)
+WITH (OIDS=FALSE);
+
+DROP TABLE IF EXISTS indexing_models_fields;
+CREATE TABLE indexing_models_fields
+(
+  id SERIAL NOT NULL,
+  model_id INTEGER NOT NULL,
+  identifier text NOT NULL,
+  mandatory BOOLEAN NOT NULL,
+  default_value json,
+  unit text NOT NULL,
+  CONSTRAINT indexing_models_fields_pkey PRIMARY KEY (id)
+)
+WITH (OIDS=FALSE);
+
+
+/* TAGS */
+DO $$ BEGIN
+  IF (SELECT count(attname) FROM pg_attribute WHERE attrelid = (SELECT oid FROM pg_class WHERE relname = 'tags') AND attname = 'tag_label') = 1 THEN
+	  ALTER TABLE tags RENAME COLUMN tag_label TO label;
+	  ALTER TABLE tags DROP COLUMN IF EXISTS coll_id;
+	  ALTER TABLE tags ADD COLUMN id serial NOT NULL;
+	  UPDATE tags SET id = tag_id;
+      ALTER TABLE tags DROP COLUMN IF EXISTS tag_id;
+  END IF;
+END$$;
+SELECT setval('tags_id_seq', (SELECT MAX(id) from tags));
+
+DROP TABLE IF EXISTS tags_entities;
+
+/* DOCTYPES */
+DO $$ BEGIN
+  IF (SELECT count(attname) FROM pg_attribute WHERE attrelid = (SELECT oid FROM pg_class WHERE relname = 'mlb_doctype_ext')) > 0 THEN
+	  ALTER TABLE doctypes ADD COLUMN process_delay INTEGER;
+	  ALTER TABLE doctypes ADD COLUMN delay1 INTEGER;
+	  ALTER TABLE doctypes ADD COLUMN delay2 INTEGER;
+	  ALTER TABLE doctypes ADD COLUMN process_mode CHARACTER VARYING(256);
+	  UPDATE doctypes SET process_delay = (SELECT process_delay FROM mlb_doctype_ext where doctypes.type_id = mlb_doctype_ext.type_id);
+	  UPDATE doctypes SET process_delay = 30 WHERE process_delay is null;
+	  UPDATE doctypes SET delay1 = (SELECT delay1 FROM mlb_doctype_ext where doctypes.type_id = mlb_doctype_ext.type_id);
+    UPDATE doctypes SET delay1 = 14 WHERE delay1 is null;
+	  UPDATE doctypes SET delay2 = (SELECT delay2 FROM mlb_doctype_ext where doctypes.type_id = mlb_doctype_ext.type_id);
+    UPDATE doctypes SET delay2 = 1 WHERE delay2 is null;
+	  UPDATE doctypes SET process_mode = (SELECT process_mode FROM mlb_doctype_ext where doctypes.type_id = mlb_doctype_ext.type_id);
+    UPDATE doctypes SET process_mode = 'NORMAL' WHERE process_mode is null;
+	  ALTER TABLE doctypes ALTER COLUMN process_delay SET NOT NULL;
+	  ALTER TABLE doctypes ALTER COLUMN delay1 SET NOT NULL;
+	  ALTER TABLE doctypes ALTER COLUMN delay2 SET NOT NULL;
+	  ALTER TABLE doctypes ALTER COLUMN process_mode SET NOT NULL;
+  END IF;
+END$$;
+
+
+/* NOTES */
+DO $$ BEGIN
+    IF (SELECT count(attname) FROM pg_attribute WHERE attrelid = (SELECT oid FROM pg_class WHERE relname = 'notes') AND attname = 'type') THEN
+        ALTER TABLE notes ADD COLUMN user_tmp_id integer;
+        UPDATE notes set user_tmp_id = (select id FROM users where users.user_id = notes.user_id);
+        UPDATE notes set user_tmp_id = 0 WHERE user_tmp_id IS NULL;
+        ALTER TABLE notes ALTER COLUMN user_tmp_id set not null;
+        ALTER TABLE notes DROP COLUMN IF EXISTS user_id;
+        ALTER TABLE notes RENAME COLUMN user_tmp_id TO user_id;
+        ALTER TABLE notes DROP COLUMN IF EXISTS type;
+    END IF;
+END$$;
+
+
+/* ATTACHMENTS */
+ALTER TABLE res_attachments DROP COLUMN IF EXISTS origin_id;
+ALTER TABLE res_attachments ADD COLUMN origin_id INTEGER;
+DO $$ BEGIN
+    IF (SELECT count(attname) FROM pg_attribute WHERE attrelid = (SELECT oid FROM pg_class WHERE relname = 'res_attachments') AND attname = 'doc_date') THEN
+        ALTER TABLE res_attachments RENAME COLUMN doc_date TO modification_date;
+        ALTER TABLE res_attachments ALTER COLUMN modification_date set DEFAULT NOW();
+    END IF;
+END$$;
+DO $$ BEGIN
+    IF (SELECT count(attname) FROM pg_attribute WHERE attrelid = (SELECT oid FROM pg_class WHERE relname = 'res_attachments') AND attname = 'updated_by') THEN
+        ALTER TABLE res_attachments ADD COLUMN modified_by integer;
+        UPDATE res_attachments set modified_by = (select id FROM users where users.user_id = res_attachments.updated_by);
+        ALTER TABLE res_attachments DROP COLUMN IF EXISTS updated_by;
+    END IF;
+END$$;
+
+
+/* DOCSERVERS */
+UPDATE docservers SET coll_id = 'attachments_coll', is_readonly = 'Y' WHERE coll_id = 'attachments_version_coll' AND docserver_type_id = 'CONVERT';
+UPDATE docservers SET coll_id = 'attachments_coll', is_readonly = 'Y' WHERE coll_id = 'attachments_version_coll' AND docserver_type_id = 'FASTHD';
+UPDATE docservers SET coll_id = 'attachments_coll', is_readonly = 'Y' WHERE coll_id = 'attachments_version_coll' AND docserver_type_id = 'FULLTEXT';
+UPDATE docservers SET coll_id = 'attachments_coll', is_readonly = 'Y' WHERE coll_id = 'attachments_version_coll' AND docserver_type_id = 'TNL';
+UPDATE docservers SET docserver_type_id = 'DOC' WHERE coll_id = 'attachments_coll' AND docserver_type_id = 'FASTHD';
+
+
+/* MLB COLL EXT */
+DO $$ BEGIN
+    IF (SELECT count(attname) FROM pg_attribute WHERE attrelid = (SELECT oid FROM pg_class WHERE relname = 'res_letterbox') AND attname = 'category_id') = 0 THEN
+        ALTER TABLE res_letterbox ADD COLUMN category_id character varying(32);
+        UPDATE res_letterbox SET category_id = mlb_coll_ext.category_id FROM mlb_coll_ext WHERE res_letterbox.res_id = mlb_coll_ext.res_id;
+        UPDATE res_letterbox set category_id = 'incoming' WHERE category_id IS NULL;
+        ALTER TABLE res_letterbox ALTER COLUMN category_id set not null;
+
+        ALTER TABLE res_letterbox ADD COLUMN exp_contact_id integer;
+        UPDATE res_letterbox SET exp_contact_id = mlb_coll_ext.exp_contact_id FROM mlb_coll_ext WHERE res_letterbox.res_id = mlb_coll_ext.res_id;
+
+        ALTER TABLE res_letterbox ADD COLUMN exp_user_id character varying(128);
+        UPDATE res_letterbox SET exp_user_id = mlb_coll_ext.exp_user_id FROM mlb_coll_ext WHERE res_letterbox.res_id = mlb_coll_ext.res_id;
+
+        ALTER TABLE res_letterbox ADD COLUMN dest_contact_id integer;
+        UPDATE res_letterbox SET dest_contact_id = mlb_coll_ext.dest_contact_id FROM mlb_coll_ext WHERE res_letterbox.res_id = mlb_coll_ext.res_id;
+
+        ALTER TABLE res_letterbox ADD COLUMN dest_user_id character varying(128);
+        UPDATE res_letterbox SET dest_user_id = mlb_coll_ext.dest_user_id FROM mlb_coll_ext WHERE res_letterbox.res_id = mlb_coll_ext.res_id;
+
+        ALTER TABLE res_letterbox ADD COLUMN alt_identifier character varying(256);
+        UPDATE res_letterbox SET alt_identifier = mlb_coll_ext.alt_identifier FROM mlb_coll_ext WHERE res_letterbox.res_id = mlb_coll_ext.res_id;
+
+        ALTER TABLE res_letterbox ADD COLUMN admission_date timestamp without time zone;
+        UPDATE res_letterbox SET admission_date = mlb_coll_ext.admission_date FROM mlb_coll_ext WHERE res_letterbox.res_id = mlb_coll_ext.res_id;
+
+        ALTER TABLE res_letterbox ADD COLUMN process_limit_date timestamp without time zone;
+        UPDATE res_letterbox SET process_limit_date = mlb_coll_ext.process_limit_date FROM mlb_coll_ext WHERE res_letterbox.res_id = mlb_coll_ext.res_id;
+
+        ALTER TABLE res_letterbox ADD COLUMN closing_date timestamp without time zone;
+        UPDATE res_letterbox SET closing_date = mlb_coll_ext.closing_date FROM mlb_coll_ext WHERE res_letterbox.res_id = mlb_coll_ext.res_id;
+
+        ALTER TABLE res_letterbox ADD COLUMN flag_alarm1 character(1) DEFAULT 'N'::character varying;
+        UPDATE res_letterbox SET flag_alarm1 = mlb_coll_ext.flag_alarm1 FROM mlb_coll_ext WHERE res_letterbox.res_id = mlb_coll_ext.res_id;
+
+        ALTER TABLE res_letterbox ADD COLUMN flag_alarm2 character(1) DEFAULT 'N'::character varying;
+        UPDATE res_letterbox SET flag_alarm2 = mlb_coll_ext.flag_alarm2 FROM mlb_coll_ext WHERE res_letterbox.res_id = mlb_coll_ext.res_id;
+
+        ALTER TABLE res_letterbox ADD COLUMN is_multicontacts character(1);
+        UPDATE res_letterbox SET is_multicontacts = mlb_coll_ext.is_multicontacts FROM mlb_coll_ext WHERE res_letterbox.res_id = mlb_coll_ext.res_id;
+
+        ALTER TABLE res_letterbox ADD COLUMN address_id INTEGER;
+        UPDATE res_letterbox SET address_id = mlb_coll_ext.address_id FROM mlb_coll_ext WHERE res_letterbox.res_id = mlb_coll_ext.res_id;
+
+        ALTER TABLE res_letterbox ADD COLUMN alarm1_date timestamp without time zone;
+        UPDATE res_letterbox SET alarm1_date = mlb_coll_ext.alarm1_date FROM mlb_coll_ext WHERE res_letterbox.res_id = mlb_coll_ext.res_id;
+
+        ALTER TABLE res_letterbox ADD COLUMN alarm2_date timestamp without time zone;
+        UPDATE res_letterbox SET alarm2_date = mlb_coll_ext.alarm2_date FROM mlb_coll_ext WHERE res_letterbox.res_id = mlb_coll_ext.res_id;
+    END IF;
+END$$;
+
+
+/* RES_LETTERBOX */
+ALTER TABLE res_letterbox DROP COLUMN IF EXISTS model_id;
+ALTER TABLE res_letterbox ADD COLUMN model_id INTEGER;
+UPDATE res_letterbox set model_id = 2 WHERE category_id = 'outgoing';
+UPDATE res_letterbox set model_id = 3 WHERE category_id = 'internal';
+UPDATE res_letterbox set model_id = 4 WHERE category_id = 'ged_doc';
+UPDATE res_letterbox set model_id = 1 WHERE model_id IS NULL;
+ALTER TABLE res_letterbox ALTER COLUMN model_id set not null;
+DO $$ BEGIN
+    IF (SELECT count(column_name) from information_schema.columns where table_name = 'res_letterbox' and column_name = 'typist' and data_type != 'integer') THEN
+        ALTER TABLE res_letterbox ADD COLUMN typist_tmp integer;
+        UPDATE res_letterbox set typist_tmp = (select id FROM users where users.user_id = res_letterbox.typist);
+        UPDATE res_letterbox set typist_tmp = 0 WHERE typist_tmp IS NULL;
+        ALTER TABLE res_letterbox ALTER COLUMN typist_tmp set not null;
+        ALTER TABLE res_letterbox DROP COLUMN IF EXISTS typist;
+        ALTER TABLE res_letterbox RENAME COLUMN typist_tmp TO typist;
+        UPDATE baskets SET basket_clause = REGEXP_REPLACE(basket_clause, 'typist(\s*)=(\s*)@user', 'typist = @user_id', 'gmi');
+        UPDATE security SET where_clause = REGEXP_REPLACE(where_clause, 'typist(\s*)=(\s*)@user', 'typist = @user_id', 'gmi');
+    END IF;
+END$$;
+ALTER TABLE res_letterbox ADD COLUMN IF NOT EXISTS scan_date timestamp without time zone;
+ALTER TABLE res_letterbox ADD COLUMN IF NOT EXISTS scan_user CHARACTER VARYING (50) DEFAULT NULL::character varying;
+ALTER TABLE res_letterbox ADD COLUMN IF NOT EXISTS scan_location CHARACTER VARYING (255) DEFAULT NULL::character varying;
+ALTER TABLE res_letterbox ADD COLUMN IF NOT EXISTS scan_wkstation CHARACTER VARYING (255) DEFAULT NULL::character varying;
+ALTER TABLE res_letterbox ADD COLUMN IF NOT EXISTS scan_batch CHARACTER VARYING (50) DEFAULT NULL::character varying;
+ALTER TABLE res_letterbox ADD COLUMN IF NOT EXISTS scan_postmark CHARACTER VARYING (50) DEFAULT NULL::character varying;
+ALTER TABLE res_letterbox ADD COLUMN IF NOT EXISTS custom_fields jsonb;
+
+ALTER TABLE res_letterbox DROP COLUMN IF EXISTS linked_resources;
+ALTER TABLE res_letterbox ADD COLUMN linked_resources jsonb NOT NULL DEFAULT '[]';
+
+
+/* USERGROUP_CONTENT */
+DO $$ BEGIN
+    IF (SELECT count(column_name) from information_schema.columns where table_name = 'usergroup_content' and column_name = 'user_id' and data_type != 'integer') THEN
+        ALTER TABLE usergroup_content ADD COLUMN user_id_tmp integer;
+        UPDATE usergroup_content set user_id_tmp = (select id FROM users where users.user_id = usergroup_content.user_id);
+        DELETE FROM usergroup_content WHERE user_id_tmp IS NULL;
+        ALTER TABLE usergroup_content ALTER COLUMN user_id_tmp set not null;
+        ALTER TABLE usergroup_content DROP COLUMN IF EXISTS user_id;
+        ALTER TABLE usergroup_content RENAME COLUMN user_id_tmp TO user_id;
+    END IF;
+END$$;
+DO $$ BEGIN
+    IF (SELECT count(column_name) from information_schema.columns where table_name = 'usergroup_content' and column_name = 'group_id' and data_type != 'integer') THEN
+        ALTER TABLE usergroup_content ADD COLUMN group_id_tmp integer;
+        UPDATE usergroup_content set group_id_tmp = (select id FROM usergroups where usergroups.group_id = usergroup_content.group_id);
+        DELETE FROM usergroup_content WHERE group_id_tmp IS NULL;
+        ALTER TABLE usergroup_content ALTER COLUMN group_id_tmp set not null;
+        ALTER TABLE usergroup_content DROP COLUMN IF EXISTS group_id;
+        ALTER TABLE usergroup_content RENAME COLUMN group_id_tmp TO group_id;
+    END IF;
+END$$;
+
+
+/* CONTACTS */
+DROP TABLE IF EXISTS contacts;
+CREATE TABLE contacts
+(
+    id SERIAL NOT NULL,
+    civility CHARACTER VARYING(256),
+    firstname CHARACTER VARYING(256),
+    lastname CHARACTER VARYING(256),
+    company CHARACTER VARYING(256),
+    department CHARACTER VARYING(256),
+    function CHARACTER VARYING(256),
+    address_number CHARACTER VARYING(256),
+    address_street CHARACTER VARYING(256),
+    address_additional1 CHARACTER VARYING(256),
+    address_additional2 CHARACTER VARYING(256),
+    address_postcode CHARACTER VARYING(256),
+    address_town CHARACTER VARYING(256),
+    address_country CHARACTER VARYING(256),
+    email CHARACTER VARYING(256),
+    phone CHARACTER VARYING(256),
+    communication_means jsonb,
+    notes text,
+    creator INTEGER NOT NULL,
+    creation_date TIMESTAMP without time zone NOT NULL DEFAULT NOW(),
+    modification_date TIMESTAMP without time zone,
+    enabled boolean NOT NULL DEFAULT TRUE,
+    custom_fields jsonb,
+    external_id jsonb DEFAULT '{}',
+    CONSTRAINT contacts_pkey PRIMARY KEY (id)
+)
+WITH (OIDS=FALSE);
+
+DROP TABLE IF EXISTS contacts_parameters;
+CREATE TABLE contacts_parameters
+(
+    id SERIAL NOT NULL,
+    identifier text NOT NULL,
+    mandatory boolean NOT NULL DEFAULT FALSE,
+    filling boolean NOT NULL DEFAULT FALSE,
+    searchable boolean NOT NULL DEFAULT FALSE,
+    displayable boolean NOT NULL DEFAULT FALSE,
+    CONSTRAINT contacts_parameters_pkey PRIMARY KEY (id)
+)
+WITH (OIDS=FALSE);
+
+ALTER TABLE acknowledgement_receipts DROP COLUMN IF EXISTS contact_id;
+ALTER TABLE acknowledgement_receipts ADD COLUMN contact_id integer;
+ALTER TABLE contacts_groups_lists DROP COLUMN IF EXISTS contact_id;
+ALTER TABLE contacts_groups_lists ADD COLUMN contact_id integer;
+ALTER TABLE res_attachments DROP COLUMN IF EXISTS recipient_type;
+ALTER TABLE res_attachments ADD COLUMN recipient_type character varying(256);
+ALTER TABLE res_attachments DROP COLUMN IF EXISTS recipient_id;
+ALTER TABLE res_attachments ADD COLUMN recipient_id integer;
+
+/* REFACTORING DATA */
+DO $$ BEGIN
+  IF (SELECT count(attname) FROM pg_attribute WHERE attrelid = (SELECT oid FROM pg_class WHERE relname = 'usergroups') AND attname = 'enabled') THEN
+    DELETE FROM usergroup_content WHERE group_id in (SELECT id FROM usergroups WHERE enabled = 'N');
+    DELETE FROM usergroups_reports WHERE group_id in (SELECT group_id FROM usergroups WHERE enabled = 'N');
+    DELETE FROM usergroups_services WHERE group_id in (SELECT group_id FROM usergroups WHERE enabled = 'N');
+    DELETE FROM security WHERE group_id in (SELECT group_id FROM usergroups WHERE enabled = 'N');
+    DELETE FROM groupbasket WHERE group_id in (SELECT group_id FROM usergroups WHERE enabled = 'N');
+    DELETE FROM groupbasket_redirect WHERE group_id in (SELECT group_id FROM usergroups WHERE enabled = 'N');
+    DELETE FROM groupbasket_status WHERE group_id in (SELECT group_id FROM usergroups WHERE enabled = 'N');
+    DELETE FROM users_baskets_preferences WHERE group_serial_id in (SELECT id FROM usergroups WHERE enabled = 'N');
+    DELETE FROM usergroups WHERE enabled = 'N';
+  END IF;
+END$$;
+
+DO $$ BEGIN
+  IF (SELECT count(attname) FROM pg_attribute WHERE attrelid = (SELECT oid FROM pg_class WHERE relname = 'actions') AND attname = 'enabled') THEN
+    DELETE FROM actions_categories WHERE action_id in (SELECT id FROM actions WHERE enabled = 'N');
+    DELETE FROM actions_groupbaskets WHERE id_action in (SELECT id FROM actions WHERE enabled = 'N');
+    DELETE FROM groupbasket_redirect WHERE action_id in (SELECT id FROM actions WHERE enabled = 'N');
+    DELETE FROM actions WHERE enabled = 'N';
+  END IF;
+END$$;
+
+DELETE FROM usergroups_services WHERE service_id = 'admin_fileplan';
+DELETE FROM usergroups_services WHERE service_id = 'put_doc_in_fileplan';
+DELETE FROM usergroups_services WHERE service_id = 'fileplan';
+DELETE FROM usergroups_services WHERE service_id = 'update_case';
+DELETE FROM usergroups_services WHERE service_id = 'join_res_case';
+DELETE FROM usergroups_services WHERE service_id = 'join_res_case_in_process';
+DELETE FROM usergroups_services WHERE service_id = 'close_case';
+DELETE FROM usergroups_services WHERE service_id = 'add_cases';
+DELETE FROM usergroups_services WHERE service_id IN ('folder_search', 'view_folder_tree', 'select_folder', 'show_history_folder', 'modify_folder', 'associate_folder', 'delete_folder', 'admin_foldertypes', 'create_folder', 'folder_freeze', 'close_folder');
+DELETE FROM usergroups_services WHERE service_id = 'add_tag_to_res';
+DELETE FROM usergroups_services WHERE service_id = 'tag_view';
+DELETE FROM usergroups_services WHERE service_id = 'admin_thesaurus';
+DELETE FROM usergroups_services WHERE service_id = 'thesaurus_view';
+DELETE FROM usergroups_services WHERE service_id = 'add_thesaurus_to_res';
+UPDATE usergroups_services SET service_id = 'manage_tags_application' WHERE service_id = 'create_tag';
+UPDATE usergroups_services SET service_id = 'update_status_mail' WHERE service_id = 'reopen_mail';
+DELETE FROM usergroups_services WHERE service_id = 'quicklaunch';
+DELETE FROM usergroups_services WHERE service_id = 'put_in_validation';
+DELETE FROM usergroups_services WHERE service_id = 'print_details';
+DELETE FROM usergroups_services WHERE service_id = 'print_doc_details_from_list';
+DELETE FROM usergroups_services WHERE service_id = 'view_attachments';
+DELETE FROM usergroups_services WHERE service_id = 'manage_attachments';
+DELETE FROM usergroups_services WHERE service_id = 'index_attachment';
+DELETE FROM usergroups_services WHERE service_id = 'display_basket_list';
+DELETE FROM usergroups_services WHERE service_id = 'choose_entity';
+DELETE FROM usergroups_services WHERE service_id = 'export_seda_view';
+DELETE FROM usergroups_services WHERE service_id = 'manage_notes_doc';
+DELETE FROM usergroups_services WHERE service_id = 'notes_restriction';
+DELETE FROM usergroups_services WHERE service_id = 'graphics_reports';
+DELETE FROM usergroups_services WHERE service_id = 'show_reports';
+DELETE FROM usergroups_services WHERE service_id = 'param_templates_doctypes';
+DELETE FROM usergroups_services WHERE service_id = 'doctype_template_use';
+DELETE FROM usergroups_services WHERE service_id = 'search_contacts';
+
+INSERT INTO usergroups_services (group_id, service_id)
+SELECT distinct(group_id), 'update_diffusion_indexing'
+FROM usergroups_services WHERE service_id = 'edit_recipient_outside_process';
+INSERT INTO usergroups_services (group_id, service_id)
+SELECT distinct(group_id), 'update_diffusion_details'
+FROM usergroups_services WHERE service_id = 'edit_recipient_outside_process';
+INSERT INTO usergroups_services (group_id, service_id)
+SELECT distinct(group_id), 'update_diffusion_except_recipient_details'
+FROM usergroups_services WHERE service_id = 'update_list_diff_in_details';
+
+INSERT INTO usergroups_services (group_id, service_id)
+SELECT distinct(group_id), 'update_diffusion_except_recipient_indexing'
+FROM usergroups_services WHERE group_id NOT IN (
+SELECT group_id FROM usergroups_services
+WHERE service_id = 'edit_recipient_outside_process' OR service_id = 'update_diffusion_indexing' OR service_id = 'update_diffusion_except_recipient_indexing'
+);
+DELETE FROM usergroups_services WHERE service_id = 'edit_recipient_outside_process';
+DELETE FROM usergroups_services WHERE service_id = 'update_list_diff_in_details';
+DELETE FROM usergroups_services WHERE service_id = 'edit_recipient_in_process';
+UPDATE usergroups_services SET service_id = 'edit_resource' WHERE service_id = 'edit_document_in_detail';
+
+UPDATE usergroups_services SET service_id = 'manage_own_attachments_in_details' WHERE service_id = 'edit_attachments_from_detail';
+INSERT INTO usergroups_services (group_id, service_id)
+SELECT distinct(group_id), 'manage_attachments'
+FROM usergroups_services WHERE group_id IN (
+    SELECT group_id FROM usergroups_services
+    WHERE service_id = 'modify_attachments' OR service_id = 'delete_attachments'
+);
+DELETE FROM usergroups_services WHERE service_id = 'modify_attachments';
+DELETE FROM usergroups_services WHERE service_id = 'delete_attachments';
+
+ALTER TABLE usergroups_services DROP COLUMN IF EXISTS parameters;
+ALTER TABLE usergroups_services ADD parameters jsonb;
+UPDATE usergroups_services SET parameters = (
+    cast('{"groups": [' || (
+        SELECT string_agg(cast(id AS VARCHAR), ', ' ORDER BY id) FROM usergroups
+    ) || ']}' AS jsonb)
+    )
+WHERE service_id = 'admin_users';
+
+DELETE FROM usergroups_services WHERE service_id = 'view_personal_data' or service_id = 'manage_personal_data';
+INSERT INTO usergroups_services (group_id, service_id)
+SELECT distinct(group_id), 'view_personal_data'
+FROM usergroups_services WHERE group_id IN (
+    SELECT group_id FROM usergroups_services
+    WHERE service_id = 'admin_users'
+);
+INSERT INTO usergroups_services (group_id, service_id)
+SELECT distinct(group_id), 'manage_personal_data'
+FROM usergroups_services WHERE group_id IN (
+    SELECT group_id FROM usergroups_services
+    WHERE service_id = 'admin_users'
+);
+
+
+UPDATE listmodels SET title = object_id WHERE title IS NULL;
+UPDATE baskets SET basket_clause = REGEXP_REPLACE(basket_clause, 'coll_id(\s*)=(\s*)''letterbox_coll''(\s*)AND', '', 'gmi') WHERE basket_id in ('CopyMailBasket', 'DdeAvisBasket');
+UPDATE baskets SET basket_clause = REGEXP_REPLACE(basket_clause, 'coll_id(\s*)=(\s*)''letterbox_coll''(\s*)and', '', 'gmi') WHERE basket_id in ('CopyMailBasket', 'DdeAvisBasket');
+
+/* ListTemplates */
+DROP TABLE IF EXISTS list_templates;
+CREATE TABLE list_templates
+(
+    id SERIAL NOT NULL,
+    title text NOT NULL,
+    description text,
+    type CHARACTER VARYING(32) NOT NULL,
+    entity_id INTEGER,
+    owner INTEGER DEFAULT NULL,
+    CONSTRAINT list_templates_pkey PRIMARY KEY (id)
+)
+WITH (OIDS=FALSE);
+
+DROP TABLE IF EXISTS list_templates_items;
+CREATE TABLE list_templates_items
+(
+    id SERIAL NOT NULL,
+    list_template_id INTEGER NOT NULL,
+    item_id INTEGER NOT NULL,
+    item_type CHARACTER VARYING(32) NOT NULL,
+    item_mode CHARACTER VARYING(64) NOT NULL,
+    sequence INTEGER NOT NULL,
+    CONSTRAINT list_templates_items_pkey PRIMARY KEY (id)
+)
+WITH (OIDS=FALSE);
+
+
+/* REFACTORING MODIFICATION */
+ALTER TABLE notif_email_stack ALTER COLUMN attachments TYPE text;
+ALTER TABLE tags ALTER COLUMN label TYPE character varying(128);
+UPDATE priorities SET delays = 30 WHERE delays IS NULL;
+ALTER TABLE priorities ALTER COLUMN delays SET NOT NULL;
+ALTER TABLE res_letterbox ALTER COLUMN status DROP NOT NULL;
+ALTER TABLE res_letterbox ALTER COLUMN docserver_id DROP NOT NULL;
+ALTER TABLE res_letterbox ALTER COLUMN format DROP NOT NULL;
+ALTER TABLE notif_email_stack ALTER COLUMN recipient TYPE text;
+ALTER TABLE notif_email_stack ALTER COLUMN cc TYPE text;
+ALTER TABLE notif_email_stack ALTER COLUMN bcc TYPE text;
+
+
+/* REFACTORING SUPPRESSION */
+DO $$ BEGIN
+  IF (SELECT count(attname) FROM pg_attribute WHERE attrelid = (SELECT oid FROM pg_class WHERE relname = 'users') AND attname = 'enabled') THEN
+    UPDATE users SET status = 'SPD' WHERE enabled = 'N' and (status = 'OK' or status = 'ABS');
+    ALTER TABLE users DROP COLUMN IF EXISTS enabled;
+  END IF;
+END$$;
+ALTER TABLE res_letterbox DROP COLUMN IF EXISTS converter_result;
+ALTER TABLE res_letterbox DROP COLUMN IF EXISTS convert_result;
+ALTER TABLE res_attachments DROP COLUMN IF EXISTS convert_result;
+ALTER TABLE res_letterbox DROP COLUMN IF EXISTS convert_attempts;
+ALTER TABLE res_attachments DROP COLUMN IF EXISTS convert_attempts;
+ALTER TABLE res_letterbox DROP COLUMN IF EXISTS fulltext_attempts;
+ALTER TABLE res_attachments DROP COLUMN IF EXISTS fulltext_attempts;
+ALTER TABLE res_letterbox DROP COLUMN IF EXISTS tnl_attempts;
+ALTER TABLE res_attachments DROP COLUMN IF EXISTS tnl_attempts;
+ALTER TABLE res_letterbox DROP COLUMN IF EXISTS tnl_result;
+ALTER TABLE res_attachments DROP COLUMN IF EXISTS tnl_result;
+ALTER TABLE res_attachments DROP COLUMN IF EXISTS coll_id;
+ALTER TABLE usergroups DROP COLUMN IF EXISTS enabled;
+ALTER TABLE actions DROP COLUMN IF EXISTS enabled;
+ALTER TABLE actions DROP COLUMN IF EXISTS origin;
+ALTER TABLE actions DROP COLUMN IF EXISTS create_id;
+ALTER TABLE actions DROP COLUMN IF EXISTS category_id;
+DROP VIEW IF EXISTS fp_view_fileplan;
+ALTER TABLE res_attachments DROP COLUMN IF EXISTS folders_system_id;
+DROP TABLE IF EXISTS foldertypes;
+DROP TABLE IF EXISTS foldertypes_doctypes;
+DROP TABLE IF EXISTS foldertypes_doctypes_level1;
+DROP TABLE IF EXISTS foldertypes_indexes;
+ALTER TABLE doctypes DROP COLUMN IF EXISTS coll_id;
+DROP TABLE IF EXISTS mlb_doctype_ext;
+ALTER TABLE priorities DROP COLUMN IF EXISTS working_days;
+DROP TABLE IF EXISTS thesaurus;
+DROP TABLE IF EXISTS thesaurus_res;
+DROP SEQUENCE IF EXISTS thesaurus_id_seq;
+ALTER TABLE res_letterbox DROP COLUMN IF EXISTS title;
+ALTER TABLE res_letterbox DROP COLUMN IF EXISTS identifier;
+ALTER TABLE res_letterbox DROP COLUMN IF EXISTS source;
+ALTER TABLE res_letterbox DROP COLUMN IF EXISTS relation;
+ALTER TABLE res_letterbox DROP COLUMN IF EXISTS offset_doc;
+ALTER TABLE res_letterbox DROP COLUMN IF EXISTS is_multi_docservers;
+ALTER TABLE res_letterbox DROP COLUMN IF EXISTS tablename;
+ALTER TABLE res_letterbox DROP COLUMN IF EXISTS validation_date;
+ALTER TABLE listinstance DROP COLUMN IF EXISTS added_by_entity;
+ALTER TABLE listinstance DROP COLUMN IF EXISTS coll_id;
+ALTER TABLE listinstance DROP COLUMN IF EXISTS listinstance_type;
+ALTER TABLE listinstance DROP COLUMN IF EXISTS visible;
+ALTER TABLE listinstance_history_details DROP COLUMN IF EXISTS added_by_entity;
+ALTER TABLE usergroup_content DROP COLUMN IF EXISTS primary_group;
+ALTER TABLE emails ALTER COLUMN document type jsonb;
+ALTER TABLE res_attachments DROP COLUMN IF EXISTS subject;
+ALTER TABLE res_attachments DROP COLUMN IF EXISTS description;
+ALTER TABLE res_attachments DROP COLUMN IF EXISTS type_id;
+ALTER TABLE res_attachments DROP COLUMN IF EXISTS author;
+ALTER TABLE res_attachments DROP COLUMN IF EXISTS source;
+ALTER TABLE res_attachments DROP COLUMN IF EXISTS folders_system_id;
+ALTER TABLE res_attachments DROP COLUMN IF EXISTS offset_doc;
+ALTER TABLE res_attachments DROP COLUMN IF EXISTS destination;
+ALTER TABLE res_attachments DROP COLUMN IF EXISTS priority;
+ALTER TABLE res_attachments DROP COLUMN IF EXISTS initiator;
+ALTER TABLE res_attachments DROP COLUMN IF EXISTS is_multicontacts;
+ALTER TABLE res_attachments DROP COLUMN IF EXISTS is_multi_docservers;
+ALTER TABLE res_attachments DROP COLUMN IF EXISTS tnl_path;
+ALTER TABLE res_attachments DROP COLUMN IF EXISTS tnl_filename;
+ALTER TABLE users DROP COLUMN IF EXISTS custom_t1;
+ALTER TABLE users DROP COLUMN IF EXISTS custom_t2;
+ALTER TABLE users DROP COLUMN IF EXISTS custom_t3;
+
+
+/* M2M */
+DO $$ BEGIN
+  IF (SELECT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'mlb_coll_ext')) THEN
+    UPDATE res_letterbox SET external_id = json_build_object('m2m', reference_number), reference_number = null FROM mlb_coll_ext WHERE res_letterbox.res_id = mlb_coll_ext.res_id AND mlb_coll_ext.nature_id = 'message_exchange';
+    UPDATE mlb_coll_ext SET nature_id = null WHERE nature_id = 'message_exchange';
+  END IF;
+END$$;
+
+/* DATA */
+TRUNCATE TABLE custom_fields;
+INSERT INTO custom_fields (id, label, type, values) VALUES (1, 'Nature', 'select', '["Courrier simple", "Courriel", "Courrier suivi", "Courrier avec AR", "Autre"]');
+SELECT setval('custom_fields_id_seq', (select max(id)+1 from custom_fields), false);
+
+DO $$ BEGIN
+  IF (SELECT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'mlb_coll_ext')) THEN
+    UPDATE res_letterbox SET custom_fields = json_build_object('1', 'Courrier simple') FROM mlb_coll_ext WHERE res_letterbox.res_id = mlb_coll_ext.res_id AND mlb_coll_ext.nature_id = 'simple_mail';
+    UPDATE res_letterbox SET custom_fields = json_build_object('1', 'Courriel') FROM mlb_coll_ext WHERE res_letterbox.res_id = mlb_coll_ext.res_id AND mlb_coll_ext.nature_id = 'email';
+    UPDATE res_letterbox SET custom_fields = json_build_object('1', 'Autre') FROM mlb_coll_ext WHERE res_letterbox.res_id = mlb_coll_ext.res_id AND mlb_coll_ext.nature_id in ('fax', 'other', 'courier');
+    UPDATE res_letterbox SET custom_fields = json_build_object('1', 'Courrier suivi') FROM mlb_coll_ext WHERE res_letterbox.res_id = mlb_coll_ext.res_id AND mlb_coll_ext.nature_id in ('chronopost', 'fedex');
+    UPDATE res_letterbox SET custom_fields = json_build_object('1', 'Courrier avec AR') FROM mlb_coll_ext WHERE res_letterbox.res_id = mlb_coll_ext.res_id AND mlb_coll_ext.nature_id = 'registered_mail';
+  END IF;
+END$$;
+UPDATE baskets set basket_clause = replace(basket_clause, 'nature_id' , 'custom_fields->>''1''');
+
+/* users followed resources */
+DROP TABLE IF EXISTS users_followed_resources;
+CREATE TABLE users_followed_resources
+(
+    id serial NOT NULL,
+    res_id int NOT NULL,
+    user_id int NOT NULL,
+    CONSTRAINT users_followed_resources_pkey PRIMARY KEY (id)
+)
+WITH (OIDS=FALSE);
+
+TRUNCATE TABLE indexing_models;
+INSERT INTO indexing_models (id, category, label, "default", owner, private) VALUES (1, 'incoming', 'Courrier arrivée', TRUE, 23, FALSE);
+INSERT INTO indexing_models (id, category, label, "default", owner, private) VALUES (2, 'outgoing', 'Courrier départ', FALSE, 23, FALSE);
+INSERT INTO indexing_models (id, category, label, "default", owner, private) VALUES (3, 'internal', 'Courrier interne', FALSE, 23, FALSE);
+INSERT INTO indexing_models (id, category, label, "default", owner, private) VALUES (4, 'ged_doc', 'Document ged', FALSE, 23, FALSE);
+Select setval('indexing_models_id_seq', (select max(id)+1 from indexing_models), false);
+
+TRUNCATE TABLE indexing_models_fields;
+/* Arrivée */
+INSERT INTO indexing_models_fields (model_id, identifier, mandatory, default_value, unit) VALUES (1, 'doctype', TRUE, null, 'mail');
+INSERT INTO indexing_models_fields (model_id, identifier, mandatory, default_value, unit) VALUES (1, 'priority', TRUE, null, 'mail');
+INSERT INTO indexing_models_fields (model_id, identifier, mandatory, default_value, unit) VALUES (1, 'confidentiality', TRUE, null, 'mail');
+INSERT INTO indexing_models_fields (model_id, identifier, mandatory, default_value, unit) VALUES (1, 'documentDate', TRUE, null, 'mail');
+INSERT INTO indexing_models_fields (model_id, identifier, mandatory, default_value, unit) VALUES (1, 'arrivalDate', TRUE, null, 'mail');
+INSERT INTO indexing_models_fields (model_id, identifier, mandatory, default_value, unit) VALUES (1, 'subject', TRUE, null, 'mail');
+INSERT INTO indexing_models_fields (model_id, identifier, mandatory, default_value, unit) VALUES (1, 'indexingCustomField_1', FALSE, '"Courrier simple"', 'mail');
+INSERT INTO indexing_models_fields (model_id, identifier, mandatory, default_value, unit) VALUES (1, 'senders', TRUE, null, 'contact');
+INSERT INTO indexing_models_fields (model_id, identifier, mandatory, default_value, unit) VALUES (1, 'recipients', FALSE, null, 'contact');
+INSERT INTO indexing_models_fields (model_id, identifier, mandatory, default_value, unit) VALUES (1, 'initiator', TRUE, null, 'process');
+INSERT INTO indexing_models_fields (model_id, identifier, mandatory, default_value, unit) VALUES (1, 'destination', TRUE, null, 'process');
+INSERT INTO indexing_models_fields (model_id, identifier, mandatory, default_value, unit) VALUES (1, 'processLimitDate', TRUE, null, 'process');
+INSERT INTO indexing_models_fields (model_id, identifier, mandatory, default_value, unit) VALUES (1, 'folders', FALSE, null, 'classifying');
+INSERT INTO indexing_models_fields (model_id, identifier, mandatory, default_value, unit) VALUES (1, 'tags', FALSE, null, 'classifying');
+
+/* Départ */
+INSERT INTO indexing_models_fields (model_id, identifier, mandatory, default_value, unit) VALUES (2, 'doctype', TRUE, null, 'mail');
+INSERT INTO indexing_models_fields (model_id, identifier, mandatory, default_value, unit) VALUES (2, 'priority', TRUE, null, 'mail');
+INSERT INTO indexing_models_fields (model_id, identifier, mandatory, default_value, unit) VALUES (2, 'confidentiality', TRUE, null, 'mail');
+INSERT INTO indexing_models_fields (model_id, identifier, mandatory, default_value, unit) VALUES (2, 'documentDate', TRUE, null, 'mail');
+INSERT INTO indexing_models_fields (model_id, identifier, mandatory, default_value, unit) VALUES (2, 'departureDate', TRUE, null, 'mail');
+INSERT INTO indexing_models_fields (model_id, identifier, mandatory, default_value, unit) VALUES (2, 'subject', TRUE, null, 'mail');
+INSERT INTO indexing_models_fields (model_id, identifier, mandatory, default_value, unit) VALUES (2, 'indexingCustomField_1', FALSE, '"Courrier simple"', 'mail');
+INSERT INTO indexing_models_fields (model_id, identifier, mandatory, default_value, unit) VALUES (2, 'senders', FALSE, null, 'contact');
+INSERT INTO indexing_models_fields (model_id, identifier, mandatory, default_value, unit) VALUES (2, 'recipients', TRUE, null, 'contact');
+INSERT INTO indexing_models_fields (model_id, identifier, mandatory, default_value, unit) VALUES (2, 'initiator', TRUE, null, 'process');
+INSERT INTO indexing_models_fields (model_id, identifier, mandatory, default_value, unit) VALUES (2, 'destination', TRUE, null, 'process');
+INSERT INTO indexing_models_fields (model_id, identifier, mandatory, default_value, unit) VALUES (2, 'processLimitDate', TRUE, null, 'process');
+INSERT INTO indexing_models_fields (model_id, identifier, mandatory, default_value, unit) VALUES (2, 'folders', FALSE, null, 'classifying');
+INSERT INTO indexing_models_fields (model_id, identifier, mandatory, default_value, unit) VALUES (2, 'tags', FALSE, null, 'classifying');
+
+/* Interne */
+INSERT INTO indexing_models_fields (model_id, identifier, mandatory, default_value, unit) VALUES (3, 'doctype', TRUE, null, 'mail');
+INSERT INTO indexing_models_fields (model_id, identifier, mandatory, default_value, unit) VALUES (3, 'priority', TRUE, null, 'mail');
+INSERT INTO indexing_models_fields (model_id, identifier, mandatory, default_value, unit) VALUES (3, 'confidentiality', TRUE, null, 'mail');
+INSERT INTO indexing_models_fields (model_id, identifier, mandatory, default_value, unit) VALUES (3, 'documentDate', TRUE, null, 'mail');
+INSERT INTO indexing_models_fields (model_id, identifier, mandatory, default_value, unit) VALUES (3, 'subject', TRUE, null, 'mail');
+INSERT INTO indexing_models_fields (model_id, identifier, mandatory, default_value, unit) VALUES (3, 'indexingCustomField_1', FALSE, '"Courrier simple"', 'mail');
+INSERT INTO indexing_models_fields (model_id, identifier, mandatory, default_value, unit) VALUES (3, 'senders', FALSE, null, 'contact');
+INSERT INTO indexing_models_fields (model_id, identifier, mandatory, default_value, unit) VALUES (3, 'recipients', FALSE, null, 'contact');
+INSERT INTO indexing_models_fields (model_id, identifier, mandatory, default_value, unit) VALUES (3, 'initiator', TRUE, null, 'process');
+INSERT INTO indexing_models_fields (model_id, identifier, mandatory, default_value, unit) VALUES (3, 'destination', TRUE, null, 'process');
+INSERT INTO indexing_models_fields (model_id, identifier, mandatory, default_value, unit) VALUES (3, 'processLimitDate', TRUE, null, 'process');
+INSERT INTO indexing_models_fields (model_id, identifier, mandatory, default_value, unit) VALUES (3, 'folders', FALSE, null, 'classifying');
+INSERT INTO indexing_models_fields (model_id, identifier, mandatory, default_value, unit) VALUES (3, 'tags', FALSE, null, 'classifying');
+
+/* GED */
+INSERT INTO indexing_models_fields (model_id, identifier, mandatory, default_value, unit) VALUES (4, 'doctype', TRUE, null, 'mail');
+INSERT INTO indexing_models_fields (model_id, identifier, mandatory, default_value, unit) VALUES (4, 'confidentiality', TRUE, null, 'mail');
+INSERT INTO indexing_models_fields (model_id, identifier, mandatory, default_value, unit) VALUES (4, 'documentDate', TRUE, null, 'mail');
+INSERT INTO indexing_models_fields (model_id, identifier, mandatory, default_value, unit) VALUES (4, 'subject', TRUE, null, 'mail');
+INSERT INTO indexing_models_fields (model_id, identifier, mandatory, default_value, unit) VALUES (4, 'senders', FALSE, null, 'contact');
+INSERT INTO indexing_models_fields (model_id, identifier, mandatory, default_value, unit) VALUES (4, 'recipients', FALSE, null, 'contact');
+INSERT INTO indexing_models_fields (model_id, identifier, mandatory, default_value, unit) VALUES (4, 'initiator', TRUE, null, 'process');
+INSERT INTO indexing_models_fields (model_id, identifier, mandatory, default_value, unit) VALUES (4, 'destination', TRUE, null, 'process');
diff --git a/migration/20.03/exportCases.php b/migration/20.03/exportCases.php
new file mode 100644
index 0000000000000000000000000000000000000000..124013affeb64ec7a94927a1993dff080ed9a2e0
--- /dev/null
+++ b/migration/20.03/exportCases.php
@@ -0,0 +1,47 @@
+<?php
+
+require '../../vendor/autoload.php';
+
+chdir('../..');
+
+$customs =  scandir('custom');
+
+foreach ($customs as $custom) {
+    if ($custom == 'custom.xml' || $custom == '.' || $custom == '..') {
+        continue;
+    }
+    $migrated = 0;
+
+    \SrcCore\models\DatabasePDO::reset();
+    new \SrcCore\models\DatabasePDO(['customId' => $custom]);
+
+
+    $cases = \SrcCore\models\DatabaseModel::select([
+        'select' => ['case_id', 'case_label', 'case_closing_date'],
+        'table'  => ['cases']
+    ]);
+
+    if (!empty($cases)) {
+        $file = fopen("migration/20.03/cases_{$custom}.csv", 'w+');
+        $csvHead = ['Identifiant affaire', 'Libellé affaire', 'Date de cloture', 'Identifiant courrier', 'Numéro chrono', 'Sujet courrier'];
+        fputcsv($file, $csvHead, ',');
+
+        foreach ($cases as $case) {
+            $resources = \SrcCore\models\DatabaseModel::select([
+                'select'    => ['r.res_id', 'r.subject', 'mlb.alt_identifier'],
+                'table'     => ['cases_res c, res_letterbox r, mlb_coll_ext mlb'],
+                'where'     => ['c.res_id = r.res_id', 'r.res_id = mlb.res_id', 'case_id = ?', 'r.status <> \'DEL\''],
+                'data'      => [$case['case_id']]
+            ]);
+            foreach ($resources as $resource) {
+                $csvContent = [$case['case_id'], $case['case_label'], $case['case_closing_date'], $resource['res_id'], $resource['alt_identifier'], $resource['subject']];
+                fputcsv($file, $csvContent, ',');
+            }
+            ++$migrated;
+        }
+
+        fclose($file);
+    }
+
+    printf("Export Affaires (CUSTOM {$custom}) : " . $migrated . " Affaire(s) exportée(s) dans le fichier cases_{$custom}.csv\n");
+}
diff --git a/migration/20.03/exportCustomFields.php b/migration/20.03/exportCustomFields.php
new file mode 100644
index 0000000000000000000000000000000000000000..aae9e74f6ae75251868c2dd23e20371d8ea10ea3
--- /dev/null
+++ b/migration/20.03/exportCustomFields.php
@@ -0,0 +1,39 @@
+<?php
+
+require '../../vendor/autoload.php';
+
+chdir('../..');
+
+$customs =  scandir('custom');
+
+foreach ($customs as $custom) {
+    if ($custom == 'custom.xml' || $custom == '.' || $custom == '..') {
+        continue;
+    }
+    $migrated = 0;
+
+    \SrcCore\models\DatabasePDO::reset();
+    new \SrcCore\models\DatabasePDO(['customId' => $custom]);
+
+
+    $fields = \SrcCore\models\DatabaseModel::select([
+        'select' => ['res_id', 'alt_identifier', 'custom_n1', 'custom_f1', 'custom_d1', 'custom_t2', 'custom_n2', 'custom_f2', 'custom_d2', 'custom_t3', 'custom_n3', 'custom_f3', 'custom_d3', 'custom_t4', 'custom_n4', 'custom_f4', 'custom_d4', 'custom_t5', 'custom_n5', 'custom_f5', 'custom_d5', 'custom_t6', 'custom_d6', 'custom_t7', 'custom_d7', 'custom_t8', 'custom_d8', 'custom_t9', 'custom_d9', 'custom_t10', 'custom_d10', 'custom_t11', 'custom_t12', 'custom_t13', 'custom_t14', 'custom_t15'],
+        'table'  => ['res_letterbox'],
+        'where' => ['custom_n1 is not null or custom_f1 is not null or custom_d1 is not null or custom_t2 is not null or custom_n2 is not null or custom_f2 is not null or custom_d2 is not null or custom_t3 is not null or custom_n3 is not null or custom_f3 is not null or custom_d3 is not null or custom_t4 is not null or custom_n4 is not null or custom_f4 is not null or custom_d4 is not null or custom_t5 is not null or custom_n5 is not null or custom_f5 is not null or custom_d5 is not null or custom_t6 is not null or custom_d6 is not null or custom_t7 is not null or custom_d7 is not null or custom_t8 is not null or custom_d8 is not null  or custom_t9 is not null or custom_d9 is not null or custom_t10 is not null or custom_d10 is not null or custom_t11 is not null or custom_t12 is not null or custom_t13 is not null or custom_t14 is not null or custom_t15 is not null']
+    ]);
+
+    if (!empty($fields)) {
+        $file = fopen("migration/20.03/customFields_{$custom}.csv", 'w+');
+        $csvHead = ['Res id', 'Numéro chrono', 'custom_n1', 'custom_f1', 'custom_d1', 'custom_t2', 'custom_n2', 'custom_f2', 'custom_d2', 'custom_t3', 'custom_n3', 'custom_f3', 'custom_d3', 'custom_t4', 'custom_n4', 'custom_f4', 'custom_d4', 'custom_t5', 'custom_n5', 'custom_f5', 'custom_d5', 'custom_t6', 'custom_d6', 'custom_t7', 'custom_d7', 'custom_t8', 'custom_d8', 'custom_t9', 'custom_d9', 'custom_t10', 'custom_d10', 'custom_t11', 'custom_t12', 'custom_t13', 'custom_t14', 'custom_t15'];
+        fputcsv($file, $csvHead, ',');
+
+        foreach ($fields as $field) {
+            fputcsv($file, $field, ',');
+            $migrated++;
+        }
+
+        fclose($file);
+    }
+
+    printf("Export champs custom de res_letterbox (CUSTOM {$custom}) : " . $migrated . " Champ(s) exporté(s) dans le fichier customFields_{$custom}.csv\n");
+}
diff --git a/migration/20.03/exportFileplans.php b/migration/20.03/exportFileplans.php
new file mode 100644
index 0000000000000000000000000000000000000000..bb60f324124d8c1c9f7107bd896a380a87c92deb
--- /dev/null
+++ b/migration/20.03/exportFileplans.php
@@ -0,0 +1,61 @@
+<?php
+
+require '../../vendor/autoload.php';
+
+chdir('../..');
+
+$customs =  scandir('custom');
+
+foreach ($customs as $custom) {
+    if ($custom == 'custom.xml' || $custom == '.' || $custom == '..') {
+        continue;
+    }
+    $migrated = 0;
+
+    \SrcCore\models\DatabasePDO::reset();
+    new \SrcCore\models\DatabasePDO(['customId' => $custom]);
+
+
+    $fileplans = \SrcCore\models\DatabaseModel::select([
+        'select'    => ['fileplan_id', 'fileplan_label'],
+        'table'     => ['fp_fileplan'],
+        'where'     => ['user_id is null']
+    ]);
+
+    if (!empty($fileplans)) {
+        $file = fopen("migration/20.03/fileplans_{$custom}.csv", 'w+');
+        $csvHead = ['Identifiant plan', 'Libellé plan', 'Identifiant position', 'Libellé position', 'Identifiant courrier', 'Numéro chrono', 'Sujet courrier'];
+        fputcsv($file, $csvHead, ',');
+
+        foreach ($fileplans as $fileplan) {
+            $csvContent = [];
+            $positions = \SrcCore\models\DatabaseModel::select([
+                'select'    => ['position_id', 'position_label'],
+                'table'     => ['fp_fileplan_positions'],
+                'where'     => ['fileplan_id = ?'],
+                'data'      => [$fileplan['fileplan_id']]
+            ]);
+
+            foreach ($positions as $position) {
+                $resources = \SrcCore\models\DatabaseModel::select([
+                    'select'    => ['r.res_id', 'r.subject', 'mlb.alt_identifier'],
+                    'table'     => ['fp_res_fileplan_positions p, res_letterbox r, mlb_coll_ext mlb'],
+                    'where'     => ['p.res_id = r.res_id', 'r.res_id = mlb.res_id', 'position_id = ?', 'r.status <> \'DEL\''],
+                    'data'      => [$position['position_id']]
+                ]);
+                foreach ($resources as $resource) {
+                    $csvContent = [$fileplan['fileplan_id'], $fileplan['fileplan_label'], $position['position_id'], $position['position_label'], $resource['res_id'], $resource['alt_identifier'], $resource['subject']];
+                    fputcsv($file, $csvContent, ',');
+                }
+            }
+
+            if (!empty($csvContent)) {
+                ++$migrated;
+            }
+        }
+
+        fclose($file);
+    }
+
+    printf("Export Plans de Classements (CUSTOM {$custom}) : " . $migrated . " Plan(s) de classement public exporté(s) dans le fichier fileplans_{$custom}.csv\n");
+}
diff --git a/migration/20.03/exportFolders.php b/migration/20.03/exportFolders.php
new file mode 100644
index 0000000000000000000000000000000000000000..2a2a9804096f8d6c04c182b30f24c37e2807d903
--- /dev/null
+++ b/migration/20.03/exportFolders.php
@@ -0,0 +1,43 @@
+<?php
+
+require '../../vendor/autoload.php';
+
+chdir('../..');
+
+$customs =  scandir('custom');
+
+foreach ($customs as $custom) {
+    if ($custom == 'custom.xml' || $custom == '.' || $custom == '..') {
+        continue;
+    }
+    $migrated = 0;
+
+    \SrcCore\models\DatabasePDO::reset();
+    new \SrcCore\models\DatabasePDO(['customId' => $custom]);
+
+    $folders = \SrcCore\models\DatabaseModel::select([
+        'select'   => ['f.folders_system_id', 'f.folder_name', 'f.status', 'r.res_id', 'mlb.alt_identifier', 'r.subject'],
+        'table'    => ['folder_tmp f, res_letterbox r, mlb_coll_ext mlb'],
+        'where'    => ['f.folders_system_id = r.folders_system_id', 'r.res_id = mlb.res_id', '(f.destination is null or f.destination = \'\')', 'f.status <> \'DEL\'', 'r.status <> \'DEL\''],
+        'order_by' => ['f.folder_name']
+    ]);
+
+    if (!empty($folders)) {
+        $file = fopen("migration/20.03/folders_{$custom}.csv", 'w+');
+        $csvHead = ['Identifiant dossier', 'Libellé dossier', 'Statut dossier', 'Identifiant courrier', 'Numéro chrono', 'Sujet courrier'];
+        fputcsv($file, $csvHead, ',');
+
+        foreach ($folders as $folder) {
+            $csvContent = [$folder['folders_system_id'], $folder['folder_name'], $folder['status'], $folder['res_id'], $folder['alt_identifier'], $folder['subject']];
+            fputcsv($file, $csvContent, ',');
+
+            if (!empty($csvContent)) {
+                ++$migrated;
+            }
+        }
+
+        fclose($file);
+    }
+
+    printf("Export Dossiers (CUSTOM {$custom}) : " . $migrated . " Dossier(s) public(s) exporté(s) dans le fichier folders_{$custom}.csv\n");
+}
diff --git a/migration/20.03/migrate.sh b/migration/20.03/migrate.sh
new file mode 100644
index 0000000000000000000000000000000000000000..4ad4fe78e6419fc0c3a3bd10affae41c0e7aac0c
--- /dev/null
+++ b/migration/20.03/migrate.sh
@@ -0,0 +1,28 @@
+#!/bin/sh
+php ./exportCustomFields.php
+php ./exportCases.php
+php ./exportFileplans.php
+php ./exportFolders.php
+php ./migrateCategories.php
+php ./migrateCustomFields.php
+php ./migrateFileplans.php
+php ./migrateFolders.php
+php ./migrateFullText.php
+php ./migrateRedirectKeywords.php
+php ./migrateIndexing.php
+php ./migrateListTemplates.php
+php ./migrateServicesEntities.php
+php ./migrateMenuEntities.php
+php ./removeProcessModes.php
+php ./migrateOldIndexingModels.php
+php ./migrateWorkingDays.php
+php ./migrateExtensions.php
+php ./migrateM2MConfiguration.php
+php ./removeNatureFromPrint.php
+php ./migrateCustomValues.php
+php ./migrateVersionAttachments.php
+php ./migrateContacts.php
+# migrateOutgoingTemplate always before migrateTemplates
+php ./migrateOutgoingTemplate.php
+php ./migrateTemplates.php
+php ./migrateLinkedResources.php
diff --git a/migration/20.03/migrateCategories.php b/migration/20.03/migrateCategories.php
new file mode 100644
index 0000000000000000000000000000000000000000..ea4bd42df4ab558884540e70f289002f81583e61
--- /dev/null
+++ b/migration/20.03/migrateCategories.php
@@ -0,0 +1,118 @@
+<?php
+
+require '../../vendor/autoload.php';
+
+chdir('../..');
+
+$nonReadableFiles = [];
+$migrated = 0;
+$customs =  scandir('custom');
+
+foreach ($customs as $custom) {
+    if ($custom == 'custom.xml' || $custom == '.' || $custom == '..') {
+        continue;
+    }
+
+    \SrcCore\models\DatabasePDO::reset();
+    new \SrcCore\models\DatabasePDO(['customId' => $custom]);
+
+    $path = "custom/{$custom}/apps/maarch_entreprise/xml/config.xml";
+    if (file_exists($path)) {
+        if (!is_readable($path) || !is_writable($path)) {
+            $nonReadableFiles[] = $path;
+            continue;
+        }
+        $loadedXml = simplexml_load_file($path);
+        
+        if ($loadedXml) {
+            if (!empty($loadedXml->COLLECTION[0]->categories->category)) {
+                foreach ($loadedXml->COLLECTION[0]->categories->category as $category) {
+                    $aCategories[] = (string)$category->id;
+                }
+                if (!empty($aCategories)) {
+                    $indexingModels = \IndexingModel\models\IndexingModelModel::get([
+                        'select'=> ['id'],
+                        'where' => ['category not in (?)'],
+                        'data'  => [$aCategories]
+                    ]);
+    
+                    if (!empty($indexingModels)) {
+                        $indexingModelsId = array_column($indexingModels, 'id');
+    
+                        \IndexingModel\models\IndexingModelFieldModel::delete([
+                            'where' => ['model_id in (?)'],
+                            'data'  => [$indexingModelsId]
+                        ]);
+                    }
+    
+                    \IndexingModel\models\IndexingModelModel::delete([
+                        'where' => ['category not in (?)'],
+                        'data'  => [$aCategories]
+                    ]);
+                }
+                $defaultCategory = (string)$loadedXml->COLLECTION[0]->categories->default_category;
+    
+                \IndexingModel\models\IndexingModelModel::update([
+                    'set'   => [
+                        '"default"' => 'false'
+                    ],
+                    'where' => ['1=?'],
+                    'data' => [1]
+                ]);
+    
+                \IndexingModel\models\IndexingModelModel::update([
+                    'set'   => [
+                        '"default"' => 'true'
+                    ],
+                    'where' => ['category = ?'],
+                    'data' => [$defaultCategory],
+                ]);
+            }
+
+            $i = 0;
+            foreach ($loadedXml->COLLECTION as $value) {
+                unset($loadedXml->COLLECTION[$i]->categories);
+                $i++;
+            }
+
+            $res = formatXml($loadedXml);
+            $fp = fopen($path, "w+");
+            if ($fp) {
+                fwrite($fp, $res);
+            }
+
+            //Default Priority
+            $defaultPriority = \Priority\models\PriorityModel::get([
+                'select' => ['id'],
+                'where'  => ['default_priority = ?'],
+                'data'   => [1]
+            ]);
+            if (!empty($defaultPriority)) {
+                \SrcCore\models\DatabaseModel::update([
+                    'set'   => ['default_value' => json_encode($defaultPriority[0]['id'])],
+                    'table' => 'indexing_models_fields',
+                    'where' => ['identifier = ?', 'model_id in (SELECT id FROM indexing_models WHERE private = FALSE)'],
+                    'data'  => ['priority']
+                ]);
+            }
+
+            $migrated++;
+        }
+    }
+}
+
+foreach ($nonReadableFiles as $file) {
+    printf("The file %s it is not readable or not writable.\n", $file);
+}
+
+printf($migrated . " custom(s) avec config.xml (categorie) trouvé(s) et migré(s).\n");
+
+function formatXml($simpleXMLElement)
+{
+    $xmlDocument = new DOMDocument('1.0');
+    $xmlDocument->preserveWhiteSpace = false;
+    $xmlDocument->formatOutput = true;
+    $xmlDocument->loadXML($simpleXMLElement->asXML());
+
+    return $xmlDocument->saveXML();
+}
diff --git a/migration/20.03/migrateContacts.php b/migration/20.03/migrateContacts.php
new file mode 100644
index 0000000000000000000000000000000000000000..0c08774af4d3be45a7475333a92e3d88d306bf2f
--- /dev/null
+++ b/migration/20.03/migrateContacts.php
@@ -0,0 +1,733 @@
+<?php
+
+require '../../vendor/autoload.php';
+
+chdir('../..');
+
+$customs =  scandir('custom');
+
+foreach ($customs as $custom) {
+    $debut = microtime(true);
+    if ($custom == 'custom.xml' || $custom == '.' || $custom == '..') {
+        continue;
+    }
+
+    if (file_exists("custom/{$custom}/apps/maarch_entreprise/xml/config.xml")) {
+        $path = "custom/{$custom}/apps/maarch_entreprise/xml/config.xml";
+    } else {
+        $path = 'apps/maarch_entreprise/xml/config.xml';
+    }
+
+    if (file_exists($path)) {
+        $loadedXml = simplexml_load_file($path);
+        if ($loadedXml) {
+            $server     = (string)$loadedXml->CONFIG->databaseserver;
+            $port       = (string)$loadedXml->CONFIG->databaseserverport;
+            $name       = (string)$loadedXml->CONFIG->databasename;
+            $user       = (string)$loadedXml->CONFIG->databaseuser;
+            $password   = (string)$loadedXml->CONFIG->databasepassword;
+        }
+
+        $databaseConnection = pg_connect(
+            'host=' . $server .
+            ' user=' . $user .
+            ' password=' . $password .
+            ' dbname=' . $name .
+            ' port=' . $port
+        );
+    } else {
+        echo "No config file found ";
+        continue;
+    }
+
+    \SrcCore\models\DatabasePDO::reset();
+    new \SrcCore\models\DatabasePDO(['customId' => $custom]);
+
+    // TRUNCATE CONTACTS TABLES
+    \SrcCore\models\DatabaseModel::delete([
+        'table' => 'contacts',
+        'where' => ['id > 0']
+    ]);
+    \SrcCore\models\DatabaseModel::delete([
+        'table' => 'contacts_custom_fields_list',
+        'where' => ['id > 0']
+    ]);
+    \SrcCore\models\DatabaseModel::delete([
+        'table' => 'contacts_parameters',
+        'where' => ['id > 0']
+    ]);
+
+    $aValues = [];
+    $migrated = 0;
+    $contactsInfo = \SrcCore\models\DatabaseModel::select([
+        'select' => ['contact_id', 'society', 'contact_firstname', 'contact_lastname', 'contact_title', 'contact_function', 'contact_other_data',
+            'creation_date', 'update_date', 'ca_id', 'departement', 'firstname', 'lastname', 'title', 'function', 'occupancy',
+            'address_num', 'address_street', 'address_complement', 'address_town', 'address_postal_code', 'address_country', 'phone', 'email', 'other_data',
+            'user_id', 'enabled', 'external_id', 'society_short', 'contact_purpose_label', 'contact_type_label', 'website', 'salutation_header', 'salutation_footer'
+        ],
+        'table' => ['view_contacts']
+    ]);
+
+    // Fields not migrated
+    //contact_user_id, contact_enabled, contact_entity_id, entity_id, is_private, is_corporate_person, contact_type, contact_purpose_id
+
+    $contactTypes = \SrcCore\models\DatabaseModel::select([
+        'select' => ['label'],
+        'table'  => ['contact_types']
+    ]);
+    $contactTypes = array_column($contactTypes, 'label');
+
+    $contactPurposes = \SrcCore\models\DatabaseModel::select([
+        'select' => ['label'],
+        'table'  => ['contact_purposes']
+    ]);
+    $contactPurposes = array_column($contactPurposes, 'label');
+
+    $customFields= [
+        ['oldId' => 'salutation_header',     'label' => 'Formule de politesse (Début)',     'type' => 'string',     'value' => ['']],
+        ['oldId' => 'salutation_footer',     'label' => 'Formule de politesse (Fin)',       'type' => 'string',     'value' => ['']],
+        ['oldId' => 'website',               'label' => 'Site internet',                    'type' => 'string',     'value' => ['']],
+        ['oldId' => 'contact_type_label',    'label' => 'Type de contact',                  'type' => 'select',     'value' => $contactTypes],
+        ['oldId' => 'contact_purpose_label', 'label' => 'Dénomination',                     'type' => 'select',     'value' => $contactPurposes],
+        ['oldId' => 'society_short',         'label' => 'Sigle de la structure',            'type' => 'string',     'value' => ['']],
+    ];
+
+    $newCustomFields = addCustomFields(['customFields' => $customFields]);
+
+    $firstMan = \User\models\UserModel::get(['select' => ['id'], 'orderBy' => ['id'], 'limit' => 1, 'where' => ['status = ?'], 'data' => ['OK']]);
+
+    $table = "contacts (id, civility, firstname, lastname, company, department, function, address_number, address_street,"
+        . "address_additional1, address_additional2, address_postcode, address_town, address_country, email, phone,"
+        . "communication_means, notes, creator, creation_date, modification_date, enabled, external_id)";
+
+    $contactInfoSeparator = "\t";
+
+    $id = 1;
+    $contacts = [];
+    $debutMigrateInProgress = microtime(true);
+    foreach ($contactsInfo as $contactInfo) {
+        $oldContactId = $contactInfo['contact_id'];
+        $oldAddressId = $contactInfo['ca_id'];
+        unset($contactInfo['contact_id']);
+        unset($contactInfo['ca_id']);
+
+        // Civility
+        $contactInfo['civility'] = !empty($contactInfo['contact_title']) ? $contactInfo['contact_title'] : $contactInfo['title'];
+        unset($contactInfo['contact_title']);
+        unset($contactInfo['title']);
+
+        // Firstname
+        $contactInfo['firstname'] = !empty($contactInfo['contact_firstname']) ? $contactInfo['contact_firstname'] : $contactInfo['firstname'];
+        unset($contactInfo['contact_firstname']);
+
+        // Lastname
+        $contactInfo['lastname'] = !empty($contactInfo['contact_lastname']) ? $contactInfo['contact_lastname'] : $contactInfo['lastname'];
+        unset($contactInfo['contact_lastname']);
+
+        // Company
+        $contactInfo['company'] = $contactInfo['society'];
+        unset($contactInfo['society']);
+
+        // Function
+        $contactInfo['function'] = !empty($contactInfo['contact_function']) ? $contactInfo['contact_function'] : $contactInfo['function'];
+        unset($contactInfo['contact_function']);
+
+        // Department
+        $contactInfo['department'] = $contactInfo['departement'];
+        unset($contactInfo['departement']);
+
+        // Address
+        $contactInfo['address_number'] = $contactInfo['address_num'];
+        unset($contactInfo['address_num']);
+        $contactInfo['address_additional1'] = $contactInfo['occupancy'];
+        unset($contactInfo['occupancy']);
+        $contactInfo['address_additional2'] = $contactInfo['address_complement'];
+        unset($contactInfo['address_complement']);
+        $contactInfo['address_postcode'] = $contactInfo['address_postal_code'];
+        unset($contactInfo['address_postal_code']);
+
+        //Moyen de communication
+        $communicationMeans = \SrcCore\models\DatabaseModel::select(['select' => ['value'], 'table' => ['contact_communication'], 'where' => ['contact_id = ?'], 'data' => [$oldContactId]]);
+        if (!empty($communicationMeans)) {
+            $communicationMeans = $communicationMeans[0]['value'];
+            if (filter_var($communicationMeans, FILTER_VALIDATE_EMAIL)) {
+                $aCommunicationMeans = ['email' => $communicationMeans];
+            } elseif (filter_var($communicationMeans, FILTER_VALIDATE_URL)) {
+                $aCommunicationMeans = ['url' => $communicationMeans];
+            }
+        }
+        $contactInfo['communication_means'] = !empty($communicationMeans) ? json_encode($aCommunicationMeans) : null;
+
+        // Notes
+        $contactInfo['notes'] = !empty($contactInfo['contact_other_data']) ? $contactInfo['contact_other_data'] : $contactInfo['other_data'];
+        unset($contactInfo['contact_other_data']);
+        unset($contactInfo['other_data']);
+
+        // Creator
+        $creator = \User\models\UserModel::getByLogin(['select' => ['id'], 'login' => $contactInfo['user_id']]);
+        if (empty($creator)) {
+            $contactInfo['creator'] = $firstMan[0]['id'];
+        } else {
+            $contactInfo['creator'] = $creator['id'];
+        }
+        unset($contactInfo['user_id']);
+
+        // Modification date
+        $contactInfo['modification_date'] = $contactInfo['update_date'];
+        unset($contactInfo['update_date']);
+
+        // Enabled
+        $contactInfo['enabled'] = $contactInfo['enabled'] == 'Y' ? 'true' : 'false';
+
+        $contactCustomInfo = [
+            'salutation_header'     => $contactInfo['salutation_header'],
+            'salutation_footer'     => $contactInfo['salutation_footer'],
+            'website'               => $contactInfo['website'],
+            'contact_type_label'    => $contactInfo['contact_type_label'],
+            'contact_purpose_label' => $contactInfo['contact_purpose_label'],
+            'society_short'         => $contactInfo['society_short'],
+        ];
+
+        unset($contactInfo['salutation_header']);
+        unset($contactInfo['salutation_footer']);
+        unset($contactInfo['website']);
+        unset($contactInfo['contact_type_label']);
+        unset($contactInfo['contact_purpose_label']);
+        unset($contactInfo['society_short']);
+
+        $contactInfo['id'] = $id;
+
+        foreach ($contactInfo as $key => $item) {
+            $contactInfo[$key] = (str_replace("\t", " ", $contactInfo[$key]));
+            $contactInfo[$key] = $contactInfo[$key] ?? "NULL";
+            if (empty($contactInfo[$key])) {
+                $contactInfo[$key] = "NULL";
+            }
+        }
+
+        $contact = $id . $contactInfoSeparator
+            . $contactInfo['civility'] . $contactInfoSeparator
+            . $contactInfo['firstname'] . $contactInfoSeparator
+            . $contactInfo['lastname'] . $contactInfoSeparator
+            . $contactInfo['company'] . $contactInfoSeparator
+            . $contactInfo['departement'] . $contactInfoSeparator
+            . $contactInfo['function'] . $contactInfoSeparator
+            . $contactInfo['address_number'] . $contactInfoSeparator
+            . $contactInfo['address_street'] . $contactInfoSeparator
+            . $contactInfo['address_additional1'] . $contactInfoSeparator
+            . $contactInfo['address_additional2'] . $contactInfoSeparator
+            . $contactInfo['address_postcode'] . $contactInfoSeparator
+            . $contactInfo['address_town'] . $contactInfoSeparator
+            . $contactInfo['address_country'] . $contactInfoSeparator
+            . $contactInfo['email'] . $contactInfoSeparator
+            . $contactInfo['phone'] . $contactInfoSeparator
+            . $contactInfo['communication_means'] . $contactInfoSeparator
+            . $contactInfo['notes'] . $contactInfoSeparator
+            . $contactInfo['creator'] . $contactInfoSeparator
+            . $contactInfo['creation_date'] . $contactInfoSeparator
+            . $contactInfo['modification_date'] . $contactInfoSeparator
+            . $contactInfo['enabled'] . $contactInfoSeparator
+            . $contactInfo['external_id'];
+
+        if (strpos($contact, "\r")) {
+            $contact = str_replace("\r", " ", $contact);
+            $contact = str_replace("\n", " ", $contact);
+        }
+
+        $contacts[] = $contact;
+
+        $ids[$id] = ['oldAddressId' => $oldAddressId, 'oldContactId' => $oldContactId];
+
+        migrateCustomField(['newContactId' => $id, 'contactCustomInfo' => $contactCustomInfo, 'newCustomFields' => $newCustomFields]);
+
+        $currentValuesContactRes = migrateContactRes(['oldAddressId' => $oldAddressId, 'oldContactId' => $oldContactId, 'newContactId' => $id]);
+
+        $aValues = array_merge($aValues, $currentValuesContactRes);
+
+        $migrated++;
+
+        if ($migrated % 5000 == 0) {
+            $finMigrateInProgress = microtime(true);
+            $delaiInProgress = $finMigrateInProgress - $debutMigrateInProgress;
+            echo "Migration En cours : ".$delaiInProgress." secondes.\n";
+            $debutMigrateInProgress = microtime(true);
+            printf($migrated . " contact(s) migré(s) - En cours...\n");
+        }
+
+        if ($migrated % 50000 == 0) {
+            echo "Migration de 50000 contacts...\n";
+
+            $beforeCopyContacts = microtime(true);
+            pg_copy_from($databaseConnection, $table, $contacts, $contactInfoSeparator, "NULL");
+            $afterCopyContacts = microtime(true);
+            $copyTimeContacts = $afterCopyContacts - $beforeCopyContacts;
+            echo "Temps copy contacts = $copyTimeContacts\n";
+
+            pg_copy_from($databaseConnection, 'resource_contacts (res_id, item_id, type, mode)', $aValues, "\t", 	"\\\\N");
+            $finMigrateInProgress = microtime(true);
+            $delaiInProgress = $finMigrateInProgress - $debutMigrateInProgress;
+            echo "Migration En cours : ".$delaiInProgress." secondes.\n";
+            $debutMigrateInProgress = microtime(true);
+            printf($migrated . " contact(s) migré(s) - En cours...\n");
+            $aValues = [];
+
+            $contacts = [];
+        }
+
+        $id++;
+    }
+
+    if (!empty($aValues)) {
+        $beforeCopy = microtime(true);
+        pg_copy_from($databaseConnection, 'resource_contacts (res_id, item_id, type, mode)', $aValues, "\t", "\\\\N");
+        $afterCopy = microtime(true);
+        $copyTime = $afterCopy - $beforeCopy;
+        echo "Temps copy resource contacts = $copyTime secondes\n";
+    }
+
+    if (!empty($contacts)) {
+        $beforeCopyContacts = microtime(true);
+        pg_copy_from($databaseConnection, $table, $contacts, $contactInfoSeparator, "NULL");
+        $afterCopyContacts = microtime(true);
+        $copyTimeContacts = $afterCopyContacts - $beforeCopyContacts;
+        echo "Temps copy contacts = $copyTimeContacts\n";
+    }
+
+
+
+    $beforeUpdates = microtime(true);
+    $valuesOldAddress = '';
+    $firstDone = false;
+    foreach ($ids as $newId => $value) {
+        $oldAddressId = $value['oldAddressId'];
+        if ($firstDone) {
+            $valuesOldAddress .= ', ';
+        }
+        $valuesOldAddress .= "( $newId , $oldAddressId)";
+        if (!$firstDone) {
+            $firstDone = true;
+        }
+    }
+
+    // Migrate addresses in res_letterbox
+    $query = "insert into resource_contacts (res_id, item_id, type, mode)
+    select res_id, tmp.new_id, 'contact_v3' as type,
+       case
+           when category_id = 'outgoing' then 'recipient'
+           else 'sender'
+       end as mode
+    from res_letterbox,
+         (values 
+             $valuesOldAddress
+         ) as tmp(new_id, old_address_id)
+    where tmp.old_address_id = res_letterbox.address_id";
+    pg_query($databaseConnection, $query);
+
+
+    // Acknowledgement Receipts
+    $query = "update acknowledgement_receipts as ar set
+            contact_id = tmp.old_address_id
+        from (values
+               $valuesOldAddress 
+            ) as tmp(new_id, old_address_id) 
+        where ar.contact_address_id = tmp.old_address_id";
+    pg_query($databaseConnection, $query);
+
+
+    // Group list
+    $query = "update contacts_groups_lists as cgl set
+            contact_id = tmp.old_address_id
+        from (values
+               $valuesOldAddress
+            ) as tmp(new_id, old_address_id) 
+        where cgl.contact_addresses_id = tmp.old_address_id";
+
+    pg_query($databaseConnection, $query);
+
+
+    // Resources contacts
+    $query = "update resource_contacts as rc set
+        item_id = tmp.old_address_id, type = 'contact_v3'
+    from (values
+           $valuesOldAddress
+        ) as tmp(new_id, old_address_id) 
+    where rc.item_id = tmp.old_address_id and type = 'contact'";
+    pg_query($databaseConnection, $query);
+
+    $valuesOld= '';
+    $firstDone = false;
+    foreach ($ids as $newId => $value) {
+        $oldAddressId = $value['oldAddressId'];
+        $oldContactId = $value['oldContactId'];
+        if ($firstDone) {
+            $valuesOld .= ', ';
+        }
+        $valuesOld .= "( $newId , $oldAddressId, $oldContactId)";
+        if (!$firstDone) {
+            $firstDone = true;
+        }
+    }
+
+    // Res attach
+    $query = "update res_attachments as ra set
+        recipient_id = tmp.old_address_id, recipient_type = 'contact_v3'
+    from (values
+           $valuesOld
+        ) as tmp(new_id, old_address_id, old_contact_id) 
+    where ra.dest_contact_id = tmp.old_contact_id and ra.dest_address_id = tmp.old_address_id";
+    pg_query($databaseConnection, $query);
+
+    $afterUpdates = microtime(true);
+    $updatesTime = $afterUpdates - $beforeUpdates;
+    echo "Temps updates = $updatesTime secondes\n";
+
+    $finMigrateInProgress = microtime(true);
+    $delaiInProgress = $finMigrateInProgress - $debutMigrateInProgress;
+    echo "Dernière Migration En cours : ".$delaiInProgress." secondes.\n";
+    $debutMigrateInProgress = microtime(true);
+    printf($migrated . " contact(s) migré(s) - Fin...\n");
+
+    $debutEndMigrate = microtime(true);
+    migrateContactRes_Users(['firstManId' => $firstMan[0]['id'], 'databaseConnection' => $databaseConnection]);
+    migrateResletterbox_Users(['firstManId' => $firstMan[0]['id'], 'databaseConnection' => $databaseConnection]);
+    migrateResattachments_Users(['firstManId' => $firstMan[0]['id']]);
+    migrateContactParameters();
+    migrateContactPrivileges();
+    $finEndMigrate = microtime(true);
+    $delaiEndMigrate = $finEndMigrate - $debutEndMigrate;
+    echo "Migration du bas : ".$delaiEndMigrate." secondes.\n";
+
+    \SrcCore\models\DatabaseModel::update([
+        'set'   => ['type' => 'contact'],
+        'table' => 'resource_contacts',
+        'where' => ['type = ?'],
+        'data'  => ['contact_v3']
+    ]);
+
+    $fin = microtime(true);
+    $delai = $fin - $debut;
+    echo "Le temps écoulé est de ".$delai." secondes.\n";
+    printf("Migration Contacts (CUSTOM {$custom}) : " . $migrated . " Contact(s) trouvée(s) et migrée(s).\n");
+}
+
+function addCustomFields($args = [])
+{
+    \SrcCore\models\DatabaseModel::beginTransaction();
+    $fillingValues = \SrcCore\models\DatabaseModel::select([
+        'select' => ['rating_columns'],
+        'table'  => ['contacts_filling']
+    ]);
+
+    $fillingValues = json_decode($fillingValues[0]['rating_columns']);
+
+    $customFields = [];
+    $aValues      = [];
+    foreach ($args['customFields'] as $value) {
+        $customFieldId = \Contact\models\ContactCustomFieldListModel::create([
+            'label'  => $value['label'],
+            'type'   => $value['type'],
+            'values' => json_encode($value['value'])
+        ]);
+
+        $filling = 'false';
+        if (in_array($value['oldId'], $fillingValues)) {
+            $filling = 'true';
+        }
+
+        $aValues[] = [
+            'contactCustomField_' . $customFieldId,
+            'false',
+            $filling,
+            'false',
+            'false',
+        ];
+
+        $customFields[$value['oldId']] = $customFieldId;
+    }
+
+    if (!empty($aValues)) {
+        \SrcCore\models\DatabaseModel::insertMultiple([
+            'table'    => 'contacts_parameters',
+            'columns'  => ['identifier', 'mandatory', 'filling', 'searchable', 'displayable'],
+            'values'   => $aValues
+        ]);
+    }
+    \SrcCore\models\DatabaseModel::commitTransaction();
+    return $customFields;
+}
+
+function migrateCustomField($args = [])
+{
+    \SrcCore\models\DatabaseModel::beginTransaction();
+    foreach ($args['contactCustomInfo'] as $key => $value) {
+        if (!empty($value)) {
+            $value = json_encode($value);
+            $value = str_replace("'", "''", $value);
+            \Contact\models\ContactModel::update([
+                'postSet' => ['custom_fields' => "jsonb_set('{}', '{{$args['newCustomFields'][$key]}}', '{$value}')"],
+                'where' => ['id = ?'],
+                'data' => [$args['newContactId']]
+            ]);
+        }
+    }
+    \SrcCore\models\DatabaseModel::commitTransaction();
+}
+
+function migrateContactRes($args = [])
+{
+    $contactRes = \SrcCore\models\DatabaseModel::select([
+        'select' => ['res_id'],
+        'table'  => ['contacts_res'],
+        'where'  => ['contact_id = ?', 'address_id = ?'],
+        'data'  => [$args['oldContactId'], $args['oldAddressId']],
+    ]);
+
+    $aValues = [];
+    foreach ($contactRes as $value) {
+        $resInfo = \SrcCore\models\DatabaseModel::select([
+            'select' => ['category_id'],
+            'table'  => ['res_letterbox'],
+            'where'  => ['res_id = ?'],
+            'data'   => [$value['res_id']]
+        ]);
+
+        $mode = 'sender';
+        if ($resInfo[0]['category_id'] == 'outgoing') {
+            $mode = 'recipient';
+        }
+
+        $aValues[] = implode("\t", [
+            $value['res_id'],
+            $args['newContactId'],
+            'contact_v3',
+            $mode
+        ]) . "\n";
+    }
+
+    return $aValues;
+}
+
+function migrateContactRes_Users($args = [])
+{
+    \SrcCore\models\DatabaseModel::beginTransaction();
+    $userContactRes = \SrcCore\models\DatabaseModel::select([
+        'select' => ['res_id', 'contact_id'],
+        'table'  => ['contacts_res'],
+        'where'  => ['address_id = 0']
+    ]);
+
+    $aValues = [];
+    foreach ($userContactRes as $value) {
+        $resInfo = \SrcCore\models\DatabaseModel::select([
+            'select' => ['category_id'],
+            'table'  => ['res_letterbox'],
+            'where'  => ['res_id = ?'],
+            'data'   => [$value['res_id']]
+        ]);
+
+        $user = \User\models\UserModel::getByLogin(['login' => $value['contact_id'], 'select' => ['id']]);
+        if (empty($user)) {
+            $user = $args['firstManId'];
+        } else {
+            $user = $user['id'];
+        }
+
+        $mode = 'sender';
+        if ($resInfo[0]['category_id'] == 'outgoing') {
+            $mode = 'recipient';
+        }
+
+        $aValues[] = implode("\t", [
+            $value['res_id'],
+            $user,
+            'user',
+            $mode
+        ]) . "\n";
+    }
+
+    if (!empty($aValues)) {
+        pg_copy_from($args['databaseConnection'], 'resource_contacts (res_id, item_id, type, mode)', $aValues, "\t", "\\\\N");
+    }
+    \SrcCore\models\DatabaseModel::commitTransaction();
+}
+
+function migrateResletterbox_Users($args = [])
+{
+    \SrcCore\models\DatabaseModel::beginTransaction();
+    $userContact = \SrcCore\models\DatabaseModel::select([
+        'select' => ['res_id', 'exp_user_id', 'dest_user_id'],
+        'table'  => ['res_letterbox'],
+        'where'  => ['(exp_user_id != \'\' and exp_user_id is not null) or (dest_user_id != \'\' and dest_user_id is not null)']
+    ]);
+
+    $aValues = [];
+    foreach ($userContact as $value) {
+        if (!empty($value['exp_user_id'])) {
+            $login = $value['exp_user_id'];
+            $mode = 'sender';
+        } else {
+            $login = $value['dest_user_id'];
+            $mode = 'recipient';
+        }
+        $user = \User\models\UserModel::getByLogin(['login' => $login, 'select' => ['id']]);
+        if (empty($user)) {
+            $user = $args['firstManId'];
+        } else {
+            $user = $user['id'];
+        }
+
+        $aValues[] = implode("\t", [
+            $value['res_id'],
+            $user,
+            'user',
+            $mode
+        ]) . "\n";
+    }
+
+    if (!empty($aValues)) {
+        pg_copy_from($args['databaseConnection'], 'resource_contacts (res_id, item_id, type, mode)', $aValues, "\t", "\\\\N");
+    }
+    \SrcCore\models\DatabaseModel::commitTransaction();
+}
+
+function migrateResattachments_Users($args = [])
+{
+    \SrcCore\models\DatabaseModel::beginTransaction();
+    $attachments = \SrcCore\models\DatabaseModel::select([
+        'select' => ['dest_user', 'res_id'],
+        'table'  => ['res_attachments'],
+        'where'  => ['dest_user != \'\' and dest_user is not null']
+    ]);
+
+    foreach ($attachments as $value) {
+        $user = \User\models\UserModel::getByLogin(['login' => $value['dest_user'], 'select' => ['id']]);
+        if (empty($user)) {
+            $user = $args['firstManId'];
+        } else {
+            $user = $user['id'];
+        }
+
+        \SrcCore\models\DatabaseModel::update([
+            'set'   => ['recipient_id' => $user, 'recipient_type' => 'user'],
+            'table' => 'res_attachments',
+            'where' => ['res_id = ?'],
+            'data'  => [$value['res_id']],
+        ]);
+    }
+    \SrcCore\models\DatabaseModel::commitTransaction();
+}
+
+function migrateContactParameters()
+{
+    \SrcCore\models\DatabaseModel::beginTransaction();
+    $fillingValues = \SrcCore\models\DatabaseModel::select([
+        'select' => ['rating_columns'],
+        'table'  => ['contacts_filling']
+    ]);
+
+    $fillingValues = json_decode($fillingValues[0]['rating_columns']);
+
+    $contactParameters = [
+        ['oldIdentifier' => 'title',                'identifier' => 'civility',             'mandatory' => 'false', 'filling' => 'false', 'searchable' => 'false', 'displayable' => 'false'],
+        ['oldIdentifier' => 'firstname',            'identifier' => 'firstname',            'mandatory' => 'false', 'filling' => 'false', 'searchable' => 'true', 'displayable' => 'true'],
+        ['oldIdentifier' => 'lastname',             'identifier' => 'lastname',             'mandatory' => 'false', 'filling' => 'false', 'searchable' => 'true', 'displayable' => 'true'],
+        ['oldIdentifier' => 'society',              'identifier' => 'company',              'mandatory' => 'false', 'filling' => 'false', 'searchable' => 'true', 'displayable' => 'true'],
+        ['oldIdentifier' => 'departement',          'identifier' => 'department',           'mandatory' => 'false', 'filling' => 'false', 'searchable' => 'false', 'displayable' => 'false'],
+        ['oldIdentifier' => 'function',             'identifier' => 'function',             'mandatory' => 'false', 'filling' => 'false', 'searchable' => 'false', 'displayable' => 'false'],
+        ['oldIdentifier' => 'address_num',          'identifier' => 'addressNumber',        'mandatory' => 'false', 'filling' => 'false', 'searchable' => 'true', 'displayable' => 'true'],
+        ['oldIdentifier' => 'address_street',       'identifier' => 'addressStreet',        'mandatory' => 'false', 'filling' => 'false', 'searchable' => 'true', 'displayable' => 'true'],
+        ['oldIdentifier' => 'occupancy',            'identifier' => 'addressAdditional1',   'mandatory' => 'false', 'filling' => 'false', 'searchable' => 'false', 'displayable' => 'false'],
+        ['oldIdentifier' => 'address_complement',   'identifier' => 'addressAdditional2',   'mandatory' => 'false', 'filling' => 'false', 'searchable' => 'false', 'displayable' => 'false'],
+        ['oldIdentifier' => 'address_postal_code',  'identifier' => 'addressPostcode',      'mandatory' => 'false', 'filling' => 'false', 'searchable' => 'false', 'displayable' => 'false'],
+        ['oldIdentifier' => 'address_town',         'identifier' => 'addressTown',          'mandatory' => 'false', 'filling' => 'false', 'searchable' => 'true', 'displayable' => 'true'],
+        ['oldIdentifier' => 'address_country',      'identifier' => 'addressCountry',       'mandatory' => 'false', 'filling' => 'false', 'searchable' => 'false', 'displayable' => 'false'],
+        ['oldIdentifier' => 'email',                'identifier' => 'email',                'mandatory' => 'false', 'filling' => 'false', 'searchable' => 'false', 'displayable' => 'false'],
+        ['oldIdentifier' => 'phone',                'identifier' => 'phone',                'mandatory' => 'false', 'filling' => 'false', 'searchable' => 'false', 'displayable' => 'false'],
+    ];
+    
+    foreach ($contactParameters as $value) {
+        $filling = 'false';
+        if (in_array($value['oldIdentifier'], ['lastname', 'society']) || in_array($value['oldIdentifier'], $fillingValues)) {
+            $filling = 'true';
+        }
+
+        $aValues[] = [
+            $value['identifier'],
+            $value['mandatory'],
+            $filling,
+            $value['searchable'],
+            $value['displayable']
+        ];
+    }
+
+    \SrcCore\models\DatabaseModel::insertMultiple([
+        'table'     => 'contacts_parameters',
+        'columns'   => ['identifier', 'mandatory', 'filling', 'searchable', 'displayable'],
+        'values'    => $aValues
+    ]);
+    
+    \SrcCore\models\DatabaseModel::commitTransaction();
+}
+
+function migrateContactPrivileges()
+{
+    \SrcCore\models\DatabaseModel::beginTransaction();
+    $usergroupServices = \SrcCore\models\DatabaseModel::select([
+        'select' => ['group_id'],
+        'table'  => ['usergroups_services'],
+        'where'  => ['service_id = ?'],
+        'data'   => ['create_contacts']
+    ]);
+
+    foreach ($usergroupServices as $usergroupService) {
+        $servicesEnabled = \SrcCore\models\DatabaseModel::select([
+            'select' => ['group_id'],
+            'table'  => ['usergroups_services'],
+            'where'  => ['service_id = ?', 'group_id = ?'],
+            'data'   => ['update_contacts', $usergroupService['group_id']]
+        ]);
+        if (empty($servicesEnabled)) {
+            \SrcCore\models\DatabaseModel::insert([
+                'table'         => 'usergroups_services',
+                'columnsValues' => [
+                    'service_id' => 'update_contacts',
+                    'group_id'   => $usergroupService['group_id']
+                ]
+            ]);
+        }
+    }
+
+    foreach (['my_contacts_menu', 'my_contacts'] as $service) {
+        $usergroupServices = \SrcCore\models\DatabaseModel::select([
+            'select' => ['group_id'],
+            'table'  => ['usergroups_services'],
+            'where'  => ['service_id = ?'],
+            'data'   => [$service]
+        ]);
+    
+        foreach ($usergroupServices as $usergroupService) {
+            $servicesEnabled = \SrcCore\models\DatabaseModel::select([
+                'select' => ['group_id', 'service_id'],
+                'table'  => ['usergroups_services'],
+                'where'  => ['service_id = ?', 'group_id = ?'],
+                'data'   => ['create_contacts', $usergroupService['group_id']]
+            ]);
+            if (empty($servicesEnabled)) {
+                \SrcCore\models\DatabaseModel::insert([
+                    'table'         => 'usergroups_services',
+                    'columnsValues' => [
+                        'service_id' => 'create_contacts',
+                        'group_id'   => $usergroupService['group_id']
+                    ]
+                ]);
+            }
+        }
+    }
+
+    \SrcCore\models\DatabaseModel::delete([
+        'table' => 'usergroups_services',
+        'where' => ['service_id in (?)'],
+        'data'  => [['my_contacts_menu', 'my_contacts']]
+    ]);
+    \SrcCore\models\DatabaseModel::commitTransaction();
+}
diff --git a/migration/20.03/migrateCustomFields.php b/migration/20.03/migrateCustomFields.php
new file mode 100644
index 0000000000000000000000000000000000000000..4b376047070f782dc888ebb581ea67083a5f3835
--- /dev/null
+++ b/migration/20.03/migrateCustomFields.php
@@ -0,0 +1,116 @@
+<?php
+
+require '../../vendor/autoload.php';
+
+chdir('../..');
+
+$customs =  scandir('custom');
+
+foreach ($customs as $custom) {
+    if ($custom == 'custom.xml' || $custom == '.' || $custom == '..') {
+        continue;
+    }
+
+    \SrcCore\models\DatabasePDO::reset();
+    new \SrcCore\models\DatabasePDO(['customId' => $custom]);
+
+    $migrated = 0;
+    $path = "custom/{$custom}/apps/maarch_entreprise/xml/index_letterbox.xml";
+    if (file_exists($path)) {
+        if (!is_readable($path) || !is_writable($path)) {
+            continue;
+        }
+        $loadedXml = simplexml_load_file($path);
+        
+        if ($loadedXml) {
+		    $indexingModels = \IndexingModel\models\IndexingModelModel::get(['select'=> ['id']]);
+            if (!empty($indexingModels)) {
+                $indexingModelsId = array_column($indexingModels, 'id');
+            }
+
+            $i = 0;
+            foreach ($loadedXml->INDEX as $value) {
+                $customExists = \SrcCore\models\DatabaseModel::select([
+                        'select' => [1],
+                        'table'  => ['doctypes_indexes'],
+                        'where'  => ['field_name = ?'],
+                        'data'   => [(string)$value->column]
+                ]);
+                if (empty($customExists)) {
+                    continue;
+                }
+
+                $label = (string)$value->label;
+                $type = trim((string)$value->type);
+                if ($type == 'float') {
+                    $type = 'integer';
+                }
+
+                $values = [];
+                if (!empty($value->values_list)) {
+                    foreach ($value->values_list->value as $valueList) {
+                        $values[] = (string)$valueList->label;
+                    }
+                }
+                if (!empty($value->table) && !empty($value->table->table_name) && !empty($value->table->foreign_label)) {
+                    $tableName    = (string)$value->table->table_name;
+                    $foreignLabel = (string)$value->table->foreign_label;
+                    $whereClause  = (string)$value->table->where_clause;
+                    $order        = (string)$value->table->order;
+
+                    $customValues = \SrcCore\models\DatabaseModel::select([
+                        'select'   => [$foreignLabel],
+                        'table'    => [$tableName],
+                        'where'    => empty($whereClause) ? [] : [$whereClause],
+                        'order_by' => [str_ireplace("order by", "", $order)]
+                    ]);
+
+                    foreach ($customValues as $valueList) {
+                        $values[] = $valueList[$foreignLabel];
+                    }
+                }
+
+                $fieldId = \CustomField\models\CustomFieldModel::create([
+                    'label'     => $label,
+                    'type'      => $type,
+                    'values'    => empty($values) ? '[]' : json_encode($values)
+                ]);
+
+                if (!empty($indexingModelsId)) {
+                    foreach ($indexingModelsId as $indexingModelId) {
+                        \IndexingModel\models\IndexingModelFieldModel::create([
+                            'model_id'   => $indexingModelId,
+                            'identifier' => 'indexingCustomField_'.$fieldId,
+                            'mandatory'  => 'false',
+                            'unit'       => 'mail'
+                        ]);
+                    }
+                }
+
+                $column = (string)$value->column;
+                $csColumn = "custom_fields->>''{$fieldId}''";
+                \Basket\models\BasketModel::update(['postSet' => ['basket_clause' => "REPLACE(basket_clause, '{$column}', '{$csColumn}')"], 'where' => ['1 = ?'], 'data' => [1]]);
+                \Basket\models\BasketModel::update(['postSet' => ['basket_clause' => "REPLACE(basket_clause, 'doc_{$column}', '{$csColumn}')"], 'where' => ['1 = ?'], 'data' => [1]]);
+                $resources = \Resource\models\ResModel::get([
+                    'select'    => ['res_id', $column],
+                    'where'     => [$column . ' is not null'],
+                ]);
+
+                foreach ($resources as $resource) {
+                    $valueColumn = json_encode($resource[$column]);
+                    $valueColumn = str_replace("'", "''", $valueColumn);
+                    $resId = $resource['res_id'];
+                    \Resource\models\ResModel::update([
+                        'postSet'   => ['custom_fields' => "jsonb_set(custom_fields, '{{$fieldId}}', '{$valueColumn}')"],
+                        'where'     => ['res_id = ?'],
+                        'data'      => [$resId]
+                    ]);
+                }
+
+                $migrated++;
+            }
+        }
+    }
+
+    printf("Migration Champs Custom (CUSTOM {$custom}) : " . $migrated . " Champs custom utilisé(s) et migré(s).\n");
+}
diff --git a/migration/20.03/migrateCustomValues.php b/migration/20.03/migrateCustomValues.php
new file mode 100644
index 0000000000000000000000000000000000000000..0abe6f8fa66de29ef9700400cc90b71dbe77cfaf
--- /dev/null
+++ b/migration/20.03/migrateCustomValues.php
@@ -0,0 +1,90 @@
+<?php
+
+require '../../vendor/autoload.php';
+
+chdir('../..');
+
+$customs =  scandir('custom');
+
+foreach ($customs as $custom) {
+    if ($custom == 'custom.xml' || $custom == '.' || $custom == '..') {
+        continue;
+    }
+
+    \SrcCore\models\DatabasePDO::reset();
+    new \SrcCore\models\DatabasePDO(['customId' => $custom]);
+
+    $migrated = [];
+
+    // Migrate others Field
+    $migrateToCustom = [
+        ['id' => 'description',         'label' => 'Autres informations',             'customType' => 'string', 'modelId' => [1, 2, 3, 4]],
+        ['id' => 'external_reference',  'label' => 'Référence courrier expéditeur',   'customType' => 'string', 'modelId' => [1]],
+        ['id' => 'reference_number',    'label' => 'N° recommandé',                   'customType' => 'string', 'modelId' => [1, 2]],
+        ['id' => 'scan_date',           'label' => 'Date de scan',                    'customType' => 'date',   'modelId' => [1, 2]],
+        ['id' => 'scan_user',           'label' => 'Utilisateur de scan',             'customType' => 'string', 'modelId' => [1, 2]],
+        ['id' => 'scan_location',       'label' => 'Lieu de scan',                    'customType' => 'string', 'modelId' => [1, 2]],
+        ['id' => 'scan_wkstation',      'label' => 'Station de scan',                 'customType' => 'string', 'modelId' => [1, 2]],
+        ['id' => 'scan_batch',          'label' => 'Batch de scan',                   'customType' => 'string', 'modelId' => [1, 2]],
+        ['id' => 'scan_postmark',       'label' => 'Tampon de scan',                  'customType' => 'string', 'modelId' => [1, 2]],
+    ];
+
+    foreach ($migrateToCustom as $migration) {
+        if ($migration['customType'] == 'date') {
+            $where = [$migration['id'].' is not null'];
+        } else {
+            $where = [$migration['id'].' is not null', $migration['id'].' != \'\''];
+        }
+        $columnValues = \SrcCore\models\DatabaseModel::select([
+            'select' => ['res_id', $migration['id']],
+            'table'  => ['res_letterbox'],
+            'where'  => $where
+        ]);
+
+        if (!empty($columnValues)) {
+            $fieldId = \CustomField\models\CustomFieldModel::create([
+                'label'     => $migration['label'],
+                'type'      => $migration['customType'],
+                'values'    => '[]'
+            ]);
+
+            $csColumn = "custom_fields->>''{$fieldId}''";
+            \Basket\models\BasketModel::update(['postSet' => ['basket_clause' => "REPLACE(basket_clause, '{$migration['id']}', '{$csColumn}')"], 'where' => ['1 = ?'], 'data' => [1]]);
+
+            foreach ($migration['modelId'] as $modelId) {
+                $indexingModels = \IndexingModel\models\IndexingModelModel::get([
+                    'select'=> [1],
+                    'where' => ['id = ?'],
+                    'data'  => [$modelId]
+                ]);
+
+                if (!empty($indexingModels)) {
+                    \IndexingModel\models\IndexingModelFieldModel::create([
+                        'model_id'   => $modelId,
+                        'identifier' => 'indexingCustomField_'.$fieldId,
+                        'mandatory'  => 'false',
+                        'unit'       => 'mail'
+                    ]);
+                }
+            }
+    
+            $aValues = [];
+            foreach ($columnValues as $columnValue) {
+                $aValues[] = [$columnValue['res_id'], $fieldId, json_encode($columnValue[$migration['id']])];
+                $valueColumn = json_encode($columnValue[$migration['id']]);
+                \Resource\models\ResModel::update([
+                    'postSet'   => ['custom_fields' => "jsonb_set(custom_fields, '{{$fieldId}}', '{$valueColumn}')"],
+                    'where'     => ['res_id = ?'],
+                    'data'      => [$columnValue['res_id']]
+                ]);
+            }
+
+            $migrated[] = $migration['id'];
+        }
+    }
+
+    if (!empty($migrated)) {
+        $migrated = implode(',', $migrated);
+        printf("Migration vers les champs personnalisés (CUSTOM {$custom}) : Les champs suivants ont été migrés : " . $migrated . ".\n");
+    }
+}
diff --git a/migration/20.03/migrateExtensions.php b/migration/20.03/migrateExtensions.php
new file mode 100644
index 0000000000000000000000000000000000000000..f88f005b18d4a20ce8d7224f23b1970d519af274
--- /dev/null
+++ b/migration/20.03/migrateExtensions.php
@@ -0,0 +1,40 @@
+<?php
+
+require '../../vendor/autoload.php';
+
+chdir('../..');
+
+$migrated = 0;
+$customs =  scandir('custom');
+foreach ($customs as $custom) {
+    if ($custom == 'custom.xml' || $custom == '.' || $custom == '..') {
+        continue;
+    }
+
+    $xmlfile = null;
+    $path = "custom/{$custom}/apps/maarch_entreprise/xml/extensions.xml";
+    if (file_exists($path)) {
+        $xmlfile = simplexml_load_file($path);
+
+        if ($xmlfile) {
+            $i = 0;
+            foreach ($xmlfile->FORMAT as $item) {
+                if (isset($item->index_frame_show)) {
+                    $xmlfile->FORMAT[$i]->canConvert = $item->index_frame_show;
+                    unset($xmlfile->FORMAT[$i]->index_frame_show);
+                }
+                ++$i;
+            }
+
+            $res = $xmlfile->asXML();
+            $fp = fopen($path, "w+");
+            if ($fp) {
+                fwrite($fp, $res);
+            }
+
+            $migrated++;
+        }
+    }
+}
+
+printf("Migration Extensions : " . $migrated . " custom(s) avec un fichier extensions.xml trouvé(s) et migré(s).\n");
diff --git a/migration/20.03/migrateFileplans.php b/migration/20.03/migrateFileplans.php
new file mode 100644
index 0000000000000000000000000000000000000000..8723c88dab4d381b82ba655f66526d3f1555fb74
--- /dev/null
+++ b/migration/20.03/migrateFileplans.php
@@ -0,0 +1,178 @@
+<?php
+
+require '../../vendor/autoload.php';
+
+chdir('../..');
+
+$customs =  scandir('custom');
+
+foreach ($customs as $custom) {
+    if ($custom == 'custom.xml' || $custom == '.' || $custom == '..') {
+        continue;
+    }
+
+    \SrcCore\models\DatabasePDO::reset();
+    new \SrcCore\models\DatabasePDO(['customId' => $custom]);
+
+    $migrated = 0;
+
+    $fileplans = \SrcCore\models\DatabaseModel::select([
+        'select' => ['*'],
+        'table'  => ['fp_fileplan']
+    ]);
+
+    if (!empty($fileplans)) {
+
+        $superadmin = \User\models\UserModel::getByLogin(['select' => ['id'], 'login' => 'superadmin']);
+        if (empty($superadmin)) {
+            $firstMan = \User\models\UserModel::get(['select' => ['id'], 'orderBy' => ['id'], 'limit' => 1]);
+            $masterOwnerId = $firstMan[0]['id'];
+        } else {
+            $masterOwnerId = $superadmin['id'];
+        }
+
+        $masterFolderId = \Folder\models\FolderModel::create([
+            'label'     => 'Reprise Plan de Classement',
+            'public'    => true,
+            'user_id'   => $masterOwnerId,
+            'parent_id' => null,
+            'level'     => 0
+        ]);
+
+        $GLOBALS['entities'] = \Entity\models\EntityModel::get(['select' => ['id']]);
+        fillEntities($masterFolderId);
+
+        foreach ($fileplans as $fileplan) {
+            $positions = \SrcCore\models\DatabaseModel::select([
+                'select'    => ['*'],
+                'table'     => ['fp_fileplan_positions'],
+                'where'     => ['fileplan_id = ?'],
+                'data'      => [$fileplan['fileplan_id']]
+            ]);
+
+            if (empty($fileplan['user_id'])) {
+                $id = \Folder\models\FolderModel::create([
+                    'label'     => $fileplan['fileplan_label'],
+                    'public'    => true,
+                    'user_id'   => $masterOwnerId,
+                    'parent_id' => $masterFolderId,
+                    'level'     => 1
+                ]);
+                fillEntities($id);
+
+                foreach ($positions as $position) {
+                    if (empty($position['parent_id'])) {
+                        $id = \Folder\models\FolderModel::create([
+                            'label'     => $position['position_label'],
+                            'public'    => true,
+                            'user_id'   => $masterOwnerId,
+                            'parent_id' => $id,
+                            'level'     => 2
+                        ]);
+                        fillEntities($id);
+
+                        runPositionsForPublic($positions, $position['position_id'], $id, 3, $masterOwnerId);
+                    }
+                }
+            } else {
+                $user = \User\models\UserModel::getByLogin(['select' => ['id'], 'login' => $fileplan['user_id']]);
+                if (empty($user)) {
+                    continue;
+                }
+
+                $id = \Folder\models\FolderModel::create([
+                    'label'     => $fileplan['fileplan_label'],
+                    'public'    => false,
+                    'user_id'   => $user['id'],
+                    'parent_id' => null,
+                    'level'     => 0
+                ]);
+
+                foreach ($positions as $position) {
+                    if (empty($position['parent_id'])) {
+                        $id = \Folder\models\FolderModel::create([
+                            'label'     => $position['position_label'],
+                            'public'    => false,
+                            'user_id'   => $user['id'],
+                            'parent_id' => $id,
+                            'level'     => 1
+                        ]);
+                        fillResources($id, $position['position_id']);
+
+                        runPositionsForPrivate($positions, $position['position_id'], $id, 2, $user['id']);
+                    }
+                }
+            }
+
+            ++$migrated;
+        }
+    }
+
+    printf("Migration Plan de Classement (CUSTOM {$custom}) : " . $migrated . " Plan trouvé(s) et migré(s).\n");
+}
+
+function runPositionsForPublic($positions, $parentPositionId, $parentFolderId, $level, $masterOwnerId)
+{
+    foreach ($positions as $position) {
+        if ($position['parent_id'] == $parentPositionId) {
+            $id = \Folder\models\FolderModel::create([
+                'label'     => $position['position_label'],
+                'public'    => true,
+                'user_id'   => $masterOwnerId,
+                'parent_id' => $parentFolderId,
+                'level'     => $level
+            ]);
+            fillEntities($id);
+
+            runPositionsForPublic($positions, $position['position_id'], $id, $level + 1, $masterOwnerId);
+            break;
+        }
+    }
+}
+
+function runPositionsForPrivate($positions, $parentPositionId, $parentFolderId, $level, $ownerId)
+{
+    foreach ($positions as $position) {
+        if ($position['parent_id'] == $parentPositionId) {
+            $id = \Folder\models\FolderModel::create([
+                'label'     => $position['position_label'],
+                'public'    => false,
+                'user_id'   => $ownerId,
+                'parent_id' => $parentFolderId,
+                'level'     => $level
+            ]);
+            fillResources($id, $position['position_id']);
+
+            runPositionsForPrivate($positions, $position['position_id'], $id, $level + 1, $ownerId);
+            break;
+        }
+    }
+}
+
+function fillEntities($folderId)
+{
+    foreach ($GLOBALS['entities'] as $entity) {
+        \Folder\models\EntityFolderModel::create([
+            'folder_id' => $folderId,
+            'entity_id' => $entity['id'],
+            'edition'   => true,
+        ]);
+    }
+}
+
+function fillResources($folderId, $positionId)
+{
+    $resources = \SrcCore\models\DatabaseModel::select([
+        'select'    => ['*'],
+        'table'     => ['fp_res_fileplan_positions'],
+        'where'     => ['position_id = ?', 'coll_id = ?'],
+        'data'      => [$positionId, 'letterbox_coll']
+    ]);
+
+    foreach ($resources as $resource) {
+        \Folder\models\ResourceFolderModel::create([
+            'folder_id' => $folderId,
+            'res_id'    => $resource['res_id']
+        ]);
+    }
+}
diff --git a/migration/20.03/migrateFolders.php b/migration/20.03/migrateFolders.php
new file mode 100644
index 0000000000000000000000000000000000000000..6ef77d43f8a6348a2c969544fd852dbed5f5f14f
--- /dev/null
+++ b/migration/20.03/migrateFolders.php
@@ -0,0 +1,119 @@
+<?php
+
+require '../../vendor/autoload.php';
+
+chdir('../..');
+
+$customs =  scandir('custom');
+
+foreach ($customs as $custom) {
+    if ($custom == 'custom.xml' || $custom == '.' || $custom == '..') {
+        continue;
+    }
+
+    \SrcCore\models\DatabasePDO::reset();
+    new \SrcCore\models\DatabasePDO(['customId' => $custom]);
+
+    $migrated = 0;
+    $folders = \SrcCore\models\DatabaseModel::select([
+        'select'   => ['folders_system_id', 'typist', 'destination', 'folder_name', 'parent_id'],
+        'table'    => ['folder_tmp'],
+        'order_by' => ['folder_level asc']
+    ]);
+
+    if (!empty($folders)) {
+        $superadmin = \User\models\UserModel::getByLogin(['select' => ['id'], 'login' => 'superadmin']);
+        if (empty($superadmin)) {
+            $firstMan = \User\models\UserModel::get(['select' => ['id'], 'orderBy' => ['id'], 'limit' => 1]);
+            $masterOwnerId = $firstMan[0]['id'];
+        } else {
+            $masterOwnerId = $superadmin['id'];
+        }
+
+        $masterFolderId = \Folder\models\FolderModel::create([
+            'label'     => 'Reprise Dossier',
+            'public'    => true,
+            'user_id'   => $masterOwnerId,
+            'parent_id' => null,
+            'level'     => 0
+        ]);
+
+        $GLOBALS['entities'] = \Entity\models\EntityModel::get(['select' => ['id']]);
+        fillEntities($masterFolderId);
+
+        $aFolderIdMap = [];
+        foreach ($folders as $folder) {
+            $user = \User\models\UserModel::getByLogin(['select' => ['id'], 'login' => $folder['typist']]);
+            if (empty($folder['destination'])) {
+                // Public
+                if (empty($user)) {
+                    $user['id'] = $masterOwnerId;
+                }
+
+                $folderId = \Folder\models\FolderModel::create([
+                    'label'     => $folder['folder_name'],
+                    'public'    => true,
+                    'user_id'   => $user['id'],
+                    'parent_id' => empty($folder['parent_id']) ? $masterFolderId : $aFolderIdMap[$folder['parent_id']],
+                    'level'     => empty($folder['parent_id']) ? 1 : 2
+                ]);
+                fillEntities($folderId);
+            } elseif (!empty($user)) {
+                // Private
+                $entity = \Entity\models\EntityModel::getByEntityId(['select' => ['id'], 'entityId' => $folder['destination']]);
+                if (empty($entity)) {
+                    continue;
+                }
+
+                $folderId = \Folder\models\FolderModel::create([
+                    'label'     => $folder['folder_name'],
+                    'public'    => true,
+                    'user_id'   => $user['id'],
+                    'parent_id' => $aFolderIdMap[$folder['parent_id']],
+                    'level'     => empty($folder['parent_id']) ? 0 : 1
+                ]);
+
+                \Folder\models\EntityFolderModel::create([
+                    'folder_id' => $folderId,
+                    'entity_id' => $entity['id'],
+                    'edition'   => true,
+                ]);
+
+                fillResources($folderId, $folder['folders_system_id']);
+            }
+
+            $aFolderIdMap[$folder['folders_system_id']] = $folderId;
+            ++$migrated;
+        }
+    }
+
+    printf("Migration Dossier (CUSTOM {$custom}) : " . $migrated . " Dossier(s) trouvé(s) et migré(s).\n");
+}
+
+function fillEntities($folderId)
+{
+    foreach ($GLOBALS['entities'] as $entity) {
+        \Folder\models\EntityFolderModel::create([
+            'folder_id' => $folderId,
+            'entity_id' => $entity['id'],
+            'edition'   => true,
+        ]);
+    }
+}
+
+function fillResources($folderId, $folderSystemId)
+{
+    $resources = \SrcCore\models\DatabaseModel::select([
+        'select'    => ['res_id'],
+        'table'     => ['res_letterbox'],
+        'where'     => ['folders_system_id = ?'],
+        'data'      => [$folderSystemId]
+    ]);
+
+    foreach ($resources as $resource) {
+        \Folder\models\ResourceFolderModel::create([
+            'folder_id' => $folderId,
+            'res_id'    => $resource['res_id']
+        ]);
+    }
+}
diff --git a/migration/20.03/migrateFullText.php b/migration/20.03/migrateFullText.php
new file mode 100644
index 0000000000000000000000000000000000000000..39a9192bfc58bfd7d8db5f5f28469f1e11d3e7ba
--- /dev/null
+++ b/migration/20.03/migrateFullText.php
@@ -0,0 +1,65 @@
+<?php
+
+require '../../vendor/autoload.php';
+
+chdir('../..');
+
+$migrated = 0;
+$customs =  scandir('custom');
+foreach ($customs as $custom) {
+    if ($custom == 'custom.xml' || $custom == '.' || $custom == '..') {
+        continue;
+    }
+
+    \SrcCore\models\DatabasePDO::reset();
+    new \SrcCore\models\DatabasePDO(['customId' => $custom]);
+
+    $xmlfile = null;
+    $path = "custom/{$custom}/apps/maarch_entreprise/xml/config.xml";
+    $xmlfile = simplexml_load_file($path);
+
+    if ($xmlfile) {
+
+        $i = 0;
+        foreach ($xmlfile->COLLECTION as $collection) {
+            if ((string)$collection->table == 'res_letterbox') {
+                $collId = 'letterbox_coll';
+            } elseif ((string)$collection->table == 'res_attachments') {
+                $collId = 'attachments_coll';
+            } else {
+                unset($xmlfile->COLLECTION[$i]);
+                continue;
+            }
+
+            \Docserver\models\DocserverModel::update([
+                'set'   => [
+                    'path_template' => (string)$collection->path_to_lucene_index
+                ],
+                'where' => ['docserver_type_id = ?', 'coll_id = ?'],
+                'data'  => ['FULLTEXT', $collId]
+            ]);
+            unset($xmlfile->COLLECTION[$i]->path_to_lucene_index);
+
+            ++$i;
+        }
+
+        $i = 0;
+        foreach ($xmlfile->MODULES as $module) {
+            if ((string)$module->moduleid == 'full_text') {
+                unset($xmlfile->MODULES[$i]);
+                break;
+            }
+            ++$i;
+        }
+
+        $res = $xmlfile->asXML();
+        $fp = fopen($path, "w+");
+        if ($fp) {
+            fwrite($fp, $res);
+        }
+
+        $migrated++;
+    }
+}
+
+printf("Migration Full Text : " . $migrated . " custom(s) avec un fichier config.xml trouvé(s) et migré(s).\n");
diff --git a/migration/20.03/migrateIndexing.php b/migration/20.03/migrateIndexing.php
new file mode 100644
index 0000000000000000000000000000000000000000..70a7e69612afd1e1c85676adc09f1038b46d01c9
--- /dev/null
+++ b/migration/20.03/migrateIndexing.php
@@ -0,0 +1,232 @@
+<?php
+
+require '../../vendor/autoload.php';
+
+chdir('../..');
+
+$customs =  scandir('custom');
+foreach ($customs as $custom) {
+    if ($custom == 'custom.xml' || $custom == '.' || $custom == '..') {
+        continue;
+    }
+
+    \SrcCore\models\DatabasePDO::reset();
+    new \SrcCore\models\DatabasePDO(['customId' => $custom]);
+
+    $groupBasket = \Basket\models\GroupBasketModel::get(['select' => ['group_id'], 'where' => ['basket_id = ?'], 'data' => ['IndexingBasket']]);
+
+    $migrated = 0;
+    foreach ($groupBasket as $value) {
+        $hasService = \SrcCore\models\DatabaseModel::select([
+            'select'    => [1],
+            'table'     => ['usergroups_services'],
+            'where'     => ['group_id = ?', 'service_id = ?'],
+            'data'      => [$value['group_id'], 'index_mlb']
+        ]);
+
+        if (!empty($hasService)) {
+            $actions = [];
+            $entities = [];
+            $keywords = [];
+            \Group\models\GroupModel::update([
+                'set'   => ['can_index' => 'true'],
+                'where' => ['group_id = ?'],
+                'data'  => [$value['group_id']]
+            ]);
+
+            // ACTIONS WITHOUT DIRECT STATUS
+            $actionsWithStatusesToCreate = \SrcCore\models\DatabaseModel::select([
+                'select'    => ['status_id', 'action_id'],
+                'table'     => ['groupbasket_status'],
+                'where'     => ['group_id = ?', 'basket_id = ?'],
+                'data'      => [$value['group_id'], 'IndexingBasket'],
+                'order_by'  => ['"order"']
+            ]);
+
+            foreach ($actionsWithStatusesToCreate as $item) {
+                $existingActions = \SrcCore\models\DatabaseModel::select([
+                    'select'    => ['id'],
+                    'table'     => ['actions'],
+                    'where'     => ['id_status = ?', '(action_page = ? OR component = ?)'],
+                    'data'      => [$item['status_id'], 'confirm_status', 'confirmAction']
+                ]);
+
+                if (!empty($existingActions[0])) {
+                    $actions[] = (string)$existingActions[0]['id'];
+                } else {
+                    $statusLabel = \Status\models\StatusModel::getById(['id' => $item['status_id'], 'select' => ['label_status']]);
+
+                    $id = \Action\models\ActionModel::create([
+                        'label_action'  => "Enregistrer vers le status : {$statusLabel['label_status']}" ,
+                        'id_status'     => $item['status_id'],
+                        'history'       => 'Y',
+                        'component'     => 'confirmAction'
+                    ]);
+                    \Action\models\ActionModel::createCategories(['id' => $id, 'categories' => ['incoming', 'outgoing', 'internal', 'ged_doc']]);
+
+                    $actions[] = (string)$id;
+                }
+
+            }
+
+            // ACTIONS WITH STATUS
+            $actionsToMigrate = \SrcCore\models\DatabaseModel::select([
+                'select'    => ['id_action'],
+                'table'     => ['actions_groupbaskets, actions'],
+                'where'     => ['group_id = ?', 'basket_id = ?', 'id_action = id', '(action_page = ? OR component = ? OR action_page = ?)'],
+                'data'      => [$value['group_id'], 'IndexingBasket', 'confirm_status', 'confirmAction', "''"]
+            ]);
+
+            foreach ($actionsToMigrate as $item) {
+                if (!in_array($item['id_action'], $actions)) {
+                    $actions[] = (string)$item['id_action'];
+                }
+            }
+
+            // KEYWORDS + ENTITIES
+            $keywordsAndEntities = \SrcCore\models\DatabaseModel::select([
+                'select'    => ['entity_id', 'keyword'],
+                'table'     => ['groupbasket_redirect'],
+                'where'     => ['group_id = ?', 'basket_id = ?', 'redirect_mode = ?'],
+                'data'      => [$value['group_id'], 'IndexingBasket', 'ENTITY']
+            ]);
+            foreach ($keywordsAndEntities as $item) {
+                if (!empty($item['keyword']) && !in_array($item['keyword'], $keywords)) {
+                    $keywords[] = $item['keyword'];
+                } elseif (!empty($item['entity_id'])) {
+                    $entityToMigrate = \Entity\models\EntityModel::getByEntityId(['entityId' => $item['entity_id'], 'select' => ['id']]);
+                    if (!in_array($entityToMigrate['id'], $entities)) {
+                        $entities[] = (string)$entityToMigrate['id'];
+                    }
+                }
+            }
+
+            // UPDATE INDEXING PARAMS
+            \Group\models\GroupModel::update([
+                'set'   => ['indexation_parameters' => json_encode(['actions' => $actions, 'entities' => $entities, 'keywords' => $keywords])],
+                'where' => ['group_id = ?'],
+                'data'  => [$value['group_id']]
+            ]);
+
+            $migrated++;
+        }
+    }
+    \SrcCore\models\DatabaseModel::delete([
+        'table' => 'groupbasket',
+        'where' => ['basket_id = ?'],
+        'data'  => ['IndexingBasket']
+    ]);
+    \SrcCore\models\DatabaseModel::delete([
+        'table' => 'actions_groupbaskets',
+        'where' => ['basket_id = ?'],
+        'data'  => ['IndexingBasket']
+    ]);
+    \SrcCore\models\DatabaseModel::delete([
+        'table' => 'groupbasket_redirect',
+        'where' => ['basket_id = ?'],
+        'data'  => ['IndexingBasket']
+    ]);
+    \SrcCore\models\DatabaseModel::delete([
+        'table' => 'groupbasket_status',
+        'where' => ['basket_id = ?'],
+        'data'  => ['IndexingBasket']
+    ]);
+    \SrcCore\models\DatabaseModel::delete([
+        'table' => 'users_baskets_preferences',
+        'where' => ['basket_id = ?'],
+        'data'  => ['IndexingBasket']
+    ]);
+    \SrcCore\models\DatabaseModel::delete([
+        'table' => 'baskets',
+        'where' => ['basket_id = ?'],
+        'data'  => ['IndexingBasket']
+    ]);
+    \SrcCore\models\DatabaseModel::delete([
+        'table' => 'usergroups_services',
+        'where' => ['service_id = ?'],
+        'data'  => ['index_mlb']
+    ]);
+
+    printf("Migration Indexing Basket (CUSTOM {$custom}) : " . $migrated . " groupe(s) avec le service et la bannette IndexingBasket trouvé(s) et migré(s).\n");
+
+
+    //MIGRATION ACTIONS AVEC STATUS
+    $basketsWithStatuses = \SrcCore\models\DatabaseModel::select([
+        'select'    => ['group_id', 'basket_id', 'status_id', 'action_id'],
+        'table'     => ['groupbasket_status']
+    ]);
+
+    $migrated = 0;
+    $actionsToDelete = [];
+    foreach ($basketsWithStatuses as $value) {
+        if (!in_array($value['action_id'], $actionsToDelete)) {
+            $actionsToDelete[] = $value['action_id'];
+        }
+
+        $existingActions = \SrcCore\models\DatabaseModel::select([
+            'select'    => ['id'],
+            'table'     => ['actions'],
+            'where'     => ['id_status = ?', '(action_page = ? OR component = ?)'],
+            'data'      => [$value['status_id'], 'confirm_status', 'confirmAction']
+        ]);
+
+        if (!empty($existingActions[0])) {
+            $id = (string)$existingActions[0]['id'];
+        } else {
+            $statusLabel = \Status\models\StatusModel::getById(['id' => $value['status_id'], 'select' => ['label_status']]);
+
+            $id = \Action\models\ActionModel::create([
+                'label_action'  => "Enregistrer vers le status : {$statusLabel['label_status']}" ,
+                'id_status'     => $value['status_id'],
+                'history'       => 'Y',
+                'component'     => 'confirmAction'
+            ]);
+            \Action\models\ActionModel::createCategories(['id' => $id, 'categories' => ['incoming', 'outgoing', 'internal', 'ged_doc']]);
+        }
+        $actionAlreadyExists = \Basket\models\ActionGroupBasketModel::get([
+            'select'            => [1],
+            'where'             => ['group_id = ?', 'basket_id = ?', 'id_action = ?'],
+            'data'          => [$value['group_id'], $value['basket_id'], $id]
+        ]);
+        if (empty($actionAlreadyExists)) {
+            \Basket\models\ActionGroupBasketModel::create([
+                'id'                => $value['basket_id'],
+                'groupId'           => $value['group_id'],
+                'actionId'          => $id,
+                'whereClause'       => '',
+                'usedInBasketlist'  => 'N',
+                'usedInActionPage'  => 'Y',
+                'defaultActionList' => 'N'
+            ]);
+        }
+
+        $migrated++;
+    }
+
+    if (!empty($actionsToDelete)) {
+        \SrcCore\models\DatabaseModel::delete([
+            'table' => 'actions',
+            'where' => ['id in (?)'],
+            'data'  => [$actionsToDelete]
+        ]);
+        \SrcCore\models\DatabaseModel::delete([
+            'table' => 'actions_groupbaskets',
+            'where' => ['id_action in (?)'],
+            'data'  => [$actionsToDelete]
+        ]);
+        \SrcCore\models\DatabaseModel::delete([
+            'table' => 'actions_categories',
+            'where' => ['action_id in (?)'],
+            'data'  => [$actionsToDelete]
+        ]);
+    }
+
+    \SrcCore\models\DatabaseModel::update([
+        'set'   => ['keyword' => ''],
+        'table' => 'actions',
+        'where' => ['keyword = ?'],
+        'data'  => ['indexing']
+    ]);
+
+    printf("Migration Indexing Basket (CUSTOM {$custom}) : " . $migrated . " action(s) avec des status (mot clé indexation) trouvé(s) et migré(s).\n");
+}
diff --git a/migration/20.03/migrateLinkedResources.php b/migration/20.03/migrateLinkedResources.php
new file mode 100644
index 0000000000000000000000000000000000000000..b7cb0d4206b3ada0ad2650e71cfcf8dda96950cb
--- /dev/null
+++ b/migration/20.03/migrateLinkedResources.php
@@ -0,0 +1,44 @@
+<?php
+
+use Resource\models\ResModel;
+
+require '../../vendor/autoload.php';
+
+chdir('../..');
+
+$customs = scandir('custom');
+
+foreach ($customs as $custom) {
+    if ($custom == 'custom.xml' || $custom == '.' || $custom == '..') {
+        continue;
+    }
+
+    \SrcCore\models\DatabasePDO::reset();
+    new \SrcCore\models\DatabasePDO(['customId' => $custom]);
+
+    $migrated = 0;
+
+    $links = \SrcCore\models\DatabaseModel::select([
+        'select' => ['res_parent', 'res_child'],
+        'table'  => ['res_linked']
+    ]);
+
+    foreach ($links as $link) {
+        $resParent = (string)$link['res_parent'];
+        $resChild = (string)$link['res_child'];
+
+        ResModel::update([
+            'postSet' => ['linked_resources' => "jsonb_insert(linked_resources, '{0}', '\"{$resChild}\"')"],
+            'where'   => ['res_id = ?', "(linked_resources @> ?) = false"],
+            'data'    => [(int)$resParent, "\"{$resChild}\""]
+        ]);
+        ResModel::update([
+            'postSet' => ['linked_resources' => "jsonb_insert(linked_resources, '{0}', '\"{$resParent}\"')"],
+            'where'   => ['res_id = ?', "(linked_resources @> ?) = false"],
+            'data'    => [(int)$resChild, "\"{$resParent}\""]
+        ]);
+        $migrated++;
+    }
+
+    printf("Migration des liaisons dans res_letterbox (CUSTOM {$custom}) : " . $migrated . " liaisons migrés.\n");
+}
diff --git a/migration/20.03/migrateListTemplates.php b/migration/20.03/migrateListTemplates.php
new file mode 100644
index 0000000000000000000000000000000000000000..b222917eb2306843f59136664168fc13d95850c3
--- /dev/null
+++ b/migration/20.03/migrateListTemplates.php
@@ -0,0 +1,89 @@
+<?php
+
+require '../../vendor/autoload.php';
+
+chdir('../..');
+
+$customs =  scandir('custom');
+foreach ($customs as $custom) {
+    if ($custom == 'custom.xml' || $custom == '.' || $custom == '..') {
+        continue;
+    }
+
+    $migrated = 0;
+
+    \SrcCore\models\DatabasePDO::reset();
+    new \SrcCore\models\DatabasePDO(['customId' => $custom]);
+
+    $listModels = \SrcCore\models\DatabaseModel::select([
+        'select'    => ['*'],
+        'table'     => ['listmodels'],
+        'order_by'  => ['sequence', 'id']
+    ]);
+
+    $formattedListModels = [];
+    foreach ($listModels as $listModel) {
+        $formattedId = $listModel['object_id'].$listModel['object_type'];
+        if (empty($formattedListModels[$formattedId])) {
+            $formattedListModels[$formattedId] = [
+                'object_id'     => $listModel['object_id'],
+                'object_type'   => $listModel['object_type'],
+                'title'         => $listModel['title'],
+                'description'   => $listModel['description'],
+                'items'         => [$listModel]
+            ];
+        } else {
+            $formattedListModels[$formattedId]['items'][] = $listModel;
+        }
+    }
+
+    foreach ($formattedListModels as $value) {
+        $entityId = null;
+        if (strpos($value['object_id'], 'VISA_CIRCUIT_') === false && strpos($value['object_id'], 'AVIS_CIRCUIT_') === false) {
+            $entity = \Entity\models\EntityModel::getByEntityId(['entityId' => $value['object_id'], 'select' => ['id']]);
+            $entityId = $entity['id'];
+        }
+
+        $type = $value['object_type'] == 'entity_id' ? 'diffusionList' : ($value['object_type'] == 'VISA_CIRCUIT' ? 'visaCircuit' : 'opinionCircuit');
+        if (!empty($value['title'])) {
+            $title = $value['title'];
+        } elseif (!empty($value['description'])) {
+            $title = $value['description'];
+        } else {
+            $title = $value['object_id'];
+        }
+
+        $listTemplateId = \Entity\models\ListTemplateModel::create([
+            'title'         => $title,
+            'description'   => $value['description'],
+            'type'          => $type,
+            'entity_id'     => $entityId
+        ]);
+
+        foreach ($value['items'] as $key => $item) {
+            if (empty($item['item_id'])) {
+                continue;
+            }
+            if ($item['item_type'] == 'user_id') {
+                $itemId = \User\models\UserModel::getByLogin(['login' => $item['item_id'], 'select' => ['id']]);
+            } else {
+                $itemId = \Entity\models\EntityModel::getByEntityId(['entityId' => $item['item_id'], 'select' => ['id']]);
+            }
+            if (empty($itemId['id'])) {
+                continue;
+            }
+
+            \Entity\models\ListTemplateItemModel::create([
+                'list_template_id'  => $listTemplateId,
+                'item_id'           => $itemId['id'],
+                'item_type'         => $item['item_type'] == 'user_id' ? 'user' : 'entity',
+                'item_mode'         => $item['item_mode'],
+                'sequence'          => $key
+            ]);
+        }
+
+        ++$migrated;
+    }
+
+    printf("Migration List templates (CUSTOM {$custom}) : " . $migrated . " modèles de listes trouvé(s) et migré(s).\n");
+}
diff --git a/migration/20.03/migrateM2MConfiguration.php b/migration/20.03/migrateM2MConfiguration.php
new file mode 100644
index 0000000000000000000000000000000000000000..94dd0479eee14f90808bd0264fcebd5aadef65e9
--- /dev/null
+++ b/migration/20.03/migrateM2MConfiguration.php
@@ -0,0 +1,35 @@
+<?php
+
+require '../../vendor/autoload.php';
+
+chdir('../..');
+
+$migrated = 0;
+$customs =  scandir('custom');
+foreach ($customs as $custom) {
+    if ($custom == 'custom.xml' || $custom == '.' || $custom == '..') {
+        continue;
+    }
+
+    $xmlfile = null;
+    $path = "custom/{$custom}/apps/maarch_entreprise/xml/m2m_config.xml";
+    if (file_exists($path)) {
+        $xmlfile = simplexml_load_file($path);
+
+        if ($xmlfile) {
+            $xmlfile->indexingModelId = 1;
+            unset($xmlfile->contacts_v2);
+            unset($xmlfile->contact_addresses);
+
+            $res = $xmlfile->asXML();
+            $fp = fopen($path, "w+");
+            if ($fp) {
+                fwrite($fp, $res);
+            }
+
+            $migrated++;
+        }
+    }
+}
+
+printf("Migration m2m configuration : " . $migrated . " custom(s) avec un fichier m2m_config.xml trouvé(s) et migré(s).\n");
diff --git a/migration/20.03/migrateMenuEntities.php b/migration/20.03/migrateMenuEntities.php
new file mode 100644
index 0000000000000000000000000000000000000000..0576d0df00aea737efccca98d8fd335e6bc22817
--- /dev/null
+++ b/migration/20.03/migrateMenuEntities.php
@@ -0,0 +1,59 @@
+<?php
+
+require '../../vendor/autoload.php';
+
+chdir('../..');
+
+$nonReadableFiles = [];
+$migrated = 0;
+$customs =  scandir('custom');
+
+foreach ($customs as $custom) {
+    if ($custom == 'custom.xml' || $custom == '.' || $custom == '..') {
+        continue;
+    }
+
+    $natures = [];
+    $path = "custom/{$custom}/modules/entities/xml/menu.xml";
+    if (file_exists($path)) {
+        if (!is_readable($path) || !is_writable($path)) {
+            $nonReadableFiles[] = $path;
+            continue;
+        }
+        $loadedXml = simplexml_load_file($path);
+        
+        if ($loadedXml) {
+            $i = 0;
+            foreach ($loadedXml->MENU as $value) {
+                if ($value->id == 'entities_print_sep_mlb') {
+                    $loadedXml->MENU[$i]->url = '/separators/print';
+                    $loadedXml->MENU[$i]->angular = 'true';
+                    break;
+                }
+                ++$i;
+            }
+            $res = formatXml($loadedXml);
+            $fp = fopen($path, "w+");
+            if ($fp) {
+                fwrite($fp, $res);
+                $migrated++;
+            }
+        }
+    }
+}
+
+foreach ($nonReadableFiles as $file) {
+    printf("The file %s it is not readable or not writable.\n", $file);
+}
+
+printf($migrated . " custom(s) avec menu.xml (entities) trouvé(s) et migré(s).\n");
+
+function formatXml($simpleXMLElement)
+{
+    $xmlDocument = new DOMDocument('1.0');
+    $xmlDocument->preserveWhiteSpace = false;
+    $xmlDocument->formatOutput = true;
+    $xmlDocument->loadXML($simpleXMLElement->asXML());
+
+    return $xmlDocument->saveXML();
+}
diff --git a/migration/20.03/migrateOldIndexingModels.php b/migration/20.03/migrateOldIndexingModels.php
new file mode 100644
index 0000000000000000000000000000000000000000..e3adfc530f6387be0f4bad8b967e6ae7f504138f
--- /dev/null
+++ b/migration/20.03/migrateOldIndexingModels.php
@@ -0,0 +1,140 @@
+<?php
+
+$aDataIncoming['incoming'] = [
+    'doctype'               => ['mandatory' => 'true', 'default_value' => '""', 'unit' => 'mail'],
+    'priority'              => ['mandatory' => 'true', 'default_value' => '""', 'unit' => 'mail'],
+    'confidentiality'          => ['mandatory' => 'true', 'default_value' => '""', 'unit' => 'mail'],
+    'docDate'               => ['mandatory' => 'true', 'default_value' => '""', 'unit' => 'mail'],
+    'arrivalDate'           => ['mandatory' => 'true', 'default_value' => '""', 'unit' => 'mail'],
+    'subject'               => ['mandatory' => 'true', 'default_value' => '""', 'unit' => 'mail'],
+    'indexingCustomField_1' => ['mandatory' => 'false', 'default_value' => '""', 'unit' => 'mail'],
+    'senders'               => ['mandatory' => 'true', 'default_value' => '""', 'unit' => 'contact'],
+    'recipients'            => ['mandatory' => 'false', 'default_value' => '""', 'unit' => 'contact'],
+    'initiator'             => ['mandatory' => 'true', 'default_value' => '""', 'unit' => 'process'],
+    'destination'           => ['mandatory' => 'true', 'default_value' => '""', 'unit' => 'process'],
+    'processLimitDate'      => ['mandatory' => 'true', 'default_value' => '""', 'unit' => 'process'],
+    'folder'                => ['mandatory' => 'false', 'default_value' => '""', 'unit' => 'classement'],
+    'tags'                  => ['mandatory' => 'false', 'default_value' => '""', 'unit' => 'classement'],
+];
+
+$aDataIncoming['outgoing'] = [
+    'doctype'               => ['mandatory' => 'true', 'default_value' => '""', 'unit' => 'mail'],
+    'priority'              => ['mandatory' => 'true', 'default_value' => '""', 'unit' => 'mail'],
+    'confidentiality'          => ['mandatory' => 'true', 'default_value' => '""', 'unit' => 'mail'],
+    'docDate'               => ['mandatory' => 'true', 'default_value' => '""', 'unit' => 'mail'],
+    'subject'               => ['mandatory' => 'true', 'default_value' => '""', 'unit' => 'mail'],
+    'indexingCustomField_1' => ['mandatory' => 'false', 'default_value' => '""', 'unit' => 'mail'],
+    'senders'               => ['mandatory' => 'false', 'default_value' => '""', 'unit' => 'contact'],
+    'recipients'            => ['mandatory' => 'true', 'default_value' => '""', 'unit' => 'contact'],
+    'initiator'             => ['mandatory' => 'true', 'default_value' => '""', 'unit' => 'process'],
+    'destination'           => ['mandatory' => 'true', 'default_value' => '""', 'unit' => 'process'],
+    'processLimitDate'      => ['mandatory' => 'true', 'default_value' => '""', 'unit' => 'process'],
+    'folder'                => ['mandatory' => 'false', 'default_value' => '""', 'unit' => 'classement'],
+    'tags'                  => ['mandatory' => 'false', 'default_value' => '""', 'unit' => 'classement'],
+];
+
+$aDataIncoming['internal'] = [
+    'doctype'               => ['mandatory' => 'true', 'default_value' => '""', 'unit' => 'mail'],
+    'priority'              => ['mandatory' => 'true', 'default_value' => '""', 'unit' => 'mail'],
+    'confidentiality'          => ['mandatory' => 'true', 'default_value' => '""', 'unit' => 'mail'],
+    'docDate'               => ['mandatory' => 'true', 'default_value' => '""', 'unit' => 'mail'],
+    'subject'               => ['mandatory' => 'true', 'default_value' => '""', 'unit' => 'mail'],
+    'indexingCustomField_1' => ['mandatory' => 'false', 'default_value' => '""', 'unit' => 'mail'],
+    'senders'               => ['mandatory' => 'true', 'default_value' => '""', 'unit' => 'contact'],
+    'recipients'            => ['mandatory' => 'false', 'default_value' => '""', 'unit' => 'contact'],
+    'initiator'             => ['mandatory' => 'true', 'default_value' => '""', 'unit' => 'process'],
+    'destination'           => ['mandatory' => 'true', 'default_value' => '""', 'unit' => 'process'],
+    'processLimitDate'      => ['mandatory' => 'true', 'default_value' => '""', 'unit' => 'process'],
+    'folder'                => ['mandatory' => 'false', 'default_value' => '""', 'unit' => 'classement'],
+    'tags'                  => ['mandatory' => 'false', 'default_value' => '""', 'unit' => 'classement'],
+];
+
+$aDataIncoming['ged_doc'] = [
+    'doctype'               => ['mandatory' => 'true', 'default_value' => '""', 'unit' => 'mail'],
+    'confidentiality'          => ['mandatory' => 'true', 'default_value' => '""', 'unit' => 'mail'],
+    'docDate'               => ['mandatory' => 'true', 'default_value' => '""', 'unit' => 'mail'],
+    'subject'               => ['mandatory' => 'true', 'default_value' => '""', 'unit' => 'mail'],
+    'senders'               => ['mandatory' => 'true', 'default_value' => '""', 'unit' => 'contact'],
+    'recipients'         => ['mandatory' => 'false', 'default_value' => '""', 'unit' => 'contact'],
+    'initiator'             => ['mandatory' => 'true', 'default_value' => '""', 'unit' => 'process'],
+    'destination'           => ['mandatory' => 'true', 'default_value' => '""', 'unit' => 'process'],
+];
+
+require '../../vendor/autoload.php';
+
+chdir('../..');
+
+$customs =  scandir('custom');
+
+foreach ($customs as $custom) {
+    if ($custom == 'custom.xml' || $custom == '.' || $custom == '..') {
+        continue;
+    }
+
+    \SrcCore\models\DatabasePDO::reset();
+    new \SrcCore\models\DatabasePDO(['customId' => $custom]);
+
+
+    $oldIndexingModels = \SrcCore\models\DatabaseModel::select([
+        'select' => ['*'],
+        'table'  => ['indexingmodels']
+    ]);
+
+    $superadmin = \User\models\UserModel::getByLogin(['select' => ['id'], 'login' => 'superadmin']);
+    if (empty($superadmin)) {
+        $firstMan = \User\models\UserModel::get(['select' => ['id'], 'orderBy' => ['id'], 'limit' => 1]);
+        $masterOwnerId = $firstMan[0]['id'];
+    } else {
+        $masterOwnerId = $superadmin['id'];
+    }
+
+    $migrated = 0;
+    foreach ($oldIndexingModels as $oldIndexingModel) {
+        $fieldContent = json_decode($oldIndexingModel['fields_content'], true);
+        if (empty($fieldContent['category_id'])) {
+            continue;
+        }
+        $datasToImport = $aDataIncoming[$fieldContent['category_id']];
+
+        $modelId = \IndexingModel\models\IndexingModelModel::create([
+            'label'     => $oldIndexingModel['label'],
+            'category'  => $fieldContent['category_id'],
+            'default'   => 'false',
+            'owner'     => $masterOwnerId,
+            'private'   => 'false'
+        ]);
+
+        foreach ($fieldContent as $key => $field) {
+            if ($key == 'type_id') {
+                $doctype = \Doctype\models\DoctypeModel::get(['select' => [1], 'where' => ['type_id = ?', 'enabled = ?'], 'data' => [$field, 'Y']]);
+                if (!empty($doctype)) {
+                    $datasToImport['doctype']['default_value'] = json_encode($field);
+                }
+            } elseif ($key == 'priority') {
+                $priority = \Priority\models\PriorityModel::getById(['select' => [1], 'id' => $field]);
+                if (!empty($priority)) {
+                    $datasToImport['priority']['default_value'] = json_encode($field);
+                }
+            } elseif ($key == 'destination') {
+                $destination = \Entity\models\EntityModel::get(['select' => ['id'], 'where' => ['entity_id = ?', 'enabled = ?'], 'data' => [$field, 'Y']]);
+                if (!empty($destination)) {
+                    $datasToImport['destination']['default_value'] = json_encode($destination[0]['id']);
+                }
+            } elseif ($key == 'subject') {
+                $datasToImport['subject']['default_value'] = json_encode($field);
+            }
+        }
+        foreach ($datasToImport as $id => $defaultValue) {
+            \IndexingModel\models\IndexingModelFieldModel::create([
+                'model_id'      => $modelId,
+                'identifier'    => $id,
+                'mandatory'     => $defaultValue['mandatory'],
+                'default_value' => $defaultValue['default_value'],
+                'unit'          => $defaultValue['unit']
+            ]);
+        }
+        ++$migrated;
+    }
+
+    printf("Migration anciens modèles d'indexation (CUSTOM {$custom}) : " . $migrated . " modèle(s) utilisé(s) et migré(s).\n");
+}
diff --git a/migration/20.03/migrateOutgoingTemplate.php b/migration/20.03/migrateOutgoingTemplate.php
new file mode 100644
index 0000000000000000000000000000000000000000..5319e1fe56fc0468e6d353cab90e7ae598e34a9e
--- /dev/null
+++ b/migration/20.03/migrateOutgoingTemplate.php
@@ -0,0 +1,402 @@
+<?php
+
+use Contact\models\ContactCustomFieldListModel;
+use Docserver\controllers\DocserverController;
+use Docserver\models\DocserverModel;
+use SrcCore\models\DatabaseModel;
+use Template\models\TemplateModel;
+
+require '../../vendor/autoload.php';
+
+include_once('../../vendor/tinybutstrong/opentbs/tbs_plugin_opentbs.php');
+
+const OFFICE_EXTENSIONS = ['odt', 'ods', 'odp', 'xlsx', 'pptx', 'docx', 'odf'];
+
+$DATA_TO_REPLACE = [
+    'res_letterbox.destination'         => '[destination.entity_id]',
+    'res_letterbox.entity_label'        => '[destination.entity_label]',
+    'res_letterbox.process_notes'       => '[notes]',
+    'res_letterbox.contact_firstname'   => '[recipient.firstname]',
+    'res_letterbox.contact_lastname'    => '[recipient.lastname]',
+    'res_letterbox.contact_society'     => '[recipient.company]',
+
+    'res_letterbox.nature_id'                     => '[res_letterbox.custom_1]',
+
+    // Initiator
+    'res_letterbox.initiator_entity_id'           => '[initiator.entity_id]',
+    'res_letterbox.initiator_entity_label'        => '[initiator.entity_label]',
+    'res_letterbox.initiator_short_label'         => '[initiator.short_label]',
+    'res_letterbox.initiator_email'               => '[initiator.email]',
+    'res_letterbox.initiator_parent_entity_id'    => '[initiator.parent_entity_id]',
+    'res_letterbox.initiator_parent_entity_label' => '[initiator.parent_entity_label]',
+    'res_letterbox.initiator_entity_type'         => '[initiator.entity_type]',
+    'res_letterbox.initiator_entity_path'         => '[initiator.entity_path]',
+    'res_letterbox.initiator_entity_fullname'     => '[initiator.entity_fullname]',
+    'res_letterbox.initiator_zipcode'             => '[initiator.zipcode]',
+    'res_letterbox.initiator_city'                => '[initiator.city]',
+    'res_letterbox.initiator_country'             => '[initiator.country]',
+    'res_letterbox.initiator_ldap_id'             => '[initiator.ldap_id]',
+    'res_letterbox.initiator_archival_agence'     => '[initiator.archival_agence]',
+    'res_letterbox.initiator_archival_agreement'  => '[initiator.archival_agreement]',
+    'res_letterbox.initiator_business_id'         => '[initiator.business_id]',
+
+    'attachments.chrono' => '[res_letterbox.alt_identifier]',
+
+    'visa.firstnameSign' => '',
+    'visa.lastnameSign'  => '[visas]',
+    'visa.entitySign'    => '',
+    'visa.firstname1'    => '',
+    'visa.lastname1'     => '[visas]',
+    'visa.firstname2'    => '',
+    'visa.lastname2'     => '',
+    'visa.firstname3'    => '',
+    'visa.lastname3'     => '',
+    'visa.firstname4'    => '',
+    'visa.lastname4'     => '',
+    'visa.firstname5'    => '',
+    'visa.lastname5'     => '',
+    'visa.firstname6'    => '',
+    'visa.lastname6'     => '',
+    'visa.firstname7'    => '',
+    'visa.lastname7'     => '',
+    'visa.firstname8'    => '',
+    'visa.lastname8'     => '',
+    'visa.firstname9'    => '',
+    'visa.lastname9'     => '',
+    'visa.entity1'       => '',
+    'visa.entity2'       => '',
+    'visa.entity3'       => '',
+    'visa.entity4'       => '',
+    'visa.entity5'       => '',
+    'visa.entity6'       => '',
+    'visa.entity7'       => '',
+    'visa.entity8'       => '',
+    'visa.entity9'       => '',
+
+    'avis.firstname1' => '',
+    'avis.lastname1'  => '[opinions]',
+    'avis.firstname2' => '',
+    'avis.lastname2'  => '',
+    'avis.firstname3' => '',
+    'avis.lastname3'  => '',
+    'avis.firstname4' => '',
+    'avis.lastname4'  => '',
+    'avis.firstname5' => '',
+    'avis.lastname5'  => '',
+    'avis.firstname6' => '',
+    'avis.lastname6'  => '',
+    'avis.firstname7' => '',
+    'avis.lastname7'  => '',
+    'avis.firstname8' => '',
+    'avis.lastname8'  => '',
+    'avis.firstname9' => '',
+    'avis.lastname9'  => '',
+    'avis.role1'      => '',
+    'avis.entity1'    => '',
+    'avis.note1'      => '',
+    'avis.role2'      => '',
+    'avis.entity2'    => '',
+    'avis.note2'      => '',
+    'avis.role3'      => '',
+    'avis.entity3'    => '',
+    'avis.note3'      => '',
+    'avis.role4'      => '',
+    'avis.entity4'    => '',
+    'avis.note4'      => '',
+    'avis.role5'      => '',
+    'avis.entity5'    => '',
+    'avis.note5'      => '',
+    'avis.role6'      => '',
+    'avis.entity6'    => '',
+    'avis.note6'      => '',
+    'avis.role7'      => '',
+    'avis.entity7'    => '',
+    'avis.note7'      => '',
+    'avis.role8'      => '',
+    'avis.entity8'    => '',
+    'avis.note8'      => '',
+    'avis.role9'      => '',
+    'avis.entity9'    => '',
+    'avis.note9'      => '',
+
+    'copies.firstname1' => '',
+    'copies.lastname1'  => '[copies]',
+    'copies.firstname2' => '',
+    'copies.lastname2'  => '',
+    'copies.firstname3' => '',
+    'copies.lastname3'  => '',
+    'copies.firstname4' => '',
+    'copies.lastname4'  => '',
+    'copies.firstname5' => '',
+    'copies.lastname5'  => '',
+    'copies.firstname6' => '',
+    'copies.lastname6'  => '',
+    'copies.firstname7' => '',
+    'copies.lastname7'  => '',
+    'copies.firstname8' => '',
+    'copies.lastname8'  => '',
+    'copies.firstname9' => '',
+    'copies.lastname9'  => '',
+    'copies.entity1'    => '',
+    'copies.entity2'    => '',
+    'copies.entity3'    => '',
+    'copies.entity4'    => '',
+    'copies.entity5'    => '',
+    'copies.entity6'    => '',
+    'copies.entity7'    => '',
+    'copies.entity8'    => '',
+    'copies.entity9'    => '',
+
+    'user.role'             => '[userPrimaryEntity.role]',
+    'user.entity_id'        => '[userPrimaryEntity.entity_id]',
+    'user.entity_label'     => '[userPrimaryEntity.entity_label]',
+    'user.short_label'      => '[userPrimaryEntity.short_label]',
+    'user.adrs_1'           => '[userPrimaryEntity.adrs_1]',
+    'user.adrs_2'           => '[userPrimaryEntity.adrs_2]',
+    'user.adrs_3'           => '[userPrimaryEntity.adrs_3]',
+    'user.zipcode'          => '[userPrimaryEntity.zipcode]',
+    'user.city'             => '[userPrimaryEntity.city]',
+    'user.email'            => '[userPrimaryEntity.email]',
+    'user.parent_entity_id' => '[userPrimaryEntity.parent_entity_id]',
+    'user.entity_type'      => '[userPrimaryEntity.entity_type]',
+    'user.entity_path'      => '[userPrimaryEntity.path]',
+
+    'contact.contact_type_label'        => '',
+    'contact.society_short'             => '',
+    'contact.contact_purpose_label'     => '',
+    'contact.website'                   => '',
+    'contact.salutation_header'         => '',
+    'contact.salutation_footer'         => '',
+    'contact.society'                   => '[recipient.company]',
+    'contact.departement'               => '[recipient.department]',
+    'contact.title'                     => '[recipient.civility]',
+    'contact.contact_title'             => '[recipient.civility]',
+    'contact.contact_lastname'          => '[recipient.lastname]',
+    'contact.contact_firstname'         => '[recipient.firstname]',
+    'contact.lastname'                  => '[recipient.lastname]',
+    'contact.firstname'                 => '[recipient.firstname]',
+    'contact.function'                  => '[recipient.function]',
+    'contact.postal_address;strconv=no' => '[recipient.postal_address;strconv=no]',
+    'contact.postal_address'            => '[recipient.postal_address]',
+    'contact.address_num'               => '[recipient.address_number]',
+    'contact.address_street'            => '[recipient.address_street]',
+    'contact.occupancy'                 => '[recipient.address_additional1]',
+    'contact.address_complement'        => '[recipient.address_additional2]',
+    'contact.address_town'              => '[recipient.address_town]',
+    'contact.address_postal_code'       => '[recipient.address_postcode]',
+    'contact.address_country'           => '[recipient.address_country]',
+    'contact.phone'                     => '[recipient.phone]',
+    'contact.email'                     => '[recipient.email]',
+
+    'notes.identifier'                       => '[res_letterbox.res_id]',
+    'notes.subject'                          => '[res_letterbox.subject]',
+    'notes.note_text'                        => '[notes]',
+    'notes.user_id'                          => '',
+    'notes.# ;frm=0000'                      => '[res_letterbox.# ;frm=0000]',
+    'notes.doc_date;block=tr;frm=dd/mm/yyyy' => '[res_letterbox.doc_date;block=tr;frm=dd/mm/yyyy]',
+    'notes.doc_date;block=tr'                => '[res_letterbox.doc_date;block=tr]',
+    'notes.doc_date;frm=dd/mm/yyyy'          => '[res_letterbox.doc_date;frm=dd/mm/yyyy]',
+    'notes.doc_date'                         => '[res_letterbox.doc_date]',
+    'notes.contact_society'                  => '[contact.company]',
+    'notes.contact_firstname'                => '[contact.firstname]',
+    'notes.contact_lastname'                 => '[contact.lastname]',
+    'notes.linktodetail'                     => '[res_letterbox.linktodetail]',
+    'notes.linktodoc'                        => '[res_letterbox.linktodoc]',
+];
+
+$customFields = [
+    ['oldId' => 'salutation_header', 'label' => 'Formule de politesse (Début)'],
+    ['oldId' => 'salutation_footer', 'label' => 'Formule de politesse (Fin)'],
+    ['oldId' => 'website', 'label' => 'Site internet'],
+    ['oldId' => 'contact_type_label', 'label' => 'Type de contact'],
+    ['oldId' => 'contact_purpose_label', 'label' => 'Dénomination'],
+    ['oldId' => 'society_short', 'label' => 'Sigle de la structure'],
+];
+
+chdir('../..');
+
+$customs = scandir('custom');
+
+foreach ($customs as $custom) {
+    if ($custom == 'custom.xml' || $custom == '.' || $custom == '..') {
+        continue;
+    }
+
+    \SrcCore\models\DatabasePDO::reset();
+    new \SrcCore\models\DatabasePDO(['customId' => $custom]);
+
+    $docserver     = DocserverModel::getByDocserverId(['docserverId' => 'TEMPLATES']);
+    $templatesPath = $docserver['path_template'];
+
+    // BEGIN Change attachment all in outgoingMail
+
+    $templatesAllAttachmentTypes = TemplateModel::get([
+        'where' => ['template_target = ?', 'template_attachment_type = ?'],
+        'data'  => ['attachments', 'all']
+    ]);
+
+    foreach ($templatesAllAttachmentTypes as $template) {
+        $path = str_replace('#', '/', $template['template_path']);
+
+        $pathToDocument = $templatesPath . $path . $template['template_file_name'];
+
+        $pathInfo = pathinfo($pathToDocument);
+        $extension = $pathInfo['extension'];
+
+        if (!in_array($extension, OFFICE_EXTENSIONS)) {
+            $nonMigrated++;
+            continue;
+        }
+
+        if (!is_writable($pathToDocument) || !is_readable($pathToDocument)) {
+            $nonMigrated++;
+            continue;
+        }
+
+        $encodedFile = base64_encode(file_get_contents($pathToDocument));
+
+        $storeResult = DocserverController::storeResourceOnDocServer([
+            'collId'            => 'templates',
+            'docserverTypeId'   => 'TEMPLATES',
+            'encodedResource'   => $encodedFile,
+            'format'            => $pathInfo['extension']
+        ]);
+
+        $template['template_path']      = $storeResult['destination_dir'];
+        $template['template_file_name'] = $storeResult['file_destination_name'];
+
+        DatabaseModel::insert([
+            'table'         => 'templates',
+            'columnsValues' => [
+                'template_label'            => $template['template_label'] . ' (départ)',
+                'template_comment'          => $template['template_comment'],
+                'template_content'          => $template['template_content'],
+                'template_type'             => $template['template_type'],
+                'template_style'            => $template['template_style'],
+                'template_datasource'       => $template['template_datasource'],
+                'template_target'           => 'indexingFile',
+                'template_attachment_type'  => 'all',
+                'template_path'             => $template['template_path'],
+                'template_file_name'        => $template['template_file_name'],
+            ]
+        ]);
+    }
+
+    // END
+
+    TemplateModel::update([
+        'set'   => [
+            'template_target'          => 'indexingFile',
+            'template_attachment_type' => 'all'
+        ],
+        'where' => ['template_target = ?', 'template_attachment_type = ?'],
+        'data'  => ['attachments', 'outgoing_mail']
+    ]);
+
+    foreach ($customFields as $customField) {
+        $idNewCustomField = ContactCustomFieldListModel::get([
+            'select' => ['id'],
+            'where'  => ['label = ?'],
+            'data'   => [$customField['label']]
+        ]);
+        $DATA_TO_REPLACE["contact." . $customField['oldId']] = "[recipient.customField_{$idNewCustomField[0]['id']}]";
+    }
+
+    $migrated      = 0;
+    $nonMigrated   = 0;
+    $templates     = TemplateModel::get([
+        'where' => ['template_target = ?', 'template_attachment_type = ?'],
+        'data'  => ['indexingFile', 'all']
+    ]);
+
+    foreach ($templates as $template) {
+        $path = str_replace('#', '/', $template['template_path']);
+
+        $pathToDocument = $templatesPath . $path . $template['template_file_name'];
+
+        $pathInfo = pathinfo($pathToDocument);
+        $extension = $pathInfo['extension'];
+
+        if (!in_array($extension, OFFICE_EXTENSIONS)) {
+            $nonMigrated++;
+            continue;
+        }
+
+        if (!is_writable($pathToDocument) || !is_readable($pathToDocument)) {
+            $nonMigrated++;
+            continue;
+        }
+
+        $tbs = new clsTinyButStrong();
+        $tbs->NoErr = true;
+        $tbs->Protect = false;
+        $tbs->PlugIn(TBS_INSTALL, OPENTBS_PLUGIN);
+
+        $tbs->LoadTemplate($pathToDocument, OPENTBS_ALREADY_UTF8);
+
+        $pages = 1;
+        if ($extension == 'xlsx') {
+            $pages = $tbs->PlugIn(OPENTBS_COUNT_SHEETS);
+        }
+
+        for ($i = 0; $i < $pages; ++$i) {
+            if ($extension == 'xlsx') {
+                $tbs->PlugIn(OPENTBS_SELECT_SHEET, $i + 1);
+            }
+
+            $tbs->ReplaceFields($DATA_TO_REPLACE);
+        }
+
+        if (in_array($extension, OFFICE_EXTENSIONS)) {
+            $tbs->Show(OPENTBS_STRING);
+        } else {
+            $tbs->Show(TBS_NOTHING);
+        }
+
+        $content = base64_encode($tbs->Source);
+
+        $result = file_put_contents($pathToDocument, base64_decode($content));
+        if ($result !== false) {
+            $migrated++;
+        } else {
+            echo "Erreur lors de la migration du modèle : $pathToDocument\n";
+            $nonMigrated++;
+        }
+    }
+
+    $path = "custom/{$custom}/apps/maarch_entreprise/xml/entreprise.xml";
+    if (file_exists($path)) {
+        if (!is_readable($path) || !is_writable($path)) {
+            printf("The file $path it is not readable or not writable.\n");
+            continue;
+        }
+        $loadedXml = simplexml_load_file($path);
+        
+        if ($loadedXml) {
+            for ($i=count($loadedXml->attachment_types->type); $i >= 0; $i--) {
+                if (in_array($loadedXml->attachment_types->type[$i]->id, ['outgoing_mail', 'outgoing_mail_signed'])) {
+                    unset($loadedXml->attachment_types->type[$i]);
+                }
+            }
+
+            $res = formatXml($loadedXml);
+            $fp = fopen($path, "w+");
+            if ($fp) {
+                fwrite($fp, $res);
+            }
+            $migrated++;
+        }
+    }
+
+    printf("Migration de Modèles de document départ spontannée (CUSTOM {$custom}) : " . $migrated . " Modèle(s) migré(s), $nonMigrated non migré(s).\n");
+}
+
+function formatXml($simpleXMLElement)
+{
+    $xmlDocument = new DOMDocument('1.0');
+    $xmlDocument->preserveWhiteSpace = false;
+    $xmlDocument->formatOutput = true;
+    $xmlDocument->loadXML($simpleXMLElement->asXML());
+
+    return $xmlDocument->saveXML();
+}
diff --git a/migration/20.03/migrateRedirectKeywords.php b/migration/20.03/migrateRedirectKeywords.php
new file mode 100644
index 0000000000000000000000000000000000000000..72c54b17a6a964f236ccf341e2483a08eaad2f8e
--- /dev/null
+++ b/migration/20.03/migrateRedirectKeywords.php
@@ -0,0 +1,46 @@
+<?php
+
+require '../../vendor/autoload.php';
+
+chdir('../..');
+
+$customs =  scandir('custom');
+foreach ($customs as $custom) {
+    if ($custom == 'custom.xml' || $custom == '.' || $custom == '..') {
+        continue;
+    }
+
+    \SrcCore\models\DatabasePDO::reset();
+    new \SrcCore\models\DatabasePDO(['customId' => $custom]);
+
+    $redirectGroupBasket = \SrcCore\models\DatabaseModel::select([
+        'select'    => ['group_id', 'basket_id', 'id'],
+        'table'     => ['actions_groupbaskets, actions'],
+        'where'     => ['keyword = ?', 'id_action = id'],
+        'data'      => ['redirect']
+    ]);
+
+    $migrated = 0;
+    foreach ($redirectGroupBasket as $value) {
+        $keywordsAndEntities = \SrcCore\models\DatabaseModel::select([
+            'select'    => [1],
+            'table'     => ['groupbasket_redirect'],
+            'where'     => ['group_id = ?', 'basket_id = ?', 'action_id in (?)'],
+            'data'      => [$value['group_id'], $value['basket_id'], $value['id']]
+        ]);
+
+        if (empty($keywordsAndEntities)) {
+            \Basket\models\GroupBasketRedirectModel::create([
+                'id'            => $value['basket_id'],
+                'groupId'       => $value['group_id'],
+                'actionId'      => $value['id'],
+                'entityId'      => '',
+                'keyword'       => 'ALL_ENTITIES',
+                'redirectMode'  => 'ENTITY'
+            ]);
+            $migrated++;
+        }
+    }
+
+    printf("Migration keywords redirection vide (CUSTOM {$custom}) : " . $migrated . " keyword(s) ALL_ENTITIES ajouté(s).\n");
+}
diff --git a/migration/20.03/migrateServicesEntities.php b/migration/20.03/migrateServicesEntities.php
new file mode 100644
index 0000000000000000000000000000000000000000..be7dc6fb3a94facfd91b175f5450550d10f11115
--- /dev/null
+++ b/migration/20.03/migrateServicesEntities.php
@@ -0,0 +1,59 @@
+<?php
+
+require '../../vendor/autoload.php';
+
+chdir('../..');
+
+$nonReadableFiles = [];
+$migrated = 0;
+$customs =  scandir('custom');
+
+foreach ($customs as $custom) {
+    if ($custom == 'custom.xml' || $custom == '.' || $custom == '..') {
+        continue;
+    }
+
+    $natures = [];
+    $path = "custom/{$custom}/modules/entities/xml/services.xml";
+    if (file_exists($path)) {
+        if (!is_readable($path) || !is_writable($path)) {
+            $nonReadableFiles[] = $path;
+            continue;
+        }
+        $loadedXml = simplexml_load_file($path);
+        
+        if ($loadedXml) {
+            $i = 0;
+            foreach ($loadedXml->SERVICE as $value) {
+                if ($value->id == 'entities_print_sep_mlb') {
+                    $loadedXml->SERVICE[$i]->servicepage = '/separators/print';
+                    $loadedXml->SERVICE[$i]->angular = 'true';
+                    break;
+                }
+                ++$i;
+            }
+            $res = formatXml($loadedXml);
+            $fp = fopen($path, "w+");
+            if ($fp) {
+                fwrite($fp, $res);
+                $migrated++;
+            }
+        }
+    }
+}
+
+foreach ($nonReadableFiles as $file) {
+    printf("The file %s it is not readable or not writable.\n", $file);
+}
+
+printf($migrated . " custom(s) avec services.xml (entities) trouvé(s) et migré(s).\n");
+
+function formatXml($simpleXMLElement)
+{
+    $xmlDocument = new DOMDocument('1.0');
+    $xmlDocument->preserveWhiteSpace = false;
+    $xmlDocument->formatOutput = true;
+    $xmlDocument->loadXML($simpleXMLElement->asXML());
+
+    return $xmlDocument->saveXML();
+}
diff --git a/migration/20.03/migrateTemplates.php b/migration/20.03/migrateTemplates.php
new file mode 100644
index 0000000000000000000000000000000000000000..e9fbb69beec8a83fb398b566855722b99e8074e9
--- /dev/null
+++ b/migration/20.03/migrateTemplates.php
@@ -0,0 +1,324 @@
+<?php
+
+use Contact\models\ContactCustomFieldListModel;
+use Docserver\models\DocserverModel;
+use Template\models\TemplateModel;
+
+require '../../vendor/autoload.php';
+
+include_once('../../vendor/tinybutstrong/opentbs/tbs_plugin_opentbs.php');
+
+const OFFICE_EXTENSIONS = ['odt', 'ods', 'odp', 'xlsx', 'pptx', 'docx', 'odf'];
+
+$DATA_TO_REPLACE = [
+    'res_letterbox.destination'         => '[destination.entity_id]',
+    'res_letterbox.entity_label'        => '[destination.entity_label]',
+    'res_letterbox.process_notes'       => '[notes]',
+    'res_letterbox.contact_firstname'   => '[sender.firstname]',
+    'res_letterbox.contact_lastname'    => '[sender.lastname]',
+    'res_letterbox.contact_society'     => '[sender.company]',
+
+    'res_letterbox.nature_id'                     => '[res_letterbox.custom_1]',
+
+    // Initiator
+    'res_letterbox.initiator_entity_id'           => '[initiator.entity_id]',
+    'res_letterbox.initiator_entity_label'        => '[initiator.entity_label]',
+    'res_letterbox.initiator_short_label'         => '[initiator.short_label]',
+    'res_letterbox.initiator_email'               => '[initiator.email]',
+    'res_letterbox.initiator_parent_entity_id'    => '[initiator.parent_entity_id]',
+    'res_letterbox.initiator_parent_entity_label' => '[initiator.parent_entity_label]',
+    'res_letterbox.initiator_entity_type'         => '[initiator.entity_type]',
+    'res_letterbox.initiator_entity_path'         => '[initiator.entity_path]',
+    'res_letterbox.initiator_entity_fullname'     => '[initiator.entity_fullname]',
+    'res_letterbox.initiator_zipcode'             => '[initiator.zipcode]',
+    'res_letterbox.initiator_city'                => '[initiator.city]',
+    'res_letterbox.initiator_country'             => '[initiator.country]',
+    'res_letterbox.initiator_ldap_id'             => '[initiator.ldap_id]',
+    'res_letterbox.initiator_archival_agence'     => '[initiator.archival_agence]',
+    'res_letterbox.initiator_archival_agreement'  => '[initiator.archival_agreement]',
+    'res_letterbox.initiator_business_id'         => '[initiator.business_id]',
+
+    'attachments.chrono' => '[attachment.chrono]',
+
+    'visa.firstnameSign' => '',
+    'visa.lastnameSign'  => '[visas]',
+    'visa.entitySign'    => '',
+    'visa.firstname1'    => '',
+    'visa.lastname1'     => '[visas]',
+    'visa.firstname2'    => '',
+    'visa.lastname2'     => '',
+    'visa.firstname3'    => '',
+    'visa.lastname3'     => '',
+    'visa.firstname4'    => '',
+    'visa.lastname4'     => '',
+    'visa.firstname5'    => '',
+    'visa.lastname5'     => '',
+    'visa.firstname6'    => '',
+    'visa.lastname6'     => '',
+    'visa.firstname7'    => '',
+    'visa.lastname7'     => '',
+    'visa.firstname8'    => '',
+    'visa.lastname8'     => '',
+    'visa.firstname9'    => '',
+    'visa.lastname9'     => '',
+    'visa.entity1'       => '',
+    'visa.entity2'       => '',
+    'visa.entity3'       => '',
+    'visa.entity4'       => '',
+    'visa.entity5'       => '',
+    'visa.entity6'       => '',
+    'visa.entity7'       => '',
+    'visa.entity8'       => '',
+    'visa.entity9'       => '',
+
+    'avis.firstname1' => '',
+    'avis.lastname1'  => '[opinions]',
+    'avis.firstname2' => '',
+    'avis.lastname2'  => '',
+    'avis.firstname3' => '',
+    'avis.lastname3'  => '',
+    'avis.firstname4' => '',
+    'avis.lastname4'  => '',
+    'avis.firstname5' => '',
+    'avis.lastname5'  => '',
+    'avis.firstname6' => '',
+    'avis.lastname6'  => '',
+    'avis.firstname7' => '',
+    'avis.lastname7'  => '',
+    'avis.firstname8' => '',
+    'avis.lastname8'  => '',
+    'avis.firstname9' => '',
+    'avis.lastname9'  => '',
+    'avis.role1'      => '',
+    'avis.entity1'    => '',
+    'avis.note1'      => '',
+    'avis.role2'      => '',
+    'avis.entity2'    => '',
+    'avis.note2'      => '',
+    'avis.role3'      => '',
+    'avis.entity3'    => '',
+    'avis.note3'      => '',
+    'avis.role4'      => '',
+    'avis.entity4'    => '',
+    'avis.note4'      => '',
+    'avis.role5'      => '',
+    'avis.entity5'    => '',
+    'avis.note5'      => '',
+    'avis.role6'      => '',
+    'avis.entity6'    => '',
+    'avis.note6'      => '',
+    'avis.role7'      => '',
+    'avis.entity7'    => '',
+    'avis.note7'      => '',
+    'avis.role8'      => '',
+    'avis.entity8'    => '',
+    'avis.note8'      => '',
+    'avis.role9'      => '',
+    'avis.entity9'    => '',
+    'avis.note9'      => '',
+
+    'copies.firstname1' => '',
+    'copies.lastname1'  => '[copies]',
+    'copies.firstname2' => '',
+    'copies.lastname2'  => '',
+    'copies.firstname3' => '',
+    'copies.lastname3'  => '',
+    'copies.firstname4' => '',
+    'copies.lastname4'  => '',
+    'copies.firstname5' => '',
+    'copies.lastname5'  => '',
+    'copies.firstname6' => '',
+    'copies.lastname6'  => '',
+    'copies.firstname7' => '',
+    'copies.lastname7'  => '',
+    'copies.firstname8' => '',
+    'copies.lastname8'  => '',
+    'copies.firstname9' => '',
+    'copies.lastname9'  => '',
+    'copies.entity1'    => '',
+    'copies.entity2'    => '',
+    'copies.entity3'    => '',
+    'copies.entity4'    => '',
+    'copies.entity5'    => '',
+    'copies.entity6'    => '',
+    'copies.entity7'    => '',
+    'copies.entity8'    => '',
+    'copies.entity9'    => '',
+
+    'user.role'             => '[userPrimaryEntity.role]',
+    'user.entity_id'        => '[userPrimaryEntity.entity_id]',
+    'user.entity_label'     => '[userPrimaryEntity.entity_label]',
+    'user.short_label'      => '[userPrimaryEntity.short_label]',
+    'user.adrs_1'           => '[userPrimaryEntity.adrs_1]',
+    'user.adrs_2'           => '[userPrimaryEntity.adrs_2]',
+    'user.adrs_3'           => '[userPrimaryEntity.adrs_3]',
+    'user.zipcode'          => '[userPrimaryEntity.zipcode]',
+    'user.city'             => '[userPrimaryEntity.city]',
+    'user.email'            => '[userPrimaryEntity.email]',
+    'user.parent_entity_id' => '[userPrimaryEntity.parent_entity_id]',
+    'user.entity_type'      => '[userPrimaryEntity.entity_type]',
+    'user.entity_path'      => '[userPrimaryEntity.path]',
+
+    'contact.contact_type_label'        => '',
+    'contact.society_short'             => '',
+    'contact.contact_purpose_label'     => '',
+    'contact.website'                   => '',
+    'contact.salutation_header'         => '',
+    'contact.salutation_footer'         => '',
+    'contact.society'                   => '[attachmentRecipient.company]',
+    'contact.departement'               => '[attachmentRecipient.department]',
+    'contact.title'                     => '[attachmentRecipient.civility]',
+    'contact.contact_title'             => '[attachmentRecipient.civility]',
+    'contact.contact_lastname'          => '[attachmentRecipient.lastname]',
+    'contact.contact_firstname'         => '[attachmentRecipient.firstname]',
+    'contact.lastname'                  => '[attachmentRecipient.lastname]',
+    'contact.firstname'                 => '[attachmentRecipient.firstname]',
+    'contact.function'                  => '[attachmentRecipient.function]',
+    'contact.postal_address;strconv=no' => '[attachmentRecipient.postal_address;strconv=no]',
+    'contact.postal_address'            => '[attachmentRecipient.postal_address]',
+    'contact.address_num'               => '[attachmentRecipient.address_number]',
+    'contact.address_street'            => '[attachmentRecipient.address_street]',
+    'contact.occupancy'                 => '[attachmentRecipient.address_additional1]',
+    'contact.address_complement'        => '[attachmentRecipient.address_additional2]',
+    'contact.address_town'              => '[attachmentRecipient.address_town]',
+    'contact.address_postal_code'       => '[attachmentRecipient.address_postcode]',
+    'contact.address_country'           => '[attachmentRecipient.address_country]',
+    'contact.phone'                     => '[attachmentRecipient.phone]',
+    'contact.email'                     => '[attachmentRecipient.email]',
+
+    'notes.identifier'                       => '[res_letterbox.res_id]',
+    'notes.subject'                          => '[res_letterbox.subject]',
+    'notes.note_text'                        => '[notes]',
+    'notes.user_id'                          => '',
+    'notes.# ;frm=0000'                      => '[res_letterbox.# ;frm=0000]',
+    'notes.doc_date;block=tr;frm=dd/mm/yyyy' => '[res_letterbox.doc_date;block=tr;frm=dd/mm/yyyy]',
+    'notes.doc_date;block=tr'                => '[res_letterbox.doc_date;block=tr]',
+    'notes.doc_date;frm=dd/mm/yyyy'          => '[res_letterbox.doc_date;frm=dd/mm/yyyy]',
+    'notes.doc_date'                         => '[res_letterbox.doc_date]',
+    'notes.contact_society'                  => '[contact.company]',
+    'notes.contact_firstname'                => '[contact.firstname]',
+    'notes.contact_lastname'                 => '[contact.lastname]',
+    'notes.linktodetail'                     => '[res_letterbox.linktodetail]',
+    'notes.linktodoc'                        => '[res_letterbox.linktodoc]',
+];
+
+$customFields = [
+    ['oldId' => 'salutation_header', 'label' => 'Formule de politesse (Début)'],
+    ['oldId' => 'salutation_footer', 'label' => 'Formule de politesse (Fin)'],
+    ['oldId' => 'website', 'label' => 'Site internet'],
+    ['oldId' => 'contact_type_label', 'label' => 'Type de contact'],
+    ['oldId' => 'contact_purpose_label', 'label' => 'Dénomination'],
+    ['oldId' => 'society_short', 'label' => 'Sigle de la structure'],
+];
+
+chdir('../..');
+
+$customs = scandir('custom');
+
+foreach ($customs as $custom) {
+    if ($custom == 'custom.xml' || $custom == '.' || $custom == '..') {
+        continue;
+    }
+
+    \SrcCore\models\DatabasePDO::reset();
+    new \SrcCore\models\DatabasePDO(['customId' => $custom]);
+
+    foreach ($customFields as $customField) {
+        $idNewCustomField = ContactCustomFieldListModel::get([
+            'select' => ['id'],
+            'where'  => ['label = ?'],
+            'data'   => [$customField['label']]
+        ]);
+        $DATA_TO_REPLACE["contact." . $customField['oldId']] = "[recipient.customField_{$idNewCustomField[0]['id']}]";
+    }
+
+    $migrated = 0;
+
+    $nonMigrated = 0;
+
+    $docserver = DocserverModel::getByDocserverId(['docserverId' => 'TEMPLATES']);
+
+    $templatesPath = $docserver['path_template'];
+
+    $templates = TemplateModel::get(['where' => ['template_target != ?'], 'data' => ['indexingFile']]);
+
+    foreach ($templates as $template) {
+        if ($template['template_type'] == 'HTML' || $template['template_type'] == 'TXT' || $template['template_type'] == 'OFFICE_HTML') {
+            $content = $template['template_content'];
+
+            $newContent = $content;
+            foreach ($DATA_TO_REPLACE as $key => $value) {
+                $newContent = str_replace('[' . $key . ']', $value, $newContent);
+            }
+
+            if ($content != $newContent) {
+                TemplateModel::update([
+                    'set'   => [
+                        'template_content' => $newContent
+                    ],
+                    'where' => ['template_id = ?'],
+                    'data'  => [$template['template_id']]
+                ]);
+                $migrated++;
+            } else {
+                $nonMigrated++;
+            }
+        }
+        if ($template['template_type'] == 'OFFICE' || $template['template_type'] == 'OFFICE_HTML') {
+            $path = str_replace('#', '/', $template['template_path']);
+
+            $pathToDocument = $templatesPath . $path . $template['template_file_name'];
+
+            $pathInfo = pathinfo($pathToDocument);
+            $extension = $pathInfo['extension'];
+
+            if (!in_array($extension, OFFICE_EXTENSIONS)) {
+                $nonMigrated++;
+                continue;
+            }
+
+            if (!is_writable($pathToDocument) || !is_readable($pathToDocument)) {
+                $nonMigrated++;
+                continue;
+            }
+
+            $tbs = new clsTinyButStrong();
+            $tbs->NoErr = true;
+            $tbs->Protect = false;
+            $tbs->PlugIn(TBS_INSTALL, OPENTBS_PLUGIN);
+
+            $tbs->LoadTemplate($pathToDocument, OPENTBS_ALREADY_UTF8);
+
+            $pages = 1;
+            if ($extension == 'xlsx') {
+                $pages = $tbs->PlugIn(OPENTBS_COUNT_SHEETS);
+            }
+
+            for ($i = 0; $i < $pages; ++$i) {
+                if ($extension == 'xlsx') {
+                    $tbs->PlugIn(OPENTBS_SELECT_SHEET, $i + 1);
+                }
+
+                $tbs->ReplaceFields($DATA_TO_REPLACE);
+            }
+
+            if (in_array($extension, OFFICE_EXTENSIONS)) {
+                $tbs->Show(OPENTBS_STRING);
+            } else {
+                $tbs->Show(TBS_NOTHING);
+            }
+
+            $content = base64_encode($tbs->Source);
+
+            $result = file_put_contents($pathToDocument, base64_decode($content));
+            if ($result !== false) {
+                $migrated++;
+            } else {
+                echo "Erreur lors de la migration du modèle : $pathToDocument\n";
+                $nonMigrated++;
+            }
+        }
+    }
+
+    printf("Migration de Modèles de documents (CUSTOM {$custom}) : " . $migrated . " Modèle(s) migré(s), $nonMigrated non migré(s).\n");
+}
diff --git a/migration/20.03/migrateVersionAttachments.php b/migration/20.03/migrateVersionAttachments.php
new file mode 100644
index 0000000000000000000000000000000000000000..26c9185f12bb4a81a18b62a3d5da99355db62001
--- /dev/null
+++ b/migration/20.03/migrateVersionAttachments.php
@@ -0,0 +1,193 @@
+<?php
+
+require '../../vendor/autoload.php';
+
+chdir('../..');
+
+$customs =  scandir('custom');
+
+foreach ($customs as $custom) {
+    if ($custom == 'custom.xml' || $custom == '.' || $custom == '..') {
+        continue;
+    }
+
+    \SrcCore\models\DatabasePDO::reset();
+    new \SrcCore\models\DatabasePDO(['customId' => $custom]);
+
+    \SrcCore\models\DatabaseModel::delete([
+        'table' => 'res_attachments',
+        'where' => ['relation > 1']
+    ]);
+
+    $superadmin = \User\models\UserModel::getByLogin(['select' => ['id'], 'login' => 'superadmin']);
+    if (empty($superadmin)) {
+        $firstMan = \User\models\UserModel::get(['select' => ['id'], 'orderBy' => ['id'], 'limit' => 1]);
+        $masterOwnerId = $firstMan[0]['id'];
+    } else {
+        $masterOwnerId = $superadmin['id'];
+    }
+
+    $migrated = 0;
+    $attachmentsInfo = \SrcCore\models\DatabaseModel::select([
+        'select'   => ['res_id', 'title', 'format', 'typist', 'creation_date', 'fulltext_result', 'identifier', 'relation', 'doc_date', 'docserver_id', 'path', 'filename', 'fingerprint', 'filesize',
+                        'status', 'validation_date', 'effective_date', 'origin', 'dest_user', 'res_id_master', 'attachment_type', 'dest_contact_id', 'dest_address_id', 'updated_by', 'in_signature_book',
+                        'signatory_user_serial_id', 'in_send_attach', 'external_id', 'attachment_id_master'
+        ],
+        'table'    => ['res_version_attachments']
+    ]);
+
+    foreach ($attachmentsInfo as $attachmentInfo) {
+        $oldResId = $attachmentInfo['res_id'];
+        unset($attachmentInfo['res_id']);
+        $attachmentInfo['origin_id'] = $attachmentInfo['attachment_id_master'];
+        unset($attachmentInfo['attachment_id_master']);
+        $attachmentInfo['modification_date'] = $attachmentInfo['doc_date'];
+        unset($attachmentInfo['doc_date']);
+        if (!empty($attachmentInfo['updated_by'])) {
+            $userSerialId = \User\models\UserModel::getByLogin(['select' => ['id'], 'login' => $attachmentInfo['updated_by']]);
+            $attachmentInfo['modified_by'] = $userSerialId['id'];
+        } else {
+            $attachmentInfo['modified_by'] = null;
+        }
+        unset($attachmentInfo['updated_by']);
+        $attachmentInfo['in_signature_book'] = empty($attachmentInfo['in_signature_book']) ?  'false' : 'true';
+        $attachmentInfo['in_send_attach']    = empty($attachmentInfo['in_send_attach']) ?  'false' : 'true';
+        $attachmentInfo['format']            = empty($attachmentInfo['format']) ?  pathinfo($attachmentInfo['filename'], PATHINFO_EXTENSION) : $attachmentInfo['format'];
+        if (empty($attachmentInfo['fingerprint'])) {
+            $docserver = \SrcCore\models\DatabaseModel::select([
+                'select' => ['path_template', 'docserver_type_id'],
+                'table'  => ['docservers'],
+                'where'  => ['docserver_id = ?'],
+                'data'   => [$attachmentInfo['docserver_id']]
+            ]);
+            $docserverType = \SrcCore\models\DatabaseModel::select([
+                'select' => ['fingerprint_mode'],
+                'table'  => ['docserver_types'],
+                'where'  => ['docserver_type_id = ?'],
+                'data'   => [$docserver[0]['docserver_type_id']]
+            ]);
+            $pathToDocument = $docserver[0]['path_template'] . str_replace('#', DIRECTORY_SEPARATOR, $attachmentInfo['path']) . $attachmentInfo['filename'];
+            if (file_exists($pathToDocument)) {
+                $fingerprint = \Resource\controllers\StoreController::getFingerPrint(['filePath' => $pathToDocument, 'mode' => $docserverType[0]['fingerprint_mode']]);
+                $attachmentInfo['fingerprint'] = $fingerprint;
+            } else {
+                $attachmentInfo['fingerprint'] = '1';
+            }
+        }
+
+        $newResId = \Attachment\models\AttachmentModel::create($attachmentInfo);
+
+        if ($attachmentInfo['fingerprint'] == 1) {
+            echo "Le document avec res_version_attachments.res_id = " . $oldResId
+                . " (nouveau res_id : res_attachment.res_id = " . $newResId . ") n'a pas été trouvé sur le docserver (path = '" . $pathToDocument . "')"
+                . ", le fingerprint du document est assigné à 1\n";
+        }
+
+        migrateOrigin(['oldResId' => $oldResId, 'newResId' => $newResId]);
+        migrateAdrVersionAttachments(['oldResId' => $oldResId, 'newResId' => $newResId]);
+        migrateHistoryVersion(['oldResId' => $oldResId, 'newResId' => $newResId]);
+        migrateEmailsVersion(['oldResId' => $oldResId, 'newResId' => $newResId]);
+        migrateMessageExchangeVersion(['oldResId' => $oldResId, 'newResId' => $newResId]);
+        migrateShippingVersion(['oldResId' => $oldResId, 'newResId' => $newResId]);
+        if (!in_array($attachmentInfo['status'], ['DEL', 'OBS', 'TMP'])) {
+            migrateFullText(['newResId' => $newResId, 'customId' => $custom, 'userId' => $masterOwnerId]);
+        }
+
+        $migrated++;
+    }
+
+    printf("Migration version attachement (CUSTOM {$custom}) : " . $migrated . " Version(s) trouvée(s) et migrée(s).\n");
+}
+
+function migrateOrigin($args = [])
+{
+    \SrcCore\models\DatabaseModel::update([
+        'set'   => ['origin' => $args['newResId'] . ',res_attachments'],
+        'table' => 'res_attachments',
+        'where' => ['origin = ?'],
+        'data'  => [$args['oldResId'] . ',res_version_attachments']
+    ]);
+}
+
+function migrateAdrVersionAttachments($args = [])
+{
+    $adrInfos = \SrcCore\models\DatabaseModel::select(['select' => ['*'], 'table' => ['adr_attachments_version'], 'where' => ['res_id = ?'], 'data' => [$args['oldResId']]]);
+    foreach ($adrInfos as $value) {
+        unset($value['id']);
+        $value['res_id'] = $args['newResId'];
+        \SrcCore\models\DatabaseModel::insert([
+            'table'         => 'adr_attachments',
+            'columnsValues' => $value
+        ]);
+    }
+}
+
+function migrateHistoryVersion($args = [])
+{
+    \SrcCore\models\DatabaseModel::update([
+        'set'   => ['table_name' => 'res_attachments', 'record_id' => $args['newResId']],
+        'postSet' => ['info' => "REPLACE(info, '{$args['oldResId']} (res_version_attachments)', '{$args['newResId']} (res_attachments)')"],
+        'table' => 'history',
+        'where' => ['table_name = ?', 'record_id = ?'],
+        'data'  => ['res_version_attachments', $args['oldResId']]
+    ]);
+}
+
+function migrateEmailsVersion($args = [])
+{
+    $emails = \SrcCore\models\DatabaseModel::select([
+        'select' => ['id', 'document'],
+        'table' => ['emails'],
+        'where' => ['document->\'attachments\' @> ?'],
+        'data' => ['[{"id":'.$args['oldResId'].', "isVersion":true}]']
+    ]);
+
+    foreach ($emails as $email) {
+        $document = json_decode($email['document'], true);
+        foreach ($document['attachments'] as $key => $attachment) {
+            if ($attachment['id'] == $args['oldResId'] && $attachment['isVersion']) {
+                $document['attachments'][$key]['id'] = $args['newResId'];
+                unset($document['attachments'][$key]['isVersion']);
+                break;
+            }
+        }
+        \SrcCore\models\DatabaseModel::update([
+            'set'   => ['document' => json_encode($document)],
+            'table' => 'emails',
+            'where' => ['id = ?'],
+            'data'  => [$email['id']]
+        ]);
+    }
+    \SrcCore\models\DatabaseModel::update([
+        'set'   => ['document' => json_encode($document)],
+        'table' => 'emails',
+        'where' => ['id = ?'],
+        'data'  => [$email['id']]
+    ]);
+}
+
+function migrateMessageExchangeVersion($args = [])
+{
+    \SrcCore\models\DatabaseModel::update([
+        'set'   => ['res_id' => $args['newResId'], 'tablename' => 'res_attachments'],
+        'table' => 'unit_identifier',
+        'where' => ['res_id = ?', 'tablename = ?'],
+        'data'  => [$args['oldResId'], 'res_version_attachments']
+    ]);
+}
+
+function migrateShippingVersion($args = [])
+{
+    \SrcCore\models\DatabaseModel::update([
+        'set'   => ['attachment_id' => $args['newResId'], 'is_version' => 'false'],
+        'table' => 'shippings',
+        'where' => ['attachment_id = ?', 'is_version = ?'],
+        'data'  => [$args['oldResId'], 'true']
+    ]);
+}
+
+function migrateFullText($args = [])
+{
+    $GLOBALS['id'] = $args['userId'];
+    exec("php src/app/convert/scripts/FullTextScript.php --customId {$args['customId']} --resId {$args['newResId']} --collId attachments_coll --userId {$GLOBALS['id']} > /dev/null");
+}
diff --git a/migration/20.03/migrateWorkingDays.php b/migration/20.03/migrateWorkingDays.php
new file mode 100644
index 0000000000000000000000000000000000000000..ca495db87a81997593a9e5909dbf7bc950441884
--- /dev/null
+++ b/migration/20.03/migrateWorkingDays.php
@@ -0,0 +1,47 @@
+<?php
+
+require '../../vendor/autoload.php';
+
+chdir('../..');
+
+$migrated = 0;
+$customs =  scandir('custom');
+foreach ($customs as $custom) {
+    if ($custom == 'custom.xml' || $custom == '.' || $custom == '..') {
+        continue;
+    }
+
+    $workingDays = 1;
+    $xmlfile = null;
+    $path = "custom/{$custom}/apps/maarch_entreprise/xml/features.xml";
+    if (file_exists($path)) {
+        $xmlfile = simplexml_load_file($path);
+
+        if ($xmlfile) {
+            $calendarType = $xmlfile->FEATURES->type_calendar;
+            if ($calendarType == 'calendar') {
+                $workingDays = 0;
+            } else {
+                $workingDays = 1;
+            }
+            unset($xmlfile->FEATURES->type_calendar);
+
+            $res = $xmlfile->asXML();
+            $fp = fopen($path, "w+");
+            if ($fp) {
+                fwrite($fp, $res);
+            }
+
+            $migrated++;
+        }
+    }
+
+    \Parameter\models\ParameterModel::delete(['id' => 'workingDays']);
+    \Parameter\models\ParameterModel::create([
+        'id'              => 'workingDays',
+        'description'     => 'Si activé (1), les délais de traitement sont calculés en jours ouvrés (Lundi à Vendredi). Sinon, en jours calendaire',
+        'param_value_int' => $workingDays
+    ]);
+}
+
+printf("Migration calendar type : " . $migrated . " custom(s) avec un fichier features.xml trouvé(s) et migré(s).\n");
diff --git a/migration/20.03/removeNatureFromPrint.php b/migration/20.03/removeNatureFromPrint.php
new file mode 100644
index 0000000000000000000000000000000000000000..d7c72ee11ec27337ca90fd3777a58a1c95177d2d
--- /dev/null
+++ b/migration/20.03/removeNatureFromPrint.php
@@ -0,0 +1,40 @@
+<?php
+
+require '../../vendor/autoload.php';
+
+chdir('../..');
+
+$migrated = 0;
+$customs =  scandir('custom');
+foreach ($customs as $custom) {
+    if ($custom == 'custom.xml' || $custom == '.' || $custom == '..') {
+        continue;
+    }
+
+    $xmlfile = null;
+    $path = "custom/{$custom}/apps/maarch_entreprise/xml/print.xml";
+    if (file_exists($path)) {
+        $xmlfile = simplexml_load_file($path);
+
+        if ($xmlfile) {
+            $i = 0;
+            foreach ($xmlfile->letterbox_coll->FIELD as $field) {
+                if ($field->DATABASE_FIELD == 'nature_id') {
+                    unset($xmlfile->letterbox_coll->FIELD[$i]);
+                    break;
+                }
+                $i++;
+            }
+
+            $res = $xmlfile->asXML();
+            $fp = fopen($path, "w+");
+            if ($fp) {
+                fwrite($fp, $res);
+            }
+
+            $migrated++;
+        }
+    }
+}
+
+printf("Remove Nature : " . $migrated . " custom(s) avec un fichier print.xml trouvé(s) et migré(s).\n");
diff --git a/migration/20.03/removeProcessModes.php b/migration/20.03/removeProcessModes.php
new file mode 100644
index 0000000000000000000000000000000000000000..dbe55ba34d192478929d6433fbc8aad3861bd60c
--- /dev/null
+++ b/migration/20.03/removeProcessModes.php
@@ -0,0 +1,33 @@
+<?php
+
+require '../../vendor/autoload.php';
+
+chdir('../..');
+
+$migrated = 0;
+$customs =  scandir('custom');
+foreach ($customs as $custom) {
+    if ($custom == 'custom.xml' || $custom == '.' || $custom == '..') {
+        continue;
+    }
+
+    $xmlfile = null;
+    $path = "custom/{$custom}/apps/maarch_entreprise/xml/entreprise.xml";
+    if (file_exists($path)) {
+        $xmlfile = simplexml_load_file($path);
+
+        if ($xmlfile) {
+            unset($xmlfile->process_modes);
+
+            $res = $xmlfile->asXML();
+            $fp = fopen($path, "w+");
+            if ($fp) {
+                fwrite($fp, $res);
+            }
+
+            $migrated++;
+        }
+    }
+}
+
+printf("Migration Process Mode : " . $migrated . " custom(s) avec un fichier entreprise.xml trouvé(s) et migré(s).\n");