Merge pull request #10548 from demarches-simplifiees/choose_pjs_to_export
ETQ Instructeur, je peux choisir les pjs que j'exporte
This commit is contained in:
commit
12c174f6cf
42 changed files with 1014 additions and 889 deletions
|
@ -51,6 +51,7 @@
|
|||
&.no-list {
|
||||
ul {
|
||||
list-style: none !important;
|
||||
padding-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -175,6 +175,30 @@
|
|||
}
|
||||
}
|
||||
|
||||
&-file-image-line {
|
||||
&:before,
|
||||
&:after {
|
||||
-webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3E%3Cpath d='M15 8V4H5V20H19V8H15ZM3 2.9918C3 2.44405 3.44749 2 3.9985 2H16L20.9997 7L21 20.9925C21 21.5489 20.5551 22 20.0066 22H3.9934C3.44476 22 3 21.5447 3 21.0082V2.9918ZM11 9.5C11 10.3284 10.3284 11 9.5 11C8.67157 11 8 10.3284 8 9.5C8 8.67157 8.67157 8 9.5 8C10.3284 8 11 8.67157 11 9.5ZM17.5 17L13.5 10L8 17H17.5Z'%3E%3C/path%3E%3C/svg%3E");
|
||||
mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3E%3Cpath d='M15 8V4H5V20H19V8H15ZM3 2.9918C3 2.44405 3.44749 2 3.9985 2H16L20.9997 7L21 20.9925C21 21.5489 20.5551 22 20.0066 22H3.9934C3.44476 22 3 21.5447 3 21.0082V2.9918ZM11 9.5C11 10.3284 10.3284 11 9.5 11C8.67157 11 8 10.3284 8 9.5C8 8.67157 8.67157 8 9.5 8C10.3284 8 11 8.67157 11 9.5ZM17.5 17L13.5 10L8 17H17.5Z'%3E%3C/path%3E%3C/svg%3E");
|
||||
}
|
||||
}
|
||||
|
||||
&-folder-line {
|
||||
&:before,
|
||||
&:after {
|
||||
-webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3E%3Cpath d='M4 5V19H20V7H11.5858L9.58579 5H4ZM12.4142 5H21C21.5523 5 22 5.44772 22 6V20C22 20.5523 21.5523 21 21 21H3C2.44772 21 2 20.5523 2 20V4C2 3.44772 2.44772 3 3 3H10.4142L12.4142 5Z'%3E%3C/path%3E%3C/svg%3E");
|
||||
mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3E%3Cpath d='M4 5V19H20V7H11.5858L9.58579 5H4ZM12.4142 5H21C21.5523 5 22 5.44772 22 6V20C22 20.5523 21.5523 21 21 21H3C2.44772 21 2 20.5523 2 20V4C2 3.44772 2.44772 3 3 3H10.4142L12.4142 5Z'%3E%3C/path%3E%3C/svg%3E");
|
||||
}
|
||||
}
|
||||
|
||||
&-folder-zip-line {
|
||||
&:before,
|
||||
&:after {
|
||||
-webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3E%3Cpath d='M10.4142 3L12.4142 5H21C21.5523 5 22 5.44772 22 6V20C22 20.5523 21.5523 21 21 21H3C2.44772 21 2 20.5523 2 20V4C2 3.44772 2.44772 3 3 3H10.4142ZM18 18H14V15H16V13H14V11H16V9H14V7H11.5858L9.58579 5H4V19H20V7H16V9H18V11H16V13H18V18Z'%3E%3C/path%3E%3C/svg%3E");
|
||||
mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3E%3Cpath d='M10.4142 3L12.4142 5H21C21.5523 5 22 5.44772 22 6V20C22 20.5523 21.5523 21 21 21H3C2.44772 21 2 20.5523 2 20V4C2 3.44772 2.44772 3 3 3H10.4142ZM18 18H14V15H16V13H14V11H16V9H14V7H11.5858L9.58579 5H4V19H20V7H16V9H18V11H16V13H18V18Z'%3E%3C/path%3E%3C/svg%3E");
|
||||
}
|
||||
}
|
||||
|
||||
&-intermediate-circle-fill {
|
||||
&:before,
|
||||
&:after {
|
||||
|
@ -191,6 +215,14 @@
|
|||
}
|
||||
}
|
||||
|
||||
&-pdf-2-line {
|
||||
&:before,
|
||||
&:after {
|
||||
-webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3E%3Cpath d='M5 4H15V8H19V20H5V4ZM3.9985 2C3.44749 2 3 2.44405 3 2.9918V21.0082C3 21.5447 3.44476 22 3.9934 22H20.0066C20.5551 22 21 21.5489 21 20.9925L20.9997 7L16 2H3.9985ZM10.4999 7.5C10.4999 9.07749 10.0442 10.9373 9.27493 12.6534C8.50287 14.3757 7.46143 15.8502 6.37524 16.7191L7.55464 18.3321C10.4821 16.3804 13.7233 15.0421 16.8585 15.49L17.3162 13.5513C14.6435 12.6604 12.4999 9.98994 12.4999 7.5H10.4999ZM11.0999 13.4716C11.3673 12.8752 11.6042 12.2563 11.8037 11.6285C12.2753 12.3531 12.8553 13.0182 13.5101 13.5953C12.5283 13.7711 11.5665 14.0596 10.6352 14.4276C10.7999 14.1143 10.9551 13.7948 11.0999 13.4716Z'%3E%3C/path%3E%3C/svg%3E");
|
||||
mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3E%3Cpath d='M5 4H15V8H19V20H5V4ZM3.9985 2C3.44749 2 3 2.44405 3 2.9918V21.0082C3 21.5447 3.44476 22 3.9934 22H20.0066C20.5551 22 21 21.5489 21 20.9925L20.9997 7L16 2H3.9985ZM10.4999 7.5C10.4999 9.07749 10.0442 10.9373 9.27493 12.6534C8.50287 14.3757 7.46143 15.8502 6.37524 16.7191L7.55464 18.3321C10.4821 16.3804 13.7233 15.0421 16.8585 15.49L17.3162 13.5513C14.6435 12.6604 12.4999 9.98994 12.4999 7.5H10.4999ZM11.0999 13.4716C11.3673 12.8752 11.6042 12.2563 11.8037 11.6285C12.2753 12.3531 12.8553 13.0182 13.5101 13.5953C12.5283 13.7711 11.5665 14.0596 10.6352 14.4276C10.7999 14.1143 10.9551 13.7948 11.0999 13.4716Z'%3E%3C/path%3E%3C/svg%3E");
|
||||
}
|
||||
}
|
||||
|
||||
&-underline {
|
||||
&:before,
|
||||
&:after {
|
||||
|
|
|
@ -22,5 +22,5 @@
|
|||
= link_to download_export_path(export_template_id: export_template.id), role: 'menuitem', data: { turbo_method: :post, turbo: true } do
|
||||
= "Exporter à partir du modèle #{export_template.name}"
|
||||
- menu.with_item do
|
||||
= link_to new_instructeur_export_template_path(procedure_id: params[:procedure_id]), role: 'menuitem' do
|
||||
= link_to [:new, :instructeur, @procedure, :export_template], role: 'menuitem' do
|
||||
Ajouter un modèle d'export
|
||||
|
|
|
@ -1,21 +1,18 @@
|
|||
module Instructeurs
|
||||
class ExportTemplatesController < InstructeurController
|
||||
before_action :set_procedure
|
||||
before_action :set_groupe_instructeur, only: [:create, :update]
|
||||
before_action :set_procedure_and_groupe_instructeurs
|
||||
before_action :set_export_template, only: [:edit, :update, :destroy]
|
||||
before_action :set_groupe_instructeurs
|
||||
before_action :set_all_pj
|
||||
before_action :ensure_legitimate_groupe_instructeur, only: [:create, :update]
|
||||
|
||||
def new
|
||||
@export_template = ExportTemplate.new(kind: 'zip', groupe_instructeur: @groupe_instructeurs.first)
|
||||
@export_template.set_default_values
|
||||
@export_template = ExportTemplate.default(groupe_instructeur: @groupe_instructeurs.first)
|
||||
end
|
||||
|
||||
def create
|
||||
@export_template = @groupe_instructeur.export_templates.build(export_template_params)
|
||||
@export_template.assign_pj_names(pj_params)
|
||||
@export_template = ExportTemplate.new(export_template_params)
|
||||
|
||||
if @export_template.save
|
||||
redirect_to exports_instructeur_procedure_path(procedure: @procedure), notice: "Le modèle d'export #{@export_template.name} a bien été créé"
|
||||
redirect_to [:exports, :instructeur, @procedure], notice: "Le modèle d'export #{@export_template.name} a bien été créé"
|
||||
else
|
||||
flash[:alert] = @export_template.errors.full_messages
|
||||
render :new
|
||||
|
@ -26,11 +23,8 @@ module Instructeurs
|
|||
end
|
||||
|
||||
def update
|
||||
@export_template.assign_attributes(export_template_params)
|
||||
@export_template.groupe_instructeur = @groupe_instructeur
|
||||
@export_template.assign_pj_names(pj_params)
|
||||
if @export_template.save
|
||||
redirect_to exports_instructeur_procedure_path(procedure: @procedure), notice: "Le modèle d'export #{@export_template.name} a bien été modifié"
|
||||
if @export_template.update(export_template_params)
|
||||
redirect_to [:exports, :instructeur, @procedure], notice: "Le modèle d'export #{@export_template.name} a bien été modifié"
|
||||
else
|
||||
flash[:alert] = @export_template.errors.full_messages
|
||||
render :edit
|
||||
|
@ -39,62 +33,40 @@ module Instructeurs
|
|||
|
||||
def destroy
|
||||
if @export_template.destroy
|
||||
redirect_to exports_instructeur_procedure_path(procedure: @procedure), notice: "Le modèle d'export #{@export_template.name} a bien été supprimé"
|
||||
redirect_to [:exports, :instructeur, @procedure], notice: "Le modèle d'export #{@export_template.name} a bien été supprimé"
|
||||
else
|
||||
redirect_to exports_instructeur_procedure_path(procedure: @procedure), alert: "Le modèle d'export #{@export_template.name} n'a pu être supprimé"
|
||||
redirect_to [:exports, :instructeur, @procedure], alert: "Le modèle d'export #{@export_template.name} n'a pu être supprimé"
|
||||
end
|
||||
end
|
||||
|
||||
def preview
|
||||
set_groupe_instructeur
|
||||
@export_template = @groupe_instructeur.export_templates.build(export_template_params)
|
||||
@export_template.assign_pj_names(pj_params)
|
||||
export_template = ExportTemplate.new(export_template_params)
|
||||
|
||||
@sample_dossier = @procedure.dossier_for_preview(current_instructeur)
|
||||
|
||||
render turbo_stream: turbo_stream.replace('preview', partial: 'preview', locals: { export_template: @export_template, procedure: @procedure, dossier: @sample_dossier })
|
||||
render turbo_stream: turbo_stream.replace('preview', partial: 'preview', locals: { export_template: })
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def export_template_params
|
||||
params.require(:export_template).permit(*export_params)
|
||||
params.require(:export_template)
|
||||
.permit(:name, :kind, :groupe_instructeur_id, dossier_folder: [:enabled, :template], export_pdf: [:enabled, :template], pjs: [:stable_id, :enabled, :template])
|
||||
end
|
||||
|
||||
def set_procedure
|
||||
@procedure = current_instructeur.procedures.find params[:procedure_id]
|
||||
Sentry.configure_scope do |scope|
|
||||
scope.set_tags(procedure: @procedure.id)
|
||||
end
|
||||
def set_procedure_and_groupe_instructeurs
|
||||
@procedure = current_instructeur.procedures.find(params[:procedure_id])
|
||||
@groupe_instructeurs = current_instructeur.groupe_instructeurs.where(procedure: @procedure)
|
||||
|
||||
Sentry.configure_scope { |scope| scope.set_tags(procedure: @procedure.id) }
|
||||
end
|
||||
|
||||
def set_export_template
|
||||
@export_template = current_instructeur.export_templates.find(params[:id])
|
||||
end
|
||||
|
||||
def set_groupe_instructeur
|
||||
@groupe_instructeur = @procedure.groupe_instructeurs.find(params.require(:export_template)[:groupe_instructeur_id])
|
||||
end
|
||||
def ensure_legitimate_groupe_instructeur
|
||||
return if export_template_params[:groupe_instructeur_id].in?(@groupe_instructeurs.map { _1.id.to_s })
|
||||
|
||||
def set_groupe_instructeurs
|
||||
@groupe_instructeurs = current_instructeur.groupe_instructeurs.where(procedure: @procedure)
|
||||
end
|
||||
|
||||
def set_all_pj
|
||||
@all_pj ||= @procedure.exportables_pieces_jointes
|
||||
end
|
||||
|
||||
def export_params
|
||||
[:name, :kind, :tiptap_default_dossier_directory, :tiptap_pdf_name]
|
||||
end
|
||||
|
||||
def pj_params
|
||||
@procedure = current_instructeur.procedures.find params[:procedure_id]
|
||||
pj_params = []
|
||||
@all_pj.each do |pj|
|
||||
pj_params << "tiptap_pj_#{pj.stable_id}".to_sym
|
||||
end
|
||||
params.require(:export_template).permit(*pj_params)
|
||||
redirect_to [:exports, :instructeur, @procedure], alert: 'Vous n’avez pas le droit de créer un modèle d’export pour ce groupe'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -150,4 +150,6 @@ module ApplicationHelper
|
|||
.map { |word| word[0].upcase }
|
||||
.join
|
||||
end
|
||||
|
||||
def asterisk = render(EditableChamp::AsteriskMandatoryComponent.new)
|
||||
end
|
||||
|
|
19
app/javascript/controllers/hide_target_controller.ts
Normal file
19
app/javascript/controllers/hide_target_controller.ts
Normal file
|
@ -0,0 +1,19 @@
|
|||
import { Controller } from '@hotwired/stimulus';
|
||||
|
||||
export class HideTargetController extends Controller {
|
||||
static targets = ['source', 'toHide'];
|
||||
declare readonly toHideTargets: HTMLDivElement[];
|
||||
declare readonly sourceTargets: HTMLInputElement[];
|
||||
|
||||
connect() {
|
||||
this.sourceTargets.forEach((source) => {
|
||||
source.addEventListener('click', this.handleInput.bind(this));
|
||||
});
|
||||
}
|
||||
|
||||
handleInput() {
|
||||
this.toHideTargets.forEach((toHide) => {
|
||||
toHide.classList.toggle('fr-hidden');
|
||||
});
|
||||
}
|
||||
}
|
|
@ -10,7 +10,8 @@ import { createEditor } from '../../shared/tiptap/editor';
|
|||
export class TiptapController extends ApplicationController {
|
||||
static targets = ['editor', 'input', 'button', 'tag'];
|
||||
static values = {
|
||||
insertAfterTag: { type: String, default: '' }
|
||||
insertAfterTag: { type: String, default: '' },
|
||||
attributes: { type: Object, default: {} }
|
||||
};
|
||||
|
||||
declare editorTarget: Element;
|
||||
|
@ -18,6 +19,7 @@ export class TiptapController extends ApplicationController {
|
|||
declare buttonTargets: HTMLButtonElement[];
|
||||
declare tagTargets: HTMLElement[];
|
||||
declare insertAfterTagValue: string;
|
||||
declare attributesValue: Record<string, string>;
|
||||
|
||||
#initializing = true;
|
||||
#editor?: Editor;
|
||||
|
@ -28,6 +30,7 @@ export class TiptapController extends ApplicationController {
|
|||
content: this.content,
|
||||
tags: this.tags,
|
||||
buttons: this.menuButtons,
|
||||
attributes: { class: 'fr-input', ...this.attributesValue },
|
||||
onChange: ({ editor }) => {
|
||||
for (const button of this.buttonTargets) {
|
||||
const action = getAction(editor, button);
|
||||
|
|
20
app/javascript/controllers/tiptap_to_template_controller.ts
Normal file
20
app/javascript/controllers/tiptap_to_template_controller.ts
Normal file
|
@ -0,0 +1,20 @@
|
|||
import { Controller } from '@hotwired/stimulus';
|
||||
|
||||
export class TiptapToTemplateController extends Controller {
|
||||
static targets = ['output', 'trigger'];
|
||||
|
||||
declare readonly outputTarget: HTMLElement;
|
||||
declare readonly triggerTarget: HTMLButtonElement;
|
||||
|
||||
connect() {
|
||||
this.triggerTarget.addEventListener('click', this.handleClick.bind(this));
|
||||
}
|
||||
|
||||
handleClick() {
|
||||
const template = this.element.querySelector('.tiptap.ProseMirror p');
|
||||
|
||||
if (template) {
|
||||
this.outputTarget.innerHTML = template.innerHTML;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -33,6 +33,7 @@ export function createEditor({
|
|||
content,
|
||||
tags,
|
||||
buttons,
|
||||
attributes,
|
||||
onChange
|
||||
}: {
|
||||
editorElement: Element;
|
||||
|
@ -40,8 +41,15 @@ export function createEditor({
|
|||
tags: TagSchema[];
|
||||
buttons: string[];
|
||||
onChange(change: { editor: Editor }): void;
|
||||
attributes?: Record<string, string>;
|
||||
}): Editor {
|
||||
const options = getEditorOptions(editorElement, tags, buttons, content);
|
||||
const options = getEditorOptions(
|
||||
editorElement,
|
||||
tags,
|
||||
buttons,
|
||||
content,
|
||||
attributes
|
||||
);
|
||||
const editor = new Editor(options);
|
||||
editor.on('transaction', onChange);
|
||||
return editor;
|
||||
|
@ -51,7 +59,8 @@ function getEditorOptions(
|
|||
element: Element,
|
||||
tags: TagSchema[],
|
||||
actions: string[],
|
||||
content?: JSONContent
|
||||
content?: JSONContent,
|
||||
attributes?: Record<string, string>
|
||||
): Partial<EditorOptions> {
|
||||
const extensions: Extensions = [];
|
||||
for (const action of actions) {
|
||||
|
@ -123,7 +132,7 @@ function getEditorOptions(
|
|||
return {
|
||||
element,
|
||||
content,
|
||||
editorProps: { attributes: { class: 'fr-input' } },
|
||||
editorProps: { attributes },
|
||||
extensions: [
|
||||
actions.includes('title') ? DocumentWithHeader : Document,
|
||||
Hystory,
|
||||
|
|
|
@ -11,14 +11,26 @@ module PiecesJointesListConcern
|
|||
pieces_jointes(exclude_titre_identite: true)
|
||||
end
|
||||
|
||||
def exportables_pieces_jointes_for_all_versions
|
||||
pieces_jointes(
|
||||
exclude_titre_identite: true,
|
||||
revision: revisions
|
||||
).sort_by { - _1.id }.uniq(&:stable_id)
|
||||
end
|
||||
|
||||
def outdated_exportables_pieces_jointes
|
||||
exportables_pieces_jointes_for_all_versions - exportables_pieces_jointes
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def pieces_jointes(
|
||||
exclude_titre_identite: false,
|
||||
public_only: false,
|
||||
wrap_with_parent: false
|
||||
wrap_with_parent: false,
|
||||
revision: active_revision
|
||||
)
|
||||
coordinates = active_revision.revision_types_de_champ
|
||||
coordinates = ProcedureRevisionTypeDeChamp.where(revision:)
|
||||
.includes(:type_de_champ, revision_types_de_champ: :type_de_champ)
|
||||
|
||||
coordinates = coordinates.public_only if public_only
|
||||
|
|
70
app/models/export_item.rb
Normal file
70
app/models/export_item.rb
Normal file
|
@ -0,0 +1,70 @@
|
|||
class ExportItem
|
||||
include TagsSubstitutionConcern
|
||||
DOSSIER_STATE = Dossier.states.fetch(:en_construction)
|
||||
FORMAT_DATE = "%Y-%m-%d".freeze
|
||||
|
||||
attr_reader :template, :enabled, :stable_id
|
||||
|
||||
def initialize(template:, enabled: true, stable_id: nil)
|
||||
@template, @enabled, @stable_id = template, enabled, stable_id
|
||||
end
|
||||
|
||||
def self.default(prefix:, enabled: true, stable_id: nil)
|
||||
new(template: prefix_dossier_id(prefix), enabled:, stable_id:)
|
||||
end
|
||||
|
||||
def self.default_pj(tdc)
|
||||
default(prefix: tdc.libelle_as_filename, enabled: false, stable_id: tdc.stable_id)
|
||||
end
|
||||
|
||||
def enabled? = enabled
|
||||
|
||||
def template_json = template.to_json
|
||||
|
||||
def template_string = TiptapService.new.to_texts_and_tags(template)
|
||||
|
||||
def path(dossier, attachment: nil, row_index: nil, index: nil)
|
||||
used_tags = TiptapService.used_tags_and_libelle_for(template)
|
||||
substitutions = tags_substitutions(used_tags, dossier, escape: false, memoize: true)
|
||||
substitutions['original-filename'] = attachment.filename.base if attachment
|
||||
|
||||
TiptapService.new.to_texts_and_tags(template, substitutions) + suffix(attachment, row_index, index)
|
||||
end
|
||||
|
||||
def ==(other)
|
||||
self.class == other.class &&
|
||||
template == other.template &&
|
||||
enabled == other.enabled &&
|
||||
stable_id == other.stable_id
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def self.prefix_dossier_id(prefix)
|
||||
{
|
||||
type: "doc",
|
||||
content: [
|
||||
{
|
||||
type: "paragraph",
|
||||
content: [
|
||||
{ text: "#{prefix}-", type: "text" },
|
||||
{ type: "mention", attrs: DOSSIER_ID_TAG.slice(:id, :label) }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
end
|
||||
|
||||
def suffix(attachment, row_index, index)
|
||||
suffix = ""
|
||||
suffix += "-#{add_one_and_pad(row_index)}" if row_index.present?
|
||||
suffix += "-#{add_one_and_pad(index)}" if index.present?
|
||||
suffix += attachment.filename.extension_with_delimiter if attachment
|
||||
|
||||
suffix
|
||||
end
|
||||
|
||||
def add_one_and_pad(number)
|
||||
(number + 1).to_s.rjust(2, '0') if number.present?
|
||||
end
|
||||
end
|
|
@ -1,157 +1,66 @@
|
|||
class ExportTemplate < ApplicationRecord
|
||||
include TagsSubstitutionConcern
|
||||
|
||||
self.ignored_columns += ["content"]
|
||||
|
||||
belongs_to :groupe_instructeur
|
||||
has_one :procedure, through: :groupe_instructeur
|
||||
has_many :exports, dependent: :nullify
|
||||
|
||||
enum kind: { zip: "zip" }, _prefix: :template
|
||||
|
||||
attribute :dossier_folder, :export_item
|
||||
attribute :export_pdf, :export_item
|
||||
attribute :pjs, :export_item, array: true
|
||||
|
||||
before_validation :ensure_pjs_are_legit
|
||||
|
||||
validates_with ExportTemplateValidator
|
||||
|
||||
DOSSIER_STATE = Dossier.states.fetch(:en_construction)
|
||||
FORMAT_DATE = "%Y-%m-%d"
|
||||
|
||||
def set_default_values
|
||||
content["default_dossier_directory"] = tiptap_json("dossier-")
|
||||
content["pdf_name"] = tiptap_json("export_")
|
||||
|
||||
content["pjs"] = []
|
||||
procedure.exportables_pieces_jointes.each do |pj|
|
||||
content["pjs"] << { "stable_id" => pj.stable_id.to_s, "path" => tiptap_json("#{pj.libelle.parameterize}-") }
|
||||
end
|
||||
# when a pj has been added to a revision, it will not be present in the previous pjs
|
||||
# a default value is provided.
|
||||
def pj(tdc)
|
||||
pjs.find { _1.stable_id == tdc.stable_id } || ExportItem.default_pj(tdc)
|
||||
end
|
||||
|
||||
def tiptap_default_dossier_directory=(body)
|
||||
self.content["default_dossier_directory"] = JSON.parse(body)
|
||||
def self.default(name: nil, kind: 'zip', groupe_instructeur:)
|
||||
dossier_folder = ExportItem.default(prefix: 'dossier')
|
||||
export_pdf = ExportItem.default(prefix: 'export')
|
||||
pjs = groupe_instructeur.procedure.exportables_pieces_jointes.map { |tdc| ExportItem.default_pj(tdc) }
|
||||
|
||||
new(name:, kind:, groupe_instructeur:, dossier_folder:, export_pdf:, pjs:)
|
||||
end
|
||||
|
||||
def tiptap_default_dossier_directory
|
||||
tiptap_content("default_dossier_directory")
|
||||
end
|
||||
|
||||
def tiptap_pdf_name=(body)
|
||||
self.content["pdf_name"] = JSON.parse(body)
|
||||
end
|
||||
|
||||
def tiptap_pdf_name
|
||||
tiptap_content("pdf_name")
|
||||
end
|
||||
|
||||
def content_for_pj(pj)
|
||||
content_for_pj_id(pj.stable_id)&.to_json
|
||||
end
|
||||
|
||||
def assign_pj_names(pj_params)
|
||||
self.content["pjs"] = []
|
||||
pj_params.each do |pj_param|
|
||||
self.content["pjs"] << { stable_id: pj_param[0].delete_prefix("tiptap_pj_"), path: JSON.parse(pj_param[1]) }
|
||||
end
|
||||
end
|
||||
|
||||
def attachment_and_path(dossier, attachment, index: 0, row_index: nil, champ: nil)
|
||||
[
|
||||
attachment,
|
||||
path(dossier, attachment, index:, row_index:, champ:)
|
||||
]
|
||||
end
|
||||
|
||||
def tiptap_convert(dossier, param)
|
||||
if content[param]["content"]&.first&.[]("content")
|
||||
render_attributes_for(content[param], dossier)
|
||||
end
|
||||
end
|
||||
|
||||
def tiptap_convert_pj(dossier, pj_stable_id, attachment = nil)
|
||||
if content_for_pj_id(pj_stable_id)["content"]&.first&.[]("content")
|
||||
render_attributes_for(content_for_pj_id(pj_stable_id), dossier, attachment)
|
||||
end
|
||||
end
|
||||
|
||||
def render_attributes_for(content_for, dossier, attachment = nil)
|
||||
used_tags = TiptapService.used_tags_and_libelle_for(content_for.deep_symbolize_keys)
|
||||
substitutions = tags_substitutions(used_tags, dossier, escape: false, memoize: true)
|
||||
substitutions['original-filename'] = attachment.filename.base if attachment
|
||||
TiptapService.new.to_path(content_for.deep_symbolize_keys, substitutions)
|
||||
end
|
||||
|
||||
def specific_tags
|
||||
def tags
|
||||
tags_categorized.slice(:individual, :etablissement, :dossier).values.flatten
|
||||
end
|
||||
|
||||
def tags_for_pj
|
||||
specific_tags.push({
|
||||
def pj_tags
|
||||
tags.push(
|
||||
libelle: 'nom original du fichier',
|
||||
id: 'original-filename',
|
||||
maybe_null: false
|
||||
})
|
||||
id: 'original-filename'
|
||||
)
|
||||
end
|
||||
|
||||
def attachment_path(dossier, attachment, index: 0, row_index: nil, champ: nil)
|
||||
file_path = if attachment.name == 'pdf_export_for_instructeur'
|
||||
export_pdf.path(dossier, attachment:)
|
||||
elsif attachment.record_type == 'Champ' && pj(champ.type_de_champ).enabled?
|
||||
pj(champ.type_de_champ).path(dossier, attachment:, index:, row_index:)
|
||||
else
|
||||
nil
|
||||
end
|
||||
|
||||
File.join(dossier_folder.path(dossier), file_path) if file_path.present?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def tiptap_content(key)
|
||||
content[key]&.to_json
|
||||
end
|
||||
def ensure_pjs_are_legit
|
||||
legitimate_pj_stable_ids = procedure.exportables_pieces_jointes_for_all_versions.map(&:stable_id)
|
||||
|
||||
def tiptap_json(prefix)
|
||||
{
|
||||
"type" => "doc",
|
||||
"content" => [
|
||||
{ "type" => "paragraph", "content" => [{ "text" => prefix, "type" => "text" }, { "type" => "mention", "attrs" => DOSSIER_ID_TAG.stringify_keys }] }
|
||||
]
|
||||
}
|
||||
end
|
||||
|
||||
def content_for_pj_id(stable_id)
|
||||
content_for_stable_id = content["pjs"].find { _1.symbolize_keys[:stable_id] == stable_id.to_s }
|
||||
content_for_stable_id.symbolize_keys.fetch(:path)
|
||||
end
|
||||
|
||||
def folder(dossier)
|
||||
render_attributes_for(content["default_dossier_directory"], dossier)
|
||||
end
|
||||
|
||||
def export_path(dossier)
|
||||
File.join(folder(dossier), export_filename(dossier))
|
||||
end
|
||||
|
||||
def export_filename(dossier)
|
||||
"#{render_attributes_for(content["pdf_name"], dossier)}.pdf"
|
||||
end
|
||||
|
||||
def path(dossier, attachment, index: 0, row_index: nil, champ: nil)
|
||||
if attachment.name == 'pdf_export_for_instructeur'
|
||||
return export_path(dossier)
|
||||
end
|
||||
|
||||
dir_path = case attachment.record_type
|
||||
when 'Dossier'
|
||||
'dossier'
|
||||
when 'Commentaire'
|
||||
'messagerie'
|
||||
when 'Avis'
|
||||
'avis'
|
||||
when 'Attestation', 'Etablissement'
|
||||
'pieces_justificatives'
|
||||
else
|
||||
# for attachment
|
||||
return attachment_path(dossier, attachment, index, row_index, champ)
|
||||
end
|
||||
|
||||
File.join(folder(dossier), dir_path, attachment.filename.to_s)
|
||||
end
|
||||
|
||||
def attachment_path(dossier, attachment, index, row_index, champ)
|
||||
stable_id = champ.stable_id
|
||||
tiptap_pj = content["pjs"].find { |pj| pj["stable_id"] == stable_id.to_s }
|
||||
if tiptap_pj
|
||||
File.join(folder(dossier), tiptap_convert_pj(dossier, stable_id, attachment) + suffix(attachment, index, row_index))
|
||||
else
|
||||
File.join(folder(dossier), "erreur_renommage", attachment.filename.to_s)
|
||||
end
|
||||
end
|
||||
|
||||
def suffix(attachment, index, row_index)
|
||||
suffix = "-#{index + 1}"
|
||||
suffix += "-#{row_index + 1}" if row_index.present?
|
||||
|
||||
suffix + attachment.filename.extension_with_delimiter
|
||||
self.pjs = pjs.filter { _1.stable_id.in?(legitimate_pj_stable_ids) }
|
||||
end
|
||||
end
|
||||
|
|
|
@ -676,6 +676,12 @@ class TypeDeChamp < ApplicationRecord
|
|||
end
|
||||
end
|
||||
|
||||
def libelle_as_filename
|
||||
libelle.gsub(/[[:space:]]+/, ' ')
|
||||
.truncate(30, omission: '', separator: ' ')
|
||||
.parameterize
|
||||
end
|
||||
|
||||
class << self
|
||||
def champ_value(type_champ, champ)
|
||||
dynamic_type_class = type_champ_to_class_name(type_champ).constantize
|
||||
|
|
|
@ -5,28 +5,26 @@ class PiecesJustificativesService
|
|||
end
|
||||
|
||||
def liste_documents(dossiers)
|
||||
bill_ids = []
|
||||
|
||||
docs = pjs_for_champs(dossiers) +
|
||||
pjs_for_commentaires(dossiers) +
|
||||
pjs_for_dossier(dossiers) +
|
||||
pjs_for_avis(dossiers)
|
||||
|
||||
if liste_documents_allows?(:with_bills)
|
||||
# we do not export bills no more with the new export system
|
||||
# the bills have never been properly understood by the users
|
||||
# their export is now deprecated
|
||||
if liste_documents_allows?(:with_bills) && @export_template.nil?
|
||||
# some bills are shared among operations
|
||||
# so first, all the bill_ids are fetched
|
||||
operation_logs, some_bill_ids = operation_logs_and_signature_ids(dossiers)
|
||||
|
||||
docs += operation_logs
|
||||
bill_ids += some_bill_ids
|
||||
end
|
||||
|
||||
if liste_documents_allows?(:with_bills)
|
||||
# then the bills are retrieved without duplication
|
||||
docs += signatures(bill_ids.uniq)
|
||||
docs += signatures(some_bill_ids.uniq)
|
||||
end
|
||||
|
||||
docs
|
||||
docs.filter { |_attachment, path| path.present? }
|
||||
end
|
||||
|
||||
def generate_dossiers_export(dossiers) # TODO: renommer generate_dossier_export sans s
|
||||
|
@ -46,14 +44,14 @@ class PiecesJustificativesService
|
|||
})
|
||||
a = ActiveStorage::FakeAttachment.new(
|
||||
file: StringIO.new(pdf),
|
||||
filename: "export-#{dossier.id}.pdf",
|
||||
filename: ActiveStorage::Filename.new("export-#{dossier.id}.pdf"),
|
||||
name: 'pdf_export_for_instructeur',
|
||||
id: dossier.id,
|
||||
created_at: dossier.updated_at
|
||||
)
|
||||
|
||||
if @export_template
|
||||
pdfs << @export_template.attachment_and_path(dossier, a)
|
||||
pdfs << [a, @export_template.attachment_path(dossier, a)]
|
||||
else
|
||||
pdfs << ActiveStorage::DownloadableFile.pj_and_path(dossier.id, a)
|
||||
end
|
||||
|
@ -148,7 +146,7 @@ class PiecesJustificativesService
|
|||
row_index = champs_id_row_index[champ.id]
|
||||
|
||||
if @export_template
|
||||
@export_template.attachment_and_path(champ.dossier, attachment, index:, row_index:, champ:)
|
||||
[attachment, @export_template.attachment_path(champ.dossier, attachment, index:, row_index:, champ:)]
|
||||
else
|
||||
ActiveStorage::DownloadableFile.pj_and_path(champ.dossier_id, attachment)
|
||||
end
|
||||
|
@ -171,7 +169,7 @@ class PiecesJustificativesService
|
|||
dossier_id = commentaire_id_dossier_id[a.record_id]
|
||||
if @export_template
|
||||
dossier = dossiers.find { _1.id == dossier_id }
|
||||
@export_template.attachment_and_path(dossier, a)
|
||||
[a, @export_template.attachment_path(dossier, a)]
|
||||
else
|
||||
ActiveStorage::DownloadableFile.pj_and_path(dossier_id, a)
|
||||
end
|
||||
|
@ -197,7 +195,7 @@ class PiecesJustificativesService
|
|||
dossier_id = etablissement_id_dossier_id[a.record_id]
|
||||
if @export_template
|
||||
dossier = dossiers.find { _1.id == dossier_id }
|
||||
@export_template.attachment_and_path(dossier, a)
|
||||
[a, @export_template.attachment_path(dossier, a)]
|
||||
else
|
||||
ActiveStorage::DownloadableFile.pj_and_path(dossier_id, a)
|
||||
end
|
||||
|
@ -213,7 +211,7 @@ class PiecesJustificativesService
|
|||
dossier_id = a.record_id
|
||||
if @export_template
|
||||
dossier = dossiers.find { _1.id == dossier_id }
|
||||
@export_template.attachment_and_path(dossier, a)
|
||||
[a, @export_template.attachment_path(dossier, a)]
|
||||
else
|
||||
ActiveStorage::DownloadableFile.pj_and_path(dossier_id, a)
|
||||
end
|
||||
|
@ -234,7 +232,7 @@ class PiecesJustificativesService
|
|||
dossier_id = attestation_id_dossier_id[a.record_id]
|
||||
if @export_template
|
||||
dossier = dossiers.find { _1.id == dossier_id }
|
||||
@export_template.attachment_and_path(dossier, a)
|
||||
[a, @export_template.attachment_path(dossier, a)]
|
||||
else
|
||||
ActiveStorage::DownloadableFile.pj_and_path(dossier_id, a)
|
||||
end
|
||||
|
@ -263,7 +261,7 @@ class PiecesJustificativesService
|
|||
dossier_id = avis_ids_dossier_id[a.record_id]
|
||||
if @export_template
|
||||
dossier = dossiers.find { _1.id == dossier_id }
|
||||
@export_template.attachment_and_path(dossier, a)
|
||||
[a, @export_template.attachment_path(dossier, a)]
|
||||
else
|
||||
ActiveStorage::DownloadableFile.pj_and_path(dossier_id, a)
|
||||
end
|
||||
|
|
|
@ -19,10 +19,10 @@ class TiptapService
|
|||
children(node[:content], substitutions, 0)
|
||||
end
|
||||
|
||||
def to_path(node, substitutions = {})
|
||||
def to_texts_and_tags(node, substitutions = {})
|
||||
return '' if node.nil?
|
||||
|
||||
children_path(node[:content], substitutions)
|
||||
children_texts_and_tags(node[:content], substitutions)
|
||||
end
|
||||
|
||||
private
|
||||
|
@ -31,18 +31,24 @@ class TiptapService
|
|||
@body_started = false
|
||||
end
|
||||
|
||||
def children_path(content, substitutions)
|
||||
content.map { node_to_path(_1, substitutions) }.join
|
||||
def children_texts_and_tags(content, substitutions)
|
||||
content.map { node_to_texts_and_tags(_1, substitutions) }.join
|
||||
end
|
||||
|
||||
def node_to_path(node, substitutions)
|
||||
def node_to_texts_and_tags(node, substitutions)
|
||||
case node
|
||||
in type: 'paragraph', content:
|
||||
children_path(content, substitutions)
|
||||
in type: 'text', text:, **rest
|
||||
children_texts_and_tags(content, substitutions)
|
||||
in type: 'paragraph' # empty paragraph
|
||||
''
|
||||
in type: 'text', text:
|
||||
text.strip
|
||||
in type: 'mention', attrs: { id: }, **rest
|
||||
substitutions.fetch(id) { "--#{id}--" }
|
||||
in type: 'mention', attrs: { id:, label: }
|
||||
if substitutions.present?
|
||||
substitutions.fetch(id) { "--#{id}--" }
|
||||
else
|
||||
"<span class='fr-tag fr-tag--sm'>#{label}</span>"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
40
app/types/export_item_type.rb
Normal file
40
app/types/export_item_type.rb
Normal file
|
@ -0,0 +1,40 @@
|
|||
class ExportItemType < ActiveRecord::Type::Value
|
||||
# form_input, or setter -> type
|
||||
def cast(value)
|
||||
value = value.deep_symbolize_keys if value.respond_to?(:deep_symbolize_keys)
|
||||
|
||||
case value
|
||||
in ExportItem
|
||||
value
|
||||
in NilClass # default value
|
||||
nil
|
||||
# from db
|
||||
in { template: Hash, enabled: TrueClass | FalseClass } => h
|
||||
|
||||
ExportItem.new(**h.slice(:template, :enabled, :stable_id))
|
||||
# from form
|
||||
in { template: String } => h
|
||||
|
||||
template = JSON.parse(h[:template]).deep_symbolize_keys
|
||||
enabled = h[:enabled] == 'true'
|
||||
stable_id = h[:stable_id]&.to_i
|
||||
ExportItem.new(template:, enabled:, stable_id:)
|
||||
end
|
||||
end
|
||||
|
||||
# db -> ruby
|
||||
def deserialize(value) = cast(value&.then { JSON.parse(_1) })
|
||||
|
||||
# ruby -> db
|
||||
def serialize(value)
|
||||
if value.is_a?(ExportItem)
|
||||
JSON.generate({
|
||||
template: value.template,
|
||||
enabled: value.enabled,
|
||||
stable_id: value.stable_id
|
||||
}.compact)
|
||||
else
|
||||
raise ArgumentError, "Invalid value for ExportItem serialization: #{value}"
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,54 +1,66 @@
|
|||
class ExportTemplateValidator < ActiveModel::Validator
|
||||
def validate(record)
|
||||
validate_default_dossier_directory(record)
|
||||
validate_pdf_name(record)
|
||||
validate_pjs(record)
|
||||
def validate(export_template)
|
||||
validate_all_templates(export_template)
|
||||
|
||||
return if export_template.errors.any? # no need to continue if the templates are invalid
|
||||
|
||||
validate_dossier_folder(export_template)
|
||||
validate_export_pdf(export_template)
|
||||
validate_pjs(export_template)
|
||||
|
||||
validate_different_templates(export_template)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def validate_default_dossier_directory(record)
|
||||
mention = attribute_content_mention(record, :default_dossier_directory)
|
||||
if mention&.fetch("id", nil) != "dossier_number"
|
||||
record.errors.add :tiptap_default_dossier_directory, :dossier_number_mandatory
|
||||
def validate_all_templates(export_template)
|
||||
[export_template.dossier_folder, export_template.export_pdf, *export_template.pjs].each(&:template_string)
|
||||
|
||||
rescue StandardError
|
||||
export_template.errors.add(:base, :invalid_template)
|
||||
end
|
||||
|
||||
def validate_dossier_folder(export_template)
|
||||
if !mentions(export_template.dossier_folder.template).include?('dossier_number')
|
||||
export_template.errors.add(:dossier_folder, :dossier_number_required)
|
||||
end
|
||||
end
|
||||
|
||||
def validate_pdf_name(record)
|
||||
if attribute_content_text(record, :pdf_name).blank? && attribute_content_mention(record, :pdf_name).blank?
|
||||
record.errors.add :tiptap_pdf_name, :blank
|
||||
def mentions(template)
|
||||
TiptapService.used_tags_and_libelle_for(template).map(&:first)
|
||||
end
|
||||
|
||||
def validate_export_pdf(export_template)
|
||||
return if !export_template.export_pdf.enabled?
|
||||
|
||||
if export_template.export_pdf.template_string.empty?
|
||||
export_template.errors.add(:export_pdf, :blank)
|
||||
end
|
||||
end
|
||||
|
||||
def attribute_content_text(record, attribute)
|
||||
attribute_content(record, attribute)&.find { |elem| elem["type"] == "text" }&.fetch("text", nil)
|
||||
end
|
||||
def validate_pjs(export_template)
|
||||
libelle_by_stable_ids = pj_libelle_by_stable_id(export_template)
|
||||
|
||||
def attribute_content_mention(record, attribute)
|
||||
attribute_content(record, attribute)&.find { |elem| elem["type"] == "mention" }&.fetch("attrs", nil)
|
||||
end
|
||||
|
||||
def attribute_content(record, attribute)
|
||||
content = record.content[attribute.to_s]&.fetch("content", nil)
|
||||
if content.is_a?(Array)
|
||||
content.first&.fetch("content", nil)
|
||||
export_template.pjs.filter(&:enabled?).each do |pj|
|
||||
if pj.template_string.empty?
|
||||
libelle = libelle_by_stable_ids[pj.stable_id]
|
||||
export_template.errors.add(libelle, I18n.t(:blank, scope: 'errors.messages'))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def validate_pjs(record)
|
||||
record.content["pjs"]&.each do |pj|
|
||||
pj_sym = pj.symbolize_keys
|
||||
libelle = record.groupe_instructeur.procedure.exportables_pieces_jointes.find { _1.stable_id.to_s == pj_sym[:stable_id] }&.libelle&.to_sym
|
||||
validate_content(record, pj_sym[:path], libelle)
|
||||
end
|
||||
def validate_different_templates(export_template)
|
||||
templates = [export_template.export_pdf, *export_template.pjs]
|
||||
.filter(&:enabled?)
|
||||
.map(&:template_string)
|
||||
|
||||
return if templates.uniq.size == templates.size
|
||||
|
||||
export_template.errors.add(:base, :different_templates)
|
||||
end
|
||||
|
||||
def validate_content(record, attribute_content, attribute)
|
||||
if attribute_content.nil? || attribute_content["content"].nil? ||
|
||||
attribute_content["content"].first.nil? ||
|
||||
attribute_content["content"].first["content"].nil? ||
|
||||
(attribute_content["content"].first["content"].find { |elem| elem["text"].blank? } && attribute_content["content"].first["content"].find { |elem| elem["type"] == "mention" }["attrs"].blank?)
|
||||
record.errors.add attribute, I18n.t(:blank, scope: 'errors.messages')
|
||||
end
|
||||
def pj_libelle_by_stable_id(export_template)
|
||||
export_template.procedure.exportables_pieces_jointes
|
||||
.pluck(:stable_id, :libelle).to_h
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
.card.no-list
|
||||
= hidden_field_tag("#{prefix}[stable_id]", item.stable_id)
|
||||
|
||||
.fr-checkbox-group{ data: { controller: 'hide-target' } }
|
||||
- id = sanitize_to_id("#{prefix}_#{item.stable_id}_enabled")
|
||||
= check_box_tag "#{prefix}[enabled]", true, item.enabled?, id:, data: { 'hide-target_target': 'source' }
|
||||
= label_tag id, libelle, class: 'fr-label'
|
||||
|
||||
%div{ class: class_names('fr-hidden': !item.enabled?), data: { hide_target_target: 'toHide' } }
|
||||
%div{ data: { controller: 'hide-target tiptap-to-template'} }
|
||||
.fr-mt-2w{ data: { hide_target_target: 'toHide' } }
|
||||
%span Nom du fichier :
|
||||
%span{ data: { 'tiptap-to-template_target': 'output'} }= sanitize(item.template_string)
|
||||
.fr-mt-2w
|
||||
%button.fr-btn.fr-btn--tertiary.fr-btn--sm{ type: 'button', data: { 'hide-target_target': 'source' } } Renommer le fichier
|
||||
|
||||
.fr-mt-2w.fr-hidden{ data: { controller: 'tiptap', 'tiptap-attributes-value': { spellcheck: false }.to_json, hide_target_target: 'toHide' } }
|
||||
%span Renommer le fichier :
|
||||
.fr-mt-2w.tiptap-editor{ data: { tiptap_target: 'editor' } }
|
||||
= hidden_field_tag "#{prefix}[template]", item.template_json, data: { tiptap_target: 'input' }, id: nil
|
||||
|
||||
.fr-mt-2w
|
||||
%span.fr-text--sm Cliquez sur les étiquettes que vous souhaitez intégrer au nom du fichier
|
||||
.fr-mt-2w= render TagsButtonListComponent.new(tags: { nil => @export_template.pj_tags })
|
||||
|
||||
= button_tag "Valider", type: 'button', class: 'fr-btn fr-mt-2w', data: { 'tiptap-to-template_target': 'trigger', 'hide-target_target': 'source'}
|
|
@ -1,3 +1,5 @@
|
|||
- procedure = @export_template.procedure
|
||||
|
||||
#export_template-edit.fr-my-4w
|
||||
.fr-mb-6w
|
||||
= render Dsfr::AlertComponent.new(state: :info, title: "Nouvel éditeur de modèle d'export", heading_level: 'h3') do |c|
|
||||
|
@ -7,72 +9,82 @@
|
|||
Essayez-le et donnez-nous votre avis
|
||||
en nous envoyant un email à #{mail_to(CONTACT_EMAIL, subject: "Editeur de modèle d'export")}.
|
||||
|
||||
.fr-grid-row.fr-grid-row--gutters
|
||||
.fr-col-12.fr-col-md-8
|
||||
= form_with url: form_url, model: @export_template, local: true, data: { turbo: 'true', controller: 'autosubmit' } do |f|
|
||||
.fr-grid-row.fr-grid-row--gutters
|
||||
.fr-col-12.fr-col-md-8.fr-pr-4w
|
||||
= form_with model: [:instructeur, procedure, export_template], data: { turbo: 'true', controller: 'autosubmit' } do |f|
|
||||
%input.hidden{ type: 'submit', formaction: preview_instructeur_procedure_export_templates_path, data: { autosubmit_target: 'submitter' }, formnovalidate: 'true', formmethod: 'get' }
|
||||
|
||||
= render Dsfr::InputComponent.new(form: f, attribute: :name, input_type: :text_field)
|
||||
= f.hidden_field :kind, value: 'zip'
|
||||
|
||||
- if groupe_instructeurs.many?
|
||||
.fr-input-group
|
||||
= render Dsfr::InputComponent.new(form: f, attribute: :name, input_type: :text_field)
|
||||
|
||||
.fr-input-group{ class: class_names('fr-hidden': groupe_instructeurs.one?) }
|
||||
= f.label :groupe_instructeur_id, class: 'fr-label' do
|
||||
= f.object.class.human_attribute_name(:groupe_instructeur_id)
|
||||
= render EditableChamp::AsteriskMandatoryComponent.new
|
||||
%span.fr-hint-text
|
||||
Avec quel groupe instructeur souhaitez-vous partager ce modèle d'export ?
|
||||
= "#{ExportTemplate.human_attribute_name('groupe_instructeur_id')} #{asterisk}"
|
||||
%span.fr-hint-text Avec quel groupe instructeur souhaitez-vous partager ce modèle d'export ?
|
||||
= f.collection_select :groupe_instructeur_id, groupe_instructeurs, :id, :label, {}, class: 'fr-select'
|
||||
- else
|
||||
= f.hidden_field :groupe_instructeur_id
|
||||
|
||||
= f.hidden_field :kind
|
||||
.fr-input-group{ data: { controller: 'tiptap', 'tiptap-attributes-value': { spellcheck: false }.to_json } }
|
||||
= f.label '[dossier_folder][template]', class: "fr-label" do
|
||||
= "#{ExportTemplate.human_attribute_name('dossier_folder')} #{asterisk}"
|
||||
%span.fr-hint-text Nom du répertoire contenant les différents fichiers à exporter
|
||||
.tiptap-editor.fr-mt-1w{ data: { tiptap_target: 'editor' } }
|
||||
= f.hidden_field "[dossier_folder][template]", data: { tiptap_target: 'input' }, value: export_template.dossier_folder.template_json
|
||||
= f.hidden_field "[dossier_folder][enabled]", value: 'true'
|
||||
.fr-mt-2w
|
||||
%span.fr-text--sm Cliquez sur les étiquettes que vous souhaitez intégrer au nom du fichier
|
||||
.fr-mt-2w= render TagsButtonListComponent.new(tags: { nil => export_template.tags })
|
||||
|
||||
.fr-input-group{ data: { controller: 'tiptap' } }
|
||||
= f.label :tiptap_default_dossier_directory, class: "fr-label" do
|
||||
= f.object.class.human_attribute_name(:tiptap_default_dossier_directory)
|
||||
= render EditableChamp::AsteriskMandatoryComponent.new
|
||||
%span.fr-hint-text
|
||||
= t('activerecord.attributes.export_template.hints.tiptap_default_dossier_directory')
|
||||
= render Dsfr::NoticeComponent.new(data_attributes: { class: 'fr-my-4w' }) do |c|
|
||||
- c.with_title do
|
||||
Sélectionnez les fichiers que vous souhaitez exporter
|
||||
|
||||
.tiptap-editor.fr-mt-1w{ data: { tiptap_target: 'editor' } }
|
||||
= f.hidden_field :tiptap_default_dossier_directory, data: { tiptap_target: 'input' }
|
||||
.fr-mt-2w= render TagsButtonListComponent.new(tags: { nil => @export_template.specific_tags })
|
||||
%h3 Dossier au format PDF
|
||||
= render partial: 'export_item',
|
||||
locals: { item: export_template.export_pdf,
|
||||
libelle: ExportTemplate.human_attribute_name(:export_pdf),
|
||||
prefix: 'export_template[export_pdf]' }
|
||||
|
||||
.fr-input-group{ data: { controller: 'tiptap' } }
|
||||
= f.label :tiptap_pdf_name, class: "fr-label" do
|
||||
= f.object.class.human_attribute_name(:tiptap_pdf_name)
|
||||
= render EditableChamp::AsteriskMandatoryComponent.new
|
||||
%span.fr-hint-text
|
||||
= t('activerecord.attributes.export_template.hints.tiptap_pdf_name')
|
||||
.tiptap-editor.fr-mt-1w{ data: { tiptap_target: 'editor' } }
|
||||
= f.hidden_field :tiptap_pdf_name, data: { tiptap_target: 'input' }
|
||||
.fr-mt-2w= render TagsButtonListComponent.new(tags: { nil => @export_template.specific_tags })
|
||||
- if procedure.exportables_pieces_jointes_for_all_versions.any?
|
||||
%h3 Pièces justificatives
|
||||
|
||||
- if @all_pj.any?
|
||||
%h3 Pieces justificatives
|
||||
- procedure.exportables_pieces_jointes.each do |tdc|
|
||||
- item = export_template.pj(tdc)
|
||||
= render partial: 'export_item',
|
||||
locals: { item:,
|
||||
libelle: tdc.libelle,
|
||||
prefix: 'export_template[pjs][]'}
|
||||
|
||||
.fr-highlight
|
||||
%p.fr-text--sm
|
||||
N'incluez pas les extensions de fichier (.pdf, .jpg, …) dans les noms de pièces jointes.
|
||||
- outdated_tdcs = procedure.outdated_exportables_pieces_jointes
|
||||
- outdated_stable_ids = outdated_tdcs.map(&:stable_id)
|
||||
- expanded = export_template.pjs.filter(&:enabled?).any? { _1.stable_id.in?(outdated_stable_ids) }
|
||||
|
||||
- @all_pj.each do |pj|
|
||||
.fr-input-group{ data: { controller: 'tiptap' } }
|
||||
= label_tag pj.libelle, nil, name: field_name(:export_template, "tiptap_pj_#{pj.stable_id}"), class: "fr-label"
|
||||
.tiptap-editor.fr-mt-1w{ data: { tiptap_target: 'editor' } }
|
||||
= hidden_field_tag field_name(:export_template, "tiptap_pj_#{pj.stable_id}"), "#{@export_template.content_for_pj(pj)}" , data: { tiptap_target: 'input' }
|
||||
.fr-mt-2w= render TagsButtonListComponent.new(tags: { nil => @export_template.tags_for_pj })
|
||||
- if outdated_tdcs.any?
|
||||
%section.fr-accordion.fr-mb-3w
|
||||
%h3.fr-accordion__title
|
||||
%button.fr-accordion__btn{ "aria-controls" => "accordion-106", "aria-expanded" => expanded.to_s, "type" => "button" }
|
||||
pièces justificatives uniquement présentes dans les versions précédentes
|
||||
.fr-collapse#accordion-106
|
||||
|
||||
- outdated_tdcs.each do |tdc|
|
||||
- item = export_template.pj(tdc)
|
||||
= render partial: 'export_item',
|
||||
locals: { item:,
|
||||
libelle: tdc.libelle,
|
||||
prefix: 'export_template[pjs][]'}
|
||||
|
||||
.fixed-footer
|
||||
.fr-container
|
||||
%ul.fr-btns-group.fr-btns-group--inline-md
|
||||
%li= f.button "Enregistrer", class: "fr-btn", data: { turbo: 'false' }
|
||||
%li= link_to "Annuler", [:exports, :instructeur, procedure], class: "fr-btn fr-btn--secondary"
|
||||
- if export_template.persisted?
|
||||
%li
|
||||
= link_to "Supprimer",
|
||||
[:instructeur, procedure, export_template],
|
||||
method: :delete,
|
||||
data: { confirm: "Voulez-vous vraiment supprimer ce modèle ? Il sera supprimé pour tous les instructeurs du groupe"},
|
||||
class: "fr-btn fr-btn--secondary"
|
||||
|
||||
.fixed-footer
|
||||
.fr-container
|
||||
%ul.fr-btns-group.fr-btns-group--inline-md
|
||||
%li
|
||||
%input.hidden{ type: 'submit', formaction: preview_instructeur_export_templates_path, data: { autosubmit_target: 'submitter' }, formnovalidate: 'true', formmethod: 'get' }
|
||||
= f.button "Enregistrer", class: "fr-btn", data: { turbo: 'false' }
|
||||
%li
|
||||
= link_to "Annuler", instructeur_procedure_path(@procedure), class: "fr-btn fr-btn--secondary"
|
||||
- if @export_template.persisted?
|
||||
%li
|
||||
= link_to "Supprimer", instructeur_export_template_path(@export_template, procedure_id: @procedure.id), method: :delete, data: { confirm: "Voulez-vous vraiment supprimer ce modèle ? Il sera supprimé pour tous les instructeurs du groupe"}, class: "fr-btn fr-btn--secondary"
|
||||
- sample_dossier = @procedure.dossier_for_preview(current_instructeur)
|
||||
- if sample_dossier
|
||||
.fr-col-12.fr-col-md-4.fr-background-alt--blue-france
|
||||
= render partial: 'preview', locals: { dossier: sample_dossier, export_template: @export_template, procedure: @procedure }
|
||||
= render partial: 'preview', locals: { export_template: }
|
||||
|
|
|
@ -1,17 +1,33 @@
|
|||
- procedure = export_template.procedure
|
||||
- dossier = procedure.dossier_for_preview(current_instructeur)
|
||||
|
||||
#preview.export-template-preview.fr-p-2w.sticky--top
|
||||
%h2.fr-h4 Aperçu
|
||||
%ul.tree.fr-text--sm
|
||||
%li #{DownloadableFileService::EXPORT_DIRNAME}/
|
||||
%li
|
||||
%ul
|
||||
%li
|
||||
%span#preview_default_dossier_directory #{export_template.tiptap_convert(dossier, "default_dossier_directory")}/
|
||||
%ul
|
||||
%li#preview_pdf_name #{export_template.tiptap_convert(dossier, "pdf_name")}.pdf
|
||||
- procedure.exportables_pieces_jointes.each do |pj|
|
||||
%li{ id: "preview_pj_#{pj.stable_id}" } #{export_template.tiptap_convert_pj(dossier, pj.stable_id)}-1.jpg
|
||||
%ul
|
||||
%li
|
||||
%span messagerie/
|
||||
%ul
|
||||
%li un-autre-fichier.png
|
||||
- if dossier.nil?
|
||||
%p.fr-text--sm
|
||||
Pour générer un aperçu fidèle avec tous les champs et les dates,
|
||||
= link_to 'créez-vous un dossier', commencer_url(procedure.path), target: '_blank'
|
||||
et acceptez-le : l’aperçu l’utilisera.
|
||||
|
||||
- else
|
||||
%ul.tree.fr-text--sm
|
||||
%li
|
||||
%span.fr-icon-folder-zip-line
|
||||
#{DownloadableFileService::EXPORT_DIRNAME}/
|
||||
%li
|
||||
%ul
|
||||
%li
|
||||
%span.fr-icon-folder-line
|
||||
#{export_template.dossier_folder.path(dossier)}/
|
||||
%ul
|
||||
- if export_template.export_pdf.enabled?
|
||||
%li
|
||||
%span.fr-icon-pdf-2-line
|
||||
#{export_template.export_pdf.path(dossier)}.pdf
|
||||
|
||||
- procedure.exportables_pieces_jointes.each do |tdc|
|
||||
- export_pj = export_template.pj(tdc)
|
||||
- if export_pj.enabled?
|
||||
%li
|
||||
%span.fr-icon-file-image-line
|
||||
#{export_pj.path(dossier)}-1.jpg
|
||||
|
|
|
@ -4,4 +4,4 @@
|
|||
.fr-container
|
||||
%h1 Mise à jour modèle d'export
|
||||
|
||||
= render partial: 'form', locals: { form_url: instructeur_export_template_path(@procedure, @export_template), groupe_instructeurs: @groupe_instructeurs }
|
||||
= render partial: 'form', locals: { export_template: @export_template, groupe_instructeurs: @groupe_instructeurs }
|
||||
|
|
|
@ -3,4 +3,4 @@
|
|||
[t('.title')]] }
|
||||
.fr-container
|
||||
%h1 Nouveau modèle d'export
|
||||
= render partial: 'form', locals: { form_url: instructeur_export_templates_path, groupe_instructeurs: @groupe_instructeurs }
|
||||
= render partial: 'form', locals: { export_template: @export_template, groupe_instructeurs: @groupe_instructeurs }
|
||||
|
|
|
@ -38,9 +38,9 @@
|
|||
%tbody
|
||||
- @export_templates.each do |export_template|
|
||||
%tr
|
||||
%td= link_to export_template.name, edit_instructeur_export_template_path(export_template, procedure_id: @procedure.id)
|
||||
%td= link_to export_template.name, [:edit, :instructeur, @procedure, export_template]
|
||||
%td= export_template.groupe_instructeur.label if @procedure.groupe_instructeurs.many?
|
||||
|
||||
%p
|
||||
= link_to new_instructeur_export_template_path(procedure_id: params[:procedure_id]), class: 'fr-btn fr-btn--secondary fr-btn--icon-left fr-icon-add-line' do
|
||||
= link_to [:new, :instructeur, @procedure, :export_template], class: 'fr-btn fr-btn--secondary fr-btn--icon-left fr-icon-add-line' do
|
||||
Ajouter un modèle d'export
|
||||
|
|
5
config/initializers/types.rb
Normal file
5
config/initializers/types.rb
Normal file
|
@ -0,0 +1,5 @@
|
|||
require Rails.root.join("app/types/export_item_type")
|
||||
|
||||
ActiveSupport.on_load(:active_record) do
|
||||
ActiveRecord::Type.register(:export_item, ExportItemType)
|
||||
end
|
|
@ -6,12 +6,14 @@ en:
|
|||
export_template:
|
||||
hints:
|
||||
name: "The name will be visible by you and the other instructors"
|
||||
tiptap_default_dossier_directory: "How would you like to name the directory containing the documents of a folder?"
|
||||
tiptap_pdf_name: "How would you like to name the pdf file containing all the user's answers?"
|
||||
dossier_folder: "How would you like to name the directory containing the documents of a folder?"
|
||||
export_pdf: "How would you like to name the pdf file containing all the user's answers?"
|
||||
name: "Template's name"
|
||||
tiptap_default_dossier_directory: "Directory's name for pdf format"
|
||||
tiptap_pdf_name: "Export's filename"
|
||||
dossier_folder: "Directory's name for pdf format"
|
||||
export_pdf: "File in pdf format"
|
||||
errors:
|
||||
models:
|
||||
export_template:
|
||||
dossier_number_mandatory: "must contain dossier's number"
|
||||
dossier_number_required: "must contain dossier's number"
|
||||
different_templates: "Files must have different names"
|
||||
invalid_template: "A file name is invalid"
|
||||
|
|
|
@ -6,12 +6,14 @@ fr:
|
|||
export_template:
|
||||
hints:
|
||||
name: "Le nom sera visible par vous et les autres instructeurs pour générer un export"
|
||||
tiptap_default_dossier_directory: "Comment souhaitez-vous nommer le répertoire contenant les documents d'un dossier ?"
|
||||
tiptap_pdf_name: "Comment souhaitez-vous nommer le fichier pdf qui contient toutes les réponses de l'usager ?"
|
||||
dossier_folder: "Comment souhaitez-vous nommer le répertoire contenant les documents d'un dossier ?"
|
||||
export_pdf: "Comment souhaitez-vous nommer le fichier pdf qui contient toutes les réponses de l'usager ?"
|
||||
name: "Nom du modèle"
|
||||
tiptap_default_dossier_directory: Nom du répertoire
|
||||
tiptap_pdf_name: "Nom du dossier au format pdf"
|
||||
dossier_folder: Nom du répertoire
|
||||
export_pdf: "Dossier au format pdf"
|
||||
errors:
|
||||
models:
|
||||
export_template:
|
||||
dossier_number_mandatory: doit contenir le numéro du dossier
|
||||
dossier_number_required: doit contenir le numéro du dossier
|
||||
different_templates: Les fichiers doivent avoir des noms différents
|
||||
invalid_template: Un nom de fichier est invalide
|
||||
|
|
|
@ -443,14 +443,17 @@ Rails.application.routes.draw do
|
|||
#
|
||||
|
||||
scope module: 'instructeurs', as: 'instructeur' do
|
||||
resources :procedures, only: [] do
|
||||
resources :export_templates, only: [:new, :create, :edit, :update, :destroy] do
|
||||
collection do
|
||||
get 'preview'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
resources :procedures, only: [:index, :show], param: :procedure_id do
|
||||
member do
|
||||
resources :archives, only: [:index, :create]
|
||||
resources :export_templates, only: [:new, :create, :edit, :update, :destroy] do
|
||||
collection do
|
||||
get 'preview'
|
||||
end
|
||||
end
|
||||
|
||||
resources :groupes, only: [:index, :show], controller: 'groupe_instructeurs' do
|
||||
resource :contact_information
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
class AddDossierFolderColumnToExportTemplate < ActiveRecord::Migration[7.0]
|
||||
def up
|
||||
execute "DELETE FROM export_templates;"
|
||||
|
||||
add_column :export_templates, :dossier_folder, :jsonb, default: nil, null: false
|
||||
add_column :export_templates, :export_pdf, :jsonb, default: nil, null: false
|
||||
add_column :export_templates, :pjs, :jsonb, array: true, default: [], null: false
|
||||
end
|
||||
|
||||
def down
|
||||
remove_column :export_templates, :dossier_folder
|
||||
remove_column :export_templates, :export_pdf
|
||||
remove_column :export_templates, :pjs
|
||||
end
|
||||
end
|
|
@ -599,9 +599,12 @@ ActiveRecord::Schema[7.0].define(version: 2024_07_16_091043) do
|
|||
create_table "export_templates", force: :cascade do |t|
|
||||
t.jsonb "content", default: {}
|
||||
t.datetime "created_at", null: false
|
||||
t.jsonb "dossier_folder", null: false
|
||||
t.jsonb "export_pdf", null: false
|
||||
t.bigint "groupe_instructeur_id", null: false
|
||||
t.string "kind", null: false
|
||||
t.string "name", null: false
|
||||
t.jsonb "pjs", default: [], null: false, array: true
|
||||
t.datetime "updated_at", null: false
|
||||
t.index ["groupe_instructeur_id"], name: "index_export_templates_on_groupe_instructeur_id"
|
||||
end
|
||||
|
|
|
@ -1,58 +1,31 @@
|
|||
describe Instructeurs::ExportTemplatesController, type: :controller do
|
||||
before { sign_in(instructeur.user) }
|
||||
let(:tiptap_pdf_name) {
|
||||
{
|
||||
"type" => "doc",
|
||||
"content" => [
|
||||
{ "type" => "paragraph", "content" => [{ "text" => "mon_export_", "type" => "text" }, { "type" => "mention", "attrs" => { "id" => "dossier_number", "label" => "numéro du dossier" } }] }
|
||||
]
|
||||
}.to_json
|
||||
}
|
||||
|
||||
let(:export_template_params) do
|
||||
{
|
||||
name: "coucou",
|
||||
kind: "zip",
|
||||
groupe_instructeur_id: groupe_instructeur.id,
|
||||
tiptap_pdf_name: tiptap_pdf_name,
|
||||
tiptap_default_dossier_directory: {
|
||||
"type" => "doc",
|
||||
"content" => [
|
||||
{ "type" => "paragraph", "content" => [{ "text" => "DOSSIER_", "type" => "text" }, { "type" => "mention", "attrs" => { "id" => "dossier_number", "label" => "numéro du dossier" } }, { "text" => " ", "type" => "text" }] }
|
||||
]
|
||||
}.to_json,
|
||||
tiptap_pj_3: {
|
||||
"type" => "doc",
|
||||
"content" => [{ "type" => "paragraph", "content" => [{ "type" => "text", "text" => "avis-commission-" }, { "type" => "mention", "attrs" => { "id" => "dossier_number", "label" => "numéro du dossier" } }] }]
|
||||
}.to_json,
|
||||
tiptap_pj_5: {
|
||||
|
||||
"type" => "doc",
|
||||
"content" => [{ "type" => "paragraph", "content" => [{ "type" => "text", "text" => "avis-commission-" }, { "type" => "mention", "attrs" => { "id" => "dossier_number", "label" => "numéro du dossier" } }] }]
|
||||
}.to_json,
|
||||
tiptap_pj_10: {
|
||||
|
||||
"type" => "doc",
|
||||
"content" => [{ "type" => "paragraph", "content" => [{ "type" => "text", "text" => "avis-commission-" }, { "type" => "mention", "attrs" => { "id" => "dossier_number", "label" => "numéro du dossier" } }] }]
|
||||
}.to_json
|
||||
}
|
||||
end
|
||||
|
||||
let(:instructeur) { create(:instructeur) }
|
||||
let(:procedure) do
|
||||
create(
|
||||
:procedure, instructeurs: [instructeur],
|
||||
types_de_champ_public: [
|
||||
{ type: :piece_justificative, libelle: "pj1", stable_id: 3 },
|
||||
{ type: :piece_justificative, libelle: "pj2", stable_id: 5 },
|
||||
{ type: :piece_justificative, libelle: "pj3", stable_id: 10 }
|
||||
]
|
||||
types_de_champ_public: [{ type: :piece_justificative, libelle: "pj1", stable_id: 3 }]
|
||||
)
|
||||
end
|
||||
let(:groupe_instructeur) { procedure.defaut_groupe_instructeur }
|
||||
let(:groupe_instructeur_id) { groupe_instructeur.id }
|
||||
|
||||
let(:export_template_params) do
|
||||
{
|
||||
name: "coucou",
|
||||
kind: "zip",
|
||||
groupe_instructeur_id:,
|
||||
export_pdf:,
|
||||
dossier_folder: item_params(text: "DOSSIER_"),
|
||||
pjs: [pj_item_params(stable_id: 3, text: "avis-commission-"), pj_item_params(stable_id: 666, text: "evil-hack")]
|
||||
}
|
||||
end
|
||||
|
||||
let(:export_pdf) { item_params(text: "mon_export_") }
|
||||
|
||||
describe '#new' do
|
||||
let(:subject) { get :new, params: { procedure_id: procedure.id } }
|
||||
subject { get :new, params: { procedure_id: procedure.id } }
|
||||
|
||||
it do
|
||||
subject
|
||||
|
@ -61,18 +34,22 @@ describe Instructeurs::ExportTemplatesController, type: :controller do
|
|||
end
|
||||
|
||||
describe '#create' do
|
||||
let(:subject) { post :create, params: { procedure_id: procedure.id, export_template: export_template_params } }
|
||||
let(:create_params) { export_template_params }
|
||||
subject { post :create, params: { procedure_id: procedure.id, export_template: create_params } }
|
||||
|
||||
context 'with valid params' do
|
||||
it 'redirect to some page' do
|
||||
subject
|
||||
expect(response).to redirect_to(exports_instructeur_procedure_path(procedure:))
|
||||
expect(response).to redirect_to(exports_instructeur_procedure_path(procedure))
|
||||
expect(flash.notice).to eq "Le modèle d'export coucou a bien été créé"
|
||||
end
|
||||
end
|
||||
|
||||
context 'with invalid params' do
|
||||
let(:tiptap_pdf_name) { { content: "invalid" }.to_json }
|
||||
let(:export_pdf) do
|
||||
item_params(text: 'toto').merge("template" => { "content" => [{ "content" => "invalid" }] }.to_json)
|
||||
end
|
||||
|
||||
it 'display error notification' do
|
||||
subject
|
||||
expect(flash.alert).to be_present
|
||||
|
@ -81,16 +58,37 @@ describe Instructeurs::ExportTemplatesController, type: :controller do
|
|||
|
||||
context 'with procedure not accessible by current instructeur' do
|
||||
let(:another_procedure) { create(:procedure) }
|
||||
let(:subject) { post :create, params: { procedure_id: another_procedure.id, export_template: export_template_params } }
|
||||
subject { post :create, params: { procedure_id: another_procedure.id, export_template: export_template_params } }
|
||||
|
||||
it 'raise exception' do
|
||||
expect { subject }.to raise_error(ActiveRecord::RecordNotFound)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with invalid groupe_instructeur_id' do
|
||||
let(:groupe_instructeur_id) { create(:groupe_instructeur).id }
|
||||
|
||||
it 'display error notification' do
|
||||
expect { subject }.not_to change(ExportTemplate, :count)
|
||||
expect(flash.alert).to be_present
|
||||
end
|
||||
end
|
||||
|
||||
context 'without pjs' do
|
||||
let(:create_params) { export_template_params.tap { _1.delete(:pjs) } }
|
||||
|
||||
it 'works' do
|
||||
subject
|
||||
|
||||
expect(flash.notice).to eq "Le modèle d'export coucou a bien été créé"
|
||||
expect(ExportTemplate.last.pjs).to match_array([])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#edit' do
|
||||
let(:export_template) { create(:export_template, groupe_instructeur:) }
|
||||
let(:subject) { get :edit, params: { procedure_id: procedure.id, id: export_template.id } }
|
||||
subject { get :edit, params: { procedure_id: procedure.id, id: export_template.id } }
|
||||
|
||||
it 'render edit' do
|
||||
subject
|
||||
|
@ -109,42 +107,53 @@ describe Instructeurs::ExportTemplatesController, type: :controller do
|
|||
|
||||
describe '#update' do
|
||||
let(:export_template) { create(:export_template, groupe_instructeur:) }
|
||||
let(:tiptap_pdf_name) {
|
||||
{
|
||||
"type" => "doc",
|
||||
"content" => [
|
||||
{ "type" => "paragraph", "content" => [{ "text" => "exPort_", "type" => "text" }, { "type" => "mention", "attrs" => { "id" => "dossier_number", "label" => "numéro du dossier" } }] }
|
||||
]
|
||||
}.to_json
|
||||
}
|
||||
let(:export_pdf) { item_params(text: "exPort_") }
|
||||
|
||||
let(:subject) { put :update, params: { procedure_id: procedure.id, id: export_template.id, export_template: export_template_params } }
|
||||
subject { put :update, params: { procedure_id: procedure.id, id: export_template.id, export_template: export_template_params } }
|
||||
|
||||
context 'with valid params' do
|
||||
it 'redirect to some page' do
|
||||
subject
|
||||
expect(response).to redirect_to(exports_instructeur_procedure_path(procedure:))
|
||||
expect(response).to redirect_to(exports_instructeur_procedure_path(procedure))
|
||||
expect(flash.notice).to eq "Le modèle d'export coucou a bien été modifié"
|
||||
|
||||
export_template.reload
|
||||
|
||||
expect(export_template.export_pdf.template_json).to eq(item_params(text: "exPort_")["template"])
|
||||
expect(export_template.pjs.map(&:template_json)).to eq([item_params(text: "avis-commission-")["template"]])
|
||||
end
|
||||
end
|
||||
|
||||
context 'with invalid params' do
|
||||
let(:tiptap_pdf_name) { { content: "invalid" }.to_json }
|
||||
let(:export_pdf) do
|
||||
item_params(text: 'a').merge("template" => { "content" => [{ "content" => "invalid" }] }.to_json)
|
||||
end
|
||||
|
||||
it 'display error notification' do
|
||||
subject
|
||||
expect(flash.alert).to be_present
|
||||
end
|
||||
end
|
||||
|
||||
context 'with invalid groupe_instructeur_id' do
|
||||
let(:groupe_instructeur_id) { create(:groupe_instructeur).id }
|
||||
|
||||
it 'display error notification' do
|
||||
subject
|
||||
expect(export_template.export_pdf.template_json).not_to eq(item_params(text: "exPort_")["template"])
|
||||
expect(flash.alert).to be_present
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#destroy' do
|
||||
let(:export_template) { create(:export_template, groupe_instructeur:) }
|
||||
let(:subject) { delete :destroy, params: { procedure_id: procedure.id, id: export_template.id } }
|
||||
subject { delete :destroy, params: { procedure_id: procedure.id, id: export_template.id } }
|
||||
|
||||
context 'with valid params' do
|
||||
it 'redirect to some page' do
|
||||
subject
|
||||
expect(response).to redirect_to(exports_instructeur_procedure_path(procedure:))
|
||||
expect(response).to redirect_to(exports_instructeur_procedure_path(procedure))
|
||||
expect(flash.notice).to eq "Le modèle d'export Mon export a bien été supprimé"
|
||||
end
|
||||
end
|
||||
|
@ -155,7 +164,7 @@ describe Instructeurs::ExportTemplatesController, type: :controller do
|
|||
|
||||
let(:export_template) { create(:export_template, groupe_instructeur:) }
|
||||
|
||||
let(:subject) { get :preview, params: { procedure_id: procedure.id, id: export_template.id, export_template: export_template_params }, format: :turbo_stream }
|
||||
subject { get :preview, params: { procedure_id: procedure.id, id: export_template.id, export_template: export_template_params }, format: :turbo_stream }
|
||||
|
||||
it '' do
|
||||
dossier = create(:dossier, procedure: procedure, for_procedure_preview: true)
|
||||
|
@ -164,4 +173,30 @@ describe Instructeurs::ExportTemplatesController, type: :controller do
|
|||
expect(response.body).to include "mon_export_#{dossier.id}.pdf"
|
||||
end
|
||||
end
|
||||
|
||||
def pj_item_params(stable_id:, text:, enabled: true)
|
||||
item_params(text: text, enabled: enabled).merge("stable_id" => stable_id.to_s)
|
||||
end
|
||||
|
||||
def item_params(text:, enabled: true)
|
||||
{
|
||||
"enabled" => enabled,
|
||||
"template" => {
|
||||
"type" => "doc",
|
||||
"content" => content(text:)
|
||||
}.to_json
|
||||
}
|
||||
end
|
||||
|
||||
def content(text:)
|
||||
[
|
||||
{
|
||||
"type" => "paragraph",
|
||||
"content" => [
|
||||
{ "text" => text, "type" => "text" },
|
||||
{ "type" => "mention", "attrs" => { "id" => "dossier_number", "label" => "numéro du dossier" } }
|
||||
]
|
||||
}
|
||||
]
|
||||
end
|
||||
end
|
||||
|
|
|
@ -2,86 +2,11 @@ FactoryBot.define do
|
|||
factory :export_template do
|
||||
name { "Mon export" }
|
||||
groupe_instructeur
|
||||
content {
|
||||
{
|
||||
"pdf_name" =>
|
||||
{
|
||||
"type" => "doc",
|
||||
"content" => [
|
||||
{ "type" => "paragraph", "content" => [{ "text" => "export_", "type" => "text" }, { "type" => "mention", "attrs" => { "id" => "dossier_id", "label" => "id dossier" } }, { "text" => " .pdf", "type" => "text" }] }
|
||||
]
|
||||
},
|
||||
"default_dossier_directory" => {
|
||||
"type" => "doc",
|
||||
"content" =>
|
||||
[
|
||||
{
|
||||
"type" => "paragraph",
|
||||
"content" =>
|
||||
[
|
||||
{ "text" => "dossier_", "type" => "text" },
|
||||
{ "type" => "mention", "attrs" => { "id" => "dossier_number", "label" => "numéro du dossier" } },
|
||||
{ "text" => " ", "type" => "text" }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
kind { "zip" }
|
||||
initialize_with { ExportTemplate.default(name:, groupe_instructeur: groupe_instructeur) }
|
||||
|
||||
to_create do |export_template, _context|
|
||||
export_template.set_default_values
|
||||
export_template.save
|
||||
end
|
||||
|
||||
trait :with_custom_content do
|
||||
to_create do |export_template, context|
|
||||
export_template.set_default_values
|
||||
export_template.content = context.content
|
||||
export_template.save
|
||||
end
|
||||
end
|
||||
|
||||
trait :with_custom_ddd_prefix do
|
||||
transient do
|
||||
ddd_prefix { 'dossier_' }
|
||||
end
|
||||
|
||||
to_create do |export_template, context|
|
||||
export_template.set_default_values
|
||||
export_template.content["default_dossier_directory"]["content"] = [
|
||||
{
|
||||
"type" => "paragraph",
|
||||
"content" =>
|
||||
[
|
||||
{ "text" => context.ddd_prefix, "type" => "text" },
|
||||
{ "type" => "mention", "attrs" => { "id" => "dossier_number", "label" => "numéro du dossier" } },
|
||||
{ "text" => " ", "type" => "text" }
|
||||
]
|
||||
}
|
||||
]
|
||||
export_template.save
|
||||
end
|
||||
end
|
||||
|
||||
trait :with_date_depot_for_export_pdf do
|
||||
to_create do |export_template, _|
|
||||
export_template.set_default_values
|
||||
export_template.content["pdf_name"]["content"] = [
|
||||
{
|
||||
"type" => "paragraph",
|
||||
"content" =>
|
||||
[
|
||||
{ "text" => "export_", "type" => "text" },
|
||||
{ "type" => "mention", "attrs" => { "id" => "dossier_number", "label" => "numéro du dossier" } },
|
||||
{ "text" => "-", "type" => "text" },
|
||||
{ "type" => "mention", "attrs" => { "id" => "dossier_depose_at", "label" => "date de dépôt" } },
|
||||
{ "text" => " ", "type" => "text" }
|
||||
]
|
||||
}
|
||||
]
|
||||
export_template.save
|
||||
trait :enabled_pjs do
|
||||
after(:build) do |export_template, _evaluator|
|
||||
export_template.pjs.each { _1.instance_variable_set('@enabled', true) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,50 +1,78 @@
|
|||
describe PiecesJointesListConcern do
|
||||
describe '#pieces_jointes_list' do
|
||||
include Logic
|
||||
let(:procedure) { create(:procedure, types_de_champ_public:, types_de_champ_private:) }
|
||||
let(:types_de_champ_public) do
|
||||
[
|
||||
{ type: :integer_number, stable_id: 900 },
|
||||
{ type: :piece_justificative, libelle: "pj1", stable_id: 910 },
|
||||
{ type: :piece_justificative, libelle: "pj-cond", stable_id: 911, condition: ds_eq(champ_value(900), constant(1)) },
|
||||
{ type: :repetition, libelle: "Répétition", stable_id: 920, children: [{ type: :piece_justificative, libelle: "pj2", stable_id: 921 }] },
|
||||
{ type: :titre_identite, libelle: "pj3", stable_id: 930 }
|
||||
]
|
||||
end
|
||||
|
||||
let(:types_de_champ_private) do
|
||||
[
|
||||
{ type: :integer_number, stable_id: 950 },
|
||||
{ type: :piece_justificative, libelle: "pj5", stable_id: 960 },
|
||||
{ type: :piece_justificative, libelle: "pj-cond2", stable_id: 961, condition: ds_eq(champ_value(900), constant(1)) },
|
||||
{ type: :repetition, libelle: "Répétition2", stable_id: 970, children: [{ type: :piece_justificative, libelle: "pj6", stable_id: 971 }] }
|
||||
]
|
||||
end
|
||||
describe 'public_wrapped_partionned_pjs and exportables_pieces_jointes' do
|
||||
let(:procedure) { create(:procedure, types_de_champ_public:, types_de_champ_private:) }
|
||||
let(:types_de_champ_public) do
|
||||
[
|
||||
{ type: :integer_number, stable_id: 900 },
|
||||
{ type: :piece_justificative, libelle: "pj1", stable_id: 910 },
|
||||
{ type: :piece_justificative, libelle: "pj-cond", stable_id: 911, condition: ds_eq(champ_value(900), constant(1)) },
|
||||
{ type: :repetition, libelle: "Répétition", stable_id: 920, children: [{ type: :piece_justificative, libelle: "pj2", stable_id: 921 }] },
|
||||
{ type: :titre_identite, libelle: "pj3", stable_id: 930 }
|
||||
]
|
||||
end
|
||||
|
||||
let(:types_de_champ) { procedure.active_revision.types_de_champ }
|
||||
def find_by_stable_id(stable_id) = types_de_champ.find { _1.stable_id == stable_id }
|
||||
let(:types_de_champ_private) do
|
||||
[
|
||||
{ type: :integer_number, stable_id: 950 },
|
||||
{ type: :piece_justificative, libelle: "pj5", stable_id: 960 },
|
||||
{ type: :piece_justificative, libelle: "pj-cond2", stable_id: 961, condition: ds_eq(champ_value(900), constant(1)) },
|
||||
{ type: :repetition, libelle: "Répétition2", stable_id: 970, children: [{ type: :piece_justificative, libelle: "pj6", stable_id: 971 }] }
|
||||
]
|
||||
end
|
||||
|
||||
let(:pj1) { find_by_stable_id(910) }
|
||||
let(:pjcond) { find_by_stable_id(911) }
|
||||
let(:repetition) { find_by_stable_id(920) }
|
||||
let(:pj2) { find_by_stable_id(921) }
|
||||
let(:pj3) { find_by_stable_id(930) }
|
||||
let(:types_de_champ) { procedure.active_revision.types_de_champ }
|
||||
def find_by_stable_id(stable_id) = types_de_champ.find { _1.stable_id == stable_id }
|
||||
|
||||
let(:pj5) { find_by_stable_id(960) }
|
||||
let(:pjcond2) { find_by_stable_id(961) }
|
||||
let(:repetition2) { find_by_stable_id(970) }
|
||||
let(:pj6) { find_by_stable_id(971) }
|
||||
let(:pj1) { find_by_stable_id(910) }
|
||||
let(:pjcond) { find_by_stable_id(911) }
|
||||
let(:repetition) { find_by_stable_id(920) }
|
||||
let(:pj2) { find_by_stable_id(921) }
|
||||
let(:pj3) { find_by_stable_id(930) }
|
||||
|
||||
it "returns the list of pieces jointes without conditional" do
|
||||
expect(procedure.public_wrapped_partionned_pjs.first).to match_array([[pj1], [pj2, repetition], [pj3]])
|
||||
end
|
||||
let(:pj5) { find_by_stable_id(960) }
|
||||
let(:pjcond2) { find_by_stable_id(961) }
|
||||
let(:repetition2) { find_by_stable_id(970) }
|
||||
let(:pj6) { find_by_stable_id(971) }
|
||||
|
||||
it "returns the list of pieces jointes having conditional" do
|
||||
expect(procedure.public_wrapped_partionned_pjs.second).to match_array([[pjcond]])
|
||||
end
|
||||
it "returns the list of pieces jointes without conditional" do
|
||||
expect(procedure.public_wrapped_partionned_pjs.first).to match_array([[pj1], [pj2, repetition], [pj3]])
|
||||
end
|
||||
|
||||
it "returns the list of pieces jointes with private, without parent repetition, without titre identite" do
|
||||
expect(procedure.exportables_pieces_jointes.map(&:libelle)).to match_array([pj1, pj2, pjcond, pj5, pjcond2, pj6].map(&:libelle))
|
||||
it "returns the list of pieces jointes having conditional" do
|
||||
expect(procedure.public_wrapped_partionned_pjs.second).to match_array([[pjcond]])
|
||||
end
|
||||
|
||||
it "returns the list of pieces jointes with private, without parent repetition, without titre identite" do
|
||||
expect(procedure.exportables_pieces_jointes.map(&:libelle)).to match_array([pj1, pj2, pjcond, pj5, pjcond2, pj6].map(&:libelle))
|
||||
end
|
||||
|
||||
it "returns the same list but for all versions" do
|
||||
expect(procedure.exportables_pieces_jointes.map(&:libelle)).to match_array([pj1, pj2, pjcond, pj5, pjcond2, pj6].map(&:libelle))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#outdated_exportables_pieces_jointes' do
|
||||
let(:types_de_champ_public) do
|
||||
[
|
||||
{ type: :piece_justificative, libelle: "outdated", stable_id: 1 },
|
||||
{ type: :piece_justificative, libelle: "kept", stable_id: 2 }
|
||||
]
|
||||
end
|
||||
|
||||
let(:procedure) { create(:procedure, :published, types_de_champ_public:) }
|
||||
|
||||
before do
|
||||
procedure.draft_revision.remove_type_de_champ(1)
|
||||
procedure.draft_revision.add_type_de_champ(type_champ: :piece_justificative, libelle: 'new', mandatory: false)
|
||||
procedure.publish_revision!
|
||||
end
|
||||
|
||||
it { expect(procedure.exportables_pieces_jointes_for_all_versions.map(&:libelle)).to eq(["new", "kept", "outdated"]) }
|
||||
it { expect(procedure.exportables_pieces_jointes.map(&:libelle)).to match_array(["kept", "new"]) }
|
||||
it { expect(procedure.outdated_exportables_pieces_jointes.map(&:libelle)).to match_array(["outdated"]) }
|
||||
end
|
||||
end
|
||||
|
|
20
spec/models/export_item_spec.rb
Normal file
20
spec/models/export_item_spec.rb
Normal file
|
@ -0,0 +1,20 @@
|
|||
describe ExportItem do
|
||||
describe 'path' do
|
||||
let(:export_item) { ExportItem.default(prefix: 'custom') }
|
||||
let(:dossier) { create(:dossier) }
|
||||
let(:attachment) do
|
||||
ActiveStorage::Attachment.new(
|
||||
name: 'filename',
|
||||
blob: ActiveStorage::Blob.new(filename: "file.pdf")
|
||||
)
|
||||
end
|
||||
|
||||
context 'without index nor row_index' do
|
||||
it do
|
||||
expect(export_item.path(dossier, attachment:)).to eq("custom-#{dossier.id}.pdf")
|
||||
expect(export_item.path(dossier, attachment:, index: 3)).to eq("custom-#{dossier.id}-04.pdf")
|
||||
expect(export_item.path(dossier, attachment:, row_index: 2, index: 3)).to eq("custom-#{dossier.id}-03-04.pdf")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -110,9 +110,10 @@ RSpec.describe Export, type: :model do
|
|||
end
|
||||
|
||||
context 'with export template' do
|
||||
let(:export_template) { build(:export_template) }
|
||||
let(:export_template) { create(:export_template, groupe_instructeur: gi_1) }
|
||||
|
||||
it 'creates new export' do
|
||||
expect { Export.find_or_create_fresh_export(:zip, [gi_1], instructeur, export_template: export_template, time_span_type: Export.time_span_types.fetch(:everything), statut: Export.statuts.fetch(:tous), procedure_presentation: pp) }
|
||||
expect { Export.find_or_create_fresh_export(:zip, [gi_1], instructeur, export_template:, time_span_type: Export.time_span_types.fetch(:everything), statut: Export.statuts.fetch(:tous), procedure_presentation: pp) }
|
||||
.to change { Export.count }.by(1)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
describe ExportTemplate do
|
||||
let(:groupe_instructeur) { create(:groupe_instructeur, procedure:) }
|
||||
let(:export_template) { create(:export_template, :with_custom_content, groupe_instructeur:, content:) }
|
||||
let(:procedure) { create(:procedure_with_dossiers, types_de_champ_public:, for_individual:) }
|
||||
let(:dossier) { procedure.dossiers.first }
|
||||
let(:export_template) { build(:export_template, groupe_instructeur:) }
|
||||
let(:procedure) { create(:procedure, types_de_champ_public:, for_individual:) }
|
||||
let(:for_individual) { false }
|
||||
let(:types_de_champ_public) do
|
||||
[
|
||||
|
@ -10,335 +9,80 @@ describe ExportTemplate do
|
|||
{ type: :titre_identite, libelle: "CNI", mandatory: true, stable_id: 5 }
|
||||
]
|
||||
end
|
||||
let(:content) do
|
||||
{
|
||||
"pdf_name" => {
|
||||
"type" => "doc",
|
||||
"content" => [
|
||||
{ "type" => "paragraph", "content" => [{ "text" => "mon_export_", "type" => "text" }, { "type" => "mention", "attrs" => { "id" => "dossier_number", "label" => "numéro du dossier" } }] }
|
||||
]
|
||||
},
|
||||
"default_dossier_directory" => {
|
||||
"type" => "doc",
|
||||
"content" => [
|
||||
{ "type" => "paragraph", "content" => [{ "text" => "DOSSIER_", "type" => "text" }, { "type" => "mention", "attrs" => { "id" => "dossier_number", "label" => "numéro du dossier" } }, { "text" => " ", "type" => "text" }] }
|
||||
]
|
||||
},
|
||||
"pjs" =>
|
||||
[
|
||||
{ path: { "type" => "doc", "content" => [{ "type" => "paragraph", "content" => [{ "type" => "mention", "attrs" => { "id" => "original-filename", "label" => "nom original du fichier" } }, { "text" => " _justif", "type" => "text" }] }] }, stable_id: "3" },
|
||||
{
|
||||
path:
|
||||
{ "type" => "doc", "content" => [{ "type" => "paragraph", "content" => [{ "text" => "cni_", "type" => "text" }, { "type" => "mention", "attrs" => { "id" => "dossier_number", "label" => "numéro du dossier" } }, { "text" => " ", "type" => "text" }] }] },
|
||||
stable_id: "5"
|
||||
},
|
||||
{
|
||||
path: { "type" => "doc", "content" => [{ "type" => "paragraph", "content" => [{ "text" => "pj_repet_", "type" => "text" }, { "type" => "mention", "attrs" => { "id" => "dossier_number", "label" => "numéro du dossier" } }, { "text" => " ", "type" => "text" }] }] },
|
||||
stable_id: "10"
|
||||
}
|
||||
]
|
||||
}
|
||||
end
|
||||
|
||||
describe 'new' do
|
||||
let(:export_template) { build(:export_template, groupe_instructeur: groupe_instructeur) }
|
||||
describe '.default' do
|
||||
it 'set default values' do
|
||||
export_template.set_default_values
|
||||
expect(export_template.content).to eq({
|
||||
"pdf_name" => {
|
||||
"type" => "doc",
|
||||
"content" => [
|
||||
{ "type" => "paragraph", "content" => [{ "text" => "export_", "type" => "text" }, { "type" => "mention", "attrs" => ExportTemplate::DOSSIER_ID_TAG.stringify_keys }] }
|
||||
]
|
||||
},
|
||||
"default_dossier_directory" => {
|
||||
"type" => "doc",
|
||||
"content" => [
|
||||
{ "type" => "paragraph", "content" => [{ "text" => "dossier-", "type" => "text" }, { "type" => "mention", "attrs" => ExportTemplate::DOSSIER_ID_TAG.stringify_keys }] }
|
||||
]
|
||||
},
|
||||
"pjs" =>
|
||||
[
|
||||
|
||||
{
|
||||
"stable_id" => "3",
|
||||
"path" => { "type" => "doc", "content" => [{ "type" => "paragraph", "content" => [{ "text" => "justificatif-de-domicile-", "type" => "text" }, { "type" => "mention", "attrs" => ExportTemplate::DOSSIER_ID_TAG.stringify_keys }] }] }
|
||||
}
|
||||
]
|
||||
})
|
||||
expect(export_template.export_pdf).to eq(ExportItem.default(prefix: "export", enabled: true))
|
||||
expect(export_template.dossier_folder).to eq(ExportItem.default(prefix: "dossier", enabled: true))
|
||||
expect(export_template.pjs).to eq([ExportItem.default(stable_id: 3, prefix: "justificatif-de-domicile", enabled: false)])
|
||||
end
|
||||
end
|
||||
|
||||
describe '#assign_pj_names' do
|
||||
let(:pj_params) do
|
||||
{
|
||||
"tiptap_pj_1" => {
|
||||
"type" => "doc", "content" => [{ "type" => "paragraph", "content" => [{ "type" => "text", "text" => "avis-commission-" }, { "type" => "mention", "attrs" => { "id" => "dossier_number", "label" => "numéro du dossier" } }] }]
|
||||
}.to_json
|
||||
}
|
||||
describe '#pj' do
|
||||
context 'when pj exists' do
|
||||
subject { export_template.pj(double(stable_id: 3)) }
|
||||
|
||||
it { is_expected.to eq(ExportItem.default(stable_id: 3, prefix: "justificatif-de-domicile", enabled: false)) }
|
||||
end
|
||||
it 'values content from pj params' do
|
||||
export_template.assign_pj_names(pj_params)
|
||||
expect(export_template.content["pjs"]).to eq [
|
||||
{ :path => { "content" => [{ "content" => [{ "text" => "avis-commission-", "type" => "text" }, { "attrs" => { "id" => "dossier_number", "label" => "numéro du dossier" }, "type" => "mention" }], "type" => "paragraph" }], "type" => "doc" }, :stable_id => "1" }
|
||||
]
|
||||
|
||||
context 'when pj does not exist' do
|
||||
subject { export_template.pj(TypeDeChamp.new(libelle: 'hi', stable_id: 10)) }
|
||||
|
||||
it { is_expected.to eq(ExportItem.default(stable_id: 10, prefix: "hi", enabled: false)) }
|
||||
end
|
||||
end
|
||||
|
||||
describe '#tiptap_default_dossier_directory' do
|
||||
it 'returns tiptap_default_dossier_directory from content' do
|
||||
expect(export_template.tiptap_default_dossier_directory).to eq({
|
||||
"type" => "doc",
|
||||
"content" => [
|
||||
{ "type" => "paragraph", "content" => [{ "text" => "DOSSIER_", "type" => "text" }, { "type" => "mention", "attrs" => { "id" => "dossier_number", "label" => "numéro du dossier" } }, { "text" => " ", "type" => "text" }] }
|
||||
]
|
||||
}.to_json)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#tiptap_pdf_name' do
|
||||
it 'returns tiptap_pdf_name from content' do
|
||||
expect(export_template.tiptap_pdf_name).to eq({
|
||||
"type" => "doc",
|
||||
"content" => [
|
||||
{ "type" => "paragraph", "content" => [{ "text" => "mon_export_", "type" => "text" }, { "type" => "mention", "attrs" => { "id" => "dossier_number", "label" => "numéro du dossier" } }] }
|
||||
]
|
||||
}.to_json)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#content_for_pj' do
|
||||
let(:type_de_champ_pj) { create(:type_de_champ_piece_justificative, stable_id: 3, libelle: 'Justificatif de domicile', procedure:) }
|
||||
let(:champ_pj) { create(:champ_piece_justificative, type_de_champ: type_de_champ_pj) }
|
||||
|
||||
let(:attachment) { ActiveStorage::Attachment.new(name: 'pj', record: champ_pj, blob: ActiveStorage::Blob.new(filename: "superpj.png")) }
|
||||
|
||||
it 'returns tiptap content for pj' do
|
||||
expect(export_template.content_for_pj(type_de_champ_pj)).to eq({
|
||||
"type" => "doc",
|
||||
"content" => [
|
||||
{ "type" => "paragraph", "content" => [{ "type" => "mention", "attrs" => { "id" => "original-filename", "label" => "nom original du fichier" } }, { "text" => " _justif", "type" => "text" }] }
|
||||
]
|
||||
}.to_json)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#attachment_and_path' do
|
||||
let(:dossier) { create(:dossier) }
|
||||
describe '#attachment_path' do
|
||||
let(:dossier) { create(:dossier, :en_construction, procedure:) }
|
||||
|
||||
context 'for export pdf' do
|
||||
let(:attachment) { double("attachment") }
|
||||
let(:export_template) do
|
||||
build(:export_template, groupe_instructeur:, dossier_folder: ExportItem.default(prefix: "DOSSIER"), export_pdf: ExportItem.default(prefix: "mon_export"))
|
||||
end
|
||||
|
||||
let(:attachment) { ActiveStorage::Attachment.new(name: 'pdf_export_for_instructeur', blob: ActiveStorage::Blob.new(filename: "export.pdf")) }
|
||||
|
||||
it 'gives absolute filename for export of specific dossier' do
|
||||
allow(attachment).to receive(:name).and_return('pdf_export_for_instructeur')
|
||||
expect(export_template.attachment_and_path(dossier, attachment)).to eq([attachment, "DOSSIER_#{dossier.id}/mon_export_#{dossier.id}.pdf"])
|
||||
expect(export_template.attachment_path(dossier, attachment)).to eq("DOSSIER-#{dossier.id}/mon_export-#{dossier.id}.pdf")
|
||||
end
|
||||
end
|
||||
|
||||
context 'for pj' do
|
||||
let(:dossier) { create(:dossier, :with_populated_champs, procedure:) }
|
||||
let(:champ_pj) { dossier.champs.find(&:piece_justificative?) }
|
||||
let(:champ_pj) { dossier.champs_public.first }
|
||||
let(:export_template) { create(:export_template, groupe_instructeur:, pjs: [ExportItem.default(stable_id: 3, prefix: "justif", enabled: true)]) }
|
||||
|
||||
let(:attachment) { ActiveStorage::Attachment.new(name: 'pj', record: champ_pj, blob: ActiveStorage::Blob.new(filename: "superpj.png")) }
|
||||
|
||||
it 'returns pj and custom name for pj' do
|
||||
expect(export_template.attachment_and_path(dossier, attachment, champ: champ_pj)).to eq([attachment, "DOSSIER_#{dossier.id}/superpj_justif-1.png"])
|
||||
end
|
||||
end
|
||||
context 'pj repetable' do
|
||||
let(:procedure) { create(:procedure, :for_individual, types_de_champ_public:) }
|
||||
let(:dossier) { create(:dossier, :with_populated_champs, procedure:) }
|
||||
let(:draft) { procedure.draft_revision }
|
||||
let(:types_de_champ_public) do
|
||||
[
|
||||
{
|
||||
type: :repetition,
|
||||
stable_id: 3333,
|
||||
mandatory: true, children: [
|
||||
{ type: :text, libelle: 'sub type de champ' },
|
||||
{ type: :piece_justificative, stable_id: 10, libelle: 'pj repet' }
|
||||
]
|
||||
}
|
||||
]
|
||||
end
|
||||
let(:champ_pj) { dossier.champs.find(&:piece_justificative?) }
|
||||
let(:attachment) { ActiveStorage::Attachment.new(name: 'pj', record: champ_pj, blob: ActiveStorage::Blob.new(filename: "superpj.png")) }
|
||||
|
||||
it 'rename repetable pj' do
|
||||
expect(export_template.attachment_and_path(dossier, attachment, champ: champ_pj)).to eq([attachment, "DOSSIER_#{dossier.id}/pj_repet_#{dossier.id}-1.png"])
|
||||
expect(export_template.attachment_path(dossier, attachment, champ: champ_pj)).to eq("dossier-#{dossier.id}/justif-#{dossier.id}-01.png")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#tiptap_convert' do
|
||||
it 'convert default dossier directory' do
|
||||
expect(export_template.tiptap_convert(procedure.dossiers.first, "default_dossier_directory")).to eq "DOSSIER_#{dossier.id}"
|
||||
end
|
||||
describe '#tags and #pj_tags' do
|
||||
let(:procedure) { build(:procedure, for_individual:) }
|
||||
|
||||
it 'convert pdf_name' do
|
||||
expect(export_template.tiptap_convert(procedure.dossiers.first, "pdf_name")).to eq "mon_export_#{dossier.id}"
|
||||
end
|
||||
|
||||
context 'for date' do
|
||||
let(:export_template) { create(:export_template, :with_date_depot_for_export_pdf, groupe_instructeur:) }
|
||||
let(:dossier) { create(:dossier, :en_construction, procedure:, depose_at: Date.parse("2024/03/30")) }
|
||||
it 'convert date with dash' do
|
||||
expect(export_template.tiptap_convert(dossier, "pdf_name")).to eq "export_#{dossier.id}-2024-03-30"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#tiptap_convert_pj' do
|
||||
let(:procedure) { create(:procedure, types_de_champ_public: [{ type: :piece_justificative, stable_id: 3, libelle: 'Justificatif de domicile' }]) }
|
||||
let(:dossier) { create(:dossier, :with_populated_champs, procedure:) }
|
||||
let(:champ_pj) { dossier.champs.first }
|
||||
let(:attachment) { ActiveStorage::Attachment.new(name: 'pj', record: champ_pj, blob: ActiveStorage::Blob.new(filename: "superpj.png")) }
|
||||
|
||||
it 'convert pj' do
|
||||
attachment
|
||||
expect(export_template.tiptap_convert_pj(dossier, 3, attachment)).to eq "superpj_justif"
|
||||
end
|
||||
end
|
||||
|
||||
describe '#valid?' do
|
||||
let(:subject) { build(:export_template, groupe_instructeur:, content:) }
|
||||
let(:ddd_text) { "DoSSIER" }
|
||||
let(:mention) { { "type" => "mention", "attrs" => { "id" => "dossier_number", "label" => "numéro du dossier" } } }
|
||||
let(:ddd_mention) { mention }
|
||||
let(:pdf_text) { "export" }
|
||||
let(:pdf_mention) { mention }
|
||||
let(:pj_text) { "_pj" }
|
||||
let(:pj_mention) { mention }
|
||||
let(:content) do
|
||||
{
|
||||
"pdf_name" => {
|
||||
"type" => "doc",
|
||||
"content" => [
|
||||
{ "type" => "paragraph", "content" => [{ "text" => pdf_text, "type" => "text" }, pdf_mention] }
|
||||
]
|
||||
},
|
||||
"default_dossier_directory" => {
|
||||
"type" => "doc",
|
||||
"content" => [
|
||||
{ "type" => "paragraph", "content" => [{ "text" => ddd_text, "type" => "text" }, ddd_mention] }
|
||||
]
|
||||
},
|
||||
"pjs" =>
|
||||
[
|
||||
{ path: { "type" => "doc", "content" => [{ "type" => "paragraph", "content" => [pj_mention, { "text" => pj_text, "type" => "text" }] }] }, stable_id: "3" }
|
||||
]
|
||||
}
|
||||
end
|
||||
|
||||
context 'with valid default dossier directory' do
|
||||
it 'has no error for default_dossier_directory' do
|
||||
expect(subject.valid?).to be_truthy
|
||||
end
|
||||
end
|
||||
|
||||
context 'with no ddd text' do
|
||||
let(:ddd_text) { " " }
|
||||
context 'with mention' do
|
||||
let(:ddd_mention) { { "type" => "mention", "attrs" => { "id" => "dossier_number", "label" => "numéro du dossier" } } }
|
||||
it 'has no error for default_dossier_directory' do
|
||||
expect(subject.valid?).to be_truthy
|
||||
end
|
||||
context 'for entreprise procedure' do
|
||||
let(:for_individual) { false }
|
||||
let(:expected_tags) do
|
||||
['entreprise_siren', 'entreprise_numero_tva_intracommunautaire', 'entreprise_siret_siege_social', 'entreprise_raison_sociale', 'entreprise_adresse', 'dossier_depose_at', 'dossier_procedure_libelle', 'dossier_service_name', 'dossier_number', 'dossier_groupe_instructeur']
|
||||
end
|
||||
|
||||
context 'without numéro de dossier' do
|
||||
let(:ddd_mention) { { "type" => "mention", "attrs" => { "id" => 'dossier_service_name', "label" => "nom du service" } } }
|
||||
it "add error for tiptap_default_dossier_directory" do
|
||||
expect(subject.valid?).to be_falsey
|
||||
expect(subject.errors[:tiptap_default_dossier_directory]).to be_present
|
||||
expect(subject.errors.full_messages).to include "Le champ « Nom du répertoire » doit contenir le numéro du dossier"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with valid pdf name' do
|
||||
it 'has no error for pdf name' do
|
||||
expect(subject.valid?).to be_truthy
|
||||
expect(subject.errors[:tiptap_pdf_name]).not_to be_present
|
||||
end
|
||||
end
|
||||
|
||||
context 'with pdf text and without mention' do
|
||||
let(:pdf_text) { "export" }
|
||||
let(:pdf_mention) { { "type" => "mention", "attrs" => {} } }
|
||||
|
||||
it "add no error" do
|
||||
expect(subject.valid?).to be_truthy
|
||||
end
|
||||
end
|
||||
|
||||
context 'with no pdf text' do
|
||||
let(:pdf_text) { " " }
|
||||
|
||||
context 'with mention' do
|
||||
it 'has no error for default_dossier_directory' do
|
||||
expect(subject.valid?).to be_truthy
|
||||
expect(subject.errors[:tiptap_pdf_name]).not_to be_present
|
||||
end
|
||||
end
|
||||
|
||||
context 'without mention' do
|
||||
let(:pdf_mention) { { "type" => "mention", "attrs" => {} } }
|
||||
it "add error for pdf name" do
|
||||
expect(subject.valid?).to be_falsey
|
||||
expect(subject.errors.full_messages).to include "Le champ « Nom du dossier au format pdf » doit être rempli"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with no pj text' do
|
||||
# let!(:type_de_champ_pj) { create(:type_de_champ_piece_justificative, stable_id: 3, libelle: 'Justificatif de domicile', procedure:) }
|
||||
let(:pj_text) { " " }
|
||||
|
||||
context 'with mention' do
|
||||
it 'has no error for pj' do
|
||||
expect(subject.valid?).to be_truthy
|
||||
end
|
||||
end
|
||||
|
||||
context 'without mention' do
|
||||
let(:pj_mention) { { "type" => "mention", "attrs" => {} } }
|
||||
it "add error for pj" do
|
||||
expect(subject.valid?).to be_falsey
|
||||
expect(subject.errors.full_messages).to include "Le champ « Justificatif de domicile » doit être rempli"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'for entreprise procedure' do
|
||||
let(:for_individual) { false }
|
||||
describe 'specific_tags' do
|
||||
it do
|
||||
tags = export_template.specific_tags
|
||||
expect(tags.map { _1[:id] }).to eq ["entreprise_siren", "entreprise_numero_tva_intracommunautaire", "entreprise_siret_siege_social", "entreprise_raison_sociale", "entreprise_adresse", "dossier_depose_at", "dossier_procedure_libelle", "dossier_service_name", "dossier_number", "dossier_groupe_instructeur"]
|
||||
expect(export_template.tags.map { _1[:id] }).to eq(expected_tags)
|
||||
expect(export_template.pj_tags.map { _1[:id] }).to eq(expected_tags + ['original-filename'])
|
||||
end
|
||||
end
|
||||
|
||||
describe 'tags_for_pj' do
|
||||
it do
|
||||
tags = export_template.tags_for_pj
|
||||
expect(tags.map { _1[:id] }).to eq ["entreprise_siren", "entreprise_numero_tva_intracommunautaire", "entreprise_siret_siege_social", "entreprise_raison_sociale", "entreprise_adresse", "dossier_depose_at", "dossier_procedure_libelle", "dossier_service_name", "dossier_number", "dossier_groupe_instructeur", "original-filename"]
|
||||
context 'for individual procedure' do
|
||||
let(:for_individual) { true }
|
||||
let(:expected_tags) do
|
||||
['individual_gender', 'individual_last_name', 'individual_first_name', 'dossier_depose_at', 'dossier_procedure_libelle', 'dossier_service_name', 'dossier_number', 'dossier_groupe_instructeur']
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'for individual procedure' do
|
||||
let(:for_individual) { true }
|
||||
describe 'specific_tags' do
|
||||
it do
|
||||
tags = export_template.specific_tags
|
||||
expect(tags.map { _1[:id] }).to eq ["individual_gender", "individual_last_name", "individual_first_name", "dossier_depose_at", "dossier_procedure_libelle", "dossier_service_name", "dossier_number", "dossier_groupe_instructeur"]
|
||||
end
|
||||
end
|
||||
|
||||
describe 'tags_for_pj' do
|
||||
it do
|
||||
tags = export_template.tags_for_pj
|
||||
expect(tags.map { _1[:id] }).to eq ["individual_gender", "individual_last_name", "individual_first_name", "dossier_depose_at", "dossier_procedure_libelle", "dossier_service_name", "dossier_number", "dossier_groupe_instructeur", "original-filename"]
|
||||
expect(export_template.tags.map { _1[:id] }).to eq(expected_tags)
|
||||
expect(export_template.pj_tags.map { _1[:id] }).to eq(expected_tags + ['original-filename'])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -302,4 +302,12 @@ describe TypeDeChamp do
|
|||
it { expect(create(:type_de_champ, :header_section, libelle: " 2.3 Test").libelle).to eq("2.3 Test") }
|
||||
it { expect(create(:type_de_champ, libelle: " fix me ").libelle).to eq("fix me") }
|
||||
end
|
||||
|
||||
describe '#safe_filename' do
|
||||
subject { build(:type_de_champ, libelle:).libelle_as_filename }
|
||||
|
||||
let(:libelle) { " #/🐉 1 très intéressant Bilan " }
|
||||
|
||||
it { is_expected.to eq("1-tres-interessant-bilan") }
|
||||
end
|
||||
end
|
||||
|
|
|
@ -25,7 +25,7 @@ describe PiecesJustificativesService do
|
|||
before { attach_file_to_champ(champ) }
|
||||
|
||||
it do
|
||||
expect(export_template).to receive(:attachment_and_path)
|
||||
expect(export_template).to receive(:attachment_path)
|
||||
.with(dossier, attachments(pj_champ(dossier)).first, index: 0, row_index: nil, champ:)
|
||||
subject
|
||||
end
|
||||
|
@ -40,10 +40,10 @@ describe PiecesJustificativesService do
|
|||
end
|
||||
|
||||
it do
|
||||
expect(export_template).to receive(:attachment_and_path)
|
||||
expect(export_template).to receive(:attachment_path)
|
||||
.with(dossier, attachments(pj_champ(dossier)).first, index: 0, row_index: nil, champ:)
|
||||
|
||||
expect(export_template).to receive(:attachment_and_path)
|
||||
expect(export_template).to receive(:attachment_path)
|
||||
.with(dossier, attachments(pj_champ(dossier)).second, index: 1, row_index: nil, champ:)
|
||||
subject
|
||||
end
|
||||
|
@ -66,13 +66,13 @@ describe PiecesJustificativesService do
|
|||
first_child_attachments = attachments(repetition(dossier).champs.first)
|
||||
second_child_attachments = attachments(repetition(dossier).champs.second)
|
||||
|
||||
expect(export_template).to receive(:attachment_and_path)
|
||||
expect(export_template).to receive(:attachment_path)
|
||||
.with(dossier, first_child_attachments.first, index: 0, row_index: 0, champ: first_champ)
|
||||
|
||||
expect(export_template).to receive(:attachment_and_path)
|
||||
expect(export_template).to receive(:attachment_path)
|
||||
.with(dossier, first_child_attachments.second, index: 1, row_index: 0, champ: first_champ)
|
||||
|
||||
expect(export_template).to receive(:attachment_and_path)
|
||||
expect(export_template).to receive(:attachment_path)
|
||||
.with(dossier, second_child_attachments.first, index: 0, row_index: 1, champ: second_champ)
|
||||
|
||||
count = 0
|
||||
|
@ -90,6 +90,7 @@ describe PiecesJustificativesService do
|
|||
describe '.liste_documents' do
|
||||
let(:dossier) { create(:dossier, procedure: procedure) }
|
||||
let(:dossiers) { Dossier.where(id: dossier.id) }
|
||||
let(:default_export_template) { build(:export_template, groupe_instructeur: procedure.defaut_groupe_instructeur) }
|
||||
let(:export_template) { nil }
|
||||
subject do
|
||||
PiecesJustificativesService.new(user_profile:, export_template:).liste_documents(dossiers).map(&:first)
|
||||
|
@ -99,39 +100,39 @@ describe PiecesJustificativesService do
|
|||
let(:user_profile) { build(:administrateur) }
|
||||
let(:procedure) { create(:procedure, types_de_champ_public: [{ type: :piece_justificative }]) }
|
||||
let(:witness) { create(:dossier, procedure: procedure) }
|
||||
let(:pj_champ) { -> (d) { d.champs_public.find { |c| c.type == 'Champs::PieceJustificativeChamp' } } }
|
||||
def pj_champ(d) = d.champs_public.find { |c| c.type == 'Champs::PieceJustificativeChamp' }
|
||||
|
||||
context 'with a single attachment' do
|
||||
before do
|
||||
attach_file_to_champ(pj_champ.call(dossier))
|
||||
attach_file_to_champ(pj_champ.call(witness))
|
||||
attach_file_to_champ(pj_champ(dossier))
|
||||
attach_file_to_champ(pj_champ(witness))
|
||||
end
|
||||
|
||||
it { expect(subject).to match_array(pj_champ.call(dossier).piece_justificative_file.attachments) }
|
||||
it { expect(subject).to match_array(pj_champ(dossier).piece_justificative_file.attachments) }
|
||||
|
||||
context 'with export_template' do
|
||||
let(:export_template) { create(:export_template, groupe_instructeur: procedure.defaut_groupe_instructeur) }
|
||||
it { expect(subject).to match_array(pj_champ.call(dossier).piece_justificative_file.attachments) }
|
||||
let(:export_template) { build(:export_template, :enabled_pjs, groupe_instructeur: procedure.defaut_groupe_instructeur) }
|
||||
|
||||
it { expect(subject).to match_array(pj_champ(dossier).piece_justificative_file.attachments) }
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a multiple attachments' do
|
||||
before do
|
||||
attach_file_to_champ(pj_champ.call(dossier))
|
||||
attach_file_to_champ(pj_champ.call(witness))
|
||||
attach_file_to_champ(pj_champ.call(dossier))
|
||||
attach_file_to_champ(pj_champ(dossier))
|
||||
attach_file_to_champ(pj_champ(witness))
|
||||
attach_file_to_champ(pj_champ(dossier))
|
||||
end
|
||||
|
||||
it { expect(subject.count).to eq(2) }
|
||||
it { expect(subject).to match_array(pj_champ.call(dossier).piece_justificative_file.attachments) }
|
||||
it { expect(subject).to match_array(pj_champ(dossier).piece_justificative_file.attachments) }
|
||||
end
|
||||
|
||||
context 'with a pj not safe on a champ' do
|
||||
let(:procedure) { create(:procedure, types_de_champ_public: [{ type: :piece_justificative }]) }
|
||||
let(:dossier) { create(:dossier, procedure: procedure) }
|
||||
let(:pj_champ) { -> (d) { d.champs_public.find { |c| c.type == 'Champs::PieceJustificativeChamp' } } }
|
||||
|
||||
before { attach_file_to_champ(pj_champ.call(dossier), safe = false) }
|
||||
before { attach_file_to_champ(pj_champ(dossier), false) }
|
||||
|
||||
it { expect(subject).to be_empty }
|
||||
end
|
||||
|
@ -139,7 +140,6 @@ describe PiecesJustificativesService do
|
|||
context 'with a identite champ pj' do
|
||||
let(:procedure) { create(:procedure, types_de_champ_public: [{ type: :titre_identite }]) }
|
||||
let(:dossier) { create(:dossier, procedure: procedure) }
|
||||
let(:witness) { create(:dossier, procedure: procedure) }
|
||||
|
||||
let(:champ_identite) { dossier.champs_public.find { |c| c.type == 'Champs::TitreIdentiteChamp' } }
|
||||
|
||||
|
@ -149,6 +149,12 @@ describe PiecesJustificativesService do
|
|||
expect(champ_identite.piece_justificative_file).to be_attached
|
||||
expect(subject).to be_empty
|
||||
end
|
||||
|
||||
context 'with export_template' do
|
||||
let(:export_template) { build(:export_template, :enabled_pjs, groupe_instructeur: procedure.defaut_groupe_instructeur) }
|
||||
|
||||
it { expect(subject).to be_empty }
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a pj on an commentaire' do
|
||||
|
@ -166,10 +172,9 @@ describe PiecesJustificativesService do
|
|||
it { expect(subject).to match_array(dossier.commentaires.first.piece_jointe.attachments) }
|
||||
|
||||
context 'with export_template' do
|
||||
let(:export_template) { create(:export_template, :with_custom_ddd_prefix, ddd_prefix: "DOSSIER-", groupe_instructeur: procedure.defaut_groupe_instructeur) }
|
||||
it 'uses specific name for dossier directory' do
|
||||
expect(PiecesJustificativesService.new(user_profile:, export_template:).liste_documents(dossiers).map(&:second)[0].starts_with?("DOSSIER-#{dossier.id}/messagerie")).to be true
|
||||
end
|
||||
let(:export_template) { default_export_template }
|
||||
|
||||
it { expect(subject).to be_empty }
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -177,7 +182,7 @@ describe PiecesJustificativesService do
|
|||
let(:dossier) { create(:dossier) }
|
||||
let!(:commentaire) { create(:commentaire, dossier: dossier) }
|
||||
|
||||
before { attach_file(commentaire.piece_jointe, safe = false) }
|
||||
before { attach_file(commentaire.piece_jointe, false) }
|
||||
|
||||
it { expect(subject).to be_empty }
|
||||
end
|
||||
|
@ -189,17 +194,16 @@ describe PiecesJustificativesService do
|
|||
it { expect(subject).to match_array(dossier.justificatif_motivation.attachment) }
|
||||
|
||||
context 'with export_template' do
|
||||
let(:export_template) { create(:export_template, :with_custom_ddd_prefix, ddd_prefix: "DOSSIER-", groupe_instructeur: procedure.defaut_groupe_instructeur) }
|
||||
it 'uses specific name for dossier directory' do
|
||||
expect(PiecesJustificativesService.new(user_profile:, export_template:).liste_documents(dossiers).map(&:second)[0].starts_with?("DOSSIER-#{dossier.id}/dossier")).to be true
|
||||
end
|
||||
let(:export_template) { default_export_template }
|
||||
|
||||
it { expect(subject).to be_empty }
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a motivation not safe' do
|
||||
let(:dossier) { create(:dossier) }
|
||||
|
||||
before { attach_file(dossier.justificatif_motivation, safe = false) }
|
||||
before { attach_file(dossier.justificatif_motivation, false) }
|
||||
|
||||
it { expect(subject).to be_empty }
|
||||
end
|
||||
|
@ -214,10 +218,9 @@ describe PiecesJustificativesService do
|
|||
end
|
||||
|
||||
context 'with export_template' do
|
||||
let(:export_template) { create(:export_template, :with_custom_ddd_prefix, ddd_prefix: "DOSSIER-", groupe_instructeur: procedure.defaut_groupe_instructeur) }
|
||||
it 'uses specific name for dossier directory' do
|
||||
expect(PiecesJustificativesService.new(user_profile:, export_template:).liste_documents(dossiers).map(&:second)[0].starts_with?("DOSSIER-#{dossier.id}/pieces_justificatives")).to be true
|
||||
end
|
||||
let(:export_template) { default_export_template }
|
||||
|
||||
it { expect(subject).to be_empty }
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -242,10 +245,9 @@ describe PiecesJustificativesService do
|
|||
end
|
||||
|
||||
context 'with export_template' do
|
||||
let(:export_template) { create(:export_template, :with_custom_ddd_prefix, ddd_prefix: "DOSSIER-", groupe_instructeur: procedure.defaut_groupe_instructeur) }
|
||||
it 'uses specific name for dossier directory' do
|
||||
expect(PiecesJustificativesService.new(user_profile:, export_template:).liste_documents(dossiers).map(&:second)[0].starts_with?("DOSSIER-#{dossier.id}/pieces_justificatives")).to be true
|
||||
end
|
||||
let(:export_template) { default_export_template }
|
||||
|
||||
it { expect(subject).to be_empty }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -256,32 +258,33 @@ describe PiecesJustificativesService do
|
|||
let(:witness) { create(:dossier, procedure: procedure) }
|
||||
|
||||
let!(:private_pj) { create(:type_de_champ_piece_justificative, procedure: procedure, private: true) }
|
||||
let(:private_pj_champ) { -> (d) { d.champs_private.find { |c| c.type == 'Champs::PieceJustificativeChamp' } } }
|
||||
def private_pj_champ(d) = d.champs_private.find { |c| c.type == 'Champs::PieceJustificativeChamp' }
|
||||
|
||||
before do
|
||||
attach_file_to_champ(private_pj_champ.call(dossier))
|
||||
attach_file_to_champ(private_pj_champ.call(witness))
|
||||
attach_file_to_champ(private_pj_champ(dossier))
|
||||
attach_file_to_champ(private_pj_champ(witness))
|
||||
end
|
||||
|
||||
context 'given an administrateur' do
|
||||
let(:user_profile) { build(:administrateur) }
|
||||
it { expect(subject).to match_array(private_pj_champ.call(dossier).piece_justificative_file.attachments) }
|
||||
it { expect(subject).to match_array(private_pj_champ(dossier).piece_justificative_file.attachments) }
|
||||
end
|
||||
|
||||
context 'given an instructeur' do
|
||||
let(:user_profile) { create(:instructeur) }
|
||||
it { expect(subject).to match_array(private_pj_champ.call(dossier).piece_justificative_file.attachments) }
|
||||
it { expect(subject).to match_array(private_pj_champ(dossier).piece_justificative_file.attachments) }
|
||||
end
|
||||
|
||||
context 'given an expert' do
|
||||
let(:user_profile) { create(:expert) }
|
||||
it { expect(subject).not_to match_array(private_pj_champ.call(dossier).piece_justificative_file.attachments) }
|
||||
it { expect(subject).not_to match_array(private_pj_champ(dossier).piece_justificative_file.attachments) }
|
||||
end
|
||||
end
|
||||
|
||||
context 'acl on bill' do
|
||||
let(:dossier) { create(:dossier) }
|
||||
let(:witness) { create(:dossier) }
|
||||
let(:default_export_template) { build(:export_template, groupe_instructeur: dossier.procedure.defaut_groupe_instructeur) }
|
||||
|
||||
let(:bill_signature) do
|
||||
bs = build(:bill_signature, :with_serialized, :with_signature)
|
||||
|
@ -309,6 +312,12 @@ describe PiecesJustificativesService do
|
|||
expect(subject).to match_array([dossier_bs.serialized.attachment, dossier_bs.signature.attachment])
|
||||
end
|
||||
|
||||
context 'with export_template' do
|
||||
let(:export_template) { default_export_template }
|
||||
|
||||
it { expect(subject).to be_empty }
|
||||
end
|
||||
|
||||
context 'with a dol' do
|
||||
let(:dol) { create(:dossier_operation_log, dossier: dossier) }
|
||||
let(:witness_dol) { create(:dossier_operation_log, dossier: witness) }
|
||||
|
@ -319,6 +328,12 @@ describe PiecesJustificativesService do
|
|||
end
|
||||
|
||||
it { expect(subject).to include(dol.serialized.attachment) }
|
||||
|
||||
context 'with export_template' do
|
||||
let(:export_template) { default_export_template }
|
||||
|
||||
it { expect(subject).to be_empty }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -361,6 +376,12 @@ describe PiecesJustificativesService do
|
|||
it "return confidentiel avis.piece_justificative_file" do
|
||||
expect(subject.size).to eq(2)
|
||||
end
|
||||
|
||||
context 'with export_template' do
|
||||
let(:export_template) { default_export_template }
|
||||
|
||||
it { expect(subject).to be_empty }
|
||||
end
|
||||
end
|
||||
|
||||
context 'given an instructeur' do
|
||||
|
@ -415,13 +436,6 @@ describe PiecesJustificativesService do
|
|||
it "return confidentiel avis.piece_justificative_file" do
|
||||
expect(subject.size).to eq(2)
|
||||
end
|
||||
|
||||
context 'with export_template' do
|
||||
let(:export_template) { create(:export_template, :with_custom_ddd_prefix, ddd_prefix: "DOSSIER-", groupe_instructeur: procedure.defaut_groupe_instructeur) }
|
||||
it 'uses specific name for dossier directory' do
|
||||
expect(PiecesJustificativesService.new(user_profile:, export_template:).liste_documents(dossiers).map(&:second)[0].starts_with?("DOSSIER-#{dossier.id}/avis")).to be true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'given an expert' do
|
||||
|
@ -464,11 +478,12 @@ describe PiecesJustificativesService do
|
|||
end
|
||||
|
||||
context 'with export template' do
|
||||
let(:export_template) { create(:export_template, :with_custom_ddd_prefix, ddd_prefix: "DOSSIER-", groupe_instructeur: procedure.defaut_groupe_instructeur) }
|
||||
let(:groupe_instructeur) { procedure.defaut_groupe_instructeur }
|
||||
let(:export_template) { create(:export_template, groupe_instructeur:, dossier_folder: ExportItem.default(prefix: 'DOSSIER')) }
|
||||
subject { PiecesJustificativesService.new(user_profile:, export_template:).generate_dossiers_export(dossiers) }
|
||||
|
||||
it 'gives custom name to export pdf file' do
|
||||
expect(subject.first.second).to eq "DOSSIER-#{dossier.id}/export_#{dossier.id}.pdf"
|
||||
expect(subject.first.second).to eq "DOSSIER-#{dossier.id}/export-#{dossier.id}.pdf"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -450,7 +450,7 @@ describe ProcedureExportService do
|
|||
context 'with export_template' do
|
||||
let!(:dossier) { create(:dossier, :accepte, :with_populated_champs, :with_individual, procedure: procedure) }
|
||||
let(:dossier_exports) { PiecesJustificativesService.new(user_profile: instructeur, export_template:).generate_dossiers_export(Dossier.where(id: dossier)) }
|
||||
let(:export_template) { create(:export_template, groupe_instructeur: procedure.defaut_groupe_instructeur) }
|
||||
let(:export_template) { create(:export_template, :enabled_pjs, groupe_instructeur: procedure.defaut_groupe_instructeur) }
|
||||
before do
|
||||
allow_any_instance_of(ActiveStorage::Attachment).to receive(:url).and_return("https://opengraph.githubassets.com/d0e7862b24d8026a3c03516d865b28151eb3859029c6c6c2e86605891fbdcd7a/socketry/async-io")
|
||||
end
|
||||
|
@ -465,10 +465,9 @@ describe ProcedureExportService do
|
|||
structure = [
|
||||
"#{base_fn}/",
|
||||
"#{base_fn}/dossier-#{dossier.id}/",
|
||||
"#{base_fn}/dossier-#{dossier.id}/piece_justificative-#{dossier.id}-1.txt",
|
||||
"#{base_fn}/dossier-#{dossier.id}/export_#{dossier.id}.pdf"
|
||||
"#{base_fn}/dossier-#{dossier.id}/piece_justificative-#{dossier.id}-01.txt",
|
||||
"#{base_fn}/dossier-#{dossier.id}/export-#{dossier.id}.pdf"
|
||||
]
|
||||
expect(files.size).to eq(structure.size)
|
||||
expect(files.map(&:filename)).to match_array(structure)
|
||||
end
|
||||
FileUtils.remove_entry_secure('tmp.zip')
|
||||
|
|
|
@ -2,7 +2,7 @@ describe ProcedureExportService do
|
|||
let(:instructeur) { create(:instructeur) }
|
||||
let(:procedure) { create(:procedure, types_de_champ_public: [{ type: :piece_justificative, libelle: 'pj' }, { type: :repetition, children: [{ type: :piece_justificative, libelle: 'repet_pj' }] }]) }
|
||||
let(:dossiers) { create_list(:dossier, 10, procedure: procedure) }
|
||||
let(:export_template) { create(:export_template, groupe_instructeur: procedure.defaut_groupe_instructeur) }
|
||||
let(:export_template) { create(:export_template, :enabled_pjs, groupe_instructeur: procedure.defaut_groupe_instructeur) }
|
||||
let(:service) { ProcedureExportService.new(procedure, procedure.dossiers, instructeur, export_template) }
|
||||
|
||||
def pj_champ(d) = d.champs_public.find_by(type: 'Champs::PieceJustificativeChamp')
|
||||
|
@ -49,11 +49,11 @@ describe ProcedureExportService do
|
|||
structure = [
|
||||
"export/",
|
||||
"export/dossier-#{dossier.id}/",
|
||||
"export/dossier-#{dossier.id}/export_#{dossier.id}.pdf",
|
||||
"export/dossier-#{dossier.id}/pj-#{dossier.id}-1.png",
|
||||
"export/dossier-#{dossier.id}/repet_pj-#{dossier.id}-1-1.png",
|
||||
"export/dossier-#{dossier.id}/repet_pj-#{dossier.id}-2-1.png",
|
||||
"export/dossier-#{dossier.id}/repet_pj-#{dossier.id}-1-2.png"
|
||||
"export/dossier-#{dossier.id}/export-#{dossier.id}.pdf",
|
||||
"export/dossier-#{dossier.id}/pj-#{dossier.id}-01.png",
|
||||
"export/dossier-#{dossier.id}/repet_pj-#{dossier.id}-01-01.png",
|
||||
"export/dossier-#{dossier.id}/repet_pj-#{dossier.id}-02-01.png",
|
||||
"export/dossier-#{dossier.id}/repet_pj-#{dossier.id}-01-02.png"
|
||||
]
|
||||
|
||||
expect(files.size).to eq(dossiers.count * 6 + 1)
|
||||
|
|
|
@ -193,19 +193,40 @@ RSpec.describe TiptapService do
|
|||
end
|
||||
end
|
||||
|
||||
describe '.to_path' do
|
||||
let(:substitutions) { { "dossier_number" => "42" } }
|
||||
let(:json) do
|
||||
{
|
||||
"content" => [
|
||||
{ "type" => "paragraph", "content" => [{ "text" => "export_", "type" => "text" }, { "type" => "mention", "attrs" => { "id" => "dossier_number", "label" => "numéro du dossier" } }, { "text" => " .pdf", "type" => "text" }] }
|
||||
]
|
||||
describe '.to_texts_and_tags' do
|
||||
subject { described_class.new.to_texts_and_tags(json, substitutions) }
|
||||
|
||||
}.deep_symbolize_keys
|
||||
context 'nominal' do
|
||||
let(:json) do
|
||||
{
|
||||
"content" => [
|
||||
{ "type" => "paragraph", "content" => [{ "text" => "export_", "type" => "text" }, { "type" => "mention", "attrs" => { "id" => "dossier_number", "label" => "numéro du dossier" } }, { "text" => " .pdf", "type" => "text" }] }
|
||||
]
|
||||
|
||||
}.deep_symbolize_keys
|
||||
end
|
||||
|
||||
context 'with substitutions' do
|
||||
let(:substitutions) { { "dossier_number" => "42" } }
|
||||
it 'returns texts_and_tags' do
|
||||
is_expected.to eq("export_42.pdf")
|
||||
end
|
||||
end
|
||||
|
||||
context 'without substitutions' do
|
||||
let(:substitutions) { nil }
|
||||
|
||||
it 'returns texts_and_tags' do
|
||||
is_expected.to eq("export_<span class='fr-tag fr-tag--sm'>numéro du dossier</span>.pdf")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it 'returns path' do
|
||||
expect(described_class.new.to_path(json, substitutions)).to eq("export_42.pdf")
|
||||
context 'empty paragraph' do
|
||||
let(:json) { { content: [{ type: 'paragraph' }] } }
|
||||
let(:substitutions) { {} }
|
||||
|
||||
it { is_expected.to eq('') }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
55
spec/types/export_item_type_spec.rb
Normal file
55
spec/types/export_item_type_spec.rb
Normal file
|
@ -0,0 +1,55 @@
|
|||
describe ExportItemType do
|
||||
let(:type) { ExportItemType.new }
|
||||
|
||||
describe 'cast' do
|
||||
it 'from ExportItem' do
|
||||
export_item = ExportItem.new(template: { foo: 'bar' }, enabled: true, stable_id: 42)
|
||||
expect(type.cast(export_item)).to eq(export_item)
|
||||
end
|
||||
|
||||
it 'from nil' do
|
||||
expect(type.cast(nil)).to eq(nil)
|
||||
end
|
||||
|
||||
it 'from db' do
|
||||
h = { template: { foo: 'bar' }, enabled: true, stable_id: 42 }
|
||||
expect(type.cast(h)).to eq(ExportItem.new(template: { foo: 'bar' }, enabled: true, stable_id: 42))
|
||||
end
|
||||
|
||||
it 'from form' do
|
||||
h = { template: '{"foo":{"bar":"zob"}}' }
|
||||
expect(type.cast(h)).to eq(ExportItem.new(template: { foo: { bar: 'zob' } }, enabled: false))
|
||||
|
||||
h = { template: '{"foo":{"bar":"zob"}}', enabled: 'true' }
|
||||
expect(type.cast(h)).to eq(ExportItem.new(template: { foo: { bar: 'zob' } }, enabled: true))
|
||||
|
||||
h = { template: '{"foo":{"bar":"zob"}}', stable_id: '42' }
|
||||
expect(type.cast(h)).to eq(ExportItem.new(template: { foo: { bar: 'zob' } }, enabled: false, stable_id: 42))
|
||||
|
||||
h = { template: '{"foo":{"bar":"zob"}}', enabled: 'true', stable_id: '42' }
|
||||
expect(type.cast(h)).to eq(ExportItem.new(template: { foo: { bar: 'zob' } }, enabled: true, stable_id: 42))
|
||||
end
|
||||
|
||||
it 'from invalid value' do
|
||||
expect { type.cast('invalid value') }.to raise_error(NoMatchingPatternError)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'deserialize' do
|
||||
it 'from nil' do
|
||||
expect(type.deserialize(nil)).to eq(nil)
|
||||
end
|
||||
|
||||
it 'from db' do
|
||||
h = { template: { foo: 'bar' }, enabled: true, stable_id: 42 }
|
||||
expect(type.deserialize(JSON.generate(h))).to eq(ExportItem.new(template: { foo: 'bar' }, enabled: true, stable_id: 42))
|
||||
end
|
||||
end
|
||||
|
||||
describe 'serialize' do
|
||||
it 'from ExportItem' do
|
||||
export_item = ExportItem.new(template: { foo: 'bar' }, enabled: true, stable_id: 42)
|
||||
expect(type.serialize(export_item)).to eq('{"template":{"foo":"bar"},"enabled":true,"stable_id":42}')
|
||||
end
|
||||
end
|
||||
end
|
79
spec/validators/export_template_validator_spec.rb
Normal file
79
spec/validators/export_template_validator_spec.rb
Normal file
|
@ -0,0 +1,79 @@
|
|||
describe ExportTemplateValidator do
|
||||
let(:validator) { ExportTemplateValidator.new }
|
||||
|
||||
describe 'validate' do
|
||||
let(:exportables_pieces_jointes) { [double('pj', stable_id: 3, libelle: 'libelle')] }
|
||||
let(:pj_libelle_by_stable_id) { exportables_pieces_jointes.map { |pj| [pj.stable_id, pj.libelle] }.to_h }
|
||||
|
||||
def empty_template(enabled: true, stable_id: nil)
|
||||
{ template: { type: "doc", content: [] }, enabled: enabled, stable_id: stable_id }.compact
|
||||
end
|
||||
|
||||
def errors(export_template) = export_template.errors.map { [_1.attribute, _1.message] }
|
||||
|
||||
before do
|
||||
allow(validator).to receive(:pj_libelle_by_stable_id).and_return(pj_libelle_by_stable_id)
|
||||
validator.validate(export_template)
|
||||
end
|
||||
|
||||
context 'with a default export template' do
|
||||
let(:export_template) { build(:export_template) }
|
||||
|
||||
it { expect(export_template.errors.count).to eq(0) }
|
||||
end
|
||||
|
||||
context 'with a invalid template' do
|
||||
let(:export_template) do
|
||||
export_pdf = { template: { is: 'invalid' }, enabled: true }
|
||||
build(:export_template, export_pdf:)
|
||||
end
|
||||
|
||||
it { expect(errors(export_template)).to eq([[:base, "Un nom de fichier est invalide"]]) }
|
||||
end
|
||||
|
||||
context 'with a empty export_pdf' do
|
||||
let(:export_template) { build(:export_template, export_pdf: empty_template) }
|
||||
|
||||
it { expect(errors(export_template)).to eq([[:export_pdf, "doit être rempli"]]) }
|
||||
end
|
||||
|
||||
context 'with a empty export_pdf disabled' do
|
||||
let(:export_template) { build(:export_template, export_pdf: empty_template(enabled: false)) }
|
||||
|
||||
it { expect(export_template.errors.count).to eq(0) }
|
||||
end
|
||||
|
||||
context 'with a dossier_folder without dossier_number' do
|
||||
let(:export_template) do
|
||||
dossier_folder = ExportItem.default(prefix: 'dossier')
|
||||
dossier_folder.template[:content][0][:content][1][:attrs][:id] = :other
|
||||
|
||||
build(:export_template, dossier_folder:)
|
||||
end
|
||||
|
||||
it { expect(errors(export_template)).to eq([[:dossier_folder, "doit contenir le numéro du dossier"]]) }
|
||||
end
|
||||
|
||||
context 'with a empty pj' do
|
||||
let(:export_template) { build(:export_template, pjs: [empty_template(stable_id: 3)]) }
|
||||
|
||||
it { expect(errors(export_template)).to eq([[:libelle, "doit être rempli"]]) }
|
||||
end
|
||||
|
||||
context 'with a empty pj disabled' do
|
||||
let(:export_template) { build(:export_template, pjs: [empty_template(enabled: false)]) }
|
||||
|
||||
it { expect(export_template.errors.count).to eq(0) }
|
||||
end
|
||||
|
||||
context 'with multiple files bearing the same template' do
|
||||
let(:export_item) { ExportItem.default(prefix: 'same') }
|
||||
|
||||
let(:export_template) do
|
||||
build(:export_template, export_pdf: export_item, pjs: [export_item])
|
||||
end
|
||||
|
||||
it { expect(errors(export_template)).to eq([[:base, "Les fichiers doivent avoir des noms différents"]]) }
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in a new issue