Class: Edi::Retriever

Inherits:
BaseService show all
Defined in:
app/services/edi/retriever.rb

Overview

I connect to a transport, retrieve files and save them in the Edi Communication log

Direct Known Subclasses

Walmart::OrderRetriever

Instance Attribute Summary

Attributes inherited from BaseService

#options

Instance Method Summary collapse

Methods inherited from BaseService

#initialize, #log_debug, #log_error, #log_info, #log_warning, #logger, #tagged_logger

Constructor Details

This class inherits a constructor from BaseService

Instance Method Details

#instantiate_transporter(transporter, transporter_profile = nil) ⇒ Object



78
79
80
81
82
83
84
85
86
87
88
89
90
91
# File 'app/services/edi/retriever.rb', line 78

def instantiate_transporter(transporter, transporter_profile = nil)
  case transporter
  when :sftp
    Transport::SftpConnection.new(transporter_profile)
  when :basic_http_api
    Transport::BasicHttpApiConnection.new({ profile: transporter_profile })
  when :http_api
    Transport::HttpApiConnection.new
  when :local_file
    Transport::LocalFile.new
  else
    raise "Unknown transporter: #{transporter}"
  end
end

#process(transporter: :sftp, transporter_profile: nil, remote_path:, file_pattern: nil, partner:, category:, data_type: 'xml', store_to_upload: false) ⇒ Object



5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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
67
68
69
70
71
72
73
74
75
76
# File 'app/services/edi/retriever.rb', line 5

def process(transporter: :sftp,
            transporter_profile: nil, # Transporter profile for sftp connection, see secrets.yml
            remote_path:, # Directory to pull from, usually specified in partner configuration
            file_pattern: nil, # Regexp file pattern to match
            partner:, # Name of partner for creating log entry
            category:, # Category for log entry
            data_type: 'xml',
            store_to_upload: false) # By default the file content is stored to the log directly, if true then an upload is created )
  transport = instantiate_transporter(transporter, transporter_profile)
  batch_data = if store_to_upload
                 transport.batch_download_to_file(remote_path, nil, file_pattern)
               else
                 transport.batch_download_data(remote_path, file_pattern)
               end
  logger.warn "No new files found at #{remote_path} with pattern: #{file_pattern}" if batch_data.blank?
  edi_logs = []
  batch_data.each do |file_name, data|
    remote_file_path = "#{remote_path}/#{file_name}"

    # Idempotency guard. A prior run may have committed this file but then
    # failed to delete it remotely (e.g. an SFTP blip *after* COMMIT, or the
    # process was killed between commit and rm). On the next pass we must not
    # create a duplicate log — recognise the already-stored file and just
    # clear the stale source copy. CommerceHub file names embed the batch
    # number + md5, so (partner, category, file_name) is unique per delivery.
    if (existing = EdiCommunicationLog.find_by(partner: partner, category: category, file_name: file_name))
      logger.info "#{file_name} already stored as edi log #{existing.id}; removing stale remote copy"
      edi_logs << existing
      transport.rm(remote_file_path)
      next
    end

    edi_log = nil
    EdiCommunicationLog.transaction do
      data_blob = store_to_upload ? nil : data.encode("UTF-8", invalid: :replace, replace: "") # clean the data so our normalizr doesn't explode here on non-UTF-8 characters

      edi_log = EdiCommunicationLog.create! partner: partner,
                                            category: category,
                                            data: data_blob,
                                            data_type: data_type,
                                            file_name: file_name,
                                            transmit_datetime: Time.current

      if store_to_upload
        upload = Upload.uploadify(data, 'custom_packing_slip_pdf', edi_log, file_name)
        raise "Could not uploadify file #{file_name}" unless upload.persisted?
      end
    end

    # Delete the source file ONLY after the row is durably committed. Removing
    # it inside the transaction (the old behaviour) coupled the file's deletion
    # to an uncommitted insert: if COMMIT then failed — failover, pool timeout,
    # a SIGKILL during deploy — the insert rolled back while the file was
    # already gone from SFTP, losing the message with no trace on either side.
    # Re-running now re-fetches the file and the guard above keeps it idempotent.
    #
    # Belt-and-suspenders: re-read the row by its natural key before deleting
    # our only copy. A silent fake-success commit (a poisoned pooled connection
    # that returns without raising — even handing back another row's id) would
    # otherwise pass save! and we'd delete the source anyway. confirm_persisted!
    # raises if the row isn't really there, so the file stays for the next run.
    Durability.confirm_persisted!(EdiCommunicationLog,
                                  { partner: partner, category: category, file_name: file_name },
                                  context: { remote_path: remote_path, edi_log_id: edi_log.id })
    logger.info "#{file_name} saved to edi log #{edi_log.id}"
    edi_logs << edi_log
    transport.rm(remote_file_path)
  rescue StandardError => e
    ErrorReporting.error(e, "Problem during the processing of batch_data in file: #{file_name}")
  end
  edi_logs
end