2019-07-30-01 (#4153)

2019-07-30-01
This commit is contained in:
Pierre de La Morinerie 2019-07-30 16:35:09 +02:00 committed by GitHub
commit 392d38ce35
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
87 changed files with 161 additions and 10762 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 202 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 34 KiB

View file

@ -25,7 +25,6 @@
// = require login
// = require main_container
// = require navbar
// = require pieces_justificatives_fields
// = require pj_modal
// = require print
// = require procedure

View file

@ -1,91 +0,0 @@
@import "colors";
$default-space: 15px;
.tour-de-france {
.blue-panel {
background-color: $blue;
}
.white-panel {
padding-top: 2 * $default-space;
padding-bottom: 2 * $default-space;
}
.photos-panel {
background: linear-gradient(#FFFFFF, #FFFFFF 50%, $light-grey 50%, $light-grey 100%);
padding-top: 2 * $default-space;
padding-bottom: 2 * $default-space;
}
.grey-panel {
background-color: $light-grey;
padding-top: 2 * $default-space;
padding-bottom: 2 * $default-space;
}
p {
margin-bottom: $default-space;
}
h2 {
font-size: 20px;
font-weight: bold;
margin-bottom: $default-space;
}
h3 {
font-weight: bold;
}
h4 {
color: $grey;
margin-bottom: $default-space;
}
ul {
list-style-type: disc;
margin-left: 15px;
}
li {
margin-bottom: 5px;
}
.cards {
.card:nth-of-type(even) {
margin-right: 0px;
}
}
$card-margin-bottom: 2 * $default-space;
.card {
padding: 15px;
margin-bottom: $card-margin-bottom;
border-radius: 5px;
box-shadow: none;
border: 1px solid rgba(0, 0, 0, 0.15);
min-height: 230px;
}
$card-half-horizontal-spacing: 4 * $default-space;
.card-half {
width: calc((100% - #{$card-half-horizontal-spacing}) / 2);
margin-right: 3 * $default-space;
}
.tour-de-france-logo {
width: 400px;
margin: 0 auto;
display: block;
padding-top: 2 * $default-space;
padding-bottom: 2 * $default-space;
}
.atelier-photo {
width: 380px;
border-radius: 5px;
}
}

View file

@ -1,5 +0,0 @@
.pieces-justificatives-fields {
.form-inline > .form-group {
vertical-align: top;
}
}

View file

@ -1,50 +0,0 @@
class Admin::PiecesJustificativesController < AdminController
before_action :retrieve_procedure
before_action :procedure_locked?
before_action :reset_procedure, only: [:update, :destroy, :move_up, :move_down]
def show
end
def update
if @procedure.update(update_params)
flash.now.notice = 'Modifications sauvegardées'
else
flash.now.notice = 'Une erreur est survenue'
end
render 'show', format: :js
end
def destroy
@procedure.types_de_piece_justificative.find(params[:id]).destroy
render 'show', format: :js
rescue ActiveRecord::RecordNotFound
render json: { message: 'Type de piece justificative not found' }, status: 404
end
def move_up
index = params[:index].to_i - 1
if @procedure.switch_types_de_piece_justificative index
render 'show', format: :js
else
render json: {}, status: 400
end
end
def move_down
if @procedure.switch_types_de_piece_justificative params[:index].to_i
render 'show', format: :js
else
render json: {}, status: 400
end
end
private
def update_params
params
.require(:procedure)
.permit(types_de_piece_justificative_attributes: [:libelle, :description, :id, :order_place, :mandatory, :lien_demarche])
end
end

View file

@ -59,7 +59,4 @@ class RootController < ApplicationController
def suivi
end
def tour_de_france
end
end

View file

@ -132,7 +132,6 @@ module Users
end
# FIXME:
# - remove PiecesJustificativesService
# - delegate draft save logic to champ ?
def update_brouillon
@dossier = dossier_with_champs
@ -158,8 +157,6 @@ module Users
@dossier = dossier_with_champs
end
# FIXME:
# - remove PiecesJustificativesService
def update
@dossier = dossier_with_champs
@ -297,7 +294,7 @@ module Users
end
def update_dossier_and_compute_errors
errors = PiecesJustificativesService.upload!(@dossier, current_user, params)
errors = []
if champs_params[:dossier] && !@dossier.update(champs_params[:dossier])
errors += @dossier.errors.full_messages
@ -305,7 +302,6 @@ module Users
if !save_draft?
errors += @dossier.check_mandatory_champs
errors += PiecesJustificativesService.missing_pj_error_messages(@dossier)
end
errors

View file

@ -15,9 +15,14 @@ class Users::SessionsController < Sessions::SessionsController
# POST /resource/sign_in
def create
remember_me = params[:user][:remember_me] == '1'
try_to_authenticate(User, remember_me)
try_to_authenticate(Gestionnaire, remember_me)
try_to_authenticate(Administrateur, remember_me)
if resource_locked?(try_to_authenticate(User, remember_me)) ||
resource_locked?(try_to_authenticate(Gestionnaire, remember_me)) ||
resource_locked?(try_to_authenticate(Administrateur, remember_me))
flash.alert = 'Votre compte est verrouillé.'
new
return render :new, status: 401
end
if user_signed_in?
current_user.update(loged_in_with_france_connect: nil)
@ -109,5 +114,10 @@ class Users::SessionsController < Sessions::SessionsController
resource.force_sync_credentials
end
end
resource
end
def resource_locked?(resource)
resource.present? && resource.access_locked?
end
end

View file

@ -8,7 +8,6 @@ class ProcedureDashboard < Administrate::BaseDashboard
# which determines how the attribute is displayed
# on pages throughout the dashboard.
ATTRIBUTE_TYPES = {
types_de_piece_justificative: TypesDePieceJustificativeCollectionField,
types_de_champ: TypesDeChampCollectionField,
types_de_champ_private: TypesDeChampCollectionField,
path: ProcedureLinkField,
@ -72,7 +71,6 @@ class ProcedureDashboard < Administrate::BaseDashboard
:archived_at,
:types_de_champ,
:types_de_champ_private,
:types_de_piece_justificative,
:for_individual,
:auto_archive_on,
:gestionnaires,

View file

@ -1,7 +0,0 @@
require "administrate/field/base"
class TypesDePieceJustificativeCollectionField < Administrate::Field::Base
def to_s
data
end
end

View file

@ -1,55 +0,0 @@
module AdminFormulaireHelper
BASE_CLASSES = ['btn', 'btn-default', 'form-control', 'fa']
def button_up(procedure, kind, index, url)
if display_up_button?(index, procedure, kind)
button(up_classes, "btn_up_#{index}", url)
end
end
def button_down(procedure, kind, index, url)
if display_down_button?(index, procedure, kind)
button(down_classes, "btn_down_#{index}", url)
end
end
private
def button(classes, id, url)
link_to(
'',
url,
class: classes,
id: id,
remote: true,
method: :post
)
end
def up_classes
BASE_CLASSES + ['fa-chevron-up']
end
def down_classes
BASE_CLASSES + ['fa-chevron-down']
end
def display_up_button?(index, procedure, kind)
index != 0 && count_type_de_champ(procedure, kind) > 1
end
def display_down_button?(index, procedure, kind)
(index + 1) < count_type_de_champ(procedure, kind)
end
def count_type_de_champ(procedure, kind)
case kind
when "public"
@count_type_de_champ_public ||= procedure.types_de_champ.count
when "private"
@count_type_de_champ_private ||= procedure.types_de_champ_private.count
when "piece_justificative"
@count_type_de_piece_justificative ||= procedure.types_de_piece_justificative.count
end
end
end

View file

@ -1,5 +0,0 @@
module PieceJustificativeHelper
def display_pj_filename(pj)
truncate(pj.original_filename, length: 60)
end
end

View file

@ -1,94 +0,0 @@
# Source: https://github.com/gitlabhq/gitlabhq/blob/master/lib/file_size_validator.rb
class FileSizeValidator < ActiveModel::EachValidator
MESSAGES = { is: :wrong_size, minimum: :size_too_small, maximum: :size_too_big }.freeze
CHECKS = { is: :==, minimum: :>=, maximum: :<= }.freeze
DEFAULT_TOKENIZER = lambda { |value| value.split(//) }
RESERVED_OPTIONS = [:minimum, :maximum, :within, :is, :tokenizer, :too_short, :too_long]
def initialize(options)
range = options.delete(:in) || options.delete(:within)
if range.present?
if !range.is_a?(Range)
raise ArgumentError, ":in and :within must be a Range"
end
options[:minimum], options[:maximum] = range.begin, range.end
if range.exclude_end?
options[:maximum] -= 1
end
end
super
end
def check_validity!
keys = CHECKS.keys & options.keys
if keys.empty?
raise ArgumentError, 'Range unspecified. Specify the :within, :maximum, :minimum, or :is option.'
end
keys.each do |key|
value = options[key]
if !(value.is_a?(Integer) && value >= 0) && !value.is_a?(Symbol)
raise ArgumentError, ":#{key} must be a nonnegative Integer or symbol"
end
end
end
def validate_each(record, attribute, value)
if !value.is_a?(CarrierWave::Uploader::Base)
raise(ArgumentError, "A CarrierWave::Uploader::Base object was expected")
end
if value.is_a?(String)
value = (options[:tokenizer] || DEFAULT_TOKENIZER).call(value)
end
CHECKS.each do |key, validity_check|
if !check_value = options[key]
next
end
check_value =
case check_value
when Integer
check_value
when Symbol
record.send(check_value)
end
if key == :maximum
value ||= []
end
value_size = value.size
if value_size.send(validity_check, check_value)
next
end
errors_options = options.except(*RESERVED_OPTIONS)
errors_options[:file_size] = help.number_to_human_size check_value
default_message = options[MESSAGES[key]]
if default_message
errors_options[:message] ||= default_message
end
record.errors.add(attribute, MESSAGES[key], errors_options)
end
end
def help
Helper.instance
end
class Helper
include Singleton
include ActionView::Helpers::NumberHelper
end
end

View file

@ -4,7 +4,7 @@ class Administrateur < ApplicationRecord
include ActiveRecord::SecureToken
devise :database_authenticatable, :registerable, :async,
:recoverable, :rememberable, :trackable, :validatable
:recoverable, :rememberable, :trackable, :validatable, :lockable
has_and_belongs_to_many :gestionnaires
has_many :administrateurs_procedures

View file

@ -1,7 +1,7 @@
class Administration < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :rememberable, :trackable, :validatable, :omniauthable, :async, omniauth_providers: [:github]
devise :database_authenticatable, :rememberable, :trackable, :validatable, :omniauthable, :lockable, :async, omniauth_providers: [:github]
def self.from_omniauth(params)
find_by(email: params["info"]["email"])

View file

@ -21,7 +21,6 @@ class Dossier < ApplicationRecord
has_one :individual, dependent: :destroy
has_one :attestation, dependent: :destroy
has_many :pieces_justificatives, inverse_of: :dossier, dependent: :destroy
has_one_attached :justificatif_motivation
has_many :champs, -> { root.public_only.ordered }, inverse_of: :dossier, dependent: :destroy
@ -128,7 +127,6 @@ class Dossier < ApplicationRecord
:etablissement,
piece_justificative_file_attachment: :blob
],
pieces_justificatives: [],
avis: [],
etablissement: [],
individual: [],
@ -138,7 +136,6 @@ class Dossier < ApplicationRecord
accepts_nested_attributes_for :individual
delegate :siret, :siren, to: :etablissement, allow_nil: true
delegate :types_de_piece_justificative, to: :procedure
delegate :types_de_champ, to: :procedure
delegate :france_connect_information, to: :user
@ -165,18 +162,6 @@ class Dossier < ApplicationRecord
self.private_search_terms = champs_private.flat_map(&:search_terms).compact.join(' ')
end
def was_piece_justificative_uploaded_for_type_id?(type_id)
pieces_justificatives.where(type_de_piece_justificative_id: type_id).count > 0
end
def retrieve_last_piece_justificative_by_type(type)
pieces_justificatives.where(type_de_piece_justificative_id: type).last
end
def retrieve_all_piece_justificative_by_type(type)
pieces_justificatives.where(type_de_piece_justificative_id: type).order(created_at: :DESC)
end
def build_default_champs
procedure.build_champs.each do |champ|
champs << champ

View file

@ -3,7 +3,7 @@ class Gestionnaire < ApplicationRecord
include EmailSanitizableConcern
devise :database_authenticatable, :registerable, :async,
:recoverable, :rememberable, :trackable, :validatable
:recoverable, :rememberable, :trackable, :validatable, :lockable
has_and_belongs_to_many :administrateurs
@ -93,11 +93,7 @@ class Gestionnaire < ApplicationRecord
.find_by(gestionnaire: self, dossier: dossier)
if follow.present?
champs_publiques = follow.dossier.champs.updated_since?(follow.demande_seen_at).any?
pieces_justificatives = follow.dossier.pieces_justificatives.updated_since?(follow.demande_seen_at).any?
demande = champs_publiques || pieces_justificatives
demande = follow.dossier.champs.updated_since?(follow.demande_seen_at).any?
annotations_privees = follow.dossier.champs_private.updated_since?(follow.annotations_privees_seen_at).any?
@ -154,10 +150,6 @@ class Gestionnaire < ApplicationRecord
.joins(:champs)
.where('champs.updated_at > follows.demande_seen_at')
updated_pieces_justificatives = dossiers
.joins(:pieces_justificatives)
.where('pieces_justificatives.updated_at > follows.demande_seen_at')
updated_annotations = dossiers
.joins(:champs_private)
.where('champs.updated_at > follows.annotations_privees_seen_at')
@ -174,7 +166,6 @@ class Gestionnaire < ApplicationRecord
[
updated_demandes,
updated_pieces_justificatives,
updated_annotations,
updated_avis,
updated_messagerie

View file

@ -1,56 +0,0 @@
class PieceJustificative < ApplicationRecord
belongs_to :dossier, inverse_of: :pieces_justificatives, touch: true
belongs_to :type_de_piece_justificative
has_one :commentaire
belongs_to :user
delegate :api_entreprise, :libelle, :order_place, to: :type_de_piece_justificative
alias_attribute :type, :type_de_piece_justificative_id
mount_uploader :content, PieceJustificativeUploader
validates :content, :file_size => { :maximum => 20.megabytes }
validates :content, presence: true, allow_blank: false, allow_nil: false
scope :updated_since?, -> (date) { where('pieces_justificatives.updated_at > ?', date) }
def empty?
content.blank?
end
def libelle
if type_de_piece_justificative.nil?
return content.to_s
else
type_de_piece_justificative.libelle
end
end
def content_url
if content.url.present?
if Flipflop.remote_storage?
(RemoteDownloader.new content.filename).url
else
(LocalDownloader.new content.path,
(type_de_piece_justificative.nil? ? content.original_filename : type_de_piece_justificative.libelle)).url
end
end
end
def self.accept_format
" application/pdf,
application/msword,
application/vnd.openxmlformats-officedocument.wordprocessingml.document,
application/vnd.ms-excel,
application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,
application/vnd.ms-powerpoint,
application/vnd.openxmlformats-officedocument.presentationml.presentation,
application/vnd.oasis.opendocument.text,
application/vnd.oasis.opendocument.presentation,
application/vnd.oasis.opendocument.spreadsheet,
image/png,
image/jpeg
"
end
end

View file

@ -3,7 +3,6 @@ require Rails.root.join('lib', 'percentile')
class Procedure < ApplicationRecord
MAX_DUREE_CONSERVATION = 36
has_many :types_de_piece_justificative, -> { ordered }, inverse_of: :procedure, dependent: :destroy
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 :dossiers, dependent: :restrict_with_exception
@ -31,7 +30,6 @@ class Procedure < ApplicationRecord
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
accepts_nested_attributes_for :types_de_piece_justificative, reject_if: proc { |attributes| attributes['libelle'].blank? }, allow_destroy: true
mount_uploader :logo, ProcedureLogoUploader
@ -51,7 +49,6 @@ class Procedure < ApplicationRecord
:administrateurs,
:types_de_champ_private,
:types_de_champ,
:types_de_piece_justificative,
:module_api_carto
)
}
@ -187,10 +184,6 @@ class Procedure < ApplicationRecord
switch_list_order(types_de_champ_private, index_of_first_element)
end
def switch_types_de_piece_justificative(index_of_first_element)
switch_list_order(types_de_piece_justificative, index_of_first_element)
end
def switch_list_order(list, index_of_first_element)
if index_of_first_element < 0 ||
index_of_first_element == list.count - 1 ||
@ -225,7 +218,6 @@ class Procedure < ApplicationRecord
procedure.remote_logo_url = self.logo_url
procedure.lien_notice = nil
procedure.types_de_champ += PiecesJustificativesService.types_pj_as_types_de_champ(self)
if is_different_admin || from_library
procedure.types_de_champ.each { |tdc| tdc.options&.delete(:old_pj) }
end
@ -280,10 +272,6 @@ class Procedure < ApplicationRecord
whitelisted_at.present?
end
def has_old_pjs?
types_de_piece_justificative.any?
end
def total_dossier
self.dossiers.state_not_brouillon.size
end

View file

@ -1,10 +0,0 @@
class TypeDePieceJustificative < ApplicationRecord
has_many :pieces_justificatives, dependent: :destroy
belongs_to :procedure
validates :libelle, presence: true, allow_blank: false, allow_nil: false
validates :lien_demarche, format: { with: URI.regexp }, allow_blank: true, allow_nil: true
scope :ordered, -> { order(order_place: :asc) }
end

View file

@ -10,12 +10,11 @@ class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable, :async,
:recoverable, :rememberable, :trackable, :validatable, :confirmable
:recoverable, :rememberable, :trackable, :validatable, :confirmable, :lockable
has_many :dossiers, dependent: :destroy
has_many :invites, dependent: :destroy
has_many :dossiers_invites, through: :invites, source: :dossier
has_many :piece_justificative, dependent: :destroy
has_many :feedbacks, dependent: :destroy
has_one :france_connect_information, dependent: :destroy

View file

@ -50,7 +50,6 @@ class DossierSerializer < ActiveModel::Serializer
end
def pieces_justificatives
ActiveModelSerializers::SerializableResource.new(object.pieces_justificatives).serializable_hash +
PiecesJustificativesService.serialize_champs_as_pjs(object)
end
@ -61,7 +60,6 @@ class DossierSerializer < ActiveModel::Serializer
end
def types_de_piece_justificative
ActiveModelSerializers::SerializableResource.new(object.types_de_piece_justificative).serializable_hash +
PiecesJustificativesService.serialize_types_de_champ_as_type_pj(object)
end

View file

@ -1,11 +0,0 @@
class PieceJustificativeSerializer < ActiveModel::Serializer
attributes :created_at,
:type_de_piece_justificative_id,
:content_url
has_one :user
def created_at
object.created_at&.in_time_zone('UTC')
end
end

View file

@ -49,7 +49,6 @@ class ProcedureSerializer < ActiveModel::Serializer
end
def types_de_piece_justificative
ActiveModelSerializers::SerializableResource.new(object.types_de_piece_justificative).serializable_hash +
PiecesJustificativesService.serialize_types_de_champ_as_type_pj(object)
end
end

View file

@ -1,7 +0,0 @@
class TypeDePieceJustificativeSerializer < ActiveModel::Serializer
attributes :id,
:libelle,
:description,
:order_place,
:lien_demarche
end

View file

@ -1,173 +0,0 @@
class CarrierwaveActiveStorageMigrationService
def ensure_openstack_copy_possible!(uploader)
ensure_active_storage_openstack!
ensure_carrierwave_openstack!(uploader)
ensure_active_storage_and_carrierwave_credetials_match(uploader)
end
def ensure_active_storage_openstack!
# If we manage to get the client, it means that ActiveStorage is on OpenStack
openstack_client!
end
def openstack_client!
@openstack_client ||= active_storage_openstack_client!
end
def active_storage_openstack_client!
service = ActiveStorage::Blob.service
if defined?(ActiveStorage::Service::DsProxyService) &&
service.is_a?(ActiveStorage::Service::DsProxyService)
service = service.wrapped
end
if !defined?(ActiveStorage::Service::OpenStackService) ||
!service.is_a?(ActiveStorage::Service::OpenStackService)
raise StandardError, 'ActiveStorage must be backed by OpenStack'
end
service.client
end
def ensure_carrierwave_openstack!(uploader)
storage = fog_client!(uploader)
if !defined?(Fog::OpenStack::Storage::Real) ||
!storage.is_a?(Fog::OpenStack::Storage::Real)
raise StandardError, 'Carrierwave must be backed by OpenStack'
end
end
def fog_client!(uploader)
storage = uploader.new.send(:storage)
if !defined?(CarrierWave::Storage::Fog) ||
!storage.is_a?(CarrierWave::Storage::Fog)
raise StandardError, 'Carrierwave must be backed by a Fog provider'
end
storage.connection
end
# OpenStack Swift's COPY object command works across different buckets, but they still need
# to be on the same object store. This method tries to ensure that Carrierwave and ActiveStorage
# are indeed pointing to the same Swift store.
def ensure_active_storage_and_carrierwave_credetials_match(uploader)
auth_keys = [
:openstack_tenant,
:openstack_api_key,
:openstack_username,
:openstack_region,
:openstack_management_url
]
active_storage_creds = openstack_client!.credentials.slice(*auth_keys)
carrierwave_creds = fog_client!(uploader).credentials.slice(*auth_keys)
if active_storage_creds != carrierwave_creds
raise StandardError, "Active Storage and Carrierwave credentials must match"
end
end
# If identify is true, force ActiveStorage to examine the beginning of the file
# to determine its MIME type. This identification does not happen immediately,
# but when the first attachment that references this blob is created.
def make_blob(uploader, created_at, filename: nil, identify: false)
content_type = uploader.content_type
identified = content_type.present? && !identify
ActiveStorage::Blob.create(
filename: filename || uploader.filename,
content_type: content_type,
byte_size: uploader.size,
checksum: checksum(uploader),
created_at: created_at,
metadata: { identified: identified, virus_scan_result: ActiveStorage::VirusScanner::SAFE }
)
end
def make_empty_blob(uploader, created_at, filename: nil)
content_type = uploader.content_type || 'text/plain'
blob = ActiveStorage::Blob.build_after_upload(
io: StringIO.new('File not found when migrating from CarrierWave.'),
filename: filename || uploader.filename,
content_type: content_type || 'text/plain',
metadata: { virus_scan_result: ActiveStorage::VirusScanner::SAFE }
)
blob.created_at = created_at
blob.save!
blob
end
def checksum(uploader)
hex_to_base64(uploader.file.send(:file).etag)
end
def hex_to_base64(hexdigest)
[[hexdigest].pack("H*")].pack("m0")
end
def copy_from_carrierwave_to_active_storage!(source_name, blob)
openstack_client!.copy_object(
carrierwave_container_name,
source_name,
active_storage_container_name,
blob.key
)
fix_content_type(blob)
end
def carrierwave_container_name
Rails.application.secrets.fog[:directory]
end
def active_storage_container_name
ENV['FOG_ACTIVESTORAGE_DIRECTORY']
end
def delete_from_active_storage!(blob)
openstack_client!.delete_object(
active_storage_container_name,
blob.key
)
end
# Before calling this method, you must make sure the file has been uploaded for the blob.
# Otherwise, this method might fail if it needs to read the beginning of the file to
# update the blobs MIME type.
def make_attachment(model, attachment_name, blob)
attachment = ActiveStorage::Attachment.create(
name: attachment_name,
record_type: model.class.base_class.name,
record_id: model.id,
blob: blob,
created_at: model.updated_at.iso8601
)
# Making the attachment may have triggerred MIME type auto detection on the blob,
# so we make sure to sync that potentially new MIME type to the object in OpenStack
fix_content_type(blob)
attachment
end
def fix_content_type(blob, retry_delay: 5)
retries ||= 0
# In OpenStack, ActiveStorage cannot inject the MIME type on the fly during direct
# download. Instead, the MIME type needs to be stored statically on the file object
# in OpenStack. This is what this call does.
blob.service.change_content_type(blob.key, blob.content_type)
rescue
# When we quickly create a new attachment, and then change its content type,
# the Object Storage may not be synchronized yet. It this cas, it will return a
# "409 Conflict" error.
#
# Wait for a while, then try again twice (before giving up).
sleep(retry_delay)
retry if (retries += 1) < 3
raise
end
end

View file

@ -1,187 +0,0 @@
require Rails.root.join("lib", "tasks", "task_helper")
class PieceJustificativeToChampPieceJointeMigrationService
def initialize(**params)
params.each do |key, value|
instance_variable_set("@#{key}", value)
end
end
def ensure_correct_storage_configuration!
storage_service.ensure_openstack_copy_possible!(PieceJustificativeUploader)
end
def procedures_with_pjs_in_range(ids_range)
procedures_with_pj = Procedure.unscope(where: :hidden_at).joins(:types_de_piece_justificative).distinct
procedures_with_pj.where(id: ids_range)
end
def number_of_champs_to_migrate(procedure)
(procedure.types_de_piece_justificative.count + 1) * procedure.dossiers.unscope(where: :hidden_at).count
end
def convert_procedure_pjs_to_champ_pjs(procedure, &progress)
types_de_champ_pj = PiecesJustificativesService.types_pj_as_types_de_champ(procedure)
populate_champs_pjs!(procedure, types_de_champ_pj, &progress)
# Only destroy the old types PJ once everything has been safely migrated to
# champs PJs.
# First destroy the individual PJ champs on all dossiers.
# It will cascade and destroy the PJs, and delete the linked objects from remote storage.
procedure.dossiers.unscope(where: :hidden_at).includes(:champs).find_each do |dossier|
destroy_pieces_justificatives(dossier)
end
# Now we can destroy the type de champ themselves,
# without cascading the timestamp update on all attached dossiers.
procedure.types_de_piece_justificative.destroy_all
end
def storage_service
@storage_service ||= CarrierwaveActiveStorageMigrationService.new
end
def populate_champs_pjs!(procedure, types_de_champ_pj, &progress)
procedure.types_de_champ += types_de_champ_pj
# Unscope to make sure all dossiers are migrated, even the soft-deleted ones
procedure.dossiers.unscope(where: :hidden_at).includes(:champs).find_each do |dossier|
migrate_dossier!(dossier, types_de_champ_pj, &progress)
end
rescue StandardError, SignalException
# If anything goes wrong, we roll back the migration by destroying the newly created
# types de champ, champs blobs and attachments.
rake_puts "Error received. Rolling back migration of procedure #{procedure.id}"
rollback_migration!(types_de_champ_pj)
rake_puts "Migration of procedure #{procedure.id} rolled back."
# Reraise the exception to abort the migration.
raise
end
def migrate_dossier!(dossier, types_de_champ_pj)
# Add the new pieces justificatives champs to the dossier
champs_pj = types_de_champ_pj.map(&:build_champ)
preserving_updated_at(dossier) do
dossier.champs += champs_pj
end
# Copy the dossier old pieces jointes to the new champs
# (even if the champs already existed, so that we ensure a clean state)
champs_pj.each do |champ|
type_pj_id = champ.type_de_champ.old_pj&.fetch('stable_id', nil)
pj = dossier.retrieve_last_piece_justificative_by_type(type_pj_id)
if pj.present?
preserving_updated_at(dossier) do
convert_pj_to_champ!(pj, champ)
end
champ.update_columns(
updated_at: pj.updated_at,
created_at: pj.created_at
)
else
champ.update_columns(
created_at: dossier.created_at,
# Set an updated_at date that won't cause notifications to appear
# on gestionnaires' dashboard.
updated_at: dossier.created_at
)
end
yield if block_given?
end
end
def convert_pj_to_champ!(pj, champ)
actual_file_exists = pj.content.file.send(:file)
if actual_file_exists
blob = make_blob(pj)
# Upload the file before creating the attachment to make sure MIME type
# identification doesnt fail.
storage_service.copy_from_carrierwave_to_active_storage!(pj.content.path, blob)
attachment = storage_service.make_attachment(champ, 'piece_justificative_file', blob)
else
make_empty_blob(pj)
rake_puts "Notice: attached file for champ #{champ.id} not found. An empty blob has been attached instead."
end
# By reloading, we force ActiveStorage to look at the attachment again, and see
# that one exists now. We do this so that, if we need to roll back and destroy the champ,
# the blob, the attachment and the actual file on OpenStack also get deleted.
champ.reload
rescue StandardError, SignalException
# Destroy partially attached object that the more general rescue in `populate_champs_pjs!`
# might not be able to handle.
if blob&.key.present?
begin
storage_service.delete_from_active_storage!(blob)
rescue => e
# The cleanup attempt failed, perhaps because the object had not been
# successfully copied to the Active Storage bucket yet.
# Continue trying to clean up the rest anyway.
pp e
end
end
blob&.destroy
attachment&.destroy
champ.reload
# Reraise the exception to abort the migration.
raise
end
def rollback_migration!(types_de_champ_pj)
types_de_champ_pj.each do |type_champ|
# First destroy all the individual champs on dossiers
type_champ.champ.each do |champ|
begin
destroy_champ_pj(Dossier.unscope(where: :hidden_at).find(champ.dossier_id), champ)
rescue => e
rake_puts e
rake_puts "Rolling back of champ #{champ.id} failed. Continuing to roll back…"
end
end
# Now we can destroy the type de champ itself,
# without cascading the timestamp update on all attached dossiers.
type_champ.reload.destroy
end
end
def make_blob(pj)
storage_service.make_blob(pj.content, pj.updated_at.iso8601, filename: pj.original_filename)
end
def make_empty_blob(pj)
storage_service.make_empty_blob(pj.content, pj.updated_at.iso8601, filename: pj.original_filename)
end
def preserving_updated_at(model)
original_modification_date = model.updated_at
begin
yield
ensure
model.update_column(:updated_at, original_modification_date)
end
end
def destroy_pieces_justificatives(dossier)
preserving_updated_at(dossier) do
dossier.pieces_justificatives.destroy_all
end
end
def destroy_champ_pj(dossier, champ)
preserving_updated_at(dossier) do
champ.piece_justificative_file.purge
champ.destroy
end
end
end

View file

@ -1,75 +1,4 @@
class PiecesJustificativesService
def self.upload!(dossier, user, params)
tpj_contents = dossier.types_de_piece_justificative
.map { |tpj| [tpj, params["piece_justificative_#{tpj.id}"]] }
.select { |_, content| content.present? }
without_virus, with_virus = tpj_contents
.partition { |_, content| ClamavService.safe_file?(content.path) }
errors = with_virus
.map { |_, content| "#{content.original_filename} : virus détecté" }
errors + without_virus
.map { |tpj, content| save_pj(content, dossier, tpj, user) }
.compact()
end
def self.save_pj(content, dossier, tpj, user)
pj = PieceJustificative.new(content: content,
dossier: dossier,
type_de_piece_justificative: tpj,
user: user)
pj.save ? nil : "le fichier #{content.original_filename} (#{pj.libelle.truncate(200)}) n'a pas pu être sauvegardé"
end
def self.missing_pj_error_messages(dossier)
mandatory_pjs = dossier.types_de_piece_justificative.select(&:mandatory)
present_pjs = dossier.pieces_justificatives.map(&:type_de_piece_justificative)
missing_pjs = mandatory_pjs - present_pjs
missing_pjs.map { |pj| "La pièce jointe #{pj.libelle.truncate(200)} doit être fournie." }
end
def self.types_pj_as_types_de_champ(procedure)
max_order_place = procedure.types_de_champ.pluck(:order_place).compact.max || -1
order_place = max_order_place + 1
types_de_champ = [
TypeDeChamp.new(
libelle: "Pièces jointes",
type_champ: TypeDeChamp.type_champs.fetch(:header_section),
order_place: order_place
)
]
types_de_champ += procedure.types_de_piece_justificative.map do |tpj|
order_place += 1
description = tpj.description
if tpj.lien_demarche.present?
if description.present?
description += "\n"
end
description += "Récupérer le formulaire vierge pour mon dossier : #{tpj.lien_demarche}"
end
TypeDeChamp.new(
libelle: tpj.libelle,
type_champ: TypeDeChamp.type_champs.fetch(:piece_justificative),
description: description,
order_place: order_place,
mandatory: tpj.mandatory,
old_pj: {
stable_id: tpj.id
}
)
end
if types_de_champ.count > 1
types_de_champ
else
[]
end
end
def self.liste_pieces_justificatives(dossier)
dossier.champs
.select { |c| c.type_champ == TypeDeChamp.type_champs.fetch(:piece_justificative) }

View file

@ -1,51 +0,0 @@
class PieceJustificativeUploader < BaseUploader
before :cache, :set_original_filename
# Choose what kind of storage to use for this uploader:
if Flipflop.remote_storage?
storage :fog
else
storage :file
end
# Override the directory where uploaded files will be stored.
# This is a sensible default for uploaders that are meant to be mounted:
def store_dir
if !Flipflop.remote_storage?
"./uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
end
end
# Add a white list of extensions which are allowed to be uploaded.
# For images you might use something like this:
def extension_whitelist
['pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'odt', 'ods', 'odp', 'jpg', 'jpeg', 'png']
end
def filename
if original_filename.present? || model.content_secure_token
if Flipflop.remote_storage?
filename = "#{model.class.to_s.underscore}-#{secure_token}.#{file.extension&.downcase}"
else
filename = "#{model.class.to_s.underscore}.#{file.extension&.downcase}"
end
end
filename
end
private
def secure_token
model.content_secure_token ||= generate_secure_token
end
def generate_secure_token
SecureRandom.uuid
end
def set_original_filename(file)
if file.respond_to?(:original_filename)
model.original_filename ||= file.original_filename
end
end
end

View file

@ -1,36 +0,0 @@
.pieces-justificatives-fields
= f.fields_for :types_de_piece_justificative, types_de_piece_justificative, remote: true do |ff|
.form-inline
.form-group
%h4 Libellé
= ff.text_field :libelle, class: 'form-control libelle', placeholder: 'Libellé'
.form-group
%h4 Description
= ff.text_area :description, class: 'form-control description', placeholder: 'Description'
.form-group
%h4
Lien du formulaire vierge
%small
(optionel)
= ff.url_field :lien_demarche, class: 'form-control', placeholder: 'Lien du document vierge'
.form-group
= ff.hidden_field :order_place, value: ff.index
= ff.hidden_field :id
- if ff.object.id.present?
.form-group
%br &nbsp;
= button_up(@procedure, "piece_justificative", ff.index, move_up_admin_procedure_pieces_justificatives_path(@procedure, ff.index))
= button_down(@procedure, "piece_justificative", ff.index, move_down_admin_procedure_pieces_justificatives_path(@procedure, ff.index))
.form-group
%h4 Obligatoire ?
.center
= ff.check_box :mandatory
.form-group
%br &nbsp;
- if ff.object.id.nil?
= f.submit('Ajouter la pièce', class: 'btn btn-success', id: 'add_piece_justificative')
- else
= link_to("", admin_procedure_piece_justificative_path(@procedure, ff.object.id), method: :delete, remote: true, id: "delete_type_de_piece_justificative_#{ff.object.id}", class: %w(form-control btn btn-danger fa fa-trash-o) )

View file

@ -1,7 +0,0 @@
= form_for [:admin, @procedure], url: admin_procedure_pieces_justificatives_path(@procedure), remote: true do |f|
#liste_piece_justificative
= render partial: 'fields', locals: { types_de_piece_justificative: @procedure.types_de_piece_justificative, f: f }
= f.submit "Enregistrer", class: 'btn btn-success', id: :save
%hr
#new_type_de_piece_justificative
= render partial: 'fields', locals: { types_de_piece_justificative: TypeDePieceJustificative.new, f: f }

View file

@ -1,25 +0,0 @@
.row.white-back
.alert.alert-info
.form-group
%p
Pour vos nouveaux besoins de pièces jointes, nous vous invitons à
= link_to(champs_procedure_path(@procedure)) do
rajouter des champs
\ <em>pièce justificative</em> à votre formulaire.
%p
Ils offrent les avantages suivants :
%ul
%li Pièces justificatives au fil du formulaire, pour un déroulé plus fluide
%li Possibilité de fournir à lusager un fichier type à remplir et renvoyer
%li Possibilité pour lusager de supprimer les documents joints par erreur
%li Support des pièces de grande taille (jusquà 200 Mo par pièce)
%li Pas de limite de soumission simultanée de plusieurs pièces, pour une expérience usager plus confortable
= link_to(champs_procedure_path(@procedure), class: 'btn btn-success') do
Ajouter un champ PJ
- if @procedure.has_old_pjs?
#piece_justificative_form
= render 'form'

View file

@ -1,2 +0,0 @@
<%= render_flash(timeout: 3000, sticky: true) %>
<%= render_to_element('#piece_justificative_form', partial: 'admin/pieces_justificatives/form', locals: { procedure: @procedure }) %>

View file

@ -1,4 +0,0 @@
.field-unit__label
= f.label field.attribute
.field-unit__field
= f.text_field field.attribute

View file

@ -1,12 +0,0 @@
- if field.data.any?
%table.collection-data{ "aria-labelledby": "page-title" }
%thead
%tr
%td.cell-label Libelle
%tbody
- field.data.order(:order_place).each do |f|
%tr
%td.cell-data
= f.libelle
- else
Aucun

View file

@ -17,20 +17,6 @@
- if champs.any?
= render partial: "shared/dossiers/champs", locals: { champs: champs, dossier: @dossier, demande_seen_at: nil, profile: 'instructeur' }
- if @dossier.types_de_piece_justificative.any?
%h3 Pièces jointes
%table
- @dossier.procedure.types_de_piece_justificative.each do |type_de_piece_justificative|
%tr
%th= "#{type_de_piece_justificative.libelle} :"
%td
- pj = @dossier.retrieve_last_piece_justificative_by_type(type_de_piece_justificative.id)
- if pj.present?
#{pj.original_filename}
- else
Pièce non fournie
%h2 Annotations privées
- champs_annotations_privees = @dossier.champs_private

View file

@ -1 +0,0 @@
= render partial: 'layouts/left_panels/left_panel_admin_procedurescontroller_navbar', locals: { active: 'Pieces' }

View file

@ -42,11 +42,6 @@
.procedure-list-element{ class: ('active' if active == 'Champs') }
Champs
- if !@procedure.locked?
%a#onglet-pieces{ href: url_for(admin_procedure_pieces_justificatives_path(@procedure)) }
.procedure-list-element{ class: ('active' if active == 'Pieces') }
Pièces jointes
- if !@procedure.locked?
%a#onglet-private-champs{ href: annotations_procedure_path(@procedure) }
.procedure-list-element{ class: ('active' if active == 'Annotations privées') }

View file

@ -1,9 +0,0 @@
- case notification.type_notif
- when "commentaire"
.type-notif.fa.fa-comments-o
- when "submitted"
.type-notif.fa.fa-thumbs-o-up
- when "champs"
.type-notif.fa.fa-list-alt
- when "piece_justificative"
.type-notif.fa.fa-chain

View file

@ -1,83 +0,0 @@
- content_for(:title, 'Tour de France 2018')
- content_for :footer do
= render partial: "root/footer"
.tour-de-france
.blue-panel
.small-container
%img.tour-de-france-logo{ src: image_url("tour-de-france/logo.svg") }
.white-panel
.small-container
%h2 Présentation
%p
Cet automne, léquipe de demarches-simplifiees.fr se lance dans un Tour de France afin de sensibiliser les agents publics aux bénéfices du numérique pour leur métier. Notre mission est de faciliter la mise en ligne de nouveaux services et daccompagner les administrations dÉtat et territoriales dans la dématérialisation.
%p
Que vous ayez un profil SI ou métier, ce sera pour vous loccasion de vous former à lutilisation de demarches-simplifiees.fr et de mettre en place des services en ligne pour les formulaires que vous traitez encore en papier.
%p
La journée type se déroulera en deux temps :
.cards
.card.card-half.pull-left
%h3 Matin
%h4 9 h 30 - 12 h 00
Interventions consacrées aux enjeux généraux de la dématérialisation, à la présentation de demarches-simplifiees.fr et à des retours dexpériences dagents utilisant déjà cette plateforme sur le territoire.
.card.card-half.pull-right
%h3 Après-midi
%h4 13 h 30 - 16 h 30
Ateliers de formation avec une réflexion étape par étape sur les problématiques liées au passage à la dématérialisation. Vous pourrez identifier des cas dusage, apprendre à créer des formulaires en ligne et organiser linstruction sur demarches-simplifiees.fr.
%p
Ces événements sont une opportunité pour tous les agents publics daméliorer leur environnement de travail et le service rendu aux usagers, nhésitez pas à relayer linformation auprès des services déconcentrés et des collectivités.
.photos-panel
.small-container
%img.atelier-photo.pull-left{ src: image_url("tour-de-france/atelier1.jpg") }
%img.atelier-photo.pull-right{ src: image_url("tour-de-france/atelier2.jpg") }
.clearfix
.grey-panel
.small-container
%h2.below-photos Inscription
%p
Vous souhaitez participer à lévénement dans votre région ? Inscrivez-vous à lévénement en remplissant le formulaire via le lien correspondant à votre région ci-dessous.
%p
Les places aux ateliers sont restreintes, ainsi si vous faite la demande de participation dans le formulaire dinscription vous recevrez une confirmation ultérieure par email.
%ul
%li
= link_to("Étape Normandie, le 26 septembre à Rouen", "https://www.demarches-simplifiees.fr/commencer/tour-de-france-etape-normandie", target: "_blank", rel: "noopener")
%li
= link_to("Étape Occitanie, le 15 octobre à Toulouse", "https://www.demarches-simplifiees.fr/commencer/tour-de-france-demarches-simplifiees-fr-occitanie", target: "_blank", rel: "noopener")
%li
= link_to("Étape Nouvelle-Aquitaine, le 18 octobre à Bordeaux", "https://www.demarches-simplifiees.fr/commencer/tour-de-france-demarches-simplifiees-fr-aquitaine", target: "_blank", rel: "noopener")
%li
= link_to("Étape Martinique, le 24 octobre à Fort-de-France", "https://www.demarches-simplifiees.fr/commencer/tour-de-france-demarches-simplifiees-fr-martinique", target: "_blank", rel: "noopener")
%li
= link_to("Étape PACA, le 6 novembre à Marseille", "https://www.demarches-simplifiees.fr/commencer/tour-de-france-demarches-simplifiees-fr-paca", target: "_blank", rel: "noopener")
%li
= link_to("Étape Corse, le 8 novembre à Ajaccio", "https://www.demarches-simplifiees.fr/commencer/tour-de-france-demarches-simplifiees-fr-corse", target: "_blank", rel: "noopener")
%li
= link_to("Étape Ile-de-France, le 13 novembre à Paris", "https://www.demarches-simplifiees.fr/commencer/tour-de-france-demarches-simplifiees-iledefrance", target: "_blank", rel: "noopener")
%li
= link_to("Étape Bourgogne Franche-Comté, le 14 novembre à Dijon", "https://www.demarches-simplifiees.fr/commencer/tour-de-france-demarches-simplifiees-bourgogne-fc", target: "_blank", rel: "noopener")
%li
= link_to("Étape Pays de la Loire, le 20 novembre à Nantes", "https://www.demarches-simplifiees.fr/commencer/tour-de-france-demarches-simplifiees-fr-paysloire", target: "_blank", rel: "noopener")
%li
= link_to("Étape Bretagne, le 22 novembre à Rennes", "https://www.demarches-simplifiees.fr/commencer/tour-de-france-demarches-simplifiees-fr-bretagne", target: "_blank", rel: "noopener")
%li
= link_to("Étape Hauts-de-France, le 27 novembre à Lille", "https://www.demarches-simplifiees.fr/commencer/tour-de-france-demarches-simplifiees-hautsdefrance", target: "_blank", rel: "noopener")
%li
= link_to("Étape Grand-Est, le 29 novembre à Metz", "https://www.demarches-simplifiees.fr/commencer/tour-de-france-demarches-simplifiees-fr-grand-est", target: "_blank", rel: "noopener")
%li
= link_to("Étape Centre Val-de-Loire, le 5 décembre à Orleans", "https://www.demarches-simplifiees.fr/commencer/tour-de-france-demarches-simplifiees-centre-vdl", target: "_blank", rel: "noopener")
%li
= link_to("Étape Auvergne-Rhone-Alpes, le 7 décembre à Lyon", "https://www.demarches-simplifiees.fr/commencer/tour-de-france-demarches-simplifiees-auvergne-rhon", target: "_blank", rel: "noopener")

View file

@ -26,8 +26,3 @@
- if champs.any?
.card
= render partial: "shared/dossiers/champs", locals: { champs: champs, demande_seen_at: demande_seen_at, profile: profile }
- if dossier.types_de_piece_justificative.any?
.tab-title Pièces jointes
.card
= render partial: "shared/dossiers/pieces_jointes", locals: { dossier: dossier, demande_seen_at: demande_seen_at }

View file

@ -31,45 +31,6 @@
= render partial: "shared/dossiers/editable_champs/editable_champ",
locals: { champ: champ, form: champ_form }
- tpjs = dossier.types_de_piece_justificative.order('order_place ASC')
- if tpjs.present?
.card.featured
.card-title
Pièces jointes
.warning
- if tpjs.many?
Pour éviter toute erreur, nous vous conseillons de limiter la taille de chaque pièce jointe à 20 Mo, et de les ajouter une par une, en enregistrant votre
= dossier.brouillon? ? "brouillon" : "dossier"
après chaque ajout.
- else
Pour éviter toute erreur, nous vous conseillons de limiter la taille de votre pièce jointe à 20 Mo.
- tpjs.each do |tpj|
.pj-input
%label{ for: "piece_justificative_#{tpj.id}" }
= tpj.libelle
- if tpj.mandatory?
%span.mandatory *
%p.piece-description= tpj.description
- if tpj.lien_demarche.present?
%p.piece-description
Récupérer le formulaire vierge pour mon dossier :
= link_to "Télécharger", tpj.lien_demarche, target: :blank, rel: :noopener
- if dossier.was_piece_justificative_uploaded_for_type_id?(tpj.id)
- pj = dossier.retrieve_last_piece_justificative_by_type(tpj.id)
%p
Pièce jointe déjà importée :
= link_to pj.original_filename, pj.content_url, target: :blank, rel: :noopener
= file_field_tag "piece_justificative_#{tpj.id}",
accept: PieceJustificative.accept_format,
max_file_size: 6.megabytes,
required: (tpj.mandatory? && !dossier.was_piece_justificative_uploaded_for_type_id?(tpj.id))
- if !apercu
.send-wrapper
- if dossier.brouillon?

View file

@ -1,33 +0,0 @@
%table.table.vertical.pj.dossier-champs
%tbody
- dossier.procedure.types_de_piece_justificative.each do |type_de_piece_justificative|
- pjs = dossier.retrieve_all_piece_justificative_by_type(type_de_piece_justificative.id).to_ary.dup
- pj = pjs.shift if pjs.present?
%tr
%th= "#{type_de_piece_justificative.libelle} :"
- if pj
%td
%span{ class: highlight_if_unseen_class(demande_seen_at, pj.updated_at) }
= display_pj_filename(pj)
·
= link_to "Télécharger", pj.content_url, class: "link", target: :blank, rel: :noopener
- if pjs.present?
%br
%span.dropdown
%button.button.dropdown-button
anciennes versions
.dropdown-content.fade-in-down
%ul.dropdown-items
- pjs.each do |pj|
%li
= link_to pj.content_url, { target: :blank, rel: :noopener } do
%span.filename= display_pj_filename(pj)
%span
ajoutée le #{try_format_datetime(pj.created_at)}
- else
%td Pièce non fournie
%td.updated-at
- if pj
%span{ class: highlight_if_unseen_class(demande_seen_at, pj.updated_at) }
modifié le
= try_format_datetime(pj.updated_at)

View file

@ -161,27 +161,27 @@ Devise.setup do |config|
# Defines which strategy will be used to lock an account.
# :failed_attempts = Locks an account after a number of failed attempts to sign in.
# :none = No lock strategy. You should handle locking by yourself.
# config.lock_strategy = :failed_attempts
config.lock_strategy = :failed_attempts
# Defines which key will be used when locking and unlocking an account
# config.unlock_keys = [ :email ]
config.unlock_keys = [:email]
# Defines which strategy will be used to unlock an account.
# :email = Sends an unlock link to the user email
# :time = Re-enables login after a certain amount of time (see :unlock_in below)
# :both = Enables both strategies
# :none = No unlock strategy. You should handle unlocking by yourself.
# config.unlock_strategy = :both
config.unlock_strategy = :both
# Number of authentication tries before locking an account if lock_strategy
# is failed attempts.
# config.maximum_attempts = 20
config.maximum_attempts = 6
# Time interval to unlock the account if :time is enabled as unlock_strategy.
# config.unlock_in = 1.hour
config.unlock_in = 1.hour
# Warn on the last attempt before the account is locked.
# config.last_attempt_warning = true
config.last_attempt_warning = true
# ==> Configuration for :recoverable
#

View file

@ -10,8 +10,6 @@ ActiveSupport::Inflector.inflections(:en) do |inflect|
# inflect.uncountable %w( fish sheep )
inflect.acronym 'API'
inflect.acronym 'RNA'
inflect.irregular 'piece_justificative', 'pieces_justificatives'
inflect.irregular 'type_de_piece_justificative', 'types_de_piece_justificative'
inflect.irregular 'type_de_champ', 'types_de_champ'
inflect.irregular 'type_de_champ_private', 'types_de_champ_private'
inflect.irregular 'assign_to', 'assign_tos'

View file

@ -138,10 +138,6 @@ fr:
attributes:
footer:
too_long: ": le pied de page est trop long."
piece_justificative:
attributes:
content:
size_too_big: "La taille du fichier joint est trop importante. Elle doit être inférieure à 20Mo."
user:
attributes:
reset_password_token:

View file

@ -138,7 +138,6 @@ Rails.application.routes.draw do
get 'attachments/:id', to: 'attachments#show', as: :attachment
delete 'attachments/:id', to: 'attachments#destroy'
get 'tour-de-france' => 'root#tour_de_france'
get "patron" => "root#patron"
get "accessibilite" => "root#accessibilite"
get "suivi" => "root#suivi"
@ -194,13 +193,6 @@ Rails.application.routes.draw do
delete :delete_notice
end
resource :pieces_justificatives, only: [:show, :update]
resources :pieces_justificatives, only: :destroy
resource :pieces_justificatives, only: [:show, :update] do
post '/:index/move_up' => 'pieces_justificatives#move_up', as: :move_up
post '/:index/move_down' => 'pieces_justificatives#move_down', as: :move_down
end
resources :mail_templates, only: [:index, :edit, :update]
put 'archive' => 'procedures#archive', as: :archive

View file

@ -0,0 +1,23 @@
class AddLockableToDevise < ActiveRecord::Migration[5.2]
def change
add_column :users, :failed_attempts, :integer, default: 0, null: false
add_column :users, :unlock_token, :string
add_column :users, :locked_at, :datetime
add_index :users, :unlock_token, unique: true
add_column :gestionnaires, :failed_attempts, :integer, default: 0, null: false
add_column :gestionnaires, :unlock_token, :string
add_column :gestionnaires, :locked_at, :datetime
add_index :gestionnaires, :unlock_token, unique: true
add_column :administrateurs, :failed_attempts, :integer, default: 0, null: false
add_column :administrateurs, :unlock_token, :string
add_column :administrateurs, :locked_at, :datetime
add_index :administrateurs, :unlock_token, unique: true
add_column :administrations, :failed_attempts, :integer, default: 0, null: false
add_column :administrations, :unlock_token, :string
add_column :administrations, :locked_at, :datetime
add_index :administrations, :unlock_token, unique: true
end
end

View file

@ -63,8 +63,12 @@ ActiveRecord::Schema.define(version: 2019_07_17_151228) do
t.boolean "active", default: false
t.jsonb "features", default: {}, null: false
t.string "encrypted_token"
t.integer "failed_attempts", default: 0, null: false
t.string "unlock_token"
t.datetime "locked_at"
t.index ["email"], name: "index_administrateurs_on_email", unique: true
t.index ["reset_password_token"], name: "index_administrateurs_on_reset_password_token", unique: true
t.index ["unlock_token"], name: "index_administrateurs_on_unlock_token", unique: true
end
create_table "administrateurs_gestionnaires", id: false, force: :cascade do |t|
@ -100,8 +104,12 @@ ActiveRecord::Schema.define(version: 2019_07_17_151228) do
t.string "last_sign_in_ip"
t.datetime "created_at"
t.datetime "updated_at"
t.integer "failed_attempts", default: 0, null: false
t.string "unlock_token"
t.datetime "locked_at"
t.index ["email"], name: "index_administrations_on_email", unique: true
t.index ["reset_password_token"], name: "index_administrations_on_reset_password_token", unique: true
t.index ["unlock_token"], name: "index_administrations_on_unlock_token", unique: true
end
create_table "assign_tos", id: :serial, force: :cascade do |t|
@ -399,8 +407,12 @@ ActiveRecord::Schema.define(version: 2019_07_17_151228) do
t.text "encrypted_login_token"
t.datetime "login_token_created_at"
t.jsonb "features", default: {"enable_email_login_token"=>true}, null: false
t.integer "failed_attempts", default: 0, null: false
t.string "unlock_token"
t.datetime "locked_at"
t.index ["email"], name: "index_gestionnaires_on_email", unique: true
t.index ["reset_password_token"], name: "index_gestionnaires_on_reset_password_token", unique: true
t.index ["unlock_token"], name: "index_gestionnaires_on_unlock_token", unique: true
end
create_table "individuals", id: :serial, force: :cascade do |t|
@ -605,10 +617,14 @@ ActiveRecord::Schema.define(version: 2019_07_17_151228) do
t.string "confirmation_token"
t.datetime "confirmed_at"
t.datetime "confirmation_sent_at"
t.integer "failed_attempts", default: 0, null: false
t.string "unlock_token"
t.datetime "locked_at"
t.text "unconfirmed_email"
t.index ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true
t.index ["email"], name: "index_users_on_email", unique: true
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
t.index ["unlock_token"], name: "index_users_on_unlock_token", unique: true
end
create_table "virus_scans", force: :cascade do |t|

View file

@ -1,291 +0,0 @@
module Tasks
class DossierProcedureMigrator
# Migrates dossiers from an old source procedure to a revised destination procedure.
class ChampMapping
def initialize(source_procedure, destination_procedure, is_private)
@source_procedure = source_procedure
@destination_procedure = destination_procedure
@is_private = is_private
@expected_source_types_de_champ = {}
@expected_destination_types_de_champ = {}
@source_to_destination_mapping = {}
@source_champs_to_discard = Set[]
@destination_champ_computations = []
setup_mapping
end
def check_source_destination_consistency
check_champs_consistency("#{privacy_label}source", @expected_source_types_de_champ, types_de_champ(@source_procedure))
check_champs_consistency("#{privacy_label}destination", @expected_destination_types_de_champ, types_de_champ(@destination_procedure))
end
def can_migrate?(dossier)
true
end
def migrate(dossier)
# Since were going to iterate and change the champs at the same time,
# we use to_a to make the list static and avoid nasty surprises
original_champs = champs(dossier).to_a
compute_new_champs(dossier)
original_champs.each do |c|
tdc_to = destination_type_de_champ(c)
if tdc_to.present?
c.update_columns(type_de_champ_id: tdc_to.id)
elsif discard_champ?(c)
champs(dossier).destroy(c)
else
fail "Unhandled source #{privacy_label}type de champ #{c.type_de_champ.order_place}"
end
end
end
private
def compute_new_champs(dossier)
@destination_champ_computations.each do |tdc, block|
champs(dossier) << block.call(dossier, tdc)
end
end
def destination_type_de_champ(champ)
@source_to_destination_mapping[champ.type_de_champ.order_place]
end
def discard_champ?(champ)
@source_champs_to_discard.member?(champ.type_de_champ.order_place)
end
def setup_mapping
end
def champs(dossier)
@is_private ? dossier.champs_private : dossier.champs
end
def types_de_champ(procedure)
@is_private ? procedure.types_de_champ_private : procedure.types_de_champ
end
def privacy_label
@is_private ? 'private ' : ''
end
def check_champs_consistency(label, expected_tdcs, actual_tdcs)
if actual_tdcs.size != expected_tdcs.size
raise "Incorrect #{label} size #{actual_tdcs.size} (expected #{expected_tdcs.size})"
end
actual_tdcs.each { |tdc| check_champ_consistency(label, expected_tdcs[tdc.order_place], tdc) }
end
def check_champ_consistency(label, expected_tdc, actual_tdc)
errors = []
if actual_tdc.libelle != expected_tdc['libelle']
errors.append("incorrect libelle #{actual_tdc.libelle} (expected #{expected_tdc['libelle']})")
end
if actual_tdc.type_champ != expected_tdc['type_champ']
errors.append("incorrect type champ #{actual_tdc.type_champ} (expected #{expected_tdc['type_champ']})")
end
if (!actual_tdc.mandatory) && expected_tdc['mandatory']
errors.append("champ should be mandatory")
end
drop_down = actual_tdc.drop_down_list.presence&.options&.presence
if drop_down != expected_tdc['drop_down']
errors.append("incorrect drop down list #{drop_down} (expected #{expected_tdc['drop_down']})")
end
if errors.present?
fail "On #{label} type de champ #{actual_tdc.order_place} (#{actual_tdc.libelle}) " + errors.join(', ')
end
end
def map_source_to_destination_champ(source_order_place, destination_order_place, source_overrides: {}, destination_overrides: {})
destination_type_de_champ = types_de_champ(@destination_procedure).find_by(order_place: destination_order_place)
@expected_source_types_de_champ[source_order_place] =
type_de_champ_to_expectation(destination_type_de_champ)
.merge!(source_overrides)
@expected_destination_types_de_champ[destination_order_place] =
type_de_champ_to_expectation(types_de_champ(@source_procedure).find_by(order_place: source_order_place))
.merge!({ "mandatory" => false }) # Even if the source was mandatory, its ok for the destination to be optional
.merge!(destination_overrides)
@source_to_destination_mapping[source_order_place] = destination_type_de_champ
end
def discard_source_champ(source_type_de_champ)
@expected_source_types_de_champ[source_type_de_champ.order_place] = type_de_champ_to_expectation(source_type_de_champ)
@source_champs_to_discard << source_type_de_champ.order_place
end
def compute_destination_champ(destination_type_de_champ, &block)
@expected_destination_types_de_champ[destination_type_de_champ.order_place] = type_de_champ_to_expectation(destination_type_de_champ)
@destination_champ_computations << [types_de_champ(@destination_procedure).find_by(order_place: destination_type_de_champ.order_place), block]
end
def type_de_champ_to_expectation(tdc)
if tdc.present?
expectation = tdc.as_json(only: [:libelle, :type_champ, :mandatory])
expectation['drop_down'] = tdc.drop_down_list.presence&.options&.presence
expectation
else
{}
end
end
end
class PieceJustificativeMapping
def initialize(source_procedure, destination_procedure)
@source_procedure = source_procedure
@destination_procedure = destination_procedure
@expected_source_pj = {}
@expected_destination_pj = {}
@source_to_destination_mapping = {}
setup_mapping
end
def check_source_destination_consistency
check_pjs_consistency('source', @expected_source_pj, @source_procedure.types_de_piece_justificative)
check_pjs_consistency('destination', @expected_destination_pj, @destination_procedure.types_de_piece_justificative)
end
def can_migrate?(dossier)
true
end
def migrate(dossier)
# Since were going to iterate and change the pjs at the same time,
# we use to_a to make the list static and avoid nasty surprises
original_pjs = dossier.pieces_justificatives.to_a
original_pjs.each do |pj|
pj_to = destination_pj(pj)
if pj_to.present?
pj.update_columns(type_de_piece_justificative_id: pj_to.id)
elsif discard_pj?(pj)
dossier.pieces_justificatives.destroy(pj)
else
fail "Unhandled source pièce justificative #{c.type_de_piece_justificative.order_place}"
end
end
end
private
def destination_pj(pj)
@source_to_destination_mapping[pj.order_place]
end
def discard_pj?(champ)
@source_pjs_to_discard.member?(pj.order_place)
end
def setup_mapping
end
def map_source_to_destination_pj(source_order_place, destination_order_place, source_overrides: {}, destination_overrides: {})
destination_pj = @destination_procedure.types_de_piece_justificative.find_by(order_place: destination_order_place)
@expected_source_pj[source_order_place] =
pj_to_expectation(destination_pj)
.merge!(source_overrides)
@expected_destination_pj[destination_order_place] =
pj_to_expectation(@source_procedure.types_de_piece_justificative.find_by(order_place: source_order_place))
.merge!({ "mandatory" => false }) # Even if the source was mandatory, its ok for the destination to be optional
.merge!(destination_overrides)
@source_to_destination_mapping[source_order_place] = destination_pj
end
def discard_source_pj(source_pj)
@expected_source_pj[source_pj.order_place] = pj_to_expectation(source_pj)
@source_pjs_to_discard << source_pj.order_place
end
def leave_destination_pj_blank(destination_pj)
@expected_destination_pj[destination_pj.order_place] = pj_to_expectation(destination_pj)
end
def pj_to_expectation(pj)
pj&.as_json(only: [:libelle, :mandatory]) || {}
end
def check_pjs_consistency(label, expected_pjs, actual_pjs)
if actual_pjs.size != expected_pjs.size
raise "Incorrect #{label} pièce justificative count #{actual_pjs.size} (expected #{expected_pjs.size})"
end
actual_pjs.each { |pj| check_pj_consistency(label, expected_pjs[pj.order_place], pj) }
end
def check_pj_consistency(label, expected_pj, actual_pj)
errors = []
if actual_pj.libelle != expected_pj['libelle']
errors.append("incorrect libelle #{actual_pj.libelle} (expected #{expected_pj['libelle']})")
end
if (!actual_pj.mandatory) && expected_pj['mandatory']
errors.append("pj should be mandatory")
end
if errors.present?
fail "On #{label} type de pièce justificative #{actual_pj.order_place} (#{actual_pj.libelle}) " + errors.join(', ')
end
end
end
def initialize(source_procedure, destination_procedure, champ_mapping, private_champ_mapping = ChampMapping, piece_justificative_mapping = PieceJustificativeMapping, &block)
@source_procedure = source_procedure
@destination_procedure = destination_procedure
@champ_mapping = champ_mapping.new(source_procedure, destination_procedure, false)
@private_champ_mapping = private_champ_mapping.new(source_procedure, destination_procedure, true)
@piece_justificative_mapping = piece_justificative_mapping.new(source_procedure, destination_procedure)
if block_given?
@on_dossier_migration = block
end
end
def migrate_procedure
check_consistency
migrate_dossiers
migrate_gestionnaires
publish_destination_procedure_in_place_of_source
end
def check_consistency
check_same_administrateur
@champ_mapping.check_source_destination_consistency
@private_champ_mapping.check_source_destination_consistency
@piece_justificative_mapping.check_source_destination_consistency
end
def check_same_administrateur
if @source_procedure.administrateur_ids.sort != @destination_procedure.administrateur_ids.sort
raise "Mismatching administrateurs #{@source_procedure.administrateurs.pluck(:email)}#{@destination_procedure.administrateurs.pluck(:email)}"
end
end
def migrate_dossiers
@source_procedure.dossiers.find_each(batch_size: 100) do |d|
if @champ_mapping.can_migrate?(d) && @private_champ_mapping.can_migrate?(d) && @piece_justificative_mapping.can_migrate?(d)
@champ_mapping.migrate(d)
@private_champ_mapping.migrate(d)
@piece_justificative_mapping.migrate(d)
# Use update_columns to avoid triggering build_default_champs
d.update_columns(procedure_id: @destination_procedure.id)
@on_dossier_migration&.call(d)
end
end
end
def migrate_gestionnaires
@source_procedure.gestionnaires.find_each(batch_size: 100) { |g| g.assign_to_procedure(@destination_procedure) }
end
def publish_destination_procedure_in_place_of_source
@destination_procedure.publish!(@source_procedure.path)
@source_procedure.archive
end
end
end

View file

@ -1,52 +0,0 @@
require Rails.root.join("lib", "tasks", "task_helper")
namespace :pieces_justificatives do
desc <<~EOD
Migrate the PJ to champs for a single PROCEDURE_ID.
EOD
task migrate_procedure_to_champs: :environment do
procedure_id = ENV['PROCEDURE_ID']
procedure = Procedure.find(procedure_id)
service = PieceJustificativeToChampPieceJointeMigrationService.new
service.ensure_correct_storage_configuration!
progress = ProgressReport.new(service.number_of_champs_to_migrate(procedure))
service.convert_procedure_pjs_to_champ_pjs(procedure) do
progress.inc
end
progress.finish
end
desc <<~EOD
Migrate the PJ to champs for several procedures ids, from RANGE_START to RANGE_END.
EOD
task migrate_procedures_range_to_champs: :environment do
if ENV['RANGE_START'].nil? || ENV['RANGE_END'].nil?
fail "RANGE_START and RANGE_END must be specified"
end
procedures_range = ENV['RANGE_START']..ENV['RANGE_END']
service = PieceJustificativeToChampPieceJointeMigrationService.new
service.ensure_correct_storage_configuration!
procedures_to_migrate = service.procedures_with_pjs_in_range(procedures_range)
total_number_of_champs_to_migrate = procedures_to_migrate
.map { |p| service.number_of_champs_to_migrate(p) }
.sum
progress = ProgressReport.new(total_number_of_champs_to_migrate)
procedures_to_migrate.find_each do |procedure|
rake_puts ''
rake_puts "Migrating procedure #{procedure.id}"
service.convert_procedure_pjs_to_champ_pjs(procedure) do
progress.inc
end
end
progress.finish
end
end

View file

@ -1,170 +0,0 @@
require 'spec_helper'
describe Admin::PiecesJustificativesController, type: :controller do
let(:admin) { create(:administrateur) }
let(:procedure) { create(:procedure, administrateur: admin) }
before do
sign_in admin
end
describe 'GET #show' do
let(:procedure_id) { procedure.id }
subject { get :show, params: { procedure_id: procedure_id } }
context 'when procedure is not found' do
let(:procedure_id) { 9_999_999 }
it { expect(subject.status).to eq(404) }
end
context 'when procedure is published' do
let(:procedure) { create(:procedure, :published, administrateur: admin) }
it { is_expected.to redirect_to admin_procedure_path id: procedure_id }
end
context 'when procedure does not belong to admin' do
let(:admin_2) { create(:administrateur) }
let(:procedure) { create(:procedure, administrateur: admin_2) }
it { expect(subject.status).to eq(404) }
end
end
describe 'PUT #update' do
let(:procedure_id) { procedure.id }
let(:libelle) { 'RIB' }
let(:description) { "relevé d'identité bancaire" }
let(:update_params) do
{
types_de_piece_justificative_attributes:
{
'0' =>
{
libelle: libelle,
description: description
}
}
}
end
let(:request) { put :update, params: { procedure_id: procedure_id, format: :js, procedure: update_params } }
subject { request }
it { is_expected.to render_template('show') }
it { expect { subject }.to change(TypeDePieceJustificative, :count).by(1) }
it 'adds type de pj to procedure' do
request
procedure.reload
pj = procedure.types_de_piece_justificative.first
expect(pj.libelle).to eq(libelle)
expect(pj.description).to eq(description)
end
context 'when procedure is not found' do
let(:procedure_id) { 9_999_999 }
it { expect(subject.status).to eq(404) }
end
context 'when libelle is blank' do
let(:libelle) { '' }
it { expect { subject }.not_to change(TypeDePieceJustificative, :count) }
end
end
describe 'DELETE #destroy' do
let!(:pj) { create(:type_de_piece_justificative, procedure: procedure) }
let(:procedure_id) { procedure.id }
let(:pj_id) { pj.id }
let(:request) { delete :destroy, params: { procedure_id: procedure_id, id: pj_id } }
subject { request }
context 'when procedure is not found' do
let(:procedure_id) { 9_999_999 }
it { expect(subject.status).to eq(404) }
end
context 'when pj id does not exist' do
let(:pj_id) { 9_999_999 }
it { expect(subject.status).to eq(404) }
end
context 'when pj id exist but is not linked to procedure' do
let(:procedure_1) { create(:procedure, administrateur: admin) }
let!(:pj_1) { create(:type_de_piece_justificative, procedure: procedure_1) }
let(:pj_id) { pj_1 }
it { expect(subject.status).to eq(404) }
end
context 'when pj is found' do
it { expect(subject.status).to eq(200) }
it { expect { subject }.to change(TypeDePieceJustificative, :count).by(-1) }
end
end
describe 'POST #move_up' do
subject { post :move_up, params: { procedure_id: procedure.id, index: index, format: :js } }
context 'when procedure have no type de champ' do
let(:index) { 0 }
it { expect(subject.status).to eq(400) }
end
context 'when procedure have only one type de champ' do
let(:index) { 1 }
let!(:type_de_piece_justificative) { create(:type_de_piece_justificative, procedure: procedure) }
it { expect(subject.status).to eq(400) }
end
context 'when procedure have tow type de champs' do
context 'when index == 0' do
let(:index) { 0 }
let!(:type_de_piece_justificative_1) { create(:type_de_piece_justificative, procedure: procedure) }
let!(:type_de_piece_justificative_2) { create(:type_de_piece_justificative, procedure: procedure) }
it { expect(subject.status).to eq(400) }
end
context 'when index > 0' do
let(:index) { 1 }
let!(:type_de_piece_justificative_0) { create(:type_de_piece_justificative, procedure: procedure, order_place: 0) }
let!(:type_de_piece_justificative_1) { create(:type_de_piece_justificative, procedure: procedure, order_place: 1) }
it { expect(subject.status).to eq(200) }
it { expect(subject).to render_template('show') }
it 'changes order places' do
post :move_up, params: { procedure_id: procedure.id, index: index, format: :js }
type_de_piece_justificative_0.reload
type_de_piece_justificative_1.reload
expect(type_de_piece_justificative_0.order_place).to eq(1)
expect(type_de_piece_justificative_1.order_place).to eq(0)
end
end
end
end
describe 'POST #move_down' do
let(:request) { post :move_down, params: { procedure_id: procedure.id, index: index, format: :js } }
let(:index) { 0 }
subject { request }
context 'when procedure have no type de champ' do
it { expect(subject.status).to eq(400) }
end
context 'when procedure have only one type de champ' do
let!(:type_de_piece_justificative_0) { create(:type_de_piece_justificative, procedure: procedure) }
it { expect(subject.status).to eq(400) }
end
context 'when procedure have 2 type de champ' do
let!(:type_de_piece_justificative_0) { create(:type_de_piece_justificative, procedure: procedure, order_place: 0) }
let!(:type_de_piece_justificative_1) { create(:type_de_piece_justificative, procedure: procedure, order_place: 1) }
context 'when index represent last type_de_piece_justificative' do
let(:index) { 1 }
it { expect(subject.status).to eq(400) }
end
context 'when index does not represent last type_de_piece_justificative' do
let(:index) { 0 }
it { expect(subject.status).to eq(200) }
it { expect(subject).to render_template('show') }
it 'changes order place' do
request
type_de_piece_justificative_0.reload
type_de_piece_justificative_1.reload
expect(type_de_piece_justificative_0.order_place).to eq(1)
expect(type_de_piece_justificative_1.order_place).to eq(0)
end
end
end
end
end

View file

@ -248,7 +248,7 @@ describe Admin::ProceduresController, type: :controller do
end
describe 'PUT #update' do
let!(:procedure) { create(:procedure, :with_type_de_champ, :with_two_type_de_piece_justificative, administrateur: admin) }
let!(:procedure) { create(:procedure, :with_type_de_champ, administrateur: admin) }
context 'when administrateur is not connected' do
before do
@ -302,7 +302,7 @@ describe Admin::ProceduresController, type: :controller do
end
context 'when procedure is brouillon' do
let(:procedure) { create(:procedure_with_dossiers, :with_path, :with_type_de_champ, :with_two_type_de_piece_justificative, administrateur: admin) }
let(:procedure) { create(:procedure_with_dossiers, :with_path, :with_type_de_champ, administrateur: admin) }
let!(:dossiers_count) { procedure.dossiers.count }
describe 'dossiers are dropped' do
@ -316,7 +316,7 @@ describe Admin::ProceduresController, type: :controller do
end
context 'when procedure is published' do
let(:procedure) { create(:procedure, :with_type_de_champ, :with_two_type_de_piece_justificative, :published, administrateur: admin) }
let(:procedure) { create(:procedure, :with_type_de_champ, :published, administrateur: admin) }
subject { update_procedure }
@ -774,7 +774,7 @@ describe Admin::ProceduresController, type: :controller do
end
describe 'PATCH #monavis' do
let!(:procedure) { create(:procedure, :with_type_de_champ, :with_two_type_de_piece_justificative, administrateur: admin) }
let!(:procedure) { create(:procedure, administrateur: admin) }
let(:procedure_params) {
{
monavis_embed: monavis_embed
@ -835,7 +835,7 @@ describe Admin::ProceduresController, type: :controller do
end
context 'when procedure is published' do
let(:procedure) { create(:procedure, :with_type_de_champ, :with_two_type_de_piece_justificative, :published, administrateur: admin) }
let(:procedure) { create(:procedure, :published, administrateur: admin) }
subject { update_monavis }

View file

@ -3,7 +3,7 @@ require 'spec_helper'
describe API::V1::DossiersController do
let(:admin) { create(:administrateur) }
let(:token) { admin.renew_api_token }
let(:procedure) { create(:procedure, :with_two_type_de_piece_justificative, :with_type_de_champ, :with_type_de_champ_private, administrateur: admin) }
let(:procedure) { create(:procedure, :with_type_de_champ, :with_type_de_champ_private, administrateur: admin) }
let(:wrong_procedure) { create(:procedure) }
it { expect(described_class).to be < APIController }
@ -204,49 +204,13 @@ describe API::V1::DossiersController do
it { expect(subject.keys).to match_array(field_list) }
end
describe 'types_de_piece_justificative' do
let(:field_list) { [:id, :libelle, :description] }
subject { super()[:types_de_piece_justificative] }
it { expect(subject.length).to eq 2 }
describe 'first type de piece justificative' do
subject { super().first }
it { expect(subject.key?(:id)).to be_truthy }
it { expect(subject[:libelle]).to eq('RIB') }
it { expect(subject[:description]).to eq('Releve identité bancaire') }
end
end
describe 'piece justificative', vcr: { cassette_name: 'controllers_api_v1_dossiers_controller_piece_justificative' } do
before do
create :piece_justificative, :rib, dossier: dossier, type_de_piece_justificative: dossier.procedure.types_de_piece_justificative.first, user: dossier.user
end
let(:field_list) { [:url, :created_at, :type_de_piece_justificative_id] }
subject { super()[:pieces_justificatives].first }
it { expect(subject.key?(:content_url)).to be_truthy }
it { expect(subject[:created_at]).not_to be_nil }
it { expect(subject[:type_de_piece_justificative_id]).not_to be_nil }
it { expect(subject.key?(:user)).to be_truthy }
describe 'user' do
subject { super()[:user] }
it { expect(subject[:email]).not_to be_nil }
end
end
describe 'champs' do
let(:field_list) { [:url] }
subject { super()[:champs] }
it { expect(subject.length).to eq 1 }
describe 'first champs' do
describe 'first champ' do
subject { super().first }
it { expect(subject.key?(:value)).to be_truthy }

View file

@ -25,7 +25,7 @@ describe API::V1::ProceduresController, type: :controller do
it { is_expected.to have_http_status(200) }
describe 'body' do
let(:procedure) { create(:procedure, :with_type_de_champ, :with_two_type_de_piece_justificative, administrateur: admin) }
let(:procedure) { create(:procedure, :with_type_de_champ, administrateur: admin) }
let(:response) { get :show, params: { id: procedure.id, token: token } }
subject { JSON.parse(response.body, symbolize_names: true)[:procedure] }
@ -51,19 +51,6 @@ describe API::V1::ProceduresController, type: :controller do
it { expect(subject[:order_place]).to eq(champ.order_place) }
it { expect(subject[:description]).to eq(champ.description) }
end
it { is_expected.to have_key(:types_de_piece_justificative) }
it { expect(subject[:types_de_piece_justificative]).to be_an(Array) }
describe 'type_de_piece_jointe' do
subject { super()[:types_de_piece_justificative][0] }
let(:pj) { procedure.types_de_piece_justificative.first }
it { expect(subject[:id]).to eq(pj.id) }
it { expect(subject[:libelle]).to eq(pj.libelle) }
it { expect(subject[:description]).to eq(pj.description) }
end
end
end
end

View file

@ -476,29 +476,16 @@ describe Users::DossiersController, type: :controller do
end
end
context 'when the pj service returns an error' do
before do
expect(PiecesJustificativesService).to receive(:upload!).and_return(['nop'])
subject
end
it { expect(response).to render_template(:brouillon) }
it { expect(flash.alert).to eq(['nop']) }
end
context 'when a mandatory champ is missing' do
let(:value) { nil }
before do
first_champ.type_de_champ.update(mandatory: true, libelle: 'l')
allow(PiecesJustificativesService).to receive(:missing_pj_error_messages).and_return(['pj'])
subject
end
it { expect(response).to render_template(:brouillon) }
it { expect(flash.alert).to eq(['Le champ l doit être rempli.', 'pj']) }
it { expect(flash.alert).to eq(['Le champ l doit être rempli.']) }
context 'and the user saves a draft' do
let(:payload) { submit_payload.merge(save_draft: true) }
@ -511,7 +498,7 @@ describe Users::DossiersController, type: :controller do
let!(:dossier) { create(:dossier, :en_construction, user: user) }
it { expect(response).to render_template(:brouillon) }
it { expect(flash.alert).to eq(['Le champ l doit être rempli.', 'pj']) }
it { expect(flash.alert).to eq(['Le champ l doit être rempli.']) }
end
end
end
@ -535,8 +522,6 @@ describe Users::DossiersController, type: :controller do
before do
first_champ.type_de_champ.update(mandatory: true, libelle: 'l')
allow(PiecesJustificativesService).to receive(:missing_pj_error_messages).and_return(['pj'])
subject
end
@ -644,29 +629,16 @@ describe Users::DossiersController, type: :controller do
end
end
context 'when the pj service returns an error' do
before do
expect(PiecesJustificativesService).to receive(:upload!).and_return(['nop'])
subject
end
it { expect(response).to render_template(:modifier) }
it { expect(flash.alert).to eq(['nop']) }
end
context 'when a mandatory champ is missing' do
let(:value) { nil }
before do
first_champ.type_de_champ.update(mandatory: true, libelle: 'l')
allow(PiecesJustificativesService).to receive(:missing_pj_error_messages).and_return(['pj'])
subject
end
it { expect(response).to render_template(:modifier) }
it { expect(flash.alert).to eq(['Le champ l doit être rempli.', 'pj']) }
it { expect(flash.alert).to eq(['Le champ l doit être rempli.']) }
end
context 'when dossier has no champ' do

View file

@ -6,7 +6,7 @@ FactoryBot.define do
before(:create) do |dossier, _evaluator|
if !dossier.procedure
procedure = create(:procedure, :published, :with_two_type_de_piece_justificative, :with_type_de_champ, :with_type_de_champ_private)
procedure = create(:procedure, :published, :with_type_de_champ, :with_type_de_champ_private)
dossier.procedure = procedure
end
end

View file

@ -1,11 +0,0 @@
FactoryBot.define do
factory :piece_justificative do
trait :rib do
content { Rack::Test::UploadedFile.new("./spec/fixtures/files/RIB.pdf", 'application/pdf') }
end
trait :contrat do
content { Rack::Test::UploadedFile.new("./spec/fixtures/files/Contrat.pdf", 'application/pdf') }
end
end
end

View file

@ -135,17 +135,6 @@ FactoryBot.define do
end
end
# Deprecated
trait :with_two_type_de_piece_justificative do
after(:build) do |procedure, _evaluator|
rib = create(:type_de_piece_justificative, :rib, order_place: 1)
msa = create(:type_de_piece_justificative, :msa, order_place: 2)
procedure.types_de_piece_justificative << rib
procedure.types_de_piece_justificative << msa
end
end
trait :published do
after(:build) do |procedure, _evaluator|
procedure.publish!(procedure.administrateurs.first, generate(:published_path), procedure.lien_site_web)

View file

@ -1,18 +0,0 @@
FactoryBot.define do
factory :type_de_piece_justificative do
libelle { 'RIB' }
description { 'Releve identité bancaire' }
trait :rib do
libelle { 'RIB' }
description { 'Releve identité bancaire' }
api_entreprise { false }
end
trait :msa do
libelle { 'Attestation MSA' }
description { 'recuperation automatique' }
api_entreprise { true }
end
end
end

View file

@ -65,13 +65,6 @@ feature 'As an administrateur I wanna create a new procedure', js: true do
procedure.update(service: create(:service))
end
context 'With old PJ' do
before do
# Create a dummy PJ, because adding PJs is no longer allowed on procedures that
# do not already have one
Procedure.last.types_de_piece_justificative.create(libelle: "dummy PJ")
end
scenario 'Add champ, add file, visualize them in procedure preview' do
page.refresh
expect(page).to have_current_path(champs_procedure_path(Procedure.last))
@ -87,18 +80,11 @@ feature 'As an administrateur I wanna create a new procedure', js: true do
expect(page).to have_selector('#champ-1-libelle')
click_on Procedure.last.libelle
click_on 'onglet-pieces'
expect(page).to have_current_path(admin_procedure_pieces_justificatives_path(Procedure.last))
fill_in 'procedure_types_de_piece_justificative_attributes_0_libelle', with: 'libelle de piece'
click_on 'add_piece_justificative'
expect(page).to have_current_path(admin_procedure_pieces_justificatives_path(Procedure.last))
expect(page).to have_selector('#procedure_types_de_piece_justificative_attributes_1_libelle')
preview_window = window_opened_by { click_on 'onglet-preview' }
within_window(preview_window) do
expect(page).to have_current_path(apercu_procedure_path(Procedure.last))
expect(page).to have_field('libelle de champ')
expect(page).to have_field('libelle de piece')
end
end
@ -110,14 +96,8 @@ feature 'As an administrateur I wanna create a new procedure', js: true do
expect(page).to have_content('Formulaire enregistré')
click_on Procedure.last.libelle
click_on 'onglet-pieces'
expect(page).to have_current_path(admin_procedure_pieces_justificatives_path(Procedure.last))
fill_in 'procedure_types_de_piece_justificative_attributes_0_libelle', with: 'libelle de piece'
click_on 'add_piece_justificative'
click_on 'onglet-infos'
expect(page).to have_current_path(admin_procedure_path(Procedure.last))
expect(page).to have_selector('#publish-procedure', visible: true)
find('#publish-procedure').click
@ -132,4 +112,3 @@ feature 'As an administrateur I wanna create a new procedure', js: true do
end
end
end
end

View file

@ -11,7 +11,7 @@ feature 'Creating a new dossier:' do
end
context 'when the procedure has identification by individual' do
let(:procedure) { create(:procedure, :published, :for_individual, :with_service, :with_type_de_champ, :with_two_type_de_piece_justificative, ask_birthday: ask_birthday) }
let(:procedure) { create(:procedure, :published, :for_individual, :with_service, ask_birthday: ask_birthday) }
let(:ask_birthday) { false }
let(:expected_birthday) { nil }
@ -63,7 +63,7 @@ feature 'Creating a new dossier:' do
end
context 'when identifying through SIRET' do
let(:procedure) { create(:procedure, :published, :with_service, :with_type_de_champ, :with_two_type_de_piece_justificative) }
let(:procedure) { create(:procedure, :published, :with_service, :with_type_de_champ) }
let(:dossier) { procedure.dossiers.last }
before do

File diff suppressed because it is too large Load diff

View file

@ -1,62 +0,0 @@
require 'rails_helper'
RSpec.describe AdminFormulaireHelper, type: :helper do
let(:procedure) { create(:procedure) }
let(:kind) { 'piece_justificative' }
let(:url) { 'http://localhost' }
let!(:type_de_piece_justificative_0) { create(:type_de_piece_justificative, procedure: procedure, order_place: 0) }
let!(:type_de_piece_justificative_1) { create(:type_de_piece_justificative, procedure: procedure, order_place: 1) }
let!(:type_de_piece_justificative_2) { create(:type_de_piece_justificative, procedure: procedure, order_place: 2) }
describe '#button_up' do
describe 'with first piece justificative' do
let(:index) { 0 }
it 'returns a button up' do
expect(button_up(procedure, kind, index, url)).to be(nil)
end
end
describe 'with second out of three piece justificative' do
let(:index) { 1 }
it 'returns a button up' do
expect(button_up(procedure, kind, index, url)).to match(/fa-chevron-up/)
end
end
describe 'with last piece justificative' do
let(:index) { 2 }
it 'returns a button up' do
expect(button_up(procedure, kind, index, url)).to match(/fa-chevron-up/)
end
end
end
describe '#button_down' do
describe 'with first piece justificative' do
let(:index) { 0 }
it 'returns a button down' do
expect(button_down(procedure, kind, index, url)).to match(/fa-chevron-down/)
end
end
describe 'with second out of three piece justificative' do
let(:index) { 1 }
it 'returns a button down' do
expect(button_down(procedure, kind, index, url)).to match(/fa-chevron-down/)
end
end
describe 'with last piece justificative' do
let(:index) { 2 }
it 'returns nil' do
expect(button_down(procedure, kind, index, url)).to be(nil)
end
end
end
end

View file

@ -1,13 +1,7 @@
describe ActiveStorage::DownloadableFile do
let(:tpjs) { [tpj_not_mandatory] }
let!(:tpj_not_mandatory) do
TypeDePieceJustificative.create(libelle: 'not mandatory', mandatory: false)
end
let(:procedure) { Procedure.create(types_de_piece_justificative: tpjs) }
let(:dossier) { Dossier.create(procedure: procedure) }
let(:procedure) { Procedure.create(types_de_piece_justificative: tpjs) }
let(:dossier) { Dossier.create(procedure: procedure) }
let(:list) { ActiveStorage::DownloadableFile.create_list_from_dossier(dossier) }
let(:dossier) { create(:dossier) }
subject(:list) { ActiveStorage::DownloadableFile.create_list_from_dossier(dossier) }
describe 'create_list_from_dossier' do
context 'when no piece_justificative is present' do
@ -15,9 +9,8 @@ describe ActiveStorage::DownloadableFile do
end
context 'when there is a piece_justificative' do
let (:pj) { create(:champ, :piece_justificative, :with_piece_justificative_file) }
before do
dossier.champs = [pj]
dossier.champs << create(:champ, :piece_justificative, :with_piece_justificative_file)
end
it { expect(list.length).to be 1 }

View file

@ -1,48 +0,0 @@
require 'spec_helper'
describe FileSizeValidator, lib: true do
let(:validator) { FileSizeValidator.new(options) }
let(:attachment) { PieceJustificativeUploader.new }
let(:note) { create(:piece_justificative, :contrat) }
describe 'options uses an integer' do
let(:options) { { maximum: 10, attributes: { content: attachment } } }
it 'attachment exceeds maximum limit' do
allow(attachment).to receive(:size) { 100 }
validator.validate_each(note, :content, attachment)
expect(note.errors).to have_key(:content)
end
it 'attachment under maximum limit' do
allow(attachment).to receive(:size) { 1 }
validator.validate_each(note, :content, attachment)
expect(note.errors).not_to have_key(:content)
end
end
describe 'options uses a symbol' do
let(:options) do
{
maximum: :test,
attributes: { content: attachment }
}
end
before do
allow(note).to receive(:test) { 10 }
end
it 'attachment exceeds maximum limit' do
allow(attachment).to receive(:size) { 100 }
validator.validate_each(note, :content, attachment)
expect(note.errors).to have_key(:content)
end
it 'attachment under maximum limit' do
allow(attachment).to receive(:size) { 1 }
validator.validate_each(note, :content, attachment)
expect(note.errors).not_to have_key(:content)
end
end
end

View file

@ -1,51 +0,0 @@
describe 'pieces_justificatives' do
describe 'migrate_procedure_to_champs' do
let(:rake_task) { Rake::Task['pieces_justificatives:migrate_procedure_to_champs'] }
let(:procedure) { create(:procedure, :with_two_type_de_piece_justificative) }
before do
ENV['PROCEDURE_ID'] = procedure.id.to_s
allow_any_instance_of(PieceJustificativeToChampPieceJointeMigrationService).to receive(:ensure_correct_storage_configuration!)
rake_task.invoke
end
after { rake_task.reenable }
it 'migrates the procedure' do
expect(procedure.reload.types_de_piece_justificative).to be_empty
end
end
describe 'migrate_procedures_range_to_champs' do
let(:rake_task) { Rake::Task['pieces_justificatives:migrate_procedures_range_to_champs'] }
let(:procedure_in_range_1) { create(:procedure, :with_two_type_de_piece_justificative) }
let(:procedure_in_range_2) { create(:procedure, :with_two_type_de_piece_justificative) }
let(:procedure_out_of_range) { create(:procedure, :with_two_type_de_piece_justificative) }
before do
procedure_in_range_1
procedure_in_range_2
procedure_out_of_range
ENV['RANGE_START'] = procedure_in_range_1.id.to_s
ENV['RANGE_END'] = procedure_in_range_2.id.to_s
allow_any_instance_of(PieceJustificativeToChampPieceJointeMigrationService).to receive(:ensure_correct_storage_configuration!)
rake_task.invoke
end
after { rake_task.reenable }
it 'migrates procedures in the ids range' do
expect(procedure_in_range_1.reload.types_de_piece_justificative).to be_empty
expect(procedure_in_range_2.reload.types_de_piece_justificative).to be_empty
end
it 'doesnt migrate procedures not in the range' do
expect(procedure_out_of_range.reload.types_de_piece_justificative).to be_present
end
end
end

View file

@ -91,50 +91,6 @@ describe Dossier do
end
end
describe '#types_de_piece_justificative' do
subject { dossier.types_de_piece_justificative }
it 'returns list of required piece justificative' do
expect(subject.size).to eq(2)
expect(subject).to include(TypeDePieceJustificative.find(TypeDePieceJustificative.first.id))
end
end
describe '#types_de_piece_justificative' do
subject { dossier.types_de_piece_justificative }
it 'returns list of required piece justificative' do
expect(subject.size).to eq(2)
expect(subject).to include(TypeDePieceJustificative.find(TypeDePieceJustificative.first.id))
end
end
describe '#retrieve_last_piece_justificative_by_type', vcr: { cassette_name: 'models_dossier_retrieve_last_piece_justificative_by_type' } do
let(:types_de_pj_dossier) { dossier.procedure.types_de_piece_justificative }
subject { dossier.retrieve_last_piece_justificative_by_type types_de_pj_dossier.first }
before do
create :piece_justificative, :rib, dossier: dossier, type_de_piece_justificative: types_de_pj_dossier.first
end
it 'returns piece justificative with given type' do
expect(subject.type).to eq(types_de_pj_dossier.first.id)
end
end
describe '#retrieve_all_piece_justificative_by_type' do
let(:types_de_pj_dossier) { dossier.procedure.types_de_piece_justificative }
subject { dossier.retrieve_all_piece_justificative_by_type types_de_pj_dossier.first }
before do
create :piece_justificative, :rib, dossier: dossier, type_de_piece_justificative: types_de_pj_dossier.first
end
it 'returns a list of the piece justificative' do
expect(subject).not_to be_empty
end
end
describe '#build_default_champs' do
context 'when dossier is linked to a procedure with type_de_champ_public and private' do
let(:dossier) { create(:dossier, user: user) }
@ -581,12 +537,6 @@ describe Dossier do
it { is_expected.not_to eq(modif_date) }
context 'when a piece justificative is modified' do
before { dossier.pieces_justificatives << create(:piece_justificative, :contrat) }
it { is_expected.to eq(modif_date) }
end
context 'when a champ is modified' do
before { dossier.champs.first.update_attribute('value', 'yop') }

View file

@ -251,12 +251,6 @@ describe Gestionnaire, type: :model do
it { is_expected.to match({ demande: true, annotations_privees: false, avis: false, messagerie: false }) }
end
context 'when there is a modification on a piece jusitificative' do
before { dossier.pieces_justificatives << create(:piece_justificative, :contrat) }
it { is_expected.to match({ demande: true, annotations_privees: false, avis: false, messagerie: false }) }
end
context 'when there is a modification on private champs' do
before { dossier.champs_private.first.update_attribute('value', 'toto') }
@ -326,12 +320,6 @@ describe Gestionnaire, type: :model do
end
end
context 'when there is a modification on a piece justificative' do
before { dossier.pieces_justificatives << create(:piece_justificative, :contrat) }
it { is_expected.to match([dossier.id]) }
end
context 'when there is a modification on public champs on a followed dossier from another procedure' do
before { dossier_on_procedure_2.champs.first.update_attribute('value', 'toto') }

View file

@ -1,20 +0,0 @@
require 'spec_helper'
describe PieceJustificative do
describe 'validations' do
context 'content' do
it { is_expected.not_to allow_value(nil).for(:content) }
it { is_expected.not_to allow_value('').for(:content) }
end
end
describe '#empty?', vcr: { cassette_name: 'model_piece_justificative' } do
let(:piece_justificative) { create(:piece_justificative, content: content) }
subject { piece_justificative.empty? }
context 'when content exist' do
let(:content) { File.open('./spec/fixtures/files/piece_justificative_388.pdf') }
it { is_expected.to be_falsey }
end
end
end

View file

@ -381,8 +381,6 @@ describe Procedure do
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!(:piece_justificative_0) { create(:type_de_piece_justificative, procedure: procedure, order_place: 0) }
let!(:piece_justificative_1) { create(:type_de_piece_justificative, procedure: procedure, order_place: 1) }
let(:received_mail) { create(:received_mail) }
let(:from_library) { false }
let(:administrateur) { procedure.administrateurs.first }
@ -408,7 +406,7 @@ 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 + 1 + procedure.types_de_piece_justificative.size)
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_list).compact.size).to eq procedure.types_de_champ.map(&:drop_down_list).compact.size
expect(subject.types_de_champ_private.map(&:drop_down_list).compact.size).to eq procedure.types_de_champ_private.map(&:drop_down_list).compact.size
@ -430,18 +428,6 @@ describe Procedure do
expect(cloned_procedure).to have_same_attributes_as(procedure, except: ["path"])
end
it 'should not clone piece justificatives but create corresponding champs' do
expect(subject.types_de_piece_justificative.size).to eq(0)
champs_pj = subject.types_de_champ[procedure.types_de_champ.size + 1, procedure.types_de_piece_justificative.size]
champs_pj.zip(procedure.types_de_piece_justificative).each do |stc, ptpj|
expect(stc.libelle).to eq(ptpj.libelle)
expect(stc.description).to eq(ptpj.description)
expect(stc.mandatory).to eq(ptpj.mandatory)
expect(stc.old_pj[:stable_id]).to eq(ptpj.id)
end
end
context 'when the procedure is cloned from the library' do
let(:from_library) { true }

View file

@ -1,26 +0,0 @@
require 'spec_helper'
describe TypeDePieceJustificative do
let!(:procedure) { create(:procedure) }
describe 'validation' do
context 'libelle' do
it { is_expected.not_to allow_value(nil).for(:libelle) }
it { is_expected.not_to allow_value('').for(:libelle) }
it { is_expected.to allow_value('RIB').for(:libelle) }
end
context 'order_place' do
# it { is_expected.not_to allow_value(nil).for(:order_place) }
# it { is_expected.not_to allow_value('').for(:order_place) }
it { is_expected.to allow_value(1).for(:order_place) }
end
context 'lien_demarche' do
it { is_expected.to allow_value(nil).for(:lien_demarche) }
it { is_expected.to allow_value('').for(:lien_demarche) }
it { is_expected.not_to allow_value('not-a-link').for(:lien_demarche) }
it { is_expected.to allow_value('http://link').for(:lien_demarche) }
end
end
end

View file

@ -46,26 +46,17 @@ describe DossierSerializer do
end
end
context 'when a type PJ was cloned to a type champ PJ' do
let(:original_procedure) do
p = create(:procedure, :published)
p.types_de_piece_justificative.create(
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,
libelle: "Vidéo de votre demande de subvention",
description: "Pour optimiser vos chances, soignez la chorégraphie et privilégiez le chant polyphonique",
lien_demarche: "https://www.dance-academy.gouv.fr",
order_place: 0
)
p
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
end
let(:procedure) do
p = original_procedure.clone(original_procedure.administrateurs.first, false)
p.save
p
end
let(:type_pj) { original_procedure.types_de_piece_justificative.first }
let(:migrated_type_champ) { procedure.types_de_champ.find_by(libelle: type_pj.libelle) }
let(:procedure) { create(:procedure, :published, types_de_champ: [cloned_type_de_champ]) }
let(:dossier) { create(:dossier, procedure: procedure) }
let(:champ_pj) { dossier.champs.last }
@ -79,18 +70,18 @@ describe DossierSerializer do
is_expected.to include(
types_de_piece_justificative: [
{
"id" => type_pj.id,
"libelle" => type_pj.libelle,
"description" => type_pj.description,
"lien_demarche" => type_pj.lien_demarche,
"order_place" => type_pj.order_place
"id" => original_pj_id,
"libelle" => cloned_type_de_champ.libelle,
"description" => 'Pour optimiser vos chances, soignez la chorégraphie et privilégiez le chant polyphonique.',
"lien_demarche" => 'https://www.dance-academy.gouv.fr',
"order_place" => cloned_type_de_champ.order_place
}
],
pieces_justificatives: [
{
"content_url" => champ_pj.for_api,
"created_at" => champ_pj.created_at.in_time_zone('UTC').iso8601(3),
"type_de_piece_justificative_id" => type_pj.id,
"type_de_piece_justificative_id" => original_pj_id,
"user" => a_hash_including("id" => dossier.user.id)
}
]
@ -98,7 +89,7 @@ describe DossierSerializer do
end
it "does not expose the PJ as a champ" do
expect(subject[:champs]).not_to include(a_hash_including(type_de_champ: a_hash_including(id: migrated_type_champ.id)))
expect(subject[:champs]).to be_empty
end
end
end

View file

@ -10,21 +10,16 @@ describe ProcedureSerializer do
end
context 'when a type PJ was cloned to a type champ PJ' do
let(:original_procedure) do
p = create(:procedure, :published)
p.types_de_piece_justificative.create(
let(:original_pj_id) { 3 }
let(:cloned_type_de_champ) do
tdc = create(: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",
lien_demarche: "https://www.dance-academy.gouv.fr",
order_place: 0
)
p
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
end
let(:procedure) { original_procedure.clone(original_procedure.administrateurs.first, false) }
let(:type_pj) { original_procedure.types_de_piece_justificative.first }
let(:migrated_type_champ) { procedure.types_de_champ.find_by(libelle: type_pj.libelle) }
let(:procedure) { create(:procedure, :published, types_de_champ: [cloned_type_de_champ]) }
subject { ProcedureSerializer.new(procedure).serializable_hash }
@ -32,18 +27,18 @@ describe ProcedureSerializer do
is_expected.to include(
types_de_piece_justificative: [
{
"id" => type_pj.id,
"libelle" => type_pj.libelle,
"description" => type_pj.description,
"lien_demarche" => type_pj.lien_demarche,
"order_place" => type_pj.order_place
"id" => original_pj_id,
"libelle" => cloned_type_de_champ.libelle,
"description" => 'Pour optimiser vos chances, soignez la chorégraphie et privilégiez le chant polyphonique.',
"lien_demarche" => 'https://www.dance-academy.gouv.fr',
"order_place" => cloned_type_de_champ.order_place
}
]
)
end
it "is not exposed as a type de champ" do
expect(subject[:types_de_champ]).not_to include(a_hash_including(libelle: type_pj.libelle))
expect(subject[:types_de_champ]).to be_empty
end
end
end

View file

@ -1,99 +0,0 @@
require 'spec_helper'
describe CarrierwaveActiveStorageMigrationService do
let(:service) { CarrierwaveActiveStorageMigrationService.new }
describe '#hex_to_base64' do
it { expect(service.hex_to_base64('deadbeef')).to eq('3q2+7w==') }
end
describe '.make_blob' do
let(:pj) { create(:piece_justificative, :rib, updated_at: Time.zone.local(2019, 01, 01, 12, 00)) }
let(:identify) { false }
before do
allow(service).to receive(:checksum).and_return('cafe')
end
subject(:blob) { service.make_blob(pj.content, pj.updated_at.iso8601, filename: pj.original_filename, identify: identify) }
it { expect(blob.created_at).to eq pj.updated_at }
it 'marks the blob as already scanned by the antivirus' do
expect(blob.metadata[:virus_scan_result]).to eq(ActiveStorage::VirusScanner::SAFE)
end
it 'sets the blob MIME type from the file' do
expect(blob.identified).to be true
expect(blob.content_type).to eq 'application/pdf'
end
context 'when asking for explicit MIME type identification' do
let(:identify) { true }
it 'marks the file as needing MIME type detection' do
expect(blob.identified).to be false
end
end
end
describe '.make_empty_blob' do
let(:pj) { create(:piece_justificative, :rib, updated_at: Time.zone.local(2019, 01, 01, 12, 00)) }
before 'set the underlying stored file as missing' do
allow(pj.content.file).to receive(:file).and_return(nil)
end
subject(:blob) { service.make_empty_blob(pj.content, pj.updated_at.iso8601, filename: pj.original_filename) }
it { expect(blob.created_at).to eq pj.updated_at }
it 'marks the blob as already scanned by the antivirus' do
expect(blob.metadata[:virus_scan_result]).to eq(ActiveStorage::VirusScanner::SAFE)
end
it 'sets the blob MIME type from the file' do
expect(blob.identified).to be true
expect(blob.content_type).to eq 'application/pdf'
end
context 'when the file metadata are also missing' do
before do
allow(pj).to receive(:original_filename).and_return(nil)
allow(pj.content).to receive(:content_type).and_return(nil)
end
it 'fallbacks on default values' do
expect(blob.filename).to eq pj.content.filename
expect(blob.content_type).to eq 'text/plain'
end
end
end
describe '.fix_content_type' do
let(:pj) { create(:piece_justificative, :rib, updated_at: Time.zone.local(2019, 01, 01, 12, 00)) }
let(:blob) { service.make_empty_blob(pj.content, pj.updated_at.iso8601, filename: pj.original_filename) }
context 'when the request is ok' do
it 'succeeds' do
expect(blob.service).to receive(:change_content_type).and_return(true)
expect { service.fix_content_type(blob) }.not_to raise_error
end
end
context 'when the request fails initially' do
it 'retries the request' do
expect(blob.service).to receive(:change_content_type).and_raise(StandardError).ordered
expect(blob.service).to receive(:change_content_type).and_return(true).ordered
expect { service.fix_content_type(blob, retry_delay: 0.01) }.not_to raise_error
end
end
context 'when the request fails too many times' do
it 'gives up' do
expect(blob.service).to receive(:change_content_type).and_raise(StandardError).thrice
expect { service.fix_content_type(blob, retry_delay: 0.01) }.to raise_error(StandardError)
end
end
end
end

View file

@ -1,317 +0,0 @@
require 'spec_helper'
describe PieceJustificativeToChampPieceJointeMigrationService do
let(:service) { PieceJustificativeToChampPieceJointeMigrationService.new(storage_service: storage_service) }
let(:storage_service) { CarrierwaveActiveStorageMigrationService.new }
let(:pj_uploader) { class_double(PieceJustificativeUploader) }
let(:pj_service) { class_double(PiecesJustificativesService) }
let(:procedure) { create(:procedure, types_de_piece_justificative: types_pj) }
let(:types_pj) { [create(:type_de_piece_justificative)] }
let!(:dossier) { make_dossier }
let(:pjs) { [] }
def make_dossier(hidden: false)
create(:dossier,
procedure: procedure,
pieces_justificatives: pjs,
hidden_at: hidden ? Time.zone.now : nil)
end
def make_pjs
types_pj.map do |tpj|
create(:piece_justificative, :contrat, type_de_piece_justificative: tpj)
end
end
def timestamps(dossier)
# Reload dossier because the resolution of in-database timestamps is
# different from the resolution of in-memory timestamps, causing the
# tests to fail on fractional time differences.
dossier.reload
{
created_at: dossier.created_at,
updated_at: dossier.updated_at
}
end
def expect_storage_service_to_convert_object
expect(storage_service).to receive(:make_blob)
expect(storage_service).to receive(:copy_from_carrierwave_to_active_storage!)
expect(storage_service).to receive(:make_attachment)
end
describe '.number_of_champs_to_migrate' do
let!(:other_dossier) { make_dossier }
it 'reports the numbers of champs to be migrated' do
expect(service.number_of_champs_to_migrate(procedure)).to eq(4)
end
context 'when the procedure has hidden dossiers' do
let!(:hidden_dossier) { make_dossier(hidden: true) }
it 'reports the numbers of champs including those of hidden dossiers' do
expect(service.number_of_champs_to_migrate(procedure)).to eq(6)
end
end
end
context 'when conversion succeeds' do
context 'for the procedure' do
it 'types de champ are created for the "pièces jointes" header and for each PJ' do
expect { service.convert_procedure_pjs_to_champ_pjs(procedure) }
.to change { procedure.types_de_champ.count }
.by(types_pj.count + 1)
end
it 'the old types de pj are removed' do
expect { service.convert_procedure_pjs_to_champ_pjs(procedure) }
.to change { procedure.types_de_piece_justificative.count }
.to(0)
end
end
context 'no notifications are sent to instructeurs' do
let!(:initial_dossier_timestamps) { timestamps(dossier) }
context 'when there is a PJ' do
let(:pjs) { make_pjs }
before do
# Reload PJ because the resolution of in-database timestamps is
# different from the resolution of in-memory timestamps, causing the
# tests to fail on fractional time differences.
pjs.last.reload
expect_storage_service_to_convert_object
Timecop.travel(1.hour) { service.convert_procedure_pjs_to_champ_pjs(procedure) }
# Reload the dossier to see the newly created champs
dossier.reload
end
it 'the champ has the same timestamps as the PJ' do
expect(dossier.champs.last.created_at).to eq(pjs.last.created_at)
expect(dossier.champs.last.updated_at).to eq(pjs.last.updated_at)
end
it 'does not change the dossier timestamps' do
expect(dossier.created_at).to eq(initial_dossier_timestamps[:created_at])
expect(dossier.updated_at).to eq(initial_dossier_timestamps[:updated_at])
end
end
context 'when there is no PJ' do
before do
Timecop.travel(1.hour) { service.convert_procedure_pjs_to_champ_pjs(procedure) }
# Reload the dossier to see the newly created champs
dossier.reload
end
it 'the champ doesnt trigger a notification' do
expect(dossier.champs.last.created_at).to eq(initial_dossier_timestamps[:created_at])
expect(dossier.champs.last.updated_at).to eq(initial_dossier_timestamps[:created_at])
end
it 'does not change the dossier timestamps' do
expect(dossier.created_at).to eq(initial_dossier_timestamps[:created_at])
expect(dossier.updated_at).to eq(initial_dossier_timestamps[:updated_at])
end
end
end
context 'for the dossier' do
let(:pjs) { make_pjs }
before { expect_storage_service_to_convert_object }
it 'champs are created for the "pièces jointes" header and for each PJ' do
expect { service.convert_procedure_pjs_to_champ_pjs(procedure) }
.to change { dossier.champs.count }
.by(types_pj.count + 1)
end
it 'the old pjs are removed' do
expect { service.convert_procedure_pjs_to_champ_pjs(procedure) }
.to change { dossier.pieces_justificatives.count }
.to(0)
end
context 'when the procedure has several dossiers' do
let!(:other_dossier) { make_dossier }
it 'sends progress callback for each migrated champ' do
number_of_champs_to_migrate = service.number_of_champs_to_migrate(procedure)
progress_count = 0
service.convert_procedure_pjs_to_champ_pjs(procedure) do
progress_count += 1
end
expect(progress_count).to eq(number_of_champs_to_migrate)
end
end
end
context 'when the dossier is soft-deleted it still gets converted' do
let(:pjs) { make_pjs }
let!(:dossier) { make_dossier(hidden: true) }
before { expect_storage_service_to_convert_object }
it 'champs are created for the "pièces jointes" header and for each PJ' do
expect { service.convert_procedure_pjs_to_champ_pjs(procedure) }
.to change { dossier.champs.count }
.by(types_pj.count + 1)
end
it 'the old pjs are removed' do
expect { service.convert_procedure_pjs_to_champ_pjs(procedure) }
.to change { dossier.pieces_justificatives.count }
.to(0)
end
end
context 'when there are several pjs for one type' do
let(:pjs) { make_pjs + make_pjs }
it 'only converts the most recent PJ for each type PJ' do
expect(storage_service).to receive(:make_blob).exactly(types_pj.count)
expect(storage_service).to receive(:copy_from_carrierwave_to_active_storage!).exactly(types_pj.count)
expect(storage_service).to receive(:make_attachment).exactly(types_pj.count)
service.convert_procedure_pjs_to_champ_pjs(procedure)
end
end
end
context 'cleanup when conversion fails' do
let(:pjs) { make_pjs }
let(:exception) { 'LOL no!' }
let!(:failing_dossier) do
create(
:dossier,
procedure: procedure,
pieces_justificatives: make_pjs
)
end
let!(:initial_dossier_timestamps) { timestamps(dossier) }
let!(:initial_failing_dossier_timestamps) { timestamps(failing_dossier) }
before do
allow(storage_service).to receive(:checksum).and_return('cafe')
allow(storage_service).to receive(:fix_content_type)
expect(storage_service).to receive(:copy_from_carrierwave_to_active_storage!)
expect(storage_service).to receive(:copy_from_carrierwave_to_active_storage!)
.and_raise(exception)
expect(storage_service).to receive(:delete_from_active_storage!)
end
def try_convert(procedure)
Timecop.travel(1.hour) { service.convert_procedure_pjs_to_champ_pjs(procedure) }
rescue StandardError, SignalException => e
dossier.reload
failing_dossier.reload
e
end
it 'passes on the exception' do
expect { service.convert_procedure_pjs_to_champ_pjs(procedure) }
.to raise_error('LOL no!')
end
it 'does not create champs' do
expect { try_convert(procedure) }
.not_to change { dossier.champs.count }
end
it 'does not remove any old pjs' do
expect { try_convert(procedure) }
.not_to change { dossier.pieces_justificatives.count }
end
it 'does not creates types de champ' do
expect { try_convert(procedure) }
.not_to change { procedure.types_de_champ.count }
end
it 'does not remove old types de pj' do
expect { try_convert(procedure) }
.not_to change { procedure.types_de_piece_justificative.count }
end
it 'does not change the dossiers timestamps' do
try_convert(procedure)
expect(dossier.updated_at).to eq(initial_dossier_timestamps[:updated_at])
expect(failing_dossier.updated_at).to eq(initial_failing_dossier_timestamps[:updated_at])
end
it 'does not leave stale blobs behind' do
expect { try_convert(procedure) }
.not_to change { ActiveStorage::Blob.count }
end
it 'does not leave stale attachments behind' do
expect { try_convert(procedure) }
.not_to change { ActiveStorage::Attachment.count }
end
context 'when some dossiers to roll back are hidden' do
before do
dossier.update_column(:hidden_at, Time.zone.now)
end
it 'does not create champs' do
expect { try_convert(procedure) }
.not_to change { dossier.champs.count }
end
it 'does not change the hidden dossier timestamps' do
try_convert(procedure)
expect(dossier.updated_at).to eq(initial_dossier_timestamps[:updated_at])
end
end
context 'when receiving a Signal interruption (like Ctrl+C)' do
let(:exception) { Interrupt }
it 'handles the exception as well' do
expect { service.convert_procedure_pjs_to_champ_pjs(procedure) }.to raise_error { Interrupt }
end
it 'does not create champs' do
expect { try_convert(procedure) }.not_to change { dossier.champs.count }
end
end
context 'when rolling back a dossier fails' do
before do
allow(service).to receive(:destroy_champ_pj)
.with(having_attributes(id: dossier.id), anything)
.and_raise(StandardError)
allow(service).to receive(:destroy_champ_pj)
.with(any_args)
.and_call_original
end
it 'continues to roll back the other dossiers' do
expect { try_convert(procedure) }
.not_to change { failing_dossier.champs.count }
end
it 'does not creates types de champ on the procedure' do
expect { try_convert(procedure) }
.not_to change { procedure.types_de_champ.count }
end
end
end
end

View file

@ -1,159 +0,0 @@
require 'spec_helper'
describe PiecesJustificativesService do
let(:user) { create(:user) }
let(:safe_file) { true }
before :each do
allow(ClamavService).to receive(:safe_file?).and_return(safe_file)
end
let(:hash) { {} }
let!(:tpj_not_mandatory) do
TypeDePieceJustificative.create(libelle: 'not mandatory', mandatory: false)
end
let!(:tpj_mandatory) do
TypeDePieceJustificative.create(libelle: 'justificatif', mandatory: true)
end
let(:procedure) { Procedure.create(types_de_piece_justificative: tpjs) }
let(:dossier) { Dossier.create(procedure: procedure) }
let(:errors) { PiecesJustificativesService.upload!(dossier, user, hash) }
let(:tpjs) { [tpj_not_mandatory] }
let(:attachment_list) { PiecesJustificativesService.liste_pieces_justificatives(dossier) }
let(:poids_total) { PiecesJustificativesService.pieces_justificatives_total_size(dossier) }
describe 'self.upload!' do
context 'when no params are given' do
it { expect(errors).to eq([]) }
end
context 'when there is something wrong with file save' do
let(:hash) do
{
"piece_justificative_#{tpj_not_mandatory.id}" =>
double(path: '', original_filename: 'filename')
}
end
it { expect(errors).to match(["le fichier filename (not mandatory) n'a pas pu être sauvegardé"]) }
end
context 'when a virus is provided' do
let(:safe_file) { false }
let(:hash) do
{
"piece_justificative_#{tpj_not_mandatory.id}" =>
double(path: '', original_filename: 'bad_file')
}
end
it { expect(errors).to match(['bad_file : virus détecté']) }
end
context 'when a regular file is provided' do
let(:content) { double(path: '', original_filename: 'filename') }
let(:hash) do
{
"piece_justificative_#{tpj_not_mandatory.id}" =>
content
}
end
before :each do
expect(PiecesJustificativesService).to receive(:save_pj)
.with(content, dossier, tpj_not_mandatory, user)
.and_return(nil)
end
it 'is saved' do
expect(errors).to match([])
end
end
end
describe 'missing_pj_error_messages' do
let(:errors) { PiecesJustificativesService.missing_pj_error_messages(dossier) }
let(:tpjs) { [tpj_mandatory] }
context 'when no params are given' do
it { expect(errors).to match(['La pièce jointe justificatif doit être fournie.']) }
end
context 'when the piece justificative is provided' do
before :each do
# we are messing around piece_justificative
# because directly doubling carrierwave params seems complicated
piece_justificative_double = double(type_de_piece_justificative: tpj_mandatory)
expect(dossier).to receive(:pieces_justificatives).and_return([piece_justificative_double])
end
it {
expect(errors).to match([])
}
end
end
describe '#attachment_list' do
context 'when no piece_justificative is present' do
it { expect(attachment_list).to match([]) }
it { expect(poids_total).to be 0 }
end
context 'when there is a piece_justificative' do
let (:pj) { create(:champ, :piece_justificative, :with_piece_justificative_file) }
before do
dossier.champs = [pj]
end
it { expect(attachment_list).not_to be_empty }
it { expect(poids_total).to be pj.piece_justificative_file.byte_size }
end
end
describe 'types_pj_as_types_de_champ' do
subject { PiecesJustificativesService.types_pj_as_types_de_champ(procedure) }
it 'generates one header champ, plus one champ per PJ' do
expect(subject.pluck(:libelle)).to contain_exactly("Pièces jointes", "not mandatory")
end
it 'remembers the id of the PJ that got converted into a champ' do
expect(subject.map(&:old_pj)).to include({ 'stable_id' => tpj_not_mandatory.id })
end
context 'without pre-existing champs' do
it 'generates a sequence of order_places incrementing from zero' do
expect(subject.pluck(:order_place)).to contain_exactly(0, 1)
end
end
context 'with pre-existing champs' do
let(:procedure) do
create(
:procedure,
types_de_piece_justificative: tpjs,
types_de_champ: [build(:type_de_champ, order_place: 0), build(:type_de_champ, order_place: 1)]
)
end
it 'generates a sequence of incrementing order_places that continues where the last type de champ left off' do
expect(subject.pluck(:order_place)).to contain_exactly(2, 3)
end
end
context 'with pre-existing champs without an order place' do
let(:procedure) do
create(
:procedure,
types_de_piece_justificative: tpjs,
types_de_champ: [build(:type_de_champ, order_place: 0), build(:type_de_champ, order_place: nil)]
)
end
it 'ignores champs without an order place' do
expect(subject.pluck(:order_place)).to contain_exactly(1, 2)
end
end
end
end

View file

@ -134,7 +134,6 @@ RSpec.configure do |config|
config.include FactoryBot::Syntax::Methods
config.before(:each) do
allow_any_instance_of(PieceJustificativeUploader).to receive(:generate_secure_token).and_return("3dbb3535-5388-4a37-bc2d-778327b9f997")
allow_any_instance_of(ProcedureLogoUploader).to receive(:generate_secure_token).and_return("3dbb3535-5388-4a37-bc2d-778327b9f998")
Flipflop::FeatureSet.current.test!.reset!

View file

@ -1,14 +0,0 @@
require 'spec_helper'
describe PieceJustificativeUploader do
let(:pj) { create(:piece_justificative, :rib) }
it { expect(pj.content.filename).to eq 'piece_justificative.pdf' }
context 'when extension is nil' do
it do
expect(pj.content.file).to receive(:extension).and_return(nil)
expect(pj.content.filename).to eq 'piece_justificative.'
end
end
end

View file

@ -1,48 +0,0 @@
require 'spec_helper'
describe 'admin/pieces_justificatives/show.html.haml', type: :view do
let(:procedure) { create(:procedure) }
describe 'fields sorted' do
let(:first_libelle) { 'salut la compagnie' }
let(:last_libelle) { 'je suis bien sur la page' }
let!(:type_de_piece_justificative_1) { create(:type_de_piece_justificative, procedure: procedure, order_place: 1, libelle: last_libelle) }
let!(:type_de_piece_justificative_0) { create(:type_de_piece_justificative, procedure: procedure, order_place: 0, libelle: first_libelle) }
before do
procedure.reload
assign(:procedure, procedure)
render
end
it 'sorts by order place' do
expect(rendered).to match(/#{first_libelle}.*#{last_libelle}/m)
end
end
describe 'arrow button' do
subject do
procedure.reload
assign(:procedure, procedure)
render
rendered
end
context 'when there is no field in database' do
it { expect(subject).not_to have_css('.fa-chevron-down') }
it { expect(subject).not_to have_css('.fa-chevron-up') }
end
context 'when there is only one field in database' do
let!(:type_de_piece_justificative_0) { create(:type_de_piece_justificative, procedure: procedure, order_place: 0) }
it { expect(subject).not_to have_css('#btn_down_0') }
it { expect(subject).not_to have_css('#btn_up_0') }
it { expect(subject).not_to have_css('#btn_up_1') }
it { expect(subject).not_to have_css('#btn_down_1') }
end
context 'when there are 2 fields in database' do
let!(:type_de_piece_justificative_0) { create(:type_de_piece_justificative, procedure: procedure, order_place: 0) }
let!(:type_de_piece_justificative_1) { create(:type_de_piece_justificative, procedure: procedure, order_place: 1) }
it { expect(subject).to have_css('#btn_down_0') }
it { expect(subject).not_to have_css('#btn_up_0') }
it { expect(subject).to have_css('#btn_up_1') }
it { expect(subject).not_to have_css('#btn_down_1') }
end
end
end

View file

@ -16,6 +16,5 @@ describe 'gestionnaires/dossiers/show.html.haml', type: :view do
it 'renders the dossier infos' do
expect(rendered).to have_text('Identité')
expect(rendered).to have_text('Demande')
expect(rendered).to have_text('Pièces jointes')
end
end

View file

@ -51,12 +51,4 @@ describe 'shared/dossiers/demande.html.haml', type: :view do
end
end
end
context 'when the dossier has pièces justificatives' do
let(:procedure) { create(:procedure, :published, :with_two_type_de_piece_justificative) }
it 'renders the pièces justificatives' do
expect(rendered).to have_text('Pièces jointes')
end
end
end

View file

@ -1,8 +1,8 @@
require 'spec_helper'
describe 'users/dossiers/brouillon.html.haml', type: :view do
let(:procedure) { create(:procedure, :with_two_type_de_piece_justificative, :with_notice, for_individual: true) }
let(:dossier) { create(:dossier, :with_entreprise, :with_service, state: Dossier.states.fetch(:brouillon), procedure: procedure) }
let(:procedure) { create(:procedure, :with_type_de_champ, :with_notice, :with_service, for_individual: true) }
let(:dossier) { create(:dossier, :with_entreprise, state: Dossier.states.fetch(:brouillon), procedure: procedure) }
let(:footer) { view.content_for(:footer) }
before do

View file

@ -1,7 +1,7 @@
require 'spec_helper'
describe 'users/dossiers/demande.html.haml', type: :view do
let(:procedure) { create(:procedure, :published, :with_two_type_de_piece_justificative, :with_type_de_champ, :with_type_de_champ_private) }
let(:procedure) { create(:procedure, :published, :with_type_de_champ, :with_type_de_champ_private) }
let(:dossier) { create(:dossier, :en_construction, :with_entreprise, procedure: procedure) }
before do
@ -19,7 +19,6 @@ describe 'users/dossiers/demande.html.haml', type: :view do
expect(rendered).to have_text('Déposé le')
expect(rendered).to have_text('Identité')
expect(rendered).to have_text('Demande')
expect(rendered).to have_text('Pièces jointes')
end
context 'when the dossier is editable' do