Class: PublicationTextReplaceWorker

Inherits:
Object
  • Object
show all
Includes:
Sidekiq::Job
Defined in:
app/workers/publication_text_replace_worker.rb

Overview

Runs Pdf::TextReplacer over one publication's literature and stages the
edited PDF as a GeneratedPdf (kind +edited+) for human review in the PDF
studio. Enqueued per matching publication by Sunny's +pdf_find_replace+ tool
after a dry-run count and explicit confirm — the worker NEVER imports; the
reviewer imports from the studio, normally as a :revise (next revision
letter — exactly what a spec correction is).

Provenance (search/replacement/meta) is persisted on the staged PDF's layout
under +'find_replace'+.

Examples:

PublicationTextReplaceWorker.perform_async(publication.id, '240V', '208V', 'created_by_id' => .id)

Constant Summary collapse

ALLOWED_SOURCE_RECORDS =

Only these classes may be linked as the review context — never constantize
an arbitrary Sidekiq arg.

%w[AssistantConversation Item].freeze

Instance Method Summary collapse

Instance Method Details

#perform(publication_id, search, replacement, options = {}) ⇒ Object

Parameters:

  • publication_id (Integer)

    the publication Item to edit

  • search (String)

    literal text to find

  • replacement (String)

    text to overlay

  • options (Hash) (defaults to: {})

Options Hash (options):

  • 'created_by_id' (Integer)

    Account id recorded on the staged PDF

  • 'source_record_type' (String)
    • 'source_record_id' polymorphic
      review context (AssistantConversation from Sunny)


34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
# File 'app/workers/publication_text_replace_worker.rb', line 34

def perform(publication_id, search, replacement, options = {})
  publication = Item.publications.find_by(id: publication_id)
  return Rails.logger.info("[PublicationTextReplaceWorker] Publication #{publication_id} not found, skipping") unless publication

  literature = publication.literature
  return Rails.logger.info("[PublicationTextReplaceWorker] Publication #{publication_id} has no literature, skipping") unless literature&.attachment_stored?

  begin
    result = Pdf::TextReplacer.replace(literature.to_file, search:, replacement:)
  rescue Pdf::Toolkit::Error => e
    # Per-document permanent: the phrase isn't replaceable in THIS doc's text
    # layer (not present, fragmented, or corrupted). Record for visibility and
    # skip — a retry would find the same text layer.
    ErrorReporting.warning(e, source: :background, publication_id:, sku: publication.sku, search:)
    return
  end

  base   = publication.publication_base_name.presence || publication.sku
  staged = GeneratedPdfGenerator.stage(
    bytes:                 result.bytes,
    layout:                find_replace_layout(search, replacement, result.meta, publication),
    kind:                  'edited',
    title:                 "#{base} — spec update",
    instructions:          "Find-and-replace: #{search.inspect}#{replacement.inspect}",
    created_by_id:         options['created_by_id'],
    source_publication_id: publication.id,
    source_record:         source_record(options),
    filename:              "#{base.to_s.parameterize}-updated.pdf"
  )
  raise staged.error.presence || 'staging failed' unless staged.success?

  Rails.logger.info("[PublicationTextReplaceWorker] Publication #{publication_id}: #{result.meta[:replacements]} replacement(s) staged as GeneratedPdf #{staged.generated_pdf.id}")
end