Added pdf analyzer
Analyzer should be moved to ActiveStorage::Analyzer First there are many bugs with ActiveStorage that needs to be fixed: Files are not deleted if job is destroyed (dependent: :purge is set) If a new file is uploaded the old file is not deleted (has_one_attechment)
This commit is contained in:
2
Gemfile
2
Gemfile
@@ -71,3 +71,5 @@ group :test do
|
|||||||
end
|
end
|
||||||
|
|
||||||
gem 'inline_svg', '~> 1.9'
|
gem 'inline_svg', '~> 1.9'
|
||||||
|
|
||||||
|
gem "pdf-reader", "~> 2.12"
|
||||||
|
|||||||
13
Gemfile.lock
13
Gemfile.lock
@@ -1,6 +1,7 @@
|
|||||||
GEM
|
GEM
|
||||||
remote: https://rubygems.org/
|
remote: https://rubygems.org/
|
||||||
specs:
|
specs:
|
||||||
|
Ascii85 (1.1.1)
|
||||||
actioncable (7.1.3.4)
|
actioncable (7.1.3.4)
|
||||||
actionpack (= 7.1.3.4)
|
actionpack (= 7.1.3.4)
|
||||||
activesupport (= 7.1.3.4)
|
activesupport (= 7.1.3.4)
|
||||||
@@ -77,6 +78,7 @@ GEM
|
|||||||
tzinfo (~> 2.0)
|
tzinfo (~> 2.0)
|
||||||
addressable (2.8.7)
|
addressable (2.8.7)
|
||||||
public_suffix (>= 2.0.2, < 7.0)
|
public_suffix (>= 2.0.2, < 7.0)
|
||||||
|
afm (0.2.2)
|
||||||
base64 (0.2.0)
|
base64 (0.2.0)
|
||||||
bigdecimal (3.1.8)
|
bigdecimal (3.1.8)
|
||||||
bindex (0.8.1)
|
bindex (0.8.1)
|
||||||
@@ -105,6 +107,7 @@ GEM
|
|||||||
i18n (>= 1.8.11, < 2)
|
i18n (>= 1.8.11, < 2)
|
||||||
globalid (1.2.1)
|
globalid (1.2.1)
|
||||||
activesupport (>= 6.1)
|
activesupport (>= 6.1)
|
||||||
|
hashery (2.1.2)
|
||||||
i18n (1.14.5)
|
i18n (1.14.5)
|
||||||
concurrent-ruby (~> 1.0)
|
concurrent-ruby (~> 1.0)
|
||||||
importmap-rails (2.0.1)
|
importmap-rails (2.0.1)
|
||||||
@@ -158,6 +161,12 @@ GEM
|
|||||||
racc (~> 1.4)
|
racc (~> 1.4)
|
||||||
nokogiri (1.16.7-x86_64-linux)
|
nokogiri (1.16.7-x86_64-linux)
|
||||||
racc (~> 1.4)
|
racc (~> 1.4)
|
||||||
|
pdf-reader (2.12.0)
|
||||||
|
Ascii85 (~> 1.0)
|
||||||
|
afm (~> 0.2.1)
|
||||||
|
hashery (~> 2.0)
|
||||||
|
ruby-rc4
|
||||||
|
ttfunk
|
||||||
psych (5.1.2)
|
psych (5.1.2)
|
||||||
stringio
|
stringio
|
||||||
public_suffix (6.0.1)
|
public_suffix (6.0.1)
|
||||||
@@ -213,6 +222,7 @@ GEM
|
|||||||
io-console (~> 0.5)
|
io-console (~> 0.5)
|
||||||
rexml (3.3.4)
|
rexml (3.3.4)
|
||||||
strscan
|
strscan
|
||||||
|
ruby-rc4 (0.1.5)
|
||||||
rubyzip (2.3.2)
|
rubyzip (2.3.2)
|
||||||
selenium-webdriver (4.23.0)
|
selenium-webdriver (4.23.0)
|
||||||
base64 (~> 0.2)
|
base64 (~> 0.2)
|
||||||
@@ -251,6 +261,8 @@ GEM
|
|||||||
railties (>= 7.0.0)
|
railties (>= 7.0.0)
|
||||||
thor (1.3.1)
|
thor (1.3.1)
|
||||||
timeout (0.4.1)
|
timeout (0.4.1)
|
||||||
|
ttfunk (1.8.0)
|
||||||
|
bigdecimal (~> 3.1)
|
||||||
turbo-rails (2.0.6)
|
turbo-rails (2.0.6)
|
||||||
actionpack (>= 6.0.0)
|
actionpack (>= 6.0.0)
|
||||||
activejob (>= 6.0.0)
|
activejob (>= 6.0.0)
|
||||||
@@ -287,6 +299,7 @@ DEPENDENCIES
|
|||||||
importmap-rails
|
importmap-rails
|
||||||
inline_svg (~> 1.9)
|
inline_svg (~> 1.9)
|
||||||
jbuilder
|
jbuilder
|
||||||
|
pdf-reader (~> 2.12)
|
||||||
puma (>= 5.0)
|
puma (>= 5.0)
|
||||||
rails (~> 7.1.3, >= 7.1.3.4)
|
rails (~> 7.1.3, >= 7.1.3.4)
|
||||||
redis (>= 4.0.1)
|
redis (>= 4.0.1)
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ class Job < ApplicationRecord
|
|||||||
belongs_to :operator, class_name: 'User', optional: true
|
belongs_to :operator, class_name: 'User', optional: true
|
||||||
belongs_to :costumer, class_name: 'User', optional: true
|
belongs_to :costumer, class_name: 'User', optional: true
|
||||||
|
|
||||||
has_one_attached :pdf, dependent: :destroy
|
has_one_attached :pdf, dependent: :purge
|
||||||
|
|
||||||
validates_presence_of :costumer_firstname, :costumer_lastname, :privacy_policy_accepted, :pdf
|
validates_presence_of :costumer_firstname, :costumer_lastname, :privacy_policy_accepted, :pdf
|
||||||
validate :acceptable_pdf
|
validate :acceptable_pdf
|
||||||
@@ -11,6 +11,9 @@ class Job < ApplicationRecord
|
|||||||
before_save :update_paid_at, if: :will_save_change_to_status?
|
before_save :update_paid_at, if: :will_save_change_to_status?
|
||||||
before_save :update_status_changed_at, if: :will_save_change_to_status?
|
before_save :update_status_changed_at, if: :will_save_change_to_status?
|
||||||
|
|
||||||
|
# TODO: works only when job is created. Should move analyzer to activestorage :https://discuss.rubyonrails.org/t/active-storage-in-production-lessons-learned-and-in-depth-look-at-how-it-works/83289
|
||||||
|
after_create_commit :analyze_pdf
|
||||||
|
|
||||||
# NOTE: Multiple status if paing before brinting?
|
# NOTE: Multiple status if paing before brinting?
|
||||||
enum status: {
|
enum status: {
|
||||||
open: 0,
|
open: 0,
|
||||||
@@ -53,12 +56,10 @@ class Job < ApplicationRecord
|
|||||||
def acceptable_pdf
|
def acceptable_pdf
|
||||||
return unless pdf.attached?
|
return unless pdf.attached?
|
||||||
|
|
||||||
errors.add(:pdf, 'is too big') unless pdf.blob.byte_size <= 100.megabyte
|
|
||||||
|
|
||||||
acceptable_types = ['application/pdf']
|
acceptable_types = ['application/pdf']
|
||||||
return if acceptable_types.include?(pdf.content_type)
|
|
||||||
|
|
||||||
errors.add(:pdf, 'must be a PDF')
|
errors.add(:pdf, 'is too big') unless pdf.blob.byte_size <= 100.megabyte
|
||||||
|
errors.add(:pdf, 'must be a PDF') unless acceptable_types.include?(pdf.content_type)
|
||||||
end
|
end
|
||||||
|
|
||||||
def able_to_cancel?
|
def able_to_cancel?
|
||||||
@@ -84,4 +85,29 @@ class Job < ApplicationRecord
|
|||||||
def update_status_changed_at
|
def update_status_changed_at
|
||||||
self.status_changed_at = Time.now
|
self.status_changed_at = Time.now
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def analyze_pdf
|
||||||
|
# return unless pdf.attached? && pdf.new_record?
|
||||||
|
|
||||||
|
# TODO: add any check if attachment has changed
|
||||||
|
|
||||||
|
# pdfs.each do |pdf|
|
||||||
|
pdf.blob.open do |file|
|
||||||
|
self.number_of_plans_a0 = 0
|
||||||
|
self.number_of_plans_a1 = 0
|
||||||
|
self.number_of_plans_a2 = 0
|
||||||
|
self.number_of_plans_a3 = 0
|
||||||
|
self.costum_qm_plan = 0
|
||||||
|
# file = ActiveStorage::Blob.service.path_for(pdf.key).to_s
|
||||||
|
pdf_analyzer = Services::PdfAnalyzer.new(file)
|
||||||
|
pdf_analyzer.analyze
|
||||||
|
self.number_of_plans_a0 += pdf_analyzer.pages_a0
|
||||||
|
self.number_of_plans_a1 += pdf_analyzer.pages_a1
|
||||||
|
self.number_of_plans_a2 += pdf_analyzer.pages_a2
|
||||||
|
self.number_of_plans_a3 += pdf_analyzer.pages_a3
|
||||||
|
self.costum_qm_plan += pdf_analyzer.costum_qm
|
||||||
|
save
|
||||||
|
end
|
||||||
|
# end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -5,13 +5,16 @@
|
|||||||
<td class="p-3 text-sm text-hsrm-gray whitespace-nowrap"> <%= job.fullname %> </td>
|
<td class="p-3 text-sm text-hsrm-gray whitespace-nowrap"> <%= job.fullname %> </td>
|
||||||
<td class="p-3 text-sm text-hsrm-gray whitespace-nowrap">
|
<td class="p-3 text-sm text-hsrm-gray whitespace-nowrap">
|
||||||
<% if job.pdf.attached? %>
|
<% if job.pdf.attached? %>
|
||||||
<%= job.pdf.filename %><span class="p-1.5 bg-gray-300 font-medium rounded-lg ml-2"><%=number_to_human_size job.pdf.blob.byte_size%></span>
|
<%#= link_to job.pdf.filename, rails_blob_path(job.pdf, disposition: "attachment") %>
|
||||||
|
<%= link_to job.pdf.filename, job.pdf, download:true %>
|
||||||
|
<span class="p-1.5 bg-gray-300 font-medium rounded-lg ml-2"><%=number_to_human_size job.pdf.blob.byte_size%></span>
|
||||||
<% end %>
|
<% end %>
|
||||||
</td>
|
</td>
|
||||||
<td class="p-3 text-sm text-left text-hsrm-gray whitespace-nowrap"><span class="p-1.5 bg-gray-300 bg-opacity-50 font-medium rounded-lg"><%= job.number_of_plans_a0 %></span></td>
|
<td class="p-3 text-sm text-left text-hsrm-gray whitespace-nowrap"><span class="p-1.5 bg-gray-300 bg-opacity-50 font-medium rounded-lg"><%= job.number_of_plans_a0 %></span></td>
|
||||||
<td class="p-3 text-sm text-left text-hsrm-gray whitespace-nowrap"><span class="p-1.5 bg-gray-300 bg-opacity-50 font-medium rounded-lg"><%= job.number_of_plans_a1 %></span></td>
|
<td class="p-3 text-sm text-left text-hsrm-gray whitespace-nowrap"><span class="p-1.5 bg-gray-300 bg-opacity-50 font-medium rounded-lg"><%= job.number_of_plans_a1 %></span></td>
|
||||||
<td class="p-3 text-sm text-left text-hsrm-gray whitespace-nowrap"><span class="p-1.5 bg-gray-300 bg-opacity-50 font-medium rounded-lg"><%= job.number_of_plans_a2 %></span></td>
|
<td class="p-3 text-sm text-left text-hsrm-gray whitespace-nowrap"><span class="p-1.5 bg-gray-300 bg-opacity-50 font-medium rounded-lg"><%= job.number_of_plans_a2 %></span></td>
|
||||||
<td class="p-3 text-sm text-left text-hsrm-gray whitespace-nowrap"><span class="p-1.5 bg-gray-300 bg-opacity-50 font-medium rounded-lg"><%= job.number_of_plans_a3 %></span></td>
|
<td class="p-3 text-sm text-left text-hsrm-gray whitespace-nowrap"><span class="p-1.5 bg-gray-300 bg-opacity-50 font-medium rounded-lg"><%= job.number_of_plans_a3 %></span></td>
|
||||||
|
<td class="p-3 text-sm text-left text-hsrm-gray whitespace-nowrap"><span class="p-1.5 bg-gray-300 bg-opacity-50 font-medium rounded-lg"><%= job.costum_qm_plan.round(2) %></span></td>
|
||||||
<td class="p-3 text-sm text-left text-hsrm-gray whitespace-nowrap">
|
<td class="p-3 text-sm text-left text-hsrm-gray whitespace-nowrap">
|
||||||
<span class="p-1.5 text-xs font-medium uppercase tracking-wider bg-opacity-50 text-status-<%= job.status.to_sym %> bg-status-<%= job.status %>-light rounded-lg"><%= job.status %></span>
|
<span class="p-1.5 text-xs font-medium uppercase tracking-wider bg-opacity-50 text-status-<%= job.status.to_sym %> bg-status-<%= job.status %>-light rounded-lg"><%= job.status %></span>
|
||||||
</td>
|
</td>
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
<th class="p-3 text-sm font-semibold tracking-wide text-left w-1"> A1 </th>
|
<th class="p-3 text-sm font-semibold tracking-wide text-left w-1"> A1 </th>
|
||||||
<th class="p-3 text-sm font-semibold tracking-wide text-left w-1"> A2 </th>
|
<th class="p-3 text-sm font-semibold tracking-wide text-left w-1"> A2 </th>
|
||||||
<th class="p-3 text-sm font-semibold tracking-wide text-left w-1"> A3 </th>
|
<th class="p-3 text-sm font-semibold tracking-wide text-left w-1"> A3 </th>
|
||||||
|
<th class="p-3 text-sm font-semibold tracking-wide text-left w-1"> qm </th>
|
||||||
<th class="p-3 text-sm font-semibold tracking-wide text-left w-1"> Status </th>
|
<th class="p-3 text-sm font-semibold tracking-wide text-left w-1"> Status </th>
|
||||||
<th class="p-3 text-sm font-semibold tracking-wide text-left w-1"> Actions </th>
|
<th class="p-3 text-sm font-semibold tracking-wide text-left w-1"> Actions </th>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
16
db/seeds.rb
16
db/seeds.rb
@@ -17,18 +17,18 @@ end
|
|||||||
['GanzWichtig.pdf', 'IchBinIn5MinDran.pdf', 'DerPlanDerImmerProblemeMacht.pdf',
|
['GanzWichtig.pdf', 'IchBinIn5MinDran.pdf', 'DerPlanDerImmerProblemeMacht.pdf',
|
||||||
'DieFarbenGefallenMirNicht.pdf', 'MachHinIchHabsEilig.pdf', 'WarumDauertDasSoLange.pdf',
|
'DieFarbenGefallenMirNicht.pdf', 'MachHinIchHabsEilig.pdf', 'WarumDauertDasSoLange.pdf',
|
||||||
'DenPlanBezahleIchNicht.pdf', 'IchWarAlsErstesDran.pdf', 'WarumIstDerPlotterDefekt.pdf', 'DasNächsteMalGeheIchWoAndersHin.pdf'].shuffle.each do |pdf|
|
'DenPlanBezahleIchNicht.pdf', 'IchWarAlsErstesDran.pdf', 'WarumIstDerPlotterDefekt.pdf', 'DasNächsteMalGeheIchWoAndersHin.pdf'].shuffle.each do |pdf|
|
||||||
a0 = rand(0...7)
|
# a0 = rand(0...7)
|
||||||
a1 = rand(0...7)
|
# a1 = rand(0...7)
|
||||||
a2 = rand(0...7)
|
# a2 = rand(0...7)
|
||||||
a3 = rand(0...7)
|
# a3 = rand(0...7)
|
||||||
a0.zero? || a1 = 0 && a2 = 0 && a3 = 0
|
# a0.zero? || a1 = 0 && a2 = 0 && a3 = 0
|
||||||
a1.zero? || a2 = 0 && a3 = 0
|
# a1.zero? || a2 = 0 && a3 = 0
|
||||||
a2.zero? || a3 = 0
|
# a2.zero? || a3 = 0
|
||||||
status = %i[open open open open open open open open open open open open open open
|
status = %i[open open open open open open open open open open open open open open
|
||||||
printing ready_for_pickup paid canceled].sample
|
printing ready_for_pickup paid canceled].sample
|
||||||
|
|
||||||
job = Job.new(costumer_firstname: Faker::Name.unique.first_name, costumer_lastname: Faker::Name.unique.last_name,
|
job = Job.new(costumer_firstname: Faker::Name.unique.first_name, costumer_lastname: Faker::Name.unique.last_name,
|
||||||
number_of_plans_a0: a0, number_of_plans_a1: a1, number_of_plans_a2: a2, number_of_plans_a3: a3,
|
# number_of_plans_a0: a0, number_of_plans_a1: a1, number_of_plans_a2: a2, number_of_plans_a3: a3,
|
||||||
status:, privacy_policy_accepted: true)
|
status:, privacy_policy_accepted: true)
|
||||||
job.pdf = File.open(Rails.root.join('db/pdfs/', pdf))
|
job.pdf = File.open(Rails.root.join('db/pdfs/', pdf))
|
||||||
job.save!
|
job.save!
|
||||||
|
|||||||
80
lib/services/pdf_analyzer.rb
Normal file
80
lib/services/pdf_analyzer.rb
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
module Services
|
||||||
|
# Class to analyze PDF to extract number of pages from a0 to a3 and costum qm
|
||||||
|
class PdfAnalyzer
|
||||||
|
require 'pdf/reader'
|
||||||
|
require 'bigdecimal'
|
||||||
|
|
||||||
|
attr_reader :costum_qm, :pages_a0, :pages_a1, :pages_a2, :pages_a3
|
||||||
|
|
||||||
|
def initialize(file)
|
||||||
|
@reader = PDF::Reader.new(file)
|
||||||
|
|
||||||
|
@pages_a0 = 0
|
||||||
|
@pages_a1 = 0
|
||||||
|
@pages_a2 = 0
|
||||||
|
@pages_a3 = 0
|
||||||
|
@costum_qm = 0
|
||||||
|
|
||||||
|
@page_width = 0
|
||||||
|
@page_height = 0
|
||||||
|
end
|
||||||
|
|
||||||
|
def analyze
|
||||||
|
@reader.pages.each do |page|
|
||||||
|
format = extract_format(page)
|
||||||
|
case format
|
||||||
|
when 'a0'
|
||||||
|
@pages_a0 += 1
|
||||||
|
when 'a1'
|
||||||
|
@pages_a1 += 1
|
||||||
|
when 'a2'
|
||||||
|
@pages_a2 += 1
|
||||||
|
when 'a3'
|
||||||
|
@pages_a3 += 1
|
||||||
|
else
|
||||||
|
@costum_qm += calculate_qm
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def calculate_qm
|
||||||
|
(@page_width / 1000) * (@page_height / 1000)
|
||||||
|
end
|
||||||
|
|
||||||
|
def extract_format(page)
|
||||||
|
# debugger
|
||||||
|
extract_size(page)
|
||||||
|
return 'a0' if @page_width.round == 841 && @page_height.round == 1189
|
||||||
|
return 'a1' if @page_width.round == 594 && @page_height.round == 841
|
||||||
|
return 'a2' if @page_width.round == 420 && @page_height.round == 594
|
||||||
|
return 'a3' if @page_width.round == 297 && @page_height.round == 420
|
||||||
|
|
||||||
|
'costum'
|
||||||
|
end
|
||||||
|
|
||||||
|
def extract_size(page)
|
||||||
|
# debugger
|
||||||
|
bbox = page.attributes[:MediaBox] # CropBox
|
||||||
|
width = bbox[2] - bbox[0]
|
||||||
|
height = bbox[3] - bbox[1]
|
||||||
|
width_in_mm = pt2mm(width)
|
||||||
|
height_in_mm = pt2mm(height)
|
||||||
|
width_in_mm, height_in_mm = height_in_mm, width_in_mm if width_in_mm > height_in_mm
|
||||||
|
|
||||||
|
@page_width = width_in_mm
|
||||||
|
@page_height = height_in_mm
|
||||||
|
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
|
||||||
|
def pt2mm(pt)
|
||||||
|
(pt2in(pt) * BigDecimal('25.4')).round(2)
|
||||||
|
end
|
||||||
|
|
||||||
|
def pt2in(pt)
|
||||||
|
(pt / BigDecimal('72')).round(2)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
Reference in New Issue
Block a user