commit
897064b0f9
22 changed files with 176 additions and 33 deletions
|
@ -210,6 +210,7 @@ module Instructeurs
|
|||
def telecharger_pjs
|
||||
return head(:forbidden) if !dossier.attachments_downloadable?
|
||||
|
||||
generate_pdf_for_instructeur_export
|
||||
files = ActiveStorage::DownloadableFile.create_list_from_dossier(dossier)
|
||||
|
||||
zipline(files, "dossier-#{dossier.id}.zip")
|
||||
|
@ -232,6 +233,12 @@ module Instructeurs
|
|||
@dossier ||= current_instructeur.dossiers.find(params[:dossier_id])
|
||||
end
|
||||
|
||||
def generate_pdf_for_instructeur_export
|
||||
@include_infos_administration = true
|
||||
pdf = render_to_string(file: 'dossiers/show', formats: [:pdf])
|
||||
dossier.pdf_export_for_instructeur.attach(io: StringIO.open(pdf), filename: "export-#{dossier.id}.pdf", content_type: 'application/pdf')
|
||||
end
|
||||
|
||||
def commentaire_params
|
||||
params.require(:commentaire).permit(:body, :piece_jointe)
|
||||
end
|
||||
|
|
|
@ -395,7 +395,7 @@ type Demarche {
|
|||
after: String
|
||||
|
||||
"""
|
||||
Si présent, permet de filtrer les dossiers archivés ou non
|
||||
Seulement les dossiers archivés.
|
||||
"""
|
||||
archived: Boolean
|
||||
|
||||
|
@ -419,11 +419,26 @@ type Demarche {
|
|||
"""
|
||||
last: Int
|
||||
|
||||
"""
|
||||
Seulement les dossiers pour les révisons avant la révision donnée.
|
||||
"""
|
||||
maxRevision: ID
|
||||
|
||||
"""
|
||||
Seulement les dossiers pour les révisons après la révision donnée.
|
||||
"""
|
||||
minRevision: ID
|
||||
|
||||
"""
|
||||
L’ordre des dossiers.
|
||||
"""
|
||||
order: Order = ASC
|
||||
|
||||
"""
|
||||
Seulement les dossiers pour la révision donnée.
|
||||
"""
|
||||
revision: ID
|
||||
|
||||
"""
|
||||
Dossiers avec statut.
|
||||
"""
|
||||
|
|
|
@ -37,7 +37,10 @@ module Types
|
|||
argument :created_since, GraphQL::Types::ISO8601DateTime, required: false, description: "Dossiers déposés depuis la date."
|
||||
argument :updated_since, GraphQL::Types::ISO8601DateTime, required: false, description: "Dossiers mis à jour depuis la date."
|
||||
argument :state, Types::DossierType::DossierState, required: false, description: "Dossiers avec statut."
|
||||
argument :archived, Boolean, required: false, description: "Si présent, permet de filtrer les dossiers archivés ou non"
|
||||
argument :archived, Boolean, required: false, description: "Seulement les dossiers archivés."
|
||||
argument :revision, ID, required: false, description: "Seulement les dossiers pour la révision donnée."
|
||||
argument :max_revision, ID, required: false, description: "Seulement les dossiers pour les révisons avant la révision donnée."
|
||||
argument :min_revision, ID, required: false, description: "Seulement les dossiers pour les révisons après la révision donnée."
|
||||
end
|
||||
|
||||
field :champ_descriptors, [Types::ChampDescriptorType], null: false, method: :types_de_champ
|
||||
|
@ -63,7 +66,7 @@ module Types
|
|||
Loaders::Association.for(object.class, :revisions).load(object)
|
||||
end
|
||||
|
||||
def dossiers(updated_since: nil, created_since: nil, state: nil, archived: nil, order:)
|
||||
def dossiers(updated_since: nil, created_since: nil, state: nil, archived: nil, revision: nil, max_revision: nil, min_revision: nil, order:)
|
||||
dossiers = object.dossiers.state_not_brouillon.for_api_v2
|
||||
|
||||
if state.present?
|
||||
|
@ -74,6 +77,18 @@ module Types
|
|||
dossiers = dossiers.where(archived: archived)
|
||||
end
|
||||
|
||||
if !revision.nil?
|
||||
dossiers = dossiers.where(revision: find_revision(revision))
|
||||
else
|
||||
if !min_revision.nil?
|
||||
dossiers = dossiers.joins(:revision).where('procedure_revisions.created_at >= ?', find_revision(min_revision).created_at)
|
||||
end
|
||||
|
||||
if !max_revision.nil?
|
||||
dossiers = dossiers.joins(:revision).where('procedure_revisions.created_at <= ?', find_revision(max_revision).created_at)
|
||||
end
|
||||
end
|
||||
|
||||
if updated_since.present?
|
||||
dossiers = dossiers.updated_since(updated_since).order_by_updated_at(order)
|
||||
else
|
||||
|
@ -90,5 +105,12 @@ module Types
|
|||
def self.authorized?(object, context)
|
||||
context.authorized_demarche?(object)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def find_revision(revision)
|
||||
revision_id = GraphQL::Schema::UniqueWithinType.decode(revision).second
|
||||
object.revisions.find(revision_id)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -108,9 +108,9 @@ module Types
|
|||
street_number: object.numero_voie,
|
||||
street_name: object.nom_voie,
|
||||
street_address: object.nom_voie.present? ? [object.numero_voie, object.type_voie, object.nom_voie].compact.join(' ') : nil,
|
||||
postal_code: object.code_postal,
|
||||
city_name: object.localite,
|
||||
city_code: object.code_insee_localite
|
||||
postal_code: object.code_postal.presence || '',
|
||||
city_name: object.localite.presence || '',
|
||||
city_code: object.code_insee_localite.presence || ''
|
||||
}
|
||||
end
|
||||
|
||||
|
|
|
@ -79,6 +79,12 @@ module ApplicationHelper
|
|||
# rubocop:enable Rails/OutputSafety
|
||||
end
|
||||
|
||||
def focus_element(selector)
|
||||
# rubocop:disable Rails/OutputSafety
|
||||
raw("document.querySelector('#{selector}').focus();")
|
||||
# rubocop:enable Rails/OutputSafety
|
||||
end
|
||||
|
||||
def disable_element(selector)
|
||||
# rubocop:disable Rails/OutputSafety
|
||||
raw("document.querySelector('#{selector}').disabled = true;")
|
||||
|
|
|
@ -25,6 +25,7 @@ export default class ProgressBar {
|
|||
const element = getDirectUploadElement(id);
|
||||
if (element) {
|
||||
element.classList.remove(PENDING_CLASS);
|
||||
element.focus();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -32,6 +33,7 @@ export default class ProgressBar {
|
|||
const element = getDirectUploadProgressElement(id);
|
||||
if (element) {
|
||||
element.style.width = `${progress}%`;
|
||||
element.setAttribute('aria-valuenow', progress);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -52,7 +54,7 @@ export default class ProgressBar {
|
|||
|
||||
static render(id, filename) {
|
||||
return `<div id="direct-upload-${id}" class="direct-upload ${PENDING_CLASS}" data-direct-upload-id="${id}">
|
||||
<div class="direct-upload__progress" style="width: 0%"></div>
|
||||
<div role="progressbar" aria-valuemin="0" aria-valuemax="100" class="direct-upload__progress" style="width: 0%"></div>
|
||||
<span class="direct-upload__filename">${filename}</span>
|
||||
</div>`;
|
||||
}
|
||||
|
|
|
@ -16,5 +16,6 @@ delegate('click', TOGGLE_SOURCE_SELECTOR, (evt) => {
|
|||
const targetElements = document.querySelectorAll(targetSelector);
|
||||
for (let target of targetElements) {
|
||||
toggle(target);
|
||||
target.focus();
|
||||
}
|
||||
});
|
||||
|
|
|
@ -1,24 +1,14 @@
|
|||
class ActiveStorage::DownloadableFile
|
||||
def initialize(attached)
|
||||
if using_local_backend?
|
||||
@url = 'file://' + ActiveStorage::Blob.service.path_for(attached.key)
|
||||
else
|
||||
@url = attached.service_url
|
||||
end
|
||||
end
|
||||
|
||||
def url
|
||||
@url
|
||||
end
|
||||
|
||||
def self.create_list_from_dossier(dossier)
|
||||
pjs = PiecesJustificativesService.liste_pieces_justificatives(dossier)
|
||||
pjs.map do |piece_justificative|
|
||||
files = pjs.map do |piece_justificative|
|
||||
[
|
||||
piece_justificative,
|
||||
self.timestamped_filename(piece_justificative)
|
||||
]
|
||||
end
|
||||
files << [dossier.pdf_export_for_instructeur, self.timestamped_filename(dossier.pdf_export_for_instructeur)]
|
||||
files
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
@ -63,6 +63,7 @@ class Dossier < ApplicationRecord
|
|||
has_one :france_connect_information, through: :user
|
||||
|
||||
has_one_attached :justificatif_motivation
|
||||
has_one_attached :pdf_export_for_instructeur
|
||||
|
||||
has_many :champs, -> { root.public_ordered }, inverse_of: :dossier, dependent: :destroy
|
||||
has_many :champs_private, -> { root.private_ordered }, class_name: 'Champ', inverse_of: :dossier, dependent: :destroy
|
||||
|
|
|
@ -4,3 +4,5 @@
|
|||
<% if attachment.virus_scanner.pending? %>
|
||||
<%= fire_event('attachment:update', { url: attachment_url(attachment.id, { signed_id: attachment.blob.signed_id, user_can_upload: true }) }.to_json ) %>
|
||||
<% end %>
|
||||
|
||||
<%= focus_element("button[data-toggle-target=\".attachment-input-#{attachment.id}\"]") %>
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
%ul#print-pj-menu.print-menu.dropdown-content
|
||||
%li
|
||||
- if PiecesJustificativesService.pieces_justificatives_total_size(dossier) < Dossier::TAILLE_MAX_ZIP
|
||||
= link_to "Télécharger toutes les pièces jointes", telecharger_pjs_instructeur_dossier_path(dossier.procedure, dossier), target: "_blank", rel: "noopener", class: "menu-item menu-link"
|
||||
= link_to "Télécharger le dossier et toutes ses pièces jointes", telecharger_pjs_instructeur_dossier_path(dossier.procedure, dossier), target: "_blank", rel: "noopener", class: "menu-item menu-link"
|
||||
- else
|
||||
%p.menu-item Le téléchargement des pièces jointes est désactivé pour les dossiers de plus de #{number_to_human_size Dossier::TAILLE_MAX_ZIP}.
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
= render partial: "shared/attachment/show", locals: { attachment: attachment, user_can_upload: true }
|
||||
- if user_can_destroy
|
||||
.attachment-action
|
||||
= link_to 'Supprimer', attachment_url(attachment.id, { signed_id: attachment.blob.signed_id }), remote: true, method: :delete, class: 'button small danger', data: { disable: true }
|
||||
= link_to 'Supprimer', attachment_url(attachment.id, { signed_id: attachment.blob.signed_id }), remote: true, method: :delete, class: 'button small danger', data: { disable: true }, role: 'button'
|
||||
.attachment-action
|
||||
= button_tag 'Remplacer', type: 'button', class: 'button small', data: { 'toggle-target': ".attachment-input-#{attachment_id}" }
|
||||
|
||||
|
|
4
app/views/shared/champs/address/_show.html.haml
Normal file
4
app/views/shared/champs/address/_show.html.haml
Normal file
|
@ -0,0 +1,4 @@
|
|||
= format_text_value(champ.to_s)
|
||||
- if champ.data.present?
|
||||
Code INSEE :
|
||||
= champ.data['city_code']
|
4
app/views/shared/champs/communes/_show.html.haml
Normal file
4
app/views/shared/champs/communes/_show.html.haml
Normal file
|
@ -0,0 +1,4 @@
|
|||
= format_text_value(champ.to_s)
|
||||
- if champ.external_id.present?
|
||||
Code INSEE :
|
||||
= champ.external_id
|
4
app/views/shared/champs/regions/_show.html.haml
Normal file
4
app/views/shared/champs/regions/_show.html.haml
Normal file
|
@ -0,0 +1,4 @@
|
|||
- if champ.external_id.present?
|
||||
= format_text_value("#{champ.external_id} - #{champ}")
|
||||
- else
|
||||
= format_text_value(champ.to_s)
|
|
@ -36,6 +36,12 @@
|
|||
= render partial: "shared/champs/textarea/show", locals: { champ: c }
|
||||
- when TypeDeChamp.type_champs.fetch(:annuaire_education)
|
||||
= render partial: "shared/champs/annuaire_education/show", locals: { champ: c }
|
||||
- when TypeDeChamp.type_champs.fetch(:address)
|
||||
= render partial: "shared/champs/address/show", locals: { champ: c }
|
||||
- when TypeDeChamp.type_champs.fetch(:communes)
|
||||
= render partial: "shared/champs/communes/show", locals: { champ: c }
|
||||
- when TypeDeChamp.type_champs.fetch(:regions)
|
||||
= render partial: "shared/champs/regions/show", locals: { champ: c }
|
||||
- when TypeDeChamp.type_champs.fetch(:date)
|
||||
= c.to_s
|
||||
- when TypeDeChamp.type_champs.fetch(:datetime)
|
||||
|
|
|
@ -4,4 +4,13 @@ Sentry.init do |config|
|
|||
config.enabled_environments = ['production']
|
||||
config.breadcrumbs_logger = [:active_support_logger]
|
||||
config.traces_sample_rate = ENV['SENTRY_ENABLED'] == 'enabled' ? 0.001 : nil
|
||||
config.excluded_exceptions += [
|
||||
# Ignore exceptions caught by ActiveJob.retry_on
|
||||
# https://github.com/getsentry/sentry-ruby/issues/1347
|
||||
'Excon::Error::BadRequest',
|
||||
'ActiveStorage::IntegrityError',
|
||||
'VirusScannerJob::FileNotAnalyzedYetError',
|
||||
'TitreIdentiteWatermarkJob::WatermarkFileNotScannedYetError',
|
||||
'APIEntreprise::API::Error::TimedOut'
|
||||
]
|
||||
end
|
||||
|
|
|
@ -3,7 +3,7 @@ namespace :after_party do
|
|||
task remove_invalid_geometries: :environment do
|
||||
puts "Running deploy task 'remove_invalid_geometries'"
|
||||
|
||||
geo_areas = GeoArea.where(source: :selection_utilisateur)
|
||||
geo_areas = GeoArea.where(source: :selection_utilisateur).includes(champ: [:geo_areas, :type_de_champ])
|
||||
progress = ProgressReport.new(geo_areas.count)
|
||||
geo_areas.find_each do |geo_area|
|
||||
if !geo_area.valid?
|
||||
|
|
|
@ -234,6 +234,64 @@ describe API::V2::GraphqlController do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "filter by minRevision" do
|
||||
let(:query) do
|
||||
"{
|
||||
demarche(number: #{procedure.id}) {
|
||||
id
|
||||
number
|
||||
dossiers(minRevision: \"#{procedure.revisions.first.to_typed_id}\") {
|
||||
nodes {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
}"
|
||||
end
|
||||
|
||||
it "should be returned" do
|
||||
expect(gql_errors).to eq(nil)
|
||||
expect(gql_data).to eq(demarche: {
|
||||
id: procedure.to_typed_id,
|
||||
number: procedure.id,
|
||||
dossiers: {
|
||||
nodes: procedure.dossiers.order(:created_at).map do |dossier|
|
||||
{ id: dossier.to_typed_id }
|
||||
end
|
||||
}
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
context "filter by maxRevision" do
|
||||
let(:query) do
|
||||
"{
|
||||
demarche(number: #{procedure.id}) {
|
||||
id
|
||||
number
|
||||
dossiers(maxRevision: \"#{procedure.revisions.last.to_typed_id}\") {
|
||||
nodes {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
}"
|
||||
end
|
||||
|
||||
it "should be returned" do
|
||||
expect(gql_errors).to eq(nil)
|
||||
expect(gql_data).to eq(demarche: {
|
||||
id: procedure.to_typed_id,
|
||||
number: procedure.id,
|
||||
dossiers: {
|
||||
nodes: procedure.dossiers.order(:created_at).map do |dossier|
|
||||
{ id: dossier.to_typed_id }
|
||||
end
|
||||
}
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "dossier" do
|
||||
|
|
|
@ -201,6 +201,15 @@ FactoryBot.define do
|
|||
end
|
||||
end
|
||||
|
||||
trait :with_pdf_export do
|
||||
after(:create) do |dossier, _evaluator|
|
||||
dossier.pdf_export_for_instructeur.attach(
|
||||
io: StringIO.new('Hello World'),
|
||||
filename: 'export.pdf'
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
trait :with_justificatif do
|
||||
after(:create) do |dossier, _evaluator|
|
||||
dossier.justificatif_motivation.attach(
|
||||
|
|
|
@ -153,7 +153,7 @@ feature 'Instructing a dossier:' do
|
|||
|
||||
scenario 'A instructeur can download an archive containing a single attachment' do
|
||||
find(:css, '.attached').click
|
||||
click_on 'Télécharger toutes les pièces jointes'
|
||||
click_on 'Télécharger le dossier et toutes ses pièces jointes'
|
||||
# For some reason, clicking the download link does not trigger the download in the headless browser ;
|
||||
# So we need to go to the download link directly
|
||||
visit telecharger_pjs_instructeur_dossier_path(procedure, dossier)
|
||||
|
@ -162,10 +162,11 @@ feature 'Instructing a dossier:' do
|
|||
files = ZipTricks::FileReader.read_zip_structure(io: File.open(DownloadHelpers.download))
|
||||
|
||||
expect(DownloadHelpers.download).to include "dossier-#{dossier.id}.zip"
|
||||
expect(files.size).to be 2
|
||||
expect(files.size).to be 3
|
||||
expect(files[0].filename.include?('piece_justificative_0')).to be_truthy
|
||||
expect(files[0].uncompressed_size).to be File.size(path)
|
||||
expect(files[1].filename.include?('horodatage/operation')).to be_truthy
|
||||
expect(files[2].filename.include?('dossier/export')).to be_truthy
|
||||
end
|
||||
|
||||
scenario 'A instructeur can download an archive containing several identical attachments' do
|
||||
|
@ -176,13 +177,14 @@ feature 'Instructing a dossier:' do
|
|||
files = ZipTricks::FileReader.read_zip_structure(io: File.open(DownloadHelpers.download))
|
||||
|
||||
expect(DownloadHelpers.download).to include "dossier-#{dossier.id}.zip"
|
||||
expect(files.size).to be 3
|
||||
expect(files.size).to be 4
|
||||
expect(files[0].filename.include?('piece_justificative_0')).to be_truthy
|
||||
expect(files[1].filename.include?('piece_justificative_0')).to be_truthy
|
||||
expect(files[0].filename).not_to eq files[1].filename
|
||||
expect(files[0].uncompressed_size).to be File.size(path)
|
||||
expect(files[1].uncompressed_size).to be File.size(path)
|
||||
expect(files[2].filename.include?('horodatage/operation')).to be_truthy
|
||||
expect(files[3].filename.include?('dossier/export')).to be_truthy
|
||||
end
|
||||
|
||||
before { DownloadHelpers.clear_downloads }
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
describe ActiveStorage::DownloadableFile do
|
||||
let(:dossier) { create(:dossier, :en_construction) }
|
||||
let(:dossier) { create(:dossier, :en_construction, :with_pdf_export) }
|
||||
|
||||
subject(:list) { ActiveStorage::DownloadableFile.create_list_from_dossier(dossier) }
|
||||
|
||||
describe 'create_list_from_dossier' do
|
||||
context 'when no piece_justificative is present' do
|
||||
it { expect(list).to match([]) }
|
||||
it { expect(list.length).to eq 1 }
|
||||
it { expect(list.first[0].record_type).to eq "Dossier" }
|
||||
end
|
||||
|
||||
context 'when there is a piece_justificative' do
|
||||
|
@ -13,7 +14,7 @@ describe ActiveStorage::DownloadableFile do
|
|||
dossier.champs << create(:champ_piece_justificative, :with_piece_justificative_file, dossier: dossier)
|
||||
end
|
||||
|
||||
it { expect(list.length).to eq 1 }
|
||||
it { expect(list.length).to eq 2 }
|
||||
end
|
||||
|
||||
context 'when there is a private piece_justificative' do
|
||||
|
@ -21,7 +22,7 @@ describe ActiveStorage::DownloadableFile do
|
|||
dossier.champs_private << create(:champ_piece_justificative, :with_piece_justificative_file, private: true, dossier: dossier)
|
||||
end
|
||||
|
||||
it { expect(list.length).to eq 1 }
|
||||
it { expect(list.length).to eq 2 }
|
||||
end
|
||||
|
||||
context 'when there is a repetition bloc' do
|
||||
|
@ -30,7 +31,7 @@ describe ActiveStorage::DownloadableFile do
|
|||
end
|
||||
|
||||
it 'should have 4 piece_justificatives' do
|
||||
expect(list.size).to eq 4
|
||||
expect(list.size).to eq 5
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -39,7 +40,7 @@ describe ActiveStorage::DownloadableFile do
|
|||
dossier.commentaires << create(:commentaire, dossier: dossier)
|
||||
end
|
||||
|
||||
it { expect(list.length).to eq 0 }
|
||||
it { expect(list.length).to eq 1 }
|
||||
end
|
||||
|
||||
context 'when there is a message with an attachment' do
|
||||
|
@ -47,7 +48,7 @@ describe ActiveStorage::DownloadableFile do
|
|||
dossier.commentaires << create(:commentaire, :with_file, dossier: dossier)
|
||||
end
|
||||
|
||||
it { expect(list.length).to eq 1 }
|
||||
it { expect(list.length).to eq 2 }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue