mirror of
https://github.com/rickbarrette/redmine_qbo.git
synced 2026-02-13 09:13:58 -05:00
Compare commits
13 Commits
2026.2.3
...
6fd355d8cc
| Author | SHA1 | Date | |
|---|---|---|---|
| 6fd355d8cc | |||
| e6b57392d1 | |||
| 331c1eabeb | |||
| 167385bb99 | |||
| 11b9876d4f | |||
| 9cf72821b0 | |||
| 57adcce431 | |||
| 7fdb15f7e8 | |||
| 6e11e05a24 | |||
| a6751d3f41 | |||
| 8944e92ffc | |||
| f0c0a42c96 | |||
| a4b51457bb |
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
<% estimates.sort.reverse.each do |estimate| %>
|
<% estimates.sort.reverse.each do |estimate| %>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<%= check_box_tag "estimate_ids[]", estimate.id, false, onchange: "updateLink()", class: "estimate-checkbox" %>
|
<%= check_box_tag "estimate_ids[]", estimate.id, false, onchange: "updateLink()", data: { url: estimate_path(estimate), text: "Estimate ##{estimate.to_s}" }, class: "estimate-checkbox appointment" %>
|
||||||
<b><%= link_to "##{estimate.doc_number}", estimate_path(estimate), target: :_blank %></b> <%= estimate.txn_date %>
|
<b><%= link_to "##{estimate.doc_number}", estimate_path(estimate), target: :_blank %></b> <%= estimate.txn_date %>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
|
|
||||||
<% invoices.sort.reverse.each do |invoice| %>
|
<% invoices.sort.reverse.each do |invoice| %>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<%= check_box_tag "invoice_ids[]", invoice.id, false, onchange: "updateLink()", class: "invoice-checkbox" if invoices.count > 1 %>
|
<%= check_box_tag "invoice_ids[]", invoice.id, false, onchange: "updateLink()", data: { url: invoice_path(invoice), text: "Invoice ##{invoice.to_s}" }, class: "invoice-checkbox appointment" if invoices.count > 1 %>
|
||||||
<b><%= link_to "##{invoice.doc_number}", invoice_path(invoice), target: :_blank %></b> <%= invoice.txn_date %>
|
<b><%= link_to "##{invoice.doc_number}", invoice_path(invoice), target: :_blank %></b> <%= invoice.txn_date %>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|||||||
@@ -1,31 +1,22 @@
|
|||||||
function updateLink() {
|
function updateLink() {
|
||||||
|
console.log("updateLink called");
|
||||||
const linkElement = document.getElementById("appointment_link");
|
const linkElement = document.getElementById("appointment_link");
|
||||||
const regex = /((?:<br\/>|%3Cbr\/?%3E))([\s\S]*?)(&dates)/gi;
|
const regex = /((?:<br\/>|%3Cbr\/?%3E))([\s\S]*?)(&dates)/gi;
|
||||||
linkElement.href = linkElement.href.replace(regex, `$1${getSelectedDocs()}$3`);
|
linkElement.href = linkElement.href.replace(regex, `$1${getSelectedDocs()}$3`);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSelectedDocs() {
|
function getSelectedDocs() {
|
||||||
const invoices = document.querySelectorAll('.invoice-checkbox');
|
const appointent_extras = document.querySelectorAll('.appointment');
|
||||||
const estimates = document.querySelectorAll('.estimate-checkbox');
|
|
||||||
|
|
||||||
const invoiceIds = Array.from(invoices)
|
|
||||||
.filter(checkbox => checkbox.checked)
|
|
||||||
.map(checkbox => checkbox.value);
|
|
||||||
|
|
||||||
const estimateIds = Array.from(estimates)
|
|
||||||
.filter(checkbox => checkbox.checked)
|
|
||||||
.map(checkbox => checkbox.value);
|
|
||||||
|
|
||||||
let output = '';
|
let output = '';
|
||||||
|
for (const item of appointent_extras) {
|
||||||
for (const value of invoiceIds) {
|
if (item.checked) {
|
||||||
output += `%0A<a href="${window.location.origin}/invoices/${value}">Invoice:%20${value}</a>%0A`;
|
console.log(`Checked item: ${item.dataset.text} with URL: ${item.dataset.url}`);
|
||||||
}
|
output += `%0A`+ encodeURIComponent(`<a href="${window.location.origin}${item.dataset.url}">${item.dataset.text}</a>`) +`%0A`;
|
||||||
|
}
|
||||||
for (const value of estimateIds) {
|
|
||||||
output += `%0A<a href="${window.location.origin}/estimates/${value}">Estimate:%20${value}</a>%0A`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// You can return the array or use it as needed
|
// You can return the array or use it as needed
|
||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
|||||||
2
init.rb
2
init.rb
@@ -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.2.3'
|
version '2026.2.7'
|
||||||
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'
|
||||||
|
|||||||
@@ -14,23 +14,15 @@ module RedmineQbo
|
|||||||
|
|
||||||
include IssuesHelper
|
include IssuesHelper
|
||||||
|
|
||||||
# Check the new issue form for a valid project.
|
|
||||||
# This is added to help prevent 422 unprocessable entity errors when creating an issue
|
|
||||||
# See https://github.com/redmine/redmine/blob/84483d63828d0cb2efbf5bd786a2f0d22e34c93d/app/controllers/issues_controller.rb#L179
|
|
||||||
def controller_issues_new_before_save(context={})
|
|
||||||
if context[:issue].project.nil?
|
|
||||||
context[:issue].project = projects_for_select(context[:issue]).first
|
|
||||||
context[:controller].flash[:error] = I18n.t(:notice_error_project_nil) + context[:issue].project.to_s
|
|
||||||
end
|
|
||||||
|
|
||||||
return context
|
|
||||||
end
|
|
||||||
|
|
||||||
# Edit Issue Form
|
# Edit Issue Form
|
||||||
# Here we build the required form components before passing them to a partial view formatting.
|
# Here we build the required form components before passing them to a partial view formatting.
|
||||||
def view_issues_form_details_bottom(context={})
|
def view_issues_form_details_bottom(context={})
|
||||||
|
Rails.logger.debug "RedmineQbo::Hooks::IssuesHookListener.view_issues_form_details_bottom: Building form components for quickbooks customer, estimate, and invoice data"
|
||||||
f = context[:form]
|
f = context[:form]
|
||||||
issue = context[:issue]
|
issue = context[:issue]
|
||||||
|
project = context[:project]
|
||||||
|
Rails.logger.debug issue.inspect
|
||||||
|
Rails.logger.debug project.inspect
|
||||||
|
|
||||||
# Customer Name Text Box with database backed autocomplete
|
# Customer Name Text Box with database backed autocomplete
|
||||||
# onchange event will update the hidden customer_id field
|
# onchange event will update the hidden customer_id field
|
||||||
@@ -42,11 +34,23 @@ module RedmineQbo
|
|||||||
value: '#issue_customer'
|
value: '#issue_customer'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# We need to handle 3 cases for the onchange event of the customer name field:
|
||||||
|
# 1. New issue Withough project: /issues/new.js
|
||||||
|
# 2. New issue With project: /projects/rmt/issues/new.js
|
||||||
|
# 3. Existing issue: /issues/<ID>/edit.js
|
||||||
|
# The built in helper update_issue_form_path requires a project object to determine the correct path for new vs existing issues,
|
||||||
|
# but it doesn't work for issue.project when creating new issues not in a project i.e. http://redmine.domain.com/issues/new .
|
||||||
|
# So we need to figure out how to get a the @project from the controller calling the hook.
|
||||||
|
#
|
||||||
|
# If this is not handled correctly, it leads to a 422 error when creating a new issue and selecting a customer.
|
||||||
|
js_path = "updateIssueFrom('#{escape_javascript update_issue_form_path(project, issue)}', this)"
|
||||||
|
Rails.logger.debug js_path
|
||||||
|
|
||||||
# This hidden field is used for the customer ID for the issue
|
# This hidden field is used for the customer ID for the issue
|
||||||
# the onchange event will reload the issue form via ajax to update the available estimates
|
# the onchange event will reload the issue form via ajax to update the available estimates
|
||||||
customer_id = f.hidden_field :customer_id,
|
customer_id = f.hidden_field :customer_id,
|
||||||
id: "issue_customer_id",
|
id: "issue_customer_id",
|
||||||
onchange: "updateIssueFrom('#{escape_javascript update_issue_form_path(issue.project, issue)}', this)".html_safe
|
onchange: js_path.html_safe
|
||||||
|
|
||||||
# Generate the drop down list of quickbooks estimates owned by the selected customer
|
# Generate the drop down list of quickbooks estimates owned by the selected customer
|
||||||
select_estimate = f.select :estimate_id,
|
select_estimate = f.select :estimate_id,
|
||||||
|
|||||||
@@ -14,27 +14,6 @@ module RedmineQbo
|
|||||||
module IssuesControllerPatch
|
module IssuesControllerPatch
|
||||||
|
|
||||||
module Helper
|
module Helper
|
||||||
|
|
||||||
# Check the new issue form for a valid project.
|
|
||||||
# This is added to help prevent 422 unprocessable entity errors when creating an issue
|
|
||||||
# See https://github.com/redmine/redmine/blob/84483d63828d0cb2efbf5bd786a2f0d22e34c93d/app/controllers/issues_controller.rb#L179
|
|
||||||
def controller_issues_new_before_save(context={})
|
|
||||||
if context[:issue].project.nil?
|
|
||||||
context[:issue].project = projects_for_select(context[:issue]).first
|
|
||||||
Rails.logger.error I18n.t(:notice_error_project_nil) + context[:issue].project.to_s
|
|
||||||
context[:controller].flash[:error] = I18n.t(:notice_error_project_nil) + context[:issue].project.to_s
|
|
||||||
end
|
|
||||||
|
|
||||||
if context[:issue].tracker.nil?
|
|
||||||
context[:issue].tracker = trackers_for_select(context[:issue]).first
|
|
||||||
context[:issue].tracker = Tracker.first if context[:issue].tracker.nil?
|
|
||||||
Rails.logger.error I18n.t(:notice_error_tracker_nil) + context[:issue].tracker.to_s
|
|
||||||
context[:controller].flash[:error] = I18n.t(:notice_error_tracker_nil) + context[:issue].tracker.to_s
|
|
||||||
end
|
|
||||||
|
|
||||||
return context
|
|
||||||
end
|
|
||||||
|
|
||||||
def watcher_link(issue, user)
|
def watcher_link(issue, user)
|
||||||
link = ''
|
link = ''
|
||||||
link = link_to(I18n.t(:label_bill_time), bill_path( issue.id ), method: :get, class: 'icon icon-email-add') if user.admin?
|
link = link_to(I18n.t(:label_bill_time), bill_path( issue.id ), method: :get, class: 'icon icon-email-add') if user.admin?
|
||||||
|
|||||||
Reference in New Issue
Block a user