From cd109f16b54f3b093761ce7aef023a9d34a7e642 Mon Sep 17 00:00:00 2001 From: Rick Barrette Date: Sat, 14 Mar 2026 00:10:10 -0400 Subject: [PATCH] Refactored QBO service calls --- app/jobs/qbo_sync_job.rb | 4 +--- app/models/qbo_base_model.rb | 6 ++---- app/services/estimate_sync_service.rb | 2 +- app/services/invoice_push_service.rb | 11 +---------- app/services/pdf_service.rb | 2 -- app/services/qbo_connection_service.rb | 18 ++++++++++++++---- app/services/service_base.rb | 21 +++------------------ app/services/sync_service_base.rb | 21 +++------------------ 8 files changed, 25 insertions(+), 60 deletions(-) diff --git a/app/jobs/qbo_sync_job.rb b/app/jobs/qbo_sync_job.rb index b1d238a..813ed5e 100644 --- a/app/jobs/qbo_sync_job.rb +++ b/app/jobs/qbo_sync_job.rb @@ -16,11 +16,9 @@ class QboSyncJob < ApplicationJob def perform(full_sync: false, id: nil, entity: nil, doc_number: nil) raise "An entity to sync is required" unless entity @entity = entity - qbo = QboConnectionService.current! - raise "No QBO configuration found" unless qbo log "Starting #{full_sync ? 'full' : 'incremental'} sync for #{entity} ##{id || doc_number || 'all'}..." - service = "#{entity}SyncService".constantize.new(qbo: qbo) + service = "#{entity}SyncService".constantize.new if id.present? service.sync_by_id(id) diff --git a/app/models/qbo_base_model.rb b/app/models/qbo_base_model.rb index b375c43..4931063 100644 --- a/app/models/qbo_base_model.rb +++ b/app/models/qbo_base_model.rb @@ -98,15 +98,13 @@ class QboBaseModel < ActiveRecord::Base # Fetches the entity's details from QuickBooks Online. def fetch_details log "Fetching details for #{model_name.name} ##{id} from QBO..." - qbo = QboConnectionService.current! - service_class.new(qbo: qbo, local: self).pull() + service_class.new(local: self).pull() end # Pushs the entity's details from QuickBooks Online. def push_to_qbo log "Starting push for #{model_name.name} ##{id}..." - qbo = QboConnectionService.current! - reslut = service_class.new(qbo: qbo, local: self).push + reslut = service_class.new(local: self).push Rails.cache.delete(details_cache_key) return reslut end diff --git a/app/services/estimate_sync_service.rb b/app/services/estimate_sync_service.rb index db8fbd9..be8448c 100644 --- a/app/services/estimate_sync_service.rb +++ b/app/services/estimate_sync_service.rb @@ -13,7 +13,7 @@ class EstimateSyncService < SyncServiceBase # sync only one estimate def sync_by_doc_number(number) log "Syncing estimate by doc number #{number}" - with_qbo_service do |service| + QboConnectionService.with_qbo_service(entity: @entity) do |service| persist(service.find_by( :doc_number, number).first) end end diff --git a/app/services/invoice_push_service.rb b/app/services/invoice_push_service.rb index 8ebfa89..f6c9842 100644 --- a/app/services/invoice_push_service.rb +++ b/app/services/invoice_push_service.rb @@ -17,20 +17,11 @@ class InvoicePushService # Push invoice changes to QBO if the invoice is linked to any issues with custom field changes that need to be synced def push return if @invoice.qbo_sync_locked? - log "Pushing invoice ##{@invoice.id} to QBO due to linked issue custom field changes" - @invoice.update_column(:qbo_sync_locked, true) - - qbo = QboConnectionService.current! - - qbo.perform_authenticated_request do |access_token| - service = Quickbooks::Service::Invoice.new( company_id: qbo.realm_id, access_token: access_token) - + remote = QboConnectionService.with_qbo_service(entity: Invoice) do |service| remote = service.fetch_by_id(@invoice.id) - # modify remote object here if needed - service.update(remote) end rescue => e diff --git a/app/services/pdf_service.rb b/app/services/pdf_service.rb index f0aa840..9557878 100644 --- a/app/services/pdf_service.rb +++ b/app/services/pdf_service.rb @@ -15,8 +15,6 @@ class PdfService def initialize(entity: entity) raise "An entity to sync is required" unless entity @entity = entity - @qbo = QboConnectionService.current! - raise "No QBO configuration found" unless @qbo end # Fetches the PDF for the given entity IDs. If multiple IDs are provided, their PDFs are combined into a single document. diff --git a/app/services/qbo_connection_service.rb b/app/services/qbo_connection_service.rb index 77f2287..4a4b16e 100644 --- a/app/services/qbo_connection_service.rb +++ b/app/services/qbo_connection_service.rb @@ -10,6 +10,11 @@ class QboConnectionService + # Returns the current QBO connection record. Raises an error if no connection exists. + def self.current! + Qbo.first || raise("QBO not connected") + end + # Replaces the existing QBO connection with new credentials. Deletes all existing records and creates a new one with the provided token, refresh token, and realm ID. Refreshes the token immediately after creation. def self.replace!(token:, refresh_token:, realm_id:) Qbo.transaction do @@ -24,9 +29,14 @@ class QboConnectionService end end - # Returns the current QBO connection record. Raises an error if no connection exists. - def self.current! - Qbo.first || raise("QBO not connected") + # Performs authenticaed requests with QBO service + def self.with_qbo_service(entity: nil) + qbo = current! + raise "An entity to sync is required" unless entity + service_class ||= "Quickbooks::Service::#{entity}".constantize + qbo.perform_authenticated_request do |access_token| + service = service_class.new( company_id: qbo.realm_id, access_token: access_token ) + yield service + end end - end \ No newline at end of file diff --git a/app/services/service_base.rb b/app/services/service_base.rb index ae8d4d3..5998978 100644 --- a/app/services/service_base.rb +++ b/app/services/service_base.rb @@ -13,11 +13,9 @@ class ServiceBase # Subclasses should Initializes the service with a QBO client and a local record. # The QBO client is used to communicate with QuickBooks Online, while the local record contains the data that needs to be pushed to QBO. # If no local is provided, the service will not perform any operations. - def initialize(qbo:, local: nil) + def initialize(local: nil) @entity = local.class.name - raise "No QBO configuration found" unless qbo raise "#{@entity} record is required for push operation" unless local - @qbo = qbo @local = local end @@ -31,7 +29,7 @@ class ServiceBase return build_qbo_remote unless @local.present? return build_qbo_remote unless @local.id log "Fetching details for #{@entity} ##{@local.id} from QBO..." - with_qbo_service do |service| + QboConnectionService.with_qbo_service(entity: @entity) do |service| service.fetch_by_id(@local.id) end rescue => e @@ -45,7 +43,7 @@ class ServiceBase # If the push is successful, it returns the remote record; otherwise, it logs the error and returns false. def push log "Pushing #{@entity} ##{@local.id} to QBO..." - remote = with_qbo_service do |service| + remote = QboConnectionService.with_qbo_service(entity: @entity) do |service| if @local.id.present? log "Updating #{@entity}" service.update(@local.details) @@ -61,22 +59,9 @@ class ServiceBase private - # Performs authenticaed requests with QBO service - def with_qbo_service - @qbo.perform_authenticated_request do |access_token| - service = service_class.new( company_id: @qbo.realm_id, access_token: access_token ) - yield service - end - end - # Log messages with the entity type for better traceability def log(msg) Rails.logger.info "[#{@entity}Service] #{msg}" end - # Dynamically get the Quickbooks Service Class - def service_class - @service_class ||= "Quickbooks::Service::#{@entity}".constantize - end - end \ No newline at end of file diff --git a/app/services/sync_service_base.rb b/app/services/sync_service_base.rb index 4e92ce7..a8c698e 100644 --- a/app/services/sync_service_base.rb +++ b/app/services/sync_service_base.rb @@ -12,9 +12,7 @@ class SyncServiceBase PAGE_SIZE = 1000 # Subclasses should initialize with a QBO client instance - def initialize(qbo:) - raise "No QBO configuration found" unless qbo - @qbo = qbo + def initialize() @entity = self.class.model_class @page_size = self.class.page_size end @@ -32,7 +30,7 @@ class SyncServiceBase # Sync all entities, or only those updated since the last sync def sync(full_sync: false) log "Starting #{full_sync ? 'full' : 'incremental'} #{@entity.name} sync with page size of: #{@page_size}" - with_qbo_service do |service| + QboConnectionService.with_qbo_service(entity: @entity) do |service| query = build_query(full_sync) service.query_in_batches(query, per_page: @page_size) do |batch| entries = Array(batch) @@ -49,7 +47,7 @@ class SyncServiceBase # Sync a single entity by its QBO ID (webhook usage) def sync_by_id(id) log "Syncing #{@entity.name} with ID #{id}" - with_qbo_service do |service| + QboConnectionService.with_qbo_service(entity: @entity) do |service| remote = service.fetch_by_id(id) persist(remote) end @@ -240,17 +238,4 @@ class SyncServiceBase end end end - - # Dynamically get the Quickbooks Service Class - def service_class - @service_class ||= "Quickbooks::Service::#{@entity}".constantize - end - - # Performs authenticaed requests with QBO service - def with_qbo_service - @qbo.perform_authenticated_request do |access_token| - service = service_class.new( company_id: @qbo.realm_id, access_token: access_token ) - yield service - end - end end \ No newline at end of file