Suppression du code des anciennes pièces jointes (#4142)

Suppression du code des anciennes pièces jointes
This commit is contained in:
Pierre de La Morinerie 2019-07-30 16:19:18 +02:00 committed by GitHub
commit d1ff311879
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
75 changed files with 99 additions and 10570 deletions

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,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

@ -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

@ -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

@ -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

@ -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

@ -15,7 +15,6 @@ class User < ApplicationRecord
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,8 +50,7 @@ class DossierSerializer < ActiveModel::Serializer
end
def pieces_justificatives
ActiveModelSerializers::SerializableResource.new(object.pieces_justificatives).serializable_hash +
PiecesJustificativesService.serialize_champs_as_pjs(object)
PiecesJustificativesService.serialize_champs_as_pjs(object)
end
def justificatif_motivation
@ -61,8 +60,7 @@ 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)
PiecesJustificativesService.serialize_types_de_champ_as_type_pj(object)
end
def email

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)
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

@ -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

@ -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

@ -193,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

@ -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,71 +65,50 @@ 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")
scenario 'Add champ, add file, visualize them in procedure preview' do
page.refresh
expect(page).to have_current_path(champs_procedure_path(Procedure.last))
expect(page).to have_selector('#champ-0-libelle')
fill_in 'champ-0-libelle', with: 'libelle de champ'
blur
expect(page).to have_content('Formulaire enregistré')
within '.buttons' do
click_on 'Ajouter un champ'
end
expect(page).to have_selector('#champ-1-libelle')
click_on Procedure.last.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')
end
end
scenario 'After adding champ and file, make publication' do
page.refresh
fill_in 'champ-0-libelle', with: 'libelle de champ'
blur
expect(page).to have_content('Formulaire enregistré')
click_on Procedure.last.libelle
expect(page).to have_current_path(admin_procedure_path(Procedure.last))
expect(page).to have_selector('#publish-procedure', visible: true)
find('#publish-procedure').click
within '#publish-modal' do
expect(page).to have_field('procedure_path')
fill_in 'lien_site_web', with: 'http://some.website'
click_on 'publish'
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))
expect(page).to have_selector('#champ-0-libelle')
fill_in 'champ-0-libelle', with: 'libelle de champ'
blur
expect(page).to have_content('Formulaire enregistré')
within '.buttons' do
click_on 'Ajouter un champ'
end
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
scenario 'After adding champ and file, make publication' do
page.refresh
fill_in 'champ-0-libelle', with: 'libelle de champ'
blur
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
within '#publish-modal' do
expect(page).to have_field('procedure_path')
fill_in 'lien_site_web', with: 'http://some.website'
click_on 'publish'
end
expect(page).to have_text('Démarche publiée')
expect(page).to have_selector('.procedure-lien')
end
expect(page).to have_text('Démarche publiée')
expect(page).to have_selector('.procedure-lien')
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