Merge branch 'main' into feature/prefill_repetible

This commit is contained in:
Damien Le Thiec 2023-01-31 16:39:00 +01:00
parent f91cc05d95
commit d7b01255fe
82 changed files with 764 additions and 563 deletions

View file

@ -635,7 +635,7 @@ GEM
nokogiri (>= 1.6.2) nokogiri (>= 1.6.2)
rexml rexml
xmlenc (>= 0.7.1) xmlenc (>= 0.7.1)
sanitize (6.0.0) sanitize (6.0.1)
crass (~> 1.0.2) crass (~> 1.0.2)
nokogiri (>= 1.12.0) nokogiri (>= 1.12.0)
sanitize-url (0.1.4) sanitize-url (0.1.4)

View file

@ -11,7 +11,6 @@
// The procedure description can still be read from the /commencer // The procedure description can still be read from the /commencer
// pages. // pages.
@media (max-width: $two-columns-breakpoint) { @media (max-width: $two-columns-breakpoint) {
.procedure-preview,
.agent-intro { .agent-intro {
display: none; display: none;
} }

View file

@ -9,6 +9,14 @@ $contact-padding: $default-space * 2;
padding-bottom: $contact-padding; padding-bottom: $contact-padding;
} }
.recommandations {
p {
font-size: 1.5rem;
font-weight: bold;
line-height: 1.5rem;
}
}
ul { ul {
margin-bottom: $default-space; margin-bottom: $default-space;
} }

View file

@ -163,6 +163,12 @@
} }
} }
.drop_down_other { // scss-lint:disable SelectorFormat
label {
font-weight: normal;
}
}
input[type=text], input[type=text],
input[type=email], input[type=email],
input[type=password], input[type=password],

View file

@ -44,6 +44,10 @@
position: relative; position: relative;
} }
.dropdown-export .dropdown-content {
width: 450px;
}
.print-menu { .print-menu {
display: none; display: none;
position: absolute; position: absolute;

View file

@ -18,3 +18,9 @@
width: 9rem; width: 9rem;
} }
} }
.fr-footer__top-link p {
margin-bottom: 0;
font-size: 0.75rem;
line-height: 1.25rem;
}

View file

@ -17,7 +17,8 @@ $procedure-description-line-height: 22px;
} }
.simple { .simple {
font-size: 24px; margin-bottom: 0.2rem;
font-size: 1.5rem;
color: $blue-france-500; color: $blue-france-500;
font-weight: bold; font-weight: bold;
} }
@ -143,6 +144,14 @@ $procedure-description-line-height: 22px;
} }
} }
.no-procedure-presentation {
margin-bottom: 1.6rem;
p {
margin: 0;
}
}
.procedure-context-content { .procedure-context-content {
@media (max-width: $procedure-context-breakpoint) { @media (max-width: $procedure-context-breakpoint) {
input[type=submit] { input[type=submit] {

View file

@ -1,21 +1,21 @@
%span.dropdown{ data: { controller: 'menu-button' } } = 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|
%button.fr-btn.fr-btn--sm.dropdown-button{ data: { menu_button_target: 'button' }, class: @class_btn.present? ? @class_btn : 'fr-btn--secondary' } - menu.with_button_inner_html do
- if @count.nil? = @count.nil? ? t(".download_all") : t(".download", count: @count)
= t(".download_all") - exports.each do |item|
- export = item[:export]
- if export.nil?
- menu.with_item do
= link_to download_export_path(export_format: item[:format]), role: 'menuitem', data: { turbo_method: :post, turbo: true } do
= t(".everything_#{item[:format]}_html")
- elsif export.available?
- menu.with_item do
%div
= link_to ready_link_label(export), export.file.service_url, target: "_blank", rel: "noopener", role: 'menuitem'
- if export.old?
= button_to download_export_path(export_format: export.format, force_export: true), refresh_button_options(export).merge(role: 'menuitem') do
.icon.retry
- else - else
= t(".download", count: @count) - menu.with_item(aria: {disabled:"true"}, class: 'selected') do
.dropdown-content.fade-in-down{ style: 'width: 450px', data: { menu_button_target: 'menu' }, id: @count.nil? ? "download_menu" : "download_all_menu" } %span{ data: poll_controller_options(export) }
%ul.dropdown-items{ 'data-turbo': 'true' } = pending_label(export)
- exports.each do |item|
- export = item[:export]
%li
- if export.nil?
= link_to t(".everything_#{item[:format]}_html"), download_export_path(export_format: item[:format]), data: { turbo_method: :post }
- elsif export.available?
= link_to ready_link_label(export), export.file.service_url, target: "_blank", rel: "noopener"
- if export.old?
= button_to download_export_path(export_format: export.format, force_export: true), **refresh_button_options(export) do
.icon.retry
- else
%span{ data: poll_controller_options(export) }
= pending_label(export)

View file

@ -0,0 +1,50 @@
class Dropdown::MenuComponent < ApplicationComponent
renders_one :button_inner_html
# beware, items elements like button_to/link_to must include role: 'menuitem' for aria reason
renders_many :items, -> (options = {}, &block) do
tag.li(**options.merge(role: 'none'), &block)
end
renders_many :forms
def initialize(wrapper:,
wrapper_options: {},
button_options: {},
menu_options: {})
@wrapper = wrapper
@wrapper_options = wrapper_options
@button_options = button_options
@menu_options = menu_options
end
def wrapper_options
@wrapper_options.deep_merge({
class: wrapper_class_names,
data: { controller: 'menu-button' }
})
end
def wrapper_class_names
['dropdown'] + Array(@wrapper_options[:class])
end
def button_id
"#{menu_id}_button"
end
def menu_id
@menu_options[:id] ||= SecureRandom.uuid
@menu_options[:id]
end
def menu_role
forms? ? :region : :menu
end
def menu_class_names
['dropdown-content'] + Array(@menu_options[:class])
end
def button_class_names
['fr-btn', 'dropdown-button'] + Array(@button_options[:class])
end
end

View file

@ -0,0 +1,15 @@
= content_tag(@wrapper, wrapper_options) do
%button{ class: button_class_names, id: button_id, data: { menu_button_target: 'button' }, "aria-expanded": "false", 'aria-haspopup': 'true', 'aria-controls': menu_id }
= button_inner_html
%div{ data: { menu_button_target: 'menu' }, id: menu_id, 'aria-labelledby': button_id, role: menu_role, 'tab-index': -1, class: menu_class_names }
-# the dropdown can be a menu with a list of item
- if items?
%ul.dropdown-items.fr-pl-0{ role: 'none' }
- items.each do |dropdown_item|
= dropdown_item
-# the dropdown can be a menu with forms
- if forms?
- forms.each do |form|
= form

View file

@ -1,4 +1,4 @@
.drop_down_other{ class: @champ.other_value_present? ? '' : 'hidden' } .drop_down_other{ class: @champ.other_value_present? ? '' : 'hidden' }
.notice .notice
%p Veuillez saisir votre autre choix %label{ for: dom_id(@champ, :value_other) } Veuillez saisir votre autre choix
= @form.text_field :value_other, maxlength: 200, size: nil, disabled: !@champ.other_value_present? = @form.text_field :value_other, maxlength: 200, size: nil, id: dom_id(@champ, :value_other), disabled: !@champ.other_value_present?

View file

@ -8,6 +8,7 @@ module Administrateurs
def edit def edit
@attestation_template = build_attestation_template @attestation_template = build_attestation_template
@attestation_template.validate
end end
def update def update

View file

@ -140,7 +140,7 @@ module Experts
end end
def telecharger_pjs def telecharger_pjs
files = ActiveStorage::DownloadableFile.create_list_from_dossiers(Dossier.where(id: @dossier.id), true) files = ActiveStorage::DownloadableFile.create_list_from_dossiers(Dossier.where(id: @dossier.id))
cleaned_files = ActiveStorage::DownloadableFile.cleanup_list_from_dossier(files) cleaned_files = ActiveStorage::DownloadableFile.cleanup_list_from_dossier(files)
zipline(cleaned_files, "dossier-#{@dossier.id}.zip") zipline(cleaned_files, "dossier-#{@dossier.id}.zip")

View file

@ -235,7 +235,7 @@ module Instructeurs
end end
def telecharger_pjs def telecharger_pjs
files = ActiveStorage::DownloadableFile.create_list_from_dossiers(Dossier.where(id: dossier.id), true) files = ActiveStorage::DownloadableFile.create_list_from_dossiers(Dossier.where(id: dossier.id), with_champs_private: true, include_infos_administration: true)
cleaned_files = ActiveStorage::DownloadableFile.cleanup_list_from_dossier(files) cleaned_files = ActiveStorage::DownloadableFile.cleanup_list_from_dossier(files)
zipline(cleaned_files, "dossier-#{dossier.id}.zip") zipline(cleaned_files, "dossier-#{dossier.id}.zip")

View file

@ -26,11 +26,7 @@ module Users
@dossiers_invites = current_user.dossiers_invites.merge(dossiers_visibles) @dossiers_invites = current_user.dossiers_invites.merge(dossiers_visibles)
@dossiers_supprimes_recemment = current_user.dossiers.hidden_by_user.merge(dossiers) @dossiers_supprimes_recemment = current_user.dossiers.hidden_by_user.merge(dossiers)
@dossiers_supprimes_definitivement = current_user.deleted_dossiers.order_by_updated_at.page(page) @dossiers_supprimes_definitivement = current_user.deleted_dossiers.order_by_updated_at.page(page)
@dossier_transfers = DossierTransfer @dossier_transfers = DossierTransfer.for_email(current_user.email).page(page)
.includes(dossiers: :user)
.with_dossiers
.where(email: current_user.email)
.page(page)
@statut = statut(@user_dossiers, @dossiers_traites, @dossiers_invites, @dossiers_supprimes_recemment, @dossiers_supprimes_definitivement, @dossier_transfers, @dossiers_close_to_expiration, params[:statut]) @statut = statut(@user_dossiers, @dossiers_traites, @dossiers_invites, @dossiers_supprimes_recemment, @dossiers_supprimes_definitivement, @dossier_transfers, @dossiers_close_to_expiration, params[:statut])
end end

View file

@ -17,8 +17,11 @@ export function useDeferredSubmit(input?: HTMLInputElement): {
runCallback(); runCallback();
if ( if (
!Array.from(form.elements).some((e) => !Array.from(form.elements).some(
e.hasAttribute('data-direct-upload-url') (e) =>
e.hasAttribute('data-direct-upload-url') &&
'value' in e &&
e.value != ''
) )
) { ) {
form.submit(); form.submit();

View file

@ -65,7 +65,10 @@ export class ApplicationController extends Controller {
FOCUS_EVENTS.includes(targetOrEventName) FOCUS_EVENTS.includes(targetOrEventName)
); );
} else { } else {
invariant(eventNameOrHandler == 'string', 'event name is required'); invariant(
typeof eventNameOrHandler == 'string',
'event name is required'
);
invariant(handler, 'handler is required'); invariant(handler, 'handler is required');
this.onTarget(targetOrEventName, eventNameOrHandler, handler); this.onTarget(targetOrEventName, eventNameOrHandler, handler);
} }

View file

@ -89,9 +89,12 @@ export class AutosaveController extends ApplicationController {
isCheckboxOrRadioInputElement(target) || isCheckboxOrRadioInputElement(target) ||
(!this.saveOnInput && isTextInputElement(target)) (!this.saveOnInput && isTextInputElement(target))
) { ) {
this.enqueueAutosaveRequest(); // Wait next tick so champs having JS can interact
// with form elements before extracting form data.
this.showConditionnalSpinner(target); setTimeout(() => {
this.enqueueAutosaveRequest();
this.showConditionnalSpinner(target);
}, 0);
} }
} }
} }

View file

@ -34,6 +34,7 @@ export class ChampDropdownController extends ApplicationController {
if (target.value == '__other__') { if (target.value == '__other__') {
show(inputGroup); show(inputGroup);
input.disabled = false; input.disabled = false;
input.focus();
} else { } else {
hide(inputGroup); hide(inputGroup);
input.disabled = true; input.disabled = true;

View file

@ -6,53 +6,26 @@ export class MenuButtonController extends ApplicationController {
declare readonly buttonTarget: HTMLButtonElement; declare readonly buttonTarget: HTMLButtonElement;
declare readonly menuTarget: HTMLElement; declare readonly menuTarget: HTMLElement;
#teardown?: () => void;
connect() { connect() {
this.setup(); this.setup();
} }
disconnect(): void {
this.#teardown?.();
}
private get isOpen() { private get isOpen() {
return (this.element as HTMLElement).classList.contains('open'); return (this.element as HTMLElement).classList.contains('open');
} }
private get isMenu() { private get isMenu() {
return !(this.element as HTMLElement).dataset.popover; return this.menuTarget.getAttribute('role') == 'menu';
} }
private setup() { private setup() {
this.buttonTarget.setAttribute( // see:
'aria-haspopup', // To progressively enhance this navigation widget that is by default accessible,
this.isMenu ? 'menu' : 'true' // the class to hide the menu and the inclusion of tabindex="-1" on the interactive menuitem
); // content should be added with JavaScript on load.
this.buttonTarget.setAttribute('aria-controls', this.menuTarget.id);
if (!this.buttonTarget.id) {
this.buttonTarget.id = `${this.menuTarget.id}_button`;
}
this.menuTarget.setAttribute('aria-labelledby', this.buttonTarget.id);
this.menuTarget.setAttribute('role', this.isMenu ? 'menu' : 'region');
this.menuTarget.classList.add('fade-in-down'); this.menuTarget.classList.add('fade-in-down');
this.menuTarget.setAttribute('tab-index', '-1');
if (this.isMenu) { if (this.isMenu) {
for (const menuItem of this.menuTarget.querySelectorAll('a')) { this.menuItems.map((menuItem) => menuItem.setAttribute('tabindex', '-1'));
menuItem.setAttribute('role', 'menuitem');
}
for (const dropdownItems of this.menuTarget.querySelectorAll(
'.dropdown-items'
)) {
dropdownItems.setAttribute('role', 'none');
}
for (const dropdownItems of this.menuTarget.querySelectorAll(
'.dropdown-items > li'
)) {
dropdownItems.setAttribute('role', 'none');
}
} }
this.on('click', (event) => { this.on('click', (event) => {
@ -78,6 +51,14 @@ export class MenuButtonController extends ApplicationController {
this.onMenuKeydown(event); this.onMenuKeydown(event);
} }
}); });
this.on(document.body, 'click', (event) => {
const target = event.target as HTMLElement;
if (this.isOpen && this.isClickOutside(target)) {
this.menuTarget.classList.remove('fade-in-down');
this.close();
}
});
} }
private open(focusMenuItem: 'first' | 'last' = 'first') { private open(focusMenuItem: 'first' | 'last' = 'first') {
@ -85,30 +66,18 @@ export class MenuButtonController extends ApplicationController {
this.menuTarget.parentElement?.classList.add('open'); this.menuTarget.parentElement?.classList.add('open');
this.menuTarget.focus(); this.menuTarget.focus();
const onClickBody = (event: Event) => {
const target = event.target as HTMLElement;
if (this.isClickOutside(target)) {
this.menuTarget.classList.remove('fade-in-down');
this.close();
}
};
requestAnimationFrame(() => { requestAnimationFrame(() => {
if (focusMenuItem == 'first') { if (focusMenuItem == 'first') {
this.setFocusToFirstMenuitem(); this.setFocusToFirstMenuitem();
} else { } else {
this.setFocusToLastMenuitem(); this.setFocusToLastMenuitem();
} }
document.body.addEventListener('click', onClickBody);
}); });
this.#teardown = () =>
document.body.removeEventListener('click', onClickBody);
} }
private close() { private close() {
this.buttonTarget.removeAttribute('aria-expanded'); this.buttonTarget.setAttribute('aria-expanded', 'false');
this.menuTarget.parentElement?.classList.remove('open'); this.menuTarget.parentElement?.classList.remove('open');
this.#teardown?.();
this.setFocusToMenuitem(null); this.setFocusToMenuitem(null);
} }

View file

@ -1,7 +1,7 @@
class ActiveStorage::DownloadableFile class ActiveStorage::DownloadableFile
def self.create_list_from_dossiers(dossiers, for_expert = false) def self.create_list_from_dossiers(dossiers, with_bills: false, with_champs_private: false, include_infos_administration: false)
PiecesJustificativesService.generate_dossier_export(dossiers, include_infos_administration: !for_expert) + PiecesJustificativesService.generate_dossier_export(dossiers, include_infos_administration:) +
PiecesJustificativesService.liste_documents(dossiers, for_expert) PiecesJustificativesService.liste_documents(dossiers, with_bills:, with_champs_private:)
end end
def self.cleanup_list_from_dossier(files) def self.cleanup_list_from_dossier(files)

View file

@ -8,6 +8,7 @@
# confidentiel :boolean default(FALSE), not null # confidentiel :boolean default(FALSE), not null
# email :string # email :string
# introduction :text # introduction :text
# reminded_at :datetime
# revoked_at :datetime # revoked_at :datetime
# created_at :datetime not null # created_at :datetime not null
# updated_at :datetime not null # updated_at :datetime not null

View file

@ -226,7 +226,7 @@ class Champ < ApplicationRecord
private private
def html_id def html_id
"#{stable_id}-#{id}" "champ-#{stable_id}-#{id}"
end end
def needs_dossier_id? def needs_dossier_id?
@ -238,7 +238,7 @@ class Champ < ApplicationRecord
end end
def cleanup_if_empty def cleanup_if_empty
if external_id_changed? if persisted? && external_id_changed?
self.data = nil self.data = nil
end end
end end

View file

@ -39,7 +39,7 @@ class Champs::DateChamp < Champ
private private
def convert_to_iso8601 def convert_to_iso8601
return if valid_iso8601? return if likely_iso8601_format? && parsable_iso8601?
self.value = if /^\d{2}\/\d{2}\/\d{4}$/.match?(value) self.value = if /^\d{2}\/\d{2}\/\d{4}$/.match?(value)
Date.parse(value).iso8601 Date.parse(value).iso8601
@ -49,12 +49,20 @@ class Champs::DateChamp < Champ
end end
def iso_8601 def iso_8601
return if valid_iso8601? || value.blank? return if parsable_iso8601? || value.blank?
# i18n-tasks-use t('errors.messages.not_a_date') # i18n-tasks-use t('errors.messages.not_a_date')
errors.add :date, errors.generate_message(:value, :not_a_date) errors.add :date, errors.generate_message(:value, :not_a_date)
end end
def valid_iso8601? def likely_iso8601_format?
/^\d{4}-\d{2}-\d{2}$/.match?(value) /^\d{4}-\d{2}-\d{2}$/.match?(value)
end end
def parsable_iso8601?
Date.parse(value)
true
rescue ArgumentError, # case 2023-27-02, out of range
TypeError # nil
false
end
end end

View file

@ -54,32 +54,32 @@ module DossierRebaseConcern
.group_by(&:op) .group_by(&:op)
.tap { _1.default = [] } .tap { _1.default = [] }
champs_by_stable_id = champs
.joins(:type_de_champ)
.group_by(&:stable_id)
.transform_values { Champ.where(id: _1) }
# add champ # add champ
changes_by_op[:add] changes_by_op[:add]
.map(&:stable_id) .each { add_new_champs_for_revision(target_coordinates_by_stable_id[_1.stable_id]) }
.map { target_coordinates_by_stable_id[_1] }
.each { add_new_champs_for_revision(_1) }
# remove champ # remove champ
changes_by_op[:remove] changes_by_op[:remove]
.each { delete_champs_for_revision(_1.stable_id) } .each { champs_by_stable_id[_1.stable_id].destroy_all }
if brouillon? if brouillon?
changes_by_op[:update] changes_by_op[:update]
.map { |change| [change, champs.joins(:type_de_champ).where(type_de_champ: { stable_id: change.stable_id })] } .each { apply(_1, champs_by_stable_id[_1.stable_id]) }
.each { |change, champs| apply(change, champs) }
end end
# due to repetition tdc clone on update or erase # due to repetition tdc clone on update or erase
# we must reassign tdc to the latest version # we must reassign tdc to the latest version
Champ champs_by_stable_id
.includes(:type_de_champ) .filter_map { |stable_id, champs| [target_coordinates_by_stable_id[stable_id].type_de_champ_id, champs] if champs.present? }
.where(dossier: self) .each { |type_de_champ_id, champs| champs.update_all(type_de_champ_id:) }
.map { [_1, target_coordinates_by_stable_id[_1.stable_id].type_de_champ] }
.each { |champ, target_tdc| champ.update_columns(type_de_champ_id: target_tdc.id, rebased_at: Time.zone.now) }
# update dossier revision # update dossier revision
self.update_column(:revision_id, target_revision.id) update_column(:revision_id, target_revision.id)
end end
def apply(change, champs) def apply(change, champs)
@ -92,18 +92,27 @@ module DossierRebaseConcern
value: nil, value: nil,
value_json: nil, value_json: nil,
external_id: nil, external_id: nil,
data: nil) data: nil,
rebased_at: Time.zone.now)
when :drop_down_options when :drop_down_options
# we are removing options, we need to remove the value if it contains one of the removed options # we are removing options, we need to remove the value if it contains one of the removed options
removed_options = change.from - change.to removed_options = change.from - change.to
if removed_options.present? && champs.any? { _1.in?(removed_options) } if removed_options.present? && champs.any? { _1.in?(removed_options) }
champs.filter { _1.in?(removed_options) }.each { _1.remove_option(removed_options) } champs.filter { _1.in?(removed_options) }.each do
_1.remove_option(removed_options)
_1.update_column(:rebased_at, Time.zone.now)
end
end end
when :carte_layers when :carte_layers
# if we are removing cadastres layer, we need to remove cadastre geo areas # if we are removing cadastres layer, we need to remove cadastre geo areas
if change.from.include?(:cadastres) && !change.to.include?(:cadastres) if change.from.include?(:cadastres) && !change.to.include?(:cadastres)
champs.each { _1.cadastres.each(&:destroy) } champs.filter { _1.cadastres.present? }.each do
_1.cadastres.each(&:destroy)
_1.update_column(:rebased_at, Time.zone.now)
end
end end
else
champs.update_all(rebased_at: Time.zone.now)
end end
end end
@ -126,20 +135,13 @@ module DossierRebaseConcern
end end
def create_champ(target_coordinate, parent, row_id: nil) def create_champ(target_coordinate, parent, row_id: nil)
params = { revision: target_coordinate.revision, row_id: }.compact params = { revision: target_coordinate.revision, rebased_at: Time.zone.now, row_id: }.compact
champ = target_coordinate champ = target_coordinate
.type_de_champ .type_de_champ
.build_champ(params) .build_champ(params)
parent.champs << champ parent.champs << champ
end end
def delete_champs_for_revision(stable_id)
champs
.joins(:type_de_champ)
.where(types_de_champ: { stable_id: })
.destroy_all
end
def purge_piece_justificative_file(champ) def purge_piece_justificative_file(champ)
ActiveStorage::Attachment.where(id: champ.piece_justificative_file.ids).delete_all ActiveStorage::Attachment.where(id: champ.piece_justificative_file.ids).delete_all
end end

View file

@ -1184,7 +1184,7 @@ class Dossier < ApplicationRecord
end end
def geo_data? def geo_data?
geo_areas.present? GeoArea.exists?(champ_id: champs_public.ids + champs_private.ids)
end end
def to_feature_collection def to_feature_collection

View file

@ -8,15 +8,18 @@
# updated_at :datetime not null # updated_at :datetime not null
# #
class DossierTransfer < ApplicationRecord class DossierTransfer < ApplicationRecord
include EmailSanitizableConcern
has_many :dossiers, dependent: :nullify has_many :dossiers, dependent: :nullify
EXPIRATION_LIMIT = 2.weeks EXPIRATION_LIMIT = 2.weeks
validates :email, format: { with: Devise.email_regexp } validates :email, format: { with: Devise.email_regexp }
before_validation -> { sanitize_email(:email) }
scope :pending, -> { where('created_at > ?', (Time.zone.now - EXPIRATION_LIMIT)) } scope :pending, -> { where('created_at > ?', (Time.zone.now - EXPIRATION_LIMIT)) }
scope :stale, -> { where('created_at < ?', (Time.zone.now - EXPIRATION_LIMIT)) } scope :stale, -> { where('created_at < ?', (Time.zone.now - EXPIRATION_LIMIT)) }
scope :with_dossiers, -> { joins(:dossiers).merge(Dossier.visible_by_user) } scope :with_dossiers, -> { joins(:dossiers).merge(Dossier.visible_by_user) }
scope :for_email, -> (email) { includes(dossiers: :user).with_dossiers.where(email: email) }
after_create_commit :send_notification after_create_commit :send_notification

View file

@ -330,8 +330,11 @@ class ProcedurePresentation < ApplicationRecord
if field['scope'].present? if field['scope'].present?
I18n.t(field['scope']).map(&:to_a).map(&:reverse) I18n.t(field['scope']).map(&:to_a).map(&:reverse)
elsif field['table'] == 'groupe_instructeur' elsif field['table'] == 'groupe_instructeur'
instructeur.groupe_instructeurs instructeur.groupe_instructeurs.filter_map do
.map { [_1.label, _1.id] } if _1.procedure_id == procedure.id
[_1.label, _1.id]
end
end
end end
end end

View file

@ -243,7 +243,7 @@ class TypeDeChamp < ApplicationRecord
end end
def only_present_on_draft? def only_present_on_draft?
revisions.size == 1 revisions.one? && revisions.first.draft?
end end
def drop_down_other? def drop_down_other?

View file

@ -1,13 +1,13 @@
class PiecesJustificativesService class PiecesJustificativesService
def self.liste_documents(dossiers, for_expert) def self.liste_documents(dossiers, with_bills:, with_champs_private:)
bill_ids = [] bill_ids = []
docs = dossiers.in_batches.flat_map do |batch| docs = dossiers.in_batches.flat_map do |batch|
pjs = pjs_for_champs(batch, for_expert) + pjs = pjs_for_champs(batch, with_champs_private:) +
pjs_for_commentaires(batch) + pjs_for_commentaires(batch) +
pjs_for_dossier(batch) pjs_for_dossier(batch)
if !for_expert if with_bills
# some bills are shared among operations # some bills are shared among operations
# so first, all the bill_ids are fetched # so first, all the bill_ids are fetched
operation_logs, some_bill_ids = operation_logs_and_signature_ids(batch) operation_logs, some_bill_ids = operation_logs_and_signature_ids(batch)
@ -19,7 +19,7 @@ class PiecesJustificativesService
pjs pjs
end end
if !for_expert if with_bills
# then the bills are retrieved without duplication # then the bills are retrieved without duplication
docs += signatures(bill_ids.uniq) docs += signatures(bill_ids.uniq)
end end
@ -117,12 +117,12 @@ class PiecesJustificativesService
private private
def self.pjs_for_champs(dossiers, for_expert = false) def self.pjs_for_champs(dossiers, with_champs_private:)
champs = Champ champs = Champ
.joins(:piece_justificative_file_attachments) .joins(:piece_justificative_file_attachments)
.where(type: "Champs::PieceJustificativeChamp", dossier: dossiers) .where(type: "Champs::PieceJustificativeChamp", dossier: dossiers)
if for_expert if !with_champs_private
champs = champs.where(private: false) champs = champs.where(private: false)
end end

View file

@ -15,7 +15,7 @@ class ProcedureArchiveService
dossiers.processed_in_month(archive.month) dossiers.processed_in_month(archive.month)
end end
attachments = ActiveStorage::DownloadableFile.create_list_from_dossiers(dossiers) attachments = ActiveStorage::DownloadableFile.create_list_from_dossiers(dossiers, with_bills: true, with_champs_private: true)
DownloadableFileService.download_and_zip(@procedure, attachments, zip_root_folder(archive)) do |zip_filepath| DownloadableFileService.download_and_zip(@procedure, attachments, zip_root_folder(archive)) do |zip_filepath|
ArchiveUploader.new(procedure: @procedure, filename: archive.filename(@procedure), filepath: zip_filepath) ArchiveUploader.new(procedure: @procedure, filename: archive.filename(@procedure), filepath: zip_filepath)

View file

@ -35,7 +35,7 @@ class ProcedureExportService
end end
def to_zip def to_zip
attachments = ActiveStorage::DownloadableFile.create_list_from_dossiers(dossiers, true) attachments = ActiveStorage::DownloadableFile.create_list_from_dossiers(dossiers, with_champs_private: true)
DownloadableFileService.download_and_zip(procedure, attachments, base_filename) do |zip_filepath| DownloadableFileService.download_and_zip(procedure, attachments, base_filename) do |zip_filepath|
ArchiveUploader.new(procedure: procedure, filename: filename(:zip), filepath: zip_filepath).blob ArchiveUploader.new(procedure: procedure, filename: filename(:zip), filepath: zip_filepath).blob

View file

@ -41,42 +41,43 @@
%li %li
= link_to admin_procedure_path(procedure), class: 'fr-btn fr-icon-draft-line fr-btn--tertiary' do = link_to admin_procedure_path(procedure), class: 'fr-btn fr-icon-draft-line fr-btn--tertiary' do
Modifier Modifier
%li.dropdown{ data: { controller: 'menu-button' } }
%button.fr-btn.fr-btn--tertiary.dropdown-button.procedures-actions-btn{ data: { menu_button_target: 'button' } } = render Dropdown::MenuComponent.new(wrapper: :li, button_options: { class: ['fr-btn--tertiary'] }, menu_options: { id: dom_id(procedure, :actions_menu)}) do |menu|
- menu.with_button_inner_html do
Actions Actions
.dropdown-content.fade-in-down{ data: { menu_button_target: 'menu' }, id: dom_id(procedure, :actions_menu) }
%ul.dropdown-items.pl-0
- if !procedure.close? && !procedure.discarded?
%li
= link_to sanitize_url(procedure.brouillon? ? commencer_test_url(path: procedure.path) : commencer_url(path: procedure.path)), target: :blank, rel: :noopener do
%span.icon.in-progress
.dropdown-description
%h4= t('administrateurs.dropdown_actions.to_test')
- unless procedure.discarded? - if !procedure.close? && !procedure.discarded?
%li - menu.with_item do
= link_to admin_procedure_clone_path(procedure.id), class: 'clone-btn', data: { method: :put } do = link_to(sanitize_url(procedure.brouillon? ? commencer_test_url(path: procedure.path) : commencer_url(path: procedure.path)), target: :blank, rel: :noopener, role: 'menuitem') do
%span.icon.new-folder %span.icon.in-progress
.dropdown-description .dropdown-description
%h4= t('administrateurs.dropdown_actions.to_clone') %h4= t('administrateurs.dropdown_actions.to_test')
- if procedure.publiee? - if !procedure.discarded?
%li - menu.with_item do
= link_to admin_procedure_close_path(procedure_id: procedure.id) do = link_to(admin_procedure_clone_path(procedure.id), role: 'menuitem', class: 'clone-btn', data: { method: :put }) do
%span.icon.archive %span.icon.new-folder
.dropdown-description .dropdown-description
%h4= t('administrateurs.dropdown_actions.to_close') %h4= t('administrateurs.dropdown_actions.to_clone')
- if procedure.can_be_deleted_by_administrateur? && !procedure.discarded? - if procedure.publiee?
%li - menu.with_item do
= link_to admin_procedure_path(procedure), method: :delete, data: { confirm: "Voulez-vous vraiment supprimer la démarche ? \nToute suppression est définitive et s'appliquera aux éventuels autres administrateurs de cette démarche !" } do = link_to(admin_procedure_close_path(procedure_id: procedure.id), role: 'menuitem') do
%span.icon.refuse %span.icon.archive
.dropdown-description .dropdown-description
%h4= t('administrateurs.dropdown_actions.delete') %h4= t('administrateurs.dropdown_actions.to_close')
- if procedure.can_be_deleted_by_administrateur? && !procedure.discarded?
- menu.with_item do
= link_to admin_procedure_path(procedure), role: 'menuitem', method: :delete, data: { confirm: "Voulez-vous vraiment supprimer la démarche ? \nToute suppression est définitive et s'appliquera aux éventuels autres administrateurs de cette démarche !" } do
%span.icon.refuse
.dropdown-description
%h4= t('administrateurs.dropdown_actions.delete')
- if procedure.discarded?
- menu.with_item do
= link_to restore_admin_procedure_path(procedure), role: 'menuitem', method: :put do
%span.icon.unarchive
.dropdown-description
%h4= t('administrateurs.dropdown_actions.restore')
- if procedure.discarded?
%li
= link_to restore_admin_procedure_path(procedure), method: :put do
%span.icon.unarchive
.dropdown-description
%h4= t('administrateurs.dropdown_actions.restore')

View file

@ -9,11 +9,11 @@
%li= link_to("Dossier nº #{dossier.id}", expert_avis_path(avis.procedure, avis)) %li= link_to("Dossier nº #{dossier.id}", expert_avis_path(avis.procedure, avis))
.header-actions .header-actions
%span.dropdown.print-menu-opener{ data: { controller: 'menu-button' } } .fr-download
%button.button.dropdown-button.icon-only{ data: { menu_button_target: 'button' } } = link_to telecharger_pjs_expert_avis_path(avis.procedure, avis), download: :download, class: "menu-item menu-link fr-download__link" do
%span.icon.attached Télécharger le dossier et toutes ses pièces jointes
%ul.print-menu.dropdown-content#print-pj-menu{ data: { menu_button_target: 'menu' } } %span.fr-download__detail
%li= link_to "Télécharger le dossier et toutes ses pièces jointes", telecharger_pjs_expert_avis_path(avis.procedure, avis), target: "_blank", rel: "noopener", class: "menu-item menu-link" ZIP
%nav.tabs %nav.tabs
%ul %ul

View file

@ -1,21 +1,31 @@
%ul.fr-btns-group.fr-btns-group--sm.fr-btns-group--inline-md.fr-btns-group--icon-right %ul.fr-btns-group.fr-btns-group--sm.fr-btns-group--inline-md.fr-btns-group--icon-right
%li.dropdown.print-menu-opener{ data: { controller: 'menu-button' } } = render Dropdown::MenuComponent.new(wrapper: :li, menu_options: { id: 'print-menu'}, button_options: { class: ['fr-btn--tertiary', 'fr-icon-printer-line']}) do |menu|
%button.fr-btn.fr-btn--tertiary.fr-icon-printer-line.dropdown-button{ title: 'imprimer', 'aria-label': 'Imprimer', data: { menu_button_target: 'button' } } Imprimer - menu.with_button_inner_html do
%ul#print-menu.print-menu.dropdown-content{ data: { menu_button_target: 'menu' } } Imprimer
%li
= link_to "Tout le dossier", print_instructeur_dossier_path(dossier.procedure, dossier), target: "_blank", rel: "noopener", class: "menu-item menu-link"
%li
= link_to "Uniquement cet onglet", "#", onclick: "window.print()", class: "menu-item menu-link"
%li
= link_to "Export PDF", instructeur_dossier_path(dossier.procedure, dossier, format: :pdf), target: "_blank", rel: "noopener", class: "menu-item menu-link"
- if dossier.geo_data?
%li
= link_to "Export GeoJSON", geo_data_instructeur_dossier_path(dossier.procedure, dossier), target: "_blank", rel: "noopener", class: "menu-item menu-link"
%li.dropdown.print-menu-opener{ data: { controller: 'menu-button' } } - menu.with_item do
%button.fr-btn.fr-btn--tertiary.fr-icon-download-line.dropdown-button{ data: { menu_button_target: 'button', 'aria-label': 'Télécharger' } } Télécharger = link_to print_instructeur_dossier_path(dossier.procedure, dossier), role: 'menuitem', target: "_blank", rel: "noopener", class: "menu-item menu-link" do
%ul#print-pj-menu.print-menu.dropdown-content{ data: { menu_button_target: 'menu' } } Tout le dossier
%li= link_to "Télécharger le dossier et toutes ses pièces jointes", telecharger_pjs_instructeur_dossier_path(dossier.procedure, dossier), target: "_blank", rel: "noopener", class: "menu-item menu-link"
- menu.with_item do
= link_to '#', role: 'menuitem', onclick: "window.print()", class: "menu-item menu-link" do
Uniquement cet onglet
- menu.with_item do
= link_to instructeur_dossier_path(dossier.procedure, dossier, format: :pdf), target: "_blank", rel: "noopener", class: "menu-item menu-link", role: 'menuitem' do
Export PDF
- if dossier.geo_data?
- menu.with_item do
= link_to geo_data_instructeur_dossier_path(dossier.procedure, dossier), target: "_blank", rel: "noopener", class: "menu-item menu-link", role: 'menuitem' do
Export GeoJSON
= render Dropdown::MenuComponent.new(wrapper: :li, menu_options: { id: 'print-pj-menu'}, button_options: { class: ['fr-btn--tertiary', 'fr-icon-download-line']}) do |menu|
- menu.with_button_inner_html do
Télécharger
- menu.with_item do
= link_to telecharger_pjs_instructeur_dossier_path(dossier.procedure, dossier), target: "_blank", rel: "noopener", class: "menu-item menu-link", role: 'menuitem' do
Télécharger le dossier et toutes ses pièces jointes
%li %li
= render partial: "instructeurs/procedures/dossier_actions", = render partial: "instructeurs/procedures/dossier_actions",

View file

@ -1,120 +1,107 @@
.dropdown{ data: { controller: 'menu-button', popover: 'true', turbo_force: true } } = render Dropdown::MenuComponent.new(wrapper: :div, wrapper_options: { data: {'turbo-force': true} }, button_options: { class: [button_or_label_class(dossier)] }) do |menu|
-# Dropdown button title - menu.with_button_inner_html do
%button.fr-btn.dropdown-button{ class: button_or_label_class(dossier), data: { menu_button_target: 'button' } } = dossier_display_state(dossier)
= dossier_display_state dossier
-# Dropdown content - if dossier.en_construction?
#state-menu.dropdown-content.fade-in-down{ data: { menu_button_target: 'menu' } } - menu.with_item(aria: {disabled:"true"}, class: 'selected') do
%span.icon.edit
.dropdown-description
%h4 En construction
Vous permettez à l'usager de modifier ses réponses au formulaire
- if dossier.en_construction? - menu.with_item('data-turbo': 'true') do
-# ------------------------------------------------------ = link_to(passer_en_instruction_instructeur_dossier_path(dossier.procedure, dossier), data: { turbo_method: :post, turbo_confirm: "Confirmez-vous le passage en instruction de ce dossier ?", turbo: true }, role: 'menuitem') do
-# EN CONSTRUCTION %span.icon.in-progress
-# ------------------------------------------------------ .dropdown-description
%ul.dropdown-items %h4 Passer en instruction
Lusager ne pourra plus modifier le formulaire
%li.selected - elsif dossier.en_instruction?
%span.icon.edit - menu.with_item('data-turbo': 'true') do
= link_to(repasser_en_construction_instructeur_dossier_path(dossier.procedure, dossier), data: { turbo_method: :post, turbo_confirm: "Confirmez-vous le passage en construction de ce dossier ?"}, role: 'menuitem') do
%span.icon.edit
.dropdown-description
%h4 Repasser en construction
Vous permettrez à l'usager de modifier ses réponses au formulaire
- menu.with_item(aria: {disabled:"true"}, class: 'selected') do
%span.icon.in-progress
.dropdown-description
%h4 En instruction
Lusager ne peut modifier son dossier pendant l'instruction
- menu.with_item do
= link_to('#', onclick: "DS.showMotivation(event, 'accept');", role: 'menuitem') do
%span.icon.accept
.dropdown-description
%h4 Accepter
Lusager sera notifié que son dossier a été accepté
- menu.with_item do
= link_to('#', onclick: "DS.showMotivation(event, 'without-continuation');", role: 'menuitem') do
%span.icon.without-continuation
.dropdown-description
%h4 Classer sans suite
Lusager sera notifié que son dossier a été classé sans suite
- menu.with_item do
= link_to('#', onclick: "DS.showMotivation(event, 'refuse');", role: 'menuitem') do
%span.icon.refuse
.dropdown-description
%h4 Refuser
Lusager sera notifié que son dossier a été refusé
- menu.with_form do
= render partial: 'instructeurs/dossiers/state_button_motivation', locals: { dossier: dossier, popup_title: 'Accepter le dossier', placeholder: 'Expliquez au demandeur pourquoi ce dossier est accepté (facultatif)', popup_class: 'accept', process_action: 'accepter', title: 'Accepter', confirm: "Confirmez-vous l'acceptation ce dossier ?" }
- menu.with_form do
= render partial: 'instructeurs/dossiers/state_button_motivation', locals: { dossier: dossier, popup_title: 'Classer le dossier sans suite', placeholder: 'Expliquez au demandeur pourquoi ce dossier est classé sans suite (obligatoire)', popup_class: 'without-continuation', process_action: 'classer_sans_suite', title: 'Classer sans suite', confirm: 'Confirmez-vous le classement sans suite de ce dossier ?' }
- menu.with_form do
= render partial: 'instructeurs/dossiers/state_button_motivation', locals: { dossier: dossier, popup_title: 'Refuser le dossier', placeholder: 'Expliquez au demandeur pourquoi ce dossier est refusé (obligatoire)', popup_class: 'refuse', process_action: 'refuser', title: 'Refuser', confirm: 'Confirmez-vous le refus de ce dossier ?' }
- elsif dossier.termine?
- if dossier.motivation.present?
- menu.with_item(class: 'inactive') do
%span.icon.info
.dropdown-description
%h4 Motivation
%p « #{dossier.motivation} »
- if dossier.justificatif_motivation.attached?
- menu.with_item(class: 'inactive') do
%span.icon.justificatif
.dropdown-description
%h4 Justificatif
%p Ce justificatif joint par linstructeur a été envoyé au demandeur.
= render partial: 'users/dossiers/show/download_justificatif', locals: { dossier: dossier }
- if dossier.attestation.present?
- menu.with_item do
= link_to(attestation_instructeur_dossier_path(dossier.procedure, dossier), target: '_blank', rel: 'noopener', role: 'menuitem') do
%span.icon.preview
.dropdown-description .dropdown-description
%h4 En construction %h4 Voir lattestation
Vous permettez à l'usager de modifier ses réponses au formulaire %p Cette attestation a été envoyée automatiquement au demandeur.
%li{ 'data-turbo': 'true' } - if dossier.can_repasser_en_instruction?
= link_to passer_en_instruction_instructeur_dossier_path(dossier.procedure, dossier), data: { turbo_method: :post, turbo_confirm: "Confirmez-vous le passage en instruction de ce dossier ?" } do - menu.with_item do
%span.icon.in-progress = link_to(repasser_en_instruction_instructeur_dossier_path(dossier.procedure, dossier), data: { turbo_method: :post, turbo_confirm: "Voulez vous remettre le dossier #{dossier.id} en instruction ?", turbo: true }, role: 'menuitem') do
.dropdown-description
%h4 Passer en instruction
Lusager ne pourra plus modifier le formulaire
- elsif dossier.en_instruction?
-# ------------------------------------------------------
-# EN INSTRUCTION
-# ------------------------------------------------------
%ul.dropdown-items
%li{ 'data-turbo': 'true' }
= link_to repasser_en_construction_instructeur_dossier_path(dossier.procedure, dossier), data: { turbo_method: :post, turbo_confirm: "Confirmez-vous le passage en construction de ce dossier ?" } do
%span.icon.edit
.dropdown-description
%h4 Repasser en construction
Vous permettrez à l'usager de modifier ses réponses au formulaire
%li.selected
%span.icon.in-progress %span.icon.in-progress
.dropdown-description .dropdown-description
%h4 En instruction %h4 Repasser en instruction
Lusager ne peut modifier son dossier pendant l'instruction Lusager sera notifié que son dossier est réexaminé.
- elsif dossier.user_deleted?
- menu.with_item do
%span.icon.info
.dropdown-description
%h4 En attente darchivage
Lusager a supprimé son compte. Vous pouvez archiver puis supprimer le dossier.
%li - menu.with_item do
%a{ href: '#', onclick: "DS.showMotivation(event, 'accept');" } = link_to(instructeur_dossier_path(dossier.procedure, dossier), method: :delete, role: 'menuitem') do
%span.icon.accept %span.icon.delete
.dropdown-description .dropdown-description
%h4 Accepter %h4 Supprimer le dossier
Lusager sera notifié que son dossier a été accepté
%li
%a{ href: '#', onclick: "DS.showMotivation(event, 'without-continuation');" }
%span.icon.without-continuation
.dropdown-description
%h4 Classer sans suite
Lusager sera notifié que son dossier a été classé sans suite
%li
%a{ href: '#', onclick: "DS.showMotivation(event, 'refuse');" }
%span.icon.refuse
.dropdown-description
%h4 Refuser
Lusager sera notifié que son dossier a été refusé
= render partial: 'instructeurs/dossiers/state_button_motivation', locals: { dossier: dossier, popup_title: 'Accepter le dossier', placeholder: 'Expliquez au demandeur pourquoi ce dossier est accepté (facultatif)', popup_class: 'accept', process_action: 'accepter', title: 'Accepter', confirm: "Confirmez-vous l'acceptation ce dossier ?" }
= render partial: 'instructeurs/dossiers/state_button_motivation', locals: { dossier: dossier, popup_title: 'Classer le dossier sans suite', placeholder: 'Expliquez au demandeur pourquoi ce dossier est classé sans suite (obligatoire)', popup_class: 'without-continuation', process_action: 'classer_sans_suite', title: 'Classer sans suite', confirm: 'Confirmez-vous le classement sans suite de ce dossier ?' }
= render partial: 'instructeurs/dossiers/state_button_motivation', locals: { dossier: dossier, popup_title: 'Refuser le dossier', placeholder: 'Expliquez au demandeur pourquoi ce dossier est refusé (obligatoire)', popup_class: 'refuse', process_action: 'refuser', title: 'Refuser', confirm: 'Confirmez-vous le refus de ce dossier ?' }
- elsif dossier.termine?
-# ---------------------------------------------------
-# TERMINÉ
-# ---------------------------------------------------
%ul.dropdown-items
- if dossier.motivation.present?
%li.inactive
%span.icon.info
.dropdown-description
%h4 Motivation
%p « #{dossier.motivation} »
- if dossier.justificatif_motivation.attached?
%li.inactive
%span.icon.justificatif
.dropdown-description
%h4 Justificatif
%p Ce justificatif joint par linstructeur a été envoyé au demandeur.
= render partial: 'users/dossiers/show/download_justificatif', locals: { dossier: dossier }
- if dossier.attestation.present?
%li
= link_to attestation_instructeur_dossier_path(dossier.procedure, dossier), target: '_blank', rel: 'noopener' do
%span.icon.preview
.dropdown-description
%h4 Voir lattestation
%p Cette attestation a été envoyée automatiquement au demandeur.
- if dossier.can_repasser_en_instruction?
%li{ 'data-turbo': 'true' }
= link_to repasser_en_instruction_instructeur_dossier_path(dossier.procedure, dossier), data: { turbo_method: :post, turbo_confirm: "Voulez vous remettre le dossier #{dossier.id} en instruction ?" } do
%span.icon.in-progress
.dropdown-description
%h4 Repasser en instruction
Lusager sera notifié que son dossier est réexaminé.
- elsif dossier.user_deleted?
%li
%span.icon.info
.dropdown-description
%h4 En attente darchivage
Lusager a supprimé son compte. Vous pouvez archiver puis supprimer le dossier.
%li
= link_to instructeur_dossier_path(dossier.procedure, dossier), method: :delete do
%span.icon.delete
.dropdown-description
%h4 Supprimer le dossier

View file

@ -2,33 +2,36 @@
= link_to restore_instructeur_dossier_path(procedure_id, dossier_id), method: :patch, class: "fr-btn fr-btn--secondary" do = link_to restore_instructeur_dossier_path(procedure_id, dossier_id), method: :patch, class: "fr-btn fr-btn--secondary" do
= t('views.instructeurs.dossiers.restore') = t('views.instructeurs.dossiers.restore')
- elsif close_to_expiration || Dossier::TERMINE.include?(state) - elsif close_to_expiration || Dossier::TERMINE.include?(state)
%li.dropdown.user-dossier-actions{ data: { controller: 'menu-button' } } = render Dropdown::MenuComponent.new(wrapper: :li, button_options: { class: ['fr-mb-0']}, menu_options: { id: "dossier_#{dossier_id}_actions_menu", class: 'user-dossier-actions' }) do |menu|
%button.fr-btn.fr-mb-0.dropdown-button{ data: { menu_button_target: 'button' } } - menu.with_button_inner_html do
Actions Actions
.dropdown-content.fade-in-down{ data: { menu_button_target: 'menu' }, id: "dossier_#{dossier_id}_actions_menu" }
%ul.dropdown-items - if close_to_expiration
- if close_to_expiration - menu.with_item do
%li = link_to(repousser_expiration_instructeur_dossier_path(procedure_id, dossier_id), method: :post, role: 'menuitem') do
= link_to repousser_expiration_instructeur_dossier_path(procedure_id, dossier_id), method: :post do %span.icon.standby
%span.icon.standby %span.dropdown-description= t('instructeurs.dossiers.header.banner.button_delay_expiration')
%span.dropdown-description= t('instructeurs.dossiers.header.banner.button_delay_expiration')
- if archived - if archived
%li - menu.with_item do
= link_to unarchive_instructeur_dossier_path(procedure_id, dossier_id), method: :patch do = link_to( unarchive_instructeur_dossier_path(procedure_id, dossier_id), role: 'menuitem', method: :patch) do
%span.icon.unarchive %span.icon.unarchive
%span.dropdown-description %span.dropdown-description
Désarchiver le dossier Désarchiver le dossier
- else
%li - else
= link_to archive_instructeur_dossier_path(procedure_id, dossier_id), method: :patch do - menu.with_item do
%span.icon.archive = link_to( archive_instructeur_dossier_path(procedure_id, dossier_id), method: :patch, role: 'menuitem') do
%span.dropdown-description %span.icon.archive
Archiver le dossier %span.dropdown-description
%li.danger Archiver le dossier
= link_to instructeur_dossier_path(procedure_id, dossier_id), method: :delete do
%span.icon.delete - menu.with_item(class: 'danger') do
%span.dropdown-description = link_to(instructeur_dossier_path(procedure_id, dossier_id), method: :delete, role: 'menuitem') do
= t('views.instructeurs.dossiers.delete_dossier') %span.icon.delete
%span.dropdown-description
= t('views.instructeurs.dossiers.delete_dossier')
- elsif Dossier::EN_CONSTRUCTION_OU_INSTRUCTION.include?(state) - elsif Dossier::EN_CONSTRUCTION_OU_INSTRUCTION.include?(state)
- if dossier_is_followed - if dossier_is_followed

View file

@ -1,5 +1,7 @@
%span.dropdown{ data: { controller: 'menu-button', popover: 'true' } } = render Dropdown::MenuComponent.new(wrapper: :div, button_options: { class: ['fr-btn--secondary', 'fr-btn--sm', 'fr-mr-1w'] }, menu_options: { id: 'filter-menu', class:['left-aligned'] }) do |menu|
%button.fr-btn.fr-btn--tertiary.fr-btn--sm.fr-mr-2w.dropdown-button{ data: { menu_button_target: 'button' } } - menu.with_button_inner_html do
= t('views.instructeurs.dossiers.filters.title') = t('views.instructeurs.dossiers.filters.title')
#filter-menu.dropdown-content.left-aligned.fade-in-down{ data: { menu_button_target: 'menu' } }
- menu.with_form do
= render Dossiers::FilterComponent.new(procedure: procedure, procedure_presentation: @procedure_presentation, statut: statut) = render Dossiers::FilterComponent.new(procedure: procedure, procedure_presentation: @procedure_presentation, statut: statut)

View file

@ -62,10 +62,10 @@
= render Dossiers::NotifiedToggleComponent.new(procedure: @procedure, procedure_presentation: @procedure_presentation) = render Dossiers::NotifiedToggleComponent.new(procedure: @procedure, procedure_presentation: @procedure_presentation)
.fr-ml-auto .fr-ml-auto
%span.dropdown{ data: { controller: 'menu-button', popover: 'true' } } = render Dropdown::MenuComponent.new(wrapper: :span, button_options: { class: ['fr-btn--sm', 'fr-btn--secondary'] }, menu_options: { id: 'custom-menu' }) do |menu|
%button.fr-btn.fr-btn--sm.fr-btn--tertiary.dropdown-button.fr-ml-1w{ data: { menu_button_target: 'button' } } - menu.with_button_inner_html do
= t('views.instructeurs.dossiers.personalize') = t('views.instructeurs.dossiers.personalize')
#custom-menu.dropdown-content.fade-in-down{ data: { menu_button_target: 'menu' } } - menu.with_form do
= form_tag update_displayed_fields_instructeur_procedure_path(@procedure), method: :patch, class: 'dropdown-form large columns-form' do = form_tag update_displayed_fields_instructeur_procedure_path(@procedure), method: :patch, class: 'dropdown-form large columns-form' do
= hidden_field_tag :values, nil = hidden_field_tag :values, nil
= react_component("ComboMultiple", = react_component("ComboMultiple",
@ -120,6 +120,7 @@
%th.action-col.follow-col %th.action-col.follow-col
Actions Actions
%tr %tr
%tbody %tbody

View file

@ -1,6 +1,6 @@
- invites = dossier.invites.load - invites = dossier.invites.load
.dropdown.invite-user-action{ data: { controller: 'menu-button', popover: 'true' } } = render Dropdown::MenuComponent.new(wrapper: :span, wrapper_options: {class: 'invite-user-action'}, button_options: { class: ['fr-btn--secondary'] }, menu_options: { id: 'invite-content' }) do |menu|
%button.button.dropdown-button{ data: { menu_button_target: 'button' } } - menu.with_button_inner_html do
%span.icon.person %span.icon.person
- if invites.present? - if invites.present?
= t('views.invites.dropdown.view_invited_people') = t('views.invites.dropdown.view_invited_people')
@ -10,6 +10,5 @@
= t('views.invites.dropdown.invite_to_view') = t('views.invites.dropdown.invite_to_view')
- else - else
= t('views.invites.dropdown.invite_to_edit') = t('views.invites.dropdown.invite_to_edit')
- menu.with_form do
#invite-content.dropdown-content.fade-in-down{ data: { menu_button_target: 'menu' } }
= render partial: "invites/form", locals: { dossier: dossier, invites: invites } = render partial: "invites/form", locals: { dossier: dossier, invites: invites }

View file

@ -1,11 +1,9 @@
#search-modal.fr-header__search.fr-modal #search-modal.fr-header__search.fr-modal
.fr-container.fr-container-lg--fluid .fr-container.fr-container-lg--fluid
%button.fr-btn--close.fr-btn{ "aria-controls" => "search-modal", :title => t('close_modal', scope: [:layouts, :header]) }= t('close_modal', scope: [:layouts, :header]) %button.fr-btn--close.fr-btn{ "aria-controls" => "search-modal", :title => t('close_modal', scope: [:layouts, :header]) }= t('close_modal', scope: [:layouts, :header])
#search-473.fr-search-bar{ :role => "search" } #search-473.fr-search-bar
= form_tag "#{search_endpoint}", method: :get, class: "flex width-100" do = form_tag "#{search_endpoint}", method: :get, :role => "search", class: "flex width-100" do
= label_tag "q", t('views.users.dossiers.search.search_file'), class: 'fr-label' = label_tag "q", t('views.users.dossiers.search.search_file'), class: 'fr-label'
= text_field_tag "q", "#{@search_terms if @search_terms.present?}", placeholder: t('views.users.dossiers.search.search_file'), aria: { label: t('views.users.dossiers.search.search_file') }, class: "fr-input" = text_field_tag "q", "#{@search_terms if @search_terms.present?}", placeholder: t('views.users.dossiers.search.placeholder'), class: "fr-input"
%button.fr-btn{ title: t('views.users.dossiers.search.search_file') } %button.fr-btn{ title: t('views.users.dossiers.search.search_file') }
= image_tag "icons/search-blue.svg", alt: t('views.users.dossiers.search.search_file'), 'aria-hidden':'true', width: 24, height: 24, loading: 'lazy' = t('views.users.dossiers.search.search_file')

View file

@ -1,15 +1,10 @@
.no-procedure .no-procedure
= image_tag "landing/hero/dematerialiser.svg", class: "paperless-logo", alt: "" = image_tag "landing/hero/dematerialiser.svg", class: "paperless-logo", alt: ""
.baseline.center .baseline.center
%p .no-procedure-presentation
%span.simple= t('.line1') %p.simple= t('.line1')
%br %p= t('.line2')
= t('.line2') %p= t('.line3')
%br
= t('.line3')
%hr %hr
%p %p.small-simple= t('.are_you_new', app_name: APPLICATION_NAME.gsub("-","&#8209;")).html_safe
%span.small-simple= t('.are_you_new', app_name: APPLICATION_NAME.gsub("-","&#8209;")).html_safe = link_to t('views.users.sessions.new.find_procedure'), t("links.common.faq.comment_trouver_ma_demarche_url"), title: new_tab_suffix(t('views.users.sessions.new.find_procedure')), class: "fr-btn fr-btn--secondary", **external_link_attributes
%br
%br
= link_to t('views.users.sessions.new.find_procedure'), t("links.common.faq.comment_trouver_ma_demarche_url"), title: new_tab_suffix(t('views.users.sessions.new.find_procedure')), class: "fr-btn fr-btn--secondary", **external_link_attributes

View file

@ -12,11 +12,8 @@
%p %p
%strong %strong
= t('.procedure_management') = t('.procedure_management')
%br
= service.nom = service.nom
%br
= service.organisme = service.organisme
%br
= service.adresse = service.adresse
%td{ width: "50%", valign: "top" } %td{ width: "50%", valign: "top" }
%p %p

View file

@ -42,7 +42,7 @@
= type_de_champ.possible_values_sentence = type_de_champ.possible_values_sentence
%br %br
- if type_de_champ.too_many_possible_values? - if type_de_champ.too_many_possible_values?
= link_to "Voir toutes les valeurs possibles", prefill_type_de_champ_path(prefill_description.path, type_de_champ) = link_to t("views.prefill_descriptions.edit.possible_values.link.text"), prefill_type_de_champ_path(prefill_description.path, type_de_champ), title: new_tab_suffix(t("views.prefill_descriptions.edit.possible_values.link.title")), **external_link_attributes
%tr{ class: prefillable ? "" : "fr-text-mention--grey" } %tr{ class: prefillable ? "" : "fr-text-mention--grey" }
%th %th
= t("views.prefill_descriptions.edit.examples.title") = t("views.prefill_descriptions.edit.examples.title")

View file

@ -67,21 +67,21 @@
- if instructeur_dossier && expert_dossier - if instructeur_dossier && expert_dossier
%td.action-col.follow-col %td.action-col.follow-col
.dropdown{ data: { controller: 'menu-button' } } = render Dropdown::MenuComponent.new(wrapper: :div, button_options: {class: ['fr-btn--sm']}) do |menu|
%button.button.dropdown-button{ data: { menu_button_target: 'button' } } - menu.with_button_inner_html do
Actions Actions
.dropdown-content{ data: { menu_button_target: 'menu' } }
%ul.dropdown-items - menu.with_item do
%li = link_to(instructeur_dossier_path(procedure_id, p.dossier_id), role: 'menuitem') do
= link_to(instructeur_dossier_path(procedure_id, p.dossier_id)) do %span.icon.in-progress>
%span.icon.in-progress> .dropdown-description
.dropdown-description Voir le dossier
Voir le dossier
%li - menu.with_item do
= link_to(expert_avis_path(procedure_id, @dossier_avis_ids_h[p.dossier_id])) do = link_to(expert_avis_path(procedure_id, @dossier_avis_ids_h[p.dossier_id]), role: 'menuitem') do
%span.icon.in-progress> %span.icon.in-progress>
.dropdown-description .dropdown-description
Donner mon avis Donner mon avis
- elsif instructeur_dossier - elsif instructeur_dossier
- if hidden_by_administration - if hidden_by_administration

View file

@ -1,15 +1,16 @@
.dropdown.help-dropdown{ data: { controller: 'menu-button' } } = render Dropdown::MenuComponent.new(wrapper: :span, wrapper_options: { class: ['help-dropdown']}, menu_options: { id: "help-menu" }) do |menu|
%button.fr-btn.dropdown-button{ data: { menu_button_target: 'button' } } - menu.with_button_inner_html do
= t('help') = t('help')
#help-menu.dropdown-content.fade-in-down{ data: { menu_button_target: 'menu' } } - title = dossier.brouillon? ? "Besoin daide pour remplir votre dossier ?" : "Une question sur votre dossier ?"
%ul.dropdown-items
- title = dossier.brouillon? ? "Besoin daide pour remplir votre dossier ?" : "Une question sur votre dossier ?"
- if dossier.messagerie_available? - if dossier.messagerie_available?
= render partial: 'shared/help/dropdown_items/messagerie_item', - menu.with_item do
locals: { dossier: dossier, title: title } = render partial: 'shared/help/dropdown_items/messagerie_item', locals: { dossier: dossier, title: title }
- elsif dossier.procedure.service.present?
= render partial: 'shared/help/dropdown_items/service_item',
locals: { service: dossier.procedure.service, title: title }
= render partial: 'shared/help/dropdown_items/faq_item' - elsif dossier.procedure.service.present?
- menu.with_item do
= render partial: 'shared/help/dropdown_items/service_item',
locals: { service: dossier.procedure.service, title: title }
- menu.with_item do
= render partial: 'shared/help/dropdown_items/faq_item'

View file

@ -1,7 +1,8 @@
.dropdown.help-dropdown{ data: { controller: 'menu-button' } } = render Dropdown::MenuComponent.new(wrapper: :span, wrapper_options: { class: ['help-dropdown']}, menu_options: { id: "help-menu" }) do |menu|
%button.fr-btn.dropdown-button{ data: { menu_button_target: 'button' } } - menu.with_button_inner_html do
= t('help') = t('help')
#help-menu.dropdown-content.fade-in-down{ data: { menu_button_target: 'menu' } }
%ul.dropdown-items - menu.with_item do
= render partial: 'shared/help/dropdown_items/faq_item' = render partial: 'shared/help/dropdown_items/faq_item'
= render partial: 'shared/help/dropdown_items/email_item' - menu.with_item do
= render partial: 'shared/help/dropdown_items/email_item'

View file

@ -1,10 +1,9 @@
.dropdown.help-dropdown{ data: { controller: 'menu-button' } } = render Dropdown::MenuComponent.new(wrapper: :span, wrapper_options: { class: ['help-dropdown']}, menu_options: { id: "help-menu" }) do |menu|
%button.fr-btn.dropdown-button{ data: { menu_button_target: 'button' } } - menu.with_button_inner_html do
= t('help') = t('help')
#help-menu.dropdown-content.fade-in-down{ data: { menu_button_target: 'menu' } }
%ul.dropdown-items
- if procedure.service.present?
= render partial: 'shared/help/dropdown_items/service_item',
locals: { service: procedure.service, title: "Une question sur cette démarche ?" }
= render partial: 'shared/help/dropdown_items/faq_item' - if procedure.service.present?
- menu.with_item do
= render partial: 'shared/help/dropdown_items/service_item', locals: { service: procedure.service, title: "Une question sur cette démarche ?" }
- menu.with_item do
= render partial: 'shared/help/dropdown_items/faq_item'

View file

@ -1,5 +1,5 @@
%li %li{ role: 'none' }
= mail_to CONTACT_EMAIL do = mail_to CONTACT_EMAIL, role: 'menuitem' do
%span.icon.mail %span.icon.mail
.dropdown-description .dropdown-description
%span.help-dropdown-title %span.help-dropdown-title

View file

@ -1,9 +1,7 @@
%li = link_to t("links.common.faq.url"), title: new_tab_suffix(t('help_dropdown.general_title')), **external_link_attributes, role: 'menuitem' do
= link_to t("links.common.faq.url"), title: new_tab_suffix(t('help_dropdown.general_title')), **external_link_attributes do %span.icon.help
%span.icon.help .dropdown-description
.dropdown-description %span.help-dropdown-title
%span.help-dropdown-title = t('help_dropdown.problem_title')
= t('help_dropdown.problem_title') %p
%p = t('help_dropdown.problem_description')
= t('help_dropdown.problem_description')

View file

@ -1,6 +1,5 @@
%li = link_to messagerie_dossier_path(dossier), role: 'menuitem' do
= link_to messagerie_dossier_path(dossier) do %span.icon.mail
%span.icon.mail .dropdown-description
.dropdown-description %span.help-dropdown-title= title
%span.help-dropdown-title= title %p Envoyez directement un message à linstructeur.
%p Envoyez directement un message à linstructeur.

View file

@ -1,15 +1,14 @@
%li.help-dropdown-service %span.icon.person
%span.icon.person .dropdown-description
.dropdown-description %span.help-dropdown-title= title
%span.help-dropdown-title= title .help-dropdown-service-action
.help-dropdown-service-action %p Contactez directement ladministration :
%p Contactez directement ladministration : %p.help-dropdown-service-item
%p.help-dropdown-service-item %span.icon.small.mail
%span.icon.small.mail = link_to service.email, "mailto:#{service.email}", role: 'menuitem'
= link_to service.email, "mailto:#{service.email}" %p.help-dropdown-service-item
%p.help-dropdown-service-item %span.icon.small.phone
%span.icon.small.phone = link_to service.telephone, service.telephone_url, role: 'menuitem'
= link_to service.telephone, service.telephone_url %p.help-dropdown-service-item
%p.help-dropdown-service-item %span.icon.small.clock
%span.icon.small.clock = service.horaires
= service.horaires

View file

@ -10,8 +10,9 @@
= form_tag contact_path, method: :post, multipart: true, class: 'fr-form-group', data: {controller: :support } do = form_tag contact_path, method: :post, multipart: true, class: 'fr-form-group', data: {controller: :support } do
.description .description
%h2= t('.intro_html') .recommandations
%br %h2
= t('.intro_html')
%p.mandatory-explanation= t('asterisk_html', scope: [:utils]) %p.mandatory-explanation= t('asterisk_html', scope: [:utils])
- if !user_signed_in? - if !user_signed_in?
@ -34,7 +35,7 @@
- if link.present? - if link.present?
.support.card.featured.mb-4.ml-4.hidden{ id: "card-#{question_type}", "aria-hidden": true , data: { "support-target": "content" } } .support.card.featured.mb-4.ml-4.hidden{ id: "card-#{question_type}", "aria-hidden": true , data: { "support-target": "content" } }
.card-title %p.card-title
= t('.our_answer') = t('.our_answer')
.card-content .card-content
-# i18n-tasks-use t("support.index.#{question_type}.answer_html") -# i18n-tasks-use t("support.index.#{question_type}.answer_html")

View file

@ -15,9 +15,10 @@
%li %li
- horaires = "#{I18n.t('users.procedure_footer.contact.schedule.prefix')}#{formatted_horaires(service.horaires)}" - horaires = "#{I18n.t('users.procedure_footer.contact.schedule.prefix')}#{formatted_horaires(service.horaires)}"
= link_to service.telephone_url, class: 'fr-footer__top-link' do = link_to service.telephone_url, class: 'fr-footer__top-link' do
= I18n.t('users.procedure_footer.contact.phone.link', service_telephone: service.telephone) %p
%br = I18n.t('users.procedure_footer.contact.phone.link', service_telephone: service.telephone)
= horaires %p
= horaires
%li %li
= link_to I18n.t('users.procedure_footer.contact.stats.link'), statistiques_path(procedure.path), class: 'fr-footer__top-link', rel: 'noopener' = link_to I18n.t('users.procedure_footer.contact.stats.link'), statistiques_path(procedure.path), class: 'fr-footer__top-link', rel: 'noopener'

View file

@ -4,49 +4,51 @@
- has_transfer_action = dossier.user == current_user - has_transfer_action = dossier.user == current_user
- has_actions = has_edit_action || has_delete_action || has_new_dossier_action || has_transfer_action - has_actions = has_edit_action || has_delete_action || has_new_dossier_action || has_transfer_action
- if has_actions - if has_actions
.dropdown.user-dossier-actions{ data: { controller: 'menu-button' } } = render Dropdown::MenuComponent.new(wrapper: :div, wrapper_options: {class: 'invite-user-actions'}, menu_options: {id: dom_id(dossier, :actions_menu)}, button_options: {class: 'fr-btn--sm fr-btn--secondary'}) do |menu|
%button.fr-btn.fr-btn--secondary.dropdown-button{ data: { menu_button_target: 'button' } } - menu.with_button_inner_html do
= t('views.users.dossiers.dossier_action.actions') = t('views.users.dossiers.dossier_action.actions')
.dropdown-content.fade-in-down{ data: { menu_button_target: 'menu' }, id: dom_id(dossier, :actions_menu) }
%ul.dropdown-items
- if has_edit_action
- if dossier.brouillon?
%li
= link_to(url_for_dossier(dossier)) do
%span.icon.edit
.dropdown-description
= t('views.users.dossiers.dossier_action.edit_draft')
- else
%li
= link_to modifier_dossier_path(dossier) do
%span.icon.edit
.dropdown-description
= t('views.users.dossiers.dossier_action.edit_dossier')
- if has_transfer_action - if has_edit_action
%li - if dossier.brouillon?
= link_to transferer_dossier_path(dossier) do - menu.with_item do
%span.icon.person = link_to(url_for_dossier(dossier), role: 'menuitem') do
.dropdown-description %span.icon.edit
= t('views.users.dossiers.dossier_action.transfer_dossier') .dropdown-description
= t('views.users.dossiers.dossier_action.edit_draft')
- else
- menu.with_item do
= link_to(modifier_dossier_path(dossier), role: 'menuitem') do
%span.icon.edit
.dropdown-description
= t('views.users.dossiers.dossier_action.edit_dossier')
- if has_new_dossier_action - if has_transfer_action
%li - menu.with_item do
= link_to procedure_lien(dossier.procedure) do = link_to(transferer_dossier_path(dossier), role: 'menuitem') do
%span.icon.new-folder %span.icon.person
.dropdown-description .dropdown-description
= t('views.users.dossiers.dossier_action.start_other_dossier') = t('views.users.dossiers.dossier_action.transfer_dossier')
%li
= link_to clone_dossier_path(dossier), method: :post do
%span.icon.new-folder
.dropdown-description
= t('views.users.dossiers.dossier_action.clone')
- if has_delete_action - if has_new_dossier_action
%li.danger - menu.with_item do
= link_to delete_dossier_dossier_path(dossier), method: :patch, data: { disable: true, confirm: "En continuant, vous allez supprimer ce dossier ainsi que les informations quil contient. Toute suppression entraîne lannulation de la démarche en cours.\n\nConfirmer la suppression ?" } do = link_to(procedure_lien(dossier.procedure), role: 'menuitem') do
%span.icon.delete %span.icon.new-folder
.dropdown-description .dropdown-description
= t('views.users.dossiers.dossier_action.delete_dossier') = t('views.users.dossiers.dossier_action.start_other_dossier')
- menu.with_item do
= link_to(clone_dossier_path(dossier), method: :post, role: 'menuitem') do
%span.icon.new-folder
.dropdown-description
= t('views.users.dossiers.dossier_action.clone')
- if has_delete_action
- menu.with_item(class: 'danger') do
= link_to(delete_dossier_dossier_path(dossier), role: 'menuitem', method: :patch, data: { disable: true, confirm: "En continuant, vous allez supprimer ce dossier ainsi que les informations quil contient. Toute suppression entraîne lannulation de la démarche en cours.\n\nConfirmer la suppression ?" }) do
%span.icon.delete
.dropdown-description
= t('views.users.dossiers.dossier_action.delete_dossier')

View file

@ -1,8 +1,8 @@
.dropdown.edit-identity-action{ data: { controller: 'menu-button', popover: 'true' } } = render Dropdown::MenuComponent.new(wrapper: :div, wrapper_options: {class: ['edit-identity-action']}, menu_options: { class:['edit-identity-content'] }) do |menu|
%button.button.dropdown-button{ data: { menu_button_target: 'button' } } - menu.with_button_inner_html do
= t("views.shared.dossiers.demande.my_identity") = t("views.shared.dossiers.demande.my_identity")
#edit-identity-content.dropdown-content.fade-in-down{ data: { menu_button_target: 'menu' } } - menu.with_form do
- if dossier.procedure.for_individual - if dossier.procedure.for_individual
= render partial: "shared/dossiers/identite_individual", locals: { individual: dossier.individual } = render partial: "shared/dossiers/identite_individual", locals: { individual: dossier.individual }

View file

@ -10,5 +10,5 @@
.container .container
- if !@dossier.read_only? - if !@dossier.read_only?
= link_to t('views.users.dossiers.demande.edit_dossier'), modifier_dossier_path(@dossier), class: 'button accepted edit-form', title: "Modifier mon dossier tant qu'il n'est pas passé en instruction" = link_to t('views.users.dossiers.demande.edit_dossier'), modifier_dossier_path(@dossier), class: 'fr-btn fr-btn-sm', 'title'=> "Modifier mon dossier tant qu'il n'est pas passé en instruction"
.clearfix .clearfix

View file

@ -18,7 +18,7 @@
.header-actions .header-actions
= render partial: 'invites/dropdown', locals: { dossier: dossier } = render partial: 'invites/dropdown', locals: { dossier: dossier }
- if dossier.can_be_updated_by_user? && !current_page?(modifier_dossier_path(dossier)) - if dossier.can_be_updated_by_user? && !current_page?(modifier_dossier_path(dossier))
= link_to t('views.users.dossiers.show.header.edit_dossier'), modifier_dossier_path(dossier), class: 'button accepted edit-form', = link_to t('views.users.dossiers.show.header.edit_dossier'), modifier_dossier_path(dossier), class: 'fr-btn fr-btn-sm',
title: { label: t('views.users.dossiers.show.header.edit_dossier_title') } title: { label: t('views.users.dossiers.show.header.edit_dossier_title') }
= render(partial: 'users/dossiers/show/print_dossier', locals: { dossier: dossier }) = render(partial: 'users/dossiers/show/print_dossier', locals: { dossier: dossier })

View file

@ -1,6 +1 @@
.dropdown.print-menu-opener{ data: { controller: 'menu-button' } } = link_to t('views.users.dossiers.show.header.print'), dossier_path(dossier, format: :pdf), target: "_blank", rel: "noopener", title: t('views.users.dossiers.show.header.print_dossier'), class: 'fr-btn fr-icon-printer-line fr-btn--tertiary'
%button.button.dropdown-button.icon-only{ title: t('views.users.dossiers.show.header.print'), 'aria-label': 'imprimer', data: { menu_button_target: 'button' } }
%span.icon.printer
%ul#print-menu.print-menu.dropdown-content{ data: { menu_button_target: 'menu' } }
%li
= link_to t('views.users.dossiers.show.header.print_dossier'), dossier_path(dossier, format: :pdf), target: "_blank", rel: "noopener", class: "menu-item menu-link"

View file

@ -4,7 +4,7 @@
= turbo_stream.hide_all(@to_hide) = turbo_stream.hide_all(@to_hide)
- @to_update.each do |champ| - @to_update.each do |champ|
= fields_for champ.input_name, champ do |form| = fields_for champ.input_name, champ do |form|
= turbo_stream.morph champ.input_group_id do = turbo_stream.replace champ.input_group_id do
= render EditableChamp::EditableChampComponent.new champ:, form: = render EditableChamp::EditableChampComponent.new champ:, form:
= turbo_stream.remove_all(".editable-champ .spinner-removable"); = turbo_stream.remove_all(".editable-champ .spinner-removable");

View file

@ -112,6 +112,9 @@ en:
champ_remove: Remove champ_remove: Remove
champ_unavailable: Unavailable champ_unavailable: Unavailable
possible_values: possible_values:
link:
title: All possible values
text: See all possible values
title: Values title: Values
text_html: A short text text_html: A short text
textarea_html: A long text textarea_html: A long text
@ -314,7 +317,8 @@ en:
demande: demande:
edit_dossier: "Edit file" edit_dossier: "Edit file"
search: search:
search_file: Search a file placeholder: Search a file
search_file: Search
index: index:
dossiers: "Files" dossiers: "Files"
dossiers_list: dossiers_list:

View file

@ -103,6 +103,9 @@ fr:
champ_remove: Retirer champ_remove: Retirer
champ_unavailable: Indisponible champ_unavailable: Indisponible
possible_values: possible_values:
link:
title: Toutes les valeurs possibles
text: Voir toutes les valeurs possibles
title: Valeurs title: Valeurs
text_html: Un texte court text_html: Un texte court
textarea_html: Un texte long textarea_html: Un texte long
@ -310,7 +313,8 @@ fr:
demande: demande:
edit_dossier: "Modifier le dossier" edit_dossier: "Modifier le dossier"
search: search:
search_file: Rechercher un dossier placeholder: Rechercher un dossier
search_file: Rechercher
index: index:
dossiers: "Dossiers" dossiers: "Dossiers"
dossiers_list: dossiers_list:

View file

@ -2,7 +2,8 @@ en:
support: support:
index: index:
contact: Contact contact: Contact
intro_html: Contact us via this form and we will answer you as quickly as possible.<br>Make sure you provide all the required information so we can help you in the best way. intro_html: "<p>Contact us via this form and we will answer you as quickly as possible.</p>
<p>Make sure you provide all the required information so we can help you in the best way.</p>"
your_question: Your question your_question: Your question
our_answer: 👉 Our answer our_answer: 👉 Our answer
notice_pj_product: A screenshot can help us identify the element to improve. notice_pj_product: A screenshot can help us identify the element to improve.
@ -10,35 +11,28 @@ en:
procedure_info: procedure_info:
question: I've encountered a problem while completing my application question: I've encountered a problem while completing my application
answer_html: "<p>Are you sure that all the mandatory fields (<span class= mandatory> * </span>) are properly filled? answer_html: "<p>Are you sure that all the mandatory fields (<span class= mandatory> * </span>) are properly filled?
<p>If you have questions about the information requested, contact the service in charge of the procedure.</p> <p>If you have questions about the information requested, <a href=\"%{link_procedure_info}\">contact the service in charge of the procedure (FAQ)</a>.</p>"
<p><a href=%{link_procedure_info}>Find more information</a></p>"
instruction_info: instruction_info:
question: I have a question about the instruction of my application question: I have a question about the instruction of my application
answer_html: "<p>If you have questions about the instruction of your application (response delay for example), contact directly the instructor via our mail system.</p> answer_html: "<p>If you have questions about the instruction of your application (response delay for example), <a href=\"%{link_instruction_info}\">contact directly the instructor via our mail system (FAQ)</a>.</p>
<p><a href=%{link_instruction_info}>Find more information</a></p> <p>If you are facing technical issues on the website, use the form below. We will not be able to inform you about the instruction of your application.</p>"
<br>
<p>If you are facing technical issues on the website, use the form below. We will not be able to inform you about the instruction of your application.</p>"
product: product:
question: I have an idea to improve the website question: I have an idea to improve the website
answer_html: "<p>Got an idea? Please check our <strong>enhancement dashboard</strong></p> answer_html: "<p>Got an idea? <a href=\"%{link_product}\" rel=\"noopener noreferrer\" target=\"_blank\" title=\"Open features dashboard - New tab\">Please check our enhancement dashboard</a> :</p>
<ul><li>Vote for your priority improvements</li> <ul><li>Vote for your priority improvements</li>
<li>Share your own ideas</li></ul> <li>Share your own ideas</li></ul>"
<p><strong><a href=%{link_product}>➡ Access the enhancement dashboard</a></strong></p>"
lost_user: lost_user:
question: I am having trouble finding the procedure I am looking for question: I am having trouble finding the procedure I am looking for
answer_html: "<p>We invite you to contact the administration in charge of the procedure so they can provide you the link. answer_html: "<p>We invite you to contact the administration in charge of the procedure so they can provide you the link.
It should look like this: %{base_url}/commencer/NOM_DE_LA_DEMARCHE.</p> It should look like this: <code>%{base_url}/commencer/NOM_DE_LA_DEMARCHE</code>.</p>
<br> <p>You can <a href=\"%{link_lost_user}\">find here the most popular procedures (licence, detr, etc.)</a>.</p>"
<p>You can find here the most popular procedures (licence, detr, etc.) :</p>
<p><a href=%{link_lost_user}>%{link_lost_user}</a></p>"
other: other:
question: Other topic question: Other topic
admin: admin:
your_question: Your question your_question: Your question
admin_intro_html: "<p>As an administration, you can contact us through this form. We'll answer you as quickly as possibly by e-mail or phone.</p> admin_intro_html: "<p>As an administration, you can contact us through this form. We'll answer you as quickly as possibly by e-mail or phone.</p>
<br> <p><strong>Caution, this form is dedicated to public bodies only.</strong>
<p><strong>Caution, this form is dedicated to public bodies only.</strong> It does not concern individuals, companies nor associations (except those recognised of public utility). If you belong to one of these categories, contact us <a href=\"%{contact_path}\">here</a>.</p>"
It does not concern individuals, companies nor associations (except those recognised of public utility). If you belong to one of these categories, contact us <a href=%{contact_path}>here</a>.</p>"
contact_team: Contact our team contact_team: Contact our team
pro_phone_number: Professional phone number (direct line) pro_phone_number: Professional phone number (direct line)
pro_mail: Professional email address pro_mail: Professional email address

View file

@ -2,7 +2,8 @@ fr:
support: support:
index: index:
contact: Contact contact: Contact
intro_html: Contactez-nous via ce formulaire et nous vous répondrons dans les plus brefs délais.<br>Pensez bien à nous donner le plus dinformations possible pour que nous puissions vous aider au mieux. intro_html: "<p>Contactez-nous via ce formulaire et nous vous répondrons dans les plus brefs délais.</p>
<p>Pensez bien à nous donner le plus dinformations possible pour que nous puissions vous aider au mieux.</p>"
your_question: Votre question your_question: Votre question
our_answer: 👉 Notre réponse our_answer: 👉 Notre réponse
notice_pj_product: Une capture décran peut nous aider à identifier plus facilement lendroit à améliorer. notice_pj_product: Une capture décran peut nous aider à identifier plus facilement lendroit à améliorer.
@ -10,35 +11,28 @@ fr:
procedure_info: procedure_info:
question: Jai un problème lors du remplissage de mon dossier question: Jai un problème lors du remplissage de mon dossier
answer_html: "<p>Avez-vous bien vérifié que tous les champs obligatoires (<span class= mandatory> * </span>) sont remplis ? answer_html: "<p>Avez-vous bien vérifié que tous les champs obligatoires (<span class= mandatory> * </span>) sont remplis ?
<p>Si vous avez des questions sur les informations à saisir, contactez les services en charge de la démarche.</p> <p>Si vous avez des questions sur les informations à saisir, <a href=\"%{link_procedure_info}\">contactez les services en charge de la démarche (FAQ)</a>.</p>"
<p><a href=%{link_procedure_info}>En savoir plus</a></p>"
instruction_info: instruction_info:
question: Jai une question sur linstruction de mon dossier question: Jai une question sur linstruction de mon dossier
answer_html: "<p>Si vous avez des questions sur linstruction de votre dossier (par exemple sur les délais), nous vous invitons à contacter directement les services qui instruisent votre dossier par votre messagerie.</p> answer_html: "<p>Si vous avez des questions sur linstruction de votre dossier (par exemple sur les délais), nous vous invitons à <a href=\"%{link_instruction_info}\">contacter directement les services qui instruisent votre dossier par votre messagerie (FAQ)</a>.</p>
<p><a href=%{link_instruction_info}>En savoir plus</a></p> <p>Si vous souhaitez poser une question pour un problème technique sur le site, utilisez le formulaire ci-dessous. Nous ne pourrons pas vous renseigner sur linstruction de votre dossier.</p>"
<br>
<p>Si vous souhaitez poser une question pour un problème technique sur le site, utilisez le formulaire ci-dessous. Nous ne pourrons pas vous renseigner sur linstruction de votre dossier.</p>"
product: product:
question: Jai une idée damélioration pour votre site question: Jai une idée damélioration pour votre site
answer_html: "<p>Une idée ? Pensez à consulter notre <strong>tableau de bord des améliorations</strong></p> answer_html: "<p>Une idée ? Pensez à <a href=\"%{link_product}\" rel=\"noopener noreferrer\" target=\"_blank\" title=\"Ouvrir le tableau des évolutions - Nouvel onglet\">consulter notre tableau de bord des améliorations</a> :</p>
<ul><li>Votez pour vos améliorations prioritaires;</li> <ul><li>Votez pour vos améliorations prioritaires,</li>
<li>Proposez votre propre idée.</li></ul> <li>Proposez votre propre idée.</li></ul>"
<p><strong><a href=%{link_product}>➡ Accéder au tableau des améliorations</a></strong></p>"
lost_user: lost_user:
question: Je ne trouve pas la démarche que je veux faire question: Je ne trouve pas la démarche que je veux faire
answer_html: "<p>Nous vous invitons à contacter ladministration en charge de votre démarche pour quelle vous indique le lien à suivre. Celui-ci devrait ressembler à cela : %{base_url}/commencer/NOM_DE_LA_DEMARCHE.</p> answer_html: "<p>Nous vous invitons à contacter ladministration en charge de votre démarche pour quelle vous indique le lien à suivre. Celui-ci devrait ressembler à cela : <code>%{base_url}/commencer/NOM_DE_LA_DEMARCHE</code>.</p>
<br> <p>Vous pouvez aussi <a href=\"%{link_lost_user}\">consulter ici la liste de nos démarches les plus fréquentes (permis, detr etc)</a>.</p>"
<p>Vous pouvez aussi consulter ici la liste de nos démarches les plus frequentes (permis, detr etc) :</p>
<p><a href=%{link_lost_user}>%{link_lost_user}</a></p>"
other: other:
question: Autre sujet question: Autre sujet
admin: admin:
your_question: Votre question your_question: Votre question
admin_intro_html: "<p>En tant quadministration, vous pouvez nous contactez via ce formulaire. Nous vous répondrons dans les plus brefs délais, par email ou par téléphone.</p> admin_intro_html: "<p>En tant quadministration, vous pouvez nous contactez via ce formulaire. Nous vous répondrons dans les plus brefs délais, par email ou par téléphone.</p>
<br> <p><strong>Attention, ce formulaire est réservé uniquement aux organismes publics.</strong>
<p><strong>Attention, ce formulaire est réservé uniquement aux organismes publics.</strong> Il ne concerne ni les particuliers, ni les entreprises, ni les associations (sauf celles reconnues dutilité publique). Si c'est votre cas, rendez-vous sur notre
Il ne concerne ni les particuliers, ni les entreprises, ni les associations (sauf celles reconnues dutilité publique). Si c'est votre cas, rendez-vous sur notre <a href=\"%{contact_path}\">formulaire de contact public</a>.</p>"
<a href=%{contact_path}>formulaire de contact public</a>.</p>"
contact_team: Contactez notre équipe contact_team: Contactez notre équipe
pro_phone_number: Numéro de téléphone professionnel (ligne directe) pro_phone_number: Numéro de téléphone professionnel (ligne directe)
pro_mail: Adresse e-mail professionnelle pro_mail: Adresse e-mail professionnelle

View file

@ -0,0 +1,5 @@
class AddRemindedAtToAvis < ActiveRecord::Migration[6.1]
def change
add_column :avis, :reminded_at, :datetime
end
end

View file

@ -10,7 +10,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 2023_01_17_094317) do ActiveRecord::Schema.define(version: 2023_01_26_145329) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "pgcrypto" enable_extension "pgcrypto"
@ -164,6 +164,7 @@ ActiveRecord::Schema.define(version: 2023_01_17_094317) do
t.text "introduction" t.text "introduction"
t.datetime "revoked_at" t.datetime "revoked_at"
t.datetime "updated_at", null: false t.datetime "updated_at", null: false
t.datetime "reminded_at"
t.index ["claimant_id"], name: "index_avis_on_claimant_id" t.index ["claimant_id"], name: "index_avis_on_claimant_id"
t.index ["dossier_id"], name: "index_avis_on_dossier_id" t.index ["dossier_id"], name: "index_avis_on_dossier_id"
t.index ["experts_procedure_id"], name: "index_avis_on_experts_procedure_id" t.index ["experts_procedure_id"], name: "index_avis_on_experts_procedure_id"

View file

@ -32,25 +32,10 @@ namespace :benchmarks do
desc 'Attestation Template parser' desc 'Attestation Template parser'
task attestation_template_parser: :environment do task attestation_template_parser: :environment do
progress = ProgressReport.new(AttestationTemplate.count) procedure = Procedure.find(68139)
AttestationTemplate.find_each do |template| Benchmark.bm do |x|
parsed = TagsSubstitutionConcern::TagsParser.parse(template.body) x.report("Empty") { TagsSubstitutionConcern::TagsParser.parse('') }
serialized = parsed.map do |token| x.report("Démarche 68139") { TagsSubstitutionConcern::TagsParser.parse(procedure.attestation_template.body) }
case token
in { tag: tag }
"--#{tag}--"
in { text: text }
text
end
end.join('')
if serialized != template.body
throw "Template '#{serialized}' is not eq '#{template.body}' with attestation template #{template.id}"
end
progress.inc
rescue => e
pp "Error with attestation template #{template.id}"
throw e
end end
progress.finish
end end
end end

View file

@ -0,0 +1,20 @@
namespace :after_party do
desc 'Deployment task: fix_dossier_transfer_with_uppercase'
task fix_dossier_transfer_with_uppercase: :environment do
puts "Running deploy task 'fix_dossier_transfer_with_uppercase'"
# in production, about 1000, no need to track progress
DossierTransfer.all.find_each do |dt|
if /A-Z/.match?(dt.email)
dt.email = dt.email.downcase
dt.save
end
end
# Put your task implementation HERE.
# Update task as completed. If you remove the line below, the task will
# run with every deploy (or every time you call after_party:run).
AfterParty::TaskRecord
.create version: AfterParty::TaskRecorder.new(__FILE__).timestamp
end
end

View file

@ -865,6 +865,10 @@ describe Instructeurs::DossiersController, type: :controller do
} }
end end
before do
allow(PiecesJustificativesService).to receive(:generate_dossier_export).with([dossier], include_infos_administration: true).and_call_original
end
it 'includes an attachment' do it 'includes an attachment' do
expect(subject.headers['Content-Disposition']).to start_with('attachment; ') expect(subject.headers['Content-Disposition']).to start_with('attachment; ')
end end

View file

@ -26,6 +26,11 @@ describe Users::TransfersController, type: :controller do
it { expect(DossierTransfer.last.dossiers).to eq([dossier]) } it { expect(DossierTransfer.last.dossiers).to eq([dossier]) }
end end
context 'with upper case email' do
let(:email) { "Test@rspec.net" }
it { expect(DossierTransfer.last.email).to eq(email.strip.downcase) }
end
shared_examples 'email error' do shared_examples 'email error' do
it { expect { subject }.not_to change { DossierTransfer.count } } it { expect { subject }.not_to change { DossierTransfer.count } }
it { expect(flash.alert).to match([/invalide/]) } it { expect(flash.alert).to match([/invalide/]) }

View file

@ -1,7 +1,7 @@
describe ActiveStorage::DownloadableFile do describe ActiveStorage::DownloadableFile do
let(:dossier) { create(:dossier, :en_construction) } let(:dossier) { create(:dossier, :en_construction) }
subject(:list) { ActiveStorage::DownloadableFile.create_list_from_dossiers(Dossier.where(id: dossier.id)) } subject(:list) { ActiveStorage::DownloadableFile.create_list_from_dossiers(Dossier.where(id: dossier.id), with_bills: true, with_champs_private: true) }
describe 'create_list_from_dossiers' do describe 'create_list_from_dossiers' do
context 'when no piece_justificative is present' do context 'when no piece_justificative is present' do

View file

@ -1,5 +1,5 @@
describe Champs::DateChamp do describe Champs::DateChamp do
let(:date_champ) { build(:champ_date) } let(:date_champ) { create(:champ_date) }
describe '#convert_to_iso8601' do describe '#convert_to_iso8601' do
it 'preserves nil' do it 'preserves nil' do
@ -37,6 +37,12 @@ describe Champs::DateChamp do
champ.save champ.save
expect(champ.reload.value).to eq("2023-12-21") expect(champ.reload.value).to eq("2023-12-21")
end end
it 'converts to nil if false iso' do
champ = champ_with_value("2023-27-02")
champ.save
expect(champ.reload.value).to eq(nil)
end
end end
def champ_with_value(number) def champ_with_value(number)

View file

@ -516,7 +516,7 @@ describe TagsSubstitutionConcern, type: :model do
describe 'parser' do describe 'parser' do
it do it do
tokens = TagsSubstitutionConcern::TagsParser.parse("hello world --public--, --numéro du dossier--, un test--yolo-- encore du text\n---\n encore du text") tokens = TagsSubstitutionConcern::TagsParser.parse("hello world --public--, --numéro du dossier--, un test--yolo-- encore du text\n---\n encore du text --- et encore du text\n--tag--")
expect(tokens).to eq([ expect(tokens).to eq([
{ text: "hello world " }, { text: "hello world " },
{ tag: "public" }, { tag: "public" },
@ -524,14 +524,15 @@ describe TagsSubstitutionConcern, type: :model do
{ tag: "numéro du dossier" }, { tag: "numéro du dossier" },
{ text: ", un test" }, { text: ", un test" },
{ tag: "yolo" }, { tag: "yolo" },
{ text: " encore du text\n" + "---\n" + " encore du text" } { text: " encore du text\n" + "---\n" + " encore du text --- et encore du text\n" },
{ tag: "tag" }
]) ])
end end
it 'allow for - before tag' do it 'allow for - before tag' do
tokens = TagsSubstitutionConcern::TagsParser.parse("hello --yolo-- -- before-- --after -- -- around -- world ---numéro-du - dossier--") tokens = TagsSubstitutionConcern::TagsParser.parse("-----------------\nhello --yolo-- -- before-- --after -- -- around -- world ---numéro-du - dossier--")
expect(tokens).to eq([ expect(tokens).to eq([
{ text: "hello " }, { text: "-----------------\nhello " },
{ tag: "yolo" }, { tag: "yolo" },
{ text: " " }, { text: " " },
{ tag: "before" }, { tag: "before" },

View file

@ -239,7 +239,7 @@ describe Dossier do
end end
describe "#rebase" do describe "#rebase" do
let(:procedure) { create(:procedure, :with_type_de_champ_mandatory, :with_yes_no, :with_repetition, :with_datetime) } let(:procedure) { create(:procedure, types_de_champ_public: [{ type: :text, mandatory: true }, { type: :repetition, children: [{ type: :text }] }, { type: :datetime }, { type: :yes_no }, { type: :integer_number }]) }
let(:dossier) { create(:dossier, procedure: procedure) } let(:dossier) { create(:dossier, procedure: procedure) }
let(:yes_no_type_de_champ) { procedure.active_revision.types_de_champ_public.find { |tdc| tdc.type_champ == TypeDeChamp.type_champs.fetch(:yes_no) } } let(:yes_no_type_de_champ) { procedure.active_revision.types_de_champ_public.find { |tdc| tdc.type_champ == TypeDeChamp.type_champs.fetch(:yes_no) } }
@ -248,6 +248,8 @@ describe Dossier do
let(:text_champ) { dossier.champs_public.find(&:mandatory?) } let(:text_champ) { dossier.champs_public.find(&:mandatory?) }
let(:rebased_text_champ) { dossier.champs_public.find { |c| c.type_champ == TypeDeChamp.type_champs.fetch(:text) } } let(:rebased_text_champ) { dossier.champs_public.find { |c| c.type_champ == TypeDeChamp.type_champs.fetch(:text) } }
let(:rebased_number_champ) { dossier.champs_public.find { |c| c.type_champ == TypeDeChamp.type_champs.fetch(:integer_number) } }
let(:datetime_type_de_champ) { procedure.active_revision.types_de_champ_public.find { |tdc| tdc.type_champ == TypeDeChamp.type_champs.fetch(:datetime) } } let(:datetime_type_de_champ) { procedure.active_revision.types_de_champ_public.find { |tdc| tdc.type_champ == TypeDeChamp.type_champs.fetch(:datetime) } }
let(:datetime_champ) { dossier.champs_public.find { |c| c.type_champ == TypeDeChamp.type_champs.fetch(:datetime) } } let(:datetime_champ) { dossier.champs_public.find { |c| c.type_champ == TypeDeChamp.type_champs.fetch(:datetime) } }
let(:rebased_datetime_champ) { dossier.champs_public.find { |c| c.type_champ == TypeDeChamp.type_champs.fetch(:date) } } let(:rebased_datetime_champ) { dossier.champs_public.find { |c| c.type_champ == TypeDeChamp.type_champs.fetch(:date) } }
@ -287,7 +289,7 @@ describe Dossier do
libelle = text_type_de_champ.libelle libelle = text_type_de_champ.libelle
expect(dossier.revision).to eq(procedure.published_revision) expect(dossier.revision).to eq(procedure.published_revision)
expect(dossier.champs_public.size).to eq(4) expect(dossier.champs_public.size).to eq(5)
expect(repetition_champ.rows.size).to eq(2) expect(repetition_champ.rows.size).to eq(2)
expect(repetition_champ.rows[0].size).to eq(1) expect(repetition_champ.rows[0].size).to eq(1)
expect(repetition_champ.rows[1].size).to eq(1) expect(repetition_champ.rows[1].size).to eq(1)
@ -299,7 +301,7 @@ describe Dossier do
expect(procedure.revisions.size).to eq(3) expect(procedure.revisions.size).to eq(3)
expect(dossier.revision).to eq(procedure.published_revision) expect(dossier.revision).to eq(procedure.published_revision)
expect(dossier.champs_public.size).to eq(4) expect(dossier.champs_public.size).to eq(5)
expect(rebased_text_champ.value).to eq(text_champ.value) expect(rebased_text_champ.value).to eq(text_champ.value)
expect(rebased_text_champ.type_de_champ_id).not_to eq(text_champ.type_de_champ_id) expect(rebased_text_champ.type_de_champ_id).not_to eq(text_champ.type_de_champ_id)
expect(rebased_datetime_champ.type_champ).to eq(TypeDeChamp.type_champs.fetch(:date)) expect(rebased_datetime_champ.type_champ).to eq(TypeDeChamp.type_champs.fetch(:date))
@ -309,6 +311,7 @@ describe Dossier do
expect(rebased_repetition_champ.rows[1].size).to eq(2) expect(rebased_repetition_champ.rows[1].size).to eq(2)
expect(rebased_text_champ.rebased_at).not_to be_nil expect(rebased_text_champ.rebased_at).not_to be_nil
expect(rebased_datetime_champ.rebased_at).not_to be_nil expect(rebased_datetime_champ.rebased_at).not_to be_nil
expect(rebased_number_champ.rebased_at).to be_nil
end end
end end

View file

@ -1405,6 +1405,46 @@ describe Dossier do
end end
end end
describe '#geo_data' do
let(:dossier) { create(:dossier) }
let(:type_de_champ_carte) { create(:type_de_champ_carte, procedure: dossier.procedure) }
let(:geo_area) { create(:geo_area) }
let(:champ_carte) { create(:champ_carte, type_de_champ: type_de_champ_carte, geo_areas: [geo_area]) }
context "without data" do
it { expect(dossier.geo_data?).to be_falsey }
end
context "with geo data in public champ" do
before do
dossier.champs_public << champ_carte
end
it { expect(dossier.geo_data?).to be_truthy }
end
context "with geo data in private champ" do
before do
dossier.champs_private << champ_carte
end
it { expect(dossier.geo_data?).to be_truthy }
end
it "should solve N+1 problem" do
dossier.champs_public << create_list(:champ_carte, 3, type_de_champ: type_de_champ_carte, geo_areas: [create(:geo_area)])
count = 0
callback = lambda { |*_args| count += 1 }
ActiveSupport::Notifications.subscribed(callback, "sql.active_record") do
dossier.geo_data?
end
expect(count).to eq(1)
end
end
describe 'dossier_operation_log after dossier deletion' do describe 'dossier_operation_log after dossier deletion' do
let(:dossier) { create(:dossier) } let(:dossier) { create(:dossier) }
let(:dossier_operation_log) { create(:dossier_operation_log, dossier: dossier) } let(:dossier_operation_log) { create(:dossier_operation_log, dossier: dossier) }
@ -1853,6 +1893,19 @@ describe Dossier do
before { dossier.champs_public << champ_piece_justificative } before { dossier.champs_public << champ_piece_justificative }
it { expect(Champs::PieceJustificativeChamp.where(dossier: new_dossier).first.piece_justificative_file.first.blob).to eq(champ_piece_justificative.piece_justificative_file.first.blob) } it { expect(Champs::PieceJustificativeChamp.where(dossier: new_dossier).first.piece_justificative_file.first.blob).to eq(champ_piece_justificative.piece_justificative_file.first.blob) }
end end
context 'for Champs::AddressChamp, original_champ.data is duped' do
let(:dossier) { create(:dossier) }
let(:type_de_champs_adress) { create(:type_de_champ_address, procedure: dossier.procedure) }
let(:etablissement) { create(:etablissement) }
let(:champ_address) { create(:champ_address, type_de_champ: type_de_champs_adress, external_id: 'Address', data: { city_code: '75019' }) }
before { dossier.champs_public << champ_address }
it { expect(Champs::AddressChamp.where(dossier: dossier).first.data).not_to be_nil }
it { expect(Champs::AddressChamp.where(dossier: dossier).first.external_id).not_to be_nil }
it { expect(Champs::AddressChamp.where(dossier: new_dossier).first.external_id).to eq(champ_address.external_id) }
it { expect(Champs::AddressChamp.where(dossier: new_dossier).first.data).to eq(champ_address.data) }
end
end end
context 'private are renewd' do context 'private are renewd' do

View file

@ -854,4 +854,15 @@ describe ProcedurePresentation do
it { is_expected.to eq(sorted_ids) } it { is_expected.to eq(sorted_ids) }
end end
end end
describe '#field_enum' do
context "field is groupe_instructeur" do
let!(:gi_2) { instructeur.groupe_instructeurs.create(label: 'gi2', procedure:) }
let!(:gi_3) { instructeur.groupe_instructeurs.create(label: 'gi3', procedure: create(:procedure)) }
subject { procedure_presentation.field_enum('groupe_instructeur/id') }
it { is_expected.to eq([['défaut', procedure.defaut_groupe_instructeur.id], ['gi2', gi_2.id]]) }
end
end
end end

View file

@ -872,4 +872,21 @@ describe ProcedureRevision do
it { expect(draft.dependent_conditions(first_champ)).to eq([second_champ]) } it { expect(draft.dependent_conditions(first_champ)).to eq([second_champ]) }
it { expect(draft.dependent_conditions(second_champ)).to eq([]) } it { expect(draft.dependent_conditions(second_champ)).to eq([]) }
end end
describe 'only_present_on_draft?' do
let(:procedure) { create(:procedure, types_de_champ_public: [{ libelle: 'Un champ texte' }]) }
let(:type_de_champ) { procedure.draft_revision.types_de_champ_public.first }
it {
expect(type_de_champ.only_present_on_draft?).to be_truthy
procedure.publish!
expect(type_de_champ.only_present_on_draft?).to be_falsey
procedure.draft_revision.remove_type_de_champ(type_de_champ.stable_id)
expect(type_de_champ.only_present_on_draft?).to be_falsey
expect(type_de_champ.revisions.count).to eq(1)
procedure.publish_revision!
expect(type_de_champ.only_present_on_draft?).to be_falsey
expect(type_de_champ.revisions.count).to eq(1)
}
end
end end

View file

@ -1,10 +1,11 @@
describe PiecesJustificativesService do describe PiecesJustificativesService do
describe '.liste_documents' do describe '.liste_documents' do
let(:for_expert) { false } let(:with_champs_private) { true }
let(:with_bills) { true }
subject do subject do
PiecesJustificativesService PiecesJustificativesService
.liste_documents(Dossier.where(id: dossier.id), for_expert) .liste_documents(Dossier.where(id: dossier.id), with_bills:, with_champs_private:)
.map(&:first) .map(&:first)
end end
@ -59,8 +60,8 @@ describe PiecesJustificativesService do
it { expect(subject).to match_array(private_pj_champ.call(dossier).piece_justificative_file.attachments) } it { expect(subject).to match_array(private_pj_champ.call(dossier).piece_justificative_file.attachments) }
context 'for expert' do context 'without private champ' do
let(:for_expert) { true } let(:with_champs_private) { false }
it { expect(subject).to be_empty } it { expect(subject).to be_empty }
end end
@ -171,8 +172,8 @@ describe PiecesJustificativesService do
expect(subject).to match_array([dossier_bs.serialized.attachment, dossier_bs.signature.attachment]) expect(subject).to match_array([dossier_bs.serialized.attachment, dossier_bs.signature.attachment])
end end
context 'for expert' do context 'without bills' do
let(:for_expert) { true } let(:with_bills) { false }
it { expect(subject).to be_empty } it { expect(subject).to be_empty }
end end
@ -192,8 +193,8 @@ describe PiecesJustificativesService do
it { expect(subject).to match_array(dol.serialized.attachment) } it { expect(subject).to match_array(dol.serialized.attachment) }
context 'for expert' do context 'without bills' do
let(:for_expert) { true } let(:with_bills) { false }
it { expect(subject).to be_empty } it { expect(subject).to be_empty }
end end

View file

@ -18,7 +18,7 @@ describe 'As an administrateur I wanna clone a procedure', js: true do
scenario do scenario do
visit admin_procedures_path visit admin_procedures_path
expect(page.find_by_id('procedures')['data-item-count']).to eq('1') expect(page.find_by_id('procedures')['data-item-count']).to eq('1')
page.all('.procedures-actions-btn').first.click page.all('.admin-procedures-list-row .dropdown .fr-btn').first.click
page.all('.clone-btn').first.click page.all('.clone-btn').first.click
visit admin_procedures_path(statut: "brouillons") visit admin_procedures_path(statut: "brouillons")
expect(page.find_by_id('procedures')['data-item-count']).to eq('1') expect(page.find_by_id('procedures')['data-item-count']).to eq('1')

View file

@ -116,7 +116,6 @@ describe 'Inviting an expert:' do
click_on '1 avis à donner' click_on '1 avis à donner'
click_on avis.dossier.user.email click_on avis.dossier.user.email
find(:css, '[aria-controls=print-pj-menu]').click
click_on 'Télécharger le dossier et toutes ses pièces jointes' click_on 'Télécharger le dossier et toutes ses pièces jointes'
# For some reason, clicking the download link does not trigger the download in the headless browser ; # For some reason, clicking the download link does not trigger the download in the headless browser ;
# So we need to go to the download link directly # So we need to go to the download link directly

View file

@ -26,6 +26,21 @@ describe 'dropdown list with other option activated', js: true do
find('.radios').find('label:last-child').find('input').select_option find('.radios').find('label:last-child').find('input').select_option
expect(page).to have_selector('.drop_down_other', visible: true) expect(page).to have_selector('.drop_down_other', visible: true)
end end
scenario "Getting back from other save the new option", js: true do
fill_individual
choose "Autre"
fill_in("Veuillez saisir votre autre choix", with: "My choice")
wait_until { user_dossier.champs_public.first.value == "My choice" }
expect(user_dossier.champs_public.first.value).to eq("My choice")
choose "Secondary 1.1"
wait_until { user_dossier.champs_public.first.value == "Secondary 1.1" }
expect(user_dossier.champs_public.first.value).to eq("Secondary 1.1")
end
end end
context 'with select' do context 'with select' do

View file

@ -27,7 +27,7 @@ describe 'users/dossiers/show/header.html.haml', type: :view do
end end
it 'can download the dossier' do it 'can download the dossier' do
expect(rendered).to have_text("Tout le dossier") expect(rendered).to have_selector('a[title="Tout le dossier"]')
end end
end end
@ -45,7 +45,7 @@ describe 'users/dossiers/show/header.html.haml', type: :view do
end end
it 'can download the dossier' do it 'can download the dossier' do
expect(rendered).to have_text("Tout le dossier") expect(rendered).to have_selector('a[title="Tout le dossier"]')
end end
it 'does not display a new procedure link' do it 'does not display a new procedure link' do
@ -68,7 +68,7 @@ describe 'users/dossiers/show/header.html.haml', type: :view do
end end
it 'can download the dossier' do it 'can download the dossier' do
expect(rendered).to have_text("Tout le dossier") expect(rendered).to have_selector('a[title="Tout le dossier"]')
end end
it 'displays a new procedure link' do it 'displays a new procedure link' do
@ -105,7 +105,7 @@ describe 'users/dossiers/show/header.html.haml', type: :view do
end end
it 'can download the dossier' do it 'can download the dossier' do
expect(rendered).to have_text("Tout le dossier") expect(rendered).to have_selector('a[title="Tout le dossier"]')
end end
end end
@ -124,7 +124,7 @@ describe 'users/dossiers/show/header.html.haml', type: :view do
end end
it 'can not download the dossier' do it 'can not download the dossier' do
expect(rendered).not_to have_text("Tout le dossier") expect(rendered).not_to have_selector('a[title="Tout le dossier"]')
end end
end end
end end