2019-11-21-01 (#4553)

2019-11-21-01
This commit is contained in:
Pierre de La Morinerie 2019-11-21 14:28:38 +01:00 committed by GitHub
commit 85accfd389
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
33 changed files with 501 additions and 1008 deletions

View file

@ -130,6 +130,10 @@
} }
} }
.state-button {
display: inline-block;
}
.dropdown { .dropdown {
display: inline-block; display: inline-block;
position: relative; position: relative;
@ -213,10 +217,13 @@
} }
} }
&.selected, &:not(.inactive) {
&:hover:not(.inactive) {
background: $light-grey;
cursor: pointer; cursor: pointer;
&:hover,
&.selected {
background: $light-grey;
}
} }
&.danger { &.danger {

View file

@ -150,6 +150,8 @@ $header-mobile-breakpoint: 550px;
} }
button { button {
@extend %outline;
padding: 9px; padding: 9px;
border: none; border: none;
background: none; background: none;

View file

@ -56,6 +56,7 @@ module Instructeurs
recipients = Instructeur.find(params[:recipients]) recipients = Instructeur.find(params[:recipients])
recipients.each do |recipient| recipients.each do |recipient|
recipient.follow(dossier)
InstructeurMailer.send_dossier(current_instructeur, dossier, recipient).deliver_later InstructeurMailer.send_dossier(current_instructeur, dossier, recipient).deliver_later
end end

View file

@ -185,21 +185,19 @@ module Instructeurs
end end
def download_dossiers def download_dossiers
options = params.permit(:version, :limit, :since, tables: [])
dossiers = current_instructeur.dossiers.for_procedure(procedure) dossiers = current_instructeur.dossiers.for_procedure(procedure)
respond_to do |format| respond_to do |format|
format.csv do format.csv do
send_data(procedure.to_csv(dossiers, options), send_data(procedure.to_csv(dossiers),
filename: procedure.export_filename(:csv)) filename: procedure.export_filename(:csv))
end end
format.xlsx do format.xlsx do
send_data(procedure.to_xlsx(dossiers, options), send_data(procedure.to_xlsx(dossiers),
filename: procedure.export_filename(:xlsx)) filename: procedure.export_filename(:xlsx))
end end
format.ods do format.ods do
send_data(procedure.to_ods(dossiers, options), send_data(procedure.to_ods(dossiers),
filename: procedure.export_filename(:ods)) filename: procedure.export_filename(:ods))
end end
end end
@ -210,12 +208,13 @@ module Instructeurs
notice_message = "Nous générons cet export. Lorsque celui-ci sera disponible, vous recevrez une notification par email accompagnée d'un lien de téléchargement." notice_message = "Nous générons cet export. Lorsque celui-ci sera disponible, vous recevrez une notification par email accompagnée d'un lien de téléchargement."
if procedure.should_generate_export?(export_format) if procedure.should_generate_export?(export_format)
procedure.queue_export(current_instructeur, export_format) procedure.queue_export(current_instructeur, export_format)
flash.notice = notice_message
respond_to do |format| respond_to do |format|
format.js do format.js do
flash.notice = notice_message
@procedure = procedure @procedure = procedure
end end
format.all { redirect_to procedure }
end end
elsif procedure.export_queued?(export_format) elsif procedure.export_queued?(export_format)
flash.notice = notice_message flash.notice = notice_message

View file

@ -143,18 +143,19 @@ module Users
errors = update_dossier_and_compute_errors errors = update_dossier_and_compute_errors
if errors.present? if passage_en_construction? && errors.blank?
@dossier.en_construction!
NotificationMailer.send_initiated_notification(@dossier).deliver_later
return redirect_to(merci_dossier_path(@dossier))
elsif errors.present?
flash.now.alert = errors flash.now.alert = errors
render :brouillon
else else
if save_draft? flash.now.notice = 'Votre brouillon a bien été sauvegardé.'
flash.now.notice = 'Votre brouillon a bien été sauvegardé.' end
render :brouillon
else respond_to do |format|
@dossier.en_construction! format.html { render :brouillon }
NotificationMailer.send_initiated_notification(@dossier).deliver_later format.json { head :ok }
redirect_to merci_dossier_path(@dossier)
end
end end
end end
@ -368,7 +369,7 @@ module Users
end end
def save_draft? def save_draft?
dossier.brouillon? && params[:save_draft] dossier.brouillon? && !params[:submit_draft]
end end
end end
end end

View file

@ -38,13 +38,6 @@ module ProcedureHelper
} }
end end
def procedure_dossiers_download_path(procedure, format:, version:)
download_dossiers_instructeur_procedure_path(format: format,
procedure_id: procedure.id,
tables: [:etablissements],
version: version)
end
private private
TOGGLES = { TOGGLES = {

View file

@ -5,16 +5,16 @@ import debounce from 'debounce';
export { debounce }; export { debounce };
export const { fire, ajax } = Rails; export const { fire, ajax } = Rails;
export function show({ classList }) { export function show(el) {
classList.remove('hidden'); el && el.classList.remove('hidden');
} }
export function hide({ classList }) { export function hide(el) {
classList.add('hidden'); el && el.classList.add('hidden');
} }
export function toggle({ classList }) { export function toggle(el) {
classList.toggle('hidden'); el && el.classList.toggle('hidden');
} }
export function delegate(eventNames, selector, callback) { export function delegate(eventNames, selector, callback) {

View file

@ -193,7 +193,7 @@ class Procedure < ApplicationRecord
end end
def prepare_export_download(format) def prepare_export_download(format)
service = ProcedureExportV2Service.new(self, self.dossiers) service = ProcedureExportService.new(self, self.dossiers)
filename = export_filename(format) filename = export_filename(format)
case format.to_sym case format.to_sym
@ -440,26 +440,20 @@ class Procedure < ApplicationRecord
"dossiers_#{procedure_identifier}_#{Time.zone.now.strftime('%Y-%m-%d_%H-%M')}.#{format}" "dossiers_#{procedure_identifier}_#{Time.zone.now.strftime('%Y-%m-%d_%H-%M')}.#{format}"
end end
def export(dossiers, options = {}) def export(dossiers)
version = options.delete(:version) ProcedureExportService.new(self, dossiers)
if version == 'v2'
options.delete(:tables)
ProcedureExportV2Service.new(self, dossiers)
else
ProcedureExportService.new(self, dossiers, **options.to_h.symbolize_keys)
end
end end
def to_csv(dossiers, options = {}) def to_csv(dossiers)
export(dossiers, options).to_csv export(dossiers).to_csv
end end
def to_xlsx(dossiers, options = {}) def to_xlsx(dossiers)
export(dossiers, options).to_xlsx export(dossiers).to_xlsx
end end
def to_ods(dossiers, options = {}) def to_ods(dossiers)
export(dossiers, options).to_ods export(dossiers).to_ods
end end
def procedure_overview(start_date) def procedure_overview(start_date)

View file

@ -1,238 +1,76 @@
class ProcedureExportService class ProcedureExportService
include DossierHelper attr_reader :dossiers
ATTRIBUTES = [ def initialize(procedure, dossiers)
:id,
:created_at,
:updated_at,
:archived,
:email,
:state,
:initiated_at,
:received_at,
:processed_at,
:motivation,
:emails_instructeurs,
:individual_gender,
:individual_prenom,
:individual_nom,
:individual_birthdate
]
ETABLISSEMENT_ATTRIBUTES = [
:siret,
:siege_social,
:naf,
:libelle_naf,
:adresse,
:numero_voie,
:type_voie,
:nom_voie,
:complement_adresse,
:code_postal,
:localite,
:code_insee_localite
]
ENTREPRISE_ATTRIBUTES = [
:siren,
:capital_social,
:numero_tva_intracommunautaire,
:forme_juridique,
:forme_juridique_code,
:nom_commercial,
:raison_sociale,
:siret_siege_social,
:code_effectif_entreprise,
:date_creation,
:nom,
:prenom
]
def initialize(procedure, dossiers, tables: [])
@procedure = procedure @procedure = procedure
@attributes = ATTRIBUTES.dup
if procedure.routee?
@attributes << :groupe_instructeur_label
end
@dossiers = dossiers.downloadable_sorted @dossiers = dossiers.downloadable_sorted
@dossiers = @dossiers.to_a @tables = [:dossiers, :etablissements, :avis] + champs_repetables_options
@tables = tables.map(&:to_sym)
end end
def to_csv def to_csv
SpreadsheetArchitect.to_csv(to_data(:dossiers)) SpreadsheetArchitect.to_csv(options_for(:dossiers, :csv))
end end
def to_xlsx def to_xlsx
package = SpreadsheetArchitect.to_axlsx_package(to_data(:dossiers)) # We recursively build multi page spreadsheet
@tables.reduce(nil) do |package, table|
# Next we recursively build multi page spreadsheet SpreadsheetArchitect.to_axlsx_package(options_for(table, :xlsx), package)
@tables.reduce(package) do |package, table|
SpreadsheetArchitect.to_axlsx_package(to_data(table), package)
end.to_stream.read end.to_stream.read
end end
def to_ods def to_ods
spreadsheet = SpreadsheetArchitect.to_rodf_spreadsheet(to_data(:dossiers)) # We recursively build multi page spreadsheet
@tables.reduce(nil) do |spreadsheet, table|
# Next we recursively build multi page spreadsheet SpreadsheetArchitect.to_rodf_spreadsheet(options_for(table, :ods), spreadsheet)
@tables.reduce(spreadsheet) do |spreadsheet, table|
SpreadsheetArchitect.to_rodf_spreadsheet(to_data(table), spreadsheet)
end.bytes end.bytes
end end
def to_data(table)
case table
when :dossiers
dossiers_table_data
when :etablissements
etablissements_table_data
end
end
private private
def empty_table_data(sheet_name, headers = []) def etablissements
{ @etablissements ||= dossiers.flat_map do |dossier|
sheet_name: sheet_name,
headers: headers,
data: [[]]
}
end
def dossiers_table_data
if @dossiers.any?
{
sheet_name: 'Dossiers',
headers: dossiers_headers,
data: dossiers_data
}
else
empty_table_data('Dossiers', dossiers_headers)
end
end
def etablissements_table_data
@etablissements = @dossiers.flat_map do |dossier|
[dossier.champs, dossier.champs_private] [dossier.champs, dossier.champs_private]
.flatten .flatten
.filter { |champ| champ.is_a?(Champs::SiretChamp) } .filter { |champ| champ.is_a?(Champs::SiretChamp) }
end.map(&:etablissement).compact end.map(&:etablissement).compact + dossiers.map(&:etablissement).compact
if @etablissements.any?
{
sheet_name: 'Etablissements',
headers: etablissements_headers,
data: etablissements_data
}
else
empty_table_data('Etablissements', etablissements_headers)
end
end end
def dossiers_headers def avis
headers = @attributes.map(&:to_s) + @avis ||= dossiers.flat_map(&:avis)
@procedure.types_de_champ.reject(&:exclude_from_export?).map(&:libelle) +
@procedure.types_de_champ_private.reject(&:exclude_from_export?).map(&:libelle) +
ETABLISSEMENT_ATTRIBUTES.map { |key| "etablissement.#{key}" } +
ENTREPRISE_ATTRIBUTES.map { |key| "entreprise.#{key}" }
headers.map { |header| label_for_export(header) }
end end
def dossiers_data def champs_repetables
@dossiers.map do |dossier| @champs_repetables ||= dossiers.flat_map do |dossier|
values = @attributes.map do |key| [dossier.champs, dossier.champs_private]
case key .flatten
when :email .filter { |champ| champ.is_a?(Champs::RepetitionChamp) }
dossier.user.email end.group_by(&:libelle_for_export)
when :state
dossier_legacy_state(dossier)
when :initiated_at
dossier.en_construction_at
when :received_at
dossier.en_instruction_at
when :individual_prenom
dossier.individual&.prenom
when :individual_nom
dossier.individual&.nom
when :individual_birthdate
dossier.individual&.birthdate
when :individual_gender
dossier.individual&.gender
when :emails_instructeurs
dossier.followers_instructeurs.map(&:email).join(' ')
when :groupe_instructeur_label
dossier.groupe_instructeur.label
else
dossier.read_attribute(key)
end
end
normalize_values(values) +
dossier.champs.reject(&:exclude_from_export?).map(&:for_export) +
dossier.champs_private.reject(&:exclude_from_export?).map(&:for_export) +
etablissement_data(dossier.etablissement)
end
end end
def etablissements_headers def champs_repetables_options
headers = ["dossier_id", "libelle"] + champs_repetables.map do |libelle, champs|
ETABLISSEMENT_ATTRIBUTES.map { |key| "etablissement.#{key}" } +
ENTREPRISE_ATTRIBUTES.map { |key| "entreprise.#{key}" }
headers.map { |header| label_for_export(header) }
end
def etablissements_data
@etablissements.map do |etablissement|
[ [
etablissement.champ.dossier_id, libelle,
label_for_export(etablissement.champ.libelle).to_s champs.flat_map(&:rows_for_export)
] + etablissement_data(etablissement) ]
end end
end end
def etablissement_data(etablissement) DEFAULT_STYLES = {
data = ETABLISSEMENT_ATTRIBUTES.map do |key| header_style: { background_color: "000000", color: "FFFFFF", font_size: 12, bold: true },
if etablissement.present? row_style: { background_color: nil, color: "000000", font_size: 12 }
case key }
when :adresse
etablissement.adresse&.chomp&.gsub("\r\n", ' ')&.delete("\r")
else
etablissement.read_attribute(key)
end
end
end
data += ENTREPRISE_ATTRIBUTES.map do |key|
if etablissement.present?
case key
when :date_creation
etablissement.entreprise_date_creation&.to_datetime
else
etablissement.read_attribute(:"entreprise_#{key}")
end
end
end
normalize_values(data)
end
def label_for_export(label) def options_for(table, format)
label.parameterize.underscore.to_sym case table
end when :dossiers
{ instances: dossiers.to_a, sheet_name: 'Dossiers', spreadsheet_columns: :"spreadsheet_columns_#{format}" }
def normalize_values(values) when :etablissements
values.map do |value| { instances: etablissements.to_a, sheet_name: 'Etablissements' }
case value when :avis
when TrueClass, FalseClass { instances: avis.to_a, sheet_name: 'Avis' }
value.to_s when Array
else { instances: table.last, sheet_name: table.first }
value.blank? ? nil : value.to_s end.merge(DEFAULT_STYLES)
end
end
end end
end end

View file

@ -1,76 +0,0 @@
class ProcedureExportV2Service
attr_reader :dossiers
def initialize(procedure, dossiers)
@procedure = procedure
@dossiers = dossiers.downloadable_sorted
@tables = [:dossiers, :etablissements, :avis] + champs_repetables_options
end
def to_csv
SpreadsheetArchitect.to_csv(options_for(:dossiers, :csv))
end
def to_xlsx
# We recursively build multi page spreadsheet
@tables.reduce(nil) do |package, table|
SpreadsheetArchitect.to_axlsx_package(options_for(table, :xlsx), 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, :ods), spreadsheet)
end.bytes
end
private
def etablissements
@etablissements ||= dossiers.flat_map do |dossier|
[dossier.champs, dossier.champs_private]
.flatten
.filter { |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
.filter { |champ| champ.is_a?(Champs::RepetitionChamp) }
end.group_by(&:libelle_for_export)
end
def champs_repetables_options
champs_repetables.map do |libelle, champs|
[
libelle,
champs.flat_map(&:rows_for_export)
]
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, format)
case table
when :dossiers
{ instances: dossiers.to_a, sheet_name: 'Dossiers', spreadsheet_columns: :"spreadsheet_columns_#{format}" }.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
{ instances: table.last, sheet_name: table.first }.merge(DEFAULT_STYLES)
end
end
end

View file

@ -4,6 +4,8 @@
%p.tab-paragraph %p.tab-paragraph
Vous êtes le seul instructeur assigné sur cette démarche Vous êtes le seul instructeur assigné sur cette démarche
- else - else
%p.tab-paragrah.mb-1
Le destinataire suivra automatiquement le dossier
= form_for dossier, url: send_to_instructeurs_instructeur_dossier_path(dossier.procedure, dossier), method: :post, html: { class: 'form recipients-form' } do |f| = form_for dossier, url: send_to_instructeurs_instructeur_dossier_path(dossier.procedure, dossier), method: :post, html: { class: 'form recipients-form' } do |f|
.flex.justify-start.align-start .flex.justify-start.align-start
= select_tag(:recipients, = select_tag(:recipients,

View file

@ -1,5 +1,5 @@
%span.dropdown.print-menu-opener %span.dropdown.print-menu-opener
%button.button.dropdown-button.icon-only %button.button.dropdown-button.icon-only{ title: 'imprimer' }
%span.icon.printer %span.icon.printer
%ul.print-menu.dropdown-content %ul.print-menu.dropdown-content
%li %li
@ -20,5 +20,5 @@
= render partial: "instructeurs/procedures/dossier_actions", locals: { procedure: dossier.procedure, dossier: dossier, dossier_is_followed: current_instructeur&.follow?(dossier) } = render partial: "instructeurs/procedures/dossier_actions", locals: { procedure: dossier.procedure, dossier: dossier, dossier_is_followed: current_instructeur&.follow?(dossier) }
%span.state-button .state-button
= render partial: "state_button", locals: { dossier: dossier } = render partial: "state_button", locals: { dossier: dossier }

View file

@ -1,4 +1,4 @@
%span.dropdown .dropdown
-# Dropdown button title -# Dropdown button title
%button.button.primary.dropdown-button{ class: button_or_label_class(dossier) } %button.button.primary.dropdown-button{ class: button_or_label_class(dossier) }
= dossier_display_state dossier = dossier_display_state dossier

View file

@ -2,9 +2,7 @@
%span.dropdown %span.dropdown
%button.button.dropdown-button %button.button.dropdown-button
Télécharger tous les dossiers Télécharger tous les dossiers
- old_format_limit_date = Date.parse("Nov 15 2019") .dropdown-content.fade-in-down{ style: 'width: 330px' }
- export_v1_enabled = old_format_limit_date > Time.zone.today
.dropdown-content.fade-in-down{ style: !export_v1_enabled ? 'width: 330px' : '' }
%ul.dropdown-items %ul.dropdown-items
%li %li
- if procedure.xlsx_export_stale? - if procedure.xlsx_export_stale?
@ -30,12 +28,3 @@
= link_to "Exporter au format .csv", download_export_instructeur_procedure_path(procedure, export_format: :csv), remote: true = link_to "Exporter au format .csv", download_export_instructeur_procedure_path(procedure, export_format: :csv), remote: true
- else - else
= link_to "Au format .csv", url_for(procedure.csv_export_file), target: "_blank", rel: "noopener" = link_to "Au format .csv", url_for(procedure.csv_export_file), target: "_blank", rel: "noopener"
- if export_v1_enabled
- old_format_message = "(ancien format, jusquau #{old_format_limit_date.strftime('%d/%m/%Y')})"
%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"

View file

@ -1,4 +1,4 @@
%span.dropdown.invite-user-action .dropdown.invite-user-action
%button.button.dropdown-button %button.button.dropdown-button
%span.icon.person %span.icon.person
- if dossier.invites.count > 0 - if dossier.invites.count > 0

View file

@ -1,4 +1,4 @@
%span.dropdown.header-menu-opener .dropdown.header-menu-opener
%button.button.dropdown-button.header-menu-button %button.button.dropdown-button.header-menu-button
= image_tag "icons/account-circle.svg", title: "Mon compte" = image_tag "icons/account-circle.svg", title: "Mon compte"
%ul.header-menu.dropdown-content %ul.header-menu.dropdown-content
@ -8,30 +8,30 @@
- if administration_signed_in? - if administration_signed_in?
%li %li
= link_to manager_root_path, class: "menu-item menu-link" do = link_to manager_root_path, class: "menu-item menu-link" do
= image_tag "icons/super-admin.svg" = image_tag "icons/super-admin.svg", alt: ''
Passer en super-admin Passer en super-admin
- if multiple_devise_profile_connect? - if multiple_devise_profile_connect?
- if user_signed_in? && nav_bar_profile != :user - if user_signed_in? && nav_bar_profile != :user
%li %li
= link_to dossiers_path, class: "menu-item menu-link" do = link_to dossiers_path, class: "menu-item menu-link" do
= image_tag "icons/switch-profile.svg" = image_tag "icons/switch-profile.svg", alt: ''
Passer en usager Passer en usager
- if instructeur_signed_in? && nav_bar_profile != :instructeur - if instructeur_signed_in? && nav_bar_profile != :instructeur
%li %li
= link_to instructeur_procedures_path, class: "menu-item menu-link" do = link_to instructeur_procedures_path, class: "menu-item menu-link" do
= image_tag "icons/switch-profile.svg" = image_tag "icons/switch-profile.svg", alt: ''
Passer en instructeur Passer en instructeur
- if administrateur_signed_in? && nav_bar_profile != :administrateur - if administrateur_signed_in? && nav_bar_profile != :administrateur
%li %li
= link_to admin_procedures_path, class: "menu-item menu-link" do = link_to admin_procedures_path, class: "menu-item menu-link" do
= image_tag "icons/switch-profile.svg" = image_tag "icons/switch-profile.svg", alt: ''
Passer en administrateur Passer en administrateur
%li %li
= link_to profil_path, class: "menu-item menu-link" do = link_to profil_path, class: "menu-item menu-link" do
= image_tag "icons/switch-profile.svg" = image_tag "icons/switch-profile.svg", alt: ''
Voir mon profil Voir mon profil
%li %li
= link_to destroy_user_session_path, method: :delete, class: "menu-item menu-link" do = link_to destroy_user_session_path, method: :delete, class: "menu-item menu-link" do
= image_tag "icons/sign-out.svg" = image_tag "icons/sign-out.svg", alt: ''
Se déconnecter Se déconnecter

View file

@ -38,7 +38,7 @@
= form_tag instructeur_recherche_path, method: :get, class: "form" do = form_tag instructeur_recherche_path, method: :get, class: "form" do
= text_field_tag "q", "#{@search_terms if @search_terms.present?}", placeholder: "Rechercher un dossier" = text_field_tag "q", "#{@search_terms if @search_terms.present?}", placeholder: "Rechercher un dossier"
%button{ title: "Rechercher" } %button{ title: "Rechercher" }
= image_tag "icons/search-blue.svg" = image_tag "icons/search-blue.svg", alt: ''
- if nav_bar_profile == :user && user_signed_in? && current_user.dossiers.count > 2 - if nav_bar_profile == :user && user_signed_in? && current_user.dossiers.count > 2
%li %li
@ -46,7 +46,7 @@
= form_tag recherche_dossiers_path, method: :post, class: "form" do = form_tag recherche_dossiers_path, method: :post, class: "form" do
= text_field_tag :dossier_id, "", placeholder: "Numéro de dossier" = text_field_tag :dossier_id, "", placeholder: "Numéro de dossier"
%button{ title: "Rechercher" } %button{ title: "Rechercher" }
= image_tag "icons/search-blue.svg" = image_tag "icons/search-blue.svg", alt: ''
- if instructeur_signed_in? || user_signed_in? - if instructeur_signed_in? || user_signed_in?
%li %li
@ -60,15 +60,14 @@
= link_to "Connexion", new_user_session_path, class: "button secondary" = link_to "Connexion", new_user_session_path, class: "button secondary"
%li %li
.header-help - if dossier.present? && nav_bar_profile == :user
- if dossier.present? && nav_bar_profile == :user = render partial: 'shared/help/help_dropdown_dossier', locals: { dossier: dossier }
= render partial: 'shared/help/help_dropdown_dossier', locals: { dossier: dossier }
- elsif procedure.present? && (nav_bar_profile == :user || nav_bar_profile == :guest) - elsif procedure.present? && (nav_bar_profile == :user || nav_bar_profile == :guest)
= render partial: 'shared/help/help_dropdown_procedure', locals: { procedure: procedure } = render partial: 'shared/help/help_dropdown_procedure', locals: { procedure: procedure }
- elsif nav_bar_profile == :instructeur - elsif nav_bar_profile == :instructeur
= render partial: 'shared/help/help_dropdown_instructeur' = render partial: 'shared/help/help_dropdown_instructeur'
- else - else
= render partial: 'shared/help/help_button' = render partial: 'shared/help/help_button'

View file

@ -16,7 +16,7 @@
%th.libelle{ class: repetition ? 'padded' : '' } %th.libelle{ class: repetition ? 'padded' : '' }
= "#{c.libelle} :" = "#{c.libelle} :"
%td.rich-text %td.rich-text
%span{ class: highlight_if_unseen_class(demande_seen_at, c.updated_at) } %div{ class: highlight_if_unseen_class(demande_seen_at, c.updated_at) }
- case c.type_champ - case c.type_champ
- when TypeDeChamp.type_champs.fetch(:carte) - when TypeDeChamp.type_champs.fetch(:carte)
= render partial: "shared/champs/carte/show", locals: { champ: c } = render partial: "shared/champs/carte/show", locals: { champ: c }

View file

@ -2,13 +2,13 @@
= render partial: "shared/dossiers/submit_is_over", locals: { dossier: dossier } = render partial: "shared/dossiers/submit_is_over", locals: { dossier: dossier }
- if apercu - if apercu
- form_options = { url: '', method: :get, html: { class: 'form', multipart: true } } - form_options = { url: '', method: :get }
- elsif dossier.brouillon? - elsif dossier.brouillon?
- form_options = { url: brouillon_dossier_url(dossier), method: :patch, html: { class: 'form', multipart: true } } - form_options = { url: brouillon_dossier_url(dossier), method: :patch }
- else - else
- form_options = { url: modifier_dossier_url(dossier), method: :patch, html: { class: 'form', multipart: true } } - form_options = { url: modifier_dossier_url(dossier), method: :patch }
= form_for dossier, form_options do |f| = form_for dossier, form_options.merge({ html: { id: 'dossier-edit-form', class: 'form', multipart: true } }) do |f|
.prologue .prologue
%p.mandatory-explanation %p.mandatory-explanation
@ -43,13 +43,14 @@
- if dossier.brouillon? - if dossier.brouillon?
= f.button 'Enregistrer le brouillon', = f.button 'Enregistrer le brouillon',
formnovalidate: true, formnovalidate: true,
name: :save_draft,
value: true, value: true,
class: 'button send secondary', class: 'button send secondary',
data: { 'disable-with': "Envoi en cours…" } data: { 'disable-with': "Envoi en cours…" }
- if dossier.can_transition_to_en_construction? - if dossier.can_transition_to_en_construction?
= f.button 'Déposer le dossier', = f.button 'Déposer le dossier',
name: :submit_draft,
value: true,
class: 'button send primary', class: 'button send primary',
disabled: !current_user.owns?(dossier), disabled: !current_user.owns?(dossier),
data: { 'disable-with': "Envoi en cours…" } data: { 'disable-with': "Envoi en cours…" }

View file

@ -7,7 +7,7 @@
%div %div
= f.file_field :piece_jointe, id: 'piece_jointe', direct_upload: true = f.file_field :piece_jointe, id: 'piece_jointe', direct_upload: true
%label{ for: :piece_jointe } %label{ for: :piece_jointe }
.notice %span.notice
(taille max : 20 Mo) (taille max : 20 Mo)
%div %div

View file

@ -1,7 +1,7 @@
- if commentaire.sent_by_system? - if commentaire.sent_by_system?
= image_tag('icons/mail.svg', class: 'person-icon') = image_tag('icons/mail.svg', class: 'person-icon', alt: '')
- elsif commentaire.sent_by?(connected_user) - elsif commentaire.sent_by?(connected_user)
= image_tag('icons/account-circle.svg', class: 'person-icon') = image_tag('icons/account-circle.svg', class: 'person-icon', alt: '')
- else - else
= image_tag('icons/blue-person.svg', class: 'person-icon') = image_tag('icons/blue-person.svg', class: 'person-icon', alt: '')

View file

@ -1,5 +1,5 @@
.dropdown.help-dropdown .dropdown.help-dropdown
.button.primary.dropdown-button Aide %button.button.primary.dropdown-button Aide
.dropdown-content.fade-in-down .dropdown-content.fade-in-down
%ul.dropdown-items %ul.dropdown-items
- title = dossier.brouillon? ? "Besoin daide pour remplir votre dossier ?" : "Une question sur votre dossier ?" - title = dossier.brouillon? ? "Besoin daide pour remplir votre dossier ?" : "Une question sur votre dossier ?"

View file

@ -1,5 +1,5 @@
.dropdown.help-dropdown .dropdown.help-dropdown
.button.primary.dropdown-button Aide %button.button.primary.dropdown-button Aide
.dropdown-content.fade-in-down .dropdown-content.fade-in-down
%ul.dropdown-items %ul.dropdown-items
= render partial: 'shared/help/dropdown_items/faq_item' = render partial: 'shared/help/dropdown_items/faq_item'

View file

@ -4,7 +4,7 @@
- has_actions = has_delete_action || has_new_dossier_action - has_actions = has_delete_action || has_new_dossier_action
- if has_actions - if has_actions
%span.dropdown.user-dossier-actions .dropdown.user-dossier-actions
%button.button.dropdown-button %button.button.dropdown-button
Actions Actions
.dropdown-content.fade-in-down .dropdown-content.fade-in-down

View file

@ -3,8 +3,9 @@
- content_for :footer do - content_for :footer do
= render partial: "users/procedure_footer", locals: { procedure: @dossier.procedure, dossier: @dossier } = render partial: "users/procedure_footer", locals: { procedure: @dossier.procedure, dossier: @dossier }
.dossier-header.sub-header #dossier-draft
.container .dossier-header.sub-header
= render partial: "shared/dossiers/header", locals: { dossier: @dossier, apercu: false } .container
= render partial: "shared/dossiers/header", locals: { dossier: @dossier, apercu: false }
= render partial: "shared/dossiers/edit", locals: { dossier: @dossier, apercu: false } = render partial: "shared/dossiers/edit", locals: { dossier: @dossier, apercu: false }

View file

@ -30,26 +30,26 @@
%th.status-col Statut %th.status-col Statut
%th.updated-at-col Mis à jour %th.updated-at-col Mis à jour
%th %th
%tbody %tbody
- @dossiers.each do |dossier| - @dossiers.each do |dossier|
%tr{ data: { 'dossier-id': dossier.id } } %tr{ data: { 'dossier-id': dossier.id } }
%td.folder-col %td.folder-col
= link_to(url_for_dossier(dossier), class: 'cell-link') do = link_to(url_for_dossier(dossier), class: 'cell-link') do
%span.icon.folder %span.icon.folder
%td.number-col %td.number-col
= link_to(url_for_dossier(dossier), class: 'cell-link') do = link_to(url_for_dossier(dossier), class: 'cell-link') do
= dossier.id = dossier.id
%td %td
= link_to(url_for_dossier(dossier), class: 'cell-link') do = link_to(url_for_dossier(dossier), class: 'cell-link') do
= procedure_libelle(dossier.procedure) = procedure_libelle(dossier.procedure)
%td.status-col %td.status-col
= link_to(url_for_dossier(dossier), class: 'cell-link') do = link_to(url_for_dossier(dossier), class: 'cell-link') do
= render partial: 'shared/dossiers/status_badge', locals: { dossier: dossier } = render partial: 'shared/dossiers/status_badge', locals: { dossier: dossier }
%td.updated-at-col %td.updated-at-col
= link_to(url_for_dossier(dossier), class: 'cell-link') do = link_to(url_for_dossier(dossier), class: 'cell-link') do
= try_format_date(dossier.updated_at) = try_format_date(dossier.updated_at)
%td.action-col.action-col %td.action-col.action-col
= render partial: 'dossier_actions', locals: { dossier: dossier } = render partial: 'dossier_actions', locals: { dossier: dossier }
= paginate(@dossiers) = paginate(@dossiers)
- if current_user.feedbacks.empty? || current_user.feedbacks.last.created_at < 1.month.ago - if current_user.feedbacks.empty? || current_user.feedbacks.last.created_at < 1.month.ago

View file

@ -13,7 +13,7 @@
.auth-options .auth-options
%div %div
= f.check_box :remember_me, as: :boolean = f.check_box :remember_me
= f.label :remember_me, "Se souvenir de moi", class: 'remember-me' = f.label :remember_me, "Se souvenir de moi", class: 'remember-me'
.text-right .text-right

View file

@ -1,20 +1,35 @@
# 3 valeurs:
# * tps: environnement de production
# * tps_dev: environnement de pre-production
# * tps_local: machine de développeur
APP_NAME="tps_local" APP_NAME="tps_local"
# Nom d'hôte de l'appli
# * Pour du dev local: localhost:3000
# * pour de la preprod: preprod.ds.organisme.fr (par exemple)
# * pour de la prod: www.demarches-simplifiees.fr
APP_HOST="localhost:3000" APP_HOST="localhost:3000"
# Utilisé pour les logs LogRage
SOURCE="tps_local" SOURCE="tps_local"
# Clé de chiffrement de rails, cf https://api.rubyonrails.org/classes/Rails/Application.html
SECRET_KEY_BASE="05a2d479d8e412198dabd08ef0eee9d6e180f5cbb48661a35fd1cae287f0a93d40b5f1da08f06780d698bbd458a0ea97f730f83ee780de5d4e31f649a0130cf0" SECRET_KEY_BASE="05a2d479d8e412198dabd08ef0eee9d6e180f5cbb48661a35fd1cae287f0a93d40b5f1da08f06780d698bbd458a0ea97f730f83ee780de5d4e31f649a0130cf0"
SIGNING_KEY="aef3153a9829fa4ba10acb02927ac855df6b92795b1ad265d654443c4b14a017" SIGNING_KEY="aef3153a9829fa4ba10acb02927ac855df6b92795b1ad265d654443c4b14a017"
# Database
DB_DATABASE="tps_development" DB_DATABASE="tps_development"
DB_HOST="localhost" DB_HOST="localhost"
DB_POOL="" DB_POOL=""
DB_USERNAME="tps_development" DB_USERNAME="tps_development"
DB_PASSWORD="tps_development" DB_PASSWORD="tps_development"
# Protection simple de l'instance par mot de passe (utile pour la pre-prod)
BASIC_AUTH_ENABLED="disabled" BASIC_AUTH_ENABLED="disabled"
BASIC_AUTH_USERNAME="" BASIC_AUTH_USERNAME=""
BASIC_AUTH_PASSWORD="" BASIC_AUTH_PASSWORD=""
# Object Storage pour les pièces jointes
FOG_OPENSTACK_TENANT="" FOG_OPENSTACK_TENANT=""
FOG_OPENSTACK_API_KEY="" FOG_OPENSTACK_API_KEY=""
FOG_OPENSTACK_USERNAME="" FOG_OPENSTACK_USERNAME=""
@ -22,29 +37,36 @@ FOG_OPENSTACK_URL=""
FOG_OPENSTACK_IDENTITY_API_VERSION="" FOG_OPENSTACK_IDENTITY_API_VERSION=""
FOG_OPENSTACK_REGION="" FOG_OPENSTACK_REGION=""
FOG_DIRECTORY="" FOG_DIRECTORY=""
FOG_ENABLED="" FOG_ENABLED="" # valeur attendue: enabled
DS_PROXY_URL="" DS_PROXY_URL=""
# Service externe: authentification France Connect
FC_PARTICULIER_ID="" FC_PARTICULIER_ID=""
FC_PARTICULIER_SECRET="" FC_PARTICULIER_SECRET=""
FC_PARTICULIER_BASE_URL="" FC_PARTICULIER_BASE_URL=""
# Service externe: Authentification pour manager (auth Github obligatoire), permet d'accéder à /manager
GITHUB_CLIENT_ID="" GITHUB_CLIENT_ID=""
GITHUB_CLIENT_SECRET="" GITHUB_CLIENT_SECRET=""
# Service externe: Support Utilisateur HelpScout | Spécifique démarches-simplifiées.fr
HELPSCOUT_MAILBOX_ID="" HELPSCOUT_MAILBOX_ID=""
HELPSCOUT_CLIENT_ID="" HELPSCOUT_CLIENT_ID=""
HELPSCOUT_CLIENT_SECRET="" HELPSCOUT_CLIENT_SECRET=""
HELPSCOUT_WEBHOOK_SECRET="" HELPSCOUT_WEBHOOK_SECRET=""
# Service externe: Supervision exterieure | Spécifique démarches-simplifiées.fr
SENTRY_ENABLED="disabled" SENTRY_ENABLED="disabled"
SENTRY_CURRENT_ENV="development" SENTRY_CURRENT_ENV="development"
SENTRY_DSN_RAILS="" SENTRY_DSN_RAILS=""
SENTRY_DSN_JS="" SENTRY_DSN_JS=""
# Statistiques web
MATOMO_ENABLED="disabled" MATOMO_ENABLED="disabled"
MATOMO_ID="73" MATOMO_ID="73"
# Missing MATOMO_HOST (thus hardcoded)
# SMTP Provider: Send In Blue
SENDINBLUE_BALANCING="" SENDINBLUE_BALANCING=""
SENDINBLUE_BALANCING_VALUE="" SENDINBLUE_BALANCING_VALUE=""
SENDINBLUE_ENABLED="" SENDINBLUE_ENABLED=""
@ -52,26 +74,34 @@ SENDINBLUE_CLIENT_KEY=""
SENDINBLUE_SMTP_KEY="" SENDINBLUE_SMTP_KEY=""
SENDINBLUE_USER_NAME="" SENDINBLUE_USER_NAME=""
# Service externe: Fournisseur de tchat pour administrateur | Spécifique démarches-simplifiées.fr
CRISP_ENABLED="disabled" CRISP_ENABLED="disabled"
CRISP_CLIENT_KEY="" CRISP_CLIENT_KEY=""
# Service externe: rattrapage de mails envoyés, utile en préprod | Spécifique démarches-simplifiées.fr
MAILTRAP_ENABLED="disabled" MAILTRAP_ENABLED="disabled"
MAILTRAP_USERNAME="" MAILTRAP_USERNAME=""
MAILTRAP_PASSWORD="" MAILTRAP_PASSWORD=""
# SMTP Provider: Mailjet
MAILJET_API_KEY="" MAILJET_API_KEY=""
MAILJET_SECRET_KEY="" MAILJET_SECRET_KEY=""
# API Entreprise https://api.gouv.fr/api/api-entreprise.html
API_ENTREPRISE_KEY="" API_ENTREPRISE_KEY=""
# Service externe: CRM de suivi de création d'administrateur | Spécifique démarches-simplifiées.fr
PIPEDRIVE_KEY="" PIPEDRIVE_KEY=""
# Liste des réseaux qui passent outre la génération de token pour identifier un device, ainsi que le throttling par rack-attack
TRUSTED_NETWORKS="" TRUSTED_NETWORKS=""
# Service externe: mesure de performance d'appli Rails | Spécifique démarches-simplifiées.fr
SKYLIGHT_AUTHENTICATION_KEY="" SKYLIGHT_AUTHENTICATION_KEY=""
# Activer ou non les logs LogRage
LOGRAGE_ENABLED="disabled" LOGRAGE_ENABLED="disabled"
# Service externe d'horodatage des changements de statut des dossiers (effectué quotidiennement)
UNIVERSIGN_API_URL="" UNIVERSIGN_API_URL=""
UNIVERSIGN_USERPWD="" UNIVERSIGN_USERPWD=""

View file

@ -48,6 +48,7 @@ describe Instructeurs::DossiersController, type: :controller do
end end
it { expect(response).to redirect_to(personnes_impliquees_instructeur_dossier_url) } it { expect(response).to redirect_to(personnes_impliquees_instructeur_dossier_url) }
it { expect(recipient.followed_dossiers).to include(dossier) }
end end
describe '#follow' do describe '#follow' do

View file

@ -426,7 +426,7 @@ describe Instructeurs::ProceduresController, type: :controller do
context "csv" do context "csv" do
before do before do
expect_any_instance_of(Procedure).to receive(:to_csv) expect_any_instance_of(Procedure).to receive(:to_csv)
.with(instructeur.dossiers.for_procedure(procedure), {}) .with(instructeur.dossiers.for_procedure(procedure))
get :download_dossiers, params: { procedure_id: procedure.id }, format: 'csv' get :download_dossiers, params: { procedure_id: procedure.id }, format: 'csv'
end end

View file

@ -100,38 +100,38 @@ describe Users::DossiersController, type: :controller do
let(:user) { create(:user) } let(:user) { create(:user) }
let(:asked_dossier) { create(:dossier) } let(:asked_dossier) { create(:dossier) }
let(:ensure_authorized) { :forbid_invite_submission! } let(:ensure_authorized) { :forbid_invite_submission! }
let(:draft) { false } let(:submit) { true }
before do before do
@controller.params = @controller.params.merge(dossier_id: asked_dossier.id, save_draft: draft) @controller.params = @controller.params.merge(dossier_id: asked_dossier.id, submit_draft: submit)
allow(@controller).to receive(:current_user).and_return(user) allow(@controller).to receive(:current_user).and_return(user)
allow(@controller).to receive(:redirect_to) allow(@controller).to receive(:redirect_to)
end end
context 'when a user save their own draft' do context 'when a user save their own draft' do
let(:asked_dossier) { create(:dossier, user: user) } let(:asked_dossier) { create(:dossier, user: user) }
let(:draft) { true } let(:submit) { false }
it_behaves_like 'does not redirect nor flash' it_behaves_like 'does not redirect nor flash'
end end
context 'when a user submit their own dossier' do context 'when a user submit their own dossier' do
let(:asked_dossier) { create(:dossier, user: user) } let(:asked_dossier) { create(:dossier, user: user) }
let(:draft) { false } let(:submit) { true }
it_behaves_like 'does not redirect nor flash' it_behaves_like 'does not redirect nor flash'
end end
context 'when an invite save the draft for a dossier where they where invited' do context 'when an invite save the draft for a dossier where they where invited' do
before { create(:invite, dossier: asked_dossier, user: user) } before { create(:invite, dossier: asked_dossier, user: user) }
let(:draft) { true } let(:submit) { false }
it_behaves_like 'does not redirect nor flash' it_behaves_like 'does not redirect nor flash'
end end
context 'when an invite submit a dossier where they where invited' do context 'when an invite submit a dossier where they where invited' do
before { create(:invite, dossier: asked_dossier, user: user) } before { create(:invite, dossier: asked_dossier, user: user) }
let(:draft) { false } let(:submit) { true }
it_behaves_like 'redirects and flashes' it_behaves_like 'redirects and flashes'
end end
@ -394,7 +394,7 @@ describe Users::DossiersController, type: :controller do
} }
} }
end end
let(:payload) { submit_payload } let(:payload) { submit_payload.merge(submit_draft: true) }
subject do subject do
Timecop.freeze(now) do Timecop.freeze(now) do
@ -480,7 +480,7 @@ describe Users::DossiersController, type: :controller do
it { expect(flash.alert).to eq(['Le champ l doit être rempli.']) } it { expect(flash.alert).to eq(['Le champ l doit être rempli.']) }
context 'and the user saves a draft' do context 'and the user saves a draft' do
let(:payload) { submit_payload.merge(save_draft: true) } let(:payload) { submit_payload.except(:submit_draft) }
it { expect(response).to render_template(:brouillon) } it { expect(response).to render_template(:brouillon) }
it { expect(flash.notice).to eq('Votre brouillon a bien été sauvegardé.') } it { expect(flash.notice).to eq('Votre brouillon a bien été sauvegardé.') }
@ -510,7 +510,7 @@ describe Users::DossiersController, type: :controller do
let!(:invite) { create(:invite, dossier: dossier, user: user) } let!(:invite) { create(:invite, dossier: dossier, user: user) }
context 'and the invite saves a draft' do context 'and the invite saves a draft' do
let(:payload) { submit_payload.merge(save_draft: true) } let(:payload) { submit_payload.except(:submit_draft) }
before do before do
first_champ.type_de_champ.update(mandatory: true, libelle: 'l') first_champ.type_de_champ.update(mandatory: true, libelle: 'l')

View file

@ -1,13 +1,21 @@
require 'spec_helper' require 'spec_helper'
require 'csv'
describe ProcedureExportService do describe ProcedureExportService do
describe 'to_data' do describe 'to_data' do
let(:procedure) { create(:procedure, :published, :with_all_champs) } let(:procedure) { create(:procedure, :published, :for_individual, :with_all_champs) }
let(:table) { :dossiers } subject do
subject { ProcedureExportService.new(procedure, procedure.dossiers).to_data(table) } Tempfile.create do |f|
f << ProcedureExportService.new(procedure, procedure.dossiers).to_xlsx
f.rewind
SimpleXlsxReader.open(f.path)
end
end
let(:headers) { subject[:headers] } let(:dossiers_sheet) { subject.sheets.first }
let(:data) { subject[:data] } let(:etablissements_sheet) { subject.sheets.second }
let(:avis_sheet) { subject.sheets.third }
let(:repetition_sheet) { subject.sheets.fourth }
before do before do
# change one tdc place to check if the header is ordered # change one tdc place to check if the header is ordered
@ -19,269 +27,324 @@ describe ProcedureExportService do
end end
context 'dossiers' do context 'dossiers' do
let(:nominal_header) 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) }
let(:nominal_headers) do
[ [
:id, "ID",
:created_at, "Email",
:updated_at, "Civilité",
:archived, "Nom",
:email, "Prénom",
:state, "Date de naissance",
:initiated_at, "Archivé",
:received_at, "État du dossier",
:processed_at, "Dernière mise à jour le",
:motivation, "Déposé le",
:emails_instructeurs, "Passé en instruction le",
:individual_gender, "Traité le",
:individual_prenom, "Motivation de la décision",
:individual_nom, "Instructeurs",
:individual_birthdate, "textarea",
"date",
:textarea, "datetime",
:date, "number",
:datetime, "decimal_number",
:number, "integer_number",
:decimal_number, "checkbox",
:integer_number, "civilite",
:checkbox, "email",
:civilite, "phone",
:email, "address",
:phone, "yes_no",
:address, "simple_drop_down_list",
:yes_no, "multiple_drop_down_list",
:simple_drop_down_list, "linked_drop_down_list",
:multiple_drop_down_list, "pays",
:linked_drop_down_list, "regions",
:pays, "departements",
:regions, "engagement",
:departements, "dossier_link",
:engagement, "piece_justificative",
:dossier_link, "siret",
:piece_justificative, "carte",
:siret, "text"
:carte,
:text,
:etablissement_siret,
:etablissement_siege_social,
:etablissement_naf,
:etablissement_libelle_naf,
:etablissement_adresse,
:etablissement_numero_voie,
:etablissement_type_voie,
:etablissement_nom_voie,
:etablissement_complement_adresse,
:etablissement_code_postal,
:etablissement_localite,
:etablissement_code_insee_localite,
:entreprise_siren,
:entreprise_capital_social,
:entreprise_numero_tva_intracommunautaire,
:entreprise_forme_juridique,
:entreprise_forme_juridique_code,
:entreprise_nom_commercial,
:entreprise_raison_sociale,
:entreprise_siret_siege_social,
:entreprise_code_effectif_entreprise,
:entreprise_date_creation,
:entreprise_nom,
:entreprise_prenom
] ]
end end
it 'should have headers' do it 'should have headers' do
expect(headers).to eq(nominal_header) expect(dossiers_sheet.headers).to match(nominal_headers)
end
it 'should have data' do
expect(dossiers_sheet.data.size).to eq(1)
expect(etablissements_sheet.data.size).to eq(1)
# SimpleXlsxReader is transforming datetimes in utc... It is only used in test so we just hack around.
offset = dossier.en_construction_at.utc_offset
en_construction_at = Time.zone.at(dossiers_sheet.data[0][9] - offset.seconds)
en_instruction_at = Time.zone.at(dossiers_sheet.data[0][10] - offset.seconds)
expect(en_construction_at).to eq(dossier.en_construction_at.round)
expect(en_instruction_at).to eq(dossier.en_instruction_at.round)
end end
context 'with a procedure routee' do context 'with a procedure routee' do
before { procedure.groupe_instructeurs.create(label: '2') } before { procedure.groupe_instructeurs.create(label: '2') }
let(:routee_header) { nominal_header.insert(nominal_header.index(:textarea), :groupe_instructeur_label) } let(:routee_header) { nominal_headers.insert(nominal_headers.index('textarea'), 'Groupe instructeur') }
it { expect(headers).to eq(routee_header) } it { expect(dossiers_sheet.headers).to match(routee_header) }
end it { expect(dossiers_sheet.data[0][dossiers_sheet.headers.index('Groupe instructeur')]).to eq('défaut') }
it 'should have empty values' do
expect(data).to eq([[]])
end
context 'with dossier' do
let!(:dossier) { create(:dossier, :en_instruction, :with_all_champs, :for_individual, procedure: procedure) }
let(:dossier_data) {
[
dossier.id.to_s,
dossier.created_at.to_s,
dossier.updated_at.to_s,
"false",
dossier.user.email,
"received",
dossier.en_construction_at.to_s,
dossier.en_instruction_at.to_s,
nil,
nil,
nil
] + individual_data
}
let(:individual_data) {
[
"M.",
"Xavier",
"Julien",
"1991-11-01"
]
}
let(:champs_data) {
dossier.reload.champs.reject(&:exclude_from_export?).map(&:for_export)
}
let(:etablissement_data) {
Array.new(24)
}
it 'should have values' do
expect(data.first[0..14]).to eq(dossier_data)
expect(data.first[15..38]).to eq(champs_data)
expect(data.first[39..62]).to eq(etablissement_data)
expect(data).to eq([
dossier_data + champs_data + etablissement_data
])
end
context 'with a procedure routee' do
before { procedure.groupe_instructeurs.create(label: '2') }
it { expect(data.first[15]).to eq('défaut') }
it { expect(data.first.count).to eq(dossier_data.count + champs_data.count + etablissement_data.count + 1) }
end
context 'and etablissement' do
let!(:dossier) { create(:dossier, :en_instruction, :with_all_champs, :with_entreprise, procedure: procedure) }
let(:etablissement_data) {
[
dossier.etablissement.siret,
dossier.etablissement.siege_social.to_s,
dossier.etablissement.naf,
dossier.etablissement.libelle_naf,
dossier.etablissement.adresse&.chomp&.gsub("\r\n", ' ')&.delete("\r"),
dossier.etablissement.numero_voie,
dossier.etablissement.type_voie,
dossier.etablissement.nom_voie,
dossier.etablissement.complement_adresse,
dossier.etablissement.code_postal,
dossier.etablissement.localite,
dossier.etablissement.code_insee_localite,
dossier.etablissement.entreprise_siren,
dossier.etablissement.entreprise_capital_social.to_s,
dossier.etablissement.entreprise_numero_tva_intracommunautaire,
dossier.etablissement.entreprise_forme_juridique,
dossier.etablissement.entreprise_forme_juridique_code,
dossier.etablissement.entreprise_nom_commercial,
dossier.etablissement.entreprise_raison_sociale,
dossier.etablissement.entreprise_siret_siege_social,
dossier.etablissement.entreprise_code_effectif_entreprise,
dossier.etablissement.entreprise_date_creation.to_datetime.to_s,
dossier.etablissement.entreprise_nom,
dossier.etablissement.entreprise_prenom
]
}
let(:individual_data) {
Array.new(4)
}
it 'should have values' do
expect(data.first[0..14]).to eq(dossier_data)
expect(data.first[15..38]).to eq(champs_data)
expect(data.first[39..62]).to eq(etablissement_data)
expect(data).to eq([
dossier_data + champs_data + etablissement_data
])
end
end
end end
end end
context 'etablissements' do context 'with etablissement' do
let(:table) { :etablissements } let(:procedure) { create(:procedure, :published, :with_all_champs) }
let!(:dossier) { create(:dossier, :en_instruction, :with_all_champs, :with_entreprise, procedure: procedure) }
let(:dossier_etablissement) { etablissements_sheet.data[1] }
let(:champ_etablissement) { etablissements_sheet.data[0] }
let(:nominal_headers) do
[
"ID",
"Email",
"Entreprise raison sociale",
"Archivé",
"État du dossier",
"Dernière mise à jour le",
"Déposé 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
context 'as csv' do
subject do
Tempfile.create do |f|
f << ProcedureExportService.new(procedure, procedure.dossiers).to_csv
f.rewind
CSV.read(f.path)
end
end
let(:nominal_headers) do
[
"ID",
"Email",
"É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",
"Archivé",
"État du dossier",
"Dernière mise à jour le",
"Déposé 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
let(:dossiers_sheet_headers) { subject.first }
it 'should have headers' do
expect(dossiers_sheet_headers).to match(nominal_headers)
end
end
it 'should have headers' do it 'should have headers' do
expect(headers).to eq([ expect(dossiers_sheet.headers).to match(nominal_headers)
:dossier_id,
:libelle, expect(etablissements_sheet.headers).to eq([
:etablissement_siret, "Dossier ID",
:etablissement_siege_social, "Champ",
:etablissement_naf, "Établissement SIRET",
:etablissement_libelle_naf, "Établissement siège social",
:etablissement_adresse, "Établissement NAF",
:etablissement_numero_voie, "Établissement libellé NAF",
:etablissement_type_voie, "Établissement Adresse",
:etablissement_nom_voie, "Établissement numero voie",
:etablissement_complement_adresse, "Établissement type voie",
:etablissement_code_postal, "Établissement nom voie",
:etablissement_localite, "Établissement complément adresse",
:etablissement_code_insee_localite, "Établissement code postal",
:entreprise_siren, "Établissement localité",
:entreprise_capital_social, "Établissement code INSEE localité",
:entreprise_numero_tva_intracommunautaire, "Entreprise SIREN",
:entreprise_forme_juridique, "Entreprise capital social",
:entreprise_forme_juridique_code, "Entreprise numero TVA intracommunautaire",
:entreprise_nom_commercial, "Entreprise forme juridique",
:entreprise_raison_sociale, "Entreprise forme juridique code",
:entreprise_siret_siege_social, "Entreprise nom commercial",
:entreprise_code_effectif_entreprise, "Entreprise raison sociale",
:entreprise_date_creation, "Entreprise SIRET siège social",
:entreprise_nom, "Entreprise code effectif entreprise",
:entreprise_prenom "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 end
it 'should have empty values' do it 'should have data' do
expect(data).to eq([[]]) expect(etablissements_sheet.data.size).to eq(2)
expect(dossier_etablissement[1]).to eq("Dossier")
expect(champ_etablissement[1]).to eq("siret")
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 end
context 'with dossier containing champ siret' do it 'should have data' do
let!(:dossier) { create(:dossier, :en_instruction, :with_all_champs, procedure: procedure) } expect(avis_sheet.data.size).to eq(1)
let(:etablissement) { dossier.champs.find { |champ| champ.type_champ == 'siret' }.etablissement } end
end
let(:etablissement_data) { context 'with repetitions' do
[ let!(:dossiers) do
dossier.id, [
'siret', create(:dossier, :en_instruction, :with_all_champs, :for_individual, procedure: procedure),
etablissement.siret, create(:dossier, :en_instruction, :with_all_champs, :for_individual, procedure: procedure)
etablissement.siege_social.to_s, ]
etablissement.naf, end
etablissement.libelle_naf, let(:champ_repetition) { dossiers.first.champs.find { |champ| champ.type_champ == 'repetition' } }
etablissement.adresse&.chomp&.gsub("\r\n", ' ')&.delete("\r"),
etablissement.numero_voie,
etablissement.type_voie,
etablissement.nom_voie,
etablissement.complement_adresse,
etablissement.code_postal,
etablissement.localite,
etablissement.code_insee_localite,
etablissement.entreprise_siren,
etablissement.entreprise_capital_social.to_s,
etablissement.entreprise_numero_tva_intracommunautaire,
etablissement.entreprise_forme_juridique,
etablissement.entreprise_forme_juridique_code,
etablissement.entreprise_nom_commercial,
etablissement.entreprise_raison_sociale,
etablissement.entreprise_siret_siege_social,
etablissement.entreprise_code_effectif_entreprise,
etablissement.entreprise_date_creation.to_datetime.to_s,
etablissement.entreprise_nom,
etablissement.entreprise_prenom
]
}
it 'should have values' do it 'should have sheets' do
expect(data.first).to eq(etablissement_data) expect(subject.sheets.map(&:name)).to eq(['Dossiers', 'Etablissements', 'Avis', champ_repetition.libelle_for_export])
end
it 'should have headers' do
expect(repetition_sheet.headers).to eq([
"Dossier ID",
"Ligne",
"Nom",
"Age"
])
end
it 'should have data' do
expect(repetition_sheet.data.size).to eq(4)
end
context 'with invalid characters' do
before do
champ_repetition.type_de_champ.update(libelle: 'A / B \ C')
end
it 'should have valid sheet name' do
expect(subject.sheets.map(&:name)).to eq(['Dossiers', 'Etablissements', 'Avis', "(#{champ_repetition.type_de_champ.stable_id}) A - B - C"])
end
end
context 'with non unique labels' 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_repetition) { create(:type_de_champ_repetition, procedure: procedure, libelle: champ_repetition.libelle) }
let!(:another_champ_repetition) { create(:champ_repetition, type_de_champ: type_de_champ_repetition, dossier: dossier) }
it 'should have sheets' do
expect(subject.sheets.map(&:name)).to eq(['Dossiers', 'Etablissements', 'Avis', champ_repetition.libelle_for_export, another_champ_repetition.libelle_for_export])
end end
end end
end end

View file

@ -1,352 +0,0 @@
require 'spec_helper'
require 'csv'
describe ProcedureExportV2Service do
describe 'to_data' do
let(:procedure) { create(:procedure, :published, :for_individual, :with_all_champs) }
subject do
Tempfile.create do |f|
f << ProcedureExportV2Service.new(procedure, procedure.dossiers).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) }
let(:nominal_headers) do
[
"ID",
"Email",
"Civilité",
"Nom",
"Prénom",
"Date de naissance",
"Archivé",
"État du dossier",
"Dernière mise à jour le",
"Déposé 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 headers' do
expect(dossiers_sheet.headers).to match(nominal_headers)
end
it 'should have data' do
expect(dossiers_sheet.data.size).to eq(1)
expect(etablissements_sheet.data.size).to eq(1)
# SimpleXlsxReader is transforming datetimes in utc... It is only used in test so we just hack around.
offset = dossier.en_construction_at.utc_offset
en_construction_at = Time.zone.at(dossiers_sheet.data[0][9] - offset.seconds)
en_instruction_at = Time.zone.at(dossiers_sheet.data[0][10] - offset.seconds)
expect(en_construction_at).to eq(dossier.en_construction_at.round)
expect(en_instruction_at).to eq(dossier.en_instruction_at.round)
end
context 'with a procedure routee' do
before { procedure.groupe_instructeurs.create(label: '2') }
let(:routee_header) { nominal_headers.insert(nominal_headers.index('textarea'), 'Groupe instructeur') }
it { expect(dossiers_sheet.headers).to match(routee_header) }
it { expect(dossiers_sheet.data[0][dossiers_sheet.headers.index('Groupe instructeur')]).to eq('défaut') }
end
end
context 'with etablissement' do
let(:procedure) { create(:procedure, :published, :with_all_champs) }
let!(:dossier) { create(:dossier, :en_instruction, :with_all_champs, :with_entreprise, procedure: procedure) }
let(:dossier_etablissement) { etablissements_sheet.data[1] }
let(:champ_etablissement) { etablissements_sheet.data[0] }
let(:nominal_headers) do
[
"ID",
"Email",
"Entreprise raison sociale",
"Archivé",
"État du dossier",
"Dernière mise à jour le",
"Déposé 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
context 'as csv' do
subject do
Tempfile.create do |f|
f << ProcedureExportV2Service.new(procedure, procedure.dossiers).to_csv
f.rewind
CSV.read(f.path)
end
end
let(:nominal_headers) do
[
"ID",
"Email",
"É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",
"Archivé",
"État du dossier",
"Dernière mise à jour le",
"Déposé 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
let(:dossiers_sheet_headers) { subject.first }
it 'should have headers' do
expect(dossiers_sheet_headers).to match(nominal_headers)
end
end
it 'should have headers' do
expect(dossiers_sheet.headers).to match(nominal_headers)
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)
expect(dossier_etablissement[1]).to eq("Dossier")
expect(champ_etablissement[1]).to eq("siret")
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!(:dossiers) do
[
create(:dossier, :en_instruction, :with_all_champs, :for_individual, procedure: procedure),
create(:dossier, :en_instruction, :with_all_champs, :for_individual, procedure: procedure)
]
end
let(:champ_repetition) { dossiers.first.champs.find { |champ| champ.type_champ == 'repetition' } }
it 'should have sheets' do
expect(subject.sheets.map(&:name)).to eq(['Dossiers', 'Etablissements', 'Avis', champ_repetition.libelle_for_export])
end
it 'should have headers' do
expect(repetition_sheet.headers).to eq([
"Dossier ID",
"Ligne",
"Nom",
"Age"
])
end
it 'should have data' do
expect(repetition_sheet.data.size).to eq(4)
end
context 'with invalid characters' do
before do
champ_repetition.type_de_champ.update(libelle: 'A / B \ C')
end
it 'should have valid sheet name' do
expect(subject.sheets.map(&:name)).to eq(['Dossiers', 'Etablissements', 'Avis', "(#{champ_repetition.type_de_champ.stable_id}) A - B - C"])
end
end
context 'with non unique labels' 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_repetition) { create(:type_de_champ_repetition, procedure: procedure, libelle: champ_repetition.libelle) }
let!(:another_champ_repetition) { create(:champ_repetition, type_de_champ: type_de_champ_repetition, dossier: dossier) }
it 'should have sheets' do
expect(subject.sheets.map(&:name)).to eq(['Dossiers', 'Etablissements', 'Avis', champ_repetition.libelle_for_export, another_champ_repetition.libelle_for_export])
end
end
end
end
end