Merge pull request #10591 from demarches-simplifiees/9827-export-select
ETQ instructeur, je peux créer un modèle d'export tabulaire
This commit is contained in:
commit
4b740f8f29
66 changed files with 1537 additions and 228 deletions
|
@ -551,3 +551,22 @@ textarea::placeholder {
|
|||
.resize-y {
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
.checkbox-group-bordered {
|
||||
border: 1px solid var(--border-default-grey);
|
||||
flex: 1 1 100%; // copied from fr-fieldset-element
|
||||
max-width: 100%; // copied from fr-fieldset-element
|
||||
}
|
||||
|
||||
.fieldset-bordered {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.fieldset-bordered::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
border-left: 2px solid var(--border-default-blue-france);
|
||||
}
|
||||
|
|
|
@ -44,8 +44,12 @@
|
|||
position: relative;
|
||||
}
|
||||
|
||||
.dropdown-export .dropdown-content {
|
||||
.dropdown-export.dropdown-content {
|
||||
width: 450px;
|
||||
|
||||
a {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown-label.dropdown-content {
|
||||
|
|
|
@ -3,13 +3,14 @@
|
|||
class Dossiers::ExportDropdownComponent < ApplicationComponent
|
||||
include ApplicationHelper
|
||||
|
||||
def initialize(procedure:, export_templates: nil, statut: nil, count: nil, class_btn: nil, export_url: nil)
|
||||
def initialize(procedure:, export_templates: nil, statut: nil, count: nil, class_btn: nil, export_url: nil, show_export_template_tab: true)
|
||||
@procedure = procedure
|
||||
@export_templates = export_templates
|
||||
@statut = statut
|
||||
@count = count
|
||||
@class_btn = class_btn
|
||||
@export_url = export_url
|
||||
@show_export_template_tab = show_export_template_tab
|
||||
end
|
||||
|
||||
def formats
|
||||
|
|
|
@ -1,26 +1,68 @@
|
|||
= render Dropdown::MenuComponent.new(wrapper: :span, button_options: { class: ['fr-btn--sm', @class_btn.present? ? @class_btn : 'fr-btn--secondary']}, menu_options: { id: @count.nil? ? "download_menu" : "download_all_menu", class: ['dropdown-export'] }) do |menu|
|
||||
- menu.with_menu_header_html do
|
||||
%p.menu-component-header.fr-px-2w.fr-pt-2w.fr-mb-0
|
||||
%span.fr-icon-info-line{ aria: { hidden: true } }
|
||||
Des macros ? Lisez la
|
||||
= link_to('doc', t('.macros_doc.url'),
|
||||
title: t('.macros_doc.title'),
|
||||
**external_link_attributes)
|
||||
|
||||
= render Dropdown::MenuComponent.new(wrapper: :div, button_options: { class: ['fr-btn--sm', @class_btn.present? ? @class_btn : 'fr-btn--secondary']}, menu_options: { id: @count.nil? ? "download_menu" : "download_all_menu", class: ['dropdown-export'] }) do |menu|
|
||||
- menu.with_button_inner_html do
|
||||
= @count.nil? ? t(".download_all") : t(".download", count: @count)
|
||||
|
||||
- formats.each do |format|
|
||||
- menu.with_item do
|
||||
= link_to download_export_path(export_format: format), role: 'menuitem', data: { turbo_method: :post, turbo: true } do
|
||||
= t(".everything_#{format}_html")
|
||||
- menu.with_form do
|
||||
.fr-container
|
||||
.fr-tabs.fr-my-3w
|
||||
%ul.fr-tabs__list{ role: 'tablist' }
|
||||
%li{ role: 'presentation' }
|
||||
%button.fr-tabs__tab.fr-tabs__tab--icon-left{ id: "tabpanel-standard#{@count}", tabindex: "0", role: "tab", "aria-selected": "true", "aria-controls": "tabpanel-standard#{@count}-panel" } Standard
|
||||
|
||||
- if @procedure.feature_enabled?(:export_template)
|
||||
- if export_templates.present?
|
||||
- export_templates.each do |export_template|
|
||||
- menu.with_item do
|
||||
= link_to download_export_path(export_template_id: export_template.id), role: 'menuitem', data: { turbo_method: :post, turbo: true } do
|
||||
= "Exporter à partir du modèle #{export_template.name}"
|
||||
- menu.with_item do
|
||||
= link_to [:new, :instructeur, @procedure, :export_template], role: 'menuitem' do
|
||||
Ajouter un modèle d'export
|
||||
- if @show_export_template_tab
|
||||
%li{ role: 'presentation' }
|
||||
%button.fr-tabs__tab.fr-tabs__tab--icon-left{ id: "tabpanel-template#{@count}", tabindex: "-1", role: "tab", "aria-selected": "false", "aria-controls": "tabpanel-template#{@count}-panel" } A partir d'un modèle
|
||||
|
||||
.fr-tabs__panel.fr-pb-8w.fr-tabs__panel--selected{ id: "tabpanel-standard#{@count}-panel", role: "tabpanel", "aria-labelledby": "tabpanel-standard#{@count}", tabindex: "0" }
|
||||
= form_with url: download_export_path, namespace: "export#{@count}", data: { turbo_method: :post, turbo: true } do |f|
|
||||
= f.hidden_field :statut, value: @statut
|
||||
%fieldset.fr-fieldset#radio-hint{ "aria-labelledby": "radio-hint-legend" }
|
||||
%legend.fr-fieldset__legend--regular.fr-fieldset__legend#radio-hint-legend Séletionner le format de l'export
|
||||
.fr-fieldset__element
|
||||
.fr-radio-group
|
||||
= f.radio_button :export_format, 'xlsx'
|
||||
= f.label :export_format_xlsx, 'Fichier xlsx'
|
||||
.fr-fieldset__element
|
||||
.fr-radio-group
|
||||
= f.radio_button :export_format, 'ods'
|
||||
= f.label :export_format_ods, 'Fichier ods'
|
||||
.fr-fieldset__element
|
||||
.fr-radio-group
|
||||
= f.radio_button :export_format, 'csv'
|
||||
= f.label :export_format_csv do
|
||||
Fichier csv
|
||||
%span.fr-hint-text Uniquement les dossiers, sans les champs répétables
|
||||
.fr-fieldset__element
|
||||
.fr-radio-group
|
||||
= f.radio_button :export_format, 'zip'
|
||||
= f.label :export_format_zip do
|
||||
Fichier zip
|
||||
%span.fr-hint-text ne contient pas l'horodatage ni le journal de log
|
||||
|
||||
.fr-fieldset__element
|
||||
%ul.fr-btns-group.fr-btns-group--sm.fr-btns-group--inline
|
||||
%li
|
||||
%button.fr-btn.fr-btn--secondary{ type: 'button', "data-action": "click->menu-button#close" } Annuler
|
||||
%li
|
||||
= f.submit "Demander l'export", "data-action": "click->menu-button#close", class: 'fr-btn'
|
||||
|
||||
|
||||
- if @show_export_template_tab
|
||||
.fr-tabs__panel.fr-pr-3w.fr-pb-8w{ id: "tabpanel-template#{@count}-panel", role: "tabpanel", "aria-labelledby": "tabpanel-template", tabindex: "0" }
|
||||
= form_with url: download_export_path, namespace: "export_template_#{@count}", data: { turbo_method: :post, turbo: true } do |f|
|
||||
= f.hidden_field :statut, value: @statut
|
||||
.fr-select-group
|
||||
- if export_templates.present?
|
||||
%label.fr-label{ for: 'select' }
|
||||
Sélectionner le modèle d'export
|
||||
= f.collection_select :export_template_id, export_templates, :id, :name, {}, { class: "fr-select fr-mb-2w" }
|
||||
- else
|
||||
%p
|
||||
%i Aucun modèle configuré
|
||||
%p
|
||||
= link_to "Configurer les modèles d'export", exports_instructeur_procedure_path(procedure_id: params[:procedure_id]), class: 'fr-link'
|
||||
%ul.fr-btns-group.fr-btns-group--sm.fr-btns-group--inline
|
||||
%li
|
||||
%button.fr-btn.fr-btn--secondary{ type: 'button', "data-action": "click->menu-button#close" } Annuler
|
||||
%li
|
||||
= f.submit "Demander l'export", "data-action": "click->menu-button#close", class: 'fr-btn'
|
||||
|
|
45
app/components/export_template/champs_component.rb
Normal file
45
app/components/export_template/champs_component.rb
Normal file
|
@ -0,0 +1,45 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class ExportTemplate::ChampsComponent < ApplicationComponent
|
||||
attr_reader :export_template, :title
|
||||
|
||||
def initialize(title, export_template, types_de_champ)
|
||||
@title = title
|
||||
@export_template = export_template
|
||||
@types_de_champ = types_de_champ
|
||||
end
|
||||
|
||||
def historical_libelle(column)
|
||||
historical_exported_column = export_template.exported_columns.find { _1.column == column }
|
||||
if historical_exported_column
|
||||
historical_exported_column.libelle
|
||||
else
|
||||
column.label
|
||||
end
|
||||
end
|
||||
|
||||
def sections
|
||||
@types_de_champ
|
||||
.reject { _1.header_section? && _1.header_section_level_value > 1 }
|
||||
.slice_before(&:header_section?)
|
||||
.filter_map do |(head, *rest)|
|
||||
libelle = head.libelle if head.header_section?
|
||||
columns = [head.header_section? ? nil : head, *rest].compact.map { tdc_to_columns(_1) }
|
||||
{ libelle:, columns: } if columns.present?
|
||||
end
|
||||
end
|
||||
|
||||
def component_prefix
|
||||
title.parameterize
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def tdc_to_columns(type_de_champ)
|
||||
prefix = type_de_champ.repetition? ? "Bloc répétable" : nil
|
||||
type_de_champ.columns(procedure: export_template.procedure, prefix:).map do |column|
|
||||
ExportedColumn.new(column:,
|
||||
libelle: historical_libelle(column))
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,23 @@
|
|||
%fieldset.fr-fieldset{ id: "#{component_prefix}-fieldset", data: { controller: 'checkbox-select-all' } }
|
||||
%legend.fr-fieldset__legend--regular.fr-fieldset__legend.fr-h5.fr-pb-0
|
||||
= title
|
||||
.checkbox-group-bordered.fr-mx-1w.fr-mb-2w
|
||||
.fr-fieldset__element.fr-background-contrast--grey.fr-py-2w.fr-px-4w
|
||||
.fr-checkbox-group
|
||||
= check_box_tag "#{component_prefix}-select-all", "select-all", false, data: { "checkbox-select-all-target": 'checkboxAll' }
|
||||
= label_tag "#{component_prefix}-select-all", "Tout sélectionner"
|
||||
- sections.each.with_index do |section, idx|
|
||||
- if section[:libelle]
|
||||
.fr-fieldset__element.fr-text--bold.fr-px-4w{ class: idx > 0 ? "fr-pt-1w" : "" }= section[:libelle]
|
||||
|
||||
- section[:columns].each do |grouped_columns|
|
||||
- if grouped_columns.many?
|
||||
.fr-fieldset__element
|
||||
.fieldset-bordered.fr-ml-3v
|
||||
- grouped_columns.each do |exported_column|
|
||||
.fr-fieldset__element.fr-px-3v
|
||||
.fr-checkbox-group= render ExportTemplate::CheckboxComponent.new(export_template:, exported_column:)
|
||||
- else
|
||||
- grouped_columns.each do |exported_column|
|
||||
.fr-fieldset__element.fr-px-4w
|
||||
.fr-checkbox-group= render ExportTemplate::CheckboxComponent.new(export_template:, exported_column:)
|
32
app/components/export_template/checkbox_component.rb
Normal file
32
app/components/export_template/checkbox_component.rb
Normal file
|
@ -0,0 +1,32 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class ExportTemplate::CheckboxComponent < ApplicationComponent
|
||||
attr_reader :exported_column, :export_template
|
||||
|
||||
def initialize(export_template:, exported_column:)
|
||||
@export_template = export_template
|
||||
@exported_column = exported_column
|
||||
end
|
||||
|
||||
def call
|
||||
safe_join([
|
||||
check_box,
|
||||
label_tag(label_id, exported_column.libelle)
|
||||
])
|
||||
end
|
||||
|
||||
def check_box
|
||||
check_box_tag(
|
||||
'export_template[exported_columns][]',
|
||||
exported_column.id,
|
||||
export_template.in_export?(exported_column),
|
||||
class: 'fr-checkbox',
|
||||
id: sanitize_to_id(label_id), # sanitize_to_id is used by rails in label_tag
|
||||
data: { "checkbox-select-all-target": 'checkbox' }
|
||||
)
|
||||
end
|
||||
|
||||
def label_id
|
||||
exported_column.column.id
|
||||
end
|
||||
end
|
|
@ -11,7 +11,7 @@ class Instructeurs::ColumnTableHeaderComponent < ApplicationComponent
|
|||
|
||||
def classname(column)
|
||||
return 'status-col' if column.dossier_state?
|
||||
return 'number-col' if column.type == :number
|
||||
return 'number-col' if column.dossier_id?
|
||||
return 'sva-col' if column.column == 'sva_svr_decision_on'
|
||||
end
|
||||
|
||||
|
|
|
@ -5,9 +5,10 @@ module Instructeurs
|
|||
before_action :set_procedure_and_groupe_instructeurs
|
||||
before_action :set_export_template, only: [:edit, :update, :destroy]
|
||||
before_action :ensure_legitimate_groupe_instructeur, only: [:create, :update]
|
||||
before_action :set_types_de_champ, only: [:new, :edit]
|
||||
|
||||
def new
|
||||
@export_template = ExportTemplate.default(groupe_instructeur: @groupe_instructeurs.first)
|
||||
@export_template = export_template
|
||||
end
|
||||
|
||||
def create
|
||||
|
@ -49,9 +50,29 @@ module Instructeurs
|
|||
|
||||
private
|
||||
|
||||
def export_template = @export_template ||= ExportTemplate.default(groupe_instructeur: @groupe_instructeurs.first, kind:)
|
||||
|
||||
def kind = params[:kind] == 'zip' ? 'zip' : 'xlsx'
|
||||
|
||||
def set_types_de_champ
|
||||
if export_template.tabular?
|
||||
@types_de_champ_public = @procedure.all_revisions_types_de_champ(parent: nil, with_header_section: true).public_only
|
||||
@types_de_champ_private = @procedure.all_revisions_types_de_champ(parent: nil, with_header_section: true).private_only
|
||||
end
|
||||
end
|
||||
|
||||
def export_template_params
|
||||
params.require(:export_template)
|
||||
.permit(:name, :kind, :groupe_instructeur_id, dossier_folder: [:enabled, :template], export_pdf: [:enabled, :template], pjs: [:stable_id, :enabled, :template])
|
||||
params
|
||||
.require(:export_template)
|
||||
.permit(
|
||||
:name,
|
||||
:kind,
|
||||
:groupe_instructeur_id,
|
||||
dossier_folder: [:enabled, :template],
|
||||
export_pdf: [:enabled, :template],
|
||||
pjs: [:stable_id, :enabled, :template],
|
||||
exported_columns: []
|
||||
)
|
||||
end
|
||||
|
||||
def set_procedure_and_groupe_instructeurs
|
||||
|
|
9
app/helpers/export_template_helper.rb
Normal file
9
app/helpers/export_template_helper.rb
Normal file
|
@ -0,0 +1,9 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module ExportTemplateHelper
|
||||
def pretty_kind(kind)
|
||||
icon = kind == 'zip' ? 'archive' : 'table'
|
||||
pretty = tag.span nil, class: "fr-icon-#{icon}-line fr-mr-1v"
|
||||
pretty + kind.upcase
|
||||
end
|
||||
end
|
71
app/javascript/controllers/checkbox_select_all_controller.ts
Normal file
71
app/javascript/controllers/checkbox_select_all_controller.ts
Normal file
|
@ -0,0 +1,71 @@
|
|||
import { ApplicationController } from './application_controller';
|
||||
|
||||
export class CheckboxSelectAll extends ApplicationController {
|
||||
declare readonly hasCheckboxAllTarget: boolean;
|
||||
declare readonly checkboxTargets: HTMLInputElement[];
|
||||
declare readonly checkboxAllTarget: HTMLInputElement;
|
||||
|
||||
static targets: string[] = ['checkboxAll', 'checkbox'];
|
||||
|
||||
initialize() {
|
||||
this.toggle = this.toggle.bind(this);
|
||||
this.refresh = this.refresh.bind(this);
|
||||
}
|
||||
|
||||
checkboxAllTargetConnected(checkbox: HTMLInputElement): void {
|
||||
checkbox.addEventListener('change', this.toggle);
|
||||
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
checkboxTargetConnected(checkbox: HTMLInputElement): void {
|
||||
checkbox.addEventListener('change', this.refresh);
|
||||
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
checkboxAllTargetDisconnected(checkbox: HTMLInputElement): void {
|
||||
checkbox.removeEventListener('change', this.toggle);
|
||||
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
checkboxTargetDisconnected(checkbox: HTMLInputElement): void {
|
||||
checkbox.removeEventListener('change', this.refresh);
|
||||
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
toggle(e: Event): void {
|
||||
e.preventDefault();
|
||||
|
||||
this.checkboxTargets.forEach((checkbox) => {
|
||||
// @ts-expect-error faut savoir hein
|
||||
checkbox.checked = e.target.checked;
|
||||
this.triggerInputEvent(checkbox);
|
||||
});
|
||||
}
|
||||
|
||||
refresh(): void {
|
||||
const checkboxesCount = this.checkboxTargets.length;
|
||||
const checkboxesCheckedCount = this.checked.length;
|
||||
|
||||
this.checkboxAllTarget.checked = checkboxesCheckedCount > 0;
|
||||
this.checkboxAllTarget.indeterminate =
|
||||
checkboxesCheckedCount > 0 && checkboxesCheckedCount < checkboxesCount;
|
||||
}
|
||||
|
||||
triggerInputEvent(checkbox: HTMLInputElement): void {
|
||||
const event = new Event('input', { bubbles: false, cancelable: true });
|
||||
|
||||
checkbox.dispatchEvent(event);
|
||||
}
|
||||
|
||||
get checked(): HTMLInputElement[] {
|
||||
return this.checkboxTargets.filter((checkbox) => checkbox.checked);
|
||||
}
|
||||
|
||||
get unchecked(): HTMLInputElement[] {
|
||||
return this.checkboxTargets.filter((checkbox) => !checkbox.checked);
|
||||
}
|
||||
}
|
|
@ -61,6 +61,12 @@ export class MenuButtonController extends ApplicationController {
|
|||
});
|
||||
}
|
||||
|
||||
close() {
|
||||
this.buttonTarget.setAttribute('aria-expanded', 'false');
|
||||
this.menuTarget.parentElement?.classList.remove('open');
|
||||
this.setFocusToMenuitem(null);
|
||||
}
|
||||
|
||||
private open(focusMenuItem: 'first' | 'last' = 'first') {
|
||||
this.buttonTarget.setAttribute('aria-expanded', 'true');
|
||||
this.menuTarget.parentElement?.classList.add('open');
|
||||
|
@ -75,12 +81,6 @@ export class MenuButtonController extends ApplicationController {
|
|||
});
|
||||
}
|
||||
|
||||
private close() {
|
||||
this.buttonTarget.setAttribute('aria-expanded', 'false');
|
||||
this.menuTarget.parentElement?.classList.remove('open');
|
||||
this.setFocusToMenuitem(null);
|
||||
}
|
||||
|
||||
private isClickOutside(target: HTMLElement) {
|
||||
return (
|
||||
target.isConnected &&
|
||||
|
|
|
@ -40,11 +40,11 @@ class Champs::RepetitionChamp < Champ
|
|||
self[attribute]
|
||||
end
|
||||
|
||||
def spreadsheet_columns(types_de_champ)
|
||||
def spreadsheet_columns(types_de_champ, export_template: nil, format:)
|
||||
[
|
||||
['Dossier ID', :dossier_id],
|
||||
['Ligne', :index]
|
||||
] + dossier.champs_for_export(types_de_champ, row_id)
|
||||
] + dossier.champ_values_for_export(types_de_champ, row_id:, export_template:, format:)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -32,6 +32,7 @@ class Column
|
|||
def ==(other) = h_id == other.h_id # using h_id instead of id to avoid inversion of keys
|
||||
|
||||
def notifications? = [table, column] == ['notifications', 'notifications']
|
||||
def dossier_id? = [table, column] == ['self', 'id']
|
||||
def dossier_state? = [table, column] == ['self', 'state']
|
||||
def groupe_instructeur? = [table, column] == ['groupe_instructeur', 'id']
|
||||
def dossier_labels? = [table, column] == ['dossier_labels', 'label_id']
|
||||
|
@ -47,6 +48,9 @@ class Column
|
|||
procedure.find_column(h_id: h_id)
|
||||
end
|
||||
|
||||
def dossier_column? = false
|
||||
def champ_column? = false
|
||||
|
||||
private
|
||||
|
||||
def column_id = "#{table}/#{column}"
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Columns::ChampColumn < Column
|
||||
attr_reader :stable_id
|
||||
attr_reader :stable_id, :tdc_type
|
||||
|
||||
def initialize(procedure_id:, label:, stable_id:, tdc_type:, displayable: true, filterable: true, type: :text, options_for_select: [])
|
||||
@stable_id = stable_id
|
||||
|
@ -45,6 +45,8 @@ class Columns::ChampColumn < Column
|
|||
end
|
||||
end
|
||||
|
||||
def champ_column? = true
|
||||
|
||||
private
|
||||
|
||||
def column_id = "type_de_champ/#{stable_id}"
|
||||
|
|
|
@ -15,4 +15,6 @@ class Columns::DossierColumn < Column
|
|||
dossier.followers_instructeurs.map(&:email).join(' ')
|
||||
end
|
||||
end
|
||||
|
||||
def dossier_column? = true
|
||||
end
|
||||
|
|
|
@ -46,10 +46,10 @@ class Columns::LinkedDropDownColumn < Columns::ChampColumn
|
|||
def column_id = "type_de_champ/#{stable_id}->#{path}"
|
||||
|
||||
def typed_value(champ)
|
||||
return nil if path == :value
|
||||
|
||||
primary_value, secondary_value = unpack_values(champ.value)
|
||||
case path
|
||||
when :value
|
||||
"#{primary_value} / #{secondary_value}"
|
||||
when :primary
|
||||
primary_value
|
||||
when :secondary
|
||||
|
|
9
app/models/columns/piece_justificative_column.rb
Normal file
9
app/models/columns/piece_justificative_column.rb
Normal file
|
@ -0,0 +1,9 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Columns::PieceJustificativeColumn < Column
|
||||
private
|
||||
|
||||
def typed_value(champ)
|
||||
champ.piece_justificative_file.map { _1.blob.filename.to_s }.join(', ')
|
||||
end
|
||||
end
|
|
@ -51,7 +51,7 @@ module ColumnsConcern
|
|||
columns.filter { _1.id.in?(self.columns.map(&:id)) }
|
||||
end
|
||||
|
||||
def dossier_id_column = dossier_col(table: 'self', column: 'id', type: :number)
|
||||
def dossier_id_column = dossier_col(table: 'self', column: 'id', type: :integer)
|
||||
|
||||
def dossier_state_column
|
||||
options_for_select = I18n.t('instructeurs.dossiers.filterable_state').map(&:to_a).map(&:reverse)
|
||||
|
@ -87,13 +87,13 @@ module ColumnsConcern
|
|||
|
||||
def followers_instructeurs_email_column = dossier_col(table: 'followers_instructeurs', column: 'email')
|
||||
|
||||
def dossier_archived_column = dossier_col(table: 'self', column: 'archived', type: :text, displayable: false, filterable: false);
|
||||
def dossier_archived_column = dossier_col(table: 'self', column: 'archived', type: :boolean, displayable: false, filterable: false);
|
||||
|
||||
def dossier_motivation_column = dossier_col(table: 'self', column: 'motivation', type: :text, displayable: false, filterable: false);
|
||||
|
||||
def user_email_for_display_column = dossier_col(table: 'self', column: 'user_email_for_display', filterable: false, displayable: false)
|
||||
|
||||
def user_france_connected_column = dossier_col(table: 'self', column: 'user_from_france_connect?', filterable: false, displayable: false)
|
||||
def user_france_connected_column = dossier_col(table: 'self', column: 'user_from_france_connect?', type: :boolean, filterable: false, displayable: false)
|
||||
|
||||
def dossier_labels_column = dossier_col(table: 'dossier_labels', column: 'label_id', type: :enum, options_for_select: labels.map { [_1.name, _1.id] })
|
||||
|
||||
|
@ -109,7 +109,7 @@ module ColumnsConcern
|
|||
|
||||
def dossier_dates_columns
|
||||
['created_at', 'updated_at', 'last_champ_updated_at', 'depose_at', 'en_construction_at', 'en_instruction_at', 'processed_at']
|
||||
.map { |column| dossier_col(table: 'self', column:, type: :date) }
|
||||
.map { |column| dossier_col(table: 'self', column:, type: :datetime) }
|
||||
end
|
||||
|
||||
def email_column
|
||||
|
@ -140,7 +140,8 @@ module ColumnsConcern
|
|||
|
||||
def individual_columns
|
||||
['gender', 'nom', 'prenom'].map { |column| dossier_col(table: 'individual', column:) }
|
||||
.concat ['for_tiers', 'mandataire_last_name', 'mandataire_first_name'].map { |column| dossier_col(table: 'self', column:) }
|
||||
.concat ['mandataire_last_name', 'mandataire_first_name'].map { |column| dossier_col(table: 'self', column:) }
|
||||
.concat ['for_tiers'].map { |column| dossier_col(table: 'self', column:, type: :boolean) }
|
||||
end
|
||||
|
||||
def moral_columns
|
||||
|
|
|
@ -82,15 +82,6 @@ module DossierChampsConcern
|
|||
.map { _1.repetition? ? project_champ(_1, nil) : champ_for_update(_1, nil, updated_by: nil) }
|
||||
end
|
||||
|
||||
def champs_for_export(types_de_champ, row_id = nil)
|
||||
types_de_champ.flat_map do |type_de_champ|
|
||||
champ = filled_champ(type_de_champ, row_id)
|
||||
type_de_champ.libelles_for_export.map do |(libelle, path)|
|
||||
[libelle, type_de_champ.champ_value_for_export(champ, path)]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def champ_value_for_tag(type_de_champ, path = :value)
|
||||
champ = filled_champ(type_de_champ, nil)
|
||||
type_de_champ.champ_value_for_tag(champ, path)
|
||||
|
|
125
app/models/concerns/dossier_export_concern.rb
Normal file
125
app/models/concerns/dossier_export_concern.rb
Normal file
|
@ -0,0 +1,125 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module DossierExportConcern
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
def spreadsheet_columns_csv(types_de_champ:, export_template: nil)
|
||||
spreadsheet_columns(with_etablissement: true, types_de_champ:, export_template:, format: :csv)
|
||||
end
|
||||
|
||||
def spreadsheet_columns_xlsx(types_de_champ:, export_template: nil)
|
||||
spreadsheet_columns(types_de_champ:, export_template:, format: :xlsx)
|
||||
end
|
||||
|
||||
def spreadsheet_columns_ods(types_de_champ:, export_template: nil)
|
||||
spreadsheet_columns(types_de_champ:, export_template:, format: :ods)
|
||||
end
|
||||
|
||||
def champ_values_for_export(types_de_champ, row_id: nil, export_template: nil, format:)
|
||||
types_de_champ.flat_map do |type_de_champ|
|
||||
champ = filled_champ(type_de_champ, row_id)
|
||||
if export_template.present?
|
||||
export_template
|
||||
.columns_for_stable_id(type_de_champ.stable_id)
|
||||
.map { |exported_column| exported_column.libelle_with_value(champ, format:) }
|
||||
else
|
||||
type_de_champ.libelles_for_export.map do |(libelle, path)|
|
||||
[libelle, type_de_champ.champ_value_for_export(champ, path)]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def spreadsheet_columns(types_de_champ:, with_etablissement: false, export_template: nil, format: nil)
|
||||
dossier_values_for_export(with_etablissement:, export_template:, format:) + champ_values_for_export(types_de_champ, export_template:, format:)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def dossier_values_for_export(with_etablissement: false, export_template: nil, format:)
|
||||
if export_template.present?
|
||||
return export_template.dossier_exported_columns.map { _1.libelle_with_value(self, format:) }
|
||||
end
|
||||
|
||||
columns = [
|
||||
['ID', id.to_s],
|
||||
['Email', user_email_for(:display)],
|
||||
['FranceConnect ?', user_from_france_connect?]
|
||||
]
|
||||
|
||||
if procedure.for_individual?
|
||||
columns += [
|
||||
['Civilité', individual&.gender],
|
||||
['Nom', individual&.nom],
|
||||
['Prénom', individual&.prenom],
|
||||
['Dépôt pour un tiers', :for_tiers],
|
||||
['Nom du mandataire', :mandataire_last_name],
|
||||
['Prénom du mandataire', :mandataire_first_name]
|
||||
]
|
||||
if procedure.ask_birthday
|
||||
columns += [['Date de naissance', individual&.birthdate]]
|
||||
end
|
||||
elsif with_etablissement
|
||||
columns += [
|
||||
['Établissement SIRET', etablissement&.siret],
|
||||
['Établissement siège social', etablissement&.siege_social],
|
||||
['Établissement NAF', etablissement&.naf],
|
||||
['Établissement libellé NAF', etablissement&.libelle_naf],
|
||||
['Établissement Adresse', etablissement&.adresse],
|
||||
['Établissement numero voie', etablissement&.numero_voie],
|
||||
['Établissement type voie', etablissement&.type_voie],
|
||||
['Établissement nom voie', etablissement&.nom_voie],
|
||||
['Établissement complément adresse', etablissement&.complement_adresse],
|
||||
['Établissement code postal', etablissement&.code_postal],
|
||||
['Établissement localité', etablissement&.localite],
|
||||
['Établissement code INSEE localité', etablissement&.code_insee_localite],
|
||||
['Entreprise SIREN', etablissement&.entreprise_siren],
|
||||
['Entreprise capital social', etablissement&.entreprise_capital_social],
|
||||
['Entreprise numero TVA intracommunautaire', etablissement&.entreprise_numero_tva_intracommunautaire],
|
||||
['Entreprise forme juridique', etablissement&.entreprise_forme_juridique],
|
||||
['Entreprise forme juridique code', etablissement&.entreprise_forme_juridique_code],
|
||||
['Entreprise nom commercial', etablissement&.entreprise_nom_commercial],
|
||||
['Entreprise raison sociale', etablissement&.entreprise_raison_sociale],
|
||||
['Entreprise SIRET siège social', etablissement&.entreprise_siret_siege_social],
|
||||
['Entreprise code effectif entreprise', etablissement&.entreprise_code_effectif_entreprise],
|
||||
['Entreprise date de création', etablissement&.entreprise_date_creation],
|
||||
['Entreprise état administratif', etablissement&.entreprise_etat_administratif],
|
||||
['Entreprise nom', etablissement&.entreprise_nom],
|
||||
['Entreprise prénom', etablissement&.entreprise_prenom],
|
||||
['Association RNA', etablissement&.association_rna],
|
||||
['Association titre', etablissement&.association_titre],
|
||||
['Association objet', etablissement&.association_objet],
|
||||
['Association date de création', etablissement&.association_date_creation],
|
||||
['Association date de déclaration', etablissement&.association_date_declaration],
|
||||
['Association date de publication', etablissement&.association_date_publication]
|
||||
]
|
||||
else
|
||||
columns << ['Entreprise raison sociale', etablissement&.entreprise_raison_sociale]
|
||||
end
|
||||
if procedure.chorusable? && procedure.chorus_configuration.complete?
|
||||
columns += [
|
||||
['Domaine Fonctionnel', procedure.chorus_configuration.domaine_fonctionnel&.fetch("code") { '' }],
|
||||
['Référentiel De Programmation', procedure.chorus_configuration.referentiel_de_programmation&.fetch("code") { '' }],
|
||||
['Centre De Coût', procedure.chorus_configuration.centre_de_cout&.fetch("code") { '' }]
|
||||
]
|
||||
end
|
||||
columns += [
|
||||
['Archivé', :archived],
|
||||
['État du dossier', Dossier.human_attribute_name("state.#{state}")],
|
||||
['Dernière mise à jour le', :updated_at],
|
||||
['Dernière mise à jour du dossier le', :last_champ_updated_at],
|
||||
['Déposé le', :depose_at],
|
||||
['Passé en instruction le', :en_instruction_at],
|
||||
procedure.sva_svr_enabled? ? ["Date décision #{procedure.sva_svr_configuration.human_decision}", :sva_svr_decision_on] : nil,
|
||||
['Traité le', :processed_at],
|
||||
['Motivation de la décision', :motivation],
|
||||
['Instructeurs', followers_instructeurs.map(&:email).join(' ')]
|
||||
].compact
|
||||
|
||||
if procedure.routing_enabled?
|
||||
columns << ['Groupe instructeur', groupe_instructeur.label]
|
||||
end
|
||||
|
||||
columns
|
||||
end
|
||||
end
|
|
@ -13,6 +13,7 @@ class Dossier < ApplicationRecord
|
|||
include DossierStateConcern
|
||||
include DossierChampsConcern
|
||||
include DossierEmptyConcern
|
||||
include DossierExportConcern
|
||||
|
||||
enum state: {
|
||||
brouillon: 'brouillon',
|
||||
|
@ -944,100 +945,6 @@ class Dossier < ApplicationRecord
|
|||
log_dossier_operation(avis.claimant, :demander_un_avis, avis)
|
||||
end
|
||||
|
||||
def spreadsheet_columns_csv(types_de_champ:)
|
||||
spreadsheet_columns(with_etablissement: true, types_de_champ: types_de_champ)
|
||||
end
|
||||
|
||||
def spreadsheet_columns_xlsx(types_de_champ:)
|
||||
spreadsheet_columns(types_de_champ: types_de_champ)
|
||||
end
|
||||
|
||||
def spreadsheet_columns_ods(types_de_champ:)
|
||||
spreadsheet_columns(types_de_champ: types_de_champ)
|
||||
end
|
||||
|
||||
def spreadsheet_columns(with_etablissement: false, types_de_champ:)
|
||||
columns = [
|
||||
['ID', id.to_s],
|
||||
['Email', user_email_for(:display)],
|
||||
['FranceConnect ?', user_from_france_connect?]
|
||||
]
|
||||
|
||||
if procedure.for_individual?
|
||||
columns += [
|
||||
['Civilité', individual&.gender],
|
||||
['Nom', individual&.nom],
|
||||
['Prénom', individual&.prenom],
|
||||
['Dépôt pour un tiers', :for_tiers],
|
||||
['Nom du mandataire', :mandataire_last_name],
|
||||
['Prénom du mandataire', :mandataire_first_name]
|
||||
]
|
||||
if procedure.ask_birthday
|
||||
columns += [['Date de naissance', individual&.birthdate]]
|
||||
end
|
||||
elsif with_etablissement
|
||||
columns += [
|
||||
['Établissement SIRET', etablissement&.siret],
|
||||
['Établissement siège social', etablissement&.siege_social],
|
||||
['Établissement NAF', etablissement&.naf],
|
||||
['Établissement libellé NAF', etablissement&.libelle_naf],
|
||||
['Établissement Adresse', etablissement&.adresse],
|
||||
['Établissement numero voie', etablissement&.numero_voie],
|
||||
['Établissement type voie', etablissement&.type_voie],
|
||||
['Établissement nom voie', etablissement&.nom_voie],
|
||||
['Établissement complément adresse', etablissement&.complement_adresse],
|
||||
['Établissement code postal', etablissement&.code_postal],
|
||||
['Établissement localité', etablissement&.localite],
|
||||
['Établissement code INSEE localité', etablissement&.code_insee_localite],
|
||||
['Entreprise SIREN', etablissement&.entreprise_siren],
|
||||
['Entreprise capital social', etablissement&.entreprise_capital_social],
|
||||
['Entreprise numero TVA intracommunautaire', etablissement&.entreprise_numero_tva_intracommunautaire],
|
||||
['Entreprise forme juridique', etablissement&.entreprise_forme_juridique],
|
||||
['Entreprise forme juridique code', etablissement&.entreprise_forme_juridique_code],
|
||||
['Entreprise nom commercial', etablissement&.entreprise_nom_commercial],
|
||||
['Entreprise raison sociale', etablissement&.entreprise_raison_sociale],
|
||||
['Entreprise SIRET siège social', etablissement&.entreprise_siret_siege_social],
|
||||
['Entreprise code effectif entreprise', etablissement&.entreprise_code_effectif_entreprise],
|
||||
['Entreprise date de création', etablissement&.entreprise_date_creation],
|
||||
['Entreprise état administratif', etablissement&.entreprise_etat_administratif],
|
||||
['Entreprise nom', etablissement&.entreprise_nom],
|
||||
['Entreprise prénom', etablissement&.entreprise_prenom],
|
||||
['Association RNA', etablissement&.association_rna],
|
||||
['Association titre', etablissement&.association_titre],
|
||||
['Association objet', etablissement&.association_objet],
|
||||
['Association date de création', etablissement&.association_date_creation],
|
||||
['Association date de déclaration', etablissement&.association_date_declaration],
|
||||
['Association date de publication', etablissement&.association_date_publication]
|
||||
]
|
||||
else
|
||||
columns << ['Entreprise raison sociale', etablissement&.entreprise_raison_sociale]
|
||||
end
|
||||
if procedure.chorusable? && procedure.chorus_configuration.complete?
|
||||
columns += [
|
||||
['Domaine Fonctionnel', procedure.chorus_configuration.domaine_fonctionnel&.fetch("code") { '' }],
|
||||
['Référentiel De Programmation', procedure.chorus_configuration.referentiel_de_programmation&.fetch("code") { '' }],
|
||||
['Centre De Coût', procedure.chorus_configuration.centre_de_cout&.fetch("code") { '' }]
|
||||
]
|
||||
end
|
||||
columns += [
|
||||
['Archivé', :archived],
|
||||
['État du dossier', Dossier.human_attribute_name("state.#{state}")],
|
||||
['Dernière mise à jour le', :updated_at],
|
||||
['Dernière mise à jour du dossier le', :last_champ_updated_at],
|
||||
['Déposé le', :depose_at],
|
||||
['Passé en instruction le', :en_instruction_at],
|
||||
procedure.sva_svr_enabled? ? ["Date décision #{procedure.sva_svr_configuration.human_decision}", :sva_svr_decision_on] : nil,
|
||||
['Traité le', :processed_at],
|
||||
['Motivation de la décision', :motivation],
|
||||
['Instructeurs', followers_instructeurs.map(&:email).join(' ')]
|
||||
].compact
|
||||
|
||||
if procedure.routing_enabled?
|
||||
columns << ['Groupe instructeur', groupe_instructeur.label]
|
||||
end
|
||||
columns + champs_for_export(types_de_champ)
|
||||
end
|
||||
|
||||
def linked_dossiers_for(instructeur_or_expert)
|
||||
dossier_ids = filled_champs.filter(&:dossier_link?).filter_map(&:value)
|
||||
instructeur_or_expert.dossiers.where(id: dossier_ids)
|
||||
|
|
|
@ -9,12 +9,14 @@ class ExportTemplate < ApplicationRecord
|
|||
has_one :procedure, through: :groupe_instructeur
|
||||
has_many :exports, dependent: :nullify
|
||||
|
||||
enum kind: { zip: "zip" }, _prefix: :template
|
||||
enum kind: { zip: 'zip', csv: 'csv', xlsx: 'xlsx', ods: 'ods' }, _prefix: :template
|
||||
|
||||
attribute :dossier_folder, :export_item
|
||||
attribute :export_pdf, :export_item
|
||||
attribute :pjs, :export_item, array: true
|
||||
|
||||
attribute :exported_columns, :exported_column, array: true
|
||||
|
||||
before_validation :ensure_pjs_are_legit
|
||||
|
||||
validates_with ExportTemplateValidator
|
||||
|
@ -28,6 +30,7 @@ class ExportTemplate < ApplicationRecord
|
|||
end
|
||||
|
||||
def self.default(name: nil, kind: 'zip', groupe_instructeur:)
|
||||
# TODO: remove default values for tabular export
|
||||
dossier_folder = ExportItem.default(prefix: 'dossier')
|
||||
export_pdf = ExportItem.default(prefix: 'export')
|
||||
pjs = groupe_instructeur.procedure.exportables_pieces_jointes.map { |tdc| ExportItem.default_pj(tdc) }
|
||||
|
@ -35,6 +38,10 @@ class ExportTemplate < ApplicationRecord
|
|||
new(name:, kind:, groupe_instructeur:, dossier_folder:, export_pdf:, pjs:)
|
||||
end
|
||||
|
||||
def tabular?
|
||||
kind != 'zip'
|
||||
end
|
||||
|
||||
def tags
|
||||
tags_categorized.slice(:individual, :etablissement, :dossier).values.flatten
|
||||
end
|
||||
|
@ -58,6 +65,19 @@ class ExportTemplate < ApplicationRecord
|
|||
File.join(dossier_folder.path(dossier), file_path) if file_path.present?
|
||||
end
|
||||
|
||||
def dossier_exported_columns = exported_columns.filter { _1.column.dossier_column? }
|
||||
|
||||
def columns_for_stable_id(stable_id)
|
||||
exported_columns
|
||||
.filter { _1.column.champ_column? }
|
||||
.filter { _1.column.stable_id == stable_id }
|
||||
end
|
||||
|
||||
def in_export?(exported_column)
|
||||
@template_exported_columns ||= exported_columns.map(&:column)
|
||||
@template_exported_columns.include?(exported_column.column)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def ensure_pjs_are_legit
|
||||
|
|
31
app/models/exported_column.rb
Normal file
31
app/models/exported_column.rb
Normal file
|
@ -0,0 +1,31 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class ExportedColumn
|
||||
attr_reader :column, :libelle
|
||||
|
||||
def initialize(column:, libelle:)
|
||||
@column = column
|
||||
@libelle = libelle
|
||||
end
|
||||
|
||||
def id = { id: column.id, libelle: }.to_json
|
||||
|
||||
def libelle_with_value(champ_or_dossier, format:)
|
||||
[libelle, ExportedColumnFormatter.format(column:, champ_or_dossier:, format:), spreadsheet_architect_type]
|
||||
end
|
||||
|
||||
def spreadsheet_architect_type
|
||||
case @column.type
|
||||
when :boolean
|
||||
:boolean
|
||||
when :decimal, :integer
|
||||
:float
|
||||
when :datetime
|
||||
:time
|
||||
when :date
|
||||
:date
|
||||
else
|
||||
:string
|
||||
end
|
||||
end
|
||||
end
|
|
@ -71,10 +71,11 @@ class Procedure < ApplicationRecord
|
|||
brouillon? ? draft_revision : published_revision
|
||||
end
|
||||
|
||||
def all_revisions_types_de_champ(parent: nil)
|
||||
def all_revisions_types_de_champ(parent: nil, with_header_section: false)
|
||||
types_de_champ_scope = with_header_section ? TypeDeChamp.with_header_section : TypeDeChamp.fillable
|
||||
if brouillon?
|
||||
if parent.nil?
|
||||
TypeDeChamp.fillable
|
||||
types_de_champ_scope
|
||||
.joins(:revision_types_de_champ)
|
||||
.where(revision_types_de_champ: { revision_id: draft_revision_id, parent_id: nil })
|
||||
.order(:private, :position)
|
||||
|
@ -82,8 +83,8 @@ class Procedure < ApplicationRecord
|
|||
draft_revision.children_of(parent)
|
||||
end
|
||||
else
|
||||
cache_key = ['all_revisions_types_de_champ', published_revision, parent].compact
|
||||
Rails.cache.fetch(cache_key, expires_in: 1.month) { published_revisions_types_de_champ(parent) }
|
||||
cache_key = ['all_revisions_types_de_champ', published_revision, parent, with_header_section].compact
|
||||
Rails.cache.fetch(cache_key, expires_in: 1.month) { published_revisions_types_de_champ(parent:, with_header_section:) }
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -875,7 +876,7 @@ class Procedure < ApplicationRecord
|
|||
@stable_ids_used_by_routing_rules ||= groupe_instructeurs.flat_map { _1.routing_rule&.sources }.compact.uniq
|
||||
end
|
||||
|
||||
def published_revisions_types_de_champ(parent = nil)
|
||||
def published_revisions_types_de_champ(parent: nil, with_header_section: false)
|
||||
# all published revisions
|
||||
revision_ids = revisions.ids - [draft_revision_id]
|
||||
# fetch all parent types de champ
|
||||
|
@ -889,8 +890,8 @@ class Procedure < ApplicationRecord
|
|||
|
||||
# fetch all type_de_champ.stable_id for all the revisions expect draft
|
||||
# and for each stable_id take the bigger (more recent) type_de_champ.id
|
||||
recent_ids = TypeDeChamp
|
||||
.fillable
|
||||
types_de_champ_scope = with_header_section ? TypeDeChamp.with_header_section : TypeDeChamp.fillable
|
||||
recent_ids = types_de_champ_scope
|
||||
.joins(:revision_types_de_champ)
|
||||
.where(revision_types_de_champ: { revision_id: revision_ids, parent_id: parent_ids })
|
||||
.group(:stable_id).select('MAX(types_de_champ.id)')
|
||||
|
|
|
@ -24,7 +24,6 @@ class TypeDeChamp < ApplicationRecord
|
|||
|
||||
TYPE_DE_CHAMP_TO_CATEGORIE = {
|
||||
engagement_juridique: REFERENTIEL_EXTERNE,
|
||||
|
||||
header_section: STRUCTURE,
|
||||
repetition: STRUCTURE,
|
||||
dossier_link: STRUCTURE,
|
||||
|
@ -171,6 +170,7 @@ class TypeDeChamp < ApplicationRecord
|
|||
scope :not_repetition, -> { where.not(type_champ: type_champs.fetch(:repetition)) }
|
||||
scope :not_condition, -> { where(condition: nil) }
|
||||
scope :fillable, -> { where.not(type_champ: [type_champs.fetch(:header_section), type_champs.fetch(:explication)]) }
|
||||
scope :with_header_section, -> { where.not(type_champ: TypeDeChamp.type_champs[:explication]) }
|
||||
|
||||
scope :dubious, -> {
|
||||
where("unaccent(types_de_champ.libelle) ~* unaccent(?)", DubiousProcedure.forbidden_regexp)
|
||||
|
|
|
@ -27,6 +27,31 @@ class TypesDeChamp::CommuneTypeDeChamp < TypesDeChamp::TypeDeChampBase
|
|||
champ.code_postal? ? "#{champ.name} (#{champ.code_postal})" : champ.name
|
||||
end
|
||||
|
||||
def columns(procedure:, displayable: true, prefix: nil)
|
||||
super.concat(
|
||||
[
|
||||
Columns::JSONPathColumn.new(
|
||||
procedure_id: procedure.id,
|
||||
stable_id:,
|
||||
tdc_type: type_champ,
|
||||
label: "#{libelle_with_prefix(prefix)} - code postal (5 chiffres)",
|
||||
jsonpath: '$.code_postal',
|
||||
displayable:,
|
||||
type: :text
|
||||
),
|
||||
Columns::JSONPathColumn.new(
|
||||
procedure_id: procedure.id,
|
||||
stable_id:,
|
||||
tdc_type: type_champ,
|
||||
label: "#{libelle_with_prefix(prefix)} - département",
|
||||
jsonpath: '$.code_departement',
|
||||
displayable:,
|
||||
type: :number
|
||||
)
|
||||
]
|
||||
)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def paths
|
||||
|
|
|
@ -26,9 +26,11 @@ class TypesDeChamp::RepetitionTypeDeChamp < TypesDeChamp::TypeDeChampBase
|
|||
end
|
||||
|
||||
def columns(procedure:, displayable: nil, prefix: nil)
|
||||
prefix = prefix.present? ? "(#{prefix} #{libelle})" : libelle
|
||||
|
||||
procedure
|
||||
.all_revisions_types_de_champ(parent: @type_de_champ)
|
||||
.flat_map { _1.columns(procedure:, displayable: false, prefix: libelle) }
|
||||
.flat_map { _1.columns(procedure:, displayable: false, prefix:) }
|
||||
end
|
||||
|
||||
def champ_blank?(champ) = champ.dossier.repetition_row_ids(@type_de_champ).blank?
|
||||
|
|
|
@ -81,7 +81,7 @@ class DossierFilterService
|
|||
else
|
||||
case table
|
||||
when 'self'
|
||||
if filtered_column.type == :date
|
||||
if filtered_column.type == :date || filtered_column.type == :datetime
|
||||
dates = values
|
||||
.filter_map { |v| Time.zone.parse(v).beginning_of_day rescue nil }
|
||||
|
||||
|
|
52
app/services/exported_column_formatter.rb
Normal file
52
app/services/exported_column_formatter.rb
Normal file
|
@ -0,0 +1,52 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class ExportedColumnFormatter
|
||||
def self.format(column:, champ_or_dossier:, format:)
|
||||
return if champ_or_dossier.nil?
|
||||
|
||||
raw_value = column.value(champ_or_dossier)
|
||||
|
||||
case column.type
|
||||
when :boolean
|
||||
format_boolean(column:, raw_value:, format:)
|
||||
when :attachements
|
||||
format_attachments(column:, raw_value:)
|
||||
when :enum
|
||||
format_enum(column:, raw_value:)
|
||||
when :enums
|
||||
format_enums(column:, raw_values: raw_value)
|
||||
else
|
||||
raw_value
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def self.format_boolean(column:, raw_value:, format:)
|
||||
if format == :ods
|
||||
raw_value ? 1 : 0
|
||||
else
|
||||
raw_value
|
||||
end
|
||||
end
|
||||
|
||||
def self.format_attachments(column:, raw_value:)
|
||||
case column.tdc_type
|
||||
when TypeDeChamp.type_champs[:titre_identite]
|
||||
raw_value.present? ? 'présent' : 'absent'
|
||||
when TypeDeChamp.type_champs[:piece_justificative]
|
||||
raw_value.map { _1.blob.filename }.join(", ")
|
||||
end
|
||||
end
|
||||
|
||||
def self.format_enums(column:, raw_values:)
|
||||
raw_values.map { format_enum(column:, raw_value: _1) }.join(', ')
|
||||
end
|
||||
|
||||
def self.format_enum(column:, raw_value:)
|
||||
# options for select store ["trad", :enum_value]
|
||||
selected_option = column.options_for_select.find { _1[1].to_s == raw_value }
|
||||
|
||||
selected_option ? selected_option.first : raw_value
|
||||
end
|
||||
end
|
|
@ -18,7 +18,7 @@ class ProcedureExportService
|
|||
|
||||
def to_xlsx
|
||||
@dossiers = @dossiers.downloadable_sorted_batch
|
||||
tables = [:dossiers, :etablissements, :avis] + champs_repetables_options
|
||||
tables = [:dossiers, :etablissements, :avis] + champs_repetables_options(format: :xlsx)
|
||||
|
||||
# We recursively build multi page spreadsheet
|
||||
io = tables.reduce(nil) do |package, table|
|
||||
|
@ -29,7 +29,7 @@ class ProcedureExportService
|
|||
|
||||
def to_ods
|
||||
@dossiers = @dossiers.downloadable_sorted_batch
|
||||
tables = [:dossiers, :etablissements, :avis] + champs_repetables_options
|
||||
tables = [:dossiers, :etablissements, :avis] + champs_repetables_options(format: :ods)
|
||||
|
||||
# We recursively build multi page spreadsheet
|
||||
io = StringIO.new(tables.reduce(nil) do |spreadsheet, table|
|
||||
|
@ -103,7 +103,7 @@ class ProcedureExportService
|
|||
@avis ||= dossiers.flat_map(&:avis)
|
||||
end
|
||||
|
||||
def champs_repetables_options
|
||||
def champs_repetables_options(format:)
|
||||
procedure
|
||||
.all_revisions_types_de_champ
|
||||
.repetition
|
||||
|
@ -115,7 +115,7 @@ class ProcedureExportService
|
|||
{
|
||||
sheet_name: type_de_champ_repetition.libelle_for_export,
|
||||
instances: rows,
|
||||
spreadsheet_columns: Proc.new { |instance| instance.spreadsheet_columns(types_de_champ) }
|
||||
spreadsheet_columns: Proc.new { |instance| instance.spreadsheet_columns(types_de_champ, export_template: @export_template, format:) }
|
||||
}
|
||||
end
|
||||
end
|
||||
|
@ -152,7 +152,7 @@ class ProcedureExportService
|
|||
types_de_champ = procedure.types_de_champ_for_procedure_export.to_a
|
||||
|
||||
Proc.new do |instance|
|
||||
instance.send(:"spreadsheet_columns_#{format}", types_de_champ: types_de_champ)
|
||||
instance.send(:"spreadsheet_columns_#{format}", types_de_champ: types_de_champ, export_template: @export_template)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -29,7 +29,10 @@ class ExportItemType < ActiveRecord::Type::Value
|
|||
|
||||
# ruby -> db
|
||||
def serialize(value)
|
||||
if value.is_a?(ExportItem)
|
||||
case value
|
||||
in NilClass
|
||||
nil
|
||||
in ExportItem
|
||||
JSON.generate({
|
||||
template: value.template,
|
||||
enabled: value.enabled,
|
||||
|
|
40
app/types/exported_column_type.rb
Normal file
40
app/types/exported_column_type.rb
Normal file
|
@ -0,0 +1,40 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class ExportedColumnType < ActiveRecord::Type::Value
|
||||
# form_input or setter -> type
|
||||
def cast(value)
|
||||
value = value.deep_symbolize_keys if value.respond_to?(:deep_symbolize_keys)
|
||||
|
||||
case value
|
||||
in ExportedColumn
|
||||
value
|
||||
in NilClass # default value
|
||||
nil
|
||||
# from db
|
||||
in { id: String|Hash, libelle: String } => h
|
||||
ExportedColumn.new(column: ColumnType.new.cast(h[:id]), libelle: h[:libelle])
|
||||
# from form
|
||||
in String
|
||||
h = JSON.parse(value).deep_symbolize_keys
|
||||
ExportedColumn.new(column: ColumnType.new.cast(h[:id]), libelle: h[:libelle])
|
||||
end
|
||||
end
|
||||
|
||||
# db -> ruby
|
||||
def deserialize(value) = cast(value&.then { JSON.parse(_1) })
|
||||
|
||||
# ruby -> db
|
||||
def serialize(value)
|
||||
case value
|
||||
in NilClass
|
||||
nil
|
||||
in ExportedColumn
|
||||
JSON.generate({
|
||||
id: value.column.h_id,
|
||||
libelle: value.libelle
|
||||
})
|
||||
else
|
||||
raise ArgumentError, "Invalid value for ExportedColumn serialization: #{value}"
|
||||
end
|
||||
end
|
||||
end
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
class ExportTemplateValidator < ActiveModel::Validator
|
||||
def validate(export_template)
|
||||
return if !export_template.template_zip?
|
||||
|
||||
validate_all_templates(export_template)
|
||||
|
||||
return if export_template.errors.any? # no need to continue if the templates are invalid
|
||||
|
|
|
@ -4,11 +4,12 @@
|
|||
['Export et Archives']] }
|
||||
|
||||
|
||||
.container
|
||||
%h1.mb-2
|
||||
.container.flex
|
||||
%h1.mb-2.mr-2
|
||||
Archives
|
||||
-# index not renderable as administrateur flagged as manager, so render it anyway
|
||||
= render Dossiers::ExportDropdownComponent.new(procedure: @procedure, export_url: method(:download_admin_procedure_exports_path))
|
||||
= render Dossiers::ExportDropdownComponent.new(procedure: @procedure, export_url: method(:download_admin_procedure_exports_path), show_export_template_tab: false)
|
||||
.container
|
||||
= render Dossiers::ExportLinkComponent.new(procedure: @procedure, exports: @exports, export_url: method(:download_admin_procedure_exports_path))
|
||||
|
||||
= render partial: "shared/archives/notice"
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
%fieldset.fr-fieldset{ id: "#{title.parameterize}-fieldset", data: { controller: 'checkbox-select-all' } }
|
||||
%legend.fr-fieldset__legend--regular.fr-fieldset__legend.fr-h5.fr-pb-0
|
||||
= title
|
||||
|
||||
.checkbox-group-bordered.fr-mx-1w.fr-mb-2w
|
||||
.fr-fieldset__element.fr-background-contrast--grey.fr-py-2w.fr-px-4w
|
||||
.fr-checkbox-group
|
||||
= check_box_tag "#{title.parameterize}-select-all", "select-all", false, data: { "checkbox-select-all-target": 'checkboxAll' }
|
||||
= label_tag "#{title.parameterize}-select-all", "Tout sélectionner"
|
||||
|
||||
- all_columns.each do |column|
|
||||
.fr-fieldset__element.fr-px-4w
|
||||
.fr-checkbox-group
|
||||
= render ExportTemplate::CheckboxComponent.new(export_template:, exported_column: ExportedColumn.new(libelle: column.label, column:))
|
|
@ -0,0 +1,57 @@
|
|||
#export_template-edit.fr-my-4w
|
||||
.fr-mb-6w
|
||||
= render Dsfr::AlertComponent.new(state: :info, title: "Nouvel éditeur de modèle d'export", heading_level: 'h3') do |c|
|
||||
- c.with_body do
|
||||
= t('.info_html', mailto: mail_to(CONTACT_EMAIL, subject: 'Editeur de modèle d\'export'))
|
||||
|
||||
.fr-grid-row.fr-grid-row--gutters
|
||||
.fr-col-12.fr-col-md-8
|
||||
= form_with model: [:instructeur, @procedure, export_template], local: true do |f|
|
||||
|
||||
%h2 Paramètres de l'export
|
||||
= f.hidden_field "[dossier_folder][template]", value: export_template.dossier_folder.template_json
|
||||
= f.hidden_field "[export_pdf][template]", value: export_template.export_pdf.template_json
|
||||
|
||||
= render Dsfr::InputComponent.new(form: f, attribute: :name, input_type: :text_field)
|
||||
|
||||
- if groupe_instructeurs.many?
|
||||
.fr-input-group
|
||||
= f.label :groupe_instructeur_id, class: 'fr-label' do
|
||||
= f.object.class.human_attribute_name(:groupe_instructeur_id)
|
||||
= render EditableChamp::AsteriskMandatoryComponent.new
|
||||
%span.fr-hint-text
|
||||
Avec quel groupe instructeur souhaitez-vous partager ce modèle d'export ?
|
||||
= f.collection_select :groupe_instructeur_id, groupe_instructeurs, :id, :label, {}, class: 'fr-select'
|
||||
- else
|
||||
= f.hidden_field :groupe_instructeur_id
|
||||
|
||||
%fieldset.fr-fieldset.fr-fieldset--inline
|
||||
%legend#radio-inline-legend.fr-fieldset__legend.fr-text--regular
|
||||
Format export
|
||||
= asterisk
|
||||
.fr-fieldset__element.fr-fieldset__element--inline
|
||||
.fr-radio-group
|
||||
= f.radio_button :kind, "xlsx", id: "xlsx"
|
||||
%label.fr-label{ for: "xlsx" } xlsx
|
||||
.fr-radio-group
|
||||
= f.radio_button :kind, "ods", id: "ods"
|
||||
%label.fr-label{ for: "ods" } ods
|
||||
.fr-radio-group
|
||||
= f.radio_button :kind, "csv", id: "csv"
|
||||
%label.fr-label{ for: "csv" } csv
|
||||
|
||||
%h2 Contenu de l'export
|
||||
%p Sélectionnez les colonnes que vous souhaitez voir affichées dans le tableau de votre export.
|
||||
|
||||
= render partial: 'checkbox_group', locals: { title: 'Informations usager', all_columns: @export_template.procedure.usager_columns_for_export, export_template: @export_template }
|
||||
= render partial: 'checkbox_group', locals: { title: 'Informations dossier', all_columns: @export_template.procedure.dossier_columns_for_export, export_template: @export_template }
|
||||
= render ExportTemplate::ChampsComponent.new("Formulaire usager", @export_template, @types_de_champ_public)
|
||||
= render ExportTemplate::ChampsComponent.new("Annotations privées", @export_template, @types_de_champ_private) if @types_de_champ_private.any?
|
||||
|
||||
.fixed-footer
|
||||
.fr-container
|
||||
%ul.fr-btns-group.fr-btns-group--inline-md
|
||||
%li
|
||||
= link_to "Annuler", instructeur_procedure_path(@procedure), class: "fr-btn fr-btn--secondary"
|
||||
%li
|
||||
= f.submit "Enregistrer", class: "fr-btn", data: @export_template.persisted? ? { confirm: t('.warning') } : {}
|
|
@ -4,4 +4,7 @@
|
|||
.fr-container
|
||||
%h1 Mise à jour modèle d'export
|
||||
|
||||
= render partial: 'form', locals: { export_template: @export_template, groupe_instructeurs: @groupe_instructeurs }
|
||||
- if @export_template.tabular?
|
||||
= render partial: 'form_tabular', locals: { export_template: @export_template, groupe_instructeurs: @groupe_instructeurs }
|
||||
- else
|
||||
= render partial: 'form', locals: { export_template: @export_template, groupe_instructeurs: @groupe_instructeurs }
|
||||
|
|
|
@ -3,4 +3,7 @@
|
|||
[t('.title')]] }
|
||||
.fr-container
|
||||
%h1 Nouveau modèle d'export
|
||||
= render partial: 'form', locals: { export_template: @export_template, groupe_instructeurs: @groupe_instructeurs }
|
||||
- if @export_template.tabular?
|
||||
= render partial: 'form_tabular', locals: { export_template: @export_template, groupe_instructeurs: @groupe_instructeurs }
|
||||
- else
|
||||
= render partial: 'form', locals: { export_template: @export_template, groupe_instructeurs: @groupe_instructeurs }
|
||||
|
|
|
@ -6,41 +6,59 @@
|
|||
[t('.title')]] }
|
||||
|
||||
.fr-container
|
||||
%h1= t('.title')
|
||||
= render Dsfr::CalloutComponent.new(title: nil) do |c|
|
||||
- c.with_body do
|
||||
%p= t('.export_description', expiration_time: Export::MAX_DUREE_CONSERVATION_EXPORT.in_hours.to_i)
|
||||
.fr-tabs.mb-3
|
||||
%ul.fr-tabs__list{ role: 'tablist' }
|
||||
%li{ role: 'presentation' }
|
||||
%button.fr-tabs__tab.fr-tabs__tab--icon-left{ id: "tabpanel-exports", tabindex: "0", role: "tab", "aria-selected": "true", "aria-controls": "tabpanel-exports-panel" } Liste des exports
|
||||
%li{ role: 'presentation' }
|
||||
%button.fr-tabs__tab.fr-tabs__tab--icon-left{ id: "tabpanel-export-templates", tabindex: "-1", role: "tab", "aria-selected": "false", "aria-controls": "tabpanel-export-templates-panel" } Modèles d'export
|
||||
|
||||
- if @exports.present?
|
||||
%div{ data: @exports.any?(&:pending?) ? { controller: "turbo-poll", turbo_poll_url_value: "", turbo_poll_interval_value: 10_000, turbo_poll_max_checks_value: 6 } : {} }
|
||||
= render Dossiers::ExportLinkComponent.new(procedure: @procedure, exports: @exports, statut: @statut, count: @dossiers_count, class_btn: 'fr-btn--tertiary', export_url: method(:download_export_instructeur_procedure_path))
|
||||
|
||||
- if @exports.any?{_1.format == Export.formats.fetch(:zip)}
|
||||
= render Dsfr::AlertComponent.new(title: t('.title_zip'), state: :info, extra_class_names: 'fr-mb-3w') do |c|
|
||||
.fr-tabs__panel.fr-tabs__panel--selected{ id: "tabpanel-exports-panel", role: "tabpanel", "aria-labelledby": "tabpanel-exports", tabindex: "0" }
|
||||
= render Dsfr::CalloutComponent.new(title: nil) do |c|
|
||||
- c.with_body do
|
||||
%p= t('.export_description_zip_html')
|
||||
%p= t('.export_description', expiration_time: Export::MAX_DUREE_CONSERVATION_EXPORT.in_hours.to_i)
|
||||
|
||||
- else
|
||||
= t('.no_export_html', expiration_time: Export::MAX_DUREE_CONSERVATION_EXPORT.in_hours.to_i )
|
||||
- if @exports.present?
|
||||
%div{ data: @exports.any?(&:pending?) ? { controller: "turbo-poll", turbo_poll_url_value: "", turbo_poll_interval_value: 10_000, turbo_poll_max_checks_value: 6 } : {} }
|
||||
= render Dossiers::ExportLinkComponent.new(procedure: @procedure, exports: @exports, statut: @statut, count: @dossiers_count, class_btn: 'fr-btn--tertiary', export_url: method(:download_export_instructeur_procedure_path))
|
||||
|
||||
- if @procedure.feature_enabled?(:export_template)
|
||||
%h2.fr-mb-1w.fr-mt-8w
|
||||
Liste des modèles d'export
|
||||
%p.fr-hint-text
|
||||
Un modèle d'export permet de personnaliser le nom des fichiers (pour un export au format Zip)
|
||||
- if @export_templates.any?
|
||||
.fr-table.fr-table--no-caption.fr-mt-5w
|
||||
%table
|
||||
%thead
|
||||
%tr
|
||||
%th{ scope: 'col' } Nom du modèle
|
||||
%th{ scope: 'col' }= "Groupe instructeur" if @procedure.groupe_instructeurs.many?
|
||||
%tbody
|
||||
- @export_templates.each do |export_template|
|
||||
%tr
|
||||
%td= link_to export_template.name, [:edit, :instructeur, @procedure, export_template]
|
||||
%td= export_template.groupe_instructeur.label if @procedure.groupe_instructeurs.many?
|
||||
- if @exports.any?{_1.format == Export.formats.fetch(:zip)}
|
||||
= render Dsfr::AlertComponent.new(title: t('.title_zip'), state: :info, extra_class_names: 'fr-mb-3w') do |c|
|
||||
- c.with_body do
|
||||
%p= t('.export_description_zip_html')
|
||||
|
||||
%p
|
||||
= link_to [:new, :instructeur, @procedure, :export_template], class: 'fr-btn fr-btn--secondary fr-btn--icon-left fr-icon-add-line' do
|
||||
Ajouter un modèle d'export
|
||||
- else
|
||||
= t('.no_export_html', expiration_time: Export::MAX_DUREE_CONSERVATION_EXPORT.in_hours.to_i )
|
||||
|
||||
.fr-tabs__panel.fr-tabs__panel{ id: "tabpanel-export-templates-panel", role: "tabpanel", "aria-labelledby": "tabpanel-export-templates", tabindex: "0" }
|
||||
= render Dsfr::AlertComponent.new(state: :info) do |c|
|
||||
- c.with_body do
|
||||
%p= t('.export_template_list_description_html')
|
||||
|
||||
|
||||
.fr-mt-5w
|
||||
= link_to t('.new_zip_export_template'), new_instructeur_procedure_export_template_path(@procedure), class: "fr-btn fr-btn--secondary fr-btn--icon-left fr-icon-add-line fr-mr-1w"
|
||||
= link_to t('.new_tabular_export_template'), new_instructeur_procedure_export_template_path(@procedure, kind: 'tabular'), class: "fr-btn fr-btn--secondary fr-btn--icon-left fr-icon-add-line"
|
||||
|
||||
.fr-table.fr-table--bordered.fr-table--no-caption.fr-mt-5w
|
||||
.fr-table__wrapper
|
||||
.fr-table__container
|
||||
.fr-table__content
|
||||
%table
|
||||
%thead
|
||||
%tr
|
||||
= tag.th "Nom du modèle", scope: 'col'
|
||||
= tag.th "Format", scope: 'col'
|
||||
= tag.th "Date de création", scope: 'col'
|
||||
= tag.th "Partagé avec (groupe instructeurs)", scope: 'col' if @procedure.groupe_instructeurs.many?
|
||||
= tag.th "Actions", scope: 'col'
|
||||
%tbody
|
||||
- @export_templates.each do |export_template|
|
||||
%tr
|
||||
%td= link_to export_template.name, [:edit, :instructeur, @procedure, export_template]
|
||||
%td= pretty_kind(export_template.kind)
|
||||
%td= l(export_template.created_at)
|
||||
= tag.td export_template.groupe_instructeur.label if @procedure.groupe_instructeurs.many?
|
||||
%td
|
||||
= link_to "Modifier", [:edit, :instructeur, @procedure, export_template], class: "fr-btn fr-btn--icon-left fr-icon-edit-line fr-mr-1w"
|
||||
= link_to "Supprimer", [:instructeur, @procedure, export_template], method: :delete, data: { confirm: "Voulez-vous vraiment supprimer ce modèle ? Il sera supprimé pour tous les instructeurs du groupe"}, class: "fr-btn fr-btn--secondary fr-btn--icon-left fr-icon-delete-line"
|
||||
|
|
|
@ -4,10 +4,12 @@ require Rails.root.join("app/types/column_type")
|
|||
require Rails.root.join("app/types/export_item_type")
|
||||
require Rails.root.join("app/types/sorted_column_type")
|
||||
require Rails.root.join("app/types/filtered_column_type")
|
||||
require Rails.root.join("app/types/exported_column_type")
|
||||
|
||||
ActiveSupport.on_load(:active_record) do
|
||||
ActiveRecord::Type.register(:column, ColumnType)
|
||||
ActiveRecord::Type.register(:export_item, ExportItemType)
|
||||
ActiveRecord::Type.register(:sorted_column, SortedColumnType)
|
||||
ActiveRecord::Type.register(:filtered_column, FilteredColumnType)
|
||||
ActiveRecord::Type.register(:exported_column, ExportedColumnType)
|
||||
end
|
||||
|
|
|
@ -17,3 +17,12 @@ en:
|
|||
dossier_number_required: "must contain dossier's number"
|
||||
different_templates: "Files must have different names"
|
||||
invalid_template: "A file name is invalid"
|
||||
base:
|
||||
invalid: "is invalid"
|
||||
instructeurs:
|
||||
export_templates:
|
||||
form_tabular:
|
||||
info_html: |
|
||||
This page allows you to edit a tabular export template and select fields that you want to export.
|
||||
Try it and let us know what you think by sending an e-mail to %{mailto}.
|
||||
warning: If you modify this template, it will also be modified for all instructors who have access to this template.
|
||||
|
|
|
@ -17,3 +17,13 @@ fr:
|
|||
dossier_number_required: doit contenir le numéro du dossier
|
||||
different_templates: Les fichiers doivent avoir des noms différents
|
||||
invalid_template: Un nom de fichier est invalide
|
||||
base:
|
||||
invalid: "est invalide"
|
||||
instructeurs:
|
||||
export_templates:
|
||||
form_tabular:
|
||||
info_html: |
|
||||
Cette page permet d'éditer un modèle d'export tabulaire et ainsi sélectionner les champs que vous souhaitez exporter.
|
||||
Essayez-le et donnez-nous votre avis
|
||||
en nous envoyant un email à %{mailto}.
|
||||
warning: Si vous modifiez ce modèle, il sera également modifié pour tous les instructeurs qui ont accès à ce modèle.
|
||||
|
|
|
@ -12,7 +12,7 @@ en:
|
|||
button_delay_expiration: "Keep for one more month"
|
||||
notification_management: notification management
|
||||
administrators_list: administrators list
|
||||
exports_list: exports list
|
||||
exports_list: exports and export templates
|
||||
exports_notification_label: A new export is ready to download
|
||||
statistics: statistics
|
||||
instructeurs: instructors
|
||||
|
|
|
@ -13,7 +13,7 @@ fr:
|
|||
button_delay_expiration: "Conserver un mois de plus"
|
||||
notification_management: Gestion des notifications
|
||||
administrators_list: Voir les administrateurs
|
||||
exports_list: Voir les exports
|
||||
exports_list: Voir les exports et modèles d'export
|
||||
exports_notification_label: Un nouvel export est prêt à être téléchargé
|
||||
statistics: Statistiques
|
||||
instructeurs: instructeurs
|
||||
|
|
|
@ -18,3 +18,9 @@ en:
|
|||
|
||||
|
||||
no_export_html: You have no export at the moment. <br> Can't find an export? It may have expired, exports are deleted after %{expiration_time} hours.
|
||||
|
||||
export_template_list_description_html: |
|
||||
Each instructor can <b>configure an export template</b> to customize exports (attachments name for a zip export, columns selection for a tabular export). It will be made <b>available to all instructors</b> assigned to the procedure.</br>
|
||||
<a href="https://doc.demarches-simplifiees.fr" target="_blank" rel="noopener noreferrer">Find out more about export template configuration</a>
|
||||
new_zip_export_template: Create zip export template
|
||||
new_tabular_export_template: Create tabular export template
|
||||
|
|
|
@ -17,3 +17,8 @@ fr:
|
|||
Vous n'arrivez pas à extraire un export au format .zip sur un réseau d'entreprise ? Essayer de renommer l'archive avec un nom plus court et ré-essayer de l'extraire.
|
||||
|
||||
no_export_html: Vous n'avez pas d'export pour le moment. <br> Vous ne trouvez pas un export ? Il a peut-être expiré, les exports sont supprimés au bout de %{expiration_time} heures.
|
||||
export_template_list_description_html: |
|
||||
Chaque instructeur a la possibilité de <b>configurer un modèle</b> d'export pour personnaliser les exports (nom des pièces jointes pour un export au format zip, sélection des colonnes pour un export tabulaire). Il sera <b>mis à disposition de l'ensemble des instructeurs</b> affectés à la démarche<br>
|
||||
<a href="https://doc.demarches-simplifiees.fr" target="_blank" rel="noopener noreferrer">En savoir plus sur la configuration des modèles d'export</a>
|
||||
new_zip_export_template: Créer un modèle d'export zip
|
||||
new_tabular_export_template: Créer un modèle d'export tabulaire
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddColumnsToExportTemplate < ActiveRecord::Migration[7.0]
|
||||
def change
|
||||
add_column :export_templates, :exported_columns, :jsonb, array: true, default: [], null: false
|
||||
end
|
||||
end
|
|
@ -626,6 +626,7 @@ ActiveRecord::Schema[7.0].define(version: 2024_11_12_090128) do
|
|||
t.datetime "created_at", null: false
|
||||
t.jsonb "dossier_folder", null: false
|
||||
t.jsonb "export_pdf", null: false
|
||||
t.jsonb "exported_columns", default: [], null: false, array: true
|
||||
t.bigint "groupe_instructeur_id", null: false
|
||||
t.string "kind", null: false
|
||||
t.string "name", null: false
|
||||
|
|
26
spec/components/export_template/champs_component_spec.rb
Normal file
26
spec/components/export_template/champs_component_spec.rb
Normal file
|
@ -0,0 +1,26 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
describe ExportTemplate::ChampsComponent, type: :component do
|
||||
let(:groupe_instructeur) { create(:groupe_instructeur, procedure:) }
|
||||
let(:export_template) { build(:export_template, kind: 'csv', groupe_instructeur:) }
|
||||
let(:procedure) { create(:procedure_with_dossiers, :published, types_de_champ_public:, for_individual:) }
|
||||
let(:for_individual) { true }
|
||||
let(:types_de_champ_public) do
|
||||
[
|
||||
{ type: :text, libelle: "Ca va ?", mandatory: true, stable_id: 1 },
|
||||
{ type: :communes, libelle: "Commune", mandatory: true, stable_id: 17 },
|
||||
{ type: :siret, libelle: 'Siret', stable_id: 20 },
|
||||
{ type: :repetition, mandatory: true, stable_id: 7, libelle: "Amis", children: [{ type: 'text', libelle: 'Prénom', stable_id: 8 }] }
|
||||
]
|
||||
end
|
||||
let(:component) { described_class.new("Champs publics", export_template, procedure.all_revisions_types_de_champ(parent: nil, with_header_section: true)) }
|
||||
before { render_inline(component).to_html }
|
||||
|
||||
it 'renders champs within fieldset' do
|
||||
procedure
|
||||
expect(page).to have_unchecked_field "Ca va ?"
|
||||
expect(page).to have_unchecked_field "Commune"
|
||||
expect(page).to have_unchecked_field "Siret"
|
||||
expect(page).to have_unchecked_field "(Bloc répétable Amis) – Prénom"
|
||||
end
|
||||
end
|
|
@ -86,6 +86,42 @@ describe Instructeurs::ExportTemplatesController, type: :controller do
|
|||
expect(ExportTemplate.last.pjs).to match_array([])
|
||||
end
|
||||
end
|
||||
|
||||
context 'with tabular params' do
|
||||
let(:procedure) do
|
||||
create(
|
||||
:procedure, instructeurs: [instructeur],
|
||||
types_de_champ_public: [{ type: :text, libelle: 'un texte', stable_id: 1 }]
|
||||
)
|
||||
end
|
||||
|
||||
let(:exported_columns) do
|
||||
[
|
||||
{ id: procedure.find_column(label: 'Demandeur').id, libelle: 'Demandeur' },
|
||||
{ id: procedure.find_column(label: 'Date du dernier évènement').id, libelle: 'Date du dernier évènement' }
|
||||
].map(&:to_json)
|
||||
end
|
||||
|
||||
let(:create_params) do
|
||||
{
|
||||
name: "ExportODS",
|
||||
kind: "ods",
|
||||
groupe_instructeur_id: groupe_instructeur.id,
|
||||
export_pdf: item_params(text: "export"),
|
||||
dossier_folder: item_params(text: "dossier"),
|
||||
exported_columns:
|
||||
}
|
||||
end
|
||||
|
||||
context 'with valid params' do
|
||||
it 'redirect to some page' do
|
||||
subject
|
||||
expect(response).to redirect_to(exports_instructeur_procedure_path(procedure))
|
||||
expect(flash.notice).to eq "Le modèle d'export ExportODS a bien été créé"
|
||||
expect(ExportTemplate.last.exported_columns.map(&:libelle)).to match_array ['Demandeur', 'Date du dernier évènement']
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#edit' do
|
||||
|
@ -146,6 +182,35 @@ describe Instructeurs::ExportTemplatesController, type: :controller do
|
|||
expect(flash.alert).to be_present
|
||||
end
|
||||
end
|
||||
|
||||
context 'for tabular' do
|
||||
let(:exported_columns) do
|
||||
[
|
||||
{ id: procedure.find_column(label: 'Demandeur').id, libelle: 'Demandeur' },
|
||||
{ id: procedure.find_column(label: 'Date du dernier évènement').id, libelle: 'Date du dernier évènement' }
|
||||
].map(&:to_json)
|
||||
end
|
||||
|
||||
let(:export_template_params) do
|
||||
{
|
||||
name: "ExportODS",
|
||||
kind: "ods",
|
||||
groupe_instructeur_id: groupe_instructeur.id,
|
||||
export_pdf: item_params(text: "export"),
|
||||
dossier_folder: item_params(text: "dossier"),
|
||||
exported_columns:
|
||||
}
|
||||
end
|
||||
|
||||
context 'with valid params' do
|
||||
it 'redirect to some page' do
|
||||
subject
|
||||
expect(response).to redirect_to(exports_instructeur_procedure_path(procedure))
|
||||
expect(flash.notice).to eq "Le modèle d'export ExportODS a bien été modifié"
|
||||
expect(ExportTemplate.last.exported_columns.map(&:libelle)).to match_array ['Demandeur', 'Date du dernier évènement']
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#destroy' do
|
||||
|
|
|
@ -170,15 +170,20 @@ FactoryBot.define do
|
|||
|
||||
factory :champ_do_not_use_rna, class: 'Champs::RNAChamp' do
|
||||
value { 'W173847273' }
|
||||
value_json { AddressProxy::ADDRESS_PARTS.index_by(&:itself) }
|
||||
end
|
||||
|
||||
factory :champ_do_not_use_engagement_juridique, class: 'Champs::EngagementJuridiqueChamp' do
|
||||
value { 'EJ' }
|
||||
end
|
||||
|
||||
factory :champ_do_not_use_cojo, class: 'Champs::COJOChamp' do
|
||||
end
|
||||
|
||||
factory :champ_do_not_use_rnf, class: 'Champs::RNFChamp' do
|
||||
value { '075-FDD-00003-01' }
|
||||
external_id { '075-FDD-00003-01' }
|
||||
value_json { AddressProxy::ADDRESS_PARTS.index_by(&:itself) }
|
||||
end
|
||||
|
||||
factory :champ_do_not_use_expression_reguliere, class: 'Champs::ExpressionReguliereChamp' do
|
||||
|
|
|
@ -13,7 +13,7 @@ describe Columns::ChampColumn do
|
|||
expect_type_de_champ_values('email', eq(['yoda@beta.gouv.fr']))
|
||||
expect_type_de_champ_values('phone', eq(['0666666666']))
|
||||
expect_type_de_champ_values('address', eq(["2 rue des Démarches"]))
|
||||
expect_type_de_champ_values('communes', eq(["Coye-la-Forêt"]))
|
||||
expect_type_de_champ_values('communes', eq(["Coye-la-Forêt", "60580", "60"]))
|
||||
expect_type_de_champ_values('departements', eq(['01']))
|
||||
expect_type_de_champ_values('regions', eq(['01']))
|
||||
expect_type_de_champ_values('pays', eq(['France']))
|
||||
|
@ -30,7 +30,7 @@ describe Columns::ChampColumn do
|
|||
expect_type_de_champ_values('checkbox', eq([true]))
|
||||
expect_type_de_champ_values('drop_down_list', eq(['val1']))
|
||||
expect_type_de_champ_values('multiple_drop_down_list', eq([["val1", "val2"]]))
|
||||
expect_type_de_champ_values('linked_drop_down_list', eq([nil, "primary", "secondary"]))
|
||||
expect_type_de_champ_values('linked_drop_down_list', eq(["primary / secondary", "primary", "secondary"]))
|
||||
expect_type_de_champ_values('yes_no', eq([true]))
|
||||
expect_type_de_champ_values('annuaire_education', eq([nil]))
|
||||
expect_type_de_champ_values('piece_justificative', be_an_instance_of(Array))
|
||||
|
|
|
@ -14,6 +14,9 @@ describe Columns::DossierColumn do
|
|||
expect(procedure.find_column(label: "Prénom").value(dossier)).to eq("Paul")
|
||||
expect(procedure.find_column(label: "Nom").value(dossier)).to eq("Sim")
|
||||
expect(procedure.find_column(label: "Civilité").value(dossier)).to eq("M.")
|
||||
expect(procedure.find_column(label: "Dépôt pour un tiers").value(dossier)).to eq(true)
|
||||
expect(procedure.find_column(label: "Nom du mandataire").value(dossier)).to eq("Christophe")
|
||||
expect(procedure.find_column(label: "Prénom du mandataire").value(dossier)).to eq("Martin")
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -22,7 +25,67 @@ describe Columns::DossierColumn do
|
|||
let(:dossier) { create(:dossier, :en_instruction, :with_entreprise, procedure:) }
|
||||
|
||||
it 'retrieve entreprise information' do
|
||||
expect(procedure.find_column(label: "Nº dossier").value(dossier)).to eq(dossier.id)
|
||||
expect(procedure.find_column(label: "Email").value(dossier)).to eq(dossier.user_email_for(:display))
|
||||
expect(procedure.find_column(label: "France connecté ?").value(dossier)).to eq(false)
|
||||
expect(procedure.find_column(label: "Entreprise forme juridique").value(dossier)).to eq("SA à conseil d'administration (s.a.i.)")
|
||||
expect(procedure.find_column(label: "Entreprise SIREN").value(dossier)).to eq('440117620')
|
||||
expect(procedure.find_column(label: "Entreprise nom commercial").value(dossier)).to eq('GRTGAZ')
|
||||
expect(procedure.find_column(label: "Entreprise raison sociale").value(dossier)).to eq('GRTGAZ')
|
||||
expect(procedure.find_column(label: "Entreprise SIRET siège social").value(dossier)).to eq('44011762001530')
|
||||
expect(procedure.find_column(label: "Date de création").value(dossier)).to be_an_instance_of(ActiveSupport::TimeWithZone)
|
||||
expect(procedure.find_column(label: "Établissement SIRET").value(dossier)).to eq('44011762001530')
|
||||
expect(procedure.find_column(label: "Libellé NAF").value(dossier)).to eq('Transports par conduites')
|
||||
expect(procedure.find_column(label: "Établissement code postal").value(dossier)).to eq('92270')
|
||||
expect(procedure.find_column(label: "Établissement siège social").value(dossier)).to eq(true)
|
||||
expect(procedure.find_column(label: "Établissement NAF").value(dossier)).to eq('4950Z')
|
||||
expect(procedure.find_column(label: "Établissement Adresse").value(dossier)).to eq("GRTGAZ\r IMMEUBLE BORA\r 6 RUE RAOUL NORDLING\r 92270 BOIS COLOMBES\r")
|
||||
expect(procedure.find_column(label: "Établissement numero voie").value(dossier)).to eq('6')
|
||||
expect(procedure.find_column(label: "Établissement type voie").value(dossier)).to eq('RUE')
|
||||
expect(procedure.find_column(label: "Établissement nom voie").value(dossier)).to eq('RAOUL NORDLING')
|
||||
expect(procedure.find_column(label: "Établissement complément adresse").value(dossier)).to eq('IMMEUBLE BORA')
|
||||
expect(procedure.find_column(label: "Établissement localité").value(dossier)).to eq('BOIS COLOMBES')
|
||||
expect(procedure.find_column(label: "Établissement code INSEE localité").value(dossier)).to eq('92009')
|
||||
expect(procedure.find_column(label: "Entreprise SIREN").value(dossier)).to eq('440117620')
|
||||
expect(procedure.find_column(label: "Entreprise capital social").value(dossier)).to eq(537_100_000)
|
||||
expect(procedure.find_column(label: "Entreprise numero TVA intracommunautaire").value(dossier)).to eq('FR27440117620')
|
||||
expect(procedure.find_column(label: "Entreprise forme juridique code").value(dossier)).to eq('5599')
|
||||
expect(procedure.find_column(label: "Entreprise code effectif entreprise").value(dossier)).to eq('51')
|
||||
expect(procedure.find_column(label: "Entreprise état administratif").value(dossier)).to eq("actif")
|
||||
expect(procedure.find_column(label: "Entreprise nom").value(dossier)).to eq(nil)
|
||||
expect(procedure.find_column(label: "Entreprise prénom").value(dossier)).to eq(nil)
|
||||
expect(procedure.find_column(label: "Association RNA").value(dossier)).to eq(nil)
|
||||
expect(procedure.find_column(label: "Association titre").value(dossier)).to eq(nil)
|
||||
expect(procedure.find_column(label: "Association objet").value(dossier)).to eq(nil)
|
||||
expect(procedure.find_column(label: "Association date de création").value(dossier)).to eq(nil)
|
||||
expect(procedure.find_column(label: "Association date de déclaration").value(dossier)).to eq(nil)
|
||||
expect(procedure.find_column(label: "Association date de publication").value(dossier)).to eq(nil)
|
||||
expect(procedure.find_column(label: "Date de création").value(dossier)).to be_an_instance_of(ActiveSupport::TimeWithZone)
|
||||
expect(procedure.find_column(label: "Date du dernier évènement").value(dossier)).to be_an_instance_of(ActiveSupport::TimeWithZone)
|
||||
expect(procedure.find_column(label: "Date de dépot").value(dossier)).to be_an_instance_of(ActiveSupport::TimeWithZone)
|
||||
expect(procedure.find_column(label: "Date de passage en construction").value(dossier)).to be_an_instance_of(ActiveSupport::TimeWithZone)
|
||||
expect(procedure.find_column(label: "Date de passage en instruction").value(dossier)).to be_an_instance_of(ActiveSupport::TimeWithZone)
|
||||
expect(procedure.find_column(label: "Date de traitement").value(dossier)).to eq(nil)
|
||||
expect(procedure.find_column(label: "État du dossier").value(dossier)).to eq('en_instruction')
|
||||
expect(procedure.find_column(label: "Archivé").value(dossier)).to eq(false)
|
||||
expect(procedure.find_column(label: "Motivation de la décision").value(dossier)).to eq(nil)
|
||||
expect(procedure.find_column(label: "Date de dernière modification (usager)").value(dossier)).to eq(nil)
|
||||
expect(procedure.find_column(label: "Instructeurs").value(dossier)).to eq('')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when procedure for entreprise which is also an association' do
|
||||
let(:procedure) { create(:procedure, for_individual: false, groupe_instructeurs: [groupe_instructeur]) }
|
||||
let(:etablissement) { create(:etablissement, :is_association) }
|
||||
let(:dossier) { create(:dossier, :en_instruction, procedure:, etablissement:) }
|
||||
|
||||
it 'retrieve also association information' do
|
||||
expect(procedure.find_column(label: "Association RNA").value(dossier)).to eq("W072000535")
|
||||
expect(procedure.find_column(label: "Association titre").value(dossier)).to eq("ASSOCIATION POUR LA PROMOTION DE SPECTACLES AU CHATEAU DE ROCHEMAURE")
|
||||
expect(procedure.find_column(label: "Association objet").value(dossier)).to eq("mise en oeuvre et réalisation de spectacles au chateau de rochemaure")
|
||||
expect(procedure.find_column(label: "Association date de création").value(dossier)).to eq(Date.parse("1990-04-24"))
|
||||
expect(procedure.find_column(label: "Association date de déclaration").value(dossier)).to eq(Date.parse("2014-11-28"))
|
||||
expect(procedure.find_column(label: "Association date de publication").value(dossier)).to eq(Date.parse("1990-05-16"))
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -183,8 +183,8 @@ RSpec.describe DossierChampsConcern do
|
|||
it { row_id; subject; expect(row_id).not_to be_in(row_ids) }
|
||||
end
|
||||
|
||||
describe "#champs_for_export" do
|
||||
subject { dossier.champs_for_export(dossier.revision.types_de_champ_public) }
|
||||
describe "#champ_values_for_export" do
|
||||
subject { dossier.champ_values_for_export(dossier.revision.types_de_champ_public, format: :xlsx) }
|
||||
|
||||
it { expect(subject.size).to eq(4) }
|
||||
it { expect(subject.first).to eq(["Un champ text", nil]) }
|
||||
|
|
|
@ -1982,7 +1982,7 @@ describe Dossier, type: :model do
|
|||
end
|
||||
end
|
||||
|
||||
describe "champs_for_export" do
|
||||
describe "champ_values_for_export" do
|
||||
context 'with integer_number' do
|
||||
let(:procedure) { create(:procedure, :published, types_de_champ_public: [{ type: :integer_number, libelle: 'c1' }]) }
|
||||
let(:dossier) { create(:dossier, :with_populated_champs, procedure:) }
|
||||
|
@ -1993,7 +1993,7 @@ describe Dossier, type: :model do
|
|||
expect {
|
||||
integer_number_type_de_champ.update(type_champ: :decimal_number)
|
||||
procedure.update(published_revision: procedure.draft_revision, draft_revision: procedure.create_new_revision)
|
||||
}.to change { dossier.reload.champs_for_export(procedure.all_revisions_types_de_champ.not_repetition.to_a) }
|
||||
}.to change { dossier.reload.champ_values_for_export(procedure.all_revisions_types_de_champ.not_repetition.to_a, format: :xlsx) }
|
||||
.from([["c1", 42]]).to([["c1", 42.0]])
|
||||
end
|
||||
end
|
||||
|
@ -2020,8 +2020,8 @@ describe Dossier, type: :model do
|
|||
let(:repetition_second_revision_champ) { dossier_second_revision.project_champs_public.find(&:repetition?) }
|
||||
let(:dossier) { create(:dossier, procedure: procedure) }
|
||||
let(:dossier_second_revision) { create(:dossier, procedure: procedure) }
|
||||
let(:dossier_champs_for_export) { dossier.champs_for_export(procedure.types_de_champ_for_procedure_export) }
|
||||
let(:dossier_second_revision_champs_for_export) { dossier_second_revision.champs_for_export(procedure.types_de_champ_for_procedure_export) }
|
||||
let(:dossier_champ_values_for_export) { dossier.champ_values_for_export(procedure.types_de_champ_for_procedure_export, format: :xlsx) }
|
||||
let(:dossier_second_revision_champ_values_for_export) { dossier_second_revision.champ_values_for_export(procedure.types_de_champ_for_procedure_export, format: :xlsx) }
|
||||
|
||||
context "when procedure published" do
|
||||
before do
|
||||
|
@ -2040,8 +2040,8 @@ describe Dossier, type: :model do
|
|||
it "should have champs from all revisions" do
|
||||
expect(dossier.types_de_champ.map(&:libelle)).to eq([text_type_de_champ.libelle, datetime_type_de_champ.libelle, "Yes/no", explication_type_de_champ.libelle, commune_type_de_champ.libelle, repetition_type_de_champ.libelle])
|
||||
expect(dossier_second_revision.types_de_champ.map(&:libelle)).to eq([datetime_type_de_champ.libelle, "Updated yes/no", explication_type_de_champ.libelle, 'Commune de naissance', "Repetition", "New text field"])
|
||||
expect(dossier_champs_for_export.map { |(libelle)| libelle }).to eq([datetime_type_de_champ.libelle, text_type_de_champ.libelle, "Updated yes/no", "Commune de naissance", "Commune de naissance (Code INSEE)", "Commune de naissance (Département)", "New text field"])
|
||||
expect(dossier_champs_for_export).to eq(dossier_second_revision_champs_for_export)
|
||||
expect(dossier_champ_values_for_export.map { |(libelle)| libelle }).to eq([datetime_type_de_champ.libelle, text_type_de_champ.libelle, "Updated yes/no", "Commune de naissance", "Commune de naissance (Code INSEE)", "Commune de naissance (Département)", "New text field"])
|
||||
expect(dossier_champ_values_for_export).to eq(dossier_second_revision_champ_values_for_export)
|
||||
end
|
||||
|
||||
context 'within a repetition having a type de champs commune (multiple values for export)' do
|
||||
|
@ -2056,7 +2056,7 @@ describe Dossier, type: :model do
|
|||
dossier_test = create(:dossier, procedure: proc_test)
|
||||
type_champs = proc_test.all_revisions_types_de_champ(parent: tdc_repetition).to_a
|
||||
expect(type_champs.size).to eq(1)
|
||||
expect(dossier.champs_for_export(type_champs).size).to eq(3)
|
||||
expect(dossier.champ_values_for_export(type_champs, format: :xlsx).size).to eq(3)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -2065,7 +2065,7 @@ describe Dossier, type: :model do
|
|||
let(:procedure) { create(:procedure, types_de_champ_public: [{ type: :text }, { type: :explication }]) }
|
||||
|
||||
it "should not contain non-exportable types de champ" do
|
||||
expect(dossier_champs_for_export.map { |(libelle)| libelle }).to eq([text_type_de_champ.libelle])
|
||||
expect(dossier_champ_values_for_export.map { |(libelle)| libelle }).to eq([text_type_de_champ.libelle])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -2079,7 +2079,7 @@ describe Dossier, type: :model do
|
|||
let(:text_tdc) { procedure.active_revision.types_de_champ_public.second }
|
||||
let(:tdcs) { dossier.project_champs_public.map(&:type_de_champ) }
|
||||
|
||||
subject { dossier.champs_for_export(tdcs) }
|
||||
subject { dossier.champ_values_for_export(tdcs, format: :xlsx) }
|
||||
|
||||
before do
|
||||
text_tdc.update(condition: ds_eq(champ_value(yes_no_tdc.stable_id), constant(true)))
|
||||
|
|
144
spec/models/export_template_tabular_spec.rb
Normal file
144
spec/models/export_template_tabular_spec.rb
Normal file
|
@ -0,0 +1,144 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
describe ExportTemplate do
|
||||
let(:groupe_instructeur) { create(:groupe_instructeur, procedure:) }
|
||||
let(:export_template) { build(:export_template, kind: 'csv', groupe_instructeur:) }
|
||||
let(:tabular_export_template) { build(:tabular_export_template, groupe_instructeur:) }
|
||||
let(:procedure) { create(:procedure_with_dossiers, :published, types_de_champ_public:, for_individual:) }
|
||||
let(:for_individual) { true }
|
||||
let(:types_de_champ_public) do
|
||||
[
|
||||
{ type: :text, libelle: "Ca va ?", mandatory: true, stable_id: 1 },
|
||||
{ type: :communes, libelle: "Commune", mandatory: true, stable_id: 17 },
|
||||
{ type: :siret, libelle: 'siret', stable_id: 20 },
|
||||
{ type: :repetition, mandatory: true, stable_id: 7, libelle: "Champ répétable", children: [{ type: 'text', libelle: 'Qqchose à rajouter?', stable_id: 8 }] }
|
||||
]
|
||||
end
|
||||
|
||||
describe '#exported_columns=' do
|
||||
it 'is assignable/readable with ExportedColumn object' do
|
||||
expect do
|
||||
export_template.exported_columns = [
|
||||
ExportedColumn.new(libelle: 'Ça va ?', column: procedure.find_column(label: "Ca va ?"))
|
||||
]
|
||||
export_template.save!
|
||||
export_template.exported_columns
|
||||
end.not_to raise_error
|
||||
end
|
||||
it 'create exported_column' do
|
||||
export_template.exported_columns = [
|
||||
ExportedColumn.new(libelle: 'Ça va ?', column: procedure.find_column(label: "Ca va ?"))
|
||||
]
|
||||
export_template.save!
|
||||
expect(export_template.exported_columns.size).to eq 1
|
||||
end
|
||||
|
||||
context 'when there is a previous revision with a renamed tdc' do
|
||||
context 'with already column in export template' do
|
||||
let(:previous_tdc) { procedure.published_revision.types_de_champ_public.find_by(stable_id: 1) }
|
||||
let(:changed_tdc) { { libelle: "Ca roule ?" } }
|
||||
|
||||
context 'with already column in export template' do
|
||||
before do
|
||||
export_template.exported_columns = [
|
||||
ExportedColumn.new(libelle: 'Ça va ?', column: procedure.find_column(label: "Ca va ?"))
|
||||
]
|
||||
export_template.save!
|
||||
|
||||
type_de_champ = procedure.draft_revision.find_and_ensure_exclusive_use(previous_tdc.stable_id)
|
||||
type_de_champ.update(changed_tdc)
|
||||
procedure.publish_revision!
|
||||
end
|
||||
|
||||
it 'update columns with original libelle for champs with new revision' do
|
||||
Current.procedure_columns = {}
|
||||
procedure.reload
|
||||
export_template.reload
|
||||
expect(export_template.exported_columns.find { _1.column.stable_id.to_s == "1" }.libelle).to eq('Ça va ?')
|
||||
end
|
||||
end
|
||||
end
|
||||
context 'without columns in export template' do
|
||||
let(:previous_tdc) { procedure.published_revision.types_de_champ_public.find_by(stable_id: 1) }
|
||||
let(:changed_tdc) { { libelle: "Ca roule ?" } }
|
||||
|
||||
before do
|
||||
type_de_champ = procedure.draft_revision.find_and_ensure_exclusive_use(previous_tdc.stable_id)
|
||||
type_de_champ.update(changed_tdc)
|
||||
procedure.publish_revision!
|
||||
|
||||
export_template.exported_columns = [
|
||||
ExportedColumn.new(libelle: 'Ça roule ?', column: procedure.find_column(label: "Ca roule ?"))
|
||||
]
|
||||
export_template.save!
|
||||
end
|
||||
|
||||
it 'update columns with original libelle for champs with new revision' do
|
||||
Current.procedure_columns = {}
|
||||
procedure.reload
|
||||
export_template.reload
|
||||
expect(export_template.exported_columns.find { _1.column.stable_id.to_s == "1" }.libelle).to eq('Ça roule ?')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'dossier_exported_columns' do
|
||||
context 'when exported_columns is empty' do
|
||||
it 'returns an empty array' do
|
||||
expect(export_template.dossier_exported_columns).to eq([])
|
||||
end
|
||||
end
|
||||
|
||||
context 'when exported_columns is not empty' do
|
||||
before do
|
||||
export_template.exported_columns = [
|
||||
ExportedColumn.new(libelle: 'Colonne usager', column: procedure.find_column(label: "Email")),
|
||||
ExportedColumn.new(libelle: 'Ça va ?', column: procedure.find_column(label: "Ca va ?"))
|
||||
]
|
||||
end
|
||||
it 'returns all columns except tdc columns' do
|
||||
expect(export_template.dossier_exported_columns.size).to eq(1) # exclude tdc
|
||||
expect(export_template.dossier_exported_columns.first.libelle).to eq("Colonne usager")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'columns_for_stable_id' do
|
||||
before do
|
||||
export_template.exported_columns = procedure.published_revision.types_de_champ.first.columns(procedure: procedure).map do |column|
|
||||
ExportedColumn.new(libelle: column.label, column:)
|
||||
end
|
||||
end
|
||||
context 'when procedure has a TypeDeChamp::Commune' do
|
||||
let(:types_de_champ_public) do
|
||||
[
|
||||
{ type: :communes, libelle: "Commune", mandatory: true, stable_id: 17 }
|
||||
]
|
||||
end
|
||||
it 'is able to resolve stable_id' do
|
||||
expect(export_template.columns_for_stable_id(17).size).to eq(3)
|
||||
end
|
||||
end
|
||||
context 'when procedure has a TypeDeChamp::Siret' do
|
||||
let(:types_de_champ_public) do
|
||||
[
|
||||
{ type: :siret, libelle: 'siret', stable_id: 20 }
|
||||
]
|
||||
end
|
||||
it 'is able to resolve stable_id' do
|
||||
expect(export_template.columns_for_stable_id(20).size).to eq(5)
|
||||
end
|
||||
end
|
||||
context 'when procedure has a TypeDeChamp::Text' do
|
||||
let(:types_de_champ_public) do
|
||||
[
|
||||
{ type: :text, libelle: "Text", mandatory: true, stable_id: 15 }
|
||||
]
|
||||
end
|
||||
it 'is able to resolve stable_id' do
|
||||
expect(export_template.columns_for_stable_id(15).size).to eq(1)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1885,6 +1885,49 @@ describe Procedure do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#all_revisions_types_de_champ' do
|
||||
let(:types_de_champ_public) do
|
||||
[
|
||||
{ type: :text },
|
||||
{ type: :header_section }
|
||||
]
|
||||
end
|
||||
|
||||
context 'when procedure brouillon' do
|
||||
let(:procedure) { create(:procedure, types_de_champ_public:) }
|
||||
|
||||
it 'returns one type de champ' do
|
||||
expect(procedure.all_revisions_types_de_champ.size).to eq 1
|
||||
end
|
||||
|
||||
it 'returns also section type de champ' do
|
||||
expect(procedure.all_revisions_types_de_champ(with_header_section: true).size).to eq 2
|
||||
end
|
||||
|
||||
it "returns types de champ on draft revision" do
|
||||
procedure.draft_revision.add_type_de_champ(type_champ: :text, libelle: 'onemorechamp')
|
||||
expect(procedure.reload.all_revisions_types_de_champ.size).to eq 2
|
||||
end
|
||||
end
|
||||
|
||||
context 'when procedure is published' do
|
||||
let(:procedure) { create(:procedure, :published, types_de_champ_public:) }
|
||||
|
||||
it 'returns one type de champ' do
|
||||
expect(procedure.all_revisions_types_de_champ.size).to eq 1
|
||||
end
|
||||
|
||||
it 'returns also section type de champ' do
|
||||
expect(procedure.all_revisions_types_de_champ(with_header_section: true).size).to eq 2
|
||||
end
|
||||
|
||||
it "doesn't return types de champ on draft revision" do
|
||||
procedure.draft_revision.add_type_de_champ(type_champ: :text, libelle: 'onemorechamp')
|
||||
expect(procedure.reload.all_revisions_types_de_champ.size).to eq 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def create_dossier_with_pj_of_size(size, procedure)
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
describe TypesDeChamp::CommuneTypeDeChamp do
|
||||
let(:subject) { create(:type_de_champ_communes, libelle: 'Ma commune') }
|
||||
|
||||
it { expect(subject.libelles_for_export).to match_array([['Ma commune', :value], ['Ma commune (Code INSEE)', :code], ['Ma commune (Département)', :departement]]) }
|
||||
let(:tdc_commune) { create(:type_de_champ_communes, libelle: 'Ma commune') }
|
||||
it { expect(tdc_commune.libelles_for_export).to match_array([['Ma commune', :value], ['Ma commune (Code INSEE)', :code], ['Ma commune (Département)', :departement]]) }
|
||||
end
|
||||
|
|
282
spec/services/procedure_export_service_tabular_spec.rb
Normal file
282
spec/services/procedure_export_service_tabular_spec.rb
Normal file
|
@ -0,0 +1,282 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'csv'
|
||||
|
||||
describe ProcedureExportService do
|
||||
let(:instructeur) { create(:instructeur) }
|
||||
let(:procedure) { create(:procedure, types_de_champ_public:, for_individual:, ask_birthday: true, instructeurs: [instructeur]) }
|
||||
let(:service) { ProcedureExportService.new(procedure, procedure.dossiers, instructeur, export_template) }
|
||||
let(:for_individual) { true }
|
||||
let(:types_de_champ_public) do
|
||||
[
|
||||
{ type: :text, libelle: "first champ", mandatory: true, stable_id: 1 },
|
||||
{ type: :communes, libelle: "Commune", mandatory: true, stable_id: 17 },
|
||||
{ type: :piece_justificative, libelle: "PJ", stable_id: 30 },
|
||||
{
|
||||
type: :repetition, mandatory: true, stable_id: 7, libelle: "Champ répétable", children:
|
||||
[
|
||||
{ type: 'text', libelle: 'child first champ', stable_id: 8 },
|
||||
{ type: 'text', libelle: 'child second champ', stable_id: 9 }
|
||||
]
|
||||
}
|
||||
]
|
||||
end
|
||||
let(:exported_columns) { [] }
|
||||
|
||||
describe 'to_xlsx' do
|
||||
let(:kind) { 'xlsx' }
|
||||
let(:export_template) { create(:export_template, kind:, exported_columns:, groupe_instructeur: procedure.defaut_groupe_instructeur) }
|
||||
let(:dossiers_sheet) { subject.sheets.first }
|
||||
let(:etablissements_sheet) { subject.sheets.second }
|
||||
let(:avis_sheet) { subject.sheets.third }
|
||||
let(:repetition_sheet) { subject.sheets.fourth }
|
||||
|
||||
subject do
|
||||
service
|
||||
.to_xlsx
|
||||
.open { |f| SimpleXlsxReader.open(f.path) }
|
||||
end
|
||||
|
||||
describe 'sheets' do
|
||||
it 'should have a sheet for each record type' do
|
||||
expect(subject.sheets.map(&:name)).to eq(['Dossiers', 'Etablissements', 'Avis'])
|
||||
end
|
||||
end
|
||||
|
||||
describe 'Dossiers sheet' do
|
||||
context 'with multiple columns' do
|
||||
let(:exported_columns) do
|
||||
[
|
||||
ExportedColumn.new(libelle: 'Date du dernier évènement', column: procedure.find_column(label: 'Date du dernier évènement')),
|
||||
ExportedColumn.new(libelle: 'Email', column: procedure.find_column(label: 'Email')),
|
||||
ExportedColumn.new(libelle: 'Groupe instructeur', column: procedure.find_column(label: 'Groupe instructeur')),
|
||||
ExportedColumn.new(libelle: 'État du dossier', column: procedure.dossier_state_column),
|
||||
ExportedColumn.new(libelle: 'first champ', column: procedure.find_column(label: 'first champ')),
|
||||
ExportedColumn.new(libelle: 'Commune', column: procedure.find_column(label: 'Commune')),
|
||||
ExportedColumn.new(libelle: 'PJ', column: procedure.find_column(label: 'PJ'))
|
||||
]
|
||||
end
|
||||
|
||||
let!(:dossier) { create(:dossier, :en_instruction, :with_populated_champs, :with_individual, procedure: procedure) }
|
||||
let(:selected_headers) { ["Email", "first champ", "Commune", "Groupe instructeur", "Date du dernier évènement", "État du dossier", "PJ"] }
|
||||
|
||||
it 'should have only headers from export template' do
|
||||
expect(dossiers_sheet.headers).to match_array(selected_headers)
|
||||
end
|
||||
|
||||
it 'should have data' do
|
||||
expect(procedure.dossiers.count).to eq 1
|
||||
expect(dossiers_sheet.data.size).to eq 1
|
||||
|
||||
expect(dossiers_sheet.data).to match_array([[anything, dossier.user_email_for_display, "défaut", "En instruction", "text", "Coye-la-Forêt", "toto.txt"]])
|
||||
end
|
||||
end
|
||||
|
||||
context 'with multiple groupe instructeur' do
|
||||
let(:exported_columns) { [ExportedColumn.new(libelle: 'Groupe instructeur', column: procedure.find_column(label: 'Groupe instructeur'))] }
|
||||
let(:types_de_champ_public) { [] }
|
||||
|
||||
before do
|
||||
create(:groupe_instructeur, label: '2', procedure:)
|
||||
create(:dossier, :en_instruction, procedure:)
|
||||
end
|
||||
|
||||
it 'find groupe instructeur data' do
|
||||
expect(dossiers_sheet.headers).to include('Groupe instructeur')
|
||||
expect(dossiers_sheet.data[0][dossiers_sheet.headers.index('Groupe instructeur')]).to eq('défaut')
|
||||
end
|
||||
end
|
||||
|
||||
context 'with multiple pjs' do
|
||||
let(:types_de_champ_public) { [{ type: :piece_justificative, libelle: "PJ" }] }
|
||||
let(:exported_columns) { [ExportedColumn.new(libelle: 'PJ', column: procedure.find_column(label: 'PJ'))] }
|
||||
before do
|
||||
dossier = create(:dossier, :en_instruction, :with_populated_champs, procedure:)
|
||||
dossier.filled_champs_public
|
||||
.find { _1.is_a? Champs::PieceJustificativeChamp }
|
||||
.piece_justificative_file
|
||||
.attach(io: StringIO.new("toto"), filename: "toto.txt", content_type: "text/plain")
|
||||
end
|
||||
it { expect(dossiers_sheet.data.last.last).to eq "toto.txt, toto.txt" }
|
||||
end
|
||||
|
||||
context 'with TypeDeChamp::MutlipleDropDownListTypeDeChamp' do
|
||||
let(:types_de_champ_public) { [{ type: :multiple_drop_down_list, libelle: "multiple_drop_down_list", mandatory: true }] }
|
||||
let(:exported_columns) { [ExportedColumn.new(libelle: 'Date du dernier évènement', column: procedure.find_column(label: 'multiple_drop_down_list'))] }
|
||||
before { create(:dossier, :with_populated_champs, procedure:) }
|
||||
it { expect(dossiers_sheet.data.last.last).to eq "val1, val2" }
|
||||
end
|
||||
|
||||
context 'with TypeDeChamp:YesNoTypeDeChamp' do
|
||||
let(:types_de_champ_public) { [{ type: :yes_no, libelle: "yes_no", mandatory: true }] }
|
||||
let(:exported_columns) { [ExportedColumn.new(libelle: 'yes_no', column: procedure.find_column(label: 'yes_no'))] }
|
||||
before { create(:dossier, :with_populated_champs, procedure:) }
|
||||
it { expect(dossiers_sheet.data.last.last).to eq true }
|
||||
end
|
||||
|
||||
context 'with TypeDeChamp:CheckboxTypeDeChamp' do
|
||||
let(:types_de_champ_public) { [{ type: :checkbox, libelle: "checkbox", mandatory: true }] }
|
||||
let(:exported_columns) { [ExportedColumn.new(libelle: 'checkbox', column: procedure.find_column(label: 'checkbox'))] }
|
||||
before { create(:dossier, :with_populated_champs, procedure:) }
|
||||
it { expect(dossiers_sheet.data.last.last).to eq true }
|
||||
end
|
||||
|
||||
context 'with TypeDeChamp:DecimalNumberTypeDeChamp' do
|
||||
let(:types_de_champ_public) { [{ type: :decimal_number, libelle: "decimal", mandatory: true }] }
|
||||
let(:exported_columns) { [ExportedColumn.new(libelle: 'decimal', column: procedure.find_column(label: 'decimal'))] }
|
||||
before { create(:dossier, :with_populated_champs, procedure:) }
|
||||
it { expect(dossiers_sheet.data.last.last).to eq 42.1 }
|
||||
end
|
||||
|
||||
context 'with TypeDeChamp:IntegerNumberTypeDeChamp' do
|
||||
let(:types_de_champ_public) { [{ type: :integer_number, libelle: "integer", mandatory: true }] }
|
||||
let(:exported_columns) { [ExportedColumn.new(libelle: 'integer', column: procedure.find_column(label: 'integer'))] }
|
||||
before { create(:dossier, :with_populated_champs, procedure:) }
|
||||
it { expect(dossiers_sheet.data.last.last).to eq 42.0 }
|
||||
end
|
||||
|
||||
context 'with TypesDeChamp::LinkedDropDownListTypeDeChamp' do
|
||||
let(:types_de_champ_public) { [{ type: :linked_drop_down_list, libelle: "linked_drop_down_list", mandatory: true }] }
|
||||
let(:exported_columns) { [ExportedColumn.new(libelle: 'linked_drop_down_list', column: procedure.find_column(label: 'linked_drop_down_list'))] }
|
||||
before { create(:dossier, :with_populated_champs, procedure:) }
|
||||
it { expect(dossiers_sheet.data.last.last).to eq "primary / secondary" }
|
||||
end
|
||||
|
||||
context 'with TypesDeChamp::DateTimeTypeDeChamp' do
|
||||
let(:types_de_champ_public) { [{ type: :datetime, libelle: "datetime", mandatory: true }] }
|
||||
let(:exported_columns) { [ExportedColumn.new(libelle: 'datetime', column: procedure.find_column(label: 'datetime'))] }
|
||||
let(:dossier) { create(:dossier, :with_populated_champs, procedure:) }
|
||||
before { dossier }
|
||||
it do
|
||||
champ_value = Time.zone.parse(dossier.champs.first.value)
|
||||
offset = champ_value.utc_offset
|
||||
sheet_value = Time.zone.at(dossiers_sheet.data.last.last - offset.seconds)
|
||||
expect(sheet_value).to eq(champ_value.round)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with TypesDeChamp::Date' do
|
||||
let(:types_de_champ_public) { [{ type: :date, libelle: "date", mandatory: true }] }
|
||||
let(:exported_columns) { [ExportedColumn.new(libelle: 'date', column: procedure.find_column(label: 'date'))] }
|
||||
before { create(:dossier, :with_populated_champs, procedure:) }
|
||||
it { expect(dossiers_sheet.data.last.last).to be_an_instance_of(Date) }
|
||||
end
|
||||
|
||||
context 'with DossierColumn as datetime' do
|
||||
let(:types_de_champ_public) { [] }
|
||||
let(:exported_columns) { [ExportedColumn.new(libelle: 'Date de passage en instruction', column: procedure.find_column(label: 'Date de passage en instruction'))] }
|
||||
before { create(:dossier, :en_instruction, :with_populated_champs, procedure:) }
|
||||
it { expect(dossiers_sheet.data.last.last).to be_an_instance_of(Time) }
|
||||
end
|
||||
end
|
||||
|
||||
describe 'Etablissement sheet' do
|
||||
let(:types_de_champ_public) { [{ type: :siret, libelle: 'siret', stable_id: 40 }] }
|
||||
let(:exported_columns) do
|
||||
[
|
||||
ExportedColumn.new(libelle: "Nº dossier", column: procedure.find_column(label: "Nº dossier")),
|
||||
ExportedColumn.new(libelle: "Demandeur", column: procedure.find_column(label: "Demandeur")),
|
||||
ExportedColumn.new(libelle: "siret", column: procedure.find_column(label: "siret"))
|
||||
]
|
||||
end
|
||||
let(:procedure) { create(:procedure, :published, types_de_champ_public:) }
|
||||
let!(:dossier) { create(:dossier, :en_instruction, :with_populated_champs, :with_entreprise, procedure: procedure) }
|
||||
|
||||
let(:dossier_etablissement) { etablissements_sheet.data[1] }
|
||||
let(:champ_etablissement) { etablissements_sheet.data[0] }
|
||||
|
||||
it 'should have siret header in dossiers sheet' do
|
||||
expect(dossiers_sheet.headers).to include('siret')
|
||||
end
|
||||
|
||||
it 'should have headers in etablissement sheet' do
|
||||
expect(etablissements_sheet.headers).to eq([
|
||||
"Dossier ID",
|
||||
"Champ",
|
||||
"Établissement SIRET",
|
||||
"Etablissement enseigne",
|
||||
"É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 état administratif",
|
||||
"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
|
||||
|
||||
describe 'Avis sheet' do
|
||||
let!(:dossier) { create(:dossier, :en_instruction, :with_populated_champs, :with_individual, procedure: procedure) }
|
||||
let!(:avis) { create(:avis, :with_answer, dossier: dossier) }
|
||||
|
||||
it 'should have headers and data' do
|
||||
expect(avis_sheet.headers).to eq([
|
||||
"Dossier ID",
|
||||
"Introduction",
|
||||
"Réponse",
|
||||
"Question",
|
||||
"Réponse oui/non",
|
||||
"Créé le",
|
||||
"Répondu le",
|
||||
"Instructeur",
|
||||
"Expert"
|
||||
])
|
||||
expect(avis_sheet.data.size).to eq(1)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'Repetitions sheet' do
|
||||
let(:exported_columns) do
|
||||
[
|
||||
ExportedColumn.new(libelle: "Champ répétable – child second champ", column: procedure.find_column(label: "Champ répétable – child second champ"))
|
||||
]
|
||||
end
|
||||
let!(:dossiers) do
|
||||
[
|
||||
create(:dossier, :en_instruction, :with_populated_champs, :with_individual, procedure: procedure),
|
||||
create(:dossier, :en_instruction, :with_populated_champs, :with_individual, procedure: procedure)
|
||||
]
|
||||
end
|
||||
|
||||
describe 'sheets' do
|
||||
it 'should have a sheet for repetition' do
|
||||
expect(subject.sheets.map(&:name)).to eq(['Dossiers', 'Etablissements', 'Avis', '(7) Champ repetable'])
|
||||
end
|
||||
end
|
||||
|
||||
it 'should have headers' do
|
||||
expect(repetition_sheet.headers).to eq([
|
||||
"Dossier ID", "Ligne", "Champ répétable – child second champ"
|
||||
])
|
||||
end
|
||||
|
||||
it 'should have data' do
|
||||
expect(repetition_sheet.data.size).to eq 4
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -41,7 +41,10 @@ describe 'Creating a new procedure', js: true do
|
|||
click_on "Télécharger tous les dossiers"
|
||||
|
||||
expect {
|
||||
click_on "Demander un export au format .xlsx"
|
||||
within(:css, '#tabpanel-standard-panel') do
|
||||
choose "Fichier xlsx", allow_label_click: true
|
||||
click_on "Demander l'export"
|
||||
end
|
||||
expect(page).to have_content("Nous générons cet export. Veuillez revenir dans quelques minutes pour le télécharger.")
|
||||
}.to have_enqueued_job(ExportJob).with(an_instance_of(Export))
|
||||
end
|
||||
|
|
|
@ -132,13 +132,14 @@ describe 'Instructing a dossier:', js: true do
|
|||
test_statut_bar(a_suivre: 1, tous_les_dossiers: 1)
|
||||
|
||||
click_on "Télécharger un dossier"
|
||||
within(:css, '.dossiers-export') do
|
||||
click_on "Demander un export au format .csv"
|
||||
within(:css, '#tabpanel-standard1-panel') do
|
||||
choose "Fichier csv", allow_label_click: true
|
||||
click_on "Demander l'export"
|
||||
end
|
||||
|
||||
expect(page).to have_text('Nous générons cet export.')
|
||||
|
||||
click_on "Voir les exports"
|
||||
click_on "Voir les exports et modèles d'export"
|
||||
expect(page).to have_text("Export .csv d’un dossier « à suivre » demandé il y a moins d'une minute")
|
||||
expect(page).to have_text("En préparation")
|
||||
|
||||
|
|
58
spec/system/instructeurs/procedure_export_tabular_spec.rb
Normal file
58
spec/system/instructeurs/procedure_export_tabular_spec.rb
Normal file
|
@ -0,0 +1,58 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
describe "procedure exports" do
|
||||
let(:instructeur) { create(:instructeur) }
|
||||
let(:procedure) { create(:procedure, :published, types_de_champ_public:, instructeurs: [instructeur]) }
|
||||
let(:types_de_champ_public) { [{ type: :text }] }
|
||||
before { login_as(instructeur.user, scope: :user) }
|
||||
|
||||
scenario "create an export_template tabular and u", js: true do
|
||||
Flipper.enable(:export_template, procedure)
|
||||
visit instructeur_procedure_path(procedure)
|
||||
|
||||
click_on "Voir les exports et modèles d'export"
|
||||
|
||||
click_on "Modèles d'export"
|
||||
|
||||
click_on "Créer un modèle d'export tabulaire"
|
||||
|
||||
fill_in "Nom du modèle", with: "Mon modèle"
|
||||
|
||||
find("#informations-usager-fieldset label", text: "Tout sélectionner").click
|
||||
within '#informations-usager-fieldset' do
|
||||
expect(all('input[type=checkbox]').all?(&:checked?)).to be_truthy
|
||||
end
|
||||
|
||||
find("#informations-dossier-fieldset label", text: "Tout sélectionner").click
|
||||
within '#informations-dossier-fieldset' do
|
||||
expect(all('input[type=checkbox]').all?(&:checked?)).to be_truthy
|
||||
end
|
||||
|
||||
click_on "Enregistrer"
|
||||
|
||||
find("#tabpanel-export-templates", wait: 5, visible: true)
|
||||
find("#tabpanel-export-templates").click
|
||||
|
||||
within 'table' do
|
||||
expect(page).to have_content('Mon modèle')
|
||||
end
|
||||
|
||||
# check if all usager colonnes are selected
|
||||
#
|
||||
click_on 'Mon modèle'
|
||||
|
||||
within '#informations-usager-fieldset' do
|
||||
expect(all('input[type=checkbox]').all?(&:checked?)).to be_truthy
|
||||
end
|
||||
|
||||
within '#informations-dossier-fieldset' do
|
||||
expect(all('input[type=checkbox]').all?(&:checked?)).to be_truthy
|
||||
end
|
||||
|
||||
# uncheck checkboxes
|
||||
find("#informations-dossier-fieldset label", text: "Tout sélectionner").click
|
||||
within '#informations-dossier-fieldset' do
|
||||
expect(all('input[type=checkbox]').none?(&:checked?)).to be_truthy
|
||||
end
|
||||
end
|
||||
end
|
|
@ -209,7 +209,7 @@ describe 'The routing with rules', js: true do
|
|||
## on the dossiers list
|
||||
click_on procedure.libelle
|
||||
expect(page).to have_current_path(instructeur_procedure_path(procedure))
|
||||
expect(find('.fr-tabs')).to have_css('span.notifications')
|
||||
expect(find('nav.fr-tabs')).to have_css('span.notifications')
|
||||
|
||||
## on the dossier itself
|
||||
click_on 'suivi'
|
||||
|
|
|
@ -20,7 +20,7 @@ module Maintenance
|
|||
end
|
||||
it 'updates value_json' do
|
||||
expect { subject }.to change { element.reload.value_json }
|
||||
.from(nil)
|
||||
.from(anything)
|
||||
.to({
|
||||
"street_number" => "33",
|
||||
"street_name" => "de Modagor",
|
||||
|
|
|
@ -53,7 +53,7 @@ module Maintenance
|
|||
|
||||
it 'updates value_json' do
|
||||
expect { subject }.to change { element.reload.value_json }
|
||||
.from(nil)
|
||||
.from(anything)
|
||||
.to({
|
||||
"street_number" => "16",
|
||||
"street_name" => "Rue du Général de Boissieu",
|
||||
|
@ -79,7 +79,7 @@ module Maintenance
|
|||
|
||||
it 'updates value_json' do
|
||||
expect { subject }.to change { element.reload.value_json }
|
||||
.from(nil)
|
||||
.from(anything)
|
||||
.to({
|
||||
"street_number" => "16",
|
||||
"street_name" => "Rue du Général de Boissieu",
|
||||
|
|
Loading…
Reference in a new issue