From a540f8dccb8268741723faf9c6a8f4bbc3e17661 Mon Sep 17 00:00:00 2001 From: Colin Darie Date: Wed, 29 May 2024 23:00:34 +0200 Subject: [PATCH] feat(attestation): can render & attach attestations v2 --- .../attestation_template_v2s_controller.rb | 22 +--- .../instructeurs/dossiers_controller.rb | 7 +- app/models/attestation_template.rb | 37 +++++- .../concerns/tags_substitution_concern.rb | 2 +- app/models/export_template.rb | 5 +- app/services/tiptap_service.rb | 26 ++-- app/services/weasyprint_service.rb | 23 ++++ spec/models/attestation_template_spec.rb | 113 +++++++++--------- spec/services/tiptap_service_spec.rb | 2 +- spec/services/weasyprint_service_spec.rb | 21 ++++ 10 files changed, 153 insertions(+), 105 deletions(-) create mode 100644 app/services/weasyprint_service.rb create mode 100644 spec/services/weasyprint_service_spec.rb diff --git a/app/controllers/administrateurs/attestation_template_v2s_controller.rb b/app/controllers/administrateurs/attestation_template_v2s_controller.rb index eb2c45743..5ae2e63c1 100644 --- a/app/controllers/administrateurs/attestation_template_v2s_controller.rb +++ b/app/controllers/administrateurs/attestation_template_v2s_controller.rb @@ -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 diff --git a/app/controllers/instructeurs/dossiers_controller.rb b/app/controllers/instructeurs/dossiers_controller.rb index 3fa84c917..189477bff 100644 --- a/app/controllers/instructeurs/dossiers_controller.rb +++ b/app/controllers/instructeurs/dossiers_controller.rb @@ -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 diff --git a/app/models/attestation_template.rb b/app/models/attestation_template.rb index 4c542b939..f5f3c047d 100644 --- a/app/models/attestation_template.rb +++ b/app/models/attestation_template.rb @@ -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 diff --git a/app/models/concerns/tags_substitution_concern.rb b/app/models/concerns/tags_substitution_concern.rb index ae899dc04..6ccef4219 100644 --- a/app/models/concerns/tags_substitution_concern.rb +++ b/app/models/concerns/tags_substitution_concern.rb @@ -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 diff --git a/app/models/export_template.rb b/app/models/export_template.rb index 550bb57cd..f3ee41235 100644 --- a/app/models/export_template.rb +++ b/app/models/export_template.rb @@ -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 diff --git a/app/services/tiptap_service.rb b/app/services/tiptap_service.rb index 5d0cb4325..3bbee3494 100644 --- a/app/services/tiptap_service.rb +++ b/app/services/tiptap_service.rb @@ -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 diff --git a/app/services/weasyprint_service.rb b/app/services/weasyprint_service.rb new file mode 100644 index 000000000..d0ab2944b --- /dev/null +++ b/app/services/weasyprint_service.rb @@ -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 diff --git a/spec/models/attestation_template_spec.rb b/spec/models/attestation_template_spec.rb index 2807b3f64..58835bf2e 100644 --- a/spec/models/attestation_template_spec.rb +++ b/spec/models/attestation_template_spec.rb @@ -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 diff --git a/spec/services/tiptap_service_spec.rb b/spec/services/tiptap_service_spec.rb index da47220f2..1ec0468b2 100644 --- a/spec/services/tiptap_service_spec.rb +++ b/spec/services/tiptap_service_spec.rb @@ -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 diff --git a/spec/services/weasyprint_service_spec.rb b/spec/services/weasyprint_service_spec.rb new file mode 100644 index 000000000..591f88233 --- /dev/null +++ b/spec/services/weasyprint_service_spec.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +describe WeasyprintService do + let(:html) { 'Hello, World!' } + 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