From a79abfac799ddce28547bc2643a600313eaf41f8 Mon Sep 17 00:00:00 2001 From: simon lehericey Date: Thu, 28 Nov 2019 15:53:51 +0100 Subject: [PATCH 01/18] Add brouillon_close_to_expiration_notice_sent_at column to Dossier --- ...0191128081324_add_near_deletion_notice_send_to_dossier.rb | 5 +++++ db/schema.rb | 3 ++- 2 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 db/migrate/20191128081324_add_near_deletion_notice_send_to_dossier.rb diff --git a/db/migrate/20191128081324_add_near_deletion_notice_send_to_dossier.rb b/db/migrate/20191128081324_add_near_deletion_notice_send_to_dossier.rb new file mode 100644 index 000000000..45c159631 --- /dev/null +++ b/db/migrate/20191128081324_add_near_deletion_notice_send_to_dossier.rb @@ -0,0 +1,5 @@ +class AddNearDeletionNoticeSendToDossier < ActiveRecord::Migration[5.2] + def change + add_column :dossiers, :brouillon_close_to_expiration_notice_sent_at, :datetime + end +end diff --git a/db/schema.rb b/db/schema.rb index 009fa088b..cb42fecf4 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2019_11_14_113700) do +ActiveRecord::Schema.define(version: 2019_11_28_081324) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -252,6 +252,7 @@ ActiveRecord::Schema.define(version: 2019_11_14_113700) do t.text "search_terms" t.text "private_search_terms" t.bigint "groupe_instructeur_id" + t.datetime "brouillon_close_to_expiration_notice_sent_at" t.index "to_tsvector('french'::regconfig, (search_terms || private_search_terms))", name: "index_dossiers_on_search_terms_private_search_terms", using: :gin t.index "to_tsvector('french'::regconfig, search_terms)", name: "index_dossiers_on_search_terms", using: :gin t.index ["archived"], name: "index_dossiers_on_archived" From 03b7e81ca4344c3d492ea3b44b7349ae56d4f644 Mon Sep 17 00:00:00 2001 From: simon lehericey Date: Tue, 3 Dec 2019 10:31:10 +0100 Subject: [PATCH 02/18] Send deletion notice for near expired brouillon --- app/mailers/dossier_mailer.rb | 3 +++ app/models/dossier.rb | 22 ++++++++++++++++++++++ spec/models/dossier_spec.rb | 31 +++++++++++++++++++++++++++++++ 3 files changed, 56 insertions(+) diff --git a/app/mailers/dossier_mailer.rb b/app/mailers/dossier_mailer.rb index 4754e526a..4e8362622 100644 --- a/app/mailers/dossier_mailer.rb +++ b/app/mailers/dossier_mailer.rb @@ -69,4 +69,7 @@ class DossierMailer < ApplicationMailer format.html { render layout: 'mailers/notifications_layout' } end end + + def notify_brouillon_near_deletion(user, dossiers) + end end diff --git a/app/models/dossier.rb b/app/models/dossier.rb index ab339ebd3..7e3aa37cf 100644 --- a/app/models/dossier.rb +++ b/app/models/dossier.rb @@ -162,6 +162,13 @@ class Dossier < ApplicationRecord user: []) } + scope :brouillon_close_to_expiration, -> do + brouillon + .joins(:procedure) + .where("dossiers.created_at + (duree_conservation_dossiers_dans_ds * interval '1 month') - (1 * interval '1 month') <= now()") + end + scope :without_notice_sent, -> { where(brouillon_close_to_expiration_notice_sent_at: nil) } + scope :for_procedure, -> (procedure) { includes(:user, :groupe_instructeur).where(groupe_instructeurs: { procedure: procedure }) } scope :for_api_v2, -> { includes(procedure: [:administrateurs], etablissement: [], individual: []) } @@ -627,4 +634,19 @@ class Dossier < ApplicationRecord ) end end + + def self.send_brouillon_expiration_notices + brouillons = Dossier + .brouillon_close_to_expiration + .without_notice_sent + + brouillons + .includes(:user) + .group_by(&:user) + .each do |(user, dossiers)| + DossierMailer.notify_brouillon_near_deletion(user, dossiers).deliver_later + end + + brouillons.update_all(brouillon_close_to_expiration_notice_sent_at: Time.zone.now) + end end diff --git a/spec/models/dossier_spec.rb b/spec/models/dossier_spec.rb index 70e141121..26c96e067 100644 --- a/spec/models/dossier_spec.rb +++ b/spec/models/dossier_spec.rb @@ -1047,4 +1047,35 @@ describe Dossier do it { expect(Dossier.for_procedure(procedure_1)).to contain_exactly(dossier_1_1, dossier_1_2) } it { expect(Dossier.for_procedure(procedure_2)).to contain_exactly(dossier_2_1) } end + + describe '#send_brouillon_expiration_notices' do + let!(:procedure) { create(:procedure, duree_conservation_dossiers_dans_ds: 6) } + let!(:date_close_to_expiration) { Date.today - procedure.duree_conservation_dossiers_dans_ds.months + 1.month } + let!(:date_expired) { Date.today - procedure.duree_conservation_dossiers_dans_ds.months - 6.days } + let!(:date_not_expired) { Date.today - procedure.duree_conservation_dossiers_dans_ds.months + 2.months } + + before { Timecop.freeze(Time.zone.parse('12/12/2012').beginning_of_day) } + after { Timecop.return } + + context "Envoi de message pour les dossiers expirant dans - d'un mois" do + let!(:expired_brouillon) { create(:dossier, procedure: procedure, created_at: date_expired) } + 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) } + + before do + allow(DossierMailer).to receive(:notify_brouillon_near_deletion).and_return(double(deliver_later: nil)) + Dossier.send_brouillon_expiration_notices + end + + it 'verification de la creation de mail' do + expect(DossierMailer).to have_received(:notify_brouillon_near_deletion).with(brouillon_close_to_expiration.user, [brouillon_close_to_expiration]) + expect(DossierMailer).to have_received(:notify_brouillon_near_deletion).with(expired_brouillon.user, [expired_brouillon]) + end + + it 'Verification du changement d etat du champ' do + expect(brouillon_close_to_expiration.reload.brouillon_close_to_expiration_notice_sent_at).not_to be_nil + end + end + end end From 6391f7ff9ce271d7ff05707b33cf086c5a492f0f Mon Sep 17 00:00:00 2001 From: simon lehericey Date: Tue, 3 Dec 2019 15:51:16 +0100 Subject: [PATCH 03/18] Work on notify_near_deletion mailer --- app/mailers/dossier_mailer.rb | 4 ++++ .../notify_brouillon_near_deletion.html.haml | 16 ++++++++++++++++ .../notify_brouillon_near_deletion/fr.yml | 12 ++++++++++++ spec/mailers/dossier_mailer_spec.rb | 14 ++++++++++++++ spec/mailers/previews/dossier_mailer_preview.rb | 7 +++++++ 5 files changed, 53 insertions(+) create mode 100644 app/views/dossier_mailer/notify_brouillon_near_deletion.html.haml create mode 100644 config/locales/views/dossier_mailer/notify_brouillon_near_deletion/fr.yml diff --git a/app/mailers/dossier_mailer.rb b/app/mailers/dossier_mailer.rb index 4e8362622..2dfeff363 100644 --- a/app/mailers/dossier_mailer.rb +++ b/app/mailers/dossier_mailer.rb @@ -71,5 +71,9 @@ class DossierMailer < ApplicationMailer end def notify_brouillon_near_deletion(user, dossiers) + @subject = default_i18n_subject(count: dossiers.count) + @dossiers = dossiers + + mail(to: user.email, subject: @subject) end end diff --git a/app/views/dossier_mailer/notify_brouillon_near_deletion.html.haml b/app/views/dossier_mailer/notify_brouillon_near_deletion.html.haml new file mode 100644 index 000000000..fc972ddd0 --- /dev/null +++ b/app/views/dossier_mailer/notify_brouillon_near_deletion.html.haml @@ -0,0 +1,16 @@ +- content_for(:title, "#{@subject}") + +%p + Bonjour, + +%p + Afin de limiter la conservation de vos données personnelles, + = t('.automatic_dossier_deletion', count: @dossiers.count) + %ul + - @dossiers.each do |d| + %li= link_to("n° #{d.id} (#{d.procedure.libelle})", dossier_url(d)) + +%p + #{sanitize(t('.send_your_draft', count: @dossiers.count))}. Et sinon, vous n'avez rien à faire. + += render partial: "layouts/mailers/signature" diff --git a/config/locales/views/dossier_mailer/notify_brouillon_near_deletion/fr.yml b/config/locales/views/dossier_mailer/notify_brouillon_near_deletion/fr.yml new file mode 100644 index 000000000..a3cbf01dc --- /dev/null +++ b/config/locales/views/dossier_mailer/notify_brouillon_near_deletion/fr.yml @@ -0,0 +1,12 @@ +fr: + dossier_mailer: + notify_brouillon_near_deletion: + subject: + one: Un dossier en brouillon va bientôt être supprimé + other: Des dossiers en brouillon vont bientôt être supprimés + automatic_dossier_deletion: + one: "le dossier en brouillon suivant sera bientôt automatiquement supprimé :" + other: "les dossiers en brouillon suivant seront bientôt automatiquement supprimés :" + send_your_draft: + one: "Si vous souhaitez toujours déposer ce dossier, vous pouvez retrouver votre brouillon pendant encore un mois" + other: "Si vous souhaitez toujours déposer ces dossiers, vous pouvez retrouver vos brouillons pendant encore un mois" diff --git a/spec/mailers/dossier_mailer_spec.rb b/spec/mailers/dossier_mailer_spec.rb index d8c1b07f4..98baa357c 100644 --- a/spec/mailers/dossier_mailer_spec.rb +++ b/spec/mailers/dossier_mailer_spec.rb @@ -83,4 +83,18 @@ RSpec.describe DossierMailer, type: :mailer do it_behaves_like 'a dossier notification' end + + describe '.notify_brouillon_near_deletion' do + let(:dossier) { create(:dossier) } + + before do + duree = dossier.procedure.duree_conservation_dossiers_dans_ds + @date_suppression = dossier.created_at + duree.months + end + + subject { described_class.notify_brouillon_near_deletion(dossier.user, [dossier]) } + + it { expect(subject.body).to include("n° #{dossier.id} ") } + it { expect(subject.body).to include(dossier.procedure.libelle) } + end end diff --git a/spec/mailers/previews/dossier_mailer_preview.rb b/spec/mailers/previews/dossier_mailer_preview.rb index 8ac4eab87..6a6ec731a 100644 --- a/spec/mailers/previews/dossier_mailer_preview.rb +++ b/spec/mailers/previews/dossier_mailer_preview.rb @@ -20,6 +20,13 @@ class DossierMailerPreview < ActionMailer::Preview DossierMailer.notify_revert_to_instruction(dossier) end + def notify_brouillon_near_deletion + DossierMailer.notify_brouillon_near_deletion(User.new(email: "usager@example.com"), [dossier]) + end + + def notify_brouillons_near_deletion + DossierMailer.notify_brouillon_near_deletion(User.new(email: "usager@example.com"), [dossier, dossier]) + end private def deleted_dossier From 79bfb8b143a4a8945ac032f14f3ec3362518868b Mon Sep 17 00:00:00 2001 From: simon lehericey Date: Tue, 3 Dec 2019 10:46:44 +0100 Subject: [PATCH 04/18] Delete expired dossiers --- app/mailers/dossier_mailer.rb | 3 +++ app/models/dossier.rb | 20 ++++++++++++++++++++ spec/models/dossier_spec.rb | 22 ++++++++++++++++++++++ 3 files changed, 45 insertions(+) diff --git a/app/mailers/dossier_mailer.rb b/app/mailers/dossier_mailer.rb index 2dfeff363..245ec7369 100644 --- a/app/mailers/dossier_mailer.rb +++ b/app/mailers/dossier_mailer.rb @@ -76,4 +76,7 @@ class DossierMailer < ApplicationMailer mail(to: user.email, subject: @subject) end + + def notify_brouillon_deletion(user, dossiers) + end end diff --git a/app/models/dossier.rb b/app/models/dossier.rb index 7e3aa37cf..8fe10c8b4 100644 --- a/app/models/dossier.rb +++ b/app/models/dossier.rb @@ -18,6 +18,8 @@ class Dossier < ApplicationRecord TAILLE_MAX_ZIP = 50.megabytes + DRAFT_EXPIRATION = 1.month + 5.days + has_one :etablissement, dependent: :destroy has_one :individual, dependent: :destroy has_one :attestation, dependent: :destroy @@ -167,6 +169,7 @@ class Dossier < ApplicationRecord .joins(:procedure) .where("dossiers.created_at + (duree_conservation_dossiers_dans_ds * interval '1 month') - (1 * interval '1 month') <= now()") end + scope :expired_brouillon, -> { brouillon.where("brouillon_close_to_expiration_notice_sent_at < ?", (Time.zone.now - (DRAFT_EXPIRATION))) } scope :without_notice_sent, -> { where(brouillon_close_to_expiration_notice_sent_at: nil) } scope :for_procedure, -> (procedure) { includes(:user, :groupe_instructeur).where(groupe_instructeurs: { procedure: procedure }) } @@ -649,4 +652,21 @@ class Dossier < ApplicationRecord brouillons.update_all(brouillon_close_to_expiration_notice_sent_at: Time.zone.now) end + + def self.destroy_brouillons_and_notify + expired_brouillons = Dossier.expired_brouillon + + expired_brouillons + .includes(:user) + .group_by(&:user) + .each do |(user, dossiers)| + + DossierMailer.notify_brouillon_deletion(user, dossiers).deliver_later + + dossiers.each do |dossier| + DeletedDossier.create_from_dossier(dossier) + dossier.destroy + end + end + end end diff --git a/spec/models/dossier_spec.rb b/spec/models/dossier_spec.rb index 26c96e067..f7f0b28e3 100644 --- a/spec/models/dossier_spec.rb +++ b/spec/models/dossier_spec.rb @@ -1078,4 +1078,26 @@ describe Dossier do end end end + + describe '#destroy_brouillons_and_notify' do + let!(:today) { Time.zone.now.at_midnight } + + let!(:expired_brouillon) { create(:dossier, brouillon_close_to_expiration_notice_sent_at: today - (Dossier::DRAFT_EXPIRATION + 1.day)) } + let!(:other_brouillon) { create(:dossier, brouillon_close_to_expiration_notice_sent_at: today - (Dossier::DRAFT_EXPIRATION - 1.day)) } + + before do + allow(DossierMailer).to receive(:notify_brouillon_deletion).and_return(double(deliver_later: nil)) + Dossier.destroy_brouillons_and_notify + end + + it 'notifies deletion' do + expect(DossierMailer).to have_received(:notify_brouillon_deletion).once + expect(DossierMailer).to have_received(:notify_brouillon_deletion).with(expired_brouillon.user, [expired_brouillon]) + end + + it 'deletes the expired brouillon' do + expect(DeletedDossier.find_by(dossier_id: expired_brouillon.id)).to be_present + expect { expired_brouillon.reload }.to raise_error(ActiveRecord::RecordNotFound) + end + end end From 006e426a11a40a2b50348d1d006547d47245445a Mon Sep 17 00:00:00 2001 From: simon lehericey Date: Thu, 28 Nov 2019 18:03:23 +0100 Subject: [PATCH 05/18] Work on deletion mail --- app/mailers/dossier_mailer.rb | 6 +++++- app/models/dossier.rb | 9 +++++++-- .../notify_brouillon_deletion.html.haml | 12 ++++++++++++ .../dossier_mailer/notify_brouillon_deletion/fr.yml | 9 +++++++++ spec/mailers/dossier_mailer_spec.rb | 9 +++++++++ spec/mailers/previews/dossier_mailer_preview.rb | 10 ++++++++++ spec/models/dossier_spec.rb | 2 +- 7 files changed, 53 insertions(+), 4 deletions(-) create mode 100644 app/views/dossier_mailer/notify_brouillon_deletion.html.haml create mode 100644 config/locales/views/dossier_mailer/notify_brouillon_deletion/fr.yml diff --git a/app/mailers/dossier_mailer.rb b/app/mailers/dossier_mailer.rb index 245ec7369..8813730fe 100644 --- a/app/mailers/dossier_mailer.rb +++ b/app/mailers/dossier_mailer.rb @@ -77,6 +77,10 @@ class DossierMailer < ApplicationMailer mail(to: user.email, subject: @subject) end - def notify_brouillon_deletion(user, dossiers) + def notify_brouillon_deletion(user, dossier_hashes) + @subject = default_i18n_subject(count: dossier_hashes.count) + @dossier_hashes = dossier_hashes + + mail(to: user.email, subject: @subject) end end diff --git a/app/models/dossier.rb b/app/models/dossier.rb index 8fe10c8b4..3075a1f32 100644 --- a/app/models/dossier.rb +++ b/app/models/dossier.rb @@ -587,6 +587,10 @@ class Dossier < ApplicationRecord Dossier.where(id: champs.filter(&:dossier_link?).map(&:value).compact) end + def hash_for_deletion_mail + { id: self.id, procedure_libelle: self.procedure.libelle } + end + private def log_dossier_operation(author, operation, subject = nil) @@ -657,11 +661,12 @@ class Dossier < ApplicationRecord expired_brouillons = Dossier.expired_brouillon expired_brouillons - .includes(:user) + .includes(:procedure, :user) .group_by(&:user) .each do |(user, dossiers)| - DossierMailer.notify_brouillon_deletion(user, dossiers).deliver_later + dossier_hashes = dossiers.map(&:hash_for_deletion_mail) + DossierMailer.notify_brouillon_deletion(user, dossier_hashes).deliver_later dossiers.each do |dossier| DeletedDossier.create_from_dossier(dossier) diff --git a/app/views/dossier_mailer/notify_brouillon_deletion.html.haml b/app/views/dossier_mailer/notify_brouillon_deletion.html.haml new file mode 100644 index 000000000..7a28ec3f7 --- /dev/null +++ b/app/views/dossier_mailer/notify_brouillon_deletion.html.haml @@ -0,0 +1,12 @@ +- content_for(:title, "#{@subject}") + +%p + Bonjour, + +%p= t('.automatic_dossier_deletion', count: @dossier_hashes.count) + +%ul + - @dossier_hashes.each do |d| + %li n° #{d[:id]} (#{d[:procedure_libelle]}) + += render partial: "layouts/mailers/signature" diff --git a/config/locales/views/dossier_mailer/notify_brouillon_deletion/fr.yml b/config/locales/views/dossier_mailer/notify_brouillon_deletion/fr.yml new file mode 100644 index 000000000..291dffc23 --- /dev/null +++ b/config/locales/views/dossier_mailer/notify_brouillon_deletion/fr.yml @@ -0,0 +1,9 @@ +fr: + dossier_mailer: + notify_brouillon_deletion: + subject: + one: "Un dossier en brouillon a été supprimé automatiquement" + other: "Des dossiers en brouillon ont été supprimés automatiquement" + automatic_dossier_deletion: + one: "Le délai maximum de conservation du dossier en brouillon suivant a été atteint, il a donc été supprimé :" + other: "Le délai maximum de conservation des dossiers en brouillon suivants a été atteint, ils ont donc été supprimés :" diff --git a/spec/mailers/dossier_mailer_spec.rb b/spec/mailers/dossier_mailer_spec.rb index 98baa357c..38c121a51 100644 --- a/spec/mailers/dossier_mailer_spec.rb +++ b/spec/mailers/dossier_mailer_spec.rb @@ -97,4 +97,13 @@ RSpec.describe DossierMailer, type: :mailer do it { expect(subject.body).to include("n° #{dossier.id} ") } it { expect(subject.body).to include(dossier.procedure.libelle) } end + + describe '.notify_brouillon_deletion' do + let(:dossier) { create(:dossier) } + + subject { described_class.notify_brouillon_deletion(dossier.user, [dossier.hash_for_deletion_mail]) } + + it { expect(subject.subject).to eq("Un dossier en brouillon a été supprimé automatiquement") } + it { expect(subject.body).to include("n° #{dossier.id} (#{dossier.procedure.libelle})") } + end end diff --git a/spec/mailers/previews/dossier_mailer_preview.rb b/spec/mailers/previews/dossier_mailer_preview.rb index 6a6ec731a..f1fc2dab7 100644 --- a/spec/mailers/previews/dossier_mailer_preview.rb +++ b/spec/mailers/previews/dossier_mailer_preview.rb @@ -27,6 +27,16 @@ class DossierMailerPreview < ActionMailer::Preview def notify_brouillons_near_deletion DossierMailer.notify_brouillon_near_deletion(User.new(email: "usager@example.com"), [dossier, dossier]) end + + def notify_brouillon_deletion + DossierMailer.notify_brouillon_deletion(User.new(email: "usager@example.com"), [dossier.hash_for_deletion_mail]) + end + + def notify_brouillons_deletion + dossier_hashes = [dossier, dossier].map(&:hash_for_deletion_mail) + DossierMailer.notify_brouillon_deletion(User.new(email: "usager@example.com"), dossier_hashes) + end + private def deleted_dossier diff --git a/spec/models/dossier_spec.rb b/spec/models/dossier_spec.rb index f7f0b28e3..e0e31b9b4 100644 --- a/spec/models/dossier_spec.rb +++ b/spec/models/dossier_spec.rb @@ -1092,7 +1092,7 @@ describe Dossier do it 'notifies deletion' do expect(DossierMailer).to have_received(:notify_brouillon_deletion).once - expect(DossierMailer).to have_received(:notify_brouillon_deletion).with(expired_brouillon.user, [expired_brouillon]) + expect(DossierMailer).to have_received(:notify_brouillon_deletion).with(expired_brouillon.user, [expired_brouillon.hash_for_deletion_mail]) end it 'deletes the expired brouillon' do From 508ba8f11624918a18f470c47e719b6ce42de5f6 Mon Sep 17 00:00:00 2001 From: simon lehericey Date: Tue, 3 Dec 2019 15:20:06 +0100 Subject: [PATCH 06/18] Add seek_and_destroy_expired_dossier --- README.md | 1 + app/jobs/seek_and_destroy_expired_dossiers_job.rb | 8 ++++++++ .../20191203142402_enable_seek_and_destroy_job.rake | 7 +++++++ 3 files changed, 16 insertions(+) create mode 100644 app/jobs/seek_and_destroy_expired_dossiers_job.rb create mode 100644 lib/tasks/deployment/20191203142402_enable_seek_and_destroy_job.rake diff --git a/README.md b/README.md index 94a643ecf..d64b44dbd 100644 --- a/README.md +++ b/README.md @@ -77,6 +77,7 @@ En local, un utilisateur de test est créé automatiquement, avec les identifian InstructeurEmailNotificationJob.set(cron: "0 10 * * 1,2,3,4,5,6").perform_later PurgeUnattachedBlobsJob.set(cron: "0 0 * * *").perform_later OperationsSignatureJob.set(cron: "0 6 * * *").perform_later + SeekAndDestroyExpiredDossiersJob.set(cron: "0 7 * * *").perform_later ### Voir les emails envoyés en local diff --git a/app/jobs/seek_and_destroy_expired_dossiers_job.rb b/app/jobs/seek_and_destroy_expired_dossiers_job.rb new file mode 100644 index 000000000..d720873bf --- /dev/null +++ b/app/jobs/seek_and_destroy_expired_dossiers_job.rb @@ -0,0 +1,8 @@ +class SeekAndDestroyExpiredDossiersJob < ApplicationJob + queue_as :cron + + def perform(*args) + Dossier.send_brouillon_expiration_notices + Dossier.destroy_brouillons_and_notify + end +end diff --git a/lib/tasks/deployment/20191203142402_enable_seek_and_destroy_job.rake b/lib/tasks/deployment/20191203142402_enable_seek_and_destroy_job.rake new file mode 100644 index 000000000..5cb1f9c65 --- /dev/null +++ b/lib/tasks/deployment/20191203142402_enable_seek_and_destroy_job.rake @@ -0,0 +1,7 @@ +namespace :after_party do + desc 'Deployment task: enable_seek_and_destroy_job' + task enable_seek_and_destroy_job: :environment do + SeekAndDestroyExpiredDossiersJob.set(cron: "0 7 * * *").perform_later + AfterParty::TaskRecord.create version: '20191203142402' + end +end From 0b518844dc5824341fa9c505df0344bd50ca325c Mon Sep 17 00:00:00 2001 From: Christophe Robillard Date: Wed, 13 Nov 2019 21:20:24 +0100 Subject: [PATCH 07/18] explique pourquoi un tel valide est important --- app/views/new_administrateur/services/_form.html.haml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/views/new_administrateur/services/_form.html.haml b/app/views/new_administrateur/services/_form.html.haml index b5d2010ae..a63a8b1a3 100644 --- a/app/views/new_administrateur/services/_form.html.haml +++ b/app/views/new_administrateur/services/_form.html.haml @@ -25,6 +25,7 @@ %span.mandatory * = f.email_field :email, placeholder: 'contact@mon-service.fr', required: true + %p.explication Les usagers doivent pouvoir vous contacter en cas de question sur la démarche. Indiquez le numéro de téléphone du service le plus à même de fournir des réponses pertinentes à vos usagers. = f.label :telephone do Numéro de téléphone %span.mandatory * From b60aff84687daf9cf0b17749dc5dd7ad7311fd8b Mon Sep 17 00:00:00 2001 From: Christophe Robillard Date: Thu, 14 Nov 2019 16:09:43 +0100 Subject: [PATCH 08/18] =?UTF-8?q?explique=20en=20d=C3=A9tail=20l'importanc?= =?UTF-8?q?e=20de=20renseigner=20des=20info=20de=20contact=20valides?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/views/new_administrateur/services/_form.html.haml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/app/views/new_administrateur/services/_form.html.haml b/app/views/new_administrateur/services/_form.html.haml index a63a8b1a3..b647bb87b 100644 --- a/app/views/new_administrateur/services/_form.html.haml +++ b/app/views/new_administrateur/services/_form.html.haml @@ -18,14 +18,20 @@ %h2.header-section Informations de contact - %p.explication Ces informations seront visibles par les utilisateurs du formulaire. + %p.explication + Votre démarche sera hébergée par demarche-simplifiees.fr – mais nous ne pouvons pas assurer le support des démarches. Et malgré la dématérialisation, les usagers se poseront parfois des questions légitimes sur le processus administratif. + Il est donc important que les usagers puissent contacter votre service s'ils ont des questions sur votre démarche. + + Ces informations seront visibles par les utilisateurs de la démarche, affichées dans le menu "Aide". + + %p.explication Indiquez une adresse mail en capacité de répondre à l'usager. = f.label :email do Adresse email %span.mandatory * = f.email_field :email, placeholder: 'contact@mon-service.fr', required: true - %p.explication Les usagers doivent pouvoir vous contacter en cas de question sur la démarche. Indiquez le numéro de téléphone du service le plus à même de fournir des réponses pertinentes à vos usagers. + %p.explication Indiquez le numéro de téléphone du service le plus à même de fournir des réponses pertinentes à vos usagers. = f.label :telephone do Numéro de téléphone %span.mandatory * From 4e7c779116c7ffdd00297947a1361634d377b44a Mon Sep 17 00:00:00 2001 From: Christophe Robillard Date: Thu, 14 Nov 2019 18:31:14 +0100 Subject: [PATCH 09/18] =?UTF-8?q?refuse=20les=20num=C3=A9ros=20de=20tel=20?= =?UTF-8?q?invalides?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit rend facultatif les numéros de téléphone --- Gemfile | 1 + Gemfile.lock | 2 ++ app/models/service.rb | 2 +- .../services/_form.html.haml | 2 +- config/initializers/phonelib.rb | 2 ++ spec/models/service_spec.rb | 29 ++++++++++++++++++- 6 files changed, 35 insertions(+), 3 deletions(-) create mode 100644 config/initializers/phonelib.rb diff --git a/Gemfile b/Gemfile index 20ddf1137..81ac050d8 100644 --- a/Gemfile +++ b/Gemfile @@ -47,6 +47,7 @@ gem 'omniauth-rails_csrf_protection', '~> 0.1' gem 'openid_connect' gem 'openstack' gem 'pg' +gem 'phonelib' gem 'prawn' # PDF Generation gem 'prawn_rails' gem 'premailer-rails' diff --git a/Gemfile.lock b/Gemfile.lock index 9a923b78b..3fccfeea8 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -420,6 +420,7 @@ GEM ast (~> 2.4.0) pdf-core (0.7.0) pg (1.1.3) + phonelib (0.6.39) powerpack (0.1.2) prawn (2.2.2) pdf-core (~> 0.7.0) @@ -767,6 +768,7 @@ DEPENDENCIES openid_connect openstack pg + phonelib prawn prawn_rails premailer-rails diff --git a/app/models/service.rb b/app/models/service.rb index 8dac51ba7..ad9bf651f 100644 --- a/app/models/service.rb +++ b/app/models/service.rb @@ -20,7 +20,7 @@ class Service < ApplicationRecord validates :organisme, presence: { message: 'doit être renseigné' }, allow_nil: false validates :type_organisme, presence: { message: 'doit être renseigné' }, allow_nil: false validates :email, presence: { message: 'doit être renseigné' }, allow_nil: false - validates :telephone, presence: { message: 'doit être renseigné' }, allow_nil: false + validates :telephone, phone: { possible: true, allow_blank: true } validates :horaires, presence: { message: 'doivent être renseignés' }, allow_nil: false validates :adresse, presence: { message: 'doit être renseignée' }, allow_nil: false validates :administrateur, presence: { message: 'doit être renseigné' }, allow_nil: false diff --git a/app/views/new_administrateur/services/_form.html.haml b/app/views/new_administrateur/services/_form.html.haml index b647bb87b..83b294585 100644 --- a/app/views/new_administrateur/services/_form.html.haml +++ b/app/views/new_administrateur/services/_form.html.haml @@ -21,7 +21,7 @@ %p.explication Votre démarche sera hébergée par demarche-simplifiees.fr – mais nous ne pouvons pas assurer le support des démarches. Et malgré la dématérialisation, les usagers se poseront parfois des questions légitimes sur le processus administratif. - Il est donc important que les usagers puissent contacter votre service s'ils ont des questions sur votre démarche. + Il est donc important que les usagers puissent vous contacter s'ils ont des questions sur votre démarche. Ces informations seront visibles par les utilisateurs de la démarche, affichées dans le menu "Aide". diff --git a/config/initializers/phonelib.rb b/config/initializers/phonelib.rb new file mode 100644 index 000000000..f76a2d441 --- /dev/null +++ b/config/initializers/phonelib.rb @@ -0,0 +1,2 @@ +Phonelib.default_country = "FR" +Phonelib.parse_special = true diff --git a/spec/models/service_spec.rb b/spec/models/service_spec.rb index e34f56dac..b8a4c479a 100644 --- a/spec/models/service_spec.rb +++ b/spec/models/service_spec.rb @@ -7,7 +7,7 @@ describe Service, type: :model do organisme: 'mairie des iles', type_organisme: Service.type_organismes.fetch(:association), email: 'super@email.com', - telephone: '1212202', + telephone: '012345678', horaires: 'du lundi au vendredi', adresse: '12 rue des schtroumpfs', administrateur_id: administrateur.id @@ -16,6 +16,33 @@ describe Service, type: :model do it { expect(Service.new(params).valid?).to be_truthy } + it 'should forbid invalid phone numbers' do + service = Service.create(params) + invalid_phone_numbers = ["1", "Néant", "01 60 50 40 30 20"] + + invalid_phone_numbers.each do |tel| + service.telephone = tel + expect(service.valid?).to be_falsey + end + end + + it 'should accept no phone numbers' do + service = Service.create(params) + service.telephone = nil + + expect(service.valid?).to be_truthy + end + + it 'should accept valid phone numbers' do + service = Service.create(params) + valid_phone_numbers = ["3646", "273115", "0160376983", "01 60 50 40 30 ", "+33160504030"] + + valid_phone_numbers.each do |tel| + service.telephone = tel + expect(service.valid?).to be_truthy + end + end + context 'when a first service exists' do before { Service.create(params) } From 8ee6657f1e257c5308c07fce8d9ebe6612f16b16 Mon Sep 17 00:00:00 2001 From: Christophe Robillard Date: Tue, 19 Nov 2019 10:35:07 +0100 Subject: [PATCH 10/18] =?UTF-8?q?rend=20le=20champ=20telephone=20optionnel?= =?UTF-8?q?=20c=C3=B4t=C3=A9=20client?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/views/new_administrateur/services/_form.html.haml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/views/new_administrateur/services/_form.html.haml b/app/views/new_administrateur/services/_form.html.haml index 83b294585..30e0ef065 100644 --- a/app/views/new_administrateur/services/_form.html.haml +++ b/app/views/new_administrateur/services/_form.html.haml @@ -34,8 +34,7 @@ %p.explication Indiquez le numéro de téléphone du service le plus à même de fournir des réponses pertinentes à vos usagers. = f.label :telephone do Numéro de téléphone - %span.mandatory * - = f.telephone_field :telephone, placeholder: '04 12 24 42 37', required: true + = f.telephone_field :telephone, placeholder: '04 12 24 42 37' = f.label :horaires do Horaires From 6eb36482bc5d8ddf26bdfb7ec90f7bb5ea35c4d9 Mon Sep 17 00:00:00 2001 From: Christophe Robillard Date: Tue, 19 Nov 2019 10:46:12 +0100 Subject: [PATCH 11/18] rend lisible l'explication d'un contact valide --- app/views/new_administrateur/services/_form.html.haml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/new_administrateur/services/_form.html.haml b/app/views/new_administrateur/services/_form.html.haml index 30e0ef065..623e18974 100644 --- a/app/views/new_administrateur/services/_form.html.haml +++ b/app/views/new_administrateur/services/_form.html.haml @@ -20,9 +20,9 @@ %p.explication Votre démarche sera hébergée par demarche-simplifiees.fr – mais nous ne pouvons pas assurer le support des démarches. Et malgré la dématérialisation, les usagers se poseront parfois des questions légitimes sur le processus administratif. - + %br Il est donc important que les usagers puissent vous contacter s'ils ont des questions sur votre démarche. - + %br Ces informations seront visibles par les utilisateurs de la démarche, affichées dans le menu "Aide". %p.explication Indiquez une adresse mail en capacité de répondre à l'usager. From 77b647fe25f5f6c57cc2e2d679fe1dcd3d9298c2 Mon Sep 17 00:00:00 2001 From: Christophe Robillard Date: Mon, 25 Nov 2019 21:22:32 +0100 Subject: [PATCH 12/18] N'affiche pas un telephone inexistant d'un service --- app/views/users/_procedure_footer.html.haml | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/app/views/users/_procedure_footer.html.haml b/app/views/users/_procedure_footer.html.haml index ca96a0316..b225927c6 100644 --- a/app/views/users/_procedure_footer.html.haml +++ b/app/views/users/_procedure_footer.html.haml @@ -24,13 +24,14 @@ Par email : = link_to service.email, "mailto:#{service.email}" - %li - Par téléphone : - = link_to service.telephone, service.telephone_url + - if service.telephone.present? + %li + Par téléphone : + = link_to service.telephone, service.telephone_url - %li - - horaires = "Horaires : #{formatted_horaires(service.horaires)}" - = simple_format(horaires, {}, wrapper_tag: 'span') + %li + - horaires = "Horaires : #{formatted_horaires(service.horaires)}" + = simple_format(horaires, {}, wrapper_tag: 'span') - politiques = politiques_conservation_de_donnees(procedure) From e35dcad5ae1dc5e284f701ed36e35604945c4f8f Mon Sep 17 00:00:00 2001 From: Christophe Robillard Date: Thu, 28 Nov 2019 11:55:10 +0100 Subject: [PATCH 13/18] =?UTF-8?q?rend=20le=20t=C3=A9l=C3=A9phone=20obligat?= =?UTF-8?q?oire=20c=C3=B4t=C3=A9=20client?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/views/new_administrateur/services/_form.html.haml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/views/new_administrateur/services/_form.html.haml b/app/views/new_administrateur/services/_form.html.haml index 623e18974..4b547cc5c 100644 --- a/app/views/new_administrateur/services/_form.html.haml +++ b/app/views/new_administrateur/services/_form.html.haml @@ -33,8 +33,9 @@ %p.explication Indiquez le numéro de téléphone du service le plus à même de fournir des réponses pertinentes à vos usagers. = f.label :telephone do + %span.mandatory * Numéro de téléphone - = f.telephone_field :telephone, placeholder: '04 12 24 42 37' + = f.telephone_field :telephone, placeholder: '04 12 24 42 37', required: true = f.label :horaires do Horaires From e429c79eb1c57fb37a2bde00495de152395ad10e Mon Sep 17 00:00:00 2001 From: Paul Chavard Date: Wed, 4 Dec 2019 12:06:51 +0100 Subject: [PATCH 14/18] =?UTF-8?q?Allow=20administrators=20to=20set=20thems?= =?UTF-8?q?elves=20d=C3=A9marches=20as=20d=C3=A9claratives?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../new_administrateur/procedures_controller.rb | 2 +- app/models/procedure.rb | 6 ++++++ .../procedures/_informations.html.haml | 10 ++++++++++ config/locales/models/procedure/fr.yml | 2 ++ 4 files changed, 19 insertions(+), 1 deletion(-) diff --git a/app/controllers/new_administrateur/procedures_controller.rb b/app/controllers/new_administrateur/procedures_controller.rb index b9236fdb6..fbcdce08e 100644 --- a/app/controllers/new_administrateur/procedures_controller.rb +++ b/app/controllers/new_administrateur/procedures_controller.rb @@ -68,7 +68,7 @@ module NewAdministrateur end def procedure_params - editable_params = [:libelle, :description, :organisation, :direction, :lien_site_web, :cadre_juridique, :deliberation, :notice, :web_hook_url, :euro_flag, :logo, :auto_archive_on, :monavis_embed] + editable_params = [:libelle, :description, :organisation, :direction, :lien_site_web, :cadre_juridique, :deliberation, :notice, :web_hook_url, :declarative_with_state, :euro_flag, :logo, :auto_archive_on, :monavis_embed] permited_params = if @procedure&.locked? params.require(:procedure).permit(*editable_params) else diff --git a/app/models/procedure.rb b/app/models/procedure.rb index 257490810..82120fe6b 100644 --- a/app/models/procedure.rb +++ b/app/models/procedure.rb @@ -305,6 +305,12 @@ class Procedure < ApplicationRecord declarative_with_state == Procedure.declarative_with_states.fetch(:accepte) end + def self.declarative_attributes_for_select + declarative_with_states.map do |state, _| + [I18n.t("activerecord.attributes.#{model_name.i18n_key}.declarative_with_state/#{state}"), state] + end + end + # Warning: dossier after_save build_default_champs must be removed # to save a dossier created from this method def new_dossier diff --git a/app/views/new_administrateur/procedures/_informations.html.haml b/app/views/new_administrateur/procedures/_informations.html.haml index a793479a4..2906bf359 100644 --- a/app/views/new_administrateur/procedures/_informations.html.haml +++ b/app/views/new_administrateur/procedures/_informations.html.haml @@ -113,3 +113,13 @@ %p.explication La clôture automatique suspend la publication de la démarche et entraîne le passage de tous les dossiers "en construction" (c'est à dire ceux qui ont été déposés), au statut "en instruction", ce qui ne permet plus aux usagers de les modifier. + + = f.label :declarative_with_state do + Démarche déclarative + = f.select :declarative_with_state, Procedure.declarative_attributes_for_select, { include_blank: true }, class: 'form-control' + + %p.explication + Par défaut, une démarche n'est pas déclarative; à son dépot, un dossier est «en construction». Vous pouvez choisir de la rendre déclarative, afin que tous les dossiers déposés soient immédiatement au statut "en instruction" en "accepté". + %br + %br + Dans le cadre d'une démarche déclarative, au dépot, seul l'email associé à l'état choisi est envoyé. (ex: démarche déclarative «accepté»: envoi uniquement de l'email d'acceptation) diff --git a/config/locales/models/procedure/fr.yml b/config/locales/models/procedure/fr.yml index b441c4524..4f58b71ba 100644 --- a/config/locales/models/procedure/fr.yml +++ b/config/locales/models/procedure/fr.yml @@ -14,3 +14,5 @@ fr: aasm_state/publiee: Publiée aasm_state/close: Close aasm_state/hidden: Suprimée + declarative_with_state/en_instruction: En instruction + declarative_with_state/accepte: Accepté From 34afc448136189e146996575ea752fd254b4824a Mon Sep 17 00:00:00 2001 From: Paul Chavard Date: Wed, 4 Dec 2019 12:07:21 +0100 Subject: [PATCH 15/18] =?UTF-8?q?Expose=20declarative=20d=C3=A9marche=20st?= =?UTF-8?q?ate=20in=20GraphQL?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/graphql/schema.graphql | 17 +++++++++++++++++ app/graphql/types/demarche_type.rb | 9 +++++++++ 2 files changed, 26 insertions(+) diff --git a/app/graphql/schema.graphql b/app/graphql/schema.graphql index 100e53bd8..6ab7884ec 100644 --- a/app/graphql/schema.graphql +++ b/app/graphql/schema.graphql @@ -184,6 +184,11 @@ type Demarche { """ datePublication: ISO8601DateTime! + """ + L'état de dossier pour une démarche déclarative + """ + declarative: DossierDeclarativeState + """ Description de la démarche. """ @@ -428,6 +433,18 @@ type DossierConnection { pageInfo: PageInfo! } +enum DossierDeclarativeState { + """ + Accepté + """ + accepte + + """ + En instruction + """ + en_instruction +} + """ An edge in a connection. """ diff --git a/app/graphql/types/demarche_type.rb b/app/graphql/types/demarche_type.rb index 6b464370c..894062b7f 100644 --- a/app/graphql/types/demarche_type.rb +++ b/app/graphql/types/demarche_type.rb @@ -6,6 +6,14 @@ module Types end end + class DossierDeclarativeState < Types::BaseEnum + Procedure.declarative_with_states.each do |symbol_name, string_name| + value(string_name, + I18n.t("declarative_with_state/#{string_name}", scope: [:activerecord, :attributes, :procedure]), + value: symbol_name) + end + end + description "Une demarche" global_id_field :id @@ -13,6 +21,7 @@ module Types field :title, String, "Le titre de la démarche.", null: false, method: :libelle field :description, String, "Description de la démarche.", null: false field :state, DemarcheState, "L'état de la démarche.", null: false + field :declarative, DossierDeclarativeState, "L'état de dossier pour une démarche déclarative", null: true, method: :declarative_with_state field :date_creation, GraphQL::Types::ISO8601DateTime, "Date de la création.", null: false, method: :created_at field :date_publication, GraphQL::Types::ISO8601DateTime, "Date de la publication.", null: false, method: :published_at From fd42fafcb4a0a7787dcb2ab25f2facef6e8171d2 Mon Sep 17 00:00:00 2001 From: Paul Chavard Date: Thu, 21 Nov 2019 18:52:07 +0100 Subject: [PATCH 16/18] [GraphQL]: informations du demandeur du dossier --- Gemfile.lock | 2 +- app/graphql/api/v2/schema.rb | 9 +- app/graphql/schema.graphql | 77 +++++++- app/graphql/types/champ_type.rb | 2 + .../types/champs/civilite_champ_type.rb | 7 + app/graphql/types/civilite.rb | 6 + app/graphql/types/demandeur_type.rb | 18 ++ app/graphql/types/dossier_type.rb | 10 ++ app/graphql/types/personne_morale_type.rb | 50 +++++- app/graphql/types/personne_physique_type.rb | 10 ++ .../api/v2/graphql_controller_spec.rb | 169 ++++++++++++------ 11 files changed, 305 insertions(+), 55 deletions(-) create mode 100644 app/graphql/types/champs/civilite_champ_type.rb create mode 100644 app/graphql/types/civilite.rb create mode 100644 app/graphql/types/demandeur_type.rb create mode 100644 app/graphql/types/personne_physique_type.rb diff --git a/Gemfile.lock b/Gemfile.lock index 3fccfeea8..423259e6f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -241,7 +241,7 @@ GEM graphiql-rails (1.7.0) railties sprockets-rails - graphql (1.9.10) + graphql (1.9.15) graphql-batch (0.4.1) graphql (>= 1.3, < 2) promise.rb (~> 0.7.2) diff --git a/app/graphql/api/v2/schema.rb b/app/graphql/api/v2/schema.rb index 390d60c44..26089ef11 100644 --- a/app/graphql/api/v2/schema.rb +++ b/app/graphql/api/v2/schema.rb @@ -26,6 +26,10 @@ class Api::V2::Schema < GraphQL::Schema Types::MessageType when Instructeur, User Types::ProfileType + when Individual + Types::PersonnePhysiqueType + when Etablissement + Types::PersonneMoraleType else raise GraphQL::ExecutionError.new("Unexpected object: #{obj}") end @@ -33,6 +37,7 @@ class Api::V2::Schema < GraphQL::Schema orphan_types Types::Champs::CarteChampType, Types::Champs::CheckboxChampType, + Types::Champs::CiviliteChampType, Types::Champs::DateChampType, Types::Champs::DecimalNumberChampType, Types::Champs::DossierLinkChampType, @@ -45,7 +50,9 @@ class Api::V2::Schema < GraphQL::Schema Types::Champs::TextChampType, Types::GeoAreas::ParcelleCadastraleType, Types::GeoAreas::QuartierPrioritaireType, - Types::GeoAreas::SelectionUtilisateurType + Types::GeoAreas::SelectionUtilisateurType, + Types::PersonneMoraleType, + Types::PersonnePhysiqueType def self.unauthorized_object(error) # Add a top-level error to the response instead of returning nil: diff --git a/app/graphql/schema.graphql b/app/graphql/schema.graphql index 6ab7884ec..5740b29ec 100644 --- a/app/graphql/schema.graphql +++ b/app/graphql/schema.graphql @@ -1,3 +1,12 @@ +type Association { + dateCreation: ISO8601Date! + dateDeclaration: ISO8601Date! + datePublication: ISO8601Date! + objet: String! + rna: String! + titre: String! +} + type Avis { attachmentUrl: URL dateQuestion: ISO8601DateTime! @@ -76,6 +85,33 @@ type CheckboxChamp implements Champ { value: Boolean! } +enum Civilite { + """ + Monsieur + """ + M + + """ + Madame + """ + Mme +} + +type CiviliteChamp implements Champ { + id: ID! + + """ + Libellé du champ. + """ + label: String! + + """ + La valeur du champ sous forme texte. + """ + stringValue: String + value: Civilite +} + """ GeoJSON coordinates """ @@ -157,6 +193,10 @@ type DecimalNumberChamp implements Champ { value: Float } +interface Demandeur { + id: ID! +} + """ Une demarche """ @@ -327,6 +367,7 @@ type Dossier { Date de traitement. """ dateTraitement: ISO8601DateTime + demandeur: Demandeur! id: ID! instructeurs: [Profile!]! messages: [Message!]! @@ -595,6 +636,22 @@ enum DossierState { sans_suite } +type Entreprise { + capitalSocial: Int! + codeEffectifEntreprise: String! + dateCreation: ISO8601Date! + formeJuridique: String! + formeJuridiqueCode: String! + inlineAdresse: String! + nom: String! + nomCommercial: String! + numeroTvaIntracommunautaire: String! + prenom: String! + raisonSociale: String! + siren: String! + siretSiegeSocial: String! +} + interface GeoArea { geometry: GeoJSON! id: ID! @@ -632,6 +689,11 @@ type GroupeInstructeur { label: String! } +""" +An ISO 8601-encoded date +""" +scalar ISO8601Date + """ An ISO 8601-encoded datetime """ @@ -775,21 +837,32 @@ type ParcelleCadastrale implements GeoArea { surfaceParcelle: Float! } -type PersonneMorale { +type PersonneMorale implements Demandeur { adresse: String! + association: Association codeInseeLocalite: String! codePostal: String! complementAdresse: String! + entreprise: Entreprise + id: ID! libelleNaf: String! localite: String! naf: String! nomVoie: String! numeroVoie: String! - siegeSocial: String! + siegeSocial: Boolean! siret: String! typeVoie: String! } +type PersonnePhysique implements Demandeur { + civilite: Civilite + dateDeNaissance: ISO8601Date + id: ID! + nom: String! + prenom: String! +} + type PieceJustificativeChamp implements Champ { id: ID! diff --git a/app/graphql/types/champ_type.rb b/app/graphql/types/champ_type.rb index 8383e409f..928e5f582 100644 --- a/app/graphql/types/champ_type.rb +++ b/app/graphql/types/champ_type.rb @@ -31,6 +31,8 @@ module Types Types::Champs::MultipleDropDownListChampType when ::Champs::LinkedDropDownListChamp Types::Champs::LinkedDropDownListChampType + when ::Champs::CiviliteChamp + Types::Champs::CiviliteChampType else Types::Champs::TextChampType end diff --git a/app/graphql/types/champs/civilite_champ_type.rb b/app/graphql/types/champs/civilite_champ_type.rb new file mode 100644 index 000000000..412daeeac --- /dev/null +++ b/app/graphql/types/champs/civilite_champ_type.rb @@ -0,0 +1,7 @@ +module Types::Champs + class CiviliteChampType < Types::BaseObject + implements Types::ChampType + + field :value, Types::Civilite, null: true + end +end diff --git a/app/graphql/types/civilite.rb b/app/graphql/types/civilite.rb new file mode 100644 index 000000000..562670651 --- /dev/null +++ b/app/graphql/types/civilite.rb @@ -0,0 +1,6 @@ +module Types + class Civilite < Types::BaseEnum + value("M", "Monsieur", value: Individual::GENDER_MALE) + value("Mme", "Madame", value: Individual::GENDER_FEMALE) + end +end diff --git a/app/graphql/types/demandeur_type.rb b/app/graphql/types/demandeur_type.rb new file mode 100644 index 000000000..9f5d01b3d --- /dev/null +++ b/app/graphql/types/demandeur_type.rb @@ -0,0 +1,18 @@ +module Types + module DemandeurType + include Types::BaseInterface + + global_id_field :id + + definition_methods do + def resolve_type(object, context) + case object + when Individual + Types::PersonnePhysiqueType + when Etablissement + Types::PersonneMoraleType + end + end + end + end +end diff --git a/app/graphql/types/dossier_type.rb b/app/graphql/types/dossier_type.rb index fa571275b..ac8961241 100644 --- a/app/graphql/types/dossier_type.rb +++ b/app/graphql/types/dossier_type.rb @@ -24,6 +24,8 @@ module Types { Extensions::Attachment => { attachment: :justificatif_motivation } } ] + field :demandeur, Types::DemandeurType, null: false + field :usager, Types::ProfileType, null: false field :instructeurs, [Types::ProfileType], null: false @@ -61,6 +63,14 @@ module Types Loaders::Association.for(object.class, :champs_private).load(object) end + def demandeur + if object.procedure.for_individual + Loaders::Association.for(object.class, :individual).load(object) + else + Loaders::Association.for(object.class, :etablissement).load(object) + end + end + def self.authorized?(object, context) authorized_demarche?(object.procedure, context) end diff --git a/app/graphql/types/personne_morale_type.rb b/app/graphql/types/personne_morale_type.rb index 999fafee3..6d15cf84e 100644 --- a/app/graphql/types/personne_morale_type.rb +++ b/app/graphql/types/personne_morale_type.rb @@ -1,7 +1,34 @@ module Types class PersonneMoraleType < Types::BaseObject + class EntrepriseType < Types::BaseObject + field :siren, String, null: false + field :capital_social, Int, null: false + field :numero_tva_intracommunautaire, String, null: false + field :forme_juridique, String, null: false + field :forme_juridique_code, String, null: false + field :nom_commercial, String, null: false + field :raison_sociale, String, null: false + field :siret_siege_social, String, null: false + field :code_effectif_entreprise, String, null: false + field :date_creation, GraphQL::Types::ISO8601Date, null: false + field :nom, String, null: false + field :prenom, String, null: false + field :inline_adresse, String, null: false + end + + class AssociationType < Types::BaseObject + field :rna, String, null: false + field :titre, String, null: false + field :objet, String, null: false + field :date_creation, GraphQL::Types::ISO8601Date, null: false + field :date_declaration, GraphQL::Types::ISO8601Date, null: false + field :date_publication, GraphQL::Types::ISO8601Date, null: false + end + + implements Types::DemandeurType + field :siret, String, null: false - field :siege_social, String, null: false + field :siege_social, Boolean, null: false field :naf, String, null: false field :libelle_naf, String, null: false field :adresse, String, null: false @@ -12,5 +39,26 @@ module Types field :code_postal, String, null: false field :localite, String, null: false field :code_insee_localite, String, null: false + field :entreprise, EntrepriseType, null: true + field :association, AssociationType, null: true + + def entreprise + if object.entreprise_siren.present? + object.entreprise + end + end + + def association + if object.association? + { + rna: object.association_rna, + titre: object.association_titre, + objet: object.association_objet, + date_creation: object.association_date_creation, + date_declaration: object.association_date_declaration, + date_publication: object.association_date_publication + } + end + end end end diff --git a/app/graphql/types/personne_physique_type.rb b/app/graphql/types/personne_physique_type.rb new file mode 100644 index 000000000..5074a04ca --- /dev/null +++ b/app/graphql/types/personne_physique_type.rb @@ -0,0 +1,10 @@ +module Types + class PersonnePhysiqueType < Types::BaseObject + implements Types::DemandeurType + + field :nom, String, null: false + field :prenom, String, null: false + field :civilite, Types::Civilite, null: true, method: :gender + field :date_de_naissance, GraphQL::Types::ISO8601Date, null: true, method: :birthdate + end +end diff --git a/spec/controllers/api/v2/graphql_controller_spec.rb b/spec/controllers/api/v2/graphql_controller_spec.rb index d87601827..982ea8bb7 100644 --- a/spec/controllers/api/v2/graphql_controller_spec.rb +++ b/spec/controllers/api/v2/graphql_controller_spec.rb @@ -3,18 +3,19 @@ require 'spec_helper' describe API::V2::GraphqlController do let(:admin) { create(:administrateur) } let(:token) { admin.renew_api_token } - let(:procedure) { create(:procedure, :with_all_champs, administrateurs: [admin]) } + let(:procedure) { create(:procedure, :published, :for_individual, :with_all_champs, administrateurs: [admin]) } let(:dossier) do dossier = create(:dossier, :en_construction, :with_all_champs, + :for_individual, procedure: procedure) create(:commentaire, dossier: dossier, email: 'test@test.com') dossier end - let(:dossier1) { create(:dossier, :en_construction, procedure: procedure, en_construction_at: 1.day.ago) } - let(:dossier2) { create(:dossier, :en_construction, procedure: procedure, en_construction_at: 3.days.ago) } - let!(:dossier_brouillon) { create(:dossier, procedure: procedure) } + let(:dossier1) { create(:dossier, :en_construction, :for_individual, procedure: procedure, en_construction_at: 1.day.ago) } + let(:dossier2) { create(:dossier, :en_construction, :for_individual, procedure: procedure, en_construction_at: 3.days.ago) } + let!(:dossier_brouillon) { create(:dossier, :for_individual, procedure: procedure) } let(:dossiers) { [dossier2, dossier1, dossier] } let(:instructeur) { create(:instructeur, followed_dossiers: dossiers) } @@ -79,7 +80,7 @@ describe API::V2::GraphqlController do number: procedure.id, title: procedure.libelle, description: procedure.description, - state: 'brouillon', + state: 'publiee', dateFermeture: nil, dateCreation: procedure.created_at.iso8601, dateDerniereModification: procedure.updated_at.iso8601, @@ -149,6 +150,15 @@ describe API::V2::GraphqlController do id email } + demandeur { + id + ... on PersonnePhysique { + nom + prenom + civilite + dateDeNaissance + } + } instructeurs { id email @@ -177,45 +187,104 @@ describe API::V2::GraphqlController do }" end - it "should be returned" do - expect(gql_errors).to eq(nil) - expect(gql_data).to eq(dossier: { - id: dossier.to_typed_id, - number: dossier.id, - state: 'en_construction', - dateDerniereModification: dossier.updated_at.iso8601, - datePassageEnConstruction: dossier.en_construction_at.iso8601, - datePassageEnInstruction: nil, - dateTraitement: nil, - motivation: nil, - motivationAttachmentUrl: nil, - usager: { - id: dossier.user.to_typed_id, - email: dossier.user.email - }, - instructeurs: [ - { - id: instructeur.to_typed_id, - email: instructeur.email + context "with individual" do + it "should be returned" do + expect(gql_errors).to eq(nil) + expect(gql_data).to eq(dossier: { + id: dossier.to_typed_id, + number: dossier.id, + state: 'en_construction', + dateDerniereModification: dossier.updated_at.iso8601, + datePassageEnConstruction: dossier.en_construction_at.iso8601, + datePassageEnInstruction: nil, + dateTraitement: nil, + motivation: nil, + motivationAttachmentUrl: nil, + usager: { + id: dossier.user.to_typed_id, + email: dossier.user.email + }, + instructeurs: [ + { + id: instructeur.to_typed_id, + email: instructeur.email + } + ], + demandeur: { + id: dossier.individual.to_typed_id, + nom: dossier.individual.nom, + prenom: dossier.individual.prenom, + civilite: 'M', + dateDeNaissance: '1991-11-01' + }, + messages: dossier.commentaires.map do |commentaire| + { + body: commentaire.body, + attachmentUrl: nil, + email: commentaire.email + } + end, + avis: [], + champs: dossier.champs.map do |champ| + { + id: champ.to_typed_id, + label: champ.libelle, + stringValue: champ.for_api_v2 + } + end + }) + expect(gql_data[:dossier][:champs][0][:id]).to eq(dossier.champs[0].type_de_champ.to_typed_id) + end + end + + context "with entreprise" do + let(:procedure) { create(:procedure, :published, administrateurs: [admin]) } + let(:dossier) { create(:dossier, :en_construction, :with_entreprise, procedure: procedure) } + + let(:query) do + "{ + dossier(number: #{dossier.id}) { + id + number + usager { + id + email + } + demandeur { + id + ... on PersonneMorale { + siret + siegeSocial + entreprise { + siren + dateCreation + } + } + } } - ], - messages: dossier.commentaires.map do |commentaire| - { - body: commentaire.body, - attachmentUrl: nil, - email: commentaire.email + }" + end + + it "should be returned" do + expect(gql_errors).to eq(nil) + expect(gql_data).to eq(dossier: { + id: dossier.to_typed_id, + number: dossier.id, + usager: { + id: dossier.user.to_typed_id, + email: dossier.user.email + }, + demandeur: { + id: dossier.etablissement.to_typed_id, + siret: dossier.etablissement.siret, + siegeSocial: dossier.etablissement.siege_social, + entreprise: { + siren: dossier.etablissement.entreprise_siren, + dateCreation: dossier.etablissement.entreprise_date_creation.iso8601 + } } - end, - avis: [], - champs: dossier.champs.map do |champ| - { - id: champ.to_typed_id, - label: champ.libelle, - stringValue: champ.for_api_v2 - } - end - }) - expect(gql_data[:dossier][:champs][0][:id]).to eq(dossier.champs[0].type_de_champ.to_typed_id) + }) + end end end @@ -296,7 +365,7 @@ describe API::V2::GraphqlController do end describe 'dossierPasserEnInstruction' do - let(:dossier) { create(:dossier, :en_construction, procedure: procedure) } + let(:dossier) { create(:dossier, :en_construction, :for_individual, procedure: procedure) } let(:query) do "mutation { dossierPasserEnInstruction(input: { @@ -331,7 +400,7 @@ describe API::V2::GraphqlController do end context 'validation error' do - let(:dossier) { create(:dossier, :en_instruction, procedure: procedure) } + let(:dossier) { create(:dossier, :en_instruction, :for_individual, procedure: procedure) } it "should fail" do expect(gql_errors).to eq(nil) @@ -344,7 +413,7 @@ describe API::V2::GraphqlController do end describe 'dossierClasserSansSuite' do - let(:dossier) { create(:dossier, :en_instruction, procedure: procedure) } + let(:dossier) { create(:dossier, :en_instruction, :for_individual, procedure: procedure) } let(:query) do "mutation { dossierClasserSansSuite(input: { @@ -380,7 +449,7 @@ describe API::V2::GraphqlController do end context 'validation error' do - let(:dossier) { create(:dossier, :accepte, procedure: procedure) } + let(:dossier) { create(:dossier, :accepte, :for_individual, procedure: procedure) } it "should fail" do expect(gql_errors).to eq(nil) @@ -393,7 +462,7 @@ describe API::V2::GraphqlController do end describe 'dossierRefuser' do - let(:dossier) { create(:dossier, :en_instruction, procedure: procedure) } + let(:dossier) { create(:dossier, :en_instruction, :for_individual, procedure: procedure) } let(:query) do "mutation { dossierRefuser(input: { @@ -429,7 +498,7 @@ describe API::V2::GraphqlController do end context 'validation error' do - let(:dossier) { create(:dossier, :sans_suite, procedure: procedure) } + let(:dossier) { create(:dossier, :sans_suite, :for_individual, procedure: procedure) } it "should fail" do expect(gql_errors).to eq(nil) @@ -442,7 +511,7 @@ describe API::V2::GraphqlController do end describe 'dossierAccepter' do - let(:dossier) { create(:dossier, :en_instruction, procedure: procedure) } + let(:dossier) { create(:dossier, :en_instruction, :for_individual, procedure: procedure) } let(:query) do "mutation { dossierAccepter(input: { @@ -511,7 +580,7 @@ describe API::V2::GraphqlController do end context 'validation error' do - let(:dossier) { create(:dossier, :refuse, procedure: procedure) } + let(:dossier) { create(:dossier, :refuse, :for_individual, procedure: procedure) } it "should fail" do expect(gql_errors).to eq(nil) From b5eafdab560b7ab82f1980aec0e18039cb42d0f1 Mon Sep 17 00:00:00 2001 From: Paul Chavard Date: Fri, 29 Nov 2019 12:45:58 +0100 Subject: [PATCH 17/18] [GraphQL]: informations du service --- app/graphql/schema.graphql | 45 +++++++++++++++++++ app/graphql/types/demarche_type.rb | 5 +++ app/graphql/types/service_type.rb | 15 +++++++ .../api/v2/graphql_controller_spec.rb | 12 ++++- 4 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 app/graphql/types/service_type.rb diff --git a/app/graphql/schema.graphql b/app/graphql/schema.graphql index 5740b29ec..e9ad183b8 100644 --- a/app/graphql/schema.graphql +++ b/app/graphql/schema.graphql @@ -285,6 +285,7 @@ type Demarche { Le numero de la démarche. """ number: Int! + service: Service! """ L'état de la démarche. @@ -935,6 +936,13 @@ type SelectionUtilisateur implements GeoArea { source: GeoAreaSource! } +type Service { + id: ID! + nom: String! + organisme: String! + typeOrganisme: TypeOrganisme! +} + type SiretChamp implements Champ { etablissement: PersonneMorale id: ID! @@ -1102,6 +1110,43 @@ enum TypeDeChamp { yes_no } +enum TypeOrganisme { + """ + Administration centrale + """ + administration_centrale + + """ + Association + """ + association + + """ + Autre + """ + autre + + """ + Collectivité territoriale + """ + collectivite_territoriale + + """ + Établissement d’enseignement + """ + etablissement_enseignement + + """ + Opérateur d'État + """ + operateur_d_etat + + """ + Service déconcentré de l'État + """ + service_deconcentre_de_l_etat +} + """ A valid URL, transported as a string """ diff --git a/app/graphql/types/demarche_type.rb b/app/graphql/types/demarche_type.rb index 894062b7f..b1643d01a 100644 --- a/app/graphql/types/demarche_type.rb +++ b/app/graphql/types/demarche_type.rb @@ -29,6 +29,7 @@ module Types field :date_fermeture, GraphQL::Types::ISO8601DateTime, "Date de la fermeture.", null: true, method: :closed_at field :groupe_instructeurs, [Types::GroupeInstructeurType], null: false + field :service, Types::ServiceType, null: false field :dossiers, Types::DossierType.connection_type, "Liste de tous les dossiers d'une démarche.", null: false do argument :order, Types::Order, default_value: :asc, required: false, description: "L'ordre des dossiers." @@ -48,6 +49,10 @@ module Types Loaders::Association.for(object.class, groupe_instructeurs: { procedure: [:administrateurs] }).load(object) end + def service + Loaders::Record.for(Service).load(object.service_id) + end + def dossiers(updated_since: nil, created_since: nil, state: nil, order:) dossiers = object.dossiers.state_not_brouillon.for_api_v2 diff --git a/app/graphql/types/service_type.rb b/app/graphql/types/service_type.rb new file mode 100644 index 000000000..71b8c7b0d --- /dev/null +++ b/app/graphql/types/service_type.rb @@ -0,0 +1,15 @@ +module Types + class ServiceType < Types::BaseObject + class TypeOrganisme < Types::BaseEnum + Service.type_organismes.each do |symbol_name, string_name| + value(string_name, I18n.t(symbol_name, scope: [:type_organisme]), value: symbol_name) + end + end + + global_id_field :id + + field :nom, String, null: false + field :type_organisme, TypeOrganisme, null: false + field :organisme, String, null: false + end +end diff --git a/spec/controllers/api/v2/graphql_controller_spec.rb b/spec/controllers/api/v2/graphql_controller_spec.rb index 982ea8bb7..0d7b6436e 100644 --- a/spec/controllers/api/v2/graphql_controller_spec.rb +++ b/spec/controllers/api/v2/graphql_controller_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe API::V2::GraphqlController do let(:admin) { create(:administrateur) } let(:token) { admin.renew_api_token } - let(:procedure) { create(:procedure, :published, :for_individual, :with_all_champs, administrateurs: [admin]) } + let(:procedure) { create(:procedure, :published, :for_individual, :with_service, :with_all_champs, administrateurs: [admin]) } let(:dossier) do dossier = create(:dossier, :en_construction, @@ -40,6 +40,11 @@ describe API::V2::GraphqlController do email } } + service { + nom + typeOrganisme + organisme + } champDescriptors { id type @@ -90,6 +95,11 @@ describe API::V2::GraphqlController do label: "défaut" } ], + service: { + nom: procedure.service.nom, + typeOrganisme: procedure.service.type_organisme, + organisme: procedure.service.organisme + }, champDescriptors: procedure.types_de_champ.map do |tdc| { id: tdc.to_typed_id, From e5f582d644c0b494947b26d2baddcc5859eb13a7 Mon Sep 17 00:00:00 2001 From: Paul Chavard Date: Tue, 3 Dec 2019 15:07:33 +0100 Subject: [PATCH 18/18] =?UTF-8?q?Afficher=20la=20description=20du=20champ?= =?UTF-8?q?=20r=C3=A9p=C3=A9table?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fix #4579 --- .../shared/dossiers/editable_champs/_editable_champ.html.haml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/views/shared/dossiers/editable_champs/_editable_champ.html.haml b/app/views/shared/dossiers/editable_champs/_editable_champ.html.haml index 3af6e390c..156fd5699 100644 --- a/app/views/shared/dossiers/editable_champs/_editable_champ.html.haml +++ b/app/views/shared/dossiers/editable_champs/_editable_champ.html.haml @@ -1,6 +1,9 @@ .editable-champ{ class: "editable-champ-#{champ.type_champ}" } - if champ.repetition? = render partial: 'shared/dossiers/editable_champs/header_section', locals: { champ: champ } + + - if champ.description.present? + %p.notice= string_to_html(champ.description, false) - elsif has_label?(champ) = render partial: 'shared/dossiers/editable_champs/champ_label', locals: { form: form, champ: champ, seen_at: defined?(seen_at) ? seen_at : nil }