diff --git a/.circleci/config.yml b/.circleci/config.yml index a7d5edd1e..4615c88d8 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -14,17 +14,22 @@ bundle_restore_cache: &bundle_restore_cache restore_cache: name: Restore Bundler Package Cache keys: - - bundle-install-v9-{{ arch }}-{{ checksum "Gemfile.lock" }} - - bundle-install-v9-{{ arch }} - - bundle-install-v9 + - bundle-install-v10-{{ arch }}-{{ checksum "Gemfile.lock" }} + - bundle-install-v10-{{ arch }} + - bundle-install-v10 bundle_save_cache: &bundle_save_cache save_cache: name: Save Bundler Package Cache - key: bundle-install-v9-{{ arch }}-{{ checksum "Gemfile.lock" }} + key: bundle-install-v10-{{ arch }}-{{ checksum "Gemfile.lock" }} paths: - ~/vendor/bundle +aptget_install: &aptget_install + run: + name: Install GEOS + command: sudo apt-get install libgeos-dev + bundle_install: &bundle_install run: name: Install Ruby Dependencies @@ -78,6 +83,7 @@ jobs: <<: *defaults steps: - checkout + - *aptget_install - *bundle_restore_cache - *bundle_install - *bundle_save_cache @@ -89,6 +95,7 @@ jobs: parallelism: 3 steps: - checkout + - *aptget_install - *bundle_restore_cache - *bundle_install - *yarn_restore_cache @@ -123,6 +130,7 @@ jobs: <<: *defaults steps: - checkout + - *aptget_install - *bundle_restore_cache - *bundle_install - *yarn_restore_cache diff --git a/Gemfile b/Gemfile index be4cfbaea..8ac290dc6 100644 --- a/Gemfile +++ b/Gemfile @@ -30,6 +30,7 @@ gem 'flipper-active_record' gem 'flipper-ui' gem 'font-awesome-rails' gem 'fugit' +gem 'geo_coord', require: "geo/coord" gem 'geocoder' gem 'gon' gem 'graphql' diff --git a/Gemfile.lock b/Gemfile.lock index 5544e433f..622f366fb 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -242,6 +242,7 @@ GEM fugit (1.3.3) et-orbi (~> 1.1, >= 1.1.8) raabro (~> 1.1) + geo_coord (0.1.0) geocoder (1.6.0) globalid (0.4.2) activesupport (>= 4.2.0) @@ -755,6 +756,7 @@ DEPENDENCIES flipper-ui font-awesome-rails fugit + geo_coord geocoder gon graphql diff --git a/README.md b/README.md index 10bd59d5d..c294ee676 100644 --- a/README.md +++ b/README.md @@ -48,8 +48,11 @@ Les informations nécessaire à l'initialisation de la base doivent être pré-c Sous Ubuntu, certains packages doivent être installés au préalable : - sudo apt-get install libcurl3 libcurl3-gnutls libcurl4-openssl-dev libcurl4-gnutls-dev zlib1g-dev + sudo apt-get install libcurl3 libcurl3-gnutls libcurl4-openssl-dev libcurl4-gnutls-dev zlib1g-dev libgeos-dev +Sous Mac, certains packages doivent être installés au préalable : + + brew install geos Afin d'initialiser l'environnement de développement, exécutez la commande suivante : diff --git a/app/controllers/instructeurs/procedures_controller.rb b/app/controllers/instructeurs/procedures_controller.rb index 7a255186e..154c6f7c1 100644 --- a/app/controllers/instructeurs/procedures_controller.rb +++ b/app/controllers/instructeurs/procedures_controller.rb @@ -8,6 +8,7 @@ module Instructeurs def index @procedures = current_instructeur .procedures + .kept .with_attached_logo .includes(:defaut_groupe_instructeur) .order(closed_at: :desc, unpublished_at: :desc, published_at: :desc, created_at: :desc) diff --git a/app/controllers/users/dossiers_controller.rb b/app/controllers/users/dossiers_controller.rb index f94983007..88ea0db37 100644 --- a/app/controllers/users/dossiers_controller.rb +++ b/app/controllers/users/dossiers_controller.rb @@ -153,7 +153,7 @@ module Users if passage_en_construction? && errors.blank? @dossier.en_construction! NotificationMailer.send_initiated_notification(@dossier).deliver_later - @dossier.procedure.instructeurs.with_instant_email_dossier_notifications.each do |instructeur| + @dossier.groupe_instructeur.instructeurs.with_instant_email_dossier_notifications.each do |instructeur| DossierMailer.notify_new_dossier_depose_to_instructeur(@dossier, instructeur.email).deliver_later end return redirect_to(merci_dossier_path(@dossier)) diff --git a/app/graphql/schema.graphql b/app/graphql/schema.graphql index 6b0b895f4..c1dea88f4 100644 --- a/app/graphql/schema.graphql +++ b/app/graphql/schema.graphql @@ -687,23 +687,24 @@ enum DossierState { } type Effectif { - """ - Année de l'effectif mensuel - """ - annee: String! - - """ - Mois de l'effectif mensuel - """ - mois: String! nb: Float! + periode: String! } type Entreprise { capitalSocial: BigInt! codeEffectifEntreprise: String! dateCreation: ISO8601Date! - effectifs: [Effectif!]! + + """ + effectif moyen d'une année + """ + effectifAnnuel: Effectif + + """ + effectif pour un mois donné + """ + effectifMensuel: Effectif formeJuridique: String! formeJuridiqueCode: String! inlineAdresse: String! diff --git a/app/graphql/types/personne_morale_type.rb b/app/graphql/types/personne_morale_type.rb index 5bc5f176b..627145cb0 100644 --- a/app/graphql/types/personne_morale_type.rb +++ b/app/graphql/types/personne_morale_type.rb @@ -2,8 +2,7 @@ module Types class PersonneMoraleType < Types::BaseObject class EntrepriseType < Types::BaseObject class EffectifType < Types::BaseObject - field :mois, String, null: false, description: "Mois de l'effectif mensuel" - field :annee, String, null: false, description: "Année de l'effectif mensuel" + field :periode, String, null: false field :nb, Float, null: false end @@ -16,21 +15,28 @@ module Types field :raison_sociale, String, null: false field :siret_siege_social, String, null: false field :code_effectif_entreprise, String, null: false - field :effectifs, [EffectifType], null: false + field :effectif_mensuel, EffectifType, null: true, description: "effectif pour un mois donné" + field :effectif_annuel, EffectifType, null: true, description: "effectif moyen d'une année" field :date_creation, GraphQL::Types::ISO8601Date, null: false field :nom, String, null: false field :prenom, String, null: false field :inline_adresse, String, null: false - def effectifs + def effectif_mensuel if object.effectif_mensuel.present? - [ - { - mois: object.effectif_mois, - annee: object.effectif_annee, - nb: object.effectif_mensuel - } - ] + { + periode: [object.effectif_mois, object.effectif_annee].join('/'), + nb: object.effectif_mensuel + } + end + end + + def effectif_annuel + if object.effectif_annuel.present? + { + periode: object.effectif_annuel_annee, + nb: object.effectif_annuel + } end end end diff --git a/app/helpers/champ_helper.rb b/app/helpers/champ_helper.rb index b41d81fa2..36416f759 100644 --- a/app/helpers/champ_helper.rb +++ b/app/helpers/champ_helper.rb @@ -49,6 +49,17 @@ module ChampHelper "#{geo_area.commune} : #{geo_area.nom}" when GeoArea.sources.fetch(:parcelle_agricole) "Culture : #{geo_area.culture} - Surface : #{geo_area.surface} ha" + when GeoArea.sources.fetch(:selection_utilisateur) + if geo_area.polygon? + capture do + concat "Une aire de surface #{geo_area.area} m" + concat content_tag(:sup, "2") + end + elsif geo_area.line? + "Une ligne longue de #{geo_area.length} m" + elsif geo_area.point? + "Un point situé à #{geo_area.location}" + end end end end diff --git a/app/javascript/shared/activestorage/file-upload-error.js b/app/javascript/shared/activestorage/file-upload-error.js index 9b8b4285a..ef4aa5ded 100644 --- a/app/javascript/shared/activestorage/file-upload-error.js +++ b/app/javascript/shared/activestorage/file-upload-error.js @@ -20,9 +20,19 @@ export const FAILURE_CONNECTIVITY = 'file-upload-failure-connectivity'; export default class FileUploadError extends Error { constructor(message, status, code) { super(message); + this.name = 'FileUploadError'; this.status = status; this.code = code; + + // Prevent the constructor stacktrace from being included. + // (it messes up with Sentry issues grouping) + if (Error.captureStackTrace) { + // V8-only + Error.captureStackTrace(this, this.constructor); + } else { + this.stack = new Error().stack; + } } /** diff --git a/app/lib/api_entreprise/api.rb b/app/lib/api_entreprise/api.rb index 8c5c66eb4..5d8443fea 100644 --- a/app/lib/api_entreprise/api.rb +++ b/app/lib/api_entreprise/api.rb @@ -4,6 +4,7 @@ class ApiEntreprise::API EXERCICES_RESOURCE_NAME = "exercices" RNA_RESOURCE_NAME = "associations" EFFECTIFS_RESOURCE_NAME = "effectifs_mensuels_acoss_covid" + EFFECTIFS_ANNUELS_RESOURCE_NAME = "effectifs_annuels_acoss_covid" TIMEOUT = 15 @@ -34,6 +35,10 @@ class ApiEntreprise::API call(endpoint, siren, procedure_id) end + def self.effectifs_annuels(siren, procedure_id) + call(EFFECTIFS_ANNUELS_RESOURCE_NAME, siren, procedure_id) + end + private def self.call(resource_name, siret_or_siren, procedure_id) diff --git a/app/lib/api_entreprise/effectifs_annuels_adapter.rb b/app/lib/api_entreprise/effectifs_annuels_adapter.rb new file mode 100644 index 000000000..2c56b41f6 --- /dev/null +++ b/app/lib/api_entreprise/effectifs_annuels_adapter.rb @@ -0,0 +1,23 @@ +class ApiEntreprise::EffectifsAnnuelsAdapter < ApiEntreprise::Adapter + def initialize(siren, procedure_id) + @siren = siren + @procedure_id = procedure_id + end + + private + + def get_resource + ApiEntreprise::API.effectifs_annuels(@siren, @procedure_id) + end + + def process_params + if data_source[:effectifs_annuels].present? + { + entreprise_effectif_annuel: data_source[:effectifs_annuels], + entreprise_effectif_annuel_annee: data_source[:annee] + } + else + {} + end + end +end diff --git a/app/mailers/dossier_mailer.rb b/app/mailers/dossier_mailer.rb index a217f4d16..36d4bd17d 100644 --- a/app/mailers/dossier_mailer.rb +++ b/app/mailers/dossier_mailer.rb @@ -134,7 +134,8 @@ class DossierMailer < ApplicationMailer def default_i18n_subject(interpolations = {}) if interpolations[:state] mailer_scope = self.class.mailer_name.tr('/', '.') - I18n.t("subject_#{interpolations[:state]}", interpolations.merge(scope: [mailer_scope, action_name])) + state = interpolations[:state].in?(Dossier::TERMINE) ? 'termine' : interpolations[:state] + I18n.t("subject_#{state}", interpolations.merge(scope: [mailer_scope, action_name])) else super end diff --git a/app/models/champ.rb b/app/models/champ.rb index 7c21fdae2..9e2c2cf63 100644 --- a/app/models/champ.rb +++ b/app/models/champ.rb @@ -1,5 +1,5 @@ class Champ < ApplicationRecord - belongs_to :dossier, inverse_of: :champs, touch: true + belongs_to :dossier, -> { with_discarded }, inverse_of: :champs, touch: true belongs_to :type_de_champ, inverse_of: :champ belongs_to :parent, class_name: 'Champ' has_many :commentaires diff --git a/app/models/dossier.rb b/app/models/dossier.rb index 0fdba3a50..b47cc927f 100644 --- a/app/models/dossier.rb +++ b/app/models/dossier.rb @@ -189,6 +189,11 @@ class Dossier < ApplicationRecord .joins(:procedure) .where("dossiers.en_instruction_at + (duree_conservation_dossiers_dans_ds * INTERVAL '1 month') - INTERVAL :expires_in < :now", { now: Time.zone.now, expires_in: INTERVAL_BEFORE_EXPIRATION }) end + scope :termine_close_to_expiration, -> do + state_termine + .joins(:procedure) + .where("dossiers.processed_at + (duree_conservation_dossiers_dans_ds * INTERVAL '1 month') - INTERVAL :expires_in < :now", { now: Time.zone.now, expires_in: INTERVAL_BEFORE_EXPIRATION }) + end scope :brouillon_expired, -> do state_brouillon @@ -198,9 +203,14 @@ class Dossier < ApplicationRecord state_en_construction .where("en_construction_close_to_expiration_notice_sent_at + INTERVAL :expires_in < :now", { now: Time.zone.now, expires_in: INTERVAL_EXPIRATION }) end + scope :termine_expired, -> do + state_termine + .where("termine_close_to_expiration_notice_sent_at + INTERVAL :expires_in < :now", { now: Time.zone.now, expires_in: INTERVAL_EXPIRATION }) + end scope :without_brouillon_expiration_notice_sent, -> { where(brouillon_close_to_expiration_notice_sent_at: nil) } scope :without_en_construction_expiration_notice_sent, -> { where(en_construction_close_to_expiration_notice_sent_at: nil) } + scope :without_termine_expiration_notice_sent, -> { where(termine_close_to_expiration_notice_sent_at: nil) } scope :discarded_brouillon_expired, -> do with_discarded diff --git a/app/models/entreprise.rb b/app/models/entreprise.rb index 5fb6cf719..5808b91e9 100644 --- a/app/models/entreprise.rb +++ b/app/models/entreprise.rb @@ -15,6 +15,8 @@ class Entreprise < Hashie::Dash property :effectif_mois property :effectif_annee property :effectif_mensuel + property :effectif_annuel + property :effectif_annuel_annee property :date_creation property :nom property :prenom diff --git a/app/models/etablissement.rb b/app/models/etablissement.rb index 4d3236e80..5b57331a2 100644 --- a/app/models/etablissement.rb +++ b/app/models/etablissement.rb @@ -105,6 +105,8 @@ class Etablissement < ApplicationRecord effectif_mensuel: entreprise_effectif_mensuel, effectif_mois: entreprise_effectif_mois, effectif_annee: entreprise_effectif_annee, + effectif_annuel: entreprise_effectif_annuel, + effectif_annuel_annee: entreprise_effectif_annuel_annee, date_creation: entreprise_date_creation, nom: entreprise_nom, prenom: entreprise_prenom, diff --git a/app/models/geo_area.rb b/app/models/geo_area.rb index ce89d7e03..510dc3b75 100644 --- a/app/models/geo_area.rb +++ b/app/models/geo_area.rb @@ -36,7 +36,7 @@ class GeoArea < ApplicationRecord { type: 'Feature', geometry: geometry, - properties: properties.merge(source: source) + properties: properties.merge(source: source, area: area, length: length).compact } end @@ -53,4 +53,34 @@ class GeoArea < ApplicationRecord ) end end + + def area + if polygon? && RGeo::Geos.supported? + rgeo_geometry.area.round(1) + end + end + + def length + if line? && RGeo::Geos.supported? + rgeo_geometry.length.round(1) + end + end + + def location + if point? + Geo::Coord.new(*rgeo_geometry.coordinates).to_s + end + end + + def line? + geometry['type'] == 'LineString' + end + + def polygon? + geometry['type'] == 'Polygon' + end + + def point? + geometry['type'] == 'Point' + end end diff --git a/app/serializers/entreprise_serializer.rb b/app/serializers/entreprise_serializer.rb index 70437d8c8..602f9e69b 100644 --- a/app/serializers/entreprise_serializer.rb +++ b/app/serializers/entreprise_serializer.rb @@ -11,6 +11,8 @@ class EntrepriseSerializer < ActiveModel::Serializer :effectif_mois, :effectif_annee, :effectif_mensuel, + :effectif_annuel, + :effectif_annuel_annee, :date_creation, :nom, :prenom diff --git a/app/services/api_entreprise_service.rb b/app/services/api_entreprise_service.rb index 2b48acf8a..3f5082765 100644 --- a/app/services/api_entreprise_service.rb +++ b/app/services/api_entreprise_service.rb @@ -29,6 +29,12 @@ class ApiEntrepriseService rescue ApiEntreprise::API::RequestFailed end + begin + effectifs_annuels_params = ApiEntreprise::EffectifsAnnuelsAdapter.new(entreprise_params[:entreprise_siren], procedure_id).to_params + etablissement_params.merge!(effectifs_annuels_params) + rescue ApiEntreprise::API::RequestFailed + end + etablissement_params.merge(entreprise_params) end end diff --git a/app/services/expired_dossiers_deletion_service.rb b/app/services/expired_dossiers_deletion_service.rb index 9b8efaa29..9e1aec31b 100644 --- a/app/services/expired_dossiers_deletion_service.rb +++ b/app/services/expired_dossiers_deletion_service.rb @@ -9,6 +9,11 @@ class ExpiredDossiersDeletionService delete_expired_en_construction_and_notify end + def self.process_expired_dossiers_termine + send_termine_expiration_notices + delete_expired_termine_and_notify + end + def self.send_brouillon_expiration_notices dossiers_close_to_expiration = Dossier .brouillon_close_to_expiration @@ -23,19 +28,27 @@ class ExpiredDossiersDeletionService dossiers, user.email ).deliver_later + + # mark as sent dossiers from current notification + Dossier.where(id: dossiers).update_all(brouillon_close_to_expiration_notice_sent_at: Time.zone.now) end + # mark as sent dossiers without notification dossiers_close_to_expiration.update_all(brouillon_close_to_expiration_notice_sent_at: Time.zone.now) end def self.send_en_construction_expiration_notices - dossiers_close_to_expiration = Dossier - .en_construction_close_to_expiration - .without_en_construction_expiration_notice_sent + send_expiration_notices( + Dossier.en_construction_close_to_expiration.without_en_construction_expiration_notice_sent, + :en_construction_close_to_expiration_notice_sent_at + ) + end - send_expiration_notices(dossiers_close_to_expiration) - - dossiers_close_to_expiration.update_all(en_construction_close_to_expiration_notice_sent_at: Time.zone.now) + def self.send_termine_expiration_notices + send_expiration_notices( + Dossier.termine_close_to_expiration.without_termine_expiration_notice_sent, + :termine_close_to_expiration_notice_sent_at + ) end def self.delete_expired_brouillons_and_notify @@ -50,8 +63,12 @@ class ExpiredDossiersDeletionService dossiers.map(&:hash_for_deletion_mail), user.email ).deliver_later + + # destroy dossiers from current notification + Dossier.where(id: dossiers).destroy_all end + # destroy dossiers without notification dossiers_to_remove.destroy_all end @@ -59,9 +76,13 @@ class ExpiredDossiersDeletionService delete_expired_and_notify(Dossier.en_construction_expired) end + def self.delete_expired_termine_and_notify + delete_expired_and_notify(Dossier.termine_expired) + end + private - def self.send_expiration_notices(dossiers_close_to_expiration) + def self.send_expiration_notices(dossiers_close_to_expiration, close_to_expiration_flag) dossiers_close_to_expiration .with_notifiable_procedure .includes(:user) @@ -78,7 +99,13 @@ class ExpiredDossiersDeletionService dossiers.to_a, email ).deliver_later + + # mark as sent dossiers from current notification + Dossier.where(id: dossiers.to_a).update_all(close_to_expiration_flag => Time.zone.now) end + + # mark as sent dossiers without notification + dossiers_close_to_expiration.update_all(close_to_expiration_flag => Time.zone.now) end def self.delete_expired_and_notify(dossiers_to_remove) @@ -100,8 +127,12 @@ class ExpiredDossiersDeletionService DeletedDossier.where(dossier_id: dossiers.map(&:id)), email ).deliver_later + + # destroy dossiers from current notification + Dossier.where(id: dossiers.to_a).destroy_all end + # destroy dossiers without notification dossiers_to_remove.destroy_all end diff --git a/app/views/dossier_mailer/notify_near_deletion_to_administration.html.haml b/app/views/dossier_mailer/notify_near_deletion_to_administration.html.haml index 62b0156ff..485757f6d 100644 --- a/app/views/dossier_mailer/notify_near_deletion_to_administration.html.haml +++ b/app/views/dossier_mailer/notify_near_deletion_to_administration.html.haml @@ -4,7 +4,10 @@ Bonjour, %p - = t('.header_en_construction', count: @dossiers.count) + - if @state == Dossier.states.fetch(:en_construction) + = t('.header_en_construction', count: @dossiers.count) + - else + = t('.header_termine', count: @dossiers.count) %ul - @dossiers.each do |d| %li @@ -13,5 +16,7 @@ %p - if @state == Dossier.states.fetch(:en_construction) = sanitize(t('.footer_en_construction', count: @dossiers.count)) + - else + = sanitize(t('.footer_termine', count: @dossiers.count)) = render partial: "layouts/mailers/signature" diff --git a/app/views/dossier_mailer/notify_near_deletion_to_user.html.haml b/app/views/dossier_mailer/notify_near_deletion_to_user.html.haml index efe0c668d..83455024a 100644 --- a/app/views/dossier_mailer/notify_near_deletion_to_user.html.haml +++ b/app/views/dossier_mailer/notify_near_deletion_to_user.html.haml @@ -4,7 +4,10 @@ Bonjour, %p - = t('.header_en_construction', count: @dossiers.count) + - if @state == Dossier.states.fetch(:en_construction) + = t('.header_en_construction', count: @dossiers.count) + - else + = t('.header_termine', count: @dossiers.count) %ul - @dossiers.each do |d| %li diff --git a/app/views/layouts/_pre_maintenance.html.haml b/app/views/layouts/_pre_maintenance.html.haml index 00d0a9c9a..28327f13d 100644 --- a/app/views/layouts/_pre_maintenance.html.haml +++ b/app/views/layouts/_pre_maintenance.html.haml @@ -1,9 +1,9 @@ - if feature_enabled?(:pre_maintenance_mode) .site-banner.warning .container - .site-banner-icon 🛑 + .site-banner-icon 🕚 .site-banner-text %strong - L’envoi de fichiers est temporairement indisponible. + Une opération de maintenance est prévue sur demarches-simplifiees.fr à 23 h 00. %br - Si vous rencontrez des erreurs, merci de ré-essayer dans quelques heures. + La plateforme sera inaccessible pendant une vingtaine de minutes. diff --git a/app/views/shared/champs/carte/_geo_areas.html.haml b/app/views/shared/champs/carte/_geo_areas.html.haml index 6f3565e3d..6f5f24b13 100644 --- a/app/views/shared/champs/carte/_geo_areas.html.haml +++ b/app/views/shared/champs/carte/_geo_areas.html.haml @@ -1,3 +1,10 @@ +- if champ.selections_utilisateur.present? + .areas-title Sélections utilisateur + .areas + %ul + - champ.selections_utilisateur.each do |geo_area| + %li= geo_area_label(geo_area) + - if champ.quartiers_prioritaires? .areas-title Quartiers prioritaires .areas diff --git a/app/views/shared/dossiers/_identite_entreprise.html.haml b/app/views/shared/dossiers/_identite_entreprise.html.haml index 51a5dc51f..a10cfdaf5 100644 --- a/app/views/shared/dossiers/_identite_entreprise.html.haml +++ b/app/views/shared/dossiers/_identite_entreprise.html.haml @@ -32,13 +32,18 @@ %th.libelle Effectif mensuel = try_format_mois_effectif(etablissement) + (URSSAF) %td= etablissement.entreprise_effectif_mensuel + %tr + %th.libelle + Effectif moyen annuel + = etablissement.entreprise_effectif_annuel_annee + (URSSAF) + %td= etablissement.entreprise_effectif_annuel %tr - %th.libelle Effectif de l'organisation : - %td= effectif(etablissement) - %tr - %th.libelle Code effectif : - %td= etablissement.entreprise.code_effectif_entreprise + %th.libelle Effectif de l'organisation (INSEE) : + %td + = effectif(etablissement) %tr %th.libelle Numéro de TVA intracommunautaire : %td= etablissement.entreprise.numero_tva_intracommunautaire diff --git a/config/locales/views/dossier_mailer/notify_near_deletion_to_administration/fr.yml b/config/locales/views/dossier_mailer/notify_near_deletion_to_administration/fr.yml index e3d24481c..c1bd54250 100644 --- a/config/locales/views/dossier_mailer/notify_near_deletion_to_administration/fr.yml +++ b/config/locales/views/dossier_mailer/notify_near_deletion_to_administration/fr.yml @@ -4,9 +4,18 @@ fr: subject_en_construction: one: Un dossier en construction va bientôt être supprimé other: Des dossiers en construction vont bientôt être supprimés + subject_termine: + one: Un dossier dont le traitement est terminé va bientôt être supprimé + other: Des dossiers dont le traitement est terminé vont bientôt être supprimés header_en_construction: one: "Le dossier en construction suivant sera bientôt automatiquement supprimé :" other: "Les dossiers en construction suivant seront bientôt automatiquement supprimés :" + header_termine: + one: "Le dossier suivant dont le traitement est terminé sera bientôt automatiquement supprimé :" + other: "Les dossiers suivant dont le traitement est terminé seront bientôt automatiquement supprimés :" footer_en_construction: one: "Vous avez un mois pour commencer l’instruction du dossier." other: "Vous avez un mois pour commencer l’instruction des dossiers." + footer_termine: + one: "Vous avez un mois pour archiver le dossier." + other: "Vous avez un mois pour archiver les dossiers." diff --git a/config/locales/views/dossier_mailer/notify_near_deletion_to_user/fr.yml b/config/locales/views/dossier_mailer/notify_near_deletion_to_user/fr.yml index e81cad44d..3a0098335 100644 --- a/config/locales/views/dossier_mailer/notify_near_deletion_to_user/fr.yml +++ b/config/locales/views/dossier_mailer/notify_near_deletion_to_user/fr.yml @@ -4,9 +4,15 @@ fr: subject_en_construction: one: Un dossier en construction va bientôt être supprimé other: Des dossiers en construction vont bientôt être supprimés + subject_termine: + one: Un dossier dont le traitement est terminé va bientôt être supprimé + other: Des dossiers dont le traitement est terminé vont bientôt être supprimés header_en_construction: one: "Afin de limiter la conservation de vos données personnelles, le dossier en construction suivant sera bientôt automatiquement supprimé :" other: "Afin de limiter la conservation de vos données personnelles, les dossiers en construction suivant seront bientôt automatiquement supprimés :" + header_termine: + one: "Afin de limiter la conservation de vos données personnelles, le dossier suivant dont le traitement est terminé sera bientôt automatiquement supprimé :" + other: "Afin de limiter la conservation de vos données personnelles, les dossiers suivant dont le traitement est terminé seront bientôt automatiquement supprimés :" footer: one: "Vous pouvez retrouver votre dossier pendant encore un mois. Vous n’avez rien à faire." other: "Vous pouvez retrouver vos dossiers pendant encore un mois. Vous n’avez rien à faire." diff --git a/db/migrate/20200331164240_add_termine_close_to_expiration_to_dossiers.rb b/db/migrate/20200331164240_add_termine_close_to_expiration_to_dossiers.rb new file mode 100644 index 000000000..5e46cc2c1 --- /dev/null +++ b/db/migrate/20200331164240_add_termine_close_to_expiration_to_dossiers.rb @@ -0,0 +1,5 @@ +class AddTermineCloseToExpirationToDossiers < ActiveRecord::Migration[5.2] + def change + add_column :dossiers, :termine_close_to_expiration_notice_sent_at, :datetime + end +end diff --git a/db/migrate/20200422090426_add_effectif_annee_anterieure.rb b/db/migrate/20200422090426_add_effectif_annee_anterieure.rb new file mode 100644 index 000000000..62f55e3fc --- /dev/null +++ b/db/migrate/20200422090426_add_effectif_annee_anterieure.rb @@ -0,0 +1,6 @@ +class AddEffectifAnneeAnterieure < ActiveRecord::Migration[5.2] + def change + add_column :etablissements, :entreprise_effectif_annuel, :decimal + add_column :etablissements, :entreprise_effectif_annuel_annee, :string + end +end diff --git a/db/schema.rb b/db/schema.rb index e7a2911e7..2f59b5fe7 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: 2020_04_21_174642) do +ActiveRecord::Schema.define(version: 2020_04_22_090426) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -258,6 +258,7 @@ ActiveRecord::Schema.define(version: 2020_04_21_174642) do 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.interval "en_construction_conservation_extension", default: "00:00:00" + t.datetime "termine_close_to_expiration_notice_sent_at" t.index ["archived"], name: "index_dossiers_on_archived" t.index ["groupe_instructeur_id"], name: "index_dossiers_on_groupe_instructeur_id" t.index ["hidden_at"], name: "index_dossiers_on_hidden_at" @@ -313,6 +314,8 @@ ActiveRecord::Schema.define(version: 2020_04_21_174642) do t.string "entreprise_effectif_mois" t.string "entreprise_effectif_annee" t.decimal "entreprise_effectif_mensuel" + t.decimal "entreprise_effectif_annuel" + t.string "entreprise_effectif_annuel_annee" t.index ["dossier_id"], name: "index_etablissements_on_dossier_id" end diff --git a/spec/controllers/api/v1/dossiers_controller_spec.rb b/spec/controllers/api/v1/dossiers_controller_spec.rb index a329464ac..1bc8b195b 100644 --- a/spec/controllers/api/v1/dossiers_controller_spec.rb +++ b/spec/controllers/api/v1/dossiers_controller_spec.rb @@ -200,6 +200,8 @@ describe API::V1::DossiersController do :effectif_mois, :effectif_annee, :effectif_mensuel, + :effectif_annuel, + :effectif_annuel_annee, :date_creation, :nom, :prenom diff --git a/spec/controllers/instructeurs/procedures_controller_spec.rb b/spec/controllers/instructeurs/procedures_controller_spec.rb index 75c75edae..9b91ed178 100644 --- a/spec/controllers/instructeurs/procedures_controller_spec.rb +++ b/spec/controllers/instructeurs/procedures_controller_spec.rb @@ -96,18 +96,24 @@ describe Instructeurs::ProceduresController, type: :controller do it { expect(response).to have_http_status(:ok) } context "with procedures assigned" do - let(:procedure1) { create(:procedure, :published) } - let(:procedure2) { create(:procedure, :closed) } - let(:procedure3) { create(:procedure) } + let(:procedure_draft) { create(:procedure) } + let(:procedure_published) { create(:procedure, :published) } + let(:procedure_closed) { create(:procedure, :closed) } + let(:procedure_draft_discarded) { create(:procedure, :discarded) } + let(:procedure_closed_discarded) { create(:procedure, :discarded) } + let(:procedure_not_assigned) { create(:procedure) } before do - instructeur.groupe_instructeurs << procedure1.defaut_groupe_instructeur - instructeur.groupe_instructeurs << procedure2.defaut_groupe_instructeur - instructeur.groupe_instructeurs << procedure3.defaut_groupe_instructeur + [procedure_draft, procedure_published, procedure_closed, procedure_draft_discarded, procedure_closed_discarded].each do |p| + instructeur.groupe_instructeurs << p.defaut_groupe_instructeur + end subject end - it { expect(assigns(:procedures)).to include(procedure1, procedure2) } + it 'assigns procedures visible to the instructeur' do + expect(assigns(:procedures)).to include(procedure_draft, procedure_published, procedure_closed) + expect(assigns(:procedures)).not_to include(procedure_draft_discarded, procedure_closed_discarded, procedure_not_assigned) + end end context "with dossiers" do @@ -218,16 +224,28 @@ describe Instructeurs::ProceduresController, type: :controller do let!(:procedure) { create(:procedure, instructeurs: [instructeur]) } let!(:gi_2) { procedure.groupe_instructeurs.create(label: '2') } let!(:gi_3) { procedure.groupe_instructeurs.create(label: '3') } + let(:statut) { nil } + + subject do + get :show, params: { procedure_id: procedure.id, statut: statut } + end context "when logged in, and belonging to gi_1, gi_2" do before do sign_in(instructeur.user) - instructeur.groupe_instructeurs << gi_2 end - context "without anything" do - before { get :show, params: { procedure_id: procedure.id } } + context 'when the procedure is discarded' do + before do + procedure.discard! + end + + it { expect { subject }.to raise_error(ActiveRecord::RecordNotFound) } + end + + context "without any dossier" do + before { subject } it { expect(response).to have_http_status(:ok) } it { expect(assigns(:procedure)).to eq(procedure) } @@ -236,9 +254,7 @@ describe Instructeurs::ProceduresController, type: :controller do context 'with a new brouillon dossier' do let!(:brouillon_dossier) { create(:dossier, procedure: procedure, state: Dossier.states.fetch(:brouillon)) } - before do - get :show, params: { procedure_id: procedure.id } - end + before { subject } it { expect(assigns(:a_suivre_dossiers)).to be_empty } it { expect(assigns(:followed_dossiers)).to be_empty } @@ -250,9 +266,7 @@ describe Instructeurs::ProceduresController, type: :controller do context 'with a new dossier without follower' do let!(:new_unfollow_dossier) { create(:dossier, procedure: procedure, state: Dossier.states.fetch(:en_instruction)) } - before do - get :show, params: { procedure_id: procedure.id } - end + before { subject } it { expect(assigns(:a_suivre_dossiers)).to match_array([new_unfollow_dossier]) } it { expect(assigns(:followed_dossiers)).to be_empty } @@ -264,9 +278,7 @@ describe Instructeurs::ProceduresController, type: :controller do let!(:new_unfollow_dossier_on_gi_2) { create(:dossier, groupe_instructeur: gi_2, state: Dossier.states.fetch(:en_instruction)) } let!(:new_unfollow_dossier_on_gi_3) { create(:dossier, groupe_instructeur: gi_3, state: Dossier.states.fetch(:en_instruction)) } - before do - get :show, params: { procedure_id: procedure.id } - end + before { subject } it { expect(assigns(:a_suivre_dossiers)).to match_array([new_unfollow_dossier, new_unfollow_dossier_on_gi_2]) } it { expect(assigns(:all_state_dossiers)).to match_array([new_unfollow_dossier, new_unfollow_dossier_on_gi_2]) } @@ -278,7 +290,7 @@ describe Instructeurs::ProceduresController, type: :controller do before do instructeur.followed_dossiers << new_followed_dossier - get :show, params: { procedure_id: procedure.id } + subject end it { expect(assigns(:a_suivre_dossiers)).to be_empty } @@ -293,7 +305,7 @@ describe Instructeurs::ProceduresController, type: :controller do before do instructeur.followed_dossiers << new_follow_dossier_on_gi_2 << new_follow_dossier_on_gi_3 - get :show, params: { procedure_id: procedure.id } + subject end # followed dossiers on another groupe should not be displayed @@ -305,9 +317,7 @@ describe Instructeurs::ProceduresController, type: :controller do context 'with a termine dossier with a follower' do let!(:termine_dossier) { create(:dossier, procedure: procedure, state: Dossier.states.fetch(:accepte)) } - before do - get :show, params: { procedure_id: procedure.id } - end + before { subject } it { expect(assigns(:a_suivre_dossiers)).to be_empty } it { expect(assigns(:followed_dossiers)).to be_empty } @@ -319,9 +329,7 @@ describe Instructeurs::ProceduresController, type: :controller do let!(:termine_dossier_on_gi_2) { create(:dossier, groupe_instructeur: gi_2, state: Dossier.states.fetch(:accepte)) } let!(:termine_dossier_on_gi_3) { create(:dossier, groupe_instructeur: gi_3, state: Dossier.states.fetch(:accepte)) } - before do - get :show, params: { procedure_id: procedure.id } - end + before { subject } it { expect(assigns(:termines_dossiers)).to match_array([termine_dossier, termine_dossier_on_gi_2]) } it { expect(assigns(:all_state_dossiers)).to match_array([termine_dossier, termine_dossier_on_gi_2]) } @@ -331,9 +339,7 @@ describe Instructeurs::ProceduresController, type: :controller do context 'with an archived dossier' do let!(:archived_dossier) { create(:dossier, procedure: procedure, state: Dossier.states.fetch(:en_instruction), archived: true) } - before do - get :show, params: { procedure_id: procedure.id } - end + before { subject } it { expect(assigns(:a_suivre_dossiers)).to be_empty } it { expect(assigns(:followed_dossiers)).to be_empty } @@ -345,9 +351,7 @@ describe Instructeurs::ProceduresController, type: :controller do let!(:archived_dossier_on_gi_2) { create(:dossier, groupe_instructeur: gi_2, state: Dossier.states.fetch(:en_instruction), archived: true) } let!(:archived_dossier_on_gi_3) { create(:dossier, groupe_instructeur: gi_3, state: Dossier.states.fetch(:en_instruction), archived: true) } - before do - get :show, params: { procedure_id: procedure.id } - end + before { subject } it { expect(assigns(:archived_dossiers)).to match_array([archived_dossier, archived_dossier_on_gi_2]) } end @@ -361,7 +365,7 @@ describe Instructeurs::ProceduresController, type: :controller do before do instructeur.followed_dossiers << new_followed_dossier - get :show, params: { procedure_id: procedure.id, statut: statut } + subject end context 'when statut is empty' do diff --git a/spec/controllers/users/dossiers_controller_spec.rb b/spec/controllers/users/dossiers_controller_spec.rb index 5779e099a..8f56f1f38 100644 --- a/spec/controllers/users/dossiers_controller_spec.rb +++ b/spec/controllers/users/dossiers_controller_spec.rb @@ -212,6 +212,9 @@ describe Users::DossiersController, type: :controller do let(:annee) { "2020" } let(:mois) { "02" } + let(:api_entreprise_effectifs_annuels_status) { 200 } + let(:api_entreprise_effectifs_annuels_body) { File.read('spec/fixtures/files/api_entreprise/effectifs_annuels.json') } + let(:api_exercices_status) { 200 } let(:api_exercices_body) { File.read('spec/fixtures/files/api_entreprise/exercices.json') } @@ -229,6 +232,8 @@ describe Users::DossiersController, type: :controller do .to_return(status: api_association_status, body: api_association_body) stub_request(:get, /https:\/\/entreprise.api.gouv.fr\/v2\/effectifs_mensuels_acoss_covid\/#{annee}\/#{mois}\/entreprise\/#{siren}?.*token=/) .to_return(body: api_entreprise_effectifs_mensuels_body, status: api_entreprise_effectifs_mensuels_status) + stub_request(:get, /https:\/\/entreprise.api.gouv.fr\/v2\/effectifs_annuels_acoss_covid\/#{siren}?.*token=/) + .to_return(body: api_entreprise_effectifs_annuels_body, status: api_entreprise_effectifs_annuels_status) end before do @@ -328,6 +333,7 @@ describe Users::DossiersController, type: :controller do expect(dossier.etablissement.exercices).to be_present expect(dossier.etablissement.association?).to be(true) expect(dossier.etablissement.entreprise_effectif_mensuel).to be_present + expect(dossier.etablissement.entreprise_effectif_annuel).to be_present end end end @@ -448,6 +454,28 @@ describe Users::DossiersController, type: :controller do end end + context 'with procedure routee' do + let(:procedure) { create(:procedure, :routee, :with_type_de_champ) } + let(:dossier_group) { create(:groupe_instructeur, procedure: procedure) } + let(:another_group) { create(:groupe_instructeur, procedure: procedure) } + let(:instructeur_of_dossier) { create(:instructeur) } + let(:instructeur_in_another_group) { create(:instructeur) } + let!(:dossier) { create(:dossier, groupe_instructeur: dossier_group, user: user) } + + before do + allow(DossierMailer).to receive(:notify_new_dossier_depose_to_instructeur).and_return(double(deliver_later: nil)) + create(:assign_to, instructeur: instructeur_of_dossier, procedure: dossier.procedure, instant_email_dossier_notifications_enabled: true, groupe_instructeur: dossier_group) + create(:assign_to, instructeur: instructeur_in_another_group, procedure: dossier.procedure, instant_email_dossier_notifications_enabled: true, groupe_instructeur: another_group) + end + + it "sends notification mail to instructeurs in the correct group instructeur" do + subject + + expect(DossierMailer).to have_received(:notify_new_dossier_depose_to_instructeur).once.with(dossier, instructeur_of_dossier.email) + expect(DossierMailer).not_to have_received(:notify_new_dossier_depose_to_instructeur).with(dossier, instructeur_in_another_group.email) + end + end + context "on an closed procedure" do before { dossier.procedure.close! } diff --git a/spec/factories/assign_to.rb b/spec/factories/assign_to.rb index 759c32d52..64955c283 100644 --- a/spec/factories/assign_to.rb +++ b/spec/factories/assign_to.rb @@ -1,7 +1,11 @@ FactoryBot.define do factory :assign_to do - after(:build) do |assign_to, _evaluator| - assign_to.groupe_instructeur = assign_to.procedure.defaut_groupe_instructeur + after(:build) do |assign_to, evaluator| + if evaluator.groupe_instructeur.persisted? + assign_to.groupe_instructeur = evaluator.groupe_instructeur + else + assign_to.groupe_instructeur = assign_to.procedure.defaut_groupe_instructeur + end end end end diff --git a/spec/features/users/dossier_creation_spec.rb b/spec/features/users/dossier_creation_spec.rb index 5849cd1b4..4dec03889 100644 --- a/spec/features/users/dossier_creation_spec.rb +++ b/spec/features/users/dossier_creation_spec.rb @@ -76,6 +76,8 @@ feature 'Creating a new dossier:' do .to_return(status: 404, body: '') stub_request(:get, /https:\/\/entreprise.api.gouv.fr\/v2\/effectifs_mensuels_acoss_covid\/2020\/02\/entreprise\/#{siren}?.*token=/) .to_return(status: 404, body: '') + stub_request(:get, /https:\/\/entreprise.api.gouv.fr\/v2\/effectifs_annuels_acoss_covid\/#{siren}?.*token=/) + .to_return(status: 404, body: '') end before { Timecop.freeze(Time.zone.local(2020, 3, 14)) } after { Timecop.return } diff --git a/spec/fixtures/files/api_entreprise/effectifs_annuels.json b/spec/fixtures/files/api_entreprise/effectifs_annuels.json new file mode 100644 index 000000000..da299e40d --- /dev/null +++ b/spec/fixtures/files/api_entreprise/effectifs_annuels.json @@ -0,0 +1,5 @@ +{ + "siren": "418166096", + "annee": "2019", + "effectifs_annuels": 100.5 +} diff --git a/spec/lib/api_entreprise/effectifs_annuels_adapter_spec.rb b/spec/lib/api_entreprise/effectifs_annuels_adapter_spec.rb new file mode 100644 index 000000000..2c52cb00b --- /dev/null +++ b/spec/lib/api_entreprise/effectifs_annuels_adapter_spec.rb @@ -0,0 +1,24 @@ +describe ApiEntreprise::EffectifsAnnuelsAdapter do + let(:siren) { '418166096' } + let(:procedure_id) { 22 } + let(:adapter) { described_class.new(siren, procedure_id) } + subject { adapter.to_params } + + before do + stub_request(:get, /https:\/\/entreprise.api.gouv.fr\/v2\/effectifs_annuels_acoss_covid\/#{siren}?.*token=/) + .to_return(body: body, status: status) + end + + context "when the SIREN is valid" do + let(:body) { File.read('spec/fixtures/files/api_entreprise/effectifs_annuels.json') } + let(:status) { 200 } + + it '#to_params class est une Hash ?' do + expect(subject).to be_an_instance_of(Hash) + end + + it "renvoie les effectifs de l'année antérieure" do + expect(subject[:entreprise_effectif_annuel]).to eq(100.5) + end + end +end diff --git a/spec/mailers/dossier_mailer_spec.rb b/spec/mailers/dossier_mailer_spec.rb index 7ba807f9a..d7ab78474 100644 --- a/spec/mailers/dossier_mailer_spec.rb +++ b/spec/mailers/dossier_mailer_spec.rb @@ -102,6 +102,19 @@ RSpec.describe DossierMailer, type: :mailer do it { expect(subject.body).to include(dossier.procedure.libelle) } it { expect(subject.body).to include("nous nous excusons de la gène occasionnée") } end + + describe 'termine' do + let(:dossier) { create(:dossier, :accepte) } + let(:deleted_dossier) { DeletedDossier.create_from_dossier(dossier, :expired) } + + subject { described_class.notify_automatic_deletion_to_user([deleted_dossier], dossier.user.email) } + + it { expect(subject.to).to eq([dossier.user.email]) } + it { expect(subject.subject).to eq("Un dossier a été supprimé automatiquement") } + it { expect(subject.body).to include("n° #{dossier.id} ") } + it { expect(subject.body).to include(dossier.procedure.libelle) } + it { expect(subject.body).not_to include("nous nous excusons de la gène occasionnée") } + end end describe '.notify_automatic_deletion_to_administration' do @@ -126,6 +139,18 @@ RSpec.describe DossierMailer, type: :mailer do it { expect(subject.body).to include("PDF") } it { expect(subject.body).to include("Vous avez un mois pour commencer l’instruction du dossier.") } end + + describe 'termine' do + let(:dossier) { create(:dossier, :accepte) } + + subject { described_class.notify_near_deletion_to_administration([dossier], dossier.user.email) } + + it { expect(subject.subject).to eq("Un dossier dont le traitement est terminé va bientôt être supprimé") } + it { expect(subject.body).to include("n° #{dossier.id} ") } + it { expect(subject.body).to include(dossier.procedure.libelle) } + it { expect(subject.body).to include("PDF") } + it { expect(subject.body).to include("Vous avez un mois pour archiver le dossier.") } + end end describe '.notify_near_deletion_to_user' do @@ -141,6 +166,19 @@ RSpec.describe DossierMailer, type: :mailer do it { expect(subject.body).to include("PDF") } it { expect(subject.body).to include("Vous pouvez retrouver votre dossier pendant encore un mois. Vous n’avez rien à faire.") } end + + describe 'termine' do + let(:dossier) { create(:dossier, :accepte) } + + subject { described_class.notify_near_deletion_to_user([dossier], dossier.user.email) } + + it { expect(subject.to).to eq([dossier.user.email]) } + it { expect(subject.subject).to eq("Un dossier dont le traitement est terminé va bientôt être supprimé") } + it { expect(subject.body).to include("n° #{dossier.id} ") } + it { expect(subject.body).to include(dossier.procedure.libelle) } + it { expect(subject.body).to include("PDF") } + it { expect(subject.body).to include("Vous pouvez retrouver votre dossier pendant encore un mois. Vous n’avez rien à faire.") } + end end describe '.notify_groupe_instructeur_changed_to_instructeur' do diff --git a/spec/mailers/previews/dossier_mailer_preview.rb b/spec/mailers/previews/dossier_mailer_preview.rb index fdda28d6c..c7e0a9e2d 100644 --- a/spec/mailers/previews/dossier_mailer_preview.rb +++ b/spec/mailers/previews/dossier_mailer_preview.rb @@ -28,6 +28,14 @@ class DossierMailerPreview < ActionMailer::Preview DossierMailer.notify_near_deletion_to_administration([dossier_en_construction, dossier_en_construction], administration_email) end + def notify_termine_near_deletion_to_user + DossierMailer.notify_near_deletion_to_user([dossier_accepte], usager_email) + end + + def notify_termine_near_deletion_to_administration + DossierMailer.notify_near_deletion_to_administration([dossier_accepte, dossier_accepte], administration_email) + end + def notify_brouillon_deletion DossierMailer.notify_brouillon_deletion([dossier.hash_for_deletion_mail], usager_email) end @@ -83,6 +91,10 @@ class DossierMailerPreview < ActionMailer::Preview Dossier.new(id: 47882, state: :en_construction, procedure: procedure, user: User.new(email: "usager@example.com")) end + def dossier_accepte + Dossier.new(id: 47882, state: :accepte, procedure: procedure, user: User.new(email: "usager@example.com")) + end + def procedure Procedure.new(id: 1234, libelle: 'Dotation d’Équipement des Territoires Ruraux - Exercice 2019', service: service, logo: Rack::Test::UploadedFile.new("./spec/fixtures/files/logo_test_procedure.png", 'image/png'), auto_archive_on: Time.zone.today + Dossier::REMAINING_DAYS_BEFORE_CLOSING.days) end diff --git a/spec/models/champ_spec.rb b/spec/models/champ_spec.rb index f0368e424..119be869d 100644 --- a/spec/models/champ_spec.rb +++ b/spec/models/champ_spec.rb @@ -3,6 +3,17 @@ describe Champ do it_should_behave_like "champ_spec" + describe "associations" do + it { is_expected.to belong_to(:dossier) } + + context 'when the parent dossier is discarded' do + let(:discarded_dossier) { create(:dossier, :discarded) } + subject(:champ) { discarded_dossier.champs.first } + + it { expect(champ.reload.dossier).to eq discarded_dossier } + end + end + describe "validations" do let(:row) { 1 } let(:champ) { create(:champ, type_de_champ: create(:type_de_champ), row: row) } diff --git a/spec/models/geo_area_spec.rb b/spec/models/geo_area_spec.rb new file mode 100644 index 000000000..08ede4acd --- /dev/null +++ b/spec/models/geo_area_spec.rb @@ -0,0 +1,50 @@ +RSpec.describe GeoArea, type: :model do + describe '#area' do + let(:geo_area) do + create(:geo_area, geometry: { + "type": "Polygon", + "coordinates": [ + [ + [2.428439855575562, 46.538476837725796], + [2.4284291267395024, 46.53842148758162], + [2.4282521009445195, 46.53841410755813], + [2.42824137210846, 46.53847314771794], + [2.428284287452698, 46.53847314771794], + [2.428364753723145, 46.538487907747864], + [2.4284291267395024, 46.538491597754714], + [2.428439855575562, 46.538476837725796] + ] + ] + }) + end + + it { expect(geo_area.area).to eq(219.0) } + end + + describe '#length' do + let(:geo_area) do + create(:geo_area, geometry: { + "type": "LineString", + "coordinates": [ + [2.4282521009445195, 46.53841410755813], + [2.42824137210846, 46.53847314771794], + [2.428284287452698, 46.53847314771794], + [2.4284291267395024, 46.538491597754714] + ] + }) + end + + it { expect(geo_area.length).to eq(30.8) } + end + + describe '#location' do + let(:geo_area) do + create(:geo_area, geometry: { + "type": "Point", + "coordinates": [2.428439855575562, 46.538476837725796] + }) + end + + it { expect(geo_area.location).to eq("2°25'42\"N 46°32'19\"E") } + end +end diff --git a/spec/services/api_entreprise_service_spec.rb b/spec/services/api_entreprise_service_spec.rb index ff6fec019..ca999d147 100644 --- a/spec/services/api_entreprise_service_spec.rb +++ b/spec/services/api_entreprise_service_spec.rb @@ -11,6 +11,8 @@ describe ApiEntrepriseService do .to_return(body: associations_body, status: associations_status) stub_request(:get, /https:\/\/entreprise.api.gouv.fr\/v2\/effectifs_mensuels_acoss_covid\/#{annee}\/#{mois}\/entreprise\/#{siren}?.*token=/) .to_return(body: effectifs_mensuels_body, status: effectifs_mensuels_status) + stub_request(:get, /https:\/\/entreprise.api.gouv.fr\/v2\/effectifs_annuels_acoss_covid\/#{siren}?.*token=/) + .to_return(body: effectifs_annuels_body, status: effectifs_annuels_status) end before { Timecop.freeze(Time.zone.local(2020, 3, 14)) } @@ -32,6 +34,10 @@ describe ApiEntrepriseService do let(:mois) { "02" } let(:effectif_mensuel) { 100.5 } + let(:effectifs_annuels_status) { 200 } + let(:effectifs_annuels_body) { File.read('spec/fixtures/files/api_entreprise/effectifs_annuels.json') } + let(:effectif_annuel) { 100.5 } + let(:exercices_status) { 200 } let(:exercices_body) { File.read('spec/fixtures/files/api_entreprise/exercices.json') } @@ -47,6 +53,7 @@ describe ApiEntrepriseService do expect(result[:association_rna]).to eq(rna) expect(result[:exercices_attributes]).to_not be_empty expect(result[:entreprise_effectif_mensuel]).to eq(effectif_mensuel) + expect(result[:entreprise_effectif_annuel]).to eq(effectif_annuel) end end diff --git a/spec/services/expired_dossiers_deletion_service_spec.rb b/spec/services/expired_dossiers_deletion_service_spec.rb index 3961bbd68..a040a2ebb 100644 --- a/spec/services/expired_dossiers_deletion_service_spec.rb +++ b/spec/services/expired_dossiers_deletion_service_spec.rb @@ -268,4 +268,141 @@ describe ExpiredDossiersDeletionService do it { expect(DossierMailer).to have_received(:notify_automatic_deletion_to_administration).with([deleted_dossier_2], dossier_2.procedure.administrateurs.first.email) } end end + + describe '#send_termine_expiration_notices' do + before { Timecop.freeze(Time.zone.now) } + after { Timecop.return } + + before do + allow(DossierMailer).to receive(:notify_near_deletion_to_user).and_return(double(deliver_later: nil)) + allow(DossierMailer).to receive(:notify_near_deletion_to_administration).and_return(double(deliver_later: nil)) + end + + context 'with a single dossier' do + let!(:dossier) { create(:dossier, :accepte, :followed, procedure: procedure, processed_at: processed_at) } + + before { ExpiredDossiersDeletionService.send_termine_expiration_notices } + + context 'when the dossier is not near deletion' do + let(:processed_at) { (conservation_par_defaut - 1.month - 1.day).ago } + + it { expect(dossier.reload.termine_close_to_expiration_notice_sent_at).to be_nil } + it { expect(DossierMailer).not_to have_received(:notify_near_deletion_to_user) } + it { expect(DossierMailer).not_to have_received(:notify_near_deletion_to_administration) } + end + + context 'when the dossier is near deletion' do + let(:processed_at) { (conservation_par_defaut - 1.month + 1.day).ago } + + it { expect(dossier.reload.termine_close_to_expiration_notice_sent_at).not_to be_nil } + + it { expect(DossierMailer).to have_received(:notify_near_deletion_to_user).once } + it { expect(DossierMailer).to have_received(:notify_near_deletion_to_administration).twice } + it { expect(DossierMailer).to have_received(:notify_near_deletion_to_user).with([dossier], dossier.user.email) } + it { expect(DossierMailer).to have_received(:notify_near_deletion_to_administration).with([dossier], dossier.procedure.administrateurs.first.email) } + it { expect(DossierMailer).to have_received(:notify_near_deletion_to_administration).with([dossier], dossier.followers_instructeurs.first.email) } + end + end + + context 'with 2 dossiers to notice' do + let!(:dossier_1) { create(:dossier, :accepte, procedure: procedure, user: user, processed_at: (conservation_par_defaut - 1.month + 1.day).ago) } + let!(:dossier_2) { create(:dossier, :accepte, procedure: procedure_2, user: user, processed_at: (conservation_par_defaut - 1.month + 1.day).ago) } + + let!(:instructeur) { create(:instructeur) } + + before do + instructeur.followed_dossiers << dossier_1 << dossier_2 + ExpiredDossiersDeletionService.send_termine_expiration_notices + end + + it { expect(DossierMailer).to have_received(:notify_near_deletion_to_user).once } + it { expect(DossierMailer).to have_received(:notify_near_deletion_to_administration).exactly(3).times } + it { expect(DossierMailer).to have_received(:notify_near_deletion_to_user).with(match_array([dossier_1, dossier_2]), user.email) } + it { expect(DossierMailer).to have_received(:notify_near_deletion_to_administration).with(match_array([dossier_1, dossier_2]), instructeur.email) } + it { expect(DossierMailer).to have_received(:notify_near_deletion_to_administration).with([dossier_1], dossier_1.procedure.administrateurs.first.email) } + it { expect(DossierMailer).to have_received(:notify_near_deletion_to_administration).with([dossier_2], dossier_2.procedure.administrateurs.first.email) } + end + + context 'when an instructeur is also administrateur' do + let!(:administrateur) { procedure.administrateurs.first } + let!(:dossier) { create(:dossier, :accepte, procedure: procedure, processed_at: (conservation_par_defaut - 1.month + 1.day).ago) } + + before do + administrateur.instructeur.followed_dossiers << dossier + ExpiredDossiersDeletionService.send_termine_expiration_notices + end + + it { expect(DossierMailer).to have_received(:notify_near_deletion_to_user).once } + it { expect(DossierMailer).to have_received(:notify_near_deletion_to_user).with([dossier], dossier.user.email) } + it { expect(DossierMailer).to have_received(:notify_near_deletion_to_administration).with([dossier], administrateur.email) } + end + end + + describe '#delete_expired_termine_and_notify' do + before { Timecop.freeze(Time.zone.now) } + after { Timecop.return } + + before do + allow(DossierMailer).to receive(:notify_automatic_deletion_to_user).and_return(double(deliver_later: nil)) + allow(DossierMailer).to receive(:notify_automatic_deletion_to_administration).and_return(double(deliver_later: nil)) + end + + context 'with a single dossier' do + let!(:dossier) { create(:dossier, :accepte, :followed, procedure: procedure, termine_close_to_expiration_notice_sent_at: notice_sent_at) } + let(:deleted_dossier) { DeletedDossier.find_by(dossier_id: dossier.id) } + + before { ExpiredDossiersDeletionService.delete_expired_termine_and_notify } + + context 'when no notice has been sent' do + let(:notice_sent_at) { nil } + + it { expect { dossier.reload }.not_to raise_error } + it { expect(DossierMailer).not_to have_received(:notify_automatic_deletion_to_user) } + it { expect(DossierMailer).not_to have_received(:notify_automatic_deletion_to_administration) } + end + + context 'when a notice has been sent not so long ago' do + let(:notice_sent_at) { (warning_period - 4.days).ago } + + it { expect { dossier.reload }.not_to raise_error } + it { expect(DossierMailer).not_to have_received(:notify_automatic_deletion_to_user) } + it { expect(DossierMailer).not_to have_received(:notify_automatic_deletion_to_administration) } + end + + context 'when a notice has been sent a long time ago' do + let(:notice_sent_at) { (warning_period + 4.days).ago } + + it { expect { dossier.reload }.to raise_error(ActiveRecord::RecordNotFound) } + + it { expect(DossierMailer).to have_received(:notify_automatic_deletion_to_user).once } + it { expect(DossierMailer).to have_received(:notify_automatic_deletion_to_user).with([deleted_dossier], dossier.user.email) } + + it { expect(DossierMailer).to have_received(:notify_automatic_deletion_to_administration).twice } + it { expect(DossierMailer).to have_received(:notify_automatic_deletion_to_administration).with([deleted_dossier], dossier.procedure.administrateurs.first.email) } + it { expect(DossierMailer).to have_received(:notify_automatic_deletion_to_administration).with([deleted_dossier], dossier.followers_instructeurs.first.email) } + end + end + + context 'with 2 dossiers to delete' do + let!(:dossier_1) { create(:dossier, :accepte, procedure: procedure, user: user, termine_close_to_expiration_notice_sent_at: (warning_period + 1.day).ago) } + let!(:dossier_2) { create(:dossier, :refuse, procedure: procedure_2, user: user, termine_close_to_expiration_notice_sent_at: (warning_period + 1.day).ago) } + let(:deleted_dossier_1) { DeletedDossier.find_by(dossier_id: dossier_1.id) } + let(:deleted_dossier_2) { DeletedDossier.find_by(dossier_id: dossier_2.id) } + + let!(:instructeur) { create(:instructeur) } + + before do + instructeur.followed_dossiers << dossier_1 << dossier_2 + ExpiredDossiersDeletionService.delete_expired_termine_and_notify + end + + it { expect(DossierMailer).to have_received(:notify_automatic_deletion_to_user).once } + it { expect(DossierMailer).to have_received(:notify_automatic_deletion_to_user).with(match_array([deleted_dossier_1, deleted_dossier_2]), user.email) } + + it { expect(DossierMailer).to have_received(:notify_automatic_deletion_to_administration).thrice } + it { expect(DossierMailer).to have_received(:notify_automatic_deletion_to_administration).with(match_array([deleted_dossier_1, deleted_dossier_2]), instructeur.email) } + it { expect(DossierMailer).to have_received(:notify_automatic_deletion_to_administration).with([deleted_dossier_1], dossier_1.procedure.administrateurs.first.email) } + it { expect(DossierMailer).to have_received(:notify_automatic_deletion_to_administration).with([deleted_dossier_2], dossier_2.procedure.administrateurs.first.email) } + end + end end