Merge branch 'main' into poc-self_hosted_runners

This commit is contained in:
kleph 2023-08-30 18:41:01 +02:00 committed by GitHub
commit 4214c31f08
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
229 changed files with 1086 additions and 2782 deletions

View file

@ -59,6 +59,7 @@ gem 'lograge'
gem 'logstash-event' gem 'logstash-event'
gem 'mailjet', require: false gem 'mailjet', require: false
gem 'matrix' # needed by prawn and not default in ruby 3.1 gem 'matrix' # needed by prawn and not default in ruby 3.1
gem 'mini_magick'
gem 'net-imap', require: false # See https://github.com/mikel/mail/pull/1439 gem 'net-imap', require: false # See https://github.com/mikel/mail/pull/1439
gem 'net-pop', require: false # same gem 'net-pop', require: false # same
gem 'net-smtp', require: false # same gem 'net-smtp', require: false # same
@ -105,6 +106,7 @@ group :test do
gem 'rack_session_access' gem 'rack_session_access'
gem 'rails-controller-testing' gem 'rails-controller-testing'
gem 'rspec_junit_formatter' gem 'rspec_junit_formatter'
gem 'rspec-retry'
gem 'selenium-devtools' gem 'selenium-devtools'
gem 'selenium-webdriver' gem 'selenium-webdriver'
gem 'shoulda-matchers', require: false gem 'shoulda-matchers', require: false
@ -114,7 +116,6 @@ group :test do
end end
group :development do group :development do
gem 'annotate'
gem 'brakeman', require: false gem 'brakeman', require: false
gem 'haml-lint' gem 'haml-lint'
gem 'letter_opener_web' gem 'letter_opener_web'

View file

@ -101,9 +101,6 @@ GEM
aes_key_wrap (1.1.0) aes_key_wrap (1.1.0)
after_party (1.11.2) after_party (1.11.2)
anchored (1.1.0) anchored (1.1.0)
annotate (3.2.0)
activerecord (>= 3.2, < 8.0)
rake (>= 10.4, < 14.0)
ast (2.4.2) ast (2.4.2)
attr_required (1.0.1) attr_required (1.0.1)
axe-core-api (4.2.1) axe-core-api (4.2.1)
@ -490,7 +487,7 @@ GEM
pry-rails (0.3.9) pry-rails (0.3.9)
pry (>= 0.10.4) pry (>= 0.10.4)
public_suffix (5.0.1) public_suffix (5.0.1)
puma (6.1.1) puma (6.3.1)
nio4r (~> 2.0) nio4r (~> 2.0)
pundit (2.2.0) pundit (2.2.0)
activesupport (>= 3.0.0) activesupport (>= 3.0.0)
@ -604,6 +601,8 @@ GEM
rspec-expectations (~> 3.11) rspec-expectations (~> 3.11)
rspec-mocks (~> 3.11) rspec-mocks (~> 3.11)
rspec-support (~> 3.11) rspec-support (~> 3.11)
rspec-retry (0.6.2)
rspec-core (> 3.3)
rspec-support (3.12.0) rspec-support (3.12.0)
rspec_junit_formatter (0.4.1) rspec_junit_formatter (0.4.1)
rspec-core (>= 2, < 4, != 2.12.0) rspec-core (>= 2, < 4, != 2.12.0)
@ -813,7 +812,6 @@ DEPENDENCIES
administrate-field-enum administrate-field-enum
after_party after_party
anchored anchored
annotate
axe-core-rspec axe-core-rspec
bcrypt bcrypt
bootsnap (>= 1.4.4) bootsnap (>= 1.4.4)
@ -872,6 +870,7 @@ DEPENDENCIES
matrix matrix
memory_profiler memory_profiler
mina mina
mini_magick
net-imap net-imap
net-pop net-pop
net-smtp net-smtp
@ -898,6 +897,7 @@ DEPENDENCIES
rexml rexml
rqrcode rqrcode
rspec-rails rspec-rails
rspec-retry
rspec_junit_formatter rspec_junit_formatter
rubocop rubocop
rubocop-performance rubocop-performance

View file

@ -102,6 +102,10 @@
white-space: nowrap; white-space: nowrap;
} }
.width-max-content {
width: max-content;
}
// sizing // sizing
.width-100 { .width-100 {
width: 100%; width: 100%;

View file

@ -2,7 +2,7 @@
// overwrite DSFR style for SimpleFormatComponent, some user use markdown with // overwrite DSFR style for SimpleFormatComponent, some user use markdown with
// ordered list having paragraph between list item // ordered list having paragraph between list item
.fr-ol-content--override { ol.fr-ol-content--override {
list-style-type: decimal; list-style-type: decimal;
li::marker { li::marker {

View file

@ -1,34 +1,6 @@
@import "constants";
#invites-form { #invites-form {
padding: $default-padding; @media (min-width: 48em) {
text-align: left; min-width: 400px;
form {
display: flex;
margin-top: $default-padding;
}
h4 {
font-weight: bold;
margin-bottom: $default-spacer;
}
p {
margin-bottom: $default-spacer;
}
ul {
list-style-position: inside;
list-style-type: disc;
margin-bottom: $default-padding;
}
input[type=email] {
margin-bottom: $default-spacer;
}
.button {
margin-left: $default-spacer;
} }
} }

View file

@ -1,10 +1,13 @@
module Dsfr module Dsfr
module InputErrorable module InputErrorable
extend ActiveSupport::Concern extend ActiveSupport::Concern
included do included do
delegate :object, to: :@form delegate :object, to: :@form
delegate :errors, to: :object delegate :errors, to: :object
renders_one :hint
private private
# lookup for edge case from `form.rich_text_area` # lookup for edge case from `form.rich_text_area`
@ -89,6 +92,10 @@ module Dsfr
end end
def hint def hint
get_slot(:hint).presence || default_hint
end
def default_hint
I18n.t("activerecord.attributes.#{object.class.name.underscore}.hints.#{@attribute}") I18n.t("activerecord.attributes.#{object.class.name.underscore}.hints.#{@attribute}")
end end
@ -101,6 +108,8 @@ module Dsfr
end end
def hint? def hint?
return true if get_slot(:hint).present?
I18n.exists?("activerecord.attributes.#{object.class.name.underscore}.hints.#{@attribute}") I18n.exists?("activerecord.attributes.#{object.class.name.underscore}.hints.#{@attribute}")
end end
end end

View file

@ -0,0 +1,6 @@
class Instructeurs::ActivateAccountFormComponent < ApplicationComponent
attr_reader :user
def initialize(user:)
@user = user
end
end

View file

@ -0,0 +1,6 @@
---
en:
title: Create your account for %{application_name}
activate: Activate your account %{email}
email_disabled: Instructor email address not changeable
submit: Choose password

View file

@ -0,0 +1,7 @@
---
fr:
title: Création de compte sur %{application_name}
activate: Se créer un compte pour %{email} en choissant un mot de passe
email_disabled: Adresse instructeur non modifiable
submit: Définir le mot de passe

View file

@ -0,0 +1,23 @@
= form_for user, url: { controller: 'users/activate', action: :create }, html: { class: "fr-py-5w" } do |f|
%h1.fr-h2.fr-mb-7w= t('.title', application_name: APPLICATION_NAME)
.fr-background-alt--grey.fr-px-12w.fr-py-7w
%fieldset.fr-mb-0.fr-fieldset{ aria: { labelledby: 'activate-account-legend' } }
%legend.fr-fieldset__legend#activate-account-legend
%h2.fr-h6.fr-mb-0= t('.activate', email: user.email)
.fr-fieldset__element
%p.fr-text--sm= t('utils.mandatory_champs')
.fr-fieldset__element= render Dsfr::InputComponent.new(form: f, attribute: :email, input_type: :email_field, opts: { disabled: :disabled, class: 'fr-input-group--disabled', value: t('.email_disabled') })
.fr-fieldset__element= render Dsfr::InputComponent.new(form: f, attribute: :password, input_type: :password_field, opts: { autocomplete: 'current-password' })
= f.hidden_field :reset_password_token, value: params[:token]
.fr-fieldset__element
.fr-btns-group--right.fr-btns-group.fr-btns-group--inline.fr-btns-group.fr-btns-group--inline
%ul
%li= f.submit t('.submit'), class: 'fr-mt-2v fr-btn fr-btn'

View file

@ -4,7 +4,7 @@ fr:
Le routage permet dacheminer les dossiers vers différents groupes dinstructeurs. Le routage permet dacheminer les dossiers vers différents groupes dinstructeurs.
routing_configuration_notice_2_html: | routing_configuration_notice_2_html: |
<p>Pour le configurer, votre formulaire doit comporter <p>Pour le configurer, votre formulaire doit comporter
au moins un champ « choix simple ».</p> au moins un champ de type « choix simple » ou « départements ».</p>
<p>Ajoutez ce champ dans la page <a href="%{path}">« Configuration des champs »</a>.</p> <p>Ajoutez ce champ dans la page <a href="%{path}">« Configuration des champs »</a>.</p>
delete_title: Aucun dossier ne sera supprimé. Les groupes d'instructeurs vont être supprimés. Seuls les instructeurs du groupe « %{defaut_label} » resteront affectés à la démarche. delete_title: Aucun dossier ne sera supprimé. Les groupes d'instructeurs vont être supprimés. Seuls les instructeurs du groupe « %{defaut_label} » resteront affectés à la démarche.
delete_confirmation: | delete_confirmation: |

View file

@ -27,7 +27,7 @@
class: 'fr-btn', class: 'fr-btn',
method: :delete, method: :delete,
title: t('.delete_title', defaut_label: @procedure.defaut_groupe_instructeur.label), title: t('.delete_title', defaut_label: @procedure.defaut_groupe_instructeur.label),
data: ( @procedure.publiee? ? { disable_with: "Suppression...", confirm: t('.delete_confirmation', defaut_label: @procedure.defaut_groupe_instructeur.label) } : nil) data: ( @procedure.publiee? ? { disable_with: "Suppression", confirm: t('.delete_confirmation', defaut_label: @procedure.defaut_groupe_instructeur.label) } : nil)
- elsif @state == 'choix' - elsif @state == 'choix'
= form_for :choice, = form_for :choice,

View file

@ -58,8 +58,18 @@ class Procedure::OneGroupeManagementComponent < ApplicationComponent
def available_values_for_select(targeted_champ) def available_values_for_select(targeted_champ)
return [] if targeted_champ.is_a?(Logic::Empty) return [] if targeted_champ.is_a?(Logic::Empty)
case @revision.types_de_champ_public.find_by(stable_id: targeted_champ.stable_id).type_champ
when TypeDeChamp.type_champs.fetch(:departements)
departements_for_select
when TypeDeChamp.type_champs.fetch(:drop_down_list)
targeted_champ targeted_champ
.options(@revision.types_de_champ_public) .options(@revision.types_de_champ_public)
.map { |tdc| [tdc.first, constant(tdc.first).to_json] } .map { |(label, value)| [label, constant(value).to_json] }
end
end
def departements_for_select
APIGeoService.departements.map { ["#{_1[:code]} #{_1[:name]}", constant(_1[:code]).to_json] }
end end
end end

View file

@ -9,6 +9,7 @@ fr:
other: "%{count} dossiers en cours de traitement portent ce champ. Les <strong>données</strong> associées avec ce champ seront <strong>supprimées</strong>." other: "%{count} dossiers en cours de traitement portent ce champ. Les <strong>données</strong> associées avec ce champ seront <strong>supprimées</strong>."
add_option: "ajoutés : %{items}" add_option: "ajoutés : %{items}"
remove_option: "supprimés : %{items}" remove_option: "supprimés : %{items}"
invalid_routing_rules_alert: "Certains groupes d'instructeurs ont une règle de routage invalide. Veuillez mettre à jour la configuration des groupes d'instructeurs après avoir publié les modifications."
public: public:
add: Le champ « %{label} » a été ajouté. add: Le champ « %{label} » a été ajouté.
add_mandatory: Le champ obligatoire « %{label} » a été ajouté. add_mandatory: Le champ obligatoire « %{label} » a été ajouté.

View file

@ -74,7 +74,7 @@
- if !total_dossiers.zero? && !change.can_rebase? - if !total_dossiers.zero? && !change.can_rebase?
.fr-alert.fr-alert--warning.fr-mt-1v .fr-alert.fr-alert--warning.fr-mt-1v
%p= t('.breaking_change', count: total_dossiers) %p= t('.breaking_change', count: total_dossiers)
- if removed.present? && change.type_de_champ.used_by_routing_rules? - if (removed.present? || added.present? ) && change.type_de_champ.used_by_routing_rules?
.fr-alert.fr-alert--warning.fr-mt-1v .fr-alert.fr-alert--warning.fr-mt-1v
= t(".#{prefix}.update_drop_down_options_alert", label: change.label) = t(".#{prefix}.update_drop_down_options_alert", label: change.label)
- when :drop_down_other - when :drop_down_other
@ -148,3 +148,7 @@
- if @private_move_changes.present? - if @private_move_changes.present?
- list.with_item do - list.with_item do
= t(".private.move", count: @private_move_changes.size) = t(".private.move", count: @private_move_changes.size)
- if @previous_revision.procedure.routing_enabled? && @previous_revision.procedure.groupe_instructeurs.any?(&:invalid_rule?)
- list.with_item do
.fr-alert.fr-alert--warning.fr-mt-1v
= t(".invalid_routing_rules_alert")

View file

@ -1,11 +1,10 @@
= form_tag(search_admin_procedures_path, data: { turbo: true }, method: :post, class: 'form') do = form_tag(search_admin_procedures_path, data: { turbo: true }, method: :post) do
= label_tag :query, 'Rechercher une procédure' .fr-input-group
= label_tag :query, class: "fr-label" do
Rechercher une démarche
%span.fr-hint-text Saisissez au moins 3 lettres
.notice = text_field_tag :query, params[:query], required: true, placeholder: 'politique de la ville', minlength: "3", class: "fr-input"
%p Entrez au minimum 3 lettres
= text_field_tag :query, params[:query], required: true, placeholder: 'politique de la ville', minlength: "3"
= submit_tag 'Rechercher', class: 'fr-btn' = submit_tag 'Rechercher', class: 'fr-btn'

View file

@ -40,12 +40,34 @@ module Administrateurs
tdc = @procedure.active_revision.routable_types_de_champ.find { |tdc| tdc.stable_id == stable_id } tdc = @procedure.active_revision.routable_types_de_champ.find { |tdc| tdc.stable_id == stable_id }
tdc_options = tdc.options["drop_down_options"].reject(&:empty?) case tdc.type_champ
when TypeDeChamp.type_champs.fetch(:departements)
tdc_options = APIGeoService.departements.map { ["#{_1[:code]} #{_1[:name]}", _1[:code]] }
tdc_options.each do |code_and_name, code|
routing_rule = ds_eq(champ_value(stable_id), constant(code))
@procedure
.groupe_instructeurs
.find_or_create_by(label: code_and_name)
.update(instructeurs: [current_administrateur.instructeur], routing_rule:)
end
when TypeDeChamp.type_champs.fetch(:drop_down_list)
tdc_options = tdc.drop_down_options.reject(&:empty?)
tdc_options.each do |option_label| tdc_options.each do |option_label|
gi = @procedure.groupe_instructeurs.find_by({ label: option_label }) || @procedure.groupe_instructeurs routing_rule = ds_eq(champ_value(stable_id), constant(option_label))
.create({ label: option_label, instructeurs: [current_administrateur.instructeur] }) @procedure
gi.update(routing_rule: ds_eq(champ_value(stable_id), constant(gi.label))) .groupe_instructeurs
.find_or_create_by(label: option_label)
.update(instructeurs: [current_administrateur.instructeur], routing_rule:)
end
end
if tdc.drop_down_other?
routing_rule = ds_eq(champ_value(stable_id), constant(Champs::DropDownListChamp::OTHER))
@procedure
.groupe_instructeurs
.find_or_create_by(label: 'Autre')
.update(instructeurs: [current_administrateur.instructeur], routing_rule:)
end end
@procedure.toggle_routing @procedure.toggle_routing

View file

@ -4,7 +4,6 @@ class API::Public::V1::DossiersController < API::Public::V1::BaseController
def create def create
dossier = Dossier.new( dossier = Dossier.new(
revision: @procedure.active_revision, revision: @procedure.active_revision,
groupe_instructeur: @procedure.defaut_groupe_instructeur_for_new_dossier,
state: Dossier.states.fetch(:brouillon), state: Dossier.states.fetch(:brouillon),
prefilled: true prefilled: true
) )

View file

@ -39,7 +39,9 @@ class API::V2::BaseController < ApplicationController
def api_token def api_token
if @api_token.nil? if @api_token.nil?
@api_token = APIToken.find_and_verify(authorization_bearer_token) || false @api_token = APIToken
.find_and_verify(authorization_bearer_token)
&.tap { _1.touch(:last_v2_authenticated_at) } || false
end end
@api_token @api_token
end end

View file

@ -6,6 +6,7 @@ class APIController < ApplicationController
def find_administrateur_for_token(procedure) def find_administrateur_for_token(procedure)
api_token = APIToken.find_and_verify(authorization_bearer_token, procedure.administrateurs) api_token = APIToken.find_and_verify(authorization_bearer_token, procedure.administrateurs)
if api_token.present? && api_token.context.fetch(:procedure_ids).include?(procedure.id) if api_token.present? && api_token.context.fetch(:procedure_ids).include?(procedure.id)
api_token.touch(:last_v1_authenticated_at)
api_token.administrateur api_token.administrateur
end end
end end

View file

@ -1,8 +1,12 @@
class Champs::OptionsController < ApplicationController class Champs::OptionsController < ApplicationController
include TurboChampsConcern
before_action :authenticate_logged_user! before_action :authenticate_logged_user!
def remove def remove
@champ = policy_scope(Champ).includes(:champs).find(params[:champ_id]) champ = policy_scope(Champ).includes(:champs).find(params[:champ_id])
@champ.remove_option([params[:option]].compact) champ.remove_option([params[:option]].compact)
champs = champ.private? ? champ.dossier.champs_private_all : champ.dossier.champs_public_all
@to_show, @to_hide, @to_update = champs_to_turbo_update({ params[:champ_id] => true }, champs)
end end
end end

View file

@ -85,7 +85,8 @@ module Instructeurs
end end
def personnes_impliquees def personnes_impliquees
@following_instructeurs_emails = dossier.followers_instructeurs.map(&:email) # sort following_instructeurs (last follower on top) for the API of Agence de l'Eau Loire-Bretagne
@following_instructeurs_emails = dossier.followers_instructeurs.joins(:follows).merge(Follow.order(id: :desc)).map(&:email)
previous_followers = dossier.previous_followers_instructeurs - dossier.followers_instructeurs previous_followers = dossier.previous_followers_instructeurs - dossier.followers_instructeurs
@previous_following_instructeurs_emails = previous_followers.map(&:email) @previous_following_instructeurs_emails = previous_followers.map(&:email)
@avis_emails = dossier.experts.map(&:email) @avis_emails = dossier.experts.map(&:email)

View file

@ -224,19 +224,18 @@ module Instructeurs
def email_usagers def email_usagers
@procedure = procedure @procedure = procedure
@commentaire = Commentaire.new @bulk_messages = BulkMessage.includes(:groupe_instructeurs).where(groupe_instructeurs: { procedure: procedure })
@email_usagers_dossiers = email_usagers_dossiers @bulk_message = current_instructeur.bulk_messages.build
@dossiers_count = @email_usagers_dossiers.count @dossiers_without_groupe_count = procedure.dossiers.state_brouillon.for_groupe_instructeur(nil).count
@groupe_instructeurs = email_usagers_groupe_instructeurs_label
@bulk_messages = BulkMessage.includes(:groupe_instructeurs).where(groupe_instructeurs: { id: current_instructeur.groupe_instructeur_ids, procedure: procedure })
end end
def create_multiple_commentaire def create_multiple_commentaire
@procedure = procedure @procedure = procedure
errors = [] errors = []
bulk_message = current_instructeur.bulk_messages.build(bulk_message_params)
email_usagers_dossiers.each do |dossier| dossiers = procedure.dossiers.state_brouillon.for_groupe_instructeur(nil)
commentaire = CommentaireService.create(current_instructeur, dossier, commentaire_params) dossiers.each do |dossier|
commentaire = CommentaireService.create(current_instructeur, dossier, bulk_message_params.except(:targets))
if commentaire.errors.empty? if commentaire.errors.empty?
commentaire.dossier.update!(last_commentaire_updated_at: Time.zone.now) commentaire.dossier.update!(last_commentaire_updated_at: Time.zone.now)
else else
@ -244,8 +243,15 @@ module Instructeurs
end end
end end
valid_dossiers_count = email_usagers_dossiers.count - errors.count valid_dossiers_count = dossiers.count - errors.count
create_bulk_message_mail(valid_dossiers_count, Dossier.states.fetch(:brouillon)) bulk_message.assign_attributes(
dossier_count: valid_dossiers_count,
dossier_state: Dossier.states.fetch(:brouillon),
sent_at: Time.zone.now,
instructeur_id: current_instructeur.id,
groupe_instructeurs: GroupeInstructeur.for_dossiers(dossiers)
)
bulk_message.save!
if errors.empty? if errors.empty?
flash[:notice] = "Tous les messages ont été envoyés avec succès" flash[:notice] = "Tous les messages ont été envoyés avec succès"
@ -262,18 +268,6 @@ module Instructeurs
private private
def create_bulk_message_mail(dossier_count, dossier_state)
BulkMessage.create(
dossier_count: dossier_count,
dossier_state: dossier_state,
body: commentaire_params[:body],
sent_at: Time.zone.now,
instructeur_id: current_instructeur.id,
piece_jointe: commentaire_params[:piece_jointe],
groupe_instructeurs: email_usagers_groupe_instructeurs
)
end
def assign_to_params def assign_to_params
params.require(:assign_to) params.require(:assign_to)
.permit(:instant_expert_avis_email_notifications_enabled, :instant_email_dossier_notifications_enabled, :instant_email_message_notifications_enabled, :daily_email_notifications_enabled, :weekly_email_notifications_enabled) .permit(:instant_expert_avis_email_notifications_enabled, :instant_email_dossier_notifications_enabled, :instant_email_message_notifications_enabled, :daily_email_notifications_enabled, :weekly_email_notifications_enabled)
@ -355,20 +349,8 @@ module Instructeurs
@current_filters ||= procedure_presentation.filters.fetch(statut, []) @current_filters ||= procedure_presentation.filters.fetch(statut, [])
end end
def email_usagers_dossiers def bulk_message_params
procedure.dossiers.state_brouillon.where(groupe_instructeur: current_instructeur.groupe_instructeur_ids).includes(:groupe_instructeur) params.require(:bulk_message).permit(:body)
end
def email_usagers_groupe_instructeurs_label
email_usagers_dossiers.map(&:groupe_instructeur).uniq.map(&:label)
end
def email_usagers_groupe_instructeurs
email_usagers_dossiers.map(&:groupe_instructeur).uniq
end
def commentaire_params
params.require(:commentaire).permit(:body, :piece_jointe)
end end
end end
end end

View file

@ -1,62 +0,0 @@
module Manager
class DemandesController < Manager::ApplicationController
def index
@pending_demandes = pending_demandes
end
def create_administrateur
administrateur = current_super_admin.invite_admin(create_administrateur_params[:email])
if administrateur.errors.empty?
PipedriveAcceptsDealsJob.perform_later(
create_administrateur_params[:person_id],
current_super_admin.id,
create_administrateur_params[:stage_id]
)
flash.notice = "Administrateur créé"
redirect_to manager_demandes_path
else
flash.now.alert = administrateur.errors.full_messages.to_sentence
@pending_demandes = pending_demandes
render :index
end
end
def refuse_administrateur
PipedriveRefusesDealsJob.perform_later(
refuse_administrateur_params[:person_id],
current_super_admin.id
)
AdministrationMailer
.refuse_admin(refuse_administrateur_params[:email])
.deliver_later
flash.notice = "La demande de #{refuse_administrateur_params[:email]} va être refusée"
redirect_to manager_demandes_path
end
private
def create_administrateur_params
params.permit(:email, :person_id, :stage_id)
end
def refuse_administrateur_params
params.permit(:email, :person_id)
end
def pending_demandes
already_approved_emails = Administrateur.eager_load(:user)
.where(users: { email: demandes.map { |d| d[:email] } })
.map(&:email)
demandes.reject { |demande| already_approved_emails.include?(demande[:email]) }
end
def demandes
@demandes ||= PipedriveService.get_demandes
end
end
end

View file

@ -0,0 +1,17 @@
class ProceduresController < ApplicationController
before_action :retrieve_procedure
def logo
if @procedure.logo.attached?
redirect_to url_for(@procedure.logo.variant(:email))
else
redirect_to ActionController::Base.helpers.image_url(PROCEDURE_DEFAULT_LOGO_SRC)
end
end
private
def retrieve_procedure
@procedure = Procedure.find(params[:id])
end
end

View file

@ -20,6 +20,8 @@ module Users
check_prefilled_dossier_ownership if @prefilled_dossier check_prefilled_dossier_ownership if @prefilled_dossier
end end
@usual_traitement_time = @procedure.stats_usual_traitement_time
render 'commencer/show' render 'commencer/show'
end end
@ -94,7 +96,6 @@ module Users
def build_prefilled_dossier def build_prefilled_dossier
@prefilled_dossier = Dossier.new( @prefilled_dossier = Dossier.new(
revision: @revision, revision: @revision,
groupe_instructeur: @procedure.defaut_groupe_instructeur_for_new_dossier,
state: Dossier.states.fetch(:brouillon), state: Dossier.states.fetch(:brouillon),
prefilled: true prefilled: true
) )

View file

@ -375,7 +375,6 @@ module Users
dossier = Dossier.new( dossier = Dossier.new(
revision: params[:brouillon] ? procedure.draft_revision : procedure.active_revision, revision: params[:brouillon] ? procedure.draft_revision : procedure.active_revision,
groupe_instructeur: procedure.defaut_groupe_instructeur_for_new_dossier,
user: current_user, user: current_user,
state: Dossier.states.fetch(:brouillon) state: Dossier.states.fetch(:brouillon)
) )
@ -542,6 +541,7 @@ module Users
def update_dossier_and_compute_errors def update_dossier_and_compute_errors
errors = [] errors = []
@dossier.assign_attributes(champs_public_params) @dossier.assign_attributes(champs_public_params)
if @dossier.champs_public_all.any?(&:changed_for_autosave?) if @dossier.champs_public_all.any?(&:changed_for_autosave?)
@dossier.last_champ_updated_at = Time.zone.now @dossier.last_champ_updated_at = Time.zone.now
@ -559,7 +559,6 @@ module Users
@dossier.valid?(**submit_validation_options) @dossier.valid?(**submit_validation_options)
errors += format_errors(errors: @dossier.errors) errors += format_errors(errors: @dossier.errors)
errors += format_errors(errors: @dossier.check_mandatory_and_visible_champs) errors += format_errors(errors: @dossier.check_mandatory_and_visible_champs)
errors errors
end end

View file

@ -6,7 +6,7 @@ class Users::SessionsController < Devise::SessionsController
layout 'login', only: [:new, :create] layout 'login', only: [:new, :create]
before_action :restore_procedure_context, only: [:new, :create] before_action :restore_procedure_context, only: [:new, :create]
skip_before_action :redirect_if_untrusted, only: [:reset_link_sent]
# POST /resource/sign_in # POST /resource/sign_in
def create def create
user = User.find_by(email: params[:user][:email]) user = User.find_by(email: params[:user][:email])
@ -18,6 +18,13 @@ class Users::SessionsController < Devise::SessionsController
super super
end end
def reset_link_sent
if send_login_token_or_bufferize(current_instructeur)
flash[:notice] = "Nous venons de vous renvoyer un nouveau lien de connexion sécurisée à #{APPLICATION_NAME}"
end
redirect_to link_sent_path(email: current_instructeur.email)
end
def link_sent def link_sent
if Devise.email_regexp.match?(params[:email]) if Devise.email_regexp.match?(params[:email])
@email = params[:email] @email = params[:email]

View file

@ -1,4 +0,0 @@
require "administrate/base_dashboard"
class DemandeDashboard < Administrate::BaseDashboard
end

View file

@ -13,6 +13,8 @@ class UserDashboard < Administrate::BaseDashboard
confirmed?: Field::Boolean, confirmed?: Field::Boolean,
created_at: Field::DateTime, created_at: Field::DateTime,
updated_at: Field::DateTime, updated_at: Field::DateTime,
blocked_at: Field::DateTime,
blocked_reason: Field::String,
current_sign_in_at: Field::DateTime, current_sign_in_at: Field::DateTime,
dossiers: Field::HasMany dossiers: Field::HasMany
}.freeze }.freeze
@ -36,7 +38,9 @@ class UserDashboard < Administrate::BaseDashboard
:email, :email,
:confirmed?, :confirmed?,
:current_sign_in_at, :current_sign_in_at,
:created_at :created_at,
:blocked_at,
:blocked_reason
].freeze ].freeze
# FORM_ATTRIBUTES # FORM_ATTRIBUTES

View file

@ -59,6 +59,7 @@ class API::V2::Context < GraphQL::Query::Context
{ {
graphql_query: query.query_string, graphql_query: query.query_string,
graphql_variables: query.provided_variables&.to_json, graphql_variables: query.provided_variables&.to_json,
graphql_mutation: mutation?,
graphql_null_error: errors.any? { _1.is_a? GraphQL::InvalidNullError }.presence, graphql_null_error: errors.any? { _1.is_a? GraphQL::InvalidNullError }.presence,
graphql_timeout_error: errors.any? { _1.is_a? GraphQL::Schema::Timeout::TimeoutError }.presence graphql_timeout_error: errors.any? { _1.is_a? GraphQL::Schema::Timeout::TimeoutError }.presence
}.compact }.compact
@ -66,6 +67,12 @@ class API::V2::Context < GraphQL::Query::Context
private private
def mutation?
query.lookahead.selections.any? { _1.field.type.respond_to?(:mutation) }.presence
rescue
false
end
def compute_demarche_authorization(demarche) def compute_demarche_authorization(demarche)
# procedure_ids and token are passed from graphql controller # procedure_ids and token are passed from graphql controller
if self[:procedure_ids].present? if self[:procedure_ids].present?
@ -73,6 +80,7 @@ class API::V2::Context < GraphQL::Query::Context
elsif self[:token].present? elsif self[:token].present?
token = APIToken.find_and_verify(self[:token], demarche.administrateurs) token = APIToken.find_and_verify(self[:token], demarche.administrateurs)
if token.present? if token.present?
token.touch(:last_v2_authenticated_at)
Current.user = token.administrateur.user Current.user = token.administrateur.user
true true
else else

View file

@ -138,7 +138,7 @@ class API::V2::Schema < GraphQL::Schema
end end
end end
use Timeout, max_seconds: 10 use Timeout, max_seconds: 30
use GraphQL::Batch use GraphQL::Batch
use GraphQL::Backtrace use GraphQL::Backtrace

View file

@ -43,6 +43,7 @@ class API::V2::StoredQuery
$includeInstructeurs: Boolean = true $includeInstructeurs: Boolean = true
$includeAvis: Boolean = false $includeAvis: Boolean = false
$includeMessages: Boolean = false $includeMessages: Boolean = false
$includeCorrections: Boolean = false
$includeGeometry: Boolean = false $includeGeometry: Boolean = false
) { ) {
demarche(number: $demarcheNumber) { demarche(number: $demarcheNumber) {
@ -135,6 +136,7 @@ class API::V2::StoredQuery
$includeInstructeurs: Boolean = true $includeInstructeurs: Boolean = true
$includeAvis: Boolean = false $includeAvis: Boolean = false
$includeMessages: Boolean = false $includeMessages: Boolean = false
$includeCorrections: Boolean = false
$includeGeometry: Boolean = false $includeGeometry: Boolean = false
) { ) {
groupeInstructeur(number: $groupeInstructeurNumber) { groupeInstructeur(number: $groupeInstructeurNumber) {
@ -201,6 +203,7 @@ class API::V2::StoredQuery
$includeInstructeurs: Boolean = true $includeInstructeurs: Boolean = true
$includeAvis: Boolean = false $includeAvis: Boolean = false
$includeMessages: Boolean = false $includeMessages: Boolean = false
$includeCorrections: Boolean = false
$includeGeometry: Boolean = false $includeGeometry: Boolean = false
) { ) {
dossier(number: $dossierNumber) { dossier(number: $dossierNumber) {
@ -239,6 +242,7 @@ class API::V2::StoredQuery
} }
fragment DossierFragment on Dossier { fragment DossierFragment on Dossier {
__typename
id id
number number
archived archived
@ -250,6 +254,7 @@ class API::V2::StoredQuery
dateTraitement dateTraitement
dateExpiration dateExpiration
dateSuppressionParUsager dateSuppressionParUsager
dateDerniereCorrectionEnAttente @include(if: $includeCorrections)
motivation motivation
motivationAttachment { motivationAttachment {
...FileFragment ...FileFragment
@ -318,8 +323,8 @@ class API::V2::StoredQuery
dateFermeture dateFermeture
notice { url } notice { url }
deliberation { url } deliberation { url }
demarcheUrl demarcheURL
cadreJuridiqueUrl cadreJuridiqueURL
service @include(if: $includeService) { service @include(if: $includeService) {
...ServiceFragment ...ServiceFragment
} }
@ -409,6 +414,10 @@ class API::V2::StoredQuery
attachments { attachments {
...FileFragment ...FileFragment
} }
correction @include(if: $includeCorrections) {
reason
dateResolution
}
} }
fragment GeoAreaFragment on GeoArea { fragment GeoAreaFragment on GeoArea {
@ -455,6 +464,7 @@ class API::V2::StoredQuery
__typename __typename
label label
stringValue stringValue
updatedAt
... on DateChamp { ... on DateChamp {
date date
} }
@ -584,11 +594,13 @@ class API::V2::StoredQuery
fragment FileFragment on File { fragment FileFragment on File {
__typename
filename filename
contentType contentType
checksum checksum
byteSize: byteSizeBigInt byteSize: byteSizeBigInt
url url
createdAt
} }
fragment AddressFragment on Address { fragment AddressFragment on Address {
@ -635,6 +647,7 @@ class API::V2::StoredQuery
fragment PageInfoFragment on PageInfo { fragment PageInfoFragment on PageInfo {
hasPreviousPage hasPreviousPage
hasNextPage hasNextPage
startCursor
endCursor endCursor
} }
GRAPHQL GRAPHQL

View file

@ -35,7 +35,7 @@ module Extensions
# is a lazy value (e.g., a Promise like in our case) # is a lazy value (e.g., a Promise like in our case)
def after_resolve(value:, **_rest) def after_resolve(value:, **_rest)
if value.respond_to?(:map) if value.respond_to?(:map)
attachments = value.map { after_resolve_attachment(_1) } attachments = value.map { after_resolve_attachment(_1) }.compact
if options[:as] == :single if options[:as] == :single
attachments.first attachments.first

View file

@ -1066,7 +1066,8 @@ type DemarcheDescriptor {
""" """
URL du cadre juridique qui justifie le droit de collecter les données demandées dans la démarche URL du cadre juridique qui justifie le droit de collecter les données demandées dans la démarche
""" """
cadreJuridiqueUrl: String cadreJuridiqueURL: String
cadreJuridiqueUrl: String @deprecated(reason: "Utilisez le champ `cadreJuridiqueURL` à la place.")
""" """
Date de la création. Date de la création.
@ -1106,7 +1107,8 @@ type DemarcheDescriptor {
""" """
URL pour commencer la démarche URL pour commencer la démarche
""" """
demarcheUrl: URL demarcheURL: URL
demarcheUrl: URL @deprecated(reason: "Utilisez le champ `demarcheURL` à la place.")
""" """
Description de la démarche. Description de la démarche.
@ -1116,7 +1118,8 @@ type DemarcheDescriptor {
""" """
URL ou email pour contacter le Délégué à la Protection des Données (DPO) URL ou email pour contacter le Délégué à la Protection des Données (DPO)
""" """
dpoUrl: String dpoURL: String
dpoUrl: String @deprecated(reason: "Utilisez le champ `dpoURL` à la place.")
""" """
Durée de conservation des dossiers en mois. Durée de conservation des dossiers en mois.
@ -1129,7 +1132,8 @@ type DemarcheDescriptor {
notice explicative de la démarche notice explicative de la démarche
""" """
notice: File notice: File
noticeUrl: URL noticeURL: URL
noticeUrl: URL @deprecated(reason: "Utilisez le champ `noticeURL` à la place.")
""" """
Numero de la démarche. Numero de la démarche.
@ -1142,7 +1146,8 @@ type DemarcheDescriptor {
""" """
URL les usagers trouvent le lien vers la démarche URL les usagers trouvent le lien vers la démarche
""" """
siteWebUrl: String siteWebURL: String
siteWebUrl: String @deprecated(reason: "Utilisez le champ `siteWebURL` à la place.")
""" """
État de la démarche. État de la démarche.

View file

@ -25,6 +25,12 @@ Cela évite laccès récursif aux dossiers."
field :duree_conservation_dossiers, Int, "Durée de conservation des dossiers en mois.", null: false field :duree_conservation_dossiers, Int, "Durée de conservation des dossiers en mois.", null: false
field :demarcheUrl, Types::URL, camelize: false, null: true, deprecation_reason: 'Utilisez le champ `demarcheURL` à la place.'
field :siteWebUrl, String, camelize: false, null: true, deprecation_reason: 'Utilisez le champ `siteWebURL` à la place.'
field :dpoUrl, String, camelize: false, null: true, deprecation_reason: 'Utilisez le champ `dpoURL` à la place.'
field :noticeUrl, Types::URL, camelize: false, null: true, deprecation_reason: 'Utilisez le champ `noticeURL` à la place.'
field :cadreJuridiqueUrl, String, camelize: false, null: true, deprecation_reason: 'Utilisez le champ `cadreJuridiqueURL` à la place.'
field :demarche_url, Types::URL, "URL pour commencer la démarche", null: true field :demarche_url, Types::URL, "URL pour commencer la démarche", null: true
field :site_web_url, String, "URL où les usagers trouvent le lien vers la démarche", null: true field :site_web_url, String, "URL où les usagers trouvent le lien vers la démarche", null: true
field :dpo_url, String, "URL ou email pour contacter le Délégué à la Protection des Données (DPO)", null: true field :dpo_url, String, "URL ou email pour contacter le Délégué à la Protection des Données (DPO)", null: true
@ -77,22 +83,27 @@ Cela évite laccès récursif aux dossiers."
def demarche_url def demarche_url
Rails.application.routes.url_helpers.commencer_url(path: procedure.path) Rails.application.routes.url_helpers.commencer_url(path: procedure.path)
end end
alias demarcheUrl demarche_url
def dpo_url def dpo_url
procedure.lien_dpo procedure.lien_dpo
end end
alias dpoUrl dpo_url
def notice_url def notice_url
procedure.lien_notice procedure.lien_notice
end end
alias noticeUrl notice_url
def cadre_juridique_url def cadre_juridique_url
procedure.cadre_juridique procedure.cadre_juridique
end end
alias cadreJuridiqueUrl cadre_juridique_url
def site_web_url def site_web_url
procedure.lien_site_web procedure.lien_site_web
end end
alias siteWebUrl site_web_url
def number def number
procedure.id procedure.id

View file

@ -1,21 +0,0 @@
module DemandeHelper
def nb_of_procedures_options
{
'je ne sais pas' => Pipedrive::DealAdapter::PIPEDRIVE_NB_OF_PROCEDURES_DO_NOT_KNOW_VALUE,
'1' => Pipedrive::DealAdapter::PIPEDRIVE_NB_OF_PROCEDURES_1_VALUE,
'1 à 10' => Pipedrive::DealAdapter::PIPEDRIVE_NB_OF_PROCEDURES_1_TO_10_VALUE,
'10 à 100 ' => Pipedrive::DealAdapter::PIPEDRIVE_NB_OF_PROCEDURES_10_TO_100_VALUE,
'plus de 100' => Pipedrive::DealAdapter::PIPEDRIVE_NB_OF_PROCEDURES_ABOVE_100_VALUE
}
end
def deadline_options
{
'le plus vite possible' => Pipedrive::DealAdapter::PIPEDRIVE_DEADLINE_ASAP_VALUE,
'dans les 3 prochains mois' => Pipedrive::DealAdapter::PIPEDRIVE_DEADLINE_NEXT_3_MONTHS_VALUE,
'dans les 6 prochains mois' => Pipedrive::DealAdapter::PIPEDRIVE_DEADLINE_NEXT_6_MONTHS_VALUE,
'dans les 12 prochains mois' => Pipedrive::DealAdapter::PIPEDRIVE_DEADLINE_NEXT_12_MONTHS_VALUE,
'pas de date' => Pipedrive::DealAdapter::PIPEDRIVE_DEADLINE_NO_DATE_VALUE
}
end
end

View file

@ -39,10 +39,10 @@ module ProcedureHelper
end end
def can_send_groupe_message?(procedure) def can_send_groupe_message?(procedure)
procedure.dossiers groupe_instructeur_on_procedure_ids = procedure.groupe_instructeurs.active.ids.sort
.state_brouillon groupe_instructeur_on_instructeur_ids = current_instructeur.groupe_instructeurs.active.where(procedure: procedure).ids.sort
.includes(:groupe_instructeur)
.exists?(groupe_instructeur: current_instructeur.groupe_instructeurs) groupe_instructeur_on_procedure_ids == groupe_instructeur_on_instructeur_ids
end end
def url_or_email_to_lien_dpo(procedure) def url_or_email_to_lien_dpo(procedure)

View file

@ -1,5 +0,0 @@
class PipedriveAcceptsDealsJob < ApplicationJob
def perform(person_id, administration_id, stage_id)
PipedriveService.accept_demande_from_person(person_id, administration_id, stage_id)
end
end

View file

@ -1,5 +0,0 @@
class PipedriveRefusesDealsJob < ApplicationJob
def perform(person_id, administration_id)
PipedriveService.refuse_demande_from_person(person_id, administration_id)
end
end

View file

@ -5,8 +5,4 @@ module BizDev
def self.full_name(administration_id) def self.full_name(administration_id)
NAME NAME
end end
def self.pipedrive_id(administration_id)
PIPEDRIVE_ID
end
end end

View file

@ -1,82 +0,0 @@
class Pipedrive::API
PIPEDRIVE_ALL_NOT_DELETED_DEALS = 'all_not_deleted'
PIPEDRIVE_DEALS_URL = [PIPEDRIVE_API_URL, 'deals'].join("/")
PIPEDRIVE_PEOPLE_URL = [PIPEDRIVE_API_URL, 'persons'].join("/")
PIPEDRIVE_ORGANIZATIONS_URL = [PIPEDRIVE_API_URL, 'organizations'].join("/")
def self.get_persons_owned_by_user(user_id)
url = PIPEDRIVE_PEOPLE_URL
params = { user_id: user_id }
self.get(url, params)
end
def self.get_deals_for_person(person_id)
url = [PIPEDRIVE_PEOPLE_URL, person_id, "deals"].join('/')
params = { status: PIPEDRIVE_ALL_NOT_DELETED_DEALS }
self.get(url, params)
end
def self.put_deal(deal_id, params)
url = [PIPEDRIVE_DEALS_URL, deal_id].join("/")
self.put(url, params)
end
def self.post_deal(params)
self.post(PIPEDRIVE_DEALS_URL, params)
end
def self.put_person(person_id, params)
url = [PIPEDRIVE_PEOPLE_URL, person_id].join("/")
self.put(url, params)
end
def self.post_person(params)
self.post(PIPEDRIVE_PEOPLE_URL, params)
end
def self.post_organization(params)
self.post(PIPEDRIVE_ORGANIZATIONS_URL, params)
end
private
def self.get(url, params)
params.merge!({
start: 0,
limit: 500,
api_token: token
})
response = Typhoeus.get(url, params: params)
if response.success?
JSON.parse(response.body)['data']
end
end
def self.put(url, params)
Typhoeus.put(
url,
params: { api_token: token },
body: params.to_json,
headers: { 'content-type' => 'application/json' }
)
end
def self.post(url, params)
Typhoeus.post(
url,
params: { api_token: token },
body: params.to_json,
headers: { 'content-type' => 'application/json' }
)
end
def self.token
Rails.application.secrets.pipedrive[:key]
end
end

View file

@ -1,63 +0,0 @@
class Pipedrive::DealAdapter
PIPEDRIVE_ADMIN_CENTRAL_STOCK_STAGE_ID = 35
PIPEDRIVE_SERVICE_DECO_REGIONAL_STOCK_STAGE_ID = 24
PIPEDRIVE_SERVICE_DECO_DEPARTEMENTAL_STOCK_STAGE_ID = 20
PIPEDRIVE_COLLECTIVITE_DEP_OU_REG_STOCK_STAGE_ID = 30
PIPEDRIVE_COLLECTIVITE_LOCALE_STOCK_STAGE_ID = 40
PIPEDRIVE_ORGANISMES_STOCK_STAGE_ID = 1
PIPEDRIVE_ORGANISMES_REFUSES_STOCK_STAGE_ID = 45
PIPEDRIVE_SUSPECTS_COMPTE_CREE_STAGE_ID = 48
PIPEDRIVE_LOST_STATUS = "lost"
PIPEDRIVE_LOST_REASON = "refusé depuis DS"
PIPEDRIVE_NB_OF_PROCEDURES_ATTRIBUTE_ID = "b22f8710352a7fb548623c062bf82ed6d1b6b704"
PIPEDRIVE_NB_OF_PROCEDURES_DO_NOT_KNOW_VALUE = "Je ne sais pas"
PIPEDRIVE_NB_OF_PROCEDURES_1_VALUE = "juste 1"
PIPEDRIVE_NB_OF_PROCEDURES_1_TO_10_VALUE = "de 1 a 10"
PIPEDRIVE_NB_OF_PROCEDURES_10_TO_100_VALUE = "de 10 a 100"
PIPEDRIVE_NB_OF_PROCEDURES_ABOVE_100_VALUE = "Plus de 100"
PIPEDRIVE_DEADLINE_ATTRIBUTE_ID = "36a72c82af9d9f0d476b041ede8876844a249bf2"
PIPEDRIVE_DEADLINE_ASAP_VALUE = "Le plus vite possible"
PIPEDRIVE_DEADLINE_NEXT_3_MONTHS_VALUE = "Dans les 3 prochain mois"
PIPEDRIVE_DEADLINE_NEXT_6_MONTHS_VALUE = "Dans les 6 prochain mois"
PIPEDRIVE_DEADLINE_NEXT_12_MONTHS_VALUE = "Dans les 12 prochain mois"
PIPEDRIVE_DEADLINE_NO_DATE_VALUE = "Pas de date"
def self.refuse_deal(deal_id, owner_id)
params = {
user_id: owner_id,
stage_id: PIPEDRIVE_ORGANISMES_REFUSES_STOCK_STAGE_ID,
status: PIPEDRIVE_LOST_STATUS,
lost_reason: PIPEDRIVE_LOST_REASON
}
Pipedrive::API.put_deal(deal_id, params)
end
def self.get_deals_ids_for_person(person_id)
deals = Pipedrive::API.get_deals_for_person(person_id) || []
deals.map { |datum| datum['id'] }
end
def self.update_deal_owner_and_stage(deal_id, owner_id, stage_id)
params = { user_id: owner_id, stage_id: stage_id }
Pipedrive::API.put_deal(deal_id, params)
end
def self.add_deal(organisation_id, person_id, title, nb_of_procedures, nb_of_dossiers, deadline)
params = {
org_id: organisation_id,
person_id: person_id,
title: title,
user_id: Pipedrive::PersonAdapter::PIPEDRIVE_ROBOT_ID,
"#{PIPEDRIVE_NB_OF_PROCEDURES_ATTRIBUTE_ID}": nb_of_procedures,
value: nb_of_dossiers,
"#{PIPEDRIVE_DEADLINE_ATTRIBUTE_ID}": deadline
}
Pipedrive::API.post_deal(params)
end
end

View file

@ -1,13 +0,0 @@
class Pipedrive::OrganizationAdapter
def self.add_organization(name, address)
params = {
name: name,
owner_id: Pipedrive::PersonAdapter::PIPEDRIVE_ROBOT_ID,
address: address
}
response = Pipedrive::API.post_organization(params)
JSON.parse(response.body)['data']['id']
end
end

View file

@ -1,52 +0,0 @@
class Pipedrive::PersonAdapter
PIPEDRIVE_POSTE_ATTRIBUTE_ID = '33a790746f1713d712fe97bcce9ac1ca6374a4d6'
PIPEDRIVE_SOURCE_ATTRIBUTE_ID = '2fa7864f467ffa97721cbcd08df5a3d591b15f50'
PIPEDRIVE_NB_DOSSIERS_ATTRIBUTE_ID = '2734a3ff19f4b88bd0d7b4cf02c47c7545617207'
PIPEDRIVE_DEADLINE_ATTRIBUTE_ID = 'ef766dd14de7da246fb5fc1704f45d1f1830f6c9'
PIPEDRIVE_ROBOT_ID = '11381160'
def self.get_demandes_from_persons_owned_by_robot
demandes = Pipedrive::API.get_persons_owned_by_user(PIPEDRIVE_ROBOT_ID)
if demandes.present?
demandes.map do |datum|
{
person_id: datum['id'],
nom: datum['name'],
poste: datum[PIPEDRIVE_POSTE_ATTRIBUTE_ID],
email: datum.dig('email', 0, 'value'),
tel: datum.dig('phone', 0, 'value'),
organisation: datum['org_name'],
nb_dossiers: datum[PIPEDRIVE_NB_DOSSIERS_ATTRIBUTE_ID],
deadline: datum[PIPEDRIVE_DEADLINE_ATTRIBUTE_ID]
}
end
else
[]
end
end
def self.update_person_owner(person_id, owner_id)
params = { owner_id: owner_id }
Pipedrive::API.put_person(person_id, params)
end
def self.add_person(email, phone, name, organization_id, poste, source, nb_of_dossiers, deadline)
params = {
email: email,
phone: phone,
name: name,
org_id: organization_id,
owner_id: PIPEDRIVE_ROBOT_ID,
"#{PIPEDRIVE_POSTE_ATTRIBUTE_ID}": poste,
"#{PIPEDRIVE_SOURCE_ATTRIBUTE_ID}": source,
"#{PIPEDRIVE_NB_DOSSIERS_ATTRIBUTE_ID}": nb_of_dossiers,
"#{PIPEDRIVE_DEADLINE_ATTRIBUTE_ID}": deadline
}
response = Pipedrive::API.post_person(params)
JSON.parse(response.body)['data']['id']
end
end

View file

@ -8,18 +8,4 @@ class ApplicationMailer < ActionMailer::Base
layout 'mailer' layout 'mailer'
before_action -> { Sentry.set_tags(mailer: mailer_name, action: action_name) } before_action -> { Sentry.set_tags(mailer: mailer_name, action: action_name) }
# Attach the procedure logo to the email (if any).
# Returns the attachment url.
def attach_logo(procedure)
if procedure.logo.attached?
logo_filename = procedure.logo.filename.to_s
attachments.inline[logo_filename] = procedure.logo.download
attachments[logo_filename].url
end
rescue StandardError => e
# A problem occured when reading logo, maybe the logo is missing and we should clean the procedure to remove logo reference ?
Sentry.capture_exception(e, extra: { procedure_id: procedure.id })
nil
end
end end

View file

@ -12,7 +12,7 @@ class DossierMailer < ApplicationMailer
@dossier = params[:dossier] @dossier = params[:dossier]
I18n.with_locale(@dossier.user_locale) do I18n.with_locale(@dossier.user_locale) do
@service = @dossier.procedure.service @service = @dossier.procedure.service
@logo_url = attach_logo(@dossier.procedure) @logo_url = procedure_logo_url(@dossier.procedure)
@subject = default_i18n_subject(libelle_demarche: @dossier.procedure.libelle) @subject = default_i18n_subject(libelle_demarche: @dossier.procedure.libelle)
mail(to: @dossier.user_email_for(:notification), subject: @subject) do |format| mail(to: @dossier.user_email_for(:notification), subject: @subject) do |format|
@ -27,7 +27,7 @@ class DossierMailer < ApplicationMailer
I18n.with_locale(dossier.user_locale) do I18n.with_locale(dossier.user_locale) do
@dossier = dossier @dossier = dossier
@service = dossier.procedure.service @service = dossier.procedure.service
@logo_url = attach_logo(dossier.procedure) @logo_url = procedure_logo_url(@dossier.procedure)
@body = commentaire.body @body = commentaire.body
@subject = default_i18n_subject(dossier_id: dossier.id, libelle_demarche: dossier.procedure.libelle) @subject = default_i18n_subject(dossier_id: dossier.id, libelle_demarche: dossier.procedure.libelle)
@ -52,7 +52,7 @@ class DossierMailer < ApplicationMailer
I18n.with_locale(dossier.user_locale) do I18n.with_locale(dossier.user_locale) do
@dossier = dossier @dossier = dossier
@service = dossier.procedure.service @service = dossier.procedure.service
@logo_url = attach_logo(dossier.procedure) @logo_url = procedure_logo_url(@dossier.procedure)
@correction = commentaire.dossier_correction @correction = commentaire.dossier_correction
@subject = default_i18n_subject(dossier_id: dossier.id, libelle_demarche: dossier.procedure.libelle) @subject = default_i18n_subject(dossier_id: dossier.id, libelle_demarche: dossier.procedure.libelle)
@ -85,7 +85,7 @@ class DossierMailer < ApplicationMailer
I18n.with_locale(dossier.user_locale) do I18n.with_locale(dossier.user_locale) do
@dossier = dossier @dossier = dossier
@service = dossier.procedure.service @service = dossier.procedure.service
@logo_url = attach_logo(dossier.procedure) @logo_url = procedure_logo_url(@dossier.procedure)
@subject = default_i18n_subject(dossier_id: dossier.id, libelle_demarche: dossier.procedure.libelle) @subject = default_i18n_subject(dossier_id: dossier.id, libelle_demarche: dossier.procedure.libelle)
mail(to: dossier.user_email_for(:notification), subject: @subject) do |format| mail(to: dossier.user_email_for(:notification), subject: @subject) do |format|

View file

@ -17,7 +17,7 @@ class NotificationMailer < ApplicationMailer
def send_notification def send_notification
@service = @dossier.procedure.service @service = @dossier.procedure.service
@logo_url = attach_logo(@dossier.procedure) @logo_url = procedure_logo_url(@dossier.procedure)
attachments[@attachment[:filename]] = @attachment[:content] if @attachment.present? attachments[@attachment[:filename]] = @attachment[:content] if @attachment.present?
I18n.with_locale(@dossier.user_locale) do I18n.with_locale(@dossier.user_locale) do
mail(subject: @subject, to: @email, template_name: 'send_notification') mail(subject: @subject, to: @email, template_name: 'send_notification')

View file

@ -1,12 +1,3 @@
# == Schema Information
#
# Table name: administrateurs
#
# id :integer not null, primary key
# created_at :datetime
# updated_at :datetime
# user_id :bigint not null
#
class Administrateur < ApplicationRecord class Administrateur < ApplicationRecord
UNUSED_ADMIN_THRESHOLD = ENV.fetch('UNUSED_ADMIN_THRESHOLD') { 6 }.to_i.months UNUSED_ADMIN_THRESHOLD = ENV.fetch('UNUSED_ADMIN_THRESHOLD') { 6 }.to_i.months
@ -29,7 +20,10 @@ class Administrateur < ApplicationRecord
.where.missing(:services) .where.missing(:services)
.left_outer_joins(:administrateurs_procedures) # needed to bypass procedure hidden default scope .left_outer_joins(:administrateurs_procedures) # needed to bypass procedure hidden default scope
.where(administrateurs_procedures: { procedure_id: nil }) .where(administrateurs_procedures: { procedure_id: nil })
.where("users.last_sign_in_at < ? ", UNUSED_ADMIN_THRESHOLD.ago) .includes(:api_tokens)
.where(users: { last_sign_in_at: ..UNUSED_ADMIN_THRESHOLD.ago })
.merge(APIToken.where(last_v1_authenticated_at: nil).or(APIToken.where(last_v1_authenticated_at: ..UNUSED_ADMIN_THRESHOLD.ago)))
.merge(APIToken.where(last_v2_authenticated_at: nil).or(APIToken.where(last_v2_authenticated_at: ..UNUSED_ADMIN_THRESHOLD.ago)))
end end
def self.by_email(email) def self.by_email(email)

View file

@ -1,12 +1,3 @@
# == Schema Information
#
# Table name: administrateurs_instructeurs
#
# created_at :datetime
# updated_at :datetime
# administrateur_id :integer not null
# instructeur_id :integer not null
#
class AdministrateursInstructeur < ApplicationRecord class AdministrateursInstructeur < ApplicationRecord
belongs_to :administrateur belongs_to :administrateur
belongs_to :instructeur belongs_to :instructeur

View file

@ -1,13 +1,3 @@
# == Schema Information
#
# Table name: administrateurs_procedures
#
# manager :boolean
# created_at :datetime not null
# updated_at :datetime not null
# administrateur_id :bigint not null
# procedure_id :bigint not null
#
class AdministrateursProcedure < ApplicationRecord class AdministrateursProcedure < ApplicationRecord
belongs_to :administrateur belongs_to :administrateur
belongs_to :procedure belongs_to :procedure

View file

@ -1,17 +1,3 @@
# == Schema Information
#
# Table name: api_tokens
#
# id :uuid not null, primary key
# allowed_procedure_ids :bigint is an Array
# encrypted_token :string not null
# name :string not null
# write_access :boolean default(TRUE), not null
# version :integer default(3), not null
# created_at :datetime not null
# updated_at :datetime not null
# administrateur_id :bigint not null
#
class APIToken < ApplicationRecord class APIToken < ApplicationRecord
include ActiveRecord::SecureToken include ActiveRecord::SecureToken

View file

@ -1,15 +1,4 @@
# == Schema Information # == Schema Information
#
# Table name: archives
#
# id :bigint not null, primary key
# job_status :string not null
# key :text not null
# month :date
# time_span_type :string not null
# created_at :datetime not null
# updated_at :datetime not null
#
class Archive < ApplicationRecord class Archive < ApplicationRecord
include TransientModelsWithPurgeableJobConcern include TransientModelsWithPurgeableJobConcern

View file

@ -1,19 +1,3 @@
# == Schema Information
#
# Table name: assign_tos
#
# id :integer not null, primary key
# daily_email_notifications_enabled :boolean default(FALSE), not null
# instant_email_dossier_notifications_enabled :boolean default(FALSE), not null
# instant_email_message_notifications_enabled :boolean default(FALSE), not null
# instant_expert_avis_email_notifications_enabled :boolean default(FALSE)
# manager :boolean default(FALSE)
# weekly_email_notifications_enabled :boolean default(TRUE), not null
# created_at :datetime
# updated_at :datetime
# groupe_instructeur_id :bigint
# instructeur_id :integer
#
class AssignTo < ApplicationRecord class AssignTo < ApplicationRecord
belongs_to :instructeur, optional: false belongs_to :instructeur, optional: false
belongs_to :groupe_instructeur, optional: false belongs_to :groupe_instructeur, optional: false

View file

@ -1,13 +1,3 @@
# == Schema Information
#
# Table name: attestations
#
# id :integer not null, primary key
# title :string
# created_at :datetime not null
# updated_at :datetime not null
# dossier_id :integer not null
#
class Attestation < ApplicationRecord class Attestation < ApplicationRecord
belongs_to :dossier, optional: false belongs_to :dossier, optional: false

View file

@ -1,16 +1,3 @@
# == Schema Information
#
# Table name: attestation_templates
#
# id :integer not null, primary key
# activated :boolean
# body :text
# footer :text
# title :text
# created_at :datetime not null
# updated_at :datetime not null
# procedure_id :integer
#
class AttestationTemplate < ApplicationRecord class AttestationTemplate < ApplicationRecord
include ActionView::Helpers::NumberHelper include ActionView::Helpers::NumberHelper
include TagsSubstitutionConcern include TagsSubstitutionConcern

View file

@ -1,23 +1,3 @@
# == Schema Information
#
# Table name: avis
#
# id :integer not null, primary key
# answer :text
# claimant_type :string
# confidentiel :boolean default(FALSE), not null
# email :string
# introduction :text
# question_answer :boolean
# question_label :string
# reminded_at :datetime
# revoked_at :datetime
# created_at :datetime not null
# updated_at :datetime not null
# claimant_id :integer not null
# dossier_id :integer
# experts_procedure_id :bigint
#
class Avis < ApplicationRecord class Avis < ApplicationRecord
include EmailSanitizableConcern include EmailSanitizableConcern

View file

@ -1,20 +1,3 @@
# == Schema Information
#
# Table name: batch_operations
#
# id :bigint not null, primary key
# failed_dossier_ids :bigint default([]), not null, is an Array
# finished_at :datetime
# operation :string not null
# payload :jsonb not null
# run_at :datetime
# seen_at :datetime
# success_dossier_ids :bigint default([]), not null, is an Array
# created_at :datetime not null
# updated_at :datetime not null
# instructeur_id :bigint not null
#
class BatchOperation < ApplicationRecord class BatchOperation < ApplicationRecord
enum operation: { enum operation: {
accepter: 'accepter', accepter: 'accepter',

View file

@ -1,12 +1,3 @@
# == Schema Information
#
# Table name: bill_signatures
#
# id :bigint not null, primary key
# digest :string
# created_at :datetime not null
# updated_at :datetime not null
#
class BillSignature < ApplicationRecord class BillSignature < ApplicationRecord
has_many :dossier_operation_logs has_many :dossier_operation_logs

View file

@ -1,18 +1,4 @@
# == Schema Information
#
# Table name: bulk_messages
#
# id :bigint not null, primary key
# body :text not null
# dossier_count :integer
# dossier_state :string
# sent_at :datetime not null
# created_at :datetime not null
# updated_at :datetime not null
# instructeur_id :bigint not null
#
class BulkMessage < ApplicationRecord class BulkMessage < ApplicationRecord
belongs_to :instructeur belongs_to :instructeur
has_and_belongs_to_many :groupe_instructeurs, -> { order(:label) } has_and_belongs_to_many :groupe_instructeurs, -> { order(:label) }
has_one_attached :piece_jointe
end end

View file

@ -1,25 +1,3 @@
# == Schema Information
#
# Table name: champs
#
# id :integer not null, primary key
# data :jsonb
# fetch_external_data_exceptions :string is an Array
# prefilled :boolean
# private :boolean default(FALSE), not null
# rebased_at :datetime
# type :string
# value :string
# value_json :jsonb
# created_at :datetime
# updated_at :datetime
# dossier_id :integer
# etablissement_id :integer
# external_id :string
# parent_id :bigint
# row_id :string
# type_de_champ_id :integer
#
class Champ < ApplicationRecord class Champ < ApplicationRecord
include ChampConditionalConcern include ChampConditionalConcern

View file

@ -1,25 +1,3 @@
# == Schema Information
#
# Table name: champs
#
# id :integer not null, primary key
# data :jsonb
# fetch_external_data_exceptions :string is an Array
# prefilled :boolean default(FALSE)
# private :boolean default(FALSE), not null
# rebased_at :datetime
# type :string
# value :string
# value_json :jsonb
# created_at :datetime
# updated_at :datetime
# dossier_id :integer
# etablissement_id :integer
# external_id :string
# parent_id :bigint
# row_id :string
# type_de_champ_id :integer
#
class Champs::AddressChamp < Champs::TextChamp class Champs::AddressChamp < Champs::TextChamp
def full_address? def full_address?
data.present? data.present?

View file

@ -1,25 +1,3 @@
# == Schema Information
#
# Table name: champs
#
# id :integer not null, primary key
# data :jsonb
# fetch_external_data_exceptions :string is an Array
# prefilled :boolean default(FALSE)
# private :boolean default(FALSE), not null
# rebased_at :datetime
# type :string
# value :string
# value_json :jsonb
# created_at :datetime
# updated_at :datetime
# dossier_id :integer
# etablissement_id :integer
# external_id :string
# parent_id :bigint
# row_id :string
# type_de_champ_id :integer
#
class Champs::AnnuaireEducationChamp < Champs::TextChamp class Champs::AnnuaireEducationChamp < Champs::TextChamp
def fetch_external_data? def fetch_external_data?
true true

View file

@ -1,25 +1,3 @@
# == Schema Information
#
# Table name: champs
#
# id :integer not null, primary key
# data :jsonb
# fetch_external_data_exceptions :string is an Array
# prefilled :boolean default(FALSE)
# private :boolean default(FALSE), not null
# rebased_at :datetime
# type :string
# value :string
# value_json :jsonb
# created_at :datetime
# updated_at :datetime
# dossier_id :integer
# etablissement_id :integer
# external_id :string
# parent_id :bigint
# row_id :string
# type_de_champ_id :integer
#
class Champs::BooleanChamp < Champ class Champs::BooleanChamp < Champ
TRUE_VALUE = 'true' TRUE_VALUE = 'true'
FALSE_VALUE = 'false' FALSE_VALUE = 'false'

View file

@ -1,25 +1,3 @@
# == Schema Information
#
# Table name: champs
#
# id :integer not null, primary key
# data :jsonb
# fetch_external_data_exceptions :string is an Array
# prefilled :boolean default(FALSE)
# private :boolean default(FALSE), not null
# rebased_at :datetime
# type :string
# value :string
# value_json :jsonb
# created_at :datetime
# updated_at :datetime
# dossier_id :integer
# etablissement_id :integer
# external_id :string
# parent_id :bigint
# row_id :string
# type_de_champ_id :integer
#
class Champs::CarteChamp < Champ class Champs::CarteChamp < Champ
# Default map location. Center of the World, ahm, France... # Default map location. Center of the World, ahm, France...
DEFAULT_LON = 2.428462 DEFAULT_LON = 2.428462

View file

@ -1,25 +1,3 @@
# == Schema Information
#
# Table name: champs
#
# id :integer not null, primary key
# data :jsonb
# fetch_external_data_exceptions :string is an Array
# prefilled :boolean default(FALSE)
# private :boolean default(FALSE), not null
# rebased_at :datetime
# type :string
# value :string
# value_json :jsonb
# created_at :datetime
# updated_at :datetime
# dossier_id :integer
# etablissement_id :integer
# external_id :string
# parent_id :bigint
# row_id :string
# type_de_champ_id :integer
#
class Champs::CheckboxChamp < Champs::BooleanChamp class Champs::CheckboxChamp < Champs::BooleanChamp
def for_export def for_export
true? ? 'on' : 'off' true? ? 'on' : 'off'

View file

@ -1,25 +1,3 @@
# == Schema Information
#
# Table name: champs
#
# id :integer not null, primary key
# data :jsonb
# fetch_external_data_exceptions :string is an Array
# prefilled :boolean default(FALSE)
# private :boolean default(FALSE), not null
# rebased_at :datetime
# type :string
# value :string
# value_json :jsonb
# created_at :datetime
# updated_at :datetime
# dossier_id :integer
# etablissement_id :integer
# external_id :string
# parent_id :bigint
# row_id :string
# type_de_champ_id :integer
#
class Champs::CiviliteChamp < Champ class Champs::CiviliteChamp < Champ
validates :value, inclusion: ["M.", "Mme"], allow_nil: true, allow_blank: false validates :value, inclusion: ["M.", "Mme"], allow_nil: true, allow_blank: false

View file

@ -1,25 +1,3 @@
# == Schema Information
#
# Table name: champs
#
# id :integer not null, primary key
# data :jsonb
# fetch_external_data_exceptions :string is an Array
# prefilled :boolean default(FALSE)
# private :boolean default(FALSE), not null
# rebased_at :datetime
# type :string
# value :string
# value_json :jsonb
# created_at :datetime
# updated_at :datetime
# dossier_id :integer
# etablissement_id :integer
# external_id :string
# parent_id :bigint
# row_id :string
# type_de_champ_id :integer
#
class Champs::CnafChamp < Champs::TextChamp class Champs::CnafChamp < Champs::TextChamp
# see https://github.com/betagouv/api-particulier/blob/master/src/presentation/middlewares/cnaf-input-validation.middleware.ts # see https://github.com/betagouv/api-particulier/blob/master/src/presentation/middlewares/cnaf-input-validation.middleware.ts
validates :numero_allocataire, format: { with: /\A\d{1,7}\z/ }, if: -> { code_postal.present? && validation_context != :brouillon } validates :numero_allocataire, format: { with: /\A\d{1,7}\z/ }, if: -> { code_postal.present? && validation_context != :brouillon }

View file

@ -1,25 +1,3 @@
# == Schema Information
#
# Table name: champs
#
# id :integer not null, primary key
# data :jsonb
# fetch_external_data_exceptions :string is an Array
# prefilled :boolean default(FALSE)
# private :boolean default(FALSE), not null
# rebased_at :datetime
# type :string
# value :string
# value_json :jsonb
# created_at :datetime
# updated_at :datetime
# dossier_id :integer
# etablissement_id :integer
# external_id :string
# parent_id :bigint
# row_id :string
# type_de_champ_id :integer
#
class Champs::CommuneChamp < Champs::TextChamp class Champs::CommuneChamp < Champs::TextChamp
store_accessor :value_json, :code_departement, :code_postal store_accessor :value_json, :code_departement, :code_postal
before_validation :on_code_postal_change before_validation :on_code_postal_change

View file

@ -1,25 +1,3 @@
# == Schema Information
#
# Table name: champs
#
# id :integer not null, primary key
# data :jsonb
# fetch_external_data_exceptions :string is an Array
# prefilled :boolean default(FALSE)
# private :boolean default(FALSE), not null
# rebased_at :datetime
# type :string
# value :string
# value_json :jsonb
# created_at :datetime
# updated_at :datetime
# dossier_id :integer
# etablissement_id :integer
# external_id :string
# parent_id :bigint
# row_id :string
# type_de_champ_id :integer
#
class Champs::DateChamp < Champ class Champs::DateChamp < Champ
before_validation :convert_to_iso8601, unless: -> { validation_context == :prefill } before_validation :convert_to_iso8601, unless: -> { validation_context == :prefill }
validate :iso_8601 validate :iso_8601

View file

@ -1,25 +1,3 @@
# == Schema Information
#
# Table name: champs
#
# id :integer not null, primary key
# data :jsonb
# fetch_external_data_exceptions :string is an Array
# prefilled :boolean default(FALSE)
# private :boolean default(FALSE), not null
# rebased_at :datetime
# type :string
# value :string
# value_json :jsonb
# created_at :datetime
# updated_at :datetime
# dossier_id :integer
# etablissement_id :integer
# external_id :string
# parent_id :bigint
# row_id :string
# type_de_champ_id :integer
#
class Champs::DatetimeChamp < Champ class Champs::DatetimeChamp < Champ
before_validation :convert_to_iso8601, unless: -> { validation_context == :prefill } before_validation :convert_to_iso8601, unless: -> { validation_context == :prefill }
validate :iso_8601 validate :iso_8601
@ -66,6 +44,6 @@ class Champs::DatetimeChamp < Champ
end end
def valid_iso8601? def valid_iso8601?
/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(:\d{2}\+\d{2}:\d{2})?$/.match?(value) /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(:\d{2}[\+\-]\d{2}:\d{2})?$/.match?(value)
end end
end end

View file

@ -1,25 +1,3 @@
# == Schema Information
#
# Table name: champs
#
# id :integer not null, primary key
# data :jsonb
# fetch_external_data_exceptions :string is an Array
# prefilled :boolean default(FALSE)
# private :boolean default(FALSE), not null
# rebased_at :datetime
# type :string
# value :string
# value_json :jsonb
# created_at :datetime
# updated_at :datetime
# dossier_id :integer
# etablissement_id :integer
# external_id :string
# parent_id :bigint
# row_id :string
# type_de_champ_id :integer
#
class Champs::DecimalNumberChamp < Champ class Champs::DecimalNumberChamp < Champ
validates :value, numericality: { validates :value, numericality: {
allow_nil: true, allow_nil: true,

View file

@ -1,25 +1,3 @@
# == Schema Information
#
# Table name: champs
#
# id :integer not null, primary key
# data :jsonb
# fetch_external_data_exceptions :string is an Array
# prefilled :boolean default(FALSE)
# private :boolean default(FALSE), not null
# rebased_at :datetime
# type :string
# value :string
# value_json :jsonb
# created_at :datetime
# updated_at :datetime
# dossier_id :integer
# etablissement_id :integer
# external_id :string
# parent_id :bigint
# row_id :string
# type_de_champ_id :integer
#
class Champs::DepartementChamp < Champs::TextChamp class Champs::DepartementChamp < Champs::TextChamp
validate :value_in_departement_names, unless: -> { value.nil? } validate :value_in_departement_names, unless: -> { value.nil? }
validate :external_id_in_departement_codes, unless: -> { external_id.nil? } validate :external_id_in_departement_codes, unless: -> { external_id.nil? }

View file

@ -1,25 +1,3 @@
# == Schema Information
#
# Table name: champs
#
# id :integer not null, primary key
# data :jsonb
# fetch_external_data_exceptions :string is an Array
# prefilled :boolean default(FALSE)
# private :boolean default(FALSE), not null
# rebased_at :datetime
# type :string
# value :string
# value_json :jsonb
# created_at :datetime
# updated_at :datetime
# dossier_id :integer
# etablissement_id :integer
# external_id :string
# parent_id :bigint
# row_id :string
# type_de_champ_id :integer
#
class Champs::DgfipChamp < Champs::TextChamp class Champs::DgfipChamp < Champs::TextChamp
# see https://github.com/betagouv/api-particulier/blob/master/src/presentation/middlewares/dgfip-input-validation.middleware.ts # see https://github.com/betagouv/api-particulier/blob/master/src/presentation/middlewares/dgfip-input-validation.middleware.ts
validates :numero_fiscal, format: { with: /\A\w{13,14}\z/ }, if: -> { reference_avis.present? && validation_context != :brouillon } validates :numero_fiscal, format: { with: /\A\w{13,14}\z/ }, if: -> { reference_avis.present? && validation_context != :brouillon }

View file

@ -1,25 +1,3 @@
# == Schema Information
#
# Table name: champs
#
# id :integer not null, primary key
# data :jsonb
# fetch_external_data_exceptions :string is an Array
# prefilled :boolean default(FALSE)
# private :boolean default(FALSE), not null
# rebased_at :datetime
# type :string
# value :string
# value_json :jsonb
# created_at :datetime
# updated_at :datetime
# dossier_id :integer
# etablissement_id :integer
# external_id :string
# parent_id :bigint
# row_id :string
# type_de_champ_id :integer
#
class Champs::DossierLinkChamp < Champ class Champs::DossierLinkChamp < Champ
validate :value_integerable, if: -> { value.present? }, on: :prefill validate :value_integerable, if: -> { value.present? }, on: :prefill

View file

@ -1,25 +1,3 @@
# == Schema Information
#
# Table name: champs
#
# id :integer not null, primary key
# data :jsonb
# fetch_external_data_exceptions :string is an Array
# prefilled :boolean default(FALSE)
# private :boolean default(FALSE), not null
# rebased_at :datetime
# type :string
# value :string
# value_json :jsonb
# created_at :datetime
# updated_at :datetime
# dossier_id :integer
# etablissement_id :integer
# external_id :string
# parent_id :bigint
# row_id :string
# type_de_champ_id :integer
#
class Champs::DropDownListChamp < Champ class Champs::DropDownListChamp < Champ
store_accessor :value_json, :other store_accessor :value_json, :other
THRESHOLD_NB_OPTIONS_AS_RADIO = 5 THRESHOLD_NB_OPTIONS_AS_RADIO = 5

View file

@ -1,24 +1,2 @@
# == Schema Information
#
# Table name: champs
#
# id :integer not null, primary key
# data :jsonb
# fetch_external_data_exceptions :string is an Array
# prefilled :boolean default(FALSE)
# private :boolean default(FALSE), not null
# rebased_at :datetime
# type :string
# value :string
# value_json :jsonb
# created_at :datetime
# updated_at :datetime
# dossier_id :integer
# etablissement_id :integer
# external_id :string
# parent_id :bigint
# row_id :string
# type_de_champ_id :integer
#
class Champs::EmailChamp < Champs::TextChamp class Champs::EmailChamp < Champs::TextChamp
end end

View file

@ -1,25 +1,3 @@
# == Schema Information
#
# Table name: champs
#
# id :integer not null, primary key
# data :jsonb
# fetch_external_data_exceptions :string is an Array
# prefilled :boolean default(FALSE)
# private :boolean default(FALSE), not null
# rebased_at :datetime
# type :string
# value :string
# value_json :jsonb
# created_at :datetime
# updated_at :datetime
# dossier_id :integer
# etablissement_id :integer
# external_id :string
# parent_id :bigint
# row_id :string
# type_de_champ_id :integer
#
class Champs::EpciChamp < Champs::TextChamp class Champs::EpciChamp < Champs::TextChamp
store_accessor :value_json, :code_departement store_accessor :value_json, :code_departement
before_validation :on_departement_change before_validation :on_departement_change

View file

@ -1,25 +1,3 @@
# == Schema Information
#
# Table name: champs
#
# id :integer not null, primary key
# data :jsonb
# fetch_external_data_exceptions :string is an Array
# prefilled :boolean default(FALSE)
# private :boolean default(FALSE), not null
# rebased_at :datetime
# type :string
# value :string
# value_json :jsonb
# created_at :datetime
# updated_at :datetime
# dossier_id :integer
# etablissement_id :integer
# external_id :string
# parent_id :bigint
# row_id :string
# type_de_champ_id :integer
#
class Champs::ExplicationChamp < Champs::TextChamp class Champs::ExplicationChamp < Champs::TextChamp
def search_terms def search_terms
# The user cannot enter any information here so it doesnt make much sense to search # The user cannot enter any information here so it doesnt make much sense to search

View file

@ -1,25 +1,3 @@
# == Schema Information
#
# Table name: champs
#
# id :integer not null, primary key
# data :jsonb
# fetch_external_data_exceptions :string is an Array
# prefilled :boolean default(FALSE)
# private :boolean default(FALSE), not null
# rebased_at :datetime
# type :string
# value :string
# value_json :jsonb
# created_at :datetime
# updated_at :datetime
# dossier_id :integer
# etablissement_id :integer
# external_id :string
# parent_id :bigint
# row_id :string
# type_de_champ_id :integer
#
class Champs::HeaderSectionChamp < Champ class Champs::HeaderSectionChamp < Champ
def level def level
if parent.present? if parent.present?

View file

@ -1,25 +1,3 @@
# == Schema Information
#
# Table name: champs
#
# id :integer not null, primary key
# data :jsonb
# fetch_external_data_exceptions :string is an Array
# prefilled :boolean default(FALSE)
# private :boolean default(FALSE), not null
# rebased_at :datetime
# type :string
# value :string
# value_json :jsonb
# created_at :datetime
# updated_at :datetime
# dossier_id :integer
# etablissement_id :integer
# external_id :string
# parent_id :bigint
# row_id :string
# type_de_champ_id :integer
#
class Champs::IbanChamp < Champ class Champs::IbanChamp < Champ
validates_with IbanValidator, if: -> { validation_context != :brouillon } validates_with IbanValidator, if: -> { validation_context != :brouillon }
after_validation :format_iban after_validation :format_iban

View file

@ -1,25 +1,3 @@
# == Schema Information
#
# Table name: champs
#
# id :integer not null, primary key
# data :jsonb
# fetch_external_data_exceptions :string is an Array
# prefilled :boolean default(FALSE)
# private :boolean default(FALSE), not null
# rebased_at :datetime
# type :string
# value :string
# value_json :jsonb
# created_at :datetime
# updated_at :datetime
# dossier_id :integer
# etablissement_id :integer
# external_id :string
# parent_id :bigint
# row_id :string
# type_de_champ_id :integer
#
class Champs::IntegerNumberChamp < Champ class Champs::IntegerNumberChamp < Champ
validates :value, numericality: { validates :value, numericality: {
only_integer: true, only_integer: true,

View file

@ -1,25 +1,3 @@
# == Schema Information
#
# Table name: champs
#
# id :integer not null, primary key
# data :jsonb
# fetch_external_data_exceptions :string is an Array
# prefilled :boolean default(FALSE)
# private :boolean default(FALSE), not null
# rebased_at :datetime
# type :string
# value :string
# value_json :jsonb
# created_at :datetime
# updated_at :datetime
# dossier_id :integer
# etablissement_id :integer
# external_id :string
# parent_id :bigint
# row_id :string
# type_de_champ_id :integer
#
class Champs::LinkedDropDownListChamp < Champ class Champs::LinkedDropDownListChamp < Champ
delegate :primary_options, :secondary_options, to: 'type_de_champ.dynamic_type' delegate :primary_options, :secondary_options, to: 'type_de_champ.dynamic_type'

View file

@ -1,25 +1,3 @@
# == Schema Information
#
# Table name: champs
#
# id :integer not null, primary key
# data :jsonb
# fetch_external_data_exceptions :string is an Array
# prefilled :boolean default(FALSE)
# private :boolean default(FALSE), not null
# rebased_at :datetime
# type :string
# value :string
# value_json :jsonb
# created_at :datetime
# updated_at :datetime
# dossier_id :integer
# etablissement_id :integer
# external_id :string
# parent_id :bigint
# row_id :string
# type_de_champ_id :integer
#
class Champs::MesriChamp < Champs::TextChamp class Champs::MesriChamp < Champs::TextChamp
# see https://github.com/betagouv/api-particulier/blob/master/src/presentation/middlewares/mesri-input-validation.middleware.ts # see https://github.com/betagouv/api-particulier/blob/master/src/presentation/middlewares/mesri-input-validation.middleware.ts
store_accessor :value_json, :ine store_accessor :value_json, :ine

View file

@ -1,25 +1,3 @@
# == Schema Information
#
# Table name: champs
#
# id :integer not null, primary key
# data :jsonb
# fetch_external_data_exceptions :string is an Array
# prefilled :boolean default(FALSE)
# private :boolean default(FALSE), not null
# rebased_at :datetime
# type :string
# value :string
# value_json :jsonb
# created_at :datetime
# updated_at :datetime
# dossier_id :integer
# etablissement_id :integer
# external_id :string
# parent_id :bigint
# row_id :string
# type_de_champ_id :integer
#
class Champs::MultipleDropDownListChamp < Champ class Champs::MultipleDropDownListChamp < Champ
validate :values_are_in_options, if: -> { value.present? } validate :values_are_in_options, if: -> { value.present? }

View file

@ -1,24 +1,2 @@
# == Schema Information
#
# Table name: champs
#
# id :integer not null, primary key
# data :jsonb
# fetch_external_data_exceptions :string is an Array
# prefilled :boolean default(FALSE)
# private :boolean default(FALSE), not null
# rebased_at :datetime
# type :string
# value :string
# value_json :jsonb
# created_at :datetime
# updated_at :datetime
# dossier_id :integer
# etablissement_id :integer
# external_id :string
# parent_id :bigint
# row_id :string
# type_de_champ_id :integer
#
class Champs::NumberChamp < Champ class Champs::NumberChamp < Champ
end end

View file

@ -1,25 +1,3 @@
# == Schema Information
#
# Table name: champs
#
# id :integer not null, primary key
# data :jsonb
# fetch_external_data_exceptions :string is an Array
# prefilled :boolean default(FALSE)
# private :boolean default(FALSE), not null
# rebased_at :datetime
# type :string
# value :string
# value_json :jsonb
# created_at :datetime
# updated_at :datetime
# dossier_id :integer
# etablissement_id :integer
# external_id :string
# parent_id :bigint
# row_id :string
# type_de_champ_id :integer
#
class Champs::PaysChamp < Champs::TextChamp class Champs::PaysChamp < Champs::TextChamp
validates :value, inclusion: APIGeoService.countries.pluck(:name), allow_nil: true, allow_blank: false validates :value, inclusion: APIGeoService.countries.pluck(:name), allow_nil: true, allow_blank: false
validates :external_id, inclusion: APIGeoService.countries.pluck(:code), allow_nil: true, allow_blank: false validates :external_id, inclusion: APIGeoService.countries.pluck(:code), allow_nil: true, allow_blank: false

View file

@ -1,25 +1,3 @@
# == Schema Information
#
# Table name: champs
#
# id :integer not null, primary key
# data :jsonb
# fetch_external_data_exceptions :string is an Array
# prefilled :boolean default(FALSE)
# private :boolean default(FALSE), not null
# rebased_at :datetime
# type :string
# value :string
# value_json :jsonb
# created_at :datetime
# updated_at :datetime
# dossier_id :integer
# etablissement_id :integer
# external_id :string
# parent_id :bigint
# row_id :string
# type_de_champ_id :integer
#
class Champs::PhoneChamp < Champs::TextChamp class Champs::PhoneChamp < Champs::TextChamp
# We want to allow: # We want to allow:
# * international (e164) phone numbers # * international (e164) phone numbers

View file

@ -1,25 +1,3 @@
# == Schema Information
#
# Table name: champs
#
# id :integer not null, primary key
# data :jsonb
# fetch_external_data_exceptions :string is an Array
# prefilled :boolean default(FALSE)
# private :boolean default(FALSE), not null
# rebased_at :datetime
# type :string
# value :string
# value_json :jsonb
# created_at :datetime
# updated_at :datetime
# dossier_id :integer
# etablissement_id :integer
# external_id :string
# parent_id :bigint
# row_id :string
# type_de_champ_id :integer
#
class Champs::PieceJustificativeChamp < Champ class Champs::PieceJustificativeChamp < Champ
FILE_MAX_SIZE = 200.megabytes FILE_MAX_SIZE = 200.megabytes

View file

@ -1,25 +1,3 @@
# == Schema Information
#
# Table name: champs
#
# id :integer not null, primary key
# data :jsonb
# fetch_external_data_exceptions :string is an Array
# prefilled :boolean default(FALSE)
# private :boolean default(FALSE), not null
# rebased_at :datetime
# type :string
# value :string
# value_json :jsonb
# created_at :datetime
# updated_at :datetime
# dossier_id :integer
# etablissement_id :integer
# external_id :string
# parent_id :bigint
# row_id :string
# type_de_champ_id :integer
#
class Champs::PoleEmploiChamp < Champs::TextChamp class Champs::PoleEmploiChamp < Champs::TextChamp
# see https://github.com/betagouv/api-particulier/blob/master/src/presentation/middlewares/pole-emploi-input-validation.middleware.ts # see https://github.com/betagouv/api-particulier/blob/master/src/presentation/middlewares/pole-emploi-input-validation.middleware.ts
store_accessor :value_json, :identifiant store_accessor :value_json, :identifiant

View file

@ -1,25 +1,3 @@
# == Schema Information
#
# Table name: champs
#
# id :integer not null, primary key
# data :jsonb
# fetch_external_data_exceptions :string is an Array
# prefilled :boolean default(FALSE)
# private :boolean default(FALSE), not null
# rebased_at :datetime
# type :string
# value :string
# value_json :jsonb
# created_at :datetime
# updated_at :datetime
# dossier_id :integer
# etablissement_id :integer
# external_id :string
# parent_id :bigint
# row_id :string
# type_de_champ_id :integer
#
class Champs::RegionChamp < Champs::TextChamp class Champs::RegionChamp < Champs::TextChamp
validate :value_in_region_names, unless: -> { value.nil? } validate :value_in_region_names, unless: -> { value.nil? }
validate :external_id_in_region_codes, unless: -> { external_id.nil? } validate :external_id_in_region_codes, unless: -> { external_id.nil? }

View file

@ -1,25 +1,3 @@
# == Schema Information
#
# Table name: champs
#
# id :integer not null, primary key
# data :jsonb
# fetch_external_data_exceptions :string is an Array
# prefilled :boolean default(FALSE)
# private :boolean default(FALSE), not null
# rebased_at :datetime
# type :string
# value :string
# value_json :jsonb
# created_at :datetime
# updated_at :datetime
# dossier_id :integer
# etablissement_id :integer
# external_id :string
# parent_id :bigint
# row_id :string
# type_de_champ_id :integer
#
class Champs::RepetitionChamp < Champ class Champs::RepetitionChamp < Champ
accepts_nested_attributes_for :champs accepts_nested_attributes_for :champs
delegate :libelle_for_export, to: :type_de_champ delegate :libelle_for_export, to: :type_de_champ

View file

@ -1,25 +1,3 @@
# == Schema Information
#
# Table name: champs
#
# id :integer not null, primary key
# data :jsonb
# fetch_external_data_exceptions :string is an Array
# prefilled :boolean default(FALSE)
# private :boolean default(FALSE), not null
# rebased_at :datetime
# type :string
# value :string
# value_json :jsonb
# created_at :datetime
# updated_at :datetime
# dossier_id :integer
# etablissement_id :integer
# external_id :string
# parent_id :bigint
# row_id :string
# type_de_champ_id :integer
#
class Champs::RNAChamp < Champ class Champs::RNAChamp < Champ
include RNAChampAssociationFetchableConcern include RNAChampAssociationFetchableConcern

View file

@ -1,25 +1,3 @@
# == Schema Information
#
# Table name: champs
#
# id :integer not null, primary key
# data :jsonb
# fetch_external_data_exceptions :string is an Array
# prefilled :boolean default(FALSE)
# private :boolean default(FALSE), not null
# rebased_at :datetime
# type :string
# value :string
# value_json :jsonb
# created_at :datetime
# updated_at :datetime
# dossier_id :integer
# etablissement_id :integer
# external_id :string
# parent_id :bigint
# row_id :string
# type_de_champ_id :integer
#
class Champs::SiretChamp < Champ class Champs::SiretChamp < Champ
include SiretChampEtablissementFetchableConcern include SiretChampEtablissementFetchableConcern

View file

@ -1,24 +1,2 @@
# == Schema Information
#
# Table name: champs
#
# id :integer not null, primary key
# data :jsonb
# fetch_external_data_exceptions :string is an Array
# prefilled :boolean default(FALSE)
# private :boolean default(FALSE), not null
# rebased_at :datetime
# type :string
# value :string
# value_json :jsonb
# created_at :datetime
# updated_at :datetime
# dossier_id :integer
# etablissement_id :integer
# external_id :string
# parent_id :bigint
# row_id :string
# type_de_champ_id :integer
#
class Champs::TextChamp < Champ class Champs::TextChamp < Champ
end end

View file

@ -1,25 +1,3 @@
# == Schema Information
#
# Table name: champs
#
# id :integer not null, primary key
# data :jsonb
# fetch_external_data_exceptions :string is an Array
# prefilled :boolean default(FALSE)
# private :boolean default(FALSE), not null
# rebased_at :datetime
# type :string
# value :string
# value_json :jsonb
# created_at :datetime
# updated_at :datetime
# dossier_id :integer
# etablissement_id :integer
# external_id :string
# parent_id :bigint
# row_id :string
# type_de_champ_id :integer
#
class Champs::TextareaChamp < Champs::TextChamp class Champs::TextareaChamp < Champs::TextChamp
def for_export def for_export
value.present? ? ActionView::Base.full_sanitizer.sanitize(value) : nil value.present? ? ActionView::Base.full_sanitizer.sanitize(value) : nil

View file

@ -1,25 +1,3 @@
# == Schema Information
#
# Table name: champs
#
# id :integer not null, primary key
# data :jsonb
# fetch_external_data_exceptions :string is an Array
# prefilled :boolean default(FALSE)
# private :boolean default(FALSE), not null
# rebased_at :datetime
# type :string
# value :string
# value_json :jsonb
# created_at :datetime
# updated_at :datetime
# dossier_id :integer
# etablissement_id :integer
# external_id :string
# parent_id :bigint
# row_id :string
# type_de_champ_id :integer
#
class Champs::TitreIdentiteChamp < Champ class Champs::TitreIdentiteChamp < Champ
FILE_MAX_SIZE = 20.megabytes FILE_MAX_SIZE = 20.megabytes
ACCEPTED_FORMATS = ['image/png', 'image/jpeg'] ACCEPTED_FORMATS = ['image/png', 'image/jpeg']

View file

@ -1,25 +1,3 @@
# == Schema Information
#
# Table name: champs
#
# id :integer not null, primary key
# data :jsonb
# fetch_external_data_exceptions :string is an Array
# prefilled :boolean default(FALSE)
# private :boolean default(FALSE), not null
# rebased_at :datetime
# type :string
# value :string
# value_json :jsonb
# created_at :datetime
# updated_at :datetime
# dossier_id :integer
# etablissement_id :integer
# external_id :string
# parent_id :bigint
# row_id :string
# type_de_champ_id :integer
#
class Champs::YesNoChamp < Champs::BooleanChamp class Champs::YesNoChamp < Champs::BooleanChamp
def yes_input_id def yes_input_id
"#{input_id}-yes" "#{input_id}-yes"

View file

@ -1,22 +1,6 @@
# == Schema Information
#
# Table name: commentaires
#
# id :integer not null, primary key
# body :string
# discarded_at :datetime
# email :string
# created_at :datetime not null
# updated_at :datetime not null
# dossier_id :integer
# expert_id :bigint
# instructeur_id :bigint
#
class Commentaire < ApplicationRecord class Commentaire < ApplicationRecord
include Discard::Model include Discard::Model
belongs_to :dossier, inverse_of: :commentaires, touch: true, optional: false belongs_to :dossier, inverse_of: :commentaires, touch: true, optional: false
belongs_to :instructeur, inverse_of: :commentaires, optional: true belongs_to :instructeur, inverse_of: :commentaires, optional: true
belongs_to :expert, inverse_of: :commentaires, optional: true belongs_to :expert, inverse_of: :commentaires, optional: true
has_one :dossier_correction, inverse_of: :commentaire, dependent: :nullify has_one :dossier_correction, inverse_of: :commentaire, dependent: :nullify

View file

@ -76,7 +76,8 @@ module DossierCloneConcern
end end
def clone(user: nil, fork: false) def clone(user: nil, fork: false)
dossier_attributes = [:autorisation_donnees, :revision_id, :groupe_instructeur_id] dossier_attributes = [:autorisation_donnees, :revision_id]
dossier_attributes += [:groupe_instructeur_id] if fork
relationships = [:individual, :etablissement] relationships = [:individual, :etablissement]
cloned_champs = champs cloned_champs = champs
@ -95,7 +96,6 @@ module DossierCloneConcern
kopy.user = user || original.user kopy.user = user || original.user
kopy.state = Dossier.states.fetch(:brouillon) kopy.state = Dossier.states.fetch(:brouillon)
kopy.champs = cloned_champs.values.map do |(_, champ)| kopy.champs = cloned_champs.values.map do |(_, champ)|
champ.dossier = kopy champ.dossier = kopy
champ.parent = cloned_champs[champ.parent_id].second if champ.child? champ.parent = cloned_champs[champ.parent_id].second if champ.child?

Some files were not shown because too many files have changed in this diff Show more