feat(attestation): can render & attach attestations v2
This commit is contained in:
parent
ffd8c5617a
commit
a540f8dccb
10 changed files with 153 additions and 105 deletions
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
Loading…
Reference in a new issue