diff --git a/Gemfile b/Gemfile index 191cc9a..5b6b4f9 100644 --- a/Gemfile +++ b/Gemfile @@ -52,6 +52,9 @@ gem "bootsnap", require: false gem "dsfr-view-components" +# Mon Devis Sans Oublis custom gems +gem "pdf-reader" + group :development, :test do # See https://guides.rubyonrails.org/debugging_rails_applications.html#debugging-with-the-debug-gem gem "debug", platforms: %i[mri mingw x64_mingw] diff --git a/Gemfile.lock b/Gemfile.lock index 62021dc..e8f53a6 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,6 +1,7 @@ GEM remote: https://rubygems.org/ specs: + Ascii85 (2.0.1) actioncable (7.2.2) actionpack (= 7.2.2) activesupport (= 7.2.2) @@ -74,6 +75,7 @@ GEM tzinfo (~> 2.0, >= 2.0.5) addressable (2.8.7) public_suffix (>= 2.0.2, < 7.0) + afm (0.2.2) ast (2.4.2) base64 (0.2.0) benchmark (0.3.0) @@ -165,6 +167,7 @@ GEM guard (~> 2.1) guard-compat (~> 1.1) rspec (>= 2.99.0, < 4.0) + hashery (2.1.2) html-attributes-utils (1.0.2) activesupport (>= 6.1.4.4) i18n (1.14.6) @@ -227,6 +230,12 @@ GEM parser (3.3.6.0) ast (~> 2.4.1) racc + pdf-reader (2.13.0) + Ascii85 (>= 1.0, < 3.0, != 2.0.0) + afm (~> 0.2.1) + hashery (~> 2.0) + ruby-rc4 + ttfunk pg (1.5.9) pry (0.14.2) coderay (~> 1.1) @@ -332,6 +341,7 @@ GEM rubocop (~> 1.61) rubocop-rspec (~> 3, >= 3.0.1) ruby-progressbar (1.13.0) + ruby-rc4 (0.1.5) securerandom (0.3.1) sentry-rails (5.21.0) railties (>= 5.0) @@ -354,6 +364,8 @@ GEM ffi (~> 1.1) thor (1.3.2) timeout (0.4.2) + ttfunk (1.8.0) + bigdecimal (~> 3.1) turbo-rails (2.0.11) actionpack (>= 6.0.0) railties (>= 6.0.0) @@ -397,6 +409,7 @@ DEPENDENCIES guard-rspec importmap-rails jbuilder + pdf-reader pg puma (~> 6.0) rails diff --git a/app/controllers/quotes_controller.rb b/app/controllers/quotes_controller.rb index 5fbe0b1..56170e6 100644 --- a/app/controllers/quotes_controller.rb +++ b/app/controllers/quotes_controller.rb @@ -3,13 +3,36 @@ # Controller for the Quotes resource class QuotesController < ApplicationController def check - @quote_attributes ||= { - siret: nil + @quote_attributes = if params[:quote_file].present? + file_to_attributes(params[:quote_file]) + else + default_quote_attributes + end + return unless @quote_attributes + + quote_validation(@quote_attributes) + rescue QuoteReader::ReadError => e + @quote_errors = [e.message] + end + + protected + + def default_quote_attributes + { + quote_number: nil, + rge_number: nil, + siret_number: nil } + end - return unless @quote_attributes + def file_to_attributes(uploaded_file) + temp_file_path = uploaded_file.tempfile.path + + QuoteReader.new(temp_file_path).read_attributes + end - quote_validation = QuoteValidator.new(@quote_attributes) + def quote_validation(quote_attributes) + quote_validation = QuoteValidator.new(quote_attributes) quote_validation.validate! @quote_valid = quote_validation.valid? diff --git a/app/views/quotes/check.html.erb b/app/views/quotes/check.html.erb index 30e7970..10462ef 100644 --- a/app/views/quotes/check.html.erb +++ b/app/views/quotes/check.html.erb @@ -3,7 +3,7 @@

Fichier du Devis :

<%= form_tag check_quotes_path, multipart: true do %> <%= file_field_tag :quote_file %> - <%= submit_tag "Check" %> + <%= submit_tag "Vérifier", class: "fr-btn fr-btn--primary fr-btn--lg" %> <% end %> <% if defined?(@quote_valid) %> diff --git a/lib/quote_reader.rb b/lib/quote_reader.rb new file mode 100644 index 0000000..4da8129 --- /dev/null +++ b/lib/quote_reader.rb @@ -0,0 +1,63 @@ +# frozen_string_literal: true + +require "pdf-reader" + +# Read Quote from PDF file to extract Quote attributes +class QuoteReader + class ReadError < StandardError; end + + attr_reader :filepath, :quote_text + + def initialize(filepath) + @filepath = filepath + end + + def read_attributes + @quote_text = extract_text_from_pdf(filepath) + + { + quote_number: find_quote_number(quote_text), + rge_number: find_rge_number(quote_text), + siret_number: find_siret_number(quote_text), + + full_text: quote_text + } + rescue PDF::Reader::MalformedPDFError, PDF::Reader::UnsupportedFeatureError, + StandardError + raise parse_error(error) + end + + private + + def parse_error(error) + error_message = case error + when PDF::Reader::MalformedPDFError + "Failed to parse PDF: The file may be corrupted." + when PDF::Reader::UnsupportedFeatureError + "Failed to parse PDF: An unsupported feature was used in the PDF." + when StandardError + "An error occurred: #{e.message}" + end + + ReadError.new(error_message) + end + + def find_quote_number(text) + text[/DEVIS\s+N°\s*(\d+)/i, 1] if text + end + + def find_rge_number(text) + text[/RGE\s+N°\s*(\d+)/i, 1] if text + end + + def find_siret_number(text) + text[/SIRET\s*:\s*(\d{3}\s*\d{3}\s*\d{3}\s*\d{5})/i, 1]&.gsub(/\s/, "") if text + end + + def extract_text_from_pdf(pdf_path) + reader = PDF::Reader.new(pdf_path) + text = reader.pages.map(&:text) + + text.join("\n") # Join all pages text into a single string, separated by new lines + end +end diff --git a/lib/quote_validator.rb b/lib/quote_validator.rb index 6822f5c..a00fb5b 100644 --- a/lib/quote_validator.rb +++ b/lib/quote_validator.rb @@ -14,7 +14,7 @@ def initialize(quote) def validate! @errors = [] - @errors << "SIRET number is missing" if @quote[:siret].blank? + @errors << "SIRET number is missing" if @quote[:siret_number].blank? valid? end