mirror of
https://github.com/rickbarrette/redmine_qbo.git
synced 2026-02-13 17:13:59 -05:00
Merge branch 'redmine-6' into HEAD
This commit is contained in:
4
Gemfile
4
Gemfile
@@ -1,11 +1,13 @@
|
|||||||
source 'https://rubygems.org'
|
source 'https://rubygems.org'
|
||||||
|
|
||||||
gem 'quickbooks-ruby'
|
gem 'quickbooks-ruby'
|
||||||
gem 'oauth2', '1.4.7'
|
gem 'oauth2'
|
||||||
gem 'roxml'
|
gem 'roxml'
|
||||||
gem 'will_paginate'
|
gem 'will_paginate'
|
||||||
gem 'rails-jquery-autocomplete'
|
gem 'rails-jquery-autocomplete'
|
||||||
gem 'jquery-ui-rails'
|
gem 'jquery-ui-rails'
|
||||||
|
gem 'rexml'
|
||||||
|
gem 'combine_pdf'
|
||||||
|
|
||||||
group :assets do
|
group :assets do
|
||||||
gem 'coffee-rails'
|
gem 'coffee-rails'
|
||||||
|
|||||||
2
LICENSE
2
LICENSE
@@ -1,6 +1,6 @@
|
|||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2016 - 2022 Rick Barrette
|
Copyright (c) 2016 - 2026 Rick Barrette
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|||||||
129
README.md
129
README.md
@@ -1,77 +1,108 @@
|
|||||||
# Redmine Quickbooks Online
|
# Redmine QuickBooks Online
|
||||||
|
|
||||||
A plugin for Redmine to connect to Quickbooks Online
|
A plugin for Redmine to connect to QuickBooks Online.
|
||||||
|
|
||||||
The goal of this project is to allow Redmine to connect with Quickbooks Online to create Time Activity Entries for billable hours loged when an Issue is closed.
|
The goal of this project is to allow Redmine to connect with QuickBooks Online to create Time Activity Entries for billable hours logged when an Issue is closed.
|
||||||
|
|
||||||
#### Disclaimer
|
## Disclaimer
|
||||||
|
|
||||||
Note: Although the core functionality is complete, this project is still under development & the master branch may be unstable. Tags should be stable and are recommended
|
**Note:** Although the core functionality is complete, this project is still under development and the master branch may be unstable. Tags should be stable and are recommended.
|
||||||
|
|
||||||
Use tags Version 1.0.0+ for Redmine 4+ and Version 0.8.1 for Redine 3
|
## Compatibility
|
||||||
|
|
||||||
#### Features
|
| Plugin Version | Redmine Version |
|
||||||
* Issues can be assigned to a Customer via drop down in the edit Issue form
|
| :--- | :--- |
|
||||||
- Once a customer is attached to an Issue, you can attach an Estimate to the issue via a drop down menu
|
| Version 2026.1.0+ | Redmine 6.1 |
|
||||||
* Employee is assigned to a user via a drop down in the user admistration page.
|
| Version 2.0.0+ | Redmine 5 |
|
||||||
* IF an Issue has been assined a Customer when an Issue is closed the following will happen:
|
| Version 1.0.0+ | Redmine 4 |
|
||||||
- A new Time Activity will be billed agaist the Customer assinged to the issue for each Redmine Time Entery.
|
| Version 0.8.1 | Redmine 3 |
|
||||||
+ Time Entries will be totalled up by Activity name. This will allow billing for diffrent activities without having to create seperate Issues.
|
|
||||||
+ The Time Activity names are used to dynamically lookup Items in Quickbooks.
|
## Features
|
||||||
+ IF there isn't any Items that match the Activity name it will be skipped, and will not be billed to the Customer
|
|
||||||
- Labor Rates are set by corresponding the Item in Quickbooks
|
* **Customer Assignment:** Issues can be assigned to a Customer via a dropdown in the edit Issue form.
|
||||||
* Customers Can be created via the New Customer Page
|
* Once a customer is attached to an Issue, you can attach an Estimate to the issue via a dropdown menu.
|
||||||
- Customers can be searched by name or phone number
|
* **Employee Mapping:** An Employee is assigned to a Redmine User via a dropdown in the User Administration page.
|
||||||
- Basic information for the Customer can be viewed/edit via the Customer page
|
* **Automatic Billing:** If an Issue has been assigned a Customer, the following happens when the Issue is closed:
|
||||||
* Webhook Support
|
* A new Time Activity will be billed against the Customer assigned to the issue for each Redmine Time Entry.
|
||||||
- Invoices are automaticly attached to an Issue if a line item has a hashtag number in a Line Item
|
* Time Entries will be totalled up by Activity name. This allows billing for different activities without having to create separate Issues.
|
||||||
+ Invoice Custom Fields are matched Issue Custom Fileds and are automaticly updated in Quickbooks. For example, this is usefull for extracting the Mileage In / Out from the Issue and updating the Invoice with the information.
|
* The Time Activity names are used to dynamically lookup Items in QuickBooks.
|
||||||
- Customers are automaticly updated in local database
|
* If there are no Items that match the Activity name, it will be skipped and will not be billed to the Customer.
|
||||||
|
* Labor Rates are set by the corresponding Item in QuickBooks.
|
||||||
|
* **Customer Management:** Customers can be created via the New Customer Page.
|
||||||
|
* Customers can be searched by name or phone number.
|
||||||
|
* Basic information for the Customer can be viewed/edited via the Customer page.
|
||||||
|
* **Webhook Support:**
|
||||||
|
* **Invoices:** Automatically attached to an Issue if a line item contains a hashtag number (e.g., `#123`).
|
||||||
|
* **Custom Fields:** Invoice Custom Fields are matched to Issue Custom Fields and are automatically updated in QuickBooks. (Useful for extracting Mileage In/Out from the Issue to update the Invoice).
|
||||||
|
* **Sync:** Customers are automatically updated in the local database.
|
||||||
|
|
||||||
## Prerequisites
|
## Prerequisites
|
||||||
|
|
||||||
* Sign up to become a developer for Intuit https://developer.intuit.com/
|
* Sign up to become a developer for Intuit: https://developer.intuit.com/
|
||||||
* Create your own aplication to obtain your API keys
|
* Create your own application to obtain your API keys.
|
||||||
* Set up webhook service to https://redmine.yourdomain.com/qbo/webhook
|
* Set up the webhook service to `https://redmine.yourdomain.com/qbo/webhook`
|
||||||
|
|
||||||
## The Install
|
## Installation
|
||||||
|
|
||||||
1. To install, clone this repo into your plugin folder & checkout a tagged version
|
1. **Clone the plugin:**
|
||||||
|
Clone this repo into your plugin folder and checkout a tagged version.
|
||||||
|
```bash
|
||||||
|
cd path/to/redmine/plugins
|
||||||
|
git clone git@github.com:rickbarrette/redmine_qbo.git
|
||||||
|
cd redmine_qbo
|
||||||
|
git checkout <tag>
|
||||||
|
```
|
||||||
|
|
||||||
`git clone git@github.com:rickbarrette/redmine_qbo.git`
|
2. **Install dependencies:** *Crucial for Redmine 6 / Rails 7 compatibility.*
|
||||||
|
|
||||||
then
|
Bash
|
||||||
|
|
||||||
`git checkout <tag>`
|
```
|
||||||
|
bundle install
|
||||||
|
```
|
||||||
|
|
||||||
2. Migrate your database
|
3. **Migrate your database:**
|
||||||
|
|
||||||
`rake redmine:plugins:migrate RAILS_ENV=production`
|
Bash
|
||||||
|
|
||||||
3. Navigate to the plugin configuration page and suppy your own OAuth key & secret.
|
```
|
||||||
|
bundle exec rake redmine:plugins:migrate RAILS_ENV=production
|
||||||
|
```
|
||||||
|
|
||||||
4. After saving your key & secret, you need to click on the Authenticate link on the plugin configuration page to authenticate with QBO.
|
4. **Restart Redmine:** You must restart your Redmine server instance for the plugin and hooks to load.
|
||||||
|
|
||||||
|
5. **Configuration:**
|
||||||
|
|
||||||
|
* Navigate to the plugin configuration page (`Administration > Plugins > Configure`).
|
||||||
|
|
||||||
|
* Supply your own OAuth Key & Secret.
|
||||||
|
|
||||||
|
* After saving the Key & Secret, click the **Authenticate** link on the configuration page to connect to QBO.
|
||||||
|
|
||||||
|
6. **User Mapping:**
|
||||||
|
|
||||||
|
* Assign an Employee to each of your users via the **User Administration Page**.
|
||||||
|
|
||||||
5. Assign an Employee to each of your users via the User Administration Page
|
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
To enable automatic Time Activity entries for an Issue , you need only to assign a Customer to an Issue via drop downs in the issue creation/update form.
|
To enable automatic Time Activity entries for an Issue, you simply need to assign a Customer to an Issue via the dropdowns in the issue creation/update form.
|
||||||
|
|
||||||
Note: After the inital synchronization, this plugin will recieve push notifications via Intuit's webhook service.
|
**Note:** After the initial synchronization, this plugin will receive push notifications via Intuit's webhook service.
|
||||||
|
|
||||||
## TODO
|
## TODO
|
||||||
* Add Setting for Sandbox Mode
|
|
||||||
* MORE Stuff as I make it up...
|
* MORE Stuff as I make it up...
|
||||||
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
The MIT License (MIT)
|
> The MIT License (MIT)
|
||||||
|
>
|
||||||
Copyright (c) 2016 - 2022 rick barrette
|
> Copyright (c) 2016 - 2026 Rick Barrette
|
||||||
|
>
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
>
|
||||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||||
|
>
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
#The MIT License (MIT)
|
#The MIT License (MIT)
|
||||||
#
|
#
|
||||||
#Copyright (c) 2022 rick barrette
|
#Copyright (c) 2016 - 2026 rick barrette
|
||||||
#
|
#
|
||||||
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
#
|
#
|
||||||
@@ -10,7 +10,6 @@
|
|||||||
|
|
||||||
# This controller class will handle map management
|
# This controller class will handle map management
|
||||||
class CustomersController < ApplicationController
|
class CustomersController < ApplicationController
|
||||||
unloadable
|
|
||||||
|
|
||||||
include AuthHelper
|
include AuthHelper
|
||||||
helper :issues
|
helper :issues
|
||||||
@@ -36,7 +35,7 @@ class CustomersController < ApplicationController
|
|||||||
autocomplete :customer, :name, :full => true, :extra_data => [:id]
|
autocomplete :customer, :name, :full => true, :extra_data => [:id]
|
||||||
|
|
||||||
def allowed_params
|
def allowed_params
|
||||||
params.require(:customer).permit(:name, :email, :primary_phone, :mobile_phone, :phone_number)
|
params.require(:customer).permit(:name, :email, :primary_phone, :mobile_phone, :phone_number, :notes)
|
||||||
end
|
end
|
||||||
|
|
||||||
# getter method for a customer's invoices
|
# getter method for a customer's invoices
|
||||||
@@ -108,7 +107,7 @@ class CustomersController < ApplicationController
|
|||||||
def update
|
def update
|
||||||
begin
|
begin
|
||||||
@customer = Customer.find_by_id(params[:id])
|
@customer = Customer.find_by_id(params[:id])
|
||||||
if @customer.update_attributes(allowed_params)
|
if @customer.update(allowed_params)
|
||||||
flash[:notice] = "Customer updated"
|
flash[:notice] = "Customer updated"
|
||||||
redirect_to @customer
|
redirect_to @customer
|
||||||
else
|
else
|
||||||
@@ -135,7 +134,7 @@ class CustomersController < ApplicationController
|
|||||||
def share
|
def share
|
||||||
|
|
||||||
Thread.new do
|
Thread.new do
|
||||||
logger.debug "Removing expired customer tokens"
|
logger.info "Removing expired customer tokens"
|
||||||
CustomerToken.remove_expired_tokens
|
CustomerToken.remove_expired_tokens
|
||||||
ActiveRecord::Base.connection.close
|
ActiveRecord::Base.connection.close
|
||||||
end
|
end
|
||||||
@@ -211,14 +210,14 @@ class CustomersController < ApplicationController
|
|||||||
# format a quickbooks address to a human readable string
|
# format a quickbooks address to a human readable string
|
||||||
def address_to_s (address)
|
def address_to_s (address)
|
||||||
return if address.nil?
|
return if address.nil?
|
||||||
string = address.line1
|
string = address.line1 if address.line1
|
||||||
string << "\n" + address.line2 if address.line2
|
string << "\n" + address.line2 if address.line2
|
||||||
string << "\n" + address.line3 if address.line3
|
string << "\n" + address.line3 if address.line3
|
||||||
string << "\n" + address.line4 if address.line4
|
string << "\n" + address.line4 if address.line4
|
||||||
string << "\n" + address.line5 if address.line5
|
string << "\n" + address.line5 if address.line5
|
||||||
string << " " + address.city
|
string << " " + address.city if address.city
|
||||||
string << ", " + address.country_sub_division_code
|
string << ", " + address.country_sub_division_code if address.country_sub_division_code
|
||||||
string << " " + address.postal_code
|
string << " " + address.postal_code if address.postal_code
|
||||||
return string
|
return string
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#The MIT License (MIT)
|
#The MIT License (MIT)
|
||||||
#
|
#
|
||||||
#Copyright (c) 2022 rick barrette
|
#Copyright (c) 2016 - 2026 rick barrette
|
||||||
#
|
#
|
||||||
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
#
|
#
|
||||||
@@ -8,7 +8,6 @@
|
|||||||
#
|
#
|
||||||
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
class EstimateController < ApplicationController
|
class EstimateController < ApplicationController
|
||||||
unloadable
|
|
||||||
|
|
||||||
include AuthHelper
|
include AuthHelper
|
||||||
|
|
||||||
@@ -16,6 +15,15 @@ class EstimateController < ApplicationController
|
|||||||
skip_before_action :verify_authenticity_token, :check_if_login_required, :unless => proc {|c| session[:token].nil? }
|
skip_before_action :verify_authenticity_token, :check_if_login_required, :unless => proc {|c| session[:token].nil? }
|
||||||
|
|
||||||
def get_estimate
|
def get_estimate
|
||||||
|
# Force sync for estimate by doc number if not found
|
||||||
|
if Estimate.find_by_doc_number(params[:search]).nil?
|
||||||
|
begin
|
||||||
|
Estimate.sync_by_doc_number(params[:search]) if params[:search]
|
||||||
|
rescue
|
||||||
|
logger.info "Estimate.find_by_doc_number failed"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
estimate = Estimate.find_by_id(params[:id]) if params[:id]
|
estimate = Estimate.find_by_id(params[:id]) if params[:id]
|
||||||
estimate = Estimate.find_by_doc_number(params[:search]) if params[:search]
|
estimate = Estimate.find_by_doc_number(params[:search]) if params[:search]
|
||||||
return estimate
|
return estimate
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#The MIT License (MIT)
|
#The MIT License (MIT)
|
||||||
#
|
#
|
||||||
#Copyright (c) 2022 rick barrette
|
#Copyright (c) 2016 - 2026 rick barrette
|
||||||
#
|
#
|
||||||
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
#
|
#
|
||||||
@@ -8,9 +8,9 @@
|
|||||||
#
|
#
|
||||||
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
class InvoiceController < ApplicationController
|
class InvoiceController < ApplicationController
|
||||||
unloadable
|
|
||||||
|
|
||||||
include AuthHelper
|
include AuthHelper
|
||||||
|
require 'combine_pdf'
|
||||||
|
|
||||||
before_action :require_user, :unless => proc {|c| session[:token].nil? }
|
before_action :require_user, :unless => proc {|c| session[:token].nil? }
|
||||||
skip_before_action :verify_authenticity_token, :check_if_login_required, :unless => proc {|c| session[:token].nil? }
|
skip_before_action :verify_authenticity_token, :check_if_login_required, :unless => proc {|c| session[:token].nil? }
|
||||||
@@ -19,11 +19,34 @@ class InvoiceController < ApplicationController
|
|||||||
# Downloads and forwards the invoice pdf
|
# Downloads and forwards the invoice pdf
|
||||||
#
|
#
|
||||||
def show
|
def show
|
||||||
|
logger.info("Processing request for URL: #{request.original_url}")
|
||||||
begin
|
begin
|
||||||
base = Invoice.get_base
|
qbo = Qbo.first
|
||||||
invoice = base.fetch_by_id(params[:id])
|
qbo.perform_authenticated_request do |access_token|
|
||||||
@pdf = base.pdf(invoice)
|
service = Quickbooks::Service::Invoice.new(:company_id => qbo.realm_id, :access_token => access_token)
|
||||||
send_data @pdf, filename: "invoice #{invoice.doc_number}.pdf", :disposition => 'inline', :type => "application/pdf"
|
|
||||||
|
# If multiple id's then pull each pdf & combine them
|
||||||
|
if params[:invoice_ids]
|
||||||
|
logger.info("Grabbing pdfs for " + params[:invoice_ids].join(', '))
|
||||||
|
ref = ""
|
||||||
|
params[:invoice_ids].each do |i|
|
||||||
|
logger.info("processing " + i)
|
||||||
|
invoice = service.fetch_by_id(i)
|
||||||
|
ref += " #{invoice.doc_number}"
|
||||||
|
@pdf << CombinePDF.parse(service.pdf(invoice)) unless @pdf.nil?
|
||||||
|
if @pdf.nil?
|
||||||
|
@pdf = CombinePDF.parse(service.pdf(invoice))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
@pdf = @pdf.to_pdf
|
||||||
|
else
|
||||||
|
invoice = service.fetch_by_id(params[:id])
|
||||||
|
@pdf = service.pdf(invoice)
|
||||||
|
ref = invoice.doc_number
|
||||||
|
end
|
||||||
|
|
||||||
|
send_data @pdf, filename: "invoice #{ref}.pdf", :disposition => 'inline', :type => "application/pdf"
|
||||||
|
end
|
||||||
rescue
|
rescue
|
||||||
redirect_to :back, :flash => { :error => "Invoice not found" }
|
redirect_to :back, :flash => { :error => "Invoice not found" }
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#The MIT License (MIT)
|
#The MIT License (MIT)
|
||||||
#
|
#
|
||||||
#Copyright (c) 2022 rick barrette
|
#Copyright (c) 2016 - 2026 rick barrette
|
||||||
#
|
#
|
||||||
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
#
|
#
|
||||||
@@ -9,7 +9,6 @@
|
|||||||
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
class QboController < ApplicationController
|
class QboController < ApplicationController
|
||||||
unloadable
|
|
||||||
|
|
||||||
require 'openssl'
|
require 'openssl'
|
||||||
|
|
||||||
@@ -26,9 +25,10 @@ class QboController < ApplicationController
|
|||||||
# Called when the user requests that Redmine to connect to QBO
|
# Called when the user requests that Redmine to connect to QBO
|
||||||
#
|
#
|
||||||
def authenticate
|
def authenticate
|
||||||
oauth2_client = Qbo.get_client
|
redirect_uri = "#{Setting.protocol}://#{Setting.host_name + qbo_oauth_callback_path}"
|
||||||
callback = Setting.host_name + "/qbo/oauth_callback/"
|
logger.info "redirect_uri: #{redirect_uri}"
|
||||||
grant_url = oauth2_client.auth_code.authorize_url(redirect_uri: callback, response_type: "code", state: SecureRandom.hex(12), scope: "com.intuit.quickbooks.accounting")
|
oauth2_client = Qbo.construct_oauth2_client
|
||||||
|
grant_url = oauth2_client.auth_code.authorize_url(redirect_uri: redirect_uri, response_type: "code", state: SecureRandom.hex(12), scope: "com.intuit.quickbooks.accounting")
|
||||||
redirect_to grant_url
|
redirect_to grant_url
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -37,9 +37,9 @@ class QboController < ApplicationController
|
|||||||
#
|
#
|
||||||
def oauth_callback
|
def oauth_callback
|
||||||
if params[:state].present?
|
if params[:state].present?
|
||||||
oauth2_client = Qbo.get_client
|
oauth2_client = Qbo.construct_oauth2_client
|
||||||
# use the state value to retrieve from your backend any information you need to identify the customer in your system
|
# use the state value to retrieve from your backend any information you need to identify the customer in your system
|
||||||
redirect_uri = Setting.host_name + "/qbo/oauth_callback/"
|
redirect_uri = "#{Setting.protocol}://#{Setting.host_name + qbo_oauth_callback_path}"
|
||||||
if resp = oauth2_client.auth_code.get_token(params[:code], redirect_uri: redirect_uri)
|
if resp = oauth2_client.auth_code.get_token(params[:code], redirect_uri: redirect_uri)
|
||||||
|
|
||||||
# Remove the last authentication information
|
# Remove the last authentication information
|
||||||
@@ -47,17 +47,13 @@ class QboController < ApplicationController
|
|||||||
|
|
||||||
# Save the authentication information
|
# Save the authentication information
|
||||||
qbo = Qbo.new
|
qbo = Qbo.new
|
||||||
qbo.company_id = params[:realmId]
|
qbo.update(oauth2_access_token: resp.token, oauth2_refresh_token: resp.refresh_token, realm_id: params[:realmId])
|
||||||
|
qbo.refresh_token!
|
||||||
# Generate Access Token & Serialize it into the database
|
|
||||||
access_token = OAuth2::AccessToken.new(oauth2_client, resp.token, refresh_token: resp.refresh_token)
|
|
||||||
qbo.token = access_token.to_hash
|
|
||||||
qbo.expire = 1.hour.from_now.utc
|
|
||||||
|
|
||||||
if qbo.save!
|
if qbo.save!
|
||||||
redirect_to qbo_sync_path, :flash => { :notice => "Successfully connected to Quickbooks" }
|
redirect_to qbo_sync_path, :flash => { :notice => I18n.t(:label_connected) }
|
||||||
else
|
else
|
||||||
redirect_to plugin_settings_path(:redmine_qbo), :flash => { :error => "Error" }
|
redirect_to plugin_settings_path(:redmine_qbo), :flash => { :error => I18n.t(:label_error) }
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
@@ -69,9 +65,9 @@ class QboController < ApplicationController
|
|||||||
i = Issue.find_by_id params[:id]
|
i = Issue.find_by_id params[:id]
|
||||||
if i.customer
|
if i.customer
|
||||||
i.bill_time
|
i.bill_time
|
||||||
redirect_to i, :flash => { :notice => "Successfully Billed #{i.customer.name}" }
|
redirect_to i, :flash => { :notice => I18n.t(:label_billed_success) + i.customer.name }
|
||||||
else
|
else
|
||||||
redirect_to i, :flash => { :error => "Cannot bill without a customer assigned" }
|
redirect_to i, :flash => { :error => I18n.t(:label_billing_error) }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -88,6 +84,7 @@ class QboController < ApplicationController
|
|||||||
|
|
||||||
# proceed if the request is good
|
# proceed if the request is good
|
||||||
if hash.eql? signature
|
if hash.eql? signature
|
||||||
|
Thread.new do
|
||||||
if request.headers['content-type'] == 'application/json'
|
if request.headers['content-type'] == 'application/json'
|
||||||
data = JSON.parse(data)
|
data = JSON.parse(data)
|
||||||
else
|
else
|
||||||
@@ -100,7 +97,7 @@ class QboController < ApplicationController
|
|||||||
id = entity['id'].to_i
|
id = entity['id'].to_i
|
||||||
name = entity['name']
|
name = entity['name']
|
||||||
|
|
||||||
logger.debug "Casting #{name.constantize} to obj"
|
logger.info "Casting #{name.constantize} to obj"
|
||||||
|
|
||||||
# Magicly initialize the correct class
|
# Magicly initialize the correct class
|
||||||
obj = name.constantize
|
obj = name.constantize
|
||||||
@@ -125,9 +122,10 @@ class QboController < ApplicationController
|
|||||||
|
|
||||||
# Record that last time we updated
|
# Record that last time we updated
|
||||||
Qbo.update_time_stamp
|
Qbo.update_time_stamp
|
||||||
|
ActiveRecord::Base.connection.close
|
||||||
|
end
|
||||||
# The webhook doesn't require a response but let's make sure we don't send anything
|
# The webhook doesn't require a response but let's make sure we don't send anything
|
||||||
render :nothing => true
|
render :nothing => true, status: 200
|
||||||
else
|
else
|
||||||
render nothing: true, status: 400
|
render nothing: true, status: 400
|
||||||
end
|
end
|
||||||
@@ -145,7 +143,6 @@ class QboController < ApplicationController
|
|||||||
if Qbo.exists?
|
if Qbo.exists?
|
||||||
Customer.sync
|
Customer.sync
|
||||||
Invoice.sync
|
Invoice.sync
|
||||||
QboItem.sync
|
|
||||||
Employee.sync
|
Employee.sync
|
||||||
Estimate.sync
|
Estimate.sync
|
||||||
|
|
||||||
@@ -155,6 +152,6 @@ class QboController < ApplicationController
|
|||||||
ActiveRecord::Base.connection.close
|
ActiveRecord::Base.connection.close
|
||||||
end
|
end
|
||||||
|
|
||||||
redirect_to :home, :flash => { :notice => "Successfully synced to Quickbooks" }
|
redirect_to :home, :flash => { :notice => I18n.t(:label_syncing) }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#The MIT License (MIT)
|
#The MIT License (MIT)
|
||||||
#
|
#
|
||||||
#Copyright (c) 2017 rick barrette
|
#Copyright (c) 2016 - 2026 rick barrette
|
||||||
#
|
#
|
||||||
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
#
|
#
|
||||||
@@ -13,7 +13,7 @@ module AuthHelper
|
|||||||
def require_user
|
def require_user
|
||||||
return unless session[:token].nil?
|
return unless session[:token].nil?
|
||||||
if !User.current.logged?
|
if !User.current.logged?
|
||||||
render :file => "public/401.html.erb", :status => :unauthorized, :layout =>true
|
render_403
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -27,14 +27,14 @@ module AuthHelper
|
|||||||
|
|
||||||
def check_permission(permission)
|
def check_permission(permission)
|
||||||
if !allowed_to?(permission)
|
if !allowed_to?(permission)
|
||||||
render :file => "public/401.html.erb", :status => :unauthorized, :layout =>true
|
render_403
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
def global_check_permission(permission)
|
def global_check_permission(permission)
|
||||||
if !globaly_allowed_to?(permission)
|
if !globaly_allowed_to?(permission)
|
||||||
render :file => "public/401.html.erb", :status => :unauthorized, :layout =>true
|
render_403
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
88
app/models/concerns/quickbooks_oauth.rb
Normal file
88
app/models/concerns/quickbooks_oauth.rb
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
#The MIT License (MIT)
|
||||||
|
#
|
||||||
|
#Copyright (c) 2016 - 2026 rick barrette
|
||||||
|
#
|
||||||
|
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
#
|
||||||
|
#The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
module QuickbooksOauth
|
||||||
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
|
#== Instance Methods
|
||||||
|
|
||||||
|
def perform_authenticated_request(&block)
|
||||||
|
attempts = 0
|
||||||
|
begin
|
||||||
|
yield oauth_access_token
|
||||||
|
rescue OAuth2::Error, Quickbooks::AuthorizationFailure => ex
|
||||||
|
Rails.logger.error("QuickbooksOauth.perform: #{ex.message}")
|
||||||
|
|
||||||
|
# to prevent an infinite loop here keep a counter and bail out after N times...
|
||||||
|
attempts += 1
|
||||||
|
|
||||||
|
raise "QuickbooksOauth:ExceededAuthAttempts" if attempts >= 3
|
||||||
|
|
||||||
|
# check if its an invalid_grant first, but assume it is for now
|
||||||
|
refresh_token!
|
||||||
|
|
||||||
|
retry
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def refresh_token!
|
||||||
|
Rails.logger.info("QuickbooksOauth.refresh_token!")
|
||||||
|
t = oauth_access_token
|
||||||
|
refreshed = t.refresh!
|
||||||
|
|
||||||
|
if refreshed.params['x_refresh_token_expires_in'].to_i > 0
|
||||||
|
oauth2_refresh_token_expires_at = Time.now + refreshed.params['x_refresh_token_expires_in'].to_i.seconds
|
||||||
|
else
|
||||||
|
oauth2_refresh_token_expires_at = 100.days.from_now
|
||||||
|
end
|
||||||
|
|
||||||
|
Rails.logger.info("QuickbooksOauth.refresh_token!: #{oauth2_refresh_token_expires_at}")
|
||||||
|
|
||||||
|
update!(
|
||||||
|
oauth2_access_token: refreshed.token,
|
||||||
|
oauth2_access_token_expires_at: Time.at(refreshed.expires_at),
|
||||||
|
oauth2_refresh_token: refreshed.refresh_token,
|
||||||
|
oauth2_refresh_token_expires_at: oauth2_refresh_token_expires_at
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def oauth_client
|
||||||
|
self.class.construct_oauth2_client
|
||||||
|
end
|
||||||
|
|
||||||
|
def oauth_access_token
|
||||||
|
OAuth2::AccessToken.new(oauth_client, oauth2_access_token, refresh_token: oauth2_refresh_token)
|
||||||
|
end
|
||||||
|
|
||||||
|
def consumer
|
||||||
|
oauth_access_token
|
||||||
|
end
|
||||||
|
|
||||||
|
module ClassMethods
|
||||||
|
|
||||||
|
def construct_oauth2_client
|
||||||
|
|
||||||
|
oauth_consumer_key = Setting.plugin_redmine_qbo['settingsOAuthConsumerKey']
|
||||||
|
oauth_consumer_secret = Setting.plugin_redmine_qbo['settingsOAuthConsumerSecret']
|
||||||
|
|
||||||
|
# Are we are playing in the sandbox?
|
||||||
|
Quickbooks.sandbox_mode = Setting.plugin_redmine_qbo['sandbox'] ? true : false
|
||||||
|
logger.info "Sandbox mode: #{Quickbooks.sandbox_mode}"
|
||||||
|
|
||||||
|
options = {
|
||||||
|
site: "https://appcenter.intuit.com/connect/oauth2",
|
||||||
|
authorize_url: "https://appcenter.intuit.com/connect/oauth2",
|
||||||
|
token_url: "https://oauth.platform.intuit.com/oauth2/v1/tokens/bearer"
|
||||||
|
}
|
||||||
|
OAuth2::Client.new(oauth_consumer_key, oauth_consumer_secret, options)
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
#The MIT License (MIT)
|
#The MIT License (MIT)
|
||||||
#
|
#
|
||||||
#Copyright (c) 2022 rick barrette
|
#Copyright (c) 2016 - 2026 rick barrette
|
||||||
#
|
#
|
||||||
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
#
|
#
|
||||||
@@ -9,10 +9,8 @@
|
|||||||
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
class Customer < ActiveRecord::Base
|
class Customer < ActiveRecord::Base
|
||||||
unloadable
|
|
||||||
|
|
||||||
has_many :issues
|
has_many :issues
|
||||||
has_many :purchases
|
|
||||||
has_many :invoices
|
has_many :invoices
|
||||||
has_many :estimates
|
has_many :estimates
|
||||||
|
|
||||||
@@ -22,7 +20,7 @@ class Customer < ActiveRecord::Base
|
|||||||
|
|
||||||
# returns a human readable string
|
# returns a human readable string
|
||||||
def to_s
|
def to_s
|
||||||
return name
|
return "#{self[:name]} - #{phone_number.split(//).last(4).join unless phone_number.nil?}"
|
||||||
end
|
end
|
||||||
|
|
||||||
# Convenience Method
|
# Convenience Method
|
||||||
@@ -87,6 +85,13 @@ class Customer < ActiveRecord::Base
|
|||||||
update_mobile_phone_number
|
update_mobile_phone_number
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Convenience Method
|
||||||
|
# Sets the notes
|
||||||
|
def notes=(s)
|
||||||
|
pull unless @details
|
||||||
|
@details.notes = s
|
||||||
|
end
|
||||||
|
|
||||||
# update the localy stored phone number as a plain string with no special chars
|
# update the localy stored phone number as a plain string with no special chars
|
||||||
def update_phone_number
|
def update_phone_number
|
||||||
begin
|
begin
|
||||||
@@ -134,28 +139,28 @@ class Customer < ActiveRecord::Base
|
|||||||
# proforms a bruteforce sync operation
|
# proforms a bruteforce sync operation
|
||||||
# This needs to be simplified
|
# This needs to be simplified
|
||||||
def self.sync
|
def self.sync
|
||||||
service = Qbo.get_base(:customer)
|
|
||||||
|
|
||||||
# Sync ALL customers if the database is empty
|
# Sync ALL customers if the database is empty
|
||||||
#if count == 0
|
qbo = Qbo.first
|
||||||
customers = service.all
|
customers = qbo.perform_authenticated_request do |access_token|
|
||||||
#else
|
service = Quickbooks::Service::Customer.new(:company_id => qbo.realm_id, :access_token => access_token)
|
||||||
# last = Qbo.first.last_sync
|
service.all
|
||||||
# query = "Select Id, DisplayName From Customer"
|
|
||||||
# query << " Where Metadata.LastUpdatedTime >= '#{last.iso8601}' " if last
|
|
||||||
# customers = service.query(query)
|
|
||||||
#end
|
|
||||||
|
|
||||||
customers.each do |customer|
|
|
||||||
customer = Customer.find_or_create_by(id: customer.id)
|
|
||||||
if customer.active?
|
|
||||||
if not customer.name.eql? customer.display_name
|
|
||||||
customer.name = customer.display_name
|
|
||||||
customer.id = customer.id
|
|
||||||
customer.save_without_push
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
return unless customers
|
||||||
|
|
||||||
|
customers.each do |c|
|
||||||
|
logger.info "Processing customer #{c.id}"
|
||||||
|
customer = Customer.find_or_create_by(id: c.id)
|
||||||
|
if c.active?
|
||||||
|
#if not customer.name.eql? c.display_name
|
||||||
|
customer.name = c.display_name
|
||||||
|
customer.id = c.id
|
||||||
|
customer.phone_number = c.primary_phone.free_form_number.tr('^0-9', '') unless c.primary_phone.nil?
|
||||||
|
customer.mobile_phone_number = c.mobile_phone.free_form_number.tr('^0-9', '') unless c.mobile_phone.nil?
|
||||||
|
customer.save_without_push
|
||||||
|
#end
|
||||||
else
|
else
|
||||||
if not customer.new_record?
|
if not c.new_record?
|
||||||
customer.delete
|
customer.delete
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -171,16 +176,23 @@ class Customer < ActiveRecord::Base
|
|||||||
# proforms a bruteforce sync operation
|
# proforms a bruteforce sync operation
|
||||||
# This needs to be simplified
|
# This needs to be simplified
|
||||||
def self.sync_by_id(id)
|
def self.sync_by_id(id)
|
||||||
service = Qbo.get_base(:customer)
|
qbo = Qbo.first
|
||||||
|
c = qbo.perform_authenticated_request do |access_token|
|
||||||
customer = service.fetch_by_id(id)
|
service = Quickbooks::Service::Customer.new(:company_id => qbo.realm_id, :access_token => access_token)
|
||||||
customer = Customer.find_or_create_by(id: customer.id)
|
service.fetch_by_id(id)
|
||||||
if customer.active?
|
|
||||||
if not customer.name.eql? customer.display_name
|
|
||||||
customer.name = customer.display_name
|
|
||||||
customer.id = customer.id
|
|
||||||
customer.save_without_push
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
return unless c
|
||||||
|
|
||||||
|
customer = Customer.find_or_create_by(id: c.id)
|
||||||
|
if c.active?
|
||||||
|
#if not customer.name.eql? c.display_name
|
||||||
|
customer.name = c.display_name
|
||||||
|
customer.id = c.id
|
||||||
|
customer.phone_number = c.primary_phone.free_form_number.tr('^0-9', '') unless c.primary_phone.nil?
|
||||||
|
customer.mobile_phone_number = c.mobile_phone.free_form_number.tr('^0-9', '') unless c.mobile_phone.nil?
|
||||||
|
customer.save_without_push
|
||||||
|
#end
|
||||||
else
|
else
|
||||||
if not customer.new_record?
|
if not customer.new_record?
|
||||||
customer.delete
|
customer.delete
|
||||||
@@ -191,7 +203,11 @@ class Customer < ActiveRecord::Base
|
|||||||
# Push the updates
|
# Push the updates
|
||||||
def save_with_push
|
def save_with_push
|
||||||
begin
|
begin
|
||||||
@details = Qbo.get_base(:customer).update(@details)
|
qbo = Qbo.first
|
||||||
|
@details = qbo.perform_authenticated_request do |access_token|
|
||||||
|
service = Quickbooks::Service::Customer.new(:company_id => qbo.realm_id, :access_token => access_token)
|
||||||
|
service.update(@details)
|
||||||
|
end
|
||||||
#raise "QBO Fault" if @details.fault?
|
#raise "QBO Fault" if @details.fault?
|
||||||
self.id = @details.id
|
self.id = @details.id
|
||||||
rescue Exception => e
|
rescue Exception => e
|
||||||
@@ -209,7 +225,11 @@ class Customer < ActiveRecord::Base
|
|||||||
def pull
|
def pull
|
||||||
begin
|
begin
|
||||||
raise Exception unless self.id
|
raise Exception unless self.id
|
||||||
@details = Qbo.get_base(:customer).fetch_by_id(self.id)
|
qbo = Qbo.first
|
||||||
|
@details = qbo.perform_authenticated_request do |access_token|
|
||||||
|
service = Quickbooks::Service::Customer.new(:company_id => qbo.realm_id, :access_token => access_token)
|
||||||
|
service.fetch_by_id(self.id)
|
||||||
|
end
|
||||||
rescue Exception => e
|
rescue Exception => e
|
||||||
@details = Quickbooks::Model::Customer.new
|
@details = Quickbooks::Model::Customer.new
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#The MIT License (MIT)
|
#The MIT License (MIT)
|
||||||
#
|
#
|
||||||
#Copyright (c) 2022 rick barrette
|
#Copyright (c) 2016 - 2026 rick barrette
|
||||||
#
|
#
|
||||||
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
#
|
#
|
||||||
@@ -9,7 +9,7 @@
|
|||||||
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
class CustomerToken < ActiveRecord::Base
|
class CustomerToken < ActiveRecord::Base
|
||||||
unloadable
|
|
||||||
has_many :issues
|
has_many :issues
|
||||||
validates_presence_of :issue_id
|
validates_presence_of :issue_id
|
||||||
before_create :generate_token, :generate_expire_date
|
before_create :generate_token, :generate_expire_date
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#The MIT License (MIT)
|
#The MIT License (MIT)
|
||||||
#
|
#
|
||||||
#Copyright (c) 2022 rick barrette
|
#Copyright (c) 2016 - 2026 rick barrette
|
||||||
#
|
#
|
||||||
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
#
|
#
|
||||||
@@ -9,30 +9,39 @@
|
|||||||
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
class Employee < ActiveRecord::Base
|
class Employee < ActiveRecord::Base
|
||||||
unloadable
|
|
||||||
has_many :users
|
has_many :users
|
||||||
validates_presence_of :id, :name
|
validates_presence_of :id, :name
|
||||||
|
|
||||||
def self.get_base
|
def self.sync
|
||||||
Qbo.get_base(:employee)
|
qbo = Qbo.first
|
||||||
|
employees = qbo.perform_authenticated_request do |access_token|
|
||||||
|
service = Quickbooks::Service::Employee.new(:company_id => qbo.realm_id, :access_token => access_token)
|
||||||
|
service.all
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.sync
|
return unless employees
|
||||||
employees = get_base.all
|
|
||||||
|
|
||||||
transaction do
|
transaction do
|
||||||
# Update the item table
|
# Update the item table
|
||||||
employees.each { |employee|
|
employees.each { |e|
|
||||||
employee = find_or_create_by(id: employee.id)
|
logger.info "Processing employee #{e.id}"
|
||||||
employee.name = employee.display_name
|
employee = find_or_create_by(id: e.id)
|
||||||
employee.id = employee.id
|
employee.name = e.display_name
|
||||||
|
employee.id = e.id
|
||||||
employee.save!
|
employee.save!
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.sync_by_id(id)
|
def self.sync_by_id(id)
|
||||||
employee = get_base.fetch_by_id(id)
|
qbo = Qbo.first
|
||||||
|
employee = qbo.perform_authenticated_request do |access_token|
|
||||||
|
service = Quickbooks::Service::Employee.new(:company_id => qbo.realm_id, :access_token => access_token)
|
||||||
|
service.fetch_by_id(id)
|
||||||
|
end
|
||||||
|
|
||||||
|
return unless employee
|
||||||
employee = find_or_create_by(id: employee.id)
|
employee = find_or_create_by(id: employee.id)
|
||||||
employee.name = employee.display_name
|
employee.name = employee.display_name
|
||||||
employee.id = employee.id
|
employee.id = employee.id
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#The MIT License (MIT)
|
#The MIT License (MIT)
|
||||||
#
|
#
|
||||||
#Copyright (c) 2022 rick barrette
|
#Copyright (c) 2016 - 2026 rick barrette
|
||||||
#
|
#
|
||||||
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
#
|
#
|
||||||
@@ -9,22 +9,23 @@
|
|||||||
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
class Estimate < ActiveRecord::Base
|
class Estimate < ActiveRecord::Base
|
||||||
unloadable
|
|
||||||
|
|
||||||
has_and_belongs_to_many :issues
|
has_and_belongs_to_many :issues
|
||||||
belongs_to :customer
|
belongs_to :customer
|
||||||
validates_presence_of :doc_number, :id
|
validates_presence_of :doc_number, :id
|
||||||
self.primary_key = :id
|
self.primary_key = :id
|
||||||
|
|
||||||
# return the QBO Estimate service
|
|
||||||
def self.get_base
|
|
||||||
Qbo.get_base(:estimate)
|
|
||||||
end
|
|
||||||
|
|
||||||
# sync all estimates
|
# sync all estimates
|
||||||
def self.sync
|
def self.sync
|
||||||
logger.debug "Syncing ALL estimates"
|
logger.info "Syncing ALL estimates"
|
||||||
estimates = get_base.all
|
qbo = Qbo.first
|
||||||
|
estimates = qbo.perform_authenticated_request do |access_token|
|
||||||
|
service = Quickbooks::Service::Estimate.new(:company_id => qbo.realm_id, :access_token => access_token)
|
||||||
|
service.all
|
||||||
|
end
|
||||||
|
|
||||||
|
return unless estimates
|
||||||
|
|
||||||
estimates.each { |estimate|
|
estimates.each { |estimate|
|
||||||
process_estimate(estimate)
|
process_estimate(estimate)
|
||||||
}
|
}
|
||||||
@@ -35,36 +36,60 @@ class Estimate < ActiveRecord::Base
|
|||||||
|
|
||||||
# sync only one estimate
|
# sync only one estimate
|
||||||
def self.sync_by_id(id)
|
def self.sync_by_id(id)
|
||||||
logger.debug "Syncing estimate #{id}"
|
logger.info "Syncing estimate #{id}"
|
||||||
process_estimate(get_base.fetch_by_id(id))
|
qbo = Qbo.first
|
||||||
|
qbo.perform_authenticated_request do |access_token|
|
||||||
|
service = Quickbooks::Service::Estimate.new(:company_id => qbo.realm_id, :access_token => access_token)
|
||||||
|
process_estimate(service.fetch_by_id(id))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# sync only one estimate
|
||||||
|
def self.sync_by_doc_number(number)
|
||||||
|
logger.info "Syncing estimate by doc number #{number}"
|
||||||
|
qbo = Qbo.first
|
||||||
|
qbo.perform_authenticated_request do |access_token|
|
||||||
|
service = Quickbooks::Service::Estimate.new(:company_id => qbo.realm_id, :access_token => access_token)
|
||||||
|
process_estimate(service.find_by( :doc_number, number).first)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# update an estimate
|
# update an estimate
|
||||||
def self.update(id)
|
def self.update(id)
|
||||||
# Update the item table
|
# Update the item table
|
||||||
estimate = get_base.fetch_by_id(id)
|
qbo = Qbo.first
|
||||||
estimate = find_or_create_by(id: id)
|
estimate = qbo.perform_authenticated_request do |access_token|
|
||||||
estimate.doc_number = estimate.doc_number
|
service = Quickbooks::Service::Estimate.new(:company_id => qbo.realm_id, :access_token => access_token)
|
||||||
estimate.txn_date = estimate.txn_date
|
service.fetch_by_id(id)
|
||||||
estimate.save!
|
end
|
||||||
|
|
||||||
|
return unless estimate
|
||||||
|
|
||||||
|
e = find_or_create_by(id: id)
|
||||||
|
e.doc_number = estimate.doc_number
|
||||||
|
e.txn_date = estimate.txn_date
|
||||||
|
e.save!
|
||||||
end
|
end
|
||||||
|
|
||||||
# process an estimate into the database
|
# process an estimate into the database
|
||||||
def self.process_estimate(estimate)
|
def self.process_estimate(qbo_estimate)
|
||||||
logger.info "Processing estimate #{estimate.id}"
|
logger.info "Processing estimate #{qbo_estimate.id}"
|
||||||
estimate = find_or_create_by(id: estimate.id)
|
estimate = find_or_create_by(id: qbo_estimate.id)
|
||||||
estimate.doc_number = estimate.doc_number
|
estimate.doc_number = qbo_estimate.doc_number
|
||||||
estimate.customer_id = estimate.customer_ref.value
|
estimate.customer_id = qbo_estimate.customer_ref.value
|
||||||
estimate.id = estimate.id
|
estimate.id = qbo_estimate.id
|
||||||
estimate.txn_date = estimate.txn_date
|
estimate.txn_date = qbo_estimate.txn_date
|
||||||
estimate.save!
|
estimate.save!
|
||||||
end
|
end
|
||||||
|
|
||||||
# download the pdf from quickbooks
|
# download the pdf from quickbooks
|
||||||
def pdf
|
def pdf
|
||||||
base = Estimate.get_base
|
qbo = Qbo.first
|
||||||
estimate = base.fetch_by_id(id)
|
qbo.perform_authenticated_request do |access_token|
|
||||||
return base.pdf(estimate)
|
service = Quickbooks::Service::Estimate.new(:company_id => qbo.realm_id, :access_token => access_token)
|
||||||
|
estimate = service.fetch_by_id(id)
|
||||||
|
service.pdf(estimate)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Magic Method
|
# Magic Method
|
||||||
@@ -91,7 +116,11 @@ class Estimate < ActiveRecord::Base
|
|||||||
def pull
|
def pull
|
||||||
begin
|
begin
|
||||||
raise Exception unless self.id
|
raise Exception unless self.id
|
||||||
@details = Qbo.get_base(:estimate).fetch_by_id(self.id)
|
qbo = Qbo.first
|
||||||
|
@details = qbo.perform_authenticated_request do |access_token|
|
||||||
|
service = Quickbooks::Service::Estimate.new(:company_id => qbo.realm_id, :access_token => access_token)
|
||||||
|
service(:estimate).fetch_by_id(self.id)
|
||||||
|
end
|
||||||
rescue Exception => e
|
rescue Exception => e
|
||||||
@details = Quickbooks::Model::Estimate.new
|
@details = Quickbooks::Model::Estimate.new
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#The MIT License (MIT)
|
#The MIT License (MIT)
|
||||||
#
|
#
|
||||||
#Copyright (c) 2022 rick barrette
|
#Copyright (c) 2016 - 2026 rick barrette
|
||||||
#
|
#
|
||||||
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
#
|
#
|
||||||
@@ -9,20 +9,15 @@
|
|||||||
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
class Invoice < ActiveRecord::Base
|
class Invoice < ActiveRecord::Base
|
||||||
unloadable
|
|
||||||
has_and_belongs_to_many :issues
|
has_and_belongs_to_many :issues
|
||||||
belongs_to :customer
|
belongs_to :customer
|
||||||
validates_presence_of :doc_number, :id, :customer_id, :txn_date
|
validates_presence_of :doc_number, :id, :customer_id, :txn_date
|
||||||
self.primary_key = :id
|
self.primary_key = :id
|
||||||
|
|
||||||
# Get the quickbooks-ruby base for invoice
|
|
||||||
def self.get_base
|
|
||||||
Qbo.get_base(:invoice)
|
|
||||||
end
|
|
||||||
|
|
||||||
# sync ALL the invoices
|
# sync ALL the invoices
|
||||||
def self.sync
|
def self.sync
|
||||||
logger.debug "Syncing all invoices"
|
logger.info "Syncing all invoices"
|
||||||
last = Qbo.first.last_sync
|
last = Qbo.first.last_sync
|
||||||
|
|
||||||
query = "SELECT Id, DocNumber FROM Invoice"
|
query = "SELECT Id, DocNumber FROM Invoice"
|
||||||
@@ -30,12 +25,14 @@ class Invoice < ActiveRecord::Base
|
|||||||
|
|
||||||
# TODO actually do something with the above query
|
# TODO actually do something with the above query
|
||||||
# .all() is never called since count is never initialized
|
# .all() is never called since count is never initialized
|
||||||
if count == 0
|
qbo = Qbo.first
|
||||||
invoices = get_base.all
|
invoices = qbo.perform_authenticated_request do |access_token|
|
||||||
else
|
service = Quickbooks::Service::Invoice.new(:company_id => qbo.realm_id, :access_token => access_token)
|
||||||
invoices = get_base.query()
|
service.all
|
||||||
end
|
end
|
||||||
|
|
||||||
|
return unless invoices
|
||||||
|
|
||||||
invoices.each { | invoice |
|
invoices.each { | invoice |
|
||||||
process_invoice invoice
|
process_invoice invoice
|
||||||
}
|
}
|
||||||
@@ -43,10 +40,14 @@ class Invoice < ActiveRecord::Base
|
|||||||
|
|
||||||
#sync by invoice ID
|
#sync by invoice ID
|
||||||
def self.sync_by_id(id)
|
def self.sync_by_id(id)
|
||||||
logger.debug "Syncing invoice #{id}"
|
logger.info "Syncing invoice #{id}"
|
||||||
invoice = get_base.fetch_by_id(id)
|
qbo = Qbo.first
|
||||||
|
qbo.perform_authenticated_request do |access_token|
|
||||||
|
service = Quickbooks::Service::Invoice.new(:company_id => qbo.realm_id, :access_token => access_token)
|
||||||
|
invoice = service.fetch_by_id(id)
|
||||||
process_invoice invoice
|
process_invoice invoice
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
@@ -57,7 +58,7 @@ class Invoice < ActiveRecord::Base
|
|||||||
# skip this issue if the issue customer is not the same as the invoice customer
|
# skip this issue if the issue customer is not the same as the invoice customer
|
||||||
return if issue.customer_id != invoice.customer_ref.value.to_i
|
return if issue.customer_id != invoice.customer_ref.value.to_i
|
||||||
|
|
||||||
logger.debug "Attaching invoice #{invoice.id} to issue #{issue.id}"
|
logger.info "Attaching invoice #{invoice.id} to issue #{issue.id}"
|
||||||
|
|
||||||
invoice = Invoice.find_or_create_by(id: invoice.id)
|
invoice = Invoice.find_or_create_by(id: invoice.id)
|
||||||
|
|
||||||
@@ -104,8 +105,7 @@ class Invoice < ActiveRecord::Base
|
|||||||
# this condions causes an infinite loop as the webhook is called when an invoice is updated
|
# this condions causes an infinite loop as the webhook is called when an invoice is updated
|
||||||
# TODO maybe add a cf_sync_confict flag to invoices
|
# TODO maybe add a cf_sync_confict flag to invoices
|
||||||
def self.compare_custom_fields(issue, invoice)
|
def self.compare_custom_fields(issue, invoice)
|
||||||
|
logger.info "Comparing custom fields"
|
||||||
logger.debug "Comparing custom fields"
|
|
||||||
# TODO break if Invoice.find(invoice.id).cf_sync_confict
|
# TODO break if Invoice.find(invoice.id).cf_sync_confict
|
||||||
is_changed = false
|
is_changed = false
|
||||||
|
|
||||||
@@ -139,8 +139,12 @@ class Invoice < ActiveRecord::Base
|
|||||||
|
|
||||||
# Push updates
|
# Push updates
|
||||||
begin
|
begin
|
||||||
logger.debug "Trying to update invoice"
|
logger.info "Trying to update invoice"
|
||||||
get_base.update(invoice) if is_changed
|
qbo = Qbo.first
|
||||||
|
qbo.perform_authenticated_request do |access_token|
|
||||||
|
service = Quickbooks::Service::Invoice.new(:company_id => qbo.realm_id, :access_token => access_token)
|
||||||
|
service.update(invoice) if is_changed
|
||||||
|
end
|
||||||
rescue
|
rescue
|
||||||
# Do nothing, probaly custome field sync confict on the invoice.
|
# Do nothing, probaly custome field sync confict on the invoice.
|
||||||
# This is a problem with how it's billed
|
# This is a problem with how it's billed
|
||||||
@@ -150,6 +154,16 @@ class Invoice < ActiveRecord::Base
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# download the pdf from quickbooks
|
||||||
|
def pdf
|
||||||
|
qbo = Qbo.first
|
||||||
|
qbo.perform_authenticated_request do |access_token|
|
||||||
|
service = Quickbooks::Service::Invoice.new(:company_id => qbo.realm_id, :access_token => access_token)
|
||||||
|
invoice = service.fetch_by_id(id)
|
||||||
|
return service.pdf(invoice)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
# Magic Method
|
# Magic Method
|
||||||
# Maps Get/Set methods to QBO invoice object
|
# Maps Get/Set methods to QBO invoice object
|
||||||
def method_missing(sym, *arguments)
|
def method_missing(sym, *arguments)
|
||||||
@@ -172,7 +186,11 @@ class Invoice < ActiveRecord::Base
|
|||||||
def pull
|
def pull
|
||||||
begin
|
begin
|
||||||
raise Exception unless self.id
|
raise Exception unless self.id
|
||||||
@details = Qbo.get_base(:invoice).fetch_by_id(self.id)
|
qbo = Qbo.first
|
||||||
|
@details = qbo.perform_authenticated_request do |access_token|
|
||||||
|
service = Quickbooks::Service::Invoice.new(:company_id => qbo.realm_id, :access_token => access_token)
|
||||||
|
service.fetch_by_id(self.id)
|
||||||
|
end
|
||||||
rescue Exception => e
|
rescue Exception => e
|
||||||
@details = Quickbooks::Model::Invoice.new
|
@details = Quickbooks::Model::Invoice.new
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#The MIT License (MIT)
|
#The MIT License (MIT)
|
||||||
#
|
#
|
||||||
#Copyright (c) 2022 rick barrette
|
#Copyright (c) 2016 - 2026 rick barrette
|
||||||
#
|
#
|
||||||
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
#
|
#
|
||||||
@@ -9,86 +9,16 @@
|
|||||||
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
class Qbo < ActiveRecord::Base
|
class Qbo < ActiveRecord::Base
|
||||||
unloadable
|
|
||||||
validates_presence_of :token, :company_id, :expire
|
|
||||||
serialize :token
|
|
||||||
|
|
||||||
OAUTH_CONSUMER_KEY = Setting.plugin_redmine_qbo['settingsOAuthConsumerKey']
|
include QuickbooksOauth
|
||||||
OAUTH_CONSUMER_SECRET = Setting.plugin_redmine_qbo['settingsOAuthConsumerSecret']
|
include Redmine::I18n
|
||||||
|
|
||||||
#
|
|
||||||
# Getter for quickbooks OAuth2 client
|
|
||||||
#
|
|
||||||
def self.get_client
|
|
||||||
oauth_params = {
|
|
||||||
site: "https://appcenter.intuit.com/connect/oauth2",
|
|
||||||
authorize_url: "https://appcenter.intuit.com/connect/oauth2",
|
|
||||||
token_url: "https://oauth.platform.intuit.com/oauth2/v1/tokens/bearer"
|
|
||||||
}
|
|
||||||
return OAuth2::Client.new(OAUTH_CONSUMER_KEY, OAUTH_CONSUMER_SECRET, oauth_params)
|
|
||||||
end
|
|
||||||
|
|
||||||
#
|
|
||||||
# Getter for oauth consumer
|
|
||||||
#
|
|
||||||
def self.get_oauth_consumer
|
|
||||||
# Quickbooks Config Info
|
|
||||||
return $qb_oauth_consumer
|
|
||||||
end
|
|
||||||
|
|
||||||
#
|
|
||||||
# Get a quickbooks base service object for type
|
|
||||||
# @params type of base
|
|
||||||
#
|
|
||||||
def self.get_base(type)
|
|
||||||
# lets getnourbold access token from the database
|
|
||||||
oauth2_client = get_client
|
|
||||||
qbo = self.first
|
|
||||||
access_token = OAuth2::AccessToken.from_hash(oauth2_client, qbo.token)
|
|
||||||
|
|
||||||
# check to see if we need to refresh the acesstoken
|
|
||||||
if qbo.expire.to_time.utc.past?
|
|
||||||
puts "Updating access token"
|
|
||||||
new_access_token_object = access_token.refresh!
|
|
||||||
qbo.token = new_access_token_object.to_hash
|
|
||||||
qbo.expire = 1.hour.from_now.utc
|
|
||||||
qbo.save!
|
|
||||||
access_token = new_access_token_object
|
|
||||||
else
|
|
||||||
puts "Using current token"
|
|
||||||
end
|
|
||||||
|
|
||||||
# build the reqiested service
|
|
||||||
case type
|
|
||||||
when :item
|
|
||||||
return Quickbooks::Service::Item.new(:company_id => qbo.company_id, :access_token => access_token)
|
|
||||||
when :time_activity
|
|
||||||
return Quickbooks::Service::TimeActivity.new(:company_id => qbo.company_id, :access_token => access_token)
|
|
||||||
when :customer
|
|
||||||
return Quickbooks::Service::Customer.new(:company_id => qbo.company_id, :access_token => access_token)
|
|
||||||
when :invoice
|
|
||||||
return Quickbooks::Service::Invoice.new(:company_id => qbo.company_id, :access_token => access_token)
|
|
||||||
when :estimate
|
|
||||||
return Quickbooks::Service::Estimate.new(:company_id => qbo.company_id, :access_token => access_token)
|
|
||||||
when :account
|
|
||||||
return Quickbooks::Service::Account.new(:company_id => qbo.company_id, :access_token => access_token)
|
|
||||||
when :employee
|
|
||||||
return Quickbooks::Service::Employee.new(:company_id => qbo.company_id, :access_token => access_token)
|
|
||||||
else
|
|
||||||
return access_token
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
# Get the QBO account
|
|
||||||
def self.get_account
|
|
||||||
first
|
|
||||||
end
|
|
||||||
|
|
||||||
# Updates last sync time stamp
|
# Updates last sync time stamp
|
||||||
def self.update_time_stamp
|
def self.update_time_stamp
|
||||||
|
date = DateTime.now
|
||||||
|
logger.info "Updating QBO timestamp to #{date}"
|
||||||
qbo = Qbo.first
|
qbo = Qbo.first
|
||||||
qbo.last_sync = DateTime.now
|
qbo.last_sync = date
|
||||||
qbo.save
|
qbo.save
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
11
app/views/customers/_actions.html.erb
Normal file
11
app/views/customers/_actions.html.erb
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<%= link_to t(:label_appointment), "https://calendar.google.com/calendar/render?action=TEMPLATE&text=#{@customer.name}+-&details=#{ link_to "Customer Details", "https://#{Setting.host_name}#{customer_path @customer.id}"}%0A#{@customer.primary_phone}&dates=#{Time.now.strftime("%Y%m%d")}T090000/#{Time.now.strftime("%Y%m%d")}T170000", target: :_blank %>
|
||||||
|
|
||||||
|
<br/>
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
<%= link_to t(:label_create_estimate), "https://qbo.intuit.com/app/estimate?nameId=#{@customer.id}", target: :_blank %>
|
||||||
|
|
||||||
|
<br/>
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
<%= button_to t(:label_edit_customer), edit_customer_path(@customer), method: :get%>
|
||||||
@@ -1,5 +1,11 @@
|
|||||||
<table>
|
<table>
|
||||||
<tbody>
|
<tbody>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<th><%=t(:label_name)%></th>
|
||||||
|
<td><%= customer.name %></td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
<tr>
|
<tr>
|
||||||
<th><%=t(:label_email)%></th>
|
<th><%=t(:label_email)%></th>
|
||||||
<td><%= customer.email %></td>
|
<td><%= customer.email %></td>
|
||||||
@@ -32,12 +38,20 @@
|
|||||||
|
|
||||||
<tr>
|
<tr>
|
||||||
<th><%=t(:field_notes)%></th>
|
<th><%=t(:field_notes)%></th>
|
||||||
<td><%= customer.notes %></td>
|
<td>
|
||||||
|
<pre id="note-display" style="text-align: left; white-space: pre-wrap; font-family: inherit;">
|
||||||
|
<%= customer.notes %>
|
||||||
|
</pre>
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const preElement = document.getElementById('note-display');
|
||||||
|
// This takes the text, trims the edges, and puts it back
|
||||||
|
preElement.textContent = preElement.textContent.trim();
|
||||||
|
</script>
|
||||||
|
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<div style="float: right;">
|
|
||||||
<%= button_to t(:label_edit_customer), edit_customer_path(customer), method: :get%>
|
|
||||||
</div>
|
|
||||||
<br/>
|
<br/>
|
||||||
<br/>
|
<br/>
|
||||||
|
|||||||
@@ -36,8 +36,7 @@
|
|||||||
<%=t(:field_notes)%>:
|
<%=t(:field_notes)%>:
|
||||||
<div class="input">
|
<div class="input">
|
||||||
<p>
|
<p>
|
||||||
<%= link_to_function content_tag(:span, l(:button_edit), :class => 'icon icon-edit'), '$(this).hide(); $("#issue_description_and_toolbar").show()' unless @customer.new_record? %>
|
<%= content_tag 'span', :id => "issue_description_and_toolbar" do %>
|
||||||
<%= content_tag 'span', :id => "issue_description_and_toolbar", :style => (@customer.new_record? ? nil : 'display:none') do %>
|
|
||||||
<%= f.text_area :notes,
|
<%= f.text_area :notes,
|
||||||
:cols => 60,
|
:cols => 60,
|
||||||
:rows => 10,
|
:rows => 10,
|
||||||
|
|||||||
@@ -3,4 +3,3 @@
|
|||||||
<%= 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?%>
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<h2><%=t(:field_customer)%> #<%= @customer.id %> - <%= @customer.name %> </h2>
|
<h2><%=t(:field_customer)%> #<%= @customer.id %> - <%= link_to @customer.to_s, "https://app.qbo.intuit.com/app/customerdetail?nameId=#{@customer.id}", target: :_blank %> </h2>
|
||||||
<div class="issue">
|
<div class="issue">
|
||||||
|
|
||||||
<div class="splitcontent">
|
<div class="splitcontent">
|
||||||
@@ -7,7 +7,22 @@
|
|||||||
|
|
||||||
<h4><%=t(:label_details)%>:</h4>
|
<h4><%=t(:label_details)%>:</h4>
|
||||||
|
|
||||||
|
<!-- Customer Info -->
|
||||||
|
|
||||||
|
<div class="splitcontent">
|
||||||
|
<div class="splitcontentleft">
|
||||||
|
<h4><%=t(:label_customer)%>:</h4>
|
||||||
<%= render :partial => 'customers/details', locals: {customer: @customer} %>
|
<%= render :partial => 'customers/details', locals: {customer: @customer} %>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="splitcontentleft">
|
||||||
|
<h4><%=t(:label_actions)%>:</h4>
|
||||||
|
<%= render :partial => 'customers/actions', locals: {customer: @customer} %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- QBO Info -->
|
||||||
|
|
||||||
<div class="splitcontent">
|
<div class="splitcontent">
|
||||||
<div class="splitcontentleft">
|
<div class="splitcontentleft">
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<% if @customer.present? %>
|
<% if @customer.present? %>
|
||||||
|
|
||||||
<% @customer.estimates.order(doc_number: :desc).each do |estimate| %>
|
<% @customer.estimates.order(id: :desc).each do |estimate| %>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<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>
|
||||||
|
|||||||
@@ -1,11 +1,24 @@
|
|||||||
<% if @customer.present? %>
|
<% if @customer.present? %>
|
||||||
|
|
||||||
<% @customer.invoices.order(doc_number: :desc).each do |invoice| %>
|
<%= form_with(url: invoice_path, method: :get) do |form| %>
|
||||||
|
|
||||||
|
<div class="form-check">
|
||||||
|
<%= check_box_tag "select-all-invoices", "1", false, id: "select-all-invoices" %>
|
||||||
|
<%= label_tag "select-all-invoices", t(:label_select_all) %>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<% @customer.invoices.order(id: :desc).each do |invoice| %>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
<%= check_box_tag "invoice_ids[]", invoice.id, false, class: "invoice-checkbox" %>
|
||||||
<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 %>
|
||||||
|
|
||||||
|
<%= form.submit t(:button_bulk_pdf) %>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
<% else %>
|
<% else %>
|
||||||
<p><%=t(:label_no_invoices)%>.</p>
|
<p><%=t(:label_no_invoices)%>.</p>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<label for="issue_customer"><%= t(:customer) %></label>
|
<label for="issue_customer"><%= t(:customer) %></label>
|
||||||
<%= search_customer %>
|
<%= search_customer %>
|
||||||
<%= customer_id %>
|
<%= customer_id %>
|
||||||
<%= link_to_function(t(:label_load_customer), "updateIssueFrom('/issues/#{context[:issue].id}/edit.js', this)") %>
|
<%= link_to t(:label_load_customer), '#', onclick: "#{js_link}; return false;" %>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
<%= flash.now[:error] = t(:label_401) %>
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
<!--
|
<!--
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2022 rick barrette
|
Copyright (c) 2016 - 2026 rick barrette
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
@@ -15,7 +15,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
|
|||||||
|
|
||||||
<!-- configure the Intuit object: 'grantUrl' is a URL in your application which kicks off the flow, see below -->
|
<!-- configure the Intuit object: 'grantUrl' is a URL in your application which kicks off the flow, see below -->
|
||||||
<script>
|
<script>
|
||||||
intuit.ipp.anywhere.setup({menuProxy: '/path/to/blue-dot', grantUrl: '<%= Setting.host_name %>/qbo/authenticate'});
|
intuit.ipp.anywhere.setup({menuProxy: '/path/to/blue-dot', grantUrl: '<%= qbo_authenticate_path %>'});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<table >
|
<table >
|
||||||
@@ -57,9 +57,21 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<th><%=t(:label_sandbox)%></th>
|
||||||
|
<td>
|
||||||
|
<%= check_box_tag 'settings[sandbox]', @settings['sandbox'], @settings['sandbox'] %>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
<tr>
|
<tr>
|
||||||
<th><%=t(:label_oauth_expires)%></th>
|
<th><%=t(:label_oauth_expires)%></th>
|
||||||
<td><%= if Qbo.exists? then Qbo.first.expire end %>
|
<td><%= if Qbo.exists? then Qbo.first.oauth2_access_token_expires_at end %>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<th><%=t(:label_oauth2_refresh_token_expires_at)%></th>
|
||||||
|
<td><%= if Qbo.exists? then Qbo.first.oauth2_refresh_token_expires_at end %>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<!--
|
<!--
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2016 rick barrette
|
Copyright (c) 2016 - 2026 rick barrette
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<!--
|
<!--
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2016 rick barrette
|
Copyright (c) 2016 - 2026 rick barrette
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
|||||||
13
app/views/qbo/webhook.erb
Normal file
13
app/views/qbo/webhook.erb
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<!--
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2016 - 2026 rick barrette
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<h2>QboController#webhook</h2>
|
||||||
17
assets/javascripts/checkbox_controller.js
Normal file
17
assets/javascripts/checkbox_controller.js
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
const select_all_invoice = document.getElementById('select-all-invoices');
|
||||||
|
const invoices = document.querySelectorAll('.invoice-checkbox');
|
||||||
|
|
||||||
|
if (select_all_invoice) {
|
||||||
|
select_all_invoice.addEventListener('change', (e) => {
|
||||||
|
invoices.forEach(item => item.checked = e.target.checked);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
invoices.forEach(item => {
|
||||||
|
item.addEventListener('change', () => {
|
||||||
|
const allChecked = Array.from(invoices).every(i => i.checked);
|
||||||
|
select_all_invoice.checked = allChecked;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
#The MIT License (MIT)
|
#The MIT License (MIT)
|
||||||
#
|
#
|
||||||
#Copyright (c) 2022 rick barrette
|
#Copyright (c) 2016 - 2026 rick barrette
|
||||||
#
|
#
|
||||||
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
#
|
#
|
||||||
@@ -22,7 +22,6 @@ en:
|
|||||||
label_search_estimates: "Search Estimates"
|
label_search_estimates: "Search Estimates"
|
||||||
label_search: "Search"
|
label_search: "Search"
|
||||||
label_estimates: "Estimates"
|
label_estimates: "Estimates"
|
||||||
label_401: "Not Authorized"
|
|
||||||
warn_ru_sure: "You sure?"
|
warn_ru_sure: "You sure?"
|
||||||
label_delete: "Delete"
|
label_delete: "Delete"
|
||||||
label_edit: "Edit"
|
label_edit: "Edit"
|
||||||
@@ -80,4 +79,12 @@ en:
|
|||||||
label_billing_error: "Cannot bill without a customer assigned"
|
label_billing_error: "Cannot bill without a customer assigned"
|
||||||
label_qbo_sync_success: "Successfully synced to Quickbooks"
|
label_qbo_sync_success: "Successfully synced to Quickbooks"
|
||||||
label_hours: "Hours"
|
label_hours: "Hours"
|
||||||
|
label_oauth2_refresh_token_expires_at: "Refresh Token Expires At"
|
||||||
|
label_name: "Name"
|
||||||
|
label_appointment: "Add Appointment"
|
||||||
|
label_actions: "Actions"
|
||||||
|
label_create_estimate: "Create Estimate"
|
||||||
|
label_syncing: "Syncing Quickbooks"
|
||||||
|
label_sandbox: "Sandbox"
|
||||||
|
button_bulk_pdf: "Bulk PDF"
|
||||||
|
label_select_all: "Select All"
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
#The MIT License (MIT)
|
#The MIT License (MIT)
|
||||||
#
|
#
|
||||||
#Copyright (c) 2022 rick barrette
|
#Copyright (c) 2016 - 2026 rick barrette
|
||||||
#
|
#
|
||||||
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#The MIT License (MIT)
|
#The MIT License (MIT)
|
||||||
#
|
#
|
||||||
#Copyright (c) 2022 rick barrette
|
#Copyright (c) 2016 - 2026 rick barrette
|
||||||
#
|
#
|
||||||
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#The MIT License (MIT)
|
#The MIT License (MIT)
|
||||||
#
|
#
|
||||||
#Copyright (c) 2022 rick barrette
|
#Copyright (c) 2016 - 2026 rick barrette
|
||||||
#
|
#
|
||||||
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#The MIT License (MIT)
|
#The MIT License (MIT)
|
||||||
#
|
#
|
||||||
#Copyright (c) 2022 rick barrette
|
#Copyright (c) 2016 - 2026 rick barrette
|
||||||
#
|
#
|
||||||
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#The MIT License (MIT)
|
#The MIT License (MIT)
|
||||||
#
|
#
|
||||||
#Copyright (c) 2022 rick barrette
|
#Copyright (c) 2016 - 2026 rick barrette
|
||||||
#
|
#
|
||||||
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#The MIT License (MIT)
|
#The MIT License (MIT)
|
||||||
#
|
#
|
||||||
#Copyright (c) 2022 rick barrette
|
#Copyright (c) 2016 - 2026 rick barrette
|
||||||
#
|
#
|
||||||
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#The MIT License (MIT)
|
#The MIT License (MIT)
|
||||||
#
|
#
|
||||||
#Copyright (c) 2022 rick barrette
|
#Copyright (c) 2016 - 2026 rick barrette
|
||||||
#
|
#
|
||||||
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#The MIT License (MIT)
|
#The MIT License (MIT)
|
||||||
#
|
#
|
||||||
#Copyright (c) 2022 rick barrette
|
#Copyright (c) 2016 - 2026 rick barrette
|
||||||
#
|
#
|
||||||
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#The MIT License (MIT)
|
#The MIT License (MIT)
|
||||||
#
|
#
|
||||||
#Copyright (c) 2022 rick barrette
|
#Copyright (c) 2016 - 2026 rick barrette
|
||||||
#
|
#
|
||||||
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#The MIT License (MIT)
|
#The MIT License (MIT)
|
||||||
#
|
#
|
||||||
#Copyright (c) 2022 rick barrette
|
#Copyright (c) 2016 - 2026 rick barrette
|
||||||
#
|
#
|
||||||
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#The MIT License (MIT)
|
#The MIT License (MIT)
|
||||||
#
|
#
|
||||||
#Copyright (c) 2022 rick barrette
|
#Copyright (c) 2016 - 2026 rick barrette
|
||||||
#
|
#
|
||||||
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#The MIT License (MIT)
|
#The MIT License (MIT)
|
||||||
#
|
#
|
||||||
#Copyright (c) 2022 rick barrette
|
#Copyright (c) 2016 - 2026 rick barrette
|
||||||
#
|
#
|
||||||
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#The MIT License (MIT)
|
#The MIT License (MIT)
|
||||||
#
|
#
|
||||||
#Copyright (c) 2022 rick barrette
|
#Copyright (c) 2016 - 2026 rick barrette
|
||||||
#
|
#
|
||||||
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#The MIT License (MIT)
|
#The MIT License (MIT)
|
||||||
#
|
#
|
||||||
#Copyright (c) 2022 rick barrette
|
#Copyright (c) 2016 - 2026 rick barrette
|
||||||
#
|
#
|
||||||
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#The MIT License (MIT)
|
#The MIT License (MIT)
|
||||||
#
|
#
|
||||||
#Copyright (c) 2022 rick barrette
|
#Copyright (c) 2016 - 2026 rick barrette
|
||||||
#
|
#
|
||||||
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#The MIT License (MIT)
|
#The MIT License (MIT)
|
||||||
#
|
#
|
||||||
#Copyright (c) 2022 rick barrette
|
#Copyright (c) 2016 - 2026 rick barrette
|
||||||
#
|
#
|
||||||
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#The MIT License (MIT)
|
#The MIT License (MIT)
|
||||||
#
|
#
|
||||||
#Copyright (c) 2022 rick barrette
|
#Copyright (c) 2016 - 2026 rick barrette
|
||||||
#
|
#
|
||||||
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#The MIT License (MIT)
|
#The MIT License (MIT)
|
||||||
#
|
#
|
||||||
#Copyright (c) 2022 rick barrette
|
#Copyright (c) 2016 - 2026 rick barrette
|
||||||
#
|
#
|
||||||
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#The MIT License (MIT)
|
#The MIT License (MIT)
|
||||||
#
|
#
|
||||||
#Copyright (c) 2022 rick barrette
|
#Copyright (c) 2016 - 2026 rick barrette
|
||||||
#
|
#
|
||||||
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#The MIT License (MIT)
|
#The MIT License (MIT)
|
||||||
#
|
#
|
||||||
#Copyright (c) 2022 rick barrette
|
#Copyright (c) 2016 - 2026 rick barrette
|
||||||
#
|
#
|
||||||
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#The MIT License (MIT)
|
#The MIT License (MIT)
|
||||||
#
|
#
|
||||||
#Copyright (c) 2022 rick barrette
|
#Copyright (c) 2016 - 2026 rick barrette
|
||||||
#
|
#
|
||||||
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#The MIT License (MIT)
|
#The MIT License (MIT)
|
||||||
#
|
#
|
||||||
#Copyright (c) 2022 rick barrette
|
#Copyright (c) 2016 - 2026 rick barrette
|
||||||
#
|
#
|
||||||
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#The MIT License (MIT)
|
#The MIT License (MIT)
|
||||||
#
|
#
|
||||||
#Copyright (c) 2022 rick barrette
|
#Copyright (c) 2016 - 2026 rick barrette
|
||||||
#
|
#
|
||||||
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#The License
|
#The License
|
||||||
#
|
#
|
||||||
#Copyright (c) 2022 Rick Barrette - All Rights Reserved
|
#Copyright (c) 2016 - 2026 Rick Barrette - All Rights Reserved
|
||||||
#
|
#
|
||||||
#Unauthorized copying of this software and associated documentation files (the "Software"), via any medium is strictly prohibited.
|
#Unauthorized copying of this software and associated documentation files (the "Software"), via any medium is strictly prohibited.
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#The MIT License (MIT)
|
#The MIT License (MIT)
|
||||||
#
|
#
|
||||||
#Copyright (c) 2022 rick barrette
|
#Copyright (c) 2016 - 2026 rick barrette
|
||||||
#
|
#
|
||||||
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#The MIT License (MIT)
|
#The MIT License (MIT)
|
||||||
#
|
#
|
||||||
#Copyright (c) 2022 rick barrette
|
#Copyright (c) 2016 - 2026 rick barrette
|
||||||
#
|
#
|
||||||
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#The MIT License (MIT)
|
#The MIT License (MIT)
|
||||||
#
|
#
|
||||||
#Copyright (c) 2022 rick barrette
|
#Copyright (c) 2016 - 2026 rick barrette
|
||||||
#
|
#
|
||||||
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#The MIT License (MIT)
|
#The MIT License (MIT)
|
||||||
#
|
#
|
||||||
#Copyright (c) 2022 rick barrette
|
#Copyright (c) 2016 - 2026 rick barrette
|
||||||
#
|
#
|
||||||
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#The MIT License (MIT)
|
#The MIT License (MIT)
|
||||||
#
|
#
|
||||||
#Copyright (c) 2022 rick barrette
|
#Copyright (c) 2016 - 2026 rick barrette
|
||||||
#
|
#
|
||||||
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#The MIT License (MIT)
|
#The MIT License (MIT)
|
||||||
#
|
#
|
||||||
#Copyright (c) 2022 rick barrette
|
#Copyright (c) 2016 - 2026 rick barrette
|
||||||
#
|
#
|
||||||
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
#
|
#
|
||||||
@@ -9,7 +9,9 @@
|
|||||||
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
class AddTxnDates < ActiveRecord::Migration[5.1]
|
class AddTxnDates < ActiveRecord::Migration[5.1]
|
||||||
|
|
||||||
def change
|
def change
|
||||||
|
begin
|
||||||
add_column :qbo_invoices, :txn_date, :date
|
add_column :qbo_invoices, :txn_date, :date
|
||||||
add_column :qbo_estimates, :txn_date, :date
|
add_column :qbo_estimates, :txn_date, :date
|
||||||
|
|
||||||
@@ -26,7 +28,13 @@ class AddTxnDates < ActiveRecord::Migration[5.1]
|
|||||||
|
|
||||||
say "Sync Invoices"
|
say "Sync Invoices"
|
||||||
|
|
||||||
invoices = QboInvoice.get_base.all
|
qbo = Qbo.first
|
||||||
|
invoices = qbo.perform_authenticated_request do |access_token|
|
||||||
|
service = Quickbooks::Service::Invoice.new(:company_id => qbo.realm_id, :access_token => access_token)
|
||||||
|
service.all
|
||||||
|
end
|
||||||
|
|
||||||
|
return unless invoices
|
||||||
|
|
||||||
invoices.each { |invoice|
|
invoices.each { |invoice|
|
||||||
# Load the invoice into the database
|
# Load the invoice into the database
|
||||||
@@ -39,6 +47,9 @@ class AddTxnDates < ActiveRecord::Migration[5.1]
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
rescue
|
||||||
|
logger.error "AddTxnDates Failed"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#The MIT License (MIT)
|
#The MIT License (MIT)
|
||||||
#
|
#
|
||||||
#Copyright (c) 2022 rick barrette
|
#Copyright (c) 2016 - 2026 rick barrette
|
||||||
#
|
#
|
||||||
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#The MIT License (MIT)
|
#The MIT License (MIT)
|
||||||
#
|
#
|
||||||
#Copyright (c) 2022 rick barrette
|
#Copyright (c) 2016 - 2026 rick barrette
|
||||||
#
|
#
|
||||||
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#The MIT License (MIT)
|
#The MIT License (MIT)
|
||||||
#
|
#
|
||||||
#Copyright (c) 2022 rick barrette
|
#Copyright (c) 2016 - 2026 rick barrette
|
||||||
#
|
#
|
||||||
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#The MIT License (MIT)
|
#The MIT License (MIT)
|
||||||
#
|
#
|
||||||
#Copyright (c) 2022 rick barrette
|
#Copyright (c) 2016 - 2026 rick barrette
|
||||||
#
|
#
|
||||||
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
#
|
#
|
||||||
@@ -8,18 +8,15 @@
|
|||||||
#
|
#
|
||||||
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
class UsersShowHookListener < Redmine::Hook::ViewListener
|
class UpdateQboToken < ActiveRecord::Migration[5.1]
|
||||||
|
def change
|
||||||
# View User
|
add_column :qbos, :oauth2_access_token, :text
|
||||||
def view_users_form(context={})
|
add_column :qbos, :oauth2_access_token_expires_at, :datetime
|
||||||
|
add_column :qbos, :oauth2_refresh_token, :text
|
||||||
# Update the users
|
add_column :qbos, :oauth2_refresh_token_expires_at, :datetime
|
||||||
#Employee.update_all
|
add_column :qbos, :realm_id, :text
|
||||||
|
remove_column :qbos, :company_id
|
||||||
# Check to see if there is a quickbooks user attached to the issue
|
remove_column :qbos, :token
|
||||||
@selected = context[:user].employee.id if context[:user].employee
|
remove_column :qbos, :expire
|
||||||
|
|
||||||
# Generate the drop down list of quickbooks contacts
|
|
||||||
return "<p>#{context[:form].select :employee_id, Employee.all.pluck(:name, :id), :selected => @selected, include_blank: true}</p>"
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
32
init.rb
32
init.rb
@@ -1,6 +1,6 @@
|
|||||||
#The MIT License (MIT)
|
z#The MIT License (MIT)
|
||||||
#
|
#
|
||||||
#Copyright (c) 2022 rick barrette
|
#Copyright (c) 2016 - 2026 rick barrette
|
||||||
#
|
#
|
||||||
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
#
|
#
|
||||||
@@ -8,25 +8,17 @@
|
|||||||
#
|
#
|
||||||
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
# Dynamically load all Hooks & Patches
|
|
||||||
ActiveSupport::Reloader.to_prepare do
|
|
||||||
Dir::foreach(File.join(File.dirname(__FILE__), 'lib')) do |file|
|
|
||||||
next unless /\.rb$/ =~ file
|
|
||||||
require_dependency file
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
Redmine::Plugin.register :redmine_qbo do
|
Redmine::Plugin.register :redmine_qbo do
|
||||||
|
|
||||||
# About
|
# About
|
||||||
name 'Redmine QBO DEVELOPMENT plugin'
|
name 'Redmine QBO DEVELOPMENT plugin'
|
||||||
author 'Rick Barrette'
|
author 'Rick Barrette'
|
||||||
description 'This is a plugin for Redmine to integrate with QuickBooks Online to allow for seamless integration CRM and invoicing of completed issues'
|
description 'This is a plugin for Redmine to intergrate with Quickbooks Online to allow for seamless intergration CRM and invoicing of completed issues'
|
||||||
version '1.1.6'
|
version '2026.1.0'
|
||||||
url 'https://github.com/rickbarrette/redmine_qbo'
|
url 'https://github.com/rickbarrette/redmine_qbo'
|
||||||
author_url 'http://rickbarrette.org'
|
author_url 'https://barrettefabrication.com'
|
||||||
settings :default => {'empty' => true}, :partial => 'qbo/settings'
|
settings :default => {'empty' => true}, :partial => 'qbo/settings'
|
||||||
requires_redmine :version_or_higher => '6.0.0'
|
requires_redmine :version_or_higher => '6.1.0'
|
||||||
|
|
||||||
# Add safe attributes for core models
|
# Add safe attributes for core models
|
||||||
Issue.safe_attributes 'customer_id'
|
Issue.safe_attributes 'customer_id'
|
||||||
@@ -36,9 +28,10 @@ Redmine::Plugin.register :redmine_qbo do
|
|||||||
User.safe_attributes 'employee_id'
|
User.safe_attributes 'employee_id'
|
||||||
TimeEntry.safe_attributes 'billed'
|
TimeEntry.safe_attributes 'billed'
|
||||||
Project.safe_attributes 'customer_id'
|
Project.safe_attributes 'customer_id'
|
||||||
|
Project.safe_attributes 'vehicle_id'
|
||||||
|
|
||||||
# We are playing in the sandbox
|
# We are playing in the sandbox
|
||||||
Quickbooks.sandbox_mode = true
|
#Quickbooks.sandbox_mode = true
|
||||||
|
|
||||||
# set per_page globally
|
# set per_page globally
|
||||||
WillPaginate.per_page = 20
|
WillPaginate.per_page = 20
|
||||||
@@ -49,5 +42,14 @@ Redmine::Plugin.register :redmine_qbo do
|
|||||||
|
|
||||||
# Register top menu items
|
# Register top menu items
|
||||||
menu :top_menu, :customers, { :controller => :customers, :action => :index }, :caption => 'Customers', :if => Proc.new {User.current.logged?}
|
menu :top_menu, :customers, { :controller => :customers, :action => :index }, :caption => 'Customers', :if => Proc.new {User.current.logged?}
|
||||||
|
menu :top_menu, :vehicles, { :controller => :vehicles, :action => :index }, :caption => 'Vehicles', :if => Proc.new { User.current.logged? }
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Dynamically load all Hooks & Patches recursively
|
||||||
|
base_dir = File.join(File.dirname(__FILE__), 'lib')
|
||||||
|
|
||||||
|
# '**' looks inside subdirectories, '*.rb' matches Ruby files
|
||||||
|
Dir.glob(File.join(base_dir, '**', '*.rb')).sort.each do |file|
|
||||||
|
require file
|
||||||
|
end
|
||||||
@@ -1,19 +1,19 @@
|
|||||||
#The MIT License (MIT)
|
#The MIT License (MIT)
|
||||||
#
|
#
|
||||||
#Copyright (c) 2022 rick barrette
|
#Copyright (c) 2016 - 2026 rick barrette
|
||||||
#
|
#
|
||||||
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
#
|
#
|
||||||
#The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
#The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||||
#
|
#
|
||||||
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
class HeaderFooterHookListener < Redmine::Hook::ViewListener
|
|
||||||
|
|
||||||
def view_layouts_base_html_head(context = {})
|
module Hooks
|
||||||
#nothing
|
|
||||||
end
|
|
||||||
|
|
||||||
|
class HeaderFooterHookListener < Redmine::Hook::ViewListener
|
||||||
def view_layouts_base_body_bottom(context = {})
|
def view_layouts_base_body_bottom(context = {})
|
||||||
return "<div id='footer' align='center'><b>#{I18n.translate(:label_last_sync)}: </b> #{Qbo.last_sync if Qbo.exists?}</div>"
|
return "<div id='footer' align='center'><b>#{I18n.translate(:label_last_sync)}: </b> #{Qbo.last_sync if Qbo.exists?}</div>"
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
86
lib/hooks/issues_form_hook_listener.rb
Normal file
86
lib/hooks/issues_form_hook_listener.rb
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
#The MIT License (MIT)
|
||||||
|
#
|
||||||
|
#Copyright (c) 2016 - 2026 rick barrette
|
||||||
|
#
|
||||||
|
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
#
|
||||||
|
#The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
module Hooks
|
||||||
|
|
||||||
|
class IssuesFormHookListener < Redmine::Hook::ViewListener
|
||||||
|
|
||||||
|
# Edit Issue Form
|
||||||
|
# Here we build the required form components before passing them to a partial view formatting.
|
||||||
|
def view_issues_form_details_bottom(context={})
|
||||||
|
f = context[:form]
|
||||||
|
issue = context[:issue]
|
||||||
|
|
||||||
|
# check project level customer ownership first
|
||||||
|
# This is done to preload customer information if the entire project is dedicated to a customer
|
||||||
|
if context[:project]
|
||||||
|
selected_customer = context[:project].customer ? context[:project].customer.id : nil
|
||||||
|
selected_vehicle = context[:project].vehicle ? context[:project].vehicle.id : nil
|
||||||
|
end
|
||||||
|
|
||||||
|
# Check to see if the issue already belongs to a customer
|
||||||
|
selected_customer = issue.customer ? issue.customer.id : nil
|
||||||
|
selected_estimate = issue.estimate ? issue.estimate.id : nil
|
||||||
|
selected_vehicle = issue.vehicles_id ? issue.vehicles_id : nil
|
||||||
|
|
||||||
|
# Gernerate edit.js link
|
||||||
|
js_link = issue.new_record? ? "updateIssueFrom('/projects/rmt/issues/new.js', this)" : "updateIssueFrom('/issues/#{issue.id}/edit.js', this)"
|
||||||
|
|
||||||
|
# Load customer information
|
||||||
|
customer = Customer.find_by_id(selected_customer) if selected_customer
|
||||||
|
|
||||||
|
# Customer Name Text Box with database backed autocomplete
|
||||||
|
search_customer = f.autocomplete_field :customer,
|
||||||
|
autocomplete_customer_name_customers_path,
|
||||||
|
:selected => selected_customer,
|
||||||
|
:onchange => js_link,
|
||||||
|
:update_elements => {
|
||||||
|
:id => '#issue_customer_id',
|
||||||
|
:value => '#issue_customer'
|
||||||
|
}
|
||||||
|
|
||||||
|
# Customer ID - Hidden Field
|
||||||
|
customer_id = f.hidden_field :customer_id,
|
||||||
|
:id => "issue_customer_id",
|
||||||
|
:onchange => js_link
|
||||||
|
|
||||||
|
# Load estimates & vehicles
|
||||||
|
if issue.customer
|
||||||
|
if customer.vehicles
|
||||||
|
vehicles = customer.vehicles.pluck(:name, :id)
|
||||||
|
else
|
||||||
|
vehicles = [nil].compact
|
||||||
|
end
|
||||||
|
estimates = customer.estimates.pluck(:doc_number, :id).sort! {|x, y| y <=> x}
|
||||||
|
else
|
||||||
|
vehicles = [nil].compact
|
||||||
|
estimates = [nil].compact
|
||||||
|
end
|
||||||
|
|
||||||
|
# Generate the drop down list of quickbooks estimates & vehicles
|
||||||
|
select_estimate = f.select :estimate_id, estimates, :selected => selected_estimate, include_blank: true
|
||||||
|
vehicle = f.select :vehicles_id, vehicles, :selected => selected_vehicle, include_blank: true
|
||||||
|
|
||||||
|
# Pass all prebuilt form components to our partial
|
||||||
|
context[:controller].send(:render_to_string, {
|
||||||
|
:partial => 'issues/form_hook',
|
||||||
|
locals: {
|
||||||
|
search_customer: search_customer,
|
||||||
|
customer_id: customer_id,
|
||||||
|
js_link: js_link,
|
||||||
|
select_estimate: select_estimate,
|
||||||
|
vehicle: vehicle
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
33
lib/hooks/issues_save_hook_listener.rb
Normal file
33
lib/hooks/issues_save_hook_listener.rb
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
#The MIT License (MIT)
|
||||||
|
#
|
||||||
|
#Copyright (c) 2016 - 2026 rick barrette
|
||||||
|
#
|
||||||
|
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
#
|
||||||
|
#The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
module Hooks
|
||||||
|
|
||||||
|
class IssuesSaveHookListener < Redmine::Hook::ViewListener
|
||||||
|
|
||||||
|
# Called Before Issue Saved
|
||||||
|
def controller_issues_edit_before_save(context={})
|
||||||
|
issue = context[:issue]
|
||||||
|
issue.subject = issue.subject.titleize
|
||||||
|
end
|
||||||
|
|
||||||
|
# Called After Issue Saved
|
||||||
|
def controller_issues_edit_after_save(context={})
|
||||||
|
issue = context[:issue]
|
||||||
|
begin
|
||||||
|
issue.bill_time if issue.status.is_closed?
|
||||||
|
rescue
|
||||||
|
# TODO flash[:error] = "Unable to bill, check QBO Auth"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
67
lib/hooks/issues_show_hook_listener.rb
Normal file
67
lib/hooks/issues_show_hook_listener.rb
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
#The MIT License (MIT)
|
||||||
|
#
|
||||||
|
#Copyright (c) 2016 - 2026 rick barrette
|
||||||
|
#
|
||||||
|
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
#
|
||||||
|
#The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
module Hooks
|
||||||
|
|
||||||
|
class IssuesShowHookListener < Redmine::Hook::ViewListener
|
||||||
|
|
||||||
|
# View Issue
|
||||||
|
# Display the quickbooks contact in the issue
|
||||||
|
def view_issues_show_details_bottom(context={})
|
||||||
|
issue = context[:issue]
|
||||||
|
|
||||||
|
# Check to see if there is a quickbooks user attached to the issue
|
||||||
|
if issue.customer
|
||||||
|
customer = link_to issue.customer.name, customer_path( issue.customer.id )
|
||||||
|
end
|
||||||
|
|
||||||
|
# Estimate Number
|
||||||
|
if issue.estimate
|
||||||
|
estimate = issue.estimate.doc_number
|
||||||
|
estimate_link = link_to estimate, estimate_path( issue.estimate.id ), :target => "_blank"
|
||||||
|
end
|
||||||
|
|
||||||
|
# Invoice Number
|
||||||
|
invoice_link = ""
|
||||||
|
if issue.invoice_ids
|
||||||
|
issue.invoice_ids.each do |i|
|
||||||
|
invoice = Invoice.find i
|
||||||
|
invoice_link = invoice_link + link_to( invoice.doc_number, invoice_path( i ), :target => "_blank").to_s + " "
|
||||||
|
invoice_link = invoice_link.html_safe
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
begin
|
||||||
|
v = Vehicle.find(issue.vehicles_id)
|
||||||
|
vehicle = link_to v.to_s, vehicle_path( v.id )
|
||||||
|
vin = v.vin
|
||||||
|
notes = v.notes
|
||||||
|
rescue
|
||||||
|
#do nothing
|
||||||
|
end
|
||||||
|
|
||||||
|
split_vin = vin.scan(/.{1,9}/) if vin
|
||||||
|
|
||||||
|
context[:controller].send(:render_to_string, {
|
||||||
|
:partial => 'issues/show_details',
|
||||||
|
locals: {
|
||||||
|
customer: customer,
|
||||||
|
estimate_link: estimate_link,
|
||||||
|
invoice_link: invoice_link,
|
||||||
|
vehicle: vehicle,
|
||||||
|
split_vin: split_vin,
|
||||||
|
notes: notes
|
||||||
|
}
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
40
lib/hooks/projects_form_hook_listener.rb
Normal file
40
lib/hooks/projects_form_hook_listener.rb
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
#The MIT License (MIT)
|
||||||
|
#
|
||||||
|
#Copyright (c) 2016 - 2026 rick barrette
|
||||||
|
#
|
||||||
|
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
#
|
||||||
|
#The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
module Hooks
|
||||||
|
|
||||||
|
class ProjectsFormHookListener < Redmine::Hook::ViewListener
|
||||||
|
|
||||||
|
# Edit Project Form
|
||||||
|
def view_projects_form(context={})
|
||||||
|
f = context[:form]
|
||||||
|
|
||||||
|
# Check to see if there is a quickbooks user attached to the issue
|
||||||
|
selected_customer = context[:project].customer ? context[:project].customer : nil
|
||||||
|
selected_vehicle = context[:project].vehicle_id ? context[:project].vehicle_id : nil
|
||||||
|
|
||||||
|
# Load customer information
|
||||||
|
customer = Customer.find_by_id(selected_customer) if selected_customer
|
||||||
|
search_customer = f.autocomplete_field :customer, autocomplete_customer_name_customers_path, :selected => selected_customer, :update_elements => {:id => '#project_customer_id', :value => '#project_customer'}
|
||||||
|
customer_id = f.hidden_field :customer_id, :id => "project_customer_id"
|
||||||
|
|
||||||
|
if context[:project].customer
|
||||||
|
vehicles = customer.vehicles.pluck(:name, :id).sort!
|
||||||
|
else
|
||||||
|
vehicles = [nil].compact
|
||||||
|
end
|
||||||
|
|
||||||
|
vehicle = f.select :vehicle_id, vehicles, :selected => selected_vehicle, include_blank: true
|
||||||
|
|
||||||
|
return "<p><label for=\"project_customer\">Customer</label>#{search_customer} #{customer_id}</p> <p>#{vehicle}</p>"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
#The MIT License (MIT)
|
#The MIT License (MIT)
|
||||||
#
|
#
|
||||||
#Copyright (c) 2022 rick barrette
|
#Copyright (c) 2016 - 2026 rick barrette
|
||||||
#
|
#
|
||||||
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
#
|
#
|
||||||
@@ -8,26 +8,22 @@
|
|||||||
#
|
#
|
||||||
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
require_dependency 'time_entry_query'
|
module Hooks
|
||||||
|
|
||||||
module TimeEntryQueryPatch
|
class UsersShowHookListener < Redmine::Hook::ViewListener
|
||||||
|
|
||||||
# Add QBO options to columns
|
# View User
|
||||||
def available_columns
|
def view_users_form(context={})
|
||||||
unless @available_columns
|
|
||||||
@available_columns = self.class.available_columns.dup
|
# Update the users
|
||||||
@available_columns << QueryColumn.new(:billed, :sortable => "#{TimeEntry.table_name}.name", :groupable => true, :caption => :field_billed)
|
#Employee.update_all
|
||||||
|
|
||||||
|
# Check to see if there is a quickbooks user attached to the issue
|
||||||
|
@selected = context[:user].employee.id if context[:user].employee
|
||||||
|
|
||||||
|
# Generate the drop down list of quickbooks contacts
|
||||||
|
return "<p>#{context[:form].select :employee_id, Employee.all.pluck(:name, :id), :selected => @selected, include_blank: true}</p>"
|
||||||
end
|
end
|
||||||
super
|
|
||||||
end
|
|
||||||
|
|
||||||
# Add QBO options to the filter
|
|
||||||
def initialize_available_filters
|
|
||||||
add_available_filter "billed", :type => :boolean
|
|
||||||
super
|
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Add module to TimeEntryQuery
|
|
||||||
TimeEntryQuery.send(:prepend, QueryPatch)
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
#The MIT License (MIT)
|
#The MIT License (MIT)
|
||||||
#
|
#
|
||||||
#Copyright (c) 2022 rick barrette
|
#Copyright (c) 2016 - 2026 rick barrette
|
||||||
#
|
#
|
||||||
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
#
|
#
|
||||||
@@ -8,8 +8,12 @@
|
|||||||
#
|
#
|
||||||
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
class ViewHookListener < Redmine::Hook::ViewListener
|
module Hooks
|
||||||
|
|
||||||
|
class ViewHookListener < Redmine::Hook::ViewListener
|
||||||
|
|
||||||
render_on :view_layouts_base_sidebar, :partial => "qbo/sidebar"
|
render_on :view_layouts_base_sidebar, :partial => "qbo/sidebar"
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
#The MIT License (MIT)
|
#The MIT License (MIT)
|
||||||
#
|
#
|
||||||
#Copyright (c) 2017 rick barrette
|
#Copyright (c) 2016 - 2026 rick barrette
|
||||||
#
|
#
|
||||||
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
#
|
#
|
||||||
@@ -8,22 +8,18 @@
|
|||||||
#
|
#
|
||||||
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
class IssuesSaveHookListener < Redmine::Hook::ViewListener
|
module Hooks
|
||||||
|
|
||||||
# Called Before Issue Saved
|
class ViewLayoutsHookListener < Redmine::Hook::ViewListener
|
||||||
def controller_issues_edit_before_save(context={})
|
|
||||||
issue = context[:issue]
|
# Load the javascript to support the autocomplete forms
|
||||||
issue.subject = issue.subject.titleize
|
def view_layouts_base_html_head(context = {})
|
||||||
|
js = javascript_include_tag 'application.js', :plugin => 'redmine_qbo'
|
||||||
|
js += javascript_include_tag 'autocomplete-rails.js', :plugin => 'redmine_qbo'
|
||||||
|
js += javascript_include_tag 'checkbox_controller.js', :plugin => 'redmine_qbo'
|
||||||
|
return js
|
||||||
end
|
end
|
||||||
|
|
||||||
# Called After Issue Saved
|
|
||||||
def controller_issues_edit_after_save(context={})
|
|
||||||
issue = context[:issue]
|
|
||||||
begin
|
|
||||||
issue.bill_time if issue.status.is_closed?
|
|
||||||
rescue
|
|
||||||
# TODO flash[:error] = "Unable to bill, check QBO Auth"
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
@@ -1,107 +0,0 @@
|
|||||||
#The MIT License (MIT)
|
|
||||||
#
|
|
||||||
#Copyright (c) 2022 rick barrette
|
|
||||||
#
|
|
||||||
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
||||||
#
|
|
||||||
#The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
||||||
#
|
|
||||||
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
|
|
||||||
require_dependency 'issue'
|
|
||||||
|
|
||||||
# Patches Redmine's Issues dynamically.
|
|
||||||
# Adds a relationships
|
|
||||||
module IssuePatch
|
|
||||||
|
|
||||||
def self.included(base) # :nodoc:
|
|
||||||
base.extend(ClassMethods)
|
|
||||||
|
|
||||||
base.send(:include, InstanceMethods)
|
|
||||||
|
|
||||||
# Same as typing in the class
|
|
||||||
base.class_eval do
|
|
||||||
unloadable # Send unloadable so it will not be unloaded in development
|
|
||||||
belongs_to :customer, primary_key: :id
|
|
||||||
belongs_to :customer_token, primary_key: :id
|
|
||||||
belongs_to :estimate, primary_key: :id
|
|
||||||
has_and_belongs_to_many :invoices
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
module ClassMethods
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
module InstanceMethods
|
|
||||||
|
|
||||||
# Create billable time entries
|
|
||||||
def bill_time
|
|
||||||
|
|
||||||
# Check to see if we have everything we need to bill the customer
|
|
||||||
return if assigned_to.nil?
|
|
||||||
return unless Qbo.first
|
|
||||||
return unless customer
|
|
||||||
|
|
||||||
# Get unbilled time entries
|
|
||||||
spent_time = time_entries.where(billed: [false, nil])
|
|
||||||
spent_hours ||= spent_time.sum(:hours) || 0
|
|
||||||
|
|
||||||
if spent_hours > 0 then
|
|
||||||
|
|
||||||
# Prepare to create a new Time Activity
|
|
||||||
time_service = Qbo.get_base(:time_activity)
|
|
||||||
item_service = Qbo.get_base(:item)
|
|
||||||
time_entry = Quickbooks::Model::TimeActivity.new
|
|
||||||
|
|
||||||
# Lets total up each activity before billing.
|
|
||||||
# This will simpify the invoicing with a single billable time entry per time activity
|
|
||||||
h = Hash.new(0)
|
|
||||||
spent_time.each do |entry|
|
|
||||||
h[entry.activity.name] += entry.hours
|
|
||||||
# update time entries billed status
|
|
||||||
entry.billed = true
|
|
||||||
entry.save
|
|
||||||
end
|
|
||||||
|
|
||||||
# Now letes upload our totals for each activity as their own billable time entry
|
|
||||||
h.each do |key, val|
|
|
||||||
|
|
||||||
# Convert float spent time to hours and minutes
|
|
||||||
hours = val.to_i
|
|
||||||
minutesDecimal = (( val - hours) * 60)
|
|
||||||
minutes = minutesDecimal.to_i
|
|
||||||
|
|
||||||
# Lets match the activity to an qbo item
|
|
||||||
item = item_service.query("SELECT * FROM Item WHERE Name = '#{key}' ").first
|
|
||||||
next if item.nil?
|
|
||||||
|
|
||||||
# Create the new billable time entry and upload it
|
|
||||||
time_entry.description = "#{tracker} ##{id}: #{subject} #{"(Partial @ #{done_ratio}%)" if not closed?}"
|
|
||||||
time_entry.employee_id = assigned_to.employee_id
|
|
||||||
time_entry.customer_id = customer_id
|
|
||||||
time_entry.billable_status = "Billable"
|
|
||||||
time_entry.hours = hours
|
|
||||||
time_entry.minutes = minutes
|
|
||||||
time_entry.name_of = "Employee"
|
|
||||||
time_entry.txn_date = Date.today
|
|
||||||
time_entry.hourly_rate = item.unit_price
|
|
||||||
time_entry.item_id = item.id
|
|
||||||
time_entry.start_time = start_date
|
|
||||||
time_entry.end_time = Time.now
|
|
||||||
time_service.create(time_entry)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Create a shareable link for a customer
|
|
||||||
def share_token
|
|
||||||
CustomerToken.get_token self
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
# Add module to Issue
|
|
||||||
Issue.send(:include, IssuePatch)
|
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
#The MIT License (MIT)
|
|
||||||
#
|
|
||||||
#Copyright (c) 2022 rick barrette
|
|
||||||
#
|
|
||||||
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
||||||
#
|
|
||||||
#The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
||||||
#
|
|
||||||
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
|
|
||||||
class IssuesFormHookListener < Redmine::Hook::ViewListener
|
|
||||||
|
|
||||||
# Load the javascript to support the autocomplete forms
|
|
||||||
def view_layouts_base_html_head(context = {})
|
|
||||||
js = javascript_include_tag 'application', :plugin => 'redmine_qbo'
|
|
||||||
js += javascript_include_tag 'autocomplete-rails', :plugin => 'redmine_qbo'
|
|
||||||
return js
|
|
||||||
end
|
|
||||||
|
|
||||||
# Edit Issue Form
|
|
||||||
# Here we build the required form components before passing them to a partial view formatting.
|
|
||||||
def view_issues_form_details_bottom(context={})
|
|
||||||
f = context[:form]
|
|
||||||
|
|
||||||
# check project level customer ownership first
|
|
||||||
# This is done to preload customer information if the entire project is dedicated to a customer
|
|
||||||
if context[:project]
|
|
||||||
selected_customer = context[:project].customer ? context[:project].customer.id : nil
|
|
||||||
end
|
|
||||||
|
|
||||||
# Check to see if the issue already belongs to a customer
|
|
||||||
selected_customer = context[:issue].customer ? context[:issue].customer.id : nil
|
|
||||||
selected_estimate = context[:issue].estimate ? context[:issue].estimate.id : nil
|
|
||||||
|
|
||||||
# Load customer information
|
|
||||||
customer = Customer.find_by_id(selected_customer) if selected_customer
|
|
||||||
|
|
||||||
# Customer Name Text Box with database backed autocomplete
|
|
||||||
search_customer = f.autocomplete_field :customer,
|
|
||||||
autocomplete_customer_name_customers_path,
|
|
||||||
:selected => selected_customer,
|
|
||||||
:onchange => "updateIssueFrom('/issues/#{context[:issue].id}/edit.js', this)",
|
|
||||||
:update_elements => {
|
|
||||||
:id => '#issue_customer_id',
|
|
||||||
:value => '#issue_customer'
|
|
||||||
}
|
|
||||||
|
|
||||||
# Customer ID - Hidden Field
|
|
||||||
customer_id = f.hidden_field :customer_id,
|
|
||||||
:id => "issue_customer_id",
|
|
||||||
:onchange => "updateIssueFrom('/issues/#{context[:issue].id}/edit.js', this)"
|
|
||||||
|
|
||||||
# Load estimates
|
|
||||||
if context[:issue].customer
|
|
||||||
estimates = customer.estimates.pluck(:doc_number, :id).sort! {|x, y| y <=> x}
|
|
||||||
else
|
|
||||||
estimates = [nil].compact
|
|
||||||
end
|
|
||||||
|
|
||||||
# Generate the drop down list of quickbooks estimates
|
|
||||||
select_estimate = f.select :estimate_id, estimates, :selected => selected_estimate, include_blank: true
|
|
||||||
|
|
||||||
# Pass all prebuilt form components to our partial
|
|
||||||
context[:controller].send(:render_to_string, {
|
|
||||||
:partial => 'issues/form_hook',
|
|
||||||
locals: {
|
|
||||||
search_customer: search_customer,
|
|
||||||
customer_id: customer_id,
|
|
||||||
context: context,
|
|
||||||
select_estimate: select_estimate,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
#The MIT License (MIT)
|
|
||||||
#
|
|
||||||
#Copyright (c) 2022 rick barrette
|
|
||||||
#
|
|
||||||
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
||||||
#
|
|
||||||
#The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
||||||
#
|
|
||||||
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
|
|
||||||
class IssuesShowHookListener < Redmine::Hook::ViewListener
|
|
||||||
|
|
||||||
# View Issue
|
|
||||||
# Display the quickbooks contact in the issue
|
|
||||||
def view_issues_show_details_bottom(context={})
|
|
||||||
issue = context[:issue]
|
|
||||||
|
|
||||||
# Check to see if there is a quickbooks user attached to the issue
|
|
||||||
if issue.customer
|
|
||||||
customer = link_to issue.customer.name, customer_path( issue.customer.id )
|
|
||||||
end
|
|
||||||
|
|
||||||
# Estimate Number
|
|
||||||
if issue.estimate
|
|
||||||
estimate = issue.estimate.doc_number
|
|
||||||
estimate_link = link_to estimate, estimate_path( issue.estimate.id ), :target => "_blank"
|
|
||||||
end
|
|
||||||
|
|
||||||
# Invoice Number
|
|
||||||
invoice_link = ""
|
|
||||||
if issue.invoice_ids
|
|
||||||
issue.invoice_ids.each do |i|
|
|
||||||
invoice = Invoice.find i
|
|
||||||
invoice_link = invoice_link + link_to( invoice.doc_number, invoice_path( i ), :target => "_blank").to_s + " "
|
|
||||||
invoice_link = invoice_link.html_safe
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context[:controller].send(:render_to_string, {
|
|
||||||
:partial => 'issues/show_details',
|
|
||||||
locals: {
|
|
||||||
issue: issue,
|
|
||||||
customer: customer,
|
|
||||||
estimate_link: estimate_link,
|
|
||||||
invoice_link: invoice_link
|
|
||||||
}
|
|
||||||
})
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
#The MIT License (MIT)
|
#The MIT License (MIT)
|
||||||
#
|
#
|
||||||
#Copyright (c) 2022 rick barrette
|
#Copyright (c) 2016 - 2026 rick barrette
|
||||||
#
|
#
|
||||||
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
#
|
#
|
||||||
@@ -10,12 +10,13 @@
|
|||||||
|
|
||||||
require_dependency 'attachments_controller'
|
require_dependency 'attachments_controller'
|
||||||
|
|
||||||
module AttachmentsControllerPatch
|
module Patches
|
||||||
|
|
||||||
|
module AttachmentsControllerPatch
|
||||||
|
|
||||||
def self.included(base)
|
def self.included(base)
|
||||||
|
|
||||||
base.class_eval do
|
base.class_eval do
|
||||||
unloadable # Send unloadable so it will not be unloaded in development
|
|
||||||
|
|
||||||
# check if login is globally required to access the application
|
# check if login is globally required to access the application
|
||||||
def check_if_login_required
|
def check_if_login_required
|
||||||
@@ -37,7 +38,9 @@ module AttachmentsControllerPatch
|
|||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Add module to AttachmentsController
|
# Add module to AttachmentsController
|
||||||
AttachmentsController.send(:include, AttachmentsControllerPatch)
|
AttachmentsController.send(:include, AttachmentsControllerPatch)
|
||||||
|
|
||||||
|
end
|
||||||
115
lib/patches/issue_patch.rb
Normal file
115
lib/patches/issue_patch.rb
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
#The MIT License (MIT)
|
||||||
|
#
|
||||||
|
#Copyright (c) 2016 - 2026 rick barrette
|
||||||
|
#
|
||||||
|
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
#
|
||||||
|
#The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
require_dependency 'issue'
|
||||||
|
|
||||||
|
module Patches
|
||||||
|
|
||||||
|
|
||||||
|
# Patches Redmine's Issues dynamically.
|
||||||
|
# Adds a relationships
|
||||||
|
module IssuePatch
|
||||||
|
|
||||||
|
def self.included(base) # :nodoc:
|
||||||
|
base.extend(ClassMethods)
|
||||||
|
|
||||||
|
base.send(:include, InstanceMethods)
|
||||||
|
|
||||||
|
# Same as typing in the class
|
||||||
|
base.class_eval do
|
||||||
|
belongs_to :customer, primary_key: :id
|
||||||
|
belongs_to :customer_token, primary_key: :id
|
||||||
|
belongs_to :estimate, primary_key: :id
|
||||||
|
has_and_belongs_to_many :invoices
|
||||||
|
belongs_to :vehicle, primary_key: :id
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
module ClassMethods
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
module InstanceMethods
|
||||||
|
|
||||||
|
# Create billable time entries
|
||||||
|
def bill_time
|
||||||
|
|
||||||
|
# Check to see if we have everything we need to bill the customer
|
||||||
|
return if assigned_to.nil?
|
||||||
|
return unless Qbo.first
|
||||||
|
return unless customer
|
||||||
|
|
||||||
|
# Get unbilled time entries
|
||||||
|
spent_time = time_entries.where(billed: [false, nil])
|
||||||
|
spent_hours ||= spent_time.sum(:hours) || 0
|
||||||
|
|
||||||
|
if spent_hours > 0 then
|
||||||
|
|
||||||
|
# Prepare to create a new Time Activity
|
||||||
|
qbo = Qbo.first
|
||||||
|
qbo.perform_authenticated_request do |access_token|
|
||||||
|
time_service = Quickbooks::Service::TimeActivity.new(:company_id => qbo.realm_id, :access_token => access_token)
|
||||||
|
item_service = Quickbooks::Service::Item.new(:company_id => qbo.realm_id, :access_token => access_token)
|
||||||
|
time_entry = Quickbooks::Model::TimeActivity.new
|
||||||
|
|
||||||
|
# Lets total up each activity before billing.
|
||||||
|
# This will simpify the invoicing with a single billable time entry per time activity
|
||||||
|
h = Hash.new(0)
|
||||||
|
spent_time.each do |entry|
|
||||||
|
h[entry.activity.name] += entry.hours
|
||||||
|
# update time entries billed status
|
||||||
|
entry.billed = true
|
||||||
|
entry.save
|
||||||
|
end
|
||||||
|
|
||||||
|
# Now letes upload our totals for each activity as their own billable time entry
|
||||||
|
h.each do |key, val|
|
||||||
|
|
||||||
|
# Convert float spent time to hours and minutes
|
||||||
|
hours = val.to_i
|
||||||
|
minutesDecimal = (( val - hours) * 60)
|
||||||
|
minutes = minutesDecimal.to_i
|
||||||
|
|
||||||
|
# Lets match the activity to an qbo item
|
||||||
|
item = item_service.query("SELECT * FROM Item WHERE Name = '#{key}' ").first
|
||||||
|
next if item.nil?
|
||||||
|
|
||||||
|
# Create the new billable time entry and upload it
|
||||||
|
time_entry.description = "#{tracker} ##{id}: #{subject} #{"(Partial @ #{done_ratio}%)" if not closed?}"
|
||||||
|
time_entry.employee_id = assigned_to.employee_id
|
||||||
|
time_entry.customer_id = customer_id
|
||||||
|
time_entry.billable_status = "Billable"
|
||||||
|
time_entry.hours = hours
|
||||||
|
time_entry.minutes = minutes
|
||||||
|
time_entry.name_of = "Employee"
|
||||||
|
time_entry.txn_date = Date.today
|
||||||
|
time_entry.hourly_rate = item.unit_price
|
||||||
|
time_entry.item_id = item.id
|
||||||
|
time_entry.start_time = start_date
|
||||||
|
time_entry.end_time = Time.now
|
||||||
|
time_service.create(time_entry)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Create a shareable link for a customer
|
||||||
|
def share_token
|
||||||
|
CustomerToken.get_token self
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
# Add module to Issue
|
||||||
|
Issue.send(:include, IssuePatch)
|
||||||
|
|
||||||
|
end
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
#The MIT License (MIT)
|
#The MIT License (MIT)
|
||||||
#
|
#
|
||||||
#Copyright (c) 2022 rick barrette
|
#Copyright (c) 2016 - 2026 rick barrette
|
||||||
#
|
#
|
||||||
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
#
|
#
|
||||||
@@ -9,7 +9,9 @@
|
|||||||
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
require_dependency 'issues_controller'
|
require_dependency 'issues_controller'
|
||||||
|
|
||||||
module IssuesControllerPatch
|
module Patches
|
||||||
|
|
||||||
|
module IssuesControllerPatch
|
||||||
|
|
||||||
module Helper
|
module Helper
|
||||||
def watcher_link(issue, user)
|
def watcher_link(issue, user)
|
||||||
@@ -23,13 +25,14 @@ module IssuesControllerPatch
|
|||||||
def self.included(base)
|
def self.included(base)
|
||||||
|
|
||||||
base.class_eval do
|
base.class_eval do
|
||||||
unloadable # Send unloadable so it will not be unloaded in development
|
|
||||||
helper Helper
|
helper Helper
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Add module to IssuessController
|
# Add module to IssuessController
|
||||||
IssuesController.send(:include, IssuesControllerPatch)
|
IssuesController.send(:include, IssuesControllerPatch)
|
||||||
|
|
||||||
|
end
|
||||||
269
lib/patches/pdf_patch.rb
Normal file
269
lib/patches/pdf_patch.rb
Normal file
@@ -0,0 +1,269 @@
|
|||||||
|
#The MIT License (MIT)
|
||||||
|
#
|
||||||
|
#Copyright (c) 2016 - 2026 rick barrette
|
||||||
|
#
|
||||||
|
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
#
|
||||||
|
#The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
require_dependency 'redmine/export/pdf'
|
||||||
|
require_dependency 'redmine/export/pdf/issues_pdf_helper'
|
||||||
|
|
||||||
|
module Patches
|
||||||
|
|
||||||
|
module PdfPatch
|
||||||
|
|
||||||
|
def self.included(base)
|
||||||
|
base.send(:include, InstanceMethods)
|
||||||
|
base.class_eval do
|
||||||
|
alias_method :issue_to_pdf, :issue_to_pdf_with_patch
|
||||||
|
alias_method :issue_to_pdf_with_patch, :issue_to_pdf
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
module InstanceMethods
|
||||||
|
|
||||||
|
def issue_to_pdf_with_patch(issue, assoc={})
|
||||||
|
pdf = ::Redmine::Export::PDF::ITCPDF.new(current_language)
|
||||||
|
pdf.set_title("#{issue.project} - #{issue.tracker} ##{issue.id}")
|
||||||
|
pdf.alias_nb_pages
|
||||||
|
pdf.footer_date = format_date(Date.today)
|
||||||
|
pdf.add_page
|
||||||
|
pdf.SetFontStyle('B',11)
|
||||||
|
buf = "#{issue.project} - #{issue.tracker} ##{issue.id}"
|
||||||
|
pdf.RDMMultiCell(190, 5, buf)
|
||||||
|
pdf.SetFontStyle('',8)
|
||||||
|
base_x = pdf.get_x
|
||||||
|
i = 1
|
||||||
|
issue.ancestors.visible.each do |ancestor|
|
||||||
|
pdf.set_x(base_x + i)
|
||||||
|
buf = "#{ancestor.tracker} # #{ancestor.id} (#{ancestor.status.to_s}): #{ancestor.subject}"
|
||||||
|
pdf.RDMMultiCell(190 - i, 5, buf)
|
||||||
|
i += 1 if i < 35
|
||||||
|
end
|
||||||
|
pdf.SetFontStyle('B',11)
|
||||||
|
pdf.RDMMultiCell(190 - i, 5, issue.subject.to_s)
|
||||||
|
pdf.SetFontStyle('',8)
|
||||||
|
pdf.RDMMultiCell(190, 5, "#{format_time(issue.created_on)} - #{issue.author}")
|
||||||
|
pdf.ln
|
||||||
|
|
||||||
|
customer = issue.customer.name if issue.customer
|
||||||
|
left = []
|
||||||
|
left << [l(:field_status), issue.status]
|
||||||
|
left << [l(:field_priority), issue.priority]
|
||||||
|
left << [l(:field_customer), customer]
|
||||||
|
left << [l(:field_assigned_to), issue.assigned_to] unless issue.disabled_core_fields.include?('assigned_to_id')
|
||||||
|
#left << [l(:field_category), issue.category] unless issue.disabled_core_fields.include?('category_id')
|
||||||
|
#left << [l(:field_fixed_version), issue.fixed_version] unless issue.disabled_core_fields.include?('fixed_version_id')
|
||||||
|
|
||||||
|
v = Vehicle.find_by_id(issue.vehicles_id)
|
||||||
|
vehicle = v ? v.to_s : nil
|
||||||
|
vin = v ? v.vin : nil
|
||||||
|
notes = v ? v.notes : nil
|
||||||
|
left << [l(:field_vehicles), vehicle]
|
||||||
|
left << [l(:field_vin), vin ? vin.gsub(/(.{9})/, '\1 ') : nil]
|
||||||
|
#left << [l(:field_notes), notes]
|
||||||
|
|
||||||
|
right = []
|
||||||
|
right << [l(:field_start_date), format_date(issue.start_date)] unless issue.disabled_core_fields.include?('start_date')
|
||||||
|
right << [l(:field_due_date), format_date(issue.due_date)] unless issue.disabled_core_fields.include?('due_date')
|
||||||
|
right << [l(:field_done_ratio), "#{issue.done_ratio}%"] unless issue.disabled_core_fields.include?('done_ratio')
|
||||||
|
right << [l(:field_estimated_hours), l_hours(issue.estimated_hours)] unless issue.disabled_core_fields.include?('estimated_hours')
|
||||||
|
right << [l(:label_spent_time), l_hours(issue.total_spent_hours)] if User.current.allowed_to?(:view_time_entries, issue.project)
|
||||||
|
right << [l(:field_notes), notes]
|
||||||
|
|
||||||
|
rows = left.size > right.size ? left.size : right.size
|
||||||
|
while left.size < rows
|
||||||
|
left << nil
|
||||||
|
end
|
||||||
|
while right.size < rows
|
||||||
|
right << nil
|
||||||
|
end
|
||||||
|
|
||||||
|
half = (issue.visible_custom_field_values.size / 2.0).ceil
|
||||||
|
issue.visible_custom_field_values.each_with_index do |custom_value, i|
|
||||||
|
(i < half ? left : right) << [custom_value.custom_field.name, show_value(custom_value, false)]
|
||||||
|
end
|
||||||
|
|
||||||
|
if pdf.get_rtl
|
||||||
|
border_first_top = 'RT'
|
||||||
|
border_last_top = 'LT'
|
||||||
|
border_first = 'R'
|
||||||
|
border_last = 'L'
|
||||||
|
else
|
||||||
|
border_first_top = 'LT'
|
||||||
|
border_last_top = 'RT'
|
||||||
|
border_first = 'L'
|
||||||
|
border_last = 'R'
|
||||||
|
end
|
||||||
|
|
||||||
|
rows = left.size > right.size ? left.size : right.size
|
||||||
|
rows.times do |i|
|
||||||
|
heights = []
|
||||||
|
pdf.SetFontStyle('B',9)
|
||||||
|
item = left[i]
|
||||||
|
heights << pdf.get_string_height(35, item ? "#{item.first}:" : "")
|
||||||
|
item = right[i]
|
||||||
|
heights << pdf.get_string_height(35, item ? "#{item.first}:" : "")
|
||||||
|
pdf.SetFontStyle('',9)
|
||||||
|
item = left[i]
|
||||||
|
heights << pdf.get_string_height(60, item ? item.last.to_s : "")
|
||||||
|
item = right[i]
|
||||||
|
heights << pdf.get_string_height(60, item ? item.last.to_s : "")
|
||||||
|
height = heights.max
|
||||||
|
|
||||||
|
item = left[i]
|
||||||
|
pdf.SetFontStyle('B',9)
|
||||||
|
pdf.RDMMultiCell(35, height, item ? "#{item.first}:" : "", (i == 0 ? border_first_top : border_first), '', 0, 0)
|
||||||
|
pdf.SetFontStyle('',9)
|
||||||
|
pdf.RDMMultiCell(60, height, item ? item.last.to_s : "", (i == 0 ? border_last_top : border_last), '', 0, 0)
|
||||||
|
|
||||||
|
item = right[i]
|
||||||
|
pdf.SetFontStyle('B',9)
|
||||||
|
pdf.RDMMultiCell(35, height, item ? "#{item.first}:" : "", (i == 0 ? border_first_top : border_first), '', 0, 0)
|
||||||
|
pdf.SetFontStyle('',9)
|
||||||
|
pdf.RDMMultiCell(60, height, item ? item.last.to_s : "", (i == 0 ? border_last_top : border_last), '', 0, 2)
|
||||||
|
|
||||||
|
pdf.set_x(base_x)
|
||||||
|
end
|
||||||
|
|
||||||
|
pdf.SetFontStyle('B',9)
|
||||||
|
pdf.RDMCell(35+155, 5, l(:field_description), "LRT", 1)
|
||||||
|
pdf.SetFontStyle('',9)
|
||||||
|
|
||||||
|
# Set resize image scale
|
||||||
|
pdf.set_image_scale(1.6)
|
||||||
|
text = textilizable(issue, :description,
|
||||||
|
:only_path => false,
|
||||||
|
:edit_section_links => false,
|
||||||
|
:headings => false,
|
||||||
|
:inline_attachments => false
|
||||||
|
)
|
||||||
|
pdf.RDMwriteFormattedCell(35+155, 5, '', '', text, issue.attachments, "LRB")
|
||||||
|
|
||||||
|
unless issue.leaf?
|
||||||
|
truncate_length = (!is_cjk? ? 90 : 65)
|
||||||
|
pdf.SetFontStyle('B',9)
|
||||||
|
pdf.RDMCell(35+155,5, l(:label_subtask_plural) + ":", "LTR")
|
||||||
|
pdf.ln
|
||||||
|
issue_list(issue.descendants.visible.sort_by(&:lft)) do |child, level|
|
||||||
|
buf = "#{child.tracker} # #{child.id}: #{child.subject}".
|
||||||
|
truncate(truncate_length)
|
||||||
|
level = 10 if level >= 10
|
||||||
|
pdf.SetFontStyle('',8)
|
||||||
|
pdf.RDMCell(35+135,5, (level >=1 ? " " * level : "") + buf, border_first)
|
||||||
|
pdf.SetFontStyle('B',8)
|
||||||
|
pdf.RDMCell(20,5, child.status.to_s, border_last)
|
||||||
|
pdf.ln
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
relations = issue.relations.select { |r| r.other_issue(issue).visible? }
|
||||||
|
unless relations.empty?
|
||||||
|
truncate_length = (!is_cjk? ? 80 : 60)
|
||||||
|
pdf.SetFontStyle('B',9)
|
||||||
|
pdf.RDMCell(35+155,5, l(:label_related_issues) + ":", "LTR")
|
||||||
|
pdf.ln
|
||||||
|
relations.each do |relation|
|
||||||
|
buf = relation.to_s(issue) {|other|
|
||||||
|
text = ""
|
||||||
|
if Setting.cross_project_issue_relations?
|
||||||
|
text += "#{relation.other_issue(issue).project} - "
|
||||||
|
end
|
||||||
|
text += "#{other.tracker} ##{other.id}: #{other.subject}"
|
||||||
|
text
|
||||||
|
}
|
||||||
|
buf = buf.truncate(truncate_length)
|
||||||
|
pdf.SetFontStyle('', 8)
|
||||||
|
pdf.RDMCell(35+155-60, 5, buf, border_first)
|
||||||
|
pdf.SetFontStyle('B',8)
|
||||||
|
pdf.RDMCell(20,5, relation.other_issue(issue).status.to_s, "")
|
||||||
|
pdf.RDMCell(20,5, format_date(relation.other_issue(issue).start_date), "")
|
||||||
|
pdf.RDMCell(20,5, format_date(relation.other_issue(issue).due_date), border_last)
|
||||||
|
pdf.ln
|
||||||
|
end
|
||||||
|
end
|
||||||
|
pdf.RDMCell(190,5, "", "T")
|
||||||
|
pdf.ln
|
||||||
|
|
||||||
|
if issue.changesets.any? &&
|
||||||
|
User.current.allowed_to?(:view_changesets, issue.project)
|
||||||
|
pdf.SetFontStyle('B',9)
|
||||||
|
pdf.RDMCell(190,5, l(:label_associated_revisions), "B")
|
||||||
|
pdf.ln
|
||||||
|
for changeset in issue.changesets
|
||||||
|
pdf.SetFontStyle('B',8)
|
||||||
|
csstr = "#{l(:label_revision)} #{changeset.format_identifier} - "
|
||||||
|
csstr += format_time(changeset.committed_on) + " - " + changeset.author.to_s
|
||||||
|
pdf.RDMCell(190, 5, csstr)
|
||||||
|
pdf.ln
|
||||||
|
unless changeset.comments.blank?
|
||||||
|
pdf.SetFontStyle('',8)
|
||||||
|
pdf.RDMwriteHTMLCell(190,5,'','',
|
||||||
|
changeset.comments.to_s, issue.attachments, "")
|
||||||
|
end
|
||||||
|
pdf.ln
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if assoc[:journals].present?
|
||||||
|
pdf.SetFontStyle('B',9)
|
||||||
|
pdf.RDMCell(190,5, l(:label_history), "B")
|
||||||
|
pdf.ln
|
||||||
|
assoc[:journals].each do |journal|
|
||||||
|
pdf.SetFontStyle('B',8)
|
||||||
|
title = "##{journal.indice} - #{format_time(journal.created_on)} - #{journal.user}"
|
||||||
|
title << " (#{l(:field_private_notes)})" if journal.private_notes?
|
||||||
|
pdf.RDMCell(190,5, title)
|
||||||
|
pdf.ln
|
||||||
|
pdf.SetFontStyle('I',8)
|
||||||
|
details_to_strings(journal.visible_details, true).each do |string|
|
||||||
|
pdf.RDMMultiCell(190,5, "- " + string)
|
||||||
|
end
|
||||||
|
if journal.notes?
|
||||||
|
pdf.ln unless journal.details.empty?
|
||||||
|
pdf.SetFontStyle('',8)
|
||||||
|
text = textilizable(journal, :notes,
|
||||||
|
:only_path => false,
|
||||||
|
:edit_section_links => false,
|
||||||
|
:headings => false,
|
||||||
|
:inline_attachments => false
|
||||||
|
)
|
||||||
|
pdf.RDMwriteFormattedCell(190,5,'','', text, issue.attachments, "")
|
||||||
|
end
|
||||||
|
pdf.ln
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if issue.attachments.any?
|
||||||
|
pdf.SetFontStyle('B',9)
|
||||||
|
pdf.RDMCell(190,5, l(:label_attachment_plural), "B")
|
||||||
|
pdf.ln
|
||||||
|
for attachment in issue.attachments
|
||||||
|
pdf.SetFontStyle('',8)
|
||||||
|
pdf.RDMCell(80,5, attachment.filename)
|
||||||
|
pdf.RDMCell(20,5, number_to_human_size(attachment.filesize),0,0,"R")
|
||||||
|
pdf.RDMCell(25,5, format_date(attachment.created_on),0,0,"R")
|
||||||
|
pdf.RDMCell(65,5, attachment.author.name,0,0,"R")
|
||||||
|
pdf.ln
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Check to see if there is an estimate attached, then combine them
|
||||||
|
if issue.estimate
|
||||||
|
pdf = CombinePDF.parse(pdf.output, allow_optional_content: true)
|
||||||
|
pdf << CombinePDF.parse(issue.estimate.pdf)
|
||||||
|
return pdf.to_pdf
|
||||||
|
end
|
||||||
|
|
||||||
|
return pdf.output
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
Redmine::Export::PDF::IssuesPdfHelper.send(:include, PdfPatch)
|
||||||
|
|
||||||
|
end
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
#The MIT License (MIT)
|
#The MIT License (MIT)
|
||||||
#
|
#
|
||||||
#Copyright (c) 2017 rick barrette
|
#Copyright (c) 2016 - 2026 rick barrette
|
||||||
#
|
#
|
||||||
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
#
|
#
|
||||||
@@ -10,6 +10,8 @@
|
|||||||
|
|
||||||
require_dependency 'project'
|
require_dependency 'project'
|
||||||
|
|
||||||
|
module Patches
|
||||||
|
|
||||||
# Patches Redmine's Projects dynamically.
|
# Patches Redmine's Projects dynamically.
|
||||||
# Adds a relationships
|
# Adds a relationships
|
||||||
module ProjectPatch
|
module ProjectPatch
|
||||||
@@ -23,6 +25,7 @@ module ProjectPatch
|
|||||||
base.class_eval do
|
base.class_eval do
|
||||||
unloadable # Send unloadable so it will not be unloaded in development
|
unloadable # Send unloadable so it will not be unloaded in development
|
||||||
belongs_to :customer, primary_key: :id
|
belongs_to :customer, primary_key: :id
|
||||||
|
belongs_to :vehicle, primary_key: :id
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -37,3 +40,5 @@ end
|
|||||||
|
|
||||||
# Add module to Project
|
# Add module to Project
|
||||||
Project.send(:include, ProjectPatch)
|
Project.send(:include, ProjectPatch)
|
||||||
|
|
||||||
|
end
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
#The MIT License (MIT)
|
#The MIT License (MIT)
|
||||||
#
|
#
|
||||||
#Copyright (c) 2022 rick barrette
|
#Copyright (c) 2016 - 2026 rick barrette
|
||||||
#
|
#
|
||||||
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
#
|
#
|
||||||
@@ -10,7 +10,9 @@
|
|||||||
|
|
||||||
require_dependency 'issue_query'
|
require_dependency 'issue_query'
|
||||||
|
|
||||||
module QueryPatch
|
module Patches
|
||||||
|
|
||||||
|
module QueryPatch
|
||||||
|
|
||||||
# Add qbo options to the aviable columns
|
# Add qbo options to the aviable columns
|
||||||
def available_columns
|
def available_columns
|
||||||
@@ -28,7 +30,9 @@ module QueryPatch
|
|||||||
super
|
super
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Add module to Issue
|
# Add module to Issue
|
||||||
IssueQuery.send(:prepend, QueryPatch)
|
IssueQuery.send(:prepend, QueryPatch)
|
||||||
|
|
||||||
|
end
|
||||||
37
lib/patches/time_entry_query_patch.rb
Normal file
37
lib/patches/time_entry_query_patch.rb
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
#The MIT License (MIT)
|
||||||
|
#
|
||||||
|
#Copyright (c) 2016 - 2026 rick barrette
|
||||||
|
#
|
||||||
|
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
#
|
||||||
|
#The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
require_dependency 'time_entry_query'
|
||||||
|
|
||||||
|
module Patches
|
||||||
|
|
||||||
|
module TimeEntryQueryPatch
|
||||||
|
|
||||||
|
# Add QBO options to columns
|
||||||
|
def available_columns
|
||||||
|
unless @available_columns
|
||||||
|
@available_columns = self.class.available_columns.dup
|
||||||
|
@available_columns << QueryColumn.new(:billed, :sortable => "#{TimeEntry.table_name}.name", :groupable => true, :caption => :field_billed)
|
||||||
|
end
|
||||||
|
super
|
||||||
|
end
|
||||||
|
|
||||||
|
# Add QBO options to the filter
|
||||||
|
def initialize_available_filters
|
||||||
|
add_available_filter "billed", :type => :boolean
|
||||||
|
super
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
# Add module to TimeEntryQuery
|
||||||
|
TimeEntryQuery.send(:prepend, QueryPatch)
|
||||||
|
|
||||||
|
end
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
#The MIT License (MIT)
|
#The MIT License (MIT)
|
||||||
#
|
#
|
||||||
#Copyright (c) 2022 rick barrette
|
#Copyright (c) 2016 - 2026 rick barrette
|
||||||
#
|
#
|
||||||
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
#
|
#
|
||||||
@@ -10,9 +10,11 @@
|
|||||||
|
|
||||||
require_dependency 'user'
|
require_dependency 'user'
|
||||||
|
|
||||||
# Patches Redmine's User dynamically.
|
module Patches
|
||||||
# Adds a relationships
|
|
||||||
module UserPatch
|
# Patches Redmine's User dynamically.
|
||||||
|
# Adds a relationships
|
||||||
|
module UserPatch
|
||||||
def self.included(base) # :nodoc:
|
def self.included(base) # :nodoc:
|
||||||
base.extend(ClassMethods)
|
base.extend(ClassMethods)
|
||||||
|
|
||||||
@@ -20,7 +22,6 @@ module UserPatch
|
|||||||
|
|
||||||
# Same as typing in the class
|
# Same as typing in the class
|
||||||
base.class_eval do
|
base.class_eval do
|
||||||
unloadable # Send unloadable so it will not be unloaded in development
|
|
||||||
belongs_to :employee, primary_key: :id
|
belongs_to :employee, primary_key: :id
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -33,7 +34,9 @@ module UserPatch
|
|||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Add module to Issue
|
# Add module to Issue
|
||||||
User.send(:include, UserPatch)
|
User.send(:include, UserPatch)
|
||||||
|
|
||||||
|
end
|
||||||
256
lib/pdf_patch.rb
256
lib/pdf_patch.rb
@@ -1,256 +0,0 @@
|
|||||||
#The MIT License (MIT)
|
|
||||||
#
|
|
||||||
#Copyright (c) 2022 rick barrette
|
|
||||||
#
|
|
||||||
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
||||||
#
|
|
||||||
#The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
||||||
#
|
|
||||||
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
|
|
||||||
require_dependency 'redmine/export/pdf'
|
|
||||||
require_dependency 'redmine/export/pdf/issues_pdf_helper'
|
|
||||||
|
|
||||||
module IssuesPdfHelperPatch
|
|
||||||
|
|
||||||
def self.included(base)
|
|
||||||
base.send(:include, InstanceMethods)
|
|
||||||
base.class_eval do
|
|
||||||
unloadable # Send unloadable so it will not be unloaded in development
|
|
||||||
alias_method :issue_to_pdf, :issue_to_pdf_with_patch
|
|
||||||
alias_method :issue_to_pdf_with_patch, :issue_to_pdf
|
|
||||||
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
module InstanceMethods
|
|
||||||
|
|
||||||
def issue_to_pdf_with_patch(issue, assoc={})
|
|
||||||
pdf = ::Redmine::Export::PDF::ITCPDF.new(current_language)
|
|
||||||
pdf.set_title("#{issue.project} - #{issue.tracker} ##{issue.id}")
|
|
||||||
pdf.alias_nb_pages
|
|
||||||
pdf.footer_date = format_date(Date.today)
|
|
||||||
pdf.add_page
|
|
||||||
pdf.SetFontStyle('B',11)
|
|
||||||
buf = "#{issue.project} - #{issue.tracker} ##{issue.id}"
|
|
||||||
pdf.RDMMultiCell(190, 5, buf)
|
|
||||||
pdf.SetFontStyle('',8)
|
|
||||||
base_x = pdf.get_x
|
|
||||||
i = 1
|
|
||||||
issue.ancestors.visible.each do |ancestor|
|
|
||||||
pdf.set_x(base_x + i)
|
|
||||||
buf = "#{ancestor.tracker} # #{ancestor.id} (#{ancestor.status.to_s}): #{ancestor.subject}"
|
|
||||||
pdf.RDMMultiCell(190 - i, 5, buf)
|
|
||||||
i += 1 if i < 35
|
|
||||||
end
|
|
||||||
pdf.SetFontStyle('B',11)
|
|
||||||
pdf.RDMMultiCell(190 - i, 5, issue.subject.to_s)
|
|
||||||
pdf.SetFontStyle('',8)
|
|
||||||
pdf.RDMMultiCell(190, 5, "#{format_time(issue.created_on)} - #{issue.author}")
|
|
||||||
pdf.ln
|
|
||||||
|
|
||||||
customer = issue.customer.name if issue.customer
|
|
||||||
left = []
|
|
||||||
left << [l(:field_status), issue.status]
|
|
||||||
left << [l(:field_priority), issue.priority]
|
|
||||||
left << [l(:field_customer), customer]
|
|
||||||
left << [l(:field_assigned_to), issue.assigned_to] unless issue.disabled_core_fields.include?('assigned_to_id')
|
|
||||||
|
|
||||||
logger.debug "Calling :pdf_left hook"
|
|
||||||
context = Redmine::Hook.call_hook :pdf_left, { array: left, issue: issue }
|
|
||||||
left = left + context.first unless context.nil?
|
|
||||||
|
|
||||||
right = []
|
|
||||||
right << [l(:field_start_date), format_date(issue.start_date)] unless issue.disabled_core_fields.include?('start_date')
|
|
||||||
right << [l(:field_due_date), format_date(issue.due_date)] unless issue.disabled_core_fields.include?('due_date')
|
|
||||||
right << [l(:field_done_ratio), "#{issue.done_ratio}%"] unless issue.disabled_core_fields.include?('done_ratio')
|
|
||||||
right << [l(:field_estimated_hours), l_hours(issue.estimated_hours)] unless issue.disabled_core_fields.include?('estimated_hours')
|
|
||||||
right << [l(:label_spent_time), l_hours(issue.total_spent_hours)] if User.current.allowed_to?(:view_time_entries, issue.project)
|
|
||||||
|
|
||||||
logger.debug "Calling :pdf_right hook"
|
|
||||||
context = Redmine::Hook.call_hook :pdf_right, { array: right, issue: issue }
|
|
||||||
right = right + context.first unless context.nil?
|
|
||||||
|
|
||||||
rows = left.size > right.size ? left.size : right.size
|
|
||||||
while left.size < rows
|
|
||||||
left << nil
|
|
||||||
end
|
|
||||||
while right.size < rows
|
|
||||||
right << nil
|
|
||||||
end
|
|
||||||
|
|
||||||
half = (issue.visible_custom_field_values.size / 2.0).ceil
|
|
||||||
issue.visible_custom_field_values.each_with_index do |custom_value, i|
|
|
||||||
(i < half ? left : right) << [custom_value.custom_field.name, show_value(custom_value, false)]
|
|
||||||
end
|
|
||||||
|
|
||||||
if pdf.get_rtl
|
|
||||||
border_first_top = 'RT'
|
|
||||||
border_last_top = 'LT'
|
|
||||||
border_first = 'R'
|
|
||||||
border_last = 'L'
|
|
||||||
else
|
|
||||||
border_first_top = 'LT'
|
|
||||||
border_last_top = 'RT'
|
|
||||||
border_first = 'L'
|
|
||||||
border_last = 'R'
|
|
||||||
end
|
|
||||||
|
|
||||||
rows = left.size > right.size ? left.size : right.size
|
|
||||||
rows.times do |i|
|
|
||||||
heights = []
|
|
||||||
pdf.SetFontStyle('B',9)
|
|
||||||
item = left[i]
|
|
||||||
heights << pdf.get_string_height(35, item ? "#{item.first}:" : "")
|
|
||||||
item = right[i]
|
|
||||||
heights << pdf.get_string_height(35, item ? "#{item.first}:" : "")
|
|
||||||
pdf.SetFontStyle('',9)
|
|
||||||
item = left[i]
|
|
||||||
heights << pdf.get_string_height(60, item ? item.last.to_s : "")
|
|
||||||
item = right[i]
|
|
||||||
heights << pdf.get_string_height(60, item ? item.last.to_s : "")
|
|
||||||
height = heights.max
|
|
||||||
|
|
||||||
item = left[i]
|
|
||||||
pdf.SetFontStyle('B',9)
|
|
||||||
pdf.RDMMultiCell(35, height, item ? "#{item.first}:" : "", (i == 0 ? border_first_top : border_first), '', 0, 0)
|
|
||||||
pdf.SetFontStyle('',9)
|
|
||||||
pdf.RDMMultiCell(60, height, item ? item.last.to_s : "", (i == 0 ? border_last_top : border_last), '', 0, 0)
|
|
||||||
|
|
||||||
item = right[i]
|
|
||||||
pdf.SetFontStyle('B',9)
|
|
||||||
pdf.RDMMultiCell(35, height, item ? "#{item.first}:" : "", (i == 0 ? border_first_top : border_first), '', 0, 0)
|
|
||||||
pdf.SetFontStyle('',9)
|
|
||||||
pdf.RDMMultiCell(60, height, item ? item.last.to_s : "", (i == 0 ? border_last_top : border_last), '', 0, 2)
|
|
||||||
|
|
||||||
pdf.set_x(base_x)
|
|
||||||
end
|
|
||||||
|
|
||||||
pdf.SetFontStyle('B',9)
|
|
||||||
pdf.RDMCell(35+155, 5, l(:field_description), "LRT", 1)
|
|
||||||
pdf.SetFontStyle('',9)
|
|
||||||
|
|
||||||
# Set resize image scale
|
|
||||||
pdf.set_image_scale(1.6)
|
|
||||||
text = textilizable(issue, :description,
|
|
||||||
:only_path => false,
|
|
||||||
:edit_section_links => false,
|
|
||||||
:headings => false,
|
|
||||||
:inline_attachments => false
|
|
||||||
)
|
|
||||||
pdf.RDMwriteFormattedCell(35+155, 5, '', '', text, issue.attachments, "LRB")
|
|
||||||
|
|
||||||
unless issue.leaf?
|
|
||||||
truncate_length = (!is_cjk? ? 90 : 65)
|
|
||||||
pdf.SetFontStyle('B',9)
|
|
||||||
pdf.RDMCell(35+155,5, l(:label_subtask_plural) + ":", "LTR")
|
|
||||||
pdf.ln
|
|
||||||
issue_list(issue.descendants.visible.sort_by(&:lft)) do |child, level|
|
|
||||||
buf = "#{child.tracker} # #{child.id}: #{child.subject}".
|
|
||||||
truncate(truncate_length)
|
|
||||||
level = 10 if level >= 10
|
|
||||||
pdf.SetFontStyle('',8)
|
|
||||||
pdf.RDMCell(35+135,5, (level >=1 ? " " * level : "") + buf, border_first)
|
|
||||||
pdf.SetFontStyle('B',8)
|
|
||||||
pdf.RDMCell(20,5, child.status.to_s, border_last)
|
|
||||||
pdf.ln
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
relations = issue.relations.select { |r| r.other_issue(issue).visible? }
|
|
||||||
unless relations.empty?
|
|
||||||
truncate_length = (!is_cjk? ? 80 : 60)
|
|
||||||
pdf.SetFontStyle('B',9)
|
|
||||||
pdf.RDMCell(35+155,5, l(:label_related_issues) + ":", "LTR")
|
|
||||||
pdf.ln
|
|
||||||
relations.each do |relation|
|
|
||||||
buf = relation.to_s(issue) {|other|
|
|
||||||
text = ""
|
|
||||||
if Setting.cross_project_issue_relations?
|
|
||||||
text += "#{relation.other_issue(issue).project} - "
|
|
||||||
end
|
|
||||||
text += "#{other.tracker} ##{other.id}: #{other.subject}"
|
|
||||||
text
|
|
||||||
}
|
|
||||||
buf = buf.truncate(truncate_length)
|
|
||||||
pdf.SetFontStyle('', 8)
|
|
||||||
pdf.RDMCell(35+155-60, 5, buf, border_first)
|
|
||||||
pdf.SetFontStyle('B',8)
|
|
||||||
pdf.RDMCell(20,5, relation.other_issue(issue).status.to_s, "")
|
|
||||||
pdf.RDMCell(20,5, format_date(relation.other_issue(issue).start_date), "")
|
|
||||||
pdf.RDMCell(20,5, format_date(relation.other_issue(issue).due_date), border_last)
|
|
||||||
pdf.ln
|
|
||||||
end
|
|
||||||
end
|
|
||||||
pdf.RDMCell(190,5, "", "T")
|
|
||||||
pdf.ln
|
|
||||||
|
|
||||||
if issue.changesets.any? &&
|
|
||||||
User.current.allowed_to?(:view_changesets, issue.project)
|
|
||||||
pdf.SetFontStyle('B',9)
|
|
||||||
pdf.RDMCell(190,5, l(:label_associated_revisions), "B")
|
|
||||||
pdf.ln
|
|
||||||
for changeset in issue.changesets
|
|
||||||
pdf.SetFontStyle('B',8)
|
|
||||||
csstr = "#{l(:label_revision)} #{changeset.format_identifier} - "
|
|
||||||
csstr += format_time(changeset.committed_on) + " - " + changeset.author.to_s
|
|
||||||
pdf.RDMCell(190, 5, csstr)
|
|
||||||
pdf.ln
|
|
||||||
unless changeset.comments.blank?
|
|
||||||
pdf.SetFontStyle('',8)
|
|
||||||
pdf.RDMwriteHTMLCell(190,5,'','',
|
|
||||||
changeset.comments.to_s, issue.attachments, "")
|
|
||||||
end
|
|
||||||
pdf.ln
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if assoc[:journals].present?
|
|
||||||
pdf.SetFontStyle('B',9)
|
|
||||||
pdf.RDMCell(190,5, l(:label_history), "B")
|
|
||||||
pdf.ln
|
|
||||||
assoc[:journals].each do |journal|
|
|
||||||
pdf.SetFontStyle('B',8)
|
|
||||||
title = "##{journal.indice} - #{format_time(journal.created_on)} - #{journal.user}"
|
|
||||||
title << " (#{l(:field_private_notes)})" if journal.private_notes?
|
|
||||||
pdf.RDMCell(190,5, title)
|
|
||||||
pdf.ln
|
|
||||||
pdf.SetFontStyle('I',8)
|
|
||||||
details_to_strings(journal.visible_details, true).each do |string|
|
|
||||||
pdf.RDMMultiCell(190,5, "- " + string)
|
|
||||||
end
|
|
||||||
if journal.notes?
|
|
||||||
pdf.ln unless journal.details.empty?
|
|
||||||
pdf.SetFontStyle('',8)
|
|
||||||
text = textilizable(journal, :notes,
|
|
||||||
:only_path => false,
|
|
||||||
:edit_section_links => false,
|
|
||||||
:headings => false,
|
|
||||||
:inline_attachments => false
|
|
||||||
)
|
|
||||||
pdf.RDMwriteFormattedCell(190,5,'','', text, issue.attachments, "")
|
|
||||||
end
|
|
||||||
pdf.ln
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if issue.attachments.any?
|
|
||||||
pdf.SetFontStyle('B',9)
|
|
||||||
pdf.RDMCell(190,5, l(:label_attachment_plural), "B")
|
|
||||||
pdf.ln
|
|
||||||
for attachment in issue.attachments
|
|
||||||
pdf.SetFontStyle('',8)
|
|
||||||
pdf.RDMCell(80,5, attachment.filename)
|
|
||||||
pdf.RDMCell(20,5, number_to_human_size(attachment.filesize),0,0,"R")
|
|
||||||
pdf.RDMCell(25,5, format_date(attachment.created_on),0,0,"R")
|
|
||||||
pdf.RDMCell(65,5, attachment.author.name,0,0,"R")
|
|
||||||
pdf.ln
|
|
||||||
end
|
|
||||||
end
|
|
||||||
pdf.output
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
Redmine::Export::PDF::IssuesPdfHelper.send(:include, IssuesPdfHelperPatch)
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
#The MIT License (MIT)
|
|
||||||
#
|
|
||||||
#Copyright (c) 2017 rick barrette
|
|
||||||
#
|
|
||||||
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
||||||
#
|
|
||||||
#The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
||||||
#
|
|
||||||
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
|
|
||||||
class ProjectsFormHookListener < Redmine::Hook::ViewListener
|
|
||||||
|
|
||||||
# Edit Project Form
|
|
||||||
def view_projects_form(context={})
|
|
||||||
f = context[:form]
|
|
||||||
|
|
||||||
# Check to see if there is a quickbooks user attached to the issue
|
|
||||||
selected_customer = context[:project].customer ? context[:project].customer : nil
|
|
||||||
|
|
||||||
# Load customer information
|
|
||||||
customer = Customer.find_by_id(selected_customer) if selected_customer
|
|
||||||
search_customer = f.autocomplete_field :customer, autocomplete_customer_name_customers_path, :selected => selected_customer, :update_elements => {:id => '#project_customer_id', :value => '#project_customer'}
|
|
||||||
customer_id = f.hidden_field :customer_id, :id => "project_customer_id"
|
|
||||||
|
|
||||||
return "<p><label for=\"project_customer\">Customer</label>#{search_customer} #{customer_id}</p>"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
#The MIT License (MIT)
|
#The MIT License (MIT)
|
||||||
#
|
#
|
||||||
#Copyright (c) 2016 rick barrette
|
#Copyright (c) 2016 - 2026 rick barrette
|
||||||
#
|
#
|
||||||
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#The MIT License (MIT)
|
#The MIT License (MIT)
|
||||||
#
|
#
|
||||||
#Copyright (c) 2016 rick barrette
|
#Copyright (c) 2016 - 2026 rick barrette
|
||||||
#
|
#
|
||||||
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#The MIT License (MIT)
|
#The MIT License (MIT)
|
||||||
#
|
#
|
||||||
#Copyright (c) 2016 rick barrette
|
#Copyright (c) 2016 - 2026 rick barrette
|
||||||
#
|
#
|
||||||
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
#
|
#
|
||||||
|
|||||||
Reference in New Issue
Block a user