mirror of
https://github.com/rickbarrette/redmine_qbo.git
synced 2026-02-13 01:03:59 -05:00
Compare commits
178 Commits
2.0.5
...
6e11e05a24
| Author | SHA1 | Date | |
|---|---|---|---|
| 6e11e05a24 | |||
| a6751d3f41 | |||
| 8944e92ffc | |||
| f0c0a42c96 | |||
| a4b51457bb | |||
| fb4a883b43 | |||
| c24ec93335 | |||
| df49964bf9 | |||
| 502ba94465 | |||
| ff038fe5ae | |||
| 3eed122598 | |||
| d8d34540a9 | |||
| c01cc5ca97 | |||
| 6a2f7a1146 | |||
| f4c844f097 | |||
| 1135c69e1b | |||
| ef86d222cb | |||
| be88a601ae | |||
| e6c4e81df2 | |||
| f4a979672f | |||
| 8a4d64ffc0 | |||
| ac05d38763 | |||
| 548dc4fba8 | |||
| 7a73b7e8a9 | |||
| b38bd951f7 | |||
| 0e3318efdd | |||
| d063494bd2 | |||
| e35a2148eb | |||
| c8f115ae02 | |||
| d59e52b111 | |||
| 2c3548d1ac | |||
| d80007bc84 | |||
| 5d7d9a81bb | |||
| b030f85b74 | |||
| 2f0ee6a6d6 | |||
| 637cfa89b4 | |||
| c36f4c905b | |||
| 83fb20044d | |||
| 928e632dd3 | |||
| 8b9cf5066e | |||
| 45bfce87d8 | |||
| 6f33e9d23d | |||
| 92460392b9 | |||
| f1bdf59697 | |||
| 60e2f1d2b0 | |||
| 6c9ae82f81 | |||
| 42e4494f6e | |||
| 7e0b2c9d09 | |||
| 5ca68b01b6 | |||
| ebd4fa7363 | |||
| e6818958ae | |||
| 5b31459629 | |||
| 92de2928f6 | |||
| a8af180de2 | |||
| e621dc9e3a | |||
| c3d7c1c867 | |||
| defeec7f8e | |||
| 37c302e274 | |||
| 006e907b35 | |||
| f1f77a8022 | |||
| ff358d806e | |||
| b80e1d4e28 | |||
| f24128ef75 | |||
| d3a8c05f50 | |||
| f023cd246d | |||
| b7e18a3c3f | |||
| 67f2dbf4d8 | |||
| 924aa7657b | |||
| 16fe07f177 | |||
| 9257b2f938 | |||
| 0227681e92 | |||
| c034696810 | |||
| ffdabccd84 | |||
| 1f03908040 | |||
| 43a5317b4e | |||
| 4c49ec6890 | |||
| ef7faee685 | |||
| 02b48d2de4 | |||
| e670d99766 | |||
| 241dd594d0 | |||
| b603cb634a | |||
| 1308a05011 | |||
| 334ed60bf7 | |||
| d63bf809f2 | |||
| 31406af681 | |||
| 479be461a6 | |||
| c1af031d22 | |||
| a741cd0217 | |||
| 4ae9374401 | |||
| b096244454 | |||
| 4983cd661c | |||
| 5f6fb4af27 | |||
| 2f2c74403f | |||
| 43579d73e5 | |||
| a90d6b839f | |||
| e76f977ca8 | |||
| 7f821d241c | |||
| 1bc9227c7f | |||
| 3c2f1d0edd | |||
| 35e303d54b | |||
| 2aeb3fa028 | |||
| c85e45b544 | |||
| 6cd7825430 | |||
| 14f411c2e1 | |||
| 623510b474 | |||
| 20d9f0a84e | |||
| f741ce5dc9 | |||
| 72ec89292f | |||
| b54eb86b7f | |||
| f74f3ad72e | |||
| 0647b7708f | |||
| 7d644f0619 | |||
| b712c328ba | |||
| 5649ba05cd | |||
| bcdd515cf1 | |||
| 704dff2a72 | |||
| 55d00f9005 | |||
| eba3f529f8 | |||
| f0a3b0193c | |||
| 19733c3f8c | |||
| f22795ac90 | |||
| 166a9ee31b | |||
| 4d85c24872 | |||
| 43c7374c42 | |||
| 60857e9dca | |||
| d38f0d6ac1 | |||
| f6da031e72 | |||
| 9779437c00 | |||
| 1a37926628 | |||
| dac9a7c756 | |||
| 9ac1261ed0 | |||
| 9b69d3f728 | |||
| a5de879260 | |||
| 6464e1cbc6 | |||
| 7f3a94229a | |||
| 395e0117fb | |||
| e04d363e42 | |||
| 3b6c0d4a70 | |||
| d1f6ccd9cb | |||
| 74f7ba41df | |||
| 4fb424faa8 | |||
| 63218e7f42 | |||
| 7f0bb3cae7 | |||
| ad7417c233 | |||
| cf0be2336b | |||
| 6e08746611 | |||
| 7eb26facaf | |||
| 9115cc662c | |||
| 9e7c1dbfb2 | |||
| e99f5d2e52 | |||
| 039d1ca993 | |||
| dd9ac3c481 | |||
| 4f789080e7 | |||
| 80fc858a35 | |||
| 6f8d280657 | |||
| 5782cbc166 | |||
| 0729d2ac41 | |||
| 6c6de0ba86 | |||
| 11dbcaf80c | |||
| 95592e542f | |||
| 472bdec4fa | |||
| c7a313e9ed | |||
| c14b590083 | |||
| b531076c18 | |||
| 9e342ced28 | |||
| 0537d9bd86 | |||
| a531ef4f87 | |||
| 1fae647381 | |||
| d1764e2203 | |||
| f830881883 | |||
| fb87e8a33a | |||
| 8bdec410c4 | |||
| dec9eee90b | |||
| 2745ecf242 | |||
| 13472c3b3a | |||
| b686110145 | |||
| d91e7892c3 | |||
| f26224de56 |
3
Gemfile
3
Gemfile
@@ -3,10 +3,11 @@ source 'https://rubygems.org'
|
||||
gem 'quickbooks-ruby'
|
||||
gem 'oauth2'
|
||||
gem 'roxml'
|
||||
gem 'nhtsa_vin'
|
||||
gem 'will_paginate'
|
||||
gem 'rails-jquery-autocomplete'
|
||||
gem 'jquery-ui-rails'
|
||||
gem 'rexml'
|
||||
gem 'combine_pdf'
|
||||
|
||||
group :assets do
|
||||
gem 'coffee-rails'
|
||||
|
||||
2
LICENSE
2
LICENSE
@@ -1,6 +1,6 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016 - 2023 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
|
||||
|
||||
151
README.md
151
README.md
@@ -1,81 +1,110 @@
|
||||
# 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 for the following Redmine Versions
|
||||
* Version 2.0.0+ for Redmine 5+
|
||||
* Version 1.0.0+ for Redmine 4+
|
||||
* Version 0.8.1 for Redine 3
|
||||
## Compatibility
|
||||
|
||||
#### Features
|
||||
* 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
|
||||
* Employee is assigned to a user via a drop down in the user admistration page.
|
||||
* IF an Issue has been assined a Customer when an Issue is closed the following will happen:
|
||||
- A new Time Activity will be billed agaist the Customer assinged to the issue for each Redmine Time Entery.
|
||||
+ 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.
|
||||
+ 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
|
||||
* 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/edit via the Customer page
|
||||
* Webhook Support
|
||||
- Invoices are automaticly attached to an Issue if a line item has a hashtag number in a Line Item
|
||||
+ 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.
|
||||
- Customers are automaticly updated in local database
|
||||
| Plugin Version | Redmine Version |
|
||||
| :--- | :--- |
|
||||
| Version 2026.1.0+ | Redmine 6.1 |
|
||||
| Version 2.0.0+ | Redmine 5 |
|
||||
| Version 1.0.0+ | Redmine 4 |
|
||||
| Version 0.8.1 | Redmine 3 |
|
||||
|
||||
## Features
|
||||
|
||||
* **Customer Assignment:** Issues can be assigned to a Customer via a dropdown in the edit Issue form.
|
||||
* Once a customer is attached to an Issue, you can attach an Estimate to the issue via a dropdown menu.
|
||||
* **Employee Mapping:** An Employee is assigned to a Redmine User via a dropdown in the User Administration page.
|
||||
* **Automatic Billing:** If an Issue has been assigned a Customer, the following happens when the Issue is closed:
|
||||
* A new Time Activity will be billed against the Customer assigned to the issue for each Redmine Time Entry.
|
||||
* Time Entries will be totalled up by Activity name. This allows billing for different activities without having to create separate Issues.
|
||||
* The Time Activity names are used to dynamically lookup Items in QuickBooks.
|
||||
* 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.
|
||||
* **Plugin View Hooks** Allows intergration of other features supported by companion plugins, for example [redmine_qbo_vehicles](https://github.com/rickbarrette/redmine_qbo_vehicles) adds customer vehicle interation
|
||||
|
||||
## Prerequisites
|
||||
|
||||
* Sign up to become a developer for Intuit https://developer.intuit.com/
|
||||
* Create your own aplication to obtain your API keys
|
||||
* Set up webhook service to https://redmine.yourdomain.com/qbo/webhook
|
||||
* Sign up to become a developer for Intuit: https://developer.intuit.com/
|
||||
* Create your own application to obtain your API keys.
|
||||
* 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`
|
||||
|
||||
then
|
||||
|
||||
`git checkout <tag>`
|
||||
|
||||
2. Migrate your database
|
||||
|
||||
`rake redmine:plugins:migrate RAILS_ENV=production`
|
||||
|
||||
3. Navigate to the plugin configuration page and suppy your own OAuth key & secret.
|
||||
|
||||
4. After saving your key & secret, you need to click on the Authenticate link on the plugin configuration page to authenticate with QBO.
|
||||
|
||||
5. Assign an Employee to each of your users via the User Administration Page
|
||||
2. **Install dependencies:** *Crucial for Redmine 6 / Rails 7 compatibility.*
|
||||
|
||||
Bash
|
||||
|
||||
```
|
||||
bundle install
|
||||
```
|
||||
|
||||
3. **Migrate your database:**
|
||||
|
||||
Bash
|
||||
|
||||
```
|
||||
bundle exec rake redmine:plugins:migrate RAILS_ENV=production
|
||||
```
|
||||
|
||||
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**.
|
||||
|
||||
|
||||
## 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
|
||||
* Add Setting for Sandbox Mode
|
||||
* Seperate Vehicles into a seperate plugin (I use redmine for my automotive shop management 😉)
|
||||
* MORE Stuff as I make it up...
|
||||
## Companion Plugin Hooks
|
||||
* :pdf_left, { issue: issue }
|
||||
* :pdf_right, { issue: issue }
|
||||
* :process_invoice_custom_fields, { issue: issue, invoice: invoice }
|
||||
* :show_customer_view_right, {customer: @customer}
|
||||
|
||||
## License
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016 - 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.
|
||||
> 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.
|
||||
@@ -1,6 +1,6 @@
|
||||
#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:
|
||||
#
|
||||
@@ -10,7 +10,6 @@
|
||||
|
||||
# This controller class will handle map management
|
||||
class CustomersController < ApplicationController
|
||||
unloadable
|
||||
|
||||
include AuthHelper
|
||||
helper :issues
|
||||
@@ -27,24 +26,18 @@ class CustomersController < ApplicationController
|
||||
include SortHelper
|
||||
helper :timelog
|
||||
|
||||
before_action :add_customer, :only => :new
|
||||
before_action :view_customer, :except => [:new, :view]
|
||||
skip_before_action :verify_authenticity_token, :check_if_login_required, :only => [:view]
|
||||
before_action :add_customer, only: :new
|
||||
before_action :view_customer, except: [:new, :view]
|
||||
skip_before_action :verify_authenticity_token, :check_if_login_required, only: [:view]
|
||||
|
||||
default_search_scope :names
|
||||
|
||||
autocomplete :customer, :name, :full => true, :extra_data => [:id]
|
||||
autocomplete :customer, :name, full: true, extra_data: [:id]
|
||||
|
||||
def allowed_params
|
||||
params.require(:customer).permit(:name, :email, :primary_phone, :mobile_phone, :phone_number, :notes)
|
||||
end
|
||||
|
||||
# getter method for a customer's vehicles
|
||||
# used for customer autocomplete field / issue form
|
||||
def filter_vehicles_by_customer
|
||||
@filtered_vehicles = Vehicle.all.where(customer_id: params[:selected_customer])
|
||||
end
|
||||
|
||||
# getter method for a customer's invoices
|
||||
# used for customer autocomplete field / issue form
|
||||
def filter_invoices_by_customer
|
||||
@@ -60,7 +53,7 @@ class CustomersController < ApplicationController
|
||||
# display a list of all customers
|
||||
def index
|
||||
if params[:search]
|
||||
@customers = Customer.search(params[:search]).paginate(:page => params[:page])
|
||||
@customers = Customer.search(params[:search]).paginate(page: params[:page])
|
||||
if only_one_non_zero?(@customers)
|
||||
redirect_to @customers.first
|
||||
end
|
||||
@@ -76,7 +69,7 @@ class CustomersController < ApplicationController
|
||||
def create
|
||||
@customer = Customer.new(allowed_params)
|
||||
if @customer.save
|
||||
flash[:notice] = "New Customer Created"
|
||||
flash[:notice] = t :notice_customer_created
|
||||
redirect_to @customer
|
||||
else
|
||||
flash[:error] = @customer.errors.full_messages.to_sentence
|
||||
@@ -88,7 +81,6 @@ class CustomersController < ApplicationController
|
||||
def show
|
||||
begin
|
||||
@customer = Customer.find_by_id(params[:id])
|
||||
@vehicles = @customer.vehicles.paginate(:page => params[:page])
|
||||
@issues = @customer.issues.order(id: :desc)
|
||||
@billing_address = address_to_s(@customer.billing_address)
|
||||
@shipping_address = address_to_s(@customer.shipping_address)
|
||||
@@ -98,6 +90,7 @@ class CustomersController < ApplicationController
|
||||
@issues.open.each { |i| @hours+= i.total_spent_hours }
|
||||
@closed_issues.each { |i| @closed_hours+= i.total_spent_hours }
|
||||
rescue
|
||||
flash[:error] = t :notice_customer_not_found
|
||||
render_404
|
||||
end
|
||||
end
|
||||
@@ -107,6 +100,7 @@ class CustomersController < ApplicationController
|
||||
begin
|
||||
@customer = Customer.find_by_id(params[:id])
|
||||
rescue
|
||||
flash[:error] = t :notice_customer_not_found
|
||||
render_404
|
||||
end
|
||||
end
|
||||
@@ -116,13 +110,14 @@ class CustomersController < ApplicationController
|
||||
begin
|
||||
@customer = Customer.find_by_id(params[:id])
|
||||
if @customer.update(allowed_params)
|
||||
flash[:notice] = "Customer updated"
|
||||
flash[:notice] = t :notice_customer_updated
|
||||
redirect_to @customer
|
||||
else
|
||||
redirect_to edit_customer_path
|
||||
flash[:error] = @customer.errors.full_messages.to_sentence if @customer.errors
|
||||
end
|
||||
rescue
|
||||
flash[:error] = t :notice_customer_not_found
|
||||
render_404
|
||||
end
|
||||
end
|
||||
@@ -131,9 +126,10 @@ class CustomersController < ApplicationController
|
||||
def destroy
|
||||
begin
|
||||
Customer.find_by_id(params[:id]).destroy
|
||||
flash[:notice] = "Customer deleted successfully"
|
||||
flash[:notice] = t :notice_customer_deleted
|
||||
redirect_to action: :index
|
||||
rescue
|
||||
flash[:error] = t :notice_customer_not_deleted
|
||||
render_404
|
||||
end
|
||||
end
|
||||
@@ -142,7 +138,7 @@ class CustomersController < ApplicationController
|
||||
def share
|
||||
|
||||
Thread.new do
|
||||
logger.debug "Removing expired customer tokens"
|
||||
logger.info "Removing expired customer tokens"
|
||||
CustomerToken.remove_expired_tokens
|
||||
ActiveRecord::Base.connection.close
|
||||
end
|
||||
@@ -151,6 +147,7 @@ class CustomersController < ApplicationController
|
||||
issue = Issue.find_by_id(params[:id])
|
||||
redirect_to view_path issue.share_token.token
|
||||
rescue
|
||||
flash[:error] = t :notice_issue_not_found
|
||||
render_404
|
||||
end
|
||||
end
|
||||
@@ -169,7 +166,7 @@ class CustomersController < ApplicationController
|
||||
@issue = Issue.find @token.issue_id
|
||||
@journals = @issue.journals.
|
||||
preload(:details).
|
||||
preload(:user => :email_address).
|
||||
preload(user: :email_address).
|
||||
reorder(:created_on, :id).to_a
|
||||
@journals.each_with_index {|j,i| j.indice = i+1}
|
||||
@journals.reject!(&:private_notes?) unless User.current.allowed_to?(:view_private_notes, @issue.project)
|
||||
@@ -183,9 +180,10 @@ class CustomersController < ApplicationController
|
||||
@relations = @issue.relations.select {|r| r.other_issue(@issue) && r.other_issue(@issue).visible? }
|
||||
@allowed_statuses = @issue.new_statuses_allowed_to(User.current)
|
||||
@priorities = IssuePriority.active
|
||||
@time_entry = TimeEntry.new(:issue => @issue, :project => @issue.project)
|
||||
@time_entry = TimeEntry.new(issue: @issue, project: @issue.project)
|
||||
@relation = IssueRelation.new
|
||||
rescue
|
||||
flash[:error] = t :notice_forbidden
|
||||
render_403
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#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:
|
||||
#
|
||||
@@ -8,14 +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.
|
||||
class EstimateController < ApplicationController
|
||||
unloadable
|
||||
|
||||
include AuthHelper
|
||||
|
||||
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? }
|
||||
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? }
|
||||
|
||||
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_doc_number(params[:search]) if params[:search]
|
||||
return estimate
|
||||
@@ -28,9 +36,9 @@ class EstimateController < ApplicationController
|
||||
estimate = get_estimate
|
||||
|
||||
begin
|
||||
send_data estimate.pdf, filename: "estimate #{estimate.doc_number}.pdf", :disposition => 'inline', :type => "application/pdf"
|
||||
send_data estimate.pdf, filename: "estimate #{estimate.doc_number}.pdf", disposition: :inline, type: "application/pdf"
|
||||
rescue
|
||||
redirect_to :back, :flash => { :error => "Estimate not found" }
|
||||
redirect_to :back, flash: { error: I18n.t(:notice_estimate_not_found) }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -41,9 +49,9 @@ class EstimateController < ApplicationController
|
||||
estimate = get_estimate
|
||||
|
||||
begin
|
||||
send_data estimate.pdf, filename: "estimate #{estimate.doc_number}.pdf", :disposition => 'inline', :type => "application/pdf"
|
||||
send_data estimate.pdf, filename: "estimate #{estimate.doc_number}.pdf", disposition: :inline, type: "application/pdf"
|
||||
rescue
|
||||
redirect_to :back, :flash => { :error => "Estimate not found" }
|
||||
redirect_to :back, flash: { error: I18n.t(:notice_estimate_not_found) }
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#The MIT License (MIT)
|
||||
#
|
||||
#Copyright (c) 2023 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:
|
||||
#
|
||||
@@ -8,27 +8,47 @@
|
||||
#
|
||||
#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
|
||||
unloadable
|
||||
|
||||
include AuthHelper
|
||||
|
||||
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? }
|
||||
require 'combine_pdf'
|
||||
|
||||
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? }
|
||||
|
||||
#
|
||||
# Downloads and forwards the invoice pdf
|
||||
#
|
||||
def show
|
||||
logger.info("Processing request for URL: #{request.original_url}")
|
||||
begin
|
||||
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(params[:id])
|
||||
@pdf = service.pdf(invoice)
|
||||
send_data @pdf, filename: "invoice #{invoice.doc_number}.pdf", :disposition => 'inline', :type => "application/pdf"
|
||||
service = Quickbooks::Service::Invoice.new(company_id: qbo.realm_id, access_token: access_token)
|
||||
|
||||
# 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
|
||||
redirect_to :back, :flash => { :error => "Invoice not found" }
|
||||
redirect_to :back, flash: { error: I18n.t(:notice_invoice_not_found) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#The MIT License (MIT)
|
||||
#
|
||||
#Copyright (c) 2023 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:
|
||||
#
|
||||
@@ -9,14 +9,13 @@
|
||||
#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
|
||||
unloadable
|
||||
|
||||
require 'openssl'
|
||||
|
||||
include AuthHelper
|
||||
|
||||
before_action :require_user, :except => :webhook
|
||||
skip_before_action :verify_authenticity_token, :check_if_login_required, :only => [:webhook]
|
||||
before_action :require_user, except: :webhook
|
||||
skip_before_action :verify_authenticity_token, :check_if_login_required, only: [:webhook]
|
||||
|
||||
def allowed_params
|
||||
params.permit(:code, :state, :realmId, :id)
|
||||
@@ -26,9 +25,10 @@ class QboController < ApplicationController
|
||||
# Called when the user requests that Redmine to connect to QBO
|
||||
#
|
||||
def authenticate
|
||||
redirect_uri = "#{Setting.protocol}://#{Setting.host_name + qbo_oauth_callback_path}"
|
||||
logger.info "redirect_uri: #{redirect_uri}"
|
||||
oauth2_client = Qbo.construct_oauth2_client
|
||||
callback = Setting.host_name + "/qbo/oauth_callback/"
|
||||
grant_url = oauth2_client.auth_code.authorize_url(redirect_uri: callback, response_type: "code", state: SecureRandom.hex(12), scope: "com.intuit.quickbooks.accounting")
|
||||
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
|
||||
end
|
||||
|
||||
@@ -39,7 +39,7 @@ class QboController < ApplicationController
|
||||
if params[:state].present?
|
||||
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
|
||||
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)
|
||||
|
||||
# Remove the last authentication information
|
||||
@@ -51,9 +51,9 @@ class QboController < ApplicationController
|
||||
qbo.refresh_token!
|
||||
|
||||
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
|
||||
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
|
||||
@@ -65,9 +65,9 @@ class QboController < ApplicationController
|
||||
i = Issue.find_by_id params[:id]
|
||||
if i.customer
|
||||
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
|
||||
redirect_to i, :flash => { :error => "Cannot bill without a customer assigned" }
|
||||
redirect_to i, flash: { error: I18n.t(:label_billing_error) }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -84,46 +84,48 @@ class QboController < ApplicationController
|
||||
|
||||
# proceed if the request is good
|
||||
if hash.eql? signature
|
||||
if request.headers['content-type'] == 'application/json'
|
||||
data = JSON.parse(data)
|
||||
else
|
||||
# application/x-www-form-urlencoded
|
||||
data = params.as_json
|
||||
end
|
||||
# Process the information
|
||||
entities = data['eventNotifications'][0]['dataChangeEvent']['entities']
|
||||
entities.each do |entity|
|
||||
id = entity['id'].to_i
|
||||
name = entity['name']
|
||||
|
||||
logger.debug "Casting #{name.constantize} to obj"
|
||||
|
||||
# Magicly initialize the correct class
|
||||
obj = name.constantize
|
||||
|
||||
# for merge events
|
||||
obj.destroy(entity['deletedId']) if entity['deletedId']
|
||||
|
||||
#Check to see if we are deleting a record
|
||||
if entity['operation'].eql? "Delete"
|
||||
obj.destroy(id)
|
||||
#if not then update!
|
||||
Thread.new do
|
||||
if request.headers['content-type'] == 'application/json'
|
||||
data = JSON.parse(data)
|
||||
else
|
||||
begin
|
||||
obj.sync_by_id(id)
|
||||
rescue => e
|
||||
logger.error "Failed to call sync_by_id on obj"
|
||||
logger.error e.message
|
||||
logger.error e.backtrace.join("\n")
|
||||
# application/x-www-form-urlencoded
|
||||
data = params.as_json
|
||||
end
|
||||
# Process the information
|
||||
entities = data['eventNotifications'][0]['dataChangeEvent']['entities']
|
||||
entities.each do |entity|
|
||||
id = entity['id'].to_i
|
||||
name = entity['name']
|
||||
|
||||
logger.info "Casting #{name.constantize} to obj"
|
||||
|
||||
# Magicly initialize the correct class
|
||||
obj = name.constantize
|
||||
|
||||
# for merge events
|
||||
obj.destroy(entity['deletedId']) if entity['deletedId']
|
||||
|
||||
#Check to see if we are deleting a record
|
||||
if entity['operation'].eql? "Delete"
|
||||
obj.destroy(id)
|
||||
#if not then update!
|
||||
else
|
||||
begin
|
||||
obj.sync_by_id(id)
|
||||
rescue => e
|
||||
logger.error "Failed to call sync_by_id on obj"
|
||||
logger.error e.message
|
||||
logger.error e.backtrace.join("\n")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Record that last time we updated
|
||||
Qbo.update_time_stamp
|
||||
ActiveRecord::Base.connection.close
|
||||
end
|
||||
|
||||
# Record that last time we updated
|
||||
Qbo.update_time_stamp
|
||||
|
||||
# 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
|
||||
render nothing: true, status: 400
|
||||
end
|
||||
@@ -150,6 +152,6 @@ class QboController < ApplicationController
|
||||
ActiveRecord::Base.connection.close
|
||||
end
|
||||
|
||||
redirect_to :home, :flash => { :notice => "Syncing Quickbooks" }
|
||||
redirect_to :home, flash: { notice: I18n.t(:label_syncing) }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,126 +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.
|
||||
|
||||
# This controller class will handle map management
|
||||
class VehiclesController < ApplicationController
|
||||
unloadable
|
||||
|
||||
include AuthHelper
|
||||
|
||||
before_action :require_user
|
||||
|
||||
def allowed_params
|
||||
params.require(:vehicle).permit(:year, :make, :model, :customer_id, :notes, :vin)
|
||||
end
|
||||
|
||||
# display a list of all vehicles
|
||||
def index
|
||||
if params[:customer_id]
|
||||
begin
|
||||
@vehicles = Customer.find_by_id(params[:customer_id]).vehicles.paginate(:page => params[:page])
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
render_404
|
||||
end
|
||||
end
|
||||
|
||||
# search for a vehicle by vin
|
||||
if params[:search]
|
||||
@vehicles = Vehicle.search(params[:search]).paginate(:page => params[:page])
|
||||
if only_one_non_zero?(@vehicles)
|
||||
redirect_to @vehicles.first
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# return an HTML form for creating a new vehicle
|
||||
def new
|
||||
@vehicle = Vehicle.new
|
||||
@customer = Customer.find_by_id(params[:customer_id]) if params[:customer_id]
|
||||
end
|
||||
|
||||
# create a new vehicle
|
||||
def create
|
||||
@vehicle = Vehicle.new(allowed_params)
|
||||
if @vehicle.save
|
||||
flash[:notice] = "New Vehicle Created"
|
||||
redirect_to @vehicle
|
||||
else
|
||||
flash[:error] = @vehicle.errors.full_messages.to_sentence
|
||||
redirect_to Vehicle.find_by_vin @vehicle.vin
|
||||
end
|
||||
end
|
||||
|
||||
# display a specific vehicle
|
||||
def show
|
||||
begin
|
||||
@vehicle = Vehicle.find_by_id(params[:id])
|
||||
@vin = @vehicle.vin.scan(/.{1,9}/) if @vehicle.vin
|
||||
@issues = @vehicle.issues.order(id: :desc)
|
||||
@closed_issues = (@issues - @issues.open)
|
||||
rescue
|
||||
render_404
|
||||
end
|
||||
end
|
||||
|
||||
# return an HTML form for editing a vehicle
|
||||
def edit
|
||||
begin
|
||||
@vehicle = Vehicle.find_by_id(params[:id])
|
||||
@customer = @vehicle.customer
|
||||
rescue
|
||||
render_404
|
||||
end
|
||||
end
|
||||
|
||||
# update a specific vehicle
|
||||
def update
|
||||
@customer = params[:customer]
|
||||
begin
|
||||
@vehicle = Vehicle.find_by_id(params[:id])
|
||||
if @vehicle.update(allowed_params)
|
||||
flash[:notice] = "Vehicle updated"
|
||||
redirect_to @vehicle
|
||||
else
|
||||
redirect_to edit_vehicle_path
|
||||
end
|
||||
#show any errors anyways
|
||||
flash[:error] = @vehicle.errors.full_messages.to_sentence unless @vehicle.errors.empty?
|
||||
rescue
|
||||
render_404
|
||||
end
|
||||
end
|
||||
|
||||
# delete a specific vehicle
|
||||
def destroy
|
||||
begin
|
||||
Vehicle.find_by_id(params[:id]).destroy
|
||||
flash[:notice] = "Vehicle deleted successfully"
|
||||
redirect_to action: :index
|
||||
rescue
|
||||
render_404
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# checks to see if there is only one item in an array
|
||||
# @return true if array only has one item
|
||||
def only_one_non_zero?( array )
|
||||
found_non_zero = false
|
||||
array.each do |val|
|
||||
if val!=0
|
||||
return false if found_non_zero
|
||||
found_non_zero = true
|
||||
end
|
||||
end
|
||||
found_non_zero
|
||||
end
|
||||
|
||||
end
|
||||
@@ -1,6 +1,6 @@
|
||||
#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:
|
||||
#
|
||||
@@ -13,6 +13,7 @@ module AuthHelper
|
||||
def require_user
|
||||
return unless session[:token].nil?
|
||||
if !User.current.logged?
|
||||
flash[:error] = t :notice_forbidden
|
||||
render_403
|
||||
end
|
||||
end
|
||||
@@ -27,6 +28,7 @@ module AuthHelper
|
||||
|
||||
def check_permission(permission)
|
||||
if !allowed_to?(permission)
|
||||
flash[:error] = t :notice_forbidden
|
||||
render_403
|
||||
end
|
||||
end
|
||||
@@ -34,6 +36,7 @@ module AuthHelper
|
||||
|
||||
def global_check_permission(permission)
|
||||
if !globaly_allowed_to?(permission)
|
||||
flash[:error] = t :notice_forbidden
|
||||
render_403
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#The MIT License (MIT)
|
||||
#
|
||||
#Copyright (c) 2023 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:
|
||||
#
|
||||
@@ -11,9 +11,6 @@
|
||||
module QuickbooksOauth
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
OAUTH_CONSUMER_KEY = Setting.plugin_redmine_qbo['settingsOAuthConsumerKey']
|
||||
OAUTH_CONSUMER_SECRET = Setting.plugin_redmine_qbo['settingsOAuthConsumerSecret']
|
||||
|
||||
#== Instance Methods
|
||||
|
||||
def perform_authenticated_request(&block)
|
||||
@@ -21,7 +18,7 @@ module QuickbooksOauth
|
||||
begin
|
||||
yield oauth_access_token
|
||||
rescue OAuth2::Error, Quickbooks::AuthorizationFailure => ex
|
||||
Rails.logger.info("QuickbooksOauth.perform: #{ex.message}")
|
||||
Rails.logger.error("QuickbooksOauth.perform: #{ex.message}")
|
||||
|
||||
# to prevent an infinite loop here keep a counter and bail out after N times...
|
||||
attempts += 1
|
||||
@@ -36,6 +33,7 @@ module QuickbooksOauth
|
||||
end
|
||||
|
||||
def refresh_token!
|
||||
Rails.logger.info("QuickbooksOauth.refresh_token!")
|
||||
t = oauth_access_token
|
||||
refreshed = t.refresh!
|
||||
|
||||
@@ -45,6 +43,8 @@ module QuickbooksOauth
|
||||
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),
|
||||
@@ -68,12 +68,20 @@ module QuickbooksOauth
|
||||
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)
|
||||
OAuth2::Client.new(oauth_consumer_key, oauth_consumer_secret, options)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#The MIT License (MIT)
|
||||
#
|
||||
#Copyright (c) 2023 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:
|
||||
#
|
||||
@@ -9,14 +9,11 @@
|
||||
#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
|
||||
unloadable
|
||||
|
||||
has_many :issues
|
||||
has_many :purchases
|
||||
has_many :invoices
|
||||
has_many :estimates
|
||||
has_many :vehicles
|
||||
|
||||
|
||||
validates_presence_of :id, :name
|
||||
|
||||
self.primary_key = :id
|
||||
@@ -145,7 +142,7 @@ class Customer < ActiveRecord::Base
|
||||
# Sync ALL customers if the database is empty
|
||||
qbo = Qbo.first
|
||||
customers = qbo.perform_authenticated_request do |access_token|
|
||||
service = Quickbooks::Service::Customer.new(:company_id => qbo.realm_id, :access_token => access_token)
|
||||
service = Quickbooks::Service::Customer.new(company_id: qbo.realm_id, access_token: access_token)
|
||||
service.all
|
||||
end
|
||||
|
||||
@@ -181,7 +178,7 @@ class Customer < ActiveRecord::Base
|
||||
def self.sync_by_id(id)
|
||||
qbo = Qbo.first
|
||||
c = qbo.perform_authenticated_request do |access_token|
|
||||
service = Quickbooks::Service::Customer.new(:company_id => qbo.realm_id, :access_token => access_token)
|
||||
service = Quickbooks::Service::Customer.new(company_id: qbo.realm_id, access_token: access_token)
|
||||
service.fetch_by_id(id)
|
||||
end
|
||||
|
||||
@@ -208,7 +205,7 @@ class Customer < ActiveRecord::Base
|
||||
begin
|
||||
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 = Quickbooks::Service::Customer.new(company_id: qbo.realm_id, access_token: access_token)
|
||||
service.update(@details)
|
||||
end
|
||||
#raise "QBO Fault" if @details.fault?
|
||||
@@ -230,7 +227,7 @@ class Customer < ActiveRecord::Base
|
||||
raise Exception unless 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 = Quickbooks::Service::Customer.new(company_id: qbo.realm_id, access_token: access_token)
|
||||
service.fetch_by_id(self.id)
|
||||
end
|
||||
rescue Exception => e
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#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:
|
||||
#
|
||||
@@ -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.
|
||||
|
||||
class CustomerToken < ActiveRecord::Base
|
||||
unloadable
|
||||
|
||||
has_many :issues
|
||||
validates_presence_of :issue_id
|
||||
before_create :generate_token, :generate_expire_date
|
||||
@@ -55,7 +55,7 @@ class CustomerToken < ActiveRecord::Base
|
||||
end
|
||||
|
||||
# only create new token if we have an issue to attach it to
|
||||
return create(:issue_id => issue.id) if User.current.logged?
|
||||
return create(issue_id: issue.id) if User.current.logged?
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#The MIT License (MIT)
|
||||
#
|
||||
#Copyright (c) 2023 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:
|
||||
#
|
||||
@@ -9,21 +9,20 @@
|
||||
#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
|
||||
unloadable
|
||||
|
||||
has_many :users
|
||||
validates_presence_of :id, :name
|
||||
|
||||
def self.sync
|
||||
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 = Quickbooks::Service::Employee.new(company_id: qbo.realm_id, access_token: access_token)
|
||||
service.all
|
||||
end
|
||||
|
||||
return unless employees
|
||||
|
||||
transaction do
|
||||
# Update the item table
|
||||
employees.each { |e|
|
||||
logger.info "Processing employee #{e.id}"
|
||||
employee = find_or_create_by(id: e.id)
|
||||
@@ -37,7 +36,7 @@ class Employee < ActiveRecord::Base
|
||||
def self.sync_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 = Quickbooks::Service::Employee.new(company_id: qbo.realm_id, access_token: access_token)
|
||||
service.fetch_by_id(id)
|
||||
end
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#The MIT License (MIT)
|
||||
#
|
||||
#Copyright (c) 2023 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:
|
||||
#
|
||||
@@ -9,19 +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.
|
||||
|
||||
class Estimate < ActiveRecord::Base
|
||||
unloadable
|
||||
|
||||
has_and_belongs_to_many :issues
|
||||
belongs_to :customer
|
||||
validates_presence_of :doc_number, :id
|
||||
self.primary_key = :id
|
||||
|
||||
# returns a human readable string
|
||||
def to_s
|
||||
return self[:doc_number]
|
||||
end
|
||||
|
||||
# sync all estimates
|
||||
def self.sync
|
||||
logger.debug "Syncing ALL estimates"
|
||||
logger.info "Syncing ALL estimates"
|
||||
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 = Quickbooks::Service::Estimate.new(company_id: qbo.realm_id, access_token: access_token)
|
||||
service.all
|
||||
end
|
||||
|
||||
@@ -37,20 +41,29 @@ class Estimate < ActiveRecord::Base
|
||||
|
||||
# sync only one estimate
|
||||
def self.sync_by_id(id)
|
||||
logger.debug "Syncing estimate #{id}"
|
||||
logger.info "Syncing estimate #{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)
|
||||
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
|
||||
|
||||
# update an estimate
|
||||
def self.update(id)
|
||||
# Update the item table
|
||||
qbo = Qbo.first
|
||||
estimate = qbo.perform_authenticated_request do |access_token|
|
||||
service = Quickbooks::Service::Estimate.new(:company_id => qbo.realm_id, :access_token => access_token)
|
||||
service = Quickbooks::Service::Estimate.new(company_id: qbo.realm_id, access_token: access_token)
|
||||
service.fetch_by_id(id)
|
||||
end
|
||||
|
||||
@@ -77,7 +90,7 @@ class Estimate < ActiveRecord::Base
|
||||
def pdf
|
||||
qbo = Qbo.first
|
||||
qbo.perform_authenticated_request do |access_token|
|
||||
service = Quickbooks::Service::Estimate.new(:company_id => qbo.realm_id, :access_token => access_token)
|
||||
service = Quickbooks::Service::Estimate.new(company_id: qbo.realm_id, access_token: access_token)
|
||||
estimate = service.fetch_by_id(id)
|
||||
service.pdf(estimate)
|
||||
end
|
||||
@@ -109,7 +122,7 @@ class Estimate < ActiveRecord::Base
|
||||
raise Exception unless 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 = Quickbooks::Service::Estimate.new(company_id: qbo.realm_id, access_token: access_token)
|
||||
service(:estimate).fetch_by_id(self.id)
|
||||
end
|
||||
rescue Exception => e
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#The MIT License (MIT)
|
||||
#
|
||||
#Copyright (c) 2023 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:
|
||||
#
|
||||
@@ -9,15 +9,20 @@
|
||||
#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
|
||||
unloadable
|
||||
|
||||
has_and_belongs_to_many :issues
|
||||
belongs_to :customer
|
||||
validates_presence_of :doc_number, :id, :customer_id, :txn_date
|
||||
self.primary_key = :id
|
||||
|
||||
# returns a human readable string
|
||||
def to_s
|
||||
return self[:doc_number]
|
||||
end
|
||||
|
||||
# sync ALL the invoices
|
||||
def self.sync
|
||||
logger.debug "Syncing all invoices"
|
||||
logger.info "Syncing all invoices"
|
||||
last = Qbo.first.last_sync
|
||||
|
||||
query = "SELECT Id, DocNumber FROM Invoice"
|
||||
@@ -27,7 +32,7 @@ class Invoice < ActiveRecord::Base
|
||||
# .all() is never called since count is never initialized
|
||||
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 = Quickbooks::Service::Invoice.new(company_id: qbo.realm_id, access_token: access_token)
|
||||
service.all
|
||||
end
|
||||
|
||||
@@ -40,10 +45,10 @@ class Invoice < ActiveRecord::Base
|
||||
|
||||
#sync by invoice ID
|
||||
def self.sync_by_id(id)
|
||||
logger.debug "Syncing invoice #{id}"
|
||||
logger.info "Syncing invoice #{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)
|
||||
service = Quickbooks::Service::Invoice.new(company_id: qbo.realm_id, access_token: access_token)
|
||||
invoice = service.fetch_by_id(id)
|
||||
process_invoice invoice
|
||||
end
|
||||
@@ -58,7 +63,7 @@ class Invoice < ActiveRecord::Base
|
||||
# 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
|
||||
|
||||
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)
|
||||
|
||||
@@ -105,61 +110,49 @@ class Invoice < ActiveRecord::Base
|
||||
# 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
|
||||
def self.compare_custom_fields(issue, invoice)
|
||||
logger.debug "Comparing custom fields"
|
||||
logger.info "Comparing custom fields"
|
||||
# TODO break if Invoice.find(invoice.id).cf_sync_confict
|
||||
is_changed = false
|
||||
|
||||
# update the invoive custom fields with infomation from the issue if available
|
||||
invoice.custom_fields.each { |cf|
|
||||
|
||||
# VIN from the attached vehicle
|
||||
# TODO move this into seperate plugin
|
||||
# TODO create hook for seperate plugin
|
||||
begin
|
||||
if cf.name.eql? "VIN"
|
||||
# Only update if blank to prevent infite loops
|
||||
# TODO check cf_sync_confict flag once implemented
|
||||
if cf.string_value.to_s.blank?
|
||||
logger.debug " VIN was blank, updating the invoice vin in quickbooks"
|
||||
vin = Vehicle.find(issue.vehicles_id).vin
|
||||
break if vin.nil?
|
||||
if not cf.string_value.to_s.eql? vin
|
||||
cf.string_value = vin.to_s
|
||||
logger.debug "VIN has changed"
|
||||
is_changed = true
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
rescue
|
||||
#do nothing
|
||||
end
|
||||
|
||||
# Custom Values
|
||||
begin
|
||||
value = issue.custom_values.find_by(custom_field_id: CustomField.find_by_name(cf.name).id)
|
||||
|
||||
# Check to see if the value is blank...
|
||||
if not value.value.to_s.blank?
|
||||
# Check to see if the value is diffrent
|
||||
if not cf.string_value.to_s.eql? value.value.to_s
|
||||
# update the custom field on the invoice
|
||||
cf.string_value = value.value.to_s
|
||||
is_changed = true
|
||||
end
|
||||
end
|
||||
rescue
|
||||
# Nothing to do here, there is no match
|
||||
end
|
||||
}
|
||||
logger.debug "Calling :process_invoice_custom_fields hook"
|
||||
context = Redmine::Hook.call_hook :process_invoice_custom_fields, { issue: issue, invoice: invoice }
|
||||
|
||||
# Push updates
|
||||
# Process updates from the hooks
|
||||
context.each do |c|
|
||||
unless c.nil?
|
||||
logger.debug "Invoice.compare_custom_fields: We have a responce from a hook"
|
||||
push_updates c[:invoice] if c[:is_changed]
|
||||
end
|
||||
end
|
||||
|
||||
# Process Issue Custom Values
|
||||
begin
|
||||
logger.debug "Trying to update invoice"
|
||||
value = issue.custom_values.find_by(custom_field_id: CustomField.find_by_name(cf.name).id)
|
||||
|
||||
# Check to see if the value is blank...
|
||||
if not value.value.to_s.blank?
|
||||
# Check to see if the value is diffrent
|
||||
if not cf.string_value.to_s.eql? value.value.to_s
|
||||
# update the custom field on the invoice
|
||||
cf.string_value = value.value.to_s
|
||||
is_changed = true
|
||||
end
|
||||
end
|
||||
rescue
|
||||
# Nothing to do here, there is no match
|
||||
end
|
||||
|
||||
push_updates invoice if is_changed
|
||||
end
|
||||
|
||||
# pushes invoice updates
|
||||
def self.push_updates(invoice)
|
||||
begin
|
||||
logger.info "Invoice.push_updates"
|
||||
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
|
||||
service = Quickbooks::Service::Invoice.new(company_id: qbo.realm_id, access_token: access_token)
|
||||
service.update invoice
|
||||
end
|
||||
rescue
|
||||
# Do nothing, probaly custome field sync confict on the invoice.
|
||||
@@ -170,6 +163,16 @@ class Invoice < ActiveRecord::Base
|
||||
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
|
||||
# Maps Get/Set methods to QBO invoice object
|
||||
def method_missing(sym, *arguments)
|
||||
@@ -194,7 +197,7 @@ class Invoice < ActiveRecord::Base
|
||||
raise Exception unless 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 = Quickbooks::Service::Invoice.new(company_id: qbo.realm_id, access_token: access_token)
|
||||
service.fetch_by_id(self.id)
|
||||
end
|
||||
rescue Exception => e
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#The MIT License (MIT)
|
||||
#
|
||||
#Copyright (c) 2023 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:
|
||||
#
|
||||
@@ -9,9 +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.
|
||||
|
||||
class Qbo < ActiveRecord::Base
|
||||
unloadable
|
||||
|
||||
include QuickbooksOauth
|
||||
include Redmine::I18n
|
||||
|
||||
# Updates last sync time stamp
|
||||
def self.update_time_stamp
|
||||
|
||||
@@ -1,100 +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 Vehicle < ActiveRecord::Base
|
||||
|
||||
unloadable
|
||||
|
||||
belongs_to :customer
|
||||
has_many :issues, :foreign_key => 'vehicles_id'
|
||||
|
||||
validates_presence_of :customer
|
||||
validates :vin, uniqueness: true
|
||||
before_save :decode_vin
|
||||
|
||||
self.primary_key = :id
|
||||
|
||||
# returns a human readable string
|
||||
def to_s
|
||||
if year.nil? or make.nil? or model.nil?
|
||||
return "#{vin}"
|
||||
else
|
||||
split_vin = vin.scan(/.{1,9}/)
|
||||
return "#{year} #{make} #{model} - #{split_vin[1]}"
|
||||
end
|
||||
end
|
||||
|
||||
# returns the raw JSON details from NHTSA
|
||||
def details
|
||||
get_details if @details.nil?
|
||||
return @details
|
||||
end
|
||||
|
||||
# Force Upper Case for make numbers
|
||||
def make=(val)
|
||||
# The to_s is in case you get nil/non-string
|
||||
write_attribute(:make, val.to_s.titleize)
|
||||
end
|
||||
|
||||
# Force Upper Case for model numbers
|
||||
def model=(val)
|
||||
# The to_s is in case you get nil/non-string
|
||||
write_attribute(:model, val.to_s.titleize)
|
||||
end
|
||||
|
||||
# Force Upper Case & strip VIN of all illegal chars (for barcode scanner)
|
||||
def vin=(val)
|
||||
val = val.to_s.upcase.gsub(/[^A-HJ-NPR-Za-hj-npr-z\d]+/,"")
|
||||
write_attribute(:vin, val)
|
||||
end
|
||||
|
||||
# search for a vin
|
||||
def self.search(search)
|
||||
where("vin LIKE ?", "%#{search}%")
|
||||
end
|
||||
|
||||
# decodes a vin and updates self
|
||||
def decode_vin
|
||||
get_details
|
||||
if @details
|
||||
begin
|
||||
self.year = @details.year unless @details.year.nil?
|
||||
self.make = @details.make unless @details.make.nil?
|
||||
self.model = @details.model unless @details.model.nil?
|
||||
self.doors = @details.doors unless @details.doors.nil?
|
||||
self.trim = @details.trim unless @details.trim.nil?
|
||||
rescue Exception => e
|
||||
errors.add(:vin, e.message)
|
||||
end
|
||||
end
|
||||
self.name = to_s
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# init method to pull JSON details from NHTSA
|
||||
def get_details
|
||||
if self.vin?
|
||||
#validate the vin before calling a remote server
|
||||
validation = NhtsaVin.validate(self.vin)
|
||||
begin
|
||||
#if the vin validation failed, raise an exception and exit
|
||||
raise RuntimeError, validation.error unless validation.valid?
|
||||
# query NHTSA for details on the vin
|
||||
query = NhtsaVin.get(self.vin)
|
||||
raise RuntimeError, query.error unless query.valid?
|
||||
@details = query.response
|
||||
rescue Exception => e
|
||||
errors.add(:vin, e.message)
|
||||
end
|
||||
end
|
||||
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 t(:customer_details), "https://#{Setting.host_name}#{customer_path @customer.id}"}%0A#{@customer.primary_phone}%3Cbr/%3E+&dates=#{Time.now.strftime("%Y%m%d")}T090000/#{Time.now.strftime("%Y%m%d")}T170000", target: :_blank, id: :appointment_link %>
|
||||
|
||||
<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>
|
||||
<tbody>
|
||||
|
||||
<tr>
|
||||
<th><%=t(:label_name)%></th>
|
||||
<td><%= customer.name %></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th><%=t(:label_email)%></th>
|
||||
<td><%= customer.email %></td>
|
||||
@@ -31,13 +37,24 @@
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th><%=t(:field_notes)%></th>
|
||||
<td><%= customer.notes %></td>
|
||||
<th colspan="2"><h4><%=t(:field_notes)%></hr></th>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<pre id="note-display" style="text-align: left; white-space: pre-wrap; font-family: inherit;">
|
||||
<%= customer.notes %>
|
||||
</pre>
|
||||
</td>
|
||||
</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>
|
||||
</table>
|
||||
<div style="float: right;">
|
||||
<%= button_to t(:label_edit_customer), edit_customer_path(customer), method: :get%>
|
||||
</div>
|
||||
</table>
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
@@ -7,28 +7,28 @@
|
||||
<div class="clearfix">
|
||||
<%=t(:label_display_name)%>
|
||||
<div class="input">
|
||||
<%= f.text_field :name, :required => true, :autocomplete => "off" %>
|
||||
<%= f.text_field :name, required: true, autocomplete: "off" %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="clearfix">
|
||||
<%=t(:label_primary_phone)%>
|
||||
<div class="input">
|
||||
<%= f.telephone_field :primary_phone, :autocomplete => "off" %>
|
||||
<%= f.telephone_field :primary_phone, autocomplete: "off" %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="clearfix">
|
||||
<%=t(:label_mobile_phone)%>:
|
||||
<div class="input">
|
||||
<%= f.telephone_field :mobile_phone, :autocomplete => "off" %>
|
||||
<%= f.telephone_field :mobile_phone, autocomplete: "off" %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="clearfix">
|
||||
<%=t(:label_email)%>:
|
||||
<div class="input">
|
||||
<%= f.email_field :email, :autocomplete => "off" %>
|
||||
<%= f.email_field :email, autocomplete: "off" %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -36,17 +36,16 @@
|
||||
<%=t(:field_notes)%>:
|
||||
<div class="input">
|
||||
<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", :style => (@customer.new_record? ? nil : 'display:none') do %>
|
||||
<%= content_tag :span, id: "issue_description_and_toolbar" do %>
|
||||
<%= f.text_area :notes,
|
||||
:cols => 60,
|
||||
:rows => 10,
|
||||
:accesskey => accesskey(:edit),
|
||||
:class => 'wiki-edit',
|
||||
:no_label => true %>
|
||||
cols: 60,
|
||||
rows: 10,
|
||||
accesskey: accesskey(:edit),
|
||||
class: 'wiki-edit',
|
||||
no_label: true %>
|
||||
<% end %>
|
||||
</p>
|
||||
<%= wikitoolbar_for 'issue_description' %>
|
||||
<%= wikitoolbar_for :issue_description %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<%= form_tag(customers_path, :method => "get", id: "search-form") do %>
|
||||
<%= text_field_tag :search, params[:search], placeholder: t(:label_search_customers), :autocomplete => "off" %>
|
||||
<%= form_tag(customers_path, method: "get", id: "search-form") do %>
|
||||
<%= text_field_tag :search, params[:search], placeholder: t(:label_search_customers), autocomplete: "off" %>
|
||||
<%= submit_tag t(:label_search) %>
|
||||
<% end %>
|
||||
<%= 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,2 +1,2 @@
|
||||
<h3><%=t(:label_customers)%></h3>
|
||||
<%= render :partial => 'customers/search' %>
|
||||
<%= render partial: 'customers/search' %>
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
<h1><%=t(:label_edit_customer)%></h1>
|
||||
<br/>
|
||||
<%= render :partial => 'customers/form' %>
|
||||
<%= render partial: 'customers/form' %>
|
||||
|
||||
@@ -1 +1 @@
|
||||
$('select#issue_estimate_id').html('<%= j content_tag(:option,'',:value=>"")+options_from_collection_for_select(@filtered_estimates, :id, :doc_number) %>');
|
||||
$('select#issue_estimate_id').html('<%= j content_tag(:option,'',value:"")+options_from_collection_for_select(@filtered_estimates, :id, :doc_number) %>');
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
$('select#issue_vehicles_id').html('<%= j content_tag(:option,'',:value=>"")+options_from_collection_for_select(@filtered_vehicles, :id, :to_s) %>');
|
||||
@@ -1,4 +1,4 @@
|
||||
<h2><%=t(:field_customers)%> <span style="float:right"> <%= render :partial => 'customers/search' %> </span> </h2>
|
||||
<h2><%=t(:field_customers)%> <span style="float:right"> <%= render partial: 'customers/search' %> </span> </h2>
|
||||
<% if @customers.present? %>
|
||||
<br/>
|
||||
<% @customers.each do |c| %>
|
||||
@@ -20,5 +20,5 @@
|
||||
<% end %>
|
||||
|
||||
<div>
|
||||
<%= render :partial => 'qbo/stats' %>
|
||||
<%= render partial: 'qbo/stats' %>
|
||||
</div>
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
<h2><%=t(:label_new_customer)%></h2>
|
||||
<br/>
|
||||
<%= render :partial => 'customers/form' %>
|
||||
<%= render partial: 'customers/form' %>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<h2><%=t(:field_customer)%> #<%= @customer.id %> - <%= link_to @customer.to_s, "https://app.qbo.intuit.com/app/customerdetail?nameId=#{@customer.id}", target: :_blank %> </h2>
|
||||
<h2><%=t(:field_customer)%> #<%= @customer.id %> - <%= link_to @customer.to_s, "https://#{Setting.plugin_redmine_qbo[:sandbox] ? "sandbox" : "app"}.qbo.intuit.com/app/customerdetail?nameId=#{@customer.id}", target: :_blank %> </h2>
|
||||
<div class="issue">
|
||||
|
||||
<div class="splitcontent">
|
||||
@@ -7,28 +7,39 @@
|
||||
|
||||
<h4><%=t(:label_details)%>:</h4>
|
||||
|
||||
<%= render :partial => 'customers/details', locals: {customer: @customer} %>
|
||||
<!-- Customer Info -->
|
||||
|
||||
<div class="splitcontent">
|
||||
<div class="splitcontentleft">
|
||||
<h4><%=t(:field_customer)%>:</h4>
|
||||
<%= 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="splitcontentleft">
|
||||
<h4><%=t(:estimates)%>:</h4>
|
||||
<%= render :partial => 'estimates/list', locals: {customer: @customer} %>
|
||||
<%= render partial: 'estimates/list', locals: {estimates: @customer.estimates} %>
|
||||
</div>
|
||||
|
||||
<div class="splitcontentleft">
|
||||
<h4><%=t(:label_invoices)%>:</h4>
|
||||
<%= render :partial => 'invoices/list', locals: {customer: @customer} %>
|
||||
<%= render partial: 'invoices/list', locals: {invoices: @customer.invoices} %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="splitcontentleft">
|
||||
<h4><%=t(:field_vehicles)%>:</h4>
|
||||
<%= render :partial => 'vehicles/list' %>
|
||||
<div style="float: right;">
|
||||
<%= button_to "New Vehicle", new_customer_vehicle_path(@customer), method: :get %>
|
||||
</div>
|
||||
<%= call_hook :show_customer_view_right, {customer: @customer} %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -36,7 +47,7 @@
|
||||
|
||||
<br/>
|
||||
<h3><%=@issues.open.count%> <%=t(:label_open_issues)%> - <%=@hours.round(1)%> <%=t(:label_hours)%></h3>
|
||||
<%= render :partial => 'issues/list_simple', locals: {issues: @issues.open} %>
|
||||
<%= render partial: 'issues/list_simple', locals: {issues: @issues.open} %>
|
||||
|
||||
<h3><%=@closed_issues.count%> <%=t(:label_closed_issues)%> - <%= @closed_hours.round(1)%> <%=t(:label_hours)%></h3>
|
||||
<%= render :partial => 'issues/list_simple', locals: {issues: @closed_issues} %>
|
||||
<%= render partial: 'issues/list_simple', locals: {issues: @closed_issues} %>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
<div class="<%= @issue.css_classes %> details">
|
||||
|
||||
<%= avatar(@issue.author, :size => "50") %>
|
||||
<%= avatar(@issue.author, size: "50") %>
|
||||
|
||||
<div class="subject">
|
||||
<%= render_issue_subject_with_tree(@issue) %>
|
||||
@@ -19,39 +19,39 @@
|
||||
|
||||
<div class="attributes">
|
||||
<%= issue_fields_rows do |rows|
|
||||
rows.left l(:field_status), @issue.status.name, :class => 'status'
|
||||
rows.left l(:field_priority), @issue.priority.name, :class => 'priority'
|
||||
unless @issue.disabled_core_fields.include?('assigned_to_id')
|
||||
rows.left l(:field_assigned_to), avatar(@issue.assigned_to, :size => "14").to_s.html_safe + (@issue.assigned_to ? @issue.assigned_to : "-"), :class => 'assigned-to'
|
||||
rows.left l(:field_status), @issue.status.name, class: :status
|
||||
rows.left l(:field_priority), @issue.priority.name, class: :priority
|
||||
# unless @issue.disabled_core_fields.include?(:assigned_to_id)
|
||||
# rows.left l(:field_assigned_to), avatar(@issue.assigned_to, size: "14").to_s.html_safe + (@issue.assigned_to ? @issue.assigned_to : "-"), class: 'assigned-to'
|
||||
# end
|
||||
unless @issue.disabled_core_fields.include?(:category_id) || (@issue.category.nil? && @issue.project.issue_categories.none?)
|
||||
rows.left l(:field_category), (@issue.category ? @issue.category.name : "-"), class: :category
|
||||
end
|
||||
unless @issue.disabled_core_fields.include?('category_id') || (@issue.category.nil? && @issue.project.issue_categories.none?)
|
||||
rows.left l(:field_category), (@issue.category ? @issue.category.name : "-"), :class => 'category'
|
||||
unless @issue.disabled_core_fields.include?(:fixed_version_id) || (@issue.fixed_version.nil? && @issue.assignable_versions.none?)
|
||||
rows.left l(:field_fixed_version), (@issue.fixed_version ? @issue.fixed_version : "-"), class: 'fixed-version'
|
||||
end
|
||||
unless @issue.disabled_core_fields.include?('fixed_version_id') || (@issue.fixed_version.nil? && @issue.assignable_versions.none?)
|
||||
rows.left l(:field_fixed_version), (@issue.fixed_version ? @issue.fixed_version : "-"), :class => 'fixed-version'
|
||||
unless @issue.disabled_core_fields.include?(:start_date)
|
||||
rows.right l(:field_start_date), format_date(@issue.start_date), class: 'start-date'
|
||||
end
|
||||
unless @issue.disabled_core_fields.include?('start_date')
|
||||
rows.right l(:field_start_date), format_date(@issue.start_date), :class => 'start-date'
|
||||
unless @issue.disabled_core_fields.include?(:due_date)
|
||||
rows.right l(:field_due_date), format_date(@issue.due_date), class: 'due-date'
|
||||
end
|
||||
unless @issue.disabled_core_fields.include?('due_date')
|
||||
rows.right l(:field_due_date), format_date(@issue.due_date), :class => 'due-date'
|
||||
unless @issue.disabled_core_fields.include?(:done_ratio)
|
||||
rows.right l(:field_done_ratio), progress_bar(@issue.done_ratio, legend: "#{@issue.done_ratio}%"), class: :progress
|
||||
end
|
||||
unless @issue.disabled_core_fields.include?('done_ratio')
|
||||
rows.right l(:field_done_ratio), progress_bar(@issue.done_ratio, :legend => "#{@issue.done_ratio}%"), :class => 'progress'
|
||||
end
|
||||
unless @issue.disabled_core_fields.include?('estimated_hours')
|
||||
unless @issue.disabled_core_fields.include?(:estimated_hours)
|
||||
if @issue.estimated_hours.present? || @issue.total_estimated_hours.to_f > 0
|
||||
rows.right l(:field_estimated_hours), issue_estimated_hours_details(@issue), :class => 'estimated-hours'
|
||||
rows.right l(:field_estimated_hours), issue_estimated_hours_details(@issue), class: 'estimated-hours'
|
||||
end
|
||||
end
|
||||
#if User.current.allowed_to_view_all_time_entries?(@project)
|
||||
if @issue.total_spent_hours > 0
|
||||
rows.right l(:label_spent_time), issue_spent_hours_details(@issue), :class => 'spent-time'
|
||||
rows.right l(:label_spent_time), issue_spent_hours_details(@issue), class: 'spent-time'
|
||||
end
|
||||
#end
|
||||
end %>
|
||||
<%= render_full_width_custom_fields_rows(@issue) %>
|
||||
<%= call_hook(:view_issues_show_details_bottom, :issue => @issue) %>
|
||||
<%= call_hook(:view_issues_show_details_bottom, issue: @issue) %>
|
||||
</div>
|
||||
|
||||
<% if @issue.description? || @issue.attachments.any? -%>
|
||||
@@ -59,19 +59,19 @@ end %>
|
||||
<% if @issue.description? %>
|
||||
<div class="description">
|
||||
<div class="contextual">
|
||||
<%= link_to l(:button_quote), quoted_issue_path(@issue), :remote => true, :method => 'post', :class => 'icon icon-comment' if @issue.notes_addable? %>
|
||||
<%= link_to l(:button_quote), quoted_issue_path(@issue), remote: true, method: :post, class: 'icon icon-comment' if @issue.notes_addable? %>
|
||||
</div>
|
||||
|
||||
<p><strong><%=l(:field_description)%></strong></p>
|
||||
<div class="wiki">
|
||||
<%= textilizable @issue, :description, :attachments => @issue.attachments %>
|
||||
<%= textilizable @issue, :description, attachments: @issue.attachments %>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
<%= link_to_attachments @issue, :thumbnails => true %>
|
||||
<%= link_to_attachments @issue, thumbnails: true %>
|
||||
<% end -%>
|
||||
|
||||
<%= call_hook(:view_issues_show_description_bottom, :issue => @issue) %>
|
||||
<%= call_hook(:view_issues_show_description_bottom, issue: @issue) %>
|
||||
|
||||
<% if !@issue.leaf? || User.current.allowed_to?(:manage_subtasks, @project) %>
|
||||
<hr />
|
||||
@@ -87,7 +87,7 @@ end %>
|
||||
<% if @relations.present? || User.current.allowed_to?(:manage_issue_relations, @project) %>
|
||||
<hr />
|
||||
<div id="relations">
|
||||
<%= render :partial => 'issues/relations' %>
|
||||
<%= render partial: 'issues/relations' %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
@@ -96,14 +96,14 @@ end %>
|
||||
<% if @changesets.present? %>
|
||||
<div id="issue-changesets">
|
||||
<h3><%=l(:label_associated_revisions)%></h3>
|
||||
<%= render :partial => 'issues/changesets', :locals => { :changesets => @changesets} %>
|
||||
<%= render partial: 'issues/changesets', locals: { changesets: @changesets} %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<% if @journals.present? %>
|
||||
<div id="history">
|
||||
<h3><%=l(:label_history)%></h3>
|
||||
<%= render :partial => 'issues/history', :locals => { :issue => @issue, :journals => @journals } %>
|
||||
<%= render partial: 'issues/history', locals: { issue: @issue, journals: @journals } %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
<% if @customer.present? %>
|
||||
<% unless estimates.empty? %>
|
||||
|
||||
<% @customer.estimates.order(doc_number: :desc).each do |estimate| %>
|
||||
<% estimates.sort.reverse.each do |estimate| %>
|
||||
<div class="row">
|
||||
<%= check_box_tag "estimate_ids[]", estimate.id, false, onchange: "updateLink()", data: { url: estimate_path(estimate), text: "Estimate ##{estimate.to_s}" }, class: "estimate-checkbox appointment" %>
|
||||
<b><%= link_to "##{estimate.doc_number}", estimate_path(estimate), target: :_blank %></b> <%= estimate.txn_date %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<%= form_tag(estimate_doc_path, :method => "get") do %>
|
||||
<%= text_field_tag :search, params[:search], placeholder: t(:label_search_estimates), :autocomplete => "off" %>
|
||||
<%= submit_tag t(:label_search), :formtarget => "_blank" %>
|
||||
<%= form_tag(estimate_doc_path, method: "get") do %>
|
||||
<%= text_field_tag :search, params[:search], placeholder: t(:label_search_estimates), autocomplete: "off" %>
|
||||
<%= submit_tag t(:label_search), formtarget: "_blank" %>
|
||||
<% end %>
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
<h3><%=t(:label_estimates) %></h3>
|
||||
<%= render :partial => 'estimates/search' %>
|
||||
<%= render partial: 'estimates/search' %>
|
||||
|
||||
@@ -1,9 +1,25 @@
|
||||
<% if @customer.present? %>
|
||||
<% unless invoices.empty? %>
|
||||
|
||||
<% @customer.invoices.order(doc_number: :desc).each do |invoice| %>
|
||||
<div class="row">
|
||||
<b><%= link_to "##{invoice.doc_number}", invoice_path(invoice), target: :_blank %></b> <%= invoice.txn_date %>
|
||||
</div>
|
||||
<%= form_with(url: invoice_path, method: :get) do |form| %>
|
||||
|
||||
<% if invoices.count > 1 %>
|
||||
<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>
|
||||
<% end %>
|
||||
|
||||
<% invoices.sort.reverse.each do |invoice| %>
|
||||
<div class="row">
|
||||
<%= check_box_tag "invoice_ids[]", invoice.id, false, onchange: "updateLink()", data: { url: invoice_path(invoice), text: "Invoice ##{invoice.to_s}" }, class: "invoice-checkbox appointment" if invoices.count > 1 %>
|
||||
<b><%= link_to "##{invoice.doc_number}", invoice_path(invoice), target: :_blank %></b> <%= invoice.txn_date %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<% if invoices.count > 1 %>
|
||||
<%= form.submit t(:button_bulk_pdf) %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
<% else %>
|
||||
|
||||
@@ -2,13 +2,8 @@
|
||||
<label for="issue_customer"><%= t(:customer) %></label>
|
||||
<%= search_customer %>
|
||||
<%= customer_id %>
|
||||
<%= link_to_function(t(:label_load_customer), "updateIssueFrom('/issues/#{context[:issue].id}/edit.js', this)") %>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<%= select_estimate %>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<%= vehicle %>
|
||||
</p>
|
||||
@@ -3,12 +3,12 @@
|
||||
<div id="change-<%= journal.id %>" class="<%= journal.css_classes %>">
|
||||
<div id="note-<%= journal.indice %>">
|
||||
<div class="contextual">
|
||||
<span class="journal-actions"><%= render_journal_actions(issue, journal, :reply_links => reply_links) %></span>
|
||||
<span class="journal-actions"><%= render_journal_actions(issue, journal, reply_links: reply_links) %></span>
|
||||
<a href="#note-<%= journal.indice %>" class="journal-link">#<%= journal.indice %></a>
|
||||
</div>
|
||||
<h4>
|
||||
<%= avatar(journal.user, :size => "24") %>
|
||||
<%= authoring journal.created_on, journal.user, :label => :label_updated_time_by %>
|
||||
<%= avatar(journal.user, size: "24") %>
|
||||
<%= authoring journal.created_on, journal.user, label: :label_updated_time_by %>
|
||||
<%= render_private_notes_indicator(journal) %>
|
||||
</h4>
|
||||
|
||||
@@ -26,10 +26,10 @@
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<%= render_notes(issue, journal, :reply_links => reply_links) unless journal.notes.blank? %>
|
||||
<%= render_notes(issue, journal, reply_links: reply_links) unless journal.notes.blank? %>
|
||||
</div>
|
||||
</div>
|
||||
<%= call_hook(:view_issues_history_journal_bottom, { :journal => journal }) %>
|
||||
<%= call_hook(:view_issues_history_journal_bottom, { journal: journal }) %>
|
||||
<% end %>
|
||||
|
||||
<% heads_for_wiki_formatter if User.current.allowed_to?(:edit_issue_notes, issue.project) || User.current.allowed_to?(:edit_own_issue_notes, issue.project) %>
|
||||
|
||||
@@ -9,9 +9,9 @@
|
||||
</tr></thead>
|
||||
<tbody>
|
||||
<% for issue in issues %>
|
||||
<tr id="issue-<%= h(issue.id) %>" class="hascontextmenu <%= cycle('odd', 'even') %> <%= issue.css_classes %>">
|
||||
<tr id="issue-<%= h(issue.id) %>" class="hascontextmenu <%= cycle(:odd, :even) %> <%= issue.css_classes %>">
|
||||
<td class="id">
|
||||
<%= check_box_tag("ids[]", issue.id, false, :style => 'display:none;', :id => nil) %>
|
||||
<%= check_box_tag("ids[]", issue.id, false, style: 'display:none;', id: nil) %>
|
||||
<%= link_to(issue.id, issue_path(issue)) %>
|
||||
</td>
|
||||
<td class="project"><%= link_to_project(issue.project) %></td>
|
||||
|
||||
@@ -17,19 +17,6 @@
|
||||
</div>
|
||||
|
||||
<div class="splitcontentleft">
|
||||
<div class="vehicle attribute">
|
||||
<div class="label"><span><%=t(:field_vehicle)%></span>:</div>
|
||||
<div class="value"><%= vehicle %></div>
|
||||
</div>
|
||||
|
||||
<div class="vehicle_vin attribute">
|
||||
<div class="label"><span><%=t(:field_vin)%></span>:</div>
|
||||
<div class="value"><%=split_vin[0] if split_vin%><b><%=split_vin[1] if split_vin%></b></div>
|
||||
</div>
|
||||
|
||||
<div class="vehicle_notes attribute">
|
||||
<div class="label"><span><%=t(:field_notes)%></span>:</div>
|
||||
<div class="value"><%=notes%></div>
|
||||
</div>
|
||||
<%= call_hook :show_issue_view_right, {issue: issue} %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
3
app/views/qbo/_footer.html.erb
Normal file
3
app/views/qbo/_footer.html.erb
Normal file
@@ -0,0 +1,3 @@
|
||||
"<div id='footer' align='center'>
|
||||
<b><%=I18n.translate(:label_last_sync)%>: </b> <%=Qbo.last_sync if Qbo.exists?%>
|
||||
</div>"
|
||||
@@ -1,7 +1,7 @@
|
||||
<!--
|
||||
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:
|
||||
|
||||
@@ -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 -->
|
||||
<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>
|
||||
|
||||
<table >
|
||||
@@ -57,6 +57,13 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th><%=t(:label_sandbox)%></th>
|
||||
<td>
|
||||
<%= check_box_tag 'settings[sandbox]', @settings[:sandbox], @settings[:sandbox] %>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th><%=t(:label_oauth_expires)%></th>
|
||||
<td><%= if Qbo.exists? then Qbo.first.oauth2_access_token_expires_at end %>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<% if User.current.logged? %>
|
||||
|
||||
<%= render :partial => 'customers/sidebar' %>
|
||||
<%= render :partial => 'estimates/sidebar' %>
|
||||
<%= render partial: 'customers/sidebar' %>
|
||||
<%= render partial: 'estimates/sidebar' %>
|
||||
|
||||
<% end %>
|
||||
|
||||
@@ -1 +1 @@
|
||||
<%= Customer.count %> <%=t(:field_customers)%> - <%= render :partial => 'qbo/last_sync' %>
|
||||
<%= Customer.count %> <%=t(:field_customers)%> - <%= render partial: 'qbo/last_sync' %>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<!--
|
||||
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:
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<!--
|
||||
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:
|
||||
|
||||
|
||||
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>
|
||||
@@ -1,45 +0,0 @@
|
||||
<div class="issue">
|
||||
<div class="splitcontent">
|
||||
<div class="splitcontentleft">
|
||||
<h4><%=t(:label_details)%>:</h4>
|
||||
|
||||
<table>
|
||||
<tbody>
|
||||
|
||||
<tr>
|
||||
<th><%= t(:field_customer)%></th>
|
||||
<td><%= link_to vehicle.customer.name, customer_path(vehicle.customer) %></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th><%= t(:field_vehicle) %></th>
|
||||
<td><%= vehicle.to_s %></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th><%= t(:field_vin) %></th>
|
||||
<td><%= @vin[0] if @vin %><b><%=@vin[1] if @vin%></b></td>
|
||||
</tr>
|
||||
|
||||
<th><%= t(:label_trim) %></th>
|
||||
<td><%= vehicle.doors %> <%=t(:label_door) if vehicle.doors? %> <%= vehicle.trim %></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="splitcontentleft">
|
||||
|
||||
<h4><%=t(:field_notes)%>:</h4>
|
||||
<td><%= vehicle.notes %></td>
|
||||
</tr>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="float: right;">
|
||||
<%= button_to t(:label_edit), edit_vehicle_path(vehicle), method: :get%>
|
||||
<%= button_to t(:label_delete), vehicle, method: :delete, data: {confirm: t(:warn_ru_sure)} %>
|
||||
</div>
|
||||
@@ -1,56 +0,0 @@
|
||||
<div class="row">
|
||||
<div class="span6 columns">
|
||||
<fieldset>
|
||||
|
||||
<%= form_for @vehicle do |f| %>
|
||||
<div class="clearfix">
|
||||
<%=t(:field_customer)%>:
|
||||
<div class="input">
|
||||
<%= f.autocomplete_field :customer, autocomplete_customer_name_customers_path, :value => @customer.name, :update_elements => {:id => '#customer_id', :value => '#issue_customer'}, :required => true %>
|
||||
<%= f.hidden_field :customer_id, :id => "customer_id", :value => @customer.id %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="clearfix">
|
||||
<%=t(:label_year)%>:
|
||||
<div class="input">
|
||||
<%= f.number_field :year, :autocomplete => "off" %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="clearfix">
|
||||
<%=t(:label_make)%>:
|
||||
<div class="input">
|
||||
<%= f.text_field :make, :autocomplete => "off" %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="clearfix">
|
||||
<%=t(:label_model)%>:
|
||||
<div class="input">
|
||||
<%= f.text_field :model, :autocomplete => "off" %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="clearfix">
|
||||
<%=t(:field_vin)%>:
|
||||
<div class="input">
|
||||
<%= f.text_field :vin , :autofocus => true %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="clearfix">
|
||||
<%=t(:field_notes)%>:
|
||||
<div class="input">
|
||||
<%= f.text_area :notes, :cols => 60, :rows => 10, :no_label => true %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
<%= f.submit %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,28 +0,0 @@
|
||||
<% if @vehicles.present? %>
|
||||
|
||||
<% @vehicles.each do |vehicle| %>
|
||||
<div class="row">
|
||||
<div>
|
||||
<b><%= link_to "##{vehicle.id}", vehicle_path(vehicle) %> </b>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<%= vehicle.to_s %>
|
||||
<br/>
|
||||
<%= vehicle.customer %>
|
||||
<br/>
|
||||
<%= vehicle.vin.scan(/.{1,9}/)[0] if vehicle.vin %><b><%=vehicle.vin.scan(/.{1,9}/)[1] if vehicle.vin%></b>
|
||||
</div>
|
||||
</div>
|
||||
<br/>
|
||||
<% end %>
|
||||
|
||||
<div class="actions">
|
||||
<%= will_paginate @vehicles %>
|
||||
</div>
|
||||
|
||||
<p><%=t(:label_matching)%> <%=@vehicles.count%> <%=t(:field_vehicles) %> </p>
|
||||
|
||||
<% else %>
|
||||
<p><%=t(:label_no_vehicles)%> <%= params[:search] %>.</p>
|
||||
<% end %>
|
||||
@@ -1,4 +0,0 @@
|
||||
<%= form_tag(vehicles_path, :method => "get", id: "search-form") do %>
|
||||
<%= text_field_tag :search, params[:search], placeholder: t(:label_search_vin), :autocomplete => "off" %>
|
||||
<%= submit_tag t(:label_search) %>
|
||||
<% end %>
|
||||
@@ -1 +0,0 @@
|
||||
<option value="<%= vehicle.id %>"><%= vehicle.to_s.titleize %></option>
|
||||
@@ -1,3 +0,0 @@
|
||||
<h1><%=t(:label_edit_customer_vehicle)%></h1>
|
||||
<br/>
|
||||
<%= render :partial => 'vehicles/form' %>
|
||||
@@ -1,4 +0,0 @@
|
||||
<h2><%=t(:label_cusomer_vehicles)%> <span style="float:right"> <%= render :partial => 'vehicles/search' %> </span> </h2>
|
||||
<br/>
|
||||
|
||||
<%= render :partial => 'vehicles/list' %>
|
||||
@@ -1,3 +0,0 @@
|
||||
<h2><%=t(:label_new_vehicle)%></h2>
|
||||
<br/>
|
||||
<%= render :partial => 'vehicles/form' %>
|
||||
@@ -1,11 +0,0 @@
|
||||
<h2><%=t(:field_vehicle)%> #<%=@vehicle.id%></h2>
|
||||
|
||||
<%= render :partial => 'vehicles/details', locals: {vehicle: @vehicle} %>
|
||||
|
||||
<h3><%=@issues.open.count%> <%=t(:label_open_issues)%></h3>
|
||||
|
||||
<%= render :partial => 'issues/list_simple', locals: {issues: @issues.open} %>
|
||||
|
||||
<h3><%=@closed_issues.count%> <%=t(:label_closed_issues)%></h3>
|
||||
|
||||
<%= render :partial => 'issues/list_simple', locals: {issues: (@closed_issues)} %>
|
||||
@@ -1,23 +1,22 @@
|
||||
$(function() {
|
||||
$("input#issue_customer_id").on("change", function() {
|
||||
$.ajax({
|
||||
url: "/filter_vehicles_by_customer",
|
||||
type: "GET",
|
||||
data: { selected_customer: $("input#issue_customer_id").val() }
|
||||
});
|
||||
|
||||
$.ajax({
|
||||
url: "/filter_estimates_by_customer",
|
||||
type: "GET",
|
||||
data: { selected_customer: $("input#issue_customer_id").val() }
|
||||
});
|
||||
});
|
||||
|
||||
$("input#project_customer_id").on("change", function() {
|
||||
$.ajax({
|
||||
url: "/filter_vehicles_by_customer",
|
||||
type: "GET",
|
||||
data: { selected_customer: $("input#project_customer_id").val() }
|
||||
});
|
||||
});
|
||||
});
|
||||
function updateLink() {
|
||||
console.log("updateLink called");
|
||||
const linkElement = document.getElementById("appointment_link");
|
||||
const regex = /((?:<br\/>|%3Cbr\/?%3E))([\s\S]*?)(&dates)/gi;
|
||||
linkElement.href = linkElement.href.replace(regex, `$1${getSelectedDocs()}$3`);
|
||||
}
|
||||
|
||||
function getSelectedDocs() {
|
||||
const appointent_extras = document.querySelectorAll('.appointment');
|
||||
|
||||
let output = '';
|
||||
for (const item of appointent_extras) {
|
||||
if (item.checked) {
|
||||
console.log(`Checked item: ${item.dataset.text} with URL: ${item.dataset.url}`);
|
||||
output += `%0A`+ encodeURIComponent(`<a href="${window.location.origin}${item.dataset.url}">${item.dataset.text}</a>`) +`%0A`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// You can return the array or use it as needed
|
||||
return output;
|
||||
}
|
||||
|
||||
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)
|
||||
#
|
||||
#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:
|
||||
#
|
||||
@@ -12,13 +12,9 @@
|
||||
# Usage I18n.t(:label)
|
||||
en:
|
||||
field_customer: "Customer"
|
||||
field_item: "Item"
|
||||
field_employee: "Employee"
|
||||
field_invoice: "Invoice"
|
||||
field_estimate: "Estimate"
|
||||
field_vehicles: "Vehicles"
|
||||
field_vehicle: "Vehicle"
|
||||
field_vin: "VIN"
|
||||
field_notes: "Notes"
|
||||
field_billed: "Billed"
|
||||
label_week: "Week"
|
||||
@@ -31,13 +27,8 @@ en:
|
||||
label_year: "Year"
|
||||
label_make: " Make"
|
||||
label_model: "Model"
|
||||
label_no_vehicles: "There are no vehicles containing the term(s)"
|
||||
label_no_customers: "There are no customers containing the term(s)"
|
||||
label_matching: "Matching "
|
||||
label_search_vin: "Search Vehicles by VIN"
|
||||
label_edit_customer_vehicle: "Edit Customer Vehicle"
|
||||
label_cusomer_vehicles: "Customer Vehicles"
|
||||
label_new_vehicle: "New Customer Vehicle"
|
||||
label_open_issues: "Open Issues"
|
||||
label_closed_issues: "Closed Issues"
|
||||
label_sync: "Sync"
|
||||
@@ -62,7 +53,6 @@ en:
|
||||
label_customer_count: "Customer Count"
|
||||
label_invoice_count: "Invoice Count"
|
||||
label_estimate_count: "Estimate Count"
|
||||
label_item_count: "Item Count"
|
||||
label_employee_count: "Employee Count"
|
||||
label_client_id: "Intuit QBO OAuth2 Client ID"
|
||||
label_client_secret: "Intuit QBO OAuth2 Client Secret"
|
||||
@@ -88,4 +78,27 @@ en:
|
||||
label_qbo_sync_success: "Successfully synced to Quickbooks"
|
||||
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"
|
||||
notice_customer_created: "Customer created in Quickbooks"
|
||||
notice_customer_updated: "Customer updated in Quickbooks"
|
||||
notice_customer_not_found: "Customer not found in Quickbooks"
|
||||
notice_customer_not_deleted: "Customer could not be deleted in Quickbooks"
|
||||
notice_customer_deleted: "Customer deleted in Quickbooks"
|
||||
notice_estimate_created: "Estimate created in Quickbooks"
|
||||
notice_estimate_updated: "Estimate updated in Quickbooks"
|
||||
notice_estimate_not_found: "Estimate not found"
|
||||
notice_invoice_created: "Invoice created in Quickbooks"
|
||||
notice_invoice_updated: "Invoice updated in Quickbooks"
|
||||
notice_invoice_not_found: "Invoice not found"
|
||||
notice_forbidden: "You do not have permission to access this resource"
|
||||
notice_issue_not_found: "Issue not found"
|
||||
customer_details: "Customer Details"
|
||||
notice_error_project_nil: "The issue's project is nil, set project to: "
|
||||
notice_error_tracker_nil: "The issue's tracker is nil, set tracker to: "
|
||||
@@ -1,6 +1,6 @@
|
||||
#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:
|
||||
#
|
||||
@@ -9,37 +9,31 @@
|
||||
#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.
|
||||
|
||||
#authentication
|
||||
get 'qbo/authenticate', :to => 'qbo#authenticate'
|
||||
get 'qbo/oauth_callback', :to => 'qbo#oauth_callback'
|
||||
get 'qbo/authenticate', to: 'qbo#authenticate'
|
||||
get 'qbo/oauth_callback', to: 'qbo#oauth_callback'
|
||||
|
||||
#manual sync
|
||||
get 'qbo/sync', :to => 'qbo#sync'
|
||||
get 'qbo/sync', to: 'qbo#sync'
|
||||
|
||||
#webhook
|
||||
post 'qbo/webhook', :to => 'qbo#webhook'
|
||||
post 'qbo/webhook', to: 'qbo#webhook'
|
||||
|
||||
# Estimate & Invoice PDF
|
||||
get 'estimates/:id', :to => 'estimate#show', as: :estimate
|
||||
get 'estimates/doc/', :to => 'estimate#doc', as: :estimate_doc
|
||||
get 'invoices/:id', :to => 'invoice#show', as: :invoice
|
||||
get 'estimates/:id', to: 'estimate#show', as: :estimate
|
||||
get 'estimates/doc/', to: 'estimate#doc', as: :estimate_doc
|
||||
get 'invoices/:id', to: 'invoice#show', as: :invoice
|
||||
|
||||
#manual billing
|
||||
get 'bill/:id', :to => 'qbo#bill', as: :bill
|
||||
get 'bill/:id', to: 'qbo#bill', as: :bill
|
||||
|
||||
#customer issue view
|
||||
get 'customers/view/:token', :to => 'customers#view', as: :view
|
||||
get 'customers/share/:id', :to => 'customers#share', as: :share
|
||||
get 'customers/view/:token', to: 'customers#view', as: :view
|
||||
get 'customers/share/:id', to: 'customers#share', as: :share
|
||||
|
||||
#java script routes
|
||||
get 'filter_vehicles_by_customer' => 'customers#filter_vehicles_by_customer'
|
||||
get 'filter_estimates_by_customer' => 'customers#filter_estimates_by_customer'
|
||||
get 'filter_invoices_by_customer' => 'customers#filter_invoices_by_customer'
|
||||
|
||||
# Nest Vehicles under customers
|
||||
resources :customers do
|
||||
resources :vehicles
|
||||
get :autocomplete_customer_name, :on => :collection
|
||||
get :autocomplete_customer_name, on: :collection
|
||||
end
|
||||
|
||||
#allow for just vehicles too
|
||||
resources :vehicles
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#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:
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#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:
|
||||
#
|
||||
@@ -11,7 +11,7 @@
|
||||
class CreateQboCustomers < ActiveRecord::Migration[5.1]
|
||||
def change
|
||||
create_table :qbo_customers, id: false do |t|
|
||||
t.integer :id, :options => 'PRIMARY KEY'
|
||||
t.integer :id, options: 'PRIMARY KEY'
|
||||
t.string :name
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#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:
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#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:
|
||||
#
|
||||
@@ -11,7 +11,7 @@
|
||||
class CreateQboItems < ActiveRecord::Migration[5.1]
|
||||
def change
|
||||
create_table :qbo_items, id: false do |t|
|
||||
t.integer :id, :options => 'PRIMARY KEY'
|
||||
t.integer :id, options: 'PRIMARY KEY'
|
||||
t.string :name
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#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:
|
||||
#
|
||||
@@ -11,7 +11,7 @@
|
||||
class CreateQboEmployees < ActiveRecord::Migration[5.1]
|
||||
def change
|
||||
create_table :qbo_employees, id: false do |t|
|
||||
t.integer :id, :options => 'PRIMARY KEY'
|
||||
t.integer :id, options: 'PRIMARY KEY'
|
||||
t.string :name
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#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:
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#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:
|
||||
#
|
||||
@@ -10,6 +10,6 @@
|
||||
|
||||
class UpdateTimeEntries < ActiveRecord::Migration[5.1]
|
||||
def change
|
||||
add_column :time_entries, :qbo_billed, :boolean, :default => false
|
||||
add_column :time_entries, :qbo_billed, :boolean, default: false
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#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:
|
||||
#
|
||||
@@ -11,7 +11,7 @@
|
||||
class CreateQboEstimates < ActiveRecord::Migration[5.1]
|
||||
def change
|
||||
create_table :qbo_estimates, id: false do |t|
|
||||
t.integer :id, :options => 'PRIMARY KEY'
|
||||
t.integer :id, options: 'PRIMARY KEY'
|
||||
t.string :doc_number
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#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:
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#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:
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#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:
|
||||
#
|
||||
@@ -11,7 +11,7 @@
|
||||
class CreateQboInvoices < ActiveRecord::Migration[5.1]
|
||||
def change
|
||||
create_table :qbo_invoices, id: false do |t|
|
||||
t.integer :id, :options => 'PRIMARY KEY'
|
||||
t.integer :id, options: 'PRIMARY KEY'
|
||||
t.string :doc_number
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#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:
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#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:
|
||||
#
|
||||
@@ -11,7 +11,7 @@
|
||||
class CreateQboPurchases< ActiveRecord::Migration[5.1]
|
||||
def change
|
||||
create_table :qbo_purchases, id: false do |t|
|
||||
t.integer :id, :options => 'PRIMARY KEY'
|
||||
t.integer :id, options: 'PRIMARY KEY'
|
||||
t.integer :line_id
|
||||
t.string :description
|
||||
t.integer :customer_id
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#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:
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#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:
|
||||
#
|
||||
|
||||
@@ -1,15 +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 UpdateIssuesWithVehicles < ActiveRecord::Migration[5.1]
|
||||
def change
|
||||
add_reference :issues, :vehicles, index: true
|
||||
end
|
||||
end
|
||||
@@ -1,15 +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 UpdateVehicles < ActiveRecord::Migration[5.1]
|
||||
def change
|
||||
add_column :vehicles, :name, :text
|
||||
end
|
||||
end
|
||||
@@ -1,6 +1,6 @@
|
||||
#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:
|
||||
#
|
||||
@@ -12,6 +12,5 @@ class QbocustomersToCustomers< ActiveRecord::Migration[5.1]
|
||||
def change
|
||||
rename_table :qbo_customers, :customers
|
||||
rename_column :issues, :qbo_customer_id, :customer_id
|
||||
rename_column :vehicles, :qbo_customer_id, :customer_id
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#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:
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#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:
|
||||
#
|
||||
@@ -10,12 +10,12 @@
|
||||
|
||||
class AddIssuesQboInvoices < ActiveRecord::Migration[5.1]
|
||||
def self.up
|
||||
create_table :issues_qbo_invoices, :id => false do |t|
|
||||
create_table :issues_qbo_invoices, id: false do |t|
|
||||
t.references :issue
|
||||
t.references :qbo_invoice
|
||||
end
|
||||
|
||||
add_index :issues_qbo_invoices, [:issue_id, :qbo_invoice_id], :unique => true
|
||||
add_index :issues_qbo_invoices, [:issue_id, :qbo_invoice_id], unique: true
|
||||
|
||||
# Now populate it with a SQL one-liner!
|
||||
execute "insert into issues_qbo_invoices(issue_id, qbo_invoice_id) select id, qbo_invoice_id from issues"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#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:
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#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:
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#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:
|
||||
#
|
||||
|
||||
@@ -1,16 +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 UpdateProjects < ActiveRecord::Migration[5.1]
|
||||
def change
|
||||
add_reference :projects, :customer, index: true
|
||||
add_reference :projects, :vehicle, index: true
|
||||
end
|
||||
end
|
||||
@@ -1,6 +1,6 @@
|
||||
#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.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#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:
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#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:
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#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:
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#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:
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#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:
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#The MIT License (MIT)
|
||||
#
|
||||
#Copyright (c) 2023 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:
|
||||
#
|
||||
@@ -9,41 +9,46 @@
|
||||
#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]
|
||||
|
||||
def change
|
||||
add_column :qbo_invoices, :txn_date, :date
|
||||
add_column :qbo_estimates, :txn_date, :date
|
||||
begin
|
||||
add_column :qbo_invoices, :txn_date, :date
|
||||
add_column :qbo_estimates, :txn_date, :date
|
||||
|
||||
reversible do |direction|
|
||||
direction.up {
|
||||
break unless Qbo.first
|
||||
reversible do |direction|
|
||||
direction.up {
|
||||
break unless Qbo.first
|
||||
|
||||
QboEstimate.reset_column_information
|
||||
QboInvoice.reset_column_information
|
||||
QboEstimate.reset_column_information
|
||||
QboInvoice.reset_column_information
|
||||
|
||||
say "Sync Estimates"
|
||||
say "Sync Estimates"
|
||||
|
||||
QboEstimate.sync
|
||||
QboEstimate.sync
|
||||
|
||||
say "Sync Invoices"
|
||||
say "Sync Invoices"
|
||||
|
||||
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
|
||||
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
|
||||
return unless invoices
|
||||
|
||||
invoices.each { |invoice|
|
||||
# Load the invoice into the database
|
||||
qbo_invoice = QboInvoice.find_or_create_by(id: invoice.id)
|
||||
qbo_invoice.doc_number = invoice.doc_number
|
||||
qbo_invoice.id = invoice.id
|
||||
qbo_invoice.customer_id = invoice.customer_ref
|
||||
qbo_invoice.txn_date = invoice.txn_date
|
||||
qbo_invoice.save!
|
||||
invoices.each { |invoice|
|
||||
# Load the invoice into the database
|
||||
qbo_invoice = QboInvoice.find_or_create_by(id: invoice.id)
|
||||
qbo_invoice.doc_number = invoice.doc_number
|
||||
qbo_invoice.id = invoice.id
|
||||
qbo_invoice.customer_id = invoice.customer_ref
|
||||
qbo_invoice.txn_date = invoice.txn_date
|
||||
qbo_invoice.save!
|
||||
}
|
||||
}
|
||||
}
|
||||
end
|
||||
rescue
|
||||
logger.error "AddTxnDates Failed"
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#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:
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#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:
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#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:
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#The MIT License (MIT)
|
||||
#
|
||||
#Copyright (c) 2023 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:
|
||||
#
|
||||
|
||||
57
init.rb
57
init.rb
@@ -1,6 +1,6 @@
|
||||
#The MIT License (MIT)
|
||||
#
|
||||
#Copyright (c) 2023 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:
|
||||
#
|
||||
@@ -8,50 +8,41 @@
|
||||
#
|
||||
#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
|
||||
|
||||
# About
|
||||
name 'Redmine Quickbooks Online plugin'
|
||||
name 'Redmine QBO plugin'
|
||||
author 'Rick Barrette'
|
||||
description 'This is a plugin for Redmine to intergrate with Quickbooks Online to allow for seamless intergration CRM and invoicing of completed issues'
|
||||
version '2.0.5'
|
||||
description 'A pluging for Redmine to connect with QuickBooks Online to create Time Activity Entries for billable hours logged when an Issue is closed'
|
||||
version '2026.2.5'
|
||||
url 'https://github.com/rickbarrette/redmine_qbo'
|
||||
author_url 'https://barrettefabrication.com'
|
||||
settings :default => {'empty' => true}, :partial => 'qbo/settings'
|
||||
requires_redmine :version_or_higher => '5.1.0'
|
||||
settings default: {empty: true}, partial: 'qbo/settings'
|
||||
requires_redmine version_or_higher: '6.1.0'
|
||||
|
||||
# Add safe attributes for core models
|
||||
Issue.safe_attributes 'customer_id'
|
||||
Issue.safe_attributes 'item_id'
|
||||
Issue.safe_attributes 'estimate_id'
|
||||
Issue.safe_attributes 'invoice_id'
|
||||
Issue.safe_attributes 'vehicles_id'
|
||||
User.safe_attributes 'employee_id'
|
||||
TimeEntry.safe_attributes 'billed'
|
||||
Project.safe_attributes 'customer_id'
|
||||
Project.safe_attributes 'vehicle_id'
|
||||
|
||||
# We are playing in the sandbox
|
||||
#Quickbooks.sandbox_mode = true
|
||||
|
||||
Issue.safe_attributes :customer_id
|
||||
Issue.safe_attributes :estimate_id
|
||||
Issue.safe_attributes :invoice_id
|
||||
User.safe_attributes :employee_id
|
||||
TimeEntry.safe_attributes :billed
|
||||
|
||||
# set per_page globally
|
||||
WillPaginate.per_page = 20
|
||||
|
||||
# Permissions for security
|
||||
permission :view_customers, :customers => :index, :public => false
|
||||
permission :add_customers, :customers => :new, :public => false
|
||||
permission :view_vehicles, :vehicles => :new, :public => false
|
||||
|
||||
permission :view_customers, customers: :index, public: false
|
||||
permission :add_customers, customers: :new, public: false
|
||||
|
||||
# Register top menu items
|
||||
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? }
|
||||
menu :top_menu, :customers, { controller: :customers, action: :index }, caption: :label_customers, if: Proc.new {User.current.logged?}
|
||||
|
||||
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 +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 HeaderFooterHookListener < Redmine::Hook::ViewListener
|
||||
|
||||
def view_layouts_base_html_head(context = {})
|
||||
#nothing
|
||||
end
|
||||
|
||||
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>"
|
||||
end
|
||||
end
|
||||
@@ -1,111 +0,0 @@
|
||||
#The MIT License (MIT)
|
||||
#
|
||||
#Copyright (c) 2023 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
|
||||
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)
|
||||
@@ -1,85 +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
|
||||
selected_vehicle = context[:project].vehicle ? context[:project].vehicle.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
|
||||
selected_vehicle = context[:issue].vehicles_id ? context[:issue].vehicles_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 & vehicles
|
||||
if context[: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,
|
||||
context: context,
|
||||
select_estimate: select_estimate,
|
||||
vehicle: vehicle
|
||||
}
|
||||
}
|
||||
)
|
||||
end
|
||||
end
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user