Class: Menard::PartnerPortalUploader

Inherits:
Object
  • Object
show all
Defined in:
app/services/menard/partner_portal_uploader.rb

Overview

Uploads an inventory spreadsheet to the Menard partner portal via browser automation.

Uses playwright-ruby-client to log in, navigate to the inventory upload page,
attach the XLSX file, and submit the form.

The Menard portal is a JavaScript SPA — the login form is rendered dynamically
by main.bundle.js, not present in the initial HTML. Chromium must fully execute
JS in headless mode, which requires --no-sandbox and --disable-dev-shm-usage
when running from background processes (Sidekiq) or on Linux servers.

Credentials are read from Heatwave::Configuration (encrypted credentials):
menard:
portal_username:
portal_password:

Defined Under Namespace

Classes: Result

Constant Summary collapse

PORTAL_BASE_URL =

URL for portal base.

'https://partners.menard-inc.com/partners'
LOGIN_URL =

URL for login.

"#{PORTAL_BASE_URL}/login.html".freeze
INVENTORY_URL =

URL for inventory.

"#{PORTAL_BASE_URL}/viewInventoryUpdates.html".freeze
60_000
SPA_RENDER_TIMEOUT =

Timeout for spa render.

45_000

Instance Method Summary collapse

Constructor Details

#initialize(file_path:, headless: true) ⇒ PartnerPortalUploader

Returns a new instance of PartnerPortalUploader.



37
38
39
40
41
# File 'app/services/menard/partner_portal_uploader.rb', line 37

def initialize(file_path:, headless: true)
  @file_path = file_path
  @headless = headless
  validate!
end

Instance Method Details

#callObject



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
# File 'app/services/menard/partner_portal_uploader.rb', line 43

def call
  require 'playwright'
  log('Starting browser automation', level: :info)

  PlaywrightRuntime.with_browser(launch_options: launch_options) do |browser|
    log("Browser ready (headless=#{@headless}, remote=#{PlaywrightRuntime.remote?})", level: :info)

    active_page = nil
    page = nil
    begin
      context = browser.new_context(
        userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ' \
                   '(KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36'
      )
      page = context.new_page
      attach_diagnostics(page)

      active_page = (page)
      upload_inventory(active_page)
    rescue Playwright::Error, Playwright::TimeoutError => e
      dump_page_html(active_page || page, 'timeout_failure')
      log("Playwright error: #{e.class}#{e.message}", level: :error)
      Result.new(uploaded: false, message: "Browser automation failed: #{e.message}")
    rescue StandardError => e
      dump_page_html(active_page || page, 'unexpected_failure')
      log("#{e.class}: #{e.message}\n#{e.backtrace&.first(5)&.join("\n")}", level: :error)
      Result.new(uploaded: false, message: "Upload failed: #{e.message}")
    end
  end
rescue StandardError => e
  log("Fatal: #{e.class}: #{e.message}", level: :error)
  Result.new(uploaded: false, message: "Upload failed: #{e.message}")
end