Merge pull request #5534 from betagouv/dev

2020-09-03-01
This commit is contained in:
Kara Diaby 2020-09-03 15:46:02 +02:00 committed by GitHub
commit 323ef832dc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
99 changed files with 815 additions and 1293 deletions

18
.github/workflows/sentry-release.yml vendored Normal file
View file

@ -0,0 +1,18 @@
on:
push:
tags:
- '*'
name: Publish release on Sentry
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Sentry Release
uses: getsentry/action-release@v1.0.0
env:
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
SENTRY_PROJECT: rails
with:
environment: production

View file

@ -22,6 +22,7 @@ gem 'delayed_job_active_record'
gem 'delayed_job_web'
gem 'devise' # Gestion des comptes utilisateurs
gem 'devise-async'
gem 'devise-i18n'
gem 'discard'
gem 'dotenv-rails', require: 'dotenv/rails-now' # dotenv should always be loaded before rails
gem 'ffi-geos', require: false

View file

@ -205,6 +205,8 @@ GEM
devise-async (1.0.0)
activejob (>= 5.0)
devise (>= 4.0)
devise-i18n (1.9.2)
devise (>= 4.7.1)
diff-lcs (1.3)
discard (1.2.0)
activerecord (>= 4.2, < 7)
@ -787,6 +789,7 @@ DEPENDENCIES
delayed_job_web
devise
devise-async
devise-i18n
discard
dotenv-rails
factory_bot

View file

@ -5,32 +5,11 @@
//
// = require trix
// We need to override trix.csss image gallery styles to accommodate the
// <action-text-attachment> element we wrap around attachments. Otherwise,
// images in galleries will be squished by the max-width: 33%; rule.
.trix-content {
.attachment-gallery {
> action-text-attachment,
> .attachment {
flex: 1 0 33%;
padding: 0 0.5em;
max-width: 33%;
}
&.attachment-gallery--2,
&.attachment-gallery--4 {
> action-text-attachment,
> .attachment {
flex-basis: 50%;
max-width: 50%;
}
}
}
action-text-attachment {
.attachment {
padding: 0 !important;
max-width: 100% !important;
}
}
.trix-button-group.trix-button-group--file-tools {
display: none;
}
trix-editor {
min-height: 10em;
background-color: #FFFFFF;
}

View file

@ -0,0 +1,11 @@
// Push the timestamps column to the right of the row
.admin-procedures-list-timestamps {
margin-left: auto;
}
// Fix a Safari flexbox bug where the inner procedure logo
// would stretch the container vertically.
// See https://stackoverflow.com/questions/57516373/image-stretching-in-flexbox-in-safari
.admin-procedures-list-row.infos {
align-items: flex-start;
}

View file

@ -1,42 +0,0 @@
class Admin::MailTemplatesController < AdminController
before_action :retrieve_procedure
def index
@mail_templates = mail_templates
end
def edit
@mail_template = find_mail_template_by_slug(params[:id])
end
def update
mail_template = find_mail_template_by_slug(params[:id])
mail_template.update(update_params)
flash.notice = "Email mis à jour"
redirect_to edit_admin_procedure_mail_template_path(mail_template.procedure_id, params[:id])
end
private
def mail_templates
[
@procedure.initiated_mail_template,
@procedure.received_mail_template,
@procedure.closed_mail_template,
@procedure.refused_mail_template,
@procedure.without_continuation_mail_template
]
end
def find_mail_template_by_slug(slug)
mail_templates.find { |template| template.class.const_get(:SLUG) == slug }
end
def update_params
{
procedure_id: params[:procedure_id],
subject: params[:mail_template][:subject],
body: params[:mail_template][:body]
}
end
end

View file

@ -2,7 +2,11 @@ class FranceConnect::ParticulierController < ApplicationController
before_action :redirect_to_login_if_fc_aborted, only: [:callback]
def login
if FranceConnectService.enabled?
redirect_to FranceConnectService.authorization_uri
else
redirect_to new_user_session_path
end
end
def callback

View file

@ -6,6 +6,24 @@ module NewAdministrateur
@mail_templates = mail_templates
end
def edit
@procedure = procedure
@mail_template = find_mail_template_by_slug(params[:id])
end
def update
@procedure = procedure
mail_template = find_mail_template_by_slug(params[:id])
if mail_template.update(update_params)
flash.notice = "Email mis à jour"
else
flash.alert = mail_template.errors.full_messages
end
redirect_to edit_admin_procedure_mail_template_path(mail_template.procedure_id, params[:id])
end
def preview
mail_template = find_mail_template_by_slug(params[:id])
dossier = Dossier.new(id: '1', procedure: procedure)
@ -13,7 +31,7 @@ module NewAdministrateur
@dossier = dossier
@logo_url = procedure.logo_url
@service = procedure.service
@rendered_template = sanitize(mail_template.body)
@rendered_template = sanitize(mail_template.rich_body.body.to_html)
@actions = mail_template.actions_for_dossier(dossier)
render(template: 'notification_mailer/send_notification', layout: 'mailers/notifications_layout')
@ -38,5 +56,14 @@ module NewAdministrateur
def find_mail_template_by_slug(slug)
mail_templates.find { |template| template.class.const_get(:SLUG) == slug }
end
def update_params
mail_template_id = params[:id]
{
procedure_id: params[:procedure_id],
subject: params["mails_#{mail_template_id}"] ? params["mails_#{mail_template_id}"][:subject] : params["mails_#{mail_template_id}_mail"][:subject],
body: params["mails_#{mail_template_id}"] ? params["mails_#{mail_template_id}"][:rich_body] : params["mails_#{mail_template_id}_mail"][:rich_body]
}
end
end
end

View file

@ -66,6 +66,7 @@ module NewAdministrateur
def create
@procedure = Procedure.new(procedure_params.merge(administrateurs: [current_administrateur]))
@procedure.draft_revision = @procedure.revisions.build
if !@procedure.save
flash.now.alert = @procedure.errors.full_messages
@ -73,8 +74,6 @@ module NewAdministrateur
else
flash.notice = 'Démarche enregistrée.'
current_administrateur.instructeur.assign_to_procedure(@procedure)
# FIXUP: needed during transition to revisions
RevisionsMigration.add_revisions(@procedure)
redirect_to champs_admin_procedure_path(@procedure)
end

View file

@ -2,7 +2,6 @@ module NewAdministrateur
class TypesDeChampController < AdministrateurController
before_action :retrieve_procedure, only: [:create, :update, :move, :destroy]
before_action :procedure_locked?, only: [:create, :update, :move, :destroy]
before_action :revisions_migration
def create
type_de_champ = @procedure.draft_revision.add_type_de_champ(type_de_champ_create_params)
@ -16,7 +15,7 @@ module NewAdministrateur
end
def update
type_de_champ = @procedure.draft_revision.find_or_clone_type_de_champ(type_de_champ_stable_id)
type_de_champ = @procedure.draft_revision.find_or_clone_type_de_champ(TypeDeChamp.to_stable_id(params[:id]))
if type_de_champ.update(type_de_champ_update_params)
reset_procedure
@ -27,13 +26,13 @@ module NewAdministrateur
end
def move
@procedure.draft_revision.move_type_de_champ(type_de_champ_stable_id, (params[:position] || params[:order_place]).to_i)
@procedure.draft_revision.move_type_de_champ(TypeDeChamp.to_stable_id(params[:id]), (params[:position] || params[:order_place]).to_i)
head :no_content
end
def destroy
@procedure.draft_revision.remove_type_de_champ(type_de_champ_stable_id)
@procedure.draft_revision.remove_type_de_champ(TypeDeChamp.to_stable_id(params[:id]))
reset_procedure
head :no_content
@ -41,19 +40,11 @@ module NewAdministrateur
private
def type_de_champ_stable_id
TypeDeChamp.find(params[:id]).stable_id
end
def revisions_migration
# FIXUP: needed during transition to revisions
RevisionsMigration.add_revisions(@procedure)
end
def serialize_type_de_champ(type_de_champ)
{
type_de_champ: type_de_champ.as_json(
except: [
:id,
:created_at,
:options,
:order_place,
@ -73,7 +64,7 @@ module NewAdministrateur
:piece_justificative_template_url,
:quartiers_prioritaires
]
)
).merge(id: TypeDeChamp.format_stable_id(type_de_champ.stable_id))
}
end
@ -92,7 +83,7 @@ module NewAdministrateur
:type_champ)
if type_de_champ_params[:parent_id].present?
type_de_champ_params[:parent_id] = TypeDeChamp.find(type_de_champ_params[:parent_id]).stable_id
type_de_champ_params[:parent_id] = TypeDeChamp.to_stable_id(type_de_champ_params[:parent_id])
end
type_de_champ_params

View file

@ -313,7 +313,8 @@ class StatsController < ApplicationController
procedure_id_type_de_champs_count = TypeDeChamp
.where(private: false)
.group(:procedure_id)
.joins(:revision)
.group('procedure_revisions.procedure_id')
.count
groupe_instructeur_id_type_de_champs_count = groupe_instructeurs.reduce({}) do |acc, (gi_id, procedure_id)|

View file

@ -259,8 +259,6 @@ module Users
return redirect_to url_for dossiers_path
end
# FIXUP: needed during transition to revisions
RevisionsMigration.add_revisions(procedure)
dossier = Dossier.new(
revision: procedure.active_revision,
groupe_instructeur: procedure.defaut_groupe_instructeur,

View file

@ -8,8 +8,8 @@ class ProcedureDashboard < Administrate::BaseDashboard
# which determines how the attribute is displayed
# on pages throughout the dashboard.
ATTRIBUTE_TYPES = {
types_de_champ: TypesDeChampCollectionField,
types_de_champ_private: TypesDeChampCollectionField,
published_types_de_champ: TypesDeChampCollectionField,
published_types_de_champ_private: TypesDeChampCollectionField,
path: ProcedureLinkField,
dossiers: Field::HasMany,
administrateurs: Field::HasMany,
@ -70,8 +70,8 @@ class ProcedureDashboard < Administrate::BaseDashboard
:whitelisted_at,
:hidden_at,
:closed_at,
:types_de_champ,
:types_de_champ_private,
:published_types_de_champ,
:published_types_de_champ_private,
:for_individual,
:auto_archive_on,
:initiated_mail_template,

View file

@ -140,7 +140,7 @@ module ApplicationHelper
end
def try_format_date(date)
date.present? ? I18n.l(date) : ''
date.present? ? I18n.l(date, format: :long) : ''
end
def try_format_datetime(datetime)

View file

@ -40,6 +40,19 @@ function MapEditor({ featureCollection, url, preview, hasCadastres, ign }) {
hasCadastres
]);
const translations = [
['.mapbox-gl-draw_line', 'Tracer une ligne'],
['.mapbox-gl-draw_polygon', 'Dessiner un polygone'],
['.mapbox-gl-draw_point', 'Ajouter un point'],
['.mapbox-gl-draw_trash', 'Supprimer']
];
for (const [selector, translation] of translations) {
const element = document.querySelector(selector);
if (element) {
element.setAttribute('title', translation);
}
}
const onFeatureFocus = useCallback(
({ detail }) => {
const { id } = detail;

View file

@ -210,7 +210,6 @@ export const FIELDS = [
'drop_down_list_value',
'libelle',
'mandatory',
'order_place',
'parcelles_agricoles',
'parent_id',
'piece_justificative_template',

View file

@ -22,7 +22,7 @@ export function moveTypeDeChampOperation(typeDeChamp, index, queue) {
return queue.enqueue({
path: `/${typeDeChamp.id}/move`,
method: 'patch',
payload: { order_place: index }
payload: { position: index }
});
}

View file

@ -37,8 +37,7 @@ export default function typeDeChampsReducer(state, { type, params, done }) {
function addTypeDeChamp(state, typeDeChamps, insertAfter, done) {
const typeDeChamp = {
...state.defaultTypeDeChampAttributes,
order_place: typeDeChamps.length
...state.defaultTypeDeChampAttributes
};
createTypeDeChampOperation(typeDeChamp, state.queue)

View file

@ -1,6 +1,7 @@
import '../shared/polyfills';
import Rails from '@rails/ujs';
import * as ActiveStorage from '@rails/activestorage';
import 'trix';
import '@rails/actiontext';
import 'whatwg-fetch'; // window.fetch polyfill
import ReactRailsUJS from 'react_ujs';

View file

@ -7,6 +7,12 @@ const COMPLETE_CLASS = 'direct-upload--complete';
rendering upload progress bar. It is used to handle
direct-upload form ujs events but also in the
Uploader delegate used with uploads on json api.
As the associated DOM element may disappear for some
reason (a dynamic React list, an element being removed
and recreated again later, etc.), this class doesn't
raise any error if the associated DOM element cannot
be found.
*/
export default class ProgressBar {
static init(input, id, file) {
@ -17,28 +23,32 @@ export default class ProgressBar {
static start(id) {
const element = getDirectUploadElement(id);
if (element) {
element.classList.remove(PENDING_CLASS);
}
}
static progress(id, progress) {
const element = getDirectUploadProgressElement(id);
if (element) {
element.style.width = `${progress}%`;
}
}
static error(id, error) {
const element = getDirectUploadElement(id);
if (element) {
element.classList.add(ERROR_CLASS);
element.setAttribute('title', error);
}
}
static end(id) {
const element = getDirectUploadElement(id);
if (element) {
element.classList.add(COMPLETE_CLASS);
}
}
static render(id, filename) {
return `<div id="direct-upload-${id}" class="direct-upload ${PENDING_CLASS}" data-direct-upload-id="${id}">

View file

@ -1,6 +1,8 @@
class ApplicationJob < ActiveJob::Base
DEFAULT_MAX_ATTEMPTS_JOBS = 25
retry_on Excon::Error::BadRequest
before_perform do |job|
Rails.logger.info("#{job.class.name} started at #{Time.zone.now}")
end

View file

@ -24,7 +24,7 @@ class FindDubiousProceduresJob < CronJob
.where(procedures: { closed_at: nil, whitelisted_at: nil })
dubious_procedures_and_tdcs = forbidden_tdcs
.group_by(&:procedure_id)
.group_by { |type_de_champ| type_de_champ.procedure.id }
.map { |_procedure_id, tdcs| [tdcs[0].procedure, tdcs] }
AdministrationMailer.dubious_procedures(dubious_procedures_and_tdcs).deliver_later

View file

@ -1,21 +0,0 @@
class TmpDossiersMigrateRevisionsJob < ApplicationJob
def perform(except)
dossiers = Dossier.with_discarded.where(revision_id: nil)
dossiers.where
.not(id: except)
.includes(procedure: [:draft_revision, :published_revision])
.limit(2000)
.find_each do |dossier|
if dossier.procedure.present?
dossier.update_column(:revision_id, dossier.procedure.active_revision.id)
else
except << dossier.id
end
end
if dossiers.where.not(id: except).exists?
TmpDossiersMigrateRevisionsJob.perform_later(except)
end
end
end

View file

@ -1,7 +1,10 @@
class VirusScannerJob < ApplicationJob
queue_as :active_storage_analysis
# If by the time the job runs the blob has been deleted, ignore the error
discard_on ActiveRecord::RecordNotFound
# If the file is deleted during the scan, ignore the error
discard_on ActiveStorage::FileNotFoundError
def perform(blob)
metadata = extract_metadata_via_virus_scanner(blob)

View file

@ -47,6 +47,9 @@ class Champ < ApplicationRecord
scope :public_only, -> { where(private: false) }
scope :private_only, -> { where(private: true) }
scope :ordered, -> { includes(:type_de_champ).order(:row, 'types_de_champ.order_place') }
scope :public_ordered, -> { public_only.joins(dossier: { revision: :revision_types_de_champ }).where('procedure_revision_types_de_champ.type_de_champ_id = champs.type_de_champ_id').order(:position) }
scope :private_ordered, -> { private_only.joins(dossier: { revision: :revision_types_de_champ_private }).where('procedure_revision_types_de_champ.type_de_champ_id = champs.type_de_champ_id').order(:position) }
scope :root, -> { where(parent_id: nil) }
before_validation :set_dossier_id, if: :needs_dossier_id?

View file

@ -33,8 +33,9 @@ module MailTemplateConcern
module ClassMethods
def default_for_procedure(procedure)
template_name = default_template_name_for_procedure(procedure)
body = ActionController::Base.new.render_to_string(template: template_name)
new(subject: const_get(:DEFAULT_SUBJECT), body: body, procedure: procedure)
rich_body = ActionController::Base.new.render_to_string(template: template_name)
trix_rich_body = rich_body.gsub(/(?<!^|[.-])(?<!<\/strong>)\n/, '')
new(subject: const_get(:DEFAULT_SUBJECT), rich_body: trix_rich_body, procedure: procedure)
end
def default_template_name_for_procedure(procedure)

View file

@ -62,8 +62,8 @@ class Dossier < ApplicationRecord
has_one_attached :justificatif_motivation
has_many :champs, -> { root.public_only.ordered }, inverse_of: :dossier, dependent: :destroy
has_many :champs_private, -> { root.private_only.ordered }, class_name: 'Champ', inverse_of: :dossier, dependent: :destroy
has_many :champs, -> { root.public_ordered }, inverse_of: :dossier, dependent: :destroy
has_many :champs_private, -> { root.private_ordered }, class_name: 'Champ', inverse_of: :dossier, dependent: :destroy
has_many :commentaires, inverse_of: :dossier, dependent: :destroy
has_many :invites, dependent: :destroy
has_many :follows, -> { active }, inverse_of: :dossier
@ -76,10 +76,13 @@ class Dossier < ApplicationRecord
has_many :dossier_operation_logs, -> { order(:created_at) }, dependent: :nullify, inverse_of: :dossier
belongs_to :groupe_instructeur, optional: false
has_one :procedure, through: :groupe_instructeur
belongs_to :revision, class_name: 'ProcedureRevision', optional: true
belongs_to :revision, class_name: 'ProcedureRevision', optional: false
belongs_to :user, optional: false
has_one :procedure, through: :revision
has_many :types_de_champ, through: :revision
has_many :types_de_champ_private, through: :revision
accepts_nested_attributes_for :champs
accepts_nested_attributes_for :champs_private
@ -315,7 +318,6 @@ class Dossier < ApplicationRecord
accepts_nested_attributes_for :individual
delegate :siret, :siren, to: :etablissement, allow_nil: true
delegate :types_de_champ, to: :procedure
delegate :france_connect_information, to: :user
before_save :build_default_champs, if: Proc.new { groupe_instructeur_id_was.nil? }
@ -326,7 +328,7 @@ class Dossier < ApplicationRecord
after_create :send_draft_notification_email
validates :user, presence: true
validates :individual, presence: true, if: -> { procedure.for_individual? }
validates :individual, presence: true, if: -> { revision.procedure.for_individual? }
validates :groupe_instructeur, presence: true
def motivation
@ -351,10 +353,10 @@ class Dossier < ApplicationRecord
end
def build_default_champs
procedure.build_champs.each do |champ|
revision.build_champs.each do |champ|
champs << champ
end
procedure.build_champs_private.each do |champ|
revision.build_champs_private.each do |champ|
champs_private << champ
end
end

View file

@ -56,13 +56,16 @@ class Procedure < ApplicationRecord
MAX_DUREE_CONSERVATION = 36
MAX_DUREE_CONSERVATION_EXPORT = 3.hours
has_many :types_de_champ, -> { root.public_only.ordered }, inverse_of: :procedure, dependent: :destroy
has_many :types_de_champ_private, -> { root.private_only.ordered }, class_name: 'TypeDeChamp', inverse_of: :procedure, dependent: :destroy
has_many :revisions, -> { order(:id) }, class_name: 'ProcedureRevision', inverse_of: :procedure, dependent: :destroy
belongs_to :draft_revision, class_name: 'ProcedureRevision', optional: true
belongs_to :draft_revision, class_name: 'ProcedureRevision', optional: false
belongs_to :published_revision, class_name: 'ProcedureRevision', optional: true
has_many :deleted_dossiers, dependent: :destroy
has_many :published_types_de_champ, through: :published_revision, source: :types_de_champ
has_many :published_types_de_champ_private, through: :published_revision, source: :types_de_champ_private
has_many :draft_types_de_champ, through: :draft_revision, source: :types_de_champ
has_many :draft_types_de_champ_private, through: :draft_revision, source: :types_de_champ_private
has_one :module_api_carto, dependent: :destroy
has_one :attestation_template, dependent: :destroy
@ -74,6 +77,14 @@ class Procedure < ApplicationRecord
brouillon? ? draft_revision : published_revision
end
def types_de_champ
brouillon? ? draft_types_de_champ : published_types_de_champ
end
def types_de_champ_private
brouillon? ? draft_types_de_champ_private : published_types_de_champ_private
end
has_many :administrateurs_procedures
has_many :administrateurs, through: :administrateurs_procedures, after_remove: -> (procedure, _admin) { procedure.validate! }
has_many :groupe_instructeurs, dependent: :destroy
@ -93,9 +104,6 @@ class Procedure < ApplicationRecord
has_one_attached :notice
has_one_attached :deliberation
accepts_nested_attributes_for :types_de_champ, reject_if: proc { |attributes| attributes['libelle'].blank? }, allow_destroy: true
accepts_nested_attributes_for :types_de_champ_private, reject_if: proc { |attributes| attributes['libelle'].blank? }, allow_destroy: true
scope :brouillons, -> { where(aasm_state: :brouillon) }
scope :publiees, -> { where(aasm_state: :publiee) }
scope :closes, -> { where(aasm_state: [:close, :depubliee]) }
@ -114,9 +122,15 @@ class Procedure < ApplicationRecord
scope :for_api, -> {
includes(
:administrateurs,
:module_api_carto,
published_revision: [
:types_de_champ_private,
:types_de_champ,
:module_api_carto
:types_de_champ
],
draft_revision: [
:types_de_champ_private,
:types_de_champ
]
)
}
@ -289,22 +303,13 @@ class Procedure < ApplicationRecord
# to save a dossier created from this method
def new_dossier
Dossier.new(
procedure: self,
revision: active_revision,
champs: build_champs,
champs_private: build_champs_private,
champs: active_revision.build_champs,
champs_private: active_revision.build_champs_private,
groupe_instructeur: defaut_groupe_instructeur
)
end
def build_champs
types_de_champ.map(&:build_champ)
end
def build_champs_private
types_de_champ_private.map(&:build_champ)
end
def path_customized?
!path.match?(/[[:xdigit:]]{8}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{12}/)
end
@ -318,9 +323,6 @@ class Procedure < ApplicationRecord
end
def clone(admin, from_library)
# FIXUP: needed during transition to revisions
RevisionsMigration.add_revisions(self)
is_different_admin = !admin.owns?(self)
populate_champ_stable_ids
@ -371,19 +373,11 @@ class Procedure < ApplicationRecord
end
procedure.save
procedure.draft_revision.types_de_champ.update_all(revision_id: procedure.draft_revision.id)
procedure.draft_revision.types_de_champ_private.update_all(revision_id: procedure.draft_revision.id)
# FIXUP: needed during transition to revisions
procedure.draft_revision.types_de_champ.each do |type_de_champ|
procedure.types_de_champ << type_de_champ
end
procedure.draft_revision.types_de_champ_private.each do |type_de_champ|
procedure.types_de_champ_private << type_de_champ
end
procedure.draft_types_de_champ.update_all(revision_id: procedure.draft_revision.id)
procedure.draft_types_de_champ_private.update_all(revision_id: procedure.draft_revision.id)
if is_different_admin || from_library
procedure.types_de_champ.each { |tdc| tdc.options&.delete(:old_pj) }
procedure.draft_types_de_champ.each { |tdc| tdc.options&.delete(:old_pj) }
end
procedure
@ -480,7 +474,7 @@ class Procedure < ApplicationRecord
def closed_mail_template_attestation_inconsistency_state
# As an optimization, dont check the predefined templates (they are presumed correct)
if closed_mail.present?
tag_present = closed_mail.body.include?("--lien attestation--")
tag_present = closed_mail.body.to_s.include?("--lien attestation--")
if attestation_template&.activated? && !tag_present
:missing_tag
elsif !attestation_template&.activated? && tag_present
@ -608,23 +602,14 @@ class Procedure < ApplicationRecord
end
def after_publish(canonical_procedure = nil)
# FIXUP: needed during transition to revisions
if RevisionsMigration.add_revisions(self)
update!(published_at: Time.zone.now, canonical_procedure: canonical_procedure)
else
update!(published_at: Time.zone.now, canonical_procedure: canonical_procedure, draft_revision: create_new_revision, published_revision: draft_revision)
end
end
def after_close
# FIXUP: needed during transition to revisions
RevisionsMigration.add_revisions(self)
update!(closed_at: Time.zone.now)
end
def after_unpublish
# FIXUP: needed during transition to revisions
RevisionsMigration.add_revisions(self)
update!(unpublished_at: Time.zone.now)
end

View file

@ -9,15 +9,22 @@
#
class ProcedureRevision < ApplicationRecord
self.implicit_order_column = :created_at
belongs_to :procedure, -> { with_discarded }, inverse_of: :revisions
belongs_to :procedure, -> { with_discarded }, inverse_of: :revisions, optional: false
has_many :revision_types_de_champ, -> { public_only.ordered }, class_name: 'ProcedureRevisionTypeDeChamp', foreign_key: :revision_id, dependent: :destroy, inverse_of: :revision
has_many :revision_types_de_champ_private, -> { private_only.ordered }, class_name: 'ProcedureRevisionTypeDeChamp', foreign_key: :revision_id, dependent: :destroy, inverse_of: :revision
has_many :types_de_champ, through: :revision_types_de_champ, source: :type_de_champ
has_many :types_de_champ_private, through: :revision_types_de_champ_private, source: :type_de_champ
def build_champs
types_de_champ.map(&:build_champ)
end
def build_champs_private
types_de_champ_private.map(&:build_champ)
end
def add_type_de_champ(params)
params[:procedure] = procedure
params[:revision] = self
if params[:parent_id]
@ -27,15 +34,9 @@ class ProcedureRevision < ApplicationRecord
params[:order_place] = types_de_champ.present? ? types_de_champ.last.order_place + 1 : 0
end.create(params)
elsif params[:private]
types_de_champ_private.tap do |types_de_champ|
# FIXUP: needed during transition to revisions
params[:order_place] = types_de_champ.present? ? types_de_champ.last.order_place + 1 : 0
end.create(params)
types_de_champ_private.create(params)
else
types_de_champ.tap do |types_de_champ|
# FIXUP: needed during transition to revisions
params[:order_place] = types_de_champ.present? ? types_de_champ.last.order_place + 1 : 0
end.create(params)
types_de_champ.create(params)
end
end
@ -112,8 +113,6 @@ class ProcedureRevision < ApplicationRecord
if types_de_champ.delete_at(old_index)
types_de_champ.insert(new_index, type_de_champ)
.map.with_index do |type_de_champ, index|
# FIXUP: needed during transition to revisions
type_de_champ.update!(order_place: index)
[type_de_champ.id, index]
end
else

View file

@ -26,15 +26,11 @@ class ProcedureRevisionTypeDeChamp < ApplicationRecord
private
def set_position
self.position ||= if private?
if revision.types_de_champ_private.present?
revision.revision_types_de_champ_private.filter(&:persisted?).last.position + 1
else
0
end
else
if revision.types_de_champ.present?
revision.revision_types_de_champ.filter(&:persisted?).last.position + 1
self.position ||= begin
types_de_champ = (private? ? revision.revision_types_de_champ_private : revision.revision_types_de_champ).filter(&:persisted?)
if types_de_champ.present?
types_de_champ.last.position + 1
else
0
end

View file

@ -18,6 +18,8 @@
# stable_id :bigint
#
class TypeDeChamp < ApplicationRecord
self.ignored_columns = ['procedure_id']
enum type_champs: {
text: 'text',
textarea: 'textarea',
@ -49,8 +51,8 @@ class TypeDeChamp < ApplicationRecord
repetition: 'repetition'
}
belongs_to :procedure, optional: false
belongs_to :revision, class_name: 'ProcedureRevision', optional: true
has_one :procedure, through: :revision
belongs_to :parent, class_name: 'TypeDeChamp', optional: true
has_many :types_de_champ, -> { ordered }, foreign_key: :parent_id, class_name: 'TypeDeChamp', inverse_of: :parent, dependent: :destroy
@ -73,7 +75,6 @@ class TypeDeChamp < ApplicationRecord
serialize :options, WithIndifferentAccess
after_initialize :set_dynamic_type
before_validation :setup_procedure
after_create :populate_stable_id
attr_reader :dynamic_type
@ -298,9 +299,34 @@ class TypeDeChamp < ApplicationRecord
.merge(include: { types_de_champ: TYPES_DE_CHAMP_BASE })
def self.as_json_for_editor
includes(piece_justificative_template_attachment: :blob,
types_de_champ: [piece_justificative_template_attachment: :blob])
.as_json(TYPES_DE_CHAMP)
includes(piece_justificative_template_attachment: :blob, types_de_champ: [piece_justificative_template_attachment: :blob]).as_json(TYPES_DE_CHAMP)
end
def read_attribute_for_serialization(name)
if name == 'id'
self.class.format_stable_id(stable_id)
else
super
end
end
# FIXME: We are changing how id is exposed to the editor.
# We used to expose type_de_champ.id as primary key to the editor. With revisions
# we need primary key to be type_de_champ.stable_id because any update can create
# a new version but we do not want editor to know about this.
# This is only needed for a clean migration without downtime. We want to ensure
# that if editor send a simple id because it was loaded before deployment
# we would still do the right thing.
def self.format_stable_id(stable_id)
"stable:#{stable_id}"
end
def self.to_stable_id(id_or_stable_id)
if id_or_stable_id.to_s =~ /^stable:/
id_or_stable_id.to_s.gsub(/^stable:/, '')
else
find(id_or_stable_id).stable_id
end
end
private
@ -311,12 +337,6 @@ class TypeDeChamp < ApplicationRecord
result.blank? ? [] : [''] + result
end
def setup_procedure
types_de_champ.each do |type_de_champ|
type_de_champ.procedure = procedure
end
end
def populate_stable_id
if !stable_id
update_column(:stable_id, id)

View file

@ -67,7 +67,7 @@ class DossierSerializer < ActiveModel::Serializer
end
def types_de_piece_justificative
PiecesJustificativesService.serialize_types_de_champ_as_type_pj(object)
PiecesJustificativesService.serialize_types_de_champ_as_type_pj(object.revision)
end
def email

View file

@ -48,6 +48,6 @@ class ProcedureSerializer < ActiveModel::Serializer
end
def types_de_piece_justificative
PiecesJustificativesService.serialize_types_de_champ_as_type_pj(object)
PiecesJustificativesService.serialize_types_de_champ_as_type_pj(object.active_revision)
end
end

View file

@ -1,4 +1,8 @@
class FranceConnectService
def self.enabled?
ENV.fetch("FRANCE_CONNECT_ENABLED", "enabled") == "enabled"
end
def self.authorization_uri
client = FranceConnectParticulierClient.new

View file

@ -12,8 +12,8 @@ class PiecesJustificativesService
.sum(&:byte_size)
end
def self.serialize_types_de_champ_as_type_pj(procedure)
tdcs = procedure.types_de_champ.filter { |type_champ| type_champ.old_pj.present? }
def self.serialize_types_de_champ_as_type_pj(revision)
tdcs = revision.types_de_champ.filter { |type_champ| type_champ.old_pj.present? }
tdcs.map.with_index do |type_champ, order_place|
description = type_champ.description
if /^(?<original_description>.*?)(?:[\r\n]+)Récupérer le formulaire vierge pour mon dossier : (?<lien_demarche>http.*)$/m =~ description

View file

@ -1,36 +0,0 @@
class RevisionsMigration
def self.add_revisions(procedure)
if procedure.draft_revision.present?
return false
end
procedure.draft_revision = procedure.revisions.create
procedure.save!(validate: false)
add_types_de_champs_to_revision(procedure, :types_de_champ)
add_types_de_champs_to_revision(procedure, :types_de_champ_private)
if !procedure.brouillon?
published_revision = procedure.draft_revision
procedure.draft_revision = procedure.create_new_revision
procedure.published_revision = published_revision
procedure.save!(validate: false)
end
true
end
def self.add_types_de_champs_to_revision(procedure, types_de_champ_scope)
types_de_champ = procedure.send(types_de_champ_scope)
types_de_champ.where(revision_id: nil).update_all(revision_id: procedure.draft_revision.id)
types_de_champ.each.with_index do |type_de_champ, index|
type_de_champ.types_de_champ.where(revision_id: nil).update_all(revision_id: procedure.draft_revision.id)
procedure.draft_revision.send(:"revision_#{types_de_champ_scope}").create!(
type_de_champ: type_de_champ,
position: index
)
end
end
end

View file

@ -1,40 +0,0 @@
- if params[:id] == 'closed_mail'
= render partial: 'admin/closed_mail_template_attestation_inconsistency_alert'
.white-back
%h3
= @mail_template.class.const_get(:DISPLAYED_NAME)
= form_for @mail_template,
as: 'mail_template',
url: admin_procedure_mail_template_path(@procedure, @mail_template.class.const_get(:SLUG)),
method: :put do |f|
.row
.col-md-6
.form-group.string.optional.mail_template_subject
= f.label :subject, "Objet de l'email", class: 'control-label string optional'
= f.text_field :subject, class: 'form-control string optional'
.form-group.text.optional.mail_template_body
= f.label :body, "Corps de l'email", class: 'control-label string optional'
= f.text_area :body, class: 'wysihtml5 form-control text optional'
.text-right
= link_to "Annuler", admin_procedure_mail_templates_path(@procedure), class: "btn btn-default"
= f.submit 'Mettre à jour', class: "btn btn-default btn-success"
= link_to "Prévisualiser", preview_admin_procedure_mail_template_path(@procedure, @mail_template.class.const_get(:SLUG)), class: "btn btn-primary", target: "_blank"
.row
.col-md-12
%table.table
%tr
%th.col-md-3
Balise
%th
Description
- @mail_template.tags.each do |tag|
%tr
%td
%code{ style: "white-space: pre-wrap;" }
= "--#{tag[:libelle]}--"
%td
= tag[:description]

View file

@ -1,14 +0,0 @@
= render partial: 'admin/closed_mail_template_attestation_inconsistency_alert'
#custom-mails
.wrapper
%h1 E-mails personnalisables
%table.table
%tr
%th{ colspan: 2 }
Type d'email
- @mail_templates.each do |mail_template|
%tr
%td
= mail_template.class.const_get(:DISPLAYED_NAME)
%td.text-right
= link_to "Personnaliser l'e-mail", edit_admin_procedure_mail_template_path(@procedure, mail_template.class.const_get(:SLUG))

View file

@ -4,5 +4,5 @@
- if defined?(service) && service && service.nom.present?
= service.nom
- else
-# The WORD JOINER unicode entity prevents email clients from auto-linking the signature
Léquipe demarches-simplifiees&#8288;.fr
-# The WORD JOINER unicode entity (&#8288;) prevents email clients from auto-linking the signature
Léquipe #{APPLICATION_NAME.gsub(".","&#8288;.").html_safe}

View file

@ -0,0 +1 @@
%iframe{ src: preview_admin_procedure_mail_template_path, width: '100%', height: '650px' }

View file

@ -0,0 +1,47 @@
= f.label :subject do
Objet de l'email
%span.mandatory *
= f.text_field :subject, required: true
= f.label :body do
Corps de l'email
%span.mandatory *
= f.rich_text_area :rich_body, required: true, class: "mb-4"
#tags-table
%h2.add-tag-title
Insérer une balise
%p.notice
Copiez-collez les balises ci-dessous pour afficher automatiquement linformation souhaitée.
.head
.tag Balise
.description Description
.items
- @mail_template.tags.each do |tag|
.item
%code.tag
= "--#{tag[:libelle]}--"
.description
= tag[:description]
-# Disable accepting dropped images and traduce toolbar tooltips
:javascript
addEventListener('trix-file-accept', function(e) { e.preventDefault(); });
addEventListener("trix-initialize", function(e) {
document.querySelector('button[data-trix-attribute="bold"]').setAttribute('title', 'Gras');
document.querySelector('button[data-trix-attribute="italic"]').setAttribute('title', 'Italique');
document.querySelector('button[data-trix-attribute="strike"]').setAttribute('title', 'Barrer');
document.querySelector('button[data-trix-attribute="href"]').setAttribute('title', 'Créer lien');
document.querySelector('button[data-trix-attribute="heading1"]').setAttribute('title', 'Titre');
document.querySelector('button[data-trix-attribute="quote"]').setAttribute('title', 'Citation');
document.querySelector('button[data-trix-attribute="bullet"]').setAttribute('title', 'Liste à puce');
document.querySelector('button[data-trix-attribute="number"]').setAttribute('title', 'Liste numérotée');
document.querySelector('button[data-trix-action="increaseNestingLevel"]').setAttribute('title', 'Indenter');
document.querySelector('button[data-trix-action="decreaseNestingLevel"]').setAttribute('title', 'Désindenter');
document.querySelector('button[data-trix-action="undo"]').setAttribute('title', 'Annuler la modification');
document.querySelector('button[data-trix-action="redo"]').setAttribute('title', 'Appliquer à nouveau la modification');
document.querySelector('.trix-button.trix-button--dialog[data-trix-method="setAttribute"]').value = "Créer lien";
document.querySelector('.trix-button.trix-button--dialog[data-trix-method="removeAttribute"]').value = "Effacer lien";
})

View file

@ -0,0 +1,33 @@
- if params[:id] == 'closed_mail'
= render partial: 'admin/closed_mail_template_attestation_inconsistency_alert'
= render partial: 'new_administrateur/breadcrumbs',
locals: { steps: [link_to('Démarches', admin_procedures_path),
link_to(@procedure.libelle, admin_procedure_path(@procedure)),
link_to("Emails", admin_procedure_mail_templates_path(@procedure)),
@mail_template.class.const_get(:DISPLAYED_NAME)] }
.procedure-form
.procedure-form__columns.container
= form_for @mail_template,
url: admin_procedure_mail_template_path(@procedure, @mail_template.class.const_get(:SLUG)),
method: :put,
html: { class: 'form procedure-form__column--form' } do |f|
%h1.page-title= @mail_template.class.const_get(:DISPLAYED_NAME)
= render partial: 'form', locals: { f: f }
.procedure-form__actions.sticky--bottom
.actions-right
= f.submit 'Enregistrer', class: 'button primary send'
.procedure-form__column--preview
.procedure-form__preview.sticky--top
%h3
.procedure-form__preview-title
Aperçu
.notice
Cet aperçu est mis à jour après chaque sauvegarde.
.procedure-preview
= render partial: 'apercu', locals: { procedure: @procedure }

View file

@ -8,6 +8,10 @@
.flex.justify-between
%div
.card-title= mail_template.class.const_get(:DISPLAYED_NAME)
- if mail_template.updated_at.blank?
%p.notice= mail_template.class.const_get(:DISPLAYED_NAME) === 'Accusé de réception' ? 'Personnalisé' : 'Modèle standard'
- else
%span.badge.baseline modifié le #{mail_template.updated_at.strftime('%d-%m-%Y')}
%div
= link_to 'Modifier', edit_admin_procedure_mail_template_path(@procedure, mail_template.class.const_get(:SLUG)), class: 'button'

View file

@ -1,7 +1,6 @@
- procedures.each do |procedure|
.card
.flex.justify-between
.flex
.admin-procedures-list-row.infos.flex
- if procedure.logo.present?
= image_tag procedure.logo, alt: procedure.libelle, width: '100'
.flex.column.ml-1
@ -9,7 +8,7 @@
= link_to procedure.libelle, admin_procedure_path(procedure), style: 'color: black;'
= link_to(procedure_lien(procedure), procedure_lien(procedure), class: 'procedure-lien mb-1')
%div
.admin-procedures-list-timestamps
%p.notice N° #{procedure.id}
%p.notice créée le #{procedure.created_at.strftime('%d/%m/%Y')}
- if procedure.published_at.present?
@ -17,7 +16,7 @@
- if procedure.closed_at.present?
%p.notice archivée le #{procedure.closed_at.strftime('%d/%m/%Y')}
.flex.justify-between
.admin-procedures-list-row.actions.flex.justify-between
%div
- if feature_enabled?(:administrateur_routage)
%span.icon.person

View file

@ -40,7 +40,7 @@
- if !@procedure.locked?
.card-admin
- if @procedure.types_de_champ.count > 0
- if @procedure.draft_types_de_champ.count > 0
%div
%span.icon.accept
%p.card-admin-status-accept Validé
@ -50,7 +50,7 @@
%p.card-admin-status-todo À faire
%div
%p.card-admin-title
%span.badge.baseline= @procedure.types_de_champ.count
%span.badge.baseline= @procedure.draft_types_de_champ.count
Champs du formulaire
%p.card-admin-subtitle À remplir par les usagers
.card-admin-action
@ -148,7 +148,7 @@
- if !@procedure.locked?
.card-admin
- if @procedure.types_de_champ_private.present?
- if @procedure.draft_types_de_champ_private.present?
%div
%span.icon.accept
%p.card-admin-status-accept Validé

View file

@ -1,4 +1,5 @@
.france-connect-login
- if FranceConnectService.enabled?
.france-connect-login
%h2
= t('views.shared.france_connect_login.title')
%p
@ -9,3 +10,5 @@
= link_to t('views.shared.france_connect_login.help_link'), "https://franceconnect.gouv.fr/", target: "_blank", rel: "noopener", class: "link"
.france-connect-login-separator
= t('views.shared.france_connect_login.separator')
- else
<!-- FranceConnect is not configured -->

View file

@ -2,45 +2,41 @@
#contact-form
.container
%h1.new-h1 Contactez notre équipe
%h1.new-h1
= t('contact_team', scope: [:supportadmin])
.description
En tant qu'administration, vous pouvez nous contactez via ce formulaire. Nous vous répondrons dans les plus brefs délais, par email ou par téléphone.
= t('admin_intro_html', scope: [:supportadmin], contact_path: contact_path)
%br
%br
%strong
Attention, ce formulaire est réservée uniquement aux organismes publics.
Il ne concerne ni les particuliers, ni les entreprises, ni les associations (sauf celles reconnues d'utilité publique). Si c'est votre cas, rendez-vous sur notre
= link_to contact_path do
formulaire de contact public
\.
%p.mandatory-explanation= t('asterisk_html', scope: [:utils])
= form_tag contact_path, method: :post, class: 'form' do |f|
- if !user_signed_in?
.contact-champ
= label_tag :email do
Adresse email professionnelle
= t('pro_mail', scope: [:supportadmin])
%span.mandatory *
= text_field_tag :email, params[:email], required: true
.contact-champ
= label_tag :type do
Catégorie
= t('your_question', scope: [:support, :question])
%span.mandatory *
= select_tag :type, options_for_select(@options, params[:type])
.contact-champ
= label_tag :phone, 'Numéro de téléphone professionnel (ligne directe)'
= label_tag :phone do
= t('professional_phone_number', scope: [:supportadmin])
= text_field_tag :phone
.contact-champ
= label_tag :subject do
Sujet
= t('subject', scope: [:utils])
= text_field_tag :subject, params[:subject], required: false
.contact-champ
= label_tag :text do
Message
= t('message', scope: [:utils])
%span.mandatory *
= text_area_tag :text, params[:text], rows: 6, required: true
@ -48,4 +44,4 @@
= hidden_field_tag :admin, true
.send-wrapper
= button_tag 'Envoyer le message', type: :submit, class: 'button send primary'
= button_tag t('send_mail', scope: [:utils]), type: :submit, class: 'button send primary'

View file

@ -25,7 +25,7 @@
%hr
%p.center
%span Vous êtes nouveau sur demarches&#8209;simplifiees.fr ?
%span Vous êtes nouveau sur #{APPLICATION_NAME.gsub("-","&#8209;").html_safe} ?
%br
%br
= link_to "Trouvez votre démarche", COMMENT_TROUVER_MA_DEMARCHE_URL, target: "_blank", class: "button expend secondary"

View file

@ -1,13 +1,13 @@
require File.expand_path('boot', __dir__)
require 'rails/all'
require_relative 'application_name'
# Require the gems listed in Gemfile, including any gems
# you've limited to :test, :development, or :production.
Bundler.require(*Rails.groups)
Dotenv::Railtie.load
require_relative 'application_name'
module TPS
class Application < Rails::Application

View file

@ -6,6 +6,9 @@ APPLICATION_NAME="demarches-simplifiees.fr"
APPLICATION_SHORTNAME="d-s.fr"
APPLICATION_BASE_URL="https://www.demarches-simplifiees.fr"
# Utilisation de France Connect
# FRANCE_CONNECT_ENABLED="disabled" # "enabled" par défaut
# Personnalisation d'instance - Adresses Email de l'application et téléphone
# CONTACT_EMAIL=""
# EQUIPE_EMAIL=""

View file

@ -1,5 +1,8 @@
Rails.application.config.active_storage.service_urls_expire_in = 1.hour
Rails.application.config.active_storage.analyzers.delete ActiveStorage::Analyzer::ImageAnalyzer
Rails.application.config.active_storage.analyzers.delete ActiveStorage::Analyzer::VideoAnalyzer
ActiveSupport.on_load(:active_storage_blob) do
include BlobSignedIdConcern
include BlobVirusScannerConcern

View file

@ -1,60 +0,0 @@
# Additional translations at https://github.com/plataformatec/devise/wiki/I18n
en:
devise:
confirmations:
confirmed: "Your email address has been successfully confirmed."
send_instructions: "You will receive an email with instructions for how to confirm your email address in a few minutes."
send_paranoid_instructions: "If your email address exists in our database, you will receive an email with instructions for how to confirm your email address in a few minutes."
failure:
already_authenticated: "You are already signed in."
inactive: "Your account is not activated yet."
invalid: "Invalid %{authentication_keys} or password."
locked: "Your account is locked."
last_attempt: "You have one more attempt before your account is locked."
not_found_in_database: "Invalid %{authentication_keys} or password."
timeout: "Your session expired. Please sign in again to continue."
unauthenticated: "You need to sign in or sign up before continuing."
unconfirmed: "You have to confirm your email address before continuing."
mailer:
confirmation_instructions:
subject: "Confirmation instructions"
reset_password_instructions:
subject: "Reset password instructions"
unlock_instructions:
subject: "Unlock instructions"
omniauth_callbacks:
failure: "Could not authenticate you from %{kind} because \"%{reason}\"."
success: "Successfully authenticated from %{kind} account."
passwords:
no_token: "You cant access this page without coming from a password reset email. If you do come from a password reset email, please make sure you used the full URL provided."
send_instructions: "You will receive an email with instructions on how to reset your password in a few minutes."
send_paranoid_instructions: "If your email address exists in our database, you will receive a password recovery link at your email address in a few minutes."
updated: "Your password has been changed successfully. You are now signed in."
updated_not_active: "Your password has been changed successfully."
registrations:
destroyed: "Bye! Your account has been successfully cancelled. We hope to see you again soon."
signed_up: "Welcome! You have signed up successfully."
signed_up_but_inactive: "You have signed up successfully. However, we could not sign you in because your account is not yet activated."
signed_up_but_locked: "You have signed up successfully. However, we could not sign you in because your account is locked."
signed_up_but_unconfirmed: "A message with a confirmation link has been sent to your email address. Please follow the link to activate your account."
update_needs_confirmation: "You updated your account successfully, but we need to verify your new email address. Please check your email and follow the confirm link to confirm your new email address."
updated: "Your account has been updated successfully."
sessions:
signed_in: "Signed in successfully."
signed_out: "Signed out successfully."
already_signed_out: "Signed out successfully."
unlocks:
send_instructions: "You will receive an email with instructions for how to unlock your account in a few minutes."
send_paranoid_instructions: "If your account exists, you will receive an email with instructions for how to unlock it in a few minutes."
unlocked: "Your account has been unlocked successfully. Please sign in to continue."
errors:
messages:
already_confirmed: "was already confirmed, please try signing in"
confirmation_period_expired: "needs to be confirmed within %{period}, please request a new one"
expired: "has expired, please request a new one"
not_found: "not found"
not_locked: "was not locked"
not_saved:
one: "1 error prohibited this %{resource} from being saved:"
other: "%{count} errors prohibited this %{resource} from being saved:"

View file

@ -1,64 +0,0 @@
# Additional translations at https://github.com/plataformatec/devise/wiki/I18n
fr:
devise:
confirmations:
confirmed: "Votre compte a été activé."
send_instructions: "Vous allez recevoir un email avec les instructions nécessaires à lactivation de votre compte dans quelques minutes."
send_paranoid_instructions: "Si votre adresse email existe dans notre base de données, vous allez bientôt recevoir un email contenant les instructions dactivation de votre compte."
failure:
already_authenticated: "Vous êtes déjà connecté"
inactive: "Votre compte nest pas encore activé."
invalid: "adresse email ou mot de passe incorrect."
last_attempt: "Vous avez droit à une tentative avant que votre compte ne soit verrouillé."
locked: "Votre compte est verrouillé."
not_found_in_database: "adresse email ou mot de passe invalide."
timeout: "Votre session est expirée. Veuillez vous reconnecter pour continuer."
unauthenticated: "Vous devez vous connecter ou vous inscrire pour continuer."
unconfirmed: "Vous devez confirmer votre adresse email pour continuer. Cliquez sur le lien qui vous a été envoyé par email."
mailer:
confirmation_instructions:
subject: "Instructions dactivation de votre compte"
reset_password_instructions:
subject: "Instructions pour changer le mot de passe"
unlock_instructions:
subject: "Instructions pour déverrouiller le compte"
email_changed:
subject: "Changement dadresse email"
password_change:
subject: "Votre mot de passe a été modifié avec succés."
omniauth_callbacks:
failure: "Nous navons pas pu vous authentifier via %{kind} : '%{reason}'."
success: "Authentifié avec succès via %{kind}."
passwords:
no_token: "Vous ne pouvez accéder à cette page sans passer par un email de réinitialisation de mot de passe. Si vous êtes passé par un email de ce type, assurez-vous dutiliser lURL complète."
send_instructions: "Vous allez recevoir les instructions de réinitialisation du mot de passe dans quelques instants"
send_paranoid_instructions: "Si votre adresse email existe dans notre base de données, vous allez recevoir un lien de réinitialisation par email"
updated: "Votre mot de passe a été changé avec succès, vous êtes maintenant connecté"
updated_not_active: "Votre mot de passe a été changé avec succès."
registrations:
destroyed: "Votre compte a été supprimé avec succès. Nous espérons vous revoir bientôt."
signed_up: "Bienvenue, vous êtes connecté."
signed_up_but_inactive: "Vous êtes bien enregistré. Vous ne pouvez cependant pas vous connecter car votre compte nest pas encore activé."
signed_up_but_locked: "Vous êtes bien enregistré. Vous ne pouvez cependant pas vous connecter car votre compte est verrouillé."
signed_up_but_unconfirmed: "Nous vous avons envoyé un email contenant un lien dactivation. Ouvrez ce lien pour activer votre compte."
update_needs_confirmation: "Vous devez confirmer votre nouvelle adresse email. Vérifiez vos emails, et cliquez sur le lien de confirmation pour confirmer votre changement dadresse."
updated: "Votre compte a été modifié avec succès."
sessions:
signed_in: "Connecté."
signed_out: "Déconnecté."
already_signed_out: "Déconnecté."
unlocks:
send_instructions: "Vous allez recevoir les instructions nécessaires au déverrouillage de votre compte dans quelques instants"
send_paranoid_instructions: "Si votre compte existe, vous allez bientôt recevoir un email contenant les instructions pour le déverrouiller."
unlocked: "Votre compte a été déverrouillé avec succès, vous êtes maintenant connecté."
errors:
messages:
already_confirmed: "a déjà été validé(e), veuillez essayer de vous connecter"
confirmation_period_expired: "à activer dans les %{period}, merci de faire une nouvelle demande"
expired: "a expiré, merci den faire une nouvelle demande"
not_found: "na pas été trouvé(e)"
not_locked: "nétait pas verrouillé(e)"
not_saved:
one: "1 erreur a empêché ce(tte) %{resource} dêtre sauvegardé(e) :"
other: "%{count} erreurs ont empêché ce(tte) %{resource} dêtre sauvegardé(e) :"

View file

@ -22,9 +22,6 @@
en:
utils:
deconnexion: "Log out"
involved: "See concerned people"
no-commentaires: "There is no message yet, feel free to start the first one."
depositaire: "Dépositaire"
pj: "Attachments"
asterisk_html: Fields marked by an asterisk ( <span class = mandatory>*</span> ) are mandatory.
file_number: File number
@ -60,63 +57,7 @@ en:
submit:
publish: Publish
reopen: Reopen
supportadmin:
# admin demande rdv: Demande de RDV pour une présentation à distance de demarches-simplifiees.fr
admin question: I have a question about demarches-simplifiees.fr
admin soucis: I am facing a technical issue on demarches-simplifiees.fr
admin suggestion produit: I have a suggestion for an evolution
admin demande compte: I want to open an admin account with an Orange, Wanadoo, etc. email
admin autre: Other topic
number:
currency:
format:
delimiter: ","
format: "%u%n"
precision: 2
separator: "."
significant: false
strip_insignificant_zeros: false
unit: "€"
format:
delimiter: ","
precision: 3
separator: "."
significant: false
strip_insignificant_zeros: false
human:
decimal_units:
format: "%n %u"
units:
billion: Billion
million: Million
quadrillion: Quadrillion
thousand: Thousand
trillion: Trillion
unit: ''
format:
delimiter: ''
precision: 3
significant: true
strip_insignificant_zeros: true
storage_units:
format: "%n %u"
units:
byte:
one: Byte
other: Bytes
gb: GB
kb: KB
mb: MB
pb: PB
tb: TB
percentage:
format:
delimiter: ''
format: "%n%"
precision:
format:
delimiter: ''
activerecord:
attributes:
user:
@ -126,13 +67,7 @@ en:
password: 'password'
errors:
messages:
blank: "must be filled"
not_a_number: 'must be a number'
not_an_integer: 'must be an integer (without digit after the comma)'
greater_than: "must be greater than %{count}"
greater_than_or_equal_to: "must be greater than or equal to %{count}"
less_than: "must be less than %{count}"
less_than_or_equal_to: "must be less than or equal to %{count}"
not_a_phone: 'Invalid phone number'
models:
attestation_template:
attributes:
@ -197,98 +132,9 @@ en:
# parcelles_agricoles_empty:
# one: "Aucune parcelle agricole sur la zone sélectionnée"
# other: "Aucune parcelle agricole sur les zones sélectionnées"
not_an_integer: "must be an integer (without decimal)"
blank: "can't be blank"
date:
abbr_day_names:
- Sun
- Mon
- Tue
- Wed
- Thu
- Fri
- Sat
abbr_month_names:
-
- Jan
- Feb
- Mar
- Apr
- May
- Jun
- Jul
- Aug
- Sep
- Oct
- Nov
- Dec
month_names:
-
- January
- February
- March
- April
- May
- June
- July
- August
- September
- October
- November
- December
order:
- :year
- :month
- :day
day_names:
- Sunday
- Monday
- Tuesday
- Wednesday
- Thursday
- Friday
- Saturday
formats:
default: "%Y-%m-%d"
long: "%B %d, %Y"
short: "%b %d"
datetime:
distance_in_words:
about_x_hours:
one: about an hour
other: about %{count} hours
about_x_months:
one: about a month
other: about %{count} months
about_x_years:
one: about a year
other: about %{count} years
almost_x_years:
one: almost a year
other: almost %{count} years
half_a_minute: half a minute
less_than_x_minutes:
zero: less than a minute
one: less than a minute
other: less than %{count} minutes
less_than_x_seconds:
zero: less than a second
one: less than a second
other: less than %{count} seconds
over_x_years:
one: more than a year
other: more than %{count} years
x_days:
one: 1 day
other: "%{count} days"
x_minutes:
one: 1 minute
other: "%{count} minutes"
x_months:
one: 1 month
other: "%{count} months"
x_seconds:
one: 1 second
other: "%{count} seconds"
time:
formats:
default: "%B %d %Y %R"

View file

@ -22,9 +22,6 @@
fr:
utils:
deconnexion: "Déconnexion"
involved: "Voir les personnes impliquées"
no-commentaires: "Il ny a aucun message dans le fil de discussion, nhésitez pas à initier le premier."
depositaire: "Dépositaire"
pj: "Pièces jointes"
asterisk_html: Les champs suivis dun astérisque ( <span class = mandatory> * </span> ) sont obligatoires.
file_number: Numéro de dossier
@ -60,62 +57,7 @@ fr:
submit:
publish: Publier
reopen: Réactiver
supportadmin:
admin demande rdv: Demande de RDV pour une présentation à distance de demarches-simplifiees.fr
admin question: Jai une question sur demarches-simplifiees.fr
admin soucis: Jai un problème technique avec demarches-simplifiees.fr
admin suggestion produit: Jai une proposition dévolution
admin demande compte: Je souhaite ouvrir un compte administrateur avec un email Orange, Wanadoo, etc.
admin autre: Autre sujet
number:
currency:
format:
delimiter: " "
format: "%n %u"
precision: 2
separator: ","
significant: false
strip_insignificant_zeros: false
unit: "€"
format:
delimiter: " "
precision: 3
separator: ","
significant: false
strip_insignificant_zeros: false
human:
decimal_units:
format: "%n %u"
units:
billion: milliard
million: million
quadrillion: million de milliards
thousand: millier
trillion: billion
unit: ''
format:
delimiter: ''
precision: 3
significant: true
strip_insignificant_zeros: true
storage_units:
format: "%n %u"
units:
byte:
one: octet
other: octets
gb: Go
kb: ko
mb: Mo
tb: To
percentage:
format:
delimiter: ''
format: "%n%"
precision:
format:
delimiter: ''
activerecord:
attributes:
user:
@ -125,14 +67,7 @@ fr:
password: 'Le mot de passe'
errors:
messages:
blank: "doit être rempli"
not_a_number: 'doit être un nombre'
not_an_integer: 'doit être un nombre entier (sans chiffres après la virgule)'
not_a_phone: 'Numéro de téléphone invalide'
greater_than: "doit être supérieur à %{count}"
greater_than_or_equal_to: "doit être supérieur ou égal à %{count}"
less_than: "doit être inférieur à %{count}"
less_than_or_equal_to: "doit être inférieur ou égal à %{count}"
models:
attestation_template:
attributes:
@ -197,98 +132,9 @@ fr:
parcelles_agricoles_empty:
one: "Aucune parcelle agricole sur la zone sélectionnée"
other: "Aucune parcelle agricole sur les zones sélectionnées"
not_an_integer: "doit être un nombre entier (sans chiffres après la virgule)"
blank: "doit être rempli"
date:
abbr_day_names:
- dim
- lun
- mar
- mer
- jeu
- ven
- sam
abbr_month_names:
-
- jan.
- fév.
- mar.
- avr.
- mai
- juin
- juil.
- août
- sept.
- oct.
- nov.
- déc.
month_names:
-
- janvier
- février
- mars
- avril
- mai
- juin
- juillet
- août
- septembre
- octobre
- novembre
- décembre
order:
- :day
- :month
- :year
day_names:
- dimanche
- lundi
- mardi
- mercredi
- jeudi
- vendredi
- samedi
formats:
default: "%d %B %Y"
short: "%e %b"
long: "%e %B %Y"
datetime:
distance_in_words:
about_x_hours:
one: environ une heure
other: environ %{count} heures
about_x_months:
one: environ un mois
other: environ %{count} mois
about_x_years:
one: environ un an
other: environ %{count} ans
almost_x_years:
one: presquun an
other: presque %{count} ans
half_a_minute: une demi-minute
less_than_x_minutes:
zero: moins dune minute
one: moins dune minute
other: moins de %{count} minutes
less_than_x_seconds:
zero: moins dune seconde
one: moins dune seconde
other: moins de %{count} secondes
over_x_years:
one: plus dun an
other: plus de %{count} ans
x_days:
one: 1 jour
other: "%{count} jours"
x_minutes:
one: 1 minute
other: "%{count} minutes"
x_months:
one: 1 mois
other: "%{count} mois"
x_seconds:
one: 1 seconde
other: "%{count} secondes"
time:
formats:
default: "%d %B %Y %R"

View file

@ -30,3 +30,17 @@ en:
<p><a href=%{link_lost_user}>%{link_lost_user}</a></p>"
notice_pj_product: A screenshot can help us identify the element to improve.
notice_pj_other: A screenshot can help us identify the issue.
supportadmin:
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>
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
pro_phone_number: Professional phone number (direct line)
pro_mail: Professional email address
admin demande rdv: I request an appointment for an online presentation of demarches-simplifiees.fr
admin question: I have a question about demarches-simplifiees.fr
admin soucis: I am facing a technical issue on demarches-simplifiees.fr
admin suggestion produit: I have a suggestion for an evolution
admin demande compte: I want to open an admin account with an Orange, Wanadoo, etc. email
admin autre: Other topic

View file

@ -29,3 +29,18 @@ fr:
<p><a href=%{link_lost_user}>%{link_lost_user}</a></p>"
notice_pj_product: Une capture décran peut nous aider à identifier plus facilement lendroit à améliorer.
notice_pj_other: Une capture décran peut nous aider à identifier plus facilement le problème.
supportadmin:
admin_intro_html: "<p>En tant qu'administration, 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>
Il ne concerne ni les particuliers, ni les entreprises, ni les associations (sauf celles reconnues d'utilité publique). Si c'est votre cas, rendez-vous sur notre
<a href=%{contact_path}>formulaire de contact public</a>.</p>"
contact_team: Contactez notre équipe
pro_phone_number: Numéro de téléphone professionnel (ligne directe)
pro_mail: Adresse e-mail professionnelle
admin demande rdv: Demande de RDV pour une présentation à distance de demarches-simplifiees.fr
admin question: Jai une question sur demarches-simplifiees.fr
admin soucis: Jai un problème technique avec demarches-simplifiees.fr
admin suggestion produit: Jai une proposition dévolution
admin demande compte: Je souhaite ouvrir un compte administrateur avec un email Orange, Wanadoo, etc.
admin autre: Autre sujet

View file

@ -189,8 +189,6 @@ Rails.application.routes.draw do
delete :delete_notice
end
resources :mail_templates, only: [:edit, :update]
put 'archive' => 'procedures#archive', as: :archive
get 'publish_validate' => 'procedures#publish_validate', as: :publish_validate
put 'publish' => 'procedures#publish', as: :publish
@ -373,6 +371,8 @@ Rails.application.routes.draw do
patch 'update_jeton'
end
resources :mail_templates, only: [:edit, :update]
resources :groupe_instructeurs, only: [:index, :show, :create, :update, :destroy] do
member do
post 'add_instructeur'

View file

@ -1,31 +0,0 @@
describe Admin::MailTemplatesController, type: :controller do
let(:procedure) { create :procedure }
let(:initiated_mail) { Mails::InitiatedMail.default_for_procedure(procedure) }
before do
sign_in(procedure.administrateurs.first.user)
end
describe 'PATCH update' do
let(:mail_subject) { 'plop modif' }
let(:mail_body) { 'plip modif' }
before :each do
patch :update,
params: {
procedure_id: procedure.id,
id: initiated_mail.class.const_get(:SLUG),
mail_template: { subject: mail_subject, body: mail_body }
}
end
it { expect(response).to redirect_to edit_admin_procedure_mail_template_path(procedure, initiated_mail.class.const_get(:SLUG)) }
context 'the mail template' do
subject { procedure.reload; procedure.initiated_mail_template }
it { expect(subject.subject).to eq(mail_subject) }
it { expect(subject.body).to eq(mail_body) }
end
end
end

View file

@ -255,9 +255,8 @@ describe API::V1::DossiersController do
end
describe 'repetition' do
let(:procedure) { create(:procedure, administrateur: admin) }
let(:champ) { build(:champ_repetition) }
let(:dossier) { create(:dossier, :en_construction, champs: [champ], procedure: procedure) }
let(:procedure) { create(:procedure, :with_repetition, administrateur: admin) }
let(:dossier) { create(:dossier, :en_construction, :with_all_champs, procedure: procedure) }
subject { super().first[:rows] }

View file

@ -560,36 +560,34 @@ describe Instructeurs::DossiersController, type: :controller do
end
describe "#update_annotations" do
let(:procedure) do
create(:procedure, :published, types_de_champ_private: [
build(:type_de_champ_multiple_drop_down_list, position: 0),
build(:type_de_champ_linked_drop_down_list, position: 1),
build(:type_de_champ_datetime, position: 2),
build(:type_de_champ_repetition, :with_types_de_champ, position: 3)
], instructeurs: instructeurs)
end
let(:dossier) { create(:dossier, :en_construction, :with_all_annotations, procedure: procedure) }
let(:now) { Time.zone.parse('01/01/2100') }
let(:champ_multiple_drop_down_list) do
tdc = create(:type_de_champ_multiple_drop_down_list, :private, procedure: procedure, libelle: 'libelle')
create(:champ_multiple_drop_down_list, :private, type_de_champ: tdc, dossier: dossier)
dossier.champs_private.first
end
let(:champ_linked_drop_down_list) do
tdc = create(:type_de_champ_linked_drop_down_list, :private, procedure: procedure, libelle: 'libelle')
create(:champ_linked_drop_down_list, :private, type_de_champ: tdc, dossier: dossier)
dossier.champs_private.second
end
let(:champ_datetime) do
tdc = create(:type_de_champ_datetime, :private, procedure: procedure, libelle: 'libelle')
create(:champ_datetime, :private, type_de_champ: tdc, dossier: dossier)
dossier.champs_private.third
end
let(:champ_repetition) do
tdc = create(:type_de_champ_repetition, :private, :with_types_de_champ, procedure: procedure, libelle: 'libelle')
tdc.types_de_champ << create(:type_de_champ_text, procedure: procedure, libelle: 'libelle')
champ = create(:champ_repetition, :private, type_de_champ: tdc, dossier: dossier)
champ.add_row
champ
dossier.champs_private.fourth
end
let(:dossier) { create(:dossier, :en_construction, procedure: procedure) }
let(:now) { Time.zone.parse('01/01/2100') }
before do
dossier.champs_private << [champ_multiple_drop_down_list, champ_linked_drop_down_list, champ_datetime, champ_repetition]
Timecop.freeze(now)
patch :update_annotations, params: params
@ -638,13 +636,16 @@ describe Instructeurs::DossiersController, type: :controller do
}
}
end
it { expect(champ_multiple_drop_down_list.value).to eq('["un", "deux"]') }
it { expect(champ_linked_drop_down_list.primary_value).to eq('primary') }
it { expect(champ_linked_drop_down_list.secondary_value).to eq('secondary') }
it { expect(champ_datetime.value).to eq('21/12/2019 13:17') }
it { expect(champ_repetition.champs.first.value).to eq('text') }
it { expect(dossier.reload.last_champ_private_updated_at).to eq(now) }
it { expect(response).to redirect_to(annotations_privees_instructeur_dossier_path(dossier.procedure, dossier)) }
it {
expect(champ_multiple_drop_down_list.value).to eq('["un", "deux"]')
expect(champ_linked_drop_down_list.primary_value).to eq('primary')
expect(champ_linked_drop_down_list.secondary_value).to eq('secondary')
expect(champ_datetime.value).to eq('21/12/2019 13:17')
expect(champ_repetition.champs.first.value).to eq('text')
expect(dossier.reload.last_champ_private_updated_at).to eq(now)
expect(response).to redirect_to(annotations_privees_instructeur_dossier_path(dossier.procedure, dossier))
}
end
context "without new values for champs_private" do
@ -663,8 +664,11 @@ describe Instructeurs::DossiersController, type: :controller do
}
}
end
it { expect(dossier.reload.last_champ_private_updated_at).to eq(nil) }
it { expect(response).to redirect_to(annotations_privees_instructeur_dossier_path(dossier.procedure, dossier)) }
it {
expect(dossier.reload.last_champ_private_updated_at).to eq(nil)
expect(response).to redirect_to(annotations_privees_instructeur_dossier_path(dossier.procedure, dossier))
}
end
end

View file

@ -17,7 +17,7 @@ describe Manager::ProceduresController, type: :controller do
describe '#show' do
render_views
let(:procedure) { create(:procedure, :with_repetition) }
let(:procedure) { create(:procedure, :published, :with_repetition) }
before do
get :show, params: { id: procedure.id }

View file

@ -42,4 +42,27 @@ describe NewAdministrateur::MailTemplatesController, type: :controller do
expect(response.body).to include(procedure.service.telephone)
end
end
describe 'PATCH update' do
let(:mail_subject) { 'Mise à jour de votre démarche' }
let(:mail_body) { '<div>Une mise à jour a été effectuée sur votre démarche n° --demarche-id--.</div>' }
before :each do
patch :update,
params: {
procedure_id: procedure.id,
id: initiated_mail.class.const_get(:SLUG),
mails_initiated_mail: { subject: mail_subject, rich_body: mail_body }
}
end
it { expect(response).to redirect_to edit_admin_procedure_mail_template_path(procedure, initiated_mail.class.const_get(:SLUG)) }
context 'the mail template' do
subject { procedure.reload; procedure.initiated_mail_template }
it { expect(subject.subject).to eq(mail_subject) }
it { expect(subject.body).to eq(mail_body) }
end
end
end

View file

@ -2,17 +2,17 @@ describe StatsController, type: :controller do
describe "#last_four_months_hash" do
context "while a regular user is logged in" do
before do
FactoryBot.create(:procedure, :created_at => 6.months.ago, :updated_at => 6.months.ago)
FactoryBot.create(:procedure, :created_at => 2.months.ago, :updated_at => 62.days.ago)
FactoryBot.create(:procedure, :created_at => 2.months.ago, :updated_at => 62.days.ago)
FactoryBot.create(:procedure, :created_at => 2.months.ago, :updated_at => 31.days.ago)
FactoryBot.create(:procedure, :created_at => 2.months.ago, :updated_at => Time.zone.now)
create(:procedure, created_at: 6.months.ago, updated_at: 6.months.ago)
create(:procedure, created_at: 2.months.ago, updated_at: 62.days.ago)
create(:procedure, created_at: 2.months.ago, updated_at: 62.days.ago)
create(:procedure, created_at: 2.months.ago, updated_at: 31.days.ago)
create(:procedure, created_at: 2.months.ago, updated_at: Time.zone.now)
@controller = StatsController.new
allow(@controller).to receive(:administration_signed_in?).and_return(false)
end
let (:association) { Procedure.all }
let(:association) { Procedure.all }
subject { @controller.send(:last_four_months_hash, association, :updated_at) }
@ -26,10 +26,10 @@ describe StatsController, type: :controller do
context "while a super admin is logged in" do
before do
FactoryBot.create(:procedure, :updated_at => 6.months.ago)
FactoryBot.create(:procedure, :updated_at => 45.days.ago)
FactoryBot.create(:procedure, :updated_at => 1.day.ago)
FactoryBot.create(:procedure, :updated_at => 1.day.ago)
create(:procedure, updated_at: 6.months.ago)
create(:procedure, updated_at: 45.days.ago)
create(:procedure, updated_at: 1.day.ago)
create(:procedure, updated_at: 1.day.ago)
@controller = StatsController.new
@ -52,11 +52,11 @@ describe StatsController, type: :controller do
describe '#cumulative_hash' do
before do
Timecop.freeze(Time.zone.local(2016, 10, 2))
FactoryBot.create(:procedure, :created_at => 55.days.ago, :updated_at => 43.days.ago)
FactoryBot.create(:procedure, :created_at => 45.days.ago, :updated_at => 40.days.ago)
FactoryBot.create(:procedure, :created_at => 45.days.ago, :updated_at => 20.days.ago)
FactoryBot.create(:procedure, :created_at => 15.days.ago, :updated_at => 20.days.ago)
FactoryBot.create(:procedure, :created_at => 15.days.ago, :updated_at => 1.hour.ago)
create(:procedure, created_at: 55.days.ago, updated_at: 43.days.ago)
create(:procedure, created_at: 45.days.ago, updated_at: 40.days.ago)
create(:procedure, created_at: 45.days.ago, updated_at: 20.days.ago)
create(:procedure, created_at: 15.days.ago, updated_at: 20.days.ago)
create(:procedure, created_at: 15.days.ago, updated_at: 1.hour.ago)
end
after { Timecop.return }
@ -104,24 +104,24 @@ describe StatsController, type: :controller do
# dossier_p1_c: 5 days
before do
procedure_1 = FactoryBot.create(:procedure)
procedure_2 = FactoryBot.create(:procedure)
dossier_p1_a = FactoryBot.create(:dossier, :accepte,
:procedure => procedure_1,
:en_construction_at => 2.months.ago.beginning_of_month,
:processed_at => 2.months.ago.beginning_of_month + 3.days)
dossier_p1_b = FactoryBot.create(:dossier, :accepte,
:procedure => procedure_1,
:en_construction_at => 2.months.ago.beginning_of_month,
:processed_at => 2.months.ago.beginning_of_month + 1.day)
dossier_p1_c = FactoryBot.create(:dossier, :accepte,
:procedure => procedure_1,
:en_construction_at => 1.month.ago.beginning_of_month,
:processed_at => 1.month.ago.beginning_of_month + 5.days)
dossier_p2_a = FactoryBot.create(:dossier, :accepte,
:procedure => procedure_2,
:en_construction_at => 2.months.ago.beginning_of_month,
:processed_at => 2.months.ago.beginning_of_month + 4.days)
procedure_1 = create(:procedure)
procedure_2 = create(:procedure)
dossier_p1_a = create(:dossier, :accepte,
procedure: procedure_1,
en_construction_at: 2.months.ago.beginning_of_month,
processed_at: 2.months.ago.beginning_of_month + 3.days)
dossier_p1_b = create(:dossier, :accepte,
procedure: procedure_1,
en_construction_at: 2.months.ago.beginning_of_month,
processed_at: 2.months.ago.beginning_of_month + 1.day)
dossier_p1_c = create(:dossier, :accepte,
procedure: procedure_1,
en_construction_at: 1.month.ago.beginning_of_month,
processed_at: 1.month.ago.beginning_of_month + 5.days)
dossier_p2_a = create(:dossier, :accepte,
procedure: procedure_2,
en_construction_at: 2.months.ago.beginning_of_month,
processed_at: 2.months.ago.beginning_of_month + 4.days)
@expected_hash = {
(2.months.ago.beginning_of_month).to_s => 3.0,
@ -149,28 +149,28 @@ describe StatsController, type: :controller do
# dossier_p1_c: 50 minutes
before do
procedure_1 = FactoryBot.create(:procedure, :with_type_de_champ, :types_de_champ_count => 24)
procedure_2 = FactoryBot.create(:procedure, :with_type_de_champ, :types_de_champ_count => 48)
dossier_p1_a = FactoryBot.create(:dossier, :accepte,
:procedure => procedure_1,
:created_at => 2.months.ago.beginning_of_month,
:en_construction_at => 2.months.ago.beginning_of_month + 30.minutes,
:processed_at => 2.months.ago.beginning_of_month + 1.day)
dossier_p1_b = FactoryBot.create(:dossier, :accepte,
:procedure => procedure_1,
:created_at => 2.months.ago.beginning_of_month,
:en_construction_at => 2.months.ago.beginning_of_month + 10.minutes,
:processed_at => 2.months.ago.beginning_of_month + 1.day)
dossier_p1_c = FactoryBot.create(:dossier, :accepte,
:procedure => procedure_1,
:created_at => 1.month.ago.beginning_of_month,
:en_construction_at => 1.month.ago.beginning_of_month + 50.minutes,
:processed_at => 1.month.ago.beginning_of_month + 1.day)
dossier_p2_a = FactoryBot.create(:dossier, :accepte,
:procedure => procedure_2,
:created_at => 2.months.ago.beginning_of_month,
:en_construction_at => 2.months.ago.beginning_of_month + 80.minutes,
:processed_at => 2.months.ago.beginning_of_month + 1.day)
procedure_1 = create(:procedure, :with_type_de_champ, types_de_champ_count: 24)
procedure_2 = create(:procedure, :with_type_de_champ, types_de_champ_count: 48)
dossier_p1_a = create(:dossier, :accepte,
procedure: procedure_1,
created_at: 2.months.ago.beginning_of_month,
en_construction_at: 2.months.ago.beginning_of_month + 30.minutes,
processed_at: 2.months.ago.beginning_of_month + 1.day)
dossier_p1_b = create(:dossier, :accepte,
procedure: procedure_1,
created_at: 2.months.ago.beginning_of_month,
en_construction_at: 2.months.ago.beginning_of_month + 10.minutes,
processed_at: 2.months.ago.beginning_of_month + 1.day)
dossier_p1_c = create(:dossier, :accepte,
procedure: procedure_1,
created_at: 1.month.ago.beginning_of_month,
en_construction_at: 1.month.ago.beginning_of_month + 50.minutes,
processed_at: 1.month.ago.beginning_of_month + 1.day)
dossier_p2_a = create(:dossier, :accepte,
procedure: procedure_2,
created_at: 2.months.ago.beginning_of_month,
en_construction_at: 2.months.ago.beginning_of_month + 80.minutes,
processed_at: 2.months.ago.beginning_of_month + 1.day)
@expected_hash = {
(2.months.ago.beginning_of_month).to_s => 30.0,

View file

@ -56,7 +56,7 @@ describe Users::ProfilController, type: :controller do
end
it { expect(response).to redirect_to(profil_path) }
it { expect(flash.alert).to eq(['Email invalide']) }
it { expect(flash.alert).to eq(['Courriel invalide']) }
end
context 'when the user has an instructeur role' do

View file

@ -62,7 +62,7 @@ describe Users::SessionsController, type: :controller do
subject
expect(response).to render_template(:new)
expect(flash.alert).to eq(I18n.t('devise.failure.invalid'))
expect(flash.alert).to eq('Courriel ou mot de passe incorrect.')
end
end
end

View file

@ -161,23 +161,20 @@ FactoryBot.define do
existing_type_de_champ_text = types_de_champ.find { |tdc| tdc.libelle == 'Nom' }
type_de_champ_text = existing_type_de_champ_text || build(
:type_de_champ_text,
order_place: 0,
procedure: champ_repetition.dossier.procedure,
position: 0,
parent: champ_repetition.type_de_champ,
libelle: 'Nom'
)
types_de_champ << type_de_champ_text
existing_type_de_champ_number = types_de_champ.find { |tdc| tdc.libelle == 'Age' }
type_de_champ_number = existing_type_de_champ_number || build(
:type_de_champ_number,
order_place: 1,
procedure: champ_repetition.dossier.procedure,
position: 1,
parent: champ_repetition.type_de_champ,
libelle: 'Age'
)
types_de_champ << type_de_champ_number
champ_repetition.type_de_champ.types_de_champ << [type_de_champ_text, type_de_champ_number]
champ_repetition.champs << [
build(:champ_text, dossier: champ_repetition.dossier, row: 0, type_de_champ: type_de_champ_text, parent: champ_repetition),
build(:champ_number, dossier: champ_repetition.dossier, row: 0, type_de_champ: type_de_champ_number, parent: champ_repetition),
@ -198,13 +195,11 @@ FactoryBot.define do
after(:build) do |champ_repetition, _evaluator|
type_de_champ_pj0 = build(:type_de_champ_piece_justificative,
procedure: champ_repetition.dossier.procedure,
order_place: 0,
position: 0,
parent: champ_repetition.type_de_champ,
libelle: 'Justificatif de domicile')
type_de_champ_pj1 = build(:type_de_champ_piece_justificative,
procedure: champ_repetition.dossier.procedure,
order_place: 1,
position: 1,
parent: champ_repetition.type_de_champ,
libelle: 'Carte d\'identité')

View file

@ -15,6 +15,8 @@ FactoryBot.define do
procedure = create(:procedure, :published, :with_type_de_champ, :with_type_de_champ_private)
end
dossier.revision = procedure.active_revision
# Assign the procedure to the dossier through the groupe_instructeur
if dossier.groupe_instructeur.nil?
dossier.groupe_instructeur = procedure.defaut_groupe_instructeur
@ -71,7 +73,7 @@ FactoryBot.define do
linked_dossier = create(:dossier, :en_construction)
# find first type de champ dossier_link
type_de_champ = dossier.procedure.types_de_champ.find do |t|
type_de_champ = dossier.types_de_champ.find do |t|
t.type_champ == TypeDeChamp.type_champs.fetch(:dossier_link)
end
@ -202,7 +204,7 @@ FactoryBot.define do
trait :with_all_champs do
after(:create) do |dossier, _evaluator|
dossier.champs = dossier.procedure.types_de_champ.map do |type_de_champ|
dossier.champs = dossier.types_de_champ.map do |type_de_champ|
build(:"champ_#{type_de_champ.type_champ}", dossier: dossier, type_de_champ: type_de_champ)
end
dossier.save!
@ -211,7 +213,7 @@ FactoryBot.define do
trait :with_all_annotations do
after(:create) do |dossier, _evaluator|
dossier.champs = dossier.procedure.types_de_champ.map do |type_de_champ|
dossier.champs = dossier.types_de_champ.map do |type_de_champ|
build(:"champ_#{type_de_champ.type_champ}", dossier: dossier, type_de_champ: type_de_champ)
end
dossier.save!

View file

@ -16,6 +16,9 @@ FactoryBot.define do
transient do
administrateur { }
instructeurs { [] }
types_de_champ { [] }
types_de_champ_private { [] }
updated_at { nil }
end
after(:build) do |procedure, evaluator|
@ -24,10 +27,35 @@ FactoryBot.define do
elsif procedure.administrateurs.empty?
procedure.administrateurs = [create(:administrateur)]
end
procedure.draft_revision = build(:procedure_revision, procedure: procedure)
evaluator.types_de_champ.each do |type_de_champ|
type_de_champ.revision = procedure.draft_revision
type_de_champ.private = false
type_de_champ.revision.revision_types_de_champ << build(:procedure_revision_type_de_champ,
revision: procedure.draft_revision,
position: type_de_champ.order_place,
type_de_champ: type_de_champ)
end
evaluator.types_de_champ_private.each do |type_de_champ|
type_de_champ.revision = procedure.draft_revision
type_de_champ.private = true
type_de_champ.revision.revision_types_de_champ_private << build(:procedure_revision_type_de_champ,
revision: procedure.draft_revision,
position: type_de_champ.order_place,
type_de_champ: type_de_champ)
end
end
after(:create) do |procedure, evaluator|
evaluator.instructeurs.each { |i| i.assign_to_procedure(procedure) }
if evaluator.updated_at
procedure.update_column(:updated_at, evaluator.updated_at)
end
procedure.reload
end
factory :procedure_with_dossiers do
@ -38,9 +66,7 @@ FactoryBot.define do
after(:create) do |procedure, evaluator|
user = create(:user)
evaluator.dossiers_count.times do
dossier = procedure.new_dossier
dossier.user = user
dossier.save!
create(:dossier, procedure: procedure, user: user)
end
end
end
@ -48,7 +74,7 @@ FactoryBot.define do
factory :simple_procedure do
after(:build) do |procedure, _evaluator|
procedure.for_individual = true
procedure.types_de_champ << build(:type_de_champ, libelle: 'Texte obligatoire', mandatory: true)
build(:type_de_champ, libelle: 'Texte obligatoire', mandatory: true, procedure: procedure)
procedure.path = generate(:published_path)
procedure.publish!
end
@ -96,10 +122,8 @@ FactoryBot.define do
end
after(:build) do |procedure, evaluator|
evaluator.types_de_champ_count.times do
type_de_champ = build(:type_de_champ)
procedure.types_de_champ << type_de_champ
evaluator.types_de_champ_count.times do |position|
build(:type_de_champ, procedure: procedure, position: position)
end
end
end
@ -110,68 +134,51 @@ FactoryBot.define do
end
after(:build) do |procedure, evaluator|
evaluator.types_de_champ_private_count.times do
type_de_champ = build(:type_de_champ, :private)
procedure.types_de_champ_private << type_de_champ
evaluator.types_de_champ_private_count.times do |position|
build(:type_de_champ, :private, procedure: procedure, position: position)
end
end
end
trait :with_type_de_champ_mandatory do
after(:build) do |procedure, _evaluator|
type_de_champ = build(:type_de_champ, mandatory: true)
procedure.types_de_champ << type_de_champ
build(:type_de_champ, mandatory: true, procedure: procedure)
end
end
trait :with_datetime do
after(:build) do |procedure, _evaluator|
type_de_champ = build(:type_de_champ_datetime, mandatory: true)
procedure.types_de_champ << type_de_champ
build(:type_de_champ_datetime, mandatory: true, procedure: procedure)
end
end
trait :with_dossier_link do
after(:build) do |procedure, _evaluator|
type_de_champ = build(:type_de_champ_dossier_link)
procedure.types_de_champ << type_de_champ
build(:type_de_champ_dossier_link, procedure: procedure)
end
end
trait :with_yes_no do
after(:build) do |procedure, _evaluator|
type_de_champ = build(:type_de_champ_yes_no)
procedure.types_de_champ << type_de_champ
build(:type_de_champ_yes_no, procedure: procedure)
end
end
trait :with_piece_justificative do
after(:build) do |procedure, _evaluator|
type_de_champ = build(:type_de_champ_piece_justificative)
procedure.types_de_champ << type_de_champ
build(:type_de_champ_piece_justificative, procedure: procedure)
end
end
trait :with_repetition do
after(:build) do |procedure, _evaluator|
type_de_champ = build(:type_de_champ_repetition)
procedure.types_de_champ << type_de_champ
type_de_champ.types_de_champ << build(:type_de_champ, libelle: 'sub type de champ')
build(:type_de_champ_repetition, :with_types_de_champ, procedure: procedure)
end
end
trait :with_number do
after(:build) do |procedure, _evaluator|
type_de_champ = build(:type_de_champ_number)
procedure.types_de_champ << type_de_champ
build(:type_de_champ_number, procedure: procedure)
end
end
@ -228,35 +235,35 @@ FactoryBot.define do
trait :with_all_champs_mandatory do
after(:build) do |procedure, _evaluator|
procedure.types_de_champ = TypeDeChamp.type_champs.map.with_index do |(libelle, type_champ), index|
TypeDeChamp.type_champs.map.with_index do |(libelle, type_champ), index|
if libelle == 'drop_down_list'
libelle = 'simple_drop_down_list'
end
build(:"type_de_champ_#{type_champ}", procedure: procedure, mandatory: true, libelle: libelle, order_place: index)
build(:"type_de_champ_#{type_champ}", procedure: procedure, mandatory: true, libelle: libelle, position: index)
end
procedure.types_de_champ << build(:type_de_champ_drop_down_list, :long, procedure: procedure, mandatory: true, libelle: 'simple_choice_drop_down_list_long')
procedure.types_de_champ << build(:type_de_champ_multiple_drop_down_list, :long, procedure: procedure, mandatory: true, libelle: 'multiple_choice_drop_down_list_long')
build(:type_de_champ_drop_down_list, :long, procedure: procedure, mandatory: true, libelle: 'simple_choice_drop_down_list_long', position: TypeDeChamp.type_champs.size)
build(:type_de_champ_multiple_drop_down_list, :long, procedure: procedure, mandatory: true, libelle: 'multiple_choice_drop_down_list_long', position: TypeDeChamp.type_champs.size + 1)
end
end
trait :with_all_champs do
after(:build) do |procedure, _evaluator|
procedure.types_de_champ = TypeDeChamp.type_champs.map.with_index do |(libelle, type_champ), index|
TypeDeChamp.type_champs.map.with_index do |(libelle, type_champ), index|
if libelle == 'drop_down_list'
libelle = 'simple_drop_down_list'
end
build(:"type_de_champ_#{type_champ}", procedure: procedure, libelle: libelle, order_place: index)
build(:"type_de_champ_#{type_champ}", procedure: procedure, libelle: libelle, position: index)
end
end
end
trait :with_all_annotations do
after(:build) do |procedure, _evaluator|
procedure.types_de_champ_private = TypeDeChamp.type_champs.map.with_index do |(libelle, type_champ), index|
TypeDeChamp.type_champs.map.with_index do |(libelle, type_champ), index|
if libelle == 'drop_down_list'
libelle = 'simple_drop_down_list'
end
build(:"type_de_champ_#{type_champ}", procedure: procedure, private: true, libelle: libelle, order_place: index)
build(:"type_de_champ_#{type_champ}", procedure: procedure, private: true, libelle: libelle, position: index)
end
end
end

View file

@ -0,0 +1,4 @@
FactoryBot.define do
factory :procedure_revision do
end
end

View file

@ -0,0 +1,4 @@
FactoryBot.define do
factory :procedure_revision_type_de_champ do
end
end

View file

@ -7,7 +7,40 @@ FactoryBot.define do
mandatory { false }
add_attribute(:private) { false }
association :procedure
transient do
procedure { nil }
position { nil }
parent { nil }
end
after(:build) do |type_de_champ, evaluator|
if evaluator.procedure
type_de_champ.revision = evaluator.procedure.active_revision
build(:procedure_revision_type_de_champ,
position: evaluator.position,
revision: evaluator.procedure.active_revision,
type_de_champ: type_de_champ)
if type_de_champ.private?
type_de_champ.revision.types_de_champ_private << type_de_champ
else
type_de_champ.revision.types_de_champ << type_de_champ
end
elsif evaluator.parent
type_de_champ.revision = evaluator.parent.revision
type_de_champ.order_place = evaluator.position || evaluator.parent.types_de_champ.size
evaluator.parent.types_de_champ << type_de_champ
else
type_de_champ.order_place = evaluator.position
end
end
trait :private do
add_attribute(:private) { true }
sequence(:libelle) { |n| "Libelle champ privé #{n}" }
sequence(:description) { |n| "description du champ privé #{n}" }
end
factory :type_de_champ_text do
type_champ { TypeDeChamp.type_champs.fetch(:text) }
@ -96,8 +129,8 @@ FactoryBot.define do
factory :type_de_champ_piece_justificative do
type_champ { TypeDeChamp.type_champs.fetch(:piece_justificative) }
after(:build) do |tc, _evaluator|
tc.piece_justificative_template.attach(io: StringIO.new("toto"), filename: "toto.txt", content_type: "text/plain")
after(:build) do |type_de_champ, _evaluator|
type_de_champ.piece_justificative_template.attach(io: StringIO.new("toto"), filename: "toto.txt", content_type: "text/plain")
end
end
factory :type_de_champ_siret do
@ -109,17 +142,23 @@ FactoryBot.define do
factory :type_de_champ_repetition do
type_champ { TypeDeChamp.type_champs.fetch(:repetition) }
trait :with_types_de_champ do
after(:build) do |type_de_champ, _evaluator|
type_de_champ.types_de_champ << build(:type_de_champ, procedure: type_de_champ.procedure, libelle: 'sub type de champ')
transient do
types_de_champ { [] }
end
after(:build) do |type_de_champ_repetition, evaluator|
evaluator.types_de_champ.each do |type_de_champ|
type_de_champ.revision = type_de_champ_repetition.revision
type_de_champ.order_place = type_de_champ_repetition.types_de_champ.size
type_de_champ_repetition.types_de_champ << type_de_champ
end
end
trait :private do
add_attribute(:private) { true }
sequence(:libelle) { |n| "Libelle champ privé #{n}" }
sequence(:description) { |n| "description du champ privé #{n}" }
trait :with_types_de_champ do
after(:build) do |type_de_champ, _evaluator|
build(:type_de_champ, libelle: 'sub type de champ', parent: type_de_champ)
end
end
end
end
end

View file

@ -6,11 +6,11 @@ feature 'As an administrateur I wanna clone a procedure', js: true do
let(:administrateur) { create(:administrateur) }
before do
create :procedure, :with_service, :with_instructeur,
aasm_state: :publiee, published_at: Time.zone.now,
create(:procedure, :with_service, :with_instructeur,
aasm_state: :publiee,
administrateurs: [administrateur],
libelle: 'libellé de la procédure',
path: 'libelle-de-la-procedure'
path: 'libelle-de-la-procedure')
login_as administrateur.user, scope: :user
end

View file

@ -91,7 +91,7 @@ feature 'Instructing a dossier:' do
end
scenario 'A instructeur can see the personnes impliquées' do
instructeur2 = FactoryBot.create(:instructeur, password: password)
instructeur2 = create(:instructeur, password: password)
log_in(instructeur.email, password)
@ -113,8 +113,8 @@ feature 'Instructing a dossier:' do
end
scenario 'A instructeur can send a dossier to several instructeurs', js: true do
instructeur_2 = FactoryBot.create(:instructeur)
instructeur_3 = FactoryBot.create(:instructeur)
instructeur_2 = create(:instructeur)
instructeur_3 = create(:instructeur)
procedure.defaut_groupe_instructeur.instructeurs << [instructeur_2, instructeur_3]
send_dossier = double()

View file

@ -122,7 +122,7 @@ feature 'As an administrateur I can edit types de champ', js: true do
fill_in 'champ-0-libelle', with: 'Libellé de champ carte', fill_options: { clear: :backspace }
check 'Cadastres'
wait_until { procedure.types_de_champ.first.cadastres == true }
wait_until { procedure.draft_types_de_champ.first.cadastres == true }
expect(page).to have_content('Formulaire enregistré')
preview_window = window_opened_by { click_on 'Prévisualiser le formulaire' }
@ -139,7 +139,7 @@ feature 'As an administrateur I can edit types de champ', js: true do
fill_in 'champ-0-libelle', with: 'Libellé de champ menu déroulant', fill_options: { clear: :backspace }
fill_in 'champ-0-drop_down_list_value', with: 'Un menu', fill_options: { clear: :backspace }
wait_until { procedure.types_de_champ.first.drop_down_list_options == ['', 'Un menu'] }
wait_until { procedure.draft_types_de_champ.first.drop_down_list_options == ['', 'Un menu'] }
expect(page).to have_content('Formulaire enregistré')
page.refresh

View file

@ -60,7 +60,7 @@ feature 'Signin in:' do
click_on 'Connexion'
sign_in_with user.email, password
expect(page).to have_content 'Vous devez confirmer votre adresse email pour continuer'
expect(page).to have_content('Vous devez confirmer votre compte par courriel.')
end
end
end

View file

@ -97,9 +97,7 @@ feature 'The user' do
end
let(:procedure_with_repetition) do
tdc = create(:type_de_champ_repetition, libelle: 'repetition')
tdc.types_de_champ << create(:type_de_champ_text, libelle: 'text')
create(:procedure, :published, :for_individual, types_de_champ: [tdc])
create(:procedure, :published, :for_individual, :with_repetition)
end
scenario 'fill a dossier with repetition', js: true do
@ -107,13 +105,13 @@ feature 'The user' do
fill_individual
fill_in('text', with: 'super texte')
expect(page).to have_field('text', with: 'super texte')
fill_in('sub type de champ', with: 'super texte')
expect(page).to have_field('sub type de champ', with: 'super texte')
click_on 'Ajouter un élément pour'
within '.row-1' do
fill_in('text', with: 'un autre texte')
fill_in('sub type de champ', with: 'un autre texte')
end
expect(page).to have_content('Supprimer', count: 2)
@ -132,7 +130,7 @@ feature 'The user' do
end
let(:simple_procedure) do
tdcs = [create(:type_de_champ, mandatory: true, libelle: 'texte obligatoire')]
tdcs = [build(:type_de_champ, mandatory: true, libelle: 'texte obligatoire')]
create(:procedure, :published, :for_individual, types_de_champ: tdcs)
end
@ -161,14 +159,14 @@ feature 'The user' do
end
let(:procedure_with_pj) do
tdcs = [create(:type_de_champ_piece_justificative, mandatory: true, libelle: 'Pièce justificative')]
tdcs = [build(:type_de_champ_piece_justificative, mandatory: true, libelle: 'Pièce justificative')]
create(:procedure, :published, :for_individual, types_de_champ: tdcs)
end
let(:procedure_with_pjs) do
tdcs = [
create(:type_de_champ_piece_justificative, mandatory: true, libelle: 'Pièce justificative 1', order_place: 1),
create(:type_de_champ_piece_justificative, mandatory: true, libelle: 'Pièce justificative 2', order_place: 2)
build(:type_de_champ_piece_justificative, mandatory: true, libelle: 'Pièce justificative 1', position: 1),
build(:type_de_champ_piece_justificative, mandatory: true, libelle: 'Pièce justificative 2', position: 2)
]
create(:procedure, :published, :for_individual, types_de_champ: tdcs)
end

View file

@ -41,7 +41,7 @@ feature 'Invitations' do
# Confirm the account
# (The user should be redirected to the dossier they was invited on)
click_confirmation_link_for invite.email
expect(page).to have_content('Votre compte a été activé')
expect(page).to have_content('Votre compte a bien été confirmé.')
expect(page).to have_current_path(brouillon_dossier_path(dossier))
end
end

View file

@ -13,12 +13,10 @@ feature 'linked dropdown lists' do
Secondary 2.3
END_OF_LIST
end
let(:type_de_champ) { create(:type_de_champ_linked_drop_down_list, libelle: 'linked dropdown', drop_down_list_value: list_items) }
let(:type_de_champ) { build(:type_de_champ_linked_drop_down_list, libelle: 'linked dropdown', drop_down_list_value: list_items) }
let!(:procedure) do
p = create(:procedure, :published, :for_individual)
p.types_de_champ << type_de_champ
p
create(:procedure, :published, :for_individual, types_de_champ: [type_de_champ])
end
let(:user_dossier) { user.dossiers.first }

View file

@ -13,7 +13,7 @@ feature 'Managing password:' do
perform_enqueued_jobs do
click_on 'Réinitialiser'
end
expect(page).to have_content 'vous allez recevoir un lien de réinitialisation par email'
expect(page).to have_content('Si votre courriel existe dans notre base de données, vous recevrez un lien vous permettant de récupérer votre mot de passe.')
click_reset_password_link_for user.email
expect(page).to have_content 'Changement de mot de passe'
@ -21,7 +21,7 @@ feature 'Managing password:' do
fill_in 'user_password', with: new_password
fill_in 'user_password_confirmation', with: new_password
click_on 'Changer le mot de passe'
expect(page).to have_content('Votre mot de passe a été changé avec succès')
expect(page).to have_content('Votre mot de passe a bien été modifié.')
end
end
@ -40,7 +40,7 @@ feature 'Managing password:' do
perform_enqueued_jobs do
click_on 'Réinitialiser'
end
expect(page).to have_content 'vous allez recevoir un lien de réinitialisation par email'
expect(page).to have_content('Si votre courriel existe dans notre base de données, vous recevrez un lien vous permettant de récupérer votre mot de passe.')
click_reset_password_link_for user.email
@ -49,7 +49,7 @@ feature 'Managing password:' do
fill_in 'user_password', with: new_password
fill_in 'user_password_confirmation', with: new_password
click_on 'Changer le mot de passe'
expect(page).to have_content('Votre mot de passe a été changé avec succès')
expect(page).to have_content('Votre mot de passe a bien été modifié.')
end
end
end

View file

@ -10,7 +10,7 @@ feature 'Signing up:' do
expect(page).to have_content "nous avons besoin de vérifier votre adresse #{user_email}"
click_confirmation_link_for user_email
expect(page).to have_content 'Votre compte a été activé'
expect(page).to have_content('Votre compte a bien été confirmé.')
expect(page).to have_current_path dossiers_path
end
@ -80,7 +80,7 @@ feature 'Signing up:' do
# After confirmation, the user is redirected to the procedure they were initially starting
# (even when confirming the account in another browser).
expect(page).to have_current_path(commencer_path(path: procedure.path))
expect(page).to have_content 'Votre compte a été activé'
expect(page).to have_content I18n.t('devise.confirmations.confirmed')
click_on 'Commencer la démarche'
expect(page).to have_current_path identite_dossier_path(procedure.reload.dossiers.last)
@ -112,7 +112,7 @@ feature 'Signing up:' do
# After confirmation, the user is redirected to the procedure they were initially starting
# (even when confirming the account in another browser).
expect(page).to have_current_path(commencer_path(path: procedure.path))
expect(page).to have_content 'Votre compte a été activé'
expect(page).to have_content I18n.t('devise.confirmations.confirmed')
expect(page).to have_content 'Commencer la démarche'
end
end

View file

@ -1,23 +1,24 @@
RSpec.describe FindDubiousProceduresJob, type: :job do
describe 'perform' do
let(:mailer_double) { double('mailer', deliver_later: true) }
let(:procedure) { create(:procedure) }
let(:allowed_tdc) { create(:type_de_champ, libelle: 'fournir') }
let(:procedure) { create(:procedure, types_de_champ: tdcs) }
let(:allowed_tdc) { build(:type_de_champ, libelle: 'fournir') }
before do
procedure
allow(AdministrationMailer).to receive(:dubious_procedures) do |arg|
@dubious_procedures_args = arg
end.and_return(mailer_double)
procedure.types_de_champ << tdcs
FindDubiousProceduresJob.new.perform
end
context 'with suspicious champs' do
let(:forbidden_tdcs) do
[
create(:type_de_champ, libelle: 'num de securite sociale, stp'),
create(:type_de_champ, libelle: "t'aurais une carte bancaire ?")
build(:type_de_champ, libelle: 'num de securite sociale, stp'),
build(:type_de_champ, libelle: "t'aurais une carte bancaire ?")
]
end

View file

@ -1,24 +0,0 @@
RSpec.describe TmpDossiersMigrateRevisionsJob, type: :job do
let(:procedure) { create(:procedure, :published) }
let!(:dossier1) { create(:dossier, procedure: procedure, updated_at: 1.day.ago) }
let!(:dossier2) { create(:dossier, procedure: procedure, updated_at: 2.days.ago) }
context "add revision to dossiers" do
before do
RevisionsMigration.add_revisions(procedure)
end
it {
expect(dossier1.revision).to be_nil
expect(dossier2.revision).to be_nil
TmpDossiersMigrateRevisionsJob.new.perform([])
[dossier1, dossier2].each(&:reload)
expect(dossier1.revision).to eq procedure.active_revision
expect(dossier2.revision).to eq procedure.active_revision
expect(dossier1.updated_at < 1.day.ago).to be_truthy
expect(dossier2.updated_at < 1.day.ago).to be_truthy
}
end
end

View file

@ -427,10 +427,9 @@ describe Champ do
end
describe 'repetition' do
let(:procedure) { build(:procedure, :published, :with_type_de_champ, :with_type_de_champ_private) }
let(:tdc_text) { build(:type_de_champ_text, procedure: procedure) }
let(:tdc_integer) { build(:type_de_champ_integer_number, procedure: procedure) }
let(:tdc_repetition) { build(:type_de_champ_repetition, procedure: procedure, types_de_champ: [tdc_text, tdc_integer]) }
let(:procedure) { create(:procedure, :published, :with_type_de_champ, :with_type_de_champ_private, types_de_champ: [build(:type_de_champ_repetition, types_de_champ: [tdc_text, tdc_integer])]) }
let(:tdc_text) { build(:type_de_champ_text) }
let(:tdc_integer) { build(:type_de_champ_integer_number) }
let(:dossier) { create(:dossier, procedure: procedure) }
let(:champ) { dossier.champs.find(&:repetition?) }
@ -438,12 +437,6 @@ describe Champ do
let(:champ_integer) { champ.champs.find { |c| c.type_champ == 'integer_number' } }
let(:champ_text_attrs) { attributes_for(:champ_text, type_de_champ: tdc_text, row: 1) }
before do
procedure.types_de_champ << tdc_repetition
procedure.save!
procedure.reload
end
context 'when creating the model directly' do
let(:champ_text_row_1) { create(:champ_text, type_de_champ: tdc_text, row: 2, parent: champ, dossier: nil) }

View file

@ -18,7 +18,7 @@ describe Champs::DecimalNumberChamp do
let(:value) { 'toto' }
it { is_expected.to_not be_valid }
it { expect(subject.errors[:value]).to eq(["« #{subject.libelle} » doit être un nombre"]) }
it { expect(subject.errors[:value]).to eq(["« #{subject.libelle} » n'est pas un nombre"]) }
end
context 'when the value is blank' do

View file

@ -2,11 +2,11 @@ describe Champs::HeaderSectionChamp do
describe '#section_index' do
let(:types_de_champ) do
[
create(:type_de_champ_header_section, order_place: 1),
create(:type_de_champ_civilite, order_place: 2),
create(:type_de_champ_text, order_place: 3),
create(:type_de_champ_header_section, order_place: 4),
create(:type_de_champ_email, order_place: 5)
build(:type_de_champ_header_section, position: 1),
build(:type_de_champ_civilite, position: 2),
build(:type_de_champ_text, position: 3),
build(:type_de_champ_header_section, position: 4),
build(:type_de_champ_email, position: 5)
]
end
@ -23,17 +23,12 @@ describe Champs::HeaderSectionChamp do
end
context 'for repetition champs' do
let(:procedure) { create(:procedure, :with_repetition) }
let(:procedure) { create(:procedure, types_de_champ: [build(:type_de_champ_repetition, types_de_champ: types_de_champ)]) }
let(:dossier) { create(:dossier, procedure: procedure) }
let(:repetition_tdc) { procedure.types_de_champ.find(&:repetition?) }
let(:first_header) { dossier.champs.first.champs[0] }
let(:second_header) { dossier.champs.first.champs[3] }
before do
repetition_tdc.types_de_champ = types_de_champ
end
it 'returns the index of the section in the repetition (starting from 1)' do
expect(first_header.section_index).to eq 1
expect(second_header.section_index).to eq 2

View file

@ -97,8 +97,8 @@ describe TagsSubstitutionConcern, type: :model do
context 'when the procedure has a type de champ named libelleA et libelleB' do
let(:types_de_champ) do
[
create(:type_de_champ, libelle: 'libelleA'),
create(:type_de_champ, libelle: 'libelleB')
build(:type_de_champ, libelle: 'libelleA'),
build(:type_de_champ, libelle: 'libelleB')
]
end
@ -141,7 +141,7 @@ describe TagsSubstitutionConcern, type: :model do
context 'when the procedure has a type de champ with apostrophes' do
let(:types_de_champ) do
[
create(:type_de_champ, libelle: "Intitulé de l'‘«\"évènement\"»’")
build(:type_de_champ, libelle: "Intitulé de l'‘«\"évènement\"»’")
]
end
@ -165,9 +165,9 @@ describe TagsSubstitutionConcern, type: :model do
let(:template) { '--Répétition--' }
let(:types_de_champ) do
[
create(:type_de_champ_repetition, libelle: 'Répétition', types_de_champ: [
create(:type_de_champ_text, libelle: 'Nom', order_place: 1),
create(:type_de_champ_text, libelle: 'Prénom', order_place: 2)
build(:type_de_champ_repetition, libelle: 'Répétition', types_de_champ: [
build(:type_de_champ_text, libelle: 'Nom', order_place: 1),
build(:type_de_champ_text, libelle: 'Prénom', order_place: 2)
])
]
end
@ -190,7 +190,7 @@ describe TagsSubstitutionConcern, type: :model do
context 'when the procedure has a linked drop down menus type de champ' do
let(:type_de_champ) do
create(:type_de_champ_linked_drop_down_list, libelle: 'libelle')
build(:type_de_champ_linked_drop_down_list, libelle: 'libelle')
end
let(:types_de_champ) { [type_de_champ] }
let(:template) { 'tout : --libelle--, primaire : --libelle/primaire--, secondaire : --libelle/secondaire--' }
@ -219,7 +219,7 @@ describe TagsSubstitutionConcern, type: :model do
let(:types_de_champ) do
[
type_de_champ,
create(:type_de_champ_header_section, libelle: 'libelle')
build(:type_de_champ_header_section, libelle: 'libelle')
]
end
@ -253,7 +253,7 @@ describe TagsSubstitutionConcern, type: :model do
end
context 'when the procedure has a type de champ prive named libelleA' do
let(:types_de_champ_private) { [create(:type_de_champ, :private, libelle: 'libelleA')] }
let(:types_de_champ_private) { [build(:type_de_champ, :private, libelle: 'libelleA')] }
context 'and it is used in the template' do
let(:template) { '--libelleA--' }
@ -274,13 +274,13 @@ describe TagsSubstitutionConcern, type: :model do
# The dossier just transitionned from brouillon to en construction,
# so champs private are not valid tags yet
let(:types_de_champ_private) { [create(:type_de_champ, :private, libelle: 'libelleA')] }
let(:types_de_champ_private) { [build(:type_de_champ, :private, libelle: 'libelleA')] }
it { is_expected.to eq('--libelleA--') }
end
context 'champs publics are valid tags' do
let(:types_de_champ) { [create(:type_de_champ, libelle: 'libelleA')] }
let(:types_de_champ) { [build(:type_de_champ, libelle: 'libelleA')] }
before { dossier.champs.first.update(value: 'libelle1') }
@ -291,8 +291,8 @@ describe TagsSubstitutionConcern, type: :model do
context 'when the procedure has 2 types de champ date and datetime' do
let(:types_de_champ) do
[
create(:type_de_champ_date, libelle: TypeDeChamp.type_champs.fetch(:date)),
create(:type_de_champ_datetime, libelle: TypeDeChamp.type_champs.fetch(:datetime))
build(:type_de_champ_date, libelle: TypeDeChamp.type_champs.fetch(:date)),
build(:type_de_champ_datetime, libelle: TypeDeChamp.type_champs.fetch(:datetime))
]
end
@ -358,13 +358,13 @@ describe TagsSubstitutionConcern, type: :model do
shared_examples "treat all kinds of space as equivalent" do
context 'and the champ has a non breaking space' do
let(:types_de_champ) { [create(:type_de_champ, libelle: 'mon tag')] }
let(:types_de_champ) { [build(:type_de_champ, libelle: 'mon tag')] }
it { is_expected.to eq('valeur') }
end
context 'and the champ has an ordinary space' do
let(:types_de_champ) { [create(:type_de_champ, libelle: 'mon tag')] }
let(:types_de_champ) { [build(:type_de_champ, libelle: 'mon tag')] }
it { is_expected.to eq('valeur') }
end
@ -401,12 +401,12 @@ describe TagsSubstitutionConcern, type: :model do
let(:types_de_champ) do
[
create(:type_de_champ, libelle: 'public'),
create(:type_de_champ_header_section, libelle: 'entête de section'),
create(:type_de_champ_explication, libelle: 'explication')
build(:type_de_champ, libelle: 'public'),
build(:type_de_champ_header_section, libelle: 'entête de section'),
build(:type_de_champ_explication, libelle: 'explication')
]
end
let(:types_de_champ_private) { [create(:type_de_champ, :private, libelle: 'privé')] }
let(:types_de_champ_private) { [build(:type_de_champ, :private, libelle: 'privé')] }
context 'do not generate tags for champs that cannot have usager content' do
it { is_expected.not_to include(include({ libelle: 'entête de section' })) }

View file

@ -29,14 +29,8 @@ describe Dossier do
end
describe 'with_champs' do
let(:procedure) { create(:procedure) }
let(:dossier) { Dossier.create(user: create(:user), groupe_instructeur: procedure.defaut_groupe_instructeur) }
before do
create(:type_de_champ, libelle: 'l1', order_place: 1, procedure: procedure)
create(:type_de_champ, libelle: 'l3', order_place: 3, procedure: procedure)
create(:type_de_champ, libelle: 'l2', order_place: 2, procedure: procedure)
end
let(:procedure) { create(:procedure, types_de_champ: [build(:type_de_champ, libelle: 'l1', position: 1), build(:type_de_champ, libelle: 'l3', position: 3), build(:type_de_champ, libelle: 'l2', position: 2)]) }
let(:dossier) { create(:dossier, procedure: procedure) }
it do
expect(Dossier.with_champs.find(dossier.id).champs.map(&:libelle)).to match(['l1', 'l2', 'l3'])
@ -255,27 +249,15 @@ describe Dossier do
end
describe '#champs' do
let(:procedure) { create(:procedure) }
let(:dossier) { Dossier.create(user: create(:user), groupe_instructeur: procedure.defaut_groupe_instructeur) }
before do
create(:type_de_champ, libelle: 'l1', order_place: 1, procedure: procedure)
create(:type_de_champ, libelle: 'l3', order_place: 3, procedure: procedure)
create(:type_de_champ, libelle: 'l2', order_place: 2, procedure: procedure)
end
let(:procedure) { create(:procedure, types_de_champ: [build(:type_de_champ, :private, libelle: 'l1', position: 1), build(:type_de_champ, :private, libelle: 'l3', position: 3), build(:type_de_champ, :private, libelle: 'l2', position: 2)]) }
let(:dossier) { create(:dossier, procedure: procedure) }
it { expect(dossier.champs.pluck(:libelle)).to match(['l1', 'l2', 'l3']) }
end
describe '#champs_private' do
let(:procedure) { create :procedure }
let(:dossier) { Dossier.create(user: create(:user), groupe_instructeur: procedure.defaut_groupe_instructeur) }
before do
create :type_de_champ, :private, libelle: 'l1', order_place: 1, procedure: procedure
create :type_de_champ, :private, libelle: 'l3', order_place: 3, procedure: procedure
create :type_de_champ, :private, libelle: 'l2', order_place: 2, procedure: procedure
end
let(:procedure) { create(:procedure, types_de_champ_private: [build(:type_de_champ, :private, libelle: 'l1', position: 1), build(:type_de_champ, :private, libelle: 'l3', position: 3), build(:type_de_champ, :private, libelle: 'l2', position: 2)]) }
let(:dossier) { create(:dossier, procedure: procedure) }
it { expect(dossier.champs_private.pluck(:libelle)).to match(['l1', 'l2', 'l3']) }
end
@ -525,7 +507,7 @@ describe Dossier do
dossier = nil
expect do
perform_enqueued_jobs do
dossier = Dossier.create(groupe_instructeur: procedure.defaut_groupe_instructeur, state: Dossier.states.fetch(:brouillon), user: user)
dossier = create(:dossier, procedure: procedure, state: Dossier.states.fetch(:brouillon), user: user)
end
end.to change(ActionMailer::Base.deliveries, :size).from(0).to(1)
@ -535,17 +517,19 @@ describe Dossier do
end
it "does not send an email when the dossier is created with a non brouillon state" do
expect { Dossier.create(groupe_instructeur: procedure.defaut_groupe_instructeur, state: Dossier.states.fetch(:en_construction), user: user) }.not_to change(ActionMailer::Base.deliveries, :size)
expect { Dossier.create(groupe_instructeur: procedure.defaut_groupe_instructeur, state: Dossier.states.fetch(:en_instruction), user: user) }.not_to change(ActionMailer::Base.deliveries, :size)
expect { Dossier.create(groupe_instructeur: procedure.defaut_groupe_instructeur, state: Dossier.states.fetch(:accepte), user: user) }.not_to change(ActionMailer::Base.deliveries, :size)
expect { Dossier.create(groupe_instructeur: procedure.defaut_groupe_instructeur, state: Dossier.states.fetch(:refuse), user: user) }.not_to change(ActionMailer::Base.deliveries, :size)
expect { Dossier.create(groupe_instructeur: procedure.defaut_groupe_instructeur, state: Dossier.states.fetch(:sans_suite), user: user) }.not_to change(ActionMailer::Base.deliveries, :size)
expect { create(:dossier, procedure: procedure, state: Dossier.states.fetch(:en_construction), user: user) }.not_to change(ActionMailer::Base.deliveries, :size)
expect { create(:dossier, procedure: procedure, state: Dossier.states.fetch(:en_instruction), user: user) }.not_to change(ActionMailer::Base.deliveries, :size)
expect { create(:dossier, procedure: procedure, state: Dossier.states.fetch(:accepte), user: user) }.not_to change(ActionMailer::Base.deliveries, :size)
expect { create(:dossier, procedure: procedure, state: Dossier.states.fetch(:refuse), user: user) }.not_to change(ActionMailer::Base.deliveries, :size)
expect { create(:dossier, procedure: procedure, state: Dossier.states.fetch(:sans_suite), user: user) }.not_to change(ActionMailer::Base.deliveries, :size)
end
end
describe "#unspecified_attestation_champs" do
let(:procedure) { create(:procedure, attestation_template: attestation_template) }
let(:procedure) { create(:procedure, attestation_template: attestation_template, types_de_champ: types_de_champ, types_de_champ_private: types_de_champ_private) }
let(:dossier) { create(:dossier, procedure: procedure, state: Dossier.states.fetch(:en_instruction)) }
let(:types_de_champ) { [] }
let(:types_de_champ_private) { [] }
subject { dossier.unspecified_attestation_champs.map(&:libelle) }
@ -574,14 +558,17 @@ describe Dossier do
context "wich is enabled" do
let(:activated) { true }
let!(:tdc_1) { create(:type_de_champ, libelle: "specified champ-in-title", procedure: procedure) }
let!(:tdc_2) { create(:type_de_champ, libelle: "unspecified champ-in-title", procedure: procedure) }
let!(:tdc_3) { create(:type_de_champ, libelle: "specified champ-in-body", procedure: procedure) }
let!(:tdc_4) { create(:type_de_champ, libelle: "unspecified champ-in-body", procedure: procedure) }
let!(:tdc_5) { create(:type_de_champ, private: true, libelle: "specified annotation privée-in-title", procedure: procedure) }
let!(:tdc_6) { create(:type_de_champ, private: true, libelle: "unspecified annotation privée-in-title", procedure: procedure) }
let!(:tdc_7) { create(:type_de_champ, private: true, libelle: "specified annotation privée-in-body", procedure: procedure) }
let!(:tdc_8) { create(:type_de_champ, private: true, libelle: "unspecified annotation privée-in-body", procedure: procedure) }
let(:types_de_champ) { [tdc_1, tdc_2, tdc_3, tdc_4] }
let(:types_de_champ_private) { [tdc_5, tdc_6, tdc_7, tdc_8] }
let(:tdc_1) { build(:type_de_champ, libelle: "specified champ-in-title") }
let(:tdc_2) { build(:type_de_champ, libelle: "unspecified champ-in-title") }
let(:tdc_3) { build(:type_de_champ, libelle: "specified champ-in-body") }
let(:tdc_4) { build(:type_de_champ, libelle: "unspecified champ-in-body") }
let(:tdc_5) { build(:type_de_champ, private: true, libelle: "specified annotation privée-in-title") }
let(:tdc_6) { build(:type_de_champ, private: true, libelle: "unspecified annotation privée-in-title") }
let(:tdc_7) { build(:type_de_champ, private: true, libelle: "specified annotation privée-in-body") }
let(:tdc_8) { build(:type_de_champ, private: true, libelle: "unspecified annotation privée-in-body") }
before do
(dossier.champs + dossier.champs_private)
@ -1014,7 +1001,7 @@ describe Dossier do
end
context "with mandatory SIRET champ" do
let(:type_de_champ) { create(:type_de_champ_siret, mandatory: true) }
let(:type_de_champ) { create(:type_de_champ_siret, mandatory: true, procedure: procedure) }
let(:champ_siret) { create(:champ_siret, type_de_champ: type_de_champ) }
before do
@ -1041,12 +1028,11 @@ describe Dossier do
end
context "with champ repetition" do
let(:procedure) { create(:procedure) }
let(:type_de_champ_repetition) { create(:type_de_champ_repetition, mandatory: true) }
let(:procedure) { create(:procedure, types_de_champ: [type_de_champ_repetition]) }
let(:type_de_champ_repetition) { build(:type_de_champ_repetition, mandatory: true) }
before do
procedure.types_de_champ << type_de_champ_repetition
type_de_champ_repetition.types_de_champ << create(:type_de_champ_text, mandatory: true)
create(:type_de_champ_text, mandatory: true, parent: type_de_champ_repetition)
end
context "when no champs" do
@ -1261,9 +1247,14 @@ describe Dossier do
end
describe "to_feature_collection" do
let(:dossier) { create(:dossier) }
let(:type_de_champ_carte) { create(:type_de_champ_carte, procedure: dossier.procedure) }
let(:geo_area) { create(:geo_area, :selection_utilisateur, :polygon) }
let(:champ) { create(:champ_carte, geo_areas: [geo_area]) }
let(:dossier) { create(:dossier, champs: [champ]) }
let(:champ_carte) { create(:champ_carte, type_de_champ: type_de_champ_carte, geo_areas: [geo_area]) }
before do
dossier.champs << champ_carte
end
it 'should have all champs carto' do
expect(dossier.to_feature_collection).to eq({
@ -1279,7 +1270,7 @@ describe Dossier do
},
properties: {
area: 219.0,
champ_id: champ.stable_id,
champ_id: champ_carte.stable_id,
dossier_id: dossier.id,
id: geo_area.id,
source: 'selection_utilisateur'

View file

@ -50,7 +50,7 @@ describe ProcedurePresentation do
describe "#fields" do
context 'when the procedure can have a SIRET number' do
let(:procedure) { create(:procedure, :with_type_de_champ, :with_type_de_champ_private, :types_de_champ_count => 4, :types_de_champ_private_count => 4) }
let(:procedure) { create(:procedure, :with_type_de_champ, :with_type_de_champ_private, types_de_champ_count: 4, types_de_champ_private_count: 4) }
let(:tdc_1) { procedure.types_de_champ[0] }
let(:tdc_2) { procedure.types_de_champ[1] }
let(:tdc_private_1) { procedure.types_de_champ_private[0] }

View file

@ -9,10 +9,6 @@ describe ProcedureRevision do
type_de_champ
end
before do
RevisionsMigration.add_revisions(procedure)
end
describe '#add_type_de_champ' do
it 'type_de_champ' do
expect(revision.types_de_champ.size).to eq(2)
@ -20,11 +16,8 @@ describe ProcedureRevision do
type_champ: TypeDeChamp.type_champs.fetch(:text),
libelle: "Un champ text"
})
procedure.reload
revision.reload
expect(revision.types_de_champ.size).to eq(3)
expect(procedure.types_de_champ.size).to eq(3)
expect(procedure.types_de_champ.last).to eq(new_type_de_champ)
expect(revision.types_de_champ.last).to eq(new_type_de_champ)
expect(revision.revision_types_de_champ.last.position).to eq(2)
expect(revision.revision_types_de_champ.last.type_de_champ).to eq(new_type_de_champ)
@ -37,9 +30,8 @@ describe ProcedureRevision do
libelle: "Un champ text",
private: true
})
procedure.reload
revision.reload
expect(revision.types_de_champ_private.size).to eq(2)
expect(procedure.types_de_champ_private.size).to eq(2)
end
it 'type_de_champ_repetition' do
@ -49,7 +41,6 @@ describe ProcedureRevision do
libelle: "Un champ text",
parent_id: type_de_champ_repetition.stable_id
})
type_de_champ_repetition.reload
expect(type_de_champ_repetition.types_de_champ.size).to eq(2)
end
end
@ -113,26 +104,21 @@ describe ProcedureRevision do
revision.remove_type_de_champ(type_de_champ.stable_id)
procedure.reload
expect(revision.types_de_champ.size).to eq(1)
expect(procedure.types_de_champ.size).to eq(1)
end
it 'type_de_champ_private' do
expect(revision.types_de_champ_private.size).to eq(1)
revision.remove_type_de_champ(type_de_champ_private.stable_id)
procedure.reload
expect(revision.types_de_champ_private.size).to eq(0)
expect(procedure.types_de_champ_private.size).to eq(0)
end
it 'type_de_champ_repetition' do
expect(type_de_champ_repetition.types_de_champ.size).to eq(1)
expect(revision.types_de_champ.size).to eq(2)
revision.remove_type_de_champ(type_de_champ_repetition.types_de_champ.first.stable_id)
procedure.reload
type_de_champ_repetition.reload
expect(type_de_champ_repetition.types_de_champ.size).to eq(0)
expect(revision.types_de_champ.size).to eq(2)
expect(procedure.types_de_champ.size).to eq(2)
end
end

View file

@ -42,7 +42,7 @@ describe Procedure do
describe 'closed mail template body' do
let(:procedure) { create(:procedure) }
subject { procedure.closed_mail_template.body }
subject { procedure.closed_mail_template.rich_body.body.to_html }
context 'for procedures without an attestation' do
it { is_expected.not_to include('lien attestation') }
@ -279,15 +279,6 @@ describe Procedure do
end
end
describe '#types_de_champ (ordered)' do
let(:procedure) { create(:procedure) }
let!(:type_de_champ_0) { create(:type_de_champ, procedure: procedure, order_place: 1) }
let!(:type_de_champ_1) { create(:type_de_champ, procedure: procedure, order_place: 0) }
subject { procedure.types_de_champ }
it { expect(subject.first).to eq(type_de_champ_1) }
it { expect(subject.last).to eq(type_de_champ_0) }
end
describe 'active' do
let(:procedure) { create(:procedure) }
subject { Procedure.active(procedure.id) }
@ -333,22 +324,22 @@ describe Procedure do
end
describe 'clone' do
let!(:service) { create(:service) }
let(:procedure) { create(:procedure, received_mail: received_mail, service: service) }
let!(:type_de_champ_0) { create(:type_de_champ, procedure: procedure, order_place: 0) }
let!(:type_de_champ_1) { create(:type_de_champ, procedure: procedure, order_place: 1) }
let!(:type_de_champ_2) { create(:type_de_champ_drop_down_list, procedure: procedure, order_place: 2) }
let!(:type_de_champ_pj) { create(:type_de_champ_piece_justificative, procedure: procedure, order_place: 3, old_pj: { stable_id: 2713 }) }
let!(:type_de_champ_private_0) { create(:type_de_champ, :private, procedure: procedure, order_place: 0) }
let!(:type_de_champ_private_1) { create(:type_de_champ, :private, procedure: procedure, order_place: 1) }
let!(:type_de_champ_private_2) { create(:type_de_champ_drop_down_list, :private, procedure: procedure, order_place: 2) }
let(:service) { create(:service) }
let(:procedure) { create(:procedure, received_mail: received_mail, service: service, types_de_champ: [type_de_champ_0, type_de_champ_1, type_de_champ_2, type_de_champ_pj], types_de_champ_private: [type_de_champ_private_0, type_de_champ_private_1, type_de_champ_private_2]) }
let(:type_de_champ_0) { build(:type_de_champ, position: 0) }
let(:type_de_champ_1) { build(:type_de_champ, position: 1) }
let(:type_de_champ_2) { build(:type_de_champ_drop_down_list, position: 2) }
let(:type_de_champ_pj) { build(:type_de_champ_piece_justificative, position: 3, old_pj: { stable_id: 2713 }) }
let(:type_de_champ_private_0) { build(:type_de_champ, :private, position: 0) }
let(:type_de_champ_private_1) { build(:type_de_champ, :private, position: 1) }
let(:type_de_champ_private_2) { build(:type_de_champ_drop_down_list, :private, position: 2) }
let(:received_mail) { build(:received_mail) }
let(:from_library) { false }
let(:administrateur) { procedure.administrateurs.first }
let!(:groupe_instructeur_1) { create(:groupe_instructeur, procedure: procedure, label: "groupe_1") }
let!(:instructeur_1) { create(:instructeur) }
let!(:instructeur_2) { create(:instructeur) }
let(:groupe_instructeur_1) { create(:groupe_instructeur, procedure: procedure, label: "groupe_1") }
let(:instructeur_1) { create(:instructeur) }
let(:instructeur_2) { create(:instructeur) }
let!(:assign_to_1) { create(:assign_to, procedure: procedure, groupe_instructeur: groupe_instructeur_1, instructeur: instructeur_1) }
let!(:assign_to_2) { create(:assign_to, procedure: procedure, groupe_instructeur: groupe_instructeur_1, instructeur: instructeur_2) }
@ -379,28 +370,18 @@ describe Procedure do
it 'should duplicate specific objects with different id' do
expect(subject.id).not_to eq(procedure.id)
expect(subject.types_de_champ.size).to eq(procedure.types_de_champ.size)
expect(subject.types_de_champ_private.size).to eq procedure.types_de_champ_private.size
expect(subject.types_de_champ.map(&:drop_down_options).compact.size).to eq procedure.types_de_champ.map(&:drop_down_options).compact.size
expect(subject.types_de_champ_private.map(&:drop_down_options).compact.size).to eq procedure.types_de_champ_private.map(&:drop_down_options).compact.size
expect(subject.draft_revision.types_de_champ.size).to eq(procedure.draft_revision.types_de_champ.size)
expect(subject.draft_revision.types_de_champ_private.size).to eq(procedure.draft_revision.types_de_champ_private.size)
expect(subject.draft_types_de_champ.size).to eq(procedure.draft_types_de_champ.size)
expect(subject.draft_types_de_champ_private.size).to eq(procedure.draft_types_de_champ_private.size)
procedure.types_de_champ.zip(subject.types_de_champ).each do |ptc, stc|
procedure.draft_types_de_champ.zip(subject.draft_types_de_champ).each do |ptc, stc|
expect(stc).to have_same_attributes_as(ptc, except: ["revision_id"])
expect(stc.revision).to eq(subject.draft_revision)
end
procedure.types_de_champ.zip(procedure.draft_revision.types_de_champ).each do |ptc, rtc|
expect(ptc).to eq(rtc)
end
subject.types_de_champ_private.zip(procedure.types_de_champ_private).each do |stc, ptc|
procedure.draft_types_de_champ_private.zip(subject.draft_types_de_champ_private).each do |ptc, stc|
expect(stc).to have_same_attributes_as(ptc, except: ["revision_id"])
expect(stc.revision).to eq(subject.draft_revision)
end
procedure.types_de_champ_private.zip(procedure.draft_revision.types_de_champ_private).each do |ptc, rtc|
expect(ptc).to eq(rtc)
end
expect(subject.attestation_template.title).to eq(procedure.attestation_template.title)
@ -423,7 +404,7 @@ describe Procedure do
end
it 'should discard old pj information' do
subject.types_de_champ.each do |stc|
subject.draft_types_de_champ.each do |stc|
expect(stc.old_pj).to be_nil
end
end
@ -459,7 +440,7 @@ describe Procedure do
end
it 'should discard old pj information' do
subject.types_de_champ.each do |stc|
subject.draft_types_de_champ.each do |stc|
expect(stc.old_pj).to be_nil
end
end
@ -519,12 +500,12 @@ describe Procedure do
end
it 'should keep types_de_champ ids stable' do
expect(subject.types_de_champ.first.id).not_to eq(procedure.types_de_champ.first.id)
expect(subject.types_de_champ.first.stable_id).to eq(procedure.types_de_champ.first.id)
expect(subject.draft_types_de_champ.first.id).not_to eq(procedure.draft_types_de_champ.first.id)
expect(subject.draft_types_de_champ.first.stable_id).to eq(procedure.draft_types_de_champ.first.id)
end
it 'should duplicate piece_justificative_template on a type_de_champ' do
expect(subject.types_de_champ.where(type_champ: "piece_justificative").first.piece_justificative_template.attached?).to be true
expect(subject.draft_types_de_champ.where(type_champ: "piece_justificative").first.piece_justificative_template.attached?).to be true
end
context 'with a notice attached' do
@ -805,7 +786,7 @@ describe Procedure do
end
describe 'suggested_path' do
let(:procedure) { create :procedure, aasm_state: :publiee, libelle: 'Inscription au Collège' }
let(:procedure) { create(:procedure, aasm_state: :publiee, libelle: 'Inscription au Collège') }
subject { procedure.suggested_path(procedure.administrateurs.first) }
@ -821,7 +802,7 @@ describe Procedure do
context 'when the suggestion conflicts with one procedure' do
before do
create :procedure, aasm_state: :publiee, path: 'inscription-au-college'
create(:procedure, aasm_state: :publiee, path: 'inscription-au-college')
end
it { is_expected.to eq 'inscription-au-college-2' }
@ -829,8 +810,8 @@ describe Procedure do
context 'when the suggestion conflicts with several procedures' do
before do
create :procedure, aasm_state: :publiee, path: 'inscription-au-college'
create :procedure, aasm_state: :publiee, path: 'inscription-au-college-2'
create(:procedure, aasm_state: :publiee, path: 'inscription-au-college')
create(:procedure, aasm_state: :publiee, path: 'inscription-au-college-2')
end
it { is_expected.to eq 'inscription-au-college-3' }
@ -838,7 +819,7 @@ describe Procedure do
context 'when the suggestion conflicts with another procedure of the same admin' do
before do
create :procedure, aasm_state: :publiee, path: 'inscription-au-college', administrateurs: procedure.administrateurs
create(:procedure, aasm_state: :publiee, path: 'inscription-au-college', administrateurs: procedure.administrateurs)
end
it { is_expected.to eq 'inscription-au-college' }
@ -903,13 +884,14 @@ describe Procedure do
describe '#new_dossier' do
let(:procedure) do
procedure = create(:procedure)
create(:type_de_champ_text, procedure: procedure, order_place: 1)
create(:type_de_champ_number, procedure: procedure, order_place: 2)
create(:type_de_champ_textarea, :private, procedure: procedure)
procedure
create(:procedure,
types_de_champ: [
build(:type_de_champ_text, position: 0),
build(:type_de_champ_number, position: 1)
],
types_de_champ_private: [
build(:type_de_champ_textarea, :private)
])
end
let(:dossier) { procedure.new_dossier }

View file

@ -153,44 +153,6 @@ shared_examples 'type_de_champ_spec' do
end
end
describe "repetition" do
let(:procedure) { create(:procedure) }
let(:type_de_champ) { create(:type_de_champ_repetition, procedure: procedure) }
let(:type_de_champ_text) { create(:type_de_champ_text, procedure: procedure) }
let(:type_de_champ_integer_number_attrs) { attributes_for(:type_de_champ_integer_number) }
it "associates nested types_de_champ to the parent procedure" do
expect(type_de_champ.types_de_champ.size).to eq(0)
expect(procedure.types_de_champ.size).to eq(1)
procedure.update!(types_de_champ_attributes: [
{
id: type_de_champ.id,
libelle: type_de_champ.libelle,
types_de_champ_attributes: [type_de_champ_integer_number_attrs]
}
])
procedure.reload
type_de_champ.reload
expect(procedure.types_de_champ.size).to eq(1)
expect(type_de_champ.types_de_champ.size).to eq(1)
expect(type_de_champ.types_de_champ.first.parent).to eq(type_de_champ)
expect(type_de_champ.types_de_champ.first.procedure).to eq(procedure)
expect(type_de_champ.types_de_champ.first.private?).to eq(false)
type_de_champ.types_de_champ << type_de_champ_text
expect(type_de_champ.types_de_champ.size).to eq(2)
expect(type_de_champ_text.parent).to eq(type_de_champ)
admin = create(:administrateur)
cloned_procedure = procedure.clone(admin, false)
expect(cloned_procedure.types_de_champ.first.types_de_champ).not_to be_empty
end
end
describe "linked_drop_down_list" do
let(:type_de_champ) { create(:type_de_champ_linked_drop_down_list) }

View file

@ -49,12 +49,11 @@ describe DossierSerializer do
context 'when a type de champ PJ was cloned from a legacy PJ' do
let(:original_pj_id) { 3 }
let(:cloned_type_de_champ) do
tdc = create(:type_de_champ_piece_justificative,
build(:type_de_champ_piece_justificative,
libelle: "Vidéo de votre demande de subvention",
description: "Pour optimiser vos chances, soignez la chorégraphie et privilégiez le chant polyphonique.\r\nRécupérer le formulaire vierge pour mon dossier : https://www.dance-academy.gouv.fr",
order_place: 0)
tdc.old_pj = { stable_id: original_pj_id }
tdc
old_pj: { stable_id: original_pj_id },
position: 0)
end
let(:procedure) { create(:procedure, :published, types_de_champ: [cloned_type_de_champ]) }
let(:dossier) { create(:dossier, procedure: procedure) }

View file

@ -12,12 +12,11 @@ describe ProcedureSerializer do
context 'when a type PJ was cloned to a type champ PJ' do
let(:original_pj_id) { 3 }
let(:cloned_type_de_champ) do
tdc = create(:type_de_champ_piece_justificative,
build(:type_de_champ_piece_justificative,
libelle: "Vidéo de votre demande de subvention",
description: "Pour optimiser vos chances, soignez la chorégraphie et privilégiez le chant polyphonique.\r\nRécupérer le formulaire vierge pour mon dossier : https://www.dance-academy.gouv.fr",
order_place: 0)
tdc.old_pj = { stable_id: original_pj_id }
tdc
old_pj: { stable_id: original_pj_id },
position: 0)
end
let(:procedure) { create(:procedure, :published, types_de_champ: [cloned_type_de_champ]) }

View file

@ -61,7 +61,7 @@ describe AdministrateurUsageStatisticsService do
end
context 'with a freshly active procedure' do
let(:procedure) { create(:procedure, aasm_state: 'publiee') }
let(:procedure) { create(:procedure, :published) }
it do
is_expected.to include(
@ -82,7 +82,7 @@ describe AdministrateurUsageStatisticsService do
end
context 'with a procedure close' do
let(:procedure) { create(:procedure, aasm_state: 'close') }
let(:procedure) { create(:procedure, :closed) }
let!(:dossiers) do
(1..7).flat_map do
[
@ -163,7 +163,7 @@ describe AdministrateurUsageStatisticsService do
end
context 'with a procedure en prod' do
let(:procedure) { create(:procedure, aasm_state: 'publiee') }
let(:procedure) { create(:procedure, :published) }
let!(:dossiers) do
[
create(:dossier, :en_construction, procedure: procedure),
@ -191,7 +191,7 @@ describe AdministrateurUsageStatisticsService do
end
context 'with a procedure en prod and more than 20 dossiers' do
let(:procedure) { create(:procedure, aasm_state: 'publiee') }
let(:procedure) { create(:procedure, :published) }
let!(:dossiers) do
(1..7).flat_map do
[

View file

@ -18,10 +18,10 @@ describe ProcedureExportService do
before do
# change one tdc place to check if the header is ordered
tdc_first = procedure.types_de_champ.first
tdc_last = procedure.types_de_champ.last
tdc_first = procedure.active_revision.revision_types_de_champ.first
tdc_last = procedure.active_revision.revision_types_de_champ.last
tdc_first.update(order_place: tdc_last.order_place + 1)
tdc_first.update(position: tdc_last.position + 1)
procedure.reload
end

View file

@ -12,11 +12,6 @@ module FeatureHelpers
login_as instructeur, scope: :instructeur
end
def create_dossier
dossier = FactoryBot.create(:dossier)
dossier
end
def sign_in_with(email, password, sign_in_by_link = false)
fill_in :user_email, with: email
fill_in :user_password, with: password

View file

@ -1,10 +1,10 @@
describe 'admin/mail_templates/edit.html.haml', type: :view do
describe 'new_administrateur/mail_templates/edit.html.haml', type: :view do
let(:procedure) { create(:procedure) }
let(:mail_template) { create(:received_mail, procedure: procedure) }
let(:all_tags) { mail_template.tags }
before do
allow(view).to receive(:admin_procedure_mail_template_path).and_return("/toto")
allow(view).to receive(:admin_procedure_mail_templates_path).and_return("/toto")
allow(view).to receive(:admin_procedure_mail_templates_path).and_return("/toto")
assign(:mail_template, mail_template)
@ -13,6 +13,5 @@ describe 'admin/mail_templates/edit.html.haml', type: :view do
context "Champs are listed in the page" do
it { expect(all_tags).to include(include({ libelle: 'numéro du dossier' })) }
it { expect(render).to include("numéro du dossier") }
end
end

View file

@ -38,7 +38,7 @@ describe 'shared/dossiers/demande.html.haml', type: :view do
expect(subject).to include(individual.gender)
expect(subject).to include(individual.nom)
expect(subject).to include(individual.prenom)
expect(subject).to include(I18n.l(individual.birthdate))
expect(subject).to include(I18n.l(individual.birthdate, format: :long))
end
end