Compare commits

..

5 Commits

7 changed files with 26 additions and 12 deletions

View File

@@ -46,9 +46,13 @@ class QboController < ApplicationController
redirect_to issue || root_path, flash: { error: e.message } redirect_to issue || root_path, flash: { error: e.message }
end end
# Manual sync endpoint to trigger a full synchronization of QuickBooks entities with the local database. Enqueues all relevant sync jobs and redirects to the home page with a notice that syncing has started. # Manual sync endpoint to trigger synchronization of QuickBooks entities
# with the local database. Supports full or partial sync depending on
# the `full_sync` boolean parameter.
def sync def sync
QboSyncDispatcher.full_sync! full_sync = ActiveModel::Type::Boolean.new.cast(params[:full_sync])
QboSyncDispatcher.sync!(full_sync: full_sync)
redirect_to :home, flash: { notice: I18n.t(:label_syncing) } redirect_to :home, flash: { notice: I18n.t(:label_syncing) }
end end

View File

@@ -17,9 +17,17 @@ class QboSyncDispatcher
Employee Employee
].freeze ].freeze
# Dispatches all synchronization jobs to perform a full sync of QuickBooks entities with the local database. Each job is enqueued with the `full_sync` flag set to true. # Dispatches all synchronization jobs to perform a full sync of QuickBooks entities with the local database.
def self.full_sync! # Each job is enqueued with the `full_sync` flag set to true.
def self.sync!(full_sync: false)
log "Manual Sync initated for #{full_sync ? "full sync" : "incremental sync"}"
enque_jobs full_sync: full_sync
end
private
# Dynamically enques all sync jobs
def self.enque_jobs(full_sync: full_sync)
jobs = SYNC_JOBS.dup jobs = SYNC_JOBS.dup
# Allow other plugins to add addtional sync jobs via Hooks # Allow other plugins to add addtional sync jobs via Hooks
@@ -29,11 +37,9 @@ class QboSyncDispatcher
log "Added additionals QBO Sync Job for #{context.to_s}" log "Added additionals QBO Sync Job for #{context.to_s}"
end end
jobs.each { |job| QboSyncJob.perform_later(entity: job, full_sync: true) } jobs.each { |job| QboSyncJob.perform_later(entity: job, full_sync: full_sync) }
end end
private
def self.log(msg) def self.log(msg)
Rails.logger.info "[QboSyncDispatcher] #{msg}" Rails.logger.info "[QboSyncDispatcher] #{msg}"
end end

View File

@@ -27,8 +27,9 @@ class Qbo < ActiveRecord::Base
# Returns the last sync time formatted for display. If no sync has occurred, returns a default message. # Returns the last sync time formatted for display. If no sync has occurred, returns a default message.
def self.last_sync def self.last_sync
qbo = QboConnectionService.current! qbo = QboConnectionService.current!
return I18n.t(:label_qbo_never_synced) unless qbo&.last_sync
format_time(qbo.last_sync) format_time(qbo.last_sync)
rescue
return I18n.t(:label_qbo_never_synced)
end end
private private

View File

@@ -10,7 +10,8 @@
class QboOauthService class QboOauthService
# Generates the QuickBooks OAuth authorization URL with the specified callback URL. The URL includes necessary parameters such as response type, state, and scope. # Generates the QuickBooks OAuth authorization URL with the specified callback URL.
# The URL includes necessary parameters such as response type, state, and scope.
def self.authorization_url(callback_url:) def self.authorization_url(callback_url:)
client.auth_code.authorize_url( client.auth_code.authorize_url(
redirect_uri: callback_url, redirect_uri: callback_url,
@@ -20,7 +21,8 @@ class QboOauthService
) )
end end
# Exchanges the authorization code for access and refresh tokens. Creates or replaces the QBO connection record with the new credentials and refreshes the token immediately after creation. # Exchanges the authorization code for access and refresh tokens.
# Creates or replaces the QBO connection record with the new credentials and refreshes the token immediately after creation.
def self.exchange!(code:, callback_url:, realm_id:) def self.exchange!(code:, callback_url:, realm_id:)
resp = client.auth_code.get_token(code, redirect_uri: callback_url) resp = client.auth_code.get_token(code, redirect_uri: callback_url)
QboConnectionService.replace!( token: resp.token, refresh_token: resp.refresh_token, realm_id: realm_id ) QboConnectionService.replace!( token: resp.token, refresh_token: resp.refresh_token, realm_id: realm_id )

View File

@@ -3,3 +3,4 @@
<%= submit_tag t(:label_search) %> <%= submit_tag t(:label_search) %>
<% end %> <% end %>
<%= button_to t(:label_new_customer), new_customer_path, method: :get%> <%= button_to t(:label_new_customer), new_customer_path, method: :get%>
<%= button_to(t(:label_sync), qbo_sync_path, method: :get) if User.current.admin?%>

View File

@@ -107,5 +107,5 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
<br/> <br/>
<div> <div>
<b><%=t(:label_last_sync)%> </b> <%= Qbo.last_sync if Qbo.exists? %> <%= link_to t(:label_sync_now), qbo_sync_path %> <b><%=t(:label_last_sync)%> </b> <%= Qbo.last_sync if Qbo.exists? %> <%= link_to t(:label_sync_now), qbo_sync_path(full_sync: true) %>
</div> </div>

View File

@@ -14,7 +14,7 @@ Redmine::Plugin.register :redmine_qbo do
name 'Redmine QBO plugin' name 'Redmine QBO plugin'
author 'Rick Barrette' author 'Rick Barrette'
description 'A pluging for Redmine to connect with QuickBooks Online to create Time Activity Entries for billable hours logged when an Issue is closed' description 'A pluging for Redmine to connect with QuickBooks Online to create Time Activity Entries for billable hours logged when an Issue is closed'
version '2026.3.7' version '2026.3.8'
url 'https://github.com/rickbarrette/redmine_qbo' url 'https://github.com/rickbarrette/redmine_qbo'
author_url 'https://barrettefabrication.com' author_url 'https://barrettefabrication.com'
settings default: {empty: true}, partial: 'qbo/settings' settings default: {empty: true}, partial: 'qbo/settings'