commit
6fe326ef82
42 changed files with 388 additions and 107 deletions
|
@ -69,6 +69,7 @@ En local, un utilisateur de test est créé automatiquement, avec les identifian
|
||||||
Administrateurs::ActivateBeforeExpirationJob.set(cron: "0 8 * * *").perform_later
|
Administrateurs::ActivateBeforeExpirationJob.set(cron: "0 8 * * *").perform_later
|
||||||
WarnExpiringDossiersJob.set(cron: "0 0 1 * *").perform_later
|
WarnExpiringDossiersJob.set(cron: "0 0 1 * *").perform_later
|
||||||
GestionnaireEmailNotificationJob.set(cron: "0 10 * * 1,2,3,4,5,6").perform_later
|
GestionnaireEmailNotificationJob.set(cron: "0 10 * * 1,2,3,4,5,6").perform_later
|
||||||
|
PurgeUnattachedBlobsJob.set("0 0 * * *").perform_later
|
||||||
|
|
||||||
### Voir les emails envoyés en local
|
### Voir les emails envoyés en local
|
||||||
|
|
||||||
|
|
|
@ -231,7 +231,9 @@ class ApplicationController < ActionController::Base
|
||||||
DS_SIGN_IN_COUNT: current_administrateur&.sign_in_count,
|
DS_SIGN_IN_COUNT: current_administrateur&.sign_in_count,
|
||||||
DS_CREATED_AT: current_administrateur&.created_at,
|
DS_CREATED_AT: current_administrateur&.created_at,
|
||||||
DS_ACTIVE: current_administrateur&.active,
|
DS_ACTIVE: current_administrateur&.active,
|
||||||
DS_ID: current_administrateur&.id
|
DS_ID: current_administrateur&.id,
|
||||||
|
DS_GESTIONNAIRE_ID: current_gestionnaire&.id,
|
||||||
|
DS_ROLES: logged_user_roles
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
9
app/controllers/attachments_controller.rb
Normal file
9
app/controllers/attachments_controller.rb
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
class AttachmentsController < ApplicationController
|
||||||
|
before_action :authenticate_logged_user!
|
||||||
|
include ActiveStorage::SetBlob
|
||||||
|
|
||||||
|
def show
|
||||||
|
@attachment = @blob.attachments.find(params[:id])
|
||||||
|
@user_can_upload = params[:user_can_upload]
|
||||||
|
end
|
||||||
|
end
|
|
@ -143,7 +143,7 @@ module Gestionnaires
|
||||||
|
|
||||||
def purge_champ_piece_justificative
|
def purge_champ_piece_justificative
|
||||||
@champ = dossier.champs_private.find(params[:champ_id])
|
@champ = dossier.champs_private.find(params[:champ_id])
|
||||||
@champ.piece_justificative_file.purge
|
@champ.piece_justificative_file.purge_later
|
||||||
|
|
||||||
flash.notice = 'La pièce jointe a bien été supprimée.'
|
flash.notice = 'La pièce jointe a bien été supprimée.'
|
||||||
end
|
end
|
||||||
|
|
|
@ -239,7 +239,7 @@ module Users
|
||||||
def purge_champ_piece_justificative
|
def purge_champ_piece_justificative
|
||||||
@champ = dossier.champs.find(params[:champ_id])
|
@champ = dossier.champs.find(params[:champ_id])
|
||||||
|
|
||||||
@champ.piece_justificative_file.purge
|
@champ.piece_justificative_file.purge_later
|
||||||
|
|
||||||
flash.notice = 'La pièce jointe a bien été supprimée.'
|
flash.notice = 'La pièce jointe a bien été supprimée.'
|
||||||
end
|
end
|
||||||
|
|
|
@ -9,6 +9,7 @@ import ReactUJS from '../shared/react-ujs';
|
||||||
import reactComponents from '../shared/react-components';
|
import reactComponents from '../shared/react-components';
|
||||||
|
|
||||||
import '../shared/activestorage/ujs';
|
import '../shared/activestorage/ujs';
|
||||||
|
import '../shared/activestorage/attachment-checker';
|
||||||
import '../shared/rails-ujs-fix';
|
import '../shared/rails-ujs-fix';
|
||||||
import '../shared/safari-11-file-xhr-workaround';
|
import '../shared/safari-11-file-xhr-workaround';
|
||||||
import '../shared/autocomplete';
|
import '../shared/autocomplete';
|
||||||
|
|
77
app/javascript/shared/activestorage/attachment-checker.js
Normal file
77
app/javascript/shared/activestorage/attachment-checker.js
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
import { ajax, delegate } from '@utils';
|
||||||
|
|
||||||
|
addEventListener('turbolinks:load', () => {
|
||||||
|
checker.deactivate();
|
||||||
|
|
||||||
|
const attachments = document.querySelectorAll('[data-attachment-check-url]');
|
||||||
|
|
||||||
|
for (let attachment of attachments) {
|
||||||
|
checker.add(attachment.dataset.attachmentCheckUrl);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
addEventListener('attachment:update', ({ detail: { url } }) => {
|
||||||
|
checker.add(url);
|
||||||
|
});
|
||||||
|
|
||||||
|
delegate('click', '[data-attachment-refresh]', event => {
|
||||||
|
event.preventDefault();
|
||||||
|
checker.check();
|
||||||
|
});
|
||||||
|
|
||||||
|
class AttachmentChecker {
|
||||||
|
urls = new Set();
|
||||||
|
timeout;
|
||||||
|
checks = 0;
|
||||||
|
|
||||||
|
constructor(settings = {}) {
|
||||||
|
this.interval = settings.interval || 5000;
|
||||||
|
this.maxChecks = settings.maxChecks || 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
get isEnabled() {
|
||||||
|
return this.checks <= this.maxChecks;
|
||||||
|
}
|
||||||
|
|
||||||
|
get isActive() {
|
||||||
|
return this.timeout !== undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
add(url) {
|
||||||
|
if (this.isEnabled) {
|
||||||
|
if (!this.isActive) {
|
||||||
|
this.activate();
|
||||||
|
}
|
||||||
|
this.urls.add(url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
check() {
|
||||||
|
let urls = this.urls;
|
||||||
|
this.reset();
|
||||||
|
for (let url of urls) {
|
||||||
|
ajax({ url, type: 'get' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
activate() {
|
||||||
|
clearTimeout(this.timeout);
|
||||||
|
this.timeout = setTimeout(() => {
|
||||||
|
this.checks++;
|
||||||
|
this.check();
|
||||||
|
}, this.interval);
|
||||||
|
}
|
||||||
|
|
||||||
|
deactivate() {
|
||||||
|
this.checks = 0;
|
||||||
|
this.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
reset() {
|
||||||
|
clearTimeout(this.timeout);
|
||||||
|
this.urls = new Set();
|
||||||
|
this.timeout = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const checker = new AttachmentChecker();
|
|
@ -3,7 +3,7 @@ import $ from 'jquery';
|
||||||
import debounce from 'debounce';
|
import debounce from 'debounce';
|
||||||
|
|
||||||
export { debounce };
|
export { debounce };
|
||||||
export const { fire } = Rails;
|
export const { fire, ajax } = Rails;
|
||||||
|
|
||||||
export function show({ classList }) {
|
export function show({ classList }) {
|
||||||
classList.remove('hidden');
|
classList.remove('hidden');
|
||||||
|
|
9
app/jobs/purge_unattached_blobs_job.rb
Normal file
9
app/jobs/purge_unattached_blobs_job.rb
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
class PurgeUnattachedBlobsJob < ApplicationJob
|
||||||
|
queue_as :cron
|
||||||
|
|
||||||
|
def perform(*args)
|
||||||
|
ActiveStorage::Blob.unattached
|
||||||
|
.where('active_storage_blobs.created_at < ?', 24.hours.ago)
|
||||||
|
.find_each(&:purge_later)
|
||||||
|
end
|
||||||
|
end
|
|
@ -40,7 +40,7 @@ class Champs::PieceJustificativeChamp < Champ
|
||||||
end
|
end
|
||||||
|
|
||||||
if errors.present?
|
if errors.present?
|
||||||
piece_justificative_file.purge
|
piece_justificative_file.purge_later
|
||||||
end
|
end
|
||||||
|
|
||||||
errors
|
errors
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
# TODO: once we're using Rails 6, use the hooks on attachments creation
|
||||||
|
# (rather than on blob creation).
|
||||||
|
# This will help to avoid cloberring metadata accidentally (as metadata
|
||||||
|
# are more stable on attachment creation than on blob creation).
|
||||||
module BlobVirusScanner
|
module BlobVirusScanner
|
||||||
extend ActiveSupport::Concern
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
|
|
|
@ -75,14 +75,15 @@ class CarrierwaveActiveStorageMigrationService
|
||||||
# but when the first attachment that references this blob is created.
|
# but when the first attachment that references this blob is created.
|
||||||
def make_blob(uploader, created_at, filename: nil, identify: false)
|
def make_blob(uploader, created_at, filename: nil, identify: false)
|
||||||
content_type = uploader.content_type
|
content_type = uploader.content_type
|
||||||
|
identified = content_type.present? && !identify
|
||||||
|
|
||||||
ActiveStorage::Blob.create(
|
ActiveStorage::Blob.create(
|
||||||
filename: filename || uploader.filename,
|
filename: filename || uploader.filename,
|
||||||
content_type: uploader.content_type,
|
content_type: uploader.content_type,
|
||||||
identified: content_type.present? && !identify,
|
|
||||||
byte_size: uploader.size,
|
byte_size: uploader.size,
|
||||||
checksum: checksum(uploader),
|
checksum: checksum(uploader),
|
||||||
created_at: created_at
|
created_at: created_at,
|
||||||
|
metadata: { identified: identified, virus_scan_result: ActiveStorage::VirusScanner::SAFE }
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -38,7 +38,8 @@ class DossierSearchService
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.to_tsquery(search_terms)
|
def self.to_tsquery(search_terms)
|
||||||
search_terms.strip
|
(search_terms || "")
|
||||||
|
.strip
|
||||||
.gsub(/['?\\:&|!<>\(\)]/, "") # drop disallowed characters
|
.gsub(/['?\\:&|!<>\(\)]/, "") # drop disallowed characters
|
||||||
.split(/\s+/) # split words
|
.split(/\s+/) # split words
|
||||||
.map { |x| "#{x}:*" } # enable prefix matching
|
.map { |x| "#{x}:*" } # enable prefix matching
|
||||||
|
|
|
@ -9,9 +9,18 @@ class PieceJustificativeToChampPieceJointeMigrationService
|
||||||
storage_service.ensure_openstack_copy_possible!(PieceJustificativeUploader)
|
storage_service.ensure_openstack_copy_possible!(PieceJustificativeUploader)
|
||||||
end
|
end
|
||||||
|
|
||||||
def convert_procedure_pjs_to_champ_pjs(procedure)
|
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)
|
types_de_champ_pj = PiecesJustificativesService.types_pj_as_types_de_champ(procedure)
|
||||||
populate_champs_pjs!(procedure, types_de_champ_pj)
|
populate_champs_pjs!(procedure, types_de_champ_pj, &progress)
|
||||||
|
|
||||||
# Only destroy the old types PJ once everything has been safely migrated to
|
# Only destroy the old types PJ once everything has been safely migrated to
|
||||||
# champs PJs. Destroying the types PJ will cascade and destroy the PJs,
|
# champs PJs. Destroying the types PJ will cascade and destroy the PJs,
|
||||||
|
@ -24,7 +33,7 @@ class PieceJustificativeToChampPieceJointeMigrationService
|
||||||
@storage_service ||= CarrierwaveActiveStorageMigrationService.new
|
@storage_service ||= CarrierwaveActiveStorageMigrationService.new
|
||||||
end
|
end
|
||||||
|
|
||||||
def populate_champs_pjs!(procedure, types_de_champ_pj)
|
def populate_champs_pjs!(procedure, types_de_champ_pj, &progress)
|
||||||
procedure.types_de_champ += types_de_champ_pj
|
procedure.types_de_champ += types_de_champ_pj
|
||||||
|
|
||||||
# Unscope to make sure all dossiers are migrated, even the soft-deleted ones
|
# Unscope to make sure all dossiers are migrated, even the soft-deleted ones
|
||||||
|
@ -49,6 +58,8 @@ class PieceJustificativeToChampPieceJointeMigrationService
|
||||||
created_at: dossier.created_at
|
created_at: dossier.created_at
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
yield if block_given?
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
rescue
|
rescue
|
||||||
|
|
|
@ -67,7 +67,24 @@
|
||||||
%p
|
%p
|
||||||
Toute personne ayant la connaissance de ce lien pourra ainsi remplir des dossiers de test sur votre démarche.
|
Toute personne ayant la connaissance de ce lien pourra ainsi remplir des dossiers de test sur votre démarche.
|
||||||
%br
|
%br
|
||||||
|
%h4 Protection des Données personnelles
|
||||||
|
%p
|
||||||
|
À ce moment du processus de création, vous devez informer votre Délégué à la Protection des Données personnelles (DPD).
|
||||||
|
(
|
||||||
|
%a{ href:'https://www.cnil.fr/fr/protection-des-donnees-les-bons-reflexes', target:'_blank' }
|
||||||
|
https://www.cnil.fr/fr/protection-des-donnees-les-bons-reflexes
|
||||||
|
)
|
||||||
|
Si votre démarche propose de collecter des données personnelles, vous devez informer votre DPD. Chaque organisme en a un.
|
||||||
|
%p
|
||||||
|
Ce dernier pourra vous aider dans la finalisation de votre démarche, et vous inviter à vous interroger sur les données collectées, et sur la pertinence de ses dernières.
|
||||||
|
N'oubliez pas : toutes les démarches qui contiennent des données personnelles doivent être consignées dans un registre des traitements :
|
||||||
|
%a{ href:'https://www.cnil.fr/fr/RGDP-le-registre-des-activites-de-traitement', target:'_blank' }
|
||||||
|
https://www.cnil.fr/fr/RGDP-le-registre-des-activites-de-traitement
|
||||||
|
%p
|
||||||
|
Comment faire :
|
||||||
|
vous pouvez soit lui communiquer par email le lien vers la démarche en test, ou bien le nommer « administrateur ». Dans tous les cas, ne publiez votre démarche qu’après avoir eu son avis.
|
||||||
|
|
||||||
|
%br
|
||||||
%h4 Ce que vous pouvez faire lorsque vous êtes en test
|
%h4 Ce que vous pouvez faire lorsque vous êtes en test
|
||||||
%p
|
%p
|
||||||
Profitez de la phase de test pour tester la saisie de dossiers, ainsi que toutes les fonctionnalités associées (instruction, emails automatiques, attestations, etc.).
|
Profitez de la phase de test pour tester la saisie de dossiers, ainsi que toutes les fonctionnalités associées (instruction, emails automatiques, attestations, etc.).
|
||||||
|
|
8
app/views/attachments/show.js.erb
Normal file
8
app/views/attachments/show.js.erb
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
<%= render_to_element(".pj-link[data-attachment-id=\"#{@attachment.id}\"]",
|
||||||
|
partial: 'shared/attachment/show',
|
||||||
|
outer: true,
|
||||||
|
locals: { attachment: @attachment, user_can_upload: @user_can_upload }) %>
|
||||||
|
|
||||||
|
<% if @attachment.virus_scanner.pending? %>
|
||||||
|
<%= fire_event('attachment:update', { url: attachment_url(@attachment.id, { signed_id: @attachment.blob.signed_id, user_can_upload: @user_can_upload }) }.to_json ) %>
|
||||||
|
<% end %>
|
|
@ -13,7 +13,7 @@
|
||||||
|
|
||||||
= form_for @avis, url: gestionnaire_avis_path(@avis), html: { class: 'form' } do |f|
|
= form_for @avis, url: gestionnaire_avis_path(@avis), html: { class: 'form' } do |f|
|
||||||
= f.text_area :answer, rows: 3, placeholder: 'Votre avis', required: true
|
= f.text_area :answer, rows: 3, placeholder: 'Votre avis', required: true
|
||||||
= render partial: "shared/piece_jointe/pj_upload_field", locals: { pj: @avis.piece_justificative_file, object: @avis, form: f }
|
= render partial: "shared/attachment/update", locals: { pj: @avis.piece_justificative_file, object: @avis, form: f }
|
||||||
|
|
||||||
.flex.justify-between.align-baseline
|
.flex.justify-between.align-baseline
|
||||||
%p.confidentiel.flex
|
%p.confidentiel.flex
|
||||||
|
|
|
@ -60,7 +60,7 @@
|
||||||
- if dossier.motivation.present?
|
- if dossier.motivation.present?
|
||||||
%h4 Motivation
|
%h4 Motivation
|
||||||
%p.dossier-motivation= dossier.motivation
|
%p.dossier-motivation= dossier.motivation
|
||||||
= render partial: 'new_user/dossiers/show/download_justificatif', locals: { dossier: dossier }
|
= render partial: 'users/dossiers/show/download_justificatif', locals: { dossier: dossier }
|
||||||
|
|
||||||
- if dossier.attestation.present?
|
- if dossier.attestation.present?
|
||||||
%h4 Attestation
|
%h4 Attestation
|
||||||
|
|
|
@ -1,2 +1,7 @@
|
||||||
<%= render_flash %>
|
<%= render_flash %>
|
||||||
<%= render_to_element('.state-button', partial: "state_button", locals: { dossier: dossier }) %>
|
<%= render_to_element('.state-button', partial: "state_button", locals: { dossier: dossier }) %>
|
||||||
|
|
||||||
|
<% attachment = dossier.justificatif_motivation.attachment %>
|
||||||
|
<% if attachment && attachment.virus_scanner.pending? %>
|
||||||
|
<%= fire_event('attachment:update', { url: attachment_url(attachment.id, { signed_id: attachment.blob.signed_id }) }.to_json ) %>
|
||||||
|
<% end %>
|
||||||
|
|
|
@ -28,6 +28,7 @@
|
||||||
Réponse donnée le #{l(avis.updated_at, format: '%d/%m/%y à %H:%M')}
|
Réponse donnée le #{l(avis.updated_at, format: '%d/%m/%y à %H:%M')}
|
||||||
- else
|
- else
|
||||||
%span.waiting En attente de réponse
|
%span.waiting En attente de réponse
|
||||||
= render partial: 'shared/piece_jointe/pj_link', locals: { pj: avis.piece_justificative_file, user_can_upload: false }
|
- if avis.piece_justificative_file.attached?
|
||||||
|
= render partial: 'shared/attachment/show', locals: { attachment: avis.piece_justificative_file.attachment }
|
||||||
.answer-body
|
.answer-body
|
||||||
= simple_format(avis.answer)
|
= simple_format(avis.answer)
|
||||||
|
|
|
@ -36,7 +36,7 @@ as well as a link to its edit page.
|
||||||
<% if page.resource.invitation_expired? %>
|
<% if page.resource.invitation_expired? %>
|
||||||
<%= link_to "renvoyer l'invitation", reinvite_manager_administrateur_path(page.resource), method: :post, class: "button" %>
|
<%= link_to "renvoyer l'invitation", reinvite_manager_administrateur_path(page.resource), method: :post, class: "button" %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<div>
|
</div>
|
||||||
|
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
|
|
|
@ -34,7 +34,7 @@ as well as a link to its edit page.
|
||||||
<% if dossier.hidden_at.nil? %>
|
<% if dossier.hidden_at.nil? %>
|
||||||
<%= link_to 'Supprimer le dossier', hide_manager_dossier_path(dossier), method: :post, class: 'button', data: { confirm: "Confirmez vous la suppression du dossier ?" } %>
|
<%= link_to 'Supprimer le dossier', hide_manager_dossier_path(dossier), method: :post, class: 'button', data: { confirm: "Confirmez vous la suppression du dossier ?" } %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<section class="main-content__body">
|
<section class="main-content__body">
|
||||||
|
|
|
@ -1,12 +0,0 @@
|
||||||
- if dossier.present?
|
|
||||||
- justificatif = dossier.justificatif_motivation
|
|
||||||
- if justificatif.attached?
|
|
||||||
- if justificatif.virus_scanner.safe?
|
|
||||||
.action
|
|
||||||
= link_to (justificatif), target: '_blank', class: 'button primary' do
|
|
||||||
%span.icon.download
|
|
||||||
Télécharger le justificatif
|
|
||||||
- elsif justificatif.virus_scanner.pending?
|
|
||||||
%p
|
|
||||||
Un justificatif a été joint. L'analyse antivirus de ce document est en cours.
|
|
||||||
= link_to "rafraichir", request.path
|
|
26
app/views/shared/attachment/_show.html.haml
Normal file
26
app/views/shared/attachment/_show.html.haml
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
- should_display_link = attachment.virus_scanner.safe? || !attachment.virus_scanner.started?
|
||||||
|
- user_can_upload = defined?(user_can_upload) ? user_can_upload : false
|
||||||
|
- if should_display_link
|
||||||
|
- attachment_check_url = false
|
||||||
|
- else
|
||||||
|
- attachment_check_url = attachment_url(attachment.id, { signed_id: attachment.blob.signed_id, user_can_upload: user_can_upload })
|
||||||
|
|
||||||
|
.pj-link{ 'data-attachment-id': attachment.id, 'data-attachment-check-url': attachment_check_url }
|
||||||
|
- if should_display_link
|
||||||
|
= link_to url_for(attachment.blob), target: '_blank', rel: 'noopener', title: "Télécharger la pièce jointe" do
|
||||||
|
%span.icon.attachment
|
||||||
|
= attachment.filename.to_s
|
||||||
|
- if !attachment.virus_scanner.started?
|
||||||
|
(ce fichier n’a pas été analysé par notre antivirus, téléchargez-le avec précaution)
|
||||||
|
|
||||||
|
- else
|
||||||
|
= attachment.filename.to_s
|
||||||
|
- if attachment.virus_scanner.pending?
|
||||||
|
(analyse antivirus en cours
|
||||||
|
= link_to "rafraichir", request.path, data: { 'attachment-refresh': true }
|
||||||
|
)
|
||||||
|
- elsif attachment.virus_scanner.infected?
|
||||||
|
- if user_can_upload
|
||||||
|
(virus détecté, merci d’envoyer un autre fichier)
|
||||||
|
- else
|
||||||
|
(virus détecté, le téléchargement de ce fichier est bloqué)
|
|
@ -2,7 +2,7 @@
|
||||||
- if pj.attached?
|
- if pj.attached?
|
||||||
.piece-justificative-actions{ id: "piece_justificative_#{object.id}" }
|
.piece-justificative-actions{ id: "piece_justificative_#{object.id}" }
|
||||||
.piece-justificative-action
|
.piece-justificative-action
|
||||||
= render partial: "shared/piece_jointe/pj_link", locals: { pj: pj, user_can_upload: true }
|
= render partial: "shared/attachment/show", locals: { attachment: pj.attachment, user_can_upload: true }
|
||||||
.piece-justificative-action
|
.piece-justificative-action
|
||||||
= button_tag 'Remplacer', type: 'button', class: 'button small', data: { 'toggle-target': "#champs_#{object.id}" }
|
= button_tag 'Remplacer', type: 'button', class: 'button small', data: { 'toggle-target': "#champs_#{object.id}" }
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
- pj = champ.piece_justificative_file
|
- pj = champ.piece_justificative_file
|
||||||
- if pj.attached?
|
- if pj.attached?
|
||||||
= render partial: "shared/piece_jointe/pj_link", locals: { pj: pj, user_can_upload: false }
|
= render partial: "shared/attachment/show", locals: { attachment: pj.attachment }
|
||||||
- else
|
- else
|
||||||
Pièce justificative non fournie
|
Pièce justificative non fournie
|
||||||
|
|
|
@ -8,4 +8,4 @@
|
||||||
%th.libelle Justificatif :
|
%th.libelle Justificatif :
|
||||||
%td
|
%td
|
||||||
.action
|
.action
|
||||||
= render partial: 'shared/piece_jointe/pj_link', locals: { object: dossier, pj: dossier.justificatif_motivation }
|
= render partial: 'shared/attachment/show', locals: { attachment: dossier.justificatif_motivation.attachment }
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
- if pj.attached?
|
- if pj.attached?
|
||||||
.piece-justificative-actions{ id: "piece_justificative_#{champ.id}" }
|
.piece-justificative-actions{ id: "piece_justificative_#{champ.id}" }
|
||||||
.piece-justificative-action
|
.piece-justificative-action
|
||||||
= render partial: "shared/piece_jointe/pj_link", locals: { pj: pj, user_can_upload: true }
|
= render partial: "shared/attachment/show", locals: { attachment: pj.attachment, user_can_upload: true }
|
||||||
.piece-justificative-action
|
.piece-justificative-action
|
||||||
- if champ.private?
|
- if champ.private?
|
||||||
= link_to 'Supprimer', gestionnaire_champ_purge_champ_piece_justificative_path(procedure_id: champ.dossier.procedure_id, dossier_id: champ.dossier_id, champ_id: champ.id), remote: true, method: :delete, class: 'button small danger'
|
= link_to 'Supprimer', gestionnaire_champ_purge_champ_piece_justificative_path(procedure_id: champ.dossier.procedure_id, dossier_id: champ.dossier_id, champ_id: champ.id), remote: true, method: :delete, class: 'button small danger'
|
||||||
|
|
|
@ -1,20 +0,0 @@
|
||||||
- if pj.attached?
|
|
||||||
.pj-link
|
|
||||||
- if pj.virus_scanner.safe? || !pj.virus_scanner.started?
|
|
||||||
= link_to url_for(pj), target: '_blank', rel: 'noopener', title: "Télécharger la pièce jointe" do
|
|
||||||
%span.icon.attachment
|
|
||||||
= pj.filename.to_s
|
|
||||||
- if !pj.virus_scanner.started?
|
|
||||||
(ce fichier n’a pas été analysé par notre antivirus, téléchargez-le avec précaution)
|
|
||||||
|
|
||||||
- else
|
|
||||||
= pj.filename.to_s
|
|
||||||
- if pj.virus_scanner.pending?
|
|
||||||
(analyse antivirus en cours
|
|
||||||
= link_to "rafraichir", request.path
|
|
||||||
)
|
|
||||||
- elsif pj.virus_scanner.infected?
|
|
||||||
- if user_can_upload
|
|
||||||
(virus détecté, merci d’envoyer un autre fichier)
|
|
||||||
- else
|
|
||||||
(virus détecté, le téléchargement de ce fichier est bloqué)
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
- if dossier.present? && dossier.justificatif_motivation.attached?
|
||||||
|
= render partial: "shared/attachment/show", locals: { attachment: dossier.justificatif_motivation.attachment }
|
|
@ -50,7 +50,7 @@
|
||||||
%h3 Motif de l’acceptation
|
%h3 Motif de l’acceptation
|
||||||
%blockquote= dossier.motivation
|
%blockquote= dossier.motivation
|
||||||
|
|
||||||
= render partial: 'new_user/dossiers/show/download_justificatif', locals: { dossier: dossier }
|
= render partial: 'users/dossiers/show/download_justificatif', locals: { dossier: dossier }
|
||||||
|
|
||||||
- if dossier.attestation.present?
|
- if dossier.attestation.present?
|
||||||
.action
|
.action
|
||||||
|
@ -71,7 +71,7 @@
|
||||||
%h3 Motif du refus
|
%h3 Motif du refus
|
||||||
%blockquote= dossier.motivation
|
%blockquote= dossier.motivation
|
||||||
|
|
||||||
= render partial: 'new_user/dossiers/show/download_justificatif', locals: { dossier: @dossier }
|
= render partial: 'users/dossiers/show/download_justificatif', locals: { dossier: dossier }
|
||||||
.action
|
.action
|
||||||
= link_to 'Envoyer un message à l’administration', messagerie_dossier_url(dossier, anchor: 'new_commentaire'), class: 'button'
|
= link_to 'Envoyer un message à l’administration', messagerie_dossier_url(dossier, anchor: 'new_commentaire'), class: 'button'
|
||||||
|
|
||||||
|
@ -83,7 +83,7 @@
|
||||||
= succeed '.' do
|
= succeed '.' do
|
||||||
%strong sans suite
|
%strong sans suite
|
||||||
|
|
||||||
= render partial: 'new_user/dossiers/show/download_justificatif', locals: { dossier: @dossier }
|
= render partial: 'users/dossiers/show/download_justificatif', locals: { dossier: dossier }
|
||||||
|
|
||||||
- if dossier.motivation.present?
|
- if dossier.motivation.present?
|
||||||
%h3 Motif du classement sans suite
|
%h3 Motif du classement sans suite
|
||||||
|
|
|
@ -17,4 +17,6 @@
|
||||||
Si vous voyez cette page trop souvent, consultez notre aide : #{link_to 'https://faq.demarches-simplifiees.fr/article/34-je-dois-confirmer-mon-compte-a-chaque-connexion', 'https://faq.demarches-simplifiees.fr/article/34-je-dois-confirmer-mon-compte-a-chaque-connexion', target: '_blank', rel: 'noopener' }
|
Si vous voyez cette page trop souvent, consultez notre aide : #{link_to 'https://faq.demarches-simplifiees.fr/article/34-je-dois-confirmer-mon-compte-a-chaque-connexion', 'https://faq.demarches-simplifiees.fr/article/34-je-dois-confirmer-mon-compte-a-chaque-connexion', target: '_blank', rel: 'noopener' }
|
||||||
%br
|
%br
|
||||||
%br
|
%br
|
||||||
En cas de difficultés, nous restons joignables sur #{link_to CONTACT_EMAIL, "mailto:#{CONTACT_EMAIL}"}.
|
En cas de difficultés, nous restons joignables
|
||||||
|
= succeed '.' do
|
||||||
|
= link_to("via ce formulaire", contact_admin_url)
|
||||||
|
|
|
@ -1,21 +1,9 @@
|
||||||
ActiveStorage::Service.url_expires_in = 1.hour
|
ActiveStorage::Service.url_expires_in = 1.hour
|
||||||
|
|
||||||
# We want to run the virus scan on every ActiveStorage attachment,
|
# In Rails 5.2, we have to hook at `on_load` on the blob themeselves, which is
|
||||||
# regardless of its type (user-uploaded document, instructeur-uploaded attestation, form template, etc.)
|
# not ideal.
|
||||||
#
|
#
|
||||||
# To do this, the best place to work on is the ActiveStorage::Attachment
|
# Rails 6 adds support for `.on_load(:active_storage_attachment)`, which is
|
||||||
# objects themselves.
|
# cleaner (as it allows to enqueue the virus scan on attachment creation, rather
|
||||||
#
|
# than on blob creation).
|
||||||
# We have to monkey patch ActiveStorage in order to always run an analyzer.
|
|
||||||
# The way analyzable blob interface work is by running the first accepted analyzer.
|
|
||||||
# This is not what we want for the virus scan. Using analyzer interface is still beneficial
|
|
||||||
# as it takes care of downloading the blob.
|
|
||||||
ActiveStorage::Attached::One.class_eval do
|
|
||||||
def virus_scanner
|
|
||||||
if attached?
|
|
||||||
ActiveStorage::VirusScanner.new(attachment.blob)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
ActiveSupport.on_load(:active_storage_blob) { include BlobVirusScanner }
|
ActiveSupport.on_load(:active_storage_blob) { include BlobVirusScanner }
|
||||||
|
|
|
@ -133,6 +133,8 @@ Rails.application.routes.draw do
|
||||||
post ':position/repetition', to: 'repetition#show', as: :repetition
|
post ':position/repetition', to: 'repetition#show', as: :repetition
|
||||||
end
|
end
|
||||||
|
|
||||||
|
get 'attachments/:id', to: 'attachments#show', as: :attachment
|
||||||
|
|
||||||
get 'tour-de-france' => 'root#tour_de_france'
|
get 'tour-de-france' => 'root#tour_de_france'
|
||||||
get "patron" => "root#patron"
|
get "patron" => "root#patron"
|
||||||
get "accessibilite" => "root#accessibilite"
|
get "accessibilite" => "root#accessibilite"
|
||||||
|
|
|
@ -1,8 +0,0 @@
|
||||||
namespace :'2019_03_13_migrate_pjs_to_champs' do
|
|
||||||
task run: :environment do
|
|
||||||
procedure_id = ENV['PROCEDURE_ID']
|
|
||||||
service = PieceJustificativeToChampPieceJointeMigrationService.new
|
|
||||||
service.ensure_correct_storage_configuration!
|
|
||||||
service.convert_procedure_pjs_to_champ_pjs(Procedure.find(procedure_id))
|
|
||||||
end
|
|
||||||
end
|
|
46
lib/tasks/pieces_justificatives.rake
Normal file
46
lib/tasks/pieces_justificatives.rake
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
require Rails.root.join("lib", "tasks", "task_helper")
|
||||||
|
|
||||||
|
namespace :pieces_justificatives do
|
||||||
|
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
|
||||||
|
|
||||||
|
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
|
|
@ -48,5 +48,16 @@ describe Gestionnaires::RechercheController, type: :controller do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'with no query param it does not crash' do
|
||||||
|
subject { get :index, params: {} }
|
||||||
|
|
||||||
|
it { is_expected.to have_http_status(200) }
|
||||||
|
|
||||||
|
it 'returns 0 dossier' do
|
||||||
|
subject
|
||||||
|
expect(assigns(:dossiers).count).to eq(0)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
51
spec/lib/tasks/pieces_justificatives_spec.rb
Normal file
51
spec/lib/tasks/pieces_justificatives_spec.rb
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
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 'doesn’t migrate procedures not in the range' do
|
||||||
|
expect(procedure_out_of_range.reload.types_de_piece_justificative).to be_present
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -390,12 +390,6 @@ describe Champ do
|
||||||
|
|
||||||
it { expect(champ.piece_justificative_file.virus_scanner.started?).to be_truthy }
|
it { expect(champ.piece_justificative_file.virus_scanner.started?).to be_truthy }
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'and there is no blob' do
|
|
||||||
before { champ.save }
|
|
||||||
|
|
||||||
it { expect(champ.piece_justificative_file.virus_scanner).to be_nil }
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,37 @@
|
||||||
require 'spec_helper'
|
require 'spec_helper'
|
||||||
|
|
||||||
describe CarrierwaveActiveStorageMigrationService do
|
describe CarrierwaveActiveStorageMigrationService do
|
||||||
describe '#hex_to_base64' do
|
let(:service) { CarrierwaveActiveStorageMigrationService.new }
|
||||||
let(:service) { CarrierwaveActiveStorageMigrationService.new }
|
|
||||||
|
|
||||||
|
describe '#hex_to_base64' do
|
||||||
it { expect(service.hex_to_base64('deadbeef')).to eq('3q2+7w==') }
|
it { expect(service.hex_to_base64('deadbeef')).to eq('3q2+7w==') }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe '.make_blob' do
|
||||||
|
let(:pj) { create(:piece_justificative, :rib) }
|
||||||
|
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 '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
|
||||||
end
|
end
|
||||||
|
|
|
@ -9,16 +9,17 @@ describe PieceJustificativeToChampPieceJointeMigrationService do
|
||||||
let(:procedure) { create(:procedure, types_de_piece_justificative: types_pj) }
|
let(:procedure) { create(:procedure, types_de_piece_justificative: types_pj) }
|
||||||
let(:types_pj) { [create(:type_de_piece_justificative)] }
|
let(:types_pj) { [create(:type_de_piece_justificative)] }
|
||||||
|
|
||||||
let!(:dossier) do
|
let!(:dossier) { make_dossier }
|
||||||
create(
|
|
||||||
:dossier,
|
|
||||||
procedure: procedure,
|
|
||||||
pieces_justificatives: pjs
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
let(:pjs) { [] }
|
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
|
def make_pjs
|
||||||
types_pj.map do |tpj|
|
types_pj.map do |tpj|
|
||||||
create(:piece_justificative, :contrat, type_de_piece_justificative: tpj)
|
create(:piece_justificative, :contrat, type_de_piece_justificative: tpj)
|
||||||
|
@ -31,6 +32,22 @@ describe PieceJustificativeToChampPieceJointeMigrationService do
|
||||||
expect(storage_service).to receive(:make_attachment)
|
expect(storage_service).to receive(:make_attachment)
|
||||||
end
|
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 'when conversion succeeds' do
|
||||||
context 'for the procedure' do
|
context 'for the procedure' do
|
||||||
it 'types de champ are created for the "pièces jointes" header and for each PJ' do
|
it 'types de champ are created for the "pièces jointes" header and for each PJ' do
|
||||||
|
@ -114,19 +131,26 @@ describe PieceJustificativeToChampPieceJointeMigrationService do
|
||||||
.to change { dossier.pieces_justificatives.count }
|
.to change { dossier.pieces_justificatives.count }
|
||||||
.to(0)
|
.to(0)
|
||||||
end
|
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
|
end
|
||||||
|
|
||||||
context 'when the dossier is soft-deleted it still gets converted' do
|
context 'when the dossier is soft-deleted it still gets converted' do
|
||||||
let(:pjs) { make_pjs }
|
let(:pjs) { make_pjs }
|
||||||
|
let!(:dossier) { make_dossier(hidden: true) }
|
||||||
let!(:dossier) do
|
|
||||||
create(
|
|
||||||
:dossier,
|
|
||||||
procedure: procedure,
|
|
||||||
pieces_justificatives: pjs,
|
|
||||||
hidden_at: Time.zone.now
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
before { expect_storage_service_to_convert_object }
|
before { expect_storage_service_to_convert_object }
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
require 'rails_helper'
|
require 'rails_helper'
|
||||||
|
|
||||||
describe 'shared/piece_jointe/_pj_link.html.haml', type: :view do
|
describe 'shared/attachment/_show.html.haml', type: :view do
|
||||||
let(:champ) { create(:champ_piece_justificative) }
|
let(:champ) { create(:champ_piece_justificative) }
|
||||||
let(:virus_scan_result) { nil }
|
let(:virus_scan_result) { nil }
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@ describe 'shared/piece_jointe/_pj_link.html.haml', type: :view do
|
||||||
champ.piece_justificative_file.blob.update(metadata: champ.piece_justificative_file.blob.metadata.merge(virus_scan_result: virus_scan_result))
|
champ.piece_justificative_file.blob.update(metadata: champ.piece_justificative_file.blob.metadata.merge(virus_scan_result: virus_scan_result))
|
||||||
end
|
end
|
||||||
|
|
||||||
subject { render 'shared/piece_jointe/pj_link', pj: champ.piece_justificative_file, user_can_upload: false }
|
subject { render 'shared/attachment/show', attachment: champ.piece_justificative_file.attachment }
|
||||||
|
|
||||||
context 'when there is no anti-virus scan' do
|
context 'when there is no anti-virus scan' do
|
||||||
let(:virus_scan_result) { nil }
|
let(:virus_scan_result) { nil }
|
Loading…
Reference in a new issue