diff --git a/Gemfile b/Gemfile
index 63370895bb064f8f55e6fd88362e1ac62b1d9d79..4563b472786745f9eede8517a6ec878d6e4dce01 100644
--- a/Gemfile
+++ b/Gemfile
@@ -14,6 +14,7 @@ gem "bootstrap5-kaminari-views"
 gem "breadcrumbs_on_rails"
 gem "bugsnag"
 gem "cancancan", "3.3.0"
+gem "caxlsx_rails", "~> 0.6.3"
 gem "cocoon", "~> 1.2"
 gem "country_select"
 gem "curation"#, path: "../../arnaudlevy/curation"
@@ -25,7 +26,7 @@ gem "enum_help"
 gem "faceted_search"#, path: "../../noesya/faceted_search"
 gem "font-awesome-sass"
 gem "front_matter_parser"
-gem "gdpr"
+gem "gdpr", "~> 1.2.5"
 gem "geocoder", "~> 1.8"
 gem "geo_point"
 gem "gitlab"
diff --git a/Gemfile.lock b/Gemfile.lock
index b067b14958b12f266689deb332dfa6dddaa31a27..0e9871c0f06fe9521de550c740743bea2411e847 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -100,16 +100,16 @@ GEM
     annotate (3.2.0)
       activerecord (>= 3.2, < 8.0)
       rake (>= 10.4, < 14.0)
-    autoprefixer-rails (10.4.7.0)
+    autoprefixer-rails (10.4.13.0)
       execjs (~> 2)
     aws-eventstream (1.2.0)
-    aws-partitions (1.711.0)
+    aws-partitions (1.725.0)
     aws-sdk-core (3.170.0)
       aws-eventstream (~> 1, >= 1.0.2)
       aws-partitions (~> 1, >= 1.651.0)
       aws-sigv4 (~> 1.5)
       jmespath (~> 1, >= 1.6.1)
-    aws-sdk-kms (1.62.0)
+    aws-sdk-kms (1.63.0)
       aws-sdk-core (~> 3, >= 3.165.0)
       aws-sigv4 (~> 1.1)
     aws-sdk-s3 (1.119.1)
@@ -145,8 +145,16 @@ GEM
       rack-test (>= 0.6.3)
       regexp_parser (>= 1.5, < 3.0)
       xpath (~> 3.2)
+    caxlsx (3.3.0)
+      htmlentities (~> 4.3, >= 4.3.4)
+      marcel (~> 1.0)
+      nokogiri (~> 1.10, >= 1.10.4)
+      rubyzip (>= 1.3.0, < 3)
+    caxlsx_rails (0.6.3)
+      actionpack (>= 3.1)
+      caxlsx (>= 3.0)
     cocoon (1.2.15)
-    concurrent-ruby (1.2.0)
+    concurrent-ruby (1.2.2)
     countries (5.3.1)
       unaccent (~> 0.3)
     country_select (8.0.1)
@@ -169,14 +177,14 @@ GEM
       delayed_job (> 2.0.3)
       rack-protection (>= 1.5.5)
       sinatra (>= 1.4.4)
-    devise (4.8.1)
+    devise (4.9.0)
       bcrypt (~> 3.0)
       orm_adapter (~> 0.1)
       railties (>= 4.1.0)
       responders
       warden (~> 1.2.3)
-    devise-i18n (1.10.2)
-      devise (>= 4.8.0)
+    devise-i18n (1.11.0)
+      devise (>= 4.9.0)
     docile (1.4.0)
     domain_name (0.5.20190701)
       unf (>= 0.0.5, < 1.0.0)
@@ -187,7 +195,7 @@ GEM
     ethon (0.16.0)
       ffi (>= 1.15.0)
     execjs (2.8.1)
-    faceted_search (3.6.0)
+    faceted_search (3.6.1)
       font-awesome-sass
       rails (>= 5.2.0)
     faraday (2.7.4)
@@ -200,7 +208,7 @@ GEM
       faraday
     faraday-follow_redirects (0.3.0)
       faraday (>= 1, < 3)
-    faraday-gzip (0.1.0)
+    faraday-gzip (1.0.0)
       faraday (>= 1.0)
       zlib (~> 2.1)
     faraday-http-cache (2.4.1)
@@ -208,7 +216,7 @@ GEM
     faraday-multipart (1.0.4)
       multipart-post (~> 2)
     faraday-net_http (3.0.2)
-    faraday-retry (2.0.0)
+    faraday-retry (2.1.0)
       faraday (~> 2.0)
     fastimage (2.2.6)
     ffi (1.15.5)
@@ -306,13 +314,13 @@ GEM
       net-smtp
     marcel (1.0.2)
     matrix (0.4.2)
-    metainspector (5.13.1)
+    metainspector (5.14.0)
       addressable (~> 2.8)
       faraday (~> 2.5)
       faraday-cookie_jar (~> 0.0)
       faraday-encoding (~> 0.0)
       faraday-follow_redirects (~> 0.3)
-      faraday-gzip (~> 0.1)
+      faraday-gzip (>= 0.1, < 2.0)
       faraday-http-cache (~> 2.4)
       faraday-retry (~> 2.0)
       fastimage (~> 2.2)
@@ -321,8 +329,8 @@ GEM
     method_source (1.0.0)
     mini_magick (4.12.0)
     mini_mime (1.1.2)
-    minitest (5.17.0)
-    msgpack (1.6.0)
+    minitest (5.18.0)
+    msgpack (1.6.1)
     multi_xml (0.6.0)
     multipart-post (2.3.0)
     mustermann (3.0.0)
@@ -353,7 +361,7 @@ GEM
       rack (>= 1.2, < 4)
       snaky_hash (~> 2.0)
       version_gem (~> 1.1)
-    octokit (6.0.1)
+    octokit (6.1.0)
       faraday (>= 1, < 3)
       sawyer (~> 0.9)
     omniauth (2.1.1)
@@ -373,13 +381,13 @@ GEM
     orm_adapter (0.5.0)
     pexels (0.5.0)
       requests (~> 1.0.2)
-    pg (1.4.5)
+    pg (1.4.6)
     popper_js (2.11.6)
     public_suffix (5.0.1)
-    puma (6.1.0)
+    puma (6.1.1)
       nio4r (~> 2.0)
     racc (1.6.2)
-    rack (2.2.6.2)
+    rack (2.2.6.3)
     rack-mini-profiler (2.3.4)
       rack (>= 1.2.0)
     rack-protection (3.0.5)
@@ -454,7 +462,7 @@ GEM
     sawyer (0.9.2)
       addressable (>= 2.3.5)
       faraday (>= 0.17.3, < 3)
-    selenium-webdriver (4.8.0)
+    selenium-webdriver (4.8.1)
       rexml (~> 3.2, >= 3.2.5)
       rubyzip (>= 1.2.2, < 3.0)
       websocket (~> 1.0)
@@ -503,10 +511,10 @@ GEM
     terminal-table (3.0.2)
       unicode-display_width (>= 1.1.1, < 3)
     thor (1.2.1)
-    tilt (2.0.11)
+    tilt (2.1.0)
     time (0.2.1)
       date
-    timeout (0.3.1)
+    timeout (0.3.2)
     typhoeus (1.4.0)
       ethon (>= 0.9.0)
     tzinfo (2.0.6)
@@ -568,6 +576,7 @@ DEPENDENCIES
   byebug
   cancancan (= 3.3.0)
   capybara (>= 3.26)
+  caxlsx_rails (~> 0.6.3)
   cocoon (~> 1.2)
   country_select
   curation
@@ -580,7 +589,7 @@ DEPENDENCIES
   figaro
   font-awesome-sass
   front_matter_parser
-  gdpr
+  gdpr (~> 1.2.5)
   geo_point
   geocoder (~> 1.8)
   gitlab
diff --git a/app/assets/stylesheets/admin/pure/navigation.sass b/app/assets/stylesheets/admin/pure/navigation.sass
index 92d103f33038bd70fbe9d2b09caa4b70be360b9d..16525a92a010bc8df13ed9ecabd802c7e298d65f 100644
--- a/app/assets/stylesheets/admin/pure/navigation.sass
+++ b/app/assets/stylesheets/admin/pure/navigation.sass
@@ -10,7 +10,7 @@
         img
             width: 100px
     .btn-open
-        margin-right: -14px
+        margin-right: -10px
 #menu
     background: black
     bottom: 0
diff --git a/app/controllers/admin/application_controller.rb b/app/controllers/admin/application_controller.rb
index 44877c2ca6c1bad74748e8d284e6308881751e23..c61a843e58791e9a9ef8d7a41d369eb5a5ead517 100644
--- a/app/controllers/admin/application_controller.rb
+++ b/app/controllers/admin/application_controller.rb
@@ -1,6 +1,7 @@
 class Admin::ApplicationController < ApplicationController
   layout 'admin/layouts/application'
 
+  include WithFeatures
   include Admin::Filterable
 
   def set_theme
diff --git a/app/controllers/admin/application_controller/with_features.rb b/app/controllers/admin/application_controller/with_features.rb
new file mode 100644
index 0000000000000000000000000000000000000000..8ba479e4723be421901213aa23d6901b86fbf5bd
--- /dev/null
+++ b/app/controllers/admin/application_controller/with_features.rb
@@ -0,0 +1,34 @@
+module Admin::ApplicationController::WithFeatures
+  extend ActiveSupport::Concern
+
+  included do
+
+    def feature_administration?
+      current_university.feature_administration &&
+        can?(:read, Administration::Qualiopi::Criterion)
+    end
+    helper_method :feature_administration?
+
+    def feature_communication?
+      current_university.feature_communication &&
+        can?(:read, Communication::Website)
+    end
+    helper_method :feature_communication?
+
+    def feature_education?
+      current_university.feature_education &&
+        can?(:read, Education::Program)
+    end
+    helper_method :feature_education?
+
+    def feature_research?
+      current_university.feature_research && (
+        can?(:read, Research::Journal) ||
+        can?(:read, Research::Hal::Publication) ||
+        can?(:read, Research::Laboratory)
+      )
+    end
+    helper_method :feature_research?
+
+  end
+end
diff --git a/app/controllers/admin/communication/extranets/contacts_controller.rb b/app/controllers/admin/communication/extranets/contacts_controller.rb
index d74dbda0e861fa1935b9d89dced9a2aa059aa2e7..53b19b0f630f3e392565e50583550e2ca4e459c8 100644
--- a/app/controllers/admin/communication/extranets/contacts_controller.rb
+++ b/app/controllers/admin/communication/extranets/contacts_controller.rb
@@ -1,7 +1,22 @@
 class Admin::Communication::Extranets::ContactsController < Admin::Communication::Extranets::ApplicationController
   def index
-    @persons = current_university.people.ordered.page params[:persons_page]
-    @organizations = current_university.organizations.ordered.page params[:organizations_page]
+    @people = current_university.people.ordered
+    @organizations = current_university.organizations.ordered
+    respond_to do |format|
+      format.html {
+        @people = @people.page params[:persons_page]
+        @organizations = @organizations.page params[:organizations_page]
+      }
+      format.xlsx {
+        # could be 2 differents controllers in Contacts/People & Contacts/Organizations, each with an index export
+        @export = params['export']
+        filename = "#{@export}-#{Time.now.strftime("%Y%m%d%H%M%S")}.xlsx"
+        response.headers['Content-Disposition'] = "attachment; filename=#{filename}"
+        render @export
+      }
+    end
+
+
     breadcrumb
     add_breadcrumb Communication::Extranet.human_attribute_name(:feature_contacts)
   end
@@ -26,4 +41,4 @@ class Admin::Communication::Extranets::ContactsController < Admin::Communication
     object_id = params[:objectId]
     @object = object_type.constantize.find object_id
   end
-end
\ No newline at end of file
+end
diff --git a/app/controllers/admin/communication/extranets/documents/application_controller.rb b/app/controllers/admin/communication/extranets/documents/application_controller.rb
new file mode 100644
index 0000000000000000000000000000000000000000..7491d21833e9f378fa47ca1492d77a8762748e36
--- /dev/null
+++ b/app/controllers/admin/communication/extranets/documents/application_controller.rb
@@ -0,0 +1,8 @@
+class Admin::Communication::Extranets::Documents::ApplicationController < Admin::Communication::Extranets::ApplicationController
+  protected
+
+  def breadcrumb
+    super
+    add_breadcrumb Communication::Extranet.human_attribute_name(:feature_library), admin_communication_extranet_documents_path
+  end
+end
\ No newline at end of file
diff --git a/app/controllers/admin/communication/extranets/documents/categories_controller.rb b/app/controllers/admin/communication/extranets/documents/categories_controller.rb
new file mode 100644
index 0000000000000000000000000000000000000000..5e265cb29c8f9fcd760884edda1a6c98a87cf963
--- /dev/null
+++ b/app/controllers/admin/communication/extranets/documents/categories_controller.rb
@@ -0,0 +1,67 @@
+class Admin::Communication::Extranets::Documents::CategoriesController < Admin::Communication::Extranets::ApplicationController
+  load_and_authorize_resource class: Communication::Extranet::Document::Category, through: :extranet, through_association: :document_categories
+
+  def index
+    @categories = @categories.ordered
+    breadcrumb
+  end
+
+  def show
+    @documents = @category.documents.ordered.page params[:page]
+    breadcrumb
+  end
+
+  def new
+    breadcrumb
+  end
+
+  def edit
+    breadcrumb
+    add_breadcrumb t('edit')
+  end
+
+  def create
+    if @category.save
+      redirect_to admin_communication_extranet_document_category_path(@category), notice: t('admin.successfully_created_html', model: @category.to_s)
+    else
+      breadcrumb
+      render :new, status: :unprocessable_entity
+    end
+  end
+
+  def update
+    if @category.update(category_params)
+      redirect_to admin_communication_extranet_document_category_path(@category), notice: t('admin.successfully_updated_html', model: @category.to_s)
+    else
+      breadcrumb
+      add_breadcrumb t('edit')
+      render :edit, status: :unprocessable_entity
+    end
+  end
+
+  def destroy
+    @category.destroy
+    redirect_to admin_communication_extranet_document_categories_url, notice: t('admin.successfully_destroyed_html', model: @category.to_s)
+  end
+
+  protected
+
+  def breadcrumb
+    super
+    add_breadcrumb Communication::Extranet.human_attribute_name(:feature_library), admin_communication_extranet_documents_path
+    add_breadcrumb Communication::Extranet::Document::Category.model_name.human(count: 2), admin_communication_extranet_document_categories_path
+    breadcrumb_for @category
+  end
+
+  def category_params
+    params.require(:communication_extranet_document_category)
+    .permit(
+      :name,
+      :slug,
+    )
+    .merge(
+      university_id: current_university.id
+    )
+  end
+
+end
\ No newline at end of file
diff --git a/app/controllers/admin/communication/extranets/documents/kinds_controller.rb b/app/controllers/admin/communication/extranets/documents/kinds_controller.rb
new file mode 100644
index 0000000000000000000000000000000000000000..45245939632cf869612bd2da00eb14599ebab4df
--- /dev/null
+++ b/app/controllers/admin/communication/extranets/documents/kinds_controller.rb
@@ -0,0 +1,66 @@
+class Admin::Communication::Extranets::Documents::KindsController < Admin::Communication::Extranets::Documents::ApplicationController
+  load_and_authorize_resource class: Communication::Extranet::Document::Kind, through: :extranet, through_association: :document_kinds
+
+  def index
+    @kinds = @kinds.ordered
+    breadcrumb
+  end
+
+  def show
+    @documents = @kind.documents.ordered.page params[:page]
+    breadcrumb
+  end
+
+  def new
+    breadcrumb
+  end
+
+  def edit
+    breadcrumb
+    add_breadcrumb t('edit')
+  end
+
+  def create
+    if @kind.save
+      redirect_to admin_communication_extranet_document_kind_path(@kind), notice: t('admin.successfully_created_html', model: @kind.to_s)
+    else
+      breadcrumb
+      render :new, status: :unprocessable_entity
+    end
+  end
+
+  def update
+    if @kind.update(kind_params)
+      redirect_to admin_communication_extranet_document_kind_path(@kind), notice: t('admin.successfully_updated_html', model: @kind.to_s)
+    else
+      breadcrumb
+      add_breadcrumb t('edit')
+      render :edit, status: :unprocessable_entity
+    end
+  end
+
+  def destroy
+    @kind.destroy
+    redirect_to admin_communication_extranet_document_kinds_url, notice: t('admin.successfully_destroyed_html', model: @kind.to_s)
+  end
+
+  protected
+
+  def breadcrumb
+    super
+    add_breadcrumb Communication::Extranet::Document::Kind.model_name.human(count: 2), admin_communication_extranet_document_kinds_path
+    breadcrumb_for @kind
+  end
+
+  def kind_params
+    params.require(:communication_extranet_document_kind)
+    .permit(
+      :name,
+      :slug,
+    )
+    .merge(
+      university_id: current_university.id
+    )
+  end
+
+end
\ No newline at end of file
diff --git a/app/controllers/admin/communication/extranets/documents_controller.rb b/app/controllers/admin/communication/extranets/documents_controller.rb
index 124981dd684b81afdf566884f6881d8355930625..3bcfdddfd0f94cf9b5e7a332e4ad98ddf7093cb9 100644
--- a/app/controllers/admin/communication/extranets/documents_controller.rb
+++ b/app/controllers/admin/communication/extranets/documents_controller.rb
@@ -3,6 +3,8 @@ class Admin::Communication::Extranets::DocumentsController < Admin::Communicatio
 
   def index
     @documents = @documents.ordered.page params[:page]
+    @categories = @extranet.document_categories.ordered
+    @kinds = @extranet.document_kinds.ordered
     breadcrumb
   end
 
@@ -55,7 +57,8 @@ class Admin::Communication::Extranets::DocumentsController < Admin::Communicatio
     params.require(:communication_extranet_document)
     .permit(
       :name, :published, :published_at, :slug,
-      :file, :file_delete
+      :file, :file_delete,
+      :category_id, :kind_id
     )
     .merge(
       university_id: current_university.id
diff --git a/app/controllers/admin/communication/websites/posts_controller.rb b/app/controllers/admin/communication/websites/posts_controller.rb
index 2bd77c0533e969dd494b495d654e0a8559aec167..f9c5a97abbd7464c7928e521460261ec6f10b281 100644
--- a/app/controllers/admin/communication/websites/posts_controller.rb
+++ b/app/controllers/admin/communication/websites/posts_controller.rb
@@ -87,6 +87,11 @@ class Admin::Communication::Websites::PostsController < Admin::Communication::We
     end
   end
 
+  def duplicate
+    redirect_to [:admin, @post.duplicate],
+                notice: t('admin.successfully_duplicated_html', model: @post.to_s)
+  end
+
   def destroy
     @post.destroy_and_sync
     redirect_to admin_communication_website_posts_url, notice: t('admin.successfully_destroyed_html', model: @post.to_s)
diff --git a/app/controllers/admin/dashboard_controller.rb b/app/controllers/admin/dashboard_controller.rb
index bec6f2d82b3d6e9cbd2bfcf02aab4f78c8e5d6b9..dd170122f339f1b0cd47007540bd1a045c95be34 100644
--- a/app/controllers/admin/dashboard_controller.rb
+++ b/app/controllers/admin/dashboard_controller.rb
@@ -1,11 +1,10 @@
 class Admin::DashboardController < Admin::ApplicationController
   def index
-    @namespaces = [
-      Education,
-      Research,
-      Communication,
-      Administration
-    ]
+    @namespaces = []
+    @namespaces << Education if feature_education?
+    @namespaces << Research if feature_research?
+    @namespaces << Communication if feature_communication?
+    @namespaces << Administration if feature_administration?
     breadcrumb
   end
 end
diff --git a/app/controllers/extranet/library/documents_controller.rb b/app/controllers/extranet/library/documents_controller.rb
index e4dd569db1e66b7502f4a818a145105ab32bbce8..7a2e43ca15b6cf0b57fe629400110576ca8eeab3 100644
--- a/app/controllers/extranet/library/documents_controller.rb
+++ b/app/controllers/extranet/library/documents_controller.rb
@@ -1,10 +1,11 @@
 class Extranet::Library::DocumentsController < Extranet::Library::ApplicationController
 
   def index
-    @documents =  current_extranet.documents
-                                  .published
-                                  .ordered
-                                  .page(params[:page])
+    @facets = Communication::Extranet::Document::Facets.new params[:facets], current_extranet
+    @documents = @facets.results
+                        .published
+                        .ordered
+                        .page params[:page]
     breadcrumb
   end
 
diff --git a/app/controllers/server/universities_controller.rb b/app/controllers/server/universities_controller.rb
index 985c8bb3abee721ecf3b4329d918a2899d1a4797..17abd7f602fbdc5df67842b8dd5073ea01e472b6 100644
--- a/app/controllers/server/universities_controller.rb
+++ b/app/controllers/server/universities_controller.rb
@@ -63,6 +63,8 @@ class Server::UniversitiesController < Server::ApplicationController
       :address, :zipcode, :city, :country,
       :private, :identifier, :logo, :logo_delete, :sms_sender_name,
       :has_sso, :sso_target_url, :sso_cert, :sso_name_identifier_format, :sso_mapping, :sso_button_label,
-      :invoice_date, :invoice_amount)
+      :invoice_date, :invoice_amount, 
+      :feature_administration, :feature_communication, :feature_education, :feature_research
+    )
   end
 end
diff --git a/app/helpers/admin/application_helper.rb b/app/helpers/admin/application_helper.rb
index c6ae3b861fdd82a9d5bb532ab821b48653e940dd..6ca939f24b1b54385cd985d8c5daa4b18b5828a1 100644
--- a/app/helpers/admin/application_helper.rb
+++ b/app/helpers/admin/application_helper.rb
@@ -52,8 +52,8 @@ module Admin::ApplicationHelper
 
   def osuny_panel(title = nil, subtitle: nil, action: nil, image: nil, &block)
     render  layout: "admin/layouts/themes/#{current_admin_theme}/panel",
-            locals: { 
-              title: title, 
+            locals: {
+              title: title,
               subtitle: subtitle,
               action: action,
               image: image
@@ -61,18 +61,18 @@ module Admin::ApplicationHelper
       capture(&block)
     end
   end
-  
+
   def osuny_label(title, classes: '')
     raw "<label class=\"form-label #{classes}\">#{title}</label>"
   end
 
   def if_appstack(string)
-    return '' if current_admin_theme != 'appstack' 
+    return '' if current_admin_theme != 'appstack'
     " #{string}"
   end
 
   def if_pure(string)
-    return '' if current_admin_theme != 'pure' 
+    return '' if current_admin_theme != 'pure'
     " #{string}"
   end
 
diff --git a/app/models/ability.rb b/app/models/ability.rb
index bb1ea2a5b78dc2771f7825c6f5fb0c7a3943009c..8f9c9fe4380930fadbb2fbecaf13cba0d956fa75 100644
--- a/app/models/ability.rb
+++ b/app/models/ability.rb
@@ -101,8 +101,8 @@ class Ability
     can :manage, Communication::Extranet::Post, university_id: @user.university_id
     can :manage, Communication::Extranet::Post::Category, university_id: @user.university_id
     can :manage, Communication::Extranet::Document, university_id: @user.university_id
-    # can :manage, Communication::Extranet::Document::Category, university_id: @user.university_id
-    # can :manage, Communication::Extranet::Document::Kind, university_id: @user.university_id
+    can :manage, Communication::Extranet::Document::Category, university_id: @user.university_id
+    can :manage, Communication::Extranet::Document::Kind, university_id: @user.university_id
     can :manage, Communication::Extranet::Connection, university_id: @user.university_id
     can :manage, Education::AcademicYear, university_id: @user.university_id
     can :manage, Education::Cohort, university_id: @user.university_id
diff --git a/app/models/communication/extranet.rb b/app/models/communication/extranet.rb
index 268fc63728594c117c8da5fd5615e2f43d5293b4..c99daf122abc88d0f162d495cacdae7a82b36e3b 100644
--- a/app/models/communication/extranet.rb
+++ b/app/models/communication/extranet.rb
@@ -60,6 +60,8 @@ class Communication::Extranet < ApplicationRecord
   has_many :posts
   has_many :post_categories, class_name: 'Communication::Extranet::Post::Category'
   has_many :documents
+  has_many :document_categories, class_name: 'Communication::Extranet::Document::Category'
+  has_many :document_kinds, class_name: 'Communication::Extranet::Document::Kind'
 
   validates_presence_of :name, :host
   validates :logo, size: { less_than: 1.megabytes }
diff --git a/app/models/communication/extranet/document.rb b/app/models/communication/extranet/document.rb
index 081f39cbb290caf496bc1cac72d3051b878208c4..9b332be81ebbaf763b0c4202fc428b07a1b3a046 100644
--- a/app/models/communication/extranet/document.rb
+++ b/app/models/communication/extranet/document.rb
@@ -8,18 +8,24 @@
 #  published_at  :datetime
 #  created_at    :datetime         not null
 #  updated_at    :datetime         not null
+#  category_id   :uuid             indexed
 #  extranet_id   :uuid             not null, indexed
+#  kind_id       :uuid             indexed
 #  university_id :uuid             not null, indexed
 #
 # Indexes
 #
+#  extranet_document_categories                             (category_id)
 #  index_communication_extranet_documents_on_extranet_id    (extranet_id)
 #  index_communication_extranet_documents_on_university_id  (university_id)
+#  index_extranet_document_kinds                            (kind_id)
 #
 # Foreign Keys
 #
 #  fk_rails_1272fd263c  (extranet_id => communication_extranets.id)
+#  fk_rails_51f7db2f66  (kind_id => communication_extranet_document_kinds.id)
 #  fk_rails_af877a8c0c  (university_id => universities.id)
+#  fk_rails_eb351dc444  (category_id => communication_extranet_document_categories.id)
 #
 class Communication::Extranet::Document < ApplicationRecord
   include Sanitizable
@@ -27,6 +33,8 @@ class Communication::Extranet::Document < ApplicationRecord
   include WithUniversity
 
   belongs_to :extranet, class_name: 'Communication::Extranet'
+  belongs_to :category
+  belongs_to :kind
 
   has_one_attached_deletable :file
 
diff --git a/app/models/communication/extranet/document/category.rb b/app/models/communication/extranet/document/category.rb
new file mode 100644
index 0000000000000000000000000000000000000000..91f8d0214a7c43a1855d10d939a118d9cb68f77a
--- /dev/null
+++ b/app/models/communication/extranet/document/category.rb
@@ -0,0 +1,47 @@
+# == Schema Information
+#
+# Table name: communication_extranet_document_categories
+#
+#  id            :uuid             not null, primary key
+#  name          :string
+#  slug          :string
+#  created_at    :datetime         not null
+#  updated_at    :datetime         not null
+#  extranet_id   :uuid             not null, indexed
+#  university_id :uuid             not null, indexed
+#
+# Indexes
+#
+#  extranet_document_categories_universities                        (university_id)
+#  index_communication_extranet_document_categories_on_extranet_id  (extranet_id)
+#
+# Foreign Keys
+#
+#  fk_rails_6f2232d9f8  (university_id => universities.id)
+#  fk_rails_76e327b90f  (extranet_id => communication_extranets.id)
+#
+class Communication::Extranet::Document::Category < ApplicationRecord
+  include WithSlug
+  include WithUniversity
+
+  belongs_to :extranet, class_name: 'Communication::Extranet'
+
+  has_many :documents
+
+  validates :name, presence: true
+
+  scope :ordered, -> { order(:name) }
+
+  def to_s
+    "#{name}"
+  end
+
+  protected
+
+  def slug_unavailable?(slug)
+    self.class.unscoped
+              .where(extranet_id: self.extranet_id, slug: slug)
+              .where.not(id: self.id)
+              .exists?
+  end
+end
diff --git a/app/models/communication/extranet/document/facets.rb b/app/models/communication/extranet/document/facets.rb
new file mode 100644
index 0000000000000000000000000000000000000000..cb8ef4f220c73004b7691b5b73cf5ff2be0d7731
--- /dev/null
+++ b/app/models/communication/extranet/document/facets.rb
@@ -0,0 +1,19 @@
+class Communication::Extranet::Document::Facets < FacetedSearch::Facets
+  def initialize(params, extranet)
+    super(params)
+    @model = extranet.documents.published
+    filter_with_text :name, {
+      title: Communication::Extranet::Document.human_attribute_name('name')
+    }
+    filter_with_list :category, {
+      title: Communication::Extranet::Document::Category.model_name.human(count: 2),
+      source: extranet.document_categories,
+      habtm: false
+    }
+    filter_with_list :kind, {
+      title: Communication::Extranet::Document::Kind.model_name.human(count: 2),
+      source: extranet.document_kinds,
+      habtm: false
+    }
+  end
+end
\ No newline at end of file
diff --git a/app/models/communication/extranet/document/kind.rb b/app/models/communication/extranet/document/kind.rb
new file mode 100644
index 0000000000000000000000000000000000000000..7f3afa35591e32ab62a3f2e4c046eac1e3a8aa71
--- /dev/null
+++ b/app/models/communication/extranet/document/kind.rb
@@ -0,0 +1,47 @@
+# == Schema Information
+#
+# Table name: communication_extranet_document_kinds
+#
+#  id            :uuid             not null, primary key
+#  name          :string
+#  slug          :string
+#  created_at    :datetime         not null
+#  updated_at    :datetime         not null
+#  extranet_id   :uuid             not null, indexed
+#  university_id :uuid             not null, indexed
+#
+# Indexes
+#
+#  extranet_document_kinds_universities                        (university_id)
+#  index_communication_extranet_document_kinds_on_extranet_id  (extranet_id)
+#
+# Foreign Keys
+#
+#  fk_rails_27a9b91ed8  (extranet_id => communication_extranets.id)
+#  fk_rails_2a55cf899a  (university_id => universities.id)
+#
+class Communication::Extranet::Document::Kind < ApplicationRecord
+  include WithSlug
+  include WithUniversity
+
+  belongs_to :extranet, class_name: 'Communication::Extranet'
+
+  has_many :documents
+
+  validates :name, presence: true
+
+  scope :ordered, -> { order(:name) }
+
+  def to_s
+    "#{name}"
+  end
+
+  protected
+
+  def slug_unavailable?(slug)
+    self.class.unscoped
+              .where(extranet_id: self.extranet_id, slug: slug)
+              .where.not(id: self.id)
+              .exists?
+  end
+end
diff --git a/app/models/communication/website/page.rb b/app/models/communication/website/page.rb
index 1a3317f754900d41953edc7c38ebcd6b7bd61d52..da268152d78e2cfc5c7c1402bcb2bb7111947ef4 100644
--- a/app/models/communication/website/page.rb
+++ b/app/models/communication/website/page.rb
@@ -48,18 +48,19 @@ class Communication::Website::Page < ApplicationRecord
 
   include Accessible
   include Sanitizable
-  include WithUniversity
   include WithBlobs
   include WithBlocks
+  include WithDuplication
   include WithFeaturedImage
   include WithGit
   include WithMenuItemTarget
   include WithPosition
   include WithTree
   include WithPath
-  include WithType
   include WithPermalink
+  include WithType
   include WithTranslations
+  include WithUniversity
 
   has_summernote :text # TODO: Remove text attribute
 
@@ -110,19 +111,6 @@ class Communication::Website::Page < ApplicationRecord
     active_storage_blobs
   end
 
-  def duplicate
-    page = self.dup
-    page.published = false
-    page.save
-    blocks.ordered.each do |block|
-      b = block.duplicate
-      b.about = page
-      b.position = block.position
-      b.save
-    end
-    page
-  end
-
   def to_s
     "#{title}"
   end
diff --git a/app/models/communication/website/post.rb b/app/models/communication/website/post.rb
index e2095f2f03f9a44b105a3600b4417bfaa345bb8b..5264c87a58e5cc09dc1be2f23935b5bc17d7235e 100644
--- a/app/models/communication/website/post.rb
+++ b/app/models/communication/website/post.rb
@@ -39,15 +39,16 @@
 #
 class Communication::Website::Post < ApplicationRecord
   include Sanitizable
-  include WithUniversity
-  include WithGit
-  include WithFeaturedImage
   include WithBlobs
   include WithBlocks
+  include WithDuplication
+  include WithFeaturedImage
+  include WithGit
   include WithMenuItemTarget
   include WithPermalink
   include WithSlug # We override slug_unavailable? method
   include WithTranslations
+  include WithUniversity
 
   has_summernote :text # TODO: Remove text attribute
 
diff --git a/app/models/communication/website/with_style.rb b/app/models/communication/website/with_style.rb
index bb2220516ecae0e9269fc112c5aa47a93ecbf88a..e60ddf1b31bd898ecab982a6235f5958dd51e41a 100644
--- a/app/models/communication/website/with_style.rb
+++ b/app/models/communication/website/with_style.rb
@@ -43,7 +43,10 @@ module Communication::Website::WithStyle
 
   def substitute_fonts_urls_in_style!
     @style.gsub! "src:url(../", "src:url(#{url}/assets/"
-    @style.gsub ",url(../", ",url(#{url}/assets/"
+    @style.gsub! "url(../", "url(#{url}/assets/"
+    @style.gsub! "url('../", "url('#{url}/assets/"
+    @style.gsub! "url(\"../", "url(\"#{url}/assets/"
+    @style
   end
 
   def style_outdated?
diff --git a/app/models/concerns/with_country.rb b/app/models/concerns/with_country.rb
new file mode 100644
index 0000000000000000000000000000000000000000..0dcc66b76dbd04da2e2792c032552d43353a022d
--- /dev/null
+++ b/app/models/concerns/with_country.rb
@@ -0,0 +1,20 @@
+module WithCountry
+  extend ActiveSupport::Concern
+
+  def country_name
+    return if country_iso.blank?
+    country_iso.translations[I18n.locale.to_s] || country_common_name
+  end
+
+  def country_common_name
+    return if country_iso.blank?
+    country_iso.common_name || country_iso.iso_short_name
+  end
+
+  private
+
+  def country_iso
+    return @country_iso if defined?(@country_iso)
+    @country_iso ||= country.blank? ? nil : ISO3166::Country[country]
+  end
+end
diff --git a/app/models/concerns/with_duplication.rb b/app/models/concerns/with_duplication.rb
new file mode 100644
index 0000000000000000000000000000000000000000..3285ff9a664e02dcabd2f992dd2b0561b16f85cf
--- /dev/null
+++ b/app/models/concerns/with_duplication.rb
@@ -0,0 +1,39 @@
+module WithDuplication
+  extend ActiveSupport::Concern
+
+  def duplicate
+    instance = duplicate_instance
+    duplicate_blocks_to(instance)
+    duplicate_featured_image_to(instance)
+    instance
+  end
+
+  protected
+
+  def duplicate_instance
+    instance = self.dup
+    instance.published = false if respond_to?(:published)
+    instance.published_at = nil if respond_to?(:published_at)
+    instance.save
+    instance
+  end
+
+  def duplicate_blocks_to(instance)
+    return unless respond_to?(:blocks)
+    blocks.ordered.each do |block|
+      b = block.duplicate
+      b.about = instance
+      b.position = block.position
+      b.save
+    end
+  end
+
+  def duplicate_featured_image_to(instance)
+    return unless respond_to?(:featured_image) && featured_image.attached?
+    instance.featured_image.attach(
+      io: URI.open(featured_image.url),
+      filename: featured_image.filename.to_s,
+      content_type: featured_image.content_type
+    )
+  end
+end
\ No newline at end of file
diff --git a/app/models/concerns/with_geolocation.rb b/app/models/concerns/with_geolocation.rb
index df92252bd5228b5eefd05626eeca2dc4a85820d6..2e975c0c70baf772f08fa5e373c9120009b76aa7 100644
--- a/app/models/concerns/with_geolocation.rb
+++ b/app/models/concerns/with_geolocation.rb
@@ -17,7 +17,7 @@ module WithGeolocation
     string += "#{address}<br>" if address.present?
     string += "#{address_additional}<br>" if address_additional.present?
     string += "#{zipcode} #{city}"
-    string += "<br>#{ISO3166::Country[country]}" if country
+    string += "<br>#{country_common_name}" if country
     string
   end
 
@@ -42,4 +42,4 @@ module WithGeolocation
   def full_street_address_changed?
     address_changed? || zipcode_changed? || city_changed?
   end
-end
\ No newline at end of file
+end
diff --git a/app/models/education.rb b/app/models/education.rb
index e4aa5f201b27d3ed21ab0216d422495eb7bb77ec..66941182445d2a1810055757f6926664e20caa40 100644
--- a/app/models/education.rb
+++ b/app/models/education.rb
@@ -12,6 +12,7 @@ module Education
       [Education::School, :admin_education_schools_path],
       [Education::Diploma, :admin_education_diplomas_path],
       [Education::Program, :admin_education_programs_path],
+      [University::Person::Alumnus, :admin_university_alumni_path],
     ]
   end
 end
diff --git a/app/models/education/school.rb b/app/models/education/school.rb
index 78ab3e1fc5fbae0c810d23ff5a7feca6ab1d30a7..b98be91e95cdfe440cc1a17aac51e782080d4353 100644
--- a/app/models/education/school.rb
+++ b/app/models/education/school.rb
@@ -25,6 +25,7 @@
 #
 class Education::School < ApplicationRecord
   include Sanitizable
+  include WithCountry
   include WithGit
   include Aboutable
   include WithPrograms # must come before WithAlumni and WithTeam
diff --git a/app/models/research/laboratory.rb b/app/models/research/laboratory.rb
index a70817fe92d6ce36ecdc4f0a4259a6b3b775099a..78a882d2565d6445604c5e37db9b8a4a3830257a 100644
--- a/app/models/research/laboratory.rb
+++ b/app/models/research/laboratory.rb
@@ -23,6 +23,7 @@
 class Research::Laboratory < ApplicationRecord
   include Aboutable
   include Sanitizable
+  include WithCountry
   include WithGit
 
   belongs_to  :university
diff --git a/app/models/university.rb b/app/models/university.rb
index 321f21333ed82efcb40e446851f6b5da3df545f0..c72f3ef0203c5be6d654d31733d672902b45f8ca 100644
--- a/app/models/university.rb
+++ b/app/models/university.rb
@@ -6,6 +6,10 @@
 #  address                    :string
 #  city                       :string
 #  country                    :string
+#  feature_administration     :boolean          default(TRUE)
+#  feature_communication      :boolean          default(TRUE)
+#  feature_education          :boolean          default(TRUE)
+#  feature_research           :boolean          default(TRUE)
 #  has_sso                    :boolean          default(FALSE)
 #  identifier                 :string
 #  invoice_amount             :string
@@ -41,6 +45,7 @@ class University < ApplicationRecord
   # We don't include Sanitizable because too many complex attributes. We handle it below.
   include WithPeopleAndOrganizations
   include WithCommunication
+  include WithCountry
   include WithEducation
   include WithIdentifier
   include WithInvoice
@@ -69,7 +74,6 @@ class University < ApplicationRecord
     [
       [University::Person, :admin_university_people_path],
       [University::Organization, :admin_university_organizations_path],
-      [University::Person::Alumnus, :admin_university_alumni_path],
       [User, :admin_users_path],
     ]
   end
diff --git a/app/models/university/organization.rb b/app/models/university/organization.rb
index 26ec7059d1f864b72ae4e27ff8894ce0e799b61a..54f1e9ba8afd6d5c1c2390e626813ce792d1177c 100644
--- a/app/models/university/organization.rb
+++ b/app/models/university/organization.rb
@@ -43,6 +43,7 @@ class University::Organization < ApplicationRecord
   include Sanitizable
   include WithBlobs
   include WithBlocks
+  include WithCountry
   include WithGeolocation
   include WithGit
   include WithPermalink
diff --git a/app/models/university/person.rb b/app/models/university/person.rb
index 59a9e253f76cc059df0e179dbfeccaf13e0dff9b..1475b5beeaa888976b69047a7edada51f60aed55 100644
--- a/app/models/university/person.rb
+++ b/app/models/university/person.rb
@@ -57,6 +57,7 @@ class University::Person < ApplicationRecord
   include WithUniversity
   include WithGit
   include WithBlobs
+  include WithCountry
   include WithEducation
   include WithExperiences
   include WithSlug
diff --git a/app/views/admin/communication/blocks/components/image/_preview.html.erb b/app/views/admin/communication/blocks/components/image/_preview.html.erb
index 066284dacd6ac732d54134f96f4aec796f0584fe..8d40fa51aaec7cf58aa816965c5c89de085f49e0 100644
--- a/app/views/admin/communication/blocks/components/image/_preview.html.erb
+++ b/app/views/admin/communication/blocks/components/image/_preview.html.erb
@@ -1,9 +1,10 @@
 <%
 blob = component.blob
 return unless blob
+is_carousel = @block.template.is_a?(Communication::Block::Template::Gallery) && @block.template.layout == "carousel"
 %>
 
-<figure>
+<figure <% if is_carousel %>class="splide__slide"<% end %>>
   <%= kamifusen_tag blob, width: 600, class: 'img-fluid mb-1', alt: component.template.try(:alt).blank? ? "" : component.template.alt %>
   <figcaption>
     <%= sanitize component.template.credit if component.template.try(:credit).present? %>
diff --git a/app/views/admin/communication/blocks/edit.html.erb b/app/views/admin/communication/blocks/edit.html.erb
index e4813e15a33e370cab21e7dfe3c010a6b4dec19a..ef3bdd551b48abed3a1b9777a873eb1824610619 100644
--- a/app/views/admin/communication/blocks/edit.html.erb
+++ b/app/views/admin/communication/blocks/edit.html.erb
@@ -130,13 +130,17 @@
         });
       },
       initCodemirror(element) {
-        CodeMirror.fromTextArea(element, {
+        var editor = CodeMirror.fromTextArea(element, {
             lineNumbers: true,
             styleActiveLine: true,
             indentUnit: 2,
             viewportMargin: Infinity,
             mode: element.getAttribute('data-codemirror-mode')
         });
+        editor.on("change", function (instance, changeObj) {
+          element.value = instance.getValue();
+          element.dispatchEvent(new Event('input'));
+        });
       }
     },
     mounted: function() {
diff --git a/app/views/admin/communication/blocks/templates/files/_preview.html.erb b/app/views/admin/communication/blocks/templates/files/_preview.html.erb
index 54f32745652572c91bccae0cb1f3a9c0493b7812..4d72187e5486d77ad39904ce7f0bf0574eabc5ae 100644
--- a/app/views/admin/communication/blocks/templates/files/_preview.html.erb
+++ b/app/views/admin/communication/blocks/templates/files/_preview.html.erb
@@ -27,10 +27,13 @@ end
           <li>
           <figure>
               <a href="<%= element.blob.url %>" target="_blank" title="<%= element.title %>"><%= element.title %></a>
-              <figcaption>
-                <abbr title="">TODO : EXTENSION</abbr>
-                - <abbr title="">TODO : TAILLE FICHIER</abbr>
-              </figcaption>
+              <% if document.file.attached? %>
+                <% file = document.file %>
+                <figcaption>
+                  <abbr title=""><%= file.filename.extension.upcase %></abbr>
+                  - <abbr title=""><%= number_to_human_size file.byte_size %></abbr>
+                </figcaption>
+              <% end %>
             </figure>
             </a>
           </li>
diff --git a/app/views/admin/communication/extranets/contacts/index.html.erb b/app/views/admin/communication/extranets/contacts/index.html.erb
index 9c0b638d69cc7b58fa37ec32d7e904833e08b193..d737b23932ba03a2c273dc7128da4c66f7b22592 100644
--- a/app/views/admin/communication/extranets/contacts/index.html.erb
+++ b/app/views/admin/communication/extranets/contacts/index.html.erb
@@ -1,29 +1,32 @@
 <% content_for :title, Communication::Extranet.human_attribute_name(:feature_contacts) %>
 
 <%= render 'admin/communication/extranets/sidebar' do %>
-  <%= osuny_panel University::Person.model_name.human(count: 2) do %>
+  <% action = link_to t('export'),
+                    admin_communication_extranet_contacts_path(extranet_id: @extranet.id, export: 'people', format: :xlsx),
+                    class: button_classes('ms-1') if can?(:show, University::Person) %>
+  <%= osuny_panel University::Person.model_name.human(count: 2), action: action do %>
     <div class="table-responsive">
       <table class="<%= table_classes%>">
         <tbody>
-          <% @persons.each do |person| %>
+          <% @people.each do |person| %>
             <tr>
               <td><%= link_to person, [:admin, person] %></td>
               <td><%= person.email %></td>
               <td>
                 <% if @extranet.connected?(person) %>
-                  <%= link_to 'Déconnecter', 
+                  <%= link_to 'Déconnecter',
                                   disconnect_admin_communication_extranet_contacts_path(
-                                    extranet_id: @extranet.id, 
-                                    objectId: person.id, 
+                                    extranet_id: @extranet.id,
+                                    objectId: person.id,
                                     objectType: person.class
                                   ),
                                   class: button_classes_danger,
                                   method: :post %>
                 <% else %>
-                  <%= link_to 'Connecter', 
+                  <%= link_to 'Connecter',
                                   connect_admin_communication_extranet_contacts_path(
-                                    extranet_id: @extranet.id, 
-                                    objectId: person.id, 
+                                    extranet_id: @extranet.id,
+                                    objectId: person.id,
                                     objectType: person.class
                                   ),
                                   class: button_classes,
@@ -35,9 +38,12 @@
         </tbody>
       </table>
     </div>
-    <%= paginate @persons, theme: 'bootstrap-5', param_name: :persons_page %>
+    <%= paginate @people, theme: 'bootstrap-5', param_name: :persons_page %>
   <% end %>
-  <%= osuny_panel University::Organization.model_name.human(count: 2) do %>
+  <% action = link_to t('export'),
+                    admin_communication_extranet_contacts_path(extranet_id: @extranet.id, export: 'organizations', format: :xlsx),
+                    class: button_classes('ms-1') if can?(:show, University::Person) %>
+  <%= osuny_panel University::Organization.model_name.human(count: 2), action: action do %>
     <div class="table-responsive">
       <table class="<%= table_classes%>">
         <tbody>
@@ -46,19 +52,19 @@
               <td><%= link_to organization, [:admin, organization] %></td>
               <td>
                 <% if @extranet.connected?(organization) %>
-                  <%= link_to 'Déconnecter', 
+                  <%= link_to 'Déconnecter',
                                   disconnect_admin_communication_extranet_contacts_path(
-                                    extranet_id: @extranet.id, 
-                                    objectId: organization.id, 
+                                    extranet_id: @extranet.id,
+                                    objectId: organization.id,
                                     objectType: organization.class
                                   ),
                                   class: button_classes_danger,
                                   method: :post %>
                 <% else %>
-                  <%= link_to 'Connecter', 
+                  <%= link_to 'Connecter',
                                   connect_admin_communication_extranet_contacts_path(
-                                    extranet_id: @extranet.id, 
-                                    objectId: organization.id, 
+                                    extranet_id: @extranet.id,
+                                    objectId: organization.id,
                                     objectType: organization.class
                                   ),
                                   class: button_classes,
@@ -72,4 +78,4 @@
     </div>
     <%= paginate @organizations, theme: 'bootstrap-5', param_name: :organizations_page %>
   <% end %>
-<% end %>
\ No newline at end of file
+<% end %>
diff --git a/app/views/admin/communication/extranets/contacts/organizations.xlsx.axlsx b/app/views/admin/communication/extranets/contacts/organizations.xlsx.axlsx
new file mode 100644
index 0000000000000000000000000000000000000000..a0e20d1940857097b2d9c4de0409c45f737f64b3
--- /dev/null
+++ b/app/views/admin/communication/extranets/contacts/organizations.xlsx.axlsx
@@ -0,0 +1,60 @@
+wb = xlsx_package.workbook
+wb.add_worksheet(name: "index") do |sheet|
+  options = [
+    University::Organization.human_attribute_name('name'),
+    University::Organization.human_attribute_name('address_name'),
+    University::Organization.human_attribute_name('address'),
+    University::Organization.human_attribute_name('address_additional'),
+    University::Organization.human_attribute_name('zipcode'),
+    University::Organization.human_attribute_name('city'),
+    University::Organization.human_attribute_name('country'),
+    University::Organization.human_attribute_name('kind'),
+    University::Organization.human_attribute_name('siren'),
+    University::Organization.human_attribute_name('phone'),
+    University::Organization.human_attribute_name('url'),
+    University::Organization.human_attribute_name('email'),
+    University::Organization.human_attribute_name('linkedin'),
+    University::Organization.human_attribute_name('twitter')
+    ]
+
+  types = [
+    :string,
+    :string,
+    :string,
+    :string,
+    :string,
+    :string,
+    :string,
+    :string,
+    :string,
+    :string,
+    :string,
+    :string,
+    :string,
+    :string,
+    :string
+  ]
+
+  sheet.add_row options, types: Array.new(types.length) { |_| :string }
+
+  @organizations.each do |organization|
+    infos = [
+      organization.name,
+      organization.address_name,
+      organization.address,
+      organization.address_additional,
+      organization.zipcode,
+      organization.city,
+      organization.country_name,
+      organization.kind_i18n,
+      organization.siren,
+      organization.phone,
+      organization.url,
+      organization.email,
+      organization.linkedin,
+      organization.twitter.present? ? "@#{organization.twitter}" : ''
+    ]
+
+    sheet.add_row infos, types: types
+  end
+end
diff --git a/app/views/admin/communication/extranets/contacts/people.xlsx.axlsx b/app/views/admin/communication/extranets/contacts/people.xlsx.axlsx
new file mode 100644
index 0000000000000000000000000000000000000000..c140b04f01bedf73417b250998aaeba24e4b4ee3
--- /dev/null
+++ b/app/views/admin/communication/extranets/contacts/people.xlsx.axlsx
@@ -0,0 +1,62 @@
+wb = xlsx_package.workbook
+wb.add_worksheet(name: "index") do |sheet|
+  options = [
+    University::Person.human_attribute_name('last_name'),
+    University::Person.human_attribute_name('first_name'),
+    University::Person.human_attribute_name('email'),
+    University::Person.human_attribute_name('gender'),
+    University::Person.human_attribute_name('address'),
+    University::Person.human_attribute_name('zipcode'),
+    University::Person.human_attribute_name('city'),
+    University::Person.human_attribute_name('country'),
+    University::Person.human_attribute_name('birthdate'),
+    University::Person.human_attribute_name('phone_personal'),
+    University::Person.human_attribute_name('phone_professional'),
+    University::Person.human_attribute_name('phone_mobile'),
+    University::Person.human_attribute_name('url'),
+    University::Person.human_attribute_name('linkedin'),
+    University::Person.human_attribute_name('twitter')
+    ]
+
+  types = [
+    :string,
+    :string,
+    :string,
+    :string,
+    :string,
+    :string,
+    :string,
+    :string,
+    :string,
+    :string,
+    :string,
+    :string,
+    :string,
+    :string,
+    :string
+  ]
+
+  sheet.add_row options, types: Array.new(types.length) { |_| :string }
+
+  @people.each do |person|
+    infos = [
+      person.last_name,
+      person.first_name,
+      person.email,
+      person.gender.present? ? t("activerecord.attributes.university/person.genders.#{person.gender}") : '',
+      person.address,
+      person.zipcode,
+      person.city,
+      person.country_name,
+      person.birthdate.present? ? l(person.birthdate, format: :birthday) : '',
+      person.phone_personal,
+      person.phone_professional,
+      person.phone_mobile,
+      person.url,
+      person.linkedin,
+      person.twitter.present? ? "@#{person.twitter}" : ''
+    ]
+
+    sheet.add_row infos, types: types
+  end
+end
diff --git a/app/views/admin/communication/extranets/documents/_form.html.erb b/app/views/admin/communication/extranets/documents/_form.html.erb
index 063aa8dc6bfef2611428dd28009d2e95cb9c0cd2..3f04d4e9f6bb4f4850766f9c53916df37f7d1ba7 100644
--- a/app/views/admin/communication/extranets/documents/_form.html.erb
+++ b/app/views/admin/communication/extranets/documents/_form.html.erb
@@ -12,13 +12,11 @@
     <div class="col-md-4">
       <%= osuny_panel t('metadata') do %>
         <% if can? :publish, document %>
-          <div class="row pure__row--small">
-            <div class="col-6">
-              <%= f.input :published %>
-            </div>
-          </div>
+          <%= f.input :published %>
           <%= f.input :published_at, html5: true, as: :date %>
         <% end %>
+        <%= f.association :kind, collection: @extranet.document_kinds.ordered, include_blank: false %>
+        <%= f.association :category, collection: @extranet.document_categories.ordered, include_blank: false %>
       <% end %>
     </div>
   </div>
diff --git a/app/views/admin/communication/extranets/documents/categories/_form.html.erb b/app/views/admin/communication/extranets/documents/categories/_form.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..1dd744a49671ffd31d4c5f43f83c4129a48eed2b
--- /dev/null
+++ b/app/views/admin/communication/extranets/documents/categories/_form.html.erb
@@ -0,0 +1,25 @@
+<%= simple_form_for [:admin, category] do |f| %>
+  <%= f.error_notification %>
+  <%= f.error_notification message: f.object.errors[:base].to_sentence if f.object.errors[:base].present? %>
+
+  <div class="row">
+    <div class="col-md-8">
+      <%= osuny_panel t('content') do %>
+        <%= f.input :name %>
+      <% end %>
+    </div>
+    <div class="col-md-4">
+      <%= osuny_panel t('metadata') do %>
+        <%= f.input :slug,
+                    as: :string,
+                    input_html: category.persisted? ? {} : {
+                      class: 'js-slug-input',
+                      data: { source: '#communication_extranet_document_category_name' }
+                    } %>
+      <% end %>
+    </div>
+  </div>
+  <% content_for :action_bar_right do %>
+    <%= submit f %>
+  <% end %>
+<% end %>
diff --git a/app/views/admin/communication/extranets/documents/categories/_list.html.erb b/app/views/admin/communication/extranets/documents/categories/_list.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..45650a1d6d713384834a443c7657890c57ebbed3
--- /dev/null
+++ b/app/views/admin/communication/extranets/documents/categories/_list.html.erb
@@ -0,0 +1,31 @@
+<div class="table-responsive">
+  <table class="<%= table_classes %>">
+    <thead>
+      <tr>
+        <th class="ps-0" width="60%"><%= Communication::Extranet::Document::Category.human_attribute_name('name') %></th>
+        <th><%= Communication::Extranet::Document::Category.human_attribute_name('quantity') %></th>
+        <th></th>
+      </tr>
+    </thead>
+    <tbody>
+      <% categories.each do |category| %>
+        <tr>
+          <td class="ps-0"><%= link_to category, admin_communication_extranet_document_category_path(extranet_id: category.extranet.id, id: category.id) %></td>
+          <td><%= category.documents.count %></td>
+          <td>
+            <div class="btn-group" role="group">
+              <%= link_to t('edit'),
+                          edit_admin_communication_extranet_document_category_path(extranet_id: category.extranet.id, id: category.id),
+                          class: button_classes if can?(:update, category) %>
+              <%= link_to t('delete'),
+                          admin_communication_extranet_document_category_path(extranet_id: category.extranet.id, id: category.id),
+                          method: :delete,
+                          data: { confirm: t('please_confirm') },
+                          class: button_classes_danger if can?(:destroy, category) %>
+            </div>
+          </td>
+        </tr>
+      <% end %>
+    </tbody>
+  </table>
+</div>
diff --git a/app/views/admin/communication/extranets/documents/categories/edit.html.erb b/app/views/admin/communication/extranets/documents/categories/edit.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..213c0056b51e4ccbfb3ed8619a16bf3b0af53ef7
--- /dev/null
+++ b/app/views/admin/communication/extranets/documents/categories/edit.html.erb
@@ -0,0 +1,5 @@
+<% content_for :title, @category %>
+
+<%= render 'admin/communication/extranets/sidebar' do %>
+  <%= render 'form', category: @category %>
+<% end %>
diff --git a/app/views/admin/communication/extranets/documents/categories/index.html.erb b/app/views/admin/communication/extranets/documents/categories/index.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..7cd4d3774dd5d3382d98bf7ab013c7e185333701
--- /dev/null
+++ b/app/views/admin/communication/extranets/documents/categories/index.html.erb
@@ -0,0 +1,9 @@
+<% content_for :title, Communication::Extranet::Document::Category.model_name.human(count: 2) %>
+
+<%= render 'admin/communication/extranets/sidebar' do %>
+  <%= render 'admin/communication/extranets/documents/categories/list', categories: @categories %>
+<% end %>
+
+<% content_for :action_bar_right do %>
+  <%= create_link Communication::Extranet::Document::Category %>
+<% end %>
\ No newline at end of file
diff --git a/app/views/admin/communication/extranets/documents/categories/new.html.erb b/app/views/admin/communication/extranets/documents/categories/new.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..04037310c7f30d0d8441899965092973963bdc4b
--- /dev/null
+++ b/app/views/admin/communication/extranets/documents/categories/new.html.erb
@@ -0,0 +1,5 @@
+<% content_for :title, Communication::Extranet::Document::Category.model_name.human %>
+
+<%= render 'admin/communication/extranets/sidebar' do %>
+  <%= render 'form', category: @category %>
+<% end %>
diff --git a/app/views/admin/communication/extranets/documents/categories/show.html.erb b/app/views/admin/communication/extranets/documents/categories/show.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..535fe68ca87b8ef5e1507faf1d2797d040fe013e
--- /dev/null
+++ b/app/views/admin/communication/extranets/documents/categories/show.html.erb
@@ -0,0 +1,16 @@
+<% content_for :title, @category %>
+
+<%= render 'admin/communication/extranets/sidebar' do %>
+  <%= osuny_panel Communication::Extranet::Document.model_name.human(count: 2) do %>
+    <%= render 'admin/communication/extranets/documents/list', documents: @documents %>
+    <%= paginate @documents, theme: 'bootstrap-5' %>
+  <% end %>
+<% end %>
+
+<% content_for :action_bar_left do %>
+  <%= destroy_link @category %>
+<% end %>
+
+<% content_for :action_bar_right do %>
+  <%= edit_link @category %>
+<% end %>
diff --git a/app/views/admin/communication/extranets/documents/index.html.erb b/app/views/admin/communication/extranets/documents/index.html.erb
index 40ec6ed9a75ee524b5dd85ef886ebdcdd295b875..8e93fb2198d4c623f6112c8970a37b41abdf1697 100644
--- a/app/views/admin/communication/extranets/documents/index.html.erb
+++ b/app/views/admin/communication/extranets/documents/index.html.erb
@@ -1,8 +1,22 @@
 <% content_for :title, Communication::Extranet.human_attribute_name(:feature_library) %>
 
 <%= render 'admin/communication/extranets/sidebar' do %>
-  <%= render 'admin/communication/extranets/documents/list', documents: @documents %>
-  <%= paginate @documents, theme: 'bootstrap-5' %>
+  <% action = create_link Communication::Extranet::Document %>
+  <%= osuny_panel Communication::Extranet::Document.model_name.human(count: 2), action: action do %>
+    <%= render 'admin/communication/extranets/documents/list', documents: @documents %>
+    <%= paginate @documents, theme: 'bootstrap-5' %>
+  <% end %>
+
+  <% action = link_to t('create'), new_admin_communication_extranet_document_category_path, class: button_classes %>
+  <%= osuny_panel Communication::Extranet::Document::Category.model_name.human(count: 2), action: action do %>
+    <%= render 'admin/communication/extranets/documents/categories/list', categories: @categories %>
+  <% end %>
+
+  <% action = link_to t('create'), new_admin_communication_extranet_document_kind_path, class: button_classes %>
+  <%= osuny_panel Communication::Extranet::Document::Kind.model_name.human(count: 2), action: action do %>
+    <%= render 'admin/communication/extranets/documents/kinds/list', kinds: @kinds %>
+  <% end %>
+
 <% end %>
 
 <% content_for :action_bar_right do %>
diff --git a/app/views/admin/communication/extranets/documents/kinds/_form.html.erb b/app/views/admin/communication/extranets/documents/kinds/_form.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..bfb75b8b56c062f9553f167c988e856abf834528
--- /dev/null
+++ b/app/views/admin/communication/extranets/documents/kinds/_form.html.erb
@@ -0,0 +1,25 @@
+<%= simple_form_for [:admin, kind] do |f| %>
+  <%= f.error_notification %>
+  <%= f.error_notification message: f.object.errors[:base].to_sentence if f.object.errors[:base].present? %>
+
+  <div class="row">
+    <div class="col-md-8">
+      <%= osuny_panel t('content') do %>
+        <%= f.input :name %>
+      <% end %>
+    </div>
+    <div class="col-md-4">
+      <%= osuny_panel t('metadata') do %>
+        <%= f.input :slug,
+                    as: :string,
+                    input_html: kind.persisted? ? {} : {
+                      class: 'js-slug-input',
+                      data: { source: '#communication_extranet_document_kind_name' }
+                    } %>
+      <% end %>
+    </div>
+  </div>
+  <% content_for :action_bar_right do %>
+    <%= submit f %>
+  <% end %>
+<% end %>
diff --git a/app/views/admin/communication/extranets/documents/kinds/_list.html.erb b/app/views/admin/communication/extranets/documents/kinds/_list.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..0cb30d55f5f0b1ed66229e1a66427a420583ac74
--- /dev/null
+++ b/app/views/admin/communication/extranets/documents/kinds/_list.html.erb
@@ -0,0 +1,31 @@
+<div class="table-responsive">
+  <table class="<%= table_classes %>">
+    <thead>
+      <tr>
+        <th class="ps-0" width="60%"><%= Communication::Extranet::Document::Kind.human_attribute_name('name') %></th>
+        <th><%= Communication::Extranet::Document::Kind.human_attribute_name('quantity') %></th>
+        <th></th>
+      </tr>
+    </thead>
+    <tbody>
+      <% kinds.each do |kind| %>
+        <tr>
+          <td class="ps-0"><%= link_to kind, admin_communication_extranet_document_kind_path(extranet_id: kind.extranet.id, id: kind.id) %></td>
+          <td><%= kind.documents.count %></td>
+          <td>
+            <div class="btn-group" role="group">
+              <%= link_to t('edit'),
+                          edit_admin_communication_extranet_document_kind_path(extranet_id: kind.extranet.id, id: kind.id),
+                          class: button_classes if can?(:update, kind) %>
+              <%= link_to t('delete'),
+                          admin_communication_extranet_document_kind_path(extranet_id: kind.extranet.id, id: kind.id),
+                          method: :delete,
+                          data: { confirm: t('please_confirm') },
+                          class: button_classes_danger if can?(:destroy, kind) %>
+            </div>
+          </td>
+        </tr>
+      <% end %>
+    </tbody>
+  </table>
+</div>
diff --git a/app/views/admin/communication/extranets/documents/kinds/edit.html.erb b/app/views/admin/communication/extranets/documents/kinds/edit.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..6a33b3912c1e57e0337b0ef8ff0b202e5e09595e
--- /dev/null
+++ b/app/views/admin/communication/extranets/documents/kinds/edit.html.erb
@@ -0,0 +1,5 @@
+<% content_for :title, @kind %>
+
+<%= render 'admin/communication/extranets/sidebar' do %>
+  <%= render 'form', kind: @kind %>
+<% end %>
diff --git a/app/views/admin/communication/extranets/documents/kinds/index.html.erb b/app/views/admin/communication/extranets/documents/kinds/index.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..c10c021d7b3b1ec146b83f22e2389bdf7561c33d
--- /dev/null
+++ b/app/views/admin/communication/extranets/documents/kinds/index.html.erb
@@ -0,0 +1,9 @@
+<% content_for :title, Communication::Extranet::Document::Kind.model_name.human(count: 2) %>
+
+<%= render 'admin/communication/extranets/sidebar' do %>
+  <%= render 'admin/communication/extranets/documents/kinds/list', kinds: @kinds %>
+<% end %>
+
+<% content_for :action_bar_right do %>
+  <%= create_link Communication::Extranet::Document::Kind %>
+<% end %>
\ No newline at end of file
diff --git a/app/views/admin/communication/extranets/documents/kinds/new.html.erb b/app/views/admin/communication/extranets/documents/kinds/new.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..c7b31afd7a099e13ffd44860b148f0c3392f5e7a
--- /dev/null
+++ b/app/views/admin/communication/extranets/documents/kinds/new.html.erb
@@ -0,0 +1,5 @@
+<% content_for :title, Communication::Extranet::Document::Kind.model_name.human %>
+
+<%= render 'admin/communication/extranets/sidebar' do %>
+  <%= render 'form', kind: @kind %>
+<% end %>
diff --git a/app/views/admin/communication/extranets/documents/kinds/show.html.erb b/app/views/admin/communication/extranets/documents/kinds/show.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..e510fd64acb77bcc591cb4bb1995c1d23afe27a1
--- /dev/null
+++ b/app/views/admin/communication/extranets/documents/kinds/show.html.erb
@@ -0,0 +1,16 @@
+<% content_for :title, @kind %>
+
+<%= render 'admin/communication/extranets/sidebar' do %>
+  <%= osuny_panel Communication::Extranet::Document.model_name.human(count: 2) do %>
+    <%= render 'admin/communication/extranets/documents/list', documents: @documents %>
+    <%= paginate @documents, theme: 'bootstrap-5' %>
+  <% end %>
+<% end %>
+
+<% content_for :action_bar_left do %>
+  <%= destroy_link @kind %>
+<% end %>
+
+<% content_for :action_bar_right do %>
+  <%= edit_link @kind %>
+<% end %>
diff --git a/app/views/admin/communication/extranets/documents/show.html.erb b/app/views/admin/communication/extranets/documents/show.html.erb
index fbc17175fb3036454f659d82484f6048cda21576..5a1494f7532a8eba7be06c1dd4f41ef7bb5e7c9f 100644
--- a/app/views/admin/communication/extranets/documents/show.html.erb
+++ b/app/views/admin/communication/extranets/documents/show.html.erb
@@ -9,16 +9,20 @@
     </div>
     <div class="col-xl-4">
       <%= osuny_panel t('metadata') do %>
-        <div class="row pure__row--small">
-          <div class="col-6">
-            <%= osuny_label Communication::Extranet::Document.human_attribute_name('published') %>
-            <p>
-              <%= t @document.published %><% if @document.published %>,
-                <%= l @document.published_at.to_date, format: :long %>
-              <% end %>
-            </p>
-          </div>
-        </div>
+        <%= osuny_label Communication::Extranet::Document.human_attribute_name('published') %>
+        <p>
+          <%= t @document.published %><% if @document.published %>,
+            <%= l @document.published_at.to_date, format: :long %>
+          <% end %>
+        </p>
+        <% if @document.category %>
+          <%= osuny_label Communication::Extranet::Document::Category.model_name.human %>
+          <p><%= link_to @document.category, [:admin, @document.category] %></p>
+        <% end %>
+        <% if @document.kind %>
+          <%= osuny_label Communication::Extranet::Document::Kind.model_name.human %>
+          <p><%= link_to @document.kind, [:admin, @document.kind] %></p>
+        <% end %>
       <% end %>
     </div>
   </div>
diff --git a/app/views/admin/communication/extranets/posts/categories/_list.html.erb b/app/views/admin/communication/extranets/posts/categories/_list.html.erb
index e553d2d004b953398e638b9bd1ff4a7b67a2efa5..e865a434c7b7eff49c79ac3a5b71ed64fe997e4c 100644
--- a/app/views/admin/communication/extranets/posts/categories/_list.html.erb
+++ b/app/views/admin/communication/extranets/posts/categories/_list.html.erb
@@ -3,6 +3,7 @@
     <thead>
       <tr>
         <th class="ps-0" width="60%"><%= Communication::Extranet::Post::Category.human_attribute_name('name') %></th>
+        <th><%= Communication::Extranet::Post::Category.human_attribute_name('quantity') %></th>
         <th></th>
       </tr>
     </thead>
@@ -10,7 +11,7 @@
       <% categories.each do |category| %>
         <tr>
           <td class="ps-0"><%= link_to category, admin_communication_extranet_post_category_path(extranet_id: category.extranet.id, id: category.id) %></td>
-
+          <td><%= category.posts.count %></td>
           <td>
             <div class="btn-group" role="group">
               <%= link_to t('edit'),
diff --git a/app/views/admin/communication/extranets/posts/categories/show.html.erb b/app/views/admin/communication/extranets/posts/categories/show.html.erb
index 30f2bd037402f50911dbb153e9453cbcd7dcdfb9..b564c8234c32559f70c1c8aef46fef455b0a73e9 100644
--- a/app/views/admin/communication/extranets/posts/categories/show.html.erb
+++ b/app/views/admin/communication/extranets/posts/categories/show.html.erb
@@ -1,8 +1,10 @@
 <% content_for :title, @category %>
 
 <%= render 'admin/communication/extranets/sidebar' do %>
-  <%= render 'admin/communication/extranets/posts/list', posts: @posts %>
-  <%= paginate @posts, theme: 'bootstrap-5' %>
+  <%= osuny_panel Communication::Extranet::Post.model_name.human(count: 2) do %>
+    <%= render 'admin/communication/extranets/posts/list', posts: @posts %>
+    <%= paginate @posts, theme: 'bootstrap-5' %>
+  <% end %>
 <% end %>
 
 <% content_for :action_bar_left do %>
diff --git a/app/views/admin/communication/extranets/posts/index.html.erb b/app/views/admin/communication/extranets/posts/index.html.erb
index cbf3d5fdf58b1c078afbc51d21172c1a77405044..9b081ed3b7d6be7dba00de95acef7694cf1d7b0b 100644
--- a/app/views/admin/communication/extranets/posts/index.html.erb
+++ b/app/views/admin/communication/extranets/posts/index.html.erb
@@ -1,14 +1,14 @@
 <% content_for :title, Communication::Extranet.human_attribute_name(:feature_posts) %>
 
 <%= render 'admin/communication/extranets/sidebar' do %>
-  <section class="mb-5">
+  <% action = create_link Communication::Extranet::Post %>
+  <%= osuny_panel Communication::Extranet::Post.model_name.human(count: 2), action: action do %>
     <%= render 'admin/communication/extranets/posts/list', posts: @posts %>
     <%= paginate @posts, theme: 'bootstrap-5' %>
-  </section>
+  <% end %>
 
   <% action = link_to t('create'), new_admin_communication_extranet_post_category_path, class: button_classes %>
-  <%# action = create_link Communication::Website::Category %>
-  <%= osuny_panel Communication::Website::Category.model_name.human(count: 2), action: action do %>
+  <%= osuny_panel Communication::Extranet::Post::Category.model_name.human(count: 2), action: action do %>
     <%= render 'admin/communication/extranets/posts/categories/list', categories: @categories %>
   <% end %>
 <% end %>
diff --git a/app/views/admin/communication/websites/posts/show.html.erb b/app/views/admin/communication/websites/posts/show.html.erb
index f1538493ae0a905a056cfa38baf380cb2ed78ab6..784067d9300f9191c868ba818a1d8f6cfa83249a 100644
--- a/app/views/admin/communication/websites/posts/show.html.erb
+++ b/app/views/admin/communication/websites/posts/show.html.erb
@@ -53,6 +53,7 @@
 
 <% content_for :action_bar_left do %>
   <%= destroy_link @post %>
+  <%= duplicate_link @post %>
   <%= static_link static_admin_communication_website_post_path(@post) %>
 <% end %>
 
diff --git a/app/views/admin/education/schools/static.html.erb b/app/views/admin/education/schools/static.html.erb
index bb1396b0e79a9e9e3f51b36b2821a731bf83d6e4..01bafc82144f5793d2658cc13da7e7ab18309292 100644
--- a/app/views/admin/education/schools/static.html.erb
+++ b/app/views/admin/education/schools/static.html.erb
@@ -9,7 +9,7 @@ zipcode: >
 city: >
   <%= @about.city %>
 country: >
-  <%= ISO3166::Country[@about.country].translations[@about.country.downcase] %>
+  <%= @about.country_common_name %>
 phone: >
   <%= @about.phone %>
 <% if administrator_involvements.any? %>
diff --git a/app/views/admin/research/laboratories/static.html.erb b/app/views/admin/research/laboratories/static.html.erb
index 01b70fa3db984a8df8b173f8ac1ed56dcd58aa4d..e294a824d91ba5419a878940eb7eeca6960545fe 100644
--- a/app/views/admin/research/laboratories/static.html.erb
+++ b/app/views/admin/research/laboratories/static.html.erb
@@ -8,5 +8,5 @@ zipcode: >
 city: >
   <%= @about.city %>
 country: >
-  <%= ISO3166::Country[@about.country].translations[@about.country.downcase] %>
+  <%= @about.country_common_name %>
 ---
diff --git a/app/views/admin/university/organizations/show.html.erb b/app/views/admin/university/organizations/show.html.erb
index 46dd34a5f1e7a11749586f6d6b25a1faf1b83bab..fdf5454779cb2c5be4806cf596f75e662efef0aa 100644
--- a/app/views/admin/university/organizations/show.html.erb
+++ b/app/views/admin/university/organizations/show.html.erb
@@ -26,7 +26,7 @@
         <% if @organization.country.present? %>
           <div class="col-xxl-6">
             <%= osuny_label University::Organization.human_attribute_name('country') %>
-            <p><%= ISO3166::Country[@organization.country].common_name %></p>
+            <p><%= @organization.country_name %></p>
           </div>
         <% end %>
         <% if @organization.geolocated? %>
diff --git a/app/views/admin/university/people/_main_infos.html.erb b/app/views/admin/university/people/_main_infos.html.erb
index caf29a011f6bd692f23ab23100d5f483841c730a..9211f6b14809bc4cfbae3d44673a34fb0d0f94eb 100644
--- a/app/views/admin/university/people/_main_infos.html.erb
+++ b/app/views/admin/university/people/_main_infos.html.erb
@@ -29,7 +29,7 @@
             <p><%= l(person.birthdate, format: :birthday) %></p>
            </div>
         <% end %>
-        <% [:address, :zipcode, :city, :country].each do |property| %>
+        <% [:address, :zipcode, :city].each do |property| %>
           <% value = person.send property %>
           <% next if value.blank? %>
           <div class="col-md-6">
@@ -37,6 +37,12 @@
             <p><%= value %></p>
           </div>
         <% end %>
+        <% if person.country.present? %>
+          <div class="col-md-6">
+            <%= osuny_label University::Person.human_attribute_name(:country) %>
+            <p><%= person.country_name %></p>
+          </div>
+        <% end %>
       </div>
       <% unless person.biography.blank? %>
         <%= osuny_label University::Person.human_attribute_name('biography') %>
diff --git a/app/views/extranet/alumni/academic_years/index.html.erb b/app/views/extranet/alumni/academic_years/index.html.erb
index 4bc33d1c7565e5afed42ab125ff54f9cc4ab30ba..034b1633d0684e95ece06a7462cb7ccd177a1e0f 100644
--- a/app/views/extranet/alumni/academic_years/index.html.erb
+++ b/app/views/extranet/alumni/academic_years/index.html.erb
@@ -1,7 +1,9 @@
 <% content_for :title, Education::AcademicYear.model_name.human(count: 2) %>
 <% content_for :header_right do %>
+  <p>
     <%= @count %>
     <%= Education::AcademicYear.model_name.human(count: @count).downcase %>
+  </p>
 <% end %>
 
 <ul class="years">
diff --git a/app/views/extranet/alumni/academic_years/show.html.erb b/app/views/extranet/alumni/academic_years/show.html.erb
index 37172bd0d229bed9a929ccb9ecb50f4e6cec637f..8bc4b640e29d91cd9593e5d3de7973c6aa9a76e7 100644
--- a/app/views/extranet/alumni/academic_years/show.html.erb
+++ b/app/views/extranet/alumni/academic_years/show.html.erb
@@ -1,7 +1,9 @@
 <% content_for :title, @academic_year %>
 <% content_for :header_right do %>
-  <%= @alumni.count %>
-  <%= University::Person::Alumnus.model_name.human(count: @alumni.count).downcase %>
+  <p>
+    <%= @alumni.count %>
+    <%= University::Person::Alumnus.model_name.human(count: @alumni.count).downcase %>
+  </p>
 <% end %>
 
 <div class="row">
diff --git a/app/views/extranet/alumni/cohorts/index.html.erb b/app/views/extranet/alumni/cohorts/index.html.erb
index bcd31aff867d45108a9de0b19f32a0d6b8e87fdb..22b159efe20d67ad12c3e671dc6ab70b74097f44 100644
--- a/app/views/extranet/alumni/cohorts/index.html.erb
+++ b/app/views/extranet/alumni/cohorts/index.html.erb
@@ -1,7 +1,9 @@
 <% content_for :title, Education::Cohort.model_name.human(count: 2) %>
 <% content_for :header_right do %>
-  <%= @count %>
-  <%= Education::Cohort.model_name.human(count: @count).downcase %>
+  <p>
+    <%= @count %>
+    <%= Education::Cohort.model_name.human(count: @count).downcase %>
+  </p>
 <% end %>
 
 <% if current_extranet.should_show_years? %>
diff --git a/app/views/extranet/alumni/cohorts/show.html.erb b/app/views/extranet/alumni/cohorts/show.html.erb
index d30a374fc73e663ed27a94a30f04d6b964962d3f..c3c507a4a35f6bb961d5b96a2e2b4f3a41d197bb 100644
--- a/app/views/extranet/alumni/cohorts/show.html.erb
+++ b/app/views/extranet/alumni/cohorts/show.html.erb
@@ -1,7 +1,9 @@
 <% content_for :title, @cohort %>
 <% content_for :header_right do %>
+<p>
   <%= @cohort.people.count %>
   <%= University::Person::Alumnus.model_name.human(count: @cohort.people.count).downcase %>
+</p>
 <% end %>
 
 <p class="mb-5">
diff --git a/app/views/extranet/alumni/organizations/index.html.erb b/app/views/extranet/alumni/organizations/index.html.erb
index 79c05f767dd8741e81911632cb71964cfb1ce061..74a2c7a90cac2e1abe1182e4ca65200fb5944be6 100644
--- a/app/views/extranet/alumni/organizations/index.html.erb
+++ b/app/views/extranet/alumni/organizations/index.html.erb
@@ -1,7 +1,9 @@
 <% content_for :title, University::Organization.model_name.human(count: 2) %>
 <% content_for :header_right do %>
+<p>
   <%= @count %>
   <%= University::Organization.model_name.human(count: @count).downcase %>
+</p>
 <% end %>
 
 <div class="row">
diff --git a/app/views/extranet/alumni/persons/index.html.erb b/app/views/extranet/alumni/persons/index.html.erb
index ca1c13a56ead0bbd270cb18060966fdd3e8eab2a..f005420b9aa494317d3d3e377f9bf8a8d3feb8da 100644
--- a/app/views/extranet/alumni/persons/index.html.erb
+++ b/app/views/extranet/alumni/persons/index.html.erb
@@ -1,7 +1,9 @@
 <% content_for :title, University::Person.model_name.human(count: 2) %>
 <% content_for :header_right do %>
+<p>
   <%= @count %>
   <%= University::Person.model_name.human(count: @count).downcase %>
+</p>
 <% end %>
 
 <div class="row">
diff --git a/app/views/extranet/application/_header.html.erb b/app/views/extranet/application/_header.html.erb
index feb3b9778f4c324d5979e5d1ccf0aaf023e01da2..104875190200567ec350e789e306eb8f7aa72f39 100644
--- a/app/views/extranet/application/_header.html.erb
+++ b/app/views/extranet/application/_header.html.erb
@@ -5,13 +5,13 @@
       <%= yield :header %>
     <% else %>
       <h1><%= yield :title %></h1>
-      <p class="header__additional_data">
+      <div class="header__additional_data">
         <% if content_for(:header_right).present? %>
           <%= yield :header_right %>
         <% else %>
           &nbsp;
         <% end %>
-      </p>
+      </div>
     <% end %>
   </div>
 </header>
\ No newline at end of file
diff --git a/app/views/extranet/contacts/organizations/index.html.erb b/app/views/extranet/contacts/organizations/index.html.erb
index f19a625583464aae635e363729ac739b0451fc8c..a3e3c47ffd05bc701a5f326125a1be8b02dd7d07 100644
--- a/app/views/extranet/contacts/organizations/index.html.erb
+++ b/app/views/extranet/contacts/organizations/index.html.erb
@@ -1,7 +1,9 @@
 <% content_for :title, University::Organization.model_name.human(count: 2) %>
 <% content_for :header_right do %>
-  <%= @count %>
-  <%= University::Organization.model_name.human(count: @count).downcase %>
+  <p>
+    <%= @count %>
+    <%= University::Organization.model_name.human(count: @count).downcase %>
+  </p>
 <% end %>
 
 <%= render "extranet/contacts/organizations/list" %>
diff --git a/app/views/extranet/contacts/persons/index.html.erb b/app/views/extranet/contacts/persons/index.html.erb
index 95104e3aed00fb8dcd1d08c5505db4c16f4f8655..8b570b2aadc4ae925e513278d7114d1d8093a3a0 100644
--- a/app/views/extranet/contacts/persons/index.html.erb
+++ b/app/views/extranet/contacts/persons/index.html.erb
@@ -1,7 +1,9 @@
 <% content_for :title, University::Person.model_name.human(count: 2) %>
 <% content_for :header_right do %>
-  <%= @count %>
-  <%= University::Person.model_name.human(count: @count).downcase %>
+  <p>
+    <%= @count %>
+    <%= University::Person.model_name.human(count: @count).downcase %>
+  </p>
 <% end %>
 
 <div class="row">
diff --git a/app/views/extranet/home/index.html.erb b/app/views/extranet/home/index.html.erb
index dcfaf67ad3356a166c43626f0fa8ce1d732f9060..37429bc56962d7d6915fdca1fe4e7f1bfcdb9305 100644
--- a/app/views/extranet/home/index.html.erb
+++ b/app/views/extranet/home/index.html.erb
@@ -9,6 +9,6 @@
   </div>
 <% end %>
 
+<%= render 'extranet/home/features/contacts' if current_extranet.feature_contacts %>
 <%= render 'extranet/home/features/posts' if current_extranet.feature_posts %>
 <%= render 'extranet/home/features/alumni' if current_extranet.feature_alumni %>
-<%= render 'extranet/home/features/contacts' if current_extranet.feature_contacts %>
diff --git a/app/views/extranet/library/documents/index.html.erb b/app/views/extranet/library/documents/index.html.erb
index c21d83314f85098eb1bf9c256d7961bec5b6637c..c832f8bb7a0892837925097515bc778910551870 100644
--- a/app/views/extranet/library/documents/index.html.erb
+++ b/app/views/extranet/library/documents/index.html.erb
@@ -1,23 +1,36 @@
 <% content_for :title, Communication::Extranet::Document.model_name.human(count: 2) %>
 
-<div class="row mt-n5 documents-list">
-  <% @documents.each do |document| %>
-    <div class="col-sm-6 col-md-4 col-xxl-3">
-      <article class="position-relative mt-5">
-        <div>
-          <i class="bi bi-file-earmark-text display-1"></i>
-          <span>PDF - 120ko</span>
+<div class="row">
+  <div class="col-lg-3">
+    <%= render 'faceted_search/facets', facets: @facets, reset: t('extranet.library.reset') %>
+  </div>
+  <div class="col-lg-9">
+    <div class="row mt-n5 documents-list">
+      <% @documents.each do |document| %>
+        <div class="col-sm-6 col-xxl-4">
+          <article class="position-relative mt-5">
+            <div>
+              <i class="bi bi-file-earmark-text display-1"></i>
+              <% if document.file.attached? %>
+                <% file = document.file %>
+                <span>
+                  <%= file.filename.extension.upcase %>
+                  - <%= number_to_human_size file.byte_size %>
+                </span>
+              <% end %>
+            </div>
+            <p class="mb-3">
+              <b><%= document %></b><br>
+              <span class="text-muted"><%= l document.published_at.to_date %><span>
+            </p>
+            <%= link_to t('extranet.library.download'), 
+                        document.file.url,
+                        class: 'btn btn-primary stretched-link',
+                        target: :_blank if document.file.attached? %>
+          </article>
         </div>
-        <p>
-          <b><%= document %></b><br>
-          <span class="text-muted"><%= l document.published_at.to_date %><span>
-        </p>
-        <%= link_to t('extranet.library.download'), 
-                    document.file.url,
-                    class: 'btn btn-primary stretched-link',
-                    target: :_blank if document.file.attached? %>
-      </article>
+      <% end %>
     </div>
-  <% end %>
-</div>
-<%= paginate @documents, theme: 'bootstrap-5' %>
\ No newline at end of file
+    <%= paginate @documents, theme: 'bootstrap-5' %>
+  </div>
+</div>
\ No newline at end of file
diff --git a/app/views/extranet/organizations/show.html.erb b/app/views/extranet/organizations/show.html.erb
index 0c6394a26b6b4c23d11efb0fd894d7e380ec959b..0042d2c29493816f9eccde170f0460bc4514713a 100644
--- a/app/views/extranet/organizations/show.html.erb
+++ b/app/views/extranet/organizations/show.html.erb
@@ -56,7 +56,7 @@
         <% if @organization.country.present? %>
           <div class="col-xxl-6">
             <%= osuny_label University::Organization.human_attribute_name('country') %>
-            <p><%= ISO3166::Country[@organization.country].common_name %></p>
+            <p><%= @organization.country_common_name %></p>
           </div>
         <% end %>
         <% if @organization.geolocated? %>
diff --git a/app/views/server/universities/_form.html.erb b/app/views/server/universities/_form.html.erb
index 792181f1a8112d203de4dabd53eaeb9223404965..a1bb265e1e510644c8bf9736041e5127fc15b70d 100644
--- a/app/views/server/universities/_form.html.erb
+++ b/app/views/server/universities/_form.html.erb
@@ -29,6 +29,10 @@
                   input_html: { accept: '.jpg,.jpeg,.png,.svg' },
                   preview: false,
                   direct_upload: true %>
+      <%= f.input :feature_education %>
+      <%= f.input :feature_research %>
+      <%= f.input :feature_communication %>
+      <%= f.input :feature_administration %>
     </div>
   </div>
 
diff --git a/config/admin_navigation.rb b/config/admin_navigation.rb
index 090a87b39db8e94e9140a7f56d906cac1dec2e0a..133f5808f2bf89d8a040571ad99bef7a8f69d72f 100644
--- a/config/admin_navigation.rb
+++ b/config/admin_navigation.rb
@@ -20,26 +20,26 @@ SimpleNavigation::Configuration.run do |navigation|
       primary.item  :dashboard, t('admin.dashboard'), admin_root_path,  { icon: Icon::DASHBOARD, highlights_on: /admin$/ }
     end
 
-    if can?(:read, Education::Program)
+    if feature_education?
       primary.item :education, Education.model_name.human, admin_education_root_path, { kind: :header, image: 'admin/education-thumb.jpg' }
       load_from_parts Education, primary
       primary.item :education, 'Ressources éducatives', nil, { icon: Icon::EDUCATION_RESOURCES }
       primary.item :education, 'Feedbacks', nil, { icon: Icon::EDUCATION_FEEDBACKS }
     end
 
-    if can?(:read, Research::Journal) || can?(:read, Research::Hal::Publication) || can?(:read, Research::Laboratory)
+    if feature_research?
       primary.item :research, Research.model_name.human, admin_research_root_path, { kind: :header, image: 'admin/research-thumb.jpg' }
       load_from_parts Research, primary
       primary.item :research_watch, 'Veille', nil, { icon: Icon::RESEARCH_WATCH }
     end
 
-    if can?(:read, Communication::Website)
+    if feature_communication?
       primary.item :communication, Communication.model_name.human, admin_communication_root_path, { kind: :header, image: 'admin/communication-thumb.jpg' }
       load_from_parts Communication, primary
       primary.item :communication_newsletters, 'Lettres d\'information', nil, { icon: Icon::COMMUNICATION_NEWSLETTERS }
     end
 
-    if can?(:read, Administration::Qualiopi::Criterion)
+    if feature_administration?
       primary.item :administration, Administration.model_name.human, admin_administration_root_path, { kind: :header, image: 'admin/administration-thumb.jpg' }
       load_from_parts Administration, primary
       primary.item :administration_campus, 'Campus', nil, { icon: Icon::ADMINISTRATION_CAMPUS }
diff --git a/config/initializers/mime_types.rb b/config/initializers/mime_types.rb
index e7f0e9b1057fe98db3e11af11878db810b5d9ec4..07ca293a6920106c19334d1b478c93f9f6ea124a 100644
--- a/config/initializers/mime_types.rb
+++ b/config/initializers/mime_types.rb
@@ -1,6 +1,3 @@
 # Be sure to restart your server when you modify this file.
-
-# Add new mime types for use in respond_to blocks:
-# Mime::Type.register "text/richtext", :rtf
-# Mime::Type.register "text/css", :css
+Mime::Type.register "application/xls", :xlsx
 Mime::Type.register "application/xml", :xml, %w( text/xml application/x-xml )
diff --git a/config/locales/communication/en.yml b/config/locales/communication/en.yml
index 66cb912bd3ecb57c4db5b03fd4a6882e6852d0f0..a30f9cc6530a7b98cf326b13ce61532888c6f700 100644
--- a/config/locales/communication/en.yml
+++ b/config/locales/communication/en.yml
@@ -15,6 +15,12 @@ en:
       communication/extranet/document:
         one: Document
         other: Documents
+      communication/extranet/document/category:
+        one: Category
+        other: Categories
+      communication/extranet/document/kind:
+        one: Kind
+        other: Kinds
       communication/extranet/post:
         one: Post
         other: Posts
@@ -88,6 +94,14 @@ en:
         name: Name
         published: Published?
         published_at: Publication date
+        category: Category
+        kind: Type
+      communication/extranet/document/category:
+        name: Name
+        quantity: Quantity of documents
+      communication/extranet/document/kind:
+        name: Name
+        quantity: Quantity of documents
       communication/extranet/post:
         author: Author
         category: Category
@@ -100,6 +114,7 @@ en:
         title: Title
       communication/extranet/post/category:
         name: Name
+        quantity: Quantity of posts
       communication/website:
         about: About
         about_: Independent website
diff --git a/config/locales/communication/fr.yml b/config/locales/communication/fr.yml
index d5129e4f8932bd7902f731506cea13d9effd5ba1..03479f4a8788d7e0df289e035561c722200470bb 100644
--- a/config/locales/communication/fr.yml
+++ b/config/locales/communication/fr.yml
@@ -15,6 +15,12 @@ fr:
       communication/extranet/document:
         one: Document
         other: Documents
+      communication/extranet/document/category:
+        one: Catégorie
+        other: Catégories
+      communication/extranet/document/kind:
+        one: Type
+        other: Types
       communication/extranet/post:
         one: Actualité
         other: Actualités
@@ -88,6 +94,14 @@ fr:
         name: Nom
         published: Publié ?
         published_at: Date de publication
+        category: Catégorie
+        kind: Type
+      communication/extranet/document/category:
+        name: Nom
+        quantity: Quantité de documents
+      communication/extranet/document/kind:
+        name: Nom
+        quantity: Quantité de documents
       communication/extranet/post:
         author: Auteur·rice
         category: Catégorie
@@ -100,6 +114,7 @@ fr:
         title: Titre
       communication/extranet/post/category:
         name: Nom
+        quantity: Quantité d'actualités
       communication/website:
         about: Sujet du site
         about_: Site indépendant
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 56de4b0f1de4e3d1c3fffac4742cb6e1bf294c56..f06aaeeba4ceb0837df7712edc899d0aa6c93c2f 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -173,6 +173,7 @@ en:
         finished: Finished
         finished_with_errors: Finished with errors
         pending: Pending
+  export: Export (xlsx)
   false: No
   featured_image:
     title: Image
diff --git a/config/locales/extranet/en.yml b/config/locales/extranet/en.yml
index 8e470e5b45f7803d2055e53a1b3df5825cea472a..4b039bc413104602babc3c99f6458aed09f3f0f4 100644
--- a/config/locales/extranet/en.yml
+++ b/config/locales/extranet/en.yml
@@ -33,6 +33,7 @@ en:
       welcome: Welcome!
     library:
       download: Download
+      reset: Reset
     menu: Menu
     organization:
       experiences: Alumni in this organization (%{count})
diff --git a/config/locales/extranet/fr.yml b/config/locales/extranet/fr.yml
index 1c80d42bc669ca36ad67f6cf15572ea5842efe8e..2ec73a62c078e5af88960546811a860385465f35 100644
--- a/config/locales/extranet/fr.yml
+++ b/config/locales/extranet/fr.yml
@@ -33,6 +33,7 @@ fr:
       welcome: Bienvenue !
     library:
       download: Télécharger
+      reset: Réinitialiser
     menu: Menu
     organization:
       experiences: Alumni dans cette organisation (%{count})
diff --git a/config/locales/fr.yml b/config/locales/fr.yml
index 711fa805dbedff947b0f666147be92f0b97f1ddb..f351c0fcd1bedd3c4f0e5b72ac865e71071d899f 100644
--- a/config/locales/fr.yml
+++ b/config/locales/fr.yml
@@ -173,6 +173,7 @@ fr:
         finished: Traité
         finished_with_errors: Traité avec des erreurs
         pending: En cours de traitement
+  export: Exporter (xlsx)
   false: Non
   featured_image:
     title: Image
diff --git a/config/locales/university/en.yml b/config/locales/university/en.yml
index b681492b3a478c70796c3d221a13bda8e9935c7d..ec6b4e97ebbeaf1f81643563eecb9f122c39d55e 100644
--- a/config/locales/university/en.yml
+++ b/config/locales/university/en.yml
@@ -7,6 +7,10 @@ en:
         country: Country
         default_language: Default language
         has_sso: Has SSO?
+        feature_administration: Administration
+        feature_communication: Communication
+        feature_education: Education
+        feature_research: Research
         identifier: Identifier
         invoice_amount: Invoice amount
         invoice_date: Invoice date
diff --git a/config/locales/university/fr.yml b/config/locales/university/fr.yml
index 2adbfaf37179e61069498280868d3204bf736a14..2e07feea498c9a079600756c258bdd0a264722aa 100644
--- a/config/locales/university/fr.yml
+++ b/config/locales/university/fr.yml
@@ -7,6 +7,10 @@ fr:
         country: Pays
         default_language: Langue par défaut
         has_sso: A un SSO ?
+        feature_administration: Administration
+        feature_communication: Communication
+        feature_education: Enseignement
+        feature_research: Recherche
         identifier: Identifiant
         invoice_amount: Montant de facturation
         invoice_date: Date de facturation
diff --git a/config/routes/admin/communication.rb b/config/routes/admin/communication.rb
index 5db6d8e7f5f1afcaa5be3d48e3afd530010e5dee..f6b9c18591232102c1ce5703f9bdae0986d1df21 100644
--- a/config/routes/admin/communication.rb
+++ b/config/routes/admin/communication.rb
@@ -40,6 +40,7 @@ namespace :communication do
       member do
         get :static
         get :preview
+        post :duplicate
       end
     end
     resources :menus, controller: 'websites/menus', path: '/:lang/menus' do
@@ -73,8 +74,6 @@ namespace :communication do
         post :disconnect
       end
     end
-    namespace :posts do
-    end
     resources :posts, controller: 'extranets/posts' do
       collection do
         resources :categories, controller: 'extranets/posts/categories', as: 'post_categories'
@@ -85,7 +84,12 @@ namespace :communication do
     end
     # Automatic routes based on feature names
     get 'library' => 'extranets/documents#index', as: :library
-    resources :documents, controller: 'extranets/documents'
+    resources :documents, controller: 'extranets/documents' do
+      collection do
+        resources :categories, controller: 'extranets/documents/categories', as: 'document_categories'
+        resources :kinds, controller: 'extranets/documents/kinds', as: 'document_kinds'
+      end
+    end
     resources :jobs, controller: 'extranets/jobs'
   end
   resources :alumni do
diff --git a/db/migrate/20230310081519_create_communication_extranet_document_kinds.rb b/db/migrate/20230310081519_create_communication_extranet_document_kinds.rb
new file mode 100644
index 0000000000000000000000000000000000000000..2471fc11fc72188fc3adf11e476589a8ea838643
--- /dev/null
+++ b/db/migrate/20230310081519_create_communication_extranet_document_kinds.rb
@@ -0,0 +1,13 @@
+class CreateCommunicationExtranetDocumentKinds < ActiveRecord::Migration[7.0]
+  def change
+    create_table :communication_extranet_document_kinds, id: :uuid do |t|
+      t.references :extranet, null: false, foreign_key: {to_table: :communication_extranets}, type: :uuid
+      t.references :university, null: false, foreign_key: true, type: :uuid, index: { name: 'extranet_document_kinds_universities' }
+      t.string :name
+
+      t.timestamps
+    end
+
+    add_reference :communication_extranet_documents, :kind, foreign_key: {to_table: :communication_extranet_document_kinds}, type: :uuid, index: { name: 'index_extranet_document_kinds' }
+  end
+end
diff --git a/db/migrate/20230310081530_create_communication_extranet_document_categories.rb b/db/migrate/20230310081530_create_communication_extranet_document_categories.rb
new file mode 100644
index 0000000000000000000000000000000000000000..83446e18c5c6283348d80fd9246f3344fca364db
--- /dev/null
+++ b/db/migrate/20230310081530_create_communication_extranet_document_categories.rb
@@ -0,0 +1,13 @@
+class CreateCommunicationExtranetDocumentCategories < ActiveRecord::Migration[7.0]
+  def change
+    create_table :communication_extranet_document_categories, id: :uuid do |t|
+      t.references :extranet, null: false, foreign_key: {to_table: :communication_extranets}, type: :uuid
+      t.references :university, null: false, foreign_key: true, type: :uuid, index: { name: 'extranet_document_categories_universities' }
+      t.string :name
+
+      t.timestamps
+    end
+
+    add_reference :communication_extranet_documents, :category, foreign_key: {to_table: :communication_extranet_document_categories}, type: :uuid, index: { name: 'extranet_document_categories' }
+  end
+end
diff --git a/db/migrate/20230310083029_add_slugs_to_document_categories_and_kinds.rb b/db/migrate/20230310083029_add_slugs_to_document_categories_and_kinds.rb
new file mode 100644
index 0000000000000000000000000000000000000000..1e64b54ce376330e0d2f8457a8265bf4f0d51565
--- /dev/null
+++ b/db/migrate/20230310083029_add_slugs_to_document_categories_and_kinds.rb
@@ -0,0 +1,6 @@
+class AddSlugsToDocumentCategoriesAndKinds < ActiveRecord::Migration[7.0]
+  def change
+    add_column :communication_extranet_document_categories, :slug, :string
+    add_column :communication_extranet_document_kinds, :slug, :string
+  end
+end
diff --git a/db/migrate/20230313140557_add_features_to_university.rb b/db/migrate/20230313140557_add_features_to_university.rb
new file mode 100644
index 0000000000000000000000000000000000000000..906720b7c7a8e04641bb146049a94d28f10d48c4
--- /dev/null
+++ b/db/migrate/20230313140557_add_features_to_university.rb
@@ -0,0 +1,8 @@
+class AddFeaturesToUniversity < ActiveRecord::Migration[7.0]
+  def change
+    add_column :universities, :feature_education, :boolean, default: true
+    add_column :universities, :feature_research, :boolean, default: true
+    add_column :universities, :feature_communication, :boolean, default: true
+    add_column :universities, :feature_administration, :boolean, default: true
+  end
+end
diff --git a/db/schema.rb b/db/schema.rb
index a22638c875fdcf348e49f48ff14b1b897ccfa479..17b0322777d8f42728b47b4fd4c1211c8de1ab28 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,13 +10,13 @@
 #
 # It's strongly recommended that you check this file into your version control system.
 
-ActiveRecord::Schema[7.0].define(version: 2023_03_09_131421) do
+ActiveRecord::Schema[7.0].define(version: 2023_03_13_140557) do
   # These are extensions that must be enabled in order to support this database
   enable_extension "pgcrypto"
   enable_extension "plpgsql"
   enable_extension "unaccent"
 
-  create_table "action_text_rich_texts", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
+  create_table "action_text_rich_texts", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t|
     t.string "name", null: false
     t.text "body"
     t.string "record_type", null: false
@@ -26,7 +26,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_03_09_131421) do
     t.index ["record_type", "record_id", "name"], name: "index_action_text_rich_texts_uniqueness", unique: true
   end
 
-  create_table "active_storage_attachments", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
+  create_table "active_storage_attachments", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t|
     t.string "name", null: false
     t.string "record_type", null: false
     t.uuid "record_id", null: false
@@ -36,7 +36,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_03_09_131421) do
     t.index ["record_type", "record_id", "name", "blob_id"], name: "index_active_storage_attachments_uniqueness", unique: true
   end
 
-  create_table "active_storage_blobs", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
+  create_table "active_storage_blobs", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t|
     t.string "key", null: false
     t.string "filename", null: false
     t.string "content_type"
@@ -50,13 +50,13 @@ ActiveRecord::Schema[7.0].define(version: 2023_03_09_131421) do
     t.index ["university_id"], name: "index_active_storage_blobs_on_university_id"
   end
 
-  create_table "active_storage_variant_records", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
+  create_table "active_storage_variant_records", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t|
     t.uuid "blob_id", null: false
     t.string "variation_digest", null: false
     t.index ["blob_id", "variation_digest"], name: "index_active_storage_variant_records_uniqueness", unique: true
   end
 
-  create_table "administration_qualiopi_criterions", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
+  create_table "administration_qualiopi_criterions", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t|
     t.integer "number"
     t.text "name"
     t.text "description"
@@ -64,7 +64,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_03_09_131421) do
     t.datetime "updated_at", null: false
   end
 
-  create_table "administration_qualiopi_indicators", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
+  create_table "administration_qualiopi_indicators", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t|
     t.uuid "criterion_id", null: false
     t.integer "number"
     t.text "name"
@@ -78,7 +78,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_03_09_131421) do
     t.index ["criterion_id"], name: "index_administration_qualiopi_indicators_on_criterion_id"
   end
 
-  create_table "communication_blocks", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
+  create_table "communication_blocks", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t|
     t.uuid "university_id", null: false
     t.string "about_type"
     t.uuid "about_id"
@@ -105,6 +105,28 @@ ActiveRecord::Schema[7.0].define(version: 2023_03_09_131421) do
     t.index ["university_id"], name: "index_communication_extranet_connections_on_university_id"
   end
 
+  create_table "communication_extranet_document_categories", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
+    t.uuid "extranet_id", null: false
+    t.uuid "university_id", null: false
+    t.string "name"
+    t.datetime "created_at", null: false
+    t.datetime "updated_at", null: false
+    t.string "slug"
+    t.index ["extranet_id"], name: "index_communication_extranet_document_categories_on_extranet_id"
+    t.index ["university_id"], name: "extranet_document_categories_universities"
+  end
+
+  create_table "communication_extranet_document_kinds", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
+    t.uuid "extranet_id", null: false
+    t.uuid "university_id", null: false
+    t.string "name"
+    t.datetime "created_at", null: false
+    t.datetime "updated_at", null: false
+    t.string "slug"
+    t.index ["extranet_id"], name: "index_communication_extranet_document_kinds_on_extranet_id"
+    t.index ["university_id"], name: "extranet_document_kinds_universities"
+  end
+
   create_table "communication_extranet_documents", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
     t.string "name"
     t.uuid "university_id", null: false
@@ -113,7 +135,11 @@ ActiveRecord::Schema[7.0].define(version: 2023_03_09_131421) do
     t.datetime "published_at"
     t.datetime "created_at", null: false
     t.datetime "updated_at", null: false
+    t.uuid "kind_id"
+    t.uuid "category_id"
+    t.index ["category_id"], name: "extranet_document_categories"
     t.index ["extranet_id"], name: "index_communication_extranet_documents_on_extranet_id"
+    t.index ["kind_id"], name: "index_extranet_document_kinds"
     t.index ["university_id"], name: "index_communication_extranet_documents_on_university_id"
   end
 
@@ -148,7 +174,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_03_09_131421) do
     t.index ["university_id"], name: "index_communication_extranet_posts_on_university_id"
   end
 
-  create_table "communication_extranets", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
+  create_table "communication_extranets", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t|
     t.string "name"
     t.uuid "university_id", null: false
     t.string "host"
@@ -180,7 +206,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_03_09_131421) do
     t.index ["university_id"], name: "index_communication_extranets_on_university_id"
   end
 
-  create_table "communication_website_categories", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
+  create_table "communication_website_categories", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t|
     t.uuid "university_id", null: false
     t.uuid "communication_website_id", null: false
     t.string "name"
@@ -214,7 +240,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_03_09_131421) do
     t.index ["communication_website_post_id", "communication_website_category_id"], name: "post_category"
   end
 
-  create_table "communication_website_git_files", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
+  create_table "communication_website_git_files", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t|
     t.string "previous_path"
     t.string "about_type", null: false
     t.uuid "about_id", null: false
@@ -226,7 +252,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_03_09_131421) do
     t.index ["website_id"], name: "index_communication_website_git_files_on_website_id"
   end
 
-  create_table "communication_website_imported_authors", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
+  create_table "communication_website_imported_authors", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t|
     t.uuid "university_id", null: false
     t.uuid "website_id", null: false
     t.uuid "author_id"
@@ -242,7 +268,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_03_09_131421) do
     t.index ["website_id"], name: "idx_communication_website_imported_auth_on_website"
   end
 
-  create_table "communication_website_imported_categories", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
+  create_table "communication_website_imported_categories", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t|
     t.uuid "university_id", null: false
     t.uuid "website_id", null: false
     t.uuid "category_id"
@@ -260,7 +286,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_03_09_131421) do
     t.index ["website_id"], name: "idx_communication_website_imported_cat_on_website"
   end
 
-  create_table "communication_website_imported_media", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
+  create_table "communication_website_imported_media", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t|
     t.string "identifier"
     t.jsonb "data"
     t.text "file_url"
@@ -275,7 +301,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_03_09_131421) do
     t.index ["website_id"], name: "index_communication_website_imported_media_on_website_id"
   end
 
-  create_table "communication_website_imported_pages", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
+  create_table "communication_website_imported_pages", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t|
     t.uuid "university_id", null: false
     t.uuid "website_id", null: false
     t.uuid "page_id"
@@ -299,7 +325,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_03_09_131421) do
     t.index ["website_id"], name: "index_communication_website_imported_pages_on_website_id"
   end
 
-  create_table "communication_website_imported_posts", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
+  create_table "communication_website_imported_posts", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t|
     t.uuid "university_id", null: false
     t.uuid "website_id", null: false
     t.uuid "post_id"
@@ -324,7 +350,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_03_09_131421) do
     t.index ["website_id"], name: "index_communication_website_imported_posts_on_website_id"
   end
 
-  create_table "communication_website_imported_websites", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
+  create_table "communication_website_imported_websites", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t|
     t.uuid "university_id", null: false
     t.uuid "website_id", null: false
     t.integer "status", default: 0
@@ -334,7 +360,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_03_09_131421) do
     t.index ["website_id"], name: "index_communication_website_imported_websites_on_website_id"
   end
 
-  create_table "communication_website_menu_items", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
+  create_table "communication_website_menu_items", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t|
     t.uuid "university_id", null: false
     t.uuid "website_id", null: false
     t.uuid "menu_id", null: false
@@ -354,7 +380,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_03_09_131421) do
     t.index ["website_id"], name: "index_communication_website_menu_items_on_website_id"
   end
 
-  create_table "communication_website_menus", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
+  create_table "communication_website_menus", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t|
     t.uuid "university_id", null: false
     t.uuid "communication_website_id", null: false
     t.string "title"
@@ -370,7 +396,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_03_09_131421) do
     t.index ["university_id"], name: "index_communication_website_menus_on_university_id"
   end
 
-  create_table "communication_website_pages", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
+  create_table "communication_website_pages", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t|
     t.uuid "university_id", null: false
     t.uuid "communication_website_id", null: false
     t.string "title"
@@ -385,10 +411,10 @@ ActiveRecord::Schema[7.0].define(version: 2023_03_09_131421) do
     t.text "github_path"
     t.string "featured_image_alt"
     t.text "text"
+    t.text "summary"
     t.string "breadcrumb_title"
     t.text "header_text"
     t.integer "kind"
-    t.text "summary"
     t.string "bodyclass"
     t.uuid "language_id", null: false
     t.text "featured_image_credit"
@@ -402,7 +428,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_03_09_131421) do
     t.index ["university_id"], name: "index_communication_website_pages_on_university_id"
   end
 
-  create_table "communication_website_permalinks", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
+  create_table "communication_website_permalinks", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t|
     t.uuid "university_id", null: false
     t.uuid "website_id", null: false
     t.string "about_type", null: false
@@ -416,7 +442,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_03_09_131421) do
     t.index ["website_id"], name: "index_communication_website_permalinks_on_website_id"
   end
 
-  create_table "communication_website_posts", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
+  create_table "communication_website_posts", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t|
     t.uuid "university_id", null: false
     t.uuid "communication_website_id", null: false
     t.string "title"
@@ -442,7 +468,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_03_09_131421) do
     t.index ["university_id"], name: "index_communication_website_posts_on_university_id"
   end
 
-  create_table "communication_websites", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
+  create_table "communication_websites", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t|
     t.uuid "university_id", null: false
     t.string "name"
     t.string "url"
@@ -494,7 +520,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_03_09_131421) do
     t.index ["priority", "run_at"], name: "delayed_jobs_priority"
   end
 
-  create_table "education_academic_years", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
+  create_table "education_academic_years", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t|
     t.uuid "university_id", null: false
     t.integer "year"
     t.datetime "created_at", null: false
@@ -509,7 +535,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_03_09_131421) do
     t.index ["university_person_id", "education_academic_year_id"], name: "index_person_academic_year"
   end
 
-  create_table "education_cohorts", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
+  create_table "education_cohorts", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t|
     t.uuid "university_id", null: false
     t.uuid "program_id", null: false
     t.uuid "academic_year_id", null: false
@@ -530,7 +556,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_03_09_131421) do
     t.index ["university_person_id", "education_cohort_id"], name: "index_person_cohort"
   end
 
-  create_table "education_diplomas", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
+  create_table "education_diplomas", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t|
     t.string "name"
     t.string "short_name"
     t.integer "level", default: 0
@@ -544,7 +570,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_03_09_131421) do
     t.index ["university_id"], name: "index_education_diplomas_on_university_id"
   end
 
-  create_table "education_programs", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
+  create_table "education_programs", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t|
     t.uuid "university_id", null: false
     t.string "name"
     t.integer "capacity"
@@ -604,7 +630,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_03_09_131421) do
     t.index ["education_program_id", "user_id"], name: "index_education_programs_users_on_program_id_and_user_id"
   end
 
-  create_table "education_schools", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
+  create_table "education_schools", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t|
     t.uuid "university_id", null: false
     t.string "name"
     t.string "address"
@@ -619,7 +645,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_03_09_131421) do
     t.index ["university_id"], name: "index_education_schools_on_university_id"
   end
 
-  create_table "imports", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
+  create_table "imports", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t|
     t.integer "number_of_lines"
     t.jsonb "processing_errors"
     t.integer "kind"
@@ -632,7 +658,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_03_09_131421) do
     t.index ["user_id"], name: "index_imports_on_user_id"
   end
 
-  create_table "languages", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
+  create_table "languages", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t|
     t.string "name"
     t.string "iso_code"
     t.datetime "created_at", null: false
@@ -699,7 +725,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_03_09_131421) do
     t.index ["university_id"], name: "index_research_journal_paper_kinds_on_university_id"
   end
 
-  create_table "research_journal_papers", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
+  create_table "research_journal_papers", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t|
     t.string "title"
     t.datetime "published_at", precision: nil
     t.uuid "university_id", null: false
@@ -732,7 +758,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_03_09_131421) do
     t.index ["researcher_id"], name: "index_research_journal_papers_researchers_on_researcher_id"
   end
 
-  create_table "research_journal_volumes", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
+  create_table "research_journal_volumes", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t|
     t.uuid "university_id", null: false
     t.uuid "research_journal_id", null: false
     t.string "title"
@@ -752,7 +778,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_03_09_131421) do
     t.index ["university_id"], name: "index_research_journal_volumes_on_university_id"
   end
 
-  create_table "research_journals", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
+  create_table "research_journals", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t|
     t.uuid "university_id", null: false
     t.string "title"
     t.text "meta_description"
@@ -763,7 +789,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_03_09_131421) do
     t.index ["university_id"], name: "index_research_journals_on_university_id"
   end
 
-  create_table "research_laboratories", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
+  create_table "research_laboratories", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t|
     t.uuid "university_id", null: false
     t.string "name"
     t.string "address"
@@ -775,7 +801,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_03_09_131421) do
     t.index ["university_id"], name: "index_research_laboratories_on_university_id"
   end
 
-  create_table "research_laboratory_axes", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
+  create_table "research_laboratory_axes", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t|
     t.uuid "university_id", null: false
     t.uuid "research_laboratory_id", null: false
     t.string "name"
@@ -789,7 +815,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_03_09_131421) do
     t.index ["university_id"], name: "index_research_laboratory_axes_on_university_id"
   end
 
-  create_table "research_theses", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
+  create_table "research_theses", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t|
     t.uuid "university_id", null: false
     t.uuid "research_laboratory_id", null: false
     t.uuid "author_id", null: false
@@ -807,7 +833,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_03_09_131421) do
     t.index ["university_id"], name: "index_research_theses_on_university_id"
   end
 
-  create_table "universities", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
+  create_table "universities", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t|
     t.string "name"
     t.string "identifier"
     t.string "address"
@@ -831,10 +857,14 @@ ActiveRecord::Schema[7.0].define(version: 2023_03_09_131421) do
     t.jsonb "sso_mapping"
     t.string "sso_button_label"
     t.uuid "default_language_id", null: false
+    t.boolean "feature_education", default: true
+    t.boolean "feature_research", default: true
+    t.boolean "feature_communication", default: true
+    t.boolean "feature_administration", default: true
     t.index ["default_language_id"], name: "index_universities_on_default_language_id"
   end
 
-  create_table "university_organizations", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
+  create_table "university_organizations", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t|
     t.uuid "university_id", null: false
     t.string "name"
     t.string "long_name"
@@ -865,7 +895,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_03_09_131421) do
     t.index ["university_id"], name: "index_university_organizations_on_university_id"
   end
 
-  create_table "university_people", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
+  create_table "university_people", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t|
     t.uuid "university_id", null: false
     t.uuid "user_id"
     t.string "last_name"
@@ -906,7 +936,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_03_09_131421) do
     t.index ["user_id"], name: "index_university_people_on_user_id"
   end
 
-  create_table "university_person_experiences", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
+  create_table "university_person_experiences", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t|
     t.uuid "university_id", null: false
     t.uuid "person_id", null: false
     t.uuid "organization_id", null: false
@@ -920,7 +950,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_03_09_131421) do
     t.index ["university_id"], name: "index_university_person_experiences_on_university_id"
   end
 
-  create_table "university_person_involvements", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
+  create_table "university_person_involvements", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t|
     t.uuid "university_id", null: false
     t.uuid "person_id", null: false
     t.integer "kind"
@@ -935,7 +965,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_03_09_131421) do
     t.index ["university_id"], name: "index_university_person_involvements_on_university_id"
   end
 
-  create_table "university_roles", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
+  create_table "university_roles", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t|
     t.uuid "university_id", null: false
     t.string "target_type"
     t.uuid "target_id"
@@ -947,7 +977,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_03_09_131421) do
     t.index ["university_id"], name: "index_university_roles_on_university_id"
   end
 
-  create_table "users", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
+  create_table "users", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t|
     t.uuid "university_id", null: false
     t.string "first_name"
     t.string "last_name"
@@ -999,6 +1029,12 @@ ActiveRecord::Schema[7.0].define(version: 2023_03_09_131421) do
   add_foreign_key "communication_blocks", "universities"
   add_foreign_key "communication_extranet_connections", "communication_extranets", column: "extranet_id"
   add_foreign_key "communication_extranet_connections", "universities"
+  add_foreign_key "communication_extranet_document_categories", "communication_extranets", column: "extranet_id"
+  add_foreign_key "communication_extranet_document_categories", "universities"
+  add_foreign_key "communication_extranet_document_kinds", "communication_extranets", column: "extranet_id"
+  add_foreign_key "communication_extranet_document_kinds", "universities"
+  add_foreign_key "communication_extranet_documents", "communication_extranet_document_categories", column: "category_id"
+  add_foreign_key "communication_extranet_documents", "communication_extranet_document_kinds", column: "kind_id"
   add_foreign_key "communication_extranet_documents", "communication_extranets", column: "extranet_id"
   add_foreign_key "communication_extranet_documents", "universities"
   add_foreign_key "communication_extranet_post_categories", "communication_extranets", column: "extranet_id"
diff --git a/test/fixtures/communication/extranet/document/categories.yml b/test/fixtures/communication/extranet/document/categories.yml
new file mode 100644
index 0000000000000000000000000000000000000000..db2c02aa9d63f0605450cda38919189323f00489
--- /dev/null
+++ b/test/fixtures/communication/extranet/document/categories.yml
@@ -0,0 +1,32 @@
+# == Schema Information
+#
+# Table name: communication_extranet_document_categories
+#
+#  id            :uuid             not null, primary key
+#  name          :string
+#  slug          :string
+#  created_at    :datetime         not null
+#  updated_at    :datetime         not null
+#  extranet_id   :uuid             not null, indexed
+#  university_id :uuid             not null, indexed
+#
+# Indexes
+#
+#  extranet_document_categories_universities                        (university_id)
+#  index_communication_extranet_document_categories_on_extranet_id  (extranet_id)
+#
+# Foreign Keys
+#
+#  fk_rails_6f2232d9f8  (university_id => universities.id)
+#  fk_rails_76e327b90f  (extranet_id => communication_extranets.id)
+#
+
+one:
+  extranet: one
+  university: one
+  name: MyString
+
+two:
+  extranet: two
+  university: two
+  name: MyString
diff --git a/test/fixtures/communication/extranet/document/kinds.yml b/test/fixtures/communication/extranet/document/kinds.yml
new file mode 100644
index 0000000000000000000000000000000000000000..480abfc3a3f956f364e177f076727962d5eb094a
--- /dev/null
+++ b/test/fixtures/communication/extranet/document/kinds.yml
@@ -0,0 +1,32 @@
+# == Schema Information
+#
+# Table name: communication_extranet_document_kinds
+#
+#  id            :uuid             not null, primary key
+#  name          :string
+#  slug          :string
+#  created_at    :datetime         not null
+#  updated_at    :datetime         not null
+#  extranet_id   :uuid             not null, indexed
+#  university_id :uuid             not null, indexed
+#
+# Indexes
+#
+#  extranet_document_kinds_universities                        (university_id)
+#  index_communication_extranet_document_kinds_on_extranet_id  (extranet_id)
+#
+# Foreign Keys
+#
+#  fk_rails_27a9b91ed8  (extranet_id => communication_extranets.id)
+#  fk_rails_2a55cf899a  (university_id => universities.id)
+#
+
+one:
+  extranet: one
+  university: one
+  name: MyString
+
+two:
+  extranet: two
+  university: two
+  name: MyString
diff --git a/test/fixtures/communication/extranet/documents.yml b/test/fixtures/communication/extranet/documents.yml
index be1d0d6ab57326b590356596a4609a335ddd6e69..fb4fa23733ee89bef580ed42a2418d63d9d1b4cd 100644
--- a/test/fixtures/communication/extranet/documents.yml
+++ b/test/fixtures/communication/extranet/documents.yml
@@ -8,18 +8,24 @@
 #  published_at  :datetime
 #  created_at    :datetime         not null
 #  updated_at    :datetime         not null
+#  category_id   :uuid             indexed
 #  extranet_id   :uuid             not null, indexed
+#  kind_id       :uuid             indexed
 #  university_id :uuid             not null, indexed
 #
 # Indexes
 #
+#  extranet_document_categories                             (category_id)
 #  index_communication_extranet_documents_on_extranet_id    (extranet_id)
 #  index_communication_extranet_documents_on_university_id  (university_id)
+#  index_extranet_document_kinds                            (kind_id)
 #
 # Foreign Keys
 #
 #  fk_rails_1272fd263c  (extranet_id => communication_extranets.id)
+#  fk_rails_51f7db2f66  (kind_id => communication_extranet_document_kinds.id)
 #  fk_rails_af877a8c0c  (university_id => universities.id)
+#  fk_rails_eb351dc444  (category_id => communication_extranet_document_categories.id)
 #
 
 one:
diff --git a/test/fixtures/universities.yml b/test/fixtures/universities.yml
index ecf1de669721a7a781d0118dcdc90a50f71960c9..c0db070bd79537a465e28e0451abc25287636f19 100644
--- a/test/fixtures/universities.yml
+++ b/test/fixtures/universities.yml
@@ -6,6 +6,10 @@
 #  address                    :string
 #  city                       :string
 #  country                    :string
+#  feature_administration     :boolean          default(TRUE)
+#  feature_communication      :boolean          default(TRUE)
+#  feature_education          :boolean          default(TRUE)
+#  feature_research           :boolean          default(TRUE)
 #  has_sso                    :boolean          default(FALSE)
 #  identifier                 :string
 #  invoice_amount             :string
diff --git a/test/models/communication/extranet/document/category_test.rb b/test/models/communication/extranet/document/category_test.rb
new file mode 100644
index 0000000000000000000000000000000000000000..6c922f2461aa953d17d095c6576465dd40bf673a
--- /dev/null
+++ b/test/models/communication/extranet/document/category_test.rb
@@ -0,0 +1,29 @@
+# == Schema Information
+#
+# Table name: communication_extranet_document_categories
+#
+#  id            :uuid             not null, primary key
+#  name          :string
+#  slug          :string
+#  created_at    :datetime         not null
+#  updated_at    :datetime         not null
+#  extranet_id   :uuid             not null, indexed
+#  university_id :uuid             not null, indexed
+#
+# Indexes
+#
+#  extranet_document_categories_universities                        (university_id)
+#  index_communication_extranet_document_categories_on_extranet_id  (extranet_id)
+#
+# Foreign Keys
+#
+#  fk_rails_6f2232d9f8  (university_id => universities.id)
+#  fk_rails_76e327b90f  (extranet_id => communication_extranets.id)
+#
+require "test_helper"
+
+class Communication::Extranet::Document::CategoryTest < ActiveSupport::TestCase
+  # test "the truth" do
+  #   assert true
+  # end
+end
diff --git a/test/models/communication/extranet/document/kind_test.rb b/test/models/communication/extranet/document/kind_test.rb
new file mode 100644
index 0000000000000000000000000000000000000000..acca11c4caa41646a8789d1eacd0952b3e64a41a
--- /dev/null
+++ b/test/models/communication/extranet/document/kind_test.rb
@@ -0,0 +1,29 @@
+# == Schema Information
+#
+# Table name: communication_extranet_document_kinds
+#
+#  id            :uuid             not null, primary key
+#  name          :string
+#  slug          :string
+#  created_at    :datetime         not null
+#  updated_at    :datetime         not null
+#  extranet_id   :uuid             not null, indexed
+#  university_id :uuid             not null, indexed
+#
+# Indexes
+#
+#  extranet_document_kinds_universities                        (university_id)
+#  index_communication_extranet_document_kinds_on_extranet_id  (extranet_id)
+#
+# Foreign Keys
+#
+#  fk_rails_27a9b91ed8  (extranet_id => communication_extranets.id)
+#  fk_rails_2a55cf899a  (university_id => universities.id)
+#
+require "test_helper"
+
+class Communication::Extranet::Document::KindTest < ActiveSupport::TestCase
+  # test "the truth" do
+  #   assert true
+  # end
+end