Merge pull request #10465 from colinux/attestations-v2-prod

ETQ admin je peux activer la délivrance des attestations v2 (sous feature flag)
This commit is contained in:
Colin Darie 2024-06-24 08:56:31 +00:00 committed by GitHub
commit ccf5b255ed
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
42 changed files with 724 additions and 258 deletions

View file

@ -623,40 +623,10 @@ textarea::placeholder {
color: $dark-grey;
}
@media (max-width: 62em) {
.padded-fixed-footer {
padding-top: 120px;
}
}
@media (min-width: 62em) {
.padded-fixed-footer {
padding-top: 60px;
}
}
[data-fr-theme="dark"] .fixed-footer {
border-top: 2px solid var(--background-action-low-blue-france-hover);
background-color: var(--background-action-low-blue-france);
}
.mandatory {
fill: currentColor;
}
.fixed-footer {
border-top: 2px solid $blue-france-500;
position: fixed;
bottom: 0;
left: 0;
right: 0;
padding-top: $default-padding;
background-color: $white;
z-index: 2;
}
.fr-menu__list {
padding: $default-spacer;
overflow-y: auto;

View file

@ -0,0 +1,49 @@
@import "constants";
.fixed-footer {
border-top: 2px solid var(--border-plain-blue-france);
position: fixed;
bottom: 0;
left: 0;
right: 0;
padding-top: $default-padding;
background-color: var(--background-default-grey);
z-index: 2;
}
@media (max-width: 62em) {
.padded-fixed-footer {
padding-top: 120px;
}
}
@media (min-width: 62em) {
.padded-fixed-footer {
padding-top: 60px;
}
}
[data-fr-theme="dark"] .fixed-footer {
background-color: var(--background-action-low-blue-france);
}
.sticky-header {
padding-top: $default-padding;
padding-bottom: $default-padding;
&-container {
position: sticky;
top: 0;
left: 0;
right: 0;
z-index: 800;
}
&-warning {
background-color: var(--background-contrast-warning);
}
p {
margin: 0;
}
}

View file

@ -0,0 +1,16 @@
# frozen_string_literal: true
class AutosaveNoticeComponent < ApplicationComponent
attr_reader :label_scope
def initialize(success:, label_scope:)
@success = success
@label_scope = label_scope
end
def success? = @success
def label
success? ? t(".#{label_scope}.saved") : t(".#{label_scope}.error")
end
end

View file

@ -0,0 +1,8 @@
---
en:
form:
saved: 'Form saved'
error: 'Form in error'
attestation:
saved: 'Attestation saved'
error: 'Attestation in error'

View file

@ -0,0 +1,8 @@
---
fr:
form:
saved: 'Formulaire enregistré'
error: 'Formulaire en erreur'
attestation:
saved: 'Attestation enregistrée'
error: 'Attestation en erreur'

View file

@ -0,0 +1,2 @@
#autosave-notice.fr-badge.fr-badge--sm{ class: class_names("fr-badge--success" => success?, "fr-badge--error" => !success?) }
= label

View file

@ -26,6 +26,8 @@ class Dsfr::CalloutComponent < ApplicationComponent
"fr-callout--brown-caramel"
when :success
"fr-callout--green-emeraude"
when :neutral
# default
else
"fr-background-alt--blue-france"
end

View file

@ -98,9 +98,7 @@ module Dsfr
})
end
if autoresize?
@opts.deep_merge!(data: { controller: 'autoresize' })
end
@opts.deep_merge!(data: { controller: token_list(@opts.dig(:data, :controller), 'autoresize' => autoresize?) })
@opts
end

View file

@ -5,6 +5,14 @@ class Procedure::Card::AttestationComponent < ApplicationComponent
private
def edit_attestation_path
if @procedure.attestation_templates_v2.any? || @procedure.feature_enabled?(:attestation_v2)
helpers.edit_admin_procedure_attestation_template_v2_path(@procedure)
else
helpers.edit_admin_procedure_attestation_template_path(@procedure)
end
end
def error_messages
@procedure.errors.messages_for(:attestation_template).to_sentence
end

View file

@ -1,5 +1,5 @@
.fr-col-6.fr-col-md-4.fr-col-lg-3
= link_to edit_admin_procedure_attestation_template_path(@procedure), class: 'fr-tile fr-enlarge-link' do
= link_to edit_attestation_path, class: 'fr-tile fr-enlarge-link' do
.fr-tile__body.flex.column.align-center.justify-between
- if @procedure.attestation_template&.activated?
%div

View file

@ -20,27 +20,9 @@ module Administrateurs
format.pdf do
html = render_to_string('/administrateurs/attestation_template_v2s/show', layout: 'attestation', formats: [:html])
headers = {
'Content-Type' => 'application/json',
'X-Request-Id' => Current.request_id
}
pdf = WeasyprintService.generate_pdf(html, procedure_id: @procedure.id, path: request.path, user_id: current_user.id)
body = {
html: html,
upstream_context: {
procedure_id: @procedure.id,
path: request.path,
user_id: current_user.id
}
}.to_json
response = Typhoeus.post(WEASYPRINT_URL, headers:, body:)
if response.success?
send_data(response.body, filename: 'attestation.pdf', type: 'application/pdf', disposition: 'inline')
else
raise StandardError.new("PDF Generation failed: #{response.return_code} #{response.status_message}")
end
send_data(pdf, filename: 'attestation.pdf', type: 'application/pdf', disposition: 'inline')
end
end
end
@ -77,6 +59,19 @@ module Administrateurs
def update
attestation_params = editor_params
# toggle activation
if @attestation_template.persisted? && @attestation_template.activated? != cast_bool(attestation_params[:activated])
@procedure.attestation_templates.v2.update_all(activated: attestation_params[:activated])
render :update && return
end
if @attestation_template.published? && should_edit_draft?
@attestation_template = @attestation_template.dup
@attestation_template.state = :draft
@attestation_template.procedure = @procedure
end
logo_file = attestation_params.delete(:logo)
signature_file = attestation_params.delete(:signature)
@ -88,15 +83,40 @@ module Administrateurs
attestation_params[:signature] = uninterlace_png(signature_file)
end
if !@attestation_template.update(attestation_params)
flash.alert = "Le modèle de lattestation contient des erreurs et n'a pas pu être enregistré. Corriger les erreurs."
end
@attestation_template.assign_attributes(attestation_params)
render :update
if @attestation_template.invalid?
flash.alert = "Lattestation contient des erreurs et n'a pas pu être enregistrée. Corriger les erreurs."
else
# - draft just published
if @attestation_template.published? && should_edit_draft?
published = @procedure.attestation_templates.published
@attestation_template.transaction do
were_published = published.destroy_all
@attestation_template.save!
flash.notice = were_published.any? ? "La nouvelle version de lattestation a été publiée." : "Lattestation a été publiée."
end
redirect_to edit_admin_procedure_attestation_template_v2_path(@procedure)
else
# - draft updated
# - or, attestation already published, without need for publication (draft procedure)
@attestation_template.save!
render :update
end
end
end
def create = update
def reset
@procedure.attestation_templates_v2.draft&.destroy_all
flash.notice = "Les modifications ont été réinitialisées."
redirect_to edit_admin_procedure_attestation_template_v2_path(@procedure)
end
private
def ensure_feature_active
@ -104,11 +124,19 @@ module Administrateurs
end
def retrieve_attestation_template
@attestation_template = @procedure.attestation_template_v2 || @procedure.build_attestation_template_v2(json_body: AttestationTemplate::TIPTAP_BODY_DEFAULT)
v2s = @procedure.attestation_templates_v2
@attestation_template = v2s.find(&:draft?) || v2s.find(&:published?) || build_default_attestation
end
def build_default_attestation
state = should_edit_draft? ? :draft : :published
@procedure.build_attestation_template(version: 2, json_body: AttestationTemplate::TIPTAP_BODY_DEFAULT, activated: true, state:)
end
def should_edit_draft? = !@procedure.brouillon?
def editor_params
params.required(:attestation_template).permit(:official_layout, :label_logo, :label_direction, :tiptap_body, :footer, :logo, :signature, :activated)
params.required(:attestation_template).permit(:activated, :official_layout, :label_logo, :label_direction, :tiptap_body, :footer, :logo, :signature, :activated, :state)
end
end
end

View file

@ -112,7 +112,7 @@ module Administrateurs
revision_types_de_champ: { type_de_champ: { piece_justificative_template_attachment: :blob } }
},
attestation_template_v1: [],
attestation_template_v2: [],
attestation_templates_v2: [],
initiated_mail: [],
received_mail: [],
closed_mail: [],

View file

@ -30,9 +30,10 @@ module Instructeurs
end
def apercu_attestation
@attestation = dossier.attestation_template.render_attributes_for(dossier: dossier)
render 'administrateurs/attestation_templates/show', formats: [:pdf]
send_data dossier.attestation_template.send(:build_pdf, dossier),
filename: 'attestation.pdf',
type: 'application/pdf',
disposition: 'inline'
end
def bilans_bdf

View file

@ -0,0 +1,43 @@
import { ApplicationController } from './application_controller';
export class StickyTopController extends ApplicationController {
// Ajusts top of sticky top components when there is a sticky header.
connect(): void {
const header = document.getElementById('sticky-header');
if (!header) {
return;
}
this.adjustTop(header);
window.addEventListener('resize', () => this.adjustTop(header));
this.listenHeaderMutations(header);
}
private listenHeaderMutations(header: HTMLElement) {
const config = { childList: true, subtree: true };
const callback: MutationCallback = (mutationsList) => {
for (const mutation of mutationsList) {
if (mutation.type === 'childList') {
this.adjustTop(header);
break;
}
}
};
const observer = new MutationObserver(callback);
observer.observe(header, config);
}
private adjustTop(header: HTMLElement) {
const headerHeight = header.clientHeight;
if (headerHeight > 0) {
(this.element as HTMLElement).style.top = `${headerHeight + 8}px`;
}
}
}

View file

@ -2,11 +2,16 @@ class AttestationTemplate < ApplicationRecord
include ActionView::Helpers::NumberHelper
include TagsSubstitutionConcern
belongs_to :procedure, inverse_of: :attestation_template_v2
belongs_to :procedure, inverse_of: :attestation_template
has_one_attached :logo
has_one_attached :signature
enum state: {
draft: 'draft',
published: 'published'
}
validates :title, tags: true, if: -> { procedure.present? && version == 1 }
validates :body, tags: true, if: -> { procedure.present? && version == 1 }
validates :json_body, tags: true, if: -> { procedure.present? && version == 2 }
@ -67,9 +72,10 @@ class AttestationTemplate < ApplicationRecord
}.freeze
def attestation_for(dossier)
attestation = Attestation.new(title: replace_tags(title, dossier, escape: false))
attestation = Attestation.new
attestation.title = replace_tags(title, dossier, escape: false) if version == 1
attestation.pdf.attach(
io: build_pdf(dossier),
io: StringIO.new(build_pdf(dossier)),
filename: "attestation-dossier-#{dossier.id}.pdf",
content_type: 'application/pdf',
# we don't want to run virus scanner on this file
@ -91,7 +97,7 @@ class AttestationTemplate < ApplicationRecord
end
def dup
attestation_template = AttestationTemplate.new(title: title, body: body, footer: footer, activated: activated)
attestation_template = super
ClonePiecesJustificativesService.clone_attachments(self, attestation_template)
attestation_template
end
@ -179,7 +185,7 @@ class AttestationTemplate < ApplicationRecord
if dossier.present?
# 2x faster this way than with `replace_tags` which would reparse text
used_tags = tiptap.used_tags_and_libelle_for(json.deep_symbolize_keys)
used_tags = TiptapService.used_tags_and_libelle_for(json.deep_symbolize_keys)
substitutions = tags_substitutions(used_tags, dossier, escape: false)
body = tiptap.to_html(json, substitutions)
@ -202,17 +208,41 @@ class AttestationTemplate < ApplicationRecord
end
def used_tags
used_tags_for(title) + used_tags_for(body)
if version == 2
json = json_body&.deep_symbolize_keys
TiptapService.used_tags_and_libelle_for(json.deep_symbolize_keys).map(&:first)
else
used_tags_for(title) + used_tags_for(body)
end
end
def build_pdf(dossier)
if version == 2
build_v2_pdf(dossier)
else
build_v1_pdf(dossier)
end
end
def build_v1_pdf(dossier)
attestation = render_attributes_for(dossier: dossier)
attestation_view = ApplicationController.render(
ApplicationController.render(
template: 'administrateurs/attestation_templates/show',
formats: :pdf,
assigns: { attestation: attestation }
)
end
StringIO.new(attestation_view)
def build_v2_pdf(dossier)
body = render_attributes_for(dossier:).fetch(:body)
html = ApplicationController.render(
template: '/administrateurs/attestation_template_v2s/show',
formats: [:html],
layout: 'attestation',
assigns: { attestation_template: self, body: body }
)
WeasyprintService.generate_pdf(html, { procedure_id: procedure.id, dossier_id: dossier.id })
end
end

View file

@ -257,7 +257,7 @@ module TagsSubstitutionConcern
def used_type_de_champ_tags(text_or_tiptap)
used_tags =
if text_or_tiptap.respond_to?(:deconstruct_keys) # hash pattern matching
TiptapService.new.used_tags_and_libelle_for(text_or_tiptap.deep_symbolize_keys)
TiptapService.used_tags_and_libelle_for(text_or_tiptap.deep_symbolize_keys)
else
used_tags_and_libelle_for(text_or_tiptap.to_s)
end

View file

@ -66,11 +66,10 @@ class ExportTemplate < ApplicationRecord
end
def render_attributes_for(content_for, dossier, attachment = nil)
tiptap = TiptapService.new
used_tags = tiptap.used_tags_and_libelle_for(content_for.deep_symbolize_keys)
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
tiptap.to_path(content_for.deep_symbolize_keys, substitutions)
TiptapService.new.to_path(content_for.deep_symbolize_keys, substitutions)
end
def specific_tags

View file

@ -50,9 +50,9 @@ class Procedure < ApplicationRecord
has_one :module_api_carto, dependent: :destroy
has_many :attestation_templates, dependent: :destroy
has_one :attestation_template_v1, -> { AttestationTemplate.v1 }, dependent: :destroy, class_name: "AttestationTemplate", inverse_of: :procedure
has_one :attestation_template_v2, -> { AttestationTemplate.v2 }, dependent: :destroy, class_name: "AttestationTemplate", inverse_of: :procedure
has_many :attestation_templates_v2, -> { AttestationTemplate.v2 }, dependent: :destroy, class_name: "AttestationTemplate", inverse_of: :procedure
has_one :attestation_template, -> { order(Arel.sql("CASE WHEN version = '1' THEN 0 ELSE 1 END")) }, dependent: :destroy, inverse_of: :procedure
has_one :attestation_template, -> { published }, dependent: :destroy, inverse_of: :procedure
belongs_to :parent_procedure, class_name: 'Procedure', optional: true
belongs_to :canonical_procedure, class_name: 'Procedure', optional: true

View file

@ -1,18 +1,6 @@
class TiptapService
def to_html(node, substitutions = {})
return '' if node.nil?
children(node[:content], substitutions, 0)
end
def to_path(node, substitutions = {})
return '' if node.nil?
children_path(node[:content], substitutions)
end
# NOTE: node must be deep symbolized keys
def used_tags_and_libelle_for(node, tags = Set.new)
def self.used_tags_and_libelle_for(node, tags = Set.new)
case node
in type: 'mention', attrs: { id:, label: }, **rest
tags << [id, label]
@ -25,6 +13,18 @@ class TiptapService
tags
end
def to_html(node, substitutions = {})
return '' if node.nil?
children(node[:content], substitutions, 0)
end
def to_path(node, substitutions = {})
return '' if node.nil?
children_path(node[:content], substitutions)
end
private
def initialize

View file

@ -0,0 +1,23 @@
# frozen_string_literal: true
class WeasyprintService
def self.generate_pdf(html, options = {})
headers = {
'Content-Type' => 'application/json',
'X-Request-Id' => Current.request_id
}
body = {
html:,
upstream_context: options
}.to_json
response = Typhoeus.post(WEASYPRINT_URL, headers:, body:)
if response.success?
response.body
else
raise StandardError, "PDF Generation failed: #{response.code} #{response.status_message}"
end
end
end

View file

@ -1,2 +0,0 @@
- success = local_assigns.fetch(:success, true)
#autosave-notice.fr-badge.fr-badge--sm{ class: class_names("fr-badge--success" => success, "fr-badge--error" => !success) }= success ? t(".form_saved") : t(".form_error")

View file

@ -0,0 +1,9 @@
.fr-container
.fr-grid-row.fr-grid-row--middle.fr-pb-3v
.fr-col-12.fr-col-md-4
= link_to admin_procedure_path(id: procedure), class: 'fr-link' do
%span.fr-icon-arrow-left-line.fr-icon--sm
Revenir à lécran de gestion
.fr-col-12.fr-col-md-8.text-right
%span#autosave-notice

View file

@ -0,0 +1,21 @@
.sticky-header.sticky-header-warning
.fr-container
%p.flex.justify-between.align-center.fr-text-default--warning
%span
= dsfr_icon("fr-icon-warning-fill fr-mr-1v")
- if @procedure.attestation_templates.many?
Les modifications effectuées ne seront appliquées quà la prochaine publication.
- else
Lattestation ne sera délivrée quaprès sa publication.
%span.no-wrap
- if @procedure.attestation_templates.many?
= link_to reset_admin_procedure_attestation_template_v2_path(@procedure), class: "fr-btn fr-btn--secondary fr-ml-2w", method: :post do
Réinitialiser les modifications
%button.fr-btn.fr-ml-2w{ form: "attestation-template", name: field_name(:attestation_template, :state), value: "published",
data: { 'disable-with': "Publication en cours…", controller: 'autosave-submit' } }
- if @procedure.attestation_templates.many?
Publier les modifications
- else
Publier

View file

@ -4,7 +4,8 @@
['Attestation']] }
= render NestedForms::FormOwnerComponent.new
= form_for @attestation_template, url: admin_procedure_attestation_template_v2_path(@procedure), html: { multipart: true },
= form_for @attestation_template, url: admin_procedure_attestation_template_v2_path(@procedure),
html: { multipart: true , id: "attestation-template" },
data: { turbo: 'true',
controller: 'autosubmit attestation',
autosubmit_debounce_delay_value: 1000,
@ -19,11 +20,12 @@
tout en respectant la charte de létat. Essayez-la et donnez-nous votre avis
en nous envoyant un email à #{mail_to(Current.contact_email, subject: "Feedback attestation v2")}.
%br
%strong Les attestations délivrées suivent encore lancien format :
lactivation des attestations basées sur ce format sera bientôt disponible.
%br
- if !@procedure.feature_enabled?(:attestation_v2)
%strong Les attestations délivrées suivent encore lancien format :
lactivation des attestations basées sur ce format sera bientôt disponible.
%br
= link_to("Suivez ce lien pour revenir aux attestations actuellement délivrées", edit_admin_procedure_attestation_template_path(@procedure))
= link_to("Suivez ce lien pour revenir aux attestations actuellement délivrées", edit_admin_procedure_attestation_template_path(@procedure))
.fr-grid-row.fr-grid-row--gutters
.fr-col-12.fr-col-lg-7
@ -34,13 +36,23 @@
Lattestation est émise au moment où un dossier est accepté, elle est jointe à lemail daccusé dacceptation.
Elle est également disponible au téléchargement depuis lespace personnel de lusager.
.fr-fieldset__element
= render Dsfr::CalloutComponent.new(title: "Activation de la délivrance de lattestation", theme: :neutral) do |c|
- c.with_html_body do
.fr-toggle.fr-toggle--label-left
= f.check_box :activated, class: "fr-toggle__input", id: dom_id(@attestation_template, :activated)
%label.fr-toggle__label{ for: dom_id(@attestation_template, :activated),
data: { fr_checked_label: "Activée", fr_unchecked_label: "Désactivée" } }
Activer cette option permet la délivrance automatique de lattestation dès lacceptation du dossier.
Désactiver cette option arrête immédiatement lémission de nouvelles attestations.
.fr-fieldset__element
%h2.fr-h4 En-tête
.fr-fieldset__element
.fr-toggle.fr-toggle--label-left
= f.check_box :official_layout, class: "fr-toggle__input", id: dom_id(@attestation_template, :official_layout), data: { "attestation-target": "layoutToggle"}
%label.fr-toggle__label{ for: dom_id(@attestation_template, :official_layout), data: { fr_checked_label: "Activé", fr_unchecked_label: "Désactivé" } }
%label.fr-toggle__label{ for: dom_id(@attestation_template, :official_layout), data: { fr_checked_label: "Oui", fr_unchecked_label: "Non" } }
Je souhaite générer une attestation à la charte de létat (logo avec Marianne)
.fr-fieldset__element{ class: class_names("hidden" => !@attestation_template.official_layout?), data: { "attestation-target": 'logoMarianneLabelFieldset'} }
@ -77,10 +89,10 @@
%button.fr-btn.fr-btn--secondary.fr-btn--sm{ type: 'button', title: label, class: icon == :hidden ? "hidden" : "fr-icon-#{icon}", data: { action: 'click->tiptap#menuButton', tiptap_target: 'button', tiptap_action: action } }
= label
#editor.tiptap-editor{ data: { tiptap_target: 'editor' }, aria: { describedby: dom_id(f.object, "json-body-messages")} }
#editor.tiptap-editor{ data: { tiptap_target: 'editor' }, aria: { describedby: "attestation-template-json-body-messages"} }
= f.hidden_field :tiptap_body, data: { tiptap_target: 'input' }
.fr-error-text{ id: dom_id(f.object, "json-body-messages"), class: class_names("hidden" => !f.object.errors.include?(:json_body)) }
.fr-error-text{ id: "attestation-template-json-body-messages", class: class_names("hidden" => !f.object.errors.include?(:json_body)) }
- if f.object.errors.include?(:json_body)
= render partial: "shared/errors_list", locals: { object: f.object, attribute: :json_body }
@ -108,7 +120,7 @@
- c.with_hint { "Exemple: 20 avenue de Ségur, 75007 Paris" }
#preview-column.fr-col-12.fr-col-lg-5.fr-background-alt--blue-france
.sticky--top.fr-px-1w
.sticky--top.fr-px-1w{ data: { controller: "sticky-top" } }
.flex.justify-between.align-center
%h2.fr-h4 Aperçu
%p= link_to 'Prévisualiser en taille réelle', admin_procedure_attestation_template_v2_path(@procedure, format: :pdf), class: 'fr-link', target: '_blank', rel: 'noopener'
@ -117,21 +129,10 @@
Laperçu est mis à jour automatiquement après chaque modification.
Pour générer un aperçu fidèle avec tous les champs et les dates, créez-vous un dossier et acceptez-le : laperçu lutilisera.
.padded-fixed-footer
.fixed-footer
.fr-container
.fr-grid-row
.fr-col-12.fr-col-md-7
%ul.fr-btns-group.fr-btns-group--inline-md
%li
= link_to admin_procedure_path(id: @procedure), class: 'fr-btn fr-btn--secondary' do
%span.fr-icon-arrow-go-back-line.fr-icon--sm.fr-mr-1v
Revenir à la démarche
- if @procedure.feature_enabled?(:attestation_v2) && @attestation_template.draft?
- content_for(:sticky_header) do
= render partial: "sticky_header"
.fr-col-12.fr-col-md-5
-# .fr-toggle
-# = f.check_box :activated, class: "fr-toggle-input", disabled: true, id: dom_id(@attestation_template, :activated)
-# %label.fr-toggle__label{ for: dom_id(@attestation_template, :activated), data: { fr_checked_label: "Attestation activée", fr_unchecked_label: "Attestation désactivée" } }
.text-right
%span#autosave-notice
%p.fr-hint-text Lactivation de cette attestation sera bientôt disponible.
.padded-fixed-footer
.fixed-footer#fixed_footer
= render partial: "fixed_footer", locals: { procedure: @procedure }

View file

@ -1,5 +1,8 @@
- if @attestation_template.draft?
= turbo_stream.update "sticky-header", render(partial: "sticky_header")
= turbo_stream.show 'autosave-notice'
= turbo_stream.replace 'autosave-notice', render(partial: 'administrateurs/autosave_notice', locals: { success: !@attestation_template.changed? })
= turbo_stream.replace('autosave-notice', render(AutosaveNoticeComponent.new(success: !@attestation_template.changed?, label_scope: :attestation)))
= turbo_stream.hide 'autosave-notice', delay: 15000
- if @attestation_template.logo_blob&.previously_new_record?
@ -10,7 +13,7 @@
= turbo_stream.update dom_id(@attestation_template, :signature_attachment) do
= render(Attachment::EditComponent.new(attached_file: @attestation_template.signature, direct_upload: false))
- body_id = dom_id(@attestation_template, "json-body-messages")
- body_id = "attestation-template-json-body-messages"
- if @attestation_template.errors.include?(:json_body)
= turbo_stream.update body_id do
= render partial: "shared/errors_list", locals: { object: @attestation_template, attribute: :json_body }

View file

@ -18,7 +18,7 @@
- unless flash.alert
= turbo_stream.show 'autosave-notice'
= turbo_stream.replace 'autosave-notice', render(partial: 'administrateurs/autosave_notice')
= turbo_stream.replace 'autosave-notice', render(AutosaveNoticeComponent.new(success: true, label_scope: :form))
= turbo_stream.hide 'autosave-notice', delay: 30000
- if @destroyed.present?

View file

@ -45,6 +45,9 @@
#beta
Env Test
#sticky-header.sticky-header-container
= content_for(:sticky_header)
= render partial: "layouts/header"
%main#contenu{ role: :main }
= render partial: "layouts/flash_messages"

View file

@ -71,6 +71,3 @@ en:
path_not_available:
owner: This URL is identical to another of your published procedures. If you publish this procedure, the old one will be unpublished and will no longer be accessible to the public.
not_owner: This URL is identical to another procedure, you must modify it.
autosave_notice:
form_saved: "Form saved"
form_error: "Form in error"

View file

@ -71,6 +71,3 @@ fr:
path_not_available:
owner: Cette url est identique à celle dune autre de vos démarches publiées. Si vous publiez cette démarche, lancienne sera dépubliée et ne sera plus accessible au public.
not_owner: Cette url est identique à celle dune autre démarche, vous devez la modifier afin de pouvoir publier votre démarche.
autosave_notice:
form_saved: "Formulaire enregistré"
form_error: "Formulaire en erreur"

View file

@ -684,7 +684,9 @@ Rails.application.routes.draw do
get 'add_champ_engagement_juridique'
end
resource :attestation_template_v2, only: [:show, :edit, :update, :create]
resource :attestation_template_v2, only: [:show, :edit, :update, :create] do
post :reset
end
resource :dossier_submitted_message, only: [:edit, :update, :create]
# ADDED TO ACCESS IT FROM THE IFRAME

View file

@ -0,0 +1,7 @@
# frozen_string_literal: true
class AddStateToAttestationTemplates < ActiveRecord::Migration[7.0]
def change
add_column :attestation_templates, :state, :string, default: 'published'
end
end

View file

@ -0,0 +1,12 @@
class AddAttestationTemplateUnicityIndex < ActiveRecord::Migration[7.0]
disable_ddl_transaction!
def change
# this index was not created on production
if index_exists?(:attestation_templates, [:procedure_id, :version])
remove_index :attestation_templates, [:procedure_id, :version], unique: true, algorithm: :concurrently
end
add_index :attestation_templates, [:procedure_id, :version, :state], name: "index_attestation_templates_on_procedure_version_state", unique: true, algorithm: :concurrently
end
end

View file

@ -175,10 +175,11 @@ ActiveRecord::Schema[7.0].define(version: 2024_05_27_090508) do
t.string "label_logo"
t.boolean "official_layout", default: true, null: false
t.integer "procedure_id"
t.string "state", default: "published"
t.text "title"
t.datetime "updated_at", precision: nil, null: false
t.integer "version", default: 1, null: false
t.index ["procedure_id", "version"], name: "index_attestation_templates_on_procedure_id_and_version", unique: true
t.index ["procedure_id", "version", "state"], name: "index_attestation_templates_on_procedure_version_state", unique: true
end
create_table "attestations", id: :serial, force: :cascade do |t|

View file

@ -0,0 +1,15 @@
# frozen_string_literal: true
namespace :after_party do
desc 'Deployment task: attestation_template_v2_as_draft'
task backfill_attestation_template_v2_as_draft: :environment do
puts "Running deploy task 'backfill_attestation_template_v2_as_draft'"
AttestationTemplate.v2.update_all(state: :draft)
# Update task as completed. If you remove the line below, the task will
# run with every deploy (or every time you call after_party:run).
AfterParty::TaskRecord
.create version: AfterParty::TaskRecorder.new(__FILE__).timestamp
end
end

View file

@ -1,7 +1,7 @@
describe Administrateurs::AttestationTemplateV2sController, type: :controller do
let(:admin) { create(:administrateur) }
let(:attestation_template) { build(:attestation_template, :v2) }
let!(:procedure) { create(:procedure, administrateur: admin, attestation_template: attestation_template, libelle: "Ma démarche") }
let(:procedure) { create(:procedure, :published, administrateur: admin, attestation_template:, libelle: "Ma démarche") }
let(:logo) { fixture_file_upload('spec/fixtures/files/white.png', 'image/png') }
let(:signature) { fixture_file_upload('spec/fixtures/files/black.png', 'image/png') }
@ -11,7 +11,7 @@ describe Administrateurs::AttestationTemplateV2sController, type: :controller do
label_logo: "Ministère des specs",
label_direction: "RSPEC",
footer: "en bas",
activated: false,
activated: true,
tiptap_body: {
type: :doc,
content: [
@ -31,11 +31,12 @@ describe Administrateurs::AttestationTemplateV2sController, type: :controller do
describe 'GET #show' do
subject do
get :show, params: { procedure_id: procedure.id }
get :show, params: { procedure_id: procedure.id, format: }
response.body
end
context 'if an attestation template exists on the procedure' do
context 'html' do
let(:format) { :html }
render_views
context 'with preview dossier' do
@ -93,20 +94,42 @@ describe Administrateurs::AttestationTemplateV2sController, type: :controller do
end
end
end
context 'pdf' do
render_views
let(:format) { :pdf }
let(:attestation_template) { build(:attestation_template, :v2, signature:) }
let(:dossier) { create(:dossier, :en_construction, procedure:, for_procedure_preview: true) }
before do
html_content = /Ministère des devs.+Mon titre pour Ma démarche.+n° #{dossier.id}/m
context = { procedure_id: procedure.id }
allow(WeasyprintService).to receive(:generate_pdf).with(a_string_matching(html_content), hash_including(context)).and_return('PDF_DATA')
end
it do
is_expected.to eq('PDF_DATA')
end
end
end
describe 'GET edit' do
render_views
let(:attestation_template) { nil }
subject do
get :edit, params: { procedure_id: procedure.id }
response.body
end
context 'if an attestation template does not exists yet on the procedure' do
let(:attestation_template) { nil }
it 'creates new v2 attestation template' do
subject
expect(assigns(:attestation_template).version).to eq(2)
expect(assigns(:attestation_template)).to be_draft
expect(response.body).to have_button("Publier")
expect(response.body).not_to have_link("Réinitialiser les modifications")
end
end
@ -116,13 +139,51 @@ describe Administrateurs::AttestationTemplateV2sController, type: :controller do
it 'build new v2 attestation template' do
subject
expect(assigns(:attestation_template).version).to eq(2)
expect(assigns(:attestation_template)).to be_draft
end
end
context 'if attestation template already exist on v2' do
it 'assigns v2 attestation template' do
context 'attestation template published exist without draft' do
let(:attestation_template) { build(:attestation_template, :v2, :published) }
it 'mention publication' do
subject
expect(assigns(:attestation_template)).to eq(attestation_template)
expect(response.body).not_to have_link("Réinitialiser les modifications")
expect(response.body).not_to have_button("Publier les modifications")
end
end
context 'attestation template draft already exist on v2' do
let(:attestation_template) { build(:attestation_template, :v2, :draft) }
it 'assigns this draft' do
subject
expect(assigns(:attestation_template)).to eq(attestation_template)
expect(response.body).not_to have_link("Réinitialiser les modifications")
expect(response.body).to have_button("Publier")
end
context 'and a published template also exists' do
before { create(:attestation_template, :v2, :published, procedure:) }
it 'mention publication' do
subject
expect(assigns(:attestation_template)).to eq(attestation_template)
expect(response.body).to have_link("Réinitialiser les modifications")
expect(response.body).to have_button("Publier les modifications")
end
end
end
context 'when procedure is draft' do
let(:procedure) { create(:procedure, :draft, administrateur: admin, attestation_template:, libelle: "Ma démarche") }
it 'built template is already live (published)' do
subject
expect(assigns(:attestation_template).version).to eq(2)
expect(assigns(:attestation_template)).to be_published
expect(response.body).not_to have_button(/Publier/)
end
end
end
@ -140,16 +201,17 @@ describe Administrateurs::AttestationTemplateV2sController, type: :controller do
it "create template" do
subject
attestation_template = procedure.reload.attestation_template
attestation_template = procedure.reload.attestation_templates.first
expect(attestation_template).to be_draft
expect(attestation_template.official_layout).to eq(true)
expect(attestation_template.label_logo).to eq("Ministère des specs")
expect(attestation_template.label_direction).to eq("RSPEC")
expect(attestation_template.footer).to eq("en bas")
expect(attestation_template.activated).to eq(false)
expect(attestation_template.activated).to eq(true)
expect(attestation_template.tiptap_body).to eq(update_params[:tiptap_body])
expect(response.body).to include("Formulaire enregistré")
expect(response.body).to include("Attestation enregistrée")
end
context "with files" do
@ -157,7 +219,7 @@ describe Administrateurs::AttestationTemplateV2sController, type: :controller do
it "upload files" do
subject
attestation_template = procedure.reload.attestation_template
attestation_template = procedure.reload.attestation_templates.first
expect(attestation_template.logo.download).to eq(logo.read)
expect(attestation_template.signature.download).to eq(signature.read)
@ -174,18 +236,25 @@ describe Administrateurs::AttestationTemplateV2sController, type: :controller do
end
context 'when attestation template is valid' do
it "update template" do
subject
attestation_template.reload
it "create a draft template" do
expect { subject }.to change { procedure.attestation_templates.count }.by(1)
# published remains inchanged
expect(attestation_template.reload).to be_published
expect(attestation_template.label_logo).to eq("Ministère des devs")
attestation_template = procedure.attestation_templates.draft.first
expect(attestation_template).to be_draft
expect(attestation_template.official_layout).to eq(true)
expect(attestation_template.label_logo).to eq("Ministère des specs")
expect(attestation_template.label_direction).to eq("RSPEC")
expect(attestation_template.footer).to eq("en bas")
expect(attestation_template.activated).to eq(false)
expect(attestation_template.activated).to eq(true)
expect(attestation_template.tiptap_body).to eq(update_params[:tiptap_body])
expect(response.body).to include("Formulaire enregistré")
expect(response.body).to include("Attestation enregistrée")
expect(response.body).to include("Publier")
end
context "with files" do
@ -193,7 +262,8 @@ describe Administrateurs::AttestationTemplateV2sController, type: :controller do
it "upload files" do
subject
attestation_template.reload
attestation_template = procedure.attestation_templates.draft.first
expect(attestation_template.logo.download).to eq(logo.read)
expect(attestation_template.signature.download).to eq(signature.read)
@ -205,12 +275,67 @@ describe Administrateurs::AttestationTemplateV2sController, type: :controller do
super().merge(tiptap_body: { type: :doc, content: [{ type: :mention, attrs: { id: "tdc12", label: "oops" } }] }.to_json)
end
it "render error" do
it "renders error" do
subject
expect(response.body).to include("Formulaire en erreur")
expect(response.body).to include('Supprimer cette balise')
expect(response.body).to include("Attestation en erreur")
expect(response.body).to include('Supprimer la balise')
end
end
context "publishing a draft" do
let(:attestation_template) { build(:attestation_template, :draft, :v2) }
let(:update_params) { super().merge(state: :published) }
it "publish and redirect with notice" do
subject
expect(attestation_template.reload).to be_published
expect(flash.notice).to eq("Lattestation a été publiée.")
end
end
end
context 'toggle activation' do
let(:update_params) { super().merge(activated: false) }
it 'toggle attribute of current published attestation' do
subject
expect(procedure.attestation_templates.v2.count).to eq(1)
expect(procedure.attestation_templates.v2.first.activated?).to eq(false)
expect(flash.notice).to be_nil
end
context 'when there is a draft' do
before {
create(:attestation_template, :v2, :draft, procedure:)
}
it 'toggle attribute of both draft & published v2 attestations' do
subject
expect(procedure.attestation_templates.v2.count).to eq(2)
expect(procedure.attestation_templates.v2.all?(&:activated?)).to eq(false)
end
end
end
end
describe 'POST reset' do
render_views
before {
create(:attestation_template, :v2, :draft, procedure:)
}
subject do
patch :reset, params: { procedure_id: procedure.id }
response.body
end
it "delete draft, keep published" do
expect(procedure.attestation_templates.count).to eq(2)
expect(subject).to redirect_to(edit_admin_procedure_attestation_template_v2_path(procedure))
expect(flash.notice).to include("réinitialisées")
expect(procedure.attestation_templates.count).to eq(1)
expect(procedure.attestation_templates.first).to eq(attestation_template)
end
end
end

View file

@ -32,10 +32,10 @@ FactoryBot.define do
{ "type" => "paragraph", "attrs" => { "textAlign" => "left" }, "content" => [{ "text" => "Dossier: n° ", "type" => "text" }, { "type" => "mention", "attrs" => { "id" => "dossier_number", "label" => "numéro du dossier" } }] },
{
"type" => "paragraph",
"content" => [
{ "text" => "Nom: ", "type" => "text" }, { "type" => "mention", "attrs" => { "id" => "individual_last_name", "label" => "prénom" } }, { "text" => " ", "type" => "text" },
{ "type" => "mention", "attrs" => { "id" => "individual_first_name", "label" => "nom" } }, { "text" => " ", "type" => "text" }
]
"content" => [
{ "text" => "Nom: ", "type" => "text" }, { "type" => "mention", "attrs" => { "id" => "individual_last_name", "label" => "prénom" } }, { "text" => " ", "type" => "text" },
{ "type" => "mention", "attrs" => { "id" => "individual_first_name", "label" => "nom" } }, { "text" => " ", "type" => "text" }
]
}
]
}

View file

@ -21,9 +21,11 @@ describe AttestationTemplate, type: :model do
context 'with an attestation without images' do
let(:attributes) { attributes_for(:attestation_template) }
it { is_expected.to have_attributes(attributes) }
it { is_expected.to have_attributes(id: nil) }
it { expect(subject.logo.attached?).to be_falsey }
it "works" do
is_expected.to have_attributes(attributes)
is_expected.to have_attributes(id: nil)
expect(subject.logo.attached?).to be_falsey
end
end
context 'with an attestation with images' do
@ -56,81 +58,78 @@ describe AttestationTemplate, type: :model do
create(:procedure,
types_de_champ_public: types_de_champ,
types_de_champ_private: types_de_champ_private,
for_individual: for_individual,
attestation_template: attestation_template)
end
let(:for_individual) { false }
let(:individual) { nil }
let(:etablissement) { create(:etablissement) }
let(:types_de_champ) { [] }
let(:types_de_champ_private) { [] }
let!(:dossier) { create(:dossier, procedure: procedure, individual: individual, etablissement: etablissement) }
let(:template_title) { 'title' }
let(:template_body) { 'body' }
let(:attestation_template) do
build(:attestation_template,
title: template_title,
body: template_body,
logo: @logo,
signature: @signature)
let(:dossier) { create(:dossier, :accepte, procedure:) }
let(:types_de_champ) do
[
{ libelle: 'libelleA' },
{ libelle: 'libelleB' }
]
end
before do
Timecop.freeze(Time.zone.now)
end
dossier.champs_public
.find { |champ| champ.libelle == 'libelleA' }
.update(value: 'libelle1')
after do
Timecop.return
end
let(:view_args) do
arguments = nil
allow(ApplicationController).to receive(:render).and_wrap_original do |m, *args|
arguments = args.first[:assigns]
m.call(*args)
end
attestation_template.attestation_for(dossier)
arguments
dossier.champs_public
.find { |champ| champ.libelle == 'libelleB' }
.update(value: 'libelle2')
end
let(:attestation) { attestation_template.attestation_for(dossier) }
context 'when the procedure has a type de champ named libelleA et libelleB' do
let(:types_de_champ) do
[
{ libelle: 'libelleA' },
{ libelle: 'libelleB' }
]
context 'attestation v1' do
let(:template_title) { 'title --libelleA--' }
let(:template_body) { 'body --libelleB--' }
let(:attestation_template) do
build(:attestation_template,
title: template_title,
body: template_body)
end
context 'and the are used in the template title and body' do
let(:template_title) { 'title --libelleA--' }
let(:template_body) { 'body --libelleB--' }
let(:view_args) do
arguments = nil
context 'and their value in the dossier are not nil' do
before do
dossier.champs_public
.find { |champ| champ.libelle == 'libelleA' }
.update(value: 'libelle1')
dossier.champs_public
.find { |champ| champ.libelle == 'libelleB' }
.update(value: 'libelle2')
end
it 'passes the correct parameters to the view' do
expect(view_args[:attestation][:title]).to eq('title libelle1')
expect(view_args[:attestation][:body]).to eq('body libelle2')
end
it 'generates an attestation' do
expect(attestation.title).to eq('title libelle1')
expect(attestation.pdf).to be_attached
end
allow(ApplicationController).to receive(:render).and_wrap_original do |m, *args|
arguments = args.first[:assigns]
m.call(*args)
end
attestation_template.attestation_for(dossier)
arguments
end
it 'passes the correct parameters and generates an attestation' do
expect(view_args[:attestation][:title]).to eq('title libelle1')
expect(view_args[:attestation][:body]).to eq('body libelle2')
expect(attestation.title).to eq('title libelle1')
expect(attestation.pdf).to be_attached
end
end
context 'attestation v2' do
let(:attestation_template) do
build(:attestation_template, :v2, :with_files, label_logo: "Ministère des specs")
end
before do
stub_request(:post, WEASYPRINT_URL)
.with(body: {
html: /Ministère des specs.+Mon titre pour #{procedure.libelle}.+Dossier: n° #{dossier.id}/m,
upstream_context: { procedure_id: procedure.id, dossier_id: dossier.id }
})
.to_return(body: 'PDF_DATA')
end
it 'generates an attestation' do
expect(attestation.pdf).to be_attached
end
end
end

View file

@ -685,8 +685,24 @@ describe Dossier, type: :model do
describe "#unspecified_attestation_champs" do
let(:procedure) { create(:procedure, attestation_template: attestation_template, types_de_champ_public: types_de_champ, types_de_champ_private: types_de_champ_private) }
let(:dossier) { create(:dossier, :en_instruction, procedure: procedure) }
let(:types_de_champ) { [] }
let(:types_de_champ_private) { [] }
let(:types_de_champ) { [tdc_1, tdc_2, tdc_3, tdc_4] }
let(:types_de_champ_private) { [tdc_5, tdc_6, tdc_7, tdc_8] }
let(:tdc_1) { { libelle: "specified champ-in-title" } }
let(:tdc_2) { { libelle: "unspecified champ-in-title" } }
let(:tdc_3) { { libelle: "specified champ-in-body" } }
let(:tdc_4) { { libelle: "unspecified champ-in-body" } }
let(:tdc_5) { { libelle: "specified annotation privée-in-title" } }
let(:tdc_6) { { libelle: "unspecified annotation privée-in-title" } }
let(:tdc_7) { { libelle: "specified annotation privée-in-body" } }
let(:tdc_8) { { libelle: "unspecified annotation privée-in-body" } }
before do
(dossier.champs_public + dossier.champs_private)
.filter { |c| c.libelle.match?(/^specified/) }
.each { |c| c.update_attribute(:value, "specified") }
end
subject { dossier.unspecified_attestation_champs.map(&:libelle) }
@ -696,11 +712,11 @@ describe Dossier, type: :model do
it { is_expected.to eq([]) }
end
context "with attestation template" do
context "with attestation template v1" do
# Test all combinations:
# - with tag specified and unspecified
# - with tag in body and tag in title
# - with tag correponsing to a champ and an annotation privée
# - with tag correponding to a champ and an annotation privée
# - with a dash in the champ libelle / tag
let(:title) { "voici --specified champ-in-title-- un --unspecified champ-in-title-- beau --specified annotation privée-in-title-- titre --unspecified annotation privée-in-title-- non --numéro du dossier--" }
let(:body) { "voici --specified champ-in-body-- un --unspecified champ-in-body-- beau --specified annotation privée-in-body-- body --unspecified annotation privée-in-body-- non ?" }
@ -712,27 +728,9 @@ describe Dossier, type: :model do
it { is_expected.to eq([]) }
end
context "wich is enabled" do
context "which is enabled" do
let(:activated) { true }
let(:types_de_champ) { [tdc_1, tdc_2, tdc_3, tdc_4] }
let(:types_de_champ_private) { [tdc_5, tdc_6, tdc_7, tdc_8] }
let(:tdc_1) { { libelle: "specified champ-in-title" } }
let(:tdc_2) { { libelle: "unspecified champ-in-title" } }
let(:tdc_3) { { libelle: "specified champ-in-body" } }
let(:tdc_4) { { libelle: "unspecified champ-in-body" } }
let(:tdc_5) { { libelle: "specified annotation privée-in-title" } }
let(:tdc_6) { { libelle: "unspecified annotation privée-in-title" } }
let(:tdc_7) { { libelle: "specified annotation privée-in-body" } }
let(:tdc_8) { { libelle: "unspecified annotation privée-in-body" } }
before do
(dossier.champs_public + dossier.champs_private)
.filter { |c| c.libelle.match?(/^specified/) }
.each { |c| c.update_attribute(:value, "specified") }
end
it do
is_expected.to eq([
"unspecified champ-in-title",
@ -743,6 +741,40 @@ describe Dossier, type: :model do
end
end
end
context "with attestation template v2" do
# Test all combinations:
# - with tag specified and unspecified
# - with tag correponding to a champ and an annotation privée
let(:body) {
[
{ "type" => "mention", "attrs" => { "id" => "tdc#{procedure.types_de_champ_for_tags.find {  _1.libelle == "unspecified champ-in-body" }.stable_id}", "label" => "unspecified champ-in-body" } }
]
}
let(:attestation_template) { build(:attestation_template, :v2) }
before do
tdc_content = (types_de_champ + types_de_champ_private).filter_map do |tdc_config|
next if tdc_config[:libelle].include?("in-title")
{
"type" => "mention",
"attrs" => { "id" => "tdc#{procedure.types_de_champ_for_tags.find { _1.libelle == tdc_config[:libelle] }.stable_id}", "label" => tdc_config[:libelle] }
}
end
json_body = attestation_template.json_body["content"]
attestation_template.json_body["content"][-1]["content"].concat(tdc_content)
attestation_template.save!
end
it do
is_expected.to eq([
"unspecified champ-in-body",
"unspecified annotation privée-in-body"
])
end
end
end
describe '#build_attestation' do

View file

@ -1813,23 +1813,23 @@ describe Procedure do
describe "#attestation_template" do
let(:procedure) { create(:procedure) }
subject { procedure.reload }
context "when there is a v2 created after v1" do
context "when there is a v2 draft and a v1" do
before do
create(:attestation_template, procedure: procedure)
create(:attestation_template, :v2, procedure: procedure)
create(:attestation_template, :v2, :draft, procedure: procedure)
end
it { expect(procedure.attestation_template.version).to eq(1) }
it { expect(subject.attestation_template.version).to eq(1) }
end
context "when there is a v2 created before v1" do
context "when there is only a v1" do
before do
create(:attestation_template, :v2, procedure: procedure)
create(:attestation_template, procedure: procedure, activated: true)
create(:attestation_template, procedure: procedure)
end
it { expect(procedure.attestation_template.version).to eq(1) }
it { expect(subject.attestation_template.version).to eq(1) }
end
context "when there is only a v2" do
@ -1837,7 +1837,23 @@ describe Procedure do
create(:attestation_template, :v2, procedure: procedure)
end
it { expect(procedure.attestation_template.version).to eq(2) }
it { expect(subject.attestation_template.version).to eq(2) }
end
context "when there is a v2 draft" do
before do
create(:attestation_template, :v2, :draft, procedure: procedure)
end
it { expect(subject.attestation_template).to be_nil }
context "and a published" do
before do
create(:attestation_template, :v2, :published, procedure: procedure)
end
it { expect(subject.attestation_template).to be_published }
end
end
end

View file

@ -189,7 +189,7 @@ RSpec.describe TiptapService do
describe '#used_tags' do
it 'returns used tags' do
expect(described_class.new.used_tags_and_libelle_for(json)).to eq(Set.new([['name', 'Nom']]))
expect(described_class.used_tags_and_libelle_for(json)).to eq(Set.new([['name', 'Nom']]))
end
end

View file

@ -0,0 +1,21 @@
# frozen_string_literal: true
describe WeasyprintService do
let(:html) { '<html><body>Hello, World!</body></html>' }
let(:options) { { procedure_id: 1, dossier_id: 2 } }
describe '#generate_pdf' do
context 'when the Weasyprint API responds successfully' do
before do
stub_request(:post, WEASYPRINT_URL)
.with(body: { html: html, upstream_context: options })
.to_return(body: 'PDF_DATA')
end
it 'returns a StringIO object with the PDF data' do
pdf = described_class.generate_pdf(html, options)
expect(pdf).to eq('PDF_DATA')
end
end
end
end

View file

@ -14,8 +14,14 @@ describe 'As an administrateur, I want to manage the procedures attestation',
before { login_as(administrateur.user, scope: :user) }
def find_attestation_card(with_nested_selector: nil)
attestation_path = if procedure.attestation_template&.version == 2 || procedure.feature_enabled?(:attestation_v2)
edit_admin_procedure_attestation_template_v2_path(procedure)
else
edit_admin_procedure_attestation_template_path(procedure)
end
full_selector = [
"a[href=\"#{edit_admin_procedure_attestation_template_path(procedure)}\"]",
"a[href=\"#{attestation_path}\"]",
with_nested_selector
].compact.join(" ")
page.find(full_selector)
@ -65,6 +71,13 @@ describe 'As an administrateur, I want to manage the procedures attestation',
end
context 'Update attestation v2' do
let(:procedure) do
create(:procedure, :published,
administrateurs: [administrateur],
libelle: 'libellé de la procédure',
path: 'libelle-de-la-procedure')
end
before do
Flipper.enable(:attestation_v2)
@ -81,7 +94,7 @@ describe 'As an administrateur, I want to manage the procedures attestation',
find("a").click
end
expect(procedure.reload.attestation_template_v2).to be_nil
expect(procedure.reload.attestation_templates.v2).to be_empty
expect(page).to have_css("label", text: "Logo additionnel")
@ -90,12 +103,12 @@ describe 'As an administrateur, I want to manage the procedures attestation',
attestation = nil
wait_until {
attestation = procedure.reload.attestation_template_v2
attestation = procedure.reload.attestation_templates.v2.draft.first
attestation.present?
}
expect(page).to have_content("Attestation enregistrée")
expect(attestation.label_logo).to eq("System Test")
expect(attestation.activated?).to be_falsey
expect(page).to have_content("Formulaire enregistré")
expect(attestation.activated?).to be_truthy
click_on "date de décision"
@ -129,7 +142,16 @@ describe 'As an administrateur, I want to manage the procedures attestation',
}
fill_in "Contenu du pied de page", with: ["line1", "line2", "line3", "line4"].join("\n")
expect(page).to have_field("Contenu du pied de page", with: "line1\nline2\nline3\nline4")
expect(page).to have_field("Contenu du pied de page", with: "line1\nline2\nline3line4")
click_on "Publier"
expect(attestation.reload).to be_published
expect(page).to have_text("Lattestation a été publiée")
fill_in "Intitulé de la direction", with: "plop"
click_on "Publier les modifications"
expect(procedure.reload.attestation_template.label_direction).to eq("plop")
expect(page).to have_text(/La nouvelle version de lattestation/)
end
context "tag in error" do
@ -137,7 +159,7 @@ describe 'As an administrateur, I want to manage the procedures attestation',
tdc = procedure.active_revision.add_type_de_champ(type_champ: :integer_number, libelle: 'age')
procedure.publish_revision!
attestation = procedure.build_attestation_template_v2(json_body: AttestationTemplate::TIPTAP_BODY_DEFAULT, label_logo: "test")
attestation = procedure.build_attestation_template(version: 2, json_body: AttestationTemplate::TIPTAP_BODY_DEFAULT, label_logo: "test")
attestation.json_body["content"] << { type: :mention, attrs: { id: "tdc#{tdc.stable_id}", label: tdc.libelle } }
attestation.save!
@ -150,14 +172,14 @@ describe 'As an administrateur, I want to manage the procedures attestation',
click_on "date de décision"
expect(page).to have_content("Formulaire en erreur")
expect(page).to have_content("Attestation en erreur")
expect(page).to have_content("Le champ « Contenu de lattestation » contient la balise \"age\"")
page.execute_script("document.getElementById('attestation_template_tiptap_body').type = 'text'")
fill_in "attestation_template[tiptap_body]", with: AttestationTemplate::TIPTAP_BODY_DEFAULT.to_json
expect(page).to have_content("Formulaire enregistré")
expect(page).not_to have_content("Formulaire en erreur")
expect(page).to have_content("Attestation enregistrée")
expect(page).not_to have_content("Attestation en erreur")
expect(page).not_to have_content("Le champ « Contenu de lattestation » contient la balise \"age\"")
end
end