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
|
format.pdf do
|
||||||
html = render_to_string('/administrateurs/attestation_template_v2s/show', layout: 'attestation', formats: [:html])
|
html = render_to_string('/administrateurs/attestation_template_v2s/show', layout: 'attestation', formats: [:html])
|
||||||
|
|
||||||
headers = {
|
pdf = WeasyprintService.generate_pdf(html, procedure_id: @procedure.id, path: request.path, user_id: current_user.id)
|
||||||
'Content-Type' => 'application/json',
|
|
||||||
'X-Request-Id' => Current.request_id
|
|
||||||
}
|
|
||||||
|
|
||||||
body = {
|
send_data(pdf, filename: 'attestation.pdf', type: 'application/pdf', disposition: 'inline')
|
||||||
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
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -30,9 +30,10 @@ module Instructeurs
|
||||||
end
|
end
|
||||||
|
|
||||||
def apercu_attestation
|
def apercu_attestation
|
||||||
@attestation = dossier.attestation_template.render_attributes_for(dossier: dossier)
|
send_data dossier.attestation_template.send(:build_pdf, dossier),
|
||||||
|
filename: 'attestation.pdf',
|
||||||
render 'administrateurs/attestation_templates/show', formats: [:pdf]
|
type: 'application/pdf',
|
||||||
|
disposition: 'inline'
|
||||||
end
|
end
|
||||||
|
|
||||||
def bilans_bdf
|
def bilans_bdf
|
||||||
|
|
|
@ -72,9 +72,10 @@ class AttestationTemplate < ApplicationRecord
|
||||||
}.freeze
|
}.freeze
|
||||||
|
|
||||||
def attestation_for(dossier)
|
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(
|
attestation.pdf.attach(
|
||||||
io: build_pdf(dossier),
|
io: StringIO.new(build_pdf(dossier)),
|
||||||
filename: "attestation-dossier-#{dossier.id}.pdf",
|
filename: "attestation-dossier-#{dossier.id}.pdf",
|
||||||
content_type: 'application/pdf',
|
content_type: 'application/pdf',
|
||||||
# we don't want to run virus scanner on this file
|
# we don't want to run virus scanner on this file
|
||||||
|
@ -184,7 +185,7 @@ class AttestationTemplate < ApplicationRecord
|
||||||
|
|
||||||
if dossier.present?
|
if dossier.present?
|
||||||
# 2x faster this way than with `replace_tags` which would reparse text
|
# 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)
|
substitutions = tags_substitutions(used_tags, dossier, escape: false)
|
||||||
body = tiptap.to_html(json, substitutions)
|
body = tiptap.to_html(json, substitutions)
|
||||||
|
|
||||||
|
@ -207,17 +208,41 @@ class AttestationTemplate < ApplicationRecord
|
||||||
end
|
end
|
||||||
|
|
||||||
def used_tags
|
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
|
end
|
||||||
|
|
||||||
def build_pdf(dossier)
|
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 = render_attributes_for(dossier: dossier)
|
||||||
attestation_view = ApplicationController.render(
|
ApplicationController.render(
|
||||||
template: 'administrateurs/attestation_templates/show',
|
template: 'administrateurs/attestation_templates/show',
|
||||||
formats: :pdf,
|
formats: :pdf,
|
||||||
assigns: { attestation: attestation }
|
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
|
||||||
end
|
end
|
||||||
|
|
|
@ -257,7 +257,7 @@ module TagsSubstitutionConcern
|
||||||
def used_type_de_champ_tags(text_or_tiptap)
|
def used_type_de_champ_tags(text_or_tiptap)
|
||||||
used_tags =
|
used_tags =
|
||||||
if text_or_tiptap.respond_to?(:deconstruct_keys) # hash pattern matching
|
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
|
else
|
||||||
used_tags_and_libelle_for(text_or_tiptap.to_s)
|
used_tags_and_libelle_for(text_or_tiptap.to_s)
|
||||||
end
|
end
|
||||||
|
|
|
@ -66,11 +66,10 @@ class ExportTemplate < ApplicationRecord
|
||||||
end
|
end
|
||||||
|
|
||||||
def render_attributes_for(content_for, dossier, attachment = nil)
|
def render_attributes_for(content_for, dossier, attachment = nil)
|
||||||
tiptap = TiptapService.new
|
used_tags = TiptapService.used_tags_and_libelle_for(content_for.deep_symbolize_keys)
|
||||||
used_tags = tiptap.used_tags_and_libelle_for(content_for.deep_symbolize_keys)
|
|
||||||
substitutions = tags_substitutions(used_tags, dossier, escape: false, memoize: true)
|
substitutions = tags_substitutions(used_tags, dossier, escape: false, memoize: true)
|
||||||
substitutions['original-filename'] = attachment.filename.base if attachment
|
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
|
end
|
||||||
|
|
||||||
def specific_tags
|
def specific_tags
|
||||||
|
|
|
@ -1,18 +1,6 @@
|
||||||
class TiptapService
|
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
|
# 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
|
case node
|
||||||
in type: 'mention', attrs: { id:, label: }, **rest
|
in type: 'mention', attrs: { id:, label: }, **rest
|
||||||
tags << [id, label]
|
tags << [id, label]
|
||||||
|
@ -25,6 +13,18 @@ class TiptapService
|
||||||
tags
|
tags
|
||||||
end
|
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
|
private
|
||||||
|
|
||||||
def initialize
|
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,
|
create(:procedure,
|
||||||
types_de_champ_public: types_de_champ,
|
types_de_champ_public: types_de_champ,
|
||||||
types_de_champ_private: types_de_champ_private,
|
types_de_champ_private: types_de_champ_private,
|
||||||
for_individual: for_individual,
|
|
||||||
attestation_template: attestation_template)
|
attestation_template: attestation_template)
|
||||||
end
|
end
|
||||||
let(:for_individual) { false }
|
|
||||||
let(:individual) { nil }
|
|
||||||
let(:etablissement) { create(:etablissement) }
|
let(:etablissement) { create(:etablissement) }
|
||||||
let(:types_de_champ) { [] }
|
let(:types_de_champ) { [] }
|
||||||
let(:types_de_champ_private) { [] }
|
let(:types_de_champ_private) { [] }
|
||||||
let!(:dossier) { create(:dossier, procedure: procedure, individual: individual, etablissement: etablissement) }
|
let(:dossier) { create(:dossier, :accepte, procedure:) }
|
||||||
let(:template_title) { 'title' }
|
|
||||||
let(:template_body) { 'body' }
|
let(:types_de_champ) do
|
||||||
let(:attestation_template) do
|
[
|
||||||
build(:attestation_template,
|
{ libelle: 'libelleA' },
|
||||||
title: template_title,
|
{ libelle: 'libelleB' }
|
||||||
body: template_body,
|
]
|
||||||
logo: @logo,
|
|
||||||
signature: @signature)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
before do
|
before do
|
||||||
Timecop.freeze(Time.zone.now)
|
dossier.champs_public
|
||||||
end
|
.find { |champ| champ.libelle == 'libelleA' }
|
||||||
|
.update(value: 'libelle1')
|
||||||
|
|
||||||
after do
|
dossier.champs_public
|
||||||
Timecop.return
|
.find { |champ| champ.libelle == 'libelleB' }
|
||||||
end
|
.update(value: 'libelle2')
|
||||||
|
|
||||||
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
|
|
||||||
end
|
end
|
||||||
|
|
||||||
let(:attestation) { attestation_template.attestation_for(dossier) }
|
let(:attestation) { attestation_template.attestation_for(dossier) }
|
||||||
|
|
||||||
context 'when the procedure has a type de champ named libelleA et libelleB' do
|
context 'attestation v1' do
|
||||||
let(:types_de_champ) do
|
let(:template_title) { 'title --libelleA--' }
|
||||||
[
|
let(:template_body) { 'body --libelleB--' }
|
||||||
{ libelle: 'libelleA' },
|
let(:attestation_template) do
|
||||||
{ libelle: 'libelleB' }
|
build(:attestation_template,
|
||||||
]
|
title: template_title,
|
||||||
|
body: template_body)
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'and the are used in the template title and body' do
|
let(:view_args) do
|
||||||
let(:template_title) { 'title --libelleA--' }
|
arguments = nil
|
||||||
let(:template_body) { 'body --libelleB--' }
|
|
||||||
|
|
||||||
context 'and their value in the dossier are not nil' do
|
allow(ApplicationController).to receive(:render).and_wrap_original do |m, *args|
|
||||||
before do
|
arguments = args.first[:assigns]
|
||||||
dossier.champs_public
|
m.call(*args)
|
||||||
.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
|
|
||||||
end
|
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
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -189,7 +189,7 @@ RSpec.describe TiptapService do
|
||||||
|
|
||||||
describe '#used_tags' do
|
describe '#used_tags' do
|
||||||
it 'returns 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
|
||||||
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