518 Commits
0.0.7 ... 0.4.1

Author SHA1 Message Date
9dda339a32 Version Bump 0.4.1
* Various Bug Fixes
* Added a share button that creates a public view-able link for an issue that lasts 24 Hours
2016-09-15 07:32:38 -04:00
1098accc8a Bug Fix #2 2016-09-15 07:06:52 -04:00
12826cf436 Bug Fix #2 2016-09-15 07:05:19 -04:00
f9f77fdcb1 Bug Fix #2 2016-09-15 07:04:50 -04:00
0bc935d3dd Bug Fix #2 2016-09-15 07:03:54 -04:00
fc40e4a6fe Update README.md 2016-09-14 17:04:33 -04:00
99b658a03d Update qbo_invoice.rb 2016-09-14 16:49:54 -04:00
dacde1e050 Update qbo_invoice.rb 2016-09-14 14:34:50 -04:00
365219eddb Update issues_form_hook_listener.rb 2016-09-14 09:31:22 -04:00
bc4dbbadbb Update issues_form_hook_listener.rb 2016-09-14 09:30:07 -04:00
c06d2300f2 Update vehicles.js 2016-09-14 09:25:05 -04:00
5e34587a53 Update issues_form_hook_listener.rb 2016-09-14 09:24:04 -04:00
eb39b297f9 Update and rename vehicles.js.coffee to vehicles.js 2016-09-14 09:23:38 -04:00
798a7c9933 Update issues_form_hook_listener.rb 2016-09-14 09:00:23 -04:00
9dee336e76 Rename update_vehicles.coffee.js to update_vehicles.js.coffee 2016-09-14 08:59:22 -04:00
9ba43d63b9 Rename vehicles.coffee.js to vehicles.js.coffee 2016-09-14 08:59:05 -04:00
8126671df9 Update vehicles.coffee.js 2016-09-14 08:54:29 -04:00
3f0ccd79f3 Update vehicles.coffee.js 2016-09-14 08:47:56 -04:00
e61023acd5 Update issues_form_hook_listener.rb 2016-09-14 08:43:05 -04:00
35c2e7a951 Update issues_form_hook_listener.rb 2016-09-14 08:42:11 -04:00
81e8a9594f Update issues_form_hook_listener.rb 2016-09-14 08:40:10 -04:00
63415f8e58 Delete autocomplete.js 2016-09-14 08:37:15 -04:00
ed9b1ea7b9 Update issues_form_hook_listener.rb 2016-09-14 08:36:54 -04:00
6026f9cdfc Update issues_form_hook_listener.rb 2016-09-14 08:31:30 -04:00
1924156a8c Rename vehicles.js.coffee to vehicles.coffee.js 2016-09-14 08:30:19 -04:00
3927b2b007 Rename update_vehicles.js.coffee to update_vehicles.coffee.js 2016-09-14 08:29:54 -04:00
7a2e984df7 Update issues_form_hook_listener.rb 2016-09-14 00:31:06 -04:00
2c9559104a Update issues_form_hook_listener.rb 2016-09-14 00:10:40 -04:00
07e342845a Update Gemfile 2016-09-14 00:04:46 -04:00
dbab5bfbca Update issues_form_hook_listener.rb 2016-09-13 23:58:32 -04:00
cc70c95115 Create update_vehicles.js.coffee 2016-09-13 23:57:12 -04:00
03bcff2b9a Create vehicles.js.coffee 2016-09-13 23:55:54 -04:00
9c4ed3d9e1 Update vehicles_controller.rb 2016-09-13 23:43:06 -04:00
8156657eb2 Update issues_show_hook_listener.rb 2016-09-13 23:37:27 -04:00
c2ffedc8de Update issues_show_hook_listener.rb 2016-09-13 23:33:48 -04:00
3600c3e80a Update issues_show_hook_listener.rb 2016-09-13 23:32:45 -04:00
2970bd092c Update issues_show_hook_listener.rb 2016-09-13 23:28:36 -04:00
a2cac188bb Update issues_show_hook_listener.rb 2016-09-13 23:26:37 -04:00
e542f098a8 Update issues_show_hook_listener.rb 2016-09-13 23:24:34 -04:00
04a1670ac2 Update qbo_invoice.rb 2016-09-13 23:17:51 -04:00
c189bc5dca Update qbo_invoice.rb 2016-09-13 23:14:18 -04:00
4b7cf407e8 Update qbo_invoice.rb 2016-09-13 22:57:45 -04:00
332deed21e Update qbo_invoice.rb 2016-09-13 22:56:36 -04:00
41313029cd Update qbo_invoice.rb 2016-09-13 22:54:52 -04:00
bbc3b138cf Update qbo_invoice.rb 2016-09-13 22:48:11 -04:00
e4d5770bdc Update qbo_invoice.rb 2016-09-13 22:44:12 -04:00
53a0a47dd6 Update qbo_invoice.rb 2016-09-13 22:41:51 -04:00
48edc85e2c Update qbo_invoice.rb 2016-09-13 22:39:25 -04:00
c685aaa245 Update qbo_invoice.rb 2016-09-13 22:30:52 -04:00
2a79389b18 Update qbo_invoice.rb 2016-09-13 22:23:36 -04:00
c3e4d0dbc2 Update invoice_controller.rb 2016-09-06 23:33:39 -04:00
89123fed31 Update header_footer_hook_listener.rb 2016-09-06 23:05:24 -04:00
571811ace6 Update header_footer_hook_listener.rb 2016-09-06 23:01:26 -04:00
cb24967713 Update qbo_invoice.rb 2016-09-06 19:09:59 -04:00
449a59188c Update qbo_invoice.rb 2016-09-06 19:02:32 -04:00
907448ce3e Update qbo_invoice.rb 2016-09-06 19:01:27 -04:00
4f08af3987 Update _form.html.erb 2016-09-06 11:00:07 -05:00
00285e1f24 Update vehicle.rb 2016-09-06 10:56:30 -05:00
5b56d7a878 Update customers_controller.rb 2016-09-05 20:40:32 -04:00
5c0d1def9f Update customers_controller.rb 2016-09-05 20:35:10 -04:00
fc3e252fff Update customers_controller.rb 2016-09-05 20:33:27 -04:00
d605f617e4 Update issues_show_hook_listener.rb 2016-09-05 19:58:40 -04:00
eb390e09d8 Update issues_show_hook_listener.rb 2016-09-05 19:57:17 -04:00
bde29ef9d0 Update issue_patch.rb 2016-09-05 19:54:13 -04:00
5ffc9ed01c Update issue_patch.rb 2016-09-05 19:53:06 -04:00
c992370962 Update issues_show_hook_listener.rb 2016-09-05 19:49:17 -04:00
b3acf9f29d Update customers_controller.rb 2016-09-05 19:27:03 -04:00
dca3735ce1 Update customers_controller.rb 2016-09-05 19:25:32 -04:00
c7a5c1147f Update customers_controller.rb 2016-09-03 23:50:24 -04:00
8940e72091 Update customers_controller.rb 2016-09-03 23:48:54 -04:00
1ed7c6fe63 Update customers_controller.rb 2016-09-03 23:47:05 -04:00
a197dcdefc Update README.md 2016-09-03 23:31:11 -04:00
e00f73a48d Update en.yml 2016-09-03 09:53:26 -04:00
1c977a6687 Update query_patch.rb 2016-09-03 09:37:27 -04:00
b3e93bb465 Update time_entry_query_patch.rb 2016-09-03 09:35:23 -04:00
628c798238 Update init.rb 2016-09-03 09:34:17 -04:00
b34bd9dd7c Update en.yml 2016-09-03 09:32:09 -04:00
4d40093fe9 Update time_entry_query_patch.rb 2016-09-03 09:31:38 -04:00
e573da2c11 Update init.rb 2016-09-03 09:30:52 -04:00
f47316efbe Create time_entry_query_patch.rb 2016-09-03 09:28:51 -04:00
f57c3c3df0 Update issue_patch.rb 2016-09-03 09:17:55 -04:00
9b91e4fd63 Update issue_patch.rb 2016-09-03 09:16:13 -04:00
a264e707a8 Update issue_patch.rb 2016-09-03 09:15:37 -04:00
a75a784e8d Update customers_controller.rb 2016-09-03 09:06:52 -04:00
1e04b6ae9f Update view.html.erb 2016-09-02 11:40:18 -04:00
6dfbfccced Update view.html.erb 2016-09-02 11:38:13 -04:00
27288c2eb2 Update view.html.erb 2016-09-02 11:36:59 -04:00
53a1be9761 Update view.html.erb 2016-09-02 11:28:06 -04:00
d1c6492ea3 Update view.html.erb 2016-09-02 11:26:51 -04:00
0f72d88c71 Update customers_controller.rb 2016-09-02 11:22:05 -04:00
9edfcecdaa Update customers_controller.rb 2016-09-02 11:20:31 -04:00
5303b3dfef Update view.html.erb 2016-09-02 11:18:42 -04:00
1213c2e57a Update view.html.erb 2016-09-02 11:17:54 -04:00
49ca69aa78 Update view.html.erb 2016-09-02 11:17:22 -04:00
586f7c8fb9 Update view.html.erb 2016-09-02 11:16:40 -04:00
7a68fcfa92 Update customers_controller.rb 2016-09-02 11:15:18 -04:00
357e5d4490 Update customers_controller.rb 2016-09-02 11:14:01 -04:00
af25326c23 Update view.html.erb 2016-09-02 11:09:12 -04:00
e1db312982 Update view.html.erb 2016-09-02 11:04:46 -04:00
59a418727e Update view.html.erb 2016-09-02 11:04:36 -04:00
c3d9833acb Update view.html.erb 2016-09-02 11:03:48 -04:00
59ebeb48ce Update view.html.erb 2016-09-02 11:03:10 -04:00
7bd23e993e Update view.html.erb 2016-09-02 11:01:54 -04:00
9b15f3f4f6 Update view.html.erb 2016-09-02 11:00:40 -04:00
f087d3c6c0 Update view.html.erb 2016-09-02 10:59:55 -04:00
806b4719fe Update customers_controller.rb 2016-09-02 10:54:58 -04:00
126f4abe0a Update customer_token.rb 2016-09-02 10:52:19 -04:00
5ec76737b3 Update customer_token.rb 2016-09-02 10:50:32 -04:00
1d7bcc24fe Update issue_patch.rb 2016-09-02 10:46:37 -04:00
76a6fce406 Update customer_token.rb 2016-09-02 10:43:50 -04:00
3d44bcb04d Update customers_controller.rb 2016-09-02 10:40:15 -04:00
fd8b5c280c Create view.html.erb 2016-09-02 10:39:43 -04:00
ef6f104d5f Update customers_controller.rb 2016-09-02 10:34:51 -04:00
92538a58e3 Update customers_controller.rb 2016-09-02 10:34:09 -04:00
44bc2f47f1 Update customers_controller.rb 2016-09-02 10:32:58 -04:00
3c9316340f Update routes.rb 2016-09-02 10:31:05 -04:00
3ef9236388 Update customers_controller.rb 2016-09-02 10:29:48 -04:00
dfdde631f9 Update routes.rb 2016-09-02 10:29:26 -04:00
372a6a1b6a Update auth_helper.rb 2016-09-02 10:28:17 -04:00
5a4996abac Update qbo_controller.rb 2016-09-02 10:10:55 -04:00
416df8d3f1 Update auth_helper.rb 2016-09-02 00:29:47 -04:00
0aa7fe8e73 Update auth_helper.rb 2016-09-02 00:25:32 -04:00
f14e82a01b Update auth_helper.rb 2016-09-02 00:24:59 -04:00
5a10065bb0 Update auth_helper.rb 2016-09-02 00:23:49 -04:00
fb801e9260 Update auth_helper.rb 2016-09-02 00:21:52 -04:00
0fa31f815e Update auth_helper.rb 2016-09-02 00:11:01 -04:00
76bd0d4e08 Update customer_token.rb 2016-09-02 00:04:33 -04:00
b31c3ad550 Update customer_token.rb 2016-09-02 00:02:39 -04:00
af7c1b0130 Update customer_token.rb 2016-09-02 00:00:48 -04:00
224b0b4238 Update 023_create_customer_tokens.rb 2016-09-01 23:57:24 -04:00
e6fee6bd97 Update customer_token.rb 2016-09-01 23:52:39 -04:00
731b811cfe Update customer_token.rb 2016-09-01 23:48:48 -04:00
63d969c844 Added Customer Token Model 2016-09-01 23:45:51 -04:00
1138b0d5c9 Rename 21_add_issues_qbo_invoices.rb to 021_add_issues_qbo_invoices.rb 2016-09-01 23:37:07 -04:00
758810135d Rename 20_update_qbos_time_stamp.rb to 020_update_qbos_time_stamp.rb 2016-09-01 23:36:53 -04:00
6eff61b19d Create 022_update_issues_remove_invoice.rb 2016-09-01 23:36:40 -04:00
b3bc17f327 Update Gemfile 2016-09-01 23:30:58 -04:00
67d4ac0ebf Update init.rb 2016-09-01 23:30:25 -04:00
11d3a2d0bf Update init.rb 2016-09-01 23:28:34 -04:00
1f76333af7 Update init.rb 2016-09-01 23:27:26 -04:00
816daeb429 Update init.rb 2016-09-01 23:24:28 -04:00
b1bc19fb7a Update init.rb 2016-09-01 23:23:20 -04:00
578258f9e2 Update Gemfile 2016-09-01 23:19:14 -04:00
41fe8f6a5d Update issues_show_hook_listener.rb 2016-09-01 12:13:06 -04:00
fee0548899 Version Bump 0.4.0
Added "Bill Time" button to Issue view to allow for manual billing of a work ticket without closing. This will be useful for work orders that might take months to complete.

Allow multiple invoices to be tied to a work ticket, since invoices are now automatically attached

Added Last Sync to footer
2016-09-01 08:57:53 -04:00
5ddb45ba24 Update qbo_invoice.rb 2016-09-01 08:52:23 -04:00
98c965c607 Update qbo_invoice.rb 2016-09-01 08:41:22 -04:00
6e1d23af4e Update qbo_invoice.rb 2016-09-01 08:37:21 -04:00
8da45bd348 Update qbo_invoice.rb 2016-09-01 08:33:59 -04:00
620c4b395e Update issue_patch.rb 2016-09-01 08:31:24 -04:00
d75208a75a Update qbo_invoice.rb 2016-09-01 08:31:04 -04:00
4f08825fb1 Update qbo_invoice.rb 2016-09-01 08:29:12 -04:00
865470fc11 Update qbo_invoice.rb 2016-09-01 08:28:01 -04:00
d827936c85 Update issue_patch.rb 2016-09-01 08:26:20 -04:00
fcc614ff54 Update 21_add_issues_qbo_invoices.rb 2016-09-01 08:22:51 -04:00
b7abe2610e Update issue_patch.rb 2016-09-01 08:20:47 -04:00
d916464423 Update qbo_invoice.rb 2016-09-01 08:19:03 -04:00
32164157c2 Update qbo_invoice.rb 2016-09-01 08:06:24 -04:00
ce4b957c8e Update qbo_invoice.rb 2016-09-01 08:00:16 -04:00
3735629073 Update qbo_invoice.rb 2016-09-01 07:58:38 -04:00
d41d618be5 Update issues_save_hook_listener.rb 2016-09-01 02:37:31 -04:00
d97f3cb2a3 Update header_footer_hook_listener.rb 2016-09-01 02:29:33 -04:00
dcf31116b4 Update header_footer_hook_listener.rb 2016-09-01 02:28:25 -04:00
219141eeee Update header_footer_hook_listener.rb 2016-09-01 02:26:47 -04:00
765b5b6024 Update header_footer_hook_listener.rb 2016-09-01 02:25:57 -04:00
4efca93d03 Update header_footer_hook_listener.rb 2016-09-01 02:25:17 -04:00
e2a4908420 Update header_footer_hook_listener.rb 2016-09-01 02:22:58 -04:00
183b8d17e6 Update header_footer_hook_listener.rb 2016-09-01 02:22:29 -04:00
ed2b84c697 Update init.rb 2016-09-01 02:20:31 -04:00
59410e6d77 Create header_footer_hook_listener.rb 2016-09-01 02:19:06 -04:00
a134d1b601 Update issues_show_hook_listener.rb 2016-09-01 01:59:42 -04:00
56161f12d0 Update issues_form_hook_listener.rb 2016-09-01 01:58:10 -04:00
146dbb137c Update issues_form_hook_listener.rb 2016-09-01 01:55:44 -04:00
4f23439dac Update issues_show_hook_listener.rb 2016-09-01 01:52:45 -04:00
8b33aa6f6a Update issues_form_hook_listener.rb 2016-09-01 01:52:19 -04:00
4f72a8e5ad Update issue_patch.rb 2016-09-01 01:48:43 -04:00
fae4782ef0 Update qbo_controller.rb 2016-09-01 01:47:43 -04:00
37ea01de8c Update qbo_controller.rb 2016-09-01 01:42:45 -04:00
2c53155207 Update qbo_controller.rb 2016-09-01 01:41:54 -04:00
dbe585ca2a Update issue_patch.rb 2016-09-01 01:40:09 -04:00
6434092306 Update qbo_controller.rb 2016-09-01 01:38:59 -04:00
8720176b57 Update qbo_controller.rb 2016-09-01 01:37:57 -04:00
5bdf313fa5 Update qbo_controller.rb 2016-09-01 01:36:33 -04:00
4527e74d29 Update issues_show_hook_listener.rb 2016-09-01 01:35:11 -04:00
8e6e543c5b Update issues_show_hook_listener.rb 2016-09-01 01:32:22 -04:00
dbbd4a2593 Update issues_show_hook_listener.rb 2016-09-01 01:31:43 -04:00
6b55f92454 Update issues_show_hook_listener.rb 2016-09-01 01:29:53 -04:00
fba9645932 Update routes.rb 2016-09-01 01:28:16 -04:00
0cc867b410 Update issues_show_hook_listener.rb 2016-09-01 01:27:24 -04:00
40f738d976 Update routes.rb 2016-09-01 01:24:14 -04:00
82ecaae156 Update qbo_controller.rb 2016-09-01 01:22:16 -04:00
8d4ac896fa Update issue_patch.rb 2016-09-01 01:14:55 -04:00
7c6246a539 Update issues_show_hook_listener.rb 2016-09-01 01:11:46 -04:00
ce88cdd258 No Duplicates! 2016-09-01 01:10:17 -04:00
f179b04af1 Update and rename 21_add_qbo_invoices_issues.rb to 21_add_issues_qbo_invoices.rb 2016-09-01 00:49:15 -04:00
0070264d51 Update 21_add_qbo_invoices_issues.rb 2016-09-01 00:18:56 -04:00
22db89a6d9 Update 21_add_qbo_invoices_issues.rb 2016-09-01 00:17:45 -04:00
78dfad9875 Update issues_show_hook_listener.rb 2016-09-01 00:12:01 -04:00
6a55138f7c Update 21_add_qbo_invoices_issues.rb 2016-09-01 00:04:27 -04:00
f95ee10290 Update qbo_invoice.rb 2016-09-01 00:01:56 -04:00
adb864c9ca Update 21_add_qbo_invoices_issues.rb 2016-09-01 00:00:10 -04:00
f3f92e48e0 Create 21_add_qbo_invoices_issues.rb 2016-08-31 23:58:52 -04:00
87a9d978c2 Update qbo_invoice.rb 2016-08-31 23:54:20 -04:00
9981b9ef70 Update issue_patch.rb 2016-08-31 23:54:00 -04:00
94f10dc9cd Update issue_patch.rb 2016-08-31 23:52:55 -04:00
e3fdc070df Update issue_patch.rb 2016-08-31 23:50:35 -04:00
4cae63d02b Update issues_show_hook_listener.rb 2016-08-31 23:47:40 -04:00
d856ceeec7 Update issues_show_hook_listener.rb 2016-08-31 23:45:26 -04:00
d461570b14 Update issues_show_hook_listener.rb 2016-08-31 23:44:02 -04:00
924e0d7bc9 Update issues_show_hook_listener.rb 2016-08-31 23:43:14 -04:00
6b3280edaf Update issues_show_hook_listener.rb 2016-08-31 23:41:44 -04:00
722d66f130 Added bill_time 2016-08-31 23:37:57 -04:00
16083a6f30 Update qbo_invoice.rb 2016-08-15 08:37:07 -04:00
1b6fe073dc Update payments_controller.rb 2016-08-15 07:48:40 -04:00
9f6103ad89 Update payments_controller.rb 2016-08-15 07:47:44 -04:00
8a67cdf37c Update payments_controller.rb 2016-08-15 07:46:48 -04:00
9b444d638b Update payments_controller.rb 2016-08-15 07:45:32 -04:00
abab81158f Update payments_controller.rb 2016-08-15 07:44:47 -04:00
26c0716d35 Sort! 2016-08-15 07:43:46 -04:00
7f7c724ef1 Update qbo_invoice.rb 2016-08-15 07:41:56 -04:00
f083e8257a Simplified & Removed the unnecessary nested loops 2016-08-15 07:39:51 -04:00
4cb588e992 Added is_changed to stop endless update loop with webhook 2016-08-12 19:25:03 -04:00
245d2b49a2 Update invoice outside of loop (woops) 2016-08-12 16:56:35 -04:00
e888bd0d38 Update README.md 2016-08-11 00:49:54 -04:00
6cfd56ed01 Update qbo_invoice.rb 2016-08-11 00:42:49 -04:00
647af6f87a Update qbo_invoice.rb 2016-08-11 00:35:53 -04:00
0e2f9b1031 Update qbo_invoice.rb 2016-08-11 00:34:09 -04:00
86ee8908b3 Update qbo_invoice.rb 2016-08-11 00:31:45 -04:00
a0618b51ba Update qbo_invoice.rb 2016-08-11 00:18:14 -04:00
272369ba4c Delete vehicles.js 2016-08-10 23:40:51 -04:00
6319c24b5c Update customer.rb 2016-08-10 23:39:52 -04:00
a3180a318c Delete _dropdown.html.erb 2016-08-10 23:37:06 -04:00
aeb890cbed Delete update_vehicles.js.erb 2016-08-10 23:36:27 -04:00
4a94ca1d17 Delete without_callback.rb 2016-08-10 23:35:47 -04:00
63d845ed97 Update issue_patch.rb 2016-08-10 23:33:53 -04:00
cb67cab974 Update routes.rb 2016-08-10 23:33:34 -04:00
df94564d9b Update issues_form_hook_listener.rb 2016-08-10 23:33:15 -04:00
540e008f68 Update issues_form_hook_listener.rb 2016-08-10 23:32:14 -04:00
c85d3ba8d5 Update routes.rb 2016-08-10 23:31:42 -04:00
1df335ed16 Update issue_patch.rb 2016-08-10 23:30:48 -04:00
7ca5076477 Update customers_controller.rb 2016-08-10 23:30:16 -04:00
6605946e62 Update issues_form_hook_listener.rb 2016-08-10 23:20:56 -04:00
ba45c776ae Update routes.rb 2016-08-10 23:19:54 -04:00
dcf17052b6 Update customers_controller.rb 2016-08-10 23:17:57 -04:00
8b9acccb8a Update issues_form_hook_listener.rb 2016-08-10 23:16:09 -04:00
2afed176f0 Update issues_form_hook_listener.rb 2016-08-10 23:10:23 -04:00
577788110e Update issues_form_hook_listener.rb 2016-08-10 23:09:00 -04:00
d251ea066f Update issues_form_hook_listener.rb 2016-08-10 23:07:39 -04:00
609e65b7cd Update issues_form_hook_listener.rb 2016-08-10 23:04:16 -04:00
6c2dd29a57 Update issues_form_hook_listener.rb 2016-08-10 23:01:56 -04:00
98896ac0a6 Update issues_form_hook_listener.rb 2016-08-10 22:59:37 -04:00
19ba3abade Update issues_form_hook_listener.rb 2016-08-10 22:55:12 -04:00
3347490b82 Update routes.rb 2016-08-10 22:54:37 -04:00
d1457b09be Update customers_controller.rb 2016-08-10 22:54:05 -04:00
1b71439f19 Update Gemfile 2016-08-10 22:51:29 -04:00
55c09d7e9d Update Gemfile 2016-08-10 22:50:26 -04:00
8429c29c30 Update issues_save_hook_listener.rb 2016-08-10 22:47:45 -04:00
10f8a7e124 Update issues_form_hook_listener.rb 2016-08-10 22:46:16 -04:00
e8763ea923 Update issues_form_hook_listener.rb 2016-08-10 22:44:20 -04:00
e5601030b1 Update issues_form_hook_listener.rb 2016-08-10 22:42:43 -04:00
ad8d15203e Update customers_controller.rb 2016-08-10 22:38:26 -04:00
3b4e55727c Update routes.rb 2016-08-10 22:36:45 -04:00
5dc2921d40 Update issues_form_hook_listener.rb 2016-08-10 22:34:49 -04:00
0c4ef8abe9 Create autocomplete.js 2016-08-10 22:33:46 -04:00
8165523acf Update Gemfile 2016-08-10 22:32:42 -04:00
7d1e9bb838 Update routes.rb 2016-08-10 22:09:16 -04:00
0d9140958f Update issue_patch.rb 2016-08-10 22:06:54 -04:00
16ca8177e9 Update README.md 2016-08-10 22:03:50 -04:00
a0e9061a8f Update qbo_controller.rb 2016-08-10 01:24:18 -04:00
a56c01fe6d Update README.md 2016-08-10 01:20:24 -04:00
1cb9639f03 Added skipping of unknown items 2016-08-10 00:33:41 -04:00
7af89db442 Removed Item & Invoice Dropdowns 2016-08-10 00:29:25 -04:00
fae815fd7f Removed Item 2016-08-10 00:26:59 -04:00
1b533d6dd8 Update issues_save_hook_listener.rb 2016-08-10 00:22:16 -04:00
bc38361348 Update issues_save_hook_listener.rb 2016-08-10 00:19:22 -04:00
a0a365c10e Update issues_save_hook_listener.rb 2016-08-10 00:14:56 -04:00
162c76471b Update issues_save_hook_listener.rb 2016-08-10 00:13:16 -04:00
328a50be47 Update issues_save_hook_listener.rb 2016-08-10 00:08:13 -04:00
7cc84277c6 Update issues_save_hook_listener.rb 2016-08-10 00:03:04 -04:00
fbac6b6d77 Update issues_save_hook_listener.rb 2016-08-09 23:47:58 -04:00
33b5ac8c87 Update issues_save_hook_listener.rb 2016-08-09 23:35:03 -04:00
74f179d64b Update init.rb 2016-08-06 23:20:54 -04:00
3cef188ff3 Update customer.rb 2016-08-06 23:19:32 -04:00
1b767f78d2 Version bump 0.3.0
* Added Payments
* Other things that I don't remember...
2016-08-05 22:00:57 -04:00
f380969082 Update payments_controller.rb 2016-08-05 21:59:21 -04:00
df6acde327 Update payments_controller.rb 2016-08-05 21:57:31 -04:00
0318ffaa10 Update payments_controller.rb 2016-08-05 21:56:23 -04:00
51c1b38197 Update init.rb 2016-08-05 21:45:08 -04:00
d96bd1a3f4 Update init.rb 2016-08-05 21:44:00 -04:00
b6e43b5837 Update payment.rb 2016-08-05 21:41:52 -04:00
62fa98a656 Update payment.rb 2016-08-05 21:39:53 -04:00
bb5a080f25 Update payment.rb 2016-08-05 21:37:20 -04:00
2afa9e4166 Update payment.rb 2016-08-05 21:36:01 -04:00
b489a2771f Update payment.rb 2016-08-05 21:28:56 -04:00
0495ac1bc5 Update payment.rb 2016-08-05 21:26:11 -04:00
e3b49358bb Update payment.rb 2016-08-05 21:25:37 -04:00
08b365e69e Update payment.rb 2016-08-05 21:24:49 -04:00
5d4c49c85d Update payment.rb 2016-08-05 21:23:07 -04:00
5bc9ca34a4 Update payment.rb 2016-08-05 21:20:58 -04:00
630a1d144b Update payment.rb 2016-08-05 21:20:06 -04:00
491684f7df Update payment.rb 2016-08-05 21:18:33 -04:00
9a28247b7f Update payment.rb 2016-08-05 21:17:31 -04:00
5a91e21d45 Update payments_controller.rb 2016-08-05 21:08:05 -04:00
f6f1ca4c04 Update _form.html.erb 2016-08-05 21:01:31 -04:00
8daa10888f Update routes.rb 2016-08-05 21:00:03 -04:00
82449642d3 Update routes.rb 2016-08-05 20:58:55 -04:00
06ad2d6971 Update and rename app/views/payment/new.html.erb to app/views/payments/new.html.erb 2016-08-05 20:58:27 -04:00
4c4ca67be8 Rename app/views/payment/_form.html.erb to app/views/payments/_form.html.erb 2016-08-05 20:58:06 -04:00
b994f7c142 Update and rename payment_controller.rb to payments_controller.rb 2016-08-05 20:57:44 -04:00
97b483031d Update payment.rb 2016-08-05 20:56:04 -04:00
c624c20354 Update payment.rb 2016-08-05 20:53:55 -04:00
695e3bd24c Update payment.rb 2016-08-05 20:43:27 -04:00
ce4883cd4c Update payment_controller.rb 2016-08-05 20:42:25 -04:00
49e19cb73f Create payment.rb 2016-08-05 20:41:52 -04:00
31bb242a61 Update new.html.erb 2016-08-05 20:39:18 -04:00
2e533e8798 Rename app/views/payments/new.html.erb to app/views/payment/new.html.erb 2016-08-05 20:37:47 -04:00
e70d0c8d17 Rename app/views/payments/_form.html.erb to app/views/payment/_form.html.erb 2016-08-05 20:37:31 -04:00
d96ecd2b66 Create new.html.erb 2016-08-05 20:28:35 -04:00
a588ac19a6 Fixed typo 2016-08-05 20:27:25 -04:00
2bc8ec4f56 Added payments 2016-08-05 20:25:34 -04:00
578a5a1228 Create payment_controller.rb 2016-08-05 20:20:05 -04:00
416595ffea Create _form.html.erb 2016-08-05 20:09:49 -04:00
d3be59fbc5 Update _details.html.erb 2016-08-05 09:07:50 -04:00
3c3b4da313 Update _details.html.erb 2016-08-05 09:06:58 -04:00
501834419b Update _details.html.erb 2016-08-05 09:02:46 -04:00
75b25a9e44 Account Balance & Adresses 2016-08-05 09:01:09 -04:00
15912b2197 QueryValidationError: property 'PrimaryPhone' is not queryable 2016-08-05 08:52:33 -04:00
b093f6136e Update customer.rb 2016-08-05 08:49:59 -04:00
33db0a53ba Update customer.rb 2016-08-05 08:46:48 -04:00
7237d2e643 Search Phone Numbers 2016-08-05 08:43:44 -04:00
77b1c1dbef Update vehicle.rb 2016-08-05 00:56:28 -04:00
f3090bd1a4 Update vehicle.rb 2016-08-05 00:54:34 -04:00
89a131018c Update vehicles_controller.rb 2016-08-05 00:47:37 -04:00
de17fb80d1 Update _form.html.erb 2016-08-05 00:45:55 -04:00
a5f1d15156 Update vehicles_controller.rb 2016-08-05 00:45:23 -04:00
d3463ce41b Removed link 2016-08-05 00:39:53 -04:00
4503150b02 Removed hiding of notes 2016-08-05 00:37:29 -04:00
36cd00822e Removed New Vehicle Button 2016-08-05 00:33:14 -04:00
d285344a61 Added customer count 2016-08-05 00:30:00 -04:00
8418dfc0b5 Update vehicles_controller.rb 2016-08-05 00:04:31 -04:00
eb039368bb Version bump 0.2.0
* New Top & Application Menus
* Move quickbooks stats into QBO settings page
* Fixed New vehicle for customer link
* Added issue linking from hashtag numbers in QBO invoice line items
* Cleaned up customer & vehicle lists
* Implemented delete calls from web hook
* Added webhook token verification
* More...
2016-08-04 10:12:12 -04:00
0dea5917a7 Update _settings.html.erb 2016-08-04 10:06:07 -04:00
a8ccde6c81 Update _settings.html.erb 2016-08-04 10:05:07 -04:00
787ae1b8df Update _settings.html.erb 2016-08-04 10:03:28 -04:00
276c89d4ac Update init.rb 2016-08-04 09:59:12 -04:00
9a395ee25c Update _list.html.erb 2016-08-04 09:53:02 -04:00
475c86eabe Update show.html.erb 2016-08-04 09:52:32 -04:00
259737a488 Update show.html.erb 2016-08-04 09:51:47 -04:00
362cb77381 Update show.html.erb 2016-08-04 09:50:55 -04:00
8cfab17136 Update show.html.erb 2016-08-04 09:50:37 -04:00
f0018ab87d Update _details.html.erb 2016-08-04 09:49:52 -04:00
8f87eb3e60 Update show.html.erb 2016-08-04 09:48:19 -04:00
2b093903b3 Update show.html.erb 2016-08-04 09:46:22 -04:00
05017dcc4f Update show.html.erb 2016-08-04 09:45:39 -04:00
0e9b5fa17a Update show.html.erb 2016-08-04 09:44:09 -04:00
dd335aff71 Update _form.html.erb 2016-08-04 00:18:32 -04:00
0f61bf54ce Update index.html.erb 2016-08-04 00:15:33 -04:00
14cb22d743 Update vehicles_controller.rb 2016-08-04 00:13:47 -04:00
702ab5013e Update index.html.erb 2016-08-04 00:11:54 -04:00
235e2c6e7b Update routes.rb 2016-08-04 00:10:46 -04:00
2e89a60d63 Update vehicles_controller.rb 2016-08-04 00:09:52 -04:00
3d5ef2cd8a Update vehicles_controller.rb 2016-08-04 00:08:21 -04:00
de8eff9bd2 Update vehicles_controller.rb 2016-08-04 00:03:35 -04:00
a9561d1694 Update vehicles_controller.rb 2016-08-04 00:02:56 -04:00
aa33de00d2 Update index.html.erb 2016-08-04 00:01:15 -04:00
ffc589fe80 Update index.html.erb 2016-08-03 23:59:50 -04:00
229e4e8d39 Update index.html.erb 2016-08-03 23:57:28 -04:00
d6dda2cdd6 Update init.rb 2016-08-03 23:54:17 -04:00
b8a101fddb Update routes.rb 2016-08-03 23:50:55 -04:00
c8a875b301 Update init.rb 2016-08-03 23:45:30 -04:00
df8e3a7465 Update init.rb 2016-08-03 23:35:43 -04:00
d91a6e3939 Update init.rb 2016-08-03 23:15:38 -04:00
48a2d683dd Update init.rb 2016-08-03 23:13:55 -04:00
44bf42c548 Update init.rb 2016-08-03 23:11:00 -04:00
d34e6cb0fd Update issues_form_hook_listener.rb 2016-08-03 23:05:19 -04:00
d8e7356ca3 Update init.rb 2016-08-03 23:03:59 -04:00
60e6dbaa6f Update issues_form_hook_listener.rb 2016-08-03 22:50:53 -04:00
47e5a7d0e4 Update issues_form_hook_listener.rb 2016-08-03 22:48:26 -04:00
9fa2165907 Update issues_form_hook_listener.rb 2016-08-03 22:46:42 -04:00
7385d7018c Update issues_form_hook_listener.rb 2016-08-03 22:45:23 -04:00
6124c1b307 Update issues_form_hook_listener.rb 2016-08-03 22:44:56 -04:00
b73535c6da Update issues_form_hook_listener.rb 2016-08-03 22:43:40 -04:00
1581023656 Update vehicles.js 2016-08-03 22:39:33 -04:00
0d21e2967d Rename vehicles.js.cof to vehicles.js 2016-08-03 22:37:48 -04:00
0dc7d83fbe Update issues_form_hook_listener.rb 2016-08-03 22:37:30 -04:00
cd18067384 Rename vehicles.js to vehicles.js.coffee 2016-08-03 22:36:12 -04:00
6c99f7095c Update issues_form_hook_listener.rb 2016-08-03 22:35:49 -04:00
eeaafce427 Rename vehicles.js.coffee to vehicles.js 2016-08-03 22:32:19 -04:00
b7cb27b5da Update vehicles.js.coffee 2016-08-03 22:30:04 -04:00
3e6286da7c Update vehicles.js.coffee 2016-08-03 22:28:31 -04:00
30ceea7fd5 Update vehicles_controller.rb 2016-08-03 22:23:07 -04:00
de9e973fd9 Update routes.rb 2016-08-03 22:22:23 -04:00
49a3bd5790 Update routes.rb 2016-08-03 22:19:23 -04:00
f1745930b1 Update routes.rb 2016-08-03 22:15:43 -04:00
d9beda8171 Rename update_vehicles.js.coffee.erb to update_vehicles.js.erb 2016-08-03 22:14:23 -04:00
65f343fb74 Update vehicles_controller.rb 2016-08-03 22:13:18 -04:00
892bd65fac Rename update_vehicles.js.coffee to update_vehicles.js.coffee.erb 2016-08-03 22:10:50 -04:00
0251191844 Update vehicles_controller.rb 2016-08-03 22:05:25 -04:00
65f6f52252 Update update_vehicles.js.coffee 2016-08-03 21:58:47 -04:00
4d94308bcc Update vehicles_controller.rb 2016-08-03 21:57:50 -04:00
7dcd8b24d2 Update vehicles.js.coffee 2016-08-03 21:55:31 -04:00
11da8e7a43 Update vehicles_controller.rb 2016-08-03 21:54:05 -04:00
56c895388d Update vehicles.js.coffee 2016-08-03 21:53:31 -04:00
8ec9567f15 Update issues_form_hook_listener.rb 2016-08-03 21:50:02 -04:00
be3dd0d131 Rename assets/javascripts/app/assets/vehicles.js.coffee to assets/javascripts/vehicles.js.coffee 2016-08-03 21:40:04 -04:00
92f51d9884 Create _vehicle.html.erb 2016-08-03 21:34:10 -04:00
c4904a0ac2 Create update_vehicles.js.coffee 2016-08-03 21:29:01 -04:00
0d87e5fb21 Create vehicles.js.coffee 2016-08-03 21:26:21 -04:00
d38e3e1702 Update routes.rb 2016-08-03 21:22:34 -04:00
fec59a7495 Update vehicles_controller.rb 2016-08-03 21:20:08 -04:00
a3b5ad0cb0 Update show.html.erb 2016-08-03 21:16:31 -04:00
bf21451819 Update show.html.erb 2016-08-03 21:15:40 -04:00
c6d3d9673b Update show.html.erb 2016-08-03 21:15:03 -04:00
3f5334a92d Update show.html.erb 2016-08-03 21:14:25 -04:00
bde7b83752 Update show.html.erb 2016-08-03 21:13:40 -04:00
c788e5724a Update show.html.erb 2016-08-03 21:12:54 -04:00
295cd12f9d Update show.html.erb 2016-08-03 21:11:57 -04:00
4a432481d9 Update show.html.erb 2016-08-03 21:11:05 -04:00
4a37d83694 Update show.html.erb 2016-08-03 21:09:15 -04:00
15a2a16379 Update show.html.erb 2016-08-03 21:08:42 -04:00
18fc7a6c8c Update vehicles_controller.rb 2016-08-03 21:02:45 -04:00
7aba8cdce3 Update qbo_invoice.rb 2016-08-03 16:40:05 -04:00
382e6675f1 Update qbo_invoice.rb 2016-08-03 16:39:07 -04:00
116d6896f4 Update qbo_invoice.rb 2016-08-03 16:38:09 -04:00
c9ced52112 Update qbo_invoice.rb 2016-08-03 16:36:50 -04:00
01b4bb4e53 Update qbo_invoice.rb 2016-08-03 16:35:04 -04:00
a266da2cd7 Update qbo_invoice.rb 2016-08-03 16:33:38 -04:00
578e7ba807 Update qbo_invoice.rb 2016-08-03 16:32:52 -04:00
b923e15d46 Update qbo_invoice.rb 2016-08-03 16:31:17 -04:00
1310d1e63e Update qbo_invoice.rb 2016-08-03 16:27:30 -04:00
a8e1e8429c Update qbo_invoice.rb 2016-08-03 16:25:33 -04:00
1b54b40f6c Update qbo_invoice.rb 2016-08-03 16:22:44 -04:00
6d7530922d Update qbo_invoice.rb 2016-08-03 16:20:26 -04:00
23698986b1 Update qbo_invoice.rb 2016-08-03 16:15:50 -04:00
1b4c377940 Update _details.html.erb 2016-08-01 22:35:12 -04:00
d33c0c9aa5 Update _details.html.erb 2016-08-01 22:34:38 -04:00
09d8c0024f Update _details.html.erb 2016-08-01 22:33:32 -04:00
06e827fff8 Version bump 0.1.0 2016-08-01 22:30:47 -04:00
b1844689df Update qbo_controller.rb 2016-08-01 22:29:53 -04:00
a4263a92ca Update issues_show_hook_listener.rb 2016-08-01 22:20:57 -04:00
14cc251809 Update Gemfile 2016-08-01 21:56:17 -04:00
471e8f3398 Update qbo_controller.rb 2016-08-01 21:56:00 -04:00
dadbda62c6 Update qbo_controller.rb 2016-08-01 21:55:30 -04:00
df47efe816 Update qbo_controller.rb 2016-08-01 21:54:31 -04:00
03cc6943a3 Update Gemfile 2016-08-01 21:51:04 -04:00
6f0163ce7d Update qbo_controller.rb 2016-08-01 21:49:17 -04:00
91110adad5 Update qbo_controller.rb 2016-08-01 21:47:30 -04:00
c2f48d0277 Update qbo_controller.rb 2016-08-01 21:41:47 -04:00
06344b6498 Update qbo_controller.rb 2016-08-01 21:34:14 -04:00
4ff2b2bdc6 Update _settings.html.erb 2016-08-01 21:28:15 -04:00
a71dd310fe Update issues_show_hook_listener.rb 2016-08-01 21:24:22 -04:00
90da7a5d74 Update issues_show_hook_listener.rb 2016-08-01 21:23:15 -04:00
6505f54c7f Update issues_show_hook_listener.rb 2016-08-01 21:22:31 -04:00
c4a488e5a7 Update issues_show_hook_listener.rb 2016-08-01 21:21:41 -04:00
71817f5ca8 Update issues_show_hook_listener.rb 2016-08-01 21:18:31 -04:00
77c97ef2c1 Update vehicle.rb 2016-08-01 21:14:02 -04:00
875ec19e38 Update customer.rb 2016-08-01 21:09:39 -04:00
f47e77f816 Version bump 0.0.8 2016-08-01 21:08:49 -04:00
144a52f813 Add files via upload 2016-08-01 21:07:01 -04:00
f9a5269fd7 Delete plugin_issue_edit.png 2016-08-01 21:06:48 -04:00
bc1445f8bb Update qbo_employee.rb 2016-08-01 21:01:06 -04:00
01e5415074 Update qbo_controller.rb 2016-08-01 20:57:51 -04:00
697ff4f9d5 Update qbo_controller.rb 2016-08-01 20:54:42 -04:00
fe7cfc6b1d Update qbo_controller.rb 2016-08-01 20:52:24 -04:00
7a7e148719 Update README.md 2016-08-01 20:38:04 -04:00
95db8f9839 Update _list.html.erb 2016-08-01 20:31:48 -04:00
7fb91ae10b Update _list.html.erb 2016-08-01 20:29:58 -04:00
0c5c778c75 Update _list.html.erb 2016-08-01 20:28:18 -04:00
38865bd062 Update vehicle.rb 2016-08-01 19:34:28 -04:00
e201765f02 Update vehicles_controller.rb 2016-08-01 19:31:51 -04:00
d9ccffe3d6 Update vehicles_controller.rb 2016-08-01 19:30:25 -04:00
86e084574e Update _list.html.erb 2016-08-01 19:28:22 -04:00
756e60b865 Update index.html.erb 2016-08-01 19:09:41 -04:00
5c49094b40 Update index.html.erb 2016-08-01 19:08:49 -04:00
336d1c7c7b Update vehicles_controller.rb 2016-08-01 19:05:49 -04:00
632b788082 Update vehicle.rb 2016-08-01 19:04:46 -04:00
ddd00a3e9a Update show.html.erb 2016-08-01 19:02:49 -04:00
54e59fbd98 Update show.html.erb 2016-08-01 17:00:54 -04:00
3f29a024f9 Update show.html.erb 2016-08-01 17:00:25 -04:00
c5f03ed03c Update _details.html.erb 2016-08-01 16:59:35 -04:00
547880443c Update _details.html.erb 2016-08-01 16:58:51 -04:00
838733fdc3 Update README.md 2016-08-01 16:55:56 -04:00
4b068266a9 Add files via upload 2016-08-01 16:48:46 -04:00
57c78f27a7 Delete plugin_config.png 2016-08-01 16:48:31 -04:00
3af5caef4a Update index.html.erb 2016-08-01 16:41:59 -04:00
49425656ee Update index.html.erb 2016-08-01 16:40:50 -04:00
3e85216e66 Update qbo.rb 2016-08-01 16:39:58 -04:00
443d6fc47c Update customer.rb 2016-08-01 16:13:58 -04:00
4d7bc59bd3 Update customer.rb 2016-08-01 16:12:08 -04:00
4dbeee0aa1 Update index.html.erb 2016-08-01 16:09:30 -04:00
9b137fed69 Update index.html.erb 2016-08-01 16:02:05 -04:00
9c667c20da Update index.html.erb 2016-08-01 16:01:24 -04:00
e503c965c3 Update index.html.erb 2016-08-01 15:59:51 -04:00
933d1eb730 Update qbo_controller.rb 2016-08-01 15:54:40 -04:00
c99fe57074 Update qbo_controller.rb 2016-08-01 15:52:02 -04:00
77fc54dc31 Update customer.rb 2016-08-01 15:48:28 -04:00
37f6518a15 Update customer.rb 2016-08-01 15:47:10 -04:00
bcaf011166 Update customers_controller.rb 2016-08-01 15:46:37 -04:00
27807e963d Update customers_controller.rb 2016-08-01 15:45:57 -04:00
f0fabc5e10 Update customers_controller.rb 2016-08-01 15:45:10 -04:00
e7c85eac4d Update index.html.erb 2016-08-01 15:43:45 -04:00
01ea01fef6 Update index.html.erb 2016-08-01 15:42:42 -04:00
134fb776f9 Update index.html.erb 2016-08-01 15:40:17 -04:00
9cd143c5ef Update index.html.erb 2016-08-01 15:39:04 -04:00
ba18275ef8 Update index.html.erb 2016-08-01 15:37:32 -04:00
6e92648d8b Update customers_controller.rb 2016-08-01 15:36:10 -04:00
8ddc612bba Extended search 2016-08-01 15:34:17 -04:00
2324aadcd5 Extended search 2016-08-01 15:31:54 -04:00
baff3f5a1b Extended search 2016-08-01 15:30:00 -04:00
68b6ea7649 Update _list.html.erb 2016-07-28 23:33:15 -04:00
c46cab6a6f Update customers_controller.rb 2016-07-28 23:30:49 -04:00
74807c73b0 Jump to customer is there is only one result 2016-07-28 23:27:02 -04:00
a26214fef7 Search 2016-07-28 20:50:23 -04:00
ec77a004a2 Search 2016-07-28 20:49:38 -04:00
f33203b0e3 Search 2016-07-28 20:48:34 -04:00
297dd8ec4e Search 2016-07-28 20:46:49 -04:00
bbc2ae4750 Search 2016-07-28 20:45:29 -04:00
fb1a560751 Search 2016-07-28 20:44:15 -04:00
48 changed files with 1084 additions and 311 deletions

View File

@@ -7,3 +7,7 @@ gem 'oauth'
gem 'roxml'
gem 'edmunds_vin'
gem 'will_paginate', '~> 3.1.0'
group :assets do
gem 'coffee-rails'
end

View File

@@ -1,25 +1,38 @@
#Redmine Quickbooks Online
A simple 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 completed work 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 completed work when an Issue is closed.
`Note: This project is under heavy development. Currently the initial functionality goal has been meet, however I am still working on adding other features. Tags should be stable`
`Note: Although the core functionality is complete, this project is still under heavy development. I am still working on refining everthing and adding other features. Tags should be stable`
####How it works
* Issues can be assigned to a QBO Customer and QBO Service Item via drop down in issues form
- The `QBO Employee` for the issue is assigned via the assigned redmine user
- IF an `Issue` has been assined a `QBO Customer`, `QBO Service Item` & `QBO Employee` when an `Issue` is closed the following will happen:
- A new `QBO Time Activity` agaist the `QBO Customer` will be created using the total spent hours logged agaist an `Issue`.
- The rate will be the set via the `QBO Service Item` price
* `Issues` with the Tracker `Quote` will generate an estimate based on the estimated hours and `QBO Service Item` cost.
- Needs to have a `QBO Customer` & `QBO Service Item` Assiged
* Users will be assigned a `QBO Employee` via a drop down in the user admistration page.
`Note: I am currently using this in a live production enviroment with no issues`
####Features
* Issues can be assigned to a `Customer` via drop down in the edit Issue form
* The `Employee` for the Issue is assigned via the assigned Redmine User
- This is set 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 lookup `Items` in Quickbooks.
+ IF there isn'tany Items that match the Activity name it will be skipped, and will not be billed to the `Customer`
- Labor Rates are set by the `Item` in Quickbooks
* `Payments` Can be created via the Redmine application menu
* `Customers` Can be created via the Redmine application menu
- `Customers` can be searched
- 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
##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
- See https://developer.intuit.com/docs/0100_accounting/0300_developer_guides/webhooks
##The Install
@@ -33,30 +46,32 @@ The goal of this project is to allow redmine to connect with Quickbooks Online t
3. Navigate to the plugin configuration page and suppy your own OAuth key & secret.
![Alt plugin_config](/Screenshots/plugin_config.png)
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
![Alt plugin_user_edit](/Screenshots/plugin_user_edit.png)
## Automatic Deploy
If you want the redmine server to be automaticly restarted after a git pull event add this hook to your git hook directory
https://gist.github.com/rickbarrette/3c999c7f37e321f9c60380de99e494f5
## Usage
To enable automatic `QBO Time Activity` entries for an `Issue` , you need only to assign a `QBO Customer` and `QBO Item` to an `Issue` via drop downs in the creation/update form.
![Alt plugin_issue-edit](/Screenshots/plugin_issue_edit.png)
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.
Note: Customers, Employees, and Service Items with automaticly update during normal usage of redmine i.e. a page refresh. You can also manualy force redmine to sync its database with QBO clicking the sync link in the Quickbooks top menu page
![Alt plugin_top_menu](/Screenshots/plugin_top_menu.png)
Note: After the inital synchronization, this plugin will recieve push notifications via Intuit's webhook service.
## TODO
* Abiltiy to add line items to a ticket in a dynamic table so they can be added to the invoice upon closing of the issue
* Customer ~~Creation~~, ~~Update~~, Deletion
* Customer Deletion
* Email Customer updates, provding a link that would: bypass the login page, go directly to the issue directing them to, and allow them to view only that issue.
* Add a rake file to create required Trackers or statuses required
* Add Setting for Sandbox Mode
* Refactor Models prefixed with Qbo...
* Seperate Vehicles into a seperate plugin
* Make HTML Pretty
* Intergrate Customer Search into Redmine Search
* Fix Issue sort by Customer
* MORE Stuff...
##License

Binary file not shown.

Before

Width:  |  Height:  |  Size: 116 KiB

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 169 KiB

After

Width:  |  Height:  |  Size: 72 KiB

View File

@@ -13,12 +13,33 @@ class CustomersController < ApplicationController
unloadable
include AuthHelper
helper :issues
helper :journals
helper :projects
helper :custom_fields
helper :issue_relations
helper :watchers
helper :attachments
helper :queries
include QueriesHelper
helper :repositories
helper :sort
include SortHelper
helper :timelog
before_filter :require_user
before_filter :require_user, :except => :view
skip_before_filter :verify_authenticity_token, :check_if_login_required, :only => [:view]
default_search_scope :names
# display a list of all customers
def index
@customers = Customer.paginate(:page => params[:page])
if params[:search]
@customers = Customer.search(params[:search]).paginate(:page => params[:page])
if only_one_non_zero?(@customers)
redirect_to @customers.first
end
end
end
def new
@@ -81,5 +102,46 @@ class CustomersController < ApplicationController
render_404
end
end
# Customer view for an issue
def view
token = CustomerToken.where("token = ? and expires_at > ?", params[:token], Time.now)
if token.first
@issue = Issue.find token.first.issue_id
@journals = @issue.journals.
preload(:details).
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)
Journal.preload_journals_details_custom_fields(@journals)
@journals.select! {|journal| journal.notes? || journal.visible_details.any?}
@journals.reverse! if User.current.wants_comments_in_reverse_order?
@changesets = @issue.changesets.visible.preload(:repository, :user).to_a
@changesets.reverse! if User.current.wants_comments_in_reverse_order?
@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)
@relation = IssueRelation.new
else
render_403
end
end
private
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

View File

@@ -18,7 +18,7 @@ class InvoiceController < ApplicationController
# Downloads and forwards the invoice pdf
#
def show
base = QboInvoice.get_base.service
base = QboInvoice.get_base
invoice = base.fetch_by_id(params[:id])
@pdf = base.pdf(invoice)
send_data @pdf, filename: "invoice #{invoice.doc_number}.pdf", :disposition => 'inline', :type => "application/pdf"

View File

@@ -0,0 +1,51 @@
#The MIT License (MIT)
#
#Copyright (c) 2016 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 PaymentsController < ApplicationController
unloadable
include AuthHelper
before_filter :require_user
def new
@payment = Payment.new
@customers = Customer.all.sort_by &:name
@accounts = Qbo.get_base(:account).service.query("SELECT Id, Name FROM Account WHERE AccountType = 'Bank' Order By Name")
@payment_methods = Qbo.get_base(:payment_method).service.all
end
def create
@payment = Payment.new(params[:payment])
if @payment.save
flash[:notice] = "Payment Saved"
redirect_to Customer.find_by_id(@payment.customer_id)
else
flash[:error] = @payment.errors.full_messages.to_sentence
redirect_to new_customer_path
end
end
private
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

View File

@@ -11,10 +11,12 @@
class QboController < ApplicationController
unloadable
require 'openssl'
include AuthHelper
before_filter :require_user, :except => :qbo_webhook
skip_before_filter :verify_authenticity_token, :check_if_login_required
skip_before_filter :verify_authenticity_token, :check_if_login_required, :only => [:qbo_webhook]
#
# Called when the QBO Top Menu us shown
@@ -34,7 +36,6 @@ class QboController < ApplicationController
def authenticate
callback = qbo_oauth_callback_url
token = Qbo.get_oauth_consumer.get_request_token(:oauth_callback => callback)
#session[:qb_request_token] = token
session[:qb_request_token] = Marshal.dump(token)
redirect_to("https://appcenter.intuit.com/Connect/Begin?oauth_token=#{token.token}") and return
end
@@ -43,8 +44,6 @@ class QboController < ApplicationController
# Called by QBO after authentication has been processed
#
def oauth_callback
#at = session[:qb_request_token].get_access_token(:oauth_verifier => params[:oauth_verifier])
# If Rails >= 4.1 you need to do this =>
at = Marshal.load(session[:qb_request_token]).get_access_token(:oauth_verifier => params[:oauth_verifier])
#There can only be one...
@@ -58,62 +57,87 @@ class QboController < ApplicationController
qbo.reconnect_token_at = 5.months.from_now.utc
qbo.company_id = params['realmId']
if qbo.save!
redirect_to qbo_path, :flash => { :notice => "Successfully connected to Quickbooks" }
redirect_to qbo_sync_path, :flash => { :notice => "Successfully connected to Quickbooks" }
else
redirect_to plugin_settings_path(:redmine_qbo), :flash => { :error => "Error" }
end
end
# Manual Billing
def bill
i = Issue.find_by_id params[:id]
i.bill_time
redirect_to i, :flash => { :notice => "Successfully Billed #{i.customer.name}" }
end
# Quickbooks Webhook Callback
def qbo_webhook
if request.headers['Content-Type'] == 'application/json'
data = JSON.parse(request.body.read)
# check the payload
signature = request.headers['intuit-signature']
key = Setting.plugin_redmine_qbo['settingsWebhookToken']
data = request.body.read
hash = Base64.encode64(OpenSSL::HMAC.digest(OpenSSL::Digest::Digest.new('sha256'), key, data)).strip()
# 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']
# TODO rename all other models!
name.prepend("Qbo") if not name.eql? "Customer"
# 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
obj.sync_by_id(id)
end
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
else
# application/x-www-form-urlencoded
data = params.as_json
render nothing: true, status: 400
end
entities = data['eventNotifications'][0]['dataChangeEvent']['entities']
entities.each do |entity|
if entity['name'].eql? "Customer"
Customer.sync_by_id(entity['id'].to_i)
end
if entity['name'].eql? "Invoice"
QboInvoice.sync_by_id(entity['id'].to_i)
end
if entity['name'].eql? "Estimate"
QboEstimate.sync_by_id(entity['id'].to_i)
end
if entity['name'].eql? "Employee"
QboEmployee.sync_by_id(entity['id'].to_i)
end
end
# The webhook doesn't require a response but let's make sure
# we don't send anything
render :nothing => true
end
#
# Synchronizes the QboCustomer table with QBO
#
def sync
if Qbo.exists?
Customer.sync
QboItem.sync
QboEmployee.sync
QboEstimate.sync
QboInvoice.sync
#QboPurchase.sync
# Record the last sync time
Qbo.update_time_stamp
# Update info in background
Thread.new do
if Qbo.exists?
Customer.sync
QboItem.sync
QboEmployee.sync
QboEstimate.sync
QboInvoice.sync
# Record the last sync time
Qbo.update_time_stamp
end
ActiveRecord::Base.connection.close
end
redirect_to qbo_path(:redmine_qbo), :flash => { :notice => "Successfully synced to Quickbooks" }

View File

@@ -18,13 +18,27 @@ class VehiclesController < ApplicationController
# display a list of all vehicles
def index
@vehicles = Vehicle.paginate(:page => params[:page])
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
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
@customers = Customer.all.order(:name)
@customer = params[:customer_id] if params[:customer_id]
end
# create a new vehicle
@@ -86,5 +100,27 @@ class VehiclesController < ApplicationController
render_404
end
end
# returns a dynamic list of vehicles owned by a customer
def update_vehicles
@vehicles = Customer.find_by(customer_id: params[:customer_id].to_i).vehicles
respond_to do |format|
format.html { render(:text => "not implemented") }
format.js
end
end
private
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

View File

@@ -140,6 +140,22 @@ class Customer < ActiveRecord::Base
end
end
# Searchs the database for a customer by name
def self.search(search)
customers = where("name LIKE ?", "%#{search}%")
#if customers.empty?
# service = Qbo.get_base(:customer).service
# results = service.query("Select Id From Customer Where PrimaryPhone LIKE '%#{search}%' AND Mobile LIKE '%#{search}%'")
# results.each do |customer|
# customers << Customer.find_by_id(customer.id)
# end
#end
return customers.order(:name)
end
# proforms a bruteforce sync operation
# This needs to be simplified
def self.sync_by_id(id)

View File

@@ -0,0 +1,23 @@
#The MIT License (MIT)
#
#Copyright (c) 2016 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 CustomerToken < ActiveRecord::Base
unloadable
has_many :issues
attr_accessible :token, :expires_at, :issue_id
validates_presence_of :expires_at, :issue_id
before_create :generate_token
OAUTH_CONSUMER_SECRET = Setting.plugin_redmine_qbo['settingsOAuthConsumerSecret']
def generate_token
self.token = SecureRandom.base64(15).tr('+/=lIO0', OAUTH_CONSUMER_SECRET)
end
end

37
app/models/payment.rb Normal file
View File

@@ -0,0 +1,37 @@
#The MIT License (MIT)
#
#Copyright (c) 2016 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 Payment
unloadable
include ActiveModel::Model
attr_accessor :errors, :customer_id, :account_id, :payment_method_id, :total_amount
validates_presence_of :customer_id, :account_id, :payment_method_id, :total_amount
validates :total_amount, numericality: true
def save
payment = Quickbooks::Model::Payment.new
payment.customer_id = @customer_id.to_i
payment.deposit_to_account_id = @account_id.to_i
payment.payment_method_id = @payment_method_id.to_i
payment.total = @total_amount
Qbo.get_base(:payment).service.update(payment)
end
def save!
save
end
# Dummy stub to make validtions happy.
def update_attribute
end
end

View File

@@ -51,4 +51,8 @@ class Qbo < ActiveRecord::Base
qbo.last_sync = DateTime.now
qbo.save
end
def self.last_sync
format_time(Qbo.first.last_sync)
end
end

View File

@@ -34,8 +34,7 @@ class QboEmployee < ActiveRecord::Base
def self.sync_by_id(id)
employee = get_base.service.fetch_by_id(id)
qbo_employee = find_or_create_by(id: employee.id)
qbo_employee = find_or_create_by(id: employee.id)
qbo_employee.name = employee.display_name
qbo_employee.id = employee.id
qbo_employee.save!

View File

@@ -10,54 +10,125 @@
class QboInvoice < ActiveRecord::Base
unloadable
has_many :issues
attr_accessible :doc_number
validates_presence_of :id, :doc_number
has_and_belongs_to_many :issues
attr_accessible :doc_number, :id
validates_presence_of :doc_number, :id
self.primary_key = :id
def self.get_base
Qbo.get_base(:invoice)
Qbo.get_base(:invoice).service
end
# sync ALL the invoices
def self.sync
#Pull the invoices from the quickbooks server
#invoices = get_base.service.all
last = Qbo.first.last_sync
query = "SELECT Id, DocNumber FROM Invoice"
query << " WHERE Metadata.LastUpdatedTime >= '#{last.iso8601}' " if last
if count == 0
invoices = get_base.service.all
invoices = get_base.all
else
invoices = get_base.service.query()
invoices = get_base.query()
end
# Update the invoice table
invoices.each { | invoice |
qbo_invoice = find_or_create_by(id: invoice.id)
qbo_invoice.doc_number = invoice.doc_number
qbo_invoice.id = invoice.id
qbo_invoice.save!
process_invoice invoice
}
end
#sync by invoice ID
def self.sync_by_id(id)
#update the information in the database
invoice = get_base.fetch_by_id(id)
process_invoice invoice
end
# processes the invoice into the system
def self.process_invoice(invoice)
is_changed = false
# Scan the line items for hashtags and attach to the applicable issues
invoice.line_items.each { |line|
if line.description
line.description.scan(/#(\w+)/).flatten.each { |issue|
i = Issue.find_by_id(issue.to_i)
# 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.save!
# Attach the invoice to the issue
unless i.qbo_invoices.include?(qbo_invoice)
i.qbo_invoices << qbo_invoice
i.save!
end
# update the invoive custom fields with infomation from the work ticket if available
invoice.custom_fields.each { |cf|
# VIN from the attached vehicle
begin
if cf.name.eql? "VIN"
vin = Vehicle.find(i.vehicles_id).vin
break if vin.nil?
if not cf.string_value.to_s.eql? vin
cf.string_value = vin.to_s
is_changed = true
break
end
end
rescue
#do nothing
end
# Custom Values
begin
value = i.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
# Use the lowest Milage
if cf.name.eql? "Mileage In"
if cf.string_value.to_i > value.value.to_i or cf.string_value.blank?
cf.string_value = value.value.to_s
is_changed = true
end
break
end
# Use the max milage
if cf.name.eql? "Mileage Out"
if cf.string_value.to_i < value.value.to_i or cf.string_value.blank?
cf.string_value = value.value.to_s
is_changed = true
end
break
end
# Everything else
cf.string_value = value.value.to_s
is_changed = true
end
end
rescue
# Nothing to do here, there is no match
end
}
}
end
}
#remove deleted invoices
#where.not(invoices.map(&:id)).destroy_all
# Push updates
get_base.update(invoice) if is_changed
end
def self.sync_by_id(id)
invoice = get_base.service.fetch_by_id(id)
qbo_invoice = find_or_create_by(id: invoice.id)
qbo_invoice.doc_number = invoice.doc_number
qbo_invoice.id = invoice.id
qbo_invoice.save!
end
def self.update(id)
# Update the item table
invoice = get_base.service.fetch_by_id(id)
qbo_invoice = find_or_create_by(id: id)
qbo_invoice.doc_number = invoice.doc_number
qbo_invoice.save!
end
end

View File

@@ -21,7 +21,7 @@ class Vehicle < ActiveRecord::Base
validates_presence_of :customer
validates :vin, uniqueness: true
validates :year, numericality: { only_integer: true }
#validates :year, numericality: { only_integer: true }
before_save :decode_vin
after_initialize :get_details
@@ -72,7 +72,12 @@ class Vehicle < ActiveRecord::Base
# Force Upper Case for VIN numbers
def vin=(val)
# The to_s is in case you get nil/non-string
write_attribute(:vin, val.to_s.upcase)
write_attribute(:vin, val.to_s.scan(/^[A-Za-z0-9]+$/).join.upcase)
end
# search for a vin
def self.search(search)
where("vin LIKE ?", "%#{search}%")
end
private
@@ -82,8 +87,8 @@ class Vehicle < ActiveRecord::Base
if self.vin?
begin
@details = JSON.parse get_decoder.full(self.vin)
raise @details['message'] if @details['status'] == "NOT_FOUND"
raise @details['message'] if @details['status'] == "BAD_REQUEST"
raise @details['message'] if @details['status'].to_s.eql? "NOT_FOUND"
raise @details['message'] if @details['status'].to_s.eql? "BAD_REQUEST"
rescue Exception => e
errors.add(:vin, e.message)
end
@@ -121,4 +126,5 @@ class Vehicle < ActiveRecord::Base
v = self.vin[0,11]
return v.slice(0,8) + v.slice(9,11)
end
end

View File

@@ -12,12 +12,37 @@
<tr>
<th>Primary Phone</th>
<td><%= customer.primary_phone %></td>
<td><%= number_to_phone(customer.primary_phone.gsub(/[^\d]/, '').to_i, area_code: true) if customer.primary_phone %></td>
</tr>
<tr>
<th>Mobile Phone</th>
<td><%= customer.mobile_phone %></td>
<td><%= number_to_phone(customer.mobile_phone.gsub(/[^\d]/, '').to_i, area_code: true) if customer.mobile_phone %></td>
</tr>
<tr>
<th>Bill Address</th>
<td><%= customer.billing_address %></td>
</tr>
<tr>
<th>Shipping Address</th>
<td><%= customer.shipping_address %></td>
</tr>
<tr>
<th>Issues</th>
<td><%= customer.issues.count %></td>
</tr>
<tr>
<th>Account Balance</th>
<td>$<%= customer.balance %></td>
</tr>
<tr>
<th>Balance With Jobs</th>
<td>$<%= customer.balance_with_jobs %></td>
</tr>
<tr>
@@ -26,12 +51,6 @@
</tr>
<tr>
<th>Issues</th>
<td><%= customer.issues.count %></td>
</tr>
<tr>
<td/>
<td>
<%= button_to "Edit Customer", edit_customer_path(customer), method: :get%>
</td>

View File

@@ -1,19 +1,28 @@
<h1>Customers</h1>
<br/>
<% @customers.each do |c| %>
<div class="row">
<div class="span6 columns">
<fieldset>
<%= c.name %>
<%= button_to "Show", customer_path(c.id), method: :get %>
</fieldset>
</div>
</div>
<%= form_tag(customers_path, :method => "get", id: "search-form") do %>
<%= text_field_tag :search, params[:search], placeholder: "Search Customers" %>
<%= submit_tag "Search" %>
<% end %>
<br/>
<% if @customers.present? %>
<br/>
<% @customers.each do |c| %>
<div class="row">
<div class="span6 columns">
<%= link_to c, customer_path(c.id) %>
</div>
</div>
<% end %>
<div class="actions">
<%= will_paginate @customers %>
<%= button_to "New", new_customer_path, method: :get %>
</div>
<% else %>
<p>There are no customers containing the term(s) <%= params[:search] %>.</p>
<% end %>
<div>
<%= Customer.count %> Customers - <b>Last Sync: </b> <%= Qbo.last_sync if Qbo.exists? %>
</div>

View File

@@ -1,7 +1,12 @@
<h1>Customer Detail</h1>
<h1>Customer #<%= @customer.id %></h1>
<br/>
<h2>Details:</h2>
<%= render :partial => 'customers/details', locals: {customer: @customer} %>
<br/>
<h2>Vehicles:</h2>
<%= render :partial => 'vehicles/list' %>
<%= button_to "New Vehicle", new_customer_vehicle_path(@customer), method: :get %>
<br/>
<br/>
<h2>Issues:</h2>
<%= render :partial => 'issues/list_simple', locals: {issues: @issues} %>

View File

@@ -0,0 +1,108 @@
<h2><%= issue_heading(@issue) %></h2>
<div class="<%= @issue.css_classes %> details">
<%= avatar(@issue.author, :size => "50") %>
<div class="subject">
<%= render_issue_subject_with_tree(@issue) %>
</div>
<p class="author">
<%= authoring @issue.created_on, @issue.author %>.
<% if @issue.created_on != @issue.updated_on %>
<%= l(:label_updated_time, time_tag(@issue.updated_on)).html_safe %>.
<% end %>
</p>
<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 ? link_to_user(@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?('fixed_version_id') || (@issue.fixed_version.nil? && @issue.assignable_versions.none?)
rows.left l(:field_fixed_version), (@issue.fixed_version ? link_to_version(@issue.fixed_version) : "-"), :class => 'fixed-version'
end
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?('due_date')
rows.right l(:field_due_date), format_date(@issue.due_date), :class => 'due-date'
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')
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'
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'
end
end
end %>
<%= render_custom_fields_rows(@issue) %>
<%= call_hook(:view_issues_show_details_bottom, :issue => @issue) %>
</div>
<% if @issue.description? || @issue.attachments.any? -%>
<hr />
<% 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? %>
</div>
<p><strong><%=l(:field_description)%></strong></p>
<div class="wiki">
<%= textilizable @issue, :description, :attachments => @issue.attachments %>
</div>
</div>
<% end %>
<%= link_to_attachments @issue, :thumbnails => true %>
<% end -%>
<%= call_hook(:view_issues_show_description_bottom, :issue => @issue) %>
<% if !@issue.leaf? || User.current.allowed_to?(:manage_subtasks, @project) %>
<hr />
<div id="issue_tree">
<div class="contextual">
<%= link_to_new_subtask(@issue) if User.current.allowed_to?(:manage_subtasks, @project) %>
</div>
<p><strong><%=l(:label_subtask_plural)%></strong></p>
<%= render_descendants_tree(@issue) unless @issue.leaf? %>
</div>
<% end %>
<% if @relations.present? || User.current.allowed_to?(:manage_issue_relations, @project) %>
<hr />
<div id="relations">
<%= render :partial => 'issues/relations' %>
</div>
<% end %>
</div>
<% if @changesets.present? %>
<div id="issue-changesets">
<h3><%=l(:label_associated_revisions)%></h3>
<%= 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 } %>
</div>
<% end %>
<% html_title "#{@issue.tracker.name} ##{@issue.id}: #{@issue.subject}" %>

View File

@@ -0,0 +1,42 @@
<div class="row">
<div class="span6 columns">
<fieldset>
<%= form_for @payment do |f| %>
<div class="clearfix">
Customer:
<div class="input">
<%= f.collection_select :customer_id, @customers, :id, :name, include_blank: true, :selected => @customer, :required => true%>
</div>
</div>
<div class="clearfix">
Deposit to Account:
<div class="input">
<%= f.collection_select :account_id, @accounts, :id, :name, include_blank: true, :selected => @account, :required => true%>
</div>
</div>
<div class="clearfix">
Payment Method:
<div class="input">
<%= f.collection_select :payment_method_id, @payment_methods, :id, :name, include_blank: true, :selected => @payment_method, :required => true%>
</div>
</div>
<div class="clearfix">
Amount:
<div class="input">
<%= f.number_field :total_amount %>
</div>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
</fieldset>
</div>
</div>

View File

@@ -0,0 +1,3 @@
<h1>New Payment</h1>
<br/>
<%= render :partial => 'payments/form' %>

View File

@@ -46,6 +46,15 @@ intuit.ipp.anywhere.setup({menuProxy: '/path/to/blue-dot', grantUrl: '<%= qbo_au
name="settings[settingsOAuthConsumerSecret]" >
</td>
</tr>
<tr>
<th>Intuit QBO Webhook Token</th>
<td>
<input type="text" style="width:350px" id="settingsWebhookToken"
value="<%= settings['settingsWebhookToken'] %>"
name="settings[settingsWebhookToken]" >
</td>
</tr>
<tr>
<th>Token Expires At</th>
@@ -63,6 +72,36 @@ intuit.ipp.anywhere.setup({menuProxy: '/path/to/blue-dot', grantUrl: '<%= qbo_au
<br/>
Note: You need to authenticate after saving your key and secret above
<br/>
<br/>
<!-- this will display a button that the user clicks to start the flow -->
<ipp:connectToIntuit></ipp:connectToIntuit>
<br/>
<br/>
<div>
<b>Customer Count:</b> <%= Customer.count%>
</div>
<div>
<b>Item Count:</b> <%= QboItem.count %>
</div>
<div>
<b>Employee Count:</b> <%= QboEmployee.count %>
</div>
<div>
<b>Invoice Count:</b> <%= QboInvoice.count %>
</div>
<div>
<b>Estimate Count:</b> <%= QboEstimate.count %>
</div>
<br/>
<div>
<b>Last Sync: </b> <%= Qbo.last_sync if Qbo.exists? %> <%= link_to " Sync Now", qbo_sync_path %>
</div>

View File

@@ -12,44 +12,31 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
<body>
<h1> Redmine Quickbooks</h1>
<%= form_for @qbo do |f|%>
<div>
<%= f.label "Customer Count:"+@customer_count.to_s%>
<br/>
<%= f.select :customer_id, Customer.all.pluck(:name, :id).sort, :selected => @selected_customer, include_blank: true %>
</div>
<br/>
<div>
<%= f.label "Item Count: "+@qbo_item_count.to_s %>
<br/>
<%= f.select :qbo_item_id, QboItem.all.pluck(:name, :id).sort.reverse, :selected => @selected_item, include_blank: true %>
</div>
<br/>
<div>
<%= f.label "Employee Count: "+@qbo_employee_count.to_s %>
<br/>
<%= f.select :qbo_employee_id, QboEmployee.all.pluck(:name, :id).sort, :selected => @selected_employee, include_blank: true %>
</div>
<p>
<%= f.label "Invoice Count: "+@qbo_invoice_count.to_s %>
<br/>
<%=f.select :qbo_invoice_id, QboInvoice.all.pluck(:doc_number, :id).sort! {|x, y| y <=> x}, :selected => @selected_invoice, include_blank: true%>
</p>
<p>
<%= f.label "Estimate Count: "+@qbo_estimate_count.to_s %>
<br/>
<%=f.select :qbo_estimate_id, QboEstimate.all.pluck(:doc_number, :id).sort! {|x, y| y <=> x}, :selected => @selected_estimate, include_blank: true%>
</p>
<div>
<b>Customer Count:</b> <%= @customer_count.to_s%>
</div>
<div>
<b>Item Count:</b> <%= @qbo_item_count.to_s %>
</div>
<div>
<b>Employee Count:</b> <%= @qbo_employee_count.to_s %>
</div>
<div>
<b>Invoice Count:</b> <%= @qbo_invoice_count.to_s %>
</div>
<div>
<b>Estimate Count:</b> <%= @qbo_estimate_count.to_s %>
</div>
<% end %>
<br/>
<br/>
<%= link_to "Sync", qbo_sync_path %>
<div>
<b>Last Sync: </b> <%= Qbo.last_sync if Qbo.exists? %>
</div>
</body>

View File

@@ -3,7 +3,7 @@
<tr>
<th>Customer</th>
<td><%= vehicle.customer.name %></td>
<td><%= link_to vehicle.customer.name, customer_path(vehicle.customer) %></td>
</tr>
<tr>

View File

@@ -1,2 +0,0 @@
<%= @f.collection_select :vehicle_id, @customer.vehicles.order(:year), :id, :vin, include_blank: true, :selected => @vehicle%>
Partial Test

View File

@@ -34,25 +34,21 @@
<div class="clearfix">
VIN:
<div class="input">
<%= f.text_field :vin %>
<%= f.text_field :vin , :autofocus => true %>
</div>
</div>
<div class="clearfix">
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 @vehicle.new_record? %>
<p>
<%= content_tag 'span', :id => "issue_description_and_toolbar", :style => (@vehicle.new_record? ? nil : 'display:none') do %>
<%= f.text_area :notes,
<%= f.text_area :notes,
:cols => 60,
:rows => 10,
:accesskey => accesskey(:edit),
:class => 'wiki-edit',
:no_label => true %>
<% end %>
</p>
<%= wikitoolbar_for 'issue_description' %>
</div>
</div>

View File

@@ -1,20 +1,26 @@
<% @vehicles.each do |vehicle| %>
<div class="row">
<div class="span6 columns">
<fieldset>
<% 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/>
<div style="float: right;" >
<%= button_to "More", vehicle_path(vehicle), method: :get %>
</div>
</fieldset>
<%= vehicle.customer %>
<br/>
<%= vehicle.vin %>
</div>
</div>
<br/>
<% end %>
<div class="actions">
<%= will_paginate @vehicles %>
</div>
<% else %>
<p>There are no vehicles containing the term(s) <%= params[:search] %>.</p>
<% end %>
<br/>
<div class="actions">
<%= will_paginate @vehicles %>
<%= button_to "New Vehicle", new_vehicle_path, method: :get %>
</div>

View File

@@ -0,0 +1 @@
<option value="<%= vehicle.id %>"><%= vehicle.to_s.titleize %></option>

View File

@@ -1,3 +1,9 @@
<h1>Customer Vehicles</h1>
<br/>
<%= form_tag(vehicles_path, :method => "get", id: "search-form") do %>
<%= text_field_tag :search, params[:search], placeholder: "Search Vehicles by VIN" %>
<%= submit_tag "Search" %>
<% end %>
<%= render :partial => 'vehicles/list' %>

View File

@@ -1,4 +1,4 @@
<h1>Customer Vehicle</h1>
<h1>Vehicle #<%=@vehicle.id%> </h1>
<br/>
<div style="text-align: left; width:90%;">

View File

@@ -0,0 +1,2 @@
$("#issue_vehicles_id").empty()
.append("<%= escape_javascript(render(:partial => @vehicles)) %>")

View File

@@ -0,0 +1,10 @@
function customerSelected() {
customer_id = $('issue_customer_id').getValue();
console.log(customer_id);
}
document.observe('dom:loaded', function() {
customerSelected();
$('issue_customer_id').observe('change', customerSelected);
});

View File

@@ -19,3 +19,5 @@ en:
field_vehicles: "Vehicle"
field_vin: "VIN"
field_notes: "Notes"
field_qbo_billed: "Billed"
label_week: "Week"

View File

@@ -8,17 +8,39 @@
#
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
# Plugin's routes
# See: http://guides.rubyonrails.org/routing.html
#
# Main Quickbooks landing page
get 'qbo', :to=> 'qbo#index'
#authentication
get 'qbo/authenticate', :to => 'qbo#authenticate'
get 'qbo/oauth_callback', :to => 'qbo#oauth_callback'
#manual sync
get 'qbo/sync', :to => 'qbo#sync'
# Estimate & Invoice PDF
get 'qbo/estimate/:id', :to => 'estimate#show', as: :estimate
get 'qbo/invoice/:id', :to => 'invoice#show', as: :invoice
#manual billing
get 'qbo/bill/:id', :to => 'qbo#bill', as: :bill
#customer issue view
get 'customers/view/:token', :to => 'customers#view', as: :view
#payments
resources :payments
#webhook
post 'qbo/webhook', :to => 'qbo#qbo_webhook'
#ajax
get "update_vehicles" => 'vehicles#update_vehicles', as: 'update_vehicles'
# Nest Vehicles under customers
resources :customers do
resources :vehicles
end
#allow for just vehicles too
resources :vehicles
resources :customers

View File

@@ -0,0 +1,27 @@
#The MIT License (MIT)
#
#Copyright (c) 2016 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 AddIssuesQboInvoices < ActiveRecord::Migration
def self.up
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
# 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"
end
def self.down
drop_table :issues_qbo_invoices
end
end

View File

@@ -8,10 +8,8 @@
#
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
module ActiveSupport::Callbacks::ClassMethods
def without_callback(*args, &block)
skip_callback(*args)
yield
set_callback(*args)
class UpdateIssuesRemoveInvoice < ActiveRecord::Migration
def change
remove_reference :issues, :qbo_invoice
end
end

View File

@@ -0,0 +1,19 @@
#The MIT License (MIT)
#
#Copyright (c) 2016 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 CreateCustomerTokens < ActiveRecord::Migration
def change
create_table :customer_tokens do |t|
t.string :token
t.timestamp :expires_at
t.references :issue
end
end
end

23
init.rb
View File

@@ -15,17 +15,23 @@ Redmine::Plugin.register :redmine_qbo do
require_dependency 'issues_save_hook_listener'
require_dependency 'issues_show_hook_listener'
require_dependency 'users_show_hook_listener'
require_dependency 'header_footer_hook_listener'
# Patches to the Redmine core. Will not work in development mode
require_dependency 'issue_patch'
require_dependency 'user_patch'
require_dependency 'query_patch'
require_dependency 'time_entry_query_patch'
require_dependency 'pdf_patch'
Rails.configuration.to_prepare do
Redmine::Search.available_search_types << 'customers'
end
name 'Redmine Quickbooks Online 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 '0.0.7'
version '0.4.1'
url 'https://github.com/rickbarrette/redmine_qbo'
author_url 'http://rickbarrette.org'
settings :default => {'empty' => true}, :partial => 'qbo/settings'
@@ -41,12 +47,21 @@ Redmine::Plugin.register :redmine_qbo do
# We are playing in the sandbox
#Quickbooks.sandbox_mode = true
# set per_page globally
WillPaginate.per_page = 10
# Register QBO top menu item
menu :top_menu, :qbo, { :controller => :qbo, :action => :index }, :caption => 'Quickbooks', :if => Proc.new { User.current.admin? }
menu :top_menu, :vehicles, { :controller => :vehicles, :action => :index }, :caption => 'Vehicles', :if => Proc.new { User.current.logged? }
#menu :top_menu, :qbo, { :controller => :qbo, :action => :index }, :caption => 'Quickbooks', :if => Proc.new { User.current.admin? }
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 :application_menu, :new_customer, { :controller => :customers, :action => :new }, :caption => 'New Customer', :if => Proc.new { User.current.logged? }
menu :application_menu, :new_payment, { :controller => :payments, :action => :new }, :caption => 'New Payment', :if => Proc.new { User.current.logged? }
permission :customers, { :customers => [:index, :new] }, :public => false
menu :project_menu, :customers, { :controller => 'customers', :action => 'new' }, :caption => 'New Customer', :after => :new_issue, :param => :project_id
permission :payments, { :payments => [:index, :new] }, :public => false
menu :project_menu, :payments, { :controller => 'payments', :action => 'new' }, :caption => 'New Payment', :after => :customers, :param => :project_id
end

View File

@@ -0,0 +1,19 @@
#The MIT License (MIT)
#
#Copyright (c) 2016 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='qbo_footer' align='center'><b>Last Sync: </b> #{Qbo.last_sync if Qbo.exists?}</div>"
end
end

View File

@@ -23,9 +23,11 @@ module IssuePatch
base.class_eval do
unloadable # Send unloadable so it will not be unloaded in development
belongs_to :customer, primary_key: :id
belongs_to :qbo_item, primary_key: :id
belongs_to :customer_token, primary_key: :id
belongs_to :qbo_estimate, primary_key: :id
belongs_to :qbo_invoice, primary_key: :id
has_and_belongs_to_many :qbo_invoices
#, :association_foreign_key => 'issue_id', :class_name => 'Issue', :join_table => 'issues_qbo_invoices'
belongs_to :vehicle, primary_key: :id
end
@@ -36,7 +38,62 @@ module IssuePatch
end
module InstanceMethods
# Create billable time entries
def bill_time
# Get unbilled time entries
spent_time = time_entries.where(qbo_billed: [false, nil])
spent_hours ||= spent_time.sum(:hours) || 0
if spent_hours > 0 then
# Prepare to create a new Time Activity
time_service = Qbo.get_base(:time_activity).service
item_service = Qbo.get_base(:item).service
time_entry = Quickbooks::Model::TimeActivity.new
h = Hash.new(0)
spent_time.each do |entry|
# Lets tottal up each activity
h[entry.activity.name] += entry.hours
# update time entries billed status
entry.qbo_billed = true
entry.save
end
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
item = item_service.query("SELECT * FROM Item WHERE Name = '#{key}' ").first
next if item.nil?
time_entry.description = "#{tracker} ##{id}: #{subject} #{"(Partial)" if not closed?}"
# TODO entry.user.qbo_employee.id
time_entry.employee_id = assigned_to.qbo_employee_id
time_entry.customer_id = customer_id
time_entry.billable_status = "Billable"
time_entry.hours = hours
time_entry.minutes = minutes
time_entry.name_of = "Employee"
time_entry.txn_date = Date.today
time_entry.hourly_rate = item.unit_price
time_entry.item_id = item.id
time_entry.start_time = start_date
time_entry.end_time = Time.now
time_service.create(time_entry)
end
end
end
end
# Create a shareable link for a customer
def share_token
CustomerToken.create(:expires_at => Time.now + 24.hours, :issue_id => id)
end
end

View File

@@ -10,6 +10,13 @@
class IssuesFormHookListener < Redmine::Hook::ViewListener
# Load the javascript
def view_layouts_base_html_head(context = {})
js = javascript_include_tag 'vehicles', :plugin => 'redmine_qbo'
#js += javascript_include_tag 'update_vehicles', :plugin => 'redmine_qbo'
return js
end
# Edit Issue Form
# Show a dropdown for quickbooks contacts
def view_issues_form_details_bottom(context={})
@@ -17,20 +24,16 @@ class IssuesFormHookListener < Redmine::Hook::ViewListener
# Check to see if there is a quickbooks user attached to the issue
selected_customer = context[:issue].customer ? context[:issue].customer.id : nil
selected_item = context[:issue].qbo_item ? context[:issue].qbo_item.id : nil
selected_invoice = context[:issue].qbo_invoice ? context[:issue].qbo_invoice.id : nil
selected_estimate = context[:issue].qbo_estimate ? context[:issue].qbo_estimate.id : nil
selected_vehicle = context[:issue].vehicles_id ? context[:issue].vehicles_id : nil
# Load customer information without callbacks
customer = Customer.find_by_id(selected_customer) if selected_customer
select_customer = f.select :customer_id, Customer.all.pluck(:name, :id).sort, :selected => selected_customer, include_blank: true
# Generate the drop down list of quickbooks items
select_item = f.select :qbo_item_id, QboItem.all.pluck(:name, :id).sort, :selected => selected_item, include_blank: true
# Generate the drop down list of quickbooks invoices
select_invoice = f.select :qbo_invoice_id, QboInvoice.all.pluck(:doc_number, :id).sort! {|x, y| y <=> x}, :selected => selected_invoice, include_blank: true
select_customer = f.select :customer_id,
Customer.all.pluck(:name, :id).sort,
:selected => selected_customer,
include_blank: true,
onchange: "$customerSelected;"
# Generate the drop down list of quickbooks extimates
select_estimate = f.select :qbo_estimate_id, QboEstimate.all.pluck(:doc_number, :id).sort! {|x, y| y <=> x}, :selected => selected_estimate, include_blank: true
@@ -43,6 +46,6 @@ class IssuesFormHookListener < Redmine::Hook::ViewListener
vehicle = f.select :vehicles_id, vehicles, :selected => selected_vehicle, include_blank: true
return "<p>#{select_customer}</p> <p>#{select_item}</p> <p>#{select_invoice}</p> <p>#{select_estimate}</p> <p>#{vehicle}</p>"
return "<p>#{select_customer}</p> <p>#{select_estimate}</p> <p>#{vehicle}</p>"
end
end

View File

@@ -15,7 +15,7 @@ class IssuesSaveHookListener < Redmine::Hook::ViewListener
issue = context[:issue]
# Check to see if we have registered with QBO
if Qbo.first && issue.customer && issue.qbo_item
if Qbo.first && issue.customer && issue. qbo_item_id
# if this is a quote, lets create a new estimate based off estimated hours
if issue.tracker.name = "Quote" && issue.status.name = "New" && issue.qbo_estimate
@@ -44,10 +44,6 @@ class IssuesSaveHookListener < Redmine::Hook::ViewListener
# Add the line items to the estimate
estimate.line_items << line_item
# Save the etimate to the issue
#issue.qbo_estimate_id = estimate_base.service.create(estimate).id
#issue.save!
end
end
end
@@ -55,55 +51,6 @@ class IssuesSaveHookListener < Redmine::Hook::ViewListener
# Called After Issue Saved
def controller_issues_edit_after_save(context={})
issue = context[:issue]
if issue.assigned_to
employee_id = issue.assigned_to.qbo_employee_id
# Check to see if we have registered with QBO and if the issue is closed.
# If so then we need to create a new billable time activity for the customer
bill_time(issue, employee_id) if Qbo.first && issue.customer && issue.qbo_item && employee_id && issue.status.is_closed?
end
end
# Create billable time entries
def bill_time(issue, employee_id)
# Get unbilled time entries
spent_time = issue.time_entries.where(qbo_billed: [false, nil])
spent_hours ||= spent_time.sum(:hours) || 0
if spent_hours > 0 then
# Prepare to create a new Time Activity
time_service = Qbo.get_base(:time_activity).service
item_service = Qbo.get_base(:item).service
time_entry = Quickbooks::Model::TimeActivity.new
# Convert float spent time to hours and minutes
hours = spent_hours.to_i
minutesDecimal = (( spent_hours - hours) * 60)
minutes = minutesDecimal.to_i
# update time entries billed status
spent_time.each do |entry|
entry.qbo_billed = true
entry.save
end
item = item_service.fetch_by_id issue.qbo_item_id
time_entry.description = "#{issue.tracker} ##{issue.id}: #{issue.subject}"
time_entry.employee_id = employee_id
time_entry.customer_id = issue.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 = issue.qbo_item_id
time_entry.start_time = issue.start_date
time_entry.end_time = Time.now
time_service.create(time_entry)
end
issue.bill_time if Qbo.first && issue.customer && issue.status.is_closed?
end
end

View File

@@ -19,15 +19,12 @@ class IssuesShowHookListener < Redmine::Hook::ViewListener
# Display the quickbooks contact in the issue
def view_issues_show_details_bottom(context={})
issue = context[:issue]
# Check to see if there is a quickbooks user attached to the issue
if issue.customer
customer = link_to issue.customer.name, "#{Redmine::Utils::relative_url_root}/customers/#{issue.customer.id}"
end
# Check to see if there is a quickbooks item attached to the issue
item = issue.qbo_item ? issue.qbo_item.name : nil
# Estimate Number
if issue.qbo_estimate
estimate = issue.qbo_estimate.doc_number
@@ -35,12 +32,14 @@ class IssuesShowHookListener < Redmine::Hook::ViewListener
end
# Invoice Number
if issue.qbo_invoice
invoice = issue.qbo_invoice.doc_number
invoice_link = link_to invoice, "#{Redmine::Utils::relative_url_root}/qbo/invoice/#{issue.qbo_invoice.id}", :target => "_blank"
invoice_link = ""
if issue.qbo_invoice_ids
issue.qbo_invoice_ids.each do |i|
invoice = QboInvoice.find i
invoice_link = invoice_link + link_to( invoice.doc_number, "#{Redmine::Utils::relative_url_root}/qbo/invoice/#{i}", :target => "_blank").to_s + " "
end
end
begin
v = Vehicle.find(issue.vehicles_id)
vehicle = link_to v.to_s, "#{Redmine::Utils::relative_url_root}/vehicles/#{v.id}"
@@ -50,46 +49,51 @@ class IssuesShowHookListener < Redmine::Hook::ViewListener
#do nothing
end
split_vin = vin.scan(/.{1,9}/) if vin
return "
<div class=\"attributes\">
<div class=\"splitcontent\">
<div class=\"customer_id attribute\">
<div class=\"label\"><span>Customer</span>:</div>
<div class=\"value\">#{customer}</div>
</div>
<div class=\"qbo_item_id attribute\">
<div class=\"label\"><span>Item</span>:</div>
<div class=\"value\">#{item}</div>
</div>
<div class=\"qbo_estimate_id attribute\">
<div class=\"label\"><span>Estimate</span>:</div>
<div class=\"value\">#{estimate_link}</div>
</div>
<div class=\"qbo_invoice_id attribute\">
<div class=\"label\"><span>Invoice</span>:</div>
<div class=\"value\">#{invoice_link}</div>
<div class=\"splitcontentleft\">
<div class=\"customer_id attribute\">
<div class=\"label\"><span>Customer</span>:</div>
<div class=\"value\">#{customer}</div>
</div>
<div class=\"qbo_estimate_id attribute\">
<div class=\"label\"><span>Estimate</span>:</div>
<div class=\"value\">#{estimate_link}</div>
</div>
<div class=\"qbo_invoice_id attribute\">
<div class=\"label\"><span>Invoice</span>:</div>
<div class=\"value\">#{invoice_link}</div>
</div>
</div>
<br/>
<div class=\"vehicle attribute\">
<div class=\"label\"><span>Vehicle</span>:</div>
<div class=\"value\">#{vehicle}</div>
</div>
<div class=\"splitcontentleft\">
<div class=\"vehicle attribute\">
<div class=\"label\"><span>Vehicle</span>:</div>
<div class=\"value\">#{vehicle}</div>
</div>
<div class=\"vehicle_vin attribute\">
<div class=\"label\"><span>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_vin attribute\">
<div class=\"label\"><span>VIN</span>:</div>
<div class=\"value\">#{vin.gsub(/(.{9})/, '\1 ') if vin}</div>
</div>
<div class=\"vehicle_notes attribute\">
<div class=\"label\"><span>Notes</span>:</div>
<div class=\"value\">#{notes}</div>
<div class=\"vehicle_notes attribute\">
<div class=\"label\"><span>Notes</span>:</div>
<div class=\"value\">#{notes}</div>
</div>
</div>
</div>"
end
def view_issues_show_description_bottom(context={})
bill_button = button_to "Bill Time", "#{Redmine::Utils::relative_url_root}/qbo/bill/#{context[:issue].id}", method: :get if User.current.admin?
share_button = button_to "Share", "#{Redmine::Utils::relative_url_root}/customers/view/#{context[:issue].share_token.token}", method: :get if User.current.logged?
return "<br/> #{bill_button} #{share_button}"
end
end

View File

@@ -36,6 +36,7 @@ module QueryPatch
unless @available_columns
@available_columns = available_columns_without_qbo
@available_columns << QueryColumn.new(:customer, :sortable => "#{Customer.table_name}.name", :groupable => true, :caption => :field_customer)
@available_columns << QueryColumn.new(:qbo_billed, :sortable => "#{TimeEntry.table_name}.name", :groupable => true, :caption => :field_qbo_billed)
end
@available_columns
end

View File

@@ -0,0 +1,71 @@
#The MIT License (MIT)
#
#Copyright (c) 2016 rick barrette
#
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
#
#The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
#
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
require_dependency 'time_entry_query'
module TimeEntryQueryPatch
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
alias_method_chain :available_columns, :qbo_billed
alias_method_chain :available_filters, :qbo_billed
end
end
module ClassMethods
end
module InstanceMethods
def available_columns_with_qbo_billed
unless @available_columns
@available_columns = available_columns_without_qbo
@available_columns << QueryColumn.new(:qbo_billed, :sortable => "#{TimeEntry.table_name}.name", :groupable => true, :caption => :field_qbo_billed)
end
@available_columns
end
def available_filters_with_qbo_billed
unless @available_filters
@available_filters = available_filters_without_qbo
#qbo_filters = {
# :customer => {
# :id => l(:field_qbo_billed),
# :type => :boolean,
# :order => @available_filters.size + 1},
#}
qbo_filters = {
"qbo_billed" => {
:id => :qbo_billed,
:type => :list_optional,
:order => @available_filters.size + 1,
#:values => Customer.find(:all).collect { |c| [c.name, c.id.to_s]}
}
}
@available_filters.merge!(qbo_filters)
end
@available_filters
end
end
end
# Add module to TimeEntryQuery
TimeEntryQuery.send(:include, QueryPatch)

View File

@@ -0,0 +1,9 @@
require File.expand_path('../../test_helper', __FILE__)
class CustomerTokenTest < ActiveSupport::TestCase
# Replace this with your real tests.
def test_truth
assert true
end
end