15 Commits

Author SHA1 Message Date
1febdc3e04 2026.3.5 2026-03-09 20:52:38 -04:00
d1f8f027c0 Allow doing math in price field for each markups 2026-03-09 20:45:26 -04:00
c5dd50a369 Minium required Redmine QBO version bump for breaking changes 2026-03-09 08:26:48 -04:00
92ecf171b9 2026.3.4 2026-03-09 08:20:45 -04:00
70dc594cb2 Fixed url for plugin 2026-03-09 08:20:05 -04:00
9736f1ac96 Added settings page for Item stats 2026-03-09 00:00:46 -04:00
3463698f8d Use self.page_size instead of a constant 2026-03-08 23:29:16 -04:00
3b51c961e3 2026.3.3 2026-03-08 22:00:47 -04:00
1780896e84 Set page size to 10 2026-03-08 21:48:03 -04:00
91cdd86c12 Add logging 2026-03-08 20:10:14 -04:00
e58bbafda7 fixed required plugin version 2026-03-08 19:29:45 -04:00
c56aa5897f 2026.3.2 2026-03-08 19:27:33 -04:00
449910c941 minium qty is 1 2026-03-08 15:48:01 -04:00
42ea5dffc9 updated comments 2026-03-08 15:37:01 -04:00
ecbfa2620f updated readme 2026-03-08 15:34:46 -04:00
9 changed files with 68 additions and 10 deletions

View File

@@ -21,7 +21,7 @@ This plugin allows **billable line items** to be attached to a Redmine issue. Wh
| Plugin Version | Redmine Version | Ruby Version | | Plugin Version | Redmine Version | Ruby Version |
| --- | --- | --- | | --- | --- | --- |
| 2026.3.2+ | 6.1.x | 3.2+ | | 2026.3.5+ | 6.1.x | 3.2+ |
--- ---

View File

@@ -15,12 +15,18 @@ class Item < ApplicationRecord
validates :unit_price, numericality: { greater_than_or_equal_to: 0 } validates :unit_price, numericality: { greater_than_or_equal_to: 0 }
self.primary_key = :id self.primary_key = :id
# Sync all employees, typically triggered by a scheduled task or manual sync request # Returns the last sync time formatted for display. If no sync has occurred, returns a default message.
def self.last_sync
return I18n.t(:label_qbo_never_synced) unless maximum(:updated_at)
format_time(maximum(:updated_at))
end
# Sync all items, typically triggered by a scheduled task or manual sync request
def self.sync def self.sync
ItemSyncJob.perform_later(full_sync: true) ItemSyncJob.perform_later(full_sync: true)
end end
# Sync a single employee by ID, typically triggered by a webhook notification or manual sync request # Sync a single items by ID, typically triggered by a webhook notification or manual sync request
def self.sync_by_id(id) def self.sync_by_id(id)
ItemSyncJob.perform_later(id: id) ItemSyncJob.perform_later(id: id)
end end

View File

@@ -17,12 +17,22 @@ class ItemSyncService < SyncServiceBase
Item Item
end end
# Specify a page size of 20, as the API only returns 20 items at a time.
def self.page_size
20
end
# Map relevant attributes from the QBO Employee to the local Employee model # Map relevant attributes from the QBO Employee to the local Employee model
def process_attributes(local, remote) def process_attributes(local, remote)
log "Processing Item ##{remote.id}"
local.id = remote.id local.id = remote.id
local.description = remote.description local.description = remote.description
local.unit_price = remote.unit_price local.unit_price = remote.unit_price
local.active = remote.active? local.active = remote.active?
end end
def log(msg)
Rails.logger.info "[ItemSyncService] #{msg}"
end
end end

View File

@@ -15,16 +15,18 @@
<td data-label="<%= t :label_qty %>"> <td data-label="<%= t :label_qty %>">
<%= f.number_field :quantity, <%= f.number_field :quantity,
step: 1, step: 1,
min: 0, min: 1,
value: (f.object.quantity.to_i > 0) ? f.object.quantity : 1,
class: "qty-field", class: "qty-field",
no_label: true, no_label: true,
disabled: readonly %> disabled: readonly %>
</td> </td>
<td data-label="<%= t :label_price %>"> <td data-label="<%= t :label_price %>">
<%= f.number_field :unit_price, <%= f.text_field :unit_price,
step: 0.01,
class: "price-field", class: "price-field",
inputmode: "decimal",
autocomplete: "off",
no_label: true, no_label: true,
disabled: readonly %> disabled: readonly %>
</td> </td>

View File

@@ -0,0 +1,5 @@
<div>
<b><%=t(:label_item_count)%></b> <%= Item.count %> @ <%= Item.last_sync %>
<br/>
<%=t(:label_last_sync)%> </b> <%= Qbo.last_sync if Qbo.exists? %>
</div>

View File

@@ -0,0 +1,33 @@
function evaluateMathExpression(expr) {
if (!expr) return null;
// allow only digits, decimal, operators, parentheses, spaces
if (!/^[0-9+\-*/().\s]+$/.test(expr)) {
return null;
}
try {
return Function('"use strict"; return (' + expr + ')')();
} catch {
return null;
}
}
document.addEventListener("blur", function(e) {
if (!e.target.classList.contains("price-field")) return;
const field = e.target;
const value = field.value.trim();
const result = evaluateMathExpression(value);
if (result !== null && !isNaN(result)) {
field.value = Number(result).toFixed(2);
}
if (typeof updateLineItemTotals === "function") {
updateLineItemTotals();
}
}, true);

View File

@@ -13,6 +13,7 @@
en: en:
label_description: "Description" label_description: "Description"
label_item: "Item" label_item: "Item"
label_item_count: "Item Count:"
label_line_items: "Line Items" label_line_items: "Line Items"
label_price: "Unit Price" label_price: "Unit Price"
label_qty: "Quantity" label_qty: "Quantity"

View File

@@ -14,21 +14,21 @@ Redmine::Plugin.register :redmine_qbo_lineitems do
name 'Redmine QBO Line Items plugin' name 'Redmine QBO Line Items plugin'
author 'Rick Barrette' author 'Rick Barrette'
description 'A plugin for Redmine to extend the capabilitys of the Redmine QuickBooks Online plugin to attach billable line items to an isuue' description 'A plugin for Redmine to extend the capabilitys of the Redmine QuickBooks Online plugin to attach billable line items to an isuue'
version '2026.3.1' version '2026.3.5'
url 'https://github.com/rickbarrette/redmine_qbo' url 'https://github.com/rickbarrette/redmine_qbo_lineitems'
author_url 'https://barrettefabrication.com' author_url 'https://barrettefabrication.com'
requires_redmine version_or_higher: '6.1.0' requires_redmine version_or_higher: '6.1.0'
settings partial: 'line_items/settings'
# Ensure redmine_qbo is installed # Ensure redmine_qbo is installed
begin begin
requires_redmine_plugin :redmine_qbo, version_or_higher: '2026.3.0' requires_redmine_plugin :redmine_qbo, version_or_higher: '2026.3.5'
rescue Redmine::PluginNotFound rescue Redmine::PluginNotFound
raise 'Please install the redmine_qbo plugin (https://github.com/rickbarrette/redmine_qbo)' raise 'Please install the redmine_qbo plugin (https://github.com/rickbarrette/redmine_qbo)'
end end
# Add safe attributes for core models # Add safe attributes for core models
Issue.safe_attributes :line_items_attributes Issue.safe_attributes :line_items_attributes
end end
# Dynamically load all Hooks & Patches recursively # Dynamically load all Hooks & Patches recursively

View File

@@ -19,6 +19,7 @@ module RedmineQboLineItems
javascript_include_tag( 'nested_form_controller', plugin: :redmine_qbo_lineitems), javascript_include_tag( 'nested_form_controller', plugin: :redmine_qbo_lineitems),
javascript_include_tag("line_items", plugin: :redmine_qbo_lineitems), javascript_include_tag("line_items", plugin: :redmine_qbo_lineitems),
javascript_include_tag("autocomplete", plugin: :redmine_qbo_lineitems), javascript_include_tag("autocomplete", plugin: :redmine_qbo_lineitems),
javascript_include_tag("blur", plugin: :redmine_qbo_lineitems),
stylesheet_link_tag("line_items", plugin: :redmine_qbo_lineitems) stylesheet_link_tag("line_items", plugin: :redmine_qbo_lineitems)
]) ])
end end