feat(attestation): can render & attach attestations v2

This commit is contained in:
Colin Darie 2024-05-29 23:00:34 +02:00
parent ffd8c5617a
commit a540f8dccb
No known key found for this signature in database
GPG key ID: 4FB865FDBCA4BCC4
10 changed files with 153 additions and 105 deletions

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

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

@ -72,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
@ -184,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)
@ -207,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)
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

@ -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

@ -58,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

@ -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