diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index ccf75d2b4..6d0925e9a 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -67,10 +67,6 @@ class ApplicationController < ActionController::Base Flipper.enabled?(feature_name, current_user) end - def feature_enabled_for?(feature_name, item) - Flipper.enabled?(feature_name, item) - end - def authenticate_logged_user! if instructeur_signed_in? authenticate_instructeur! diff --git a/app/controllers/instructeurs/avis_controller.rb b/app/controllers/instructeurs/avis_controller.rb index 8b2c0487c..2e552b707 100644 --- a/app/controllers/instructeurs/avis_controller.rb +++ b/app/controllers/instructeurs/avis_controller.rb @@ -72,7 +72,7 @@ module Instructeurs def create_avis @procedure = Procedure.find(params[:procedure_id]) - if !feature_enabled_for?(:expert_not_allowed_to_invite, @procedure) + if !@procedure.feature_enabled?(:expert_not_allowed_to_invite) @new_avis = create_avis_from_params(avis.dossier, avis.confidentiel) if @new_avis.nil? diff --git a/app/controllers/instructeurs/dossiers_controller.rb b/app/controllers/instructeurs/dossiers_controller.rb index 09e9a432b..9ef7a9653 100644 --- a/app/controllers/instructeurs/dossiers_controller.rb +++ b/app/controllers/instructeurs/dossiers_controller.rb @@ -72,7 +72,8 @@ module Instructeurs end def send_to_instructeurs - recipients = Instructeur.find(JSON.parse(params[:recipients])) + recipients = params['recipients'].presence || [].to_json + recipients = Instructeur.find(JSON.parse(recipients)) recipients.each do |recipient| recipient.follow(dossier) diff --git a/app/controllers/instructeurs/procedures_controller.rb b/app/controllers/instructeurs/procedures_controller.rb index 8e780b5e3..4aea5f5bd 100644 --- a/app/controllers/instructeurs/procedures_controller.rb +++ b/app/controllers/instructeurs/procedures_controller.rb @@ -138,7 +138,8 @@ module Instructeurs end def update_displayed_fields - procedure_presentation.update_displayed_fields(JSON.parse(params[:values])) + values = params['values'].presence || [].to_json + procedure_presentation.update_displayed_fields(JSON.parse(values)) redirect_back(fallback_location: instructeur_procedure_url(procedure)) end diff --git a/app/graphql/api/v2/context.rb b/app/graphql/api/v2/context.rb index 4b395d730..080170698 100644 --- a/app/graphql/api/v2/context.rb +++ b/app/graphql/api/v2/context.rb @@ -10,6 +10,32 @@ class API::V2::Context < GraphQL::Query::Context end end + def internal_use? + self[:internal_use] + end + + def authorized_demarche?(demarche) + if internal_use? + return true + end + + # We are caching authorization logic because it is called for each node + # of the requested graph and can be expensive. Context is reset per request so it is safe. + self[:authorized] ||= Hash.new do |hash, demarche_id| + # Compute the hash value dynamically when first requested + authorized_administrateur = demarche.administrateurs.find do |administrateur| + if self[:token] + administrateur.valid_api_token?(self[:token]) + else + administrateur.id == self[:administrateur_id] + end + end + hash[demarche_id] = authorized_administrateur.present? + end + + self[:authorized][demarche.id] + end + class HasFragment < GraphQL::Language::Visitor def initialize(document, name) super(document) diff --git a/app/graphql/loaders/champ.rb b/app/graphql/loaders/champ.rb new file mode 100644 index 000000000..b33672b10 --- /dev/null +++ b/app/graphql/loaders/champ.rb @@ -0,0 +1,27 @@ +# references: +# https://github.com/Shopify/graphql-batch/blob/master/examples/record_loader.rb + +module Loaders + class Champ < GraphQL::Batch::Loader + def initialize(dossier, private: false) + @where = { dossier: dossier, private: private } + end + + def load(key) + super(key.to_i) + end + + def perform(keys) + query(keys).each { |record| fulfill(record.stable_id, [record].compact) } + keys.each { |key| fulfill(key, nil) unless fulfilled?(key) } + end + + private + + def query(keys) + ::Champ.where(@where) + .includes(:type_de_champ) + .where(types_de_champ: { stable_id: keys }) + end + end +end diff --git a/app/graphql/loaders/record.rb b/app/graphql/loaders/record.rb index 9602a3b60..821a9f1cd 100644 --- a/app/graphql/loaders/record.rb +++ b/app/graphql/loaders/record.rb @@ -3,11 +3,13 @@ module Loaders class Record < GraphQL::Batch::Loader - def initialize(model, column: model.primary_key, where: nil) + def initialize(model, column: model.primary_key, where: nil, includes: nil, array: false) @model = model @column = column.to_s @column_type = model.type_for_attribute(@column) @where = where + @includes = includes + @array = array end def load(key) @@ -15,7 +17,10 @@ module Loaders end def perform(keys) - query(keys).each { |record| fulfill(record.public_send(@column), record) } + query(keys).each do |record| + fulfilled_value = @array ? [record].compact : record + fulfill(record.public_send(@column), fulfilled_value) + end keys.each { |key| fulfill(key, nil) unless fulfilled?(key) } end @@ -24,6 +29,7 @@ module Loaders def query(keys) scope = @model scope = scope.where(@where) if @where + scope = scope.includes(@includes) if @includes scope.where(@column => keys) end end diff --git a/app/graphql/schema.graphql b/app/graphql/schema.graphql index 6debefe95..215e6d2ff 100644 --- a/app/graphql/schema.graphql +++ b/app/graphql/schema.graphql @@ -508,15 +508,15 @@ type DirectUpload { Un dossier """ type Dossier { - annotations: [Champ!]! + annotations(id: ID): [Champ!]! archived: Boolean! """ L’URL de l’attestation au format PDF. """ attestation: File - avis: [Avis!]! - champs: [Champ!]! + avis(id: ID): [Avis!]! + champs(id: ID): [Champ!]! """ Date de la dernière modification. @@ -546,7 +546,7 @@ type Dossier { groupeInstructeur: GroupeInstructeur! id: ID! instructeurs: [Profile!]! - messages: [Message!]! + messages(id: ID): [Message!]! motivation: String motivationAttachment: File diff --git a/app/graphql/types/base_object.rb b/app/graphql/types/base_object.rb index d4dc9aaba..40a81ccd2 100644 --- a/app/graphql/types/base_object.rb +++ b/app/graphql/types/base_object.rb @@ -1,25 +1,4 @@ module Types class BaseObject < GraphQL::Schema::Object - def self.authorized_demarche?(demarche, context) - # We are caching authorization logic because it is called for each node - # of the requested graph and can be expensive. Context is reset per request so it is safe. - context[:authorized] ||= {} - if context[:authorized][demarche.id] - return true - end - - administrateur = demarche.administrateurs.find do |administrateur| - if context[:token] - administrateur.valid_api_token?(context[:token]) - else - administrateur.id == context[:administrateur_id] - end - end - - if administrateur - context[:authorized][demarche.id] = true - true - end - end end end diff --git a/app/graphql/types/demarche_type.rb b/app/graphql/types/demarche_type.rb index 71a0e042e..ec14270fe 100644 --- a/app/graphql/types/demarche_type.rb +++ b/app/graphql/types/demarche_type.rb @@ -88,7 +88,7 @@ module Types end def self.authorized?(object, context) - authorized_demarche?(object, context) + context.authorized_demarche?(object) end end end diff --git a/app/graphql/types/dossier_type.rb b/app/graphql/types/dossier_type.rb index 5f06dce75..7eecf2b18 100644 --- a/app/graphql/types/dossier_type.rb +++ b/app/graphql/types/dossier_type.rb @@ -35,11 +35,19 @@ module Types field :demandeur, Types::DemandeurType, null: false field :instructeurs, [Types::ProfileType], null: false - field :messages, [Types::MessageType], null: false - field :avis, [Types::AvisType], null: false - field :champs, [Types::ChampType], null: false - field :annotations, [Types::ChampType], null: false + field :messages, [Types::MessageType], null: false do + argument :id, ID, required: false + end + field :avis, [Types::AvisType], null: false do + argument :id, ID, required: false + end + field :champs, [Types::ChampType], null: false do + argument :id, ID, required: false + end + field :annotations, [Types::ChampType], null: false do + argument :id, ID, required: false + end def state object.state @@ -69,20 +77,44 @@ module Types Loaders::Association.for(object.class, :followers_instructeurs).load(object) end - def messages - Loaders::Association.for(object.class, commentaires: [:instructeur, :user]).load(object) + def messages(id: nil) + if id.present? + Loaders::Record + .for(Commentaire, where: { dossier: object }, includes: [:instructeur, :user], array: true) + .load(ApplicationRecord.id_from_typed_id(id)) + else + Loaders::Association.for(object.class, commentaires: [:instructeur, :user]).load(object) + end end - def avis - Loaders::Association.for(object.class, avis: [:instructeur, :claimant]).load(object) + def avis(id: nil) + if id.present? + Loaders::Record + .for(Avis, where: { dossier: object }, includes: [:instructeur, :claimant], array: true) + .load(ApplicationRecord.id_from_typed_id(id)) + else + Loaders::Association.for(object.class, avis: [:instructeur, :claimant]).load(object) + end end - def champs - Loaders::Association.for(object.class, champs: [:type_de_champ]).load(object) + def champs(id: nil) + if id.present? + Loaders::Champ + .for(object, private: false) + .load(ApplicationRecord.id_from_typed_id(id)) + else + Loaders::Association.for(object.class, champs: :type_de_champ).load(object) + end end - def annotations - Loaders::Association.for(object.class, champs_private: [:type_de_champ]).load(object) + def annotations(id: nil) + if id.present? + Loaders::Champ + .for(object, private: true) + .load(ApplicationRecord.id_from_typed_id(id)) + else + Loaders::Association.for(object.class, champs_private: :type_de_champ).load(object) + end end def pdf @@ -110,7 +142,7 @@ module Types end def self.authorized?(object, context) - authorized_demarche?(object.procedure, context) + context.authorized_demarche?(object.procedure) end end end diff --git a/app/graphql/types/groupe_instructeur_type.rb b/app/graphql/types/groupe_instructeur_type.rb index 8914bc1b7..b89119f3c 100644 --- a/app/graphql/types/groupe_instructeur_type.rb +++ b/app/graphql/types/groupe_instructeur_type.rb @@ -12,7 +12,7 @@ module Types end def self.authorized?(object, context) - authorized_demarche?(object.procedure, context) + context.authorized_demarche?(object.procedure) end end end diff --git a/app/graphql/types/query_type.rb b/app/graphql/types/query_type.rb index 34ba29f5b..cdcd2c56d 100644 --- a/app/graphql/types/query_type.rb +++ b/app/graphql/types/query_type.rb @@ -19,7 +19,11 @@ module Types end def dossier(number:) - Dossier.state_not_brouillon.for_api_v2.find(number) + if context.internal_use? + Dossier.state_not_brouillon.with_discarded.for_api_v2.find(number) + else + Dossier.state_not_brouillon.for_api_v2.find(number) + end rescue => e raise GraphQL::ExecutionError.new(e.message, extensions: { code: :not_found }) end diff --git a/app/helpers/flipper_helper.rb b/app/helpers/flipper_helper.rb index 8f1cf221a..aa81299c5 100644 --- a/app/helpers/flipper_helper.rb +++ b/app/helpers/flipper_helper.rb @@ -2,8 +2,4 @@ module FlipperHelper def feature_enabled?(feature_name) Flipper.enabled?(feature_name, current_user) end - - def feature_enabled_for?(feature_name, item) - Flipper.enabled?(feature_name, item) - end end diff --git a/app/javascript/components/shared/queryClient.js b/app/javascript/components/shared/queryClient.js index 82baed60a..8d64f0689 100644 --- a/app/javascript/components/shared/queryClient.js +++ b/app/javascript/components/shared/queryClient.js @@ -14,7 +14,7 @@ export const queryClient = new QueryClient({ }); function buildURL(scope, term) { - term = encodeURIComponent(term); + term = encodeURIComponent(term.replace(/\(|\)/g, '')); if (scope === 'adresse') { return `${api_adresse_url}/search?q=${term}&limit=5`; } else if (scope === 'annuaire-education') { @@ -42,7 +42,12 @@ async function defaultQueryFn({ queryKey: [scope, term] }) { const url = buildURL(scope, term); const [options, controller] = buildOptions(); - const promise = fetch(url, options).then((response) => response.json()); + const promise = fetch(url, options).then((response) => { + if (response.ok) { + return response.json(); + } + throw new Error(`Error fetching from "${scope}" API`); + }); promise.cancel = () => controller && controller.abort(); return promise; } diff --git a/app/models/application_record.rb b/app/models/application_record.rb index cece5f813..427e65704 100644 --- a/app/models/application_record.rb +++ b/app/models/application_record.rb @@ -13,6 +13,10 @@ class ApplicationRecord < ActiveRecord::Base raise ActiveRecord::RecordNotFound, e.message end + def self.id_from_typed_id(id) + GraphQL::Schema::UniqueWithinType.decode(id)[1] + end + def to_typed_id GraphQL::Schema::UniqueWithinType.encode(self.class.name, id) end diff --git a/app/models/dossier.rb b/app/models/dossier.rb index d4dd49e37..4796b18f0 100644 --- a/app/models/dossier.rb +++ b/app/models/dossier.rb @@ -418,6 +418,14 @@ class Dossier < ApplicationRecord Dossier.en_construction_close_to_expiration.where(id: self).present? end + def show_groupe_instructeur_details? + procedure.routee? && (!procedure.feature_enabled?(:procedure_routage_api) || !defaut_groupe_instructeur?) + end + + def show_groupe_instructeur_selector? + procedure.routee? && !procedure.feature_enabled?(:procedure_routage_api) + end + def assign_to_groupe_instructeur(groupe_instructeur, author = nil) if groupe_instructeur.procedure == procedure && groupe_instructeur != self.groupe_instructeur if update(groupe_instructeur: groupe_instructeur, groupe_instructeur_updated_at: Time.zone.now) @@ -832,6 +840,10 @@ class Dossier < ApplicationRecord private + def defaut_groupe_instructeur? + groupe_instructeur == procedure.defaut_groupe_instructeur + end + def geo_areas champs.includes(:geo_areas).flat_map(&:geo_areas) + champs_private.includes(:geo_areas).flat_map(&:geo_areas) end diff --git a/app/models/dossier_operation_log.rb b/app/models/dossier_operation_log.rb index 846dbadda..2144ebc57 100644 --- a/app/models/dossier_operation_log.rb +++ b/app/models/dossier_operation_log.rb @@ -80,7 +80,25 @@ class DossierOperationLog < ApplicationRecord if author.nil? nil else - OperationAuthorSerializer.new(author).as_json + { + id: serialize_author_id(author), + email: author.email + }.as_json + end + end + + def self.serialize_author_id(object) + case object + when User + "Usager##{object.id}" + when Instructeur + "Instructeur##{object.id}" + when Administrateur + "Administrateur##{object.id}" + when SuperAdmin + "Manager##{object.id}" + else + nil end end @@ -96,11 +114,11 @@ class DossierOperationLog < ApplicationRecord else case subject when Dossier - DossierSerializer.new(subject).as_json + SerializerService.dossier(subject) when Champ - ChampSerializer.new(subject).as_json + SerializerService.champ(subject) when Avis - AvisSerializer.new(subject).as_json + SerializerService.avis(subject) end end end diff --git a/app/models/groupe_instructeur.rb b/app/models/groupe_instructeur.rb index e9a3e735b..0bf07f7c2 100644 --- a/app/models/groupe_instructeur.rb +++ b/app/models/groupe_instructeur.rb @@ -9,7 +9,7 @@ # procedure_id :bigint not null # class GroupeInstructeur < ApplicationRecord - DEFAULT_LABEL = 'défaut' + DEFAUT_LABEL = 'défaut' belongs_to :procedure, -> { with_discarded }, inverse_of: :groupe_instructeurs, optional: false has_many :assign_tos, dependent: :destroy has_many :instructeurs, through: :assign_tos diff --git a/app/models/procedure.rb b/app/models/procedure.rb index 0689d8f95..c121e4590 100644 --- a/app/models/procedure.rb +++ b/app/models/procedure.rb @@ -135,7 +135,7 @@ class Procedure < ApplicationRecord has_one :refused_mail, class_name: "Mails::RefusedMail", dependent: :destroy has_one :without_continuation_mail, class_name: "Mails::WithoutContinuationMail", dependent: :destroy - has_one :defaut_groupe_instructeur, -> { order(:id) }, class_name: 'GroupeInstructeur', inverse_of: :procedure + has_one :defaut_groupe_instructeur, -> { order(:label) }, class_name: 'GroupeInstructeur', inverse_of: :procedure has_one_attached :logo has_one_attached :notice @@ -221,7 +221,7 @@ class Procedure < ApplicationRecord before_save :update_juridique_required after_initialize :ensure_path_exists before_save :ensure_path_exists - after_create :ensure_default_groupe_instructeur + after_create :ensure_defaut_groupe_instructeur include AASM @@ -342,6 +342,10 @@ class Procedure < ApplicationRecord end end + def feature_enabled?(feature) + Flipper.enabled?(feature, self) + end + # Warning: dossier after_save build_default_champs must be removed # to save a dossier created from this method def new_dossier @@ -541,9 +545,12 @@ class Procedure < ApplicationRecord end def populate_champ_stable_ids - TypeDeChamp.where(procedure: self, stable_id: nil).find_each do |type_de_champ| - type_de_champ.update_column(:stable_id, type_de_champ.id) - end + TypeDeChamp + .joins(:revisions) + .where(procedure_revisions: { procedure_id: id }, stable_id: nil) + .find_each do |type_de_champ| + type_de_champ.update_column(:stable_id, type_de_champ.id) + end end def missing_steps @@ -715,9 +722,9 @@ class Procedure < ApplicationRecord end end - def ensure_default_groupe_instructeur + def ensure_defaut_groupe_instructeur if self.groupe_instructeurs.empty? - groupe_instructeurs.create(label: GroupeInstructeur::DEFAULT_LABEL) + groupe_instructeurs.create(label: GroupeInstructeur::DEFAUT_LABEL) end end end diff --git a/app/serializers/operation_author_serializer.rb b/app/serializers/operation_author_serializer.rb deleted file mode 100644 index 00ee2b903..000000000 --- a/app/serializers/operation_author_serializer.rb +++ /dev/null @@ -1,18 +0,0 @@ -class OperationAuthorSerializer < ActiveModel::Serializer - attributes :id, :email - - def id - case object - when User - "Usager##{object.id}" - when Instructeur - "Instructeur##{object.id}" - when Administrateur - "Administrateur##{object.id}" - when SuperAdmin - "Manager##{object.id}" - else - nil - end - end -end diff --git a/app/services/serializer_service.rb b/app/services/serializer_service.rb new file mode 100644 index 000000000..4f3b8dabe --- /dev/null +++ b/app/services/serializer_service.rb @@ -0,0 +1,233 @@ +class SerializerService + def self.dossier(dossier) + data = execute_query('serializeDossier', { number: dossier.id }) + data && data['dossier'] + end + + def self.avis(avis) + data = execute_query('serializeAvis', { number: avis.dossier_id, id: avis.to_typed_id }) + data && data['dossier']['avis'].first + end + + def self.champ(champ) + if champ.private? + data = execute_query('serializeAnnotation', { number: champ.dossier_id, id: champ.to_typed_id }) + data && data['dossier']['annotations'].first + else + data = execute_query('serializeChamp', { number: champ.dossier_id, id: champ.to_typed_id }) + data && data['dossier']['champs'].first + end + end + + def self.execute_query(operation_name, variables) + result = API::V2::Schema.execute(QUERY, + variables: variables, + context: { internal_use: true }, + operation_name: operation_name) + if result['errors'].present? + raise result['errors'].first['message'] + end + result['data'] + end + + QUERY = <<-'GRAPHQL' + query serializeDossier($number: Int!) { + dossier(number: $number) { + ...DossierFragment + } + } + + query serializeAvis($number: Int!, $id: ID!) { + dossier(number: $number) { + avis(id: $id) { + ...AvisFragment + } + } + } + + query serializeChamp($number: Int!, $id: ID!) { + dossier(number: $number) { + champs(id: $id) { + ...RootChampFragment + } + } + } + + query serializeAnnotation($number: Int!, $id: ID!) { + dossier(number: $number) { + annotations(id: $id) { + ...RootChampFragment + } + } + } + + fragment DossierFragment on Dossier { + id + number + archived + state + dateDerniereModification + datePassageEnConstruction + datePassageEnInstruction + dateTraitement + instructeurs { + email + } + groupeInstructeur { + label + } + champs { + ...RootChampFragment + } + annotations { + ...RootChampFragment + } + avis { + ...AvisFragment + } + demandeur { + ... on PersonnePhysique { + civilite + nom + prenom + dateDeNaissance + } + ...PersonneMoraleFragment + } + motivation + motivationAttachment { + byteSize + checksum + filename + contentType + } + } + + fragment AvisFragment on Avis { + id + question + reponse + dateQuestion + dateReponse + instructeur { + email + } + expert { + email + } + attachment { + byteSize + checksum + filename + contentType + } + } + + fragment ChampFragment on Champ { + id + label + stringValue + ... on SiretChamp { + etablissement { + ...PersonneMoraleFragment + } + } + ... on LinkedDropDownListChamp { + primaryValue + secondaryValue + } + ... on MultipleDropDownListChamp { + values + } + ... on PieceJustificativeChamp { + file { + byteSize + checksum + filename + contentType + } + } + ... on AddressChamp { + address { + ...AddressFragment + } + } + } + + fragment RootChampFragment on Champ { + ...ChampFragment + ... on RepetitionChamp { + champs { + ...ChampFragment + } + } + ... on CarteChamp { + geoAreas { + source + geometry { + type + coordinates + } + ... on ParcelleCadastrale { + codeArr + codeCom + codeDep + feuille + nomCom + numero + section + surfaceParcelle + surfaceIntersection + } + } + } + } + + fragment PersonneMoraleFragment on PersonneMorale { + siret + siegeSocial + naf + libelleNaf + address { + ...AddressFragment + } + entreprise { + siren + capitalSocial + numeroTvaIntracommunautaire + formeJuridique + formeJuridiqueCode + nomCommercial + raisonSociale + siretSiegeSocial + codeEffectifEntreprise + dateCreation + nom + prenom + } + association { + rna + titre + objet + dateCreation + dateDeclaration + datePublication + } + } + + fragment AddressFragment on Address { + label + type + streetAddress + streetNumber + streetName + postalCode + cityName + cityCode + departmentName + departmentCode + regionName + regionCode + } + GRAPHQL +end diff --git a/app/views/commencer/show.html.haml b/app/views/commencer/show.html.haml index c08c940ea..81c357f55 100644 --- a/app/views/commencer/show.html.haml +++ b/app/views/commencer/show.html.haml @@ -41,7 +41,7 @@ = link_to 'Voir mes dossiers en cours', dossiers_path, class: ['button large expand primary'] = link_to 'Commencer un nouveau dossier', url_for_new_dossier(@procedure), class: ['button large expand'] - - if feature_enabled_for?(:dossier_pdf_vide, @procedure) + - if @procedure.feature_enabled?(:dossier_pdf_vide) - pdf_link = commencer_dossier_vide_path(path: @procedure.path) - if @procedure.brouillon? - pdf_link = commencer_dossier_vide_test_path(path: @procedure.path) diff --git a/app/views/instructeurs/avis/instruction.html.haml b/app/views/instructeurs/avis/instruction.html.haml index 80c0c359c..46a9df510 100644 --- a/app/views/instructeurs/avis/instruction.html.haml +++ b/app/views/instructeurs/avis/instruction.html.haml @@ -31,7 +31,7 @@ .send-wrapper = f.submit 'Envoyer votre avis', class: 'button send' - - if !@dossier.termine? && !feature_enabled_for?(:expert_not_allowed_to_invite, @avis.procedure) + - if !@dossier.termine? && !@avis.procedure.feature_enabled?(:expert_not_allowed_to_invite) = render partial: "instructeurs/shared/avis/form", locals: { url: avis_instructeur_avis_path(@avis.procedure, @avis), linked_dossiers: @dossier.linked_dossiers_for(current_instructeur), must_be_confidentiel: @avis.confidentiel?, avis: @new_avis } - if @dossier.avis_for(current_instructeur).present? diff --git a/app/views/layouts/_header.haml b/app/views/layouts/_header.haml index 87e1b7aa0..b823b60bc 100644 --- a/app/views/layouts/_header.haml +++ b/app/views/layouts/_header.haml @@ -5,10 +5,10 @@ -# only display the coronavirus to usagers (instructeurs know there are delays) when they are logged in, or on the public pages. - if user_signed_in? - - if dossier.present? && feature_enabled_for?(:coronavirus_banner, dossier.procedure) + - if dossier.present? && dossier.procedure.feature_enabled?(:coronavirus_banner) = render partial: 'layouts/coronavirus_banner' - else - - if procedure.present? && feature_enabled_for?(:coronavirus_banner, procedure) + - if procedure.present? && procedure.feature_enabled?(:coronavirus_banner) = render partial: 'layouts/coronavirus_banner' %header.new-header{ class: current_page?(root_path) ? nil : "new-header-with-border", role: 'banner' } diff --git a/app/views/new_administrateur/procedures/invited_expert_list.html.haml b/app/views/new_administrateur/procedures/invited_expert_list.html.haml index d783e8caf..2d894e5b3 100644 --- a/app/views/new_administrateur/procedures/invited_expert_list.html.haml +++ b/app/views/new_administrateur/procedures/invited_expert_list.html.haml @@ -10,7 +10,7 @@ %thead %tr %th Liste des experts - - if feature_enabled_for?(:make_experts_notifiable, @procedure) + - if @procedure.feature_enabled?(:make_experts_notifiable) %th Notifier des décisions sur les dossiers %tbody - @experts_procedure.each do |expert_procedure| @@ -18,7 +18,7 @@ %td %span.icon.person = expert_procedure.expert.email - - if feature_enabled_for?(:make_experts_notifiable, @procedure) + - if @procedure.feature_enabled?(:make_experts_notifiable) %td = form_for expert_procedure, url: admin_procedure_update_allow_decision_access_path(expert_procedure: expert_procedure), diff --git a/app/views/shared/dossiers/_champs.html.haml b/app/views/shared/dossiers/_champs.html.haml index 381d389e6..11e0bbd37 100644 --- a/app/views/shared/dossiers/_champs.html.haml +++ b/app/views/shared/dossiers/_champs.html.haml @@ -1,6 +1,6 @@ %table.table.vertical.dossier-champs %tbody - - if dossier.procedure.routee? + - if dossier.show_groupe_instructeur_details? %th= dossier.procedure.routing_criteria_name %td{ class: highlight_if_unseen_class(demande_seen_at, dossier.groupe_instructeur_updated_at) }= dossier.groupe_instructeur.label %td.updated-at diff --git a/app/views/shared/dossiers/_edit.html.haml b/app/views/shared/dossiers/_edit.html.haml index 67e21b38a..290d91e6f 100644 --- a/app/views/shared/dossiers/_edit.html.haml +++ b/app/views/shared/dossiers/_edit.html.haml @@ -30,7 +30,7 @@ %hr - - if dossier.procedure.routee? + - if dossier.show_groupe_instructeur_selector? = f.label :groupe_instructeur_id do = dossier.procedure.routing_criteria_name %span.mandatory * diff --git a/config/initializers/flipper.rb b/config/initializers/flipper.rb index 962fe52a3..cb1d72894 100644 --- a/config/initializers/flipper.rb +++ b/config/initializers/flipper.rb @@ -25,14 +25,21 @@ end # A list of features to be deployed on first push features = [ + :administrateur_routage, :administrateur_web_hook, + :carte_ign, + :coronavirus_banner, + :dossier_pdf_vide, + :expert_not_allowed_to_invite, + :hide_instructeur_email, :insee_api_v3, :instructeur_bypass_email_login_token, + :localization, :maintenance_mode, + :make_experts_notifiable, :mini_profiler, - :xray, - :carte_ign, - :localization + :procedure_routage_api, + :xray ] def database_exists? diff --git a/spec/controllers/instructeurs/dossiers_controller_spec.rb b/spec/controllers/instructeurs/dossiers_controller_spec.rb index c477fb06c..21985265d 100644 --- a/spec/controllers/instructeurs/dossiers_controller_spec.rb +++ b/spec/controllers/instructeurs/dossiers_controller_spec.rb @@ -5,8 +5,8 @@ describe Instructeurs::DossiersController, type: :controller do let(:administrateur) { create(:administrateur) } let(:administration) { create(:administration) } let(:instructeurs) { [instructeur] } - let(:procedure) { create(:procedure, :published, instructeurs: instructeurs) } - let(:dossier) { create(:dossier, :en_construction, procedure: procedure) } + let(:procedure) { create(:procedure, :published, :for_individual, instructeurs: instructeurs) } + let(:dossier) { create(:dossier, :en_construction, :with_individual, procedure: procedure) } let(:fake_justificatif) { fixture_file_upload('spec/fixtures/files/piece_justificative_0.pdf', 'application/pdf') } before { sign_in(instructeur.user) } @@ -328,7 +328,7 @@ describe Instructeurs::DossiersController, type: :controller do context 'when the attestation template uses the motivation field' do let(:emailable) { false } let(:template) { create(:attestation_template) } - let(:procedure) { create(:procedure, :published, attestation_template: template, instructeurs: [instructeur]) } + let(:procedure) { create(:procedure, :published, :for_individual, attestation_template: template, instructeurs: [instructeur]) } subject do post :terminer, params: { @@ -552,6 +552,7 @@ describe Instructeurs::DossiersController, type: :controller do describe "#show" do context "when the dossier is exported as PDF" do let(:instructeur) { create(:instructeur) } + let(:procedure) { create(:procedure, :published, instructeurs: instructeurs) } let(:dossier) do create(:dossier, :accepte, diff --git a/spec/controllers/manager/dossiers_controller_spec.rb b/spec/controllers/manager/dossiers_controller_spec.rb index b418d943d..d70870371 100644 --- a/spec/controllers/manager/dossiers_controller_spec.rb +++ b/spec/controllers/manager/dossiers_controller_spec.rb @@ -20,7 +20,7 @@ describe Manager::DossiersController, type: :controller do end describe '#restore' do - let(:dossier) { create(:dossier, :en_construction) } + let(:dossier) { create(:dossier, :en_construction, :with_individual) } before do dossier.discard_and_keep_track!(super_admin, :manager_request) diff --git a/spec/controllers/manager/procedures_controller_spec.rb b/spec/controllers/manager/procedures_controller_spec.rb index cb9cd5b36..c4a48eb11 100644 --- a/spec/controllers/manager/procedures_controller_spec.rb +++ b/spec/controllers/manager/procedures_controller_spec.rb @@ -46,7 +46,7 @@ describe Manager::ProceduresController, type: :controller do end describe '#restore' do - let(:dossier) { create(:dossier, :en_construction) } + let(:dossier) { create(:dossier, :en_construction, :with_individual) } let(:procedure) { dossier.procedure } let(:deleted_dossier) { DeletedDossier.find_by(dossier_id: dossier.id) } let(:operations) { dossier.dossier_operation_logs.map(&:operation).map(&:to_sym) } diff --git a/spec/controllers/new_administrateur/groupe_instructeurs_controller_spec.rb b/spec/controllers/new_administrateur/groupe_instructeurs_controller_spec.rb index 697409c6b..2940a17ee 100644 --- a/spec/controllers/new_administrateur/groupe_instructeurs_controller_spec.rb +++ b/spec/controllers/new_administrateur/groupe_instructeurs_controller_spec.rb @@ -2,7 +2,7 @@ describe NewAdministrateur::GroupeInstructeursController, type: :controller do render_views let(:admin) { create(:administrateur) } - let(:procedure) { create(:procedure, :published, administrateurs: [admin]) } + let(:procedure) { create(:procedure, :published, :for_individual, administrateurs: [admin]) } let!(:gi_1_1) { procedure.defaut_groupe_instructeur } let(:procedure2) { create(:procedure, :published) } @@ -137,9 +137,9 @@ describe NewAdministrateur::GroupeInstructeursController, type: :controller do describe '#reaffecter' do let!(:gi_1_2) { procedure.groupe_instructeurs.create(label: 'groupe instructeur 2') } - let!(:dossier12) { create(:dossier, :en_construction, procedure: procedure, groupe_instructeur: gi_1_1) } + let!(:dossier12) { create(:dossier, :en_construction, :with_individual, procedure: procedure, groupe_instructeur: gi_1_1) } let!(:dossier_discarded) do - dossier = create(:dossier, :en_construction, procedure: procedure, groupe_instructeur: gi_1_1) + dossier = create(:dossier, :en_construction, :with_individual, procedure: procedure, groupe_instructeur: gi_1_1) dossier.discard! dossier end diff --git a/spec/factories/procedure.rb b/spec/factories/procedure.rb index aff410e8d..516c3e927 100644 --- a/spec/factories/procedure.rb +++ b/spec/factories/procedure.rb @@ -101,7 +101,7 @@ FactoryBot.define do trait :routee do after(:create) do |procedure, _evaluator| - procedure.groupe_instructeurs.create(label: '2nd groupe') + procedure.groupe_instructeurs.create(label: 'deuxième groupe') end end diff --git a/spec/features/instructeurs/instruction_spec.rb b/spec/features/instructeurs/instruction_spec.rb index 13f2a2669..6d111ba01 100644 --- a/spec/features/instructeurs/instruction_spec.rb +++ b/spec/features/instructeurs/instruction_spec.rb @@ -5,7 +5,7 @@ feature 'Instructing a dossier:' do let!(:instructeur) { create(:instructeur, password: password) } let!(:procedure) { create(:procedure, :published, instructeurs: [instructeur]) } - let!(:dossier) { create(:dossier, :en_construction, procedure: procedure) } + let!(:dossier) { create(:dossier, :en_construction, :with_entreprise, procedure: procedure) } context 'the instructeur is also a user' do scenario 'a instructeur can fill a dossier' do visit commencer_path(path: procedure.path) diff --git a/spec/jobs/cron/declarative_procedures_job_spec.rb b/spec/jobs/cron/declarative_procedures_job_spec.rb index 6b96939fe..f2ad8c486 100644 --- a/spec/jobs/cron/declarative_procedures_job_spec.rb +++ b/spec/jobs/cron/declarative_procedures_job_spec.rb @@ -4,10 +4,10 @@ RSpec.describe Cron::DeclarativeProceduresJob, type: :job do let(:instruction_date) { date + 120 } let(:state) { nil } - let(:procedure) { create(:procedure, :published, :with_instructeur, declarative_with_state: state) } - let(:nouveau_dossier1) { create(:dossier, :en_construction, procedure: procedure) } - let(:nouveau_dossier2) { create(:dossier, :en_construction, procedure: procedure) } - let(:dossier_recu) { create(:dossier, :en_instruction, procedure: procedure) } + let(:procedure) { create(:procedure, :published, :for_individual, :with_instructeur, declarative_with_state: state) } + let(:nouveau_dossier1) { create(:dossier, :en_construction, :with_individual, procedure: procedure) } + let(:nouveau_dossier2) { create(:dossier, :en_construction, :with_individual, procedure: procedure) } + let(:dossier_recu) { create(:dossier, :en_instruction, :with_individual, procedure: procedure) } let(:dossier_brouillon) { create(:dossier, procedure: procedure) } before do diff --git a/spec/jobs/cron/discarded_dossiers_deletion_job_spec.rb b/spec/jobs/cron/discarded_dossiers_deletion_job_spec.rb index 61078f168..c938414ff 100644 --- a/spec/jobs/cron/discarded_dossiers_deletion_job_spec.rb +++ b/spec/jobs/cron/discarded_dossiers_deletion_job_spec.rb @@ -1,12 +1,13 @@ RSpec.describe Cron::DiscardedDossiersDeletionJob, type: :job do describe '#perform' do let(:instructeur) { create(:instructeur) } - let(:dossier) { create(:dossier, state, hidden_at: hidden_at) } + let(:dossier) { create(:dossier, :with_individual, state) } before do # hack to add passer_en_instruction and supprimer to dossier.dossier_operation_logs dossier.send(:log_dossier_operation, instructeur, :passer_en_instruction, dossier) dossier.send(:log_dossier_operation, instructeur, :supprimer, dossier) + dossier.update_column(:hidden_at, hidden_at) Cron::DiscardedDossiersDeletionJob.perform_now end @@ -35,7 +36,7 @@ RSpec.describe Cron::DiscardedDossiersDeletionJob, type: :job do end end - [:brouillon, :en_construction, :en_instruction, :accepte, :refuse, :sans_suite].each do |state| + [:en_construction, :en_instruction, :accepte, :refuse, :sans_suite].each do |state| context "with a dossier #{state}" do let(:state) { state } diff --git a/spec/models/dossier_spec.rb b/spec/models/dossier_spec.rb index 6d958587c..fcdc624d9 100644 --- a/spec/models/dossier_spec.rb +++ b/spec/models/dossier_spec.rb @@ -354,8 +354,7 @@ describe Dossier do end describe '#update_state_dates' do - let(:state) { Dossier.states.fetch(:brouillon) } - let(:dossier) { create(:dossier, state: state) } + let(:dossier) { create(:dossier, :brouillon, :with_individual) } let(:beginning_of_day) { Time.zone.now.beginning_of_day } let(:instructeur) { create(:instructeur) } @@ -381,7 +380,7 @@ describe Dossier do end context 'when dossier is en_instruction' do - let(:state) { Dossier.states.fetch(:en_construction) } + let(:dossier) { create(:dossier, :en_construction, :with_individual) } let(:instructeur) { create(:instructeur) } before do @@ -402,7 +401,7 @@ describe Dossier do end context 'when dossier is accepte' do - let(:state) { Dossier.states.fetch(:en_instruction) } + let(:dossier) { create(:dossier, :en_instruction, :with_individual) } before do dossier.accepter!(instructeur, nil, nil) @@ -415,7 +414,7 @@ describe Dossier do end context 'when dossier is refuse' do - let(:state) { Dossier.states.fetch(:en_instruction) } + let(:dossier) { create(:dossier, :en_instruction, :with_individual) } before do dossier.refuser!(instructeur, nil, nil) @@ -427,7 +426,7 @@ describe Dossier do end context 'when dossier is sans_suite' do - let(:state) { Dossier.states.fetch(:en_instruction) } + let(:dossier) { create(:dossier, :en_instruction, :with_individual) } before do dossier.classer_sans_suite!(instructeur, nil, nil) @@ -469,11 +468,11 @@ describe Dossier do end describe "#unfollow_stale_instructeurs" do - let(:procedure) { create(:procedure, :published) } + let(:procedure) { create(:procedure, :published, :for_individual) } let(:instructeur) { create(:instructeur) } let(:new_groupe_instructeur) { create(:groupe_instructeur, procedure: procedure) } let(:instructeur2) { create(:instructeur, groupe_instructeurs: [procedure.defaut_groupe_instructeur, new_groupe_instructeur]) } - let(:dossier) { create(:dossier, :en_construction, procedure: procedure) } + let(:dossier) { create(:dossier, :en_construction, :with_individual, procedure: procedure) } let(:last_operation) { DossierOperationLog.last } before do @@ -918,7 +917,7 @@ describe Dossier do end describe '#accepter!' do - let(:dossier) { create(:dossier, :en_instruction) } + let(:dossier) { create(:dossier, :en_instruction, :with_individual) } let(:last_operation) { dossier.dossier_operation_logs.last } let(:operation_serialized) { JSON.parse(last_operation.serialized.download) } let!(:instructeur) { create(:instructeur) } @@ -953,7 +952,7 @@ describe Dossier do end describe '#accepter_automatiquement!' do - let(:dossier) { create(:dossier, :en_construction) } + let(:dossier) { create(:dossier, :en_construction, :with_individual) } let(:last_operation) { dossier.dossier_operation_logs.last } let!(:now) { Time.zone.parse('01/01/2100') } let(:attestation) { Attestation.new } @@ -1372,7 +1371,7 @@ describe Dossier do end describe "remove_titres_identite!" do - let(:dossier) { create(:dossier, :en_instruction, :followed) } + let(:dossier) { create(:dossier, :en_instruction, :followed, :with_individual) } let(:type_de_champ_titre_identite) { create(:type_de_champ_titre_identite, procedure: dossier.procedure) } let(:champ_titre_identite) { create(:champ_titre_identite, type_de_champ: type_de_champ_titre_identite) } let(:type_de_champ_titre_identite_vide) { create(:type_de_champ_titre_identite, procedure: dossier.procedure) } @@ -1412,7 +1411,7 @@ describe Dossier do end context 'en_construction' do - let(:dossier) { create(:dossier, :en_construction, :followed) } + let(:dossier) { create(:dossier, :en_construction, :followed, :with_individual) } it "clean up titres identite on accepter_automatiquement" do expect(champ_titre_identite.piece_justificative_file.attached?).to be_truthy diff --git a/spec/models/instructeur_spec.rb b/spec/models/instructeur_spec.rb index 37dbfa3dc..afc62ae84 100644 --- a/spec/models/instructeur_spec.rb +++ b/spec/models/instructeur_spec.rb @@ -529,7 +529,7 @@ describe Instructeur, type: :model do let(:instructeur_a) { create(:instructeur, groupe_instructeurs: [procedure_a.defaut_groupe_instructeur]) } before do - gi2 = procedure_a.groupe_instructeurs.create(label: '2') + gi2 = procedure_a.groupe_instructeurs.create(label: 'gi2') instructeur_a.groupe_instructeurs << gi2 end diff --git a/spec/models/procedure_presentation_spec.rb b/spec/models/procedure_presentation_spec.rb index 8039d4a44..6d600bfb1 100644 --- a/spec/models/procedure_presentation_spec.rb +++ b/spec/models/procedure_presentation_spec.rb @@ -730,8 +730,8 @@ describe ProcedurePresentation do context 'for groupe_instructeur table' do let(:filter) { [{ 'table' => 'groupe_instructeur', 'column' => 'label', 'value' => 'défaut' }] } - let!(:gi_2) { procedure.groupe_instructeurs.create(label: '2') } - let!(:gi_3) { procedure.groupe_instructeurs.create(label: '3') } + let!(:gi_2) { procedure.groupe_instructeurs.create(label: 'gi2') } + let!(:gi_3) { procedure.groupe_instructeurs.create(label: 'gi3') } let!(:kept_dossier) { create(:dossier, procedure: procedure) } let!(:discarded_dossier) { create(:dossier, procedure: procedure, groupe_instructeur: gi_2) } @@ -742,7 +742,7 @@ describe ProcedurePresentation do let(:filter) do [ { 'table' => 'groupe_instructeur', 'column' => 'label', 'value' => 'défaut' }, - { 'table' => 'groupe_instructeur', 'column' => 'label', 'value' => '3' } + { 'table' => 'groupe_instructeur', 'column' => 'label', 'value' => 'gi3' } ] end diff --git a/spec/models/procedure_spec.rb b/spec/models/procedure_spec.rb index 7db2c00f7..f767a1fce 100644 --- a/spec/models/procedure_spec.rb +++ b/spec/models/procedure_spec.rb @@ -466,7 +466,7 @@ describe Procedure do it 'should have a default groupe instructeur' do expect(subject.groupe_instructeurs.size).to eq(1) - expect(subject.groupe_instructeurs.first.label).to eq(GroupeInstructeur::DEFAULT_LABEL) + expect(subject.groupe_instructeurs.first.label).to eq(GroupeInstructeur::DEFAUT_LABEL) expect(subject.groupe_instructeurs.first.instructeurs.size).to eq(0) end end @@ -1011,7 +1011,7 @@ describe Procedure do let!(:procedure) { create(:procedure) } it { expect(procedure.groupe_instructeurs.count).to eq(1) } - it { expect(procedure.groupe_instructeurs.first.label).to eq(GroupeInstructeur::DEFAULT_LABEL) } + it { expect(procedure.groupe_instructeurs.first.label).to eq(GroupeInstructeur::DEFAUT_LABEL) } end describe '.missing_instructeurs?' do diff --git a/spec/services/expired_dossiers_deletion_service_spec.rb b/spec/services/expired_dossiers_deletion_service_spec.rb index 6417a0b74..c24a7fb7a 100644 --- a/spec/services/expired_dossiers_deletion_service_spec.rb +++ b/spec/services/expired_dossiers_deletion_service_spec.rb @@ -13,7 +13,7 @@ describe ExpiredDossiersDeletionService do let(:date_not_expired) { Date.today - procedure.duree_conservation_dossiers_dans_ds.months + 2.months } context 'send messages for dossiers expiring soon and delete expired' do - let!(:expired_brouillon) { create(:dossier, procedure: procedure, created_at: date_expired, brouillon_close_to_expiration_notice_sent_at: today - (warning_period + 1.day)) } + let!(:expired_brouillon) { create(:dossier, procedure: procedure, created_at: date_expired, brouillon_close_to_expiration_notice_sent_at: today - (warning_period + 3.days)) } let!(:brouillon_close_to_expiration) { create(:dossier, procedure: procedure, created_at: date_close_to_expiration) } let!(:brouillon_close_but_with_notice_sent) { create(:dossier, procedure: procedure, created_at: date_close_to_expiration, brouillon_close_to_expiration_notice_sent_at: Time.zone.now) } let!(:valid_brouillon) { create(:dossier, procedure: procedure, created_at: date_not_expired) }