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:
commit
ccf5b255ed
42 changed files with 724 additions and 258 deletions
|
@ -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;
|
||||
|
|
49
app/assets/stylesheets/sticky.scss
Normal file
49
app/assets/stylesheets/sticky.scss
Normal 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;
|
||||
}
|
||||
}
|
16
app/components/autosave_notice_component.rb
Normal file
16
app/components/autosave_notice_component.rb
Normal 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
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
en:
|
||||
form:
|
||||
saved: 'Form saved'
|
||||
error: 'Form in error'
|
||||
attestation:
|
||||
saved: 'Attestation saved'
|
||||
error: 'Attestation in error'
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
fr:
|
||||
form:
|
||||
saved: 'Formulaire enregistré'
|
||||
error: 'Formulaire en erreur'
|
||||
attestation:
|
||||
saved: 'Attestation enregistrée'
|
||||
error: 'Attestation en erreur'
|
|
@ -0,0 +1,2 @@
|
|||
#autosave-notice.fr-badge.fr-badge--sm{ class: class_names("fr-badge--success" => success?, "fr-badge--error" => !success?) }
|
||||
= label
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 l’attestation 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 = "L’attestation 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 l’attestation a été publiée." : "L’attestation 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
|
||||
|
|
|
@ -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: [],
|
||||
|
|
|
@ -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
|
||||
|
|
43
app/javascript/controllers/sticky_top_controller.ts
Normal file
43
app/javascript/controllers/sticky_top_controller.ts
Normal 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`;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
23
app/services/weasyprint_service.rb
Normal file
23
app/services/weasyprint_service.rb
Normal 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
|
|
@ -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")
|
|
@ -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
|
|
@ -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
|
||||
L’attestation ne sera délivrée qu’aprè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
|
|
@ -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 l’ancien format :
|
||||
l’activation 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 l’ancien format :
|
||||
l’activation 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 @@
|
|||
L’attestation est émise au moment où un dossier est accepté, elle est jointe à l’email d’accusé d’acceptation.
|
||||
Elle est également disponible au téléchargement depuis l’espace personnel de l’usager.
|
||||
|
||||
.fr-fieldset__element
|
||||
= render Dsfr::CalloutComponent.new(title: "Activation de la délivrance de l’attestation", 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 l’attestation dès l’acceptation 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 @@
|
|||
L’aperç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 : l’aperçu l’utilisera.
|
||||
|
||||
.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 L’activation de cette attestation sera bientôt disponible.
|
||||
.padded-fixed-footer
|
||||
.fixed-footer#fixed_footer
|
||||
= render partial: "fixed_footer", locals: { procedure: @procedure }
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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?
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -71,6 +71,3 @@ fr:
|
|||
path_not_available:
|
||||
owner: Cette url est identique à celle d’une autre de vos démarches publiées. Si vous publiez cette démarche, l’ancienne sera dépubliée et ne sera plus accessible au public.
|
||||
not_owner: Cette url est identique à celle d’une 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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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|
|
||||
|
|
|
@ -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
|
|
@ -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("L’attestation 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
|
||||
|
|
|
@ -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" }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
21
spec/services/weasyprint_service_spec.rb
Normal file
21
spec/services/weasyprint_service_spec.rb
Normal 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
|
|
@ -14,8 +14,14 @@ describe 'As an administrateur, I want to manage the procedure’s 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 procedure’s 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 procedure’s 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 procedure’s 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 procedure’s 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("L’attestation 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 l’attestation/)
|
||||
end
|
||||
|
||||
context "tag in error" do
|
||||
|
@ -137,7 +159,7 @@ describe 'As an administrateur, I want to manage the procedure’s 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 procedure’s 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 l’attestation » 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 l’attestation » contient la balise \"age\"")
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue