commit
53db2c95ab
47 changed files with 1872 additions and 633 deletions
2
Gemfile
2
Gemfile
|
@ -1,6 +1,7 @@
|
|||
source 'https://rubygems.org'
|
||||
|
||||
gem 'aasm'
|
||||
gem 'actiontext', github: 'kobaltz/actiontext', branch: 'archive', require: 'action_text'
|
||||
gem 'active_link_to' # Automatically set a class on active links
|
||||
gem 'active_model_serializers'
|
||||
gem 'activestorage-openstack', git: 'https://github.com/fredZen/activestorage-openstack.git', branch: 'frederic/fix_upload_signature'
|
||||
|
@ -103,6 +104,7 @@ group :development, :test do
|
|||
gem 'rspec-rails'
|
||||
gem 'rspec_junit_formatter', require: false
|
||||
gem 'ruby-debug-ide', require: false
|
||||
gem 'simple_xlsx_reader'
|
||||
gem 'spring' # Spring speeds up development by keeping your application running in the background
|
||||
gem 'spring-commands-rspec'
|
||||
end
|
||||
|
|
14
Gemfile.lock
14
Gemfile.lock
|
@ -1,3 +1,12 @@
|
|||
GIT
|
||||
remote: git://github.com/kobaltz/actiontext.git
|
||||
revision: ef59c4ba99d1b7614dd47f5a294eef553224db88
|
||||
branch: archive
|
||||
specs:
|
||||
actiontext (0.1.0)
|
||||
nokogiri
|
||||
rails (>= 5.2.0)
|
||||
|
||||
GIT
|
||||
remote: https://github.com/fredZen/activestorage-openstack.git
|
||||
revision: c71d5107a51701eab9d9267dd0000e6c1cf3e39a
|
||||
|
@ -576,6 +585,9 @@ GEM
|
|||
simple_form (4.1.0)
|
||||
actionpack (>= 5.0)
|
||||
activemodel (>= 5.0)
|
||||
simple_xlsx_reader (1.0.4)
|
||||
nokogiri
|
||||
rubyzip
|
||||
sinatra (2.0.5)
|
||||
mustermann (~> 1.0)
|
||||
rack (~> 2.0)
|
||||
|
@ -666,6 +678,7 @@ PLATFORMS
|
|||
|
||||
DEPENDENCIES
|
||||
aasm
|
||||
actiontext!
|
||||
active_link_to
|
||||
active_model_serializers
|
||||
activestorage-openstack!
|
||||
|
@ -747,6 +760,7 @@ DEPENDENCIES
|
|||
sentry-raven
|
||||
shoulda-matchers
|
||||
simple_form
|
||||
simple_xlsx_reader
|
||||
skylight
|
||||
smart_listing
|
||||
spreadsheet_architect
|
||||
|
|
|
@ -63,7 +63,7 @@ En local, un utilisateur de test est créé automatiquement, avec les identifian
|
|||
|
||||
AutoArchiveProcedureJob.set(cron: "* * * * *").perform_later
|
||||
WeeklyOverviewJob.set(cron: "0 7 * * 1").perform_later
|
||||
AutoReceiveDossiersForProcedureJob.set(cron: "* * * * *").perform_later(procedure_declaratoire_id, Dossier.states.fetch(:en_instruction))
|
||||
DeclarativeProceduresJob.set(cron: "* * * * *").perform_later
|
||||
UpdateAdministrateurUsageStatisticsJob.set(cron: "0 10 * * *").perform_later
|
||||
FindDubiousProceduresJob.set(cron: "0 0 * * *").perform_later
|
||||
Administrateurs::ActivateBeforeExpirationJob.set(cron: "0 8 * * *").perform_later
|
||||
|
|
36
app/assets/stylesheets/new_design/actiontext.scss
Normal file
36
app/assets/stylesheets/new_design/actiontext.scss
Normal file
|
@ -0,0 +1,36 @@
|
|||
//
|
||||
// Provides a drop-in pointer for the default Trix stylesheet that will format the toolbar and
|
||||
// the trix-editor content (whether displayed or under editing). Feel free to incorporate this
|
||||
// inclusion directly in any other asset bundle and remove this file.
|
||||
//
|
||||
// = require trix
|
||||
|
||||
// We need to override trix.css’s image gallery styles to accommodate the
|
||||
// <action-text-attachment> element we wrap around attachments. Otherwise,
|
||||
// images in galleries will be squished by the max-width: 33%; rule.
|
||||
.trix-content {
|
||||
.attachment-gallery {
|
||||
> action-text-attachment,
|
||||
> .attachment {
|
||||
flex: 1 0 33%;
|
||||
padding: 0 0.5em;
|
||||
max-width: 33%;
|
||||
}
|
||||
|
||||
&.attachment-gallery--2,
|
||||
&.attachment-gallery--4 {
|
||||
> action-text-attachment,
|
||||
> .attachment {
|
||||
flex-basis: 50%;
|
||||
max-width: 50%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
action-text-attachment {
|
||||
.attachment {
|
||||
padding: 0 !important;
|
||||
max-width: 100% !important;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -183,7 +183,7 @@
|
|||
color: $black;
|
||||
padding: $default-padding;
|
||||
|
||||
h4 {
|
||||
.title {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
|
@ -266,6 +266,10 @@
|
|||
color: $black;
|
||||
margin-bottom: $default-spacer;
|
||||
}
|
||||
|
||||
&.with-top-border {
|
||||
border-top: 1px solid $grey;
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown-form {
|
||||
|
|
|
@ -93,6 +93,13 @@ module Gestionnaires
|
|||
render partial: 'state_button_refresh', locals: { dossier: dossier }
|
||||
end
|
||||
|
||||
def repasser_en_instruction
|
||||
flash.notice = "Le dossier #{dossier.id} a été repassé en instruction."
|
||||
dossier.repasser_en_instruction!(current_gestionnaire)
|
||||
|
||||
render partial: 'state_button_refresh', locals: { dossier: dossier }
|
||||
end
|
||||
|
||||
def terminer
|
||||
motivation = params[:dossier] && params[:dossier][:motivation]
|
||||
justificatif = params[:dossier] && params[:dossier][:justificatif_motivation]
|
||||
|
|
|
@ -168,7 +168,7 @@ module Gestionnaires
|
|||
end
|
||||
|
||||
def download_dossiers
|
||||
options = params.permit(:limit, :since, tables: [])
|
||||
options = params.permit(:version, :limit, :since, tables: [])
|
||||
|
||||
respond_to do |format|
|
||||
format.csv do
|
||||
|
|
|
@ -20,16 +20,6 @@ module Manager
|
|||
# Custom actions
|
||||
#
|
||||
|
||||
def change_state_to_instruction
|
||||
dossier = Dossier.find(params[:id])
|
||||
dossier.update(state: Dossier.states.fetch(:en_instruction), processed_at: nil, motivation: nil)
|
||||
dossier.attestation&.destroy
|
||||
logger.info("Le dossier #{dossier.id} est repassé en instruction par #{current_administration.email}")
|
||||
flash[:notice] = "Le dossier #{dossier.id} est repassé en instruction"
|
||||
DossierMailer.notify_revert_to_instruction(dossier).deliver_later
|
||||
redirect_to manager_dossier_path(dossier)
|
||||
end
|
||||
|
||||
def hide
|
||||
dossier = Dossier.find(params[:id])
|
||||
deleted_dossier = dossier.hide!(current_administration)
|
||||
|
|
|
@ -5,7 +5,7 @@ module DossierHelper
|
|||
elsif dossier.sans_suite?
|
||||
'without-continuation'
|
||||
elsif dossier.refuse?
|
||||
'refuse'
|
||||
'refused'
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -54,6 +54,13 @@ module ProcedureHelper
|
|||
}
|
||||
end
|
||||
|
||||
def procedure_dossiers_download_path(procedure, format:, version:)
|
||||
download_dossiers_gestionnaire_procedure_path(format: format,
|
||||
procedure_id: procedure.id,
|
||||
tables: [:etablissements],
|
||||
version: version)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
TOGGLES = {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import '../shared/polyfills';
|
||||
import Turbolinks from 'turbolinks';
|
||||
import Rails from 'rails-ujs';
|
||||
import * as ActiveStorage from 'activestorage';
|
||||
import Rails from '@rails/ujs';
|
||||
import * as ActiveStorage from '@rails/activestorage';
|
||||
import jQuery from 'jquery';
|
||||
|
||||
import '../shared/activestorage/ujs';
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import '../shared/polyfills';
|
||||
import Turbolinks from 'turbolinks';
|
||||
import Rails from 'rails-ujs';
|
||||
import * as ActiveStorage from 'activestorage';
|
||||
import Rails from '@rails/ujs';
|
||||
import * as ActiveStorage from '@rails/activestorage';
|
||||
import '@rails/actiontext';
|
||||
import Chartkick from 'chartkick';
|
||||
import Highcharts from 'highcharts';
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { DirectUpload } from 'activestorage';
|
||||
import { DirectUpload } from '@rails/activestorage';
|
||||
import ProgressBar from './progress-bar';
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import Rails from 'rails-ujs';
|
||||
import Rails from '@rails/ujs';
|
||||
import jQuery from 'jquery';
|
||||
import { delegate } from '@utils';
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import Rails from 'rails-ujs';
|
||||
import Rails from '@rails/ujs';
|
||||
import $ from 'jquery';
|
||||
import debounce from 'debounce';
|
||||
|
||||
|
|
7
app/jobs/declarative_procedures_job.rb
Normal file
7
app/jobs/declarative_procedures_job.rb
Normal file
|
@ -0,0 +1,7 @@
|
|||
class DeclarativeProceduresJob < ApplicationJob
|
||||
queue_as :cron
|
||||
|
||||
def perform(*args)
|
||||
Procedure.declarative.find_each(&:process_dossiers!)
|
||||
end
|
||||
end
|
|
@ -37,6 +37,16 @@ class Avis < ApplicationRecord
|
|||
Avis.find_by(id: avis_id)&.email == email
|
||||
end
|
||||
|
||||
def spreadsheet_columns
|
||||
[
|
||||
['Dossier ID', dossier_id.to_s],
|
||||
['Question / Introduction', :introduction],
|
||||
['Réponse', :answer],
|
||||
['Créé le', :created_at],
|
||||
['Répondu le', :updated_at]
|
||||
]
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def notify_gestionnaire
|
||||
|
|
|
@ -20,4 +20,25 @@ class Champs::RepetitionChamp < Champ
|
|||
def search_terms
|
||||
# The user cannot enter any information here so it doesn’t make much sense to search
|
||||
end
|
||||
|
||||
class Row < Hashie::Dash
|
||||
property :index
|
||||
property :dossier_id
|
||||
property :champs
|
||||
|
||||
def spreadsheet_columns
|
||||
[
|
||||
['Dossier ID', :dossier_id],
|
||||
['Ligne', :index]
|
||||
] + exported_champs
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def exported_champs
|
||||
champs.reject(&:exclude_from_export?).map do |champ|
|
||||
[champ.libelle, champ.for_export]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -11,6 +11,15 @@ module MailTemplateConcern
|
|||
replace_tags(body, dossier)
|
||||
end
|
||||
|
||||
def update_rich_body
|
||||
self.rich_body = self.body
|
||||
end
|
||||
|
||||
included do
|
||||
has_rich_text :rich_body
|
||||
before_save :update_rich_body
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
def default_for_procedure(procedure)
|
||||
template_name = default_template_name_for_procedure(procedure)
|
||||
|
|
|
@ -40,6 +40,54 @@ class Dossier < ApplicationRecord
|
|||
accepts_nested_attributes_for :champs
|
||||
accepts_nested_attributes_for :champs_private
|
||||
|
||||
include AASM
|
||||
|
||||
aasm whiny_persistence: true, column: :state, enum: true do
|
||||
state :brouillon, initial: true
|
||||
state :en_construction
|
||||
state :en_instruction
|
||||
state :accepte
|
||||
state :refuse
|
||||
state :sans_suite
|
||||
|
||||
event :passer_en_construction, after: :after_passer_en_construction do
|
||||
transitions from: :brouillon, to: :en_construction
|
||||
end
|
||||
|
||||
event :passer_en_instruction, after: :after_passer_en_instruction do
|
||||
transitions from: :en_construction, to: :en_instruction
|
||||
end
|
||||
|
||||
event :passer_automatiquement_en_instruction, after: :after_passer_automatiquement_en_instruction do
|
||||
transitions from: :en_construction, to: :en_instruction
|
||||
end
|
||||
|
||||
event :repasser_en_construction, after: :after_repasser_en_construction do
|
||||
transitions from: :en_instruction, to: :en_construction
|
||||
end
|
||||
|
||||
event :accepter, after: :after_accepter do
|
||||
transitions from: :en_instruction, to: :accepte
|
||||
end
|
||||
|
||||
event :accepter_automatiquement, after: :after_accepter_automatiquement do
|
||||
transitions from: :en_construction, to: :accepte
|
||||
end
|
||||
|
||||
event :refuser, after: :after_refuser do
|
||||
transitions from: :en_instruction, to: :refuse
|
||||
end
|
||||
|
||||
event :classer_sans_suite, after: :after_classer_sans_suite do
|
||||
transitions from: :en_instruction, to: :sans_suite
|
||||
end
|
||||
|
||||
event :repasser_en_instruction, after: :after_repasser_en_instruction do
|
||||
transitions from: :refuse, to: :en_instruction
|
||||
transitions from: :sans_suite, to: :en_instruction
|
||||
end
|
||||
end
|
||||
|
||||
default_scope { where(hidden_at: nil) }
|
||||
scope :state_brouillon, -> { where(state: states.fetch(:brouillon)) }
|
||||
scope :state_not_brouillon, -> { where.not(state: states.fetch(:brouillon)) }
|
||||
|
@ -59,7 +107,7 @@ class Dossier < ApplicationRecord
|
|||
scope :en_construction, -> { not_archived.state_en_construction }
|
||||
scope :en_instruction, -> { not_archived.state_en_instruction }
|
||||
scope :termine, -> { not_archived.state_termine }
|
||||
scope :downloadable_sorted, -> { state_not_brouillon.includes(:etablissement, :user, :individual, :followers_gestionnaires, champs: { etablissement: [], type_de_champ: :drop_down_list }, champs_private: { etablissement: [], type_de_champ: :drop_down_list }).order(en_construction_at: 'asc') }
|
||||
scope :downloadable_sorted, -> { state_not_brouillon.includes(:etablissement, :user, :individual, :followers_gestionnaires, :avis, champs: { etablissement: [:champ], type_de_champ: :drop_down_list }, champs_private: { etablissement: [:champ], type_de_champ: :drop_down_list }).order(en_construction_at: 'asc') }
|
||||
scope :en_cours, -> { not_archived.state_en_construction_ou_instruction }
|
||||
scope :without_followers, -> { left_outer_joins(:follows).where(follows: { id: nil }) }
|
||||
scope :followed_by, -> (gestionnaire) { joins(:follows).where(follows: { gestionnaire: gestionnaire }) }
|
||||
|
@ -282,55 +330,85 @@ class Dossier < ApplicationRecord
|
|||
DossierMailer.notify_deletion_to_user(deleted_dossier, user.email).deliver_later
|
||||
end
|
||||
|
||||
def passer_en_instruction!(gestionnaire)
|
||||
en_instruction!
|
||||
def after_passer_en_instruction(gestionnaire)
|
||||
gestionnaire.follow(self)
|
||||
|
||||
log_dossier_operation(gestionnaire, :passer_en_instruction)
|
||||
end
|
||||
|
||||
def passer_automatiquement_en_instruction!
|
||||
en_instruction!
|
||||
|
||||
def after_passer_automatiquement_en_instruction
|
||||
log_automatic_dossier_operation(:passer_en_instruction)
|
||||
end
|
||||
|
||||
def repasser_en_construction!(gestionnaire)
|
||||
def after_repasser_en_construction(gestionnaire)
|
||||
self.en_instruction_at = nil
|
||||
en_construction!
|
||||
|
||||
save!
|
||||
log_dossier_operation(gestionnaire, :repasser_en_construction)
|
||||
end
|
||||
|
||||
def accepter!(gestionnaire, motivation, justificatif = nil)
|
||||
def after_repasser_en_instruction(gestionnaire)
|
||||
self.processed_at = nil
|
||||
self.motivation = nil
|
||||
attestation&.destroy
|
||||
|
||||
save!
|
||||
DossierMailer.notify_revert_to_instruction(self).deliver_later
|
||||
log_dossier_operation(gestionnaire, :repasser_en_instruction)
|
||||
end
|
||||
|
||||
def after_accepter(gestionnaire, motivation, justificatif = nil)
|
||||
self.motivation = motivation
|
||||
self.en_instruction_at ||= Time.zone.now
|
||||
|
||||
if justificatif
|
||||
self.justificatif_motivation.attach(justificatif)
|
||||
end
|
||||
accepte!
|
||||
|
||||
if attestation.nil?
|
||||
update(attestation: build_attestation)
|
||||
self.attestation = build_attestation
|
||||
end
|
||||
|
||||
save!
|
||||
NotificationMailer.send_closed_notification(self).deliver_later
|
||||
log_dossier_operation(gestionnaire, :accepter, self)
|
||||
end
|
||||
|
||||
def accepter_automatiquement!
|
||||
def after_accepter_automatiquement
|
||||
self.en_instruction_at ||= Time.zone.now
|
||||
|
||||
accepte!
|
||||
|
||||
if attestation.nil?
|
||||
update(attestation: build_attestation)
|
||||
self.attestation = build_attestation
|
||||
end
|
||||
|
||||
save!
|
||||
NotificationMailer.send_closed_notification(self).deliver_later
|
||||
log_automatic_dossier_operation(:accepter, self)
|
||||
end
|
||||
|
||||
def after_refuser(gestionnaire, motivation, justificatif = nil)
|
||||
self.motivation = motivation
|
||||
|
||||
if justificatif
|
||||
self.justificatif_motivation.attach(justificatif)
|
||||
end
|
||||
|
||||
save!
|
||||
NotificationMailer.send_refused_notification(self).deliver_later
|
||||
log_dossier_operation(gestionnaire, :refuser, self)
|
||||
end
|
||||
|
||||
def after_classer_sans_suite(gestionnaire, motivation, justificatif = nil)
|
||||
self.motivation = motivation
|
||||
|
||||
if justificatif
|
||||
self.justificatif_motivation.attach(justificatif)
|
||||
end
|
||||
|
||||
save!
|
||||
NotificationMailer.send_without_continuation_notification(self).deliver_later
|
||||
log_dossier_operation(gestionnaire, :classer_sans_suite, self)
|
||||
end
|
||||
|
||||
def hide!(administration)
|
||||
update(hidden_at: Time.zone.now)
|
||||
|
||||
|
@ -338,30 +416,6 @@ class Dossier < ApplicationRecord
|
|||
log_dossier_operation(administration, :supprimer, self)
|
||||
end
|
||||
|
||||
def refuser!(gestionnaire, motivation, justificatif = nil)
|
||||
self.motivation = motivation
|
||||
self.en_instruction_at ||= Time.zone.now
|
||||
if justificatif
|
||||
self.justificatif_motivation.attach(justificatif)
|
||||
end
|
||||
refuse!
|
||||
|
||||
NotificationMailer.send_refused_notification(self).deliver_later
|
||||
log_dossier_operation(gestionnaire, :refuser, self)
|
||||
end
|
||||
|
||||
def classer_sans_suite!(gestionnaire, motivation, justificatif = nil)
|
||||
self.motivation = motivation
|
||||
self.en_instruction_at ||= Time.zone.now
|
||||
if justificatif
|
||||
self.justificatif_motivation.attach(justificatif)
|
||||
end
|
||||
sans_suite!
|
||||
|
||||
NotificationMailer.send_without_continuation_notification(self).deliver_later
|
||||
log_dossier_operation(gestionnaire, :classer_sans_suite, self)
|
||||
end
|
||||
|
||||
def check_mandatory_champs
|
||||
(champs + champs.select(&:repetition?).flat_map(&:champs))
|
||||
.select(&:mandatory_and_blank?)
|
||||
|
@ -380,6 +434,37 @@ class Dossier < ApplicationRecord
|
|||
log_dossier_operation(avis.claimant, :demander_un_avis, avis)
|
||||
end
|
||||
|
||||
def spreadsheet_columns
|
||||
[
|
||||
['ID', id.to_s],
|
||||
['Email', user.email],
|
||||
['Civilité', individual&.gender],
|
||||
['Nom', individual&.nom],
|
||||
['Prénom', individual&.prenom],
|
||||
['Date de naissance', individual&.birthdate],
|
||||
['Archivé', :archived],
|
||||
['État du dossier', I18n.t(state, scope: [:activerecord, :attributes, :dossier, :state])],
|
||||
['Dernière mise à jour le', :updated_at],
|
||||
['Passé en construction le', :en_instruction_at],
|
||||
['Passé en instruction le', :en_construction_at],
|
||||
['Traité le', :processed_at],
|
||||
['Motivation de la décision', :motivation],
|
||||
['Instructeurs', followers_gestionnaires.map(&:email).join(' ')]
|
||||
] + champs_for_export + annotations_for_export
|
||||
end
|
||||
|
||||
def champs_for_export
|
||||
champs.reject(&:exclude_from_export?).map do |champ|
|
||||
[champ.libelle, champ.for_export]
|
||||
end
|
||||
end
|
||||
|
||||
def annotations_for_export
|
||||
champs_private.reject(&:exclude_from_export?).map do |champ|
|
||||
[champ.libelle, champ.for_export]
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def log_dossier_operation(author, operation, subject = nil)
|
||||
|
@ -411,13 +496,13 @@ class Dossier < ApplicationRecord
|
|||
end
|
||||
|
||||
def send_dossier_received
|
||||
if saved_change_to_state? && en_instruction?
|
||||
if saved_change_to_state? && en_instruction? && !procedure.declarative_accepte?
|
||||
NotificationMailer.send_dossier_received(self).deliver_later
|
||||
end
|
||||
end
|
||||
|
||||
def send_draft_notification_email
|
||||
if brouillon?
|
||||
if brouillon? && !procedure.declarative?
|
||||
DossierMailer.notify_new_draft(self).deliver_later
|
||||
end
|
||||
end
|
||||
|
|
|
@ -2,6 +2,7 @@ class DossierOperationLog < ApplicationRecord
|
|||
enum operation: {
|
||||
passer_en_instruction: 'passer_en_instruction',
|
||||
repasser_en_construction: 'repasser_en_construction',
|
||||
repasser_en_instruction: 'repasser_en_instruction',
|
||||
accepter: 'accepter',
|
||||
refuser: 'refuser',
|
||||
classer_sans_suite: 'classer_sans_suite',
|
||||
|
|
|
@ -33,6 +33,43 @@ class Etablissement < ApplicationRecord
|
|||
]
|
||||
end
|
||||
|
||||
def spreadsheet_columns
|
||||
[
|
||||
['Dossier ID', :dossier_id_for_export],
|
||||
['Champ', :libelle_for_export],
|
||||
['Établissement SIRET', :siret],
|
||||
['Établissement siège social', :siege_social],
|
||||
['Établissement NAF', :naf],
|
||||
['Établissement libellé NAF', :libelle_naf],
|
||||
['Établissement Adresse', :adresse],
|
||||
['Établissement numero voie', :numero_voie],
|
||||
['Établissement type voie', :type_voie],
|
||||
['Établissement nom voie', :nom_voie],
|
||||
['Établissement complément adresse', :complement_adresse],
|
||||
['Établissement code postal', :code_postal],
|
||||
['Établissement localité', :localite],
|
||||
['Établissement code INSEE localité', :code_insee_localite],
|
||||
['Entreprise SIREN', :entreprise_siren],
|
||||
['Entreprise capital social', :entreprise_capital_social],
|
||||
['Entreprise numero TVA intracommunautaire', :entreprise_numero_tva_intracommunautaire],
|
||||
['Entreprise forme juridique', :entreprise_forme_juridique],
|
||||
['Entreprise forme juridique code', :entreprise_forme_juridique_code],
|
||||
['Entreprise nom commercial', :entreprise_nom_commercial],
|
||||
['Entreprise raison sociale', :entreprise_raison_sociale],
|
||||
['Entreprise SIRET siège social', :entreprise_siret_siege_social],
|
||||
['Entreprise code effectif entreprise', :entreprise_code_effectif_entreprise],
|
||||
['Entreprise date de création', :entreprise_date_creation],
|
||||
['Entreprise nom', :entreprise_nom],
|
||||
['Entreprise prénom', :entreprise_prenom],
|
||||
['Association RNA', :association_rna],
|
||||
['Association titre', :association_titre],
|
||||
['Association objet', :association_objet],
|
||||
['Association date de création', :association_date_creation],
|
||||
['Association date de déclaration', :association_date_declaration],
|
||||
['Association date de publication', :association_date_publication]
|
||||
]
|
||||
end
|
||||
|
||||
def siren
|
||||
entreprise_siren
|
||||
end
|
||||
|
@ -71,4 +108,18 @@ class Etablissement < ApplicationRecord
|
|||
inline_adresse: inline_adresse
|
||||
)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def dossier_id_for_export
|
||||
if dossier_id
|
||||
dossier_id.to_s
|
||||
elsif champ
|
||||
champ.dossier_id.to_s
|
||||
end
|
||||
end
|
||||
|
||||
def libelle_for_export
|
||||
champ&.libelle
|
||||
end
|
||||
end
|
||||
|
|
|
@ -46,6 +46,7 @@ class Procedure < ApplicationRecord
|
|||
scope :created_during, -> (range) { where(created_at: range) }
|
||||
scope :cloned_from_library, -> { where(cloned_from_library: true) }
|
||||
scope :avec_lien, -> { where.not(path: nil) }
|
||||
scope :declarative, -> { where.not(declarative_with_state: nil) }
|
||||
|
||||
scope :for_api, -> {
|
||||
includes(
|
||||
|
@ -57,6 +58,11 @@ class Procedure < ApplicationRecord
|
|||
)
|
||||
}
|
||||
|
||||
enum declarative_with_state: {
|
||||
en_instruction: 'en_instruction',
|
||||
accepte: 'accepte'
|
||||
}
|
||||
|
||||
validates :libelle, presence: true, allow_blank: false, allow_nil: false
|
||||
validates :description, presence: true, allow_blank: false, allow_nil: false
|
||||
validates :administrateurs, presence: true
|
||||
|
@ -141,6 +147,14 @@ class Procedure < ApplicationRecord
|
|||
module_api_carto&.use_api_carto? && module_api_carto&.migrated?
|
||||
end
|
||||
|
||||
def declarative?
|
||||
declarative_with_state.present?
|
||||
end
|
||||
|
||||
def declarative_accepte?
|
||||
declarative_with_state == Procedure.declarative_with_states.fetch(:accepte)
|
||||
end
|
||||
|
||||
# Warning: dossier after_save build_default_champs must be removed
|
||||
# to save a dossier created from this method
|
||||
def new_dossier
|
||||
|
@ -282,8 +296,14 @@ class Procedure < ApplicationRecord
|
|||
end
|
||||
|
||||
def export(options = {})
|
||||
version = options.delete(:version)
|
||||
if version == 'v2'
|
||||
options.delete(:tables)
|
||||
ProcedureExportV2Service.new(self, **options.to_h.symbolize_keys)
|
||||
else
|
||||
ProcedureExportService.new(self, **options.to_h.symbolize_keys)
|
||||
end
|
||||
end
|
||||
|
||||
def to_csv(options = {})
|
||||
export(options).to_csv
|
||||
|
@ -431,6 +451,19 @@ class Procedure < ApplicationRecord
|
|||
update!(collection_attribute_name => attributes)
|
||||
end
|
||||
|
||||
def process_dossiers!
|
||||
case declarative_with_state
|
||||
when Procedure.declarative_with_states.fetch(:en_instruction)
|
||||
dossiers
|
||||
.state_en_construction
|
||||
.find_each(&:passer_automatiquement_en_instruction!)
|
||||
when Procedure.declarative_with_states.fetch(:accepte)
|
||||
dossiers
|
||||
.state_en_construction
|
||||
.find_each(&:accepter_automatiquement!)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def move_type_de_champ_attributes(types_de_champ, type_de_champ, new_index)
|
||||
|
|
88
app/services/procedure_export_v2_service.rb
Normal file
88
app/services/procedure_export_v2_service.rb
Normal file
|
@ -0,0 +1,88 @@
|
|||
class ProcedureExportV2Service
|
||||
attr_reader :dossiers
|
||||
|
||||
def initialize(procedure, ids: nil, since: nil, limit: nil)
|
||||
@procedure = procedure
|
||||
@dossiers = procedure.dossiers.downloadable_sorted
|
||||
if ids
|
||||
@dossiers = @dossiers.where(id: ids)
|
||||
end
|
||||
if since
|
||||
@dossiers = @dossiers.since(since)
|
||||
end
|
||||
if limit
|
||||
@dossiers = @dossiers.limit(limit)
|
||||
end
|
||||
@tables = [:dossiers, :etablissements, :avis] + champs_repetables_options
|
||||
end
|
||||
|
||||
def to_csv(table = :dossiers)
|
||||
SpreadsheetArchitect.to_csv(options_for(table))
|
||||
end
|
||||
|
||||
def to_xlsx
|
||||
# We recursively build multi page spreadsheet
|
||||
@tables.reduce(nil) do |package, table|
|
||||
SpreadsheetArchitect.to_axlsx_package(options_for(table), package)
|
||||
end.to_stream.read
|
||||
end
|
||||
|
||||
def to_ods
|
||||
# We recursively build multi page spreadsheet
|
||||
@tables.reduce(nil) do |spreadsheet, table|
|
||||
SpreadsheetArchitect.to_rodf_spreadsheet(options_for(table), spreadsheet)
|
||||
end.bytes
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def etablissements
|
||||
@etablissements ||= dossiers.flat_map do |dossier|
|
||||
[dossier.champs, dossier.champs_private]
|
||||
.flatten
|
||||
.select { |champ| champ.is_a?(Champs::SiretChamp) }
|
||||
end.map(&:etablissement).compact + dossiers.map(&:etablissement).compact
|
||||
end
|
||||
|
||||
def avis
|
||||
@avis ||= dossiers.flat_map(&:avis)
|
||||
end
|
||||
|
||||
def champs_repetables
|
||||
@champs_repetables ||= dossiers.flat_map do |dossier|
|
||||
[dossier.champs, dossier.champs_private]
|
||||
.flatten
|
||||
.select { |champ| champ.is_a?(Champs::RepetitionChamp) }
|
||||
end
|
||||
end
|
||||
|
||||
def champs_repetables_options
|
||||
champs_repetables.map do |champ|
|
||||
[
|
||||
champ.libelle,
|
||||
champ.rows.each_with_index.map do |champs, index|
|
||||
Champs::RepetitionChamp::Row.new(index: index + 1, dossier_id: champ.dossier_id.to_s, champs: champs)
|
||||
end
|
||||
]
|
||||
end
|
||||
end
|
||||
|
||||
DEFAULT_STYLES = {
|
||||
header_style: { background_color: "000000", color: "FFFFFF", font_size: 12, bold: true },
|
||||
row_style: { background_color: nil, color: "000000", font_size: 12 }
|
||||
}
|
||||
|
||||
def options_for(table)
|
||||
case table
|
||||
when :dossiers
|
||||
{ instances: dossiers.to_a, sheet_name: 'Dossiers' }.merge(DEFAULT_STYLES)
|
||||
when :etablissements
|
||||
{ instances: etablissements.to_a, sheet_name: 'Etablissements' }.merge(DEFAULT_STYLES)
|
||||
when :avis
|
||||
{ instances: avis.to_a, sheet_name: 'Avis' }.merge(DEFAULT_STYLES)
|
||||
when Array
|
||||
# We have to truncate the label here as spreadsheets have a (30 char) limit on length.
|
||||
{ instances: table.last, sheet_name: table.first.to_s.truncate(30) }.merge(DEFAULT_STYLES)
|
||||
end
|
||||
end
|
||||
end
|
14
app/views/active_storage/blobs/_blob.html.erb
Normal file
14
app/views/active_storage/blobs/_blob.html.erb
Normal file
|
@ -0,0 +1,14 @@
|
|||
<figure class="attachment attachment--<%= blob.representable? ? "preview" : "file" %> attachment--<%= blob.filename.extension %>">
|
||||
<% if blob.representable? %>
|
||||
<%= image_tag blob.representation(resize_to_fit: local_assigns[:in_gallery] ? [ 800, 600 ] : [ 1024, 768 ]) %>
|
||||
<% end %>
|
||||
|
||||
<figcaption class="attachment__caption">
|
||||
<% if caption = blob.try(:caption) %>
|
||||
<%= caption %>
|
||||
<% else %>
|
||||
<span class="attachment__name"><%= blob.filename %></span>
|
||||
<span class="attachment__size"><%= number_to_human_size blob.byte_size %></span>
|
||||
<% end %>
|
||||
</figcaption>
|
||||
</figure>
|
|
@ -58,14 +58,24 @@
|
|||
= dossier_display_state(dossier, lower: true)
|
||||
.dropdown-content.fade-in-down.terminated
|
||||
- if dossier.motivation.present?
|
||||
%h4 Motivation
|
||||
%h4.title Motivation
|
||||
%p.dossier-motivation= dossier.motivation
|
||||
= render partial: 'users/dossiers/show/download_justificatif', locals: { dossier: dossier }
|
||||
|
||||
- if dossier.attestation.present?
|
||||
%h4 Attestation
|
||||
%h4.title Attestation
|
||||
%p.attestation L'acceptation du dossier a envoyé automatiquement une attestation au demandeur
|
||||
= link_to "Voir l'attestation", attestation_gestionnaire_dossier_path(dossier.procedure, dossier), target: '_blank', rel: 'noopener', class: 'button'
|
||||
|
||||
|
||||
- if dossier.refuse? || dossier.sans_suite?
|
||||
%ul.dropdown-items.with-top-border
|
||||
%li
|
||||
= link_to repasser_en_instruction_gestionnaire_dossier_path(dossier.procedure, dossier), method: :post, data: { remote:true, confirm: "Voulez vous remettre le dossier #{dossier.id} en instruction ?" } do
|
||||
%span.icon.in-progress
|
||||
.dropdown-description
|
||||
%h4 Repasser en instruction
|
||||
L’usager sera notifié que son dossier est réexaminé.
|
||||
- else
|
||||
%span.label{ class: button_or_label_class(dossier) }
|
||||
= dossier_display_state(dossier, lower: true)
|
||||
|
|
|
@ -2,11 +2,23 @@
|
|||
%span.dropdown
|
||||
%button.button.dropdown-button
|
||||
Télécharger tous les dossiers
|
||||
.dropdown-content.fade-in-down
|
||||
- old_format_limit_date = Date.parse("Oct 31 2019")
|
||||
- export_v1_enabled = old_format_limit_date > Time.zone.today
|
||||
- export_v2_enabled = Flipflop.procedure_export_v2_enabled? || !export_v1_enabled
|
||||
- old_format_message = export_v1_enabled && export_v2_enabled ? "(ancien format, jusqu’au #{old_format_limit_date.strftime('%d/%m/%Y')})" : ''
|
||||
.dropdown-content.fade-in-down{ style: export_v1_enabled && export_v2_enabled ? 'width: 330px' : '' }
|
||||
%ul.dropdown-items
|
||||
- if export_v2_enabled
|
||||
%li
|
||||
= link_to "Au format .csv", download_dossiers_gestionnaire_procedure_path(format: :csv, procedure_id: procedure.id), target: "_blank", rel: "noopener"
|
||||
= link_to "Au format .xlsx", procedure_dossiers_download_path(procedure, format: :xlsx, version: 'v2'), target: "_blank", rel: "noopener"
|
||||
%li
|
||||
= link_to "Au format .xlsx", download_dossiers_gestionnaire_procedure_path(format: :xlsx, procedure_id: procedure.id, tables: [:etablissements]), target: "_blank", rel: "noopener"
|
||||
= link_to "Au format .ods", procedure_dossiers_download_path(procedure, format: :ods, version: 'v2'), target: "_blank", rel: "noopener"
|
||||
%li
|
||||
= link_to "Au format .ods", download_dossiers_gestionnaire_procedure_path(format: :ods, procedure_id: procedure.id, tables: [:etablissements]), target: "_blank", rel: "noopener"
|
||||
= link_to "Au format .csv", procedure_dossiers_download_path(procedure, format: :csv, version: 'v2'), target: "_blank", rel: "noopener"
|
||||
- if export_v1_enabled
|
||||
%li
|
||||
= link_to "Au format .xlsx #{old_format_message}", procedure_dossiers_download_path(procedure, format: :xlsx, version: 'v1'), target: "_blank", rel: "noopener"
|
||||
%li
|
||||
= link_to "Au format .ods #{old_format_message}", procedure_dossiers_download_path(procedure, format: :ods, version: 'v1'), target: "_blank", rel: "noopener"
|
||||
%li
|
||||
= link_to "Au format .csv #{old_format_message}", procedure_dossiers_download_path(procedure, format: :csv, version: 'v1'), target: "_blank", rel: "noopener"
|
||||
|
|
|
@ -28,9 +28,6 @@ as well as a link to its edit page.
|
|||
</h1>
|
||||
|
||||
<div>
|
||||
<% if dossier.termine? %>
|
||||
<%= link_to 'Repasser en instruction', change_state_to_instruction_manager_dossier_path(dossier), method: :post, class: 'button', data: { confirm: "Repasser en instruction ?" } %>
|
||||
<% end %>
|
||||
<% if dossier.hidden_at.nil? %>
|
||||
<%= link_to 'Supprimer le dossier', hide_manager_dossier_path(dossier), method: :post, class: 'button', data: { confirm: "Confirmez vous la suppression du dossier ?" } %>
|
||||
<% end %>
|
||||
|
|
|
@ -26,8 +26,8 @@
|
|||
%h1.role-panel-title Vous souhaitez effectuer une demande auprès d'une administration ?
|
||||
%p.role-panel-explanation Réalisez vos demandes en toute simplicité et retrouvez vos dossiers en ligne
|
||||
|
||||
= link_to "Voir les démarches disponibles",
|
||||
LISTE_DES_DEMARCHES_URL,
|
||||
= link_to "Comment trouver ma démarche ?",
|
||||
COMMENT_TROUVER_MA_DEMARCHE_URL,
|
||||
target: "_blank",
|
||||
rel: "noopener noreferrer",
|
||||
class: "role-panel-button-primary"
|
||||
|
|
|
@ -30,6 +30,7 @@ module.exports = function(api) {
|
|||
{
|
||||
forceAllTransforms: true,
|
||||
useBuiltIns: 'entry',
|
||||
corejs: 2,
|
||||
modules: false,
|
||||
exclude: ['transform-typeof-symbol']
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ Flipflop.configure do
|
|||
feature :web_hook
|
||||
feature :enable_email_login_token
|
||||
|
||||
feature :procedure_export_v2_enabled
|
||||
feature :operation_log_serialize_subject
|
||||
|
||||
group :development do
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
Rails.application.config.assets.version = '1.0'
|
||||
|
||||
# Add additional assets to the asset load path
|
||||
# Rails.application.config.assets.paths << Emoji.images_path
|
||||
Rails.application.config.assets.paths << Rails.root.join('node_modules', 'trix', 'dist')
|
||||
|
||||
# Precompile additional assets.
|
||||
# application.js, application.css, and all non-JS/CSS in app/assets folder are already added.
|
||||
|
|
|
@ -25,5 +25,6 @@ API_DOC_URL = [DOC_URL, "pour-aller-plus-loin", "api"].join("/")
|
|||
WEBHOOK_DOC_URL = [DOC_URL, "pour-aller-plus-loin", "webhook"].join("/")
|
||||
FAQ_URL = "https://faq.demarches-simplifiees.fr"
|
||||
FAQ_ADMIN_URL = "https://faq.demarches-simplifiees.fr/collection/1-administrateur"
|
||||
COMMENT_TROUVER_MA_DEMARCHE_URL = [FAQ_URL, "article", "59-comment-trouver-ma-demarche"].join("/")
|
||||
STATUS_PAGE_URL = "https://status.demarches-simplifiees.fr"
|
||||
MATOMO_IFRAME_URL = "https://stats.data.gouv.fr/index.php?module=CoreAdminHome&action=optOut&language=fr&&fontColor=333333&fontSize=16px&fontFamily=Muli"
|
||||
|
|
|
@ -15,7 +15,6 @@ Rails.application.routes.draw do
|
|||
end
|
||||
|
||||
resources :dossiers, only: [:index, :show] do
|
||||
post 'change_state_to_instruction', on: :member
|
||||
post 'hide', on: :member
|
||||
end
|
||||
|
||||
|
@ -325,6 +324,7 @@ Rails.application.routes.draw do
|
|||
post 'commentaire' => 'dossiers#create_commentaire'
|
||||
post 'passer-en-instruction' => 'dossiers#passer_en_instruction'
|
||||
post 'repasser-en-construction' => 'dossiers#repasser_en_construction'
|
||||
post 'repasser-en-instruction' => 'dossiers#repasser_en_instruction'
|
||||
post 'terminer'
|
||||
post 'send-to-instructeurs' => 'dossiers#send_to_instructeurs'
|
||||
post 'avis' => 'dossiers#create_avis'
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
# This migration comes from action_text (originally 201805281641)
|
||||
class CreateActionTextTables < ActiveRecord::Migration[5.2]
|
||||
def change
|
||||
create_table :action_text_rich_texts do |t|
|
||||
t.string :name, null: false
|
||||
t.text :body, limit: 16777215
|
||||
t.references :record, null: false, polymorphic: true, index: false
|
||||
|
||||
t.datetime :created_at, null: false
|
||||
t.datetime :updated_at, null: false
|
||||
|
||||
t.index [:record_type, :record_id, :name], name: "index_action_text_rich_texts_uniqueness", unique: true
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,6 @@
|
|||
class AddDeclarativeWithStateToProcedures < ActiveRecord::Migration[5.2]
|
||||
def change
|
||||
add_column :procedures, :declarative_with_state, :string
|
||||
add_index :procedures, :declarative_with_state
|
||||
end
|
||||
end
|
12
db/schema.rb
12
db/schema.rb
|
@ -16,6 +16,16 @@ ActiveRecord::Schema.define(version: 2019_06_27_132911) do
|
|||
enable_extension "plpgsql"
|
||||
enable_extension "unaccent"
|
||||
|
||||
create_table "action_text_rich_texts", force: :cascade do |t|
|
||||
t.string "name", null: false
|
||||
t.text "body"
|
||||
t.string "record_type", null: false
|
||||
t.bigint "record_id", null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.index ["record_type", "record_id", "name"], name: "index_action_text_rich_texts_uniqueness", unique: true
|
||||
end
|
||||
|
||||
create_table "active_storage_attachments", force: :cascade do |t|
|
||||
t.string "name", null: false
|
||||
t.string "record_type", null: false
|
||||
|
@ -492,6 +502,8 @@ ActiveRecord::Schema.define(version: 2019_06_27_132911) do
|
|||
t.boolean "durees_conservation_required", default: true
|
||||
t.string "path"
|
||||
t.boolean "expects_multiple_submissions", default: false, null: false
|
||||
t.string "declarative_with_state"
|
||||
t.index ["declarative_with_state"], name: "index_procedures_on_declarative_with_state"
|
||||
t.index ["hidden_at"], name: "index_procedures_on_hidden_at"
|
||||
t.index ["parent_procedure_id"], name: "index_procedures_on_parent_procedure_id"
|
||||
t.index ["service_id"], name: "index_procedures_on_service_id"
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
namespace :after_party do
|
||||
desc 'Deployment task: migrate_mail_body_to_actiontext'
|
||||
task migrate_mail_body_to_actiontext: :environment do
|
||||
puts "Running deploy task 'migrate_mail_body_to_actiontext'"
|
||||
|
||||
# Put your task implementation HERE.
|
||||
|
||||
[Mails::InitiatedMail, Mails::ReceivedMail, Mails::ClosedMail, Mails::WithoutContinuationMail, Mails::RefusedMail].each do |mt_class|
|
||||
progress = ProgressReport.new(mt_class.all.count)
|
||||
|
||||
mt_class.all.each do |mt|
|
||||
if mt.body.present?
|
||||
mt.rich_body = mt.body
|
||||
mt.save
|
||||
end
|
||||
progress.inc
|
||||
end
|
||||
|
||||
progress.finish
|
||||
end
|
||||
|
||||
# Update task as completed. If you remove the line below, the task will
|
||||
# run with every deploy (or every time you call after_party:run).
|
||||
AfterParty::TaskRecord.create version: '20190410131747'
|
||||
end # task :migrate_mail_body_to_actiontext
|
||||
end # namespace :after_party
|
|
@ -0,0 +1,22 @@
|
|||
namespace :after_party do
|
||||
desc 'Deployment task: set_declarative_procedures'
|
||||
task set_declarative_procedures: :environment do
|
||||
puts "Running deploy task 'set_declarative_procedures'"
|
||||
|
||||
Delayed::Job.where.not(cron: nil).find_each do |job|
|
||||
job_data = YAML.load_dj(job.handler).job_data
|
||||
|
||||
if job_data['job_class'] == 'AutoReceiveDossiersForProcedureJob'
|
||||
procedure_id, state = job_data['arguments']
|
||||
procedure = Procedure.find(procedure_id)
|
||||
procedure.declarative_with_state = state
|
||||
procedure.save!
|
||||
job.delete
|
||||
end
|
||||
end
|
||||
|
||||
# Update task as completed. If you remove the line below, the task will
|
||||
# run with every deploy (or every time you call after_party:run).
|
||||
AfterParty::TaskRecord.create version: '20190523122639'
|
||||
end
|
||||
end
|
|
@ -16,6 +16,6 @@ namespace :after_party do
|
|||
|
||||
# Update task as completed. If you remove the line below, the task will
|
||||
# run with every deploy (or every time you call after_party:run).
|
||||
AfterParty::TaskRecord.create version: '20190521131030'
|
||||
AfterParty::TaskRecord.create version: '20190701131030'
|
||||
end
|
||||
end
|
28
package.json
28
package.json
|
@ -1,26 +1,28 @@
|
|||
{
|
||||
"dependencies": {
|
||||
"@babel/preset-react": "^7.0.0",
|
||||
"@fortawesome/fontawesome-svg-core": "^1.2.18",
|
||||
"@fortawesome/free-solid-svg-icons": "^5.8.2",
|
||||
"@fortawesome/fontawesome-svg-core": "^1.2.19",
|
||||
"@fortawesome/free-solid-svg-icons": "^5.9.0",
|
||||
"@fortawesome/react-fontawesome": "^0.1.4",
|
||||
"@rails/webpacker": "4.0.2",
|
||||
"@sentry/browser": "^5.2.1",
|
||||
"@rails/actiontext": "^6.0.0-alpha",
|
||||
"@rails/activestorage": "^6.0.0-alpha",
|
||||
"@rails/ujs": "^6.0.0-alpha",
|
||||
"@rails/webpacker": "4.0.7",
|
||||
"@sentry/browser": "^5.4.3",
|
||||
"@turf/area": "^6.0.1",
|
||||
"activestorage": "^5.2.3",
|
||||
"autocomplete.js": "^0.36.0",
|
||||
"babel-plugin-macros": "^2.5.1",
|
||||
"babel-plugin-macros": "^2.6.1",
|
||||
"babel-plugin-transform-react-remove-prop-types": "^0.4.24",
|
||||
"chartkick": "^3.0.2",
|
||||
"core-js": "^2.0.0",
|
||||
"debounce": "^1.2.0",
|
||||
"dom4": "^2.1.4",
|
||||
"dom4": "^2.1.5",
|
||||
"highcharts": "^6.1.2",
|
||||
"intersection-observer": "^0.7.0",
|
||||
"jquery": "^3.4.1",
|
||||
"leaflet": "^1.4.0",
|
||||
"leaflet-freedraw": "^2.10.0",
|
||||
"prop-types": "^15.7.2",
|
||||
"rails-ujs": "^5.2.3",
|
||||
"ramda": "=0.24.1",
|
||||
"react": "^16.8.6",
|
||||
"react-dom": "^16.8.6",
|
||||
|
@ -32,15 +34,15 @@
|
|||
"turbolinks": "^5.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-eslint": "^10.0.1",
|
||||
"babel-eslint": "^10.0.2",
|
||||
"eclint": "^2.8.1",
|
||||
"eslint": "^5.16.0",
|
||||
"eslint-config-prettier": "^4.2.0",
|
||||
"eslint-plugin-prettier": "^3.1.0",
|
||||
"eslint-plugin-react": "^7.13.0",
|
||||
"eslint-plugin-react-hooks": "^1.6.0",
|
||||
"prettier": "^1.17.1",
|
||||
"webpack-dev-server": "^3.3.1"
|
||||
"eslint-plugin-react": "^7.14.2",
|
||||
"eslint-plugin-react-hooks": "^1.6.1",
|
||||
"prettier": "^1.18.2",
|
||||
"webpack-dev-server": "^3.7.2"
|
||||
},
|
||||
"scripts": {
|
||||
"lint:ec": "eclint check $({ git ls-files ; find vendor -type f ; echo 'db/schema.rb' ; } | sort | uniq -u)",
|
||||
|
|
82
spec/jobs/declarative_procedures_job_spec.rb
Normal file
82
spec/jobs/declarative_procedures_job_spec.rb
Normal file
|
@ -0,0 +1,82 @@
|
|||
require 'rails_helper'
|
||||
|
||||
RSpec.describe DeclarativeProceduresJob, type: :job do
|
||||
describe "perform" do
|
||||
let(:date) { Time.utc(2017, 9, 1, 10, 5, 0) }
|
||||
let(:instruction_date) { date + 120 }
|
||||
|
||||
let(:state) { nil }
|
||||
let(:procedure) { create(:procedure, :with_gestionnaire, declarative_with_state: state) }
|
||||
let(:nouveau_dossier1) { create(:dossier, :en_construction, procedure: procedure) }
|
||||
let(:nouveau_dossier2) { create(:dossier, :en_construction, procedure: procedure) }
|
||||
let(:dossier_recu) { create(:dossier, :en_instruction, procedure: procedure) }
|
||||
let(:dossier_brouillon) { create(:dossier, procedure: procedure) }
|
||||
|
||||
before do
|
||||
Timecop.freeze(date)
|
||||
dossiers = [
|
||||
nouveau_dossier1,
|
||||
nouveau_dossier2,
|
||||
dossier_recu,
|
||||
dossier_brouillon
|
||||
]
|
||||
|
||||
create(:attestation_template, procedure: procedure)
|
||||
DeclarativeProceduresJob.new.perform
|
||||
|
||||
dossiers.each(&:reload)
|
||||
end
|
||||
|
||||
after { Timecop.return }
|
||||
|
||||
context "with some dossiers" do
|
||||
context "en_construction" do
|
||||
let(:state) { Dossier.states.fetch(:en_instruction) }
|
||||
let(:last_operation) { nouveau_dossier1.dossier_operation_logs.last }
|
||||
|
||||
it {
|
||||
expect(nouveau_dossier1.en_instruction?).to be true
|
||||
expect(nouveau_dossier1.en_instruction_at).to eq(date)
|
||||
expect(last_operation.operation).to eq('passer_en_instruction')
|
||||
expect(last_operation.automatic_operation?).to be_truthy
|
||||
|
||||
expect(nouveau_dossier2.en_instruction?).to be true
|
||||
expect(nouveau_dossier2.en_instruction_at).to eq(date)
|
||||
|
||||
expect(dossier_recu.en_instruction?).to be true
|
||||
expect(dossier_recu.en_instruction_at).to eq(instruction_date)
|
||||
|
||||
expect(dossier_brouillon.brouillon?).to be true
|
||||
expect(dossier_brouillon.en_instruction_at).to eq(nil)
|
||||
}
|
||||
end
|
||||
|
||||
context "accepte" do
|
||||
let(:state) { Dossier.states.fetch(:accepte) }
|
||||
let(:last_operation) { nouveau_dossier1.dossier_operation_logs.last }
|
||||
|
||||
it {
|
||||
expect(nouveau_dossier1.accepte?).to be true
|
||||
expect(nouveau_dossier1.en_instruction_at).to eq(date)
|
||||
expect(nouveau_dossier1.processed_at).to eq(date)
|
||||
expect(nouveau_dossier1.attestation).to be_present
|
||||
expect(last_operation.operation).to eq('accepter')
|
||||
expect(last_operation.automatic_operation?).to be_truthy
|
||||
|
||||
expect(nouveau_dossier2.accepte?).to be true
|
||||
expect(nouveau_dossier2.en_instruction_at).to eq(date)
|
||||
expect(nouveau_dossier2.processed_at).to eq(date)
|
||||
expect(nouveau_dossier2.attestation).to be_present
|
||||
|
||||
expect(dossier_recu.en_instruction?).to be true
|
||||
expect(dossier_recu.en_instruction_at).to eq(instruction_date)
|
||||
expect(dossier_recu.processed_at).to eq(nil)
|
||||
|
||||
expect(dossier_brouillon.brouillon?).to be true
|
||||
expect(dossier_brouillon.en_instruction_at).to eq(nil)
|
||||
expect(dossier_brouillon.processed_at).to eq(nil)
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,24 @@
|
|||
describe '20190410131747_migrate_mail_body_to_actiontext.rake' do
|
||||
let(:rake_task) { Rake::Task['after_party:migrate_mail_body_to_actiontext'] }
|
||||
|
||||
let!(:closed_mail) { create(:closed_mail, body: body) }
|
||||
|
||||
before do
|
||||
rake_task.invoke
|
||||
closed_mail.reload
|
||||
end
|
||||
|
||||
after { rake_task.reenable }
|
||||
|
||||
context 'with a plain text body' do
|
||||
let(:body) { "Test de body" }
|
||||
|
||||
it { expect(closed_mail.rich_body.to_plain_text).to eq(closed_mail.body) }
|
||||
end
|
||||
|
||||
context 'with a html text body' do
|
||||
let(:body) { "Test de body<br>" }
|
||||
|
||||
it { expect(closed_mail.rich_body.to_s.squish).to eq("<div class=\"trix-content\"> #{closed_mail.body} </div>".squish) }
|
||||
end
|
||||
end
|
|
@ -113,4 +113,10 @@ describe MailTemplateConcern do
|
|||
expect(initiated_mail.body_for_dossier(dossier2)).to eq("n #{dossier2.id}")
|
||||
end
|
||||
end
|
||||
|
||||
describe '#update_rich_body' do
|
||||
before { initiated_mail.update(body: "Voici le corps du mail") }
|
||||
|
||||
it { expect(initiated_mail.rich_body.to_plain_text).to eq(initiated_mail.body) }
|
||||
end
|
||||
end
|
||||
|
|
|
@ -794,7 +794,7 @@ describe Dossier do
|
|||
end
|
||||
|
||||
describe '#accepter!' do
|
||||
let(:dossier) { create(:dossier) }
|
||||
let(:dossier) { create(:dossier, :en_instruction) }
|
||||
let(:last_operation) { dossier.dossier_operation_logs.last }
|
||||
let(:operation_serialized) { JSON.parse(last_operation.serialized.download) }
|
||||
let!(:gestionnaire) { create(:gestionnaire) }
|
||||
|
@ -813,7 +813,7 @@ describe Dossier do
|
|||
after { Timecop.return }
|
||||
|
||||
it { expect(dossier.motivation).to eq('motivation') }
|
||||
it { expect(dossier.en_instruction_at).to eq(now) }
|
||||
it { expect(dossier.en_instruction_at).to eq(dossier.en_instruction_at) }
|
||||
it { expect(dossier.processed_at).to eq(now) }
|
||||
it { expect(dossier.state).to eq('accepte') }
|
||||
it { expect(last_operation.operation).to eq('accepter') }
|
||||
|
@ -826,7 +826,7 @@ describe Dossier do
|
|||
end
|
||||
|
||||
describe '#accepter_automatiquement!' do
|
||||
let(:dossier) { create(:dossier) }
|
||||
let(:dossier) { create(:dossier, :en_construction) }
|
||||
let(:last_operation) { dossier.dossier_operation_logs.last }
|
||||
let!(:now) { Time.zone.parse('01/01/2100') }
|
||||
let(:attestation) { Attestation.new }
|
||||
|
@ -853,7 +853,7 @@ describe Dossier do
|
|||
end
|
||||
|
||||
describe '#passer_en_instruction!' do
|
||||
let(:dossier) { create(:dossier) }
|
||||
let(:dossier) { create(:dossier, :en_construction) }
|
||||
let(:last_operation) { dossier.dossier_operation_logs.last }
|
||||
let(:operation_serialized) { JSON.parse(last_operation.serialized.download) }
|
||||
let(:gestionnaire) { create(:gestionnaire) }
|
||||
|
@ -870,7 +870,7 @@ describe Dossier do
|
|||
end
|
||||
|
||||
describe '#passer_automatiquement_en_instruction!' do
|
||||
let(:dossier) { create(:dossier) }
|
||||
let(:dossier) { create(:dossier, :en_construction) }
|
||||
let(:last_operation) { dossier.dossier_operation_logs.last }
|
||||
let(:operation_serialized) { JSON.parse(last_operation.serialized.download) }
|
||||
let(:gestionnaire) { create(:gestionnaire) }
|
||||
|
@ -987,4 +987,28 @@ describe Dossier do
|
|||
it { expect(last_operation.operation).to eq('supprimer') }
|
||||
it { expect(last_operation.automatic_operation?).to be_falsey }
|
||||
end
|
||||
|
||||
describe '#repasser_en_instruction!' do
|
||||
let(:dossier) { create(:dossier, :refuse, :with_attestation) }
|
||||
let!(:gestionnaire) { create(:gestionnaire) }
|
||||
let(:last_operation) { dossier.dossier_operation_logs.last }
|
||||
|
||||
before do
|
||||
Timecop.freeze
|
||||
allow(DossierMailer).to receive(:notify_revert_to_instruction)
|
||||
.and_return(double(deliver_later: true))
|
||||
dossier.repasser_en_instruction!(gestionnaire)
|
||||
dossier.reload
|
||||
end
|
||||
|
||||
it { expect(dossier.state).to eq('en_instruction') }
|
||||
it { expect(dossier.processed_at).to be_nil }
|
||||
it { expect(dossier.motivation).to be_nil }
|
||||
it { expect(dossier.attestation).to be_nil }
|
||||
it { expect(last_operation.operation).to eq('repasser_en_instruction') }
|
||||
it { expect(JSON.parse(last_operation.serialized.download)['author']['email']).to eq(gestionnaire.email) }
|
||||
it { expect(DossierMailer).to have_received(:notify_revert_to_instruction).with(dossier) }
|
||||
|
||||
after { Timecop.return }
|
||||
end
|
||||
end
|
||||
|
|
181
spec/services/procedure_export_v2_service_spec.rb
Normal file
181
spec/services/procedure_export_v2_service_spec.rb
Normal file
|
@ -0,0 +1,181 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe ProcedureExportV2Service do
|
||||
describe 'to_data' do
|
||||
let(:procedure) { create(:procedure, :published, :with_all_champs) }
|
||||
subject do
|
||||
Tempfile.create do |f|
|
||||
f << ProcedureExportV2Service.new(procedure).to_xlsx
|
||||
f.rewind
|
||||
SimpleXlsxReader.open(f.path)
|
||||
end
|
||||
end
|
||||
|
||||
let(:dossiers_sheet) { subject.sheets.first }
|
||||
let(:etablissements_sheet) { subject.sheets.second }
|
||||
let(:avis_sheet) { subject.sheets.third }
|
||||
let(:repetition_sheet) { subject.sheets.fourth }
|
||||
|
||||
before do
|
||||
# change one tdc place to check if the header is ordered
|
||||
tdc_first = procedure.types_de_champ.first
|
||||
tdc_last = procedure.types_de_champ.last
|
||||
|
||||
tdc_first.update(order_place: tdc_last.order_place + 1)
|
||||
procedure.reload
|
||||
end
|
||||
|
||||
context 'dossiers' do
|
||||
it 'should have sheets' do
|
||||
expect(subject.sheets.map(&:name)).to eq(['Dossiers', 'Etablissements', 'Avis'])
|
||||
end
|
||||
end
|
||||
|
||||
context 'with dossier' do
|
||||
let!(:dossier) { create(:dossier, :en_instruction, :with_all_champs, :for_individual, procedure: procedure) }
|
||||
|
||||
it 'should have headers' do
|
||||
expect(dossiers_sheet.headers).to eq([
|
||||
"ID",
|
||||
"Email",
|
||||
"Civilité",
|
||||
"Nom",
|
||||
"Prénom",
|
||||
"Date de naissance",
|
||||
"Archivé",
|
||||
"État du dossier",
|
||||
"Dernière mise à jour le",
|
||||
"Passé en construction le",
|
||||
"Passé en instruction le",
|
||||
"Traité le",
|
||||
"Motivation de la décision",
|
||||
"Instructeurs",
|
||||
"textarea",
|
||||
"date",
|
||||
"datetime",
|
||||
"number",
|
||||
"decimal_number",
|
||||
"integer_number",
|
||||
"checkbox",
|
||||
"civilite",
|
||||
"email",
|
||||
"phone",
|
||||
"address",
|
||||
"yes_no",
|
||||
"simple_drop_down_list",
|
||||
"multiple_drop_down_list",
|
||||
"linked_drop_down_list",
|
||||
"pays",
|
||||
"regions",
|
||||
"departements",
|
||||
"engagement",
|
||||
"dossier_link",
|
||||
"piece_justificative",
|
||||
"siret",
|
||||
"carte",
|
||||
"text"
|
||||
])
|
||||
end
|
||||
|
||||
it 'should have data' do
|
||||
expect(dossiers_sheet.data.size).to eq(1)
|
||||
expect(etablissements_sheet.data.size).to eq(1)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with etablissement' do
|
||||
let!(:dossier) { create(:dossier, :en_instruction, :with_all_champs, :with_entreprise, procedure: procedure) }
|
||||
|
||||
it 'should have headers' do
|
||||
expect(etablissements_sheet.headers).to eq([
|
||||
"Dossier ID",
|
||||
"Champ",
|
||||
"Établissement SIRET",
|
||||
"Établissement siège social",
|
||||
"Établissement NAF",
|
||||
"Établissement libellé NAF",
|
||||
"Établissement Adresse",
|
||||
"Établissement numero voie",
|
||||
"Établissement type voie",
|
||||
"Établissement nom voie",
|
||||
"Établissement complément adresse",
|
||||
"Établissement code postal",
|
||||
"Établissement localité",
|
||||
"Établissement code INSEE localité",
|
||||
"Entreprise SIREN",
|
||||
"Entreprise capital social",
|
||||
"Entreprise numero TVA intracommunautaire",
|
||||
"Entreprise forme juridique",
|
||||
"Entreprise forme juridique code",
|
||||
"Entreprise nom commercial",
|
||||
"Entreprise raison sociale",
|
||||
"Entreprise SIRET siège social",
|
||||
"Entreprise code effectif entreprise",
|
||||
"Entreprise date de création",
|
||||
"Entreprise nom",
|
||||
"Entreprise prénom",
|
||||
"Association RNA",
|
||||
"Association titre",
|
||||
"Association objet",
|
||||
"Association date de création",
|
||||
"Association date de déclaration",
|
||||
"Association date de publication"
|
||||
])
|
||||
end
|
||||
|
||||
it 'should have data' do
|
||||
expect(etablissements_sheet.data.size).to eq(2)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with avis' do
|
||||
let!(:dossier) { create(:dossier, :en_instruction, :with_all_champs, :for_individual, procedure: procedure) }
|
||||
let!(:avis) { create(:avis, :with_answer, dossier: dossier) }
|
||||
|
||||
it 'should have headers' do
|
||||
expect(avis_sheet.headers).to eq([
|
||||
"Dossier ID",
|
||||
"Question / Introduction",
|
||||
"Réponse",
|
||||
"Créé le",
|
||||
"Répondu le"
|
||||
])
|
||||
end
|
||||
|
||||
it 'should have data' do
|
||||
expect(avis_sheet.data.size).to eq(1)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with repetitions' do
|
||||
let!(:dossier) { create(:dossier, :en_instruction, :with_all_champs, :for_individual, procedure: procedure) }
|
||||
let(:champ_repetition) { dossier.champs.find { |champ| champ.type_champ == 'repetition' } }
|
||||
let(:type_de_champ_text) { create(:type_de_champ_text, order_place: 0, parent: champ_repetition.type_de_champ) }
|
||||
let(:type_de_champ_number) { create(:type_de_champ_number, order_place: 1, parent: champ_repetition.type_de_champ) }
|
||||
|
||||
before do
|
||||
create(:champ_text, row: 0, type_de_champ: type_de_champ_text, parent: champ_repetition)
|
||||
create(:champ_number, row: 0, type_de_champ: type_de_champ_number, parent: champ_repetition)
|
||||
create(:champ_text, row: 1, type_de_champ: type_de_champ_text, parent: champ_repetition)
|
||||
create(:champ_number, row: 1, type_de_champ: type_de_champ_number, parent: champ_repetition)
|
||||
end
|
||||
|
||||
it 'should have sheets' do
|
||||
expect(subject.sheets.map(&:name)).to eq(['Dossiers', 'Etablissements', 'Avis', champ_repetition.libelle])
|
||||
end
|
||||
|
||||
it 'should have headers' do
|
||||
expect(repetition_sheet.headers).to eq([
|
||||
"Dossier ID",
|
||||
"Ligne",
|
||||
type_de_champ_text.libelle,
|
||||
type_de_champ_number.libelle
|
||||
])
|
||||
end
|
||||
|
||||
it 'should have data' do
|
||||
expect(repetition_sheet.data.size).to eq(2)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in a new issue