10 Commits

Author SHA1 Message Date
20b7564c38 Added items to admin menu 2026-03-19 07:13:43 -04:00
9820646857 allow math on unit price field 2026-03-18 22:02:53 -04:00
647ce4c63a 2026.3.10 2026-03-17 23:36:29 -04:00
4367c77393 updaed locale 2026-03-17 20:49:58 -04:00
8c0ce8ce48 updated locale 2026-03-17 20:45:21 -04:00
9fd1f4fb7d removed back link 2026-03-17 20:37:58 -04:00
cca822c570 better display flags 2026-03-17 20:35:40 -04:00
795906b4f5 Sync Item account numbers 2026-03-17 20:26:52 -04:00
d5e59c120c Make qbo item inactive if deleted 2026-03-17 19:45:36 -04:00
e640e61648 Added missing label_item_new 2026-03-17 19:45:14 -04:00
9 changed files with 55 additions and 22 deletions

View File

@@ -18,6 +18,7 @@ class Item < QboBaseModel
self.inheritance_column = :_type_disabled self.inheritance_column = :_type_disabled
qbo_sync push: true qbo_sync push: true
after_initialize :set_defaults, if: :new_record? after_initialize :set_defaults, if: :new_record?
before_destroy :make_inactive
# Updates Both local & remote DB account ref # Updates Both local & remote DB account ref
def account_id=(id) def account_id=(id)
@@ -37,6 +38,11 @@ class Item < QboBaseModel
super super
end end
def make_inactive
details.active = false
push_to_qbo
end
def ref def ref
Quickbooks::Model::BaseReference.new Quickbooks::Model::BaseReference.new
end end

View File

@@ -24,6 +24,9 @@ class ItemSyncService < SyncServiceBase
map_attribute :active, :active? map_attribute :active, :active?
map_attribute :taxable, :taxable? map_attribute :taxable, :taxable?
map_attribute :account do |remote|
Account.find remote.income_account_ref.value.to_i
end
map_attributes :description, :id, :name, :sku, :type, :unit_price map_attributes :description, :id, :name, :sku, :type, :unit_price
end end

View File

@@ -15,7 +15,10 @@
</p> </p>
<p> <p>
<%= f.number_field :unit_price, step: 0.01, size: 10 %> <%= f.text_field :unit_price,
class: "price-field",
inputmode: "decimal",
autocomplete: "off"%>
</p> </p>
<p> <p>

View File

@@ -1,5 +1,3 @@
<h2>Edit Item</h2> <h2><%=t(:label_edit_item)%></h2>
<%= render "form" %> <%= render "form" %>
<%= link_to "Back", items_path %>

View File

@@ -26,10 +26,18 @@
<td class="description"><%= truncate(item.description, length: 60) %></td> <td class="description"><%= truncate(item.description, length: 60) %></td>
<td class="unit_price"><%= number_to_currency(item.unit_price) %></td> <td class="unit_price"><%= number_to_currency(item.unit_price) %></td>
<td class="taxable center"> <td class="taxable center">
<%= item.taxable ? content_tag(:span, '', class: 'icon icon-ok') : "" %> <% if item.taxable %>
<span class="icon icon-ok" style="color: green;"><%=t(:yes)%></span>
<% else %>
<span class="icon icon-not-ok" style="color: #999;"><%=t(:no)%></span>
<% end %>
</td> </td>
<td class="active center"> <td class="active center">
<%= checked_image item.active %> <% if item.active %>
<span class="icon icon-ok" style="color: green;"><%=t(:yes)%></span>
<% else %>
<span class="icon icon-not-ok" style="color: #999;"><%=t(:no)%></span>
<% end %>
</td> </td>
<td class="buttons"> <td class="buttons">
<%= link_to l(:button_edit), edit_item_path(item), class: 'icon icon-edit' %> <%= link_to l(:button_edit), edit_item_path(item), class: 'icon icon-edit' %>

View File

@@ -1,5 +1,5 @@
<h2>New Item</h2> <h2><%=t(:label_item_new)%></h2>
<%= render "form" %> <%= render "form" %>
<%= link_to "Back", items_path %> <%= link_to t(:label_back), items_path %>

View File

@@ -8,27 +8,27 @@
<div class="issue details"> <div class="attributes"> <div class="issue details"> <div class="attributes">
<div class="splitcontent"> <div class="splitcontent">
<div class="splitcontentleft"> <div class="splitcontentleft">
<p><strong>SKU:</strong> <%= @item.sku.presence || "-" %></p> <p><strong><%=t(:field_sku)%>:</strong> <%= @item.sku.presence || "-" %></p>
<p><strong>Type:</strong> <%= @item.type.presence || "-" %></p> <p><strong><%=t(:label_type)%>:</strong> <%= @item.type.presence || "-" %></p>
<p><strong>Unit Price:</strong> <%= number_to_currency(@item.unit_price) %></p> <p><strong><%=t(:field_unit_price)%>:</strong> <%= number_to_currency(@item.unit_price) %></p>
</div> </div>
<div class="splitcontentleft"> <div class="splitcontentleft">
<p><strong>Account:</strong> <%= @item.account&.name || "-" %></p> <p><strong><%=t(:label_account)%>:</strong> <%= @item.account&.name || "-" %></p>
<p> <p>
<strong>Taxable:</strong> <strong><%=t(:field_taxable)%>:</strong>
<% if @item.taxable %> <% if @item.taxable %>
<span class="icon icon-ok" style="color: green;">Yes</span> <span class="icon icon-ok" style="color: green;"><%=t(:yes)%></span>
<% else %> <% else %>
<span class="icon icon-not-ok" style="color: #999;">No</span> <span class="icon icon-not-ok" style="color: #999;"><%=t(:no)%></span>
<% end %> <% end %>
</p> </p>
<p> <p>
<strong>Active:</strong> <strong><%=t(:label_active)%>:</strong>
<% if @item.active %> <% if @item.active %>
<span class="icon icon-ok" style="color: green;">Yes</span> <span class="icon icon-ok" style="color: green;"><%=t(:yes)%></span>
<% else %> <% else %>
<span class="icon icon-not-ok" style="color: #999;">No</span> <span class="icon icon-not-ok" style="color: #999;"><%=t(:no)%></span>
<% end %> <% end %>
</p> </p>
</div> </div>
@@ -36,7 +36,7 @@
<hr /> <hr />
<p><strong>Description:</strong></p> <p><strong><%=t(:label_description)%>:</strong></p>
<div class="wiki" style="padding-left: 20px;"> <div class="wiki" style="padding-left: 20px;">
<%= @item.description.presence || "<em>No description provided</em>".html_safe %> <%= @item.description.presence || "<em>No description provided</em>".html_safe %>
</div> </div>

View File

@@ -16,21 +16,28 @@ en:
field_taxable: "Taxable" field_taxable: "Taxable"
field_unit_price: "Unit Price" field_unit_price: "Unit Price"
label_active: "Activie"
label_account: "Account" label_account: "Account"
label_accounts: "Accounts" label_accounts: "Accounts"
label_back: "Back"
label_account_count: "Number of Accounts:" label_account_count: "Number of Accounts:"
label_default_account: "Default Item Income Account" label_default_account: "Default Item Income Account"
label_description: "Description" label_description: "Description"
label_edit_item: "Edit Item"
label_item: "Item" label_item: "Item"
label_item_count: "Item Count:" label_item_count: "Item Count:"
label_items: "Items" label_items: "Items"
label_line_items: "Line Items" label_line_items: "Line Items"
label_price: "Unit Price" label_price: "Unit Price"
label_item_new: "New Item"
label_no: "No"
label_qty: "Quantity" label_qty: "Quantity"
label_remove: "Remove" label_remove: "Remove"
label_sync_now_accounts: "Sync Accounts" label_sync_now_accounts: "Sync Accounts"
label_sync_now_items: "Sync Items" label_sync_now_items: "Sync Items"
label_type: "Type"
label_total: "Total" label_total: "Total"
label_yes: "Yes"
notice_added_from: "Added from issue #" notice_added_from: "Added from issue #"

10
init.rb
View File

@@ -14,7 +14,7 @@ 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.9' version '2026.3.10'
url 'https://github.com/rickbarrette/redmine_qbo_lineitems' 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'
@@ -31,6 +31,14 @@ Redmine::Plugin.register :redmine_qbo_lineitems do
Issue.safe_attributes :line_items_attributes Issue.safe_attributes :line_items_attributes
end end
# Administration menu extension
Redmine::MenuManager.map :admin_menu do |menu|
menu.push :redmine_qbo_lineitems, { controller: 'items', action: 'index' },
icon: 'list',
caption: :label_items,
html: { class: 'icon icon-list' }
end
# Dynamically load all Hooks & Patches recursively # Dynamically load all Hooks & Patches recursively
base_dir = File.join(File.dirname(__FILE__), 'lib') base_dir = File.join(File.dirname(__FILE__), 'lib')